Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3_2_support.js 19KB

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