1 var comb = require("comb"),
  2         hitch = comb.hitch,
  3 		logging = comb.logging,
  4 		Logger = logging.Logger,
  5         util = require('util'),
  6         Promise = comb.Promise,
  7         PromiseList = comb.PromiseList;
  8 
  9 var moose, adapter;
 10 
 11 /**
 12  * @class Wrapper for {@link SQL} adpaters to allow execution functions such as:
 13  * <ul>
 14  *     <li>forEach</li>
 15  *     <li>one</li>
 16  *     <li>all</li>
 17  *     <li>first</li>
 18  *     <li>last</li>
 19  *     <li>all</li>
 20  *     <li>save</li>
 21  * </ul>
 22  *
 23  * This class should be used insead of SQL directly, becuase:
 24  * <ul>
 25  *     <li>Allows for Model creation if needed</li>
 26  *     <li>Handles the massaging of data to make the use of results easier.</li>
 27  *     <li>Closing of database connections</li>
 28  * </ul>
 29  * @name Dataset
 30  * @augments SQL
 31  *
 32  *
 33  */
 34 
 35 var LOGGER = Logger.getLogger("moose.Dataset");
 36 
 37 var Dataset = comb.define(null, {
 38     instance : {
 39         /**@lends Dataset.prototype*/
 40 
 41         constructor: function(table, db, type, model) {
 42             if (!table) throw new Error("table is required by dataset");
 43             if (!db) throw new Error("db is required by dataset");
 44             //if(!model) throw new Error("model is required by dataset");
 45             this.super(arguments);
 46             this.model = model;
 47             this.type = type;
 48         },
 49 
 50         _load : function(results) {
 51             var promises = [], retPromise;
 52             promises = results.map(function(o) {
 53                 var p = new Promise();
 54                 if (this.model) {
 55                     var m = this.model.load(o).then(function(m) {
 56                         m.__isNew = false;
 57                         p.callback(m);
 58                     });
 59                 } else {
 60                     p.callback(o);
 61                 }
 62                 return p;
 63             }, this);
 64 
 65             retPromise = new PromiseList(promises);
 66             return retPromise;
 67         },
 68 
 69         /**
 70          * Provide Array style looping a query results.
 71          *
 72          * @example
 73          * dataset.forEach(function(r, i){
 74          *     console.log("Row %d", i);
 75          * });
 76          *
 77          *
 78          * @param {Function} [callback] executed for each row returned.
 79          * @param {Function} [errback] executed if an error occurs.
 80          * @param {Object} [scope] scope to execute the callback and errback in.
 81          *
 82          * @return {comb.Promise} called back with results or the error if one occurs.
 83          */
 84         forEach : function(callback, errback, scope) {
 85             var retPromise = new Promise();
 86             if (callback) {
 87                 this.all().addCallback(hitch(this, function(results) {
 88                     if (results && results.length) {
 89                         results.forEach(callback, scope);
 90                     } else {
 91                         results = null;
 92                         callback.call(scope || this, null);
 93                     }
 94                     retPromise.callback(results);
 95                 })).addErrback(hitch(retPromise, "errback"));
 96                 retPromise.addErrback(errback);
 97             } else {
 98                 throw new Error("callback required");
 99             }
100             return retPromise;
101         },
102 
103         /**
104          * Retrieve one row result from the query.
105          *
106          * @example
107          *
108          * dataset.one(function(r){
109          *     Do something....
110          * }, function(err){
111          *     Do something...
112          * });
113          *
114          * //OR
115          *
116          * dataset.one().then(function(r){
117          *     Do something....
118          * }, function(err){
119          *     Do something...
120          * });
121          *
122          * @param {Function} [callback] executed with the row
123          * @param {Function} [errback] executed if an error occurs.
124          *
125          * @return {comb.Promise} called back with result or the error if one occurs.
126          */
127         one : function(callback, errback) {
128             var retPromise = new Promise();
129             this.limit(1);
130             this.exec().addCallback(hitch(this, function(results, fields) {
131                 if (results && results.length) {
132                     results = this._load(results).then(hitch(this, function(results) {
133                         results = results[0][1];
134                         callback && callback(results);
135                         retPromise.callback(results);
136                     }));
137                 } else {
138                     results = null;
139                     callback && callback(results);
140                     retPromise.callback(results);
141                 }
142 
143             })).addErrback(hitch(retPromise, "errback"));
144             retPromise.addErrback(errback);
145             return retPromise;
146         },
147 
148         /**
149          * Retrieve the first result from an ordered query.
150          *
151          * @example
152          * dataset.first(function(r){
153          *     Do something....
154          * }, function(err){
155          *     Do something...
156          * });
157          *
158          * //OR
159          *
160          * dataset.first().then(function(r){
161          *     Do something....
162          * }, function(err){
163          *     Do something...
164          * });
165          *
166          * @param {Function} [callback] executed with the row
167          * @param {Function} [errback] executed if an error occurs.
168          *
169          * @return {comb.Promise} called back with result or the error if one occurs.
170          */
171         first : function(callback, errback) {
172             var retPromise = new Promise();
173             this.exec().addCallback(hitch(this, function(results, fields) {
174                 if (results && results.length) {
175                     results = this._load(results).then(hitch(this, function(results) {
176                         results = results[0][1];
177                         callback && callback(results);
178                         retPromise.callback(results);
179                     }));
180                 } else {
181                     results = null;
182                     callback && callback(results);
183                     retPromise.callback(results);
184                 }
185             })).addErrback(hitch(retPromise, "errback"));
186             retPromise.addErrback(errback);
187             return retPromise;
188         },
189 
190         /**
191          * Retrieve the last result from an ordered query. If the query is not ordered then the result is ambiguous.
192          *
193          * @example
194          *
195          * dataset.last(function(r){
196          *     Do something....
197          * }, function(err){
198          *     Do something...
199          * });
200          *
201          * //OR
202          *
203          * dataset.last().then(function(r){
204          *     Do something....
205          * }, function(err){
206          *     Do something...
207          * });
208          *
209          * @param {Function} [callback] executed with the row
210          * @param {Function} [errback] executed if an error occurs.
211          *
212          * @return {comb.Promise} called back with result or the error if one occurs.
213          */
214         last : function(callback, errback) {
215             var retPromise = new Promise();
216             this.exec().addCallback(hitch(this, function(results, fields) {
217                 if (results && results.length) {
218                     results = this._load(results).then(hitch(this, function(results) {
219                         results = results[results.length - 1][1];
220                         callback && callback(results);
221                         retPromise.callback(results);
222                     }));
223                 } else {
224                     results = null;
225                     callback && callback(results);
226                     retPromise.callback(results);
227                 }
228             })).addErrback(hitch(retPromise, "errback"));
229             retPromise.addErrback(errback);
230             return retPromise;
231 
232         },
233 
234         /**
235          * Retrieve all rows from the query.
236          *
237          * @example
238          *
239          * dataset.all(function(r){
240          *     Do something....
241          * }, function(err){
242          *     Do something...
243          * });
244          *
245          * //OR
246          *
247          * dataset.all().then(function(r){
248          *     Do something....
249          * }, function(err){
250          *     Do something...
251          * });
252          *
253          * @param {Function} [callback] executed with the results.
254          * @param {Function} [errback] executed if an error occurs.
255          *
256          * @return {comb.Promise} called back with results or the error if one occurs.
257          */
258         all : function(callback, errback) {
259             var retPromise = new Promise();
260             this.exec().addCallback(hitch(this, function(results, fields) {
261                 if (results && results.length) {
262                     results = this._load(results).then(hitch(this, function(results) {
263                         results = results.map(function(r) {
264                             return r[1];
265                         });
266                         callback && callback(results);
267                         retPromise.callback(results);
268                     }));
269                 } else {
270                     callback && callback(results);
271                     retPromise.callback(results);
272                 }
273             })).addErrback(hitch(retPromise, "errback"));
274             retPromise.addErrback(errback);
275             return retPromise;
276         },
277 
278         /**
279          * Retrieve the last inserted id from the database.
280          *
281          * @example
282          *
283          * dataset.getLastInsertId(function(r){
284          *     Do something....
285          * }, function(err){
286          *     Do something...
287          * });
288          *
289          * //OR
290          *
291          * dataset.getLastInsertId().then(function(r){
292          *     Do something....
293          * }, function(err){
294          *     Do something...
295          * });
296          *
297          * @param {Function} [callback] executed with the id
298          * @param {Function} [errback] executed if an error occurs.
299          *
300          * @return {comb.Promise} called back with id or the error if one occurs.
301          */
302         getLastInsertId : function(callback, errback) {
303             var retPromise = new Promise();
304             adapter.getLastInsertId(this.db).addCallback(hitch(this, function(results) {
305                 if (results) {
306                     retPromise.callback(results[0].id);
307                 } else {
308                     retPromise.callback(null);
309                 }
310             })).addErrback(hitch(retPromise, "errback"));
311             retPromise.then(callback, errback);
312             return retPromise;
313         },
314 
315         /**
316          * Save values to a table.
317          *
318          * </br>
319          * <b>This should not be used directly</b>
320          *
321          * @param {Function} [callback] executed with the row
322          * @param {Function} [errback] executed if an error occurs.
323          *
324          * @return {comb.Promise} called back with results or the error if one occurs.
325          */
326         save : function(vals, loadId, callback, errback) {
327             var retPromise = new Promise();
328             adapter.save(this.table, vals, this.db).addCallback(hitch(this, function(results) {
329                 if (loadId) {
330                     retPromise.callback(results.insertId);
331                 } else {
332                     retPromise.callback(results);
333                 }
334             })).addErrback(hitch(retPromise, "errback"));
335             retPromise.addErrback(errback);
336             return retPromise;
337         },
338 
339         /**
340          * Alias for {@link Dataset#all}
341          */
342         run : function(callback, errback) {
343             return this.all(callback, errback);
344         }
345     }
346 });
347 
348 
349 //returns a dataset for a particular type
350 exports.getDataSet = function(table, db, type, model) {
351 	if(!moose){
352 		moose = require("./index"), adapter = moose.adapter;
353 	}
354     var dataset = comb.define([adapter, Dataset], {});
355     return new dataset(table, db, type, model);
356 };