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