find_cursor.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.FindCursor = exports.FLAGS = void 0;
  4. const error_1 = require("../error");
  5. const count_1 = require("../operations/count");
  6. const execute_operation_1 = require("../operations/execute_operation");
  7. const find_1 = require("../operations/find");
  8. const sort_1 = require("../sort");
  9. const utils_1 = require("../utils");
  10. const abstract_cursor_1 = require("./abstract_cursor");
  11. /** @internal */
  12. const kFilter = Symbol('filter');
  13. /** @internal */
  14. const kNumReturned = Symbol('numReturned');
  15. /** @internal */
  16. const kBuiltOptions = Symbol('builtOptions');
  17. /** @public Flags allowed for cursor */
  18. exports.FLAGS = [
  19. 'tailable',
  20. 'oplogReplay',
  21. 'noCursorTimeout',
  22. 'awaitData',
  23. 'exhaust',
  24. 'partial'
  25. ];
  26. /** @public */
  27. class FindCursor extends abstract_cursor_1.AbstractCursor {
  28. /** @internal */
  29. constructor(topology, namespace, filter, options = {}) {
  30. super(topology, namespace, options);
  31. this[kFilter] = filter || {};
  32. this[kBuiltOptions] = options;
  33. if (options.sort != null) {
  34. this[kBuiltOptions].sort = (0, sort_1.formatSort)(options.sort);
  35. }
  36. }
  37. clone() {
  38. const clonedOptions = (0, utils_1.mergeOptions)({}, this[kBuiltOptions]);
  39. delete clonedOptions.session;
  40. return new FindCursor(this.topology, this.namespace, this[kFilter], {
  41. ...clonedOptions
  42. });
  43. }
  44. map(transform) {
  45. return super.map(transform);
  46. }
  47. /** @internal */
  48. _initialize(session, callback) {
  49. const findOperation = new find_1.FindOperation(undefined, this.namespace, this[kFilter], {
  50. ...this[kBuiltOptions],
  51. ...this.cursorOptions,
  52. session
  53. });
  54. (0, execute_operation_1.executeOperation)(this.topology, findOperation, (err, response) => {
  55. if (err || response == null)
  56. return callback(err);
  57. // TODO: We only need this for legacy queries that do not support `limit`, maybe
  58. // the value should only be saved in those cases.
  59. if (response.cursor) {
  60. this[kNumReturned] = response.cursor.firstBatch.length;
  61. }
  62. else {
  63. this[kNumReturned] = response.documents ? response.documents.length : 0;
  64. }
  65. // TODO: NODE-2882
  66. callback(undefined, { server: findOperation.server, session, response });
  67. });
  68. }
  69. /** @internal */
  70. _getMore(batchSize, callback) {
  71. // NOTE: this is to support client provided limits in pre-command servers
  72. const numReturned = this[kNumReturned];
  73. if (numReturned) {
  74. const limit = this[kBuiltOptions].limit;
  75. batchSize =
  76. limit && limit > 0 && numReturned + batchSize > limit ? limit - numReturned : batchSize;
  77. if (batchSize <= 0) {
  78. return this.close(callback);
  79. }
  80. }
  81. super._getMore(batchSize, (err, response) => {
  82. if (err)
  83. return callback(err);
  84. // TODO: wrap this in some logic to prevent it from happening if we don't need this support
  85. if (response) {
  86. this[kNumReturned] = this[kNumReturned] + response.cursor.nextBatch.length;
  87. }
  88. callback(undefined, response);
  89. });
  90. }
  91. count(options, callback) {
  92. if (typeof options === 'boolean') {
  93. throw new error_1.MongoInvalidArgumentError('Invalid first parameter to count');
  94. }
  95. if (typeof options === 'function')
  96. (callback = options), (options = {});
  97. options = options !== null && options !== void 0 ? options : {};
  98. return (0, execute_operation_1.executeOperation)(this.topology, new count_1.CountOperation(this.namespace, this[kFilter], {
  99. ...this[kBuiltOptions],
  100. ...this.cursorOptions,
  101. ...options
  102. }), callback);
  103. }
  104. explain(verbosity, callback) {
  105. if (typeof verbosity === 'function')
  106. (callback = verbosity), (verbosity = true);
  107. if (verbosity == null)
  108. verbosity = true;
  109. return (0, execute_operation_1.executeOperation)(this.topology, new find_1.FindOperation(undefined, this.namespace, this[kFilter], {
  110. ...this[kBuiltOptions],
  111. ...this.cursorOptions,
  112. explain: verbosity
  113. }), callback);
  114. }
  115. /** Set the cursor query */
  116. filter(filter) {
  117. (0, abstract_cursor_1.assertUninitialized)(this);
  118. this[kFilter] = filter;
  119. return this;
  120. }
  121. /**
  122. * Set the cursor hint
  123. *
  124. * @param hint - If specified, then the query system will only consider plans using the hinted index.
  125. */
  126. hint(hint) {
  127. (0, abstract_cursor_1.assertUninitialized)(this);
  128. this[kBuiltOptions].hint = hint;
  129. return this;
  130. }
  131. /**
  132. * Set the cursor min
  133. *
  134. * @param min - Specify a $min value to specify the inclusive lower bound for a specific index in order to constrain the results of find(). The $min specifies the lower bound for all keys of a specific index in order.
  135. */
  136. min(min) {
  137. (0, abstract_cursor_1.assertUninitialized)(this);
  138. this[kBuiltOptions].min = min;
  139. return this;
  140. }
  141. /**
  142. * Set the cursor max
  143. *
  144. * @param max - Specify a $max value to specify the exclusive upper bound for a specific index in order to constrain the results of find(). The $max specifies the upper bound for all keys of a specific index in order.
  145. */
  146. max(max) {
  147. (0, abstract_cursor_1.assertUninitialized)(this);
  148. this[kBuiltOptions].max = max;
  149. return this;
  150. }
  151. /**
  152. * Set the cursor returnKey.
  153. * If set to true, modifies the cursor to only return the index field or fields for the results of the query, rather than documents.
  154. * If set to true and the query does not use an index to perform the read operation, the returned documents will not contain any fields.
  155. *
  156. * @param value - the returnKey value.
  157. */
  158. returnKey(value) {
  159. (0, abstract_cursor_1.assertUninitialized)(this);
  160. this[kBuiltOptions].returnKey = value;
  161. return this;
  162. }
  163. /**
  164. * Modifies the output of a query by adding a field $recordId to matching documents. $recordId is the internal key which uniquely identifies a document in a collection.
  165. *
  166. * @param value - The $showDiskLoc option has now been deprecated and replaced with the showRecordId field. $showDiskLoc will still be accepted for OP_QUERY stye find.
  167. */
  168. showRecordId(value) {
  169. (0, abstract_cursor_1.assertUninitialized)(this);
  170. this[kBuiltOptions].showRecordId = value;
  171. return this;
  172. }
  173. /**
  174. * Add a query modifier to the cursor query
  175. *
  176. * @param name - The query modifier (must start with $, such as $orderby etc)
  177. * @param value - The modifier value.
  178. */
  179. addQueryModifier(name, value) {
  180. (0, abstract_cursor_1.assertUninitialized)(this);
  181. if (name[0] !== '$') {
  182. throw new error_1.MongoInvalidArgumentError(`${name} is not a valid query modifier`);
  183. }
  184. // Strip of the $
  185. const field = name.substr(1);
  186. // NOTE: consider some TS magic for this
  187. switch (field) {
  188. case 'comment':
  189. this[kBuiltOptions].comment = value;
  190. break;
  191. case 'explain':
  192. this[kBuiltOptions].explain = value;
  193. break;
  194. case 'hint':
  195. this[kBuiltOptions].hint = value;
  196. break;
  197. case 'max':
  198. this[kBuiltOptions].max = value;
  199. break;
  200. case 'maxTimeMS':
  201. this[kBuiltOptions].maxTimeMS = value;
  202. break;
  203. case 'min':
  204. this[kBuiltOptions].min = value;
  205. break;
  206. case 'orderby':
  207. this[kBuiltOptions].sort = (0, sort_1.formatSort)(value);
  208. break;
  209. case 'query':
  210. this[kFilter] = value;
  211. break;
  212. case 'returnKey':
  213. this[kBuiltOptions].returnKey = value;
  214. break;
  215. case 'showDiskLoc':
  216. this[kBuiltOptions].showRecordId = value;
  217. break;
  218. default:
  219. throw new error_1.MongoInvalidArgumentError(`Invalid query modifier: ${name}`);
  220. }
  221. return this;
  222. }
  223. /**
  224. * Add a comment to the cursor query allowing for tracking the comment in the log.
  225. *
  226. * @param value - The comment attached to this query.
  227. */
  228. comment(value) {
  229. (0, abstract_cursor_1.assertUninitialized)(this);
  230. this[kBuiltOptions].comment = value;
  231. return this;
  232. }
  233. /**
  234. * Set a maxAwaitTimeMS on a tailing cursor query to allow to customize the timeout value for the option awaitData (Only supported on MongoDB 3.2 or higher, ignored otherwise)
  235. *
  236. * @param value - Number of milliseconds to wait before aborting the tailed query.
  237. */
  238. maxAwaitTimeMS(value) {
  239. (0, abstract_cursor_1.assertUninitialized)(this);
  240. if (typeof value !== 'number') {
  241. throw new error_1.MongoInvalidArgumentError('Argument for maxAwaitTimeMS must be a number');
  242. }
  243. this[kBuiltOptions].maxAwaitTimeMS = value;
  244. return this;
  245. }
  246. /**
  247. * Set a maxTimeMS on the cursor query, allowing for hard timeout limits on queries (Only supported on MongoDB 2.6 or higher)
  248. *
  249. * @param value - Number of milliseconds to wait before aborting the query.
  250. */
  251. maxTimeMS(value) {
  252. (0, abstract_cursor_1.assertUninitialized)(this);
  253. if (typeof value !== 'number') {
  254. throw new error_1.MongoInvalidArgumentError('Argument for maxTimeMS must be a number');
  255. }
  256. this[kBuiltOptions].maxTimeMS = value;
  257. return this;
  258. }
  259. /**
  260. * Add a project stage to the aggregation pipeline
  261. *
  262. * @remarks
  263. * In order to strictly type this function you must provide an interface
  264. * that represents the effect of your projection on the result documents.
  265. *
  266. * By default chaining a projection to your cursor changes the returned type to the generic
  267. * {@link Document} type.
  268. * You should specify a parameterized type to have assertions on your final results.
  269. *
  270. * @example
  271. * ```typescript
  272. * // Best way
  273. * const docs: FindCursor<{ a: number }> = cursor.project<{ a: number }>({ _id: 0, a: true });
  274. * // Flexible way
  275. * const docs: FindCursor<Document> = cursor.project({ _id: 0, a: true });
  276. * ```
  277. *
  278. * @remarks
  279. *
  280. * **Note for Typescript Users:** adding a transform changes the return type of the iteration of this cursor,
  281. * it **does not** return a new instance of a cursor. This means when calling project,
  282. * you should always assign the result to a new variable in order to get a correctly typed cursor variable.
  283. * Take note of the following example:
  284. *
  285. * @example
  286. * ```typescript
  287. * const cursor: FindCursor<{ a: number; b: string }> = coll.find();
  288. * const projectCursor = cursor.project<{ a: number }>({ _id: 0, a: true });
  289. * const aPropOnlyArray: {a: number}[] = await projectCursor.toArray();
  290. *
  291. * // or always use chaining and save the final cursor
  292. *
  293. * const cursor = coll.find().project<{ a: string }>({
  294. * _id: 0,
  295. * a: { $convert: { input: '$a', to: 'string' }
  296. * }});
  297. * ```
  298. */
  299. project(value) {
  300. (0, abstract_cursor_1.assertUninitialized)(this);
  301. this[kBuiltOptions].projection = value;
  302. return this;
  303. }
  304. /**
  305. * Sets the sort order of the cursor query.
  306. *
  307. * @param sort - The key or keys set for the sort.
  308. * @param direction - The direction of the sorting (1 or -1).
  309. */
  310. sort(sort, direction) {
  311. (0, abstract_cursor_1.assertUninitialized)(this);
  312. if (this[kBuiltOptions].tailable) {
  313. throw new error_1.MongoTailableCursorError('Tailable cursor does not support sorting');
  314. }
  315. this[kBuiltOptions].sort = (0, sort_1.formatSort)(sort, direction);
  316. return this;
  317. }
  318. /**
  319. * Allows disk use for blocking sort operations exceeding 100MB memory. (MongoDB 3.2 or higher)
  320. *
  321. * @remarks
  322. * {@link https://docs.mongodb.com/manual/reference/command/find/#find-cmd-allowdiskuse | find command allowDiskUse documentation}
  323. */
  324. allowDiskUse() {
  325. (0, abstract_cursor_1.assertUninitialized)(this);
  326. if (!this[kBuiltOptions].sort) {
  327. throw new error_1.MongoInvalidArgumentError('Option "allowDiskUse" requires a sort specification');
  328. }
  329. this[kBuiltOptions].allowDiskUse = true;
  330. return this;
  331. }
  332. /**
  333. * Set the collation options for the cursor.
  334. *
  335. * @param value - The cursor collation options (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields).
  336. */
  337. collation(value) {
  338. (0, abstract_cursor_1.assertUninitialized)(this);
  339. this[kBuiltOptions].collation = value;
  340. return this;
  341. }
  342. /**
  343. * Set the limit for the cursor.
  344. *
  345. * @param value - The limit for the cursor query.
  346. */
  347. limit(value) {
  348. (0, abstract_cursor_1.assertUninitialized)(this);
  349. if (this[kBuiltOptions].tailable) {
  350. throw new error_1.MongoTailableCursorError('Tailable cursor does not support limit');
  351. }
  352. if (typeof value !== 'number') {
  353. throw new error_1.MongoInvalidArgumentError('Operation "limit" requires an integer');
  354. }
  355. this[kBuiltOptions].limit = value;
  356. return this;
  357. }
  358. /**
  359. * Set the skip for the cursor.
  360. *
  361. * @param value - The skip for the cursor query.
  362. */
  363. skip(value) {
  364. (0, abstract_cursor_1.assertUninitialized)(this);
  365. if (this[kBuiltOptions].tailable) {
  366. throw new error_1.MongoTailableCursorError('Tailable cursor does not support skip');
  367. }
  368. if (typeof value !== 'number') {
  369. throw new error_1.MongoInvalidArgumentError('Operation "skip" requires an integer');
  370. }
  371. this[kBuiltOptions].skip = value;
  372. return this;
  373. }
  374. }
  375. exports.FindCursor = FindCursor;
  376. //# sourceMappingURL=find_cursor.js.map