Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support decimal128 type for nested properties #483

Merged
merged 4 commits into from
Nov 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
node_modules
coverage
.idea

.vscode/*
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ You can set the `url` property to a connection URL in `datasources.json` to over
Additionally, you can override the global `url` property in environment-specific data source configuration files, for example for production in `datasources.production.json`, and use the individual connection parameters `host`, `user`, `password`, and `port`. To do this, you _must_ set `url` to `false`, null, or “” (empty string).
If you set `url` to `undefined` or remove the `url` property altogether, the override will not work.

For example, for production, use `datasources.production.json` as follows (for example) to overide the `url` setting in `datasources.json:
For example, for production, use `datasources.production.json` as follows (for example) to override the `url` setting in `datasources.json:

```javascript
"mydb": {
Expand Down Expand Up @@ -280,6 +280,12 @@ myModelName.find(
)
```

## Advanced features

### decimal128 type

You can check [document](https://github.com/strongloop/loopback-connector-mongodb/blob/master/docs/decimal128.md) for details.

## Release notes

* 1.1.7 - Do not return MongoDB-specific _id to client API, except if specifically specified in the model definition
74 changes: 65 additions & 9 deletions docs/decimal128.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,66 @@
## Decimal128

You can define a Decimal128 type property as
You can define Decimal128 type properties as

```js
// in model.json file
"aproperty": {
// 1st level property
"count": {
"type": "String",
"mongodb": {"dataType": "Decimal128"}
}
},
// nested array
"lines": [
{
"unitPrice": {
"type": "string",
"title": "The unitPrice Schema ",
"mongodb": {
"dataType": "Decimal128"
}
}
}
],
// nested object
"summary": {
"totalValue": {
"type": "string",
"title": "The totalValue Schema ",
"mongodb": {
"dataType": "Decimal128"
}
}
},
```

Suppose model `Order` has a decimal property called `count`. Read the sections below for the usage of CRUD operations.
Suppose model `Order` has the model definition above, read the sections below for the usage of CRUD operations.

### Create

```js
Order.create({count: '0.0005'}, function(err, order) {
// order: {_id: '5bc8cc7f71cade4a8b5af886', count: '0.0005'}
const sample = {
count: '0.0005',
summary: {totalValue: '100.0005'},
lines: [{unitPrice: '0.0005'}],
};
Order.create(sample, function(err, order) {
// order: {
// _id: '5bc8cc7f71cade4a8b5af886',
// count: '0.0005',
// summary: {totalValue: '100.0005'},
// lines: [{unitPrice: '0.0005'}],
// }
});
```
The created record in the mongodb database will be:

```js
{ "_id" : ObjectId("5bc8cc7f71cade4a8b5af886"), "count" : NumberDecimal("0.0005") }
{
"_id" : ObjectId("5bc8cc7f71cade4a8b5af886"),
"count" : NumberDecimal("0.0005"),
"lines" : [ { "unitPrice" : NumberDecimal("0.0006") ],
"summary": { "totalValue": NumberDecimal("100.0005")}
}
```

The returned model instance is generated by the `create` method in `loopback-datasource-juggler/lib/dao.js`, connector only
Expand All @@ -34,12 +72,13 @@ var Decimal128 = require('mongodb').Decimal128;
Order.create({count: '0.0005'}, function(err, order) {
// convert the string to decimal
order.count = Decimal128.fromString(order.count);
// ... same conversion for nested properties
});
```

### Find

You can filter a decimal property like
You can filter a first level decimal property like

```js
OrderDecimal.find({where: {count: '0.0005'}}, function(err, orders) {
Expand All @@ -49,11 +88,28 @@ OrderDecimal.find({where: {count: '0.0005'}}, function(err, orders) {

The connector automatically converts the condition from string to decimal.

When query nested properties, you still need to do the conversion yourself, as follows

```js
// query decimal property in a nested array
const arrCond = {where: {lines: {elemMatch: {unitPrice: Decimal128.fromString('0.0005')}}}};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a question. How can LB users query nested Decimal128 properties via the REST API?

IIUC, the following query will not work because there is no code to automatically convert "0.0005" string into Decimal128 instance.

GET /orderdecimals?filter[where][lines][elemMatch][unitPrice]=0.0005

Is this something we should worry about and improve? (Possibly in a follow-up pull-request.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bajtos yes. Detecting the decimal property in a nested query is more complicated than in a model instance, since a query could contain extended operators. I would suggest customer use some workaround for the query...at least give the feature a lower severity level than sev1 to give us more time plan for it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jannyHou Makes sense. Could you please open a follow-up GH issue for this?

OrderDecimal.find(arrCond, function(err, orders) {});

// query decimal property in a nested object
const objCond = {where: {'summary.totalValue': Decimal128.fromString('100.0005')}};
OrderDecimal.find(objCond, function(err, orders) {});
```

### DestroyAll

You can destroy data with a filter contains decimal property. For example:

```js
OrderDecimal.destroyAll({count: '0.0005'}, cb);
```
// or
const arrCond = {where: {lines: {elemMatch: {unitPrice: Decimal128.fromString('0.0005')}}}};
OrderDecimal.destroyAll(arrCond, cb);
// or
const objCond = {where: {'summary.totalValue': Decimal128.fromString('100.0005')}};
OrderDecimal.destroyAll(objCond, cb);
```
77 changes: 53 additions & 24 deletions lib/mongodb.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,21 +410,16 @@ MongoDB.prototype.fromDatabase = function(model, data) {
MongoDB.prototype.toDatabase = function(model, data) {
var props = this._models[model].properties;

if (this.settings.enableGeoIndexing !== true) {
convertDecimalProps(data, props);
// Override custom column names
data = this.fromPropertyToDatabaseNames(model, data);
return data;
}

for (var p in props) {
var prop = props[p];
const isGeoPoint = data[p] && prop && prop.type && prop.type.name === 'GeoPoint';
if (isGeoPoint) {
data[p] = {
coordinates: [data[p].lng, data[p].lat],
type: 'Point',
};
if (this.settings.enableGeoIndexing === true) {
for (var p in props) {
var prop = props[p];
const isGeoPoint = data[p] && prop && prop.type && prop.type.name === 'GeoPoint';
if (isGeoPoint) {
data[p] = {
coordinates: [data[p].lng, data[p].lat],
type: 'Point',
};
}
}
}

Expand Down Expand Up @@ -956,6 +951,7 @@ MongoDB.prototype.buildWhere = function(model, where, options) {
cond = cond[spec];
}
if (spec) {
if (spec.charAt(0) === '$') spec = spec.substr(1);
if (spec === 'between') {
query[k] = {$gte: cond[0], $lte: cond[1]};
} else if (spec === 'inq') {
Expand Down Expand Up @@ -1996,17 +1992,50 @@ function optimizedFindOrCreate(model, filter, data, options, callback) {
* @param {Object} data The data that might contain a decimal property
* @param {Object} props The model property definitions
*/
function convertDecimalProps(data, props) {
if (debug.enabled) debug('convertDecimalProps props: ', util.inspect(props));
for (const p in props) {
const prop = props[p];
const isDecimal = data[p] && prop && prop.mongodb &&
prop.mongodb.dataType &&
prop.mongodb.dataType.toLowerCase() === 'decimal128';
function convertDecimalProps(data, propDef) {
if (propDef == null) return data;

if (Array.isArray(data)) {
const arrType = getArrayItemDef(propDef);
if (arrType) {
data.forEach(function(elem, i) {
data[i] = convertDecimalProps(elem, arrType);
});
if (debug.enabled) debug('convertDecimalProps converted array: ', util.inspect(data));
};
} else if (!!data && typeof data === 'object') {
// !!data: skips executing the code when data is `null`
const ownData = Object.getOwnPropertyNames(data);
ownData.forEach(function(k) {
data[k] = convertDecimalProps(data[k], getObjectDef(propDef, k));
});
if (debug.enabled) debug('convertDecimalProps converted object: ', util.inspect(data));
} else {
const isDecimal = propDef && propDef.mongodb &&
propDef.mongodb.dataType &&
propDef.mongodb.dataType.toLowerCase() === 'decimal128';
if (isDecimal) {
data[p] = Decimal128.fromString(data[p]);
debug('convertDecimalProps decimal value: ', data[p]);
data = Decimal128.fromString(data);
if (debug.enabled) debug('convertDecimalProps decimal value: ', data);
}
}

return data;
}

function getArrayItemDef(propDef) {
if (debug.enabled) debug('getArrayItemDef property definition: ', util.inspect(propDef));
if (isLBArr(propDef)) return propDef[0];
return null;
}

function getObjectDef(propDef, name) {
if (debug.enabled) debug('getObjectDef property definition: %o, name: %s',
util.inspect(propDef), name);
if (typeof propDef === 'object') return propDef[name];
return null;
}

function isLBArr(propDef) {
return typeof propDef === 'object' && Array.isArray(propDef.type);
}
Loading