1 /** 2 * DDBC - D DataBase Connector - abstraction layer for RDBMS access, with interface similar to JDBC. 3 * 4 * Source file ddbc/drivers/pgsqlddbc.d. 5 * 6 * DDBC library attempts to provide implementation independent interface to different databases. 7 * 8 * Set of supported RDBMSs can be extended by writing Drivers for particular DBs. 9 * Currently it only includes MySQL driver. 10 * 11 * JDBC documentation can be found here: 12 * $(LINK http://docs.oracle.com/javase/1.5.0/docs/api/java/sql/package-summary.html)$(BR) 13 * 14 * This module contains implementation of PostgreSQL Driver 15 * 16 * 17 * You can find usage examples in unittest{} sections. 18 * 19 * Copyright: Copyright 2013 20 * License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 21 * Author: Vadim Lopatin 22 */ 23 module ddbc.drivers.pgsqlddbc; 24 25 26 version(USE_PGSQL) { 27 28 import std.algorithm; 29 import std.conv; 30 import std.datetime : Date, DateTime, TimeOfDay; 31 import std.datetime.date; 32 import std.datetime.systime; 33 import std.exception; 34 35 // For backwards compatibily 36 // 'enforceEx' will be removed with 2.089 37 static if(__VERSION__ < 2080) { 38 alias enforceHelper = enforceEx; 39 } else { 40 alias enforceHelper = enforce; 41 } 42 43 static if(__traits(compiles, (){ import std.experimental.logger; } )) { 44 import std.experimental.logger; 45 } 46 import std.stdio; 47 import std..string; 48 import std.variant; 49 import std.array; 50 import core.sync.mutex; 51 52 import ddbc.common; 53 import ddbc.core; 54 import derelict.pq.pq; 55 //import ddbc.drivers.pgsql; 56 import ddbc.drivers.utils; 57 58 const int BOOLOID = 16; 59 const int BYTEAOID = 17; 60 const int CHAROID = 18; 61 const int NAMEOID = 19; 62 const int INT8OID = 20; 63 const int INT2OID = 21; 64 const int INT2VECTOROID = 22; 65 const int INT4OID = 23; 66 const int REGPROCOID = 24; 67 const int TEXTOID = 25; 68 const int OIDOID = 26; 69 const int TIDOID = 27; 70 const int XIDOID = 28; 71 const int CIDOID = 29; 72 const int OIDVECTOROID = 30; 73 const int JSONOID = 114; 74 const int JSONBOID = 3802; 75 const int XMLOID = 142; 76 const int PGNODETREEOID = 194; 77 const int POINTOID = 600; 78 const int LSEGOID = 601; 79 const int PATHOID = 602; 80 const int BOXOID = 603; 81 const int POLYGONOID = 604; 82 const int LINEOID = 628; 83 const int FLOAT4OID = 700; 84 const int FLOAT8OID = 701; 85 const int ABSTIMEOID = 702; 86 const int RELTIMEOID = 703; 87 const int TINTERVALOID = 704; 88 const int UNKNOWNOID = 705; 89 const int CIRCLEOID = 718; 90 const int CASHOID = 790; 91 const int MACADDROID = 829; 92 const int INETOID = 869; 93 const int CIDROID = 650; 94 const int INT4ARRAYOID = 1007; 95 const int TEXTARRAYOID = 1009; 96 const int FLOAT4ARRAYOID = 1021; 97 const int ACLITEMOID = 1033; 98 const int CSTRINGARRAYOID = 1263; 99 const int BPCHAROID = 1042; 100 const int VARCHAROID = 1043; 101 const int DATEOID = 1082; 102 const int TIMEOID = 1083; 103 const int TIMESTAMPOID = 1114; 104 const int TIMESTAMPTZOID = 1184; 105 const int INTERVALOID = 1186; 106 const int TIMETZOID = 1266; 107 const int BITOID = 1560; 108 const int VARBITOID = 1562; 109 const int NUMERICOID = 1700; 110 const int REFCURSOROID = 1790; 111 const int REGPROCEDUREOID = 2202; 112 const int REGOPEROID = 2203; 113 const int REGOPERATOROID = 2204; 114 const int REGCLASSOID = 2205; 115 const int REGTYPEOID = 2206; 116 const int REGTYPEARRAYOID = 2211; 117 const int UUIDOID = 2950; 118 const int TSVECTOROID = 3614; 119 const int GTSVECTOROID = 3642; 120 const int TSQUERYOID = 3615; 121 const int REGCONFIGOID = 3734; 122 const int REGDICTIONARYOID = 3769; 123 const int INT4RANGEOID = 3904; 124 const int RECORDOID = 2249; 125 const int RECORDARRAYOID = 2287; 126 const int CSTRINGOID = 2275; 127 const int ANYOID = 2276; 128 const int ANYARRAYOID = 2277; 129 const int VOIDOID = 2278; 130 const int TRIGGEROID = 2279; 131 const int EVTTRIGGEROID = 3838; 132 const int LANGUAGE_HANDLEROID = 2280; 133 const int INTERNALOID = 2281; 134 const int OPAQUEOID = 2282; 135 const int ANYELEMENTOID = 2283; 136 const int ANYNONARRAYOID = 2776; 137 const int ANYENUMOID = 3500; 138 const int FDW_HANDLEROID = 3115; 139 const int ANYRANGEOID = 3831; 140 141 string bytesToBytea(byte[] bytes) { 142 return ubytesToBytea(cast(ubyte[])bytes); 143 } 144 145 string ubytesToBytea(ubyte[] bytes) { 146 if (bytes is null || !bytes.length) 147 return null; 148 char[] res; 149 res.assumeSafeAppend; 150 res ~= "\\x"; 151 immutable static char[16] hex_digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']; 152 foreach(b; bytes) { 153 res ~= hex_digits[(b >> 4) & 0x0F]; 154 res ~= hex_digits[b & 0x0F]; 155 } 156 return cast(string)res; 157 } 158 159 byte[] byteaToBytes(string s) { 160 return cast(byte[])byteaToUbytes(s); 161 } 162 163 private static int fromHexDigit(char ch, int defValue = -1) { 164 if (ch >= '0' && ch <= '9') 165 return ch - '0'; 166 if (ch >= 'A' && ch <= 'F') 167 return ch - 'A' + 10; 168 if (ch >= 'a' && ch <= 'f') 169 return ch - 'a' + 10; 170 return defValue; 171 } 172 ubyte[] byteaToUbytes(string s) { 173 if (s is null || !s.length) 174 return null; 175 ubyte[] res; 176 if (s.length > 2 && s[0] == '\\' && s[1] == 'x') { 177 // hex string format 178 for (int i = 2; i + 1 < s.length; i += 2) { 179 int d1 = fromHexDigit(s[i], 0); 180 int d2 = fromHexDigit(s[i + 1], 0); 181 res ~= cast(ubyte)((d1 << 4) | (d2)); 182 } 183 } else { 184 // escaped string format 185 bool lastBackSlash = 0; 186 foreach(ch; s) { 187 if (ch == '\\') { 188 if (lastBackSlash) { 189 res ~= '\\'; 190 lastBackSlash = false; 191 } else { 192 lastBackSlash = true; 193 } 194 } else { 195 if (lastBackSlash) { 196 if (ch == '0') { 197 res ~= 0; 198 } else if (ch == 'r') { 199 res ~= '\r'; 200 } else if (ch == 'n') { 201 res ~= '\n'; 202 } else if (ch == 't') { 203 res ~= '\t'; 204 } else { 205 } 206 } else { 207 res ~= cast(byte)ch; 208 } 209 lastBackSlash = false; 210 } 211 } 212 } 213 return res; 214 } 215 version(unittest) { 216 /* 217 To allow unit tests using PostgreSQL server, 218 run postgres client using admin privileges, e.g. for postgres server on localhost: 219 sudo -u postgres psql 220 221 Then create a user and test database: 222 223 postgres=# CREATE USER testuser WITH ENCRYPTED PASSWORD 'testpassword'; 224 postgres=# CREATE DATABASE testdb OWNER testuser; 225 226 CREATE DATABASE testdb WITH OWNER testuser ENCODING 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8' TEMPLATE template0; 227 */ 228 /// change to false to disable tests on real PostgreSQL server 229 immutable bool PGSQL_TESTS_ENABLED = true; 230 /// change parameters if necessary 231 const string PGSQL_UNITTEST_HOST = "localhost"; 232 const int PGSQL_UNITTEST_PORT = 5432; 233 const string PGSQL_UNITTEST_USER = "postgres"; // "testuser"; 234 const string PGSQL_UNITTEST_PASSWORD = ""; // "testpassword"; 235 const string PGSQL_UNITTEST_DB = "testdb"; 236 237 static if (PGSQL_TESTS_ENABLED) { 238 /// use this data source for tests 239 DataSource createUnitTestPGSQLDataSource() { 240 //string url = makeDDBCUrl("postgresql", PGSQL_UNITTEST_HOST, PGSQL_UNITTEST_PORT, PGSQL_UNITTEST_DB); 241 string url = "ddbc:postgresql://localhost:5432/testdb"; 242 243 string[string] params; 244 setUserAndPassword(params, PGSQL_UNITTEST_USER, PGSQL_UNITTEST_PASSWORD); 245 return createConnectionPool(url, params); 246 } 247 } 248 } 249 250 251 class PGSQLConnection : ddbc.core.Connection { 252 private: 253 string url; 254 string[string] params; 255 string dbName; 256 string username; 257 string password; 258 string hostname; 259 int port = 5432; 260 PGconn * conn; 261 bool closed; 262 bool autocommit = true; 263 bool useSsl = true; 264 Mutex mutex; 265 266 267 PGSQLStatement [] activeStatements; 268 269 void closeUnclosedStatements() { 270 PGSQLStatement [] list = activeStatements.dup; 271 foreach(stmt; list) { 272 stmt.close(); 273 } 274 } 275 276 void onStatementClosed(PGSQLStatement stmt) { 277 myRemove(activeStatements, stmt); 278 } 279 280 void checkClosed() { 281 if (closed) 282 throw new SQLException("Connection is already closed"); 283 } 284 285 public: 286 287 void lock() { 288 mutex.lock(); 289 } 290 291 void unlock() { 292 mutex.unlock(); 293 } 294 295 PGconn * getConnection() { return conn; } 296 297 298 this(string url, string[string] params) { 299 mutex = new Mutex(); 300 this.url = url; 301 this.params = params; 302 //writeln("parsing url " ~ url); 303 extractParamsFromURL(url, this.params); 304 string dbName = ""; 305 ptrdiff_t firstSlashes = std..string.indexOf(url, "//"); 306 ptrdiff_t lastSlash = std..string.lastIndexOf(url, '/'); 307 ptrdiff_t hostNameStart = firstSlashes >= 0 ? firstSlashes + 2 : 0; 308 ptrdiff_t hostNameEnd = lastSlash >=0 && lastSlash > firstSlashes + 1 ? lastSlash : url.length; 309 if (hostNameEnd < url.length - 1) { 310 dbName = url[hostNameEnd + 1 .. $]; 311 } 312 hostname = url[hostNameStart..hostNameEnd]; 313 if (hostname.length == 0) 314 hostname = "localhost"; 315 ptrdiff_t portDelimiter = std..string.indexOf(hostname, ":"); 316 if (portDelimiter >= 0) { 317 string portString = hostname[portDelimiter + 1 .. $]; 318 hostname = hostname[0 .. portDelimiter]; 319 if (portString.length > 0) 320 port = to!int(portString); 321 if (port < 1 || port > 65535) 322 port = 5432; 323 } 324 if ("user" in this.params) 325 username = this.params["user"]; 326 if ("password" in this.params) 327 password = this.params["password"]; 328 if ("ssl" in this.params) 329 useSsl = (this.params["ssl"] == "true"); 330 331 332 //writeln("host " ~ hostname ~ " : " ~ to!string(port) ~ " db=" ~ dbName ~ " user=" ~ username ~ " pass=" ~ password); 333 // TODO: support SSL param 334 335 const char ** keywords = [std..string.toStringz("host"), std..string.toStringz("port"), std..string.toStringz("dbname"), std..string.toStringz("user"), std..string.toStringz("password"), null].ptr; 336 const char ** values = [std..string.toStringz(hostname), std..string.toStringz(to!string(port)), std..string.toStringz(dbName), std..string.toStringz(username), std..string.toStringz(password), null].ptr; 337 //writeln("trying to connect"); 338 conn = PQconnectdbParams(keywords, values, 0); 339 if(conn is null) 340 throw new SQLException("Cannot get Postgres connection"); 341 if(PQstatus(conn) != CONNECTION_OK) 342 throw new SQLException(copyCString(PQerrorMessage(conn))); 343 closed = false; 344 setAutoCommit(true); 345 updateConnectionParams(); 346 } 347 348 void updateConnectionParams() { 349 Statement stmt = createStatement(); 350 scope(exit) stmt.close(); 351 stmt.executeUpdate("SET NAMES 'utf8'"); 352 } 353 354 override void close() { 355 checkClosed(); 356 357 lock(); 358 scope(exit) unlock(); 359 360 closeUnclosedStatements(); 361 362 PQfinish(conn); 363 closed = true; 364 } 365 366 override void commit() { 367 checkClosed(); 368 369 lock(); 370 scope(exit) unlock(); 371 372 Statement stmt = createStatement(); 373 scope(exit) stmt.close(); 374 stmt.executeUpdate("COMMIT"); 375 } 376 377 override Statement createStatement() { 378 checkClosed(); 379 380 lock(); 381 scope(exit) unlock(); 382 383 PGSQLStatement stmt = new PGSQLStatement(this); 384 activeStatements ~= stmt; 385 return stmt; 386 } 387 388 PreparedStatement prepareStatement(string sql) { 389 checkClosed(); 390 391 lock(); 392 scope(exit) unlock(); 393 394 PGSQLPreparedStatement stmt = new PGSQLPreparedStatement(this, sql); 395 activeStatements ~= stmt; 396 return stmt; 397 } 398 399 override string getCatalog() { 400 return dbName; 401 } 402 403 /// Sets the given catalog name in order to select a subspace of this Connection object's database in which to work. 404 override void setCatalog(string catalog) { 405 checkClosed(); 406 if (dbName == catalog) 407 return; 408 409 lock(); 410 scope(exit) unlock(); 411 412 //conn.selectDB(catalog); 413 dbName = catalog; 414 // TODO: 415 416 throw new SQLException("Not implemented"); 417 } 418 419 override bool isClosed() { 420 return closed; 421 } 422 423 override void rollback() { 424 checkClosed(); 425 426 lock(); 427 scope(exit) unlock(); 428 429 Statement stmt = createStatement(); 430 scope(exit) stmt.close(); 431 stmt.executeUpdate("ROLLBACK"); 432 } 433 override bool getAutoCommit() { 434 return autocommit; 435 } 436 override void setAutoCommit(bool autoCommit) { 437 checkClosed(); 438 if (this.autocommit == autoCommit) 439 return; 440 lock(); 441 scope(exit) unlock(); 442 443 autocommit = true; 444 445 //assert(0, "AUTOCOMMIT is no longer supported."); 446 } 447 } 448 449 class PGSQLStatement : Statement { 450 private: 451 PGSQLConnection conn; 452 // Command * cmd; 453 // ddbc.drivers.mysql.ResultSet rs; 454 PGSQLResultSet resultSet; 455 456 bool closed; 457 458 public: 459 void checkClosed() { 460 enforceHelper!SQLException(!closed, "Statement is already closed"); 461 } 462 463 void lock() { 464 conn.lock(); 465 } 466 467 void unlock() { 468 conn.unlock(); 469 } 470 471 this(PGSQLConnection conn) { 472 this.conn = conn; 473 } 474 475 ResultSetMetaData createMetadata(PGresult * res) { 476 int rows = PQntuples(res); 477 int fieldCount = PQnfields(res); 478 ColumnMetadataItem[] list = new ColumnMetadataItem[fieldCount]; 479 for(int i = 0; i < fieldCount; i++) { 480 ColumnMetadataItem item = new ColumnMetadataItem(); 481 //item.schemaName = field.db; 482 item.name = copyCString(PQfname(res, i)); 483 //item.tableName = copyCString(PQftable(res, i)); 484 int fmt = PQfformat(res, i); 485 ulong t = PQftype(res, i); 486 item.label = copyCString(PQfname(res, i)); 487 //item.precision = field.length; 488 //item.scale = field.scale; 489 //item.isNullable = !field.notNull; 490 //item.isSigned = !field.unsigned; 491 //item.type = fromPGSQLType(field.type); 492 // // TODO: fill more params 493 list[i] = item; 494 } 495 return new ResultSetMetaDataImpl(list); 496 } 497 ParameterMetaData createParameterMetadata(int paramCount) { 498 ParameterMetaDataItem[] res = new ParameterMetaDataItem[paramCount]; 499 for(int i = 0; i < paramCount; i++) { 500 ParameterMetaDataItem item = new ParameterMetaDataItem(); 501 item.precision = 0; 502 item.scale = 0; 503 item.isNullable = true; 504 item.isSigned = true; 505 item.type = SqlType.VARCHAR; 506 res[i] = item; 507 } 508 return new ParameterMetaDataImpl(res); 509 } 510 public: 511 PGSQLConnection getConnection() { 512 checkClosed(); 513 return conn; 514 } 515 516 private void fillData(PGresult * res, ref Variant[][] data) { 517 int rows = PQntuples(res); 518 int fieldCount = PQnfields(res); 519 int[] fmts = new int[fieldCount]; 520 int[] types = new int[fieldCount]; 521 for (int col = 0; col < fieldCount; col++) { 522 fmts[col] = PQfformat(res, col); 523 types[col] = cast(int)PQftype(res, col); 524 } 525 for (int row = 0; row < rows; row++) { 526 Variant[] v = new Variant[fieldCount]; 527 for (int col = 0; col < fieldCount; col++) { 528 int n = PQgetisnull(res, row, col); 529 if (n != 0) { 530 v[col] = null; 531 } else { 532 int len = PQgetlength(res, row, col); 533 const ubyte * value = PQgetvalue(res, row, col); 534 int t = types[col]; 535 //writeln("[" ~ to!string(row) ~ "][" ~ to!string(col) ~ "] type = " ~ to!string(t) ~ " len = " ~ to!string(len)); 536 if (fmts[col] == 0) { 537 // text 538 string s = copyCString(value, len); 539 //writeln("text: " ~ s); 540 switch(t) { 541 case INT4OID: 542 v[col] = parse!int(s); 543 break; 544 case BOOLOID: 545 if( s == "true" || s == "t" || s == "1" ) 546 v[col] = true; 547 else if( s == "false" || s == "f" || s == "0" ) 548 v[col] = false; 549 else 550 v[col] = parse!int(s) != 0; 551 break; 552 case CHAROID: 553 v[col] = cast(char)(s.length > 0 ? s[0] : 0); 554 break; 555 case INT8OID: 556 v[col] = parse!long(s); 557 break; 558 case INT2OID: 559 v[col] = parse!short(s); 560 break; 561 case FLOAT4OID: 562 v[col] = parse!float(s); 563 break; 564 case FLOAT8OID: 565 v[col] = parse!double(s); 566 break; 567 case VARCHAROID: 568 case TEXTOID: 569 case NAMEOID: 570 v[col] = s; 571 break; 572 case BYTEAOID: 573 v[col] = byteaToUbytes(s); 574 break; 575 case TIMESTAMPOID: 576 //writeln("TIMESTAMPOID: " ~ s); 577 v[col] = DateTime.fromISOExtString( s.translate( [ ' ': 'T' ] ).split( '.' ).front() ); 578 // todo: use new function in ddbc.utils: parseDateTime(s); 579 break; 580 case TIMESTAMPTZOID: 581 //writeln("TIMESTAMPTZOID: " ~ s); 582 v[col] = SysTime.fromISOExtString( s.translate( [ ' ': 'T' ] ) ); 583 // todo: use new function in ddbc.utils: parseSysTime(s); 584 break; 585 case TIMEOID: 586 v[col] = parseTimeoid(s); 587 break; 588 case DATEOID: 589 v[col] = parseDateoid(s); 590 break; 591 case UUIDOID: 592 v[col] = s; 593 break; 594 case JSONOID: 595 v[col] = s; 596 break; 597 case JSONBOID: 598 v[col] = s; 599 break; 600 default: 601 throw new SQLException("Unsupported column type " ~ to!string(t)); 602 } 603 } else { 604 // binary 605 //writeln("binary:"); 606 byte[] b = new byte[len]; 607 for (int i=0; i<len; i++) 608 b[i] = value[i]; 609 v[col] = b; 610 } 611 } 612 } 613 data ~= v; 614 } 615 } 616 617 override ddbc.core.ResultSet executeQuery(string query) { 618 //throw new SQLException("Not implemented"); 619 checkClosed(); 620 lock(); 621 scope(exit) unlock(); 622 623 static if(__traits(compiles, (){ import std.experimental.logger; } )) { 624 sharedLog.trace(query); 625 } 626 PGresult * res = PQexec(conn.getConnection(), std..string.toStringz(query)); 627 enforceHelper!SQLException(res !is null, "Failed to execute statement " ~ query); 628 auto status = PQresultStatus(res); 629 enforceHelper!SQLException(status == PGRES_TUPLES_OK, getError()); 630 scope(exit) PQclear(res); 631 632 // cmd = new Command(conn.getConnection(), query); 633 // rs = cmd.execSQLResult(); 634 auto metadata = createMetadata(res); 635 int rows = PQntuples(res); 636 int fieldCount = PQnfields(res); 637 Variant[][] data; 638 fillData(res, data); 639 resultSet = new PGSQLResultSet(this, data, metadata); 640 return resultSet; 641 } 642 643 string getError() { 644 return copyCString(PQerrorMessage(conn.getConnection())); 645 } 646 647 override int executeUpdate(string query) { 648 Variant dummy; 649 return executeUpdate(query, dummy); 650 } 651 652 void readInsertId(PGresult * res, ref Variant insertId) { 653 int rows = PQntuples(res); 654 int fieldCount = PQnfields(res); 655 //writeln("readInsertId - rows " ~ to!string(rows) ~ " " ~ to!string(fieldCount)); 656 if (rows == 1 && fieldCount == 1) { 657 int len = PQgetlength(res, 0, 0); 658 const ubyte * value = PQgetvalue(res, 0, 0); 659 string s = copyCString(value, len); 660 insertId = parse!long(s); 661 } 662 } 663 664 override int executeUpdate(string query, out Variant insertId) { 665 checkClosed(); 666 lock(); 667 scope(exit) unlock(); 668 669 static if(__traits(compiles, (){ import std.experimental.logger; } )) { 670 sharedLog.trace(query); 671 } 672 673 PGresult * res = PQexec(conn.getConnection(), std..string.toStringz(query)); 674 enforceHelper!SQLException(res !is null, "Failed to execute statement " ~ query); 675 auto status = PQresultStatus(res); 676 enforceHelper!SQLException(status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK, getError()); 677 scope(exit) PQclear(res); 678 679 string rowsAffected = copyCString(PQcmdTuples(res)); 680 681 readInsertId(res, insertId); 682 // auto lastid = PQoidValue(res); 683 // writeln("lastId = " ~ to!string(lastid)); 684 int affected = rowsAffected.length > 0 ? to!int(rowsAffected) : 0; 685 // insertId = Variant(cast(long)lastid); 686 return affected; 687 } 688 689 override void close() { 690 checkClosed(); 691 lock(); 692 scope(exit) unlock(); 693 closeResultSet(); 694 closed = true; 695 conn.onStatementClosed(this); 696 } 697 698 void closeResultSet() { 699 //throw new SQLException("Not implemented"); 700 // if (cmd == null) { 701 // return; 702 // } 703 // cmd.releaseStatement(); 704 // delete cmd; 705 // cmd = null; 706 // if (resultSet !is null) { 707 // resultSet.onStatementClosed(); 708 // resultSet = null; 709 // } 710 } 711 } 712 713 ulong preparedStatementIndex = 1; 714 715 class PGSQLPreparedStatement : PGSQLStatement, PreparedStatement { 716 string query; 717 int paramCount; 718 ResultSetMetaData metadata; 719 ParameterMetaData paramMetadata; 720 string stmtName; 721 bool[] paramIsSet; 722 string[] paramValue; 723 //PGresult * rs; 724 725 string convertParams(string query) { 726 string res; 727 int count = 0; 728 bool insideString = false; 729 char lastChar = 0; 730 foreach(ch; query) { 731 if (ch == '\'') { 732 if (insideString) { 733 if (lastChar != '\\') 734 insideString = false; 735 } else { 736 insideString = true; 737 } 738 res ~= ch; 739 } else if (ch == '?') { 740 if (!insideString) { 741 count++; 742 res ~= "$" ~ to!string(count); 743 } else { 744 res ~= ch; 745 } 746 } else { 747 res ~= ch; 748 } 749 lastChar = ch; 750 } 751 paramCount = count; 752 return res; 753 } 754 755 this(PGSQLConnection conn, string query) { 756 super(conn); 757 query = convertParams(query); 758 this.query = query; 759 paramMetadata = createParameterMetadata(paramCount); 760 stmtName = "ddbcstmt" ~ to!string(preparedStatementIndex); 761 paramIsSet = new bool[paramCount]; 762 paramValue = new string[paramCount]; 763 // rs = PQprepare(conn.getConnection(), 764 // toStringz(stmtName), 765 // toStringz(query), 766 // paramCount, 767 // null); 768 // enforceHelper!SQLException(rs !is null, "Error while preparing statement " ~ query); 769 // auto status = PQresultStatus(rs); 770 //writeln("prepare paramCount = " ~ to!string(paramCount)); 771 // enforceHelper!SQLException(status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK, "Error while preparing statement " ~ query ~ " : " ~ getError(rs)); 772 // metadata = createMetadata(rs); 773 //scope(exit) PQclear(rs); 774 } 775 string getError(PGresult * res) { 776 return copyCString(PQresultErrorMessage(res)); 777 } 778 void checkIndex(int index) { 779 if (index < 1 || index > paramCount) 780 throw new SQLException("Parameter index " ~ to!string(index) ~ " is out of range"); 781 } 782 void checkParams() { 783 foreach(i, b; paramIsSet) 784 enforceHelper!SQLException(b, "Parameter " ~ to!string(i) ~ " is not set"); 785 } 786 void setParam(int index, string value) { 787 checkIndex(index); 788 paramValue[index - 1] = value; 789 paramIsSet[index - 1] = true; 790 } 791 792 PGresult * exec() { 793 checkParams(); 794 const (char) * [] values = new const(char)*[paramCount]; 795 int[] lengths = new int[paramCount]; 796 int[] formats = new int[paramCount]; 797 for (int i=0; i<paramCount; i++) { 798 if (paramValue[i] is null) 799 values[i] = null; 800 else 801 values[i] = toStringz(paramValue[i]); 802 lengths[i] = cast(int)paramValue[i].length; 803 } 804 // PGresult * res = PQexecPrepared(conn.getConnection(), 805 // toStringz(stmtName), 806 // paramCount, 807 // cast(const char * *)values.ptr, 808 // cast(const int *)lengths.ptr, 809 // cast(const int *)formats.ptr, 810 // 0); 811 PGresult * res = PQexecParams(conn.getConnection(), 812 cast(const char *)toStringz(query), 813 paramCount, 814 null, 815 cast(const (ubyte *) *)values.ptr, 816 cast(const int *)lengths.ptr, 817 cast(const int *)formats.ptr, 818 0); 819 enforceHelper!SQLException(res !is null, "Error while executing prepared statement " ~ query); 820 metadata = createMetadata(res); 821 return res; 822 } 823 824 public: 825 826 override void close() { 827 checkClosed(); 828 lock(); 829 scope(exit) unlock(); 830 //PQclear(rs); 831 closeResultSet(); 832 closed = true; 833 } 834 835 /// Retrieves a ResultSetMetaData object that contains information about the columns of the ResultSet object that will be returned when this PreparedStatement object is executed. 836 override ResultSetMetaData getMetaData() { 837 checkClosed(); 838 lock(); 839 scope(exit) unlock(); 840 return metadata; 841 } 842 843 /// Retrieves the number, types and properties of this PreparedStatement object's parameters. 844 override ParameterMetaData getParameterMetaData() { 845 //throw new SQLException("Not implemented"); 846 checkClosed(); 847 lock(); 848 scope(exit) unlock(); 849 return paramMetadata; 850 } 851 852 override int executeUpdate() { 853 Variant dummy; 854 return executeUpdate(dummy); 855 } 856 857 override int executeUpdate(out Variant insertId) { 858 checkClosed(); 859 lock(); 860 scope(exit) unlock(); 861 PGresult * res = exec(); 862 scope(exit) PQclear(res); 863 auto status = PQresultStatus(res); 864 enforceHelper!SQLException(status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK, getError(res)); 865 866 string rowsAffected = copyCString(PQcmdTuples(res)); 867 //auto lastid = PQoidValue(res); 868 readInsertId(res, insertId); 869 //writeln("lastId = " ~ to!string(lastid)); 870 int affected = rowsAffected.length > 0 ? to!int(rowsAffected) : 0; 871 //insertId = Variant(cast(long)lastid); 872 return affected; 873 } 874 875 override ddbc.core.ResultSet executeQuery() { 876 checkClosed(); 877 lock(); 878 scope(exit) unlock(); 879 880 static if(__traits(compiles, (){ import std.experimental.logger; } )) { 881 sharedLog.trace(this.query); 882 } 883 884 PGresult * res = exec(); 885 scope(exit) PQclear(res); 886 int rows = PQntuples(res); 887 int fieldCount = PQnfields(res); 888 Variant[][] data; 889 fillData(res, data); 890 resultSet = new PGSQLResultSet(this, data, metadata); 891 return resultSet; 892 } 893 894 override void clearParameters() { 895 throw new SQLException("Not implemented"); 896 // checkClosed(); 897 // lock(); 898 // scope(exit) unlock(); 899 // for (int i = 1; i <= paramCount; i++) 900 // setNull(i); 901 } 902 903 override void setFloat(int parameterIndex, float x) { 904 checkClosed(); 905 lock(); 906 scope(exit) unlock(); 907 setParam(parameterIndex, to!string(x)); 908 } 909 override void setDouble(int parameterIndex, double x){ 910 checkClosed(); 911 lock(); 912 scope(exit) unlock(); 913 setParam(parameterIndex, to!string(x)); 914 } 915 override void setBoolean(int parameterIndex, bool x) { 916 checkClosed(); 917 lock(); 918 scope(exit) unlock(); 919 setParam(parameterIndex, x ? "true" : "false"); 920 } 921 override void setLong(int parameterIndex, long x) { 922 checkClosed(); 923 lock(); 924 scope(exit) unlock(); 925 setParam(parameterIndex, to!string(x)); 926 } 927 928 override void setUlong(int parameterIndex, ulong x) { 929 checkClosed(); 930 lock(); 931 scope(exit) unlock(); 932 setParam(parameterIndex, to!string(x)); 933 } 934 935 override void setInt(int parameterIndex, int x) { 936 checkClosed(); 937 lock(); 938 scope(exit) unlock(); 939 setParam(parameterIndex, to!string(x)); 940 } 941 942 override void setUint(int parameterIndex, uint x) { 943 checkClosed(); 944 lock(); 945 scope(exit) unlock(); 946 setParam(parameterIndex, to!string(x)); 947 } 948 949 override void setShort(int parameterIndex, short x) { 950 checkClosed(); 951 lock(); 952 scope(exit) unlock(); 953 setParam(parameterIndex, to!string(x)); 954 } 955 956 override void setUshort(int parameterIndex, ushort x) { 957 checkClosed(); 958 lock(); 959 scope(exit) unlock(); 960 setParam(parameterIndex, to!string(x)); 961 } 962 963 override void setByte(int parameterIndex, byte x) { 964 checkClosed(); 965 lock(); 966 scope(exit) unlock(); 967 setParam(parameterIndex, to!string(x)); 968 } 969 970 override void setUbyte(int parameterIndex, ubyte x) { 971 checkClosed(); 972 lock(); 973 scope(exit) unlock(); 974 checkIndex(parameterIndex); 975 setParam(parameterIndex, to!string(x)); 976 } 977 978 override void setBytes(int parameterIndex, byte[] x) { 979 setString(parameterIndex, bytesToBytea(x)); 980 } 981 override void setUbytes(int parameterIndex, ubyte[] x) { 982 setString(parameterIndex, ubytesToBytea(x)); 983 } 984 override void setString(int parameterIndex, string x) { 985 checkClosed(); 986 lock(); 987 scope(exit) unlock(); 988 setParam(parameterIndex, x); 989 } 990 override void setSysTime(int parameterIndex, SysTime x) { 991 setString(parameterIndex, x.toISOString()); 992 } 993 994 override void setDateTime(int parameterIndex, DateTime x) { 995 setString(parameterIndex, x.toISOString()); 996 } 997 override void setDate(int parameterIndex, Date x) { 998 setString(parameterIndex, x.toISOString()); 999 } 1000 override void setTime(int parameterIndex, TimeOfDay x) { 1001 setString(parameterIndex, x.toISOString()); 1002 } 1003 1004 override void setVariant(int parameterIndex, Variant x) { 1005 checkClosed(); 1006 lock(); 1007 scope(exit) unlock(); 1008 if (x.convertsTo!DateTime) 1009 setDateTime(parameterIndex, x.get!DateTime); 1010 else if (x.convertsTo!Date) 1011 setDate(parameterIndex, x.get!Date); 1012 else if (x.convertsTo!TimeOfDay) 1013 setTime(parameterIndex, x.get!TimeOfDay); 1014 else if (x.convertsTo!(byte[])) 1015 setBytes(parameterIndex, x.get!(byte[])); 1016 else if (x.convertsTo!(ubyte[])) 1017 setUbytes(parameterIndex, x.get!(ubyte[])); 1018 else 1019 setParam(parameterIndex, x.toString()); 1020 } 1021 1022 override void setNull(int parameterIndex) { 1023 checkClosed(); 1024 lock(); 1025 scope(exit) unlock(); 1026 setParam(parameterIndex, null); 1027 } 1028 1029 override void setNull(int parameterIndex, int sqlType) { 1030 checkClosed(); 1031 lock(); 1032 scope(exit) unlock(); 1033 setParam(parameterIndex, null); 1034 } 1035 1036 override string toString() { 1037 return this.query; 1038 } 1039 } 1040 1041 class PGSQLResultSet : ResultSetImpl { 1042 private PGSQLStatement stmt; 1043 private Variant[][] data; 1044 ResultSetMetaData metadata; 1045 private bool closed; 1046 private int currentRowIndex; 1047 private int rowCount; 1048 private int[string] columnMap; 1049 private bool lastIsNull; 1050 private int columnCount; 1051 1052 Variant getValue(int columnIndex) { 1053 checkClosed(); 1054 enforceHelper!SQLException(columnIndex >= 1 && columnIndex <= columnCount, "Column index out of bounds: " ~ to!string(columnIndex)); 1055 enforceHelper!SQLException(currentRowIndex >= 0 && currentRowIndex < rowCount, "No current row in result set"); 1056 Variant res = data[currentRowIndex][columnIndex - 1]; 1057 lastIsNull = (res == null); 1058 return res; 1059 } 1060 1061 void checkClosed() { 1062 if (closed) 1063 throw new SQLException("Result set is already closed"); 1064 } 1065 1066 public: 1067 1068 void lock() { 1069 stmt.lock(); 1070 } 1071 1072 void unlock() { 1073 stmt.unlock(); 1074 } 1075 1076 this(PGSQLStatement stmt, Variant[][] data, ResultSetMetaData metadata) { 1077 this.stmt = stmt; 1078 this.data = data; 1079 this.metadata = metadata; 1080 closed = false; 1081 rowCount = cast(int)data.length; 1082 currentRowIndex = -1; 1083 columnCount = metadata.getColumnCount(); 1084 for (int i=0; i<columnCount; i++) { 1085 columnMap[metadata.getColumnName(i + 1)] = i; 1086 } 1087 //writeln("created result set: " ~ to!string(rowCount) ~ " rows, " ~ to!string(columnCount) ~ " cols"); 1088 } 1089 1090 void onStatementClosed() { 1091 closed = true; 1092 } 1093 1094 // ResultSet interface implementation 1095 1096 //Retrieves the number, types and properties of this ResultSet object's columns 1097 override ResultSetMetaData getMetaData() { 1098 checkClosed(); 1099 lock(); 1100 scope(exit) unlock(); 1101 return metadata; 1102 } 1103 1104 override void close() { 1105 checkClosed(); 1106 lock(); 1107 scope(exit) unlock(); 1108 stmt.closeResultSet(); 1109 closed = true; 1110 } 1111 override bool first() { 1112 checkClosed(); 1113 lock(); 1114 scope(exit) unlock(); 1115 currentRowIndex = 0; 1116 return currentRowIndex >= 0 && currentRowIndex < rowCount; 1117 } 1118 override bool isFirst() { 1119 checkClosed(); 1120 lock(); 1121 scope(exit) unlock(); 1122 return rowCount > 0 && currentRowIndex == 0; 1123 } 1124 override bool isLast() { 1125 checkClosed(); 1126 lock(); 1127 scope(exit) unlock(); 1128 return rowCount > 0 && currentRowIndex == rowCount - 1; 1129 } 1130 override bool next() { 1131 checkClosed(); 1132 lock(); 1133 scope(exit) unlock(); 1134 if (currentRowIndex + 1 >= rowCount) 1135 return false; 1136 currentRowIndex++; 1137 return true; 1138 } 1139 1140 override int findColumn(string columnName) { 1141 checkClosed(); 1142 lock(); 1143 scope(exit) unlock(); 1144 int * p = (columnName in columnMap); 1145 if (!p) 1146 throw new SQLException("Column " ~ columnName ~ " not found"); 1147 return *p + 1; 1148 } 1149 1150 override bool getBoolean(int columnIndex) { 1151 checkClosed(); 1152 lock(); 1153 scope(exit) unlock(); 1154 Variant v = getValue(columnIndex); 1155 if (lastIsNull) 1156 return false; 1157 if (v.convertsTo!(bool)) 1158 return v.get!(bool); 1159 if (v.convertsTo!(int)) 1160 return v.get!(int) != 0; 1161 if (v.convertsTo!(long)) 1162 return v.get!(long) != 0; 1163 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to boolean"); 1164 } 1165 override ubyte getUbyte(int columnIndex) { 1166 checkClosed(); 1167 lock(); 1168 scope(exit) unlock(); 1169 Variant v = getValue(columnIndex); 1170 if (lastIsNull) 1171 return 0; 1172 if (v.convertsTo!(ubyte)) 1173 return v.get!(ubyte); 1174 if (v.convertsTo!(long)) 1175 return to!ubyte(v.get!(long)); 1176 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to ubyte"); 1177 } 1178 override byte getByte(int columnIndex) { 1179 checkClosed(); 1180 lock(); 1181 scope(exit) unlock(); 1182 Variant v = getValue(columnIndex); 1183 if (lastIsNull) 1184 return 0; 1185 if (v.convertsTo!(byte)) 1186 return v.get!(byte); 1187 if (v.convertsTo!(long)) 1188 return to!byte(v.get!(long)); 1189 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to byte"); 1190 } 1191 override short getShort(int columnIndex) { 1192 checkClosed(); 1193 lock(); 1194 scope(exit) unlock(); 1195 Variant v = getValue(columnIndex); 1196 if (lastIsNull) 1197 return 0; 1198 if (v.convertsTo!(short)) 1199 return v.get!(short); 1200 if (v.convertsTo!(long)) 1201 return to!short(v.get!(long)); 1202 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to short"); 1203 } 1204 override ushort getUshort(int columnIndex) { 1205 checkClosed(); 1206 lock(); 1207 scope(exit) unlock(); 1208 Variant v = getValue(columnIndex); 1209 if (lastIsNull) 1210 return 0; 1211 if (v.convertsTo!(ushort)) 1212 return v.get!(ushort); 1213 if (v.convertsTo!(long)) 1214 return to!ushort(v.get!(long)); 1215 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to ushort"); 1216 } 1217 override int getInt(int columnIndex) { 1218 checkClosed(); 1219 lock(); 1220 scope(exit) unlock(); 1221 Variant v = getValue(columnIndex); 1222 if (lastIsNull) 1223 return 0; 1224 if (v.convertsTo!(int)) 1225 return v.get!(int); 1226 if (v.convertsTo!(long)) 1227 return to!int(v.get!(long)); 1228 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to int"); 1229 } 1230 override uint getUint(int columnIndex) { 1231 checkClosed(); 1232 lock(); 1233 scope(exit) unlock(); 1234 Variant v = getValue(columnIndex); 1235 if (lastIsNull) 1236 return 0; 1237 if (v.convertsTo!(uint)) 1238 return v.get!(uint); 1239 if (v.convertsTo!(ulong)) 1240 return to!uint(v.get!(ulong)); 1241 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to uint"); 1242 } 1243 override long getLong(int columnIndex) { 1244 checkClosed(); 1245 lock(); 1246 scope(exit) unlock(); 1247 Variant v = getValue(columnIndex); 1248 if (lastIsNull) 1249 return 0; 1250 if (v.convertsTo!(long)) 1251 return v.get!(long); 1252 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to long"); 1253 } 1254 override ulong getUlong(int columnIndex) { 1255 checkClosed(); 1256 lock(); 1257 scope(exit) unlock(); 1258 Variant v = getValue(columnIndex); 1259 if (lastIsNull) 1260 return 0; 1261 if (v.convertsTo!(ulong)) 1262 return v.get!(ulong); 1263 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to ulong"); 1264 } 1265 override double getDouble(int columnIndex) { 1266 checkClosed(); 1267 lock(); 1268 scope(exit) unlock(); 1269 Variant v = getValue(columnIndex); 1270 if (lastIsNull) 1271 return 0; 1272 if (v.convertsTo!(double)) 1273 return v.get!(double); 1274 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to double"); 1275 } 1276 override float getFloat(int columnIndex) { 1277 checkClosed(); 1278 lock(); 1279 scope(exit) unlock(); 1280 Variant v = getValue(columnIndex); 1281 if (lastIsNull) 1282 return 0; 1283 if (v.convertsTo!(float)) 1284 return v.get!(float); 1285 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to float"); 1286 } 1287 override byte[] getBytes(int columnIndex) { 1288 checkClosed(); 1289 lock(); 1290 scope(exit) unlock(); 1291 Variant v = getValue(columnIndex); 1292 if (lastIsNull) 1293 return null; 1294 if (v.convertsTo!(byte[])) { 1295 return v.get!(byte[]); 1296 } 1297 return byteaToBytes(v.toString()); 1298 } 1299 override ubyte[] getUbytes(int columnIndex) { 1300 checkClosed(); 1301 lock(); 1302 scope(exit) unlock(); 1303 Variant v = getValue(columnIndex); 1304 if (lastIsNull) 1305 return null; 1306 if (v.convertsTo!(ubyte[])) { 1307 return v.get!(ubyte[]); 1308 } 1309 return byteaToUbytes(v.toString()); 1310 } 1311 override string getString(int columnIndex) { 1312 checkClosed(); 1313 lock(); 1314 scope(exit) unlock(); 1315 Variant v = getValue(columnIndex); 1316 if (lastIsNull) 1317 return null; 1318 // if (v.convertsTo!(ubyte[])) { 1319 // // assume blob encoding is utf-8 1320 // // TODO: check field encoding 1321 // return decodeTextBlob(v.get!(ubyte[])); 1322 // } 1323 return v.toString(); 1324 } 1325 1326 override SysTime getSysTime(int columnIndex) { 1327 checkClosed(); 1328 lock(); 1329 scope(exit) unlock(); 1330 Variant v = getValue(columnIndex); 1331 if (lastIsNull) 1332 return Clock.currTime(); 1333 if (v.convertsTo!(SysTime)) { 1334 return v.get!SysTime(); 1335 } 1336 throw new SQLException("Cannot convert '" ~ v.toString() ~ "' to SysTime"); 1337 } 1338 1339 override DateTime getDateTime(int columnIndex) { 1340 checkClosed(); 1341 lock(); 1342 scope(exit) unlock(); 1343 Variant v = getValue(columnIndex); 1344 if (lastIsNull) 1345 return cast(DateTime) Clock.currTime(); 1346 if (v.convertsTo!(DateTime)) { 1347 return v.get!DateTime(); 1348 } 1349 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to DateTime. '" ~ v.toString() ~ "'"); 1350 } 1351 override Date getDate(int columnIndex) { 1352 checkClosed(); 1353 lock(); 1354 scope(exit) unlock(); 1355 Variant v = getValue(columnIndex); 1356 if (lastIsNull) 1357 return Date(); 1358 if (v.convertsTo!(Date)) { 1359 return v.get!Date(); 1360 } 1361 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to Date. '" ~ v.toString() ~ "'"); 1362 } 1363 override TimeOfDay getTime(int columnIndex) { 1364 checkClosed(); 1365 lock(); 1366 scope(exit) unlock(); 1367 Variant v = getValue(columnIndex); 1368 if (lastIsNull) 1369 return TimeOfDay(); 1370 if (v.convertsTo!(TimeOfDay)) { 1371 return v.get!TimeOfDay(); 1372 } 1373 throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to TimeOfDay. '" ~ v.toString() ~ "'"); 1374 } 1375 1376 override Variant getVariant(int columnIndex) { 1377 checkClosed(); 1378 lock(); 1379 scope(exit) unlock(); 1380 Variant v = getValue(columnIndex); 1381 if (lastIsNull) { 1382 Variant vnull = null; 1383 return vnull; 1384 } 1385 return v; 1386 } 1387 override bool wasNull() { 1388 checkClosed(); 1389 lock(); 1390 scope(exit) unlock(); 1391 return lastIsNull; 1392 } 1393 override bool isNull(int columnIndex) { 1394 checkClosed(); 1395 lock(); 1396 scope(exit) unlock(); 1397 enforceHelper!SQLException(columnIndex >= 1 && columnIndex <= columnCount, "Column index out of bounds: " ~ to!string(columnIndex)); 1398 enforceHelper!SQLException(currentRowIndex >= 0 && currentRowIndex < rowCount, "No current row in result set"); 1399 return data[currentRowIndex][columnIndex - 1] == null; 1400 } 1401 1402 //Retrieves the Statement object that produced this ResultSet object. 1403 override Statement getStatement() { 1404 checkClosed(); 1405 lock(); 1406 scope(exit) unlock(); 1407 return stmt; 1408 } 1409 1410 //Retrieves the current row number 1411 override int getRow() { 1412 checkClosed(); 1413 lock(); 1414 scope(exit) unlock(); 1415 if (currentRowIndex <0 || currentRowIndex >= rowCount) 1416 return 0; 1417 return currentRowIndex + 1; 1418 } 1419 1420 //Retrieves the fetch size for this ResultSet object. 1421 override ulong getFetchSize() { 1422 checkClosed(); 1423 lock(); 1424 scope(exit) unlock(); 1425 return rowCount; 1426 } 1427 } 1428 1429 1430 // sample URL: 1431 // mysql://localhost:3306/DatabaseName 1432 1433 //String url = "jdbc:postgresql://localhost/test"; 1434 //Properties props = new Properties(); 1435 //props.setProperty("user","fred"); 1436 //props.setProperty("password","secret"); 1437 //props.setProperty("ssl","true"); 1438 //Connection conn = DriverManager.getConnection(url, props); 1439 private __gshared static bool _pqIsLoaded = false; 1440 class PGSQLDriver : Driver { 1441 this() { 1442 if (!_pqIsLoaded) { 1443 DerelictPQ.load(); 1444 _pqIsLoaded = true; 1445 } 1446 } 1447 // helper function 1448 public static string generateUrl(string host, ushort port, string dbname) { 1449 return "ddbc:postgresql://" ~ host ~ ":" ~ to!string(port) ~ "/" ~ dbname; 1450 } 1451 public static string[string] setUserAndPassword(string username, string password) { 1452 string[string] params; 1453 params["user"] = username; 1454 params["password"] = password; 1455 params["ssl"] = "true"; 1456 return params; 1457 } 1458 override ddbc.core.Connection connect(string url, string[string] params) { 1459 url = stripDdbcPrefix(url); 1460 //writeln("PGSQLDriver.connect " ~ url); 1461 return new PGSQLConnection(url, params); 1462 } 1463 } 1464 1465 unittest { 1466 static if (PGSQL_TESTS_ENABLED) { 1467 1468 import std.conv; 1469 DataSource ds = createUnitTestPGSQLDataSource(); 1470 1471 auto conn = ds.getConnection(); 1472 assert(conn !is null); 1473 scope(exit) conn.close(); 1474 { 1475 //writeln("dropping table"); 1476 Statement stmt = conn.createStatement(); 1477 scope(exit) stmt.close(); 1478 stmt.executeUpdate("DROP TABLE IF EXISTS t1"); 1479 } 1480 { 1481 //writeln("creating table"); 1482 Statement stmt = conn.createStatement(); 1483 scope(exit) stmt.close(); 1484 stmt.executeUpdate("CREATE TABLE IF NOT EXISTS t1 (id SERIAL, name VARCHAR(255) NOT NULL, flags int null)"); 1485 //writeln("populating table"); 1486 Variant id = 0; 1487 assert(stmt.executeUpdate("INSERT INTO t1 (name) VALUES ('test1') returning id", id) == 1); 1488 assert(id.get!long > 0); 1489 } 1490 { 1491 PreparedStatement stmt = conn.prepareStatement("INSERT INTO t1 (name) VALUES ('test2') returning id"); 1492 scope(exit) stmt.close(); 1493 Variant id = 0; 1494 assert(stmt.executeUpdate(id) == 1); 1495 assert(id.get!long > 0); 1496 } 1497 { 1498 //writeln("reading table"); 1499 Statement stmt = conn.createStatement(); 1500 scope(exit) stmt.close(); 1501 ResultSet rs = stmt.executeQuery("SELECT id, name, flags FROM t1"); 1502 assert(rs.getMetaData().getColumnCount() == 3); 1503 assert(rs.getMetaData().getColumnName(1) == "id"); 1504 assert(rs.getMetaData().getColumnName(2) == "name"); 1505 assert(rs.getMetaData().getColumnName(3) == "flags"); 1506 scope(exit) rs.close(); 1507 //writeln("id" ~ "\t" ~ "name"); 1508 while (rs.next()) { 1509 long id = rs.getLong(1); 1510 string name = rs.getString(2); 1511 assert(rs.isNull(3)); 1512 //writeln("" ~ to!string(id) ~ "\t" ~ name); 1513 } 1514 } 1515 { 1516 //writeln("reading table"); 1517 Statement stmt = conn.createStatement(); 1518 scope(exit) stmt.close(); 1519 ResultSet rs = stmt.executeQuery("SELECT id, name, flags FROM t1"); 1520 assert(rs.getMetaData().getColumnCount() == 3); 1521 assert(rs.getMetaData().getColumnName(1) == "id"); 1522 assert(rs.getMetaData().getColumnName(2) == "name"); 1523 assert(rs.getMetaData().getColumnName(3) == "flags"); 1524 scope(exit) rs.close(); 1525 //writeln("id" ~ "\t" ~ "name"); 1526 while (rs.next()) { 1527 //writeln("calling getLong"); 1528 long id = rs.getLong(1); 1529 //writeln("done getLong"); 1530 string name = rs.getString(2); 1531 assert(rs.isNull(3)); 1532 //writeln("" ~ to!string(id) ~ "\t" ~ name); 1533 } 1534 } 1535 { 1536 //writeln("reading table with parameter id=1"); 1537 PreparedStatement stmt = conn.prepareStatement("SELECT id, name, flags FROM t1 WHERE id = ?"); 1538 scope(exit) stmt.close(); 1539 // assert(stmt.getMetaData().getColumnCount() == 3); 1540 // assert(stmt.getMetaData().getColumnName(1) == "id"); 1541 // assert(stmt.getMetaData().getColumnName(2) == "name"); 1542 // assert(stmt.getMetaData().getColumnName(3) == "flags"); 1543 //writeln("calling setLong"); 1544 stmt.setLong(1, 1); 1545 //writeln("done setLong"); 1546 { 1547 ResultSet rs = stmt.executeQuery(); 1548 scope(exit) rs.close(); 1549 //writeln("id" ~ "\t" ~ "name"); 1550 while (rs.next()) { 1551 long id = rs.getLong(1); 1552 string name = rs.getString(2); 1553 assert(rs.isNull(3)); 1554 //writeln("" ~ to!string(id) ~ "\t" ~ name); 1555 } 1556 } 1557 //writeln("changing parameter id=2"); 1558 //writeln("calling setLong"); 1559 stmt.setLong(1, 2); 1560 //writeln("done setLong"); 1561 { 1562 ResultSet rs = stmt.executeQuery(); 1563 scope(exit) rs.close(); 1564 //writeln("id" ~ "\t" ~ "name"); 1565 while (rs.next()) { 1566 long id = rs.getLong(1); 1567 string name = rs.getString(2); 1568 //writeln("" ~ to!string(id) ~ "\t" ~ name); 1569 } 1570 } 1571 } 1572 } 1573 } 1574 1575 __gshared static this() { 1576 // register PGSQLDriver 1577 import ddbc.common; 1578 DriverFactory.registerDriverFactory("postgresql", delegate() { return new PGSQLDriver(); }); 1579 } 1580 1581 } else { // version(USE_PGSQL) 1582 immutable bool PGSQL_TESTS_ENABLED = false; 1583 }