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 }