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