probe.js 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  1. "use strict";
  2. /**
  3. @fileOverview Queries objects in memory using a mongo-like notation for reaching into objects and filtering for records
  4. @module documents/probe
  5. @author Terry Weiss
  6. @license MIT
  7. @requires lodash
  8. */
  9. var sys = require( "lodash" );
  10. /**
  11. The list of operators that are nested within the expression object. These take the form <code>{path:{operator:operand}}</code>
  12. @private
  13. @type {array.<string>}
  14. **/
  15. var nestedOps = ["$eq", "$gt", "$gte", "$in", "$lt", "$lte", "$ne", "$nin", "$exists", "$mod", "$size", "$all"];
  16. /**
  17. The list of operators that prefix the expression object. These take the form <code>{operator:{operands}}</code> or <code>{operator: [operands]}</code>
  18. @private
  19. @type {array.<string>}
  20. **/
  21. var prefixOps = ["$and", "$or", "$nor", "$not"];
  22. /**
  23. Processes a nested operator by picking the operator out of the expression object. Returns a formatted object that can be used for querying
  24. @private
  25. @param {string} path The path to element to work with
  26. @param {object} operand The operands to use for the query
  27. @return {object} A formatted operation definition
  28. **/
  29. function processNestedOperator( path, operand ) {
  30. var opKeys = Object.keys( operand );
  31. return {
  32. operation : opKeys[ 0 ],
  33. operands : [operand[ opKeys[ 0 ] ]],
  34. path : path
  35. };
  36. }
  37. /**
  38. Interrogates a single query expression object and calls the appropriate handler for its contents
  39. @private
  40. @param {object} val The expression
  41. @param {object} key The prefix
  42. @returns {object} A formatted operation definition
  43. **/
  44. function processExpressionObject( val, key ) {
  45. var operator;
  46. if ( sys.isObject( val ) ) {
  47. var opKeys = Object.keys( val );
  48. var op = opKeys[ 0 ];
  49. if ( sys.indexOf( nestedOps, op ) > -1 ) {
  50. operator = processNestedOperator( key, val );
  51. } else if ( sys.indexOf( prefixOps, key ) > -1 ) {
  52. operator = processPrefixOperator( key, val );
  53. } else if ( op === "$regex" ) {
  54. // special handling for regex options
  55. operator = processNestedOperator( key, val );
  56. } else if ( op === "$elemMatch" ) {
  57. // elemMatch is just a weird duck
  58. operator = {
  59. path : key,
  60. operation : op,
  61. operands : []
  62. };
  63. sys.each( val[ op ], function ( entry ) {
  64. operator.operands = parseQueryExpression( entry );
  65. } );
  66. }
  67. else {
  68. throw new Error( "Unrecognized operator" );
  69. }
  70. } else {
  71. operator = processNestedOperator( key, { $eq : val } );
  72. }
  73. return operator;
  74. }
  75. /**
  76. Processes a prefixed operator and then passes control to the nested operator method to pick out the contained values
  77. @private
  78. @param {string} operation The operation prefix
  79. @param {object} operand The operands to use for the query
  80. @return {object} A formatted operation definition
  81. **/
  82. function processPrefixOperator( operation, operand ) {
  83. var component = {
  84. operation : operation,
  85. path : null,
  86. operands : []
  87. };
  88. if ( sys.isArray( operand ) ) {
  89. //if it is an array we need to loop through the array and parse each operand
  90. //if it is an array we need to loop through the array and parse each operand
  91. sys.each( operand, function ( obj ) {
  92. sys.each( obj, function ( val, key ) {
  93. component.operands.push( processExpressionObject( val, key ) );
  94. } );
  95. } );
  96. } else {
  97. //otherwise it is an object and we can parse it directly
  98. sys.each( operand, function ( val, key ) {
  99. component.operands.push( processExpressionObject( val, key ) );
  100. } );
  101. }
  102. return component;
  103. }
  104. /**
  105. Parses a query request and builds an object that can used to process a query target
  106. @private
  107. @param {object} obj The expression object
  108. @returns {object} All components of the expression in a kind of execution tree
  109. **/
  110. function parseQueryExpression( obj ) {
  111. if ( sys.size( obj ) > 1 ) {
  112. var arr = sys.map( obj, function ( v, k ) {
  113. var entry = {};
  114. entry[k] = v;
  115. return entry;
  116. } );
  117. obj = {
  118. $and : arr
  119. };
  120. }
  121. var payload = [];
  122. sys.each( obj, function ( val, key ) {
  123. var exprObj = processExpressionObject( val, key );
  124. if ( exprObj.operation === "$regex" ) {
  125. exprObj.options = val.$options;
  126. }
  127. payload.push( exprObj );
  128. } );
  129. return payload;
  130. }
  131. /**
  132. The delimiter to use when splitting an expression
  133. @type {string}
  134. @static
  135. @default '.'
  136. **/
  137. exports.delimiter = '.';
  138. /**
  139. Splits a path expression into its component parts
  140. @private
  141. @param {string} path The path to split
  142. @returns {array}
  143. **/
  144. function splitPath( path ) {
  145. return path.split( exports.delimiter );
  146. }
  147. /**
  148. Reaches into an object and allows you to get at a value deeply nested in an object
  149. @private
  150. @param {array} path The split path of the element to work with
  151. @param {object} record The record to reach into
  152. @return {*} Whatever was found in the record
  153. **/
  154. function reachin( path, record ) {
  155. var context = record;
  156. var part;
  157. var _i;
  158. var _len;
  159. for ( _i = 0, _len = path.length; _i < _len; _i++ ) {
  160. part = path[_i];
  161. context = context[part];
  162. if ( sys.isNull( context ) || sys.isUndefined( context ) ) {
  163. break;
  164. }
  165. }
  166. return context;
  167. }
  168. /**
  169. This will write the value into a record at the path, creating intervening objects if they don't exist
  170. @private
  171. @param {array} path The split path of the element to work with
  172. @param {object} record The record to reach into
  173. @param {string} setter The set command, defaults to $set
  174. @param {object} newValue The value to write to the, or if the operator is $pull, the query of items to look for
  175. */
  176. function pushin( path, record, setter, newValue ) {
  177. var context = record;
  178. var parent = record;
  179. var lastPart = null;
  180. var _i;
  181. var _len;
  182. var part;
  183. var keys;
  184. for ( _i = 0, _len = path.length; _i < _len; _i++ ) {
  185. part = path[_i];
  186. lastPart = part;
  187. parent = context;
  188. context = context[part];
  189. if ( sys.isNull( context ) || sys.isUndefined( context ) ) {
  190. parent[part] = {};
  191. context = parent[part];
  192. }
  193. }
  194. if ( sys.isEmpty( setter ) || setter === '$set' ) {
  195. parent[lastPart] = newValue;
  196. return parent[lastPart];
  197. } else {
  198. switch ( setter ) {
  199. case '$inc':
  200. /**
  201. * Increments a field by the amount you specify. It takes the form
  202. * `{ $inc: { field1: amount } }`
  203. * @name $inc
  204. * @memberOf module:documents/probe.updateOperators
  205. * @example
  206. * var probe = require("documents/probe");
  207. * probe.update( obj, {'name.last' : 'Owen', 'name.first' : 'LeRoy'},
  208. * {$inc : {'password.changes' : 2}} );
  209. */
  210. if ( !sys.isNumber( newValue ) ) {
  211. newValue = 1;
  212. }
  213. if ( sys.isNumber( parent[lastPart] ) ) {
  214. parent[lastPart] = parent[lastPart] + newValue;
  215. return parent[lastPart];
  216. }
  217. break;
  218. case '$dec':
  219. /**
  220. * Decrements a field by the amount you specify. It takes the form
  221. * `{ $dec: { field1: amount }`
  222. * @name $dec
  223. * @memberOf module:documents/probe.updateOperators
  224. * @example
  225. * var probe = require("documents/probe");
  226. * probe.update( obj, {'name.last' : 'Owen', 'name.first' : 'LeRoy'},
  227. * {$dec : {'password.changes' : 2}} );
  228. */
  229. if ( !sys.isNumber( newValue ) ) {
  230. newValue = 1;
  231. }
  232. if ( sys.isNumber( parent[lastPart] ) ) {
  233. parent[lastPart] = parent[lastPart] - newValue;
  234. return parent[lastPart];
  235. }
  236. break;
  237. case '$unset':
  238. /**
  239. * Removes the field from the object. It takes the form
  240. * `{ $unset: { field1: "" } }`
  241. * @name $unset
  242. * @memberOf module:documents/probe.updateOperators
  243. * @example
  244. * var probe = require("documents/probe");
  245. * probe.update( data, {'name.first' : 'Yogi'}, {$unset : {'name.first' : ''}} );
  246. */
  247. return delete parent[lastPart];
  248. case '$pop':
  249. /**
  250. * The $pop operator removes the first or last element of an array. Pass $pop a value of 1 to remove the last element
  251. * in an array and a value of -1 to remove the first element of an array. This will only work on arrays. Syntax:
  252. * `{ $pop: { field: 1 } }` or `{ $pop: { field: -1 } }`
  253. * @name $pop
  254. * @memberOf module:documents/probe.updateOperators
  255. * @example
  256. * var probe = require("documents/probe");
  257. * // attr is the name of the array field
  258. * probe.update( data, {_id : '511d18827da2b88b09000133'}, {$pop : {attr : 1}} );
  259. */
  260. if ( sys.isArray( parent[lastPart] ) ) {
  261. if ( !sys.isNumber( newValue ) ) {
  262. newValue = 1;
  263. }
  264. if ( newValue === 1 ) {
  265. return parent[lastPart].pop();
  266. } else {
  267. return parent[lastPart].shift();
  268. }
  269. }
  270. break;
  271. case '$push':
  272. /**
  273. * The $push operator appends a specified value to an array. It looks like this:
  274. * `{ $push: { <field>: <value> } }`
  275. * @name $push
  276. * @memberOf module:documents/probe.updateOperators
  277. * @example
  278. * var probe = require("documents/probe");
  279. * // attr is the name of the array field
  280. * probe.update( data, {_id : '511d18827da2b88b09000133'},
  281. * {$push : {attr : {"hand" : "new", "color" : "new"}}} );
  282. */
  283. if ( sys.isArray( parent[lastPart] ) ) {
  284. return parent[lastPart].push( newValue );
  285. }
  286. break;
  287. case '$pull':
  288. /**
  289. * The $pull operator removes all instances of a value from an existing array. It looks like this:
  290. * `{ $pull: { field: <query> } }`
  291. * @name $pull
  292. * @memberOf module:documents/probe.updateOperators
  293. * @example
  294. * var probe = require("documents/probe");
  295. * // attr is the name of the array field
  296. * probe.update( data, {'email' : 'EWallace.43@fauxprisons.com'},
  297. * {$pull : {attr : {"color" : "green"}}} );
  298. */
  299. if ( sys.isArray( parent[lastPart] ) ) {
  300. keys = exports.findKeys( parent[lastPart], newValue );
  301. sys.each( keys, function ( val, index ) {
  302. return delete parent[lastPart][index];
  303. } );
  304. parent[lastPart] = sys.compact( parent[lastPart] );
  305. return parent[lastPart];
  306. }
  307. }
  308. }
  309. }
  310. /**
  311. The query operations that evaluate directly from an operation
  312. @private
  313. **/
  314. var operations = {
  315. /**
  316. * `$eq` performs a `===` comparison by comparing the value directly if it is an atomic value.
  317. * otherwise if it is an array, it checks to see if the value looked for is in the array.
  318. * `{field: value}` or `{field: {$eq : value}}` or `{array: value}` or `{array: {$eq : value}}`
  319. * @name $eq
  320. * @memberOf module:documents/probe.queryOperators
  321. * @example
  322. * var probe = require("documents/probe");
  323. * probe.find( data, {categories : "cat1"} );
  324. * // is the same as
  325. * probe.find( data, {categories : {$eq: "cat1"}} );
  326. */
  327. $eq : function ( qu, value ) {
  328. if ( sys.isArray( value ) ) {
  329. return sys.find( value, function ( entry ) {
  330. return JSON.stringify( qu.operands[0] ) === JSON.stringify( entry );
  331. } ) !== void 0;
  332. } else {
  333. return JSON.stringify( qu.operands[0] ) === JSON.stringify( value );
  334. }
  335. },
  336. /**
  337. * `$ne` performs a `!==` comparison by comparing the value directly if it is an atomic value. Otherwise, if it is an array
  338. * this is performs a "not in array".
  339. * '{field: {$ne : value}}` or '{array: {$ne : value}}`
  340. * @name $ne
  341. * @memberOf module:documents/probe.queryOperators
  342. * @example
  343. * var probe = require("documents/probe");
  344. * probe.find( data, {"name.first" : {$ne : "Sheryl"}} );
  345. */
  346. $ne : function ( qu, value ) {
  347. if ( sys.isArray( value ) ) {
  348. return sys.find( value, function ( entry ) {
  349. return JSON.stringify( qu.operands[0] ) !== JSON.stringify( entry );
  350. } ) !== void 0;
  351. } else {
  352. return JSON.stringify( qu.operands[0] ) !== JSON.stringify( value );
  353. }
  354. },
  355. /**
  356. * `$all` checks to see if all of the members of the query are included in an array
  357. * `{array: {$all: [val1, val2, val3]}}`
  358. * @name $all
  359. * @memberOf module:documents/probe.queryOperators
  360. * @example
  361. * var probe = require("documents/probe");
  362. * probe.find( data, {"categories" : {$all : ["cat4", "cat2", "cat1"]}} );
  363. */
  364. $all : function ( qu, value ) {
  365. var operands, result;
  366. result = false;
  367. if ( sys.isArray( value ) ) {
  368. operands = sys.flatten( qu.operands );
  369. result = sys.intersection( operands, value ).length === operands.length;
  370. }
  371. return result;
  372. },
  373. /**
  374. * `$gt` Sees if a field is greater than the value
  375. * `{field: {$gt: value}}`
  376. * @name $gt
  377. * @memberOf module:documents/probe.queryOperators
  378. * @example
  379. * var probe = require("documents/probe");
  380. * probe.find( data, {"age" : {$gt : 24}} );
  381. */
  382. $gt : function ( qu, value ) {
  383. return qu.operands[0] < value;
  384. },
  385. /**
  386. * `$gte` Sees if a field is greater than or equal to the value
  387. * `{field: {$gte: value}}`
  388. * @name $gte
  389. * @memberOf module:documents/probe.queryOperators
  390. * @example
  391. * var probe = require("documents/probe");
  392. * probe.find( data, {"age" : {$gte : 50}} );
  393. */
  394. $gte : function ( qu, value ) {
  395. return qu.operands[0] <= value;
  396. },
  397. /**
  398. * `$lt` Sees if a field is less than the value
  399. * `{field: {$lt: value}}`
  400. * @name $lt
  401. * @memberOf module:documents/probe.queryOperators
  402. * @example
  403. * var probe = require("documents/probe");
  404. * probe.find( data, {"age" : {$lt : 24}} );
  405. */
  406. $lt : function ( qu, value ) {
  407. return qu.operands[0] > value;
  408. },
  409. /**
  410. * `$lte` Sees if a field is less than or equal to the value
  411. * `{field: {$lte: value}}`
  412. * @name $lte
  413. * @memberOf module:documents/probe.queryOperators
  414. * @example
  415. * var probe = require("documents/probe");
  416. * probe.find( data, {"age" : {$lte : 50}} );
  417. */
  418. $lte : function ( qu, value ) {
  419. return qu.operands[0] >= value;
  420. },
  421. /**
  422. * `$in` Sees if a field has one of the values in the query
  423. * `{field: {$in: [test1, test2, test3,...]}}`
  424. * @name $in
  425. * @memberOf module:documents/probe.queryOperators
  426. * @example
  427. * var probe = require("documents/probe");
  428. * probe.find( data, {"age" : {$in : [24, 28, 60]}} );
  429. */
  430. $in : function ( qu, value ) {
  431. var operands;
  432. operands = sys.flatten( qu.operands );
  433. return sys.indexOf( operands, value ) > -1;
  434. },
  435. /**
  436. * `$nin` Sees if a field has none of the values in the query
  437. * `{field: {$nin: [test1, test2, test3,...]}}`
  438. * @name $nin
  439. * @memberOf module:documents/probe.queryOperators
  440. * @example
  441. * var probe = require("documents/probe");
  442. * probe.find( data, {"age" : {$nin : [24, 28, 60]}} );
  443. */
  444. $nin : function ( qu, value ) {
  445. var operands;
  446. operands = sys.flatten( qu.operands );
  447. return sys.indexOf( operands, value ) === -1;
  448. },
  449. /**
  450. * `$exists` Sees if a field exists.
  451. * `{field: {$exists: true|false}}`
  452. * @name $exists
  453. * @memberOf module:documents/probe.queryOperators
  454. * @example
  455. * var probe = require("documents/probe");
  456. * probe.find( data, {"name.middle" : {$exists : true}} );
  457. */
  458. $exists : function ( qu, value ) {
  459. return (sys.isNull( value ) || sys.isUndefined( value )) !== qu.operands[0];
  460. },
  461. /**
  462. * Checks equality to a modulus operation on a field
  463. * `{field: {$mod: [divisor, remainder]}}`
  464. * @name $mod
  465. * @memberOf module:documents/probe.queryOperators
  466. * @example
  467. * var probe = require("documents/probe");
  468. * probe.find( data, {"age" : {$mod : [2, 0]}} );
  469. */
  470. $mod : function ( qu, value ) {
  471. var operands = sys.flatten( qu.operands );
  472. if ( operands.length !== 2 ) {
  473. throw new Error( "$mod requires two operands" );
  474. }
  475. var mod = operands[0];
  476. var rem = operands[1];
  477. return value % mod === rem;
  478. },
  479. /**
  480. * Compares the size of the field/array to the query. This can be used on arrays, strings and objects (where it will count keys)
  481. * `{'field|array`: {$size: value}}`
  482. * @name $size
  483. * @memberOf module:documents/probe.queryOperators
  484. * @example
  485. * var probe = require("documents/probe");
  486. * probe.find( data, {attr : {$size : 3}} );
  487. */
  488. $size : function ( qu, value ) {
  489. return sys.size( value ) === qu.operands[0];
  490. },
  491. /**
  492. * Performs a regular expression test againts the field
  493. * `{field: {$regex: re, $options: reOptions}}`
  494. * @name $regex
  495. * @memberOf module:documents/probe.queryOperators
  496. * @example
  497. * var probe = require("documents/probe");
  498. * probe.find( data, {"name.first" : {$regex : "m*", $options : "i"}} );
  499. */
  500. $regex : function ( qu, value ) {
  501. var r = new RegExp( qu.operands[0], qu.options );
  502. return r.test( value );
  503. },
  504. /**
  505. * This is like $all except that it works with an array of objects or value. It checks to see the array matches all
  506. * of the conditions of the query
  507. * `{array: {$elemMatch: {path: value, path: {$operation: value2}}}`
  508. * @name $elemMatch
  509. * @memberOf module:documents/probe.queryOperators
  510. * @example
  511. * var probe = require("documents/probe");
  512. * probe.find( data, {attr : {$elemMatch : [
  513. * {color : "red", "hand" : "left"}
  514. * ]}} );
  515. */
  516. $elemMatch : function ( qu, value ) {
  517. var expression, test, _i, _len;
  518. if ( sys.isArray( value ) ) {
  519. var _ref = qu.operands;
  520. for ( _i = 0, _len = _ref.length; _i < _len; _i++ ) {
  521. expression = _ref[_i];
  522. if ( expression.path ) {
  523. expression.splitPath = splitPath( expression.path );
  524. }
  525. }
  526. test = execQuery( value, qu.operands, null, true ).arrayResults;
  527. }
  528. return test.length > 0;
  529. },
  530. /**
  531. * Returns true if all of the conditions of the query are met
  532. * `{$and: [query1, query2, query3]}`
  533. * @name $and
  534. * @memberOf module:documents/probe.queryOperators
  535. * @example
  536. * var probe = require("documents/probe");
  537. * probe.find( data, {$and : [
  538. * {"name.first" : "Mildred"},
  539. * {"name.last" : "Graves"}
  540. * ]} );
  541. */
  542. $and : function ( qu, value, record ) {
  543. var isAnd = false;
  544. sys.each( qu.operands, function ( expr ) {
  545. if ( expr.path ) {
  546. expr.splitPath = expr.splitPath || splitPath( expr.path );
  547. }
  548. var test = reachin( expr.splitPath, record, expr.operation );
  549. isAnd = operations[expr.operation]( expr, test, record );
  550. if ( !isAnd ) {
  551. return false;
  552. }
  553. } );
  554. return isAnd;
  555. },
  556. /**
  557. * Returns true if any of the conditions of the query are met
  558. * `{$or: [query1, query2, query3]}`
  559. * @name $or
  560. * @memberOf module:documents/probe.queryOperators
  561. * @example
  562. * var probe = require("documents/probe");
  563. * probe.find( data, {$or : [
  564. * "age" : {$in : [24, 28, 60]}},
  565. * {categories : "cat1"}
  566. * ]} );
  567. */
  568. $or : function ( qu, value, record ) {
  569. var isOr = false;
  570. sys.each( qu.operands, function ( expr ) {
  571. if ( expr.path ) {
  572. expr.splitPath = expr.splitPath || splitPath( expr.path );
  573. }
  574. var test = reachin( expr.splitPath, record, expr.operation );
  575. isOr = operations[expr.operation]( expr, test, record );
  576. if ( isOr ) {
  577. return false;
  578. }
  579. } );
  580. return isOr;
  581. },
  582. /**
  583. * Returns true if none of the conditions of the query are met
  584. * `{$nor: [query1, query2, query3]}`
  585. * @name $nor
  586. * @memberOf module:documents/probe.queryOperators
  587. * @example
  588. * var probe = require("documents/probe");
  589. * probe.find( data, {$nor : [
  590. * {"age" : {$in : [24, 28, 60]}},
  591. * {categories : "cat1"}
  592. * ]} );
  593. */
  594. $nor : function ( qu, value, record ) {
  595. var isOr = false;
  596. sys.each( qu.operands, function ( expr ) {
  597. if ( expr.path ) {
  598. expr.splitPath = expr.splitPath || splitPath( expr.path );
  599. }
  600. var test = reachin( expr.splitPath, record, expr.operation );
  601. isOr = operations[expr.operation]( expr, test, record );
  602. if ( isOr ) {
  603. return false;
  604. }
  605. } );
  606. return !isOr;
  607. },
  608. /**
  609. * Logical NOT on the conditions of the query
  610. * `{$not: [query1, query2, query3]}`
  611. * @name $not
  612. * @memberOf module:documents/probe.queryOperators
  613. * @example
  614. * var probe = require("documents/probe");
  615. * probe.find( data, {$not : {"age" : {$lt : 24}}} );
  616. */
  617. $not : function ( qu, value, record ) {
  618. var result = false;
  619. sys.each( qu.operands, function ( expr ) {
  620. if ( expr.path ) {
  621. expr.splitPath = expr.splitPath || splitPath( expr.path );
  622. }
  623. var test = reachin( expr.splitPath, record, expr.operation );
  624. result = operations[expr.operation]( expr, test, record );
  625. if ( result ) {
  626. return false;
  627. }
  628. } );
  629. return !result;
  630. }
  631. };
  632. /**
  633. Executes a query by traversing a document and evaluating each record
  634. @private
  635. @param {array|object} obj The object to query
  636. @param {object} qu The query to execute
  637. @param {?boolean} shortCircuit When true, the condition that matches the query stops evaluation for that record, otherwise all conditions have to be met
  638. @param {?boolean} stopOnFirst When true all evaluation stops after the first record is found to match the conditons
  639. **/
  640. function execQuery( obj, qu, shortCircuit, stopOnFirst ) {
  641. var arrayResults = [];
  642. var keyResults = [];
  643. sys.each( obj, function ( record, key ) {
  644. var expr, result, test, _i, _len;
  645. for ( _i = 0, _len = qu.length; _i < _len; _i++ ) {
  646. expr = qu[_i];
  647. if ( expr.splitPath ) {
  648. test = reachin( expr.splitPath, record, expr.operation );
  649. }
  650. result = operations[expr.operation]( expr, test, record );
  651. if ( result ) {
  652. arrayResults.push( record );
  653. keyResults.push( key );
  654. }
  655. if ( !result && shortCircuit ) {
  656. break;
  657. }
  658. }
  659. if ( arrayResults.length > 0 && stopOnFirst ) {
  660. return false;
  661. }
  662. } );
  663. return {
  664. arrayResults : arrayResults,
  665. keyResults : keyResults
  666. };
  667. }
  668. /**
  669. Updates all records in obj that match the query. See {@link module:documents/probe.updateOperators} for the operators that are supported.
  670. @param {object|array} obj The object to update
  671. @param {object} qu The query which will be used to identify the records to updated
  672. @param {object} setDocument The update operator. See {@link module:documents/probe.updateOperators}
  673. */
  674. exports.update = function ( obj, qu, setDocument ) {
  675. var records = exports.find( obj, qu );
  676. return sys.each( records, function ( record ) {
  677. return sys.each( setDocument, function ( fields, operator ) {
  678. return sys.each( fields, function ( newValue, path ) {
  679. return pushin( splitPath( path ), record, operator, newValue );
  680. } );
  681. } );
  682. } );
  683. };
  684. /**
  685. Find all records that match a query
  686. @param {array|object} obj The object to query
  687. @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use.
  688. @returns {array} The results
  689. **/
  690. exports.find = function ( obj, qu ) {
  691. var expression, _i, _len;
  692. var query = parseQueryExpression( qu );
  693. for ( _i = 0, _len = query.length; _i < _len; _i++ ) {
  694. expression = query[_i];
  695. if ( expression.path ) {
  696. expression.splitPath = splitPath( expression.path );
  697. }
  698. }
  699. return execQuery( obj, query ).arrayResults;
  700. };
  701. /**
  702. Find all records that match a query and returns the keys for those items. This is similar to {@link module:documents/probe.find} but instead of returning
  703. records, returns the keys. If `obj` is an object it will return the hash key. If 'obj' is an array, it will return the index
  704. @param {array|object} obj The object to query
  705. @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use.
  706. @returns {array}
  707. */
  708. exports.findKeys = function ( obj, qu ) {
  709. var expression, _i, _len;
  710. var query = parseQueryExpression( qu );
  711. for ( _i = 0, _len = query.length; _i < _len; _i++ ) {
  712. expression = query[_i];
  713. if ( expression.path ) {
  714. expression.splitPath = splitPath( expression.path );
  715. }
  716. }
  717. return execQuery( obj, query ).keyResults;
  718. };
  719. /**
  720. Returns the first record that matches the query. Aliased as `seek`.
  721. @param {array|object} obj The object to query
  722. @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use.
  723. @returns {object}
  724. */
  725. exports.findOne = function ( obj, qu ) {
  726. var expression, _i, _len;
  727. var query = parseQueryExpression( qu );
  728. for ( _i = 0, _len = query.length; _i < _len; _i++ ) {
  729. expression = query[_i];
  730. if ( expression.path ) {
  731. expression.splitPath = splitPath( expression.path );
  732. }
  733. }
  734. var results = execQuery( obj, query, false, true ).arrayResults;
  735. if ( results.length > 0 ) {
  736. return results[0];
  737. } else {
  738. return null;
  739. }
  740. };
  741. exports.seek = exports.findOne;
  742. /**
  743. Returns the first record that matches the query and returns its key or index depending on whether `obj` is an object or array respectively.
  744. Aliased as `seekKey`.
  745. @param {array|object} obj The object to query
  746. @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use.
  747. @returns {object}
  748. */
  749. exports.findOneKey = function ( obj, qu ) {
  750. var expression, _i, _len;
  751. var query = parseQueryExpression( qu );
  752. for ( _i = 0, _len = query.length; _i < _len; _i++ ) {
  753. expression = query[_i];
  754. if ( expression.path ) {
  755. expression.splitPath = splitPath( expression.path );
  756. }
  757. }
  758. var results = execQuery( obj, query, false, true ).keyResults;
  759. if ( results.length > 0 ) {
  760. return results[0];
  761. } else {
  762. return null;
  763. }
  764. };
  765. exports.seekKey = exports.findOneKey;
  766. /**
  767. Remove all items in the object/array that match the query
  768. @param {array|object} obj The object to query
  769. @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use.
  770. @return {object|array} The array or object as appropriate without the records.
  771. **/
  772. exports.remove = function ( obj, qu ) {
  773. var expression, _i, _len;
  774. var query = parseQueryExpression( qu );
  775. for ( _i = 0, _len = query.length; _i < _len; _i++ ) {
  776. expression = query[_i];
  777. if ( expression.path ) {
  778. expression.splitPath = splitPath( expression.path );
  779. }
  780. }
  781. var results = execQuery( obj, query, false, false ).keyResults;
  782. if ( sys.isArray( obj ) ) {
  783. var newArr = [];
  784. sys.each( obj, function ( item, index ) {
  785. if ( sys.indexOf( results, index ) === -1 ) {
  786. return newArr.push( item );
  787. }
  788. } );
  789. return newArr;
  790. } else {
  791. sys.each( results, function ( key ) {
  792. return delete obj[key];
  793. } );
  794. return obj;
  795. }
  796. };
  797. /**
  798. Returns true if all items match the query
  799. @param {array|object} obj The object to query
  800. @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use.
  801. @returns {boolean}
  802. **/
  803. exports.all = function ( obj, qu ) {
  804. return exports.find( obj, qu ).length === sys.size( obj );
  805. };
  806. /**
  807. Returns true if any of the items match the query
  808. @param {array|object} obj The object to query
  809. @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use.
  810. @returns {boolean}
  811. **/
  812. exports.any = function ( obj, qu ) {
  813. var expression, _i, _len;
  814. var query = parseQueryExpression( qu );
  815. for ( _i = 0, _len = query.length; _i < _len; _i++ ) {
  816. expression = query[_i];
  817. if ( expression.path ) {
  818. expression.splitPath = splitPath( expression.path );
  819. }
  820. }
  821. var results = execQuery( obj, query, true, true ).keyResults;
  822. return results.length > 0;
  823. };
  824. /**
  825. Returns the set of unique records that match a query
  826. @param {array|object} obj The object to query
  827. @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use.
  828. @return {array}
  829. **/
  830. exports.unique = function ( obj, qu ) {
  831. var test = exports.find( obj, qu );
  832. return sys.unique( test, function ( item ) {
  833. return JSON.stringify( item );
  834. } );
  835. };
  836. /**
  837. This will write the value into a record at the path, creating intervening objects if they don't exist. This does not work as filtered
  838. update and is meant to be used on a single record. It is a nice way of setting a property at an arbitrary depth at will.
  839. @param {array} path The split path of the element to work with
  840. @param {object} record The record to reach into
  841. @param {string} setter The set operation. See {@link module:documents/probe.updateOperators} for the operators you can use.
  842. @param {object} newValue The value to write to the, or if the operator is $pull, the query of items to look for
  843. */
  844. exports.set = function ( record, path, setter, newValue ) {
  845. return pushin( splitPath( path ), record, setter, newValue );
  846. };
  847. /**
  848. Reaches into an object and allows you to get at a value deeply nested in an object. This is not a query, but a
  849. straight reach in, useful for event bindings
  850. @param {array} path The split path of the element to work with
  851. @param {object} record The record to reach into
  852. @return {*} Whatever was found in the record
  853. **/
  854. exports.get = function ( record, path ) {
  855. return reachin( splitPath( path ), record );
  856. };
  857. /**
  858. Returns true if any of the items match the query. Aliases as `any`
  859. @function
  860. @param {array|object} obj The object to query
  861. @param {object} qu The query to execute
  862. @returns {boolean}
  863. */
  864. exports.some = exports.any;
  865. /**
  866. Returns true if all items match the query. Aliases as `all`
  867. @function
  868. @param {array|object} obj The object to query
  869. @param {object} qu The query to execute
  870. @returns {boolean}
  871. */
  872. exports.every = exports.all;
  873. var bindables = {
  874. any : exports.any,
  875. all : exports.all,
  876. remove : exports.remove,
  877. seekKey : exports.seekKey,
  878. seek : exports.seek,
  879. findOneKey : exports.findOneKey,
  880. findOne : exports.findOne,
  881. findKeys : exports.findKeys,
  882. find : exports.find,
  883. update : exports.update,
  884. some : exports.some,
  885. every : exports.every,
  886. "get" : exports.get,
  887. "set" : exports.set
  888. };
  889. /**
  890. Binds the query and update methods to a new object. When called these
  891. methods can skip the first parameter so that find(object, query) can just be called as find(query)
  892. @param {object|array} obj The object or array to bind to
  893. @return {object} An object with method bindings in place
  894. **/
  895. exports.proxy = function ( obj ) {
  896. var retVal;
  897. retVal = {};
  898. sys.each( bindables, function ( val, key ) {
  899. retVal[key] = sys.bind( val, obj, obj );
  900. } );
  901. return retVal;
  902. };
  903. /**
  904. Binds the query and update methods to a specific object and adds the methods to that object. When called these
  905. methods can skip the first parameter so that find(object, query) can just be called as object.find(query)
  906. @param {object|array} obj The object or array to bind to
  907. @param {object|array=} collection If the collection is not the same as <code>this</code> but is a property, or even
  908. a whole other object, you specify that here. Otherwise the <code>obj</code> is assumed to be the same as the collecion
  909. **/
  910. exports.mixin = function ( obj, collection ) {
  911. collection = collection || obj;
  912. return sys.each( bindables, function ( val, key ) {
  913. obj[key] = sys.bind( val, obj, collection );
  914. } );
  915. };
  916. /**
  917. * These are the supported query operators
  918. *
  919. * @memberOf module:documents/probe
  920. * @name queryOperators
  921. * @class This is not actually a class, but an artifact of the documentation system
  922. */
  923. /**
  924. * These are the supported update operators
  925. *
  926. * @memberOf module:documents/probe
  927. * @name updateOperators
  928. * @class This is not actually a class, but an artifact of the documentation system
  929. */