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

DynRef #2640

Merged
merged 1 commit into from
Feb 3, 2015
Merged

DynRef #2640

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
279 changes: 176 additions & 103 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2191,31 +2191,17 @@ Model.populate = function (docs, paths, cb) {
/*!
* Populates `docs`
*/
var excludeIdReg = /\s?-_id\s?/,
excludeIdRegGlobal = /\s?-_id\s?/g;

function populate (model, docs, options, cb) {
var select = options.select
, match = options.match
, path = options.path;
function populate(model, docs, options, cb) {
var schema = model._getSchema(options.path),
modelsMap, rawIds;

var schema = model._getSchema(path);
var subpath;

// handle document arrays
if (schema && schema.caster) {
schema = schema.caster;
}

// model name for the populate query
var modelName = options.model && options.model.modelName
|| options.model // query options
|| schema && schema.options && schema.options.ref // declared in schema
|| model.modelName; // an ad-hoc structure

var Model = model.db.model(modelName);

// expose the model used
options.model = Model;

// normalize single / multiple docs passed
if (!Array.isArray(docs)) {
docs = [docs];
Expand All @@ -2225,93 +2211,18 @@ function populate (model, docs, options, cb) {
return cb();
}

var rawIds = [];
var i, doc, id;
var ret;
var found = 0;
var isDocument;
var numDocuments = docs.length;

for (i = 0; i < numDocuments; ++i) {
ret = undefined;
doc = docs[i];
id = String(utils.getValue("_id", doc));
isDocument = !! doc.$__;

if (!ret || Array.isArray(ret) && 0 === ret.length) {
ret = utils.getValue(path, doc);
}

if (ret) {
ret = convertTo_id(ret);

// previously we always assigned this even if the document had no _id
options._docs[id] = Array.isArray(ret)
? ret.slice()
: ret;
}

// always retain original values, even empty values. these are
// used to map the query results back to the correct position.
rawIds.push(ret);

if (isDocument) {
// cache original populated _ids and model used
doc.populated(path, options._docs[id], options);
}
}

var ids = utils.array.flatten(rawIds, function (item) {
// no need to include undefined values in our query
return undefined !== item;
});

if (0 === ids.length || ids.every(utils.isNullOrUndefined)) {
return cb();
}

// preserve original match conditions by copying
if (match) {
match = utils.object.shallowCopy(match);
} else {
match = {};
}
modelsMap = getModelsMapForPopulate(model, schema, docs, options);
rawIds = getIdsForAndAddIdsInMapPopulate(modelsMap);

match._id || (match._id = { $in: ids });
var i, len = modelsMap.length,
mod, match, select, promise, vals = [];

var assignmentOpts = {};
assignmentOpts.sort = options.options && options.options.sort || undefined;
assignmentOpts.excludeId = /\s?-_id\s?/.test(select) || (select && 0 === select._id);

if (assignmentOpts.excludeId) {
// override the exclusion from the query so we can use the _id
// for document matching during assignment. we'll delete the
// _id back off before returning the result.
if ('string' == typeof select) {
select = select.replace(/\s?-_id\s?/g, ' ');
} else {
// preserve original select conditions by copying
select = utils.object.shallowCopy(select);
delete select._id;
}
}

// if a limit option is passed, we should have the limit apply to *each*
// document, not apply in the aggregate
if (options.options && options.options.limit) {
assignmentOpts.originalLimit = options.options.limit;
options.options.limit = options.options.limit * numDocuments;
}

Model.find(match, select, options.options, function (err, vals) {
promise = new Promise(function(err, vals, options, assignmentOpts) {
if (err) return cb(err);

var lean = options.options && options.options.lean;
var len = vals.length;
var rawOrder = {};
var rawDocs = {}
var key;
var val;
var lean = options.options && options.options.lean,
len = vals.length,
rawOrder = {}, rawDocs = {}, key, val;

// optimization:
// record the document positions as returned by
Expand All @@ -2331,12 +2242,174 @@ function populate (model, docs, options, cb) {
rawDocs: rawDocs,
rawOrder: rawOrder,
docs: docs,
path: path,
path: options.path,
options: assignmentOpts
});

cb();
});

for (i = 0; i < len; i++) {
mod = modelsMap[i];
select = mod.options.select;

if (mod.options.match) {
match = utils.object.shallowCopy(mod.options.match);
} else {
match = {};
}

var ids = utils.array.flatten(mod.ids, function(item) {
// no need to include undefined values in our query
return undefined !== item;
});

if (0 === ids.length || ids.every(utils.isNullOrUndefined)) {
return cb();
}

match._id || (match._id = {
$in: ids
});

var assignmentOpts = {};
assignmentOpts.sort = mod.options.options && mod.options.options.sort || undefined;
assignmentOpts.excludeId = excludeIdReg.test(select) || (select && 0 === select._id);

if (assignmentOpts.excludeId) {
// override the exclusion from the query so we can use the _id
// for document matching during assignment. we'll delete the
// _id back off before returning the result.
if ('string' == typeof select) {
select = select.replace(excludeIdRegGlobal, ' ');
} else {
// preserve original select conditions by copying
select = utils.object.shallowCopy(select);
delete select._id;
}
}

if (mod.options.options && mod.options.options.limit) {
assignmentOpts.originalLimit = mod.options.options.limit;
mod.options.options.limit = mod.options.options.limit * ids.length;
}

mod.Model.find(match, select, mod.options.options, next.bind(this, i + 1 < len, mod.options, assignmentOpts));
}

function next(end, options, assignmentOpts, err, valsFromDb) {
if (err) return promise.resolve(err);
vals = vals.concat(valsFromDb);

if (!end) {
promise.resolve(err, vals, options, assignmentOpts);
}
}
}

function getModelsMapForPopulate(model, schema, docs, options) {
var i, doc, len = docs.length,
available = {}, map = [],
refPath = schema && schema.options && schema.options.refPath,
Model, currentOptions, modelNames, modelName;

if (refPath) {
for (i = 0; i < len; i++) {
doc = docs[i];
modelNames = utils.getValue(refPath, doc);

if (!modelNames)
continue;

if (!Array.isArray(modelNames)) {
modelNames = [modelNames];
}

var k = modelNames.length;
while (k--) {
modelName = modelNames[k];
if (!available[modelName]) {
Model = model.db.model(modelName);
currentOptions = {
model: Model
};

utils.merge(currentOptions, options);

available[modelName] = {
Model: Model,
options: currentOptions,
docs: [doc],
ids: []
};
map.push(available[modelName]);
} else {
available[modelName].docs.push(doc);
}

}
}

} else {

// model name for the populate query
modelName = options.model && options.model.modelName || options.model // query options
|| schema && schema.options && schema.options.ref // declared in schema
|| model.modelName; // an ad-hoc structure

Model = model.db.model(modelName);
options.model = Model;

map = [{
Model: Model,
options: options,
docs: docs,
ids: []
}];
}

return map;
}

function getIdsForAndAddIdsInMapPopulate(modelsMap) {
var rawIds = [] // for the correct position
,
i, j, doc, docs, id, len, len2, ret, isDocument, populated, options, path;

len2 = modelsMap.length;
for (j = 0; j < len2; j++) {
docs = modelsMap[j].docs;
len = docs.length;
options = modelsMap[j].options;
path = options.path;

for (i = 0; i < len; i++) {
ret = undefined;
doc = docs[i];
id = String(utils.getValue("_id", doc));
isDocument = !! doc.$__;

if (!ret || Array.isArray(ret) && 0 === ret.length) {
ret = utils.getValue(path, doc);
}

if (ret) {
ret = convertTo_id(ret);

options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
}

rawIds.push(ret);
modelsMap[j].ids.push(ret);

if (isDocument) {
// cache original populated _ids and model used
doc.populated(path, options._docs[id], options);
}
}
}

return rawIds;
}

/*!
Expand Down
Loading