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