mquery.js 79 KB

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