      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 };