1 //
  2 // A view of a collection against which operations are explained rather than executed
  3 // normally.
  4 //
  5 
  6 var Explainable = (function() {
  7 
  8     var parseVerbosity = function(verbosity) {
  9         // Truthy non-strings are interpreted as "allPlansExecution" verbosity.
 10         if (verbosity && (typeof verbosity !== "string")) {
 11             return "allPlansExecution";
 12         }
 13 
 14         // Falsy non-strings are interpreted as "queryPlanner" verbosity.
 15         if (!verbosity && (typeof verbosity !== "string")) {
 16             return "queryPlanner";
 17         }
 18 
 19         // If we're here, then the verbosity is a string. We reject invalid strings.
 20         if (verbosity !== "queryPlanner" && verbosity !== "executionStats" &&
 21             verbosity !== "allPlansExecution") {
 22             throw Error("explain verbosity must be one of {" + "'queryPlanner'," +
 23                         "'executionStats'," + "'allPlansExecution'}");
 24         }
 25 
 26         return verbosity;
 27     };
 28 
 29     var throwOrReturn = function(explainResult) {
 30         if (("ok" in explainResult && !explainResult.ok) || explainResult.$err) {
 31             throw _getErrorWithCode(explainResult, "explain failed: " + tojson(explainResult));
 32         }
 33 
 34         return explainResult;
 35     };
 36 
 37     function constructor(collection, verbosity) {
 38         //
 39         // Private vars.
 40         //
 41 
 42         this._collection = collection;
 43         this._verbosity = parseVerbosity(verbosity);
 44 
 45         //
 46         // Public methods.
 47         //
 48 
 49         this.getCollection = function() {
 50             return this._collection;
 51         };
 52 
 53         this.getVerbosity = function() {
 54             return this._verbosity;
 55         };
 56 
 57         this.setVerbosity = function(verbosity) {
 58             this._verbosity = parseVerbosity(verbosity);
 59             return this;
 60         };
 61 
 62         this.help = function() {
 63             print("Explainable operations");
 64             print("\t.aggregate(...) - explain an aggregation operation");
 65             print("\t.count(...) - explain a count operation");
 66             print("\t.distinct(...) - explain a distinct operation");
 67             print("\t.find(...) - get an explainable query");
 68             print("\t.findAndModify(...) - explain a findAndModify operation");
 69             print("\t.group(...) - explain a group operation");
 70             print("\t.remove(...) - explain a remove operation");
 71             print("\t.update(...) - explain an update operation");
 72             print("Explainable collection methods");
 73             print("\t.getCollection()");
 74             print("\t.getVerbosity()");
 75             print("\t.setVerbosity(verbosity)");
 76             return __magicNoPrint;
 77         };
 78 
 79         //
 80         // Pretty representations.
 81         //
 82 
 83         this.toString = function() {
 84             return "Explainable(" + this._collection.getFullName() + ")";
 85         };
 86 
 87         this.shellPrint = function() {
 88             return this.toString();
 89         };
 90 
 91         //
 92         // Explainable operations.
 93         //
 94 
 95         /**
 96          * Adds "explain: true" to "extraOpts", and then passes through to the regular collection's
 97          * aggregate helper.
 98          */
 99         this.aggregate = function(pipeline, extraOpts) {
100             if (!(pipeline instanceof Array)) {
101                 // support legacy varargs form. (Also handles db.foo.aggregate())
102                 pipeline = Array.from(arguments);
103                 extraOpts = {};
104             }
105 
106             // Add the explain option.
107             extraOpts = extraOpts || {};
108             extraOpts.explain = true;
109 
110             return this._collection.aggregate(pipeline, extraOpts);
111         };
112 
113         this.count = function(query) {
114             return this.find(query).count();
115         };
116 
117         /**
118          * .explain().find() and .find().explain() mean the same thing. In both cases, we use
119          * the DBExplainQuery abstraction in order to construct the proper explain command to send
120          * to the server.
121          */
122         this.find = function() {
123             var cursor = this._collection.find.apply(this._collection, arguments);
124             return new DBExplainQuery(cursor, this._verbosity);
125         };
126 
127         this.findAndModify = function(params) {
128             var famCmd = Object.extend({"findAndModify": this._collection.getName()}, params);
129             var explainCmd = {"explain": famCmd, "verbosity": this._verbosity};
130             var explainResult = this._collection.runReadCommand(explainCmd);
131             return throwOrReturn(explainResult);
132         };
133 
134         this.group = function(params) {
135             params.ns = this._collection.getName();
136             var grpCmd = {"group": this._collection.getDB()._groupFixParms(params)};
137             var explainCmd = {"explain": grpCmd, "verbosity": this._verbosity};
138             var explainResult = this._collection.runReadCommand(explainCmd);
139             return throwOrReturn(explainResult);
140         };
141 
142         this.distinct = function(keyString, query, options) {
143             var distinctCmd = {
144                 distinct: this._collection.getName(),
145                 key: keyString,
146                 query: query || {}
147             };
148 
149             if (options && options.hasOwnProperty("collation")) {
150                 distinctCmd.collation = options.collation;
151             }
152 
153             var explainCmd = {explain: distinctCmd, verbosity: this._verbosity};
154             var explainResult = this._collection.runReadCommand(explainCmd);
155             return throwOrReturn(explainResult);
156         };
157 
158         this.remove = function() {
159             var parsed = this._collection._parseRemove.apply(this._collection, arguments);
160             var query = parsed.query;
161             var justOne = parsed.justOne;
162             var collation = parsed.collation;
163 
164             var bulk = this._collection.initializeOrderedBulkOp();
165             var removeOp = bulk.find(query);
166 
167             if (collation) {
168                 removeOp.collation(collation);
169             }
170 
171             if (justOne) {
172                 removeOp.removeOne();
173             } else {
174                 removeOp.remove();
175             }
176 
177             var explainCmd = bulk.convertToExplainCmd(this._verbosity);
178             var explainResult = this._collection.runCommand(explainCmd);
179             return throwOrReturn(explainResult);
180         };
181 
182         this.update = function() {
183             var parsed = this._collection._parseUpdate.apply(this._collection, arguments);
184             var query = parsed.query;
185             var obj = parsed.obj;
186             var upsert = parsed.upsert;
187             var multi = parsed.multi;
188             var collation = parsed.collation;
189 
190             var bulk = this._collection.initializeOrderedBulkOp();
191             var updateOp = bulk.find(query);
192 
193             if (upsert) {
194                 updateOp = updateOp.upsert();
195             }
196 
197             if (collation) {
198                 updateOp.collation(collation);
199             }
200 
201             if (multi) {
202                 updateOp.update(obj);
203             } else {
204                 updateOp.updateOne(obj);
205             }
206 
207             var explainCmd = bulk.convertToExplainCmd(this._verbosity);
208             var explainResult = this._collection.runCommand(explainCmd);
209             return throwOrReturn(explainResult);
210         };
211     }
212 
213     //
214     // Public static methods.
215     //
216 
217     constructor.parseVerbosity = parseVerbosity;
218     constructor.throwOrReturn = throwOrReturn;
219 
220     return constructor;
221 })();
222 
223 /**
224  * This is the user-facing method for creating an Explainable from a collection.
225  */
226 DBCollection.prototype.explain = function(verbosity) {
227     return new Explainable(this, verbosity);
228 };
229