Merge pull request #611 from strongloop/feature/dedupe-in-query

Dedupe ids args of inq for include
This commit is contained in:
Raymond Feng 2015-05-29 12:25:37 -07:00
commit 8cf899d1b5
4 changed files with 104 additions and 18 deletions

View File

@ -3,6 +3,7 @@ var utils = require('./utils');
var List = require('./list');
var isPlainObject = utils.isPlainObject;
var defineCachedRelations = utils.defineCachedRelations;
var uniq = utils.uniq;
/*!
* Normalize the include to be an array
@ -308,7 +309,7 @@ Inclusion.include = function (objects, include, options, cb) {
where: {}
};
throughFilter.where[relation.keyTo] = {
inq: sourceIds
inq: uniq(sourceIds)
};
if (polymorphic) {
//handle polymorphic hasMany (reverse) in which case we need to filter
@ -351,7 +352,7 @@ Inclusion.include = function (objects, include, options, cb) {
//Polymorphic relation does not have idKey of modelTo. Find it manually
var modelToIdName = idName(relation.modelTo);
filter.where[modelToIdName] = {
inq: targetIds
inq: uniq(targetIds)
};
//make sure that the modelToIdName is included if fields are specified
@ -432,7 +433,7 @@ Inclusion.include = function (objects, include, options, cb) {
obj.__cachedRelations[relationName] = [];
}
filter.where[relation.keyTo] = {
inq: allTargetIds
inq: uniq(allTargetIds)
};
relation.applyScope(null, filter);
/**
@ -496,7 +497,7 @@ Inclusion.include = function (objects, include, options, cb) {
obj.__cachedRelations[relationName] = [];
}
filter.where[relation.keyTo] = {
inq: sourceIds
inq: uniq(sourceIds)
};
relation.applyScope(null, filter);
relation.modelTo.find(filter, targetFetchHandler);
@ -574,7 +575,7 @@ Inclusion.include = function (objects, include, options, cb) {
utils.mergeQuery(typeFilter, filter);
var targetIds = targetIdsByType[modelType];
typeFilter.where[relation.keyTo] = {
inq: targetIds
inq: uniq(targetIds)
};
var Model = lookupModel(relation.modelFrom.dataSource.modelBuilder.
models, modelType);
@ -650,7 +651,7 @@ Inclusion.include = function (objects, include, options, cb) {
obj.__cachedRelations[relationName] = null;
}
filter.where[relation.keyTo] = {
inq: targetIds
inq: uniq(targetIds)
};
relation.applyScope(null, filter);
relation.modelTo.find(filter, targetFetchHandler);

View File

@ -10,9 +10,11 @@ exports.sortObjectsByIds = sortObjectsByIds;
exports.setScopeValuesFromWhere = setScopeValuesFromWhere;
exports.mergeQuery = mergeQuery;
exports.mergeIncludes = mergeIncludes;
exports.createPromiseCallback = createPromiseCallback
exports.createPromiseCallback = createPromiseCallback;
exports.uniq = uniq;
var traverse = require('traverse');
var assert = require('assert');
function safeRequire(module) {
try {
@ -475,3 +477,22 @@ function throwPromiseNotDefined() {
'Your Node runtime does support ES6 Promises. ' +
'Set "global.Promise" to your preferred implementation of promises.');
}
/**
* Dedupe an array
* @param {Array} an array
* @returns {Array} an array with unique items
*/
function uniq(a) {
var uniqArray = [];
if (!a) {
return uniqArray;
}
assert(Array.isArray(a), 'array argument is required');
for (var i = 0, n = a.length; i < n; i++) {
if (a.indexOf(a[i]) === i) {
uniqArray.push(a[i]);
}
}
return uniqArray;
}

View File

@ -160,6 +160,26 @@ describe('include', function () {
});
});
it('should support limit', function(done) {
Passport.find({
include: {
owner: {
relation: 'posts', scope: {
fields: ['title'], include: ['author'],
order: 'title DESC',
limit: 2
}
}
},
limit: 1
}, function(err, passports) {
if (err) return done(err);
passports.length.should.equal(1);
passports[0].toJSON().owner.posts.length.should.equal(2);
done();
});
});
it('should fetch Users with include scope on Posts - belongsTo', function (done) {
Post.find({
include: { relation: 'author', scope:{ fields: ['name'] }}

View File

@ -5,6 +5,7 @@ var removeUndefined = utils.removeUndefined;
var mergeSettings = utils.mergeSettings;
var mergeIncludes = utils.mergeIncludes;
var sortObjectsByIds = utils.sortObjectsByIds;
var uniq = utils.uniq;
describe('util.fieldsToArray', function () {
function sample(fields, excludeUnknown) {
@ -242,7 +243,7 @@ describe('util.mergeIncludes', function () {
'Merged include should match the expectation');
}
it('Merge string values to object', function () {
it('Merge string values to object', function() {
var baseInclude = 'relation1';
var updateInclude = 'relation2';
var expectedInclude = [
@ -252,7 +253,7 @@ describe('util.mergeIncludes', function () {
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge string & array values to object', function () {
it('Merge string & array values to object', function() {
var baseInclude = 'relation1';
var updateInclude = ['relation2'];
var expectedInclude = [
@ -262,7 +263,7 @@ describe('util.mergeIncludes', function () {
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge string & object values to object', function () {
it('Merge string & object values to object', function() {
var baseInclude = ['relation1'];
var updateInclude = {relation2: 'relation2Include'};
var expectedInclude = [
@ -272,7 +273,7 @@ describe('util.mergeIncludes', function () {
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge array & array values to object', function () {
it('Merge array & array values to object', function() {
var baseInclude = ['relation1'];
var updateInclude = ['relation2'];
var expectedInclude = [
@ -282,7 +283,7 @@ describe('util.mergeIncludes', function () {
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge array & object values to object', function () {
it('Merge array & object values to object', function() {
var baseInclude = ['relation1'];
var updateInclude = {relation2: 'relation2Include'};
var expectedInclude = [
@ -292,7 +293,7 @@ describe('util.mergeIncludes', function () {
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge object & object values to object', function () {
it('Merge object & object values to object', function() {
var baseInclude = {relation1: 'relation1Include'};
var updateInclude = {relation2: 'relation2Include'};
var expectedInclude = [
@ -302,7 +303,7 @@ describe('util.mergeIncludes', function () {
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Override property collision with update value', function () {
it('Override property collision with update value', function() {
var baseInclude = {relation1: 'baseValue'};
var updateInclude = {relation1: 'updateValue'};
var expectedInclude = [
@ -312,7 +313,7 @@ describe('util.mergeIncludes', function () {
});
it('Merge string includes & include with relation syntax properly',
function () {
function() {
var baseInclude = 'relation1';
var updateInclude = {relation: 'relation1'};
var expectedInclude = [
@ -321,7 +322,7 @@ describe('util.mergeIncludes', function () {
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge string includes & include with scope properly', function () {
it('Merge string includes & include with scope properly', function() {
var baseInclude = 'relation1';
var updateInclude = {
relation: 'relation1',
@ -334,7 +335,7 @@ describe('util.mergeIncludes', function () {
});
it('Merge includes with and without relation syntax properly',
function () {
function() {
//w & w/o relation syntax - no collision
var baseInclude = ['relation2'];
var updateInclude = {
@ -361,7 +362,7 @@ describe('util.mergeIncludes', function () {
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge includes with mixture of strings, arrays & objects properly', function () {
it('Merge includes with mixture of strings, arrays & objects properly', function() {
var baseInclude = ['relation1', {relation2: true},
{relation: 'relation3', scope: {where: {id: 'some id'}}},
{relation: 'relation5', scope: {where: {id: 'some id'}}}
@ -374,5 +375,48 @@ describe('util.mergeIncludes', function () {
{relation: 'relation5', scope: {where: {id: 'some id'}}}];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
});
describe('util.uniq', function() {
it('should dedupe an array with duplicate number entries', function() {
var a = [1, 2, 1, 3];
var b = uniq(a);
b.should.eql([1, 2, 3]);
});
it('should dedupe an array with duplicate string entries', function() {
var a = ['a', 'a', 'b', 'a'];
var b = uniq(a);
b.should.eql(['a', 'b']);
});
it('should dedupe an array without duplicate number entries', function() {
var a = [1, 3, 2];
var b = uniq(a);
b.should.eql([1, 3, 2]);
});
it('should dedupe an array without duplicate string entries', function() {
var a = ['a', 'c', 'b'];
var b = uniq(a);
b.should.eql(['a', 'c', 'b']);
});
it('should allow null/undefined array', function() {
var a = null;
var b = uniq(a);
b.should.eql([]);
});
it('should report error for non-array arg', function() {
var a = '1';
try {
var b = uniq(a);
throw new Error('The test should have thrown an error');
} catch (err) {
err.should.be.instanceof(Error);
}
});
});