      1 var Client = require("mysql").Client,
      2         comb = require("comb"),
      3         Promise = comb.Promise,
      4         PromiseList = comb.PromiseList,
      5         hitch = comb.hitch,
      6         mysqlTypes = require("./types/mysql"),
      7         Table = require("../table").Table,
      8         MySQLClient = require("./clients/mysql"),
      9         SQL = require("./sql");
     10
     11 /*
     12  MYSQL query adapter to convert a query into mysql syntax;
     13  * */
     14
     15 var conditionedJoinTypes = {
     16     innerJoin : "inner join",
     17     fullOuterJoin : "full outer join",
     18     rightOuterJoin : "right outer join",
     19     leftOuterJoin : "left outer join",
     20     fullJoin : "full join",
     21     rightJoin : "right join",
     22     leftJoin : "left join"
     23 };
     24
     25 /* These symbols have _join methods created (e.g. natural_join) that
     26  call join_table with the symbol.  They only accept a single table
     27  argument which is passed to join_table, and they raise an error
     28  if called with a block. */
     29 var unConditionedJoinTypes = {
     30     naturalJoin : "natural join",
     31     naturalLeftJoin : "natural left join",
     32     naturalRightJoin : "natural right join",
     33     naturalFullJoin : "natural full join",
     34     crossJoin : "cross join"
     35 };
     36
     37 // All methods that return modified datasets with a joined table added.
     38 var joinMethods = {};
     39 for (var i in conditionedJoinTypes) {
     40     joinMethods[i] = conditionedJoinTypes[i];
     41 }
     42 for (i in unConditionedJoinTypes) {
     43     joinMethods[i] = unConditionedJoinTypes[i];
     44 }
     45
     46 var inOperator = {
     47     "in" : "in",
     48     notIn : "not in"
     49 };
     50
     51 var betweenOperator = {
     52     between : "between",
     53     notBetween : "not between"
     54 };
     55
     56 var booleanOperator = {
     57     gt : ">",
     58     gte : ">=",
     59     lt : "<",
     60     lte : "<=",
     61     eq : "=",
     62     neq : "!="
     63 };
     64
     65 var logicalOperator = {
     66     and : "and",
     67     or : "or"
     68 };
     69
     70 var isOperator = {
     71     is : "is",
     72     isNot : "is not",
     73     isNull : "is null",
     74     isNotNull : "is not null"
     75 };
     76
     77
     78 var aggregateOperator = {
     79     count : "count",
     80     sum : "sum",
     81     avg : "avg",
     82     min : "min",
     83     max : "max",
     84     bitAnd : "bit_and",
     85     bitOr : "bit_or",
     86     bitXor : "bit_xor",
     87     std : "std",
     88     stdDevPop : "stddev_pop",
     89     stdDevSamp : "stddev_samp",
     90     stdDev : "stddev",
     91     varPop : "var_pop",
     92     varSamp : "var_samp",
     93     variance : "variance"
     94 };
     95
     96 var likeOperator = {
     97     like : "like",
     98     notLike : "not like"
     99 };
    100
    101 var transformBooleanOperator = function(val) {
    102     if (typeof val == "boolean") {
    103         val = val.toString();
    104     } else if (val == "unknown") {
    105     } else if (val == null) {
    106         val = "null";
    107     } else {
    108         throw new Error(val + " is not a boolean type");
    109     }
    110     return val;
    111 };
    112
    113 var addIsFunction = function(oper, object, callback) {
    114     object[oper] = function(options) {
    115         if ((oper == "is" || oper == "isNot") && typeof options != "object") throw new Error("options must be an object {key : value} when using " + oper);
    116         if ((oper == "isNull" || oper == "isNotNull") && typeof options != "string") throw new Error("options must be a string <columnName> when using " + oper);
    117         return callback.apply(object, [oper, options]);
    118     };
    119 };
    120
    121 var addJoinFunction = function(oper, object, callback) {
    122     object[oper] = function(table, options) {
    123         return callback.apply(object, [oper, table, options]);
    124     };
    125 };
    126
    127 var addGroupFunction = function(oper, object, callback) {
    128     var name = "groupAnd" + oper.charAt(0).toUpperCase() + oper.substr(1);
    129     object[name] = function(key, options) {
    130         object.group(key, options);
    131         return callback.apply(object, [oper, key]);
    132     };
    133     addFunction(oper, object, callback);
    134 };
    135
    136 var addFunction = function(oper, object, callback) {
    137     object[oper] = function(options) {
    138         return callback.apply(object, [oper, options]);
    139     };
    140 };
    141
    142
    143 var createInsertStatement = function(table, object, db) {
    144     var sql = "INSERT INTO " + table + " ";
    145     var values = [], columns = [];
    146     for (var i in object) {
    147         columns.push(i);
    148         values.push(object[i]);
    149     }
    150     sql += "(" + columns.join(",") + ") VALUES ";
    151     sql += SQL.format("(" + values.map(
    152             function() {
    153                 return "?";
    154             }).join(",") + ");", values);
    155     return sql;
    156 };
    157
    158 /**
    159  * @class mysql implementaton of {@link SQL} this class exposes all MySql relevant functionality.
    160  * <p>You should not have to use this class directly, but will be exposed for you by specifying the type when initializing moose.createConnection</p>
    161  *
    162  * @name mysql
    163  * @augments SQL
    164  * @memberOf moose.adapters
    165  */
    166 var Mysql = (module.exports = exports = comb.define(SQL, {
    167     instance : {
    168
    169         sqlObject : null,
    170
    171         _needLogic : false,
    172
    173         table : null,
    174
    175         db : null,
    176
    177         constructor: function(table, db) {
    178             this.super(arguments);
    179             //override all super methods for operators
    180             for (var i in isOperator) {
    181
    182                 addIsFunction(i, this, this._isOp);
    183             }
    184             for (i in logicalOperator) {
    185                 addFunction(i, this, this._logicalOp);
    186             }
    187             for (i in booleanOperator) {
    188                 addFunction(i, this, this._booleanOp);
    189             }
    190             for (i in conditionedJoinTypes) {
    191                 addJoinFunction(i, this, this._joinOp);
    192             }
    193             for (i in unConditionedJoinTypes) {
    194                 addJoinFunction(i, this, this._joinOp);
    195             }
    196             for (i in inOperator) {
    197                 addFunction(i, this, this._inOp);
    198             }
    199             for (i in betweenOperator) {
    200                 addFunction(i, this, this._betweenOp);
    201             }
    202             for (i in aggregateOperator) {
    203                 addGroupFunction(i, this, this._aggregateOp);
    204             }
    205             for (i in likeOperator) {
    206                 addFunction(i, this, this._likeOp);
    207             }
    208         },
    209
    210         _set : function(vals) {
    211             var sqlObject = this.sqlObject;
    212             if (sqlObject.update) {
    213                 if (!sqlObject.set) {
    214                     sqlObject.set = "set ";
    215                 }
    216                 if (typeof vals == "object" && !(vals instanceof Array)) {
    217                     var values = [];
    218                     var set = [];
    219                     for (var i in vals) {
    220                         set.push(i + "=?");
    221                         values.push(vals[i]);
    222                     }
    223                     sqlObject.set += this.format(set.join(","), values);
    224                 } else {
    225                     throw new Error("Vals must be an object");
    226                 }
    227             }
    228             return this;
    229         },
    230
    231         _betweenOp : function(oper, options) {
    232             if (!this.sql) this.find();
    233             if (options) {
    234                 var sqlObject = this.sqlObject;
    235                 if (sqlObject.having) {
    236                     this.having();
    237                 } else if (!sqlObject.where) {
    238                     this.where();
    239                 }
    240                 for (var i in options) {
    241                     if (this._needLogic) {
    242                         this.and();
    243                     }
    244                     var key = i, val = options[i];
    245                     if (val instanceof Array && val.length == 2) {
    246                         var opts = {};
    247                         if (oper == "between") {
    248                             opts[key] = val[0];
    249                             this.gte(opts);
    250                             opts[key] = val[1];
    251                             this.lte(opts);
    252                         } else if (oper == "notBetween") {
    253                             opts[key] = val[0];
    254                             this.lte(opts);
    255                             opts[key] = val[1];
    256                             this.gte(opts);
    257                         } else {
    258                             throw new Error(oper + " is not supported");
    259                         }
    260                         this._needLogic = true;
    261                     } else {
    262                         throw new Error("when calling between value must be an array and have a of length 2");
    263                     }
    264                 }
    265             }
    266             return this;
    267         },
    268
    269         _inOp : function(oper, options) {
    270             if (!this.sql) this.find();
    271             if (options) {
    272                 oper = inOperator[oper];
    273                 var sqlObject = this.sqlObject;
    274                 var sqlKey = "where";
    275                 if (sqlObject.having) {
    276                     this.having();
    277                     sqlKey = "having";
    278                 } else if (!sqlObject.where) {
    279                     this.where();
    280                 }
    281                 for (var i in options) {
    282                     if (this._needLogic) {
    283                         this.and();
    284                     }
    285                     var key = i, val = options[i];
    286                     if (val instanceof Mysql) {
    287                         sqlObject[sqlKey] += key + " " + oper + " (" + val.sql + ")";
    288                         this._needLogic = true;
    289                     } else if (val instanceof Array) {
    290                         var vals = "";
    291                         vals = val.map(function() {
    292                             return "?";
    293                         });
    294                         sqlObject[sqlKey] += key + " " + oper + " (" + this.format(vals.join(","), val) + ")";
    295                         this._needLogic = true;
    296                     } else if (typeof val == "string") {
    297                         sqlObject[sqlKey] += key + " in (" + val + ")";
    298                         this._needLogic = true;
    299                     } else {
    300                         throw new Error("when calling in value must be a string or array");
    301                     }
    302                 }
    303             }
    304             return this;
    305         },
    306
    307         _aggregateOp : function(oper, options) {
    308             var op = aggregateOperator[oper];
    309             var sqlObject = this.sqlObject;
    310             if (sqlObject.update || sqlObject["delete"]) throw new Error(oper + " cannot be used with update or delete");
    311             this._aggregated = true;
    312             if (options) {
    313                 if (typeof options == "string") {
    314                     if (sqlObject.select) {
    315                         var lastChar = sqlObject.select.charAt(sqlObject.select.length - 1);
    316                         if (sqlObject != "select" && lastChar == "*" || lastChar == ' ') {
    317                             sqlObject.select += ", ";
    318                         }
    319                     } else {
    320                         sqlObject.select = "select ";
    321                     }
    322                     sqlObject.select += op + "(" + options + ") as " + options + "_" + op;
    323                 } else if (options instanceof Array) {
    324                     options.forEach(this[op], this);
    325                 } else {
    326                     throw new Error("when calling " + oper + " you must pass in a string or array or nothing");
    327                 }
    328             } else {
    329                 if (sqlObject.select) {
    330                     if (sqlObject.select.charAt(sqlObject.select.length - 1) == " ") {
    331                         sqlObject.select = sqlObject.select.substr(0, sqlObject.select.length - 1) + ", ";
    332                     }
    333                 } else {
    334                     sqlObject.select = "select ";
    335                 }
    336                 sqlObject.select += op + "(*) as " + op;
    337             }
    338             if (!sqlObject.find) this._from();
    339             return this;
    340         },
    341
    342         _logicalOp : function(oper, options) {
    343             oper = logicalOperator[oper];
    344             if (this._needLogic) {
    345                 var sqlObject = this.sqlObject;
    346                 var sqlKey = "where";
    347                 if (sqlObject.having) {
    348                     //this.having();
    349                     sqlKey = "having";
    350                 } else if (!sqlObject.where) {
    351                     this.where();
    352                 }
    353                 sqlObject[sqlKey] += " " + logicalOperator[oper] + " ";
    354                 this._needLogic = false;
    355                 if (options) {
    356                     var params = [];
    357                     var count = 0;
    358                     for (var i in options) {
    359                         if (count) throw new Error(oper + "operation can only be one deep");
    360                         this._createSQLFromObject(i, options[i]);
    361                         count++;
    362                     }
    363                 }
    364             } else {
    365                 throw new Error("logical operator not needed");
    366             }
    367             return this;
    368         },
    369
    370         _joinOp : function(oper, table, options) {
    371             if (!this.sql) this.find();
    372             var sqlObject = this.sqlObject;
    373             var fromClause = "from";
    374
    375             if (!sqlObject.update && !sqlObject.from) {
    376                 this._from();
    377             } else if (sqlObject.update) {
    378                 fromClause = "update";
    379             }
    380             if (typeof table == "string") {
    381                 if (sqlObject["delete"]) {
    382                     if (sqlObject["delete"] == "delete ") {
    383                         sqlObject["delete"] += this.table;
    384                     }
    385                 }
    386                 var joinType;
    387                 if (oper in conditionedJoinTypes) {
    388                     if (options) {
    389                         joinType = conditionedJoinTypes[oper];
    390                         sqlObject[fromClause] += " " + joinType + " " + table;
    391                         if (options instanceof Array) {
    392                             sqlObject[fromClause] += " using (" + options.join(", ") + ")";
    393                         } else if (typeof options == "object") {
    394                             sqlObject[fromClause] += " on ";
    395                             for (var i in options) {
    396                                 sqlObject[fromClause] += this.table + "." + i + "=" + table + "." + options[i];
    397                             }
    398                         } else {
    399                             throw new Error("join options must be an array or object");
    400                         }
    401                     } else {
    402                         throw new Error(oper + " requires a join condition");
    403                     }
    404                 } else if (oper in unConditionedJoinTypes) {
    405                     joinType = unConditionedJoinTypes[oper];
    406                     sqlObject[fromClause] += " " + joinType + " " + table;
    407                 }
    408             } else if (table instanceof Mysql) {
    409                 this._joinOp(oper, table.sql, options);
    410             }
    411             return this;
    412         },
    413
    414         _booleanOp : function(oper, options) {
    415             if (!this.sql) this.find();
    416             var sqlObject = this.sqlObject;
    417             var sqlKey = "where";
    418             if (sqlObject.having) {
    419                 //this.having();
    420                 sqlKey = "having";
    421             } else if (!sqlObject.where) {
    422                 this.where();
    423             }
    424             oper = booleanOperator[oper];
    425             var params = [];
    426             for (var i in options) {
    427                 if (this._needLogic) {
    428                     this.and();
    429                 }
    430                 sqlObject[sqlKey] += this.format(i + " " + oper + " ?", [options[i]]);
    431                 this._needLogic = true;
    432             }
    433             return this;
    434         },
    435
    436         _isOp : function(oper, options) {
    437             if (!this.sql) this.find();
    438             var sqlObject = this.sqlObject;
    439             var sqlKey = "where";
    440             if (sqlObject.having) {
    441                 this.having();
    442                 sqlKey = "having";
    443             } else if (!sqlObject.where) {
    444                 this.where();
    445             }
    446             oper = isOperator[oper];
    447             var sql = "";
    448             if (typeof options == "object") {
    449                 for (var i in options) {
    450                     if (this._needLogic) {
    451                         this.and();
    452                     }
    453                     sqlObject[sqlKey] += i + " " + oper + " " + transformBooleanOperator(options[i]);
    454                     this._needLogic = true;
    455                 }
    456             } else {
    457                 if (this._needLogic) {
    458                     this.and();
    459                 }
    460                 sqlObject[sqlKey] += options + " " + oper;
    461                 this._needLogic = true;
    462             }
    463             return this;
    464         },
    465
    466         _likeOp : function(oper, options) {
    467             if (!this.sql) this.find();
    468             var sqlObject = this.sqlObject;
    469             var sqlKey = "where";
    470             if (sqlObject.having) {
    471                 this.having();
    472                 sqlKey = "having";
    473             } else if (!sqlObject.where) {
    474                 this.where();
    475             }
    476             oper = likeOperator[oper];
    477             var sql = "";
    478             if (typeof options == "object") {
    479                 for (var i in options) {
    480                     if (this._needLogic) {
    481                         this.and();
    482                     }
    483                     sqlObject[sqlKey] += this.format(i + " " + oper + " ?", [options[i]]);
    484                     this._needLogic = true;
    485                 }
    486             } else {
    487                 throw new Error("when calling like options must be a hash of {columnName : <like condition>}");
    488             }
    489             return this;
    490         },
    491
    492         where : function(options) {
    493             if (!this.sql) this.find();
    494             var sqlObject = this.sqlObject;
    495             if (!sqlObject.update && !sqlObject.from) {
    496                 this._from();
    497             }
    498             if (!sqlObject.where) {
    499                 sqlObject.where = "where ";
    500             }
    501             this._parseObjectAndCreateSQL(options);
    502             return this;
    503         },
    504
    505         select : function(values, options) {
    506             var sqlObject = this.sqlObject;
    507             if (!sqlObject.update && !sqlObject["delete"]) {
    508                 if (!sqlObject.select || sqlObject.select.indexOf("*") != -1) {
    509                     sqlObject.select = "select ";
    510                 }
    511                 if (values) {
    512                     if (typeof values == "string") {
    513                         sqlObject.select += values;
    514                     } else if (values instanceof Array) {
    515                         for (var i in values) {
    516                             var val = values[i];
    517                             if (typeof val == "string") {
    518                                 if (i > 0) sqlObject.select += ", ";
    519                                 sqlObject.select += val;
    520                             } else {
    521                                 throw new Error("select params must be a string");
    522                             }
    523                         }
    524                     }
    525                 }
    526                 if (options) {
    527                     this.where(options);
    528                 }
    529             } else {
    530                 throw new Error("Cannot call select after update or delete");
    531             }
    532             return this;
    533         },
    534
    535         distinct : function() {
    536             var sqlObject = this.sqlObject;
    537             if (!sqlObject.update && !sqlObject["delete"]) {
    538                 if (!sqlObject.select) {
    539                     sqlObject.select = "select distinct *";
    540                 } else {
    541                     sqlObject.select = sqlObject.select.replace("select", "select distinct");
    542                 }
    543             } else {
    544                 throw new Error("Cannot call select after update or delete");
    545             }
    546             return this;
    547         },
    548
    549         update : function(values, options) {
    550             var sqlObject = this.sqlObject;
    551             if (!sqlObject.select && !sqlObject["delete"]) {
    552                 if (values) {
    553                     if (!sqlObject.update) {
    554                         sqlObject.update = "update " + this.table;
    555                     }
    556                     this._set(values);
    557                     if (options) {
    558                         this.where(options);
    559                     }
    560                 } else {
    561                     throw new Error("To call update you must provide values to update");
    562                 }
    563             } else {
    564                 throw new Error("Cannot call udpate after select or delete!");
    565             }
    566             return this;
    567         },
    568
    569         remove : function(values, options) {
    570             var sqlObject = this.sqlObject;
    571             if (!sqlObject.update && !sqlObject["delete"]) {
    572                 if (!sqlObject["delete"]) {
    573                     sqlObject["delete"] = "delete ";
    574                 }
    575                 if (values) {
    576                     if (sqlObject["delete"] == "delete ") {
    577                         sqlObject["delete"] += this.table;
    578                     }
    579                     if (typeof values == "string") {
    580                         sqlObject["delete"] += ", " + values;
    581                     } else if (values instanceof Array) {
    582                         sqlObject["delete"] += ", " + values.join(", ");
    583                     }
    584                 } else if (!sqlObject.from) {
    585                     this._from();
    586                 }
    587                 if (options) {
    588                     this.where(options);
    589                 }
    590             } else {
    591                 throw new Error("Cannot call delete after update or delete");
    592             }
    593             return this;
    594         },
    595
    596         /*
    597          options = {name : "fred"},
    598          select * from <table> where name = 'fred'
    599          options = {x : {gt : 2}}
    600          select * from <table> where x > 2
    601          options = {x : {lt : 2}}
    602          select * from <table> where x < 2
    603          options = {x : {gte : 2}}
    604          select * from <table> where x >= 2
    605          options = {x : {lte : 2}}
    606          select * from <table> where x <= 2
    607          options = {x : {in : [1, 2, 3]}}
    608          select * from <table> where x in (1,2,3);
    609          options = {x : {ne : 2}}
    610          select * from <table> where x != 2;
    611          options = {flag : {is : (TRUE|FALSE|UNKNOWN)}}
    612          select * from <table> where flag is (TRUE|FALSE|UNKNOWN);
    613          options = {flag : {isNot : (TRUE|FALSE|UNKNOWN)}}
    614          select * from <table> where flag IS NOT (TRUE|FALSE|UNKNOWN);
    615          options = {x : {isNull : (TRUE|FALSE|UNKNOWN)}}
    616          select * from <table> where flag IS NULL;
    617          options = {x : {isNotNull : (TRUE|FALSE|UNKNOWN)}}
    618          select * from <table> where flag IS NOT NULL;
    619          options = {x : {between : [1,5]}}
    620          select * from <table> where x BETWEEN 1 AND 5;
    621          options = {x : {notBetween : [1,5]}}
    622          select * from <table> where x NOT BETWEEN 1 AND 5;
    623          options = {name : {like : "Fred"}}
    624          select * from <table> where x NOT BETWEEN 1 AND 5;
    625          */
    626         find : function(options) {
    627             //reset sql
    628             var sqlObject = this.sqlObject;
    629             if (!sqlObject.select && !sqlObject.update && !sqlObject["delete"]) {
    630                 sqlObject.select = "select *";
    631             }
    632             if (!sqlObject.update && !sqlObject.from) this._from();
    633             if (options) {
    634                 if (sqlObject.having) {
    635                     this.having(options);
    636                 } else {
    637                     this.where(options);
    638                 }
    639             }
    640             return this;
    641         },
    642
    643         /*
    644          *   add order tp query
    645          *   mysql.order(x)
    646          *   select * from <table> order by x
    647          *   mysql.order([x,y])
    648          *   select * from <table> order by x,y
    649          *   mysql.order({x : "desc"})
    650          *   select * from <table> order by x desc
    651          *   mysql.order([{x : "desc"}, y])
    652          *   select * from <table> order by x desc, y
    653          *   mysql.order([{x : "desc"}, {y : desc}])
    654          *   select * from <table> order by x desc, y desc
    655
    656          */
    657         order : function(options) {
    658             if (!this.sql) this.find();
    659             var sqlObject = this.sqlObject;
    660             if (!sqlObject.from) {
    661                 this._from();
    662             }
    663             if (options) {
    664                 if (!sqlObject.order) {
    665                     sqlObject.order = "order by ";
    666                     this._orderedCount = 0;
    667                 } else if (this._orderedCount) {
    668                     sqlObject.order += ", ";
    669                 }
    670                 if (typeof options == "string") {
    671                     sqlObject.order += options;
    672                 } else if (options instanceof Array) {
    673                     options.forEach(this.order, this);
    674                 } else if (typeof options == "object") {
    675                     var count = 0;
    676                     for (var i in options) {
    677                         if (count) throw new Error("when providing an object to order only one key is allowed");
    678                         var type = options[i];
    679                         if (type == 'desc' || type == "asc") {
    680                             sqlObject.order += i + " " + type;
    681                         } else {
    682                             throw new Error("Only 'asc' or 'desc' is allowed as a value for an ordered object");
    683                         }
    684                     }
    685                 }
    686                 this._orderedCount++;
    687             }
    688             return this;
    689         },
    690
    691         orderBy : function(options) {
    692             return this.order(options);
    693         },
    694
    695
    696         limit : function(limit, offset) {
    697             var sqlObject = this.sqlObject;
    698             if (!sqlObject.update && !sqlObject.from) {
    699                 this.find();
    700             }
    701             if (limit) {
    702                 if (typeof limit == "number") {
    703                     sqlObject.limit = "limit " + limit;
    704                 } else {
    705                     throw new Error("when using limit the param must be a number");
    706                 }
    707             }
    708             offset && this.offset(offset);
    709             return this;
    710         },
    711
    712         offset : function(offset) {
    713             if (!this.sql) this.find();
    714             var sqlObject = this.sqlObject;
    715             if (!sqlObject.update && !sqlObject["delete"]) {
    716                 if (!sqlObject.from) {
    717                     this._from();
    718                 }
    719                 if (offset) {
    720                     if (typeof offset == "number") {
    721                         sqlObject.offset = "offset " + offset;
    722                     } else {
    723                         throw new Error("when using offset the param must be a number");
    724                     }
    725                 }
    726             } else {
    727                 throw new Error("Cannot call offset on update or delete");
    728             }
    729             return this;
    730         },
    731
    732         /*
    733          *   mysql.join(<tablename>, options);
    734          *   select * from inner join <thisTable
    735          * */
    736         join : function(table, options) {
    737             return this._joinOp("innerJoin", table, options);
    738
    739         },
    740
    741         /*
    742          * Creats a group clause for sql
    743          * mysql.group(<columnName>);
    744          * select * from <tableName> group by <columnName>
    745          * mysql.group([col1,col2...]);
    746          * select * from <tableName> group by col1, col2...
    747          * mysql.group(col, havingOptions);
    748          * select * from <tableName> group by col having <having options>
    749          * mysql.group([col1, col2...], {col1 : a});
    750          * select * from <tableName> group by col1, col2.... having col2 = 'a'
    751          * */
    752         group : function(key, options) {
    753             if (!this.sql) this.find();
    754             var sqlObject = this.sqlObject;
    755             if (!sqlObject.update && !sqlObject["delete"]) {
    756                 if (!sqlObject.from) {
    757                     this._from();
    758                 }
    759                 if (key) {
    760                     if (typeof key == "string") {
    761                         if (!sqlObject.group) {
    762                             sqlObject.group = "group by";
    763                         }
    764                         sqlObject.group += " " + key;
    765                     } else if (key instanceof Array) {
    766                         if (!sqlObject.group) {
    767                             sqlObject.group = "group by";
    768                         }
    769                         sqlObject.group += " " + key.join(", ");
    770                     } else {
    771                         throw new Error("key must be a string or array");
    772                     }
    773                     if (sqlObject.group && options) {
    774                         this.having(options);
    775                     }
    776                 } else {
    777                     throw new Error("when calling group a grouping column is required");
    778                 }
    779             } else {
    780                 throw new Error("Cannot group on an update or delete");
    781             }
    782             return this;
    783         },
    784
    785         /*
    786          * Creates a having clause, group must have been previously called
    787          * mysql.having({x : 1});
    788          * select * from <tableName> group by <*> having x = 1
    789          * you may also use find if a group clause has been defined
    790          * */
    791         having : function(options) {
    792             var sqlObject = this.sqlObject;
    793             if (sqlObject.group) {
    794                 if (!sqlObject.having) {
    795                     sqlObject.having = "having ";
    796                     this._needLogic = false;
    797                 }
    798                 if (options) {
    799                     this._parseObjectAndCreateSQL(options);
    800                 }
    801             } else {
    802                 throw new Error("a group clause must be previously defined");
    803             }
    804             return this;
    805         },
    806
    807         logicGroup : function(options) {
    808             if (!this.sql) this.find();
    809             var sqlObject = this.sqlObject;
    810             var sqlKey = "where";
    811             if (sqlObject.having) {
    812                 this.having();
    813                 sqlKey = "having";
    814             } else if (!sqlObject.where) {
    815                 this.where();
    816             }
    817             if (options) {
    818                 sqlObject[sqlKey] += "(";
    819                 this.where(options);
    820                 sqlObject[sqlKey] += ")";
    821             }
    822             return this;
    823         },
    824
    825         /*call this to finish off select clause and not execute
    826          *else just call execute();
    827          *i.e you just call mysql.find() or mysql.select(params);
    828          *mysql.find.end(); mysql.find.select().end();
    829          * */
    830         end : function() {
    831             var sqlObject = this.sqlObject;
    832             if (!sqlObject.select && !sqlObject.update && !sqlObject["delete"]) {
    833                 this.find();
    834             }
    835             if (sqlObject.select) {
    836                 if (!sqlObject.from) this._from();
    837             }
    838             return this;
    839         }
    840     },
    841
    842     static : {
    843         createTable : function(table, db) {
    844             var promise = new Promise();
    845             db.query(table.createTableSql).then(hitch(promise, "callback", true), hitch(promise, "errback"));
    846             return promise;
    847         },
    848
    849         alterTable : function(table, db) {
    850             var promise = new Promise();
    851             db.query(table.alterTableSql).then(hitch(promise, "callback", true), hitch(promise, "errback"));
    852             return promise;
    853         },
    854
    855         dropTable : function(table, db) {
    856             var promise = new Promise();
    857             db.query(table.dropTableSql).then(hitch(promise, "callback", true), hitch(promise, "errback"));
    858             return promise;
    859         },
    860
    861         save : function(table, object, db) {
    862             if (table && object && db) {
    863                 var promise = new Promise();
    864                 var sql = "";
    865                 if (object instanceof Array) {
    866                     sql = object.map(
    867                             function(o) {
    868                                 return createInsertStatement(table, o, db);
    869                             }).join("");
    870                 } else {
    871                     sql = createInsertStatement(table, object, db);
    872                 }
    873                 db.query(sql).then(hitch(promise, "callback"), hitch(promise, "errback"));
    874                 return promise;
    875             } else {
    876                 throw new Error("Table, object, and db required when calling mysql.save");
    877             }
    878         },
    879
    880         schema : function(tableName, db) {
    881             if (typeof tableName != "string") throw new Error("tablename must be a string");
    882             if (tableName && db) {
    883                 var promise = new Promise();
    884 	            var database = db.database;
    885                 db.query("DESCRIBE " + escape(tableName)).then(hitch(this, function(database,results) {
    886                     var obj = {};
    887                     if (results.length) {
    888                         var schema = {database : database};
    889                         var pks = [];
    890                         results.forEach(function(o) {
    891                             var t = mysqlTypes.fromColDef(o);
    892                             if (t.isPrimaryKey()) {
    893                                 pks.push(o.Field);
    894                             }
    895                             schema[o.Field] = t;
    896                         });
    897                         pks.length && (schema.primaryKey = pks);
    898                         promise.callback(new Table(tableName, schema));
    899                     } else {
    900                         promise.callback(null);
    901                     }
    902                 }, database), hitch(promise, "errback"));
    903                 return promise;
    904             } else {
    905                 throw new Error("Table name and db conneciton required to retrieve schema");
    906             }
    907         },
    908
    909         getLastInsertId : function(db) {
    910             var promise = new Promise();
    911             var sql = "SELECT LAST_INSERT_ID() as id";
    912             db.query(sql).then(hitch(promise, "callback"), hitch(promise, "errback"));
    913             return promise;
    914         },
    915
    916         foreignKey : mysqlTypes.foreignKey,
    917         addForeignKey : mysqlTypes.addForeignKey,
    918         dropForeignKey : mysqlTypes.dropForeignKey,
    919         primaryKey : mysqlTypes.primaryKey,
    920         addPrimaryKey : mysqlTypes.addPrimaryKey,
    921         dropPrimaryKey : mysqlTypes.dropPrimaryKey,
    922         unique : mysqlTypes.unique,
    923         addUnique : mysqlTypes.addUnique,
    924         dropUnique : mysqlTypes.dropUnique,
    925         dropColumn : mysqlTypes.dropColumn,
    926         alterColumn : mysqlTypes.alterColumn,
    927         column : mysqlTypes.column,
    928         addColumn : mysqlTypes.addColumn,
    929         isValidType : mysqlTypes.isValidType,
    930         client : MySQLClient.Client,
    931         ConnectionPool : MySQLClient.ConnectionPool,
    932         types : mysqlTypes.types
    933
    934     }
    935 }));
    936
    937
    938
    939
    940
    941
    942