Skip to content

Commit

Permalink
fix: null value not persisted for properties of type JSON, Any, or Ob…
Browse files Browse the repository at this point in the history
…ject

Signed-off-by: Siim Sams <[email protected]>
  • Loading branch information
siimsams authored and aaqilniz committed Sep 26, 2024
1 parent c905f02 commit 720f3c9
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 3 deletions.
40 changes: 40 additions & 0 deletions lib/connectors/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,13 +496,53 @@ Memory.prototype._findAllSkippingIncludes = function(model, filter) {

// field selection
if (filter.fields) {
if (filter.count) filter.fields.push('count');
if (filter.max) filter.fields.push('max');
if (filter.min) filter.fields.push('min');
if (filter.sum) filter.fields.push('sum');
if (filter.avg) filter.fields.push('avg');
nodes = nodes.map(utils.selectFields(filter.fields));
}

// limit/skip
const skip = filter.skip || filter.offset || 0;
const limit = filter.limit || nodes.length;
// groupBy
nodes = nodes.slice(skip, skip + limit);
if (filter.groupBy) {
nodes = utils.groupBy(nodes, filter.groupBy);
const tempNodes = [];
Object.keys(nodes).forEach(nodeKey => {
let count = undefined;
const tempNode = {...nodes[nodeKey][0]};
if (filter.count) {
count = nodes[nodeKey].filter((obj) => {
const id = obj[filter.count];
return obj[filter.count] === id;
}).length;
tempNode.count = count;
}
if (filter.max) {
tempNode.max = Math.max(...nodes[nodeKey].map(o => o[filter.max]));
}
if (filter.min) {
tempNode.min = Math.min(...nodes[nodeKey].map(o => o[filter.min]));
}
if (filter.sum) {
tempNode.sum = nodes[nodeKey].reduce((accumulator, object) => {
return accumulator + object[filter.sum];
}, 0);
}
if (filter.avg) {
tempNode.avg = nodes[nodeKey].reduce((accumulator, object) => {
return accumulator + object[filter.avg];
}, 0);
tempNode.avg = tempNode.avg / nodes[nodeKey].length;
}
tempNodes.push(tempNode);
});
nodes = tempNodes;
}
}
return nodes;

Expand Down
20 changes: 17 additions & 3 deletions lib/dao.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,12 @@ DataAccessObject._forDB = function(data) {
const res = {};
for (const propName in data) {
const type = this.getPropertyType(propName);
if (type === 'JSON' || type === 'Any' || type === 'Object' || data[propName] instanceof Array) {
res[propName] = JSON.stringify(data[propName]);
const value = data[propName];
if (value !== null && (type === 'JSON' || type === 'Any' ||
type === 'Object' || value instanceof Array)) {
res[propName] = JSON.stringify(value);
} else {
res[propName] = data[propName];
res[propName] = value;
}
}
return res;
Expand Down Expand Up @@ -1928,6 +1930,18 @@ DataAccessObject.find = function find(query, options, cb) {
}
}

const keys = Object.keys(data);
keys.forEach(key => {
if (
key.includes('sumOf') ||
key.includes('countOf') ||
key.includes('avgOf') ||
key.includes('minOf') ||
key.includes('maxOf')
) {
obj.__data[key] = data[key];
}
});
callback(null, obj);
}

Expand Down
14 changes: 14 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ exports.idsHaveDuplicates = idsHaveDuplicates;
exports.isClass = isClass;
exports.escapeRegExp = escapeRegExp;
exports.applyParentProperty = applyParentProperty;
exports.groupBy = groupBy;

const g = require('strong-globalize')();
const traverse = require('traverse');
Expand Down Expand Up @@ -893,3 +894,16 @@ function applyParentProperty(element, parent) {
});
}
}

function groupBy(items, key) {
return items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);
}
50 changes: 50 additions & 0 deletions test/crud-with-options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,56 @@ describe('crud-with-options', function() {
User.find({limit: 3});
});

it('should allow filter with groupBy, count, max, min, sum & avg', function(done) {
User.find({
groupBy: ['vip'],
count: 'vip',
max: 'id',
min: 'id',
sum: 'id',
avg: 'id',
}, options, function(err, users) {
should.not.exist(err);
should.exist(users);
users.length.should.be.above(0);
users.forEach(user => {
user.should.have.property('count', user.count);
user.should.have.property('max');
user.should.have.property('min');
user.should.have.property('sum');
user.should.have.property('avg');
});
done();
});
});

it('should allow filter with groupBy, aggregate methods and other filters', function(done) {
User.find({
groupBy: ['vip'],
count: 'vip',
max: 'id',
min: 'id',
sum: 'id',
avg: 'id',
limit: 1,
fields: ['name', 'id'],
}, options, function(err, users) {
should.not.exist(err);
should.exist(users);
users.length.should.be.equal(1);
users.forEach(user => {
user.should.have.property('count', user.count);
user.should.have.property('max');
user.should.have.property('min');
user.should.have.property('sum');
user.should.have.property('avg');
user.should.have.property('name');
user.should.have.property('id');
});
done();
});
});

it('should skip trailing undefined args', function(done) {
User.find({limit: 3}, function(err, users) {
should.exists(users);
Expand Down
52 changes: 52 additions & 0 deletions test/loopback-dl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,58 @@ describe('Model define with scopes configuration', function() {
});
});

describe('DataAccessObject._forDB', function() {
const ds = new DataSource('memory');
const dao = ds.DataAccessObject;

it('should return input data if dataSource is not relational', function() {
const inputData = {testKey: 'testValue'};
dao.getDataSource = () => ({isRelational: () => false});

const outputData = dao._forDB(inputData);

assert.deepEqual(outputData, inputData);
});

it('should return JSON stringified values for appropriate types', function() {
const inputData = {
key1: [1, 2, 3],
key2: {subKey: 'value'},
key3: 'nonJSONvalue',
};
dao.getDataSource = () => ({isRelational: () => true});
dao.getPropertyType = (propName) => (propName !== 'key3' ? 'JSON' : 'String');

const outputData = dao._forDB(inputData);

assert.deepEqual(outputData, {
key1: JSON.stringify([1, 2, 3]),
key2: JSON.stringify({subKey: 'value'}),
key3: 'nonJSONvalue',
});
});

it('should return original value for non JSON, non Array types', function() {
const inputData = {key1: 'string', key2: 123, key3: true};
dao.getDataSource = () => ({isRelational: () => true});
dao.getPropertyType = () => 'String';

const outputData = dao._forDB(inputData);

assert.deepEqual(outputData, inputData);
});

it('should not process null values', function() {
const inputData = {key1: 'value', key2: null};
dao.getDataSource = () => ({isRelational: () => true});
dao.getPropertyType = (propName) => 'JSON';

const outputData = dao._forDB(inputData);

assert.deepEqual(outputData, {key1: JSON.stringify('value'), key2: null});
});
});

describe('DataAccessObject', function() {
let ds, model, where, error, filter;

Expand Down

0 comments on commit 720f3c9

Please sign in to comment.