commands.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.BinMsg = exports.Msg = exports.Response = exports.Query = void 0;
  4. const BSON = require("../bson");
  5. const error_1 = require("../error");
  6. const read_preference_1 = require("../read_preference");
  7. const constants_1 = require("./wire_protocol/constants");
  8. // Incrementing request id
  9. let _requestId = 0;
  10. // Query flags
  11. const OPTS_TAILABLE_CURSOR = 2;
  12. const OPTS_SECONDARY = 4;
  13. const OPTS_OPLOG_REPLAY = 8;
  14. const OPTS_NO_CURSOR_TIMEOUT = 16;
  15. const OPTS_AWAIT_DATA = 32;
  16. const OPTS_EXHAUST = 64;
  17. const OPTS_PARTIAL = 128;
  18. // Response flags
  19. const CURSOR_NOT_FOUND = 1;
  20. const QUERY_FAILURE = 2;
  21. const SHARD_CONFIG_STALE = 4;
  22. const AWAIT_CAPABLE = 8;
  23. /**************************************************************
  24. * QUERY
  25. **************************************************************/
  26. /** @internal */
  27. class Query {
  28. constructor(databaseName, query, options) {
  29. this.databaseName = databaseName;
  30. this.query = query;
  31. // Basic options needed to be passed in
  32. // TODO(NODE-3483): Replace with MongoCommandError
  33. const ns = `${databaseName}.$cmd`;
  34. if (typeof databaseName !== 'string') {
  35. throw new error_1.MongoRuntimeError('Database name must be a string for a query');
  36. }
  37. // TODO(NODE-3483): Replace with MongoCommandError
  38. if (query == null)
  39. throw new error_1.MongoRuntimeError('A query document must be specified for query');
  40. // Validate that we are not passing 0x00 in the collection name
  41. if (ns.indexOf('\x00') !== -1) {
  42. // TODO(NODE-3483): Use MongoNamespace static method
  43. throw new error_1.MongoRuntimeError('Namespace cannot contain a null character');
  44. }
  45. // Basic options
  46. this.ns = ns;
  47. // Additional options
  48. this.numberToSkip = options.numberToSkip || 0;
  49. this.numberToReturn = options.numberToReturn || 0;
  50. this.returnFieldSelector = options.returnFieldSelector || undefined;
  51. this.requestId = Query.getRequestId();
  52. // special case for pre-3.2 find commands, delete ASAP
  53. this.pre32Limit = options.pre32Limit;
  54. // Serialization option
  55. this.serializeFunctions =
  56. typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
  57. this.ignoreUndefined =
  58. typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
  59. this.maxBsonSize = options.maxBsonSize || 1024 * 1024 * 16;
  60. this.checkKeys = typeof options.checkKeys === 'boolean' ? options.checkKeys : false;
  61. this.batchSize = this.numberToReturn;
  62. // Flags
  63. this.tailable = false;
  64. this.secondaryOk = typeof options.secondaryOk === 'boolean' ? options.secondaryOk : false;
  65. this.oplogReplay = false;
  66. this.noCursorTimeout = false;
  67. this.awaitData = false;
  68. this.exhaust = false;
  69. this.partial = false;
  70. }
  71. /** Assign next request Id. */
  72. incRequestId() {
  73. this.requestId = _requestId++;
  74. }
  75. /** Peek next request Id. */
  76. nextRequestId() {
  77. return _requestId + 1;
  78. }
  79. /** Increment then return next request Id. */
  80. static getRequestId() {
  81. return ++_requestId;
  82. }
  83. // Uses a single allocated buffer for the process, avoiding multiple memory allocations
  84. toBin() {
  85. const buffers = [];
  86. let projection = null;
  87. // Set up the flags
  88. let flags = 0;
  89. if (this.tailable) {
  90. flags |= OPTS_TAILABLE_CURSOR;
  91. }
  92. if (this.secondaryOk) {
  93. flags |= OPTS_SECONDARY;
  94. }
  95. if (this.oplogReplay) {
  96. flags |= OPTS_OPLOG_REPLAY;
  97. }
  98. if (this.noCursorTimeout) {
  99. flags |= OPTS_NO_CURSOR_TIMEOUT;
  100. }
  101. if (this.awaitData) {
  102. flags |= OPTS_AWAIT_DATA;
  103. }
  104. if (this.exhaust) {
  105. flags |= OPTS_EXHAUST;
  106. }
  107. if (this.partial) {
  108. flags |= OPTS_PARTIAL;
  109. }
  110. // If batchSize is different to this.numberToReturn
  111. if (this.batchSize !== this.numberToReturn)
  112. this.numberToReturn = this.batchSize;
  113. // Allocate write protocol header buffer
  114. const header = Buffer.alloc(4 * 4 + // Header
  115. 4 + // Flags
  116. Buffer.byteLength(this.ns) +
  117. 1 + // namespace
  118. 4 + // numberToSkip
  119. 4 // numberToReturn
  120. );
  121. // Add header to buffers
  122. buffers.push(header);
  123. // Serialize the query
  124. const query = BSON.serialize(this.query, {
  125. checkKeys: this.checkKeys,
  126. serializeFunctions: this.serializeFunctions,
  127. ignoreUndefined: this.ignoreUndefined
  128. });
  129. // Add query document
  130. buffers.push(query);
  131. if (this.returnFieldSelector && Object.keys(this.returnFieldSelector).length > 0) {
  132. // Serialize the projection document
  133. projection = BSON.serialize(this.returnFieldSelector, {
  134. checkKeys: this.checkKeys,
  135. serializeFunctions: this.serializeFunctions,
  136. ignoreUndefined: this.ignoreUndefined
  137. });
  138. // Add projection document
  139. buffers.push(projection);
  140. }
  141. // Total message size
  142. const totalLength = header.length + query.length + (projection ? projection.length : 0);
  143. // Set up the index
  144. let index = 4;
  145. // Write total document length
  146. header[3] = (totalLength >> 24) & 0xff;
  147. header[2] = (totalLength >> 16) & 0xff;
  148. header[1] = (totalLength >> 8) & 0xff;
  149. header[0] = totalLength & 0xff;
  150. // Write header information requestId
  151. header[index + 3] = (this.requestId >> 24) & 0xff;
  152. header[index + 2] = (this.requestId >> 16) & 0xff;
  153. header[index + 1] = (this.requestId >> 8) & 0xff;
  154. header[index] = this.requestId & 0xff;
  155. index = index + 4;
  156. // Write header information responseTo
  157. header[index + 3] = (0 >> 24) & 0xff;
  158. header[index + 2] = (0 >> 16) & 0xff;
  159. header[index + 1] = (0 >> 8) & 0xff;
  160. header[index] = 0 & 0xff;
  161. index = index + 4;
  162. // Write header information OP_QUERY
  163. header[index + 3] = (constants_1.OP_QUERY >> 24) & 0xff;
  164. header[index + 2] = (constants_1.OP_QUERY >> 16) & 0xff;
  165. header[index + 1] = (constants_1.OP_QUERY >> 8) & 0xff;
  166. header[index] = constants_1.OP_QUERY & 0xff;
  167. index = index + 4;
  168. // Write header information flags
  169. header[index + 3] = (flags >> 24) & 0xff;
  170. header[index + 2] = (flags >> 16) & 0xff;
  171. header[index + 1] = (flags >> 8) & 0xff;
  172. header[index] = flags & 0xff;
  173. index = index + 4;
  174. // Write collection name
  175. index = index + header.write(this.ns, index, 'utf8') + 1;
  176. header[index - 1] = 0;
  177. // Write header information flags numberToSkip
  178. header[index + 3] = (this.numberToSkip >> 24) & 0xff;
  179. header[index + 2] = (this.numberToSkip >> 16) & 0xff;
  180. header[index + 1] = (this.numberToSkip >> 8) & 0xff;
  181. header[index] = this.numberToSkip & 0xff;
  182. index = index + 4;
  183. // Write header information flags numberToReturn
  184. header[index + 3] = (this.numberToReturn >> 24) & 0xff;
  185. header[index + 2] = (this.numberToReturn >> 16) & 0xff;
  186. header[index + 1] = (this.numberToReturn >> 8) & 0xff;
  187. header[index] = this.numberToReturn & 0xff;
  188. index = index + 4;
  189. // Return the buffers
  190. return buffers;
  191. }
  192. }
  193. exports.Query = Query;
  194. /** @internal */
  195. class Response {
  196. constructor(message, msgHeader, msgBody, opts) {
  197. this.documents = new Array(0);
  198. this.parsed = false;
  199. this.raw = message;
  200. this.data = msgBody;
  201. this.opts = opts ?? {
  202. useBigInt64: false,
  203. promoteLongs: true,
  204. promoteValues: true,
  205. promoteBuffers: false,
  206. bsonRegExp: false
  207. };
  208. // Read the message header
  209. this.length = msgHeader.length;
  210. this.requestId = msgHeader.requestId;
  211. this.responseTo = msgHeader.responseTo;
  212. this.opCode = msgHeader.opCode;
  213. this.fromCompressed = msgHeader.fromCompressed;
  214. // Flag values
  215. this.useBigInt64 = typeof this.opts.useBigInt64 === 'boolean' ? this.opts.useBigInt64 : false;
  216. this.promoteLongs = typeof this.opts.promoteLongs === 'boolean' ? this.opts.promoteLongs : true;
  217. this.promoteValues =
  218. typeof this.opts.promoteValues === 'boolean' ? this.opts.promoteValues : true;
  219. this.promoteBuffers =
  220. typeof this.opts.promoteBuffers === 'boolean' ? this.opts.promoteBuffers : false;
  221. this.bsonRegExp = typeof this.opts.bsonRegExp === 'boolean' ? this.opts.bsonRegExp : false;
  222. }
  223. isParsed() {
  224. return this.parsed;
  225. }
  226. parse(options) {
  227. // Don't parse again if not needed
  228. if (this.parsed)
  229. return;
  230. options = options ?? {};
  231. // Allow the return of raw documents instead of parsing
  232. const raw = options.raw || false;
  233. const documentsReturnedIn = options.documentsReturnedIn || null;
  234. const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
  235. const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
  236. const promoteValues = options.promoteValues ?? this.opts.promoteValues;
  237. const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
  238. const bsonRegExp = options.bsonRegExp ?? this.opts.bsonRegExp;
  239. let bsonSize;
  240. // Set up the options
  241. const _options = {
  242. useBigInt64,
  243. promoteLongs,
  244. promoteValues,
  245. promoteBuffers,
  246. bsonRegExp
  247. };
  248. // Position within OP_REPLY at which documents start
  249. // (See https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/#wire-op-reply)
  250. this.index = 20;
  251. // Read the message body
  252. this.responseFlags = this.data.readInt32LE(0);
  253. this.cursorId = new BSON.Long(this.data.readInt32LE(4), this.data.readInt32LE(8));
  254. this.startingFrom = this.data.readInt32LE(12);
  255. this.numberReturned = this.data.readInt32LE(16);
  256. // Preallocate document array
  257. this.documents = new Array(this.numberReturned);
  258. this.cursorNotFound = (this.responseFlags & CURSOR_NOT_FOUND) !== 0;
  259. this.queryFailure = (this.responseFlags & QUERY_FAILURE) !== 0;
  260. this.shardConfigStale = (this.responseFlags & SHARD_CONFIG_STALE) !== 0;
  261. this.awaitCapable = (this.responseFlags & AWAIT_CAPABLE) !== 0;
  262. // Parse Body
  263. for (let i = 0; i < this.numberReturned; i++) {
  264. bsonSize =
  265. this.data[this.index] |
  266. (this.data[this.index + 1] << 8) |
  267. (this.data[this.index + 2] << 16) |
  268. (this.data[this.index + 3] << 24);
  269. // If we have raw results specified slice the return document
  270. if (raw) {
  271. this.documents[i] = this.data.slice(this.index, this.index + bsonSize);
  272. }
  273. else {
  274. this.documents[i] = BSON.deserialize(this.data.slice(this.index, this.index + bsonSize), _options);
  275. }
  276. // Adjust the index
  277. this.index = this.index + bsonSize;
  278. }
  279. if (this.documents.length === 1 && documentsReturnedIn != null && raw) {
  280. const fieldsAsRaw = {};
  281. fieldsAsRaw[documentsReturnedIn] = true;
  282. _options.fieldsAsRaw = fieldsAsRaw;
  283. const doc = BSON.deserialize(this.documents[0], _options);
  284. this.documents = [doc];
  285. }
  286. // Set parsed
  287. this.parsed = true;
  288. }
  289. }
  290. exports.Response = Response;
  291. // Implementation of OP_MSG spec:
  292. // https://github.com/mongodb/specifications/blob/master/source/message/OP_MSG.rst
  293. //
  294. // struct Section {
  295. // uint8 payloadType;
  296. // union payload {
  297. // document document; // payloadType == 0
  298. // struct sequence { // payloadType == 1
  299. // int32 size;
  300. // cstring identifier;
  301. // document* documents;
  302. // };
  303. // };
  304. // };
  305. // struct OP_MSG {
  306. // struct MsgHeader {
  307. // int32 messageLength;
  308. // int32 requestID;
  309. // int32 responseTo;
  310. // int32 opCode = 2013;
  311. // };
  312. // uint32 flagBits;
  313. // Section+ sections;
  314. // [uint32 checksum;]
  315. // };
  316. // Msg Flags
  317. const OPTS_CHECKSUM_PRESENT = 1;
  318. const OPTS_MORE_TO_COME = 2;
  319. const OPTS_EXHAUST_ALLOWED = 1 << 16;
  320. /** @internal */
  321. class Msg {
  322. constructor(databaseName, command, options) {
  323. this.databaseName = databaseName;
  324. this.command = command;
  325. this.options = options;
  326. // Basic options needed to be passed in
  327. if (command == null)
  328. throw new error_1.MongoInvalidArgumentError('Query document must be specified for query');
  329. // Basic options
  330. this.command.$db = databaseName;
  331. if (options.readPreference && options.readPreference.mode !== read_preference_1.ReadPreference.PRIMARY) {
  332. this.command.$readPreference = options.readPreference.toJSON();
  333. }
  334. // Ensure empty options
  335. this.options = options ?? {};
  336. // Additional options
  337. this.requestId = options.requestId ? options.requestId : Msg.getRequestId();
  338. // Serialization option
  339. this.serializeFunctions =
  340. typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
  341. this.ignoreUndefined =
  342. typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
  343. this.checkKeys = typeof options.checkKeys === 'boolean' ? options.checkKeys : false;
  344. this.maxBsonSize = options.maxBsonSize || 1024 * 1024 * 16;
  345. // flags
  346. this.checksumPresent = false;
  347. this.moreToCome = options.moreToCome || false;
  348. this.exhaustAllowed =
  349. typeof options.exhaustAllowed === 'boolean' ? options.exhaustAllowed : false;
  350. }
  351. toBin() {
  352. const buffers = [];
  353. let flags = 0;
  354. if (this.checksumPresent) {
  355. flags |= OPTS_CHECKSUM_PRESENT;
  356. }
  357. if (this.moreToCome) {
  358. flags |= OPTS_MORE_TO_COME;
  359. }
  360. if (this.exhaustAllowed) {
  361. flags |= OPTS_EXHAUST_ALLOWED;
  362. }
  363. const header = Buffer.alloc(4 * 4 + // Header
  364. 4 // Flags
  365. );
  366. buffers.push(header);
  367. let totalLength = header.length;
  368. const command = this.command;
  369. totalLength += this.makeDocumentSegment(buffers, command);
  370. header.writeInt32LE(totalLength, 0); // messageLength
  371. header.writeInt32LE(this.requestId, 4); // requestID
  372. header.writeInt32LE(0, 8); // responseTo
  373. header.writeInt32LE(constants_1.OP_MSG, 12); // opCode
  374. header.writeUInt32LE(flags, 16); // flags
  375. return buffers;
  376. }
  377. makeDocumentSegment(buffers, document) {
  378. const payloadTypeBuffer = Buffer.alloc(1);
  379. payloadTypeBuffer[0] = 0;
  380. const documentBuffer = this.serializeBson(document);
  381. buffers.push(payloadTypeBuffer);
  382. buffers.push(documentBuffer);
  383. return payloadTypeBuffer.length + documentBuffer.length;
  384. }
  385. serializeBson(document) {
  386. return BSON.serialize(document, {
  387. checkKeys: this.checkKeys,
  388. serializeFunctions: this.serializeFunctions,
  389. ignoreUndefined: this.ignoreUndefined
  390. });
  391. }
  392. static getRequestId() {
  393. _requestId = (_requestId + 1) & 0x7fffffff;
  394. return _requestId;
  395. }
  396. }
  397. exports.Msg = Msg;
  398. /** @internal */
  399. class BinMsg {
  400. constructor(message, msgHeader, msgBody, opts) {
  401. this.parsed = false;
  402. this.raw = message;
  403. this.data = msgBody;
  404. this.opts = opts ?? {
  405. useBigInt64: false,
  406. promoteLongs: true,
  407. promoteValues: true,
  408. promoteBuffers: false,
  409. bsonRegExp: false
  410. };
  411. // Read the message header
  412. this.length = msgHeader.length;
  413. this.requestId = msgHeader.requestId;
  414. this.responseTo = msgHeader.responseTo;
  415. this.opCode = msgHeader.opCode;
  416. this.fromCompressed = msgHeader.fromCompressed;
  417. // Read response flags
  418. this.responseFlags = msgBody.readInt32LE(0);
  419. this.checksumPresent = (this.responseFlags & OPTS_CHECKSUM_PRESENT) !== 0;
  420. this.moreToCome = (this.responseFlags & OPTS_MORE_TO_COME) !== 0;
  421. this.exhaustAllowed = (this.responseFlags & OPTS_EXHAUST_ALLOWED) !== 0;
  422. this.useBigInt64 = typeof this.opts.useBigInt64 === 'boolean' ? this.opts.useBigInt64 : false;
  423. this.promoteLongs = typeof this.opts.promoteLongs === 'boolean' ? this.opts.promoteLongs : true;
  424. this.promoteValues =
  425. typeof this.opts.promoteValues === 'boolean' ? this.opts.promoteValues : true;
  426. this.promoteBuffers =
  427. typeof this.opts.promoteBuffers === 'boolean' ? this.opts.promoteBuffers : false;
  428. this.bsonRegExp = typeof this.opts.bsonRegExp === 'boolean' ? this.opts.bsonRegExp : false;
  429. this.documents = [];
  430. }
  431. isParsed() {
  432. return this.parsed;
  433. }
  434. parse(options) {
  435. // Don't parse again if not needed
  436. if (this.parsed)
  437. return;
  438. options = options ?? {};
  439. this.index = 4;
  440. // Allow the return of raw documents instead of parsing
  441. const raw = options.raw || false;
  442. const documentsReturnedIn = options.documentsReturnedIn || null;
  443. const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
  444. const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
  445. const promoteValues = options.promoteValues ?? this.opts.promoteValues;
  446. const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
  447. const bsonRegExp = options.bsonRegExp ?? this.opts.bsonRegExp;
  448. const validation = this.parseBsonSerializationOptions(options);
  449. // Set up the options
  450. const bsonOptions = {
  451. useBigInt64,
  452. promoteLongs,
  453. promoteValues,
  454. promoteBuffers,
  455. bsonRegExp,
  456. validation
  457. // Due to the strictness of the BSON libraries validation option we need this cast
  458. };
  459. while (this.index < this.data.length) {
  460. const payloadType = this.data.readUInt8(this.index++);
  461. if (payloadType === 0) {
  462. const bsonSize = this.data.readUInt32LE(this.index);
  463. const bin = this.data.slice(this.index, this.index + bsonSize);
  464. this.documents.push(raw ? bin : BSON.deserialize(bin, bsonOptions));
  465. this.index += bsonSize;
  466. }
  467. else if (payloadType === 1) {
  468. // It was decided that no driver makes use of payload type 1
  469. // TODO(NODE-3483): Replace with MongoDeprecationError
  470. throw new error_1.MongoRuntimeError('OP_MSG Payload Type 1 detected unsupported protocol');
  471. }
  472. }
  473. if (this.documents.length === 1 && documentsReturnedIn != null && raw) {
  474. const fieldsAsRaw = {};
  475. fieldsAsRaw[documentsReturnedIn] = true;
  476. bsonOptions.fieldsAsRaw = fieldsAsRaw;
  477. const doc = BSON.deserialize(this.documents[0], bsonOptions);
  478. this.documents = [doc];
  479. }
  480. this.parsed = true;
  481. }
  482. parseBsonSerializationOptions({ enableUtf8Validation }) {
  483. if (enableUtf8Validation === false) {
  484. return { utf8: false };
  485. }
  486. return { utf8: { writeErrors: false } };
  487. }
  488. }
  489. exports.BinMsg = BinMsg;
  490. //# sourceMappingURL=commands.js.map