mquery.js 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748
  1. 'use strict';
  2. /**
  3. * Dependencies
  4. */
  5. const assert = require('assert');
  6. const util = require('util');
  7. const utils = require('./utils');
  8. const debug = util.debuglog('mquery');
  9. /**
  10. * Query constructor used for building queries.
  11. *
  12. * #### Example:
  13. *
  14. * var query = new Query({ name: 'mquery' });
  15. * query.setOptions({ collection: moduleCollection })
  16. * await query.where('age').gte(21).exec();
  17. *
  18. * @param {Object} [criteria] criteria for the query OR the collection instance to use
  19. * @param {Object} [options]
  20. * @api public
  21. */
  22. function Query(criteria, options) {
  23. if (!(this instanceof Query))
  24. return new Query(criteria, options);
  25. const proto = this.constructor.prototype;
  26. this.op = proto.op || undefined;
  27. this.options = Object.assign({}, proto.options);
  28. this._conditions = proto._conditions
  29. ? utils.clone(proto._conditions)
  30. : {};
  31. this._fields = proto._fields
  32. ? utils.clone(proto._fields)
  33. : undefined;
  34. this._updateDoc = proto._updateDoc
  35. ? utils.clone(proto._updateDoc)
  36. : undefined;
  37. this._path = proto._path || undefined;
  38. this._distinctDoc = proto._distinctDoc || undefined;
  39. this._collection = proto._collection || undefined;
  40. this._traceFunction = proto._traceFunction || undefined;
  41. if (options) {
  42. this.setOptions(options);
  43. }
  44. if (criteria) {
  45. this.find(criteria);
  46. }
  47. }
  48. /**
  49. * Converts this query to a constructor function with all arguments and options retained.
  50. *
  51. * #### Example:
  52. *
  53. * // Create a query that will read documents with a "video" category from
  54. * // `aCollection` on the primary node in the replica-set unless it is down,
  55. * // in which case we'll read from a secondary node.
  56. * var query = mquery({ category: 'video' })
  57. * query.setOptions({ collection: aCollection, read: 'primaryPreferred' });
  58. *
  59. * // create a constructor based off these settings
  60. * var Video = query.toConstructor();
  61. *
  62. * // Video is now a subclass of mquery() and works the same way but with the
  63. * // default query parameters and options set.
  64. *
  65. * // run a query with the previous settings but filter for movies with names
  66. * // that start with "Life".
  67. * Video().where({ name: /^Life/ }).exec(cb);
  68. *
  69. * @return {Query} new Query
  70. * @api public
  71. */
  72. Query.prototype.toConstructor = function toConstructor() {
  73. function CustomQuery(criteria, options) {
  74. if (!(this instanceof CustomQuery))
  75. return new CustomQuery(criteria, options);
  76. Query.call(this, criteria, options);
  77. }
  78. utils.inherits(CustomQuery, Query);
  79. // set inherited defaults
  80. const p = CustomQuery.prototype;
  81. p.options = {};
  82. p.setOptions(this.options);
  83. p.op = this.op;
  84. p._conditions = utils.clone(this._conditions);
  85. p._fields = utils.clone(this._fields);
  86. p._updateDoc = utils.clone(this._updateDoc);
  87. p._path = this._path;
  88. p._distinctDoc = this._distinctDoc;
  89. p._collection = this._collection;
  90. p._traceFunction = this._traceFunction;
  91. return CustomQuery;
  92. };
  93. /**
  94. * Sets query options.
  95. *
  96. * #### Options:
  97. *
  98. * - [tailable](http://www.mongodb.org/display/DOCS/Tailable+Cursors) *
  99. * - [sort](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsort(\)%7D%7D) *
  100. * - [limit](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D) *
  101. * - [skip](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D) *
  102. * - [maxTime](http://docs.mongodb.org/manual/reference/operator/meta/maxTimeMS/#op._S_maxTimeMS) *
  103. * - [batchSize](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D) *
  104. * - [comment](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment) *
  105. * - [hint](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint) *
  106. * - [slaveOk](http://docs.mongodb.org/manual/applications/replication/#read-preference) *
  107. * - [safe](http://www.mongodb.org/display/DOCS/getLastError+Command)
  108. * - collection the collection to query against
  109. *
  110. * _* denotes a query helper method is also available_
  111. *
  112. * @param {Object} options
  113. * @api public
  114. */
  115. Query.prototype.setOptions = function(options) {
  116. if (!(options && utils.isObject(options)))
  117. return this;
  118. // set arbitrary options
  119. const methods = utils.keys(options);
  120. let method;
  121. for (let i = 0; i < methods.length; ++i) {
  122. method = methods[i];
  123. // use methods if exist (safer option manipulation)
  124. if ('function' == typeof this[method]) {
  125. const args = Array.isArray(options[method])
  126. ? options[method]
  127. : [options[method]];
  128. this[method].apply(this, args);
  129. } else {
  130. this.options[method] = options[method];
  131. }
  132. }
  133. return this;
  134. };
  135. /**
  136. * Sets this Querys collection.
  137. *
  138. * @param {Collection} coll
  139. * @return {Query} this
  140. */
  141. Query.prototype.collection = function collection(coll) {
  142. this._collection = new Query.Collection(coll);
  143. return this;
  144. };
  145. /**
  146. * Adds a collation to this op (MongoDB 3.4 and up)
  147. *
  148. * #### Example:
  149. *
  150. * query.find().collation({ locale: "en_US", strength: 1 })
  151. *
  152. * @param {Object} value
  153. * @return {Query} this
  154. * @see MongoDB docs https://docs.mongodb.com/manual/reference/method/cursor.collation/#cursor.collation
  155. * @api public
  156. */
  157. Query.prototype.collation = function(value) {
  158. this.options.collation = value;
  159. return this;
  160. };
  161. /**
  162. * Specifies a `$where` condition
  163. *
  164. * Use `$where` when you need to select documents using a JavaScript expression.
  165. *
  166. * #### Example:
  167. *
  168. * query.$where('this.comments.length > 10 || this.name.length > 5')
  169. *
  170. * query.$where(function () {
  171. * return this.comments.length > 10 || this.name.length > 5;
  172. * })
  173. *
  174. * @param {String|Function} js javascript string or function
  175. * @return {Query} this
  176. * @memberOf Query
  177. * @method $where
  178. * @api public
  179. */
  180. Query.prototype.$where = function(js) {
  181. this._conditions.$where = js;
  182. return this;
  183. };
  184. /**
  185. * Specifies a `path` for use with chaining.
  186. *
  187. * #### Example:
  188. *
  189. * // instead of writing:
  190. * await User.find({age: {$gte: 21, $lte: 65}});
  191. *
  192. * // we can instead write:
  193. * User.where('age').gte(21).lte(65);
  194. *
  195. * // passing query conditions is permitted
  196. * User.find().where({ name: 'vonderful' })
  197. *
  198. * // chaining
  199. * await User
  200. * .where('age').gte(21).lte(65)
  201. * .where('name', /^vonderful/i)
  202. * .where('friends').slice(10)
  203. * .exec()
  204. *
  205. * @param {String} [path]
  206. * @param {Object} [val]
  207. * @return {Query} this
  208. * @api public
  209. */
  210. Query.prototype.where = function() {
  211. if (!arguments.length) return this;
  212. if (!this.op) this.op = 'find';
  213. const type = typeof arguments[0];
  214. if ('string' == type) {
  215. this._path = arguments[0];
  216. if (2 === arguments.length) {
  217. this._conditions[this._path] = arguments[1];
  218. }
  219. return this;
  220. }
  221. if ('object' == type && !Array.isArray(arguments[0])) {
  222. return this.merge(arguments[0]);
  223. }
  224. throw new TypeError('path must be a string or object');
  225. };
  226. /**
  227. * Specifies the complementary comparison value for paths specified with `where()`
  228. *
  229. * #### Example:
  230. *
  231. * User.where('age').equals(49);
  232. *
  233. * // is the same as
  234. *
  235. * User.where('age', 49);
  236. *
  237. * @param {Object} val
  238. * @return {Query} this
  239. * @api public
  240. */
  241. Query.prototype.equals = function equals(val) {
  242. this._ensurePath('equals');
  243. const path = this._path;
  244. this._conditions[path] = val;
  245. return this;
  246. };
  247. /**
  248. * Specifies the complementary comparison value for paths specified with `where()`
  249. * This is alias of `equals`
  250. *
  251. * #### Example:
  252. *
  253. * User.where('age').eq(49);
  254. *
  255. * // is the same as
  256. *
  257. * User.shere('age').equals(49);
  258. *
  259. * // is the same as
  260. *
  261. * User.where('age', 49);
  262. *
  263. * @param {Object} val
  264. * @return {Query} this
  265. * @api public
  266. */
  267. Query.prototype.eq = function eq(val) {
  268. this._ensurePath('eq');
  269. const path = this._path;
  270. this._conditions[path] = val;
  271. return this;
  272. };
  273. /**
  274. * Specifies arguments for an `$or` condition.
  275. *
  276. * #### Example:
  277. *
  278. * query.or([{ color: 'red' }, { status: 'emergency' }])
  279. *
  280. * @param {Array} array array of conditions
  281. * @return {Query} this
  282. * @api public
  283. */
  284. Query.prototype.or = function or(array) {
  285. const or = this._conditions.$or || (this._conditions.$or = []);
  286. if (!Array.isArray(array)) array = [array];
  287. or.push.apply(or, array);
  288. return this;
  289. };
  290. /**
  291. * Specifies arguments for a `$nor` condition.
  292. *
  293. * #### Example:
  294. *
  295. * query.nor([{ color: 'green' }, { status: 'ok' }])
  296. *
  297. * @param {Array} array array of conditions
  298. * @return {Query} this
  299. * @api public
  300. */
  301. Query.prototype.nor = function nor(array) {
  302. const nor = this._conditions.$nor || (this._conditions.$nor = []);
  303. if (!Array.isArray(array)) array = [array];
  304. nor.push.apply(nor, array);
  305. return this;
  306. };
  307. /**
  308. * Specifies arguments for a `$and` condition.
  309. *
  310. * #### Example:
  311. *
  312. * query.and([{ color: 'green' }, { status: 'ok' }])
  313. *
  314. * @see $and http://docs.mongodb.org/manual/reference/operator/and/
  315. * @param {Array} array array of conditions
  316. * @return {Query} this
  317. * @api public
  318. */
  319. Query.prototype.and = function and(array) {
  320. const and = this._conditions.$and || (this._conditions.$and = []);
  321. if (!Array.isArray(array)) array = [array];
  322. and.push.apply(and, array);
  323. return this;
  324. };
  325. /**
  326. * Specifies a $gt query condition.
  327. *
  328. * When called with one argument, the most recent path passed to `where()` is used.
  329. *
  330. * #### Example:
  331. *
  332. * Thing.find().where('age').gt(21)
  333. *
  334. * // or
  335. * Thing.find().gt('age', 21)
  336. *
  337. * @method gt
  338. * @memberOf Query
  339. * @param {String} [path]
  340. * @param {Number} val
  341. * @api public
  342. */
  343. /**
  344. * Specifies a $gte query condition.
  345. *
  346. * When called with one argument, the most recent path passed to `where()` is used.
  347. *
  348. * @method gte
  349. * @memberOf Query
  350. * @param {String} [path]
  351. * @param {Number} val
  352. * @api public
  353. */
  354. /**
  355. * Specifies a $lt query condition.
  356. *
  357. * When called with one argument, the most recent path passed to `where()` is used.
  358. *
  359. * @method lt
  360. * @memberOf Query
  361. * @param {String} [path]
  362. * @param {Number} val
  363. * @api public
  364. */
  365. /**
  366. * Specifies a $lte query condition.
  367. *
  368. * When called with one argument, the most recent path passed to `where()` is used.
  369. *
  370. * @method lte
  371. * @memberOf Query
  372. * @param {String} [path]
  373. * @param {Number} val
  374. * @api public
  375. */
  376. /**
  377. * Specifies a $ne query condition.
  378. *
  379. * When called with one argument, the most recent path passed to `where()` is used.
  380. *
  381. * @method ne
  382. * @memberOf Query
  383. * @param {String} [path]
  384. * @param {Number} val
  385. * @api public
  386. */
  387. /**
  388. * Specifies an $in query condition.
  389. *
  390. * When called with one argument, the most recent path passed to `where()` is used.
  391. *
  392. * @method in
  393. * @memberOf Query
  394. * @param {String} [path]
  395. * @param {Number} val
  396. * @api public
  397. */
  398. /**
  399. * Specifies an $nin query condition.
  400. *
  401. * When called with one argument, the most recent path passed to `where()` is used.
  402. *
  403. * @method nin
  404. * @memberOf Query
  405. * @param {String} [path]
  406. * @param {Number} val
  407. * @api public
  408. */
  409. /**
  410. * Specifies an $all query condition.
  411. *
  412. * When called with one argument, the most recent path passed to `where()` is used.
  413. *
  414. * @method all
  415. * @memberOf Query
  416. * @param {String} [path]
  417. * @param {Number} val
  418. * @api public
  419. */
  420. /**
  421. * Specifies a $size query condition.
  422. *
  423. * When called with one argument, the most recent path passed to `where()` is used.
  424. *
  425. * @method size
  426. * @memberOf Query
  427. * @param {String} [path]
  428. * @param {Number} val
  429. * @api public
  430. */
  431. /**
  432. * Specifies a $regex query condition.
  433. *
  434. * When called with one argument, the most recent path passed to `where()` is used.
  435. *
  436. * @method regex
  437. * @memberOf Query
  438. * @param {String} [path]
  439. * @param {String|RegExp} val
  440. * @api public
  441. */
  442. /**
  443. * Specifies a $maxDistance query condition.
  444. *
  445. * When called with one argument, the most recent path passed to `where()` is used.
  446. *
  447. * @method maxDistance
  448. * @memberOf Query
  449. * @param {String} [path]
  450. * @param {Number} val
  451. * @api public
  452. */
  453. /*!
  454. * gt, gte, lt, lte, ne, in, nin, all, regex, size, maxDistance
  455. *
  456. * Thing.where('type').nin(array)
  457. */
  458. 'gt gte lt lte ne in nin all regex size maxDistance minDistance'.split(' ').forEach(function($conditional) {
  459. Query.prototype[$conditional] = function() {
  460. let path, val;
  461. if (1 === arguments.length) {
  462. this._ensurePath($conditional);
  463. val = arguments[0];
  464. path = this._path;
  465. } else {
  466. val = arguments[1];
  467. path = arguments[0];
  468. }
  469. const conds = this._conditions[path] === null || typeof this._conditions[path] === 'object' ?
  470. this._conditions[path] :
  471. (this._conditions[path] = {});
  472. conds['$' + $conditional] = val;
  473. return this;
  474. };
  475. });
  476. /**
  477. * Specifies a `$mod` condition
  478. *
  479. * @param {String} [path]
  480. * @param {Number} val
  481. * @return {Query} this
  482. * @api public
  483. */
  484. Query.prototype.mod = function() {
  485. let val, path;
  486. if (1 === arguments.length) {
  487. this._ensurePath('mod');
  488. val = arguments[0];
  489. path = this._path;
  490. } else if (2 === arguments.length && !Array.isArray(arguments[1])) {
  491. this._ensurePath('mod');
  492. val = [arguments[0], arguments[1]];
  493. path = this._path;
  494. } else if (3 === arguments.length) {
  495. val = [arguments[1], arguments[2]];
  496. path = arguments[0];
  497. } else {
  498. val = arguments[1];
  499. path = arguments[0];
  500. }
  501. const conds = this._conditions[path] || (this._conditions[path] = {});
  502. conds.$mod = val;
  503. return this;
  504. };
  505. /**
  506. * Specifies an `$exists` condition
  507. *
  508. * #### Example:
  509. *
  510. * // { name: { $exists: true }}
  511. * Thing.where('name').exists()
  512. * Thing.where('name').exists(true)
  513. * Thing.find().exists('name')
  514. *
  515. * // { name: { $exists: false }}
  516. * Thing.where('name').exists(false);
  517. * Thing.find().exists('name', false);
  518. *
  519. * @param {String} [path]
  520. * @param {Number} val
  521. * @return {Query} this
  522. * @api public
  523. */
  524. Query.prototype.exists = function() {
  525. let path, val;
  526. if (0 === arguments.length) {
  527. this._ensurePath('exists');
  528. path = this._path;
  529. val = true;
  530. } else if (1 === arguments.length) {
  531. if ('boolean' === typeof arguments[0]) {
  532. this._ensurePath('exists');
  533. path = this._path;
  534. val = arguments[0];
  535. } else {
  536. path = arguments[0];
  537. val = true;
  538. }
  539. } else if (2 === arguments.length) {
  540. path = arguments[0];
  541. val = arguments[1];
  542. }
  543. const conds = this._conditions[path] || (this._conditions[path] = {});
  544. conds.$exists = val;
  545. return this;
  546. };
  547. /**
  548. * Specifies an `$elemMatch` condition
  549. *
  550. * #### Example:
  551. *
  552. * query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}})
  553. *
  554. * query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}})
  555. *
  556. * query.elemMatch('comment', function (elem) {
  557. * elem.where('author').equals('autobot');
  558. * elem.where('votes').gte(5);
  559. * })
  560. *
  561. * query.where('comment').elemMatch(function (elem) {
  562. * elem.where({ author: 'autobot' });
  563. * elem.where('votes').gte(5);
  564. * })
  565. *
  566. * @param {String|Object|Function} path
  567. * @param {Object|Function} criteria
  568. * @return {Query} this
  569. * @api public
  570. */
  571. Query.prototype.elemMatch = function() {
  572. if (null == arguments[0])
  573. throw new TypeError('Invalid argument');
  574. let fn, path, criteria;
  575. if ('function' === typeof arguments[0]) {
  576. this._ensurePath('elemMatch');
  577. path = this._path;
  578. fn = arguments[0];
  579. } else if (utils.isObject(arguments[0])) {
  580. this._ensurePath('elemMatch');
  581. path = this._path;
  582. criteria = arguments[0];
  583. } else if ('function' === typeof arguments[1]) {
  584. path = arguments[0];
  585. fn = arguments[1];
  586. } else if (arguments[1] && utils.isObject(arguments[1])) {
  587. path = arguments[0];
  588. criteria = arguments[1];
  589. } else {
  590. throw new TypeError('Invalid argument');
  591. }
  592. if (fn) {
  593. criteria = new Query;
  594. fn(criteria);
  595. criteria = criteria._conditions;
  596. }
  597. const conds = this._conditions[path] || (this._conditions[path] = {});
  598. conds.$elemMatch = criteria;
  599. return this;
  600. };
  601. // Spatial queries
  602. /**
  603. * Sugar for geo-spatial queries.
  604. *
  605. * #### Example:
  606. *
  607. * query.within().box()
  608. * query.within().circle()
  609. * query.within().geometry()
  610. *
  611. * query.where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true });
  612. * query.where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] });
  613. * query.where('loc').within({ polygon: [[],[],[],[]] });
  614. *
  615. * query.where('loc').within([], [], []) // polygon
  616. * query.where('loc').within([], []) // box
  617. * query.where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry
  618. *
  619. * #### Note:
  620. *
  621. * Must be used after `where()`.
  622. *
  623. * @memberOf Query
  624. * @return {Query} this
  625. * @api public
  626. */
  627. Query.prototype.within = function within() {
  628. // opinionated, must be used after where
  629. this._ensurePath('within');
  630. this._geoComparison = '$geoWithin';
  631. if (0 === arguments.length) {
  632. return this;
  633. }
  634. if (2 === arguments.length) {
  635. return this.box.apply(this, arguments);
  636. } else if (2 < arguments.length) {
  637. return this.polygon.apply(this, arguments);
  638. }
  639. const area = arguments[0];
  640. if (!area)
  641. throw new TypeError('Invalid argument');
  642. if (area.center)
  643. return this.circle(area);
  644. if (area.box)
  645. return this.box.apply(this, area.box);
  646. if (area.polygon)
  647. return this.polygon.apply(this, area.polygon);
  648. if (area.type && area.coordinates)
  649. return this.geometry(area);
  650. throw new TypeError('Invalid argument');
  651. };
  652. /**
  653. * Specifies a $box condition
  654. *
  655. * #### Example:
  656. *
  657. * var lowerLeft = [40.73083, -73.99756]
  658. * var upperRight= [40.741404, -73.988135]
  659. *
  660. * query.where('loc').within().box(lowerLeft, upperRight)
  661. * query.box('loc', lowerLeft, upperRight )
  662. *
  663. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  664. * @see Query#within #query_Query-within
  665. * @param {String} path
  666. * @param {Object} val
  667. * @return {Query} this
  668. * @api public
  669. */
  670. Query.prototype.box = function() {
  671. let path, box;
  672. if (3 === arguments.length) {
  673. // box('loc', [], [])
  674. path = arguments[0];
  675. box = [arguments[1], arguments[2]];
  676. } else if (2 === arguments.length) {
  677. // box([], [])
  678. this._ensurePath('box');
  679. path = this._path;
  680. box = [arguments[0], arguments[1]];
  681. } else {
  682. throw new TypeError('Invalid argument');
  683. }
  684. const conds = this._conditions[path] || (this._conditions[path] = {});
  685. conds[this._geoComparison || '$geoWithin'] = { $box: box };
  686. return this;
  687. };
  688. /**
  689. * Specifies a $polygon condition
  690. *
  691. * #### Example:
  692. *
  693. * query.where('loc').within().polygon([10,20], [13, 25], [7,15])
  694. * query.polygon('loc', [10,20], [13, 25], [7,15])
  695. *
  696. * @param {String|Array} [path]
  697. * @param {Array|Object} [val]
  698. * @return {Query} this
  699. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  700. * @api public
  701. */
  702. Query.prototype.polygon = function() {
  703. let val, path;
  704. if ('string' == typeof arguments[0]) {
  705. // polygon('loc', [],[],[])
  706. val = Array.from(arguments);
  707. path = val.shift();
  708. } else {
  709. // polygon([],[],[])
  710. this._ensurePath('polygon');
  711. path = this._path;
  712. val = Array.from(arguments);
  713. }
  714. const conds = this._conditions[path] || (this._conditions[path] = {});
  715. conds[this._geoComparison || '$geoWithin'] = { $polygon: val };
  716. return this;
  717. };
  718. /**
  719. * Specifies a $center or $centerSphere condition.
  720. *
  721. * #### Example:
  722. *
  723. * var area = { center: [50, 50], radius: 10, unique: true }
  724. * query.where('loc').within().circle(area)
  725. * query.center('loc', area);
  726. *
  727. * // for spherical calculations
  728. * var area = { center: [50, 50], radius: 10, unique: true, spherical: true }
  729. * query.where('loc').within().circle(area)
  730. * query.center('loc', area);
  731. *
  732. * @param {String} [path]
  733. * @param {Object} area
  734. * @return {Query} this
  735. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  736. * @api public
  737. */
  738. Query.prototype.circle = function() {
  739. let path, val;
  740. if (1 === arguments.length) {
  741. this._ensurePath('circle');
  742. path = this._path;
  743. val = arguments[0];
  744. } else if (2 === arguments.length) {
  745. path = arguments[0];
  746. val = arguments[1];
  747. } else {
  748. throw new TypeError('Invalid argument');
  749. }
  750. if (!('radius' in val && val.center))
  751. throw new Error('center and radius are required');
  752. const conds = this._conditions[path] || (this._conditions[path] = {});
  753. const type = val.spherical
  754. ? '$centerSphere'
  755. : '$center';
  756. const wKey = this._geoComparison || '$geoWithin';
  757. conds[wKey] = {};
  758. conds[wKey][type] = [val.center, val.radius];
  759. if ('unique' in val)
  760. conds[wKey].$uniqueDocs = !!val.unique;
  761. return this;
  762. };
  763. /**
  764. * Specifies a `$near` or `$nearSphere` condition
  765. *
  766. * These operators return documents sorted by distance.
  767. *
  768. * #### Example:
  769. *
  770. * query.where('loc').near({ center: [10, 10] });
  771. * query.where('loc').near({ center: [10, 10], maxDistance: 5 });
  772. * query.where('loc').near({ center: [10, 10], maxDistance: 5, spherical: true });
  773. * query.near('loc', { center: [10, 10], maxDistance: 5 });
  774. * query.near({ center: { type: 'Point', coordinates: [..] }})
  775. * query.near().geometry({ type: 'Point', coordinates: [..] })
  776. *
  777. * @param {String} [path]
  778. * @param {Object} val
  779. * @return {Query} this
  780. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  781. * @api public
  782. */
  783. Query.prototype.near = function near() {
  784. let path, val;
  785. this._geoComparison = '$near';
  786. if (0 === arguments.length) {
  787. return this;
  788. } else if (1 === arguments.length) {
  789. this._ensurePath('near');
  790. path = this._path;
  791. val = arguments[0];
  792. } else if (2 === arguments.length) {
  793. path = arguments[0];
  794. val = arguments[1];
  795. } else {
  796. throw new TypeError('Invalid argument');
  797. }
  798. if (!val.center) {
  799. throw new Error('center is required');
  800. }
  801. const conds = this._conditions[path] || (this._conditions[path] = {});
  802. const type = val.spherical
  803. ? '$nearSphere'
  804. : '$near';
  805. // center could be a GeoJSON object or an Array
  806. if (Array.isArray(val.center)) {
  807. conds[type] = val.center;
  808. const radius = 'maxDistance' in val
  809. ? val.maxDistance
  810. : null;
  811. if (null != radius) {
  812. conds.$maxDistance = radius;
  813. }
  814. if (null != val.minDistance) {
  815. conds.$minDistance = val.minDistance;
  816. }
  817. } else {
  818. // GeoJSON?
  819. if (val.center.type != 'Point' || !Array.isArray(val.center.coordinates)) {
  820. throw new Error(util.format('Invalid GeoJSON specified for %s', type));
  821. }
  822. conds[type] = { $geometry: val.center };
  823. // MongoDB 2.6 insists on maxDistance being in $near / $nearSphere
  824. if ('maxDistance' in val) {
  825. conds[type]['$maxDistance'] = val.maxDistance;
  826. }
  827. if ('minDistance' in val) {
  828. conds[type]['$minDistance'] = val.minDistance;
  829. }
  830. }
  831. return this;
  832. };
  833. /**
  834. * Declares an intersects query for `geometry()`.
  835. *
  836. * #### Example:
  837. *
  838. * query.where('path').intersects().geometry({
  839. * type: 'LineString'
  840. * , coordinates: [[180.0, 11.0], [180, 9.0]]
  841. * })
  842. *
  843. * query.where('path').intersects({
  844. * type: 'LineString'
  845. * , coordinates: [[180.0, 11.0], [180, 9.0]]
  846. * })
  847. *
  848. * @param {Object} [arg]
  849. * @return {Query} this
  850. * @api public
  851. */
  852. Query.prototype.intersects = function intersects() {
  853. // opinionated, must be used after where
  854. this._ensurePath('intersects');
  855. this._geoComparison = '$geoIntersects';
  856. if (0 === arguments.length) {
  857. return this;
  858. }
  859. const area = arguments[0];
  860. if (null != area && area.type && area.coordinates)
  861. return this.geometry(area);
  862. throw new TypeError('Invalid argument');
  863. };
  864. /**
  865. * Specifies a `$geometry` condition
  866. *
  867. * #### Example:
  868. *
  869. * var polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]]
  870. * query.where('loc').within().geometry({ type: 'Polygon', coordinates: polyA })
  871. *
  872. * // or
  873. * var polyB = [[ 0, 0 ], [ 1, 1 ]]
  874. * query.where('loc').within().geometry({ type: 'LineString', coordinates: polyB })
  875. *
  876. * // or
  877. * var polyC = [ 0, 0 ]
  878. * query.where('loc').within().geometry({ type: 'Point', coordinates: polyC })
  879. *
  880. * // or
  881. * query.where('loc').intersects().geometry({ type: 'Point', coordinates: polyC })
  882. *
  883. * #### Note:
  884. *
  885. * `geometry()` **must** come after either `intersects()` or `within()`.
  886. *
  887. * The `object` argument must contain `type` and `coordinates` properties.
  888. * - type {String}
  889. * - coordinates {Array}
  890. *
  891. * The most recent path passed to `where()` is used.
  892. *
  893. * @param {Object} object Must contain a `type` property which is a String and a `coordinates` property which is an Array. See the examples.
  894. * @return {Query} this
  895. * @see http://docs.mongodb.org/manual/release-notes/2.4/#new-geospatial-indexes-with-geojson-and-improved-spherical-geometry
  896. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  897. * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/
  898. * @api public
  899. */
  900. Query.prototype.geometry = function geometry() {
  901. if (!('$within' == this._geoComparison ||
  902. '$geoWithin' == this._geoComparison ||
  903. '$near' == this._geoComparison ||
  904. '$geoIntersects' == this._geoComparison)) {
  905. throw new Error('geometry() must come after `within()`, `intersects()`, or `near()');
  906. }
  907. let val, path;
  908. if (1 === arguments.length) {
  909. this._ensurePath('geometry');
  910. path = this._path;
  911. val = arguments[0];
  912. } else {
  913. throw new TypeError('Invalid argument');
  914. }
  915. if (!(val.type && Array.isArray(val.coordinates))) {
  916. throw new TypeError('Invalid argument');
  917. }
  918. const conds = this._conditions[path] || (this._conditions[path] = {});
  919. conds[this._geoComparison] = { $geometry: val };
  920. return this;
  921. };
  922. // end spatial
  923. /**
  924. * Specifies which document fields to include or exclude
  925. *
  926. * #### String syntax
  927. *
  928. * When passing a string, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included.
  929. *
  930. * #### Example:
  931. *
  932. * // include a and b, exclude c
  933. * query.select('a b -c');
  934. *
  935. * // or you may use object notation, useful when
  936. * // you have keys already prefixed with a "-"
  937. * query.select({a: 1, b: 1, c: 0});
  938. *
  939. * #### Note:
  940. *
  941. * Cannot be used with `distinct()`
  942. *
  943. * @param {Object|String} arg
  944. * @return {Query} this
  945. * @see SchemaType
  946. * @api public
  947. */
  948. Query.prototype.select = function select() {
  949. let arg = arguments[0];
  950. if (!arg) return this;
  951. if (arguments.length !== 1) {
  952. throw new Error('Invalid select: select only takes 1 argument');
  953. }
  954. this._validate('select');
  955. const fields = this._fields || (this._fields = {});
  956. const type = typeof arg;
  957. let i, len;
  958. if (('string' == type || utils.isArgumentsObject(arg)) &&
  959. 'number' == typeof arg.length || Array.isArray(arg)) {
  960. if ('string' == type)
  961. arg = arg.split(/\s+/);
  962. for (i = 0, len = arg.length; i < len; ++i) {
  963. let field = arg[i];
  964. if (!field) continue;
  965. const include = '-' == field[0] ? 0 : 1;
  966. if (include === 0) field = field.substring(1);
  967. fields[field] = include;
  968. }
  969. return this;
  970. }
  971. if (utils.isObject(arg)) {
  972. const keys = utils.keys(arg);
  973. for (i = 0; i < keys.length; ++i) {
  974. fields[keys[i]] = arg[keys[i]];
  975. }
  976. return this;
  977. }
  978. throw new TypeError('Invalid select() argument. Must be string or object.');
  979. };
  980. /**
  981. * Specifies a $slice condition for a `path`
  982. *
  983. * #### Example:
  984. *
  985. * query.slice('comments', 5)
  986. * query.slice('comments', -5)
  987. * query.slice('comments', [10, 5])
  988. * query.where('comments').slice(5)
  989. * query.where('comments').slice([-10, 5])
  990. *
  991. * @param {String} [path]
  992. * @param {Number} val number/range of elements to slice
  993. * @return {Query} this
  994. * @see mongodb http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields#RetrievingaSubsetofFields-RetrievingaSubrangeofArrayElements
  995. * @api public
  996. */
  997. Query.prototype.slice = function() {
  998. if (0 === arguments.length)
  999. return this;
  1000. this._validate('slice');
  1001. let path, val;
  1002. if (1 === arguments.length) {
  1003. const arg = arguments[0];
  1004. if (typeof arg === 'object' && !Array.isArray(arg)) {
  1005. const keys = Object.keys(arg);
  1006. const numKeys = keys.length;
  1007. for (let i = 0; i < numKeys; ++i) {
  1008. this.slice(keys[i], arg[keys[i]]);
  1009. }
  1010. return this;
  1011. }
  1012. this._ensurePath('slice');
  1013. path = this._path;
  1014. val = arguments[0];
  1015. } else if (2 === arguments.length) {
  1016. if ('number' === typeof arguments[0]) {
  1017. this._ensurePath('slice');
  1018. path = this._path;
  1019. val = [arguments[0], arguments[1]];
  1020. } else {
  1021. path = arguments[0];
  1022. val = arguments[1];
  1023. }
  1024. } else if (3 === arguments.length) {
  1025. path = arguments[0];
  1026. val = [arguments[1], arguments[2]];
  1027. }
  1028. const myFields = this._fields || (this._fields = {});
  1029. myFields[path] = { $slice: val };
  1030. return this;
  1031. };
  1032. /**
  1033. * Sets the sort order
  1034. *
  1035. * If an object is passed, values allowed are 'asc', 'desc', 'ascending', 'descending', 1, and -1.
  1036. *
  1037. * If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with `-` which will be treated as descending.
  1038. *
  1039. * #### Example:
  1040. *
  1041. * // these are equivalent
  1042. * query.sort({ field: 'asc', test: -1 });
  1043. * query.sort('field -test');
  1044. * query.sort([['field', 1], ['test', -1]]);
  1045. *
  1046. * #### Note:
  1047. *
  1048. * - The array syntax `.sort([['field', 1], ['test', -1]])` can only be used with [mongodb driver >= 2.0.46](https://github.com/mongodb/node-mongodb-native/blob/2.1/HISTORY.md#2046-2015-10-15).
  1049. * - Cannot be used with `distinct()`
  1050. *
  1051. * @param {Object|String|Array} arg
  1052. * @return {Query} this
  1053. * @api public
  1054. */
  1055. Query.prototype.sort = function(arg) {
  1056. if (!arg) return this;
  1057. let i, len, field;
  1058. this._validate('sort');
  1059. const type = typeof arg;
  1060. // .sort([['field', 1], ['test', -1]])
  1061. if (Array.isArray(arg)) {
  1062. len = arg.length;
  1063. for (i = 0; i < arg.length; ++i) {
  1064. if (!Array.isArray(arg[i])) {
  1065. throw new Error('Invalid sort() argument, must be array of arrays');
  1066. }
  1067. _pushArr(this.options, arg[i][0], arg[i][1]);
  1068. }
  1069. return this;
  1070. }
  1071. // .sort('field -test')
  1072. if (1 === arguments.length && 'string' == type) {
  1073. arg = arg.split(/\s+/);
  1074. len = arg.length;
  1075. for (i = 0; i < len; ++i) {
  1076. field = arg[i];
  1077. if (!field) continue;
  1078. const ascend = '-' == field[0] ? -1 : 1;
  1079. if (ascend === -1) field = field.substring(1);
  1080. push(this.options, field, ascend);
  1081. }
  1082. return this;
  1083. }
  1084. // .sort({ field: 1, test: -1 })
  1085. if (utils.isObject(arg)) {
  1086. const keys = utils.keys(arg);
  1087. for (i = 0; i < keys.length; ++i) {
  1088. field = keys[i];
  1089. push(this.options, field, arg[field]);
  1090. }
  1091. return this;
  1092. }
  1093. if (typeof Map !== 'undefined' && arg instanceof Map) {
  1094. _pushMap(this.options, arg);
  1095. return this;
  1096. }
  1097. throw new TypeError('Invalid sort() argument. Must be a string, object, or array.');
  1098. };
  1099. /*!
  1100. * @ignore
  1101. */
  1102. const _validSortValue = {
  1103. 1: 1,
  1104. '-1': -1,
  1105. asc: 1,
  1106. ascending: 1,
  1107. desc: -1,
  1108. descending: -1
  1109. };
  1110. function push(opts, field, value) {
  1111. if (Array.isArray(opts.sort)) {
  1112. throw new TypeError('Can\'t mix sort syntaxes. Use either array or object:' +
  1113. '\n- `.sort([[\'field\', 1], [\'test\', -1]])`' +
  1114. '\n- `.sort({ field: 1, test: -1 })`');
  1115. }
  1116. let s;
  1117. if (value && value.$meta) {
  1118. s = opts.sort || (opts.sort = {});
  1119. s[field] = { $meta: value.$meta };
  1120. return;
  1121. }
  1122. s = opts.sort || (opts.sort = {});
  1123. let val = String(value || 1).toLowerCase();
  1124. val = _validSortValue[val];
  1125. if (!val) throw new TypeError('Invalid sort value: { ' + field + ': ' + value + ' }');
  1126. s[field] = val;
  1127. }
  1128. function _pushArr(opts, field, value) {
  1129. opts.sort = opts.sort || [];
  1130. if (!Array.isArray(opts.sort)) {
  1131. throw new TypeError('Can\'t mix sort syntaxes. Use either array or object:' +
  1132. '\n- `.sort([[\'field\', 1], [\'test\', -1]])`' +
  1133. '\n- `.sort({ field: 1, test: -1 })`');
  1134. }
  1135. let val = String(value || 1).toLowerCase();
  1136. val = _validSortValue[val];
  1137. if (!val) throw new TypeError('Invalid sort value: [ ' + field + ', ' + value + ' ]');
  1138. opts.sort.push([field, val]);
  1139. }
  1140. function _pushMap(opts, map) {
  1141. opts.sort = opts.sort || new Map();
  1142. if (!(opts.sort instanceof Map)) {
  1143. throw new TypeError('Can\'t mix sort syntaxes. Use either array or ' +
  1144. 'object or map consistently');
  1145. }
  1146. map.forEach(function(value, key) {
  1147. let val = String(value || 1).toLowerCase();
  1148. val = _validSortValue[val];
  1149. if (!val) throw new TypeError('Invalid sort value: < ' + key + ': ' + value + ' >');
  1150. opts.sort.set(key, val);
  1151. });
  1152. }
  1153. /**
  1154. * Specifies the limit option.
  1155. *
  1156. * #### Example:
  1157. *
  1158. * query.limit(20)
  1159. *
  1160. * #### Note:
  1161. *
  1162. * Cannot be used with `distinct()`
  1163. *
  1164. * @method limit
  1165. * @memberOf Query
  1166. * @param {Number} val
  1167. * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D
  1168. * @api public
  1169. */
  1170. /**
  1171. * Specifies the skip option.
  1172. *
  1173. * #### Example:
  1174. *
  1175. * query.skip(100).limit(20)
  1176. *
  1177. * #### Note:
  1178. *
  1179. * Cannot be used with `distinct()`
  1180. *
  1181. * @method skip
  1182. * @memberOf Query
  1183. * @param {Number} val
  1184. * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D
  1185. * @api public
  1186. */
  1187. /**
  1188. * Specifies the batchSize option.
  1189. *
  1190. * #### Example:
  1191. *
  1192. * query.batchSize(100)
  1193. *
  1194. * #### Note:
  1195. *
  1196. * Cannot be used with `distinct()`
  1197. *
  1198. * @method batchSize
  1199. * @memberOf Query
  1200. * @param {Number} val
  1201. * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D
  1202. * @api public
  1203. */
  1204. /**
  1205. * Specifies the `comment` option.
  1206. *
  1207. * #### Example:
  1208. *
  1209. * query.comment('login query')
  1210. *
  1211. * #### Note:
  1212. *
  1213. * Cannot be used with `distinct()`
  1214. *
  1215. * @method comment
  1216. * @memberOf Query
  1217. * @param {Number} val
  1218. * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment
  1219. * @api public
  1220. */
  1221. /*!
  1222. * limit, skip, batchSize, comment
  1223. *
  1224. * Sets these associated options.
  1225. *
  1226. * query.comment('feed query');
  1227. */
  1228. ['limit', 'skip', 'batchSize', 'comment'].forEach(function(method) {
  1229. Query.prototype[method] = function(v) {
  1230. this._validate(method);
  1231. this.options[method] = v;
  1232. return this;
  1233. };
  1234. });
  1235. /**
  1236. * Specifies the maxTimeMS option.
  1237. *
  1238. * #### Example:
  1239. *
  1240. * query.maxTime(100)
  1241. * query.maxTimeMS(100)
  1242. *
  1243. * @method maxTime
  1244. * @memberOf Query
  1245. * @param {Number} ms
  1246. * @see mongodb http://docs.mongodb.org/manual/reference/operator/meta/maxTimeMS/#op._S_maxTimeMS
  1247. * @api public
  1248. */
  1249. Query.prototype.maxTime = Query.prototype.maxTimeMS = function(ms) {
  1250. this._validate('maxTime');
  1251. this.options.maxTimeMS = ms;
  1252. return this;
  1253. };
  1254. /**
  1255. * Sets query hints.
  1256. *
  1257. * #### Example:
  1258. *
  1259. * query.hint({ indexA: 1, indexB: -1});
  1260. * query.hint('indexA_1_indexB_1');
  1261. *
  1262. * #### Note:
  1263. *
  1264. * Cannot be used with `distinct()`
  1265. *
  1266. * @param {Object|string} val a hint object or the index name
  1267. * @return {Query} this
  1268. * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint
  1269. * @api public
  1270. */
  1271. Query.prototype.hint = function() {
  1272. if (0 === arguments.length) return this;
  1273. this._validate('hint');
  1274. const arg = arguments[0];
  1275. if (utils.isObject(arg)) {
  1276. const hint = this.options.hint || (this.options.hint = {});
  1277. // must keep object keys in order so don't use Object.keys()
  1278. for (const k in arg) {
  1279. hint[k] = arg[k];
  1280. }
  1281. return this;
  1282. }
  1283. if (typeof arg === 'string') {
  1284. this.options.hint = arg;
  1285. return this;
  1286. }
  1287. throw new TypeError('Invalid hint. ' + arg);
  1288. };
  1289. /**
  1290. * Requests acknowledgement that this operation has been persisted to MongoDB's
  1291. * on-disk journal.
  1292. * This option is only valid for operations that write to the database:
  1293. *
  1294. * - `deleteOne()`
  1295. * - `deleteMany()`
  1296. * - `findOneAndDelete()`
  1297. * - `findOneAndUpdate()`
  1298. * - `updateOne()`
  1299. * - `updateMany()`
  1300. *
  1301. * Defaults to the `j` value if it is specified in writeConcern options
  1302. *
  1303. * #### Example:
  1304. *
  1305. * mquery().w(2).j(true).wtimeout(2000);
  1306. *
  1307. * @method j
  1308. * @memberOf Query
  1309. * @instance
  1310. * @param {boolean} val
  1311. * @see mongodb https://docs.mongodb.com/manual/reference/write-concern/#j-option
  1312. * @return {Query} this
  1313. * @api public
  1314. */
  1315. Query.prototype.j = function j(val) {
  1316. this.options.j = val;
  1317. return this;
  1318. };
  1319. /**
  1320. * Sets the slaveOk option. _Deprecated_ in MongoDB 2.2 in favor of read preferences.
  1321. *
  1322. * #### Example:
  1323. *
  1324. * query.slaveOk() // true
  1325. * query.slaveOk(true)
  1326. * query.slaveOk(false)
  1327. *
  1328. * @deprecated use read() preferences instead if on mongodb >= 2.2
  1329. * @param {Boolean} v defaults to true
  1330. * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference
  1331. * @see read()
  1332. * @return {Query} this
  1333. * @api public
  1334. */
  1335. Query.prototype.slaveOk = function(v) {
  1336. this.options.slaveOk = arguments.length ? !!v : true;
  1337. return this;
  1338. };
  1339. /**
  1340. * Sets the readPreference option for the query.
  1341. *
  1342. * #### Example:
  1343. *
  1344. * new Query().read('primary')
  1345. * new Query().read('p') // same as primary
  1346. *
  1347. * new Query().read('primaryPreferred')
  1348. * new Query().read('pp') // same as primaryPreferred
  1349. *
  1350. * new Query().read('secondary')
  1351. * new Query().read('s') // same as secondary
  1352. *
  1353. * new Query().read('secondaryPreferred')
  1354. * new Query().read('sp') // same as secondaryPreferred
  1355. *
  1356. * new Query().read('nearest')
  1357. * new Query().read('n') // same as nearest
  1358. *
  1359. * // you can also use mongodb.ReadPreference class to also specify tags
  1360. * new Query().read(mongodb.ReadPreference('secondary', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }]))
  1361. *
  1362. * new Query().setReadPreference('primary') // alias of .read()
  1363. *
  1364. * #### Preferences:
  1365. *
  1366. * primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags.
  1367. * secondary Read from secondary if available, otherwise error.
  1368. * primaryPreferred Read from primary if available, otherwise a secondary.
  1369. * secondaryPreferred Read from a secondary if available, otherwise read from the primary.
  1370. * nearest All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection.
  1371. *
  1372. * Aliases
  1373. *
  1374. * p primary
  1375. * pp primaryPreferred
  1376. * s secondary
  1377. * sp secondaryPreferred
  1378. * n nearest
  1379. *
  1380. * Read more about how to use read preferences [here](http://docs.mongodb.org/manual/applications/replication/#read-preference) and [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences).
  1381. *
  1382. * @param {String|ReadPreference} pref one of the listed preference options or their aliases
  1383. * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference
  1384. * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences
  1385. * @return {Query} this
  1386. * @api public
  1387. */
  1388. Query.prototype.read = Query.prototype.setReadPreference = function(pref) {
  1389. if (arguments.length > 1 && !Query.prototype.read.deprecationWarningIssued) {
  1390. console.error('Deprecation warning: \'tags\' argument is not supported anymore in Query.read() method. Please use mongodb.ReadPreference object instead.');
  1391. Query.prototype.read.deprecationWarningIssued = true;
  1392. }
  1393. this.options.readPreference = utils.readPref(pref);
  1394. return this;
  1395. };
  1396. /**
  1397. * Sets the readConcern option for the query.
  1398. *
  1399. * #### Example:
  1400. *
  1401. * new Query().readConcern('local')
  1402. * new Query().readConcern('l') // same as local
  1403. *
  1404. * new Query().readConcern('available')
  1405. * new Query().readConcern('a') // same as available
  1406. *
  1407. * new Query().readConcern('majority')
  1408. * new Query().readConcern('m') // same as majority
  1409. *
  1410. * new Query().readConcern('linearizable')
  1411. * new Query().readConcern('lz') // same as linearizable
  1412. *
  1413. * new Query().readConcern('snapshot')
  1414. * new Query().readConcern('s') // same as snapshot
  1415. *
  1416. * new Query().r('s') // r is alias of readConcern
  1417. *
  1418. *
  1419. * #### Read Concern Level:
  1420. *
  1421. * local MongoDB 3.2+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
  1422. * available MongoDB 3.6+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
  1423. * majority MongoDB 3.2+ The query returns the data that has been acknowledged by a majority of the replica set members. The documents returned by the read operation are durable, even in the event of failure.
  1424. * linearizable MongoDB 3.4+ The query returns data that reflects all successful majority-acknowledged writes that completed prior to the start of the read operation. The query may wait for concurrently executing writes to propagate to a majority of replica set members before returning results.
  1425. * snapshot MongoDB 4.0+ Only available for operations within multi-document transactions. Upon transaction commit with write concern "majority", the transaction operations are guaranteed to have read from a snapshot of majority-committed data.
  1426. *
  1427. * Aliases
  1428. *
  1429. * l local
  1430. * a available
  1431. * m majority
  1432. * lz linearizable
  1433. * s snapshot
  1434. *
  1435. * Read more about how to use read concern [here](https://docs.mongodb.com/manual/reference/read-concern/).
  1436. *
  1437. * @param {String} level one of the listed read concern level or their aliases
  1438. * @see mongodb https://docs.mongodb.com/manual/reference/read-concern/
  1439. * @return {Query} this
  1440. * @api public
  1441. */
  1442. Query.prototype.readConcern = Query.prototype.r = function(level) {
  1443. this.options.readConcern = utils.readConcern(level);
  1444. return this;
  1445. };
  1446. /**
  1447. * Sets tailable option.
  1448. *
  1449. * #### Example:
  1450. *
  1451. * query.tailable() <== true
  1452. * query.tailable(true)
  1453. * query.tailable(false)
  1454. *
  1455. * #### Note:
  1456. *
  1457. * Cannot be used with `distinct()`
  1458. *
  1459. * @param {Boolean} v defaults to true
  1460. * @see mongodb http://www.mongodb.org/display/DOCS/Tailable+Cursors
  1461. * @api public
  1462. */
  1463. Query.prototype.tailable = function() {
  1464. this._validate('tailable');
  1465. this.options.tailable = arguments.length
  1466. ? !!arguments[0]
  1467. : true;
  1468. return this;
  1469. };
  1470. /**
  1471. * Sets the specified number of `mongod` servers, or tag set of `mongod` servers,
  1472. * that must acknowledge this write before this write is considered successful.
  1473. * This option is only valid for operations that write to the database:
  1474. *
  1475. * - `deleteOne()`
  1476. * - `deleteMany()`
  1477. * - `findOneAndDelete()`
  1478. * - `findOneAndUpdate()`
  1479. * - `updateOne()`
  1480. * - `updateMany()`
  1481. *
  1482. * Defaults to the `w` value if it is specified in writeConcern options
  1483. *
  1484. * #### Example:
  1485. *
  1486. * mquery().writeConcern(0)
  1487. * mquery().writeConcern(1)
  1488. * mquery().writeConcern({ w: 1, j: true, wtimeout: 2000 })
  1489. * mquery().writeConcern('majority')
  1490. * mquery().writeConcern('m') // same as majority
  1491. * mquery().writeConcern('tagSetName') // if the tag set is 'm', use .writeConcern({ w: 'm' }) instead
  1492. * mquery().w(1) // w is alias of writeConcern
  1493. *
  1494. * @method writeConcern
  1495. * @memberOf Query
  1496. * @instance
  1497. * @param {String|number|object} concern 0 for fire-and-forget, 1 for acknowledged by one server, 'majority' for majority of the replica set, or [any of the more advanced options](https://docs.mongodb.com/manual/reference/write-concern/#w-option).
  1498. * @see mongodb https://docs.mongodb.com/manual/reference/write-concern/#w-option
  1499. * @return {Query} this
  1500. * @api public
  1501. */
  1502. Query.prototype.writeConcern = Query.prototype.w = function writeConcern(concern) {
  1503. if ('object' === typeof concern) {
  1504. if ('undefined' !== typeof concern.j) this.options.j = concern.j;
  1505. if ('undefined' !== typeof concern.w) this.options.w = concern.w;
  1506. if ('undefined' !== typeof concern.wtimeout) this.options.wtimeout = concern.wtimeout;
  1507. } else {
  1508. this.options.w = 'm' === concern ? 'majority' : concern;
  1509. }
  1510. return this;
  1511. };
  1512. /**
  1513. * Specifies a time limit, in milliseconds, for the write concern.
  1514. * If `ms > 1`, it is maximum amount of time to wait for this write
  1515. * to propagate through the replica set before this operation fails.
  1516. * The default is `0`, which means no timeout.
  1517. *
  1518. * This option is only valid for operations that write to the database:
  1519. *
  1520. * - `deleteOne()`
  1521. * - `deleteMany()`
  1522. * - `findOneAndDelete()`
  1523. * - `findOneAndUpdate()`
  1524. * - `updateOne()`
  1525. * - `updateMany()`
  1526. *
  1527. * Defaults to `wtimeout` value if it is specified in writeConcern
  1528. *
  1529. * #### Example:
  1530. *
  1531. * mquery().w(2).j(true).wtimeout(2000)
  1532. *
  1533. * @method wtimeout
  1534. * @memberOf Query
  1535. * @instance
  1536. * @param {number} ms number of milliseconds to wait
  1537. * @see mongodb https://docs.mongodb.com/manual/reference/write-concern/#wtimeout
  1538. * @return {Query} this
  1539. * @api public
  1540. */
  1541. Query.prototype.wtimeout = Query.prototype.wTimeout = function wtimeout(ms) {
  1542. this.options.wtimeout = ms;
  1543. return this;
  1544. };
  1545. /**
  1546. * Merges another Query or conditions object into this one.
  1547. *
  1548. * When a Query is passed, conditions, field selection and options are merged.
  1549. *
  1550. * @param {Query|Object} source
  1551. * @return {Query} this
  1552. */
  1553. Query.prototype.merge = function(source) {
  1554. if (!source)
  1555. return this;
  1556. if (!Query.canMerge(source))
  1557. throw new TypeError('Invalid argument. Expected instanceof mquery or plain object');
  1558. if (source instanceof Query) {
  1559. // if source has a feature, apply it to ourselves
  1560. if (source._conditions) {
  1561. utils.merge(this._conditions, source._conditions);
  1562. }
  1563. if (source._fields) {
  1564. this._fields || (this._fields = {});
  1565. utils.merge(this._fields, source._fields);
  1566. }
  1567. if (source.options) {
  1568. this.options || (this.options = {});
  1569. utils.merge(this.options, source.options);
  1570. }
  1571. if (source._updateDoc) {
  1572. this._updateDoc || (this._updateDoc = {});
  1573. utils.mergeClone(this._updateDoc, source._updateDoc);
  1574. }
  1575. if (source._distinctDoc) {
  1576. this._distinctDoc = source._distinctDoc;
  1577. }
  1578. return this;
  1579. }
  1580. // plain object
  1581. utils.merge(this._conditions, source);
  1582. return this;
  1583. };
  1584. /**
  1585. * Finds documents.
  1586. *
  1587. * #### Example:
  1588. *
  1589. * query.find()
  1590. * await query.find()
  1591. * await query.find({ name: 'Burning Lights' })
  1592. *
  1593. * @param {Object} [criteria] mongodb selector
  1594. * @return {Query} this
  1595. * @api public
  1596. */
  1597. Query.prototype.find = function(criteria) {
  1598. this.op = 'find';
  1599. if (Query.canMerge(criteria)) {
  1600. this.merge(criteria);
  1601. }
  1602. return this;
  1603. };
  1604. /**
  1605. * Executes a `find` Query
  1606. * @returns the result
  1607. */
  1608. Query.prototype._find = async function _find() {
  1609. const conds = this._conditions;
  1610. const options = this._optionsForExec();
  1611. if (this.$useProjection) {
  1612. options.projection = this._fieldsForExec();
  1613. } else {
  1614. options.fields = this._fieldsForExec();
  1615. }
  1616. debug('find', this._collection.collectionName, conds, options);
  1617. return this._collection.find(conds, options);
  1618. };
  1619. /**
  1620. * Returns the query cursor
  1621. *
  1622. * #### Examples:
  1623. *
  1624. * query.find().cursor();
  1625. * query.cursor({ name: 'Burning Lights' });
  1626. *
  1627. * @param {Object} [criteria] mongodb selector
  1628. * @return {Object} cursor
  1629. * @api public
  1630. */
  1631. Query.prototype.cursor = function cursor(criteria) {
  1632. if (this.op) {
  1633. if (this.op !== 'find') {
  1634. throw new TypeError('.cursor only support .find method');
  1635. }
  1636. } else {
  1637. this.find(criteria);
  1638. }
  1639. const conds = this._conditions;
  1640. const options = this._optionsForExec();
  1641. if (this.$useProjection) {
  1642. options.projection = this._fieldsForExec();
  1643. } else {
  1644. options.fields = this._fieldsForExec();
  1645. }
  1646. debug('findCursor', this._collection.collectionName, conds, options);
  1647. return this._collection.findCursor(conds, options);
  1648. };
  1649. /**
  1650. * Executes the query as a findOne() operation.
  1651. *
  1652. * #### Example:
  1653. *
  1654. * query.findOne().where('name', /^Burning/);
  1655. *
  1656. * query.findOne({ name: /^Burning/ })
  1657. *
  1658. * await query.findOne({ name: /^Burning/ }); // executes
  1659. *
  1660. * @param {Object|Query} [criteria] mongodb selector
  1661. * @return {Query} this
  1662. * @api public
  1663. */
  1664. Query.prototype.findOne = function(criteria) {
  1665. this.op = 'findOne';
  1666. if (Query.canMerge(criteria)) {
  1667. this.merge(criteria);
  1668. }
  1669. return this;
  1670. };
  1671. /**
  1672. * Executes a `findOne` Query
  1673. * @returns the results
  1674. */
  1675. Query.prototype._findOne = async function _findOne() {
  1676. const conds = this._conditions;
  1677. const options = this._optionsForExec();
  1678. if (this.$useProjection) {
  1679. options.projection = this._fieldsForExec();
  1680. } else {
  1681. options.fields = this._fieldsForExec();
  1682. }
  1683. debug('findOne', this._collection.collectionName, conds, options);
  1684. return this._collection.findOne(conds, options);
  1685. };
  1686. /**
  1687. * Executes the query as a countDocuments() operation.
  1688. *
  1689. * #### Example:
  1690. *
  1691. * query.countDocuments().where('color', 'black').exec();
  1692. *
  1693. * query.countDocuments({ color: 'black' })
  1694. *
  1695. * await query.countDocuments({ color: 'black' });
  1696. *
  1697. * const count = await query.where('color', 'black').countDocuments();
  1698. * console.log('there are %d kittens', count);
  1699. *
  1700. * @param {Object} [filter] mongodb selector
  1701. * @return {Query} this
  1702. * @api public
  1703. */
  1704. Query.prototype.countDocuments = function(filter) {
  1705. this.op = 'countDocuments';
  1706. this._validate();
  1707. if (Query.canMerge(filter)) {
  1708. this.merge(filter);
  1709. }
  1710. return this;
  1711. };
  1712. /**
  1713. * Executes a `countDocuments` Query
  1714. * @returns the results
  1715. */
  1716. Query.prototype._countDocuments = async function _countDocuments() {
  1717. const conds = this._conditions;
  1718. const options = this._optionsForExec();
  1719. debug('countDocuments', this._collection.collectionName, conds, options);
  1720. return this._collection.countDocuments(conds, options);
  1721. };
  1722. /**
  1723. * Executes the query as a estimatedDocumentCount() operation.
  1724. *
  1725. * #### Example:
  1726. *
  1727. * query.estimatedDocumentCount();
  1728. *
  1729. * const count = await query.estimatedDocumentCount();
  1730. * console.log('there are %d kittens', count);
  1731. *
  1732. * @return {Query} this
  1733. * @api public
  1734. */
  1735. Query.prototype.estimatedDocumentCount = function() {
  1736. this.op = 'estimatedDocumentCount';
  1737. this._validate();
  1738. return this;
  1739. };
  1740. /**
  1741. * Executes a `count` Query
  1742. * @returns the results
  1743. */
  1744. Query.prototype._estimatedDocumentCount = async function _estimatedDocumentCount() {
  1745. const conds = this._conditions;
  1746. const options = this._optionsForExec();
  1747. debug('estimatedDocumentCount', this._collection.collectionName, conds, options);
  1748. return this._collection.estimatedDocumentCount(conds, options);
  1749. };
  1750. /**
  1751. * Declares or executes a distinct() operation.
  1752. *
  1753. * #### Example:
  1754. *
  1755. * await distinct(criteria, field)
  1756. * distinct(criteria, field)
  1757. * await distinct(field)
  1758. * distinct(field)
  1759. * await distinct()
  1760. * distinct()
  1761. *
  1762. * @param {Object|Query} [criteria]
  1763. * @param {String} [field]
  1764. * @return {Query} this
  1765. * @see mongodb http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Distinct
  1766. * @api public
  1767. */
  1768. Query.prototype.distinct = function(criteria, field) {
  1769. this.op = 'distinct';
  1770. this._validate();
  1771. if (!field && typeof criteria === 'string') {
  1772. field = criteria;
  1773. criteria = undefined;
  1774. }
  1775. if ('string' == typeof field) {
  1776. this._distinctDoc = field;
  1777. }
  1778. if (Query.canMerge(criteria)) {
  1779. this.merge(criteria);
  1780. }
  1781. return this;
  1782. };
  1783. /**
  1784. * Executes a `distinct` Query
  1785. * @returns the results
  1786. */
  1787. Query.prototype._distinct = async function _distinct() {
  1788. if (!this._distinctDoc) {
  1789. throw new Error('No value for `distinct` has been declared');
  1790. }
  1791. const conds = this._conditions,
  1792. options = this._optionsForExec();
  1793. return this._collection.distinct(this._distinctDoc, conds, options);
  1794. };
  1795. /**
  1796. * Declare and/or execute this query as an `updateMany()` operation. This function will update _all_ documents that match
  1797. * `criteria`, rather than just the first one.
  1798. *
  1799. * _All paths passed that are not $atomic operations will become $set ops._
  1800. *
  1801. * #### Example:
  1802. *
  1803. * // Update every document whose `title` contains 'test'
  1804. * mquery().updateMany({ title: /test/ }, { year: 2017 })
  1805. *
  1806. * @param {Object} [criteria]
  1807. * @param {Object} [doc] the update command
  1808. * @param {Object} [options]
  1809. * @return {Query} this
  1810. * @api public
  1811. */
  1812. Query.prototype.updateMany = function updateMany(criteria, doc, options) {
  1813. return _update(this, 'updateMany', criteria, doc, options);
  1814. };
  1815. /**
  1816. * Executes a `updateMany` Query
  1817. * @returns the results
  1818. */
  1819. Query.prototype._updateMany = async function() {
  1820. return _updateExec(this, 'updateMany');
  1821. };
  1822. /**
  1823. * Declare and/or execute this query as an `updateOne()` operation. This function will _always_ update just one document,
  1824. * regardless of the `multi` option.
  1825. *
  1826. * _All paths passed that are not $atomic operations will become $set ops._
  1827. *
  1828. * #### Example:
  1829. *
  1830. * // Update the first document whose `title` contains 'test'
  1831. * mquery().updateMany({ title: /test/ }, { year: 2017 })
  1832. *
  1833. * @param {Object} [criteria]
  1834. * @param {Object} [doc] the update command
  1835. * @param {Object} [options]
  1836. * @return {Query} this
  1837. * @api public
  1838. */
  1839. Query.prototype.updateOne = function updateOne(criteria, doc, options) {
  1840. return _update(this, 'updateOne', criteria, doc, options);
  1841. };
  1842. /**
  1843. * Executes a `updateOne` Query
  1844. * @returns the results
  1845. */
  1846. Query.prototype._updateOne = async function() {
  1847. return _updateExec(this, 'updateOne');
  1848. };
  1849. /**
  1850. * Declare and/or execute this query as an `replaceOne()` operation. Similar
  1851. * to `updateOne()`, except `replaceOne()` is not allowed to use atomic
  1852. * modifiers (`$set`, `$push`, etc.). Calling `replaceOne()` will always
  1853. * replace the existing doc.
  1854. *
  1855. * #### Example:
  1856. *
  1857. * // Replace the document with `_id` 1 with `{ _id: 1, year: 2017 }`
  1858. * mquery().replaceOne({ _id: 1 }, { year: 2017 })
  1859. *
  1860. * @param {Object} [criteria]
  1861. * @param {Object} [doc] the update command
  1862. * @param {Object} [options]
  1863. * @return {Query} this
  1864. * @api public
  1865. */
  1866. Query.prototype.replaceOne = function replaceOne(criteria, doc, options) {
  1867. this.setOptions({ overwrite: true });
  1868. return _update(this, 'replaceOne', criteria, doc, options);
  1869. };
  1870. /**
  1871. * Executes a `replaceOne` Query
  1872. * @returns the results
  1873. */
  1874. Query.prototype._replaceOne = async function() {
  1875. return _updateExec(this, 'replaceOne');
  1876. };
  1877. /*!
  1878. * Internal helper for updateMany, updateOne
  1879. */
  1880. function _update(query, op, criteria, doc, options) {
  1881. query.op = op;
  1882. if (Query.canMerge(criteria)) {
  1883. query.merge(criteria);
  1884. }
  1885. if (doc) {
  1886. query._mergeUpdate(doc);
  1887. }
  1888. if (utils.isObject(options)) {
  1889. // { overwrite: true }
  1890. query.setOptions(options);
  1891. }
  1892. return query;
  1893. }
  1894. /**
  1895. * Helper for de-duplicating "update*" functions
  1896. * @param {Query} query The Query Object (replacement for "this")
  1897. * @param {String} op The Operation to be done
  1898. * @returns the results
  1899. */
  1900. async function _updateExec(query, op) {
  1901. const options = query._optionsForExec();
  1902. const criteria = query._conditions;
  1903. const doc = query._updateForExec();
  1904. debug(op, query._collection.collectionName, criteria, doc, options);
  1905. return query._collection[op](criteria, doc, options);
  1906. }
  1907. /**
  1908. * Declare and/or execute this query as a `deleteOne()` operation.
  1909. *
  1910. * #### Example:
  1911. *
  1912. * await mquery(collection).deleteOne({ artist: 'Anne Murray' })
  1913. *
  1914. * @param {Object|Query} [criteria] mongodb selector
  1915. * @return {Query} this
  1916. * @api public
  1917. */
  1918. Query.prototype.deleteOne = function(criteria) {
  1919. this.op = 'deleteOne';
  1920. if (Query.canMerge(criteria)) {
  1921. this.merge(criteria);
  1922. }
  1923. return this;
  1924. };
  1925. /**
  1926. * Executes a `deleteOne` Query
  1927. * @returns the results
  1928. */
  1929. Query.prototype._deleteOne = async function() {
  1930. const options = this._optionsForExec();
  1931. delete options.justOne;
  1932. const conds = this._conditions;
  1933. debug('deleteOne', this._collection.collectionName, conds, options);
  1934. return this._collection.deleteOne(conds, options);
  1935. };
  1936. /**
  1937. * Declare and/or execute this query as a `deleteMany()` operation. Always deletes
  1938. * _every_ document that matches `criteria`.
  1939. *
  1940. * #### Example:
  1941. *
  1942. * await mquery(collection).deleteMany({ artist: 'Anne Murray' })
  1943. *
  1944. * @param {Object|Query} [criteria] mongodb selector
  1945. * @return {Query} this
  1946. * @api public
  1947. */
  1948. Query.prototype.deleteMany = function(criteria) {
  1949. this.op = 'deleteMany';
  1950. if (Query.canMerge(criteria)) {
  1951. this.merge(criteria);
  1952. }
  1953. return this;
  1954. };
  1955. /**
  1956. * Executes a `deleteMany` Query
  1957. * @returns the results
  1958. */
  1959. Query.prototype._deleteMany = async function() {
  1960. const options = this._optionsForExec();
  1961. delete options.justOne;
  1962. const conds = this._conditions;
  1963. return this._collection.deleteMany(conds, options);
  1964. };
  1965. /**
  1966. * Issues a mongodb findOneAndUpdate command.
  1967. *
  1968. * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any).
  1969. *
  1970. * #### Available options
  1971. *
  1972. * - `new`: bool - true to return the modified document rather than the original. defaults to true
  1973. * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
  1974. * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
  1975. *
  1976. * #### Examples:
  1977. *
  1978. * await query.findOneAndUpdate(conditions, update, options) // executes
  1979. * query.findOneAndUpdate(conditions, update, options) // returns Query
  1980. * await query.findOneAndUpdate(conditions, update) // executes
  1981. * query.findOneAndUpdate(conditions, update) // returns Query
  1982. * await query.findOneAndUpdate(update) // returns Query
  1983. * query.findOneAndUpdate(update) // returns Query
  1984. * await query.findOneAndUpdate() // executes
  1985. * query.findOneAndUpdate() // returns Query
  1986. *
  1987. * @param {Object|Query} [query]
  1988. * @param {Object} [doc]
  1989. * @param {Object} [options]
  1990. * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
  1991. * @return {Query} this
  1992. * @api public
  1993. */
  1994. Query.prototype.findOneAndUpdate = function(criteria, doc, options) {
  1995. this.op = 'findOneAndUpdate';
  1996. this._validate();
  1997. if (Query.canMerge(criteria)) {
  1998. this.merge(criteria);
  1999. }
  2000. // apply doc
  2001. if (doc) {
  2002. this._mergeUpdate(doc);
  2003. }
  2004. options && this.setOptions(options);
  2005. return this;
  2006. };
  2007. /**
  2008. * Executes a `findOneAndUpdate` Query
  2009. * @returns the results
  2010. */
  2011. Query.prototype._findOneAndUpdate = async function() {
  2012. const conds = this._conditions;
  2013. const update = this._updateForExec();
  2014. const options = this._optionsForExec();
  2015. return this._collection.findOneAndUpdate(conds, update, options);
  2016. };
  2017. /**
  2018. * Issues a mongodb findOneAndReplace command.
  2019. *
  2020. * Finds a matching document, replaces it according to the `replacement` arg, passing any `options`, and returns the found document (if any).
  2021. *
  2022. * #### Available options
  2023. *
  2024. * - `new`: bool - true to return the modified document rather than the original. defaults to true
  2025. * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
  2026. * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
  2027. *
  2028. * #### Examples:
  2029. *
  2030. * await query.findOneAndReplace(conditions, replacement, options) // executes
  2031. * query.findOneAndReplace(conditions, replacement, options) // returns Query
  2032. * await query.findOneAndReplace(conditions, replacement) // executes
  2033. * query.findOneAndReplace(conditions, replacement) // returns Query
  2034. * await query.findOneAndReplace(replacement) // returns Query
  2035. * query.findOneAndReplace(replacement) // returns Query
  2036. * await query.findOneAndReplace() // executes
  2037. * query.findOneAndReplace() // returns Query
  2038. *
  2039. * @param {Object|Query} [query]
  2040. * @param {Object} [replacement]
  2041. * @param {Object} [options]
  2042. * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
  2043. * @return {Query} this
  2044. * @api public
  2045. */
  2046. Query.prototype.findOneAndReplace = function(criteria, replacement, options) {
  2047. this.op = 'findOneAndReplace';
  2048. this._validate();
  2049. if (Query.canMerge(criteria)) {
  2050. this.merge(criteria);
  2051. }
  2052. // apply replacement
  2053. if (replacement) {
  2054. this._updateDoc = replacement;
  2055. this.options = this.options || {};
  2056. this.options.overwrite = true;
  2057. }
  2058. options && this.setOptions(options);
  2059. return this;
  2060. };
  2061. /**
  2062. * Executes a `findOneAndReplace` Query
  2063. * @returns the results
  2064. */
  2065. Query.prototype._findOneAndReplace = async function() {
  2066. const conds = this._conditions;
  2067. const replacement = this._updateForExec();
  2068. const options = this._optionsForExec();
  2069. debug('findOneAndReplace', this._collection.collectionName, conds, replacement, options);
  2070. return this._collection.findOneAndReplace(conds, replacement, options);
  2071. };
  2072. /**
  2073. * Issues a mongodb findOneAndDelete.
  2074. *
  2075. * Finds a matching document, removes it, returning the found document (if any).
  2076. *
  2077. * #### Available options
  2078. *
  2079. * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
  2080. *
  2081. * #### Examples:
  2082. *
  2083. * await A.where().findOneAndDelete(conditions, options) // executes
  2084. * A.where().findOneAndDelete(conditions, options) // return Query
  2085. * await A.where().findOneAndDelete(conditions) // executes
  2086. * A.where().findOneAndDelete(conditions) // returns Query
  2087. * await A.where().findOneAndDelete() // executes
  2088. * A.where().findOneAndDelete() // returns Query
  2089. *
  2090. * @param {Object} [filter]
  2091. * @param {Object} [options]
  2092. * @return {Query} this
  2093. * @api public
  2094. */
  2095. Query.prototype.findOneAndDelete = function(filter, options) {
  2096. this.op = 'findOneAndDelete';
  2097. this._validate();
  2098. if (Query.canMerge(filter)) {
  2099. this.merge(filter);
  2100. }
  2101. // apply options
  2102. options && this.setOptions(options);
  2103. return this;
  2104. };
  2105. /**
  2106. * Executes a `findOneAndRemove` Query
  2107. * @returns the results
  2108. */
  2109. Query.prototype._findOneAndDelete = async function() {
  2110. const options = this._optionsForExec();
  2111. const conds = this._conditions;
  2112. debug('findOneAndDelete', this._collection.collectionName, conds, options);
  2113. return this._collection.findOneAndDelete(conds, options);
  2114. };
  2115. /**
  2116. * Add trace function that gets called when the query is executed.
  2117. * The function will be called with (method, queryInfo, query) and
  2118. * should return a callback function which will be called
  2119. * with (err, result, millis) when the query is complete.
  2120. *
  2121. * queryInfo is an object containing: {
  2122. * collectionName: <name of the collection>,
  2123. * conditions: <query criteria>,
  2124. * options: <comment, fields, readPreference, etc>,
  2125. * doc: [document to update, if applicable]
  2126. * }
  2127. *
  2128. * NOTE: Does not trace stream queries.
  2129. *
  2130. * @param {Function} traceFunction
  2131. * @return {Query} this
  2132. * @api public
  2133. */
  2134. Query.prototype.setTraceFunction = function(traceFunction) {
  2135. this._traceFunction = traceFunction;
  2136. return this;
  2137. };
  2138. /**
  2139. * Executes the query
  2140. *
  2141. * #### Examples:
  2142. *
  2143. * query.exec();
  2144. * await query.exec();
  2145. * query.exec('update');
  2146. * await query.exec('find');
  2147. *
  2148. * @param {String|Function} [operation]
  2149. * @api public
  2150. */
  2151. Query.prototype.exec = async function exec(op) {
  2152. if (typeof op === 'string') {
  2153. this.op = op;
  2154. }
  2155. assert.ok(this.op, 'Missing query type: (find, etc)');
  2156. const fnName = '_' + this.op;
  2157. // better error, because default would list it as "this[fnName] is not a function"
  2158. if (typeof this[fnName] !== 'function') {
  2159. throw new TypeError(`this[${fnName}] is not a function`);
  2160. }
  2161. return this[fnName]();
  2162. };
  2163. /**
  2164. * Executes the query returning a `Promise` which will be
  2165. * resolved with either the doc(s) or rejected with the error.
  2166. *
  2167. * @param {Function} [resolve]
  2168. * @param {Function} [reject]
  2169. * @return {Promise}
  2170. * @api public
  2171. */
  2172. Query.prototype.then = async function(res, rej) {
  2173. return this.exec().then(res, rej);
  2174. };
  2175. /**
  2176. * Returns a cursor for the given `find` query.
  2177. *
  2178. * @throws Error if operation is not a find
  2179. * @returns {Cursor} MongoDB driver cursor
  2180. */
  2181. Query.prototype.cursor = function() {
  2182. if ('find' != this.op)
  2183. throw new Error('cursor() is only available for find');
  2184. const conds = this._conditions;
  2185. const options = this._optionsForExec();
  2186. if (this.$useProjection) {
  2187. options.projection = this._fieldsForExec();
  2188. } else {
  2189. options.fields = this._fieldsForExec();
  2190. }
  2191. return this._collection.findCursor(conds, options);
  2192. };
  2193. /**
  2194. * Determines if field selection has been made.
  2195. *
  2196. * @return {Boolean}
  2197. * @api public
  2198. */
  2199. Query.prototype.selected = function selected() {
  2200. return !!(this._fields && Object.keys(this._fields).length > 0);
  2201. };
  2202. /**
  2203. * Determines if inclusive field selection has been made.
  2204. *
  2205. * query.selectedInclusively() // false
  2206. * query.select('name')
  2207. * query.selectedInclusively() // true
  2208. * query.selectedExlusively() // false
  2209. *
  2210. * @returns {Boolean}
  2211. */
  2212. Query.prototype.selectedInclusively = function selectedInclusively() {
  2213. if (!this._fields) return false;
  2214. const keys = Object.keys(this._fields);
  2215. if (0 === keys.length) return false;
  2216. for (let i = 0; i < keys.length; ++i) {
  2217. const key = keys[i];
  2218. if (0 === this._fields[key]) return false;
  2219. if (this._fields[key] &&
  2220. typeof this._fields[key] === 'object' &&
  2221. this._fields[key].$meta) {
  2222. return false;
  2223. }
  2224. }
  2225. return true;
  2226. };
  2227. /**
  2228. * Determines if exclusive field selection has been made.
  2229. *
  2230. * query.selectedExlusively() // false
  2231. * query.select('-name')
  2232. * query.selectedExlusively() // true
  2233. * query.selectedInclusively() // false
  2234. *
  2235. * @returns {Boolean}
  2236. */
  2237. Query.prototype.selectedExclusively = function selectedExclusively() {
  2238. if (!this._fields) return false;
  2239. const keys = Object.keys(this._fields);
  2240. if (0 === keys.length) return false;
  2241. for (let i = 0; i < keys.length; ++i) {
  2242. const key = keys[i];
  2243. if (0 === this._fields[key]) return true;
  2244. }
  2245. return false;
  2246. };
  2247. /**
  2248. * Merges `doc` with the current update object.
  2249. *
  2250. * @param {Object} doc
  2251. */
  2252. Query.prototype._mergeUpdate = function(doc) {
  2253. if (!this._updateDoc) this._updateDoc = {};
  2254. if (doc instanceof Query) {
  2255. if (doc._updateDoc) {
  2256. utils.mergeClone(this._updateDoc, doc._updateDoc);
  2257. }
  2258. } else {
  2259. utils.mergeClone(this._updateDoc, doc);
  2260. }
  2261. };
  2262. /**
  2263. * Returns default options.
  2264. *
  2265. * @return {Object}
  2266. * @api private
  2267. */
  2268. Query.prototype._optionsForExec = function() {
  2269. const options = utils.clone(this.options);
  2270. return options;
  2271. };
  2272. /**
  2273. * Returns fields selection for this query.
  2274. *
  2275. * @return {Object}
  2276. * @api private
  2277. */
  2278. Query.prototype._fieldsForExec = function() {
  2279. return utils.clone(this._fields);
  2280. };
  2281. /**
  2282. * Return an update document with corrected $set operations.
  2283. *
  2284. * @api private
  2285. */
  2286. Query.prototype._updateForExec = function() {
  2287. const update = this._updateDoc == null ? {} : utils.clone(this._updateDoc);
  2288. const ops = utils.keys(update);
  2289. const ret = {};
  2290. for (const op of ops) {
  2291. if (this.options.overwrite) {
  2292. ret[op] = update[op];
  2293. continue;
  2294. }
  2295. if ('$' !== op[0]) {
  2296. // fix up $set sugar
  2297. if (!ret.$set) {
  2298. if (update.$set) {
  2299. ret.$set = update.$set;
  2300. } else {
  2301. ret.$set = {};
  2302. }
  2303. }
  2304. ret.$set[op] = update[op];
  2305. if (!~ops.indexOf('$set')) ops.push('$set');
  2306. } else if ('$set' === op) {
  2307. if (!ret.$set) {
  2308. ret[op] = update[op];
  2309. }
  2310. } else {
  2311. ret[op] = update[op];
  2312. }
  2313. }
  2314. this._compiledUpdate = ret;
  2315. return ret;
  2316. };
  2317. /**
  2318. * Make sure _path is set.
  2319. *
  2320. * @parmam {String} method
  2321. */
  2322. Query.prototype._ensurePath = function(method) {
  2323. if (!this._path) {
  2324. const msg = method + '() must be used after where() '
  2325. + 'when called with these arguments';
  2326. throw new Error(msg);
  2327. }
  2328. };
  2329. /*!
  2330. * Permissions
  2331. */
  2332. Query.permissions = require('./permissions');
  2333. Query._isPermitted = function(a, b) {
  2334. const denied = Query.permissions[b];
  2335. if (!denied) return true;
  2336. return true !== denied[a];
  2337. };
  2338. Query.prototype._validate = function(action) {
  2339. let fail;
  2340. let validator;
  2341. if (undefined === action) {
  2342. validator = Query.permissions[this.op];
  2343. if ('function' != typeof validator) return true;
  2344. fail = validator(this);
  2345. } else if (!Query._isPermitted(action, this.op)) {
  2346. fail = action;
  2347. }
  2348. if (fail) {
  2349. throw new Error(fail + ' cannot be used with ' + this.op);
  2350. }
  2351. };
  2352. /**
  2353. * Determines if `conds` can be merged using `mquery().merge()`
  2354. *
  2355. * @param {Object} conds
  2356. * @return {Boolean}
  2357. */
  2358. Query.canMerge = function(conds) {
  2359. return conds instanceof Query || utils.isObject(conds);
  2360. };
  2361. /**
  2362. * Set a trace function that will get called whenever a
  2363. * query is executed.
  2364. *
  2365. * See `setTraceFunction()` for details.
  2366. *
  2367. * @param {Object} conds
  2368. * @return {Boolean}
  2369. */
  2370. Query.setGlobalTraceFunction = function(traceFunction) {
  2371. Query.traceFunction = traceFunction;
  2372. };
  2373. /*!
  2374. * Exports.
  2375. */
  2376. Query.utils = utils;
  2377. Query.env = require('./env');
  2378. Query.Collection = require('./collection');
  2379. Query.BaseCollection = require('./collection/collection');
  2380. module.exports = exports = Query;
  2381. // TODO
  2382. // test utils