"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"); /** * * This documentation was automatically generated. Please update when you touch this function. * @method _isExpressionDocument * @param * */ proto._isExpressionDocument = function _isExpressionDocument(element){ // File: expression_parser.cpp lines: 340-355 if (!(element instanceof Object)) return false; if (element.isEmpty()) return false; var name = element.keys()[0]; if (name[0] != '$') return false; if ("$ref" == name) return false; return true; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parse * @param * */ proto._parse = function _parse(obj, topLevel){ // File: expression_parser.cpp lines: 217-319 var rest, temp, status, element, eq, real; var root = new AndMatchExpression(); for (var i = 0; i < obj.length; i++) { element = obj[i]; if (element[0] == '$' ) { rest = element.substr(1, element.length); // TODO: optimize if block? if ("or" == rest) { if (!(element instanceof Array)) return {code:ErrorCodes.BadValue, description:"$or needs an array"}; temp = new OrMatchExpression(); status = this._parseTreeList(element, temp.get()); if (status.code != ErrorCodes.OK) return status; root.add(temp.release()); } else if ("and" == rest) { if (!(element instanceof Array)) return {code:ErrorCodes.BadValue, description:"and needs an array"}; temp = new AndMatchExpression(); status = this._parseTreeList(element, temp.get()); if (status.code != ErrorCodes.OK) return status; root.add(temp.release()); } else if ("nor" == rest) { if (!(element instanceof Array)) return {code:ErrorCodes.BadValue, description:"and needs an array"}; temp = new NorMatchExpression(); status = this._parseTreeList(element, temp.get()); if (status.code != ErrorCodes.OK) return status; root.add(temp.release()); } else if (("atomic" == rest) || ("isolated" == rest)) { if (!topLevel) return {code:ErrorCodes.BadValue, 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::BadValue, "$where has to be at the top level" ); */ status = this.expressionParserWhereCallback(element); if (status.code != ErrorCodes.OK) return status; root.add(status.result); } else if ("comment" == rest) { } else { return {code:ErrorCodes.BadValue, description:"unknown top level operator: " + element}; } continue; } if (this._isExpressionDocument(element)) { status = this._parseSub(element, element, root.get()); if (status.code != ErrorCodes.OK) return status; continue; } if (element instanceof RegExp) { status = this._parseRegexElement(element, element); if (status.code != ErrorCodes.OK) return status; root.add(status.result); continue; } eq = new EqualityMatchExpression(); status = eq.init(element, element); if (status.code != ErrorCodes.OK) return status; root.add(eq.release()); } if (root.numChildren() == 1) { real = new MatchExpression(root.getChild(0)); root.clearAndRelease(); return {code:ErrorCodes.OK, result:real}; } return {code:ErrorCodes.OK, result:root.release()}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseAll * @param * */ proto._parseAll = function _parseAll(name, element){ // File: expression_parser.cpp lines: 512-583 var status, i; if (!(element instanceof Array)) return {code:ErrorCodes.BadValue, description:"$all needs an array"}; var arr = element; if ((arr[0] instanceof Object) && ("$elemMatch" == arr[0].keys()[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.BadValue, description:"$all/$elemMatch has to be consistent"}; } if ("$elemMatch" != hopefullyElemMatchElement.keys()[0]) { // $all : [ { $elemMatch : ... }, { x : 5 } ] return {code:ErrorCodes.BadValue, description:"$all/$elemMatch has to be consistent"}; } status = this._parseElemMatch("", hopefullyElemMatchElement[hopefullyElemMatchElement.keys()[0]]); // TODO: wrong way to do this? if (status.code != ErrorCodes.OK) return status; temp.add(new ArrayMatchingMatchExpression(status.result)); } return {code:ErrorCodes.OK, result:temp.release()}; } 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.release()); } else if ((e instanceof Object) && (e.keys()[0].getGtLtOp(-1) != -1)) { return {code:ErrorCodes.BadValue, 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.release()); } } if (myAnd.numChildren() === 0) { return {code:ErrorCodes.OK, result:new FalseMatchExpression()}; } return {code:ErrorCodes.OK, result:myAnd.release()}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseArrayFilterEntries * @param * */ 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.release()); if (status.code != ErrorCodes.OK) return status; } else { status = entries.addEquality(e); if (status.code != ErrorCodes.OK) return status; } } return {code:ErrorCodes.OK}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseComparison * @param * */ 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.release()}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseElemMatch * @param * */ proto._parseElemMatch = function _parseElemMatch(name, element){ // File: expression_parser.cpp lines: 471-509 var temp, status; if (!(element instanceof Object)) return {code:ErrorCodes.BadValue, 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.release()}; } // 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.release()}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseMOD * @param * */ proto._parseMOD = function _parseMOD(name, element){ // File: expression_parser.cpp lines: 360-387 if (!(element instanceof Array)) return {code:ErrorCodes.BadValue, result:"malformed mod, needs to be an array"}; if (element.length < 2) return {code:ErrorCodes.BadValue, result:"malformed mod, not enough elements"}; if (element.length > 2) return {code:ErrorCodes.BadValue, result:"malformed mod, too many elements"}; if (!(element[0] instanceof Number)) return {code:ErrorCodes.BadValue, result:"malformed mod, divisor not a number"}; if (!(element[1] instanceof Number)) return {code:ErrorCodes.BadValue, result:"malformed mod, remainder not a number"}; var temp = new ModMatchExpression(); var status = temp.init( name, element[0], element[1]); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp.release()}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseNot * @param * */ 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.release()}; } if (!(element instanceof Object)) return {code:ErrorCodes.BadValue, result:"$not needs a regex or a document"}; if (element == {}) return {code:ErrorCodes.BadValue, result:"$not cannot be empty"}; var theAnd = new AndMatchExpression(); status = this._parseSub(name, element, theAnd.get()); 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.BadValue, result:"$not cannot have a regex"}; var theNot = new NotMatchExpression(); status = theNot.init(theAnd.release()); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:theNot.release()}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseRegexDocument * @param * */ proto._parseRegexDocument = function _parseRegexDocument(name, doc){ // File: expression_parser.cpp lines: 402-442 var regex, regexOptions, e; for (var i = 0; i < doc.length; i++) { e = doc[i]; switch (e.getGtLtOp()) { case 'opREGEX': if (e instanceof String) { regex = e; } else 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 = str.substr(1, flagIndex-1); regexOptions = str.substr(flagIndex, str.length); } else { return {code:ErrorCodes.BadValue, description:"$regex has to be a string"}; } break; case 'opOPTIONS': if (!(e instanceof String)) return {code:ErrorCodes.BadValue, description:"$options has to be a string"}; regexOptions = e; break; default: break; } } var temp = new RegexMatchExpression(); var status = temp.init(name, regex, regexOptions); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp.release()}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseRegexElement * @param * */ proto._parseRegexElement = function _parseRegexElement(name, element){ // File: expression_parser.cpp lines: 390-399 if (!(element instanceof RegExp)) return {code:ErrorCodes.BadValue, 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, str.length), temp = new RegexMatchExpression(), status = temp.init(name, regex, regexOptions); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp.release()}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseSub * @param * */ proto._parseSub = function _parseSub(name, sub, root){ // File: expression_parser.cpp lines: 322-337 for (var i = 0; i < sub.length; i++) { var deep = sub[i]; 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}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseSubField * @param * */ proto._parseSubField = function _parseSubField(context, andSoFar, name, element){ // File: expression_parser.cpp lines: 46-214 // TODO: these should move to getGtLtOp, or its replacement if ("$eq" == element) return this._parseComparison(name, new EqualityMatchExpression(), element); if ("$not" == element) { return this._parseNot(name, element); } var x = element.getGtLtOp(-1), status, temp, temp2; switch (x) { case -1: return {code:ErrorCodes.BadValue, description:"unknown operator: " + element}; case 'LT': return this._parseComparison(name, new LTMatchExpression(), element); case 'LTE': return this._parseComparison(name, new LTEMatchExpression(), element); case 'GT': return this._parseComparison(name, new GTMatchExpression(), element); case 'GTE': return this._parseComparison(name, new GTEMatchExpression(), element); case 'NE': status = this._parseComparison(name, new EqualityMatchExpression(), 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.release()}; case 'Equality': return this._parseComparison(name, new EqualityMatchExpression(), element); case 'opIN': if (!(element instanceof Array)) return {code:ErrorCodes.BadValue, description:"$in needs an array"}; temp = new InMatchExpression(); status = temp.init(name); if (status.code != ErrorCodes.OK) return status; status = this._parseArrayFilterEntries(temp.getArrayFilterEntries(), element); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp.release()}; case 'NIN': if (!(element instanceof Array)) return {code:ErrorCodes.BadValue, description:"$nin needs an array"}; temp = new InMatchExpression(); status = temp.init(name); if (status.code != ErrorCodes.OK) return status; status = this._parseArrayFilterEntries(temp.getArrayFilterEntries(), element); if (status.code != ErrorCodes.OK) return status; temp2 = new NotMatchExpression(); status = temp2.init(temp.release()); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp2.release()}; case 'opSIZE': var size = 0; if (element instanceof String) // matching old odd semantics size = 0; else if (element instanceof Number) size = element; else { return {code:ErrorCodes.BadValue, 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.release()}; case 'opEXISTS': if (element == {}) return {code:ErrorCodes.BadValue, description:"$exists can't be eoo"}; temp = new ExistsMatchExpression(); status = temp.init(name); if (status.code != ErrorCodes.OK) return status; if (element) return {code:ErrorCodes.OK, result:temp.release()}; temp2 = new NotMatchExpression(); status = temp2.init(temp.release()); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp2.release()}; case 'opTYPE': if (!(element instanceof Number)) return {code:ErrorCodes.BadValue, description:"$type has to be a number"}; var type = element; temp = new TypeMatchExpression(); status = temp.init(name, type); if (status.code != ErrorCodes.OK) return status; return {code:ErrorCodes.OK, result:temp.release()}; case 'opMOD': return this._parseMOD(name, element); case 'opOPTIONS': // 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 < context.length; i++) { temp = context[i]; if (temp.getGtLtOp(-1) == 'opREGEX') return {code:ErrorCodes.OK, result:null}; } return {code:ErrorCodes.BadValue, description:"$options needs a $regex"}; case 'opREGEX': return this._parseRegexDocument(name, context); case 'opELEM_MATCH': return this._parseElemMatch(name, element); case 'opALL': return this._parseAll(name, element); case 'opWITHIN': case 'opGEO_INTERSECTS': case 'opNEAR': return this.expressionParserGeoCallback(name, x, context); } // end switch return {code:ErrorCodes.BadValue, description:"not handled: " + element}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method _parseTreeList * @param * */ proto._parseTreeList = function _parseTreeList(arr, out){ // File: expression_parser_tree.cpp lines: 33-52 if (arr.length === 0) return {code:ErrorCodes.BadValue, 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.BadValue, 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}; }; /** * * This documentation was automatically generated. Please update when you touch this function. * @method parse * @param * */ proto.parse = function parse(obj){ // File: expression_parser.h lines: 40-41 return this._parse(obj, true); };