|
|
@@ -1,6 +1,6 @@
|
|
|
"use strict";
|
|
|
|
|
|
-// Autogenerated by cport.py on 2013-09-17 14:37
|
|
|
+// File: expression_parser.cpp
|
|
|
var MatchExpressionParser = module.exports = function (){
|
|
|
|
|
|
}, klass = MatchExpressionParser, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
|
|
|
@@ -32,6 +32,11 @@ var errors = require("../../Errors.js"),
|
|
|
AllElemMatchOp = require("./AllElemMatchOp.js"),
|
|
|
AtomicMatchExpression = require("./AtomicMatchExpression.js");
|
|
|
|
|
|
+proto.expressionParserTextCallback = require('./TextMatchExpressionParser').expressionParserTextCallbackReal;
|
|
|
+
|
|
|
+// The maximum allowed depth of a query tree. Just to guard against stack overflow.
|
|
|
+var MAXIMUM_TREE_DEPTH = 100;
|
|
|
+
|
|
|
/**
|
|
|
*
|
|
|
* Check if the input element is an expression
|
|
|
@@ -39,8 +44,7 @@ var errors = require("../../Errors.js"),
|
|
|
* @param element
|
|
|
*
|
|
|
*/
|
|
|
-proto._isExpressionDocument = function _isExpressionDocument(element){
|
|
|
- // File: expression_parser.cpp lines: 340-355
|
|
|
+proto._isExpressionDocument = function _isExpressionDocument(element, allowIncompleteDBRef){
|
|
|
if (!(element instanceof Object))
|
|
|
return false;
|
|
|
|
|
|
@@ -51,26 +55,50 @@ proto._isExpressionDocument = function _isExpressionDocument(element){
|
|
|
if (name[0] != '$')
|
|
|
return false;
|
|
|
|
|
|
- if ("$ref" == name)
|
|
|
+ if (this._isDBRefDocument(element, allowIncompleteDBRef))
|
|
|
return false;
|
|
|
|
|
|
return true;
|
|
|
};
|
|
|
|
|
|
+proto._isDBRefDocument = function _isDBRefDocument(obj, allowIncompleteDBRef) {
|
|
|
+ var hasRef, hasID, hasDB = false;
|
|
|
+
|
|
|
+ var i, fieldName, element = null,
|
|
|
+ keys = Object.keys(obj), length = keys.length;
|
|
|
+ for (i = 0; i < length; i++) {
|
|
|
+ fieldName = keys[i];
|
|
|
+ element = obj[fieldName];
|
|
|
+ if (!hasRef && fieldName === '$ref')
|
|
|
+ hasRef = true;
|
|
|
+ else if (!hasID && fieldName === '$id')
|
|
|
+ hasID = true;
|
|
|
+ else if (!hasDB && fieldName === '$db')
|
|
|
+ hasDB = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return allowIncompleteDBRef && (hasRef || hasID || hasDB) || (hasRef && hasID);
|
|
|
+};
|
|
|
+
|
|
|
/**
|
|
|
*
|
|
|
* Parse the input object into individual elements
|
|
|
* @method _parse
|
|
|
* @param obj
|
|
|
- * @param topLevel
|
|
|
+ * @param level
|
|
|
*
|
|
|
*/
|
|
|
-proto._parse = function _parse(obj, topLevel){
|
|
|
- // File: expression_parser.cpp lines: 217-319
|
|
|
+proto._parse = function _parse(obj, level){
|
|
|
+ if (level > MAXIMUM_TREE_DEPTH)
|
|
|
+ return {code:ErrorCodes.BAD_VALUE, description:"exceeded maximum query tree depth of " +
|
|
|
+ MAXIMUM_TREE_DEPTH + " at " + JSON.stringify(obj)};
|
|
|
+
|
|
|
var rest, temp, status, element, eq, real;
|
|
|
var root = new AndMatchExpression();
|
|
|
var objkeys = Object.keys(obj);
|
|
|
var currname, currval;
|
|
|
+ var topLevel = level === 0;
|
|
|
+ level++;
|
|
|
for (var i = 0; i < objkeys.length; i++) {
|
|
|
currname = objkeys[i];
|
|
|
currval = obj[currname];
|
|
|
@@ -82,7 +110,7 @@ proto._parse = function _parse(obj, topLevel){
|
|
|
if (!(currval instanceof Array))
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"$or needs an array"};
|
|
|
temp = new OrMatchExpression();
|
|
|
- status = this._parseTreeList(currval, temp);
|
|
|
+ status = this._parseTreeList(currval, temp, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
root.add(temp);
|
|
|
@@ -91,7 +119,7 @@ proto._parse = function _parse(obj, topLevel){
|
|
|
if (!(currval instanceof Array))
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"and needs an array"};
|
|
|
temp = new AndMatchExpression();
|
|
|
- status = this._parseTreeList(currval, temp);
|
|
|
+ status = this._parseTreeList(currval, temp, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
root.add(temp);
|
|
|
@@ -100,7 +128,7 @@ proto._parse = function _parse(obj, topLevel){
|
|
|
if (!(currval instanceof Array))
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"and needs an array"};
|
|
|
temp = new NorMatchExpression();
|
|
|
- status = this._parseTreeList(currval, temp);
|
|
|
+ status = this._parseTreeList(currval, temp, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
root.add(temp);
|
|
|
@@ -123,10 +151,14 @@ proto._parse = function _parse(obj, topLevel){
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
root.add(status.result);*/
|
|
|
+ } else if ('text' === rest) {
|
|
|
+ if (typeof currval !== 'object') {
|
|
|
+ return {code: ErrorCodes.BAD_VALUE, description: '$text expects an object'};
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.expressionTextCallback(currval);
|
|
|
}
|
|
|
- else if ("comment" == rest) {
|
|
|
- 1+1;
|
|
|
- }
|
|
|
+ else if ("comment" == rest) {}
|
|
|
else {
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"unknown top level operator: " + currname};
|
|
|
}
|
|
|
@@ -135,7 +167,7 @@ proto._parse = function _parse(obj, topLevel){
|
|
|
}
|
|
|
|
|
|
if (this._isExpressionDocument(currval)) {
|
|
|
- status = this._parseSub(currname, currval, root);
|
|
|
+ status = this._parseSub(currname, currval, root, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
continue;
|
|
|
@@ -172,8 +204,7 @@ proto._parse = function _parse(obj, topLevel){
|
|
|
* @param element
|
|
|
*
|
|
|
*/
|
|
|
-proto._parseAll = function _parseAll(name, element){
|
|
|
- // File: expression_parser.cpp lines: 512-583
|
|
|
+proto._parseAll = function _parseAll(name, element, level){
|
|
|
var status, i;
|
|
|
|
|
|
if (!(element instanceof Array))
|
|
|
@@ -201,7 +232,7 @@ proto._parseAll = function _parseAll(name, element){
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"$all/$elemMatch has to be consistent"};
|
|
|
}
|
|
|
|
|
|
- status = this._parseElemMatch("", hopefullyElemMatchElement.$elemMatch ); // TODO: wrong way to do this?
|
|
|
+ status = this._parseElemMatch("", hopefullyElemMatchElement.$elemMatch, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
temp.add(status.result);
|
|
|
@@ -249,11 +280,14 @@ proto._parseAll = function _parseAll(name, element){
|
|
|
*
|
|
|
*/
|
|
|
proto._parseArrayFilterEntries = function _parseArrayFilterEntries(entries, theArray){
|
|
|
- // File: expression_parser.cpp lines: 445-468
|
|
|
var status, e, r;
|
|
|
for (var i = 0; i < theArray.length; i++) {
|
|
|
e = theArray[i];
|
|
|
|
|
|
+ if (this._isExpressionDocument(e, false)) {
|
|
|
+ return {code:ErrorCodes.BAD_VALUE, description:"cannot nest $ under $in"};
|
|
|
+ }
|
|
|
+
|
|
|
if (e instanceof RegExp ) {
|
|
|
r = new RegexMatchExpression();
|
|
|
status = r.init("", e);
|
|
|
@@ -282,7 +316,6 @@ proto._parseArrayFilterEntries = function _parseArrayFilterEntries(entries, theA
|
|
|
*
|
|
|
*/
|
|
|
proto._parseComparison = function _parseComparison(name, cmp, element){
|
|
|
- // File: expression_parser.cpp lines: 34-43
|
|
|
var temp = new ComparisonMatchExpression(cmp);
|
|
|
|
|
|
var status = temp.init(name, element);
|
|
|
@@ -300,17 +333,31 @@ proto._parseComparison = function _parseComparison(name, cmp, element){
|
|
|
* @param element
|
|
|
*
|
|
|
*/
|
|
|
-proto._parseElemMatch = function _parseElemMatch(name, element){
|
|
|
- // File: expression_parser.cpp lines: 471-509
|
|
|
+proto._parseElemMatch = function _parseElemMatch(name, element, level){
|
|
|
var temp, status;
|
|
|
if (!(element instanceof Object))
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"$elemMatch needs an Object"};
|
|
|
|
|
|
- if (this._isExpressionDocument(element)) {
|
|
|
+ // $elemMatch value case applies when the children all
|
|
|
+ // work on the field 'name'.
|
|
|
+ // This is the case when:
|
|
|
+ // 1) the argument is an expression document; and
|
|
|
+ // 2) expression is not a AND/NOR/OR logical operator. Children of
|
|
|
+ // these logical operators are initialized with field names.
|
|
|
+ // 3) expression is not a WHERE operator. WHERE works on objects instead
|
|
|
+ // of specific field.
|
|
|
+ var elt = element[Object.keys(element)[0]],
|
|
|
+ isElemMatchValue = this._isExpressionDocument(element, true) &&
|
|
|
+ elt !== '$and' &&
|
|
|
+ elt !== '$nor' &&
|
|
|
+ elt !== '$or' &&
|
|
|
+ elt !== '$where';
|
|
|
+
|
|
|
+ if (isElemMatchValue) {
|
|
|
// value case
|
|
|
|
|
|
var theAnd = new AndMatchExpression();
|
|
|
- status = this._parseSub("", element, theAnd);
|
|
|
+ status = this._parseSub("", element, theAnd, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
|
|
|
@@ -327,9 +374,13 @@ proto._parseElemMatch = function _parseElemMatch(name, element){
|
|
|
return {code:ErrorCodes.OK, result:temp};
|
|
|
}
|
|
|
|
|
|
+ // DBRef value case
|
|
|
+ // A DBRef document under a $elemMatch should be treated as an object case
|
|
|
+ // because it may contain non-DBRef fields in addition to $ref, $id and $db.
|
|
|
+
|
|
|
// object case
|
|
|
|
|
|
- status = this._parse(element, false);
|
|
|
+ status = this._parse(element, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
|
|
|
@@ -350,7 +401,6 @@ proto._parseElemMatch = function _parseElemMatch(name, element){
|
|
|
*
|
|
|
*/
|
|
|
proto._parseMOD = function _parseMOD(name, element){
|
|
|
- // File: expression_parser.cpp lines: 360-387
|
|
|
var d,r;
|
|
|
if (!(element instanceof Array))
|
|
|
return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, needs to be an array"};
|
|
|
@@ -358,13 +408,13 @@ proto._parseMOD = function _parseMOD(name, element){
|
|
|
return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, not enough elements"};
|
|
|
if (element.length > 2)
|
|
|
return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, too many elements"};
|
|
|
- if ((typeof(element[0]) != 'number')) {
|
|
|
+ if (typeof element[0] !== 'number') {
|
|
|
return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, divisor not a number"};
|
|
|
} else {
|
|
|
d = element[0];
|
|
|
}
|
|
|
- if (( typeof(element[1]) != 'number') ) {
|
|
|
- r = 0;
|
|
|
+ if (typeof element[1] !== 'number') {
|
|
|
+ return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, remainder not a number"};
|
|
|
} else {
|
|
|
r = element[1];
|
|
|
}
|
|
|
@@ -385,8 +435,7 @@ proto._parseMOD = function _parseMOD(name, element){
|
|
|
* @param element
|
|
|
*
|
|
|
*/
|
|
|
-proto._parseNot = function _parseNot(name, element){
|
|
|
- // File: expression_parser_tree.cpp lines: 55-91
|
|
|
+proto._parseNot = function _parseNot(name, element, level){
|
|
|
var status;
|
|
|
if (element instanceof RegExp) {
|
|
|
status = this._parseRegexElement(name, element);
|
|
|
@@ -406,7 +455,7 @@ proto._parseNot = function _parseNot(name, element){
|
|
|
return {code:ErrorCodes.BAD_VALUE, result:"$not cannot be empty"};
|
|
|
|
|
|
var theAnd = new AndMatchExpression();
|
|
|
- status = this._parseSub(name, element, theAnd);
|
|
|
+ status = this._parseSub(name, element, theAnd, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
|
|
|
@@ -434,7 +483,6 @@ proto._parseNot = function _parseNot(name, element){
|
|
|
*
|
|
|
*/
|
|
|
proto._parseRegexDocument = function _parseRegexDocument(name, doc){
|
|
|
- // File: expression_parser.cpp lines: 402-442
|
|
|
var regex = '', regexOptions = '', e;
|
|
|
|
|
|
if(doc.$regex) {
|
|
|
@@ -450,7 +498,7 @@ proto._parseRegexDocument = function _parseRegexDocument(name, doc){
|
|
|
}
|
|
|
regex = (flagIndex? str : str.substr(1, flagIndex-1));
|
|
|
regexOptions = str.substr(flagIndex, str.length);
|
|
|
- } else if (typeof(e) == 'string') {
|
|
|
+ } else if (typeof e === 'string') {
|
|
|
regex = e;
|
|
|
} else {
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"$regex has to be a string"};
|
|
|
@@ -459,7 +507,7 @@ proto._parseRegexDocument = function _parseRegexDocument(name, doc){
|
|
|
|
|
|
if(doc.$options) {
|
|
|
e = doc.$options;
|
|
|
- if(typeof(e) == 'string') {
|
|
|
+ if(typeof(e) === 'string') {
|
|
|
regexOptions = e;
|
|
|
} else {
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"$options has to be a string"};
|
|
|
@@ -484,7 +532,6 @@ proto._parseRegexDocument = function _parseRegexDocument(name, doc){
|
|
|
*
|
|
|
*/
|
|
|
proto._parseRegexElement = function _parseRegexElement(name, element){
|
|
|
- // File: expression_parser.cpp lines: 390-399
|
|
|
if (!(element instanceof RegExp))
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"not a regex"};
|
|
|
|
|
|
@@ -516,18 +563,26 @@ proto._parseRegexElement = function _parseRegexElement(name, element){
|
|
|
* @param root
|
|
|
*
|
|
|
*/
|
|
|
-proto._parseSub = function _parseSub(name, sub, root){
|
|
|
- // File: expression_parser.cpp lines: 322-337
|
|
|
+proto._parseSub = function _parseSub(name, sub, root, level){
|
|
|
var subkeys = Object.keys(sub),
|
|
|
currname, currval;
|
|
|
|
|
|
+ if (level > MAXIMUM_TREE_DEPTH) {
|
|
|
+ return {code:ErrorCodes.BAD_VALUE, description:"exceeded maximum query tree depth of " +
|
|
|
+ MAXIMUM_TREE_DEPTH + " at " + JSON.stringify(sub)};
|
|
|
+ }
|
|
|
+
|
|
|
+ level++;
|
|
|
+
|
|
|
+ // DERIVATION: We are not implementing Geo functions yet.
|
|
|
+
|
|
|
for (var i = 0; i < subkeys.length; i++) {
|
|
|
currname = subkeys[i];
|
|
|
currval = sub[currname];
|
|
|
var deep = {};
|
|
|
deep[currname] = currval;
|
|
|
|
|
|
- var status = this._parseSubField(sub, root, name, deep);
|
|
|
+ var status = this._parseSubField(sub, root, name, deep, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
|
|
|
@@ -548,20 +603,19 @@ proto._parseSub = function _parseSub(name, sub, root){
|
|
|
* @param element
|
|
|
*
|
|
|
*/
|
|
|
-proto._parseSubField = function _parseSubField(context, andSoFar, name, element){
|
|
|
- // File: expression_parser.cpp lines: 46-214
|
|
|
+proto._parseSubField = function _parseSubField(context, andSoFar, name, element, level){
|
|
|
// TODO: these should move to getGtLtOp, or its replacement
|
|
|
var currname = Object.keys(element)[0];
|
|
|
var currval = element[currname];
|
|
|
if ("$eq" == currname)
|
|
|
return this._parseComparison(name, 'EQ', currval);
|
|
|
|
|
|
- if ("$not" == currname) {
|
|
|
- return this._parseNot(name, currval);
|
|
|
- }
|
|
|
+ if ("$not" == currname)
|
|
|
+ return this._parseNot(name, currval, level);
|
|
|
|
|
|
var status, temp, temp2;
|
|
|
switch (currname) {
|
|
|
+ // TODO: -1 is apparently a value for mongo, but we handle strings so...
|
|
|
case '$lt':
|
|
|
return this._parseComparison(name, 'LT', currval);
|
|
|
case '$lte':
|
|
|
@@ -571,6 +625,11 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
|
|
|
case '$gte':
|
|
|
return this._parseComparison(name, 'GTE', currval);
|
|
|
case '$ne':
|
|
|
+ // Just because $ne can be rewritten as the negation of an
|
|
|
+ // equality does not mean that $ne of a regex is allowed. See SERVER-1705.
|
|
|
+ if (currval instanceof RegExp) {
|
|
|
+ return {code:ErrorCodes.BAD_VALUE, description:"Can't have regex as arg to $ne."};
|
|
|
+ }
|
|
|
status = this._parseComparison(name, 'EQ', currval);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
@@ -618,11 +677,19 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
|
|
|
// matching old odd semantics
|
|
|
size = 0;
|
|
|
else if (typeof(currval) === 'number')
|
|
|
- size = currval;
|
|
|
+ // SERVER-11952. Setting 'size' to -1 means that no documents
|
|
|
+ // should match this $size expression.
|
|
|
+ if (currval < 0)
|
|
|
+ size = -1;
|
|
|
+ else
|
|
|
+ size = currval;
|
|
|
else {
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"$size needs a number"};
|
|
|
}
|
|
|
|
|
|
+ // DERIVATION/Potential bug: Mongo checks to see if doube values are exactly equal to
|
|
|
+ // their int converted version. If not, size = -1.
|
|
|
+
|
|
|
temp = new SizeMatchExpression();
|
|
|
status = temp.init(name, size);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
@@ -637,7 +704,7 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
|
|
|
status = temp.init(name);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
- if (currval)
|
|
|
+ if (currval) // DERIVATION: This may have to check better than truthy? Need to look at TrueValue
|
|
|
return {code:ErrorCodes.OK, result:temp};
|
|
|
temp2 = new NotMatchExpression();
|
|
|
status = temp2.init(temp);
|
|
|
@@ -674,10 +741,10 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
|
|
|
return this._parseRegexDocument(name, context);
|
|
|
|
|
|
case '$elemMatch':
|
|
|
- return this._parseElemMatch(name, currval);
|
|
|
+ return this._parseElemMatch(name, currval, level);
|
|
|
|
|
|
case '$all':
|
|
|
- return this._parseAll(name, currval);
|
|
|
+ return this._parseAll(name, currval, level);
|
|
|
|
|
|
case '$geoWithin':
|
|
|
case '$geoIntersects':
|
|
|
@@ -701,8 +768,7 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
|
|
|
* @param out
|
|
|
*
|
|
|
*/
|
|
|
-proto._parseTreeList = function _parseTreeList(arr, out){
|
|
|
- // File: expression_parser_tree.cpp lines: 33-52
|
|
|
+proto._parseTreeList = function _parseTreeList(arr, out, level){
|
|
|
if (arr.length === 0)
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"$and/$or/$nor must be a nonempty array"};
|
|
|
|
|
|
@@ -713,7 +779,7 @@ proto._parseTreeList = function _parseTreeList(arr, out){
|
|
|
if (!(element instanceof Object))
|
|
|
return {code:ErrorCodes.BAD_VALUE, description:"$or/$and/$nor entries need to be full objects"};
|
|
|
|
|
|
- status = this._parse(element, false);
|
|
|
+ status = this._parse(element, level);
|
|
|
if (status.code != ErrorCodes.OK)
|
|
|
return status;
|
|
|
|
|
|
@@ -730,6 +796,5 @@ proto._parseTreeList = function _parseTreeList(arr, out){
|
|
|
*
|
|
|
*/
|
|
|
proto.parse = function parse(obj){
|
|
|
- // File: expression_parser.h lines: 40-41
|
|
|
- return this._parse(obj, true);
|
|
|
+ return this._parse(obj, 0);
|
|
|
};
|