1 /// 2 module dpq.connection; 3 4 import libpq.libpq; 5 6 import dpq.exception; 7 import dpq.result; 8 import dpq.value; 9 import dpq.attributes; 10 import dpq.querybuilder; 11 import dpq.meta; 12 import dpq.prepared; 13 import dpq.smartptr; 14 import dpq.serialisation; 15 16 import dpq.serialisers.array; 17 import dpq.serialisers.composite; 18 19 import std..string; 20 import libpq.libpq; 21 import std.conv : to; 22 import std.traits; 23 import std.typecons; 24 25 version (unittest) 26 { 27 import std.stdio; 28 29 Connection c; 30 } 31 32 /** 33 Represents the PostgreSQL connection and allows executing queries on it. 34 35 Examples: 36 ------------- 37 auto conn = Connection("host=localhost dbname=testdb user=testuser"); 38 //conn.exec ... 39 ------------- 40 */ 41 struct Connection 42 { 43 private alias ConnectionPtr = SmartPointer!(PGconn*, PQfinish); 44 45 private ConnectionPtr _connection; 46 private PreparedStatement[string] _prepared; 47 48 /** 49 Connection constructor 50 51 Params: 52 connString = connection string 53 54 See Also: 55 http://www.postgresql.org/docs/9.3/static/libpq-connect.html#LIBPQ-CONNSTRING 56 */ 57 this(string connString) 58 { 59 char* err; 60 auto opts = PQconninfoParse(cast(char*)connString.toStringz, &err); 61 62 if (err != null) 63 throw new DPQException(err.fromStringz.to!string); 64 65 _connection = new ConnectionPtr(PQconnectdb(connString.toStringz)); 66 67 if (status != CONNECTION_OK) 68 throw new DPQException(errorMessage); 69 } 70 71 unittest 72 { 73 writeln(" * Try to connect host=127.0.0.1 dbname=test user=test"); 74 c = Connection("host=127.0.0.1 dbname=test user=test"); 75 writeln(" * Database connection with connection string"); 76 assert(c.status == CONNECTION_OK); 77 } 78 79 /** 80 Close the connection manually 81 */ 82 void close() 83 { 84 _connection.clear(); 85 } 86 87 @property const(ConnStatusType) status() 88 { 89 return PQstatus(_connection); 90 } 91 92 /** Returns the name of the database currently selected */ 93 @property const(string) db() 94 { 95 return PQdb(_connection).to!string; 96 } 97 98 /** Returns the name of the current user */ 99 @property const(string) user() 100 { 101 return PQuser(_connection).to!string; 102 } 103 104 /// ditto, but password 105 @property const(string) password() 106 { 107 return PQpass(_connection).to!string; 108 } 109 110 /// ditto, but host 111 @property const(string) host() 112 { 113 return PQhost(_connection).to!string; 114 } 115 116 /// ditto, but port 117 @property const(ushort) port() 118 { 119 return PQport(_connection).fromStringz.to!ushort; 120 } 121 122 /** 123 Executes the given string directly 124 125 Throws on fatal query errors like bad syntax. 126 WARNING: Only returns textual values!!! 127 128 Examples: 129 ---------------- 130 Connection conn; // An established connection 131 132 conn.exec("CREATE TABLE IF NOT EXISTS test_table"); 133 ---------------- 134 */ 135 Result exec(string command) 136 { 137 PGresult* res = PQexec(_connection, cast(const char*)command.toStringz); 138 return Result(res); 139 } 140 141 unittest 142 { 143 auto res = c.exec("SELECT 1::INT4 AS int4, 2::INT8 AS some_long"); 144 writeln(" * exec for selecting INT4 and INT8"); 145 assert(res.rows == 1); 146 assert(res.columns == 2); 147 148 auto r = res[0]; 149 assert(r[0].as!string == "1"); 150 assert(r[1].as!string == "2"); 151 152 writeln(" * Row opIndex(int) and opIndex(string) equality "); 153 assert(r[0] == r["int4"]); 154 assert(r[1] == r["some_long"]); 155 156 } 157 158 /// ditto, async 159 bool send(string command) 160 { 161 return PQsendQuery(_connection, cast(const char*)command.toStringz) == 1; 162 } 163 164 /** 165 Executes the given string with given params 166 167 Params should be given as $1, $2, ... $n in the actual command. 168 All params are sent in a binary format and should not be escaped. 169 If a param's type cannot be inferred, this method will throw an exception, 170 in this case, either specify the type using the :: (cast) notation or 171 make sure the type can be inferred by PostgreSQL in your query. 172 173 Examples: 174 ---------------- 175 Connection conn; // An established connection 176 177 conn.execParams("SELECT $1::string, $2::int, $3::double"); 178 ---------------- 179 180 See also: 181 http://www.postgresql.org/docs/9.3/static/libpq-exec.html 182 */ 183 Result execParams(T...)(string command, T params) 184 { 185 Value[] values; 186 foreach (param; params) 187 values ~= Value(param); 188 189 return execParams(command, values); 190 } 191 192 /// ditty, but async 193 void sendParams(T...)(string command, T params) 194 { 195 Value[] values; 196 foreach (param; params) 197 values ~= Value(param); 198 199 execParams(command, values, true); 200 } 201 202 /// ditto, but taking an array of params, instead of variadic template 203 Result execParams(string command, Value[] params, bool async = false) 204 { 205 const char* cStr = cast(const char*)command.toStringz; 206 207 auto pTypes = params.paramTypes; 208 auto pValues = params.paramValues; 209 auto pLengths = params.paramLengths; 210 auto pFormats = params.paramFormats; 211 212 if (async) 213 { 214 PQsendQueryParams(_connection, cStr, params.length.to!int, pTypes.ptr, cast(const(char*)*)pValues.ptr, 215 pLengths.ptr, pFormats.ptr, 1); 216 217 return Result(null); 218 } 219 else 220 return Result(PQexecParams(_connection, cStr, params.length.to!int, pTypes.ptr, 221 cast(const(char*)*)pValues.ptr, pLengths.ptr, pFormats.ptr, 1)); 222 } 223 224 /// ditto, async 225 void sendParams(string command, Value[] params) 226 { 227 execParams(command, params, true); 228 } 229 230 unittest 231 { 232 writeln("\t * execParams"); 233 writeln("\t\t * Rows and cols"); 234 235 // we're not testing value here, specify types in the query to avoid any oid issues 236 auto res = c.execParams("SELECT 1::INT4 AS int4, 2::INT8 AS some_long", []); 237 assert(res.rows == 1); 238 assert(res.columns == 2); 239 240 writeln("\t\t * Static values"); 241 auto r = res[0]; 242 assert(r[0].as!int == 1); 243 assert(r[1].as!long == 2); 244 245 writeln("\t\t * opIndex(int) and opIndex(string) equality"); 246 assert(r[0] == r["int4"]); 247 assert(r[1] == r["some_long"]); 248 249 int int4 = 1; 250 long int8 = 2; 251 string str = "foo bar baz"; 252 float float4 = 3.14; 253 double float8 = 3.1415; 254 255 writeln("\t\t * Passed values"); 256 res = c.execParams("SELECT $1::INT4, $2::INT8, $3::TEXT, $4::FLOAT4, $5::FLOAT8", int4, int8, str, float4, float8); 257 258 assert(res.rows == 1); 259 r = res[0]; 260 261 // This should probably be tested by the serialisers, not here. 262 assert(r[0].as!int == int4); 263 assert(r[1].as!long == int8); 264 assert(r[2].as!string == str); 265 assert(r[3].as!float == float4); 266 assert(r[4].as!double == float8); 267 } 268 269 /** 270 Returns the last error message 271 272 Examples: 273 -------------------- 274 Connection conn; // An established connection 275 276 writeln(conn.errorMessage); 277 -------------------- 278 */ 279 @property string errorMessage() 280 { 281 return PQerrorMessage(_connection).to!string; 282 } 283 284 unittest 285 { 286 writeln("\t * errorMessage"); 287 try 288 { 289 c.execParams("SELECT_BADSYNTAX $1::INT4", 1); 290 } 291 catch (Throwable) 292 { 293 } 294 295 assert(c.errorMessage.length != 0); 296 } 297 298 /** 299 Escapes a string, to be used in a query 300 */ 301 string escapeLiteral(string str) 302 { 303 const(char)* cStr = str.toStringz; 304 auto esc = PQescapeLiteral(_connection, cStr, str.length); 305 306 if (esc == null) 307 throw new DPQException("escapeLiteral failed: " ~ this.errorMessage); 308 309 str = esc.fromStringz.dup; // make a copy, escaped data must be freed 310 PQfreemem(esc); 311 return str; 312 } 313 314 /** 315 Escapes an identifier (column, function, table name, ...) to be used in a query. 316 */ 317 string escapeIdentifier(string str) 318 { 319 const(char)* cStr = str.toStringz; 320 auto esc = PQescapeIdentifier(_connection, cStr, str.length); 321 322 if (esc == null) 323 throw new DPQException("escapeIdentifier failed: " ~ this.errorMessage); 324 325 str = esc.fromStringz.dup; // make a copy, escaped data must be freed 326 PQfreemem(esc); 327 return str; 328 } 329 330 /** 331 Will create the relation and return the queries (foreign key and index creation) 332 that still need to be ran, usually after ALL the relations are created. 333 */ 334 private string[] createRelation(T)() 335 { 336 alias members = serialisableMembers!T; 337 338 string relName = SerialiserFor!T.nameForType!T; 339 string escRelName = escapeIdentifier(relName); 340 341 // A list of columns in the table 342 string[] columns; 343 // Queries that must be ran after the table is created 344 string[] additionalQueries; 345 346 foreach (mName; members) 347 { 348 // Is there a better way to do this? 349 enum member = "T." ~ mName; 350 351 // If the member is a property, typeof will fail with the wront context error 352 // so we construct it and then check the type 353 static if (is(FunctionTypeOf!(mixin(member)) == function)) 354 alias MType = RealType!(typeof(mixin("T()." ~ mName))); 355 else 356 alias MType = RealType!(typeof(mixin(member))); 357 358 alias serialiser = SerialiserFor!MType; 359 360 // The attribute's name 361 string attrName = attributeName!(mixin(member)); 362 string escAttrName = escapeIdentifier(attrName); 363 364 // And the type, the user-specified type overwrites anything else 365 static if (hasUDA!(mixin(member), PGTypeAttribute)) 366 string attrType = getUDAs!(mixin(member), PGTypeAttribute)[0].type; 367 else 368 string attrType = serialiser.nameForType!MType; 369 370 string attr = escAttrName ~ " " ~ attrType; 371 372 // A type must be created before using it. 373 serialiser.ensureExistence!MType(this); 374 375 // Take care of primary keys 376 static if (hasUDA!(mixin(member), PrimaryKeyAttribute)) 377 attr ~= " PRIMARY KEY"; 378 // And foreign key 379 else static if (hasUDA!(mixin(member), ForeignKeyAttribute)) 380 { 381 enum uda = getUDAs!(mixin(member), ForeignKeyAttribute)[0]; 382 383 // Create the FK 384 additionalQueries ~= `ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY(%s) REFERENCES %s (%s)`.format(escRelName, 385 escapeIdentifier("%s_%s_fk_%s".format(relName, attrName, uda.relation)), escAttrName, 386 escapeIdentifier(uda.relation), escapeIdentifier(uda.pkey)); 387 388 // Also create an index on the foreign key 389 additionalQueries ~= "CREATE INDEX %s ON %s (%s)".format(escapeIdentifier("%s_%s_fk_index".format(relName, 390 attrName)), escRelName, escAttrName); 391 } 392 else static if (hasUDA!(mixin(member), IndexAttribute)) 393 { 394 enum uda = getUDAs!(mixin(member), IndexAttribute)[0]; 395 396 additionalQueries ~= "CREATE%sINDEX %s ON %s (%s)".format(uda.unique ? " UNIQUE " : " ", 397 escapeIdentifier("%s_%s_fk_index".format(relName, attrName)), escRelName, escAttrName); 398 } 399 400 // Custom suffixes 401 static if (hasUDA!(mixin(member), ColumnSuffixAttribute)) 402 { 403 enum suffix = getUDAs!(mixin(member), ColumnSuffixAttribute)[0].suffix; 404 attr ~= " " ~ suffix; 405 } 406 407 columns ~= attr; 408 } 409 410 // Create the table 411 exec("CREATE TABLE IF NOT EXISTS %s (%s)".format(escRelName, columns.join(", "))); 412 413 addOidsFor(relName); 414 415 return additionalQueries; 416 } 417 418 package void addOidsFor(string typeName) 419 { 420 auto r = execParams("SELECT $1::regtype::oid, $2::regtype::oid", typeName, typeName ~ "[]"); 421 Oid typeOid = r[0][0].as!(int).get; 422 Oid arrOid = r[0][1].as!(int).get; 423 424 CompositeTypeSerialiser.addCustomOid(typeName, typeOid); 425 ArraySerialiser.addCustomOid(typeOid, arrOid); 426 } 427 428 /** 429 Generates and runs the DDL from the given structures 430 431 Attributes from dpq.attributes should be used to define 432 primary keys, indexes, and relationships. 433 434 A custom type can be specified with the @type attribute. 435 436 Examples: 437 ----------------------- 438 Connection conn; // An established connection 439 struct User 440 { 441 @serial8 @PKey long id; 442 string username; 443 byte[] passwordHash; 444 }; 445 446 struct Article { ... }; 447 448 conn.ensureSchema!(User, Article); 449 ----------------------- 450 */ 451 void ensureSchema(T...)(bool createType = false) 452 { 453 import std.stdio; 454 455 string[] additional; 456 457 foreach (Type; T) 458 additional ~= createRelation!Type; 459 460 foreach (cmd; additional) 461 try 462 { 463 exec(cmd); 464 } 465 catch 466 { 467 } // Horrible, I know, but this just means the constraint/index already exists 468 } 469 470 unittest 471 { 472 // Probably needs more thorough testing, let's assume right now 473 // everything is correct if the creating was successful. 474 475 writeln("\t * ensureSchema"); 476 struct Inner 477 { 478 string innerStr; 479 int innerInt; 480 } 481 482 struct TestTable1 483 { 484 @serial8 @PK long id; 485 string str; 486 int n; 487 Inner inner; 488 } 489 490 c.ensureSchema!TestTable1; 491 492 auto res = c.execParams("SELECT COUNT(*) FROM pg_catalog.pg_tables WHERE tablename = $1", relationName!TestTable1); 493 494 assert(res.rows == 1); 495 assert(res[0][0].as!long == 1); 496 497 c.exec("DROP TABLE " ~ relationName!TestTable1); 498 c.exec("DROP TYPE \"" ~ relationName!Inner ~ "\" CASCADE"); 499 } 500 501 /** 502 Returns the requested structure or a Nullable null value if no rows are returned 503 504 This method queries for the given structure by its primary key. If no 505 primary key can be found, a compile-time error will be generated. 506 507 Examples: 508 ---------------------- 509 Connection conn; // An established connection 510 struct User 511 { 512 @serial @PKey int id; 513 ... 514 }; 515 516 auto user = conn.findOne!User(1); // will search by the id attribute 517 ---------------------- 518 */ 519 Nullable!T findOne(T, U)(U id) 520 { 521 return findOneBy!T(primaryKeyAttributeName!T, id); 522 } 523 524 unittest 525 { 526 writeln("\t * findOne(T)(U id), findOneBy, findOne"); 527 struct Testy 528 { 529 @serial @PK int id; 530 string foo; 531 int bar; 532 long baz; 533 int[] intArr; 534 } 535 536 c.ensureSchema!Testy; 537 538 writeln("\t\t * Null result"); 539 auto shouldBeNull = c.findOne!Testy(0); 540 assert(shouldBeNull.isNull); 541 542 c.exec("INSERT INTO %s (id, foo, bar, baz, %s) VALUES (1, 'somestr', 2, 3, '{1,2,3}')".format(relationName!Testy, 543 attributeName!(Testy.intArr))); 544 545 writeln("\t\t * Valid result"); 546 Testy t = c.findOne!Testy(1).get; 547 assert(t.id == 1, `t.id == 1`); 548 assert(t.foo == "somestr", `t.foo == "somestr"`); 549 assert(t.bar == 2, `t.bar == 2`); 550 assert(t.baz == 3, `t.baz == 3`); 551 assert(t.intArr == [1, 2, 3], `t.intArr == [1,2,3]`); 552 553 writeln("\t\t * findOne with custom filter"); 554 Testy t2 = c.findOne!Testy("id = $1", 1).get; 555 assert(t == t2); 556 557 c.exec("DROP TABLE " ~ relationName!Testy); 558 } 559 560 /** 561 Returns the requested structure, searches by the given column name 562 with the given value 563 If not rows are returned, a Nullable null value is returned 564 565 Examples: 566 ---------------------- 567 Connection conn; // An established connection 568 struct User 569 { 570 @serial @PKey int id; 571 ... 572 }; 573 574 auto user = conn.findOneBy!User("id", 1); // will search by "id" 575 ---------------------- 576 */ 577 Nullable!T findOneBy(T, U)(string col, U val) 578 { 579 import std.stdio; 580 581 auto members = AttributeList!T; 582 583 QueryBuilder qb; 584 qb.select(members).from(relationName!T).where([col: val]).limit(1); 585 586 auto q = qb.query(this); 587 588 auto r = q.run(); 589 if (r.rows == 0) 590 return Nullable!T.init; 591 592 //return T(); 593 594 auto res = deserialise!T(r[0]); 595 return Nullable!T(res); 596 } 597 598 /** 599 Returns the requested structure, searches by the specified filter 600 with given params 601 602 The filter is not further escaped, so programmer needs to make sure 603 not to properly escape or enclose reserved keywords (like user -> "user") 604 so PostgreSQL can understand them. 605 606 If not rows are returned, a Nullable null value is returned 607 608 Examples: 609 ---------------------- 610 Connection conn; // An established connection 611 struct User 612 { 613 @serial @PKey int id; 614 string username; 615 int posts; 616 }; 617 618 auto user = conn.findOne!User("username = $1 OR posts > $2", "foo", 42); 619 if (!user.isNull) 620 { 621 ... // do something 622 } 623 ---------------------- 624 */ 625 Nullable!T findOne(T, U...)(string filter, U vals) 626 { 627 QueryBuilder qb; 628 qb.select(AttributeList!T).from(relationName!T).where(filter).limit(1); 629 630 auto q = qb.query(this); 631 auto r = q.run(vals); 632 633 if (r.rows == 0) 634 return Nullable!T.init; 635 636 auto res = deserialise!T(r[0]); 637 return Nullable!T(res); 638 } 639 640 /** 641 Returns an array of the specified type, filtered with the given filter and 642 params 643 644 If no rows are returned by PostgreSQL, an empty array is returned. 645 646 Examples: 647 ---------------------- 648 Connection conn; // An established connection 649 struct User 650 { 651 @serial @PKey int id; 652 string username; 653 int posts; 654 }; 655 656 auto users = conn.find!User("username = $1 OR posts > $2", "foo", 42); 657 foreach (u; users) 658 { 659 ... // do something 660 } 661 ---------------------- 662 */ 663 T[] find(T, U...)(string filter = "", U vals = U.init) 664 { 665 QueryBuilder qb; 666 qb.select(AttributeList!T).from(relationName!T).where(filter); 667 668 auto q = qb.query(this); 669 670 T[] res; 671 foreach (r; q.run(vals)) 672 res ~= deserialise!T(r); 673 674 return res; 675 } 676 677 /** 678 Returns an array of the specified type, filtered with the given filter and 679 params with length limited by limit 680 681 If no rows are returned by PostgreSQL, an empty array is returned. 682 If limit is set to -1 then all filtered rows are returned. 683 684 Examples: 685 ---------------------- 686 Connection conn; // An established connection 687 struct User 688 { 689 @serial @PKey int id; 690 string username; 691 int posts; 692 }; 693 694 auto users = conn.findL!User("username = $1 OR posts > $2", 5, "foo", 42); 695 foreach (u; users) 696 { 697 ... // do something 698 } 699 ---------------------- 700 */ 701 T[] findL(T, U...)(string filter, int limit, U vals) 702 { 703 QueryBuilder qb; 704 qb.select(AttributeList!T).from(relationName!T).where(filter).limit(limit); 705 706 auto q = qb.query(this); 707 708 T[] res; 709 foreach (r; q.run(vals)) 710 res ~= deserialise!T(r); 711 712 return res; 713 } 714 715 unittest 716 { 717 writeln("\t * find, findL"); 718 719 @relation("find_test") 720 struct Test 721 { 722 @serial @PK int id; 723 @attr("my_n") int n; 724 } 725 726 c.ensureSchema!Test; 727 728 Test t; 729 t.n = 1; 730 731 c.insert(t); 732 c.insert(t); 733 ++t.n; 734 c.insert(t); 735 c.insert(t); 736 c.insert(t); 737 738 Test[] ts = c.find!Test("my_n = $1", 1); 739 assert(ts.length == 2); 740 ts = c.find!Test("my_n > 0"); 741 assert(ts.length == 5); 742 ts = c.find!Test("false"); 743 assert(ts.length == 0); 744 ts = c.findL!Test("0 = 0", -1); 745 assert(ts.length == 5); 746 ts = c.findL!Test("my_n = $1", -1, 2); 747 assert(ts.length == 3); 748 ts = c.findL!Test("my_n = $1", 2, 2); 749 assert(ts.length == 2); 750 751 c.exec("DROP TABLE find_test"); 752 } 753 754 /** 755 Updates records filtered by the filter string, setting the values 756 as specified in the update string. Both should be SQL-syntax 757 758 Useful when updating just a single or a bunch of values in the table, or 759 when setting the values relatively to their current value. 760 761 Params: 762 filter = the SQL filter string 763 update = the SQL update string 764 vals = values to be used in the query 765 766 Examples: 767 ---------------- 768 Connection c; // an established connection 769 struct User { int id; int posts ...} 770 c.update!User("id = $1", "posts = posts + $2", 123, 1); 771 ---------------- 772 */ 773 int update(T, U...)(string filter, string update, U vals) 774 { 775 QueryBuilder qb; 776 qb.update(relationName!T).set(update).where(filter); 777 778 auto r = qb.query(this).run(vals); 779 return r.rows; 780 } 781 782 unittest 783 { 784 writeln("\t * update"); 785 786 @relation("update_test") 787 struct Test 788 { 789 @serial @PK int id; 790 int n; 791 } 792 793 c.ensureSchema!Test; 794 795 Test t; 796 t.n = 5; 797 c.insert(t); 798 799 int nUpdates = c.update!Test("n = $1", "n = $2", 5, 123); 800 assert(nUpdates == 1, `nUpdates == 1`); 801 802 t = c.findOneBy!Test("n", 123).get; 803 assert(t.n == 123, `t.n == 123`); 804 805 writeln("\t\t * async"); 806 c.updateAsync!Test("n = $1", "n = $2", 123, 6); 807 auto r = c.nextResult(); 808 809 assert(r.rows == 1); 810 assert(!c.findOneBy!Test("n", 6).isNull); 811 812 c.exec("DROP TABLE update_test"); 813 } 814 815 /// ditto, async 816 void updateAsync(T, U...)(string filter, string update, U vals) 817 { 818 QueryBuilder qb; 819 qb.update(relationName!T).set(update).where(filter); 820 821 qb.query(this).runAsync(vals); 822 } 823 824 /** 825 Similar to above update, but instead of acceptign a filter and and update string, 826 always filters by the PK and updates with absolute values from the updates AA. 827 828 Params: 829 id = the value of the relation's PK to filter by 830 updates = an AA, mapping column name to the new value to be set for the column 831 async = optionally send this query async 832 833 Examples: 834 ------------------ 835 Connection c; // en established connection 836 struct User { @PK int id; int x; string y } 837 c.update!User(1, [ 838 "x": Value(2), 839 "y": Value("Hello there")]); 840 ------------------ 841 */ 842 int update(T, U)(U id, Value[string] updates, bool async = false) 843 { 844 QueryBuilder qb; 845 846 qb.update(relationName!T).set(updates).where([primaryKeyAttributeName!T: id]); 847 848 auto q = qb.query(this); 849 850 if (async) 851 { 852 q.runAsync(); 853 return -1; 854 } 855 856 auto r = q.run(); 857 return r.rows; 858 } 859 860 unittest 861 { 862 writeln("\t * update with AA updates"); 863 864 @relation("update_aa_test") 865 struct Test 866 { 867 @serial @PK int id; 868 int n; 869 } 870 871 c.ensureSchema!Test; 872 873 Test t; 874 t.n = 1; 875 c.insert(t); 876 877 int rows = c.update!Test(1, ["n": Value(2)]); 878 assert(rows == 1, `r.rows == 1`); 879 880 c.exec("DROP TABLE update_aa_test"); 881 } 882 883 // ditto, but async 884 void updateAsync(T, U)(U id, Value[string] updates) 885 { 886 update!T(id, updates, true); 887 } 888 889 /** 890 Similar to above, but accepts the whole structure as an update param. 891 Filters by the PK, updates ALL the values in the filtered rows. 892 893 Params: 894 id = value of the relation's PK to filter by 895 updates = the structure that will provide values for the UPDATE 896 asnyc = whether the query should be sent async 897 898 Examples: 899 ------------------ 900 Connection c; // an established connection 901 struct User { @PK int id; int a; int b; } 902 c.update(1, myUser); 903 ------------------ 904 */ 905 int update(T, U)(U id, T updates, bool async = false) 906 { 907 import dpq.attributes; 908 909 QueryBuilder qb; 910 911 qb.update(relationName!T).where([primaryKeyAttributeName!T: id]); 912 913 foreach (m; serialisableMembers!T) 914 qb.set(attributeName!(mixin("T." ~ m)), __traits(getMember, updates, m)); 915 916 auto q = qb.query(this); 917 if (async) 918 { 919 qb.query(this).runAsync(); 920 return -1; 921 } 922 923 auto r = q.run(); 924 return r.rows; 925 } 926 927 // ditto, async 928 void updateAsync(T, U)(U id, T updates) 929 { 930 update!T(id, updates, true); 931 } 932 933 unittest 934 { 935 writeln("\t * update with object"); 936 937 @relation("update_object_test") 938 struct Test 939 { 940 @serial @PK int id; 941 int n; 942 } 943 944 c.ensureSchema!Test; 945 946 Test t; 947 t.n = 1; 948 t.id = 1; // assumptions <3 949 950 c.insert(t); 951 952 t.n = 2; 953 c.update!Test(1, t); 954 955 t = c.findOne!Test(1).get; 956 assert(t.n == 2); 957 958 t.n = 3; 959 c.updateAsync!Test(1, t); 960 auto r = c.nextResult(); 961 962 writeln("\t\t * async"); 963 assert(r.rows == 1); 964 965 c.exec("DROP TABLE update_object_test"); 966 } 967 968 /** 969 Inserts the given structure, returning whatever columns are specified by the 970 second param as a normal Result. 971 972 Equivalent to specifying RETURNING at the end of the query. 973 974 Examples: 975 ------------------- 976 Connection c; // an established connection 977 struct Data { @PK int id, int a; int b; } 978 Data myData; 979 auto result = c.insert(myData, "id"); 980 ------------------- 981 */ 982 Result insertR(T)(T val, string ret = "") if (!isArray!T) 983 { 984 QueryBuilder qb; 985 qb.insert(relationName!T, AttributeList!(T, true, true)); 986 if (ret.length > 0) 987 qb.returning(ret); 988 989 qb.addValues!T(val); 990 991 return qb.query(this).run(); 992 } 993 994 /** 995 Inserts the given array structures in a singl query, returning whatever columns are specified by the 996 second param as a normal Result. 997 998 Equivalent to specifying RETURNING at the end of the query. 999 1000 Examples: 1001 ------------------- 1002 Connection c; // an established connection 1003 struct Data { @PK int id, int a; int b; } 1004 Data[2] myData; 1005 auto result = c.insert(myData, "id"); 1006 ------------------- 1007 */ 1008 Result insertR(T)(T vals, string ret = "") if (isArray!T) 1009 { 1010 alias BT = BaseType!T; 1011 1012 if (!vals.length) 1013 return Result.init; 1014 1015 QueryBuilder qb; 1016 qb.insert(relationName!BT, AttributeList!(BT, true, true)); 1017 if (ret.length > 0) 1018 qb.returning(ret); 1019 1020 foreach (val; vals) 1021 qb.addValues!BT(val); 1022 1023 return qb.query(this).run(); 1024 } 1025 1026 /** 1027 Inserts the given structure to the DB 1028 1029 Examples: 1030 --------------- 1031 Connection c; // An established connection 1032 struct User {@PK @serial int id; int a } 1033 User myUser; 1034 c.insert(myUser); 1035 --------------- 1036 */ 1037 bool insert(T)(T val, bool async = false) if (!isArray!T) 1038 { 1039 1040 QueryBuilder qb; 1041 qb.insert(relationName!T, AttributeList!(T, true, true)); 1042 qb.addValues!T(val); 1043 1044 if (async) 1045 return qb.query(this).runAsync(); 1046 1047 auto r = qb.query(this).run(); 1048 return r.rows > 0; 1049 } 1050 1051 /** 1052 Inserts the given array of structures to the DB as one query 1053 1054 Examples: 1055 --------------- 1056 Connection c; // An established connection 1057 struct User {@PK @serial int id; int a } 1058 User[2] myUsers; 1059 c.insert(myUsers); 1060 --------------- 1061 */ 1062 int insert(T)(T vals, bool async = false) if (isArray!T) 1063 { 1064 alias BT = BaseType!T; 1065 1066 QueryBuilder qb; 1067 qb.insert(relationName!BT, AttributeList!(BT, true, true)); 1068 1069 if (!vals.length) 1070 return 0; 1071 1072 foreach (val; vals) 1073 qb.addValues!BT(val); 1074 1075 if (async) 1076 return qb.query(this).runAsync(); 1077 1078 auto r = qb.query(this).run(); 1079 return r.rows; 1080 } 1081 1082 unittest 1083 { 1084 writeln("\t * insert"); 1085 1086 @relation("insert_test_inner") 1087 struct Inner 1088 { 1089 int bar; 1090 } 1091 1092 @relation("insert_test") 1093 struct Test 1094 { 1095 int n; 1096 Nullable!int n2; 1097 Inner foo; 1098 } 1099 1100 c.ensureSchema!Test; 1101 1102 Test t; 1103 t.n = 1; 1104 t.n2 = 2; 1105 t.foo.bar = 2; 1106 1107 auto r = c.insert(t); 1108 assert(r == true); 1109 1110 auto r2 = c.insertR(t, "n"); 1111 assert(r2.rows == 1); 1112 assert(r2[0][0].as!int == t.n); 1113 1114 Test t2 = c.findOneBy!Test("n", 1).get; 1115 assert(t2 == t, t.to!string ~ " != " ~ t2.to!string); 1116 1117 Test[] t_arr; 1118 t_arr ~= Test.init; 1119 t_arr[0].n = 1; 1120 t_arr[0].n2 = 2; 1121 t_arr[0].foo.bar = 3; 1122 t_arr ~= Test.init; 1123 t_arr[1].n = 4; 1124 t_arr[1].n2 = 5; 1125 t_arr[1].foo.bar = 6; 1126 1127 auto r3 = c.insert(t_arr); 1128 assert(r3 == 2); 1129 1130 auto r4 = c.insertR(t_arr, "n"); 1131 assert(r4[0][0].as!int == t_arr[0].n); 1132 assert(r4[1][0].as!int == t_arr[1].n); 1133 1134 writeln("\t\t * async"); 1135 t.n = 123; 1136 t.n2.nullify; 1137 c.insertAsync(t); 1138 1139 auto res = c.nextResult(); 1140 assert(res.rows == 1); 1141 t2 = c.findOneBy!Test("n", 123).get; 1142 assert(t.n2.isNull); 1143 assert(t2.n2.isNull); 1144 1145 c.exec("DROP TABLE insert_test"); 1146 c.exec("DROP TYPE \"%s\" CASCADE".format(relationName!Inner)); 1147 } 1148 1149 /// ditto, async 1150 void insertAsync(T)(T val) 1151 { 1152 insert(val, true); 1153 } 1154 1155 /** 1156 Deletes the record in the given table, by its PK 1157 1158 Examples: 1159 --------------- 1160 Connection c; // An established connection 1161 struct User {@PK @serial int id; int a } 1162 c.remove!User(1); 1163 --------------- 1164 */ 1165 int remove(T, U)(U id) 1166 { 1167 QueryBuilder qb; 1168 qb.remove!T.where([primaryKeyAttributeName!T: id]); 1169 1170 return qb.query(this).run().rows; 1171 } 1172 1173 // ditto, async 1174 bool removeAsync(T, U)(U id) 1175 { 1176 QueryBuilder qb; 1177 qb.remove!T.where([primaryKeyAttributeName!T: id]); 1178 1179 return qb.query(this).runAsync() == 1; 1180 } 1181 1182 /** 1183 Deletes rows in the specified relation, filtered by the given filter string and values 1184 1185 Examples: 1186 --------------- 1187 Connection c; // An established connection 1188 struct User { @PK @serial int id; int posts } 1189 c.remove!User("id > $1 AND posts == $2", 50, 0); 1190 --------------- 1191 */ 1192 int remove(T, U...)(string filter, U vals) 1193 { 1194 QueryBuilder qb; 1195 qb.remove!T.where(filter); 1196 1197 foreach (v; vals) 1198 qb.addValue(v); 1199 1200 return qb.query(this).run().rows; 1201 } 1202 1203 /// ditto, async 1204 bool removeAsync(T, U...)(string filter, U vals) 1205 { 1206 QueryBuilder qb; 1207 qb.remove!T.where(filter); 1208 1209 foreach (v; vals) 1210 qb.addValue(v); 1211 1212 return qb.query(this).runAsync() == 1; 1213 } 1214 1215 unittest 1216 { 1217 @relation("remove_test") 1218 struct Test 1219 { 1220 @serial @PK int id; 1221 int n; 1222 } 1223 1224 c.ensureSchema!Test; 1225 1226 foreach (i; 0 .. 10) 1227 c.insert(Test(0, i)); 1228 1229 writeln("\t * remove(id)"); 1230 int n = c.remove!Test(1); 1231 assert(n == 1, `n == 1`); 1232 1233 writeln("\t\t * async"); 1234 c.removeAsync!Test(2); 1235 auto r = c.nextResult(); 1236 assert(r.rows == 1, `r.rows == 1`); 1237 1238 writeln("\t * remove(filter, vals...)"); 1239 n = c.remove!Test("id IN($1,$2,$3,$4,$5)", 3, 4, 5, 6, 7); 1240 assert(n == 5); 1241 1242 writeln("\t\t * async"); 1243 c.removeAsync!Test("id >= $1", 7); 1244 r = c.nextResult(); 1245 assert(r.rows == 3); 1246 1247 c.exec("DROP TABLE remove_test"); 1248 } 1249 1250 /** 1251 Returns a count of the rows matching the filter in the specified relation. 1252 Filter can be empty or not given to select a count of all the rows in the relation. 1253 1254 Examples: 1255 --------------- 1256 Connection c; // An established connection 1257 struct User {@PK @serial int id; int a } 1258 long nUsers = c.count!User; 1259 nUsers = c.count!User("id > $1", 123); 1260 --------------- 1261 */ 1262 long count(T, U...)(string filter = "", U vals = U.init) 1263 { 1264 import dpq.query; 1265 1266 auto q = Query(this); 1267 string str = `SELECT COUNT(*) FROM "%s"`.format(relationName!T); 1268 1269 if (filter.length > 0) 1270 str ~= " WHERE " ~ filter; 1271 1272 q = str; 1273 auto r = q.run(vals); 1274 1275 return r[0][0].as!long.get; 1276 } 1277 1278 unittest 1279 { 1280 writeln("\t * count"); 1281 1282 @relation("test_count") 1283 struct Test 1284 { 1285 @serial @PK int id; 1286 int n; 1287 } 1288 1289 c.ensureSchema!Test; 1290 1291 Test t; 1292 c.insert(t); 1293 1294 assert(c.count!Test == 1, `count == 1`); 1295 c.insert(t); 1296 assert(c.count!Test == 2, `count == 2`); 1297 1298 c.exec("DROP TABLE test_count"); 1299 } 1300 1301 /** 1302 Equivalent to calling PQisBusy from libpq. Only useful if you're doing async 1303 stuff manually. 1304 */ 1305 bool isBusy() 1306 { 1307 return PQisBusy(_connection) == 1; 1308 } 1309 1310 unittest 1311 { 1312 writeln("\t * isBusy"); 1313 1314 assert(c.isBusy() == false); 1315 1316 c.send("SELECT 1::INT"); 1317 1318 // This could fail in theory, but in practice ... fat chance. 1319 assert(c.isBusy() == true); 1320 1321 c.nextResult(); 1322 assert(c.isBusy() == false); 1323 } 1324 1325 /** 1326 Blocks until a result is read, then returns it 1327 1328 If no more results remain, a null result will be returned 1329 1330 Make sure to call this until a null is returned or just use allResults. 1331 */ 1332 Result nextResult() 1333 { 1334 PGresult* res = PQgetResult(_connection); 1335 return Result(res); 1336 } 1337 1338 /** 1339 Calls nextResult until the value returned is null, the returns them 1340 as an array. 1341 */ 1342 Result[] allResults() 1343 { 1344 Result[] res; 1345 1346 PGresult* r; 1347 while ((r = PQgetResult(_connection)) != null) 1348 res ~= Result(r); 1349 1350 return res; 1351 } 1352 1353 /** 1354 Calls nextResult until null is returned, then retuns only the last non-null result. 1355 */ 1356 Result lastResult() 1357 { 1358 Result res; 1359 1360 PGresult* r; 1361 while ((r = PQgetResult(_connection)) != null) 1362 res = Result(r); 1363 1364 return res; 1365 } 1366 1367 unittest 1368 { 1369 writeln("\t * nextResult"); 1370 auto x = c.nextResult(); 1371 assert(x.isNull); 1372 1373 int int1 = 1; 1374 int int2 = 2; 1375 1376 c.sendParams("SELECT $1", int1); 1377 1378 // In every way the same as lastResult 1379 Result r, t; 1380 while (!(t = c.nextResult()).isNull) 1381 r = t; 1382 1383 assert(r.rows == 1); 1384 assert(r.columns == 1); 1385 assert(r[0][0].as!int == int1); 1386 1387 writeln("\t * lastResult"); 1388 c.sendParams("SELECT $1", int2); 1389 r = c.lastResult(); 1390 1391 assert(r.rows == 1); 1392 assert(r.columns == 1); 1393 assert(r[0][0].as!int == int2); 1394 } 1395 1396 Result prepare(T...)(string name, string command, T paramTypes) 1397 { 1398 Oid[] oids; 1399 foreach (pType; paramTypes) 1400 oids ~= pType; 1401 1402 char* cName = cast(char*)name.toStringz; 1403 char* cComm = cast(char*)command.toStringz; 1404 1405 auto p = PreparedStatement(this, name, command, oids); 1406 _prepared[name] = p; 1407 1408 return Result(PQprepare(_connection, cName, cComm, oids.length.to!int, oids.ptr)); 1409 } 1410 1411 Result execPrepared(string name, Value[] params...) 1412 { 1413 char* cStr = cast(char*)name.toStringz; 1414 1415 return Result(PQexecPrepared(_connection, cStr, params.length.to!int, cast(char**)params.paramValues.ptr, 1416 params.paramLengths.ptr, params.paramFormats.ptr, 1)); 1417 } 1418 1419 Result execPrepared(T...)(string name, T params) 1420 { 1421 Value[] vals; 1422 foreach (p; params) 1423 vals ~= Value(p); 1424 1425 return execPrepared(name, vals); 1426 } 1427 1428 bool sendPrepared(string name, Value[] params...) 1429 { 1430 char* cStr = cast(char*)name.toStringz; 1431 1432 return PQsendQueryPrepared(_connection, cStr, params.length.to!int, cast(char**)params.paramValues.ptr, 1433 params.paramLengths.ptr, params.paramFormats.ptr, 1) == 1; 1434 } 1435 1436 bool sendPrepared(T...)(string name, T params) 1437 { 1438 Value[] vals; 1439 foreach (p; params) 1440 vals ~= Value(p); 1441 1442 return sendPrepared(name, vals); 1443 } 1444 1445 unittest 1446 { 1447 writeln("\t * prepare"); 1448 // The result of this isn't really all that useful, but as long as it 1449 // throws on errors, it kinda is 1450 c.prepare("prepare_test", "SELECT $1", Type.INT4); 1451 1452 writeln("\t * execPrepared"); 1453 auto r = c.execPrepared("prepare_test", 1); 1454 assert(r.rows == 1); 1455 assert(r[0][0].as!int == 1); 1456 1457 writeln("\t\t * sendPrepared"); 1458 bool s = c.sendPrepared("prepare_test", 1); 1459 assert(s); 1460 1461 r = c.lastResult(); 1462 assert(r.rows == 1); 1463 assert(r[0][0].as!int == 1); 1464 } 1465 1466 /** 1467 Begins a transaction block. 1468 */ 1469 void begin() 1470 { 1471 import dpq.query; 1472 1473 auto q = Query(this, "BEGIN"); 1474 q.run(); 1475 } 1476 1477 /** 1478 Commits current transaction 1479 */ 1480 void commit() 1481 { 1482 import dpq.query; 1483 1484 auto q = Query(this, "COMMIT"); 1485 q.run(); 1486 } 1487 1488 /** 1489 Creates savepoint in the current transaction block. 1490 1491 Params: 1492 name = name of created savepoint 1493 1494 Returns: created Savepoint. 1495 */ 1496 Savepoint savepoint(string name) 1497 { 1498 import dpq.query; 1499 1500 Savepoint s = new Savepoint(name); 1501 auto q = Query(this, "SAVEPOINT " ~ s.name); 1502 q.run(); 1503 1504 return s; 1505 } 1506 1507 /** 1508 Destroys savepoint in the current transaction block. 1509 1510 Params: 1511 s = savepoint to destroy 1512 */ 1513 void releaseSavepoint(Savepoint s) 1514 { 1515 import dpq.query; 1516 1517 auto q = Query(this, "RELEASE SAVEPOINT " ~ s.name); 1518 q.run(); 1519 } 1520 1521 /** 1522 Rollback the current transaction to savepoint. 1523 1524 Params: 1525 s = savepoint to rollback to. If savepoint s is null or no savepoint is specified then transaction 1526 will be rolled back to the begining. 1527 */ 1528 void rollback(Savepoint s = null) 1529 { 1530 import dpq.query; 1531 1532 auto q = Query(this); 1533 1534 if (s is null) 1535 q.command = "ROLLBACK"; 1536 else 1537 q.command = "ROLLBACK TO " ~ s.name; 1538 1539 q.run(); 1540 } 1541 1542 unittest 1543 { 1544 writeln("\t * transaction"); 1545 1546 @relation("transaction_test") 1547 struct Test 1548 { 1549 @serial @PK int id; 1550 string t; 1551 } 1552 1553 c.ensureSchema!Test; 1554 1555 Test t; 1556 t.t = "before transaction"; 1557 auto r = c.insertR(t, "id"); 1558 t.id = r[0][0].as!int.get; 1559 1560 c.begin(); 1561 t.t = "this value is ignored"; 1562 c.update(t.id, t); 1563 1564 auto s1 = c.savepoint("s1"); 1565 t.t = "before savepoint s2"; 1566 c.update(t.id, t); 1567 1568 auto s2 = c.savepoint("s2"); 1569 t.t = "after savepoint s2"; 1570 c.update(t.id, t); 1571 1572 assert(c.findOne!Test(t.id).get.t == "after savepoint s2"); 1573 1574 c.rollback(s2); 1575 assert(c.findOne!Test(t.id).get.t == "before savepoint s2"); 1576 1577 c.rollback(); 1578 assert(c.findOne!Test(t.id).get.t == "before transaction"); 1579 1580 Connection c2 = Connection("host=127.0.0.1 dbname=test user=test"); 1581 c.begin(); 1582 t.t = "inside transaction"; 1583 c.update(t.id, t); 1584 1585 assert(c.findOne!Test(t.id).get.t == "inside transaction"); 1586 assert(c2.findOne!Test(t.id).get.t == "before transaction"); 1587 1588 c.commit(); 1589 assert(c.findOne!Test(t.id).get.t == "inside transaction"); 1590 assert(c2.findOne!Test(t.id).get.t == "inside transaction"); 1591 1592 c.exec("DROP TABLE transaction_test"); 1593 c2.close(); 1594 } 1595 1596 ref PreparedStatement prepared(string name) 1597 { 1598 return _prepared[name]; 1599 } 1600 1601 ref PreparedStatement opIndex(string name) 1602 { 1603 return prepared(name); 1604 } 1605 1606 private class Savepoint 1607 { 1608 string name; 1609 1610 this(string name) 1611 { 1612 this.name = name; 1613 } 1614 } 1615 } 1616 1617 /** 1618 Deserialises the given Row to the requested type 1619 1620 Params: 1621 T = (template) type to deserialise into 1622 r = Row to deserialise 1623 */ 1624 T deserialise(T)(Row r, string prefix = "") 1625 { 1626 static if (is(T == class)) 1627 T res = new T(); 1628 else 1629 T res; 1630 foreach (m; serialisableMembers!T) 1631 { 1632 enum member = "T." ~ m; 1633 enum n = attributeName!(mixin(member)); 1634 alias OType = typeof(mixin(member)); 1635 alias MType = RealType!OType; 1636 1637 try 1638 { 1639 auto x = r[prefix ~ n].as!MType; 1640 if (!x.isNull) 1641 __traits(getMember, res, m) = x.get.to!OType; 1642 } 1643 catch (DPQException e) 1644 { 1645 if (!isInstanceOf!(Nullable, MType)) 1646 throw e; 1647 } 1648 } 1649 return res; 1650 } 1651 1652 /** 1653 * Creates global dpq connection. 1654 * 1655 * If no connection is provided to Query, this newly created connection will be used instead. 1656 * If the function is called consiquently the previously created connection will be closed. 1657 * 1658 * Params: 1659 * connString = connection string 1660 */ 1661 void dpqConnect(string connString) 1662 { 1663 if (_dpqConnection !is null) 1664 { 1665 _dpqConnection.close(); 1666 _dpqConnection.destroy(); 1667 } 1668 1669 Connection* connection = new Connection(connString); 1670 _dpqConnection = connection; 1671 } 1672 1673 /** 1674 * Returns the default global connection. 1675 */ 1676 Connection* dpqDefaultConnection() 1677 { 1678 return _dpqConnection; 1679 } 1680 1681 /** 1682 * dpq's default global connection. 1683 */ 1684 package Connection* _dpqConnection; 1685 1686 unittest 1687 { 1688 writeln("\t * dpqDefaultConnection"); 1689 1690 assert (dpqDefaultConnection() is null); 1691 1692 dpqConnect("host=127.0.0.1 dbname=test user=test"); 1693 assert (dpqDefaultConnection() !is null); 1694 1695 auto oldConnectionPtr = dpqDefaultConnection(); 1696 dpqConnect("host=127.0.0.1 dbname=test user=test"); 1697 assert (dpqDefaultConnection() !is oldConnectionPtr); 1698 }