1 /// 2 module dpq.querybuilder; 3 4 import dpq.value; 5 import dpq.query; 6 import dpq.connection; 7 import dpq.attributes; 8 import dpq.column; 9 10 import std.typecons; 11 import std..string; 12 13 import std.algorithm : map, sum; 14 import std.conv : to; 15 16 version (unittest) import std.stdio; 17 18 enum Order : string 19 { 20 none = "", 21 asc = "ASC", 22 desc = "DESC" 23 }; 24 25 private enum QueryType 26 { 27 select = "SELECT", 28 update = "UPDATE", 29 insert = "INSERT", 30 delete_ = "DELETE" 31 } 32 33 /** 34 A filter builder struct meant for internal usage. 35 36 Simplifies building a combination of AND/OR filters and makes the code more 37 readable. 38 */ 39 private struct FilterBuilder 40 { 41 private string[][] _filters; 42 43 ref FilterBuilder and(string filter) 44 { 45 if (_filters.length == 0) 46 _filters.length++; 47 48 _filters[$ - 1] ~= '(' ~ filter ~ ')'; 49 50 return this; 51 } 52 53 ref FilterBuilder or() 54 { 55 _filters ~= []; 56 57 return this; 58 } 59 60 /// Returns the actual number of lowest-level filters 61 long length() 62 { 63 return _filters.map!(f => f.length).sum; 64 } 65 66 67 string toString() 68 { 69 // Join inner filters by AND, outer by OR 70 return _filters.map!(innerFilter => 71 innerFilter.join(" AND ") 72 ).join(" OR "); 73 } 74 } 75 76 /** 77 Provides a nice way of writing queries in D, as well as some handy shortcuts 78 to working with D structures in the DB. 79 80 Most method names are synonimous with the same keyword in SQL, but their order 81 does not matter. 82 83 All of the methods can be chained. 84 85 Examples: 86 --------------------- 87 auto qb = QueryBuilder() 88 .select("id") 89 .from!User 90 .where("posts > {posts}") // placeholders can be used 91 .order("posts", Order.desc) 92 .limit(5); 93 94 // Placeholders will be replaced ONLY if they are specified. 95 // No need to escape anything, as it sent with execParams 96 qb["posts"] = 50; 97 --------------------- 98 */ 99 struct QueryBuilder 100 { 101 private 102 { 103 // Columns to select 104 string[] _columns; 105 106 // Table to select from 107 string _table; 108 109 // A list of filters, lowest-level representing AND, and OR between those 110 FilterBuilder _filters; 111 112 // List of ORDER BY columns and list of orders (ASC/DESC) 113 string[] _orderBy; 114 Order[] _orders; 115 116 // Limit and offset values, using -1 is null value (not set) 117 Nullable!(int, -1) _limit = -1; 118 Nullable!(int, -1) _offset = -1; 119 120 // Params to be used in the filters 121 Value[string] _params; 122 123 // Params to be be used as positional 124 Value[] _indexParams; 125 126 // Columns to list in RETURNING 127 string[] _returning; 128 129 // UPDATE's SET 130 string[] _set; 131 132 // Current index for positional params, needed because we allow mixing 133 // placeholders and positional params 134 int _paramIndex = 0; 135 136 // Type of the query, (SELECT, UPDATE, ...) 137 QueryType _type; 138 139 Connection* _connection; 140 } 141 142 @property QueryBuilder dup() 143 { 144 // It's a struct, I'll copy anyway 145 return this; 146 } 147 148 private string escapeIdentifier(string identifier) 149 { 150 if (_connection != null) 151 return _connection.escapeIdentifier(identifier); 152 153 // This could potentionally be dangerous, I don't like it. 154 return `"%s"`.format(identifier); 155 } 156 157 /** 158 Constructs a new QueryBuilder with the Connection, so we can directly 159 run queries with it. 160 */ 161 this(ref Connection connection) 162 { 163 _connection = &connection; 164 } 165 166 /** 167 Remembers the given key value pair, replacing the placeholders in the query 168 before running it. 169 170 If the same key is set multiple times, the last value will apply. 171 */ 172 void opIndexAssign(T)(T val, string key) 173 { 174 _params[key] = val; 175 } 176 177 unittest 178 { 179 writeln(" * QueryBuilder"); 180 writeln("\t * opIndexAssign"); 181 182 QueryBuilder qb; 183 qb["foo"] = 123; 184 qb["bar"] = "456"; 185 186 assert(qb._params["foo"] == Value(123)); 187 assert(qb._params["bar"] == Value("456")); 188 } 189 190 /** 191 Sets the builder's type to SELECT, a variadic array of column names to select 192 */ 193 ref QueryBuilder select(string[] cols...) 194 { 195 _columns = cols; 196 _type = QueryType.select; 197 return this; 198 } 199 200 /** 201 Same as above, except it accepts a variadic array of Column type. Mostly 202 used internally. 203 */ 204 ref QueryBuilder select(Column[] cols...) 205 { 206 _type = QueryType.select; 207 _columns = []; 208 foreach(col; cols) 209 { 210 if (col.column != col.asName) 211 _columns ~= "%s AS %s".format(col.column, col.asName); 212 else 213 _columns ~= col.column; 214 } 215 216 return this; 217 } 218 219 /** 220 Selects all the given relation's properties 221 222 Examples: 223 ----------------- 224 struct User { @PK @serial int id; } 225 auto qb = QueryBuilder() 226 .select!User 227 .from!User 228 .where( ... ); 229 ----------------- 230 231 */ 232 ref QueryBuilder select(T)() 233 { 234 return select(AttributeList!T); 235 } 236 237 unittest 238 { 239 writeln("\t * select"); 240 241 QueryBuilder qb; 242 qb.select("foo", "bar", "baz"); 243 assert(qb._columns == ["foo", "bar", "baz"]); 244 245 Column[] cs = [Column("foo", "foo_test"), Column("bar")]; 246 qb.select(cs); 247 assert(qb._columns == ["foo AS foo_test", "bar"]); 248 } 249 250 /** 251 Sets the builder's FROM value to the given string. 252 */ 253 ref QueryBuilder from(string from) 254 { 255 assert( 256 _type == QueryType.select || _type == QueryType.delete_, 257 "QueryBuilder.from() can only be used for SELECT or DELETE queries."); 258 259 _table = from; 260 return this; 261 } 262 263 /** 264 Same as above, but instead of accepting a string parameter, it instead 265 accepts a type as a template parameter, then sets the value to that 266 type's relation name. Preferred over the above version. 267 */ 268 ref QueryBuilder from(T)() 269 { 270 return from(relationName!T); 271 } 272 273 unittest 274 { 275 writeln("\t\t * from"); 276 QueryBuilder qb; 277 278 qb.from("sometable"); 279 assert(qb._table == "sometable"); 280 281 struct Test {} 282 qb.from!Test; 283 assert(qb._table == "test"); 284 } 285 286 /** 287 Generates a placeholder that should be unique every time. 288 289 This is required because we might filter by the same column twice (e.g. 290 where(["id": 1])).or.where(["id": 2]), in which case the second value for 291 ID would overwrite the first one. 292 */ 293 private string safePlaceholder(string key) 294 { 295 /* 296 Because we only really need to be unique within this specific QB, just 297 a simple static counter is good enough. It could be put on the QB 298 instance instead, but this works just as well no need to complicate for 299 now. 300 */ 301 static int count = 0; 302 return "%s_%d".format(key, ++count); 303 } 304 305 /** 306 Adds new filter(s). Param placeholders are used, with the same names as 307 the AA keys. Calling this multiple times will AND the filters. 308 309 Internally, a value placeholder will be used for each of the values, with 310 the same name as the column itself. Be careful not to overwrite these 311 before running the query. 312 */ 313 ref QueryBuilder where(T)(T[string] filters) 314 { 315 foreach (key, value; filters) 316 { 317 auto placeholder = safePlaceholder(key); 318 _filters.and("%s = {%s}".format(escapeIdentifier(key), placeholder)); 319 _params[placeholder] = value; 320 } 321 322 return this; 323 } 324 325 /** 326 Adds a new custom filter. 327 328 Useful for filters that are not simple equality comparisons, or usage psql 329 functions. Nothing is escaped, make sure you properly escape the reserved 330 keywords if they are used as identifier names. 331 332 Placeholders can be used with this, and even 333 positional params, since the order is predictable. Read addParam for 334 more information about that. 335 */ 336 ref QueryBuilder where(T...)(string filter, T params) 337 { 338 _filters.and("%s".format(filter)); 339 340 foreach (param; params) 341 addParam(param); 342 343 return this; 344 } 345 346 /// Alias and to where, to allow stuff like User.where( ... ).and( ... ) 347 alias and = where; 348 349 /** 350 Once called, all additional parameters will be placed into their own group, 351 OR placed between each group of ANDs 352 353 Examples: 354 -------------------- 355 auto qb = QueryBuilder() 356 .select!User 357 .from!User 358 .where(["id ": 1]) 359 .or 360 .where(["id": 2]); 361 362 // Which will produce a filter like "... WHERE (id = $1) OR (id = $2)" 363 -------------------- 364 */ 365 @property ref QueryBuilder or() 366 { 367 _filters.or(); 368 369 return this; 370 } 371 372 unittest 373 { 374 writeln("\t\t * where"); 375 376 auto qb = QueryBuilder(); 377 378 qb.where(["something": "asd"]); 379 assert(qb._filters.length == 1); 380 381 qb.where(["two": 2, "three": 3]); 382 assert(qb._filters.length == 3); 383 } 384 385 /** 386 Sets the ORDER part of the query. Accepts a column name and an Order value. 387 */ 388 ref QueryBuilder order(string col, Order order) 389 { 390 assert(_type == QueryType.select, "QueryBuilder.order() can only be used for SELECT queries."); 391 _orderBy ~= col; 392 _orders ~= order; 393 return this; 394 } 395 396 unittest 397 { 398 writeln("\t\t * order"); 399 400 QueryBuilder qb; 401 402 qb.order("some_col", Order.asc); 403 404 assert(qb._orderBy[0] == "some_col"); 405 assert(qb._orders[0] == Order.asc); 406 407 qb.order("some_other_col", Order.desc); 408 409 assert(qb._orderBy[1] == "some_other_col"); 410 assert(qb._orders[1] == Order.desc); 411 } 412 413 /** 414 Sets the LIMIT in the query. Only for SELECT queries, obviously. 415 */ 416 ref QueryBuilder limit(int limit) 417 { 418 assert(_type == QueryType.select, "QueryBuilder.limit() can only be used for SELECT queries."); 419 420 _limit = limit; 421 return this; 422 } 423 424 unittest 425 { 426 writeln("\t\t * limit"); 427 428 QueryBuilder qb; 429 qb.limit(1); 430 assert(qb._limit == 1); 431 } 432 433 /// OFFSET for queries 434 ref QueryBuilder offset(int offset) 435 { 436 assert(_type == QueryType.select, "QueryBuilder.offset() can only be used for SELECT queries."); 437 _offset = offset; 438 return this; 439 } 440 441 unittest 442 { 443 writeln("\t\t * offset"); 444 445 QueryBuilder qb; 446 qb.offset(1); 447 assert(qb._offset == 1); 448 } 449 450 // UPDATE methods 451 ref QueryBuilder update(string table) 452 { 453 _table = table; 454 _type = QueryType.update; 455 return this; 456 } 457 458 ref QueryBuilder update(T)() 459 { 460 return update(relationName!T); 461 } 462 463 unittest 464 { 465 QueryBuilder qb; 466 qb.update("sometable"); 467 468 assert(qb._table == "sometable"); 469 assert(qb._type == QueryType.update); 470 471 struct Test {} 472 473 qb.update!Test; 474 assert(qb._type == QueryType.update); 475 assert(qb._table == relationName!Test); 476 } 477 478 ref QueryBuilder set(T)(T[string] params) 479 { 480 foreach (col, val; params) 481 set(col, val); 482 483 return this; 484 } 485 486 ref QueryBuilder set(T)(string col, T val) 487 { 488 assert(_type == QueryType.update, "QueryBuilder.set() can only be used on UPDATE queries"); 489 490 _params[col] = val; 491 _set ~= "%s = {%s}".format(escapeIdentifier(col), col); 492 493 return this; 494 } 495 496 ref QueryBuilder set(string set) 497 { 498 _set ~= set; 499 500 return this; 501 } 502 503 unittest 504 { 505 writeln("\t * set"); 506 507 QueryBuilder qb; 508 qb.update("foo") 509 .set("some_col", 1); 510 511 assert(qb._params["some_col"] == Value(1)); 512 assert(qb._set.length == 1); 513 assert(qb._set[0] == "\"some_col\" = {some_col}"); 514 515 qb.set([ 516 "col1": Value(1), 517 "col2": Value(2)]); 518 519 assert(qb._params.length == 3); 520 assert(qb._set.length == 3); 521 assert(qb._set[1] == "\"col1\" = {col1}"); 522 assert(qb._set[2] == "\"col2\" = {col2}"); 523 524 string str = "asd = $1"; 525 qb.set(str); 526 assert(qb._params.length == 3); 527 assert(qb._set.length == 4); 528 assert(qb._set[3] == str); 529 } 530 531 // INSERT methods 532 ref QueryBuilder insert(string table, string[] cols...) 533 { 534 _table = table; 535 _columns = cols; 536 _type = QueryType.insert; 537 return this; 538 } 539 540 541 ref QueryBuilder insert(string table, Column[] cols...) 542 { 543 import std.array; 544 return insert(table, array(cols.map!(c => c.column))); 545 } 546 547 unittest 548 { 549 writeln("\t * insert"); 550 551 QueryBuilder qb; 552 qb.insert("table", "col1", "col2"); 553 554 assert(qb._type == QueryType.insert); 555 assert(qb._table == "table"); 556 assert(qb._columns == ["col1", "col2"]); 557 558 Column[] cs = [ 559 Column("some_col", "stupid_as_name"), 560 Column("qwe")]; 561 562 qb.insert("table2", cs); 563 assert(qb._table == "table2"); 564 assert(qb._columns.length == 2); 565 assert(qb._columns == ["some_col", "qwe"]); 566 } 567 568 ref QueryBuilder values(T...)(T vals) 569 { 570 assert(_type == QueryType.insert, "QueryBuilder.values() can only be used on INSERT queries"); 571 572 foreach (val; vals) 573 addValue(val); 574 575 return this; 576 } 577 578 ref QueryBuilder values(Value[] vals) 579 { 580 assert(_type == QueryType.insert, "QueryBuilder.values() can only be used on INSERT queries"); 581 582 foreach (val; vals) 583 addValue(val); 584 585 return this; 586 } 587 588 unittest 589 { 590 writeln("\t * values"); 591 592 QueryBuilder qb; 593 qb.insert("table", "col") 594 .values(1, 2, 3); 595 596 assert(qb._type == QueryType.insert); 597 assert(qb._indexParams.length == 3); 598 assert(qb._indexParams == [Value(1), Value(2), Value(3)]); 599 600 qb.values([Value(4), Value(5)]); 601 assert(qb._indexParams.length == 5); 602 assert(qb._indexParams == [Value(1), Value(2), Value(3), Value(4), Value(5)]); 603 } 604 605 ref QueryBuilder remove() 606 { 607 _type = QueryType.delete_; 608 return this; 609 } 610 611 ref QueryBuilder remove(string table) 612 { 613 from(table); 614 return remove(); 615 } 616 617 ref QueryBuilder remove(T)() 618 { 619 return remove(relationName!T); 620 } 621 622 ref QueryBuilder returning(string[] ret...) 623 { 624 foreach (r; ret) 625 _returning ~= r; 626 627 return this; 628 } 629 630 unittest 631 { 632 writeln("\t * remove"); 633 634 struct Test {} 635 QueryBuilder qb; 636 qb.remove!Test; 637 638 assert(qb._type == QueryType.delete_); 639 assert(qb._table == relationName!Test); 640 } 641 642 ref QueryBuilder addValue(T)(T val) 643 { 644 _indexParams ~= Value(val); 645 return this; 646 } 647 648 ref QueryBuilder addValues(T, U)(U val) 649 { 650 import std.traits; 651 import dpq.meta; 652 import dpq.serialisation; 653 654 if (isAnyNull(val)) 655 addValue(null); 656 else 657 { 658 foreach (m; serialisableMembers!(NoNullable!T)) 659 { 660 static if (isPK!(T, m) || hasUDA!(mixin("T." ~ m), IgnoreAttribute)) 661 continue; 662 else 663 addValue(__traits(getMember, val, m)); 664 } 665 } 666 667 return this; 668 } 669 670 // Other stuff 671 672 private string replaceParams(string str) 673 { 674 int index = _paramIndex; 675 676 foreach (param, val; _params) 677 str = str.replace("{" ~ param ~ "}", "$%d".format(++index)); 678 679 return str; 680 } 681 682 unittest 683 { 684 writeln("\t * replaceParams"); 685 QueryBuilder qb; 686 string str = "SELECT {foo} FROM table WHERE id = {bar} AND name = '{baz}'"; 687 qb["foo"] = "a"; 688 qb["bar"] = "b"; 689 690 str = qb.replaceParams(str); 691 692 // No idea what the order might be 693 assert( 694 str == "SELECT $1 FROM table WHERE id = $2 AND name = '{baz}'" || 695 str == "SELECT $2 FROM table WHERE id = $1 AND name = '{baz}'"); 696 } 697 698 private string selectCommand() 699 { 700 string cols; 701 if (_columns.length == 0) 702 cols = "*"; 703 else 704 cols = _columns 705 //.map!(c => escapeIdentifier(c)) 706 .join(", "); 707 708 string table = escapeIdentifier(_table); 709 string str = "SELECT %s FROM %s".format(cols, table); 710 711 if (_filters.length > 0) 712 str ~= " WHERE " ~ _filters.to!string; 713 714 if (_orderBy.length > 0) 715 { 716 str ~= " ORDER BY "; 717 for (int i = 0; i < _orderBy.length; ++i) 718 { 719 if (_orders[i] == Order.none) 720 continue; 721 722 str ~= "\"" ~ _orderBy[i] ~ "\" " ~ _orders[i] ~ ", "; 723 } 724 str = str[0 .. $ - 2]; 725 } 726 727 if (!_limit.isNull) 728 str ~= " LIMIT %d".format(_limit); 729 730 if (!_offset.isNull) 731 str ~= " OFFSET %d".format(_offset); 732 733 return replaceParams(str); 734 } 735 736 unittest 737 { 738 writeln("\t * selectCommand"); 739 740 QueryBuilder qb; 741 qb.select("col") 742 .from("table") 743 .where(["id": 1]) 744 .limit(1) 745 .offset(1); 746 747 string str = qb.command(); 748 assert(str == `SELECT col FROM "table" WHERE ("id" = $1) LIMIT 1 OFFSET 1`, str); 749 } 750 751 private string insertCommand() 752 { 753 int index = 0; 754 755 string params = "("; 756 foreach (i, v; _indexParams) 757 { 758 params ~= "$%d".format(i + 1); 759 if ((i + 1) % _columns.length) 760 params ~= ", "; 761 else if ( (i + 1) < _indexParams.length) 762 params ~= "),("; 763 } 764 params ~= ")"; 765 766 string str = "INSERT INTO \"%s\" (%s) VALUES %s".format( 767 _table, 768 _columns.join(","), 769 params 770 ); 771 772 if (_returning.length > 0) 773 { 774 str ~= " RETURNING "; 775 str ~= _returning.join(", "); 776 } 777 778 return str; 779 } 780 781 unittest 782 { 783 writeln("\t * insertCommand"); 784 785 QueryBuilder qb; 786 qb.insert("table", "col") 787 .values(1, 2) 788 .returning("id"); 789 790 string str = qb.command(); 791 assert(str == `INSERT INTO "table" (col) VALUES ($1),($2) RETURNING id`); 792 } 793 794 private string updateCommand() 795 { 796 string str = "UPDATE \"%s\" SET %s".format( 797 _table, 798 _set.join(", ")); 799 800 if (_filters.length > 0) 801 str ~= " WHERE " ~ _filters.to!string; 802 803 if (_returning.length > 0) 804 { 805 str ~= " RETURNING "; 806 str ~= _returning.join(", "); 807 } 808 809 return replaceParams(str); 810 } 811 812 unittest 813 { 814 writeln("\t * updateCommand"); 815 816 QueryBuilder qb; 817 qb.update("table") 818 .set("col", 1) 819 .where(["foo": 2]) 820 .returning("id"); 821 822 string str = qb.command(); 823 assert( 824 str == `UPDATE "table" SET "col" = $1 WHERE ("foo" = $2) RETURNING id` || 825 str == `UPDATE "table" SET "col" = $2 WHERE ("foo" = $1) RETURNING id`); 826 } 827 828 private string deleteCommand() 829 { 830 string str = "DELETE FROM \"%s\"".format(_table); 831 832 if (_filters.length > 0) 833 str ~= " WHERE " ~ _filters.to!string; 834 835 if (_returning.length > 0) 836 { 837 str ~= " RETURNING "; 838 str ~= _returning.join(", "); 839 } 840 841 return replaceParams(str); 842 } 843 844 unittest 845 { 846 writeln("\t * deleteCommand"); 847 848 QueryBuilder qb; 849 qb.remove("table") 850 .where(["id": 1]) 851 .returning("id"); 852 853 string str = qb.command(); 854 assert(str == `DELETE FROM "table" WHERE ("id" = $1) RETURNING id`, str); 855 } 856 857 @property string command() 858 { 859 final switch (_type) 860 { 861 case QueryType.select: 862 return selectCommand(); 863 case QueryType.update: 864 return updateCommand(); 865 case QueryType.insert: 866 return insertCommand(); 867 case QueryType.delete_: 868 return deleteCommand(); 869 } 870 } 871 872 @property private Value[] paramsArr() 873 { 874 Value[] res = _indexParams; 875 //foreach (param; _indexParams) 876 // res ~= param; 877 878 foreach (param, val; _params) 879 res ~= val; 880 881 return res; 882 } 883 884 unittest 885 { 886 writeln("\t * paramsArr"); 887 888 QueryBuilder qb; 889 qb.addParams("1", "2", "3"); 890 qb["foo"] = 1; 891 qb["bar"] = 2; 892 893 auto ps = qb.paramsArr(); 894 assert( 895 ps == [Value("1"), Value("2"), Value("3"), Value(1), Value(2)] || 896 ps == [Value("1"), Value("2"), Value("3"), Value(2), Value(1)]); 897 } 898 899 void addParam(T)(T val) 900 { 901 _indexParams ~= Value(val); 902 ++_paramIndex; 903 } 904 905 unittest 906 { 907 writeln("\t * addParam"); 908 909 QueryBuilder qb; 910 911 assert(qb._paramIndex == 0); 912 913 qb.addParam(1); 914 assert(qb._paramIndex == 1); 915 assert(qb._indexParams.length == 1); 916 assert(qb._indexParams[0] == Value(1)); 917 918 qb.addParam(2); 919 assert(qb._paramIndex == 2); 920 assert(qb._indexParams.length == 2); 921 assert(qb._indexParams[1] == Value(2)); 922 } 923 924 ref QueryBuilder addParams(T...)(T vals) 925 { 926 foreach (val; vals) 927 addParam(val); 928 929 return this; 930 } 931 932 unittest 933 { 934 writeln("\t * addParams"); 935 936 QueryBuilder qb; 937 qb.addParams(1, 2, 3); 938 939 assert(qb._indexParams.length == 3); 940 assert(qb._paramIndex == 3); 941 } 942 943 ref QueryBuilder opBinary(string op, T)(T val) 944 if (op == "<<") 945 { 946 return addParam(val); 947 } 948 949 950 Query query() 951 { 952 if (_connection != null) 953 return Query(*_connection, command, paramsArr); 954 955 return Query(command, paramsArr); 956 } 957 958 Query query(ref Connection conn) 959 { 960 return Query(conn, command, paramsArr); 961 } 962 }