diff --git a/lib/include.js b/lib/include.js index a88a2458..d88f1a4b 100644 --- a/lib/include.js +++ b/lib/include.js @@ -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,108 @@ 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 objIdMap = includeUtils.buildOneToOneIdentityMap(objs, relation.keyFrom); + // all ids of primary objects to use in query + var sourceIds = Object.keys(objIdMap); + + filter.where[relation.keyTo] = { + inq: uniq(sourceIds) + }; + + relation.applyScope(null, filter); + relation.modelTo.find(filter, options, targetFetchHandler2); + + function targetFetchHandler2(err, targets) { + if(err) { + return callback(err); + } + + var targetsIdMap = includeUtils.buildOneToManyIdentityMap(targets, relation.keyTo); + includeUtils.join(objIdMap, targetsIdMap, function(obj1, valueToMergeIn){ + obj1[relation.name] = valueToMergeIn; + }); + callback(err, objs); + } + /** + * Process fetched related objects + * @param err + * @param {Array} targets + * @returns {*} + */ + function targetFetchHandler(err, targets) { + console.log("### query done. I'm in targetFetchHandler"); + console.log("### obtained: \ntargets(permission) count: " + targets.length + "\nobjs(users) count: " + objs.length); + if (err) { + return callback(err); + } + + var modelToFKIdMap = includeUtils.buildOneToManyIdentityMap(targets, relation.keyTo); + + var tasks = []; + //simultaneously process subIncludes + if (subInclude && targets) { + tasks.push(function subIncludesTask(next) { + relation.modelTo.include(targets, subInclude, options, next); + }); + } + //process each target object + tasks.push(targetLinkingTask); + function targetLinkingTask(next) { + if (targets.length === 0) { + return async.each(objs, function(obj, next) { + processTargetObj(obj, next); + }, next); + } + // TODO: show targets length + // почему так сложно???? Не очень понимаю. + async.each(targets, linkManyToOne, next); + function linkManyToOne(target, next) { + console.log("### processing target: " + JSON.stringify(target)); + //fix for bug in hasMany with referencesMany + var targetIds = [].concat(target[relation.keyTo]); + console.log("### targetIds: " + JSON.stringify(targetIds)); + async.each(targetIds, function (targetId, next) { + var obj = objIdMap[targetId.toString()]; + if (!obj) return next(); + obj.__cachedRelations[relationName].push(target); + //console.time("@@@Prepare1"); + processTargetObj(obj, next); + //console.timeEnd("@@@Prepare1"); + }, function(err, processedTargets) { + if (err) { + return next(err); + } + console.time("finding object with empty ids"); + var objsWithEmptyRelation = objs.filter(function(obj) { + return obj.__cachedRelations[relationName].length === 0; + }); + console.timeEnd("finding object with empty ids"); // 0-1 ms + console.log("### objs with empty relations count: " + objsWithEmptyRelation.length); + //next(processedTargets); + console.time("Prepare empty"); + async.each(objsWithEmptyRelation, function(obj, next) { + console.time("Prepare one"); + processTargetObj(obj, next); + console.timeEnd("Prepare one"); + }, function(err) { + console.timeEnd("Prepare empty"); + next(err, processedTargets); + }); + }); + } + } + + console.log("### tasks count: " + tasks.length); + execTasksWithInterLeave(tasks, callback); + } + } + /** * Handle inclusion of HasMany relation * @param callback diff --git a/test/include.test.js b/test/include.test.js index c9b82bee..3f954a70 100644 --- a/test/include.test.js +++ b/test/include.test.js @@ -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;