Merge branch 'wertlex-with-simpe-and-fast-hasmany'
This commit is contained in:
commit
c67922cc59
|
@ -1,6 +1,7 @@
|
|||
var async = require('async');
|
||||
var utils = require('./utils');
|
||||
var List = require('./list');
|
||||
var includeUtils = require('./include_utils');
|
||||
var isPlainObject = utils.isPlainObject;
|
||||
var defineCachedRelations = utils.defineCachedRelations;
|
||||
var uniq = utils.uniq;
|
||||
|
@ -267,6 +268,11 @@ Inclusion.include = function (objects, include, options, cb) {
|
|||
if (relation.type === 'referencesMany') {
|
||||
return includeReferencesMany(cb);
|
||||
}
|
||||
|
||||
//This handles exactly hasMany. Fast and straightforward. Without parallel, each and other boilerplate.
|
||||
if(relation.type === 'hasMany' && relation.multiple && !subInclude){
|
||||
return includeHasManySimple(cb);
|
||||
}
|
||||
//assuming all other relations with multiple=true as hasMany
|
||||
return includeHasMany(cb);
|
||||
}
|
||||
|
@ -482,6 +488,35 @@ Inclusion.include = function (objects, include, options, cb) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle inclusion of HasMany relation
|
||||
* @param callback
|
||||
*/
|
||||
function includeHasManySimple(callback) {
|
||||
//Map for Indexing objects by their id for faster retrieval
|
||||
var objIdMap2 = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, relation.keyFrom);
|
||||
|
||||
filter.where[relation.keyTo] = {
|
||||
inq: uniq(objIdMap2.getKeys())
|
||||
};
|
||||
|
||||
relation.applyScope(null, filter);
|
||||
relation.modelTo.find(filter, options, targetFetchHandler);
|
||||
|
||||
function targetFetchHandler(err, targets) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
var targetsIdMap = includeUtils.buildOneToManyIdentityMapWithOrigKeys(targets, relation.keyTo);
|
||||
includeUtils.join(objIdMap2, targetsIdMap, function(obj1, valueToMergeIn){
|
||||
defineCachedRelations(obj1);
|
||||
obj1.__cachedRelations[relationName] = valueToMergeIn;
|
||||
processTargetObj(obj1, function(){});
|
||||
});
|
||||
callback(err, objs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle inclusion of HasMany relation
|
||||
* @param callback
|
||||
|
@ -808,7 +843,9 @@ Inclusion.include = function (objects, include, options, cb) {
|
|||
* @returns {*}
|
||||
*/
|
||||
function processTargetObj(obj, callback) {
|
||||
var inst = (obj instanceof self) ? obj : new self(obj);
|
||||
|
||||
var isInst = obj instanceof self;
|
||||
|
||||
// Calling the relation method on the instance
|
||||
if (relation.type === 'belongsTo') {
|
||||
// If the belongsTo relation doesn't have an owner
|
||||
|
@ -816,7 +853,7 @@ Inclusion.include = function (objects, include, options, cb) {
|
|||
defineCachedRelations(obj);
|
||||
// Set to null if the owner doesn't exist
|
||||
obj.__cachedRelations[relationName] = null;
|
||||
if (obj === inst) {
|
||||
if (isInst) {
|
||||
obj.__data[relationName] = null;
|
||||
} else {
|
||||
obj[relationName] = null;
|
||||
|
@ -830,7 +867,7 @@ Inclusion.include = function (objects, include, options, cb) {
|
|||
* @param cb
|
||||
*/
|
||||
function setIncludeData(result, cb) {
|
||||
if (obj === inst) {
|
||||
if (isInst) {
|
||||
if (Array.isArray(result) && !(result instanceof List)) {
|
||||
result = new List(result, relation.modelTo);
|
||||
}
|
||||
|
@ -848,6 +885,9 @@ Inclusion.include = function (objects, include, options, cb) {
|
|||
return setIncludeData(obj.__cachedRelations[relationName],
|
||||
callback);
|
||||
}
|
||||
|
||||
var inst = (obj instanceof self) ? obj : new self(obj);
|
||||
|
||||
//If related objects are not cached by include Handlers, directly call
|
||||
//related accessor function even though it is not very efficient
|
||||
var related; // relation accessor function
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
module.exports.buildOneToOneIdentityMapWithOrigKeys = buildOneToOneIdentityMapWithOrigKeys;
|
||||
module.exports.buildOneToManyIdentityMapWithOrigKeys = buildOneToManyIdentityMapWithOrigKeys;
|
||||
module.exports.join = join;
|
||||
module.exports.KVMap = KVMap;
|
||||
|
||||
/**
|
||||
* Effectively builds associative map on id -> object relation and stores original keys.
|
||||
* Map returned in form of object with ids in keys and object as values.
|
||||
* @param objs array of objects to build from
|
||||
* @param idName name of property to be used as id. Such property considered to be unique across array.
|
||||
* In case of collisions last wins. For non-unique ids use buildOneToManyIdentityMap()
|
||||
* @returns {} object where keys are ids and values are objects itself
|
||||
*/
|
||||
function buildOneToOneIdentityMapWithOrigKeys(objs, idName) {
|
||||
var kvMap = new KVMap();
|
||||
for(var i = 0; i < objs.length; i++) {
|
||||
var obj = objs[i];
|
||||
var id = obj[idName];
|
||||
kvMap.set(id, obj);
|
||||
}
|
||||
return kvMap;
|
||||
}
|
||||
|
||||
function buildOneToManyIdentityMapWithOrigKeys(objs, idName) {
|
||||
var kvMap = new KVMap();
|
||||
for(var i = 0; i < objs.length; i++) {
|
||||
var obj = objs[i];
|
||||
var id = obj[idName];
|
||||
var value = kvMap.get(id) || [];
|
||||
value.push(obj);
|
||||
kvMap.set(id, value);
|
||||
}
|
||||
return kvMap;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Yeah, it joins. You need three things id -> obj1 map, id -> [obj2] map and merge function.
|
||||
* This functions will take each obj1, locate all data to join in map2 and call merge function.
|
||||
* @param oneToOneIdMap
|
||||
* @param oneToManyIdMap
|
||||
* @param mergeF function(obj, objectsToMergeIn)
|
||||
*/
|
||||
function join(oneToOneIdMap, oneToManyIdMap, mergeF) {
|
||||
var ids = oneToOneIdMap.getKeys();
|
||||
for(var i = 0; i < ids.length; i++) {
|
||||
var id = ids[i];
|
||||
var obj = oneToOneIdMap.get(id);
|
||||
var objectsToMergeIn = oneToManyIdMap.get(id) || [];
|
||||
mergeF(obj, objectsToMergeIn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map with arbitrary keys and values. User .set() and .get() to work with values instead of []
|
||||
* @returns {{set: Function, get: Function, remove: Function, exist: Function, getKeys: Function}}
|
||||
* @constructor
|
||||
*/
|
||||
function KVMap(){
|
||||
var _originalKeyFieldName = 'originalKey';
|
||||
var _valueKeyFieldName = 'value';
|
||||
var _dict = {};
|
||||
var keyToString = function(key){ return key.toString() };
|
||||
var mapImpl = {
|
||||
set: function(key, value){
|
||||
var recordObj = {};
|
||||
recordObj[_originalKeyFieldName] = key;
|
||||
recordObj[_valueKeyFieldName] = value;
|
||||
_dict[keyToString(key)] = recordObj;
|
||||
return true;
|
||||
},
|
||||
get: function(key){
|
||||
var storeObj = _dict[keyToString(key)];
|
||||
if(storeObj) {
|
||||
return storeObj[_valueKeyFieldName];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
remove: function(key){
|
||||
delete _dict[keyToString(key)];
|
||||
return true;
|
||||
},
|
||||
exist: function(key) {
|
||||
var result = _dict.hasOwnProperty(keyToString(key));
|
||||
return result;
|
||||
},
|
||||
getKeys: function(){
|
||||
var result = [];
|
||||
for(var key in _dict) {
|
||||
result.push(_dict[key][_originalKeyFieldName]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
return mapImpl;
|
||||
}
|
|
@ -56,6 +56,7 @@ describe('include', function () {
|
|||
|
||||
it('should fetch Passport - Owner - Posts', function (done) {
|
||||
Passport.find({include: {owner: 'posts'}}, function (err, passports) {
|
||||
|
||||
should.not.exist(err);
|
||||
should.exist(passports);
|
||||
passports.length.should.be.ok;
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
var assert = require("assert");
|
||||
var should = require("should");
|
||||
|
||||
var includeUtils = require("../lib/include_utils");
|
||||
|
||||
describe('include_util', function(){
|
||||
describe('#buildOneToOneIdentityMapWithOrigKeys', function(){
|
||||
it('should return an object with keys', function(){
|
||||
var objs = [
|
||||
{id: 11, letter: "A"},
|
||||
{id: 22, letter: "B"}
|
||||
];
|
||||
var result = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, "id");
|
||||
result.get(11).should.be.ok;
|
||||
result.get(22).should.be.ok;
|
||||
});
|
||||
|
||||
it('should overwrite keys in case of collision', function(){
|
||||
var objs = [
|
||||
{id: 11, letter: "A"},
|
||||
{id: 22, letter: "B"},
|
||||
{id: 33, letter: "C"},
|
||||
{id: 11, letter: "HA!"}
|
||||
];
|
||||
|
||||
var result = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, "id");
|
||||
result.getKeys().should.containEql(11);
|
||||
result.getKeys().should.containEql(22);
|
||||
result.getKeys().should.containEql(33);
|
||||
result.get(11)["letter"].should.equal("HA!");
|
||||
result.get(33)["letter"].should.equal("C");
|
||||
});
|
||||
});
|
||||
describe('#buildOneToOneIdentityMapWithOrigKeys', function(){
|
||||
it('should return an object with keys', function(){
|
||||
var objs = [
|
||||
{id: 11, letter: "A"},
|
||||
{id: 22, letter: "B"}
|
||||
];
|
||||
var result = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, 'id');
|
||||
result.get(11).should.be.ok;
|
||||
result.get(22).should.be.ok;
|
||||
result.getKeys().should.have.lengthOf(2); // no additional properties
|
||||
});
|
||||
});
|
||||
describe('#buildOneToManyIdentityMap', function(){
|
||||
it('should return an object with keys', function(){
|
||||
var objs = [
|
||||
{id: 11, letter: "A"},
|
||||
{id: 22, letter: "B"}
|
||||
];
|
||||
var result = includeUtils.buildOneToManyIdentityMapWithOrigKeys(objs, "id");
|
||||
result.exist(11).should.be.true;
|
||||
result.exist(22).should.be.true;
|
||||
});
|
||||
|
||||
it('should collect keys in case of collision', function(){
|
||||
var objs = [
|
||||
{fk_id: 11, letter: "A"},
|
||||
{fk_id: 22, letter: "B"},
|
||||
{fk_id: 33, letter: "C"},
|
||||
{fk_id: 11, letter: "HA!"}
|
||||
];
|
||||
|
||||
var result = includeUtils.buildOneToManyIdentityMapWithOrigKeys(objs, "fk_id");
|
||||
result.get(11)[0]["letter"].should.equal("A");
|
||||
result.get(11)[1]["letter"].should.equal("HA!");
|
||||
result.get(33)[0]["letter"].should.equal("C");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('KVMap', function(){
|
||||
it('should allow to set and get value with key string', function(){
|
||||
var map = new includeUtils.KVMap();
|
||||
map.set('name', 'Alex');
|
||||
map.set('gender', true);
|
||||
map.set('age', 25);
|
||||
map.get('name').should.be.equal('Alex');
|
||||
map.get('gender').should.be.equal(true);
|
||||
map.get('age').should.be.equal(25);
|
||||
});
|
||||
it('should allow to set and get value with arbitrary key type', function(){
|
||||
var map = new includeUtils.KVMap();
|
||||
map.set('name', 'Alex');
|
||||
map.set(true, 'male');
|
||||
map.set(false, false);
|
||||
map.set({isTrue: 'yes'}, 25);
|
||||
map.get('name').should.be.equal('Alex');
|
||||
map.get(true).should.be.equal('male');
|
||||
map.get(false).should.be.equal(false);
|
||||
map.get({isTrue: 'yes'}).should.be.equal(25);
|
||||
});
|
||||
it('should not allow to get values with [] operator', function(){
|
||||
var map = new includeUtils.KVMap();
|
||||
map.set('name', 'Alex');
|
||||
(map['name'] === undefined).should.be.equal(true);
|
||||
});
|
||||
it('should provide .exist() method for checking if key presented', function(){
|
||||
var map = new includeUtils.KVMap();
|
||||
map.set('one', 1);
|
||||
map.set(2, 'two');
|
||||
map.set(true, 'true');
|
||||
map.exist('one').should.be.true;
|
||||
map.exist(2).should.be.true;
|
||||
map.exist(true).should.be.true;
|
||||
map.exist('two').should.be.false;
|
||||
});
|
||||
it('should return array of original keys with .getKeys()', function(){
|
||||
var map = new includeUtils.KVMap();
|
||||
map.set('one', 1);
|
||||
map.set(2, 'two');
|
||||
map.set(true, 'true');
|
||||
var keys = map.getKeys();
|
||||
keys.should.containEql('one');
|
||||
keys.should.containEql(2);
|
||||
keys.should.containEql(true);
|
||||
});
|
||||
it('should allow to store and fetch arrays', function(){
|
||||
var map = new includeUtils.KVMap();
|
||||
map.set(1, [1, 2, 3]);
|
||||
map.set(2, [2, 3, 4]);
|
||||
var valueOne = map.get(1);
|
||||
valueOne.should.be.eql([1, 2, 3]);
|
||||
valueOne.push(99);
|
||||
map.set(1, valueOne);
|
||||
var valueOneUpdated = map.get(1);
|
||||
valueOneUpdated.should.be.eql([1, 2, 3, 99]);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue