1 module dpq.querybuilder; 2 3 import dpq.value; 4 import dpq.query; 5 import dpq.connection; 6 import dpq.attributes; 7 import dpq.column; 8 9 import std.typecons; 10 import std.string; 11 12 version (unittest) import std.stdio; 13 14 15 enum Order : string 16 { 17 asc = "ASC", 18 desc = "DESC" 19 }; 20 21 private enum QueryType 22 { 23 select = "SELECT", 24 update = "UPDATE", 25 insert = "INSERT", 26 delete_ = "DELETE" 27 } 28 29 struct QueryBuilder 30 { 31 private 32 { 33 string[] _columns; 34 string _table; 35 string _filter; 36 37 string[] _orderBy; 38 Order[] _orders; 39 40 int _limit = -1; 41 42 Nullable!int _offset; 43 Value[string] _params; 44 Value[] _indexParams; 45 46 // UPDATE's SET 47 string[] _set; 48 49 int _paramIndex = 0; 50 QueryType _type; 51 Connection* _connection; 52 } 53 54 this(ref Connection connection) 55 { 56 _connection = &connection; 57 } 58 59 void opIndexAssign(T)(T val, string key) 60 { 61 _params[key] = val; 62 } 63 64 unittest 65 { 66 writeln(" * QueryBuilder"); 67 writeln("\t * opIndexAssign"); 68 69 QueryBuilder qb; 70 qb["foo"] = 123; 71 qb["bar"] = "456"; 72 73 assert(qb._params["foo"] == Value(123)); 74 assert(qb._params["bar"] == Value("456")); 75 } 76 77 // SELECT methods 78 ref QueryBuilder select(string[] cols...) 79 { 80 _columns = cols; 81 _type = QueryType.select; 82 return this; 83 } 84 85 86 ref QueryBuilder select(Column[] cols...) 87 { 88 _type = QueryType.select; 89 _columns = []; 90 foreach(col; cols) 91 { 92 if (col.column != col.asName) 93 _columns ~= "%s AS %s".format(col.column, col.asName); 94 else 95 _columns ~= col.column; 96 } 97 98 return this; 99 } 100 101 unittest 102 { 103 writeln("\t * select"); 104 105 QueryBuilder qb; 106 qb.select("foo", "bar", "baz"); 107 assert(qb._columns == ["foo", "bar", "baz"]); 108 109 Column[] cs = [Column("foo", "foo_test"), Column("bar")]; 110 qb.select(cs); 111 assert(qb._columns == ["foo AS foo_test", "bar"]); 112 } 113 114 ref QueryBuilder from(string from) 115 { 116 assert( 117 _type == QueryType.select || _type == QueryType.delete_, 118 "QueryBuilder.from() can only be used for SELECT or DELETE queries."); 119 120 _table = from; 121 return this; 122 } 123 124 ref QueryBuilder from(T)() 125 { 126 return from(relationName!T); 127 } 128 129 unittest 130 { 131 writeln("\t\t * from"); 132 QueryBuilder qb; 133 134 qb.from("sometable"); 135 assert(qb._table == "sometable"); 136 137 struct Test {} 138 qb.from!Test; 139 assert(qb._table == "test"); 140 } 141 142 ref QueryBuilder where(string filter) 143 { 144 _filter = filter; 145 return this; 146 } 147 148 ref QueryBuilder where(T)(string col, T val) 149 { 150 _params["__where_filt"] = Value(val); 151 _filter = "%s = {__where_filt}".format(col); 152 153 return this; 154 } 155 156 unittest 157 { 158 writeln("\t\t * where"); 159 160 string str = "a = $1 AND b = $2"; 161 QueryBuilder qb; 162 qb.where(str); 163 assert(qb._filter == str); 164 165 qb.where("some_field", 1); 166 assert(qb._filter == "some_field = {__where_filt}"); 167 assert(qb._params["__where_filt"] == Value(1)); 168 } 169 170 ref QueryBuilder order(string col, Order order) 171 { 172 assert(_type == QueryType.select, "QueryBuilder.order() can only be used for SELECT queries."); 173 _orderBy ~= col; 174 _orders ~= order; 175 return this; 176 } 177 178 unittest 179 { 180 writeln("\t\t * order"); 181 182 QueryBuilder qb; 183 184 qb.order("some_col", Order.asc); 185 186 assert(qb._orderBy[0] == "some_col"); 187 assert(qb._orders[0] == Order.asc); 188 189 qb.order("some_other_col", Order.desc); 190 191 assert(qb._orderBy[1] == "some_other_col"); 192 assert(qb._orders[1] == Order.desc); 193 } 194 195 ref QueryBuilder limit(int limit) 196 { 197 assert(_type == QueryType.select, "QueryBuilder.limit() can only be used for SELECT queries."); 198 199 _limit = limit; 200 return this; 201 } 202 203 unittest 204 { 205 writeln("\t\t * limit"); 206 207 QueryBuilder qb; 208 qb.limit(1); 209 assert(qb._limit == 1); 210 } 211 212 ref QueryBuilder offset(int offset) 213 { 214 assert(_type == QueryType.select, "QueryBuilder.offset() can only be used for SELECT queries."); 215 _offset = offset; 216 return this; 217 } 218 219 unittest 220 { 221 writeln("\t\t * offset"); 222 223 QueryBuilder qb; 224 qb.offset(1); 225 assert(qb._offset == 1); 226 } 227 228 // UPDATE methods 229 ref QueryBuilder update(string table) 230 { 231 _table = table; 232 _type = QueryType.update; 233 return this; 234 } 235 236 ref QueryBuilder update(T)() 237 { 238 return update(relationName!T); 239 } 240 241 unittest 242 { 243 QueryBuilder qb; 244 qb.update("sometable"); 245 246 assert(qb._table == "sometable"); 247 assert(qb._type == QueryType.update); 248 249 struct Test {} 250 251 qb.update!Test; 252 assert(qb._type == QueryType.update); 253 assert(qb._table == relationName!Test); 254 } 255 256 ref QueryBuilder set(Value[string] params) 257 { 258 foreach (col, val; params) 259 set(col, val); 260 261 return this; 262 } 263 264 ref QueryBuilder set(T)(string col, T val) 265 { 266 assert(_type == QueryType.update, "QueryBuilder.set() can only be used on UPDATE queries"); 267 268 _params[col] = val; 269 _set ~= "\"%s\" = {%s}".format(col, col); 270 271 return this; 272 } 273 274 ref QueryBuilder set(string set) 275 { 276 _set ~= set; 277 278 return this; 279 } 280 281 unittest 282 { 283 writeln("\t * set"); 284 285 QueryBuilder qb; 286 qb.update("foo") 287 .set("some_col", 1); 288 289 assert(qb._params["some_col"] == Value(1)); 290 assert(qb._set.length == 1); 291 assert(qb._set[0] == "\"some_col\" = {some_col}"); 292 293 qb.set([ 294 "col1": Value(1), 295 "col2": Value(2)]); 296 297 assert(qb._params.length == 3); 298 assert(qb._set.length == 3); 299 assert(qb._set[1] == "\"col1\" = {col1}"); 300 assert(qb._set[2] == "\"col2\" = {col2}"); 301 302 string str = "asd = $1"; 303 qb.set(str); 304 assert(qb._params.length == 3); 305 assert(qb._set.length == 4); 306 assert(qb._set[3] == str); 307 } 308 309 // INSERT methods 310 ref QueryBuilder insert(string table, string[] cols...) 311 { 312 _table = table; 313 _columns = cols; 314 _type = QueryType.insert; 315 return this; 316 } 317 318 319 ref QueryBuilder insert(string table, Column[] cols...) 320 { 321 import std.array; 322 return insert(table, array(cols.map!(c => c.column))); 323 } 324 325 unittest 326 { 327 writeln("\t * insert"); 328 329 QueryBuilder qb; 330 qb.insert("table", "col1", "col2"); 331 332 assert(qb._type == QueryType.insert); 333 assert(qb._table == "table"); 334 assert(qb._columns == ["col1", "col2"]); 335 336 Column[] cs = [ 337 Column("some_col", "stupid_as_name"), 338 Column("qwe")]; 339 340 qb.insert("table2", cs); 341 assert(qb._table == "table2"); 342 assert(qb._columns.length == 2); 343 assert(qb._columns == ["some_col", "qwe"]); 344 } 345 346 ref QueryBuilder values(T...)(T vals) 347 { 348 assert(_type == QueryType.insert, "QueryBuilder.values() can only be used on INSERT queries"); 349 350 foreach (val; vals) 351 addValue(val); 352 353 return this; 354 } 355 356 ref QueryBuilder values(Value[] vals) 357 { 358 assert(_type == QueryType.insert, "QueryBuilder.values() can only be used on INSERT queries"); 359 360 foreach (val; vals) 361 addValue(val); 362 363 return this; 364 } 365 366 unittest 367 { 368 writeln("\t * values"); 369 370 QueryBuilder qb; 371 qb.insert("table", "col") 372 .values(1, 2, 3); 373 374 assert(qb._type == QueryType.insert); 375 assert(qb._indexParams.length == 3); 376 assert(qb._indexParams == [Value(1), Value(2), Value(3)]); 377 378 qb.values([Value(4), Value(5)]); 379 assert(qb._indexParams.length == 5); 380 assert(qb._indexParams == [Value(1), Value(2), Value(3), Value(4), Value(5)]); 381 } 382 383 ref QueryBuilder remove() 384 { 385 _type = QueryType.delete_; 386 return this; 387 } 388 389 ref QueryBuilder remove(string table) 390 { 391 from(table); 392 return remove(); 393 } 394 395 ref QueryBuilder remove(T)() 396 { 397 return remove(relationName!T); 398 } 399 400 unittest 401 { 402 writeln("\t * remove"); 403 404 struct Test {} 405 QueryBuilder qb; 406 qb.remove!Test; 407 408 assert(qb._type == QueryType.delete_); 409 assert(qb._table == relationName!Test); 410 } 411 412 ref QueryBuilder addValue(T)(T val) 413 { 414 _indexParams ~= Value(val); 415 return this; 416 } 417 418 // Other stuff 419 420 private string replaceParams(string str) 421 { 422 int index = _paramIndex; 423 424 foreach (param, val; _params) 425 str = str.replace("{" ~ param ~ "}", "$%d".format(++index)); 426 427 return str; 428 } 429 430 unittest 431 { 432 writeln("\t * replaceParams"); 433 QueryBuilder qb; 434 string str = "SELECT {foo} FROM table WHERE id = {bar} AND name = '{baz}'"; 435 qb["foo"] = "a"; 436 qb["bar"] = "b"; 437 438 str = qb.replaceParams(str); 439 440 // No idea what the order might be 441 assert( 442 str == "SELECT $1 FROM table WHERE id = $2 AND name = '{baz}'" || 443 str == "SELECT $2 FROM table WHERE id = $1 AND name = '{baz}'"); 444 } 445 446 private string selectCommand() 447 { 448 string cols; 449 if (_columns.length == 0) 450 cols = "*"; 451 else 452 cols = _columns.join(", "); 453 454 string str = "SELECT %s FROM \"%s\"".format(cols, _table); 455 456 if (_filter.length > 0) 457 str ~= " WHERE " ~ _filter; 458 459 if (_orderBy.length > 0) 460 { 461 str ~= " ORDER BY "; 462 for (int i = 0; i < _orderBy.length; ++i) 463 str ~= "\"" ~ _orderBy[i] ~ "\" " ~ _orders[i] ~ ", "; 464 str = str[0 .. $ - 2]; 465 } 466 467 if (_limit != -1) 468 str ~= " LIMIT %d".format(_limit); 469 470 if (!_offset.isNull) 471 str ~= " OFFSET %d".format(_offset); 472 473 return replaceParams(str); 474 } 475 476 unittest 477 { 478 writeln("\t * selectCommand"); 479 480 QueryBuilder qb; 481 qb.select("col") 482 .from("table") 483 .where("id", 1) 484 .limit(1) 485 .offset(1); 486 487 string str = qb.command(); 488 assert(str == `SELECT col FROM "table" WHERE id = $1 LIMIT 1 OFFSET 1`, str); 489 } 490 491 private string insertCommand() 492 { 493 int index = 0; 494 string str = "INSERT INTO \"%s\" (%s) VALUES (%s)".format( 495 _table, 496 _columns.join(","), 497 _indexParams.map!(v => "$%d".format(++index)).join(", ") 498 ); 499 500 return str; 501 } 502 503 unittest 504 { 505 writeln("\t * insertCommand"); 506 507 QueryBuilder qb; 508 qb.insert("table", "col") 509 .values(1); 510 511 string str = qb.command(); 512 assert(str == `INSERT INTO "table" (col) VALUES ($1)`); 513 } 514 515 private string updateCommand() 516 { 517 string str = "UPDATE \"%s\" SET %s".format( 518 _table, 519 _set.join(", ")); 520 521 if (_filter.length > 0) 522 str ~= " WHERE " ~ _filter; 523 524 return replaceParams(str); 525 } 526 527 unittest 528 { 529 writeln("\t * updateCommand"); 530 531 QueryBuilder qb; 532 qb.update("table") 533 .set("col", 1) 534 .where("foo", 2); 535 536 string str = qb.command(); 537 assert( 538 str == `UPDATE "table" SET "col" = $1 WHERE foo = $2` || 539 str == `UPDATE "table" SET "col" = $2 WHERE foo = $1`); 540 } 541 542 private string deleteCommand() 543 { 544 string str = "DELETE FROM \"%s\"".format(_table); 545 546 if (_filter.length > 0) 547 str ~= " WHERE " ~ _filter; 548 549 return replaceParams(str); 550 } 551 552 unittest 553 { 554 writeln("\t * deleteCommand"); 555 556 QueryBuilder qb; 557 qb.remove("table") 558 .where("id", 1); 559 560 string str = qb.command(); 561 assert(str == `DELETE FROM "table" WHERE id = $1`); 562 } 563 564 @property string command() 565 { 566 final switch (_type) 567 { 568 case QueryType.select: 569 return selectCommand(); 570 case QueryType.update: 571 return updateCommand(); 572 case QueryType.insert: 573 return insertCommand(); 574 case QueryType.delete_: 575 return deleteCommand(); 576 } 577 } 578 579 @property private Value[] paramsArr() 580 { 581 Value[] res = _indexParams; 582 //foreach (param; _indexParams) 583 // res ~= param; 584 585 foreach (param, val; _params) 586 res ~= val; 587 588 return res; 589 } 590 591 unittest 592 { 593 writeln("\t * paramsArr"); 594 595 QueryBuilder qb; 596 qb.addParams("1", "2", "3"); 597 qb["foo"] = 1; 598 qb["bar"] = 2; 599 600 auto ps = qb.paramsArr(); 601 assert( 602 ps == [Value("1"), Value("2"), Value("3"), Value(1), Value(2)] || 603 ps == [Value("1"), Value("2"), Value("3"), Value(2), Value(1)]); 604 } 605 606 void addParam(T)(T val) 607 { 608 _indexParams ~= Value(val); 609 ++_paramIndex; 610 } 611 612 unittest 613 { 614 writeln("\t * addParam"); 615 616 QueryBuilder qb; 617 618 assert(qb._paramIndex == 0); 619 620 qb.addParam(1); 621 assert(qb._paramIndex == 1); 622 assert(qb._indexParams.length == 1); 623 assert(qb._indexParams[0] == Value(1)); 624 625 qb.addParam(2); 626 assert(qb._paramIndex == 2); 627 assert(qb._indexParams.length == 2); 628 assert(qb._indexParams[1] == Value(2)); 629 } 630 631 ref QueryBuilder addParams(T...)(T vals) 632 { 633 foreach (val; vals) 634 addParam(val); 635 636 return this; 637 } 638 639 unittest 640 { 641 writeln("\t * addParams"); 642 643 QueryBuilder qb; 644 qb.addParams(1, 2, 3); 645 646 assert(qb._indexParams.length == 3); 647 assert(qb._paramIndex == 3); 648 } 649 650 ref QueryBuilder opBinary(string op, T)(T val) 651 if (op == "<<") 652 { 653 return addParam(val); 654 } 655 656 657 Query query() 658 { 659 if (_connection != null) 660 return Query(*_connection, command, paramsArr); 661 662 return Query(command, paramsArr); 663 } 664 665 Query query(ref Connection conn) 666 { 667 return Query(conn, command, paramsArr); 668 } 669 670 } 671 672 void param(alias P)(QueryBuilder b) 673 { 674 b[P.stringof] = P; 675 }