Source: lib/bulk/common.js

  1. "use strict";
  2. var Long = require('mongodb-core').BSON.Long,
  3. Timestamp = require('mongodb-core').BSON.Timestamp;
  4. // Error codes
  5. var UNKNOWN_ERROR = 8;
  6. var INVALID_BSON_ERROR = 22;
  7. var WRITE_CONCERN_ERROR = 64;
  8. var MULTIPLE_ERROR = 65;
  9. // Insert types
  10. var INSERT = 1;
  11. var UPDATE = 2;
  12. var REMOVE = 3
  13. // Get write concern
  14. var writeConcern = function(target, col, options) {
  15. var writeConcern = {};
  16. // Collection level write concern
  17. if(col.writeConcern && col.writeConcern.w != null) writeConcern.w = col.writeConcern.w;
  18. if(col.writeConcern && col.writeConcern.j != null) writeConcern.j = col.writeConcern.j;
  19. if(col.writeConcern && col.writeConcern.fsync != null) writeConcern.fsync = col.writeConcern.fsync;
  20. if(col.writeConcern && col.writeConcern.wtimeout != null) writeConcern.wtimeout = col.writeConcern.wtimeout;
  21. // Options level write concern
  22. if(options && options.w != null) writeConcern.w = options.w;
  23. if(options && options.wtimeout != null) writeConcern.wtimeout = options.wtimeout;
  24. if(options && options.j != null) writeConcern.j = options.j;
  25. if(options && options.fsync != null) writeConcern.fsync = options.fsync;
  26. // Return write concern
  27. return writeConcern;
  28. }
  29. /**
  30. * Helper function to define properties
  31. * @ignore
  32. */
  33. var defineReadOnlyProperty = function(self, name, value) {
  34. Object.defineProperty(self, name, {
  35. enumerable: true
  36. , get: function() {
  37. return value;
  38. }
  39. });
  40. }
  41. /**
  42. * Keeps the state of a unordered batch so we can rewrite the results
  43. * correctly after command execution
  44. * @ignore
  45. */
  46. var Batch = function(batchType, originalZeroIndex) {
  47. this.originalZeroIndex = originalZeroIndex;
  48. this.currentIndex = 0;
  49. this.originalIndexes = [];
  50. this.batchType = batchType;
  51. this.operations = [];
  52. this.size = 0;
  53. this.sizeBytes = 0;
  54. }
  55. /**
  56. * Wraps a legacy operation so we can correctly rewrite it's error
  57. * @ignore
  58. */
  59. var LegacyOp = function(batchType, operation, index) {
  60. this.batchType = batchType;
  61. this.index = index;
  62. this.operation = operation;
  63. }
  64. /**
  65. * Create a new BulkWriteResult instance (INTERNAL TYPE, do not instantiate directly)
  66. *
  67. * @class
  68. * @property {boolean} ok Did bulk operation correctly execute
  69. * @property {number} nInserted number of inserted documents
  70. * @property {number} nUpdated number of documents updated logically
  71. * @property {number} nUpserted Number of upserted documents
  72. * @property {number} nModified Number of documents updated physically on disk
  73. * @property {number} nRemoved Number of removed documents
  74. * @return {BulkWriteResult} a BulkWriteResult instance
  75. */
  76. var BulkWriteResult = function(bulkResult) {
  77. defineReadOnlyProperty(this, "ok", bulkResult.ok);
  78. defineReadOnlyProperty(this, "nInserted", bulkResult.nInserted);
  79. defineReadOnlyProperty(this, "nUpserted", bulkResult.nUpserted);
  80. defineReadOnlyProperty(this, "nMatched", bulkResult.nMatched);
  81. defineReadOnlyProperty(this, "nModified", bulkResult.nModified);
  82. defineReadOnlyProperty(this, "nRemoved", bulkResult.nRemoved);
  83. /**
  84. * Return an array of inserted ids
  85. *
  86. * @return {object[]}
  87. */
  88. this.getInsertedIds = function() {
  89. return bulkResult.insertedIds;
  90. }
  91. /**
  92. * Return an array of upserted ids
  93. *
  94. * @return {object[]}
  95. */
  96. this.getUpsertedIds = function() {
  97. return bulkResult.upserted;
  98. }
  99. /**
  100. * Return the upserted id at position x
  101. *
  102. * @param {number} index the number of the upserted id to return, returns undefined if no result for passed in index
  103. * @return {object}
  104. */
  105. this.getUpsertedIdAt = function(index) {
  106. return bulkResult.upserted[index];
  107. }
  108. /**
  109. * Return raw internal result
  110. *
  111. * @return {object}
  112. */
  113. this.getRawResponse = function() {
  114. return bulkResult;
  115. }
  116. /**
  117. * Returns true if the bulk operation contains a write error
  118. *
  119. * @return {boolean}
  120. */
  121. this.hasWriteErrors = function() {
  122. return bulkResult.writeErrors.length > 0;
  123. }
  124. /**
  125. * Returns the number of write errors off the bulk operation
  126. *
  127. * @return {number}
  128. */
  129. this.getWriteErrorCount = function() {
  130. return bulkResult.writeErrors.length;
  131. }
  132. /**
  133. * Returns a specific write error object
  134. *
  135. * @param {number} index of the write error to return, returns null if there is no result for passed in index
  136. * @return {WriteError}
  137. */
  138. this.getWriteErrorAt = function(index) {
  139. if(index < bulkResult.writeErrors.length) {
  140. return bulkResult.writeErrors[index];
  141. }
  142. return null;
  143. }
  144. /**
  145. * Retrieve all write errors
  146. *
  147. * @return {object[]}
  148. */
  149. this.getWriteErrors = function() {
  150. return bulkResult.writeErrors;
  151. }
  152. /**
  153. * Retrieve lastOp if available
  154. *
  155. * @return {object}
  156. */
  157. this.getLastOp = function() {
  158. return bulkResult.lastOp;
  159. }
  160. /**
  161. * Retrieve the write concern error if any
  162. *
  163. * @return {WriteConcernError}
  164. */
  165. this.getWriteConcernError = function() {
  166. if(bulkResult.writeConcernErrors.length == 0) {
  167. return null;
  168. } else if(bulkResult.writeConcernErrors.length == 1) {
  169. // Return the error
  170. return bulkResult.writeConcernErrors[0];
  171. } else {
  172. // Combine the errors
  173. var errmsg = "";
  174. for(var i = 0; i < bulkResult.writeConcernErrors.length; i++) {
  175. var err = bulkResult.writeConcernErrors[i];
  176. errmsg = errmsg + err.errmsg;
  177. // TODO: Something better
  178. if(i == 0) errmsg = errmsg + " and ";
  179. }
  180. return new WriteConcernError({ errmsg : errmsg, code : WRITE_CONCERN_ERROR });
  181. }
  182. }
  183. this.toJSON = function() {
  184. return bulkResult;
  185. }
  186. this.toString = function() {
  187. return "BulkWriteResult(" + this.toJSON(bulkResult) + ")";
  188. }
  189. this.isOk = function() {
  190. return bulkResult.ok == 1;
  191. }
  192. }
  193. /**
  194. * Create a new WriteConcernError instance (INTERNAL TYPE, do not instantiate directly)
  195. *
  196. * @class
  197. * @property {number} code Write concern error code.
  198. * @property {string} errmsg Write concern error message.
  199. * @return {WriteConcernError} a WriteConcernError instance
  200. */
  201. var WriteConcernError = function(err) {
  202. if(!(this instanceof WriteConcernError)) return new WriteConcernError(err);
  203. // Define properties
  204. defineReadOnlyProperty(this, "code", err.code);
  205. defineReadOnlyProperty(this, "errmsg", err.errmsg);
  206. this.toJSON = function() {
  207. return {code: err.code, errmsg: err.errmsg};
  208. }
  209. this.toString = function() {
  210. return "WriteConcernError(" + err.errmsg + ")";
  211. }
  212. }
  213. /**
  214. * Create a new WriteError instance (INTERNAL TYPE, do not instantiate directly)
  215. *
  216. * @class
  217. * @property {number} code Write concern error code.
  218. * @property {number} index Write concern error original bulk operation index.
  219. * @property {string} errmsg Write concern error message.
  220. * @return {WriteConcernError} a WriteConcernError instance
  221. */
  222. var WriteError = function(err) {
  223. if(!(this instanceof WriteError)) return new WriteError(err);
  224. // Define properties
  225. defineReadOnlyProperty(this, "code", err.code);
  226. defineReadOnlyProperty(this, "index", err.index);
  227. defineReadOnlyProperty(this, "errmsg", err.errmsg);
  228. //
  229. // Define access methods
  230. this.getOperation = function() {
  231. return err.op;
  232. }
  233. this.toJSON = function() {
  234. return {code: err.code, index: err.index, errmsg: err.errmsg, op: err.op};
  235. }
  236. this.toString = function() {
  237. return "WriteError(" + JSON.stringify(this.toJSON()) + ")";
  238. }
  239. }
  240. /**
  241. * Merges results into shared data structure
  242. * @ignore
  243. */
  244. var mergeBatchResults = function(ordered, batch, bulkResult, err, result) {
  245. // If we have an error set the result to be the err object
  246. if(err) {
  247. result = err;
  248. } else if(result && result.result) {
  249. result = result.result;
  250. } else if(result == null) {
  251. return;
  252. }
  253. // Do we have a top level error stop processing and return
  254. if(result.ok == 0 && bulkResult.ok == 1) {
  255. bulkResult.ok = 0;
  256. var writeError = {
  257. index: 0
  258. , code: result.code || 0
  259. , errmsg: result.message
  260. , op: batch.operations[0]
  261. };
  262. bulkResult.writeErrors.push(new WriteError(writeError));
  263. return;
  264. } else if(result.ok == 0 && bulkResult.ok == 0) {
  265. return;
  266. }
  267. // Deal with opTime if available
  268. if(result.opTime || result.lastOp) {
  269. var opTime = result.lastOp || result.opTime;
  270. var lastOpTS = null;
  271. var lastOpT = null;
  272. // We have a time stamp
  273. if(opTime && opTime._bsontype == 'Timestamp') {
  274. if(bulkResult.lastOp == null) {
  275. bulkResult.lastOp = opTime;
  276. } else if(opTime.greaterThan(bulkResult.lastOp)) {
  277. bulkResult.lastOp = opTime;
  278. }
  279. } else {
  280. // Existing TS
  281. if(bulkResult.lastOp) {
  282. lastOpTS = typeof bulkResult.lastOp.ts == 'number'
  283. ? Long.fromNumber(bulkResult.lastOp.ts) : bulkResult.lastOp.ts;
  284. lastOpT = typeof bulkResult.lastOp.t == 'number'
  285. ? Long.fromNumber(bulkResult.lastOp.t) : bulkResult.lastOp.t;
  286. }
  287. // Current OpTime TS
  288. var opTimeTS = typeof opTime.ts == 'number'
  289. ? Long.fromNumber(opTime.ts) : opTime.ts;
  290. var opTimeT = typeof opTime.t == 'number'
  291. ? Long.fromNumber(opTime.t) : opTime.t;
  292. // Compare the opTime's
  293. if(bulkResult.lastOp == null) {
  294. bulkResult.lastOp = opTime;
  295. } else if(opTimeTS.greaterThan(lastOpTS)) {
  296. bulkResult.lastOp = opTime;
  297. } else if(opTimeTS.equals(lastOpTS)) {
  298. if(opTimeT.greaterThan(lastOpT)) {
  299. bulkResult.lastOp = opTime;
  300. }
  301. }
  302. }
  303. }
  304. // If we have an insert Batch type
  305. if(batch.batchType == INSERT && result.n) {
  306. bulkResult.nInserted = bulkResult.nInserted + result.n;
  307. }
  308. // If we have an insert Batch type
  309. if(batch.batchType == REMOVE && result.n) {
  310. bulkResult.nRemoved = bulkResult.nRemoved + result.n;
  311. }
  312. var nUpserted = 0;
  313. // We have an array of upserted values, we need to rewrite the indexes
  314. if(Array.isArray(result.upserted)) {
  315. nUpserted = result.upserted.length;
  316. for(var i = 0; i < result.upserted.length; i++) {
  317. bulkResult.upserted.push({
  318. index: result.upserted[i].index + batch.originalZeroIndex
  319. , _id: result.upserted[i]._id
  320. });
  321. }
  322. } else if(result.upserted) {
  323. nUpserted = 1;
  324. bulkResult.upserted.push({
  325. index: batch.originalZeroIndex
  326. , _id: result.upserted
  327. });
  328. }
  329. // If we have an update Batch type
  330. if(batch.batchType == UPDATE && result.n) {
  331. var nModified = result.nModified;
  332. bulkResult.nUpserted = bulkResult.nUpserted + nUpserted;
  333. bulkResult.nMatched = bulkResult.nMatched + (result.n - nUpserted);
  334. if(typeof nModified == 'number') {
  335. bulkResult.nModified = bulkResult.nModified + nModified;
  336. } else {
  337. bulkResult.nModified = null;
  338. }
  339. }
  340. if(Array.isArray(result.writeErrors)) {
  341. for(i = 0; i < result.writeErrors.length; i++) {
  342. writeError = {
  343. index: batch.originalZeroIndex + result.writeErrors[i].index
  344. , code: result.writeErrors[i].code
  345. , errmsg: result.writeErrors[i].errmsg
  346. , op: batch.operations[result.writeErrors[i].index]
  347. };
  348. bulkResult.writeErrors.push(new WriteError(writeError));
  349. }
  350. }
  351. if(result.writeConcernError) {
  352. bulkResult.writeConcernErrors.push(new WriteConcernError(result.writeConcernError));
  353. }
  354. }
  355. //
  356. // Clone the options
  357. var cloneOptions = function(options) {
  358. var clone = {};
  359. var keys = Object.keys(options);
  360. for(var i = 0; i < keys.length; i++) {
  361. clone[keys[i]] = options[keys[i]];
  362. }
  363. return clone;
  364. }
  365. // Exports symbols
  366. exports.BulkWriteResult = BulkWriteResult;
  367. exports.WriteError = WriteError;
  368. exports.Batch = Batch;
  369. exports.LegacyOp = LegacyOp;
  370. exports.mergeBatchResults = mergeBatchResults;
  371. exports.cloneOptions = cloneOptions;
  372. exports.writeConcern = writeConcern;
  373. exports.INVALID_BSON_ERROR = INVALID_BSON_ERROR;
  374. exports.WRITE_CONCERN_ERROR = WRITE_CONCERN_ERROR;
  375. exports.MULTIPLE_ERROR = MULTIPLE_ERROR;
  376. exports.UNKNOWN_ERROR = UNKNOWN_ERROR;
  377. exports.INSERT = INSERT;
  378. exports.UPDATE = UPDATE;
  379. exports.REMOVE = REMOVE;