commands.js 19 KB

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