find_cursor.js 15 KB

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