MatchExpressionParser.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. "use strict";
  2. // File: expression_parser.cpp
  3. var MatchExpressionParser = module.exports = function (){
  4. }, klass = MatchExpressionParser, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  5. // DEPENDENCIES
  6. var errors = require("../../Errors.js"),
  7. ErrorCodes = errors.ErrorCodes,
  8. AndMatchExpression = require("./AndMatchExpression.js"),
  9. MatchExpression = require("./MatchExpression.js"),
  10. OrMatchExpression = require("./OrMatchExpression.js"),
  11. ModMatchExpression = require("./ModMatchExpression.js"),
  12. NorMatchExpression = require("./NorMatchExpression.js"),
  13. NotMatchExpression = require("./NotMatchExpression.js"),
  14. LTMatchExpression = require("./LTMatchExpression.js"),
  15. LTEMatchExpression = require("./LTEMatchExpression.js"),
  16. GTMatchExpression = require("./GTMatchExpression.js"),
  17. GTEMatchExpression = require("./GTEMatchExpression.js"),
  18. InMatchExpression = require("./InMatchExpression.js"),
  19. SizeMatchExpression = require("./SizeMatchExpression.js"),
  20. TypeMatchExpression = require("./TypeMatchExpression.js"),
  21. ExistsMatchExpression = require("./ExistsMatchExpression.js"),
  22. EqualityMatchExpression = require("./EqualityMatchExpression.js"),
  23. ArrayMatchingMatchExpression = require("./ArrayMatchingMatchExpression.js"),
  24. RegexMatchExpression = require("./RegexMatchExpression.js"),
  25. FalseMatchExpression = require("./FalseMatchExpression.js"),
  26. ComparisonMatchExpression = require("./ComparisonMatchExpression.js"),
  27. ElemMatchValueMatchExpression = require("./ElemMatchValueMatchExpression.js"),
  28. ElemMatchObjectMatchExpression = require("./ElemMatchObjectMatchExpression.js"),
  29. AllElemMatchOp = require("./AllElemMatchOp.js"),
  30. AtomicMatchExpression = require("./AtomicMatchExpression.js");
  31. proto.expressionParserTextCallback = require('./TextMatchExpressionParser').expressionParserTextCallbackReal;
  32. // The maximum allowed depth of a query tree. Just to guard against stack overflow.
  33. var MAXIMUM_TREE_DEPTH = 100;
  34. /**
  35. *
  36. * Check if the input element is an expression
  37. * @method _isExpressionDocument
  38. * @param element
  39. *
  40. */
  41. proto._isExpressionDocument = function _isExpressionDocument(element, allowIncompleteDBRef){
  42. if (!(element instanceof Object))
  43. return false;
  44. if (Object.keys(element).length === 0)
  45. return false;
  46. var name = Object.keys(element)[0];
  47. if (name[0] != '$')
  48. return false;
  49. if (this._isDBRefDocument(element, allowIncompleteDBRef))
  50. return false;
  51. return true;
  52. };
  53. proto._isDBRefDocument = function _isDBRefDocument(obj, allowIncompleteDBRef) {
  54. var hasRef, hasID, hasDB = false;
  55. var i, fieldName, element = null,
  56. keys = Object.keys(obj), length = keys.length;
  57. for (i = 0; i < length; i++) {
  58. fieldName = keys[i];
  59. element = obj[fieldName];
  60. if (!hasRef && fieldName === '$ref')
  61. hasRef = true;
  62. else if (!hasID && fieldName === '$id')
  63. hasID = true;
  64. else if (!hasDB && fieldName === '$db')
  65. hasDB = true;
  66. }
  67. return allowIncompleteDBRef && (hasRef || hasID || hasDB) || (hasRef && hasID);
  68. };
  69. /**
  70. *
  71. * Parse the input object into individual elements
  72. * @method _parse
  73. * @param obj
  74. * @param level
  75. *
  76. */
  77. proto._parse = function _parse(obj, level){
  78. if (level > MAXIMUM_TREE_DEPTH)
  79. return {code:ErrorCodes.BAD_VALUE, description:"exceeded maximum query tree depth of " +
  80. MAXIMUM_TREE_DEPTH + " at " + JSON.stringify(obj)};
  81. var rest, temp, status, element, eq, real;
  82. var root = new AndMatchExpression();
  83. var objkeys = Object.keys(obj);
  84. var currname, currval;
  85. var topLevel = level === 0;
  86. level++;
  87. for (var i = 0; i < objkeys.length; i++) {
  88. currname = objkeys[i];
  89. currval = obj[currname];
  90. if (currname[0] == '$' ) {
  91. rest = currname.substr(1);
  92. // TODO: optimize if block?
  93. if ("or" == rest) {
  94. if (!(currval instanceof Array))
  95. return {code:ErrorCodes.BAD_VALUE, description:"$or needs an array"};
  96. temp = new OrMatchExpression();
  97. status = this._parseTreeList(currval, temp, level);
  98. if (status.code != ErrorCodes.OK)
  99. return status;
  100. root.add(temp);
  101. }
  102. else if ("and" == rest) {
  103. if (!(currval instanceof Array))
  104. return {code:ErrorCodes.BAD_VALUE, description:"and needs an array"};
  105. temp = new AndMatchExpression();
  106. status = this._parseTreeList(currval, temp, level);
  107. if (status.code != ErrorCodes.OK)
  108. return status;
  109. root.add(temp);
  110. }
  111. else if ("nor" == rest) {
  112. if (!(currval instanceof Array))
  113. return {code:ErrorCodes.BAD_VALUE, description:"and needs an array"};
  114. temp = new NorMatchExpression();
  115. status = this._parseTreeList(currval, temp, level);
  116. if (status.code != ErrorCodes.OK)
  117. return status;
  118. root.add(temp);
  119. }
  120. else if (("atomic" == rest) || ("isolated" == rest)) {
  121. if (!topLevel)
  122. return {code:ErrorCodes.BAD_VALUE, description:"$atomic/$isolated has to be at the top level"};
  123. if (element)
  124. root.add(new AtomicMatchExpression());
  125. }
  126. else if ("where" == rest) {
  127. /*
  128. if ( !topLevel )
  129. return StatusWithMatchExpression( ErrorCodes::BAD_VALUE, "$where has to be at the top level" );
  130. */
  131. return {'code':'FAILED_TO_PARSE', 'desc':'Where unimplimented.'};
  132. /*
  133. status = this.expressionParserWhereCallback(element);
  134. if (status.code != ErrorCodes.OK)
  135. return status;
  136. root.add(status.result);*/
  137. } else if ('text' === rest) {
  138. if (typeof currval !== 'object') {
  139. return {code: ErrorCodes.BAD_VALUE, description: '$text expects an object'};
  140. }
  141. return this.expressionTextCallback(currval);
  142. }
  143. else if ("comment" == rest) {}
  144. else {
  145. return {code:ErrorCodes.BAD_VALUE, description:"unknown top level operator: " + currname};
  146. }
  147. continue;
  148. }
  149. if (this._isExpressionDocument(currval)) {
  150. status = this._parseSub(currname, currval, root, level);
  151. if (status.code != ErrorCodes.OK)
  152. return status;
  153. continue;
  154. }
  155. if (currval instanceof RegExp) {
  156. status = this._parseRegexElement(currname, currval);
  157. if (status.code != ErrorCodes.OK)
  158. return status;
  159. root.add(status.result);
  160. continue;
  161. }
  162. eq = new EqualityMatchExpression();
  163. status = eq.init(currname, currval);
  164. if (status.code != ErrorCodes.OK)
  165. return status;
  166. root.add(eq);
  167. }
  168. if (root.numChildren() == 1) {
  169. return {code:ErrorCodes.OK, result:root.getChild(0)};
  170. }
  171. return {code:ErrorCodes.OK, result:root};
  172. };
  173. /**
  174. *
  175. * Parse the $all element
  176. * @method _parseAll
  177. * @param name
  178. * @param element
  179. *
  180. */
  181. proto._parseAll = function _parseAll(name, element, level){
  182. var status, i;
  183. if (!(element instanceof Array))
  184. return {code:ErrorCodes.BAD_VALUE, description:"$all needs an array"};
  185. var arr = element;
  186. if ((arr[0] instanceof Object) && ("$elemMatch" == Object.keys(arr[0])[0])) {
  187. // $all : [ { $elemMatch : {} } ... ]
  188. var temp = new AllElemMatchOp();
  189. status = temp.init(name);
  190. if (status.code != ErrorCodes.OK)
  191. return status;
  192. for (i = 0; i < arr.length; i++) {
  193. var hopefullyElemMatchElement = arr[i];
  194. if (!(hopefullyElemMatchElement instanceof Object)) {
  195. // $all : [ { $elemMatch : ... }, 5 ]
  196. return {code:ErrorCodes.BAD_VALUE, description:"$all/$elemMatch has to be consistent"};
  197. }
  198. if ("$elemMatch" != Object.keys(hopefullyElemMatchElement)[0]) {
  199. // $all : [ { $elemMatch : ... }, { x : 5 } ]
  200. return {code:ErrorCodes.BAD_VALUE, description:"$all/$elemMatch has to be consistent"};
  201. }
  202. status = this._parseElemMatch("", hopefullyElemMatchElement.$elemMatch, level);
  203. if (status.code != ErrorCodes.OK)
  204. return status;
  205. temp.add(status.result);
  206. }
  207. return {code:ErrorCodes.OK, result:temp};
  208. }
  209. var myAnd = new AndMatchExpression();
  210. for (i = 0; i < arr.length; i++) {
  211. var e = arr[i];
  212. if (e instanceof RegExp) {
  213. var r = new RegexMatchExpression();
  214. status = r.init(name, e);
  215. if (status.code != ErrorCodes.OK)
  216. return status;
  217. myAnd.add(r);
  218. }
  219. else if ((e instanceof Object) && (typeof(Object.keys(e)[0] == 'string' && Object.keys(e)[0][0] == '$' ))) {
  220. return {code:ErrorCodes.BAD_VALUE, description:"no $ expressions in $all"};
  221. }
  222. else {
  223. var x = new EqualityMatchExpression();
  224. status = x.init(name, e);
  225. if (status.code != ErrorCodes.OK)
  226. return status;
  227. myAnd.add(x);
  228. }
  229. }
  230. if (myAnd.numChildren() === 0) {
  231. return {code:ErrorCodes.OK, result:new FalseMatchExpression()};
  232. }
  233. return {code:ErrorCodes.OK, result:myAnd};
  234. };
  235. /**
  236. *
  237. * Parse the input array and add new RegexMatchExpressions to entries
  238. * @method _parseArrayFilterEntries
  239. * @param entries
  240. * @param theArray
  241. *
  242. */
  243. proto._parseArrayFilterEntries = function _parseArrayFilterEntries(entries, theArray){
  244. var status, e, r;
  245. for (var i = 0; i < theArray.length; i++) {
  246. e = theArray[i];
  247. if (this._isExpressionDocument(e, false)) {
  248. return {code:ErrorCodes.BAD_VALUE, description:"cannot nest $ under $in"};
  249. }
  250. if (e instanceof RegExp ) {
  251. r = new RegexMatchExpression();
  252. status = r.init("", e);
  253. if (status.code != ErrorCodes.OK)
  254. return status;
  255. status = entries.addRegex(r);
  256. if (status.code != ErrorCodes.OK)
  257. return status;
  258. }
  259. else {
  260. status = entries.addEquality(e);
  261. if (status.code != ErrorCodes.OK)
  262. return status;
  263. }
  264. }
  265. return {code:ErrorCodes.OK};
  266. };
  267. /**
  268. *
  269. * Parse the input ComparisonMatchExpression
  270. * @method _parseComparison
  271. * @param name
  272. * @param cmp
  273. * @param element
  274. *
  275. */
  276. proto._parseComparison = function _parseComparison(name, cmp, element){
  277. var temp = new ComparisonMatchExpression(cmp);
  278. var status = temp.init(name, element);
  279. if (status.code != ErrorCodes.OK)
  280. return status;
  281. return {code:ErrorCodes.OK, result:temp};
  282. };
  283. /**
  284. *
  285. * Parse an element match into the appropriate expression
  286. * @method _parseElemMatch
  287. * @param name
  288. * @param element
  289. *
  290. */
  291. proto._parseElemMatch = function _parseElemMatch(name, element, level){
  292. var temp, status;
  293. if (!(element instanceof Object))
  294. return {code:ErrorCodes.BAD_VALUE, description:"$elemMatch needs an Object"};
  295. // $elemMatch value case applies when the children all
  296. // work on the field 'name'.
  297. // This is the case when:
  298. // 1) the argument is an expression document; and
  299. // 2) expression is not a AND/NOR/OR logical operator. Children of
  300. // these logical operators are initialized with field names.
  301. // 3) expression is not a WHERE operator. WHERE works on objects instead
  302. // of specific field.
  303. var elt = element[Object.keys(element)[0]],
  304. isElemMatchValue = this._isExpressionDocument(element, true) &&
  305. elt !== '$and' &&
  306. elt !== '$nor' &&
  307. elt !== '$or' &&
  308. elt !== '$where';
  309. if (isElemMatchValue) {
  310. // value case
  311. var theAnd = new AndMatchExpression();
  312. status = this._parseSub("", element, theAnd, level);
  313. if (status.code != ErrorCodes.OK)
  314. return status;
  315. temp = new ElemMatchValueMatchExpression();
  316. status = temp.init(name);
  317. if (status.code != ErrorCodes.OK)
  318. return status;
  319. for (var i = 0; i < theAnd.numChildren(); i++ ) {
  320. temp.add(theAnd.getChild(i));
  321. }
  322. theAnd.clearAndRelease();
  323. return {code:ErrorCodes.OK, result:temp};
  324. }
  325. // DBRef value case
  326. // A DBRef document under a $elemMatch should be treated as an object case
  327. // because it may contain non-DBRef fields in addition to $ref, $id and $db.
  328. // object case
  329. status = this._parse(element, level);
  330. if (status.code != ErrorCodes.OK)
  331. return status;
  332. temp = new ElemMatchObjectMatchExpression();
  333. status = temp.init(name, status.result);
  334. if (status.code != ErrorCodes.OK)
  335. return status;
  336. return {code:ErrorCodes.OK, result:temp};
  337. };
  338. /**
  339. *
  340. * Parse a ModMatchExpression
  341. * @method _parseMOD
  342. * @param name
  343. * @param element
  344. *
  345. */
  346. proto._parseMOD = function _parseMOD(name, element){
  347. var d,r;
  348. if (!(element instanceof Array))
  349. return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, needs to be an array"};
  350. if (element.length < 2)
  351. return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, not enough elements"};
  352. if (element.length > 2)
  353. return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, too many elements"};
  354. if (typeof element[0] !== 'number') {
  355. return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, divisor not a number"};
  356. } else {
  357. d = element[0];
  358. }
  359. if (typeof element[1] !== 'number') {
  360. return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, remainder not a number"};
  361. } else {
  362. r = element[1];
  363. }
  364. var temp = new ModMatchExpression();
  365. var status = temp.init( name, d, r);
  366. if (status.code != ErrorCodes.OK)
  367. return status;
  368. return {code:ErrorCodes.OK, result:temp};
  369. };
  370. /**
  371. *
  372. * Parse a NotMatchExpression
  373. * @method _parseNot
  374. * @param name
  375. * @param element
  376. *
  377. */
  378. proto._parseNot = function _parseNot(name, element, level){
  379. var status;
  380. if (element instanceof RegExp) {
  381. status = this._parseRegexElement(name, element);
  382. if (status.code != ErrorCodes.OK)
  383. return status;
  384. var n = new NotMatchExpression();
  385. status = n.init(status.result);
  386. if (status.code != ErrorCodes.OK)
  387. return status;
  388. return {code:ErrorCodes.OK, result:n};
  389. }
  390. if (!(element instanceof Object))
  391. return {code:ErrorCodes.BAD_VALUE, result:"$not needs a regex or a document"};
  392. if (element == {})
  393. return {code:ErrorCodes.BAD_VALUE, result:"$not cannot be empty"};
  394. var theAnd = new AndMatchExpression();
  395. status = this._parseSub(name, element, theAnd, level);
  396. if (status.code != ErrorCodes.OK)
  397. return status;
  398. // TODO: this seems arbitrary?
  399. // tested in jstests/not2.js
  400. for (var i = 0; i < theAnd.numChildren(); i++) {
  401. if (theAnd.getChild(i).matchType == MatchExpression.REGEX) {
  402. return {code:ErrorCodes.BAD_VALUE, result:"$not cannot have a regex"};
  403. }
  404. }
  405. var theNot = new NotMatchExpression();
  406. status = theNot.init(theAnd);
  407. if (status.code != ErrorCodes.OK)
  408. return status;
  409. return {code:ErrorCodes.OK, result:theNot};
  410. };
  411. /**
  412. *
  413. * Parse a RegexMatchExpression
  414. * @method _parseRegexDocument
  415. * @param name
  416. * @param doc
  417. *
  418. */
  419. proto._parseRegexDocument = function _parseRegexDocument(name, doc){
  420. var regex = '', regexOptions = '', e;
  421. if(doc.$regex) {
  422. e = doc.$regex;
  423. if(e instanceof RegExp) {
  424. var str = e.toString(),
  425. flagIndex = 0;
  426. for (var c = str.length; c > 0; c--){
  427. if (str[c] == '/') {
  428. flagIndex = c;
  429. break;
  430. }
  431. }
  432. regex = (flagIndex? str : str.substr(1, flagIndex-1));
  433. regexOptions = str.substr(flagIndex, str.length);
  434. } else if (typeof e === 'string') {
  435. regex = e;
  436. } else {
  437. return {code:ErrorCodes.BAD_VALUE, description:"$regex has to be a string"};
  438. }
  439. }
  440. if(doc.$options) {
  441. e = doc.$options;
  442. if(typeof(e) === 'string') {
  443. regexOptions = e;
  444. } else {
  445. return {code:ErrorCodes.BAD_VALUE, description:"$options has to be a string"};
  446. }
  447. }
  448. var temp = new RegexMatchExpression();
  449. var status = temp.init(name, regex, regexOptions);
  450. if (status.code != ErrorCodes.OK)
  451. return status;
  452. return {code:ErrorCodes.OK, result:temp};
  453. };
  454. /**
  455. *
  456. * Parse an element into a RegexMatchExpression
  457. * @method _parseRegexElement
  458. * @param name
  459. * @param element
  460. *
  461. */
  462. proto._parseRegexElement = function _parseRegexElement(name, element){
  463. if (!(element instanceof RegExp))
  464. return {code:ErrorCodes.BAD_VALUE, description:"not a regex"};
  465. var str = element.toString(),
  466. flagIndex = 0;
  467. for (var c = str.length; c > 0; c--){
  468. if (str[c] == '/') {
  469. flagIndex = c;
  470. break;
  471. }
  472. }
  473. var regex = str.substr(1, flagIndex-1),
  474. regexOptions = str.substr(flagIndex+1, str.length),
  475. temp = new RegexMatchExpression(),
  476. status = temp.init(name, regex, regexOptions);
  477. if (status.code != ErrorCodes.OK)
  478. return status;
  479. return {code:ErrorCodes.OK, result:temp};
  480. };
  481. /**
  482. *
  483. * Parse a sub expression
  484. * @method _parseSub
  485. * @param name
  486. * @param sub
  487. * @param root
  488. *
  489. */
  490. proto._parseSub = function _parseSub(name, sub, root, level){
  491. var subkeys = Object.keys(sub),
  492. currname, currval;
  493. if (level > MAXIMUM_TREE_DEPTH) {
  494. return {code:ErrorCodes.BAD_VALUE, description:"exceeded maximum query tree depth of " +
  495. MAXIMUM_TREE_DEPTH + " at " + JSON.stringify(sub)};
  496. }
  497. level++;
  498. // DERIVATION: We are not implementing Geo functions yet.
  499. for (var i = 0; i < subkeys.length; i++) {
  500. currname = subkeys[i];
  501. currval = sub[currname];
  502. var deep = {};
  503. deep[currname] = currval;
  504. var status = this._parseSubField(sub, root, name, deep, level);
  505. if (status.code != ErrorCodes.OK)
  506. return status;
  507. if (status.result)
  508. root.add(status.result);
  509. }
  510. return {code:ErrorCodes.OK, result:root};
  511. };
  512. /**
  513. *
  514. * Parse a sub expression field
  515. * @method _parseSubField
  516. * @param context
  517. * @param andSoFar
  518. * @param name
  519. * @param element
  520. *
  521. */
  522. proto._parseSubField = function _parseSubField(context, andSoFar, name, element, level){
  523. // TODO: these should move to getGtLtOp, or its replacement
  524. var currname = Object.keys(element)[0];
  525. var currval = element[currname];
  526. if ("$eq" == currname)
  527. return this._parseComparison(name, 'EQ', currval);
  528. if ("$not" == currname)
  529. return this._parseNot(name, currval, level);
  530. var status, temp, temp2;
  531. switch (currname) {
  532. // TODO: -1 is apparently a value for mongo, but we handle strings so...
  533. case '$lt':
  534. return this._parseComparison(name, 'LT', currval);
  535. case '$lte':
  536. return this._parseComparison(name, 'LTE', currval);
  537. case '$gt':
  538. return this._parseComparison(name, 'GT', currval);
  539. case '$gte':
  540. return this._parseComparison(name, 'GTE', currval);
  541. case '$ne':
  542. // Just because $ne can be rewritten as the negation of an
  543. // equality does not mean that $ne of a regex is allowed. See SERVER-1705.
  544. if (currval instanceof RegExp) {
  545. return {code:ErrorCodes.BAD_VALUE, description:"Can't have regex as arg to $ne."};
  546. }
  547. status = this._parseComparison(name, 'EQ', currval);
  548. if (status.code != ErrorCodes.OK)
  549. return status;
  550. var n = new NotMatchExpression();
  551. status = n.init(status.result);
  552. if (status.code != ErrorCodes.OK)
  553. return status;
  554. return {code:ErrorCodes.OK, result:n};
  555. case '$eq':
  556. return this._parseComparison(name, 'EQ', currval);
  557. case '$in':
  558. if (!(currval instanceof Array))
  559. return {code:ErrorCodes.BAD_VALUE, description:"$in needs an array"};
  560. temp = new InMatchExpression();
  561. status = temp.init(name);
  562. if (status.code != ErrorCodes.OK)
  563. return status;
  564. status = this._parseArrayFilterEntries(temp.getArrayFilterEntries(), currval);
  565. if (status.code != ErrorCodes.OK)
  566. return status;
  567. return {code:ErrorCodes.OK, result:temp};
  568. case '$nin':
  569. if (!(currval instanceof Array))
  570. return {code:ErrorCodes.BAD_VALUE, description:"$nin needs an array"};
  571. temp = new InMatchExpression();
  572. status = temp.init(name);
  573. if (status.code != ErrorCodes.OK)
  574. return status;
  575. status = this._parseArrayFilterEntries(temp.getArrayFilterEntries(), currval);
  576. if (status.code != ErrorCodes.OK)
  577. return status;
  578. temp2 = new NotMatchExpression();
  579. status = temp2.init(temp);
  580. if (status.code != ErrorCodes.OK)
  581. return status;
  582. return {code:ErrorCodes.OK, result:temp2};
  583. case '$size':
  584. var size = 0;
  585. if ( typeof(currval) === 'string')
  586. // matching old odd semantics
  587. size = 0;
  588. else if (typeof(currval) === 'number')
  589. // SERVER-11952. Setting 'size' to -1 means that no documents
  590. // should match this $size expression.
  591. if (currval < 0)
  592. size = -1;
  593. else
  594. size = currval;
  595. else {
  596. return {code:ErrorCodes.BAD_VALUE, description:"$size needs a number"};
  597. }
  598. // DERIVATION/Potential bug: Mongo checks to see if doube values are exactly equal to
  599. // their int converted version. If not, size = -1.
  600. temp = new SizeMatchExpression();
  601. status = temp.init(name, size);
  602. if (status.code != ErrorCodes.OK)
  603. return status;
  604. return {code:ErrorCodes.OK, result:temp};
  605. case '$exists':
  606. if (currval == {})
  607. return {code:ErrorCodes.BAD_VALUE, description:"$exists can't be eoo"};
  608. temp = new ExistsMatchExpression();
  609. status = temp.init(name);
  610. if (status.code != ErrorCodes.OK)
  611. return status;
  612. if (currval) // DERIVATION: This may have to check better than truthy? Need to look at TrueValue
  613. return {code:ErrorCodes.OK, result:temp};
  614. temp2 = new NotMatchExpression();
  615. status = temp2.init(temp);
  616. if (status.code != ErrorCodes.OK)
  617. return status;
  618. return {code:ErrorCodes.OK, result:temp2};
  619. case '$type':
  620. if (typeof(currval) != 'number')
  621. return {code:ErrorCodes.BAD_VALUE, description:"$type has to be a number"};
  622. var type = currval;
  623. temp = new TypeMatchExpression();
  624. status = temp.init(name, type);
  625. if (status.code != ErrorCodes.OK)
  626. return status;
  627. return {code:ErrorCodes.OK, result:temp};
  628. case '$mod':
  629. return this._parseMOD(name, currval);
  630. case '$options':
  631. // TODO: try to optimize this
  632. // we have to do this since $options can be before or after a $regex
  633. // but we validate here
  634. for(var i = 0; i < Object.keys(context).length; i++) {
  635. temp = Object.keys(context)[i];
  636. if (temp == '$regex')
  637. return {code:ErrorCodes.OK, result:null};
  638. }
  639. return {code:ErrorCodes.BAD_VALUE, description:"$options needs a $regex"};
  640. case '$regex':
  641. return this._parseRegexDocument(name, context);
  642. case '$elemMatch':
  643. return this._parseElemMatch(name, currval, level);
  644. case '$all':
  645. return this._parseAll(name, currval, level);
  646. case '$geoWithin':
  647. case '$geoIntersects':
  648. case '$near':
  649. case '$nearSphere':
  650. var x = 'Temporary value until Geo fns implimented.';
  651. return this.expressionParserGeoCallback(name, x, context);
  652. default:
  653. return {code:ErrorCodes.BAD_VALUE, description:"not handled: " + element};
  654. } // end switch
  655. return {code:ErrorCodes.BAD_VALUE, description:"not handled: " + element};
  656. };
  657. /**
  658. *
  659. * Parse a list of parsable elements
  660. * @method _parseTreeList
  661. * @param arr
  662. * @param out
  663. *
  664. */
  665. proto._parseTreeList = function _parseTreeList(arr, out, level){
  666. if (arr.length === 0)
  667. return {code:ErrorCodes.BAD_VALUE, description:"$and/$or/$nor must be a nonempty array"};
  668. var status, element;
  669. for (var i = 0; i < arr.length; i++) {
  670. element = arr[i];
  671. if (!(element instanceof Object))
  672. return {code:ErrorCodes.BAD_VALUE, description:"$or/$and/$nor entries need to be full objects"};
  673. status = this._parse(element, level);
  674. if (status.code != ErrorCodes.OK)
  675. return status;
  676. out.add(status.result);
  677. }
  678. return {code:ErrorCodes.OK};
  679. };
  680. /**
  681. *
  682. * Wrapper for _parse
  683. * @method parse
  684. * @param obj
  685. *
  686. */
  687. proto.parse = function parse(obj){
  688. return this._parse(obj, 0);
  689. };