1 /*
2  * Copyright 2015-2018 HuntLabs.cn.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 module hunt.database.query.QueryBuilder;
18 
19 import hunt.database.query.Common;
20 import hunt.database.query.Comparison;
21 import hunt.database.query.Expr;
22 import hunt.database.query.Expression;
23 
24 
25 import hunt.database.driver.postgresql.PgUtil;
26 import hunt.database.driver.mysql.MySQLUtil;
27 
28 import hunt.sql;
29 import hunt.Byte;
30 import hunt.Exceptions;
31 import hunt.Integer;
32 import hunt.Long;
33 import hunt.logging;
34 import hunt.Double;
35 import hunt.Float;
36 import hunt.Short;
37 import hunt.String;
38 import hunt.util.StringBuilder;
39 import hunt.Nullable;
40 
41 
42 import std.array;
43 import std.conv;
44 import std.regex;
45 import std.string;
46 import std.variant;
47 
48 /**
49  * 
50  */
51 class QueryBuilder
52 {
53 
54     private DBType _dbType = DBType.MYSQL;
55 
56     private QUERY_TYPE _type = QUERY_TYPE.SELECT;
57     private string _table;
58     private string _tableAlias;
59     private string[] _select;
60     private JoinExpression[] _join;
61     private string _where;
62     private string[] _whereAnd;
63     private string[] _whereOr;
64 
65     private ValueVariant[string] _values;
66     private string _having;
67     private string[] _groupBy;
68     private string[] _orderBy;
69     private int _offset;
70     private int _limit;
71     private Variant[string] _parameters; 
72     // private Expr _expr;
73     private bool _distinct;
74     private string _autoIncreaseKey;
75     private FormatOption _formatOption;
76     private char _quotes = '"';
77 
78     
79     this(DBType dbType) {
80         _dbType = dbType;
81         _formatOption = new FormatOption(true, true);
82         _formatOption.config(VisitorFeature.OutputQuotedIdentifier, true);
83         if(dbType == dbType.POSTGRESQL) {
84             _quotes = '"';
85         } else if(dbType == dbType.MYSQL) {
86             _quotes = '`';
87         }
88     }
89 
90     FormatOption formatOption() {
91         return _formatOption;
92     }
93 
94     void formatOption(FormatOption value) {
95         _formatOption = value;
96     }
97 
98     // @property Expr expr()
99     // {
100     //     if (_expr is null)
101     //         _expr = new Expr();
102     //     return _expr;
103     // }
104 
105     QueryBuilder from(string table, string _alias = null)
106     {
107         // logDebug("QueryBuilder From : %s , %s ".format(table,_alias));
108         _table = table;
109         _tableAlias = _alias;
110 
111         return this;
112     }
113 
114     QueryBuilder select(string[] column...)
115     {
116         _type = QUERY_TYPE.SELECT;
117         _select = column;
118         return this;
119     }
120 
121     QueryBuilder update(string table = null, string _alias = null)
122     {
123         _type = QUERY_TYPE.UPDATE;
124         if (table != null)
125             _table = table;
126         if (_alias != null)
127             _tableAlias = _alias;
128         return this;
129     }
130 
131     QueryBuilder del(string table = null, string _alias = null)
132     {
133         _type = QUERY_TYPE.DELETE;
134         if (table != null)
135             _table = table;
136         if (_alias != null)
137             _tableAlias = _alias;
138         return this;
139     }
140 
141     QueryBuilder insert(string table)
142     {
143         _type = QUERY_TYPE.INSERT;
144         _table = table;
145         return this;
146     }
147 
148     QueryBuilder showTables()
149     {
150         _type = QUERY_TYPE.SHOW_TABLES;
151         return this;
152     }
153 
154     QueryBuilder descTable(string tableName)
155     {
156         _type = QUERY_TYPE.DESC_TABLE;
157         _table = tableName;
158         return this;
159     }
160 
161     QueryBuilder join(JoinMethod joinMethod, string table,
162             string tablealias, string joinWhere)
163     {
164         _join ~= new JoinExpression(joinMethod, table, tablealias, joinWhere);
165         return this;
166     }
167 
168     QueryBuilder join(JoinMethod joinMethod, string table, string joinWhere)
169     {
170         return join(joinMethod, table, table, joinWhere);
171     }
172 
173     QueryBuilder innerJoin(string table, string tablealias, string joinWhere)
174     {
175         return join(JoinMethod.InnerJoin, table, tablealias, joinWhere);
176     }
177 
178     QueryBuilder innerJoin(string table, string joinWhere)
179     {
180         return innerJoin(table, table, joinWhere);
181     }
182 
183     QueryBuilder leftJoin(string table, string tableAlias, string joinWhere)
184     {
185         return join(JoinMethod.LeftJoin, table, tableAlias, joinWhere);
186     }
187 
188     QueryBuilder leftJoin(string table, string joinWhere)
189     {
190         return leftJoin(table, table, joinWhere);
191     }
192 
193     QueryBuilder rightJoin(string table, string tableAlias, string joinWhere)
194     {
195         return join(JoinMethod.RightJoin, table, tableAlias, joinWhere);
196     }
197 
198     QueryBuilder rightJoin(string table, string joinWhere)
199     {
200         return rightJoin(table, table, joinWhere);
201     }
202 
203     QueryBuilder fullJoin(string table, string tableAlias, string joinWhere)
204     {
205         return join(JoinMethod.FullJoin, table, tableAlias, joinWhere);
206     }
207 
208     QueryBuilder fullJoin(string table, string joinWhere)
209     {
210         return fullJoin(table, table, joinWhere);
211     }
212 
213     QueryBuilder crossJoin(string table, string tableAlias)
214     {
215         return join(JoinMethod.CrossJoin, table, tableAlias, null);
216     }
217 
218     QueryBuilder crossJoin(string table)
219     {
220         return crossJoin(table, table);
221     }
222 
223     QueryBuilder setAutoIncrease(string key)
224     {
225         _autoIncreaseKey = key;
226         return this;
227     }
228 
229     string getAutoIncrease()
230     {
231         return _autoIncreaseKey;
232     }
233 
234     bool getDistinct()
235     {
236         return _distinct;
237     }
238 
239     QueryBuilder setDistinct(bool b)
240     {
241         _distinct = b;
242         return this;
243     }
244 
245     QueryBuilder where(T)(Comparison!T comExpr)
246     {
247         _where = getExprStr!T(comExpr);
248         return this;
249     }
250 
251     QueryBuilder whereAnd(T)(Comparison!T comExpr)
252     {
253         _whereAnd ~= getExprStr!T(comExpr);
254         return this;
255     }
256 
257     QueryBuilder whereOr(T)(Comparison!T comExpr)
258     {
259         _whereOr ~= getExprStr!T(comExpr);
260         return this;
261     }
262 
263     private string getExprStr(T)(Comparison!T comExpr)
264     {
265         static if (is(T == string) || is(T == String) || is(T == Nullable!string)) {
266             return comExpr.variant ~ " " ~ comExpr.operator ~ " " ~ 
267                 escapeWithQuotes(comExpr.value.to!string);
268         } else {
269             return comExpr.variant ~ " " ~ comExpr.operator ~ " " ~ comExpr.value.to!string;
270         }
271     }
272 
273     QueryBuilder where(string expression)
274     {
275         _where = expression;
276         return this;
277     }
278 
279     QueryBuilder whereAnd(string expression)
280     {
281         _whereAnd ~= expression;
282         return this;
283     }
284 
285     QueryBuilder whereOr(string expression)
286     {
287         _whereOr ~= expression;
288         return this;
289     }
290 
291     QueryBuilder groupBy(string expression)
292     {
293         _groupBy ~= expression;
294         return this;
295     }
296 
297     QueryBuilder orderBy(string column)
298     {
299         _orderBy ~= column;
300         return this;
301     }
302 
303     QueryBuilder orderBy(string[] columns...)
304     {
305         _orderBy = columns;
306         return this;
307     }
308 
309     QueryBuilder offset(int offset)
310     {
311         _offset = offset;
312         return this;
313     }
314 
315     QueryBuilder limit(int limit)
316     {
317         _limit = limit;
318         return this;
319     }
320 
321     QueryBuilder having(string expression)
322     {
323         _having = expression;
324         return this;
325     }
326 
327     QueryBuilder values(Variant[string] arr)
328     {
329         version(HUNT_DB_DEBUG) logDebug("set values: ",arr);
330         foreach (key, value; arr)
331         {
332             auto expr = new ValueVariant(key, value);
333             _values[key] = expr;
334         }
335         return this;
336     }
337 
338     QueryBuilder set(R)(string fieldName, string columnName, string tableName, R param)
339     {
340         version(HUNT_DB_DEBUG) {
341             tracef("---(fieldName: %s, columnName: %s, value: %s,  type: %s )", 
342                 fieldName, columnName, param, typeid(param));
343         }
344 
345         if(fieldName in _values) {
346             warningf("Key exists: %s", fieldName);
347         }
348 
349         _values[fieldName] = new ValueVariant(columnName, param);
350 
351         // static if (is(R == int) || is(R == uint))
352         // {
353         //     _values[fieldName] = new ValueVariant(columnName, param);
354         // }
355         // else static if (is(R == string) || is(R == char) || is(R == byte[]))
356         // {
357         //     _values[fieldName] = new ValueVariant(columnName, new String(param));
358         // }
359         // else static if (is(R == bool))
360         // {
361         //     _values[fieldName] = new ValueVariant(columnName, new Boolean(param));
362         // }
363         // else static if (is(R == double))
364         // {
365         //     _values[fieldName] = new ValueVariant(columnName, new Double(param));
366         // }
367         // else static if (is(R == float))
368         // {
369         //     _values[fieldName] = new ValueVariant(columnName, new Float(param));
370         // }
371         // else static if (is(R == short) || is(R == ushort))
372         // {
373         //     _values[fieldName] = new ValueVariant(columnName, new Short(param));
374         // }
375         // else static if (is(R == long) || is(R == ulong))
376         // {
377         //     _values[fieldName] = new ValueVariant(columnName, new Long(param));
378         // }
379         // else static if (is(R == byte) || is(R == ubyte))
380         // {
381         //     _values[fieldName] = new ValueVariant(columnName, new Byte(param));
382         // }
383         // // else static if (is(R == Object))
384         // // {
385         // //     _values[fieldName] = new ValueVariant(columnName,new String(param.toString));
386         // // }
387         // else
388         // {
389         //     _values[fieldName] = new ValueVariant(columnName, param);
390         // }
391 
392         return this;
393     }
394 
395 
396     QueryBuilder setParameter(R)(string key, R param)
397     {
398         // static if (is(R == int) || is(R == uint))
399         // {
400         //     _parameters[key] = new Integer(param);
401         // }
402         // else static if (is(R == string) || is(R == char) || is(R == byte[]))
403         // {
404         //     _parameters[key] = new String(param);
405         // }
406         // else static if (is(R == bool))
407         // {
408         //     _parameters[key] = new Boolean(param);
409         // }
410         // else static if (is(R == double))
411         // {
412         //     _parameters[key] = new Double(param);
413         // }
414         // else static if (is(R == float))
415         // {
416         //     _parameters[key] = new Float(param);
417         // }
418         // else static if (is(R == short) || is(R == ushort))
419         // {
420         //     _parameters[key] = new Short(param);
421         // }
422         // else static if (is(R == long) || is(R == ulong))
423         // {
424         //     _parameters[key] = new Long(param);
425         // }
426         // else static if (is(R == byte) || is(R == ubyte))
427         // {
428         //     _parameters[key] = new Byte(param);
429         // }
430         // else static if(is(R == class))
431         // {
432         //     _parameters[key] = param;
433         // }
434         // else
435         // {
436         //     throw new Exception("IllegalArgument not support : " ~ R.stringof);
437         // }
438 
439         _parameters[key] = Variant(param);
440 
441         return this;
442     }
443 
444     string parameterized(string sql, Variant[string] params)
445     {
446         foreach (string k, Variant v; params)
447         {
448             auto re = regex(r":" ~ k ~ r"([^\w]*)", "g");
449             version(HUNT_DB_DEBUG) tracef("value: %s, type: %s", v.toString(), typeid(v));
450             // if (cast(Nullable!string)v !is null)
451             version(HUNT_DB_DEBUG) warning("type: ", v.type);
452             if(v.type == typeid(string))
453             {
454                 sql = sql.replaceAll(re,  escapeLiteral(v.toString()) ~ "$1");
455             }
456             else
457             {
458                 sql = sql.replaceAll(re, v.toString() ~ "$1" );
459             }
460         }
461         return sql;
462     }
463 
464     override string toString()
465     {
466         if (!_table.length && _type != QUERY_TYPE.SHOW_TABLES)
467             throw new Exception("query build table name not exists");
468         string str;
469 
470         version(HUNT_DB_DEBUG) {
471             tracef("Query type: %s", _type);
472         }
473 
474         try
475         {
476             switch (_type)
477             {
478             case QUERY_TYPE.SELECT:
479                 {
480                     SQLSelectBuilderImpl builder = new SQLSelectBuilderImpl(_dbType.name);
481                     builder.from(_table, _tableAlias);
482                     // warning(_select);
483                     builder.select(_select);
484                     
485                     foreach (item; _join)
486                     {
487                         if (item._join == JoinMethod.LeftJoin)
488                             builder.leftJoin(item._table, item._tableAlias, item._on);
489                         else if (item._join == JoinMethod.RightJoin)
490                             builder.rightJoin(item._table, item._tableAlias, item._on);
491                         else if (item._join == JoinMethod.InnerJoin)
492                             builder.innerJoin(item._table, item._tableAlias, item._on);
493                     }
494 
495                     {
496                         if (_where.length > 0)
497                             builder.where(_where);
498                         if (_whereAnd.length > 0)
499                         {
500                             foreach (item; _whereAnd)
501                                 builder.whereAnd(item);
502                         }
503                         if (_whereOr.length > 0)
504                         {
505                             foreach (item; _whereOr)
506                                 builder.whereOr(item);
507                         }
508                     }
509 
510                     if (_groupBy.length > 0)
511                     {
512                         foreach (item; _groupBy)
513                         {
514                             builder.groupBy(item);
515                         }
516                     }
517                     if (_orderBy.length > 0)
518                     {
519                         foreach (item; _orderBy)
520                         {
521                             builder.orderBy(item);
522                         }
523                     }
524                     if (_having.length > 0)
525                         builder.having(_having);
526                     if (_limit != int.init)
527                         builder.limit(_limit);
528                     if (_offset != int.init)
529                         builder.offset(_offset);
530                     if (_distinct)
531                         builder.setDistinct();
532                     str = builder.toString(_formatOption);
533                     str = parameterized(str, _parameters);
534                 }
535                 break;
536 
537             case QUERY_TYPE.UPDATE:
538                 {
539                     SQLUpdateBuilderImpl builder = new SQLUpdateBuilderImpl(_dbType.name);
540                     builder.from(_table, _tableAlias);
541                     // logDebug("set values len : ",_values.length);
542                     if (_values.length > 0)
543                     {
544                         import std.algorithm.searching;
545                         string[] existKeys;
546                         if(_dbType == DBType.MYSQL) {
547                             foreach (string key, ref ValueVariant item; _values) {
548                                 if(!canFind(existKeys, item.key)) {
549                                     version(HUNT_DB_DEBUG) tracef("Field: %s, Update: %s", key, item);
550                                     builder.setValue(item.key, item.value);
551                                     existKeys ~= item.key;
552                                 } else {
553                                     version(HUNT_DB_DEBUG) warningf("Field: %s, Update: %s exists.", key, item);
554                                 }
555                                 
556                             }
557                         } else {
558                             foreach (string key, ref ValueVariant item; _values) {
559                                 if(!canFind(existKeys, item.key)) {
560                                     version(HUNT_DB_DEBUG) tracef("Field: %s, Update: %s", key, item);
561                                     builder.setValue(item.key, item.value);
562                                     existKeys ~= item.key;
563                                 } else {
564                                     version(HUNT_DB_DEBUG) warningf("Field: %s, Update: %s exists.", key, item);
565                                 }
566                             }
567                         }
568                     }
569 
570                     {
571                         if (_where.length > 0)
572                             builder.where(_where);
573                         if (_whereAnd.length > 0)
574                         {
575                             foreach (item; _whereAnd)
576                                 builder.whereAnd(item);
577                         }
578                         if (_whereOr.length > 0)
579                         {
580                             foreach (item; _whereOr)
581                                 builder.whereOr(item);
582                         }
583                     }
584 
585                     if (_groupBy.length > 0)
586                     {
587                         foreach (item; _groupBy)
588                         {
589                             builder.groupBy(item);
590                         }
591                     }
592                     if (_orderBy.length > 0)
593                     {
594                         foreach (item; _orderBy)
595                         {
596                             builder.orderBy(item);
597                         }
598                     }
599                     if (_having.length > 0)
600                         builder.having(_having);
601                     if (_limit != int.init)
602                         builder.limit(_limit);
603                     if (_offset != int.init)
604                         builder.offset(_offset);
605 
606                     str = builder.toString(_formatOption);
607                     str = parameterized(str, _parameters);
608                 }
609                 break;
610             case QUERY_TYPE.DELETE:
611                 {
612                     auto builder = new SQLDeleteBuilderImpl(_dbType.name);
613                     builder.from(_table, _tableAlias);
614 
615                     {
616                         if (_where.length > 0)
617                             builder.where(_where);
618                         if (_whereAnd.length > 0)
619                         {
620                             foreach (item; _whereAnd)
621                                 builder.whereAnd(item);
622                         }
623                         if (_whereOr.length > 0)
624                         {
625                             foreach (item; _whereOr)
626                                 builder.whereOr(item);
627                         }
628                     }
629 
630                     if (_groupBy.length > 0)
631                     {
632                         foreach (item; _groupBy)
633                         {
634                             builder.groupBy(item);
635                         }
636                     }
637                     if (_orderBy.length > 0)
638                     {
639                         foreach (item; _orderBy)
640                         {
641                             builder.orderBy(item);
642                         }
643                     }
644                     if (_having.length > 0)
645                         builder.having(_having);
646                     if (_limit != int.init)
647                         builder.limit(_limit);
648                     if (_offset != int.init)
649                         builder.offset(_offset);
650 
651                     str = builder.toString(_formatOption);
652                     str = parameterized(str, _parameters);
653 
654                 }
655                 break;
656 
657             case QUERY_TYPE.INSERT:
658                 {
659                     str ~= " INSERT INTO ";
660 
661                     if(formatOption.isEnabled(VisitorFeature.OutputQuotedIdentifier)) {
662                         str ~= _quotes ~ _table ~ _quotes;
663                     } else {
664                         str ~= _table;
665                     }
666                     string keys;
667                     string values;
668                     string tempValue;
669                     bool isFirstItem = true;
670                     string tablePrefix = _table ~ ".";
671 
672                     foreach (string k, ValueVariant v; _values)
673                     {
674                         // if ((cast(String)(v.value) !is null) || (cast(Nullable!string)(v.value) !is null))
675                         TypeInfo fieldTypeInfo = v.value.type;
676                         version(HUNT_DB_DEBUG) {
677                             tracef("field: %s, type: %s, value: %s", k, fieldTypeInfo, v.value);
678                         }
679 
680                         if(fieldTypeInfo == typeid(string))
681                         {
682                             // logDebug("---Insert(%s , %s )".format(k,v.value));
683                             tempValue = escapeWithQuotes(v.value.get!string());
684                         } else if(fieldTypeInfo == typeid(byte[])) {
685                             if(_dbType == DBType.POSTGRESQL) {
686                                 tempValue = format("'\\x%(%02X%)'", v.value.get!(byte[]));
687                             } else if(_dbType == DBType.MYSQL) {
688                                 tempValue = format("unhex('%(%02X%)')", v.value.get!(byte[]));
689                             } else {
690                                 throw new Exception("Unsupported type: " ~ fieldTypeInfo.toString());
691                             }
692                         } else if(fieldTypeInfo == typeid(ubyte[])) {
693                             if(_dbType == DBType.POSTGRESQL) {
694                                 tempValue = format("'\\x%(%02X%)'", v.value.get!(ubyte[]));
695                             } else if(_dbType == DBType.POSTGRESQL) {
696                                 tempValue = format("unhex('%(%02X%)')", v.value.get!(byte[]));
697                             } else {
698                                 throw new Exception("Unsupported type: " ~ fieldTypeInfo.toString());
699                             }
700                         } else if(fieldTypeInfo == typeid(float) || fieldTypeInfo == typeid(double)) {
701                             tempValue = v.value.toString();
702                             if(tempValue == "nan") {
703                                 tempValue = escapeWithQuotes(tempValue);
704                             }
705                         } else {
706                             tempValue = v.value.toString();
707                         }
708 
709                         // chomp the table prefix
710                         k = chompPrefix(k, tablePrefix);
711 
712                         string columnName;
713                         if(formatOption.isEnabled(VisitorFeature.OutputQuotedIdentifier)) {
714                             columnName = _quotes ~ k ~ _quotes;
715                         } else {
716                             columnName = k;
717                         }
718 
719                         if(isFirstItem) {
720                             isFirstItem = false;
721                             keys = columnName;
722                             values = tempValue;
723                         } else {
724                             keys ~= ", " ~ columnName;
725                             values ~= ", " ~ tempValue;
726                         }
727 
728                     }
729                     str ~= " (" ~ keys ~ ") VALUES(" ~ values ~ ")";
730 
731                     if(_dbType == DBType.POSTGRESQL) {
732                         // To get the last insert id
733                         if(!_autoIncreaseKey.empty())
734                             str ~= " returning \"" ~ _autoIncreaseKey ~ "\"";
735                     }
736                 }
737                 break;
738             case QUERY_TYPE.COUNT:
739                 // str ~= " select count(*) " ~ _table;
740                 break;
741             case QUERY_TYPE.SHOW_TABLES:
742                 if (_dbType == DBType.POSTGRESQL)
743                     str ~= "select tablename from pg_tables where schemaname = 'public'";
744                 else
745                     str ~= " show tables ";
746                 break;
747             case QUERY_TYPE.DESC_TABLE:
748                 if (_dbType == DBType.POSTGRESQL) {
749                     str ~= "SELECT column_name as Field, data_type FROM information_schema.columns" ~
750                             " WHERE table_schema='public' and table_name='" ~ _table ~ "'";
751                 } else if (_dbType == DBType.MYSQL) {
752                     str ~= "desc " ~ _table;
753                 // } else if (_dbType == DBType.SQLITE) {
754                 //     str ~= "select * from sqlite_master where type=\"table\" and name=\""
755                 //         ~ _table ~ "\"";
756                 }
757                 break;
758             default:
759                 throw new Exception("query build method not found");
760             }
761             version(HUNT_DB_DEBUG) infof("Query string: %s", str);
762         }
763         catch (Exception ex)
764         {
765             warning(ex.msg);
766             version(HUNT_DEBUG) warning(ex);
767             sqlDebugInfo();
768         }
769 
770         return str;
771     }
772 
773     private void sqlDebugInfo()
774     {
775         logDebug("{Type : %s  \n
776                    table : ( %s , %s ) \n
777                    select item :  %s  \n
778                    where : %s  \n
779                    whereAnd : %s \n
780                    whereOr : %s \n
781                    order by : %s \n
782                    having : %s \n
783                    group by : %s \n
784                    limit : %s  \n
785                    offset : %s \n
786                    }".format(_type, _table, _tableAlias, _select, _where,
787                 _whereAnd, _whereOr, _orderBy, _having, _groupBy, _limit, _offset));
788     }
789 
790     string escapeLiteral(string str) {
791         
792         if(_dbType == DBType.POSTGRESQL) {
793             scope StringBuilder sb = new StringBuilder((cast(int)str.length + 10) / 10 * 11); // Add 10% for escaping.
794             PgUtil.escapeLiteral(sb, str, true);
795 
796             return sb.toString();
797         } else if(_dbType == DBType.MYSQL) {
798             return MySQLUtil.escapeLiteral(str);
799         }
800 
801         return str;
802     }
803 
804     string escapeWithQuotes(string str) {
805 
806         if(_dbType == DBType.POSTGRESQL) {
807             return PgUtil.escapeWithQuotes(str);
808         } else if(_dbType == DBType.MYSQL) {
809             return MySQLUtil.escapeWithQuotes(str);
810         }
811 
812         return str;
813     }
814 }