/node_modules/mongoose/node_modules/mongodb-core/lib/wireprotocol/3_2_support.js

https://bitbucket.org/coleman333/smartsite · JavaScript · 590 lines · 330 code · 97 blank · 163 comment · 100 complexity · 4da5b80939d2000669807c932332dd02 MD5 · raw file

  1. 'use strict';
  2. var Query = require('../connection/commands').Query,
  3. retrieveBSON = require('../connection/utils').retrieveBSON,
  4. f = require('util').format,
  5. MongoError = require('../error').MongoError,
  6. MongoNetworkError = require('../error').MongoNetworkError,
  7. getReadPreference = require('./shared').getReadPreference;
  8. var BSON = retrieveBSON(),
  9. Long = BSON.Long;
  10. var WireProtocol = function(legacyWireProtocol) {
  11. this.legacyWireProtocol = legacyWireProtocol;
  12. };
  13. //
  14. // Execute a write operation
  15. var executeWrite = function(pool, bson, type, opsField, ns, ops, options, callback) {
  16. if (ops.length === 0) throw new MongoError('insert must contain at least one document');
  17. if (typeof options === 'function') {
  18. callback = options;
  19. options = {};
  20. options = options || {};
  21. }
  22. // Split the ns up to get db and collection
  23. var p = ns.split('.');
  24. var d = p.shift();
  25. // Options
  26. var ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
  27. var writeConcern = options.writeConcern;
  28. // return skeleton
  29. var writeCommand = {};
  30. writeCommand[type] = p.join('.');
  31. writeCommand[opsField] = ops;
  32. writeCommand.ordered = ordered;
  33. // Did we specify a write concern
  34. if (writeConcern && Object.keys(writeConcern).length > 0) {
  35. writeCommand.writeConcern = writeConcern;
  36. }
  37. // If we have collation passed in
  38. if (options.collation) {
  39. for (var i = 0; i < writeCommand[opsField].length; i++) {
  40. if (!writeCommand[opsField][i].collation) {
  41. writeCommand[opsField][i].collation = options.collation;
  42. }
  43. }
  44. }
  45. // Do we have bypassDocumentValidation set, then enable it on the write command
  46. if (typeof options.bypassDocumentValidation === 'boolean') {
  47. writeCommand.bypassDocumentValidation = options.bypassDocumentValidation;
  48. }
  49. // optionally add a `txnNumber` if retryable writes are being attempted
  50. if (typeof options.txnNumber !== 'undefined') {
  51. writeCommand.txnNumber = options.txnNumber;
  52. }
  53. // Options object
  54. var opts = { command: true };
  55. if (typeof options.session !== 'undefined') opts.session = options.session;
  56. var queryOptions = { checkKeys: false, numberToSkip: 0, numberToReturn: 1 };
  57. if (type === 'insert') queryOptions.checkKeys = false;
  58. if (typeof options.checkKeys === 'boolean') queryOptions.checkKeys = options.checkKeys;
  59. // Ensure we support serialization of functions
  60. if (options.serializeFunctions) queryOptions.serializeFunctions = options.serializeFunctions;
  61. // Do not serialize the undefined fields
  62. if (options.ignoreUndefined) queryOptions.ignoreUndefined = options.ignoreUndefined;
  63. try {
  64. // Create write command
  65. var cmd = new Query(bson, f('%s.$cmd', d), writeCommand, queryOptions);
  66. // Execute command
  67. pool.write(cmd, opts, callback);
  68. } catch (err) {
  69. callback(err);
  70. }
  71. };
  72. //
  73. // Needs to support legacy mass insert as well as ordered/unordered legacy
  74. // emulation
  75. //
  76. WireProtocol.prototype.insert = function(pool, ismaster, ns, bson, ops, options, callback) {
  77. executeWrite(pool, bson, 'insert', 'documents', ns, ops, options, callback);
  78. };
  79. WireProtocol.prototype.update = function(pool, ismaster, ns, bson, ops, options, callback) {
  80. executeWrite(pool, bson, 'update', 'updates', ns, ops, options, callback);
  81. };
  82. WireProtocol.prototype.remove = function(pool, ismaster, ns, bson, ops, options, callback) {
  83. executeWrite(pool, bson, 'delete', 'deletes', ns, ops, options, callback);
  84. };
  85. WireProtocol.prototype.killCursor = function(bson, ns, cursorState, pool, callback) {
  86. // Build command namespace
  87. var parts = ns.split(/\./);
  88. // Command namespace
  89. var commandns = f('%s.$cmd', parts.shift());
  90. const cursorId = cursorState.cursorId;
  91. // Create killCursor command
  92. var killcursorCmd = {
  93. killCursors: parts.join('.'),
  94. cursors: [cursorId]
  95. };
  96. // Build Query object
  97. var query = new Query(bson, commandns, killcursorCmd, {
  98. numberToSkip: 0,
  99. numberToReturn: -1,
  100. checkKeys: false,
  101. returnFieldSelector: null
  102. });
  103. // Set query flags
  104. query.slaveOk = true;
  105. // Kill cursor callback
  106. var killCursorCallback = function(err, result) {
  107. if (err) {
  108. if (typeof callback !== 'function') return;
  109. return callback(err);
  110. }
  111. // Result
  112. var r = result.message;
  113. // If we have a timed out query or a cursor that was killed
  114. if ((r.responseFlags & (1 << 0)) !== 0) {
  115. if (typeof callback !== 'function') return;
  116. return callback(new MongoNetworkError('cursor killed or timed out'), null);
  117. }
  118. if (!Array.isArray(r.documents) || r.documents.length === 0) {
  119. if (typeof callback !== 'function') return;
  120. return callback(
  121. new MongoError(f('invalid killCursors result returned for cursor id %s', cursorId))
  122. );
  123. }
  124. // Return the result
  125. if (typeof callback === 'function') {
  126. callback(null, r.documents[0]);
  127. }
  128. };
  129. const options = { command: true };
  130. if (typeof cursorState.session === 'object') {
  131. options.session = cursorState.session;
  132. }
  133. // Execute the kill cursor command
  134. if (pool && pool.isConnected()) {
  135. try {
  136. pool.write(query, options, killCursorCallback);
  137. } catch (err) {
  138. killCursorCallback(err, null);
  139. }
  140. return;
  141. }
  142. // Callback
  143. if (typeof callback === 'function') callback(null, null);
  144. };
  145. WireProtocol.prototype.getMore = function(
  146. bson,
  147. ns,
  148. cursorState,
  149. batchSize,
  150. raw,
  151. connection,
  152. options,
  153. callback
  154. ) {
  155. options = options || {};
  156. // Build command namespace
  157. var parts = ns.split(/\./);
  158. // Command namespace
  159. var commandns = f('%s.$cmd', parts.shift());
  160. // Create getMore command
  161. var getMoreCmd = {
  162. getMore: cursorState.cursorId,
  163. collection: parts.join('.'),
  164. batchSize: Math.abs(batchSize)
  165. };
  166. if (cursorState.cmd.tailable && typeof cursorState.cmd.maxAwaitTimeMS === 'number') {
  167. getMoreCmd.maxTimeMS = cursorState.cmd.maxAwaitTimeMS;
  168. }
  169. // Build Query object
  170. var query = new Query(bson, commandns, getMoreCmd, {
  171. numberToSkip: 0,
  172. numberToReturn: -1,
  173. checkKeys: false,
  174. returnFieldSelector: null
  175. });
  176. // Set query flags
  177. query.slaveOk = true;
  178. // Query callback
  179. var queryCallback = function(err, result) {
  180. if (err) return callback(err);
  181. // Get the raw message
  182. var r = result.message;
  183. // If we have a timed out query or a cursor that was killed
  184. if ((r.responseFlags & (1 << 0)) !== 0) {
  185. return callback(new MongoNetworkError('cursor killed or timed out'), null);
  186. }
  187. // Raw, return all the extracted documents
  188. if (raw) {
  189. cursorState.documents = r.documents;
  190. cursorState.cursorId = r.cursorId;
  191. return callback(null, r.documents);
  192. }
  193. // We have an error detected
  194. if (r.documents[0].ok === 0) {
  195. return callback(new MongoError(r.documents[0]));
  196. }
  197. // Ensure we have a Long valid cursor id
  198. var cursorId =
  199. typeof r.documents[0].cursor.id === 'number'
  200. ? Long.fromNumber(r.documents[0].cursor.id)
  201. : r.documents[0].cursor.id;
  202. // Set all the values
  203. cursorState.documents = r.documents[0].cursor.nextBatch;
  204. cursorState.cursorId = cursorId;
  205. // Return the result
  206. callback(null, r.documents[0], r.connection);
  207. };
  208. // Query options
  209. var queryOptions = { command: true };
  210. // If we have a raw query decorate the function
  211. if (raw) {
  212. queryOptions.raw = raw;
  213. }
  214. // Add the result field needed
  215. queryOptions.documentsReturnedIn = 'nextBatch';
  216. // Check if we need to promote longs
  217. if (typeof cursorState.promoteLongs === 'boolean') {
  218. queryOptions.promoteLongs = cursorState.promoteLongs;
  219. }
  220. if (typeof cursorState.promoteValues === 'boolean') {
  221. queryOptions.promoteValues = cursorState.promoteValues;
  222. }
  223. if (typeof cursorState.promoteBuffers === 'boolean') {
  224. queryOptions.promoteBuffers = cursorState.promoteBuffers;
  225. }
  226. if (typeof cursorState.session === 'object') {
  227. queryOptions.session = cursorState.session;
  228. }
  229. // Write out the getMore command
  230. connection.write(query, queryOptions, queryCallback);
  231. };
  232. WireProtocol.prototype.command = function(bson, ns, cmd, cursorState, topology, options) {
  233. options = options || {};
  234. // Check if this is a wire protocol command or not
  235. var wireProtocolCommand =
  236. typeof options.wireProtocolCommand === 'boolean' ? options.wireProtocolCommand : true;
  237. // Establish type of command
  238. if (cmd.find && wireProtocolCommand) {
  239. // Create the find command
  240. var query = executeFindCommand(bson, ns, cmd, cursorState, topology, options);
  241. // Mark the cmd as virtual
  242. cmd.virtual = false;
  243. // Signal the documents are in the firstBatch value
  244. query.documentsReturnedIn = 'firstBatch';
  245. // Return the query
  246. return query;
  247. } else if (cursorState.cursorId != null) {
  248. return;
  249. } else if (cmd) {
  250. return setupCommand(bson, ns, cmd, cursorState, topology, options);
  251. } else {
  252. throw new MongoError(f('command %s does not return a cursor', JSON.stringify(cmd)));
  253. }
  254. };
  255. // // Command
  256. // {
  257. // find: ns
  258. // , query: <object>
  259. // , limit: <n>
  260. // , fields: <object>
  261. // , skip: <n>
  262. // , hint: <string>
  263. // , explain: <boolean>
  264. // , snapshot: <boolean>
  265. // , batchSize: <n>
  266. // , returnKey: <boolean>
  267. // , maxScan: <n>
  268. // , min: <n>
  269. // , max: <n>
  270. // , showDiskLoc: <boolean>
  271. // , comment: <string>
  272. // , maxTimeMS: <n>
  273. // , raw: <boolean>
  274. // , readPreference: <ReadPreference>
  275. // , tailable: <boolean>
  276. // , oplogReplay: <boolean>
  277. // , noCursorTimeout: <boolean>
  278. // , awaitdata: <boolean>
  279. // , exhaust: <boolean>
  280. // , partial: <boolean>
  281. // }
  282. // FIND/GETMORE SPEC
  283. // {
  284. // “find”: <string>,
  285. // “filter”: { ... },
  286. // “sort”: { ... },
  287. // “projection”: { ... },
  288. // “hint”: { ... },
  289. // “skip”: <int>,
  290. // “limit”: <int>,
  291. // “batchSize”: <int>,
  292. // “singleBatch”: <bool>,
  293. // “comment”: <string>,
  294. // “maxScan”: <int>,
  295. // “maxTimeMS”: <int>,
  296. // “max”: { ... },
  297. // “min”: { ... },
  298. // “returnKey”: <bool>,
  299. // “showRecordId”: <bool>,
  300. // “snapshot”: <bool>,
  301. // “tailable”: <bool>,
  302. // “oplogReplay”: <bool>,
  303. // “noCursorTimeout”: <bool>,
  304. // “awaitData”: <bool>,
  305. // “partial”: <bool>,
  306. // “$readPreference”: { ... }
  307. // }
  308. //
  309. // Execute a find command
  310. var executeFindCommand = function(bson, ns, cmd, cursorState, topology, options) {
  311. // Ensure we have at least some options
  312. options = options || {};
  313. // Get the readPreference
  314. var readPreference = getReadPreference(cmd, options);
  315. // Set the optional batchSize
  316. cursorState.batchSize = cmd.batchSize || cursorState.batchSize;
  317. // Build command namespace
  318. var parts = ns.split(/\./);
  319. // Command namespace
  320. var commandns = f('%s.$cmd', parts.shift());
  321. // Build actual find command
  322. var findCmd = {
  323. find: parts.join('.')
  324. };
  325. // I we provided a filter
  326. if (cmd.query) {
  327. // Check if the user is passing in the $query parameter
  328. if (cmd.query['$query']) {
  329. findCmd.filter = cmd.query['$query'];
  330. } else {
  331. findCmd.filter = cmd.query;
  332. }
  333. }
  334. // Sort value
  335. var sortValue = cmd.sort;
  336. // Handle issue of sort being an Array
  337. if (Array.isArray(sortValue)) {
  338. var sortObject = {};
  339. if (sortValue.length > 0 && !Array.isArray(sortValue[0])) {
  340. var sortDirection = sortValue[1];
  341. // Translate the sort order text
  342. if (sortDirection === 'asc') {
  343. sortDirection = 1;
  344. } else if (sortDirection === 'desc') {
  345. sortDirection = -1;
  346. }
  347. // Set the sort order
  348. sortObject[sortValue[0]] = sortDirection;
  349. } else {
  350. for (var i = 0; i < sortValue.length; i++) {
  351. sortDirection = sortValue[i][1];
  352. // Translate the sort order text
  353. if (sortDirection === 'asc') {
  354. sortDirection = 1;
  355. } else if (sortDirection === 'desc') {
  356. sortDirection = -1;
  357. }
  358. // Set the sort order
  359. sortObject[sortValue[i][0]] = sortDirection;
  360. }
  361. }
  362. sortValue = sortObject;
  363. }
  364. // Add sort to command
  365. if (cmd.sort) findCmd.sort = sortValue;
  366. // Add a projection to the command
  367. if (cmd.fields) findCmd.projection = cmd.fields;
  368. // Add a hint to the command
  369. if (cmd.hint) findCmd.hint = cmd.hint;
  370. // Add a skip
  371. if (cmd.skip) findCmd.skip = cmd.skip;
  372. // Add a limit
  373. if (cmd.limit) findCmd.limit = cmd.limit;
  374. // Check if we wish to have a singleBatch
  375. if (cmd.limit < 0) {
  376. findCmd.limit = Math.abs(cmd.limit);
  377. findCmd.singleBatch = true;
  378. }
  379. // Add a batchSize
  380. if (typeof cmd.batchSize === 'number') {
  381. if (cmd.batchSize < 0) {
  382. if (cmd.limit !== 0 && Math.abs(cmd.batchSize) < Math.abs(cmd.limit)) {
  383. findCmd.limit = Math.abs(cmd.batchSize);
  384. }
  385. findCmd.singleBatch = true;
  386. }
  387. findCmd.batchSize = Math.abs(cmd.batchSize);
  388. }
  389. // If we have comment set
  390. if (cmd.comment) findCmd.comment = cmd.comment;
  391. // If we have maxScan
  392. if (cmd.maxScan) findCmd.maxScan = cmd.maxScan;
  393. // If we have maxTimeMS set
  394. if (cmd.maxTimeMS) findCmd.maxTimeMS = cmd.maxTimeMS;
  395. // If we have min
  396. if (cmd.min) findCmd.min = cmd.min;
  397. // If we have max
  398. if (cmd.max) findCmd.max = cmd.max;
  399. // If we have returnKey set
  400. if (cmd.returnKey) findCmd.returnKey = cmd.returnKey;
  401. // If we have showDiskLoc set
  402. if (cmd.showDiskLoc) findCmd.showRecordId = cmd.showDiskLoc;
  403. // If we have snapshot set
  404. if (cmd.snapshot) findCmd.snapshot = cmd.snapshot;
  405. // If we have tailable set
  406. if (cmd.tailable) findCmd.tailable = cmd.tailable;
  407. // If we have oplogReplay set
  408. if (cmd.oplogReplay) findCmd.oplogReplay = cmd.oplogReplay;
  409. // If we have noCursorTimeout set
  410. if (cmd.noCursorTimeout) findCmd.noCursorTimeout = cmd.noCursorTimeout;
  411. // If we have awaitData set
  412. if (cmd.awaitData) findCmd.awaitData = cmd.awaitData;
  413. if (cmd.awaitdata) findCmd.awaitData = cmd.awaitdata;
  414. // If we have partial set
  415. if (cmd.partial) findCmd.partial = cmd.partial;
  416. // If we have collation passed in
  417. if (cmd.collation) findCmd.collation = cmd.collation;
  418. // If we have explain, we need to rewrite the find command
  419. // to wrap it in the explain command
  420. if (cmd.explain) {
  421. findCmd = {
  422. explain: findCmd
  423. };
  424. }
  425. // Did we provide a readConcern
  426. if (cmd.readConcern) findCmd.readConcern = cmd.readConcern;
  427. // Set up the serialize and ignoreUndefined fields
  428. var serializeFunctions =
  429. typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
  430. var ignoreUndefined =
  431. typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
  432. // We have a Mongos topology, check if we need to add a readPreference
  433. if (topology.type === 'mongos' && readPreference && readPreference.preference !== 'primary') {
  434. findCmd = {
  435. $query: findCmd,
  436. $readPreference: readPreference.toJSON()
  437. };
  438. }
  439. // Build Query object
  440. var query = new Query(bson, commandns, findCmd, {
  441. numberToSkip: 0,
  442. numberToReturn: 1,
  443. checkKeys: false,
  444. returnFieldSelector: null,
  445. serializeFunctions: serializeFunctions,
  446. ignoreUndefined: ignoreUndefined
  447. });
  448. // Set query flags
  449. query.slaveOk = readPreference.slaveOk();
  450. // Return the query
  451. return query;
  452. };
  453. //
  454. // Set up a command cursor
  455. var setupCommand = function(bson, ns, cmd, cursorState, topology, options) {
  456. // Set empty options object
  457. options = options || {};
  458. // Get the readPreference
  459. var readPreference = getReadPreference(cmd, options);
  460. // Final query
  461. var finalCmd = {};
  462. for (var name in cmd) {
  463. finalCmd[name] = cmd[name];
  464. }
  465. // Build command namespace
  466. var parts = ns.split(/\./);
  467. // Serialize functions
  468. var serializeFunctions =
  469. typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
  470. // Set up the serialize and ignoreUndefined fields
  471. var ignoreUndefined =
  472. typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
  473. // We have a Mongos topology, check if we need to add a readPreference
  474. if (topology.type === 'mongos' && readPreference && readPreference.preference !== 'primary') {
  475. finalCmd = {
  476. $query: finalCmd,
  477. $readPreference: readPreference.toJSON()
  478. };
  479. }
  480. // Build Query object
  481. var query = new Query(bson, f('%s.$cmd', parts.shift()), finalCmd, {
  482. numberToSkip: 0,
  483. numberToReturn: -1,
  484. checkKeys: false,
  485. serializeFunctions: serializeFunctions,
  486. ignoreUndefined: ignoreUndefined
  487. });
  488. // Set query flags
  489. query.slaveOk = readPreference.slaveOk();
  490. // Return the query
  491. return query;
  492. };
  493. module.exports = WireProtocol;