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