"use strict"; // Autogenerated by cport.py on 2013-09-17 14:37 var MatchExpressionParser = module.exports = function (){ }, klass = MatchExpressionParser, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); // DEPENDENCIES var errors = require("../../Errors.js"), ErrorCodes = errors.ErrorCodes, AndMatchExpression = require("./AndMatchExpression.js"), MatchExpression = require("./MatchExpression.js"), OrMatchExpression = require("./OrMatchExpression.js"), ModMatchExpression = require("./ModMatchExpression.js"), NorMatchExpression = require("./NorMatchExpression.js"), NotMatchExpression = require("./NotMatchExpression.js"), LTMatchExpression = require("./LTMatchExpression.js"), LTEMatchExpression = require("./LTEMatchExpression.js"), GTMatchExpression = require("./GTMatchExpression.js"), GTEMatchExpression = require("./GTEMatchExpression.js"), InMatchExpression = require("./InMatchExpression.js"), SizeMatchExpression = require("./SizeMatchExpression.js"), TypeMatchExpression = require("./TypeMatchExpression.js"), ExistsMatchExpression = require("./ExistsMatchExpression.js"), EqualityMatchExpression = require("./EqualityMatchExpression.js"), ArrayMatchingMatchExpression = require("./ArrayMatchingMatchExpression.js"), RegexMatchExpression = require("./RegexMatchExpression.js"), FalseMatchExpression = require("./FalseMatchExpression.js"), ComparisonMatchExpression = require("./ComparisonMatchExpression.js"), ElemMatchValueMatchExpression = require("./ElemMatchValueMatchExpression.js"), ElemMatchObjectMatchExpression = require("./ElemMatchObjectMatchExpression.js"), AllElemMatchOp = require("./AllElemMatchOp.js"), AtomicMatchExpression = require("./AtomicMatchExpression.js"); /** * * Check if the input element is an expression * @method _isExpressionDocument * @param element * */ proto._isExpressionDocument = function _isExpressionDocument(element){ // File: expression_parser.cpp lines: 340-355 if (!(element instanceof Object)) return false; if (Object.keys(element).length === 0) return false; var name = Object.keys(element)[0]; if (name[0] != '$') return false; if ("$ref" == name) return false; return true; }; /** * * Parse the input object into individual elements * @method _parse * @param obj * @param topLevel * */ proto._parse = function _parse(obj, topLevel){ // File: expression_parser.cpp lines: 217-319 var rest, temp, status, element, eq, real; var root = new AndMatchExpression(); var objkeys = Object.keys(obj); var currname, currval; for (var i = 0; i < objkeys.length; i++) { currname = objkeys[i]; currval = obj[currname]; if (currname[0] == '$' ) { rest = currname.substr(1); // TODO: optimize if block? if ("or" == rest) { if (!(currval instanceof Array)) return {code:ErrorCodes.BAD_VALUE, description:"$or needs an array"}; temp = new OrMatchExpression(); status = this._parseTreeList(currval, temp); if (status.code != ErrorCodes.OK) return status; root.add(temp); } else if ("and" == rest) { if (!(currval instanceof Array)) return {code:ErrorCodes.BAD_VALUE, description:"and needs an array"}; temp = new AndMatchExpression(); status = this._parseTreeList(currval, temp); if (status.code != ErrorCodes.OK) return status; root.add(temp); } else if ("nor" == rest) { if (!(currval instanceof Array)) return {code:ErrorCodes.BAD_VALUE, description:"and needs an array"}; temp = new NorMatchExpression(); status = this._parseTreeList(currval, temp); if (status.code != ErrorCodes.OK) return status; root.add(temp); } else if (("atomic" == rest) || ("isolated" == rest)) { if (!topLevel) return {code:ErrorCodes.BAD_VALUE, description:"$atomic/$isolated has to be at the top level"}; if (element) root.add(new AtomicMatchExpression()); } else if ("where" == rest) { /* if ( !topLevel ) return StatusWithMatchExpression( ErrorCodes::BAD_VALUE, "$where has to be at the top level" ); */ return {'code':'FAILED_TO_PARSE', 'desc':'Where unimplimented.'}; /* status = this.expressionParserWhereCallback(element); if (status.code != ErrorCodes.OK) return status; root.add(status.result);*/ } else if ("comment" == rest) { 1+1; } else { return {code:ErrorCodes.BAD_VALUE, description:"unknown top level operator: " + currname}; } continue; } if (this._isExpressionDocument(currval)) { status = this._parseSub(currname, currval, root); if (status.code != ErrorCodes.OK) return status; continue; } if (currval instanceof RegExp) { status = this._parseRegexElement(currname, currval); if (status.code != ErrorCodes.OK) return status; root.add(status.result); continue; } eq = new EqualityMatchExpression(); status = eq.init(currname, currval); if (status.code != ErrorCodes.OK) return status; root.add(eq); } if (root.numChildren() == 1) { return {code:ErrorCodes.OK, result:root.getChild(0)}; } return {code:ErrorCodes.OK, result:root}; }; /** * * Parse the $all element * @method _parseAll * @param name * @param element * */ proto._parseAll = function _parseAll(name, element){ // File: expression_parser.cpp lines: 512-583 var status, i; if (!(element instanceof Array)) return {code:ErrorCodes.BAD_VALUE, description:"$all needs an array"}; var arr = element; if ((arr[0] instanceof Object) && ("$elemMatch" == Object.keys(arr[0])[0])) { // $all : [ { $elemMatch : {} } ... ] var temp = new AllElemMatchOp(); status = temp.init(name); if (status.code != ErrorCodes.OK) return status; for (i = 0; i < arr.length; i++) { var hopefullyElemMatchElement = arr[i]; if (!(hopefullyElemMatchElement instanceof Object)) { // $all : [ { $elemMatch : ... }, 5 ] return {code:ErrorCodes.BAD_VALUE, description:"$all/$elemMatch has to be consistent"}; } if ("$elemMatch" != Object.keys(hopefullyElemMatchElement)[0]) { // $all : [ { $elemMatch : ... }, { x : 5 } ] return {code:ErrorCodes.BAD_VALUE, description:"$all/$elemMatch has to be consistent"}; } status = this._parseElemMatch("", hopefullyElemMatchElement.$elemMatch ); // TODO: wrong way to do this? if (status.code != ErrorCodes.OK) return status; temp.add(status.result); } return {code:ErrorCodes.OK, result:temp}; } var myAnd = new AndMatchExpression(); for (i = 0; i < arr.length; i++) { var e = arr[i]; if (e instanceof RegExp) { var r = new RegexMatchExpression(); status = r.init(name, e); if (status.code != ErrorCodes.OK) return status; myAnd.add(r); } else if ((e instanceof Object) && (typeof(Object.keys(e)[0] == 'string' && Object.keys(e)[0][0] == '$' ))) { return {code:ErrorCodes.BAD_VALUE, description:"no $ expressions in $all"}; } else { var x = new EqualityMatchExpression(); status = x.init(name, e); if (status.code != ErrorCodes.OK) return status; myAnd.add(x); } } if (myAnd.numChildren() === 0) { return {code:ErrorCodes.OK, result:new FalseMatchExpression()}; } return {code:ErrorCodes.OK, result:myAnd}; }; /** * * Parse the input array and add new RegexMatchExpressions to entries * @method _parseArrayFilterEntries * @param entries * @param theArray * */ 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 (e instanceof RegExp ) { r = new RegexMatchExpression(); status = r.init("", e); if (status.code != ErrorCodes.OK) return status; status = entries.addRegex(r); if (status.code != ErrorCodes.OK) return status; } else { status = entries.addEquality(e); if (status.code != ErrorCodes.OK) return status; } } return {code:ErrorCodes.OK}; }; /** * * Parse the input ComparisonMatchExpression * @method _parseComparison * @param name * @param cmp * @param element * */ 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); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp}; }; /** * * Parse an element match into the appropriate expression * @method _parseElemMatch * @param name * @param element * */ proto._parseElemMatch = function _parseElemMatch(name, element){ // File: expression_parser.cpp lines: 471-509 var temp, status; if (!(element instanceof Object)) return {code:ErrorCodes.BAD_VALUE, description:"$elemMatch needs an Object"}; if (this._isExpressionDocument(element)) { // value case var theAnd = new AndMatchExpression(); status = this._parseSub("", element, theAnd); if (status.code != ErrorCodes.OK) return status; temp = new ElemMatchValueMatchExpression(); status = temp.init(name); if (status.code != ErrorCodes.OK) return status; for (var i = 0; i < theAnd.numChildren(); i++ ) { temp.add(theAnd.getChild(i)); } theAnd.clearAndRelease(); return {code:ErrorCodes.OK, result:temp}; } // object case status = this._parse(element, false); if (status.code != ErrorCodes.OK) return status; temp = new ElemMatchObjectMatchExpression(); status = temp.init(name, status.result); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp}; }; /** * * Parse a ModMatchExpression * @method _parseMOD * @param name * @param 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"}; if (element.length < 2) 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')) { return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, divisor not a number"}; } else { d = element[0]; } if (( typeof(element[1]) != 'number') ) { r = 0; } else { r = element[1]; } var temp = new ModMatchExpression(); var status = temp.init( name, d, r); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp}; }; /** * * Parse a NotMatchExpression * @method _parseNot * @param name * @param element * */ proto._parseNot = function _parseNot(name, element){ // File: expression_parser_tree.cpp lines: 55-91 var status; if (element instanceof RegExp) { status = this._parseRegexElement(name, element); if (status.code != ErrorCodes.OK) return status; var n = new NotMatchExpression(); status = n.init(status.result); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:n}; } if (!(element instanceof Object)) return {code:ErrorCodes.BAD_VALUE, result:"$not needs a regex or a document"}; if (element == {}) return {code:ErrorCodes.BAD_VALUE, result:"$not cannot be empty"}; var theAnd = new AndMatchExpression(); status = this._parseSub(name, element, theAnd); if (status.code != ErrorCodes.OK) return status; // TODO: this seems arbitrary? // tested in jstests/not2.js for (var i = 0; i < theAnd.numChildren(); i++) { if (theAnd.getChild(i).matchType == MatchExpression.REGEX) { return {code:ErrorCodes.BAD_VALUE, result:"$not cannot have a regex"}; } } var theNot = new NotMatchExpression(); status = theNot.init(theAnd); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:theNot}; }; /** * * Parse a RegexMatchExpression * @method _parseRegexDocument * @param name * @param doc * */ proto._parseRegexDocument = function _parseRegexDocument(name, doc){ // File: expression_parser.cpp lines: 402-442 var regex = '', regexOptions = '', e; if(doc.$regex) { e = doc.$regex; if(e instanceof RegExp) { var str = e.toString(), flagIndex = 0; for (var c = str.length; c > 0; c--){ if (str[c] == '/') { flagIndex = c; break; } } regex = (flagIndex? str : str.substr(1, flagIndex-1)); regexOptions = str.substr(flagIndex, str.length); } else if (typeof(e) == 'string') { regex = e; } else { return {code:ErrorCodes.BAD_VALUE, description:"$regex has to be a string"}; } } if(doc.$options) { e = doc.$options; if(typeof(e) == 'string') { regexOptions = e; } else { return {code:ErrorCodes.BAD_VALUE, description:"$options has to be a string"}; } } var temp = new RegexMatchExpression(); var status = temp.init(name, regex, regexOptions); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp}; }; /** * * Parse an element into a RegexMatchExpression * @method _parseRegexElement * @param name * @param element * */ 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"}; var str = element.toString(), flagIndex = 0; for (var c = str.length; c > 0; c--){ if (str[c] == '/') { flagIndex = c; break; } } var regex = str.substr(1, flagIndex-1), regexOptions = str.substr(flagIndex+1, str.length), temp = new RegexMatchExpression(), status = temp.init(name, regex, regexOptions); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp}; }; /** * * Parse a sub expression * @method _parseSub * @param name * @param sub * @param root * */ proto._parseSub = function _parseSub(name, sub, root){ // File: expression_parser.cpp lines: 322-337 var subkeys = Object.keys(sub), currname, currval; 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); if (status.code != ErrorCodes.OK) return status; if (status.result) root.add(status.result); } return {code:ErrorCodes.OK, result:root}; }; /** * * Parse a sub expression field * @method _parseSubField * @param context * @param andSoFar * @param name * @param element * */ proto._parseSubField = function _parseSubField(context, andSoFar, name, element){ // File: expression_parser.cpp lines: 46-214 // 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); } var status, temp, temp2; switch (currname) { case '$lt': return this._parseComparison(name, 'LT', currval); case '$lte': return this._parseComparison(name, 'LTE', currval); case '$gt': return this._parseComparison(name, 'GT', currval); case '$gte': return this._parseComparison(name, 'GTE', currval); case '$ne': status = this._parseComparison(name, 'EQ', currval); if (status.code != ErrorCodes.OK) return status; var n = new NotMatchExpression(); status = n.init(status.result); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:n}; case '$eq': return this._parseComparison(name, 'EQ', currval); case '$in': if (!(currval instanceof Array)) return {code:ErrorCodes.BAD_VALUE, description:"$in needs an array"}; temp = new InMatchExpression(); status = temp.init(name); if (status.code != ErrorCodes.OK) return status; status = this._parseArrayFilterEntries(temp.getArrayFilterEntries(), currval); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp}; case '$nin': if (!(currval instanceof Array)) return {code:ErrorCodes.BAD_VALUE, description:"$nin needs an array"}; temp = new InMatchExpression(); status = temp.init(name); if (status.code != ErrorCodes.OK) return status; status = this._parseArrayFilterEntries(temp.getArrayFilterEntries(), currval); if (status.code != ErrorCodes.OK) return status; temp2 = new NotMatchExpression(); status = temp2.init(temp); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp2}; case '$size': var size = 0; if ( typeof(currval) === 'string') // matching old odd semantics size = 0; else if (typeof(currval) === 'number') size = currval; else { return {code:ErrorCodes.BAD_VALUE, description:"$size needs a number"}; } temp = new SizeMatchExpression(); status = temp.init(name, size); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp}; case '$exists': if (currval == {}) return {code:ErrorCodes.BAD_VALUE, description:"$exists can't be eoo"}; temp = new ExistsMatchExpression(); status = temp.init(name); if (status.code != ErrorCodes.OK) return status; if (currval) return {code:ErrorCodes.OK, result:temp}; temp2 = new NotMatchExpression(); status = temp2.init(temp); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp2}; case '$type': if (typeof(currval) != 'number') return {code:ErrorCodes.BAD_VALUE, description:"$type has to be a number"}; var type = currval; temp = new TypeMatchExpression(); status = temp.init(name, type); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp}; case '$mod': return this._parseMOD(name, currval); case '$options': // TODO: try to optimize this // we have to do this since $options can be before or after a $regex // but we validate here for(var i = 0; i < Object.keys(context).length; i++) { temp = Object.keys(context)[i]; if (temp == '$regex') return {code:ErrorCodes.OK, result:null}; } return {code:ErrorCodes.BAD_VALUE, description:"$options needs a $regex"}; case '$regex': return this._parseRegexDocument(name, context); case '$elemMatch': return this._parseElemMatch(name, currval); case '$all': return this._parseAll(name, currval); case '$geoWithin': case '$geoIntersects': case '$near': case '$nearSphere': var x = 'Temporary value until Geo fns implimented.'; return this.expressionParserGeoCallback(name, x, context); default: return {code:ErrorCodes.BAD_VALUE, description:"not handled: " + element}; } // end switch return {code:ErrorCodes.BAD_VALUE, description:"not handled: " + element}; }; /** * * Parse a list of parsable elements * @method _parseTreeList * @param arr * @param out * */ proto._parseTreeList = function _parseTreeList(arr, out){ // File: expression_parser_tree.cpp lines: 33-52 if (arr.length === 0) return {code:ErrorCodes.BAD_VALUE, description:"$and/$or/$nor must be a nonempty array"}; var status, element; for (var i = 0; i < arr.length; i++) { element = arr[i]; if (!(element instanceof Object)) return {code:ErrorCodes.BAD_VALUE, description:"$or/$and/$nor entries need to be full objects"}; status = this._parse(element, false); if (status.code != ErrorCodes.OK) return status; out.add(status.result); } return {code:ErrorCodes.OK}; }; /** * * Wrapper for _parse * @method parse * @param obj * */ proto.parse = function parse(obj){ // File: expression_parser.h lines: 40-41 return this._parse(obj, true); };