|
@@ -5,20 +5,32 @@
|
|
|
*
|
|
*
|
|
|
* NOTE: An object expression can take any of the following forms:
|
|
* NOTE: An object expression can take any of the following forms:
|
|
|
*
|
|
*
|
|
|
- * f0: {f1: ..., f2: ..., f3: ...}
|
|
|
|
|
- * f0: {$operator:[operand1, operand2, ...]}
|
|
|
|
|
|
|
+ * f0: {f1: ..., f2: ..., f3: ...}
|
|
|
|
|
+ * f0: {$operator:[operand1, operand2, ...]}
|
|
|
*
|
|
*
|
|
|
* @class Expression
|
|
* @class Expression
|
|
|
* @namespace mungedb-aggregate.pipeline.expressions
|
|
* @namespace mungedb-aggregate.pipeline.expressions
|
|
|
* @module mungedb-aggregate
|
|
* @module mungedb-aggregate
|
|
|
* @constructor
|
|
* @constructor
|
|
|
**/
|
|
**/
|
|
|
-var Expression = module.exports = function Expression(){
|
|
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+var Expression = module.exports = function Expression() {
|
|
|
if (arguments.length !== 0) throw new Error("zero args expected");
|
|
if (arguments.length !== 0) throw new Error("zero args expected");
|
|
|
-}, klass = Expression, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
|
|
|
|
|
|
|
+}, klass = Expression,
|
|
|
|
|
+ base = Object,
|
|
|
|
|
+ proto = klass.prototype = Object.create(base.prototype, {
|
|
|
|
|
+ constructor: {
|
|
|
|
|
+ value: klass
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+function fn(){
|
|
|
|
|
+ return;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-// DEPENDENCIES
|
|
|
|
|
-var Document = require("../Document");
|
|
|
|
|
|
|
|
|
|
// NESTED CLASSES
|
|
// NESTED CLASSES
|
|
|
/**
|
|
/**
|
|
@@ -26,7 +38,7 @@ var Document = require("../Document");
|
|
|
* @static
|
|
* @static
|
|
|
* @property ObjectCtx
|
|
* @property ObjectCtx
|
|
|
**/
|
|
**/
|
|
|
-var ObjectCtx = Expression.ObjectCtx = (function(){
|
|
|
|
|
|
|
+var ObjectCtx = Expression.ObjectCtx = (function() {
|
|
|
// CONSTRUCTOR
|
|
// CONSTRUCTOR
|
|
|
/**
|
|
/**
|
|
|
* Utility class for parseObject() below. isDocumentOk indicates that it is OK to use a Document in the current context.
|
|
* Utility class for parseObject() below. isDocumentOk indicates that it is OK to use a Document in the current context.
|
|
@@ -38,237 +50,134 @@ var ObjectCtx = Expression.ObjectCtx = (function(){
|
|
|
* @module mungedb-aggregate
|
|
* @module mungedb-aggregate
|
|
|
* @constructor
|
|
* @constructor
|
|
|
* @param opts
|
|
* @param opts
|
|
|
- * @param [opts.isDocumentOk] {Boolean}
|
|
|
|
|
- * @param [opts.isTopLevel] {Boolean}
|
|
|
|
|
- * @param [opts.isInclusionOk] {Boolean}
|
|
|
|
|
|
|
+ * @param [opts.isDocumentOk] {Boolean}
|
|
|
|
|
+ * @param [opts.isTopLevel] {Boolean}
|
|
|
|
|
+ * @param [opts.isInclusionOk] {Boolean}
|
|
|
**/
|
|
**/
|
|
|
- var klass = function ObjectCtx(opts /*= {isDocumentOk:..., isTopLevel:..., isInclusionOk:...}*/){
|
|
|
|
|
- if(!(opts instanceof Object && opts.constructor == Object)) throw new Error("opts is required and must be an Object containing named args");
|
|
|
|
|
|
|
+ var klass = function ObjectCtx(opts /*= {isDocumentOk:..., isTopLevel:..., isInclusionOk:...}*/ ) {
|
|
|
|
|
+ if (!(opts instanceof Object && opts.constructor == Object)) throw new Error("opts is required and must be an Object containing named args");
|
|
|
for (var k in opts) { // assign all given opts to self so long as they were part of klass.prototype as undefined properties
|
|
for (var k in opts) { // assign all given opts to self so long as they were part of klass.prototype as undefined properties
|
|
|
if (opts.hasOwnProperty(k) && proto.hasOwnProperty(k) && proto[k] === undefined) this[k] = opts[k];
|
|
if (opts.hasOwnProperty(k) && proto.hasOwnProperty(k) && proto[k] === undefined) this[k] = opts[k];
|
|
|
}
|
|
}
|
|
|
- }, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
|
|
|
|
|
-
|
|
|
|
|
- // PROTOTYPE MEMBERS
|
|
|
|
|
- proto.isDocumentOk =
|
|
|
|
|
- proto.isTopLevel =
|
|
|
|
|
- proto.isInclusionOk = undefined;
|
|
|
|
|
-
|
|
|
|
|
- return klass;
|
|
|
|
|
-})();
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Reference to the `mungedb-aggregate.pipeline.expressions.Expression.OpDesc` class
|
|
|
|
|
- * @static
|
|
|
|
|
- * @property OpDesc
|
|
|
|
|
- **/
|
|
|
|
|
-var OpDesc = Expression.OpDesc = (function(){
|
|
|
|
|
- // CONSTRUCTOR
|
|
|
|
|
- /**
|
|
|
|
|
- * Decribes how and when to create an Op instance
|
|
|
|
|
- *
|
|
|
|
|
- * @class OpDesc
|
|
|
|
|
- * @namespace mungedb-aggregate.pipeline.expressions.Expression
|
|
|
|
|
- * @module mungedb-aggregate
|
|
|
|
|
- * @constructor
|
|
|
|
|
- * @param name
|
|
|
|
|
- * @param factory
|
|
|
|
|
- * @param flags
|
|
|
|
|
- * @param argCount
|
|
|
|
|
- **/
|
|
|
|
|
- var klass = function OpDesc(name, factory, flags, argCount){
|
|
|
|
|
- var firstArg = arguments[0];
|
|
|
|
|
- if (firstArg instanceof Object && firstArg.constructor == Object) { //TODO: using this?
|
|
|
|
|
- var opts = firstArg;
|
|
|
|
|
- for (var k in opts) { // assign all given opts to self so long as they were part of klass.prototype as undefined properties
|
|
|
|
|
- if (opts.hasOwnProperty(k) && proto.hasOwnProperty(k) && proto[k] === undefined) this[k] = opts[k];
|
|
|
|
|
|
|
+ }, base = Object,
|
|
|
|
|
+ proto = klass.prototype = Object.create(base.prototype, {
|
|
|
|
|
+ constructor: {
|
|
|
|
|
+ value: klass
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- this.name = name;
|
|
|
|
|
- this.factory = factory;
|
|
|
|
|
- this.flags = flags || 0;
|
|
|
|
|
- this.argCount = argCount || 0;
|
|
|
|
|
- }
|
|
|
|
|
- }, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
|
|
|
|
|
-
|
|
|
|
|
- // STATIC MEMBERS
|
|
|
|
|
- klass.FIXED_COUNT = 1;
|
|
|
|
|
- klass.OBJECT_ARG = 2;
|
|
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
// PROTOTYPE MEMBERS
|
|
// PROTOTYPE MEMBERS
|
|
|
- proto.name =
|
|
|
|
|
- proto.factory =
|
|
|
|
|
- proto.flags =
|
|
|
|
|
- proto.argCount = undefined;
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * internal `OpDesc#name` comparer
|
|
|
|
|
- * @method cmp
|
|
|
|
|
- * @param that the other `OpDesc` instance
|
|
|
|
|
- **/
|
|
|
|
|
- proto.cmp = function cmp(that) {
|
|
|
|
|
- return this.name < that.name ? -1 : this.name > that.name ? 1 : 0;
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ proto.isDocumentOk =
|
|
|
|
|
+ proto.isTopLevel =
|
|
|
|
|
+ proto.isInclusionOk = undefined;
|
|
|
|
|
|
|
|
return klass;
|
|
return klass;
|
|
|
})();
|
|
})();
|
|
|
-// END OF NESTED CLASSES
|
|
|
|
|
-/**
|
|
|
|
|
- * @class Expression
|
|
|
|
|
- * @namespace mungedb-aggregate.pipeline.expressions
|
|
|
|
|
- * @module mungedb-aggregate
|
|
|
|
|
- **/
|
|
|
|
|
-
|
|
|
|
|
-var kinds = {
|
|
|
|
|
- UNKNOWN: "UNKNOWN",
|
|
|
|
|
- OPERATOR: "OPERATOR",
|
|
|
|
|
- NOT_OPERATOR: "NOT_OPERATOR"
|
|
|
|
|
-};
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-// STATIC MEMBERS
|
|
|
|
|
-/**
|
|
|
|
|
- * Enumeration of comparison operators. These are shared between a few expression implementations, so they are factored out here.
|
|
|
|
|
- *
|
|
|
|
|
- * @static
|
|
|
|
|
- * @property CmpOp
|
|
|
|
|
- **/
|
|
|
|
|
-klass.CmpOp = {
|
|
|
|
|
- EQ: "$eq", // return true for a == b, false otherwise
|
|
|
|
|
- NE: "$ne", // return true for a != b, false otherwise
|
|
|
|
|
- GT: "$gt", // return true for a > b, false otherwise
|
|
|
|
|
- GTE: "$gte", // return true for a >= b, false otherwise
|
|
|
|
|
- LT: "$lt", // return true for a < b, false otherwise
|
|
|
|
|
- LTE: "$lte", // return true for a <= b, false otherwise
|
|
|
|
|
- CMP: "$cmp" // return -1, 0, 1 for a < b, a == b, a > b
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// DEPENDENCIES (later in this file as compared to others to ensure that the required statics are setup first)
|
|
|
|
|
-var FieldPathExpression = require("./FieldPathExpression"),
|
|
|
|
|
- ObjectExpression = require("./ObjectExpression"),
|
|
|
|
|
- ConstantExpression = require("./ConstantExpression"),
|
|
|
|
|
- CompareExpression = require("./CompareExpression");
|
|
|
|
|
-
|
|
|
|
|
-// DEFERRED DEPENDENCIES
|
|
|
|
|
-/**
|
|
|
|
|
- * Expressions, as exposed to users
|
|
|
|
|
- *
|
|
|
|
|
- * @static
|
|
|
|
|
- * @property opMap
|
|
|
|
|
- **/
|
|
|
|
|
-setTimeout(function(){ // Even though `opMap` is deferred, force it to load early rather than later to prevent even *more* potential silliness
|
|
|
|
|
- Object.defineProperty(klass, "opMap", {value:klass.opMap});
|
|
|
|
|
-}, 0);
|
|
|
|
|
-Object.defineProperty(klass, "opMap", { //NOTE: deferred requires using a getter to allow circular requires (to maintain the ported API)
|
|
|
|
|
- configurable: true,
|
|
|
|
|
- get: function getOpMapOnce() {
|
|
|
|
|
- return Object.defineProperty(klass, "opMap", {
|
|
|
|
|
- value: [ //NOTE: rather than OpTable because it gets converted to a dict via OpDesc#name in the Array#reduce() below
|
|
|
|
|
- new OpDesc("$add", require("./AddExpression"), 0),
|
|
|
|
|
- new OpDesc("$and", require("./AndExpression"), 0),
|
|
|
|
|
- new OpDesc("$cmp", CompareExpression.bind(null, Expression.CmpOp.CMP), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$concat", require("./ConcatExpression"), 0),
|
|
|
|
|
- new OpDesc("$cond", require("./CondExpression"), OpDesc.FIXED_COUNT, 3),
|
|
|
|
|
- // $const handled specially in parseExpression
|
|
|
|
|
- new OpDesc("$dayOfMonth", require("./DayOfMonthExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$dayOfWeek", require("./DayOfWeekExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$dayOfYear", require("./DayOfYearExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$divide", require("./DivideExpression"), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$eq", CompareExpression.bind(null, Expression.CmpOp.EQ), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$gt", CompareExpression.bind(null, Expression.CmpOp.GT), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$gte", CompareExpression.bind(null, Expression.CmpOp.GTE), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$hour", require("./HourExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$ifNull", require("./IfNullExpression"), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$lt", CompareExpression.bind(null, Expression.CmpOp.LT), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$lte", CompareExpression.bind(null, Expression.CmpOp.LTE), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$minute", require("./MinuteExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$mod", require("./ModExpression"), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$month", require("./MonthExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$multiply", require("./MultiplyExpression"), 0),
|
|
|
|
|
- new OpDesc("$ne", CompareExpression.bind(null, Expression.CmpOp.NE), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$not", require("./NotExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$or", require("./OrExpression"), 0),
|
|
|
|
|
- new OpDesc("$second", require("./SecondExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$strcasecmp", require("./StrcasecmpExpression"), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$substr", require("./SubstrExpression"), OpDesc.FIXED_COUNT, 3),
|
|
|
|
|
- new OpDesc("$subtract", require("./SubtractExpression"), OpDesc.FIXED_COUNT, 2),
|
|
|
|
|
- new OpDesc("$toLower", require("./ToLowerExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$toUpper", require("./ToUpperExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$week", require("./WeekExpression"), OpDesc.FIXED_COUNT, 1),
|
|
|
|
|
- new OpDesc("$year", require("./YearExpression"), OpDesc.FIXED_COUNT, 1)
|
|
|
|
|
- ].reduce(function(r,o){r[o.name]=o; return r;}, {})
|
|
|
|
|
- }).opMap;
|
|
|
|
|
|
|
+proto.removeFieldPrefix = function removeFieldPrefix(prefixedField) {
|
|
|
|
|
+ if (prefixedField.indexOf("\0") !== -1) {
|
|
|
|
|
+ // field path must not contain embedded null characters - 16419
|
|
|
}
|
|
}
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
|
|
+ if (prefixedField[0] !== '$') {
|
|
|
|
|
+ // "field path references must be prefixed with a '$'"
|
|
|
|
|
+ }
|
|
|
|
|
+ return prefixedField.slice(1);
|
|
|
|
|
+};
|
|
|
|
|
+var KIND_UNKNOWN = 0,
|
|
|
|
|
+ KIND_NOTOPERATOR = 1,
|
|
|
|
|
+ KIND_OPERATOR = 2;
|
|
|
/**
|
|
/**
|
|
|
* Parse an Object. The object could represent a functional expression or a Document expression.
|
|
* Parse an Object. The object could represent a functional expression or a Document expression.
|
|
|
*
|
|
*
|
|
|
* An object expression can take any of the following forms:
|
|
* An object expression can take any of the following forms:
|
|
|
*
|
|
*
|
|
|
- * f0: {f1: ..., f2: ..., f3: ...}
|
|
|
|
|
- * f0: {$operator:[operand1, operand2, ...]}
|
|
|
|
|
|
|
+ * f0: {f1: ..., f2: ..., f3: ...}
|
|
|
|
|
+ * f0: {$operator:[operand1, operand2, ...]}
|
|
|
*
|
|
*
|
|
|
* @static
|
|
* @static
|
|
|
* @method parseObject
|
|
* @method parseObject
|
|
|
- * @param obj the element representing the object
|
|
|
|
|
- * @param ctx a MiniCtx representing the options above
|
|
|
|
|
|
|
+ * @param obj the element representing the object
|
|
|
|
|
+ * @param ctx a MiniCtx representing the options above
|
|
|
* @returns the parsed Expression
|
|
* @returns the parsed Expression
|
|
|
**/
|
|
**/
|
|
|
-klass.parseObject = function parseObject(obj, ctx){
|
|
|
|
|
- if(!(ctx instanceof ObjectCtx)) throw new Error("ctx must be ObjectCtx");
|
|
|
|
|
- var kind = kinds.UNKNOWN,
|
|
|
|
|
- expr, // the result
|
|
|
|
|
- exprObj; // the alt result
|
|
|
|
|
- if (obj === undefined) return new ObjectExpression();
|
|
|
|
|
|
|
+klass.parseObject = function parseObject(obj, ctx, vps) {
|
|
|
|
|
+ if (!(ctx instanceof ObjectCtx)) throw new Error("ctx must be ObjectCtx");
|
|
|
|
|
+ var kind = KIND_UNKNOWN,
|
|
|
|
|
+ pExpression, // the result
|
|
|
|
|
+ pExpressionObject; // the alt result
|
|
|
|
|
+ if (obj === undefined || obj == {}) return new ObjectExpression();
|
|
|
var fieldNames = Object.keys(obj);
|
|
var fieldNames = Object.keys(obj);
|
|
|
- for (var fc = 0, n = fieldNames.length; fc < n; ++fc) {
|
|
|
|
|
- var fn = fieldNames[fc];
|
|
|
|
|
- if (fn[0] === "$") {
|
|
|
|
|
- if (fc !== 0) throw new Error("the operator must be the only field in a pipeline object (at '" + fn + "'.; code 16410");
|
|
|
|
|
- if(ctx.isTopLevel) throw new Error("$expressions are not allowed at the top-level of $project; code 16404");
|
|
|
|
|
- kind = kinds.OPERATOR; //we've determined this "object" is an operator expression
|
|
|
|
|
- expr = Expression.parseExpression(fn, obj[fn]);
|
|
|
|
|
|
|
+ if (fieldNames.length === 0) { //NOTE: Added this for mongo 2.5 port of document sources. Should reconsider when porting the expressions themselves
|
|
|
|
|
+ return new ObjectExpression();
|
|
|
|
|
+ }
|
|
|
|
|
+ for (var fieldCount = 0, n = fieldNames.length; fieldCount < n; ++fieldCount) {
|
|
|
|
|
+ var pFieldName = fieldNames[fieldCount];
|
|
|
|
|
+
|
|
|
|
|
+ if (pFieldName[0] === "$") {
|
|
|
|
|
+ if (fieldCount !== 0)
|
|
|
|
|
+ throw new Error("the operator must be the only field in a pipeline object (at '" + pFieldName + "'.; code 16410");
|
|
|
|
|
+
|
|
|
|
|
+ if (ctx.isTopLevel)
|
|
|
|
|
+ throw new Error("$expressions are not allowed at the top-level of $project; code 16404");
|
|
|
|
|
+ kind = KIND_OPERATOR; //we've determined this "object" is an operator expression
|
|
|
|
|
+ pExpression = Expression.parseExpression(pFieldName, obj[pFieldName], vps);
|
|
|
} else {
|
|
} else {
|
|
|
- if (kind === kinds.OPERATOR) throw new Error("this object is already an operator expression, and can't be used as a document expression (at '" + fn + "'.; code 15990");
|
|
|
|
|
- if (!ctx.isTopLevel && fn.indexOf(".") != -1) throw new Error("dotted field names are only allowed at the top level; code 16405");
|
|
|
|
|
- if (expr === undefined) { // if it's our first time, create the document expression
|
|
|
|
|
- if (!ctx.isDocumentOk) throw new Error("document not allowed in this context"); // CW TODO error: document not allowed in this context
|
|
|
|
|
- expr = exprObj = new ObjectExpression();
|
|
|
|
|
- kind = kinds.NOT_OPERATOR; //this "object" is not an operator expression
|
|
|
|
|
|
|
+ if (kind === KIND_OPERATOR)
|
|
|
|
|
+ throw new Error("this object is already an operator expression, and can't be used as a document expression (at '" + pFieldName + "'.; code 15990");
|
|
|
|
|
+
|
|
|
|
|
+ if (!ctx.isTopLevel && pFieldName.indexOf(".") != -1)
|
|
|
|
|
+ throw new Error("dotted field names are only allowed at the top level; code 16405");
|
|
|
|
|
+ if (pExpression === undefined) { // if it's our first time, create the document expression
|
|
|
|
|
+ if (!ctx.isDocumentOk)
|
|
|
|
|
+ throw new Error("document not allowed in this context"); // CW TODO error: document not allowed in this context
|
|
|
|
|
+ pExpression = pExpressionObject = new ObjectExpression(); //check for top level?
|
|
|
|
|
+ kind = KIND_NOTOPERATOR; //this "object" is not an operator expression
|
|
|
}
|
|
}
|
|
|
- var fv = obj[fn];
|
|
|
|
|
- switch (typeof(fv)) {
|
|
|
|
|
- case "object":
|
|
|
|
|
- // it's a nested document
|
|
|
|
|
- var subCtx = new ObjectCtx({
|
|
|
|
|
- isDocumentOk: ctx.isDocumentOk,
|
|
|
|
|
- isInclusionOk: ctx.isInclusionOk
|
|
|
|
|
- });
|
|
|
|
|
- exprObj.addField(fn, Expression.parseObject(fv, subCtx));
|
|
|
|
|
- break;
|
|
|
|
|
- case "string":
|
|
|
|
|
- // it's a renamed field // CW TODO could also be a constant
|
|
|
|
|
- var pathExpr = new FieldPathExpression(Expression.removeFieldPrefix(fv));
|
|
|
|
|
- exprObj.addField(fn, pathExpr);
|
|
|
|
|
- break;
|
|
|
|
|
- case "boolean":
|
|
|
|
|
- case "number":
|
|
|
|
|
- // it's an inclusion specification
|
|
|
|
|
- if (fv) {
|
|
|
|
|
- if (!ctx.isInclusionOk) throw new Error("field inclusion is not allowed inside of $expressions; code 16420");
|
|
|
|
|
- exprObj.includePath(fn);
|
|
|
|
|
- } else {
|
|
|
|
|
- if (!(ctx.isTopLevel && fn == Document.ID_PROPERTY_NAME)) throw new Error("The top-level " + Document.ID_PROPERTY_NAME + " field is the only field currently supported for exclusion; code 16406");
|
|
|
|
|
- exprObj.excludeId = true;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
- default:
|
|
|
|
|
- throw new Error("disallowed field type " + (fv ? fv.constructor.name + ":" : "") + typeof(fv) + " in object expression (at '" + fn + "')");
|
|
|
|
|
|
|
+ var fieldValue = obj[pFieldName];
|
|
|
|
|
+ switch (typeof(fieldValue)) {
|
|
|
|
|
+ case "object":
|
|
|
|
|
+ // it's a nested document
|
|
|
|
|
+ var subCtx = new ObjectCtx({
|
|
|
|
|
+ isDocumentOk: ctx.isDocumentOk,
|
|
|
|
|
+ isInclusionOk: ctx.isInclusionOk
|
|
|
|
|
+ });
|
|
|
|
|
+ pExpressionObject.addField(pFieldName, Expression.parseObject(fieldValue, subCtx, vps));
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "string":
|
|
|
|
|
+ // it's a renamed field // CW TODO could also be a constant
|
|
|
|
|
+ var pathExpr = new FieldPathExpression.parse(fieldValue);
|
|
|
|
|
+ pExpressionObject.addField(pFieldName, pathExpr);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "boolean":
|
|
|
|
|
+ case "number":
|
|
|
|
|
+ // it's an inclusion specification
|
|
|
|
|
+ if (fieldValue) {
|
|
|
|
|
+ if (!ctx.isInclusionOk)
|
|
|
|
|
+ throw new Error("field inclusion is not allowed inside of $expressions; code 16420");
|
|
|
|
|
+ pExpressionObject.includePath(pFieldName);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (!(ctx.isTopLevel && fn == Document.ID_PROPERTY_NAME))
|
|
|
|
|
+ throw new Error("The top-level " + Document.ID_PROPERTY_NAME + " field is the only field currently supported for exclusion; code 16406");
|
|
|
|
|
+ pExpressionObject.excludeId = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ throw new Error("disallowed field type " + (fieldValue ? fieldValue.constructor.name + ":" : "") + typeof(fieldValue) + " in object expression (at '" + pFieldName + "')");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return expr;
|
|
|
|
|
|
|
+ return pExpression;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+klass.expressionParserMap = {};
|
|
|
|
|
+
|
|
|
|
|
+klass.registerExpression = function registerExpression(key, parserFunc) {
|
|
|
|
|
+ if (key in klass.expressionParserMap) {
|
|
|
|
|
+ throw new Error("Duplicate expression registrarion for " + key);
|
|
|
|
|
+ }
|
|
|
|
|
+ klass.expressionParserMap[key] = parserFunc;
|
|
|
|
|
+ return 0; // Should
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -276,40 +185,15 @@ klass.parseObject = function parseObject(obj, ctx){
|
|
|
*
|
|
*
|
|
|
* @static
|
|
* @static
|
|
|
* @method parseExpression
|
|
* @method parseExpression
|
|
|
- * @param opName the name of the (prefix) operator
|
|
|
|
|
- * @param obj the BSONElement to parse
|
|
|
|
|
|
|
+ * @param opName the name of the (prefix) operator
|
|
|
|
|
+ * @param obj the BSONElement to parse
|
|
|
* @returns the parsed Expression
|
|
* @returns the parsed Expression
|
|
|
**/
|
|
**/
|
|
|
-klass.parseExpression = function parseExpression(opName, obj) {
|
|
|
|
|
- // look for the specified operator
|
|
|
|
|
- if (opName === "$const") return new ConstantExpression(obj); //TODO: createFromBsonElement was here, not needed since this isn't BSON?
|
|
|
|
|
- var op = klass.opMap[opName];
|
|
|
|
|
- if (!(op instanceof OpDesc)) throw new Error("invalid operator " + opName + "; code 15999");
|
|
|
|
|
-
|
|
|
|
|
- // make the expression node
|
|
|
|
|
- var IExpression = op.factory, //TODO: should this get renamed from `factory` to `ctor` or something?
|
|
|
|
|
- expr = new IExpression();
|
|
|
|
|
-
|
|
|
|
|
- // add the operands to the expression node
|
|
|
|
|
- if (op.flags & OpDesc.FIXED_COUNT && op.argCount > 1 && !(obj instanceof Array)) throw new Error("the " + op.name + " operator requires an array of " + op.argCount + " operands; code 16019");
|
|
|
|
|
- var operand; // used below
|
|
|
|
|
- if (obj.constructor === Object) { // the operator must be unary and accept an object argument
|
|
|
|
|
- if (!(op.flags & OpDesc.OBJECT_ARG)) throw new Error("the " + op.name + " operator does not accept an object as an operand");
|
|
|
|
|
- operand = Expression.parseObject(obj, new ObjectCtx({isDocumentOk: 1}));
|
|
|
|
|
- expr.addOperand(operand);
|
|
|
|
|
- } else if (obj instanceof Array) { // multiple operands - an n-ary operator
|
|
|
|
|
- if (op.flags & OpDesc.FIXED_COUNT && op.argCount !== obj.length) throw new Error("the " + op.name + " operator requires " + op.argCount + " operand(s); code 16020");
|
|
|
|
|
- for (var i = 0, n = obj.length; i < n; ++i) {
|
|
|
|
|
- operand = Expression.parseOperand(obj[i]);
|
|
|
|
|
- expr.addOperand(operand);
|
|
|
|
|
- }
|
|
|
|
|
- } else { //assume it's an atomic operand
|
|
|
|
|
- if (op.flags & OpDesc.FIXED_COUNT && op.argCount != 1) throw new Error("the " + op.name + " operator requires an array of " + op.argCount + " operands; code 16022");
|
|
|
|
|
- operand = Expression.parseOperand(obj);
|
|
|
|
|
- expr.addOperand(operand);
|
|
|
|
|
|
|
+klass.parseExpression = function parseExpression(exprKey, exprValue, vps) {
|
|
|
|
|
+ if (!(exprKey in Expression.expressionParserMap)) {
|
|
|
|
|
+ throw new Error("Invalid operator : " + exprKey);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return expr;
|
|
|
|
|
|
|
+ return Expression.expressionParserMap[exprKey](exprValue, vps);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -319,14 +203,16 @@ klass.parseExpression = function parseExpression(opName, obj) {
|
|
|
* @param pBsonElement the expected operand's BSONElement
|
|
* @param pBsonElement the expected operand's BSONElement
|
|
|
* @returns the parsed operand, as an Expression
|
|
* @returns the parsed operand, as an Expression
|
|
|
**/
|
|
**/
|
|
|
-klass.parseOperand = function parseOperand(obj){
|
|
|
|
|
- var t = typeof(obj);
|
|
|
|
|
- if (t === "string" && obj[0] == "$") { //if we got here, this is a field path expression
|
|
|
|
|
- var path = Expression.removeFieldPrefix(obj);
|
|
|
|
|
- return new FieldPathExpression(path);
|
|
|
|
|
- }
|
|
|
|
|
- else if (t === "object" && obj && obj.constructor === Object) return Expression.parseObject(obj, new ObjectCtx({isDocumentOk: true}));
|
|
|
|
|
- else return new ConstantExpression(obj);
|
|
|
|
|
|
|
+klass.parseOperand = function parseOperand(exprElement, vps) {
|
|
|
|
|
+ var t = typeof(exprElement);
|
|
|
|
|
+ if (t === "string" && exprElement[0] == "$") { //if we got here, this is a field path expression
|
|
|
|
|
+ return new FieldPathExpression.parse(exprElement, vps);
|
|
|
|
|
+ } else
|
|
|
|
|
+ if (t === "object" && exprElement && exprElement.constructor === Object)
|
|
|
|
|
+ return Expression.parseObject(exprElement, new ObjectCtx({
|
|
|
|
|
+ isDocumentOk: true
|
|
|
|
|
+ }), vps);
|
|
|
|
|
+ else return ConstantExpression.parse(exprElement, vps);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -343,19 +229,6 @@ klass.removeFieldPrefix = function removeFieldPrefix(prefixedField) {
|
|
|
return prefixedField.substr(1);
|
|
return prefixedField.substr(1);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * returns the signe of a number
|
|
|
|
|
- *
|
|
|
|
|
- * @static
|
|
|
|
|
- * @method signum
|
|
|
|
|
- * @returns the sign of a number; -1, 1, or 0
|
|
|
|
|
- **/
|
|
|
|
|
-klass.signum = function signum(i) {
|
|
|
|
|
- if (i < 0) return -1;
|
|
|
|
|
- if (i > 0) return 1;
|
|
|
|
|
- return 0;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
// PROTOTYPE MEMBERS
|
|
// PROTOTYPE MEMBERS
|
|
|
/**
|
|
/**
|
|
@@ -364,7 +237,7 @@ klass.signum = function signum(i) {
|
|
|
* @method evaluate
|
|
* @method evaluate
|
|
|
* @returns the computed value
|
|
* @returns the computed value
|
|
|
**/
|
|
**/
|
|
|
-proto.evaluate = function evaluate(obj) {
|
|
|
|
|
|
|
+proto.evaluateInternal = function evaluateInternal(obj) {
|
|
|
throw new Error("WAS NOT IMPLEMENTED BY INHERITOR!");
|
|
throw new Error("WAS NOT IMPLEMENTED BY INHERITOR!");
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -392,8 +265,8 @@ proto.optimize = function optimize() {
|
|
|
* If any other Expression is an ancestor, or in other cases where {a:1} inclusion objects aren't allowed, they get NULL.
|
|
* If any other Expression is an ancestor, or in other cases where {a:1} inclusion objects aren't allowed, they get NULL.
|
|
|
*
|
|
*
|
|
|
* @method addDependencies
|
|
* @method addDependencies
|
|
|
- * @param deps output parameter
|
|
|
|
|
- * @param path path to self if all ancestors are ExpressionObjects.
|
|
|
|
|
|
|
+ * @param deps output parameter
|
|
|
|
|
+ * @param path path to self if all ancestors are ExpressionObjects.
|
|
|
**/
|
|
**/
|
|
|
proto.addDependencies = function addDependencies(deps, path) {
|
|
proto.addDependencies = function addDependencies(deps, path) {
|
|
|
throw new Error("WAS NOT IMPLEMENTED BY INHERITOR!");
|
|
throw new Error("WAS NOT IMPLEMENTED BY INHERITOR!");
|
|
@@ -407,6 +280,13 @@ proto.getIsSimple = function getIsSimple() {
|
|
|
return false;
|
|
return false;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-proto.toMatcherBson = function toMatcherBson(){
|
|
|
|
|
- throw new Error("WAS NOT IMPLEMENTED BY INHERITOR!"); //verify(false && "Expression::toMatcherBson()");
|
|
|
|
|
|
|
+proto.toMatcherBson = function toMatcherBson() {
|
|
|
|
|
+ throw new Error("WAS NOT IMPLEMENTED BY INHERITOR!"); //verify(false && "Expression::toMatcherBson()");
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// DEPENDENCIES
|
|
|
|
|
+var Document = require("../Document");
|
|
|
|
|
+var ObjectExpression = require("./ObjectExpression");
|
|
|
|
|
+var FieldPathExpression = require("./FieldPathExpression");
|
|
|
|
|
+var ConstantExpression = require("./ConstantExpression");
|