initial
This commit is contained in:
+87
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var ObjectId = require('../types/objectid');
|
||||
var utils = require('../utils');
|
||||
|
||||
exports.flatten = flatten;
|
||||
exports.modifiedPaths = modifiedPaths;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function flatten(update, path, options) {
|
||||
var keys;
|
||||
if (update && utils.isMongooseObject(update) && !Buffer.isBuffer(update)) {
|
||||
keys = Object.keys(update.toObject({ transform: false, virtuals: false }));
|
||||
} else {
|
||||
keys = Object.keys(update || {});
|
||||
}
|
||||
|
||||
var numKeys = keys.length;
|
||||
var result = {};
|
||||
path = path ? path + '.' : '';
|
||||
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
var key = keys[i];
|
||||
var val = update[key];
|
||||
result[path + key] = val;
|
||||
if (shouldFlatten(val)) {
|
||||
if (options && options.skipArrays && Array.isArray(val)) {
|
||||
continue;
|
||||
}
|
||||
var flat = flatten(val, path + key, options);
|
||||
for (var k in flat) {
|
||||
result[k] = flat[k];
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
result[path + key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function modifiedPaths(update, path, result) {
|
||||
var keys = Object.keys(update || {});
|
||||
var numKeys = keys.length;
|
||||
result = result || {};
|
||||
path = path ? path + '.' : '';
|
||||
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
var key = keys[i];
|
||||
var val = update[key];
|
||||
|
||||
result[path + key] = true;
|
||||
if (utils.isMongooseObject(val) && !Buffer.isBuffer(val)) {
|
||||
val = val.toObject({ transform: false, virtuals: false });
|
||||
}
|
||||
if (shouldFlatten(val)) {
|
||||
modifiedPaths(val, path + key, result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function shouldFlatten(val) {
|
||||
return val &&
|
||||
typeof val === 'object' &&
|
||||
!(val instanceof Date) &&
|
||||
!(val instanceof ObjectId) &&
|
||||
(!Array.isArray(val) || val.length > 0) &&
|
||||
!(val instanceof Buffer);
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var PromiseProvider = require('../../promise_provider');
|
||||
var async = require('async');
|
||||
|
||||
/**
|
||||
* Execute `fn` for every document in the cursor. If `fn` returns a promise,
|
||||
* will wait for the promise to resolve before iterating on to the next one.
|
||||
* Returns a promise that resolves when done.
|
||||
*
|
||||
* @param {Function} next the thunk to call to get the next document
|
||||
* @param {Function} fn
|
||||
* @param {Object} options
|
||||
* @param {Function} [callback] executed when all docs have been processed
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
* @method eachAsync
|
||||
*/
|
||||
|
||||
module.exports = function eachAsync(next, fn, options, callback) {
|
||||
var Promise = PromiseProvider.get();
|
||||
var parallel = options.parallel || 1;
|
||||
|
||||
var handleNextResult = function(doc, callback) {
|
||||
var promise = fn(doc);
|
||||
if (promise && typeof promise.then === 'function') {
|
||||
promise.then(
|
||||
function() { callback(null); },
|
||||
function(error) { callback(error || new Error('`eachAsync()` promise rejected without error')); });
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
};
|
||||
|
||||
var iterate = function(callback) {
|
||||
var drained = false;
|
||||
var nextQueue = async.queue(function(task, cb) {
|
||||
if (drained) return cb();
|
||||
next(function(err, doc) {
|
||||
if (err) return cb(err);
|
||||
if (!doc) drained = true;
|
||||
cb(null, doc);
|
||||
});
|
||||
}, 1);
|
||||
|
||||
var getAndRun = function(cb) {
|
||||
nextQueue.push({}, function(err, doc) {
|
||||
if (err) return cb(err);
|
||||
if (!doc) return cb();
|
||||
handleNextResult(doc, function(err) {
|
||||
if (err) return cb(err);
|
||||
// Make sure to clear the stack re: gh-4697
|
||||
setTimeout(function() {
|
||||
getAndRun(cb);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
async.times(parallel, function(n, cb) {
|
||||
getAndRun(cb);
|
||||
}, callback);
|
||||
};
|
||||
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
iterate(function(error) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
return reject(error);
|
||||
}
|
||||
callback && callback(null);
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function cleanModifiedSubpaths(doc, path) {
|
||||
var _modifiedPaths = Object.keys(doc.$__.activePaths.states.modify);
|
||||
var _numModifiedPaths = _modifiedPaths.length;
|
||||
var deleted = 0;
|
||||
for (var j = 0; j < _numModifiedPaths; ++j) {
|
||||
if (_modifiedPaths[j].indexOf(path + '.') === 0) {
|
||||
delete doc.$__.activePaths.states.modify[_modifiedPaths[j]];
|
||||
++deleted;
|
||||
}
|
||||
}
|
||||
return deleted;
|
||||
};
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
'use strict';
|
||||
|
||||
var Document;
|
||||
var utils = require('../../utils');
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
exports.compile = compile;
|
||||
exports.defineKey = defineKey;
|
||||
|
||||
/*!
|
||||
* Compiles schemas.
|
||||
*/
|
||||
|
||||
function compile(tree, proto, prefix, options) {
|
||||
Document = Document || require('../../document');
|
||||
var keys = Object.keys(tree);
|
||||
var i = keys.length;
|
||||
var len = keys.length;
|
||||
var limb;
|
||||
var key;
|
||||
|
||||
if (options.retainKeyOrder) {
|
||||
for (i = 0; i < len; ++i) {
|
||||
key = keys[i];
|
||||
limb = tree[key];
|
||||
|
||||
defineKey(key,
|
||||
((utils.getFunctionName(limb.constructor) === 'Object'
|
||||
&& Object.keys(limb).length)
|
||||
&& (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type))
|
||||
? limb
|
||||
: null)
|
||||
, proto
|
||||
, prefix
|
||||
, keys
|
||||
, options);
|
||||
}
|
||||
} else {
|
||||
while (i--) {
|
||||
key = keys[i];
|
||||
limb = tree[key];
|
||||
|
||||
defineKey(key,
|
||||
((utils.getFunctionName(limb.constructor) === 'Object'
|
||||
&& Object.keys(limb).length)
|
||||
&& (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type))
|
||||
? limb
|
||||
: null)
|
||||
, proto
|
||||
, prefix
|
||||
, keys
|
||||
, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Defines the accessor named prop on the incoming prototype.
|
||||
*/
|
||||
|
||||
function defineKey(prop, subprops, prototype, prefix, keys, options) {
|
||||
Document = Document || require('../../document');
|
||||
var path = (prefix ? prefix + '.' : '') + prop;
|
||||
prefix = prefix || '';
|
||||
|
||||
if (subprops) {
|
||||
Object.defineProperty(prototype, prop, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: function() {
|
||||
var _this = this;
|
||||
if (!this.$__.getters) {
|
||||
this.$__.getters = {};
|
||||
}
|
||||
|
||||
if (!this.$__.getters[path]) {
|
||||
var nested = Object.create(Document.prototype, getOwnPropertyDescriptors(this));
|
||||
|
||||
// save scope for nested getters/setters
|
||||
if (!prefix) {
|
||||
nested.$__.scope = this;
|
||||
}
|
||||
nested.$__.nestedPath = path;
|
||||
|
||||
Object.defineProperty(nested, 'schema', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: prototype.schema
|
||||
});
|
||||
|
||||
Object.defineProperty(nested, 'toObject', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: function() {
|
||||
return utils.clone(_this.get(path), { retainKeyOrder: true });
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(nested, 'toJSON', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: function() {
|
||||
return _this.get(path);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(nested, '$__isNested', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: true
|
||||
});
|
||||
|
||||
compile(subprops, nested, path, options);
|
||||
this.$__.getters[path] = nested;
|
||||
}
|
||||
|
||||
return this.$__.getters[path];
|
||||
},
|
||||
set: function(v) {
|
||||
if (v instanceof Document) {
|
||||
v = v.toObject({ transform: false });
|
||||
}
|
||||
var doc = this.$__.scope || this;
|
||||
return doc.$set(path, v);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Object.defineProperty(prototype, prop, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: function() {
|
||||
return this.get.call(this.$__.scope || this, path);
|
||||
},
|
||||
set: function(v) {
|
||||
return this.$set.call(this.$__.scope || this, path, v);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// gets descriptors for all properties of `object`
|
||||
// makes all properties non-enumerable to match previous behavior to #2211
|
||||
function getOwnPropertyDescriptors(object) {
|
||||
var result = {};
|
||||
|
||||
Object.getOwnPropertyNames(object).forEach(function(key) {
|
||||
result[key] = Object.getOwnPropertyDescriptor(object, key);
|
||||
// Assume these are schema paths, ignore them re: #5470
|
||||
if (result[key].get) {
|
||||
delete result[key];
|
||||
return;
|
||||
}
|
||||
result[key].enumerable = ['isNew', '$__', 'errors', '_doc'].indexOf(key) === -1;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
'use strict';
|
||||
|
||||
var PromiseProvider = require('../../promise_provider');
|
||||
var VersionError = require('../../error').VersionError;
|
||||
|
||||
module.exports = applyHooks;
|
||||
|
||||
/*!
|
||||
* Register hooks for this model
|
||||
*
|
||||
* @param {Model} model
|
||||
* @param {Schema} schema
|
||||
*/
|
||||
|
||||
function applyHooks(model, schema) {
|
||||
var q = schema && schema.callQueue;
|
||||
var toWrapEl;
|
||||
var len;
|
||||
var i;
|
||||
var j;
|
||||
var pointCut;
|
||||
var keys;
|
||||
var newName;
|
||||
|
||||
model.$appliedHooks = true;
|
||||
for (i = 0; i < schema.childSchemas.length; ++i) {
|
||||
var childModel = schema.childSchemas[i].model;
|
||||
if (childModel.$appliedHooks) {
|
||||
continue;
|
||||
}
|
||||
applyHooks(childModel, schema.childSchemas[i].schema);
|
||||
if (childModel.discriminators != null) {
|
||||
keys = Object.keys(childModel.discriminators);
|
||||
for (j = 0; j < keys.length; ++j) {
|
||||
applyHooks(childModel.discriminators[keys[j]],
|
||||
childModel.discriminators[keys[j]].schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!q.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interested in 'pre' hooks, and group by point-cut
|
||||
var toWrap = { post: [] };
|
||||
var pair;
|
||||
|
||||
for (i = 0; i < q.length; ++i) {
|
||||
pair = q[i];
|
||||
if (pair[0] !== 'pre' && pair[0] !== 'post' && pair[0] !== 'on') {
|
||||
continue;
|
||||
}
|
||||
var args = [].slice.call(pair[1]);
|
||||
pointCut = pair[0] === 'on' ? 'post' : args[0];
|
||||
if (!(pointCut in toWrap)) {
|
||||
toWrap[pointCut] = {post: [], pre: []};
|
||||
}
|
||||
if (pair[0] === 'post') {
|
||||
toWrap[pointCut].post.push(args);
|
||||
} else if (pair[0] === 'on') {
|
||||
toWrap[pointCut].push(args);
|
||||
} else {
|
||||
toWrap[pointCut].pre.push(args);
|
||||
}
|
||||
}
|
||||
|
||||
// 'post' hooks are simpler
|
||||
len = toWrap.post.length;
|
||||
toWrap.post.forEach(function(args) {
|
||||
model.on.apply(model, args);
|
||||
});
|
||||
delete toWrap.post;
|
||||
|
||||
// 'init' should be synchronous on subdocuments
|
||||
if (toWrap.init && (model.$isSingleNested || model.$isArraySubdocument)) {
|
||||
if (toWrap.init.pre) {
|
||||
toWrap.init.pre.forEach(function(args) {
|
||||
model.prototype.$pre.apply(model.prototype, args);
|
||||
});
|
||||
}
|
||||
if (toWrap.init.post) {
|
||||
toWrap.init.post.forEach(function(args) {
|
||||
model.prototype.$post.apply(model.prototype, args);
|
||||
});
|
||||
}
|
||||
delete toWrap.init;
|
||||
}
|
||||
if (toWrap.set) {
|
||||
// Set hooks also need to be sync re: gh-3479
|
||||
newName = '$__original_set';
|
||||
model.prototype[newName] = model.prototype.set;
|
||||
if (toWrap.set.pre) {
|
||||
toWrap.set.pre.forEach(function(args) {
|
||||
model.prototype.$pre.apply(model.prototype, args);
|
||||
});
|
||||
}
|
||||
if (toWrap.set.post) {
|
||||
toWrap.set.post.forEach(function(args) {
|
||||
model.prototype.$post.apply(model.prototype, args);
|
||||
});
|
||||
}
|
||||
delete toWrap.set;
|
||||
}
|
||||
|
||||
toWrap.validate = toWrap.validate || { pre: [], post: [] };
|
||||
|
||||
keys = Object.keys(toWrap);
|
||||
len = keys.length;
|
||||
for (i = 0; i < len; ++i) {
|
||||
pointCut = keys[i];
|
||||
// this is so we can wrap everything into a promise;
|
||||
newName = ('$__original_' + pointCut);
|
||||
if (!model.prototype[pointCut]) {
|
||||
continue;
|
||||
}
|
||||
if (model.prototype[pointCut].$isWrapped) {
|
||||
continue;
|
||||
}
|
||||
if (!model.prototype[pointCut].$originalFunction) {
|
||||
model.prototype[newName] = model.prototype[pointCut];
|
||||
}
|
||||
model.prototype[pointCut] = (function(_newName) {
|
||||
return function wrappedPointCut() {
|
||||
var Promise = PromiseProvider.get();
|
||||
|
||||
var _this = this;
|
||||
var args = [].slice.call(arguments);
|
||||
var lastArg = args.pop();
|
||||
var fn;
|
||||
var originalError = new Error();
|
||||
var $results;
|
||||
if (lastArg && typeof lastArg !== 'function') {
|
||||
args.push(lastArg);
|
||||
} else {
|
||||
fn = lastArg;
|
||||
}
|
||||
|
||||
var promise = new Promise.ES6(function(resolve, reject) {
|
||||
args.push(function(error) {
|
||||
if (error) {
|
||||
// gh-2633: since VersionError is very generic, take the
|
||||
// stack trace of the original save() function call rather
|
||||
// than the async trace
|
||||
if (error instanceof VersionError) {
|
||||
error.stack = originalError.stack;
|
||||
}
|
||||
if (!fn) {
|
||||
_this.$__handleReject(error);
|
||||
}
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// There may be multiple results and promise libs other than
|
||||
// mpromise don't support passing multiple values to `resolve()`
|
||||
$results = Array.prototype.slice.call(arguments, 1);
|
||||
resolve.apply(promise, $results);
|
||||
});
|
||||
|
||||
_this[_newName].apply(_this, args);
|
||||
});
|
||||
if (fn) {
|
||||
if (this.constructor.$wrapCallback) {
|
||||
fn = this.constructor.$wrapCallback(fn);
|
||||
}
|
||||
promise.then(
|
||||
function() {
|
||||
process.nextTick(function() {
|
||||
fn.apply(null, [null].concat($results));
|
||||
});
|
||||
},
|
||||
function(error) {
|
||||
process.nextTick(function() {
|
||||
fn(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
})(newName);
|
||||
model.prototype[pointCut].$originalFunction = newName;
|
||||
model.prototype[pointCut].$isWrapped = true;
|
||||
|
||||
toWrapEl = toWrap[pointCut];
|
||||
var _len = toWrapEl.pre.length;
|
||||
for (j = 0; j < _len; ++j) {
|
||||
args = toWrapEl.pre[j];
|
||||
args[0] = newName;
|
||||
model.prototype.$pre.apply(model.prototype, args);
|
||||
}
|
||||
|
||||
_len = toWrapEl.post.length;
|
||||
for (j = 0; j < _len; ++j) {
|
||||
args = toWrapEl.post[j];
|
||||
args[0] = newName;
|
||||
model.prototype.$post.apply(model.prototype, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Register methods for this model
|
||||
*
|
||||
* @param {Model} model
|
||||
* @param {Schema} schema
|
||||
*/
|
||||
|
||||
module.exports = function applyMethods(model, schema) {
|
||||
function apply(method, schema) {
|
||||
Object.defineProperty(model.prototype, method, {
|
||||
get: function() {
|
||||
var h = {};
|
||||
for (var k in schema.methods[method]) {
|
||||
h[k] = schema.methods[method][k].bind(this);
|
||||
}
|
||||
return h;
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
for (var method in schema.methods) {
|
||||
if (schema.tree.hasOwnProperty(method)) {
|
||||
throw new Error('You have a method and a property in your schema both ' +
|
||||
'named "' + method + '"');
|
||||
}
|
||||
if (typeof schema.methods[method] === 'function') {
|
||||
model.prototype[method] = schema.methods[method];
|
||||
} else {
|
||||
apply(method, schema);
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively call `applyMethods()` on child schemas
|
||||
model.$appliedMethods = true;
|
||||
for (var i = 0; i < schema.childSchemas.length; ++i) {
|
||||
if (schema.childSchemas[i].model.$appliedMethods) {
|
||||
continue;
|
||||
}
|
||||
applyMethods(schema.childSchemas[i].model, schema.childSchemas[i].schema);
|
||||
}
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Register statics for this model
|
||||
* @param {Model} model
|
||||
* @param {Schema} schema
|
||||
*/
|
||||
module.exports = function applyStatics(model, schema) {
|
||||
for (var i in schema.statics) {
|
||||
model[i] = schema.statics[i];
|
||||
}
|
||||
};
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
'use strict';
|
||||
|
||||
var defineKey = require('../document/compile').defineKey;
|
||||
var utils = require('../../utils');
|
||||
|
||||
var CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
|
||||
toJSON: true,
|
||||
toObject: true,
|
||||
_id: true,
|
||||
id: true
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function discriminator(model, name, schema) {
|
||||
if (!(schema && schema.instanceOfSchema)) {
|
||||
throw new Error('You must pass a valid discriminator Schema');
|
||||
}
|
||||
|
||||
if (model.base && model.base.options.applyPluginsToDiscriminators) {
|
||||
model.base._applyPlugins(schema);
|
||||
}
|
||||
|
||||
if (model.schema.discriminatorMapping &&
|
||||
!model.schema.discriminatorMapping.isRoot) {
|
||||
throw new Error('Discriminator "' + name +
|
||||
'" can only be a discriminator of the root model');
|
||||
}
|
||||
|
||||
var key = model.schema.options.discriminatorKey;
|
||||
|
||||
var baseSchemaAddition = {};
|
||||
baseSchemaAddition[key] = {
|
||||
default: void 0,
|
||||
select: true,
|
||||
set: function(newName) {
|
||||
if (newName === name) {
|
||||
return name;
|
||||
}
|
||||
throw new Error('Can\'t set discriminator key "' + key + '", "' +
|
||||
name + '" !== "' + newName + '"');
|
||||
},
|
||||
$skipDiscriminatorCheck: true
|
||||
};
|
||||
baseSchemaAddition[key][model.schema.options.typeKey] = String;
|
||||
model.schema.add(baseSchemaAddition);
|
||||
defineKey(key, null, model.prototype, null, [key], model.schema.options);
|
||||
|
||||
if (schema.path(key) && schema.path(key).options.$skipDiscriminatorCheck !== true) {
|
||||
throw new Error('Discriminator "' + name +
|
||||
'" cannot have field with name "' + key + '"');
|
||||
}
|
||||
|
||||
function merge(schema, baseSchema) {
|
||||
if (baseSchema.paths._id &&
|
||||
baseSchema.paths._id.options &&
|
||||
!baseSchema.paths._id.options.auto) {
|
||||
var originalSchema = schema;
|
||||
utils.merge(schema, originalSchema, { retainKeyOrder: true });
|
||||
delete schema.paths._id;
|
||||
delete schema.tree._id;
|
||||
}
|
||||
utils.merge(schema, baseSchema, {
|
||||
retainKeyOrder: true,
|
||||
omit: { discriminators: true }
|
||||
});
|
||||
|
||||
var obj = {};
|
||||
obj[key] = {
|
||||
default: name,
|
||||
select: true,
|
||||
set: function(newName) {
|
||||
if (newName === name) {
|
||||
return name;
|
||||
}
|
||||
throw new Error('Can\'t set discriminator key "' + key + '"');
|
||||
},
|
||||
$skipDiscriminatorCheck: true
|
||||
};
|
||||
obj[key][schema.options.typeKey] = String;
|
||||
schema.add(obj);
|
||||
schema.discriminatorMapping = {key: key, value: name, isRoot: false};
|
||||
|
||||
if (baseSchema.options.collection) {
|
||||
schema.options.collection = baseSchema.options.collection;
|
||||
}
|
||||
|
||||
var toJSON = schema.options.toJSON;
|
||||
var toObject = schema.options.toObject;
|
||||
var _id = schema.options._id;
|
||||
var id = schema.options.id;
|
||||
|
||||
var keys = Object.keys(schema.options);
|
||||
schema.options.discriminatorKey = baseSchema.options.discriminatorKey;
|
||||
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
var _key = keys[i];
|
||||
if (!CUSTOMIZABLE_DISCRIMINATOR_OPTIONS[_key]) {
|
||||
if (!utils.deepEqual(schema.options[_key], baseSchema.options[_key])) {
|
||||
throw new Error('Can\'t customize discriminator option ' + _key +
|
||||
' (can only modify ' +
|
||||
Object.keys(CUSTOMIZABLE_DISCRIMINATOR_OPTIONS).join(', ') +
|
||||
')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
schema.options = utils.clone(baseSchema.options);
|
||||
if (toJSON) schema.options.toJSON = toJSON;
|
||||
if (toObject) schema.options.toObject = toObject;
|
||||
if (typeof _id !== 'undefined') {
|
||||
schema.options._id = _id;
|
||||
}
|
||||
schema.options.id = id;
|
||||
schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
|
||||
|
||||
schema.plugins = Array.prototype.slice(baseSchema.plugins);
|
||||
schema.callQueue = baseSchema.callQueue.
|
||||
concat(schema.callQueue.slice(schema._defaultMiddleware.length));
|
||||
schema._requiredpaths = undefined; // reset just in case Schema#requiredPaths() was called on either schema
|
||||
}
|
||||
|
||||
// merges base schema into new discriminator schema and sets new type field.
|
||||
merge(schema, model.schema);
|
||||
|
||||
if (!model.discriminators) {
|
||||
model.discriminators = {};
|
||||
}
|
||||
|
||||
if (!model.schema.discriminatorMapping) {
|
||||
model.schema.discriminatorMapping = {key: key, value: null, isRoot: true};
|
||||
model.schema.discriminators = {};
|
||||
}
|
||||
|
||||
model.schema.discriminators[name] = schema;
|
||||
|
||||
if (model.discriminators[name]) {
|
||||
throw new Error('Discriminator with name "' + name + '" already exists');
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
var Mixed = require('../../schema/mixed');
|
||||
var mpath = require('mpath');
|
||||
|
||||
/*!
|
||||
* @param {Schema} schema
|
||||
* @param {Object} doc POJO
|
||||
* @param {string} path
|
||||
*/
|
||||
|
||||
module.exports = function getSchemaTypes(schema, doc, path) {
|
||||
var pathschema = schema.path(path);
|
||||
|
||||
if (pathschema) {
|
||||
return pathschema;
|
||||
}
|
||||
|
||||
function search(parts, schema) {
|
||||
var p = parts.length + 1;
|
||||
var foundschema;
|
||||
var trypath;
|
||||
|
||||
while (p--) {
|
||||
trypath = parts.slice(0, p).join('.');
|
||||
foundschema = schema.path(trypath);
|
||||
if (foundschema) {
|
||||
if (foundschema.caster) {
|
||||
// array of Mixed?
|
||||
if (foundschema.caster instanceof Mixed) {
|
||||
return foundschema.caster;
|
||||
}
|
||||
|
||||
var schemas = null;
|
||||
if (doc != null && foundschema.schema != null && foundschema.schema.discriminators != null) {
|
||||
var discriminators = foundschema.schema.discriminators;
|
||||
var keys = mpath.get(trypath + '.' + foundschema.schema.options.discriminatorKey,
|
||||
doc);
|
||||
schemas = Object.keys(discriminators).
|
||||
reduce(function(cur, discriminator) {
|
||||
if (keys.indexOf(discriminator) !== -1) {
|
||||
cur.push(discriminators[discriminator]);
|
||||
}
|
||||
return cur;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Now that we found the array, we need to check if there
|
||||
// are remaining document paths to look up for casting.
|
||||
// Also we need to handle array.$.path since schema.path
|
||||
// doesn't work for that.
|
||||
// If there is no foundschema.schema we are dealing with
|
||||
// a path like array.$
|
||||
if (p !== parts.length && foundschema.schema) {
|
||||
var ret;
|
||||
if (parts[p] === '$') {
|
||||
if (p + 1 === parts.length) {
|
||||
// comments.$
|
||||
return foundschema;
|
||||
}
|
||||
// comments.$.comments.$.title
|
||||
ret = search(parts.slice(p + 1), schema);
|
||||
if (ret) {
|
||||
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
|
||||
!foundschema.schema.$isSingleNested;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (schemas != null && schemas.length > 0) {
|
||||
ret = [];
|
||||
for (var i = 0; i < schemas.length; ++i) {
|
||||
var _ret = search(parts.slice(p), schemas[i]);
|
||||
if (_ret != null) {
|
||||
_ret.$isUnderneathDocArray = _ret.$isUnderneathDocArray ||
|
||||
!foundschema.schema.$isSingleNested;
|
||||
if (_ret.$isUnderneathDocArray) {
|
||||
ret.$isUnderneathDocArray = true;
|
||||
}
|
||||
ret.push(_ret);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
ret = search(parts.slice(p), foundschema.schema);
|
||||
|
||||
if (ret) {
|
||||
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
|
||||
!foundschema.schema.$isSingleNested;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundschema;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// look for arrays
|
||||
var parts = path.split('.');
|
||||
for (var i = 0; i < parts.length; ++i) {
|
||||
if (parts[i] === '$') {
|
||||
// Re: gh-5628, because `schema.path()` doesn't take $ into account.
|
||||
parts[i] = '0';
|
||||
}
|
||||
}
|
||||
return search(parts, schema);
|
||||
};
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isDefiningProjection(val) {
|
||||
if (val == null) {
|
||||
// `undefined` or `null` become exclusive projections
|
||||
return true;
|
||||
}
|
||||
if (typeof val === 'object') {
|
||||
// Only cases where a value does **not** define whether the whole projection
|
||||
// is inclusive or exclusive are `$meta` and `$slice`.
|
||||
return !('$meta' in val) && !('$slice' in val);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
var isDefiningProjection = require('./isDefiningProjection');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isInclusive(projection) {
|
||||
if (projection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var props = Object.keys(projection);
|
||||
var numProps = props.length;
|
||||
if (numProps === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < numProps; ++i) {
|
||||
var prop = props[i];
|
||||
// If field is truthy (1, true, etc.) and not an object, then this
|
||||
// projection must be inclusive. If object, assume its $meta, $slice, etc.
|
||||
if (isDefiningProjection(projection[prop]) && !!projection[prop]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isPathSelectedInclusive(fields, path) {
|
||||
var chunks = path.split('.');
|
||||
var cur = '';
|
||||
var j;
|
||||
var keys;
|
||||
var numKeys;
|
||||
for (var i = 0; i < chunks.length; ++i) {
|
||||
cur += cur.length ? '.' : '' + chunks[i];
|
||||
if (fields[cur]) {
|
||||
keys = Object.keys(fields);
|
||||
numKeys = keys.length;
|
||||
for (j = 0; j < numKeys; ++j) {
|
||||
if (keys[i].indexOf(cur + '.') === 0 && keys[i].indexOf(path) !== 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
'use strict';
|
||||
|
||||
var StrictModeError = require('../../error/strict');
|
||||
var ValidationError = require('../../error/validation');
|
||||
var utils = require('../../utils');
|
||||
|
||||
/*!
|
||||
* Casts an update op based on the given schema
|
||||
*
|
||||
* @param {Schema} schema
|
||||
* @param {Object} obj
|
||||
* @param {Object} options
|
||||
* @param {Boolean} [options.overwrite] defaults to false
|
||||
* @param {Boolean|String} [options.strict] defaults to true
|
||||
* @param {Query} context passed to setters
|
||||
* @return {Boolean} true iff the update is non-empty
|
||||
*/
|
||||
|
||||
module.exports = function castUpdate(schema, obj, options, context) {
|
||||
if (!obj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var ops = Object.keys(obj);
|
||||
var i = ops.length;
|
||||
var ret = {};
|
||||
var hasKeys;
|
||||
var val;
|
||||
var hasDollarKey = false;
|
||||
var overwrite = options.overwrite;
|
||||
|
||||
while (i--) {
|
||||
var op = ops[i];
|
||||
// if overwrite is set, don't do any of the special $set stuff
|
||||
if (op[0] !== '$' && !overwrite) {
|
||||
// fix up $set sugar
|
||||
if (!ret.$set) {
|
||||
if (obj.$set) {
|
||||
ret.$set = obj.$set;
|
||||
} else {
|
||||
ret.$set = {};
|
||||
}
|
||||
}
|
||||
ret.$set[op] = obj[op];
|
||||
ops.splice(i, 1);
|
||||
if (!~ops.indexOf('$set')) ops.push('$set');
|
||||
} else if (op === '$set') {
|
||||
if (!ret.$set) {
|
||||
ret[op] = obj[op];
|
||||
}
|
||||
} else {
|
||||
ret[op] = obj[op];
|
||||
}
|
||||
}
|
||||
|
||||
// cast each value
|
||||
i = ops.length;
|
||||
|
||||
// if we get passed {} for the update, we still need to respect that when it
|
||||
// is an overwrite scenario
|
||||
if (overwrite) {
|
||||
hasKeys = true;
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
op = ops[i];
|
||||
val = ret[op];
|
||||
hasDollarKey = hasDollarKey || op.charAt(0) === '$';
|
||||
if (val &&
|
||||
typeof val === 'object' &&
|
||||
(!overwrite || hasDollarKey)) {
|
||||
hasKeys |= walkUpdatePath(schema, val, op, options.strict, context);
|
||||
} else if (overwrite && ret && typeof ret === 'object') {
|
||||
// if we are just using overwrite, cast the query and then we will
|
||||
// *always* return the value, even if it is an empty object. We need to
|
||||
// set hasKeys above because we need to account for the case where the
|
||||
// user passes {} and wants to clobber the whole document
|
||||
// Also, _walkUpdatePath expects an operation, so give it $set since that
|
||||
// is basically what we're doing
|
||||
walkUpdatePath(schema, ret, '$set', options.strict, context);
|
||||
} else {
|
||||
var msg = 'Invalid atomic update value for ' + op + '. '
|
||||
+ 'Expected an object, received ' + typeof val;
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return hasKeys && ret;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Walk each path of obj and cast its values
|
||||
* according to its schema.
|
||||
*
|
||||
* @param {Schema} schema
|
||||
* @param {Object} obj - part of a query
|
||||
* @param {String} op - the atomic operator ($pull, $set, etc)
|
||||
* @param {Boolean|String} strict
|
||||
* @param {Query} context
|
||||
* @param {String} pref - path prefix (internal only)
|
||||
* @return {Bool} true if this path has keys to update
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function walkUpdatePath(schema, obj, op, strict, context, pref) {
|
||||
var prefix = pref ? pref + '.' : '';
|
||||
var keys = Object.keys(obj);
|
||||
var i = keys.length;
|
||||
var hasKeys = false;
|
||||
var schematype;
|
||||
var key;
|
||||
var val;
|
||||
|
||||
var hasError = false;
|
||||
var aggregatedError = new ValidationError();
|
||||
|
||||
var useNestedStrict = schema.options.useNestedStrict;
|
||||
|
||||
while (i--) {
|
||||
key = keys[i];
|
||||
val = obj[key];
|
||||
|
||||
if (val && val.constructor.name === 'Object') {
|
||||
// watch for embedded doc schemas
|
||||
schematype = schema._getSchema(prefix + key);
|
||||
if (schematype && schematype.caster && op in castOps) {
|
||||
// embedded doc schema
|
||||
hasKeys = true;
|
||||
|
||||
if ('$each' in val) {
|
||||
try {
|
||||
obj[key] = {
|
||||
$each: castUpdateVal(schematype, val.$each, op, context)
|
||||
};
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
_handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
|
||||
if (val.$slice != null) {
|
||||
obj[key].$slice = val.$slice | 0;
|
||||
}
|
||||
|
||||
if (val.$sort) {
|
||||
obj[key].$sort = val.$sort;
|
||||
}
|
||||
|
||||
if (!!val.$position || val.$position === 0) {
|
||||
obj[key].$position = val.$position;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
obj[key] = castUpdateVal(schematype, val, op, context);
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
_handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
}
|
||||
} else if ((op === '$currentDate') || (op in castOps && schematype)) {
|
||||
// $currentDate can take an object
|
||||
try {
|
||||
obj[key] = castUpdateVal(schematype, val, op, context);
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
_handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
|
||||
hasKeys = true;
|
||||
} else {
|
||||
var pathToCheck = (prefix + key);
|
||||
var v = schema._getPathType(pathToCheck);
|
||||
var _strict = strict;
|
||||
if (useNestedStrict &&
|
||||
v &&
|
||||
v.schema &&
|
||||
'strict' in v.schema.options) {
|
||||
_strict = v.schema.options.strict;
|
||||
}
|
||||
|
||||
if (v.pathType === 'undefined') {
|
||||
if (_strict === 'throw') {
|
||||
throw new StrictModeError(pathToCheck);
|
||||
} else if (_strict) {
|
||||
delete obj[key];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// gh-2314
|
||||
// we should be able to set a schema-less field
|
||||
// to an empty object literal
|
||||
hasKeys |= walkUpdatePath(schema, val, op, strict, context, prefix + key) ||
|
||||
(utils.isObject(val) && Object.keys(val).length === 0);
|
||||
}
|
||||
} else {
|
||||
var checkPath = (key === '$each' || key === '$or' || key === '$and' || key === '$in') ?
|
||||
pref : prefix + key;
|
||||
schematype = schema._getSchema(checkPath);
|
||||
|
||||
var pathDetails = schema._getPathType(checkPath);
|
||||
var isStrict = strict;
|
||||
if (useNestedStrict &&
|
||||
pathDetails &&
|
||||
pathDetails.schema &&
|
||||
'strict' in pathDetails.schema.options) {
|
||||
isStrict = pathDetails.schema.options.strict;
|
||||
}
|
||||
|
||||
var skip = isStrict &&
|
||||
!schematype &&
|
||||
!/real|nested/.test(pathDetails.pathType);
|
||||
|
||||
if (skip) {
|
||||
if (isStrict === 'throw') {
|
||||
throw new StrictModeError(prefix + key);
|
||||
} else {
|
||||
delete obj[key];
|
||||
}
|
||||
} else {
|
||||
// gh-1845 temporary fix: ignore $rename. See gh-3027 for tracking
|
||||
// improving this.
|
||||
if (op === '$rename') {
|
||||
hasKeys = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
hasKeys = true;
|
||||
try {
|
||||
obj[key] = castUpdateVal(schematype, val, op, key, context);
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
_handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
throw aggregatedError;
|
||||
}
|
||||
|
||||
return hasKeys;
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _handleCastError(error, query, key, aggregatedError) {
|
||||
if (typeof query !== 'object' || !query.options.multipleCastError) {
|
||||
throw error;
|
||||
}
|
||||
aggregatedError.addError(key, error);
|
||||
}
|
||||
|
||||
/*!
|
||||
* These operators should be cast to numbers instead
|
||||
* of their path schema type.
|
||||
*/
|
||||
|
||||
var numberOps = {
|
||||
$pop: 1,
|
||||
$unset: 1,
|
||||
$inc: 1
|
||||
};
|
||||
|
||||
/*!
|
||||
* These operators require casting docs
|
||||
* to real Documents for Update operations.
|
||||
*/
|
||||
|
||||
var castOps = {
|
||||
$push: 1,
|
||||
$pushAll: 1,
|
||||
$addToSet: 1,
|
||||
$set: 1,
|
||||
$setOnInsert: 1
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
var overwriteOps = {
|
||||
$set: 1,
|
||||
$setOnInsert: 1
|
||||
};
|
||||
|
||||
/*!
|
||||
* Casts `val` according to `schema` and atomic `op`.
|
||||
*
|
||||
* @param {SchemaType} schema
|
||||
* @param {Object} val
|
||||
* @param {String} op - the atomic operator ($pull, $set, etc)
|
||||
* @param {String} $conditional
|
||||
* @param {Query} context
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function castUpdateVal(schema, val, op, $conditional, context) {
|
||||
if (!schema) {
|
||||
// non-existing schema path
|
||||
return op in numberOps
|
||||
? Number(val)
|
||||
: val;
|
||||
}
|
||||
|
||||
var cond = schema.caster && op in castOps &&
|
||||
(utils.isObject(val) || Array.isArray(val));
|
||||
if (cond) {
|
||||
// Cast values for ops that add data to MongoDB.
|
||||
// Ensures embedded documents get ObjectIds etc.
|
||||
var tmp = schema.cast(val);
|
||||
if (Array.isArray(val)) {
|
||||
val = tmp;
|
||||
} else if (Array.isArray(tmp)) {
|
||||
val = tmp[0];
|
||||
} else {
|
||||
val = tmp;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
if (op in numberOps) {
|
||||
if (op === '$inc') {
|
||||
return schema.castForQueryWrapper({ val: val, context: context });
|
||||
}
|
||||
return Number(val);
|
||||
}
|
||||
if (op === '$currentDate') {
|
||||
if (typeof val === 'object') {
|
||||
return {$type: val.$type};
|
||||
}
|
||||
return Boolean(val);
|
||||
}
|
||||
|
||||
if (/^\$/.test($conditional)) {
|
||||
return schema.castForQueryWrapper({
|
||||
$conditional: $conditional,
|
||||
val: val,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
|
||||
if (overwriteOps[op]) {
|
||||
return schema.castForQueryWrapper({
|
||||
val: val,
|
||||
context: context,
|
||||
$skipQueryCastForUpdate: val != null && schema.$isMongooseArray
|
||||
});
|
||||
}
|
||||
|
||||
return schema.castForQueryWrapper({ val: val, context: context });
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function(obj) {
|
||||
var keys = Object.keys(obj);
|
||||
var len = keys.length;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (keys[i].charAt(0) === '$') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function selectPopulatedFields(query) {
|
||||
var opts = query._mongooseOptions;
|
||||
|
||||
if (opts.populate != null) {
|
||||
var paths = Object.keys(opts.populate);
|
||||
var i;
|
||||
var userProvidedFields = query._userProvidedFields || {};
|
||||
if (query.selectedInclusively()) {
|
||||
for (i = 0; i < paths.length; ++i) {
|
||||
if (!isPathInFields(userProvidedFields, paths[i])) {
|
||||
query.select(paths[i]);
|
||||
}
|
||||
}
|
||||
} else if (query.selectedExclusively()) {
|
||||
for (i = 0; i < paths.length; ++i) {
|
||||
if (userProvidedFields[paths[i]] == null) {
|
||||
delete query._fields[paths[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function isPathInFields(userProvidedFields, path) {
|
||||
var pieces = path.split('.');
|
||||
var len = pieces.length;
|
||||
var cur = pieces[0];
|
||||
for (var i = 1; i < len; ++i) {
|
||||
if (userProvidedFields[cur] != null) {
|
||||
return true;
|
||||
}
|
||||
cur += '.' + pieces[i];
|
||||
}
|
||||
return userProvidedFields[cur] != null;
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
'use strict';
|
||||
|
||||
var modifiedPaths = require('./common').modifiedPaths;
|
||||
|
||||
/**
|
||||
* Applies defaults to update and findOneAndUpdate operations.
|
||||
*
|
||||
* @param {Object} filter
|
||||
* @param {Schema} schema
|
||||
* @param {Object} castedDoc
|
||||
* @param {Object} options
|
||||
* @method setDefaultsOnInsert
|
||||
* @api private
|
||||
*/
|
||||
|
||||
module.exports = function(filter, schema, castedDoc, options) {
|
||||
var keys = Object.keys(castedDoc || {});
|
||||
var updatedKeys = {};
|
||||
var updatedValues = {};
|
||||
var numKeys = keys.length;
|
||||
var hasDollarUpdate = false;
|
||||
var modified = {};
|
||||
|
||||
if (options && options.upsert) {
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
if (keys[i].charAt(0) === '$') {
|
||||
modifiedPaths(castedDoc[keys[i]], '', modified);
|
||||
hasDollarUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDollarUpdate) {
|
||||
modifiedPaths(castedDoc, '', modified);
|
||||
}
|
||||
|
||||
var paths = Object.keys(filter);
|
||||
var numPaths = paths.length;
|
||||
for (i = 0; i < numPaths; ++i) {
|
||||
var path = paths[i];
|
||||
var condition = filter[path];
|
||||
if (condition && typeof condition === 'object') {
|
||||
var conditionKeys = Object.keys(condition);
|
||||
var numConditionKeys = conditionKeys.length;
|
||||
var hasDollarKey = false;
|
||||
for (var j = 0; j < numConditionKeys; ++j) {
|
||||
if (conditionKeys[j].charAt(0) === '$') {
|
||||
hasDollarKey = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasDollarKey) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
updatedKeys[path] = true;
|
||||
modified[path] = true;
|
||||
}
|
||||
|
||||
if (options && options.overwrite && !hasDollarUpdate) {
|
||||
// Defaults will be set later, since we're overwriting we'll cast
|
||||
// the whole update to a document
|
||||
return castedDoc;
|
||||
}
|
||||
|
||||
if (options.setDefaultsOnInsert) {
|
||||
schema.eachPath(function(path, schemaType) {
|
||||
if (path === '_id') {
|
||||
// Ignore _id for now because it causes bugs in 2.4
|
||||
return;
|
||||
}
|
||||
if (schemaType.$isSingleNested) {
|
||||
// Only handle nested schemas 1-level deep to avoid infinite
|
||||
// recursion re: https://github.com/mongodb-js/mongoose-autopopulate/issues/11
|
||||
schemaType.schema.eachPath(function(_path, _schemaType) {
|
||||
if (path === '_id') {
|
||||
// Ignore _id for now because it causes bugs in 2.4
|
||||
return;
|
||||
}
|
||||
|
||||
var def = _schemaType.getDefault(null, true);
|
||||
if (!isModified(modified, path + '.' + _path) &&
|
||||
typeof def !== 'undefined') {
|
||||
castedDoc = castedDoc || {};
|
||||
castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
|
||||
castedDoc.$setOnInsert[path + '.' + _path] = def;
|
||||
updatedValues[path + '.' + _path] = def;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var def = schemaType.getDefault(null, true);
|
||||
if (!isModified(modified, path) && typeof def !== 'undefined') {
|
||||
castedDoc = castedDoc || {};
|
||||
castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
|
||||
castedDoc.$setOnInsert[path] = def;
|
||||
updatedValues[path] = def;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return castedDoc;
|
||||
};
|
||||
|
||||
function isModified(modified, path) {
|
||||
if (modified[path]) {
|
||||
return true;
|
||||
}
|
||||
var sp = path.split('.');
|
||||
var cur = sp[0];
|
||||
for (var i = 0; i < sp.length; ++i) {
|
||||
if (modified[cur]) {
|
||||
return true;
|
||||
}
|
||||
cur += '.' + sp[i];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Mixed = require('../schema/mixed');
|
||||
var ValidationError = require('../error/validation');
|
||||
var parallel = require('async/parallel');
|
||||
var flatten = require('./common').flatten;
|
||||
var modifiedPaths = require('./common').modifiedPaths;
|
||||
|
||||
/**
|
||||
* Applies validators and defaults to update and findOneAndUpdate operations,
|
||||
* specifically passing a null doc as `this` to validators and defaults
|
||||
*
|
||||
* @param {Query} query
|
||||
* @param {Schema} schema
|
||||
* @param {Object} castedDoc
|
||||
* @param {Object} options
|
||||
* @method runValidatorsOnUpdate
|
||||
* @api private
|
||||
*/
|
||||
|
||||
module.exports = function(query, schema, castedDoc, options) {
|
||||
var _keys;
|
||||
var keys = Object.keys(castedDoc || {});
|
||||
var updatedKeys = {};
|
||||
var updatedValues = {};
|
||||
var arrayAtomicUpdates = {};
|
||||
var numKeys = keys.length;
|
||||
var hasDollarUpdate = false;
|
||||
var modified = {};
|
||||
var currentUpdate;
|
||||
var key;
|
||||
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
if (keys[i].charAt(0) === '$') {
|
||||
hasDollarUpdate = true;
|
||||
if (keys[i] === '$push' || keys[i] === '$addToSet') {
|
||||
_keys = Object.keys(castedDoc[keys[i]]);
|
||||
for (var ii = 0; ii < _keys.length; ++ii) {
|
||||
currentUpdate = castedDoc[keys[i]][_keys[ii]];
|
||||
if (currentUpdate && currentUpdate.$each) {
|
||||
arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []).
|
||||
concat(currentUpdate.$each);
|
||||
} else {
|
||||
arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []).
|
||||
concat([currentUpdate]);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
modifiedPaths(castedDoc[keys[i]], '', modified);
|
||||
var flat = flatten(castedDoc[keys[i]]);
|
||||
var paths = Object.keys(flat);
|
||||
var numPaths = paths.length;
|
||||
for (var j = 0; j < numPaths; ++j) {
|
||||
var updatedPath = paths[j].replace('.$.', '.0.');
|
||||
updatedPath = updatedPath.replace(/\.\$$/, '.0');
|
||||
key = keys[i];
|
||||
if (key === '$set' || key === '$setOnInsert' ||
|
||||
key === '$pull' || key === '$pullAll') {
|
||||
updatedValues[updatedPath] = flat[paths[j]];
|
||||
} else if (key === '$unset') {
|
||||
updatedValues[updatedPath] = undefined;
|
||||
}
|
||||
updatedKeys[updatedPath] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDollarUpdate) {
|
||||
modifiedPaths(castedDoc, '', modified);
|
||||
updatedValues = flatten(castedDoc);
|
||||
updatedKeys = Object.keys(updatedValues);
|
||||
}
|
||||
|
||||
var updates = Object.keys(updatedValues);
|
||||
var numUpdates = updates.length;
|
||||
var validatorsToExecute = [];
|
||||
var validationErrors = [];
|
||||
function iter(i, v) {
|
||||
var schemaPath = schema._getSchema(updates[i]);
|
||||
if (schemaPath) {
|
||||
// gh-4305: `_getSchema()` will report all sub-fields of a 'Mixed' path
|
||||
// as 'Mixed', so avoid double validating them.
|
||||
if (schemaPath instanceof Mixed && schemaPath.$fullPath !== updates[i]) {
|
||||
return;
|
||||
}
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(
|
||||
v,
|
||||
function(err) {
|
||||
if (err) {
|
||||
err.path = updates[i];
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
options && options.context === 'query' ? query : null,
|
||||
{updateValidator: true});
|
||||
});
|
||||
}
|
||||
}
|
||||
for (i = 0; i < numUpdates; ++i) {
|
||||
iter(i, updatedValues[updates[i]]);
|
||||
}
|
||||
|
||||
var arrayUpdates = Object.keys(arrayAtomicUpdates);
|
||||
var numArrayUpdates = arrayUpdates.length;
|
||||
for (i = 0; i < numArrayUpdates; ++i) {
|
||||
(function(i) {
|
||||
var schemaPath = schema._getSchema(arrayUpdates[i]);
|
||||
if (schemaPath && schemaPath.$isMongooseDocumentArray) {
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(
|
||||
arrayAtomicUpdates[arrayUpdates[i]],
|
||||
function(err) {
|
||||
if (err) {
|
||||
err.path = arrayUpdates[i];
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
options && options.context === 'query' ? query : null);
|
||||
});
|
||||
} else {
|
||||
schemaPath = schema._getSchema(arrayUpdates[i] + '.0');
|
||||
for (var j = 0; j < arrayAtomicUpdates[arrayUpdates[i]].length; ++j) {
|
||||
(function(j) {
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(
|
||||
arrayAtomicUpdates[arrayUpdates[i]][j],
|
||||
function(err) {
|
||||
if (err) {
|
||||
err.path = arrayUpdates[i];
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
options && options.context === 'query' ? query : null,
|
||||
{ updateValidator: true });
|
||||
});
|
||||
})(j);
|
||||
}
|
||||
}
|
||||
})(i);
|
||||
}
|
||||
|
||||
return function(callback) {
|
||||
parallel(validatorsToExecute, function() {
|
||||
if (validationErrors.length) {
|
||||
var err = new ValidationError(null);
|
||||
for (var i = 0; i < validationErrors.length; ++i) {
|
||||
err.addError(validationErrors[i].path, validationErrors[i]);
|
||||
}
|
||||
return callback(err);
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user