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