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 * 10 * JDBC documentation can be found here: 11 * $(LINK http://docs.oracle.com/javase/1.5.0/docs/api/java/sql/package-summary.html)$(BR) 12 * 13 * This module contains implementation of SQLite Driver 14 * 15 * You can find usage examples in unittest{} sections. 16 * 17 * Copyright: Copyright 2013 18 * License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 19 * Author: Vadim Lopatin 20 */ 21 module ddbc.drivers.sqliteddbc; 22 23 24 version(USE_SQLITE) { 25 26 import std.algorithm; 27 import std.conv; 28 import std.datetime; 29 import std.exception; 30 import std.stdio; 31 import std.string; 32 import std.variant; 33 import core.sync.mutex; 34 import ddbc.common; 35 import ddbc.core; 36 //import ddbc.drivers.sqlite; 37 import ddbc.drivers.utils; 38 import etc.c.sqlite3; 39 40 41 version (Windows) { 42 pragma (lib, "sqlite3.lib"); 43 } else version (linux) { 44 pragma (lib, "sqlite3"); 45 } else version (Posix) { 46 pragma (lib, "libsqlite3.so"); 47 } else version (darwin) { 48 pragma (lib, "libsqlite3.so"); 49 } else { 50 pragma (msg, "You will need to manually link in the SQLite library."); 51 } 52 53 version(unittest) { 54 /* 55 To allow unit tests using PostgreSQL server, 56 */ 57 /// change to false to disable tests on real PostgreSQL server 58 immutable bool SQLITE_TESTS_ENABLED = true; 59 /// change parameters if necessary 60 const string SQLITE_UNITTEST_FILENAME = "ddbctest.sqlite"; 61 62 static if (SQLITE_TESTS_ENABLED) { 63 /// use this data source for tests 64 DataSource createUnitTestSQLITEDataSource() { 65 SQLITEDriver driver = new SQLITEDriver(); 66 string[string] params; 67 return new ConnectionPoolDataSourceImpl(driver, SQLITE_UNITTEST_FILENAME, params); 68 } 69 } 70 } 71 72 class SQLITEConnection : ddbc.core.Connection { 73 private: 74 string filename; 75 76 sqlite3 * conn; 77 78 bool closed; 79 bool autocommit; 80 Mutex mutex; 81 82 83 SQLITEStatement [] activeStatements; 84 85 void closeUnclosedStatements() { 86 SQLITEStatement [] list = activeStatements.dup; 87 foreach(stmt; list) { 88 stmt.close(); 89 } 90 } 91 92 void checkClosed() { 93 if (closed) 94 throw new SQLException("Connection is already closed"); 95 } 96 97 public: 98 99 private string getError() { 100 return copyCString(sqlite3_errmsg(conn)); 101 } 102 103 void lock() { 104 mutex.lock(); 105 } 106 107 void unlock() { 108 mutex.unlock(); 109 } 110 111 sqlite3 * getConnection() { return conn; } 112 113 114 void onStatementClosed(SQLITEStatement stmt) { 115 foreach(index, item; activeStatements) { 116 if (item == stmt) { 117 remove(activeStatements, index); 118 return; 119 } 120 } 121 } 122 123 this(string url, string[string] params) { 124 mutex = new Mutex(); 125 if (url.startsWith("sqlite::")) 126 url = url[8 .. $]; 127 this.filename = url; 128 //writeln("trying to connect"); 129 int res = sqlite3_open(toStringz(filename), &conn); 130 if(res != SQLITE_OK) 131 throw new SQLException("SQLITE Error " ~ to!string(res) ~ " while trying to open DB " ~ filename ~ " : " ~ getError()); 132 assert(conn !is null); 133 closed = false; 134 setAutoCommit(true); 135 } 136 137 override void close() { 138 checkClosed(); 139 140 lock(); 141 scope(exit) unlock(); 142 143 closeUnclosedStatements(); 144 int res = sqlite3_close(conn); 145 if (res != SQLITE_OK) 146 throw new SQLException("SQLITE Error " ~ to!string(res) ~ " while trying to close DB " ~ filename ~ " : " ~ getError()); 147 closed = true; 148 } 149 150 override void commit() { 151 checkClosed(); 152 153 lock(); 154 scope(exit) unlock(); 155 156 Statement stmt = createStatement(); 157 scope(exit) stmt.close(); 158 stmt.executeUpdate("COMMIT"); 159 } 160 161 override Statement createStatement() { 162 checkClosed(); 163 164 lock(); 165 scope(exit) unlock(); 166 167 SQLITEStatement stmt = new SQLITEStatement(this); 168 activeStatements ~= stmt; 169 return stmt; 170 } 171 172 PreparedStatement prepareStatement(string sql) { 173 checkClosed(); 174 175 lock(); 176 scope(exit) unlock(); 177 178 SQLITEPreparedStatement stmt = new SQLITEPreparedStatement(this, sql); 179 activeStatements ~= stmt; 180 return stmt; 181 } 182 183 override string getCatalog() { 184 return "default"; 185 } 186 187 /// Sets the given catalog name in order to select a subspace of this Connection object's database in which to work. 188 override void setCatalog(string catalog) { 189 checkClosed(); 190 throw new SQLException("Not implemented"); 191 } 192 193 override bool isClosed() { 194 return closed; 195 } 196 197 override void rollback() { 198 checkClosed(); 199 200 lock(); 201 scope(exit) unlock(); 202 203 Statement stmt = createStatement(); 204 scope(exit) stmt.close(); 205 //TODO: 206 //stmt.executeUpdate("ROLLBACK"); 207 } 208 override bool getAutoCommit() { 209 return autocommit; 210 } 211 override void setAutoCommit(bool autoCommit) { 212 checkClosed(); 213 if (this.autocommit == autoCommit) 214 return; 215 lock(); 216 scope(exit) unlock(); 217 218 Statement stmt = createStatement(); 219 scope(exit) stmt.close(); 220 //TODO: 221 //stmt.executeUpdate("SET autocommit = " ~ (autoCommit ? "ON" : "OFF")); 222 this.autocommit = autoCommit; 223 } 224 } 225 226 class SQLITEStatement : Statement { 227 private: 228 SQLITEConnection conn; 229 // Command * cmd; 230 // ddbc.drivers.mysql.ResultSet rs; 231 SQLITEResultSet resultSet; 232 233 bool closed; 234 235 public: 236 void checkClosed() { 237 enforceEx!SQLException(!closed, "Statement is already closed"); 238 } 239 240 void lock() { 241 conn.lock(); 242 } 243 244 void unlock() { 245 conn.unlock(); 246 } 247 248 this(SQLITEConnection conn) { 249 this.conn = conn; 250 } 251 252 public: 253 SQLITEConnection getConnection() { 254 checkClosed(); 255 return conn; 256 } 257 258 private PreparedStatement _currentStatement; 259 private ResultSet _currentResultSet; 260 261 private void closePreparedStatement() { 262 if (_currentResultSet !is null) { 263 _currentResultSet.close(); 264 _currentResultSet = null; 265 } 266 if (_currentStatement !is null) { 267 _currentStatement.close(); 268 _currentStatement = null; 269 } 270 } 271 272 override ddbc.core.ResultSet executeQuery(string query) { 273 closePreparedStatement(); 274 _currentStatement = conn.prepareStatement(query); 275 _currentResultSet = _currentStatement.executeQuery(); 276 return _currentResultSet; 277 } 278 279 // string getError() { 280 // return copyCString(PQerrorMessage(conn.getConnection())); 281 // } 282 283 override int executeUpdate(string query) { 284 Variant dummy; 285 return executeUpdate(query, dummy); 286 } 287 288 override int executeUpdate(string query, out Variant insertId) { 289 closePreparedStatement(); 290 _currentStatement = conn.prepareStatement(query); 291 return _currentStatement.executeUpdate(insertId); 292 } 293 294 override void close() { 295 checkClosed(); 296 lock(); 297 scope(exit) unlock(); 298 closePreparedStatement(); 299 closed = true; 300 } 301 302 void closeResultSet() { 303 } 304 } 305 306 class SQLITEPreparedStatement : SQLITEStatement, PreparedStatement { 307 string query; 308 int paramCount; 309 310 sqlite3_stmt * stmt; 311 312 bool done; 313 bool preparing; 314 315 ResultSetMetaData metadata; 316 ParameterMetaData paramMetadata; 317 this(SQLITEConnection conn, string query) { 318 super(conn); 319 this.query = query; 320 321 int res = sqlite3_prepare_v2( 322 conn.getConnection(), /* Database handle */ 323 toStringz(query), /* SQL statement, UTF-8 encoded */ 324 cast(int)query.length, /* Maximum length of zSql in bytes. */ 325 &stmt, /* OUT: Statement handle */ 326 null /* OUT: Pointer to unused portion of zSql */ 327 ); 328 enforceEx!SQLException(res == SQLITE_OK, "Error #" ~ to!string(res) ~ " while preparing statement " ~ query ~ " : " ~ conn.getError()); 329 paramMetadata = createParamMetadata(); 330 paramCount = paramMetadata.getParameterCount(); 331 metadata = createMetadata(); 332 resetParams(); 333 preparing = true; 334 } 335 bool[] paramIsSet; 336 void resetParams() { 337 paramIsSet = new bool[paramCount]; 338 } 339 // before execution of query 340 private void allParamsSet() { 341 for(int i = 0; i < paramCount; i++) { 342 enforceEx!SQLException(paramIsSet[i], "Parameter " ~ to!string(i + 1) ~ " is not set"); 343 } 344 if (preparing) { 345 preparing = false; 346 } else { 347 closeResultSet(); 348 sqlite3_reset(stmt); 349 } 350 } 351 // before setting any parameter 352 private void checkIndex(int index) { 353 if (index < 1 || index > paramCount) 354 throw new SQLException("Parameter index " ~ to!string(index) ~ " is out of range"); 355 if (!preparing) { 356 closeResultSet(); 357 sqlite3_reset(stmt); 358 preparing = true; 359 } 360 } 361 ref Variant getParam(int index) { 362 throw new SQLException("Not implemented"); 363 // checkIndex(index); 364 // return cmd.param(cast(ushort)(index - 1)); 365 } 366 public: 367 SqlType sqliteToSqlType(int t) { 368 switch(t) { 369 case SQLITE_INTEGER: return SqlType.BIGINT; 370 case SQLITE_FLOAT: return SqlType.DOUBLE; 371 case SQLITE3_TEXT: return SqlType.VARCHAR; 372 case SQLITE_BLOB: return SqlType.BLOB; 373 case SQLITE_NULL: return SqlType.NULL; 374 default: 375 return SqlType.BLOB; 376 } 377 } 378 379 ResultSetMetaData createMetadata() { 380 int fieldCount = sqlite3_column_count(stmt); 381 ColumnMetadataItem[] list = new ColumnMetadataItem[fieldCount]; 382 for(int i = 0; i < fieldCount; i++) { 383 ColumnMetadataItem item = new ColumnMetadataItem(); 384 item.label = copyCString(sqlite3_column_origin_name(stmt, i)); 385 item.name = copyCString(sqlite3_column_name(stmt, i)); 386 item.schemaName = copyCString(sqlite3_column_database_name(stmt, i)); 387 item.tableName = copyCString(sqlite3_column_table_name(stmt, i)); 388 item.type = sqliteToSqlType(sqlite3_column_type(stmt, i)); 389 list[i] = item; 390 } 391 return new ResultSetMetaDataImpl(list); 392 } 393 394 ParameterMetaData createParamMetadata() { 395 int fieldCount = sqlite3_bind_parameter_count(stmt); 396 ParameterMetaDataItem[] res = new ParameterMetaDataItem[fieldCount]; 397 for(int i = 0; i < fieldCount; i++) { 398 ParameterMetaDataItem item = new ParameterMetaDataItem(); 399 item.type = SqlType.VARCHAR; 400 res[i] = item; 401 } 402 paramCount = fieldCount; 403 return new ParameterMetaDataImpl(res); 404 } 405 406 override void close() { 407 checkClosed(); 408 lock(); 409 scope(exit) unlock(); 410 411 closeResultSet(); 412 int res = sqlite3_finalize(stmt); 413 enforceEx!SQLException(res == SQLITE_OK, "Error #" ~ to!string(res) ~ " while closing prepared statement " ~ query ~ " : " ~ conn.getError()); 414 closed = true; 415 } 416 417 418 /// Retrieves a ResultSetMetaData object that contains information about the columns of the ResultSet object that will be returned when this PreparedStatement object is executed. 419 override ResultSetMetaData getMetaData() { 420 checkClosed(); 421 lock(); 422 scope(exit) unlock(); 423 return metadata; 424 } 425 426 /// Retrieves the number, types and properties of this PreparedStatement object's parameters. 427 override ParameterMetaData getParameterMetaData() { 428 checkClosed(); 429 lock(); 430 scope(exit) unlock(); 431 return paramMetadata; 432 } 433 434 override int executeUpdate(out Variant insertId) { 435 //throw new SQLException("Not implemented"); 436 checkClosed(); 437 lock(); 438 scope(exit) unlock(); 439 allParamsSet(); 440 441 int rowsAffected = 0; 442 int res = sqlite3_step(stmt); 443 if (res == SQLITE_DONE) { 444 insertId = Variant(sqlite3_last_insert_rowid(conn.getConnection())); 445 rowsAffected = sqlite3_changes(conn.getConnection()); 446 done = true; 447 } else if (res == SQLITE_ROW) { 448 // row is available 449 rowsAffected = -1; 450 } else { 451 enforceEx!SQLException(false, "Error #" ~ to!string(res) ~ " while trying to execute prepared statement: " ~ " : " ~ conn.getError()); 452 } 453 return rowsAffected; 454 } 455 456 override int executeUpdate() { 457 Variant insertId; 458 return executeUpdate(insertId); 459 } 460 461 override ddbc.core.ResultSet executeQuery() { 462 checkClosed(); 463 lock(); 464 scope(exit) unlock(); 465 allParamsSet(); 466 enforceEx!SQLException(metadata.getColumnCount() > 0, "Query doesn't return result set"); 467 resultSet = new SQLITEResultSet(this, stmt, getMetaData()); 468 return resultSet; 469 } 470 471 override void clearParameters() { 472 throw new SQLException("Not implemented"); 473 // checkClosed(); 474 // lock(); 475 // scope(exit) unlock(); 476 // for (int i = 1; i <= paramCount; i++) 477 // setNull(i); 478 } 479 480 override void setFloat(int parameterIndex, float x) { 481 setDouble(parameterIndex, x); 482 } 483 override void setDouble(int parameterIndex, double x){ 484 checkClosed(); 485 lock(); 486 scope(exit) unlock(); 487 checkIndex(parameterIndex); 488 sqlite3_bind_double(stmt, parameterIndex, x); 489 paramIsSet[parameterIndex - 1] = true; 490 } 491 override void setBoolean(int parameterIndex, bool x) { 492 setLong(parameterIndex, x ? 1 : 0); 493 } 494 override void setLong(int parameterIndex, long x) { 495 checkClosed(); 496 lock(); 497 scope(exit) unlock(); 498 checkIndex(parameterIndex); 499 sqlite3_bind_int64(stmt, parameterIndex, x); 500 paramIsSet[parameterIndex - 1] = true; 501 } 502 override void setUlong(int parameterIndex, ulong x) { 503 setLong(parameterIndex, cast(long)x); 504 } 505 override void setInt(int parameterIndex, int x) { 506 setLong(parameterIndex, cast(long)x); 507 } 508 override void setUint(int parameterIndex, uint x) { 509 setLong(parameterIndex, cast(long)x); 510 } 511 override void setShort(int parameterIndex, short x) { 512 setLong(parameterIndex, cast(long)x); 513 } 514 override void setUshort(int parameterIndex, ushort x) { 515 setLong(parameterIndex, cast(long)x); 516 } 517 override void setByte(int parameterIndex, byte x) { 518 setLong(parameterIndex, cast(long)x); 519 } 520 override void setUbyte(int parameterIndex, ubyte x) { 521 setLong(parameterIndex, cast(long)x); 522 } 523 override void setBytes(int parameterIndex, byte[] x) { 524 checkClosed(); 525 lock(); 526 scope(exit) unlock(); 527 checkIndex(parameterIndex); 528 if (x is null) { 529 setNull(parameterIndex); 530 return; 531 } 532 sqlite3_bind_blob(stmt, parameterIndex, cast(const (void *))x.ptr, cast(int)x.length, SQLITE_TRANSIENT); 533 paramIsSet[parameterIndex - 1] = true; 534 } 535 override void setUbytes(int parameterIndex, ubyte[] x) { 536 checkClosed(); 537 lock(); 538 scope(exit) unlock(); 539 checkIndex(parameterIndex); 540 if (x is null) { 541 setNull(parameterIndex); 542 return; 543 } 544 sqlite3_bind_blob(stmt, parameterIndex, cast(const char *)x.ptr, cast(int)x.length, SQLITE_TRANSIENT); 545 paramIsSet[parameterIndex - 1] = true; 546 } 547 override void setString(int parameterIndex, string x) { 548 checkClosed(); 549 lock(); 550 scope(exit) unlock(); 551 checkIndex(parameterIndex); 552 if (x is null) { 553 setNull(parameterIndex); 554 return; 555 } 556 sqlite3_bind_text(stmt, parameterIndex, cast(const char *)x.ptr, cast(int)x.length, SQLITE_TRANSIENT); 557 paramIsSet[parameterIndex - 1] = true; 558 } 559 override void setDateTime(int parameterIndex, DateTime x) { 560 setString(parameterIndex, x.toISOString()); 561 } 562 override void setDate(int parameterIndex, Date x) { 563 setString(parameterIndex, x.toISOString()); 564 } 565 override void setTime(int parameterIndex, TimeOfDay x) { 566 setString(parameterIndex, x.toISOString()); 567 } 568 override void setVariant(int parameterIndex, Variant x) { 569 if (x == null) 570 setNull(parameterIndex); 571 else if (x.convertsTo!long) 572 setLong(parameterIndex, x.get!long); 573 else if (x.convertsTo!ulong) 574 setLong(parameterIndex, x.get!ulong); 575 else if (x.convertsTo!double) 576 setDouble(parameterIndex, x.get!double); 577 else if (x.convertsTo!(byte[])) 578 setBytes(parameterIndex, x.get!(byte[])); 579 else if (x.convertsTo!(ubyte[])) 580 setUbytes(parameterIndex, x.get!(ubyte[])); 581 else if (x.convertsTo!DateTime) 582 setDateTime(parameterIndex, x.get!DateTime); 583 else if (x.convertsTo!Date) 584 setDate(parameterIndex, x.get!Date); 585 else if (x.convertsTo!TimeOfDay) 586 setTime(parameterIndex, x.get!TimeOfDay); 587 else 588 setString(parameterIndex, x.toString()); 589 } 590 override void setNull(int parameterIndex) { 591 checkClosed(); 592 lock(); 593 scope(exit) unlock(); 594 checkIndex(parameterIndex); 595 sqlite3_bind_null(stmt, parameterIndex); 596 paramIsSet[parameterIndex - 1] = true; 597 } 598 override void setNull(int parameterIndex, int sqlType) { 599 setNull(parameterIndex); 600 } 601 } 602 603 class SQLITEResultSet : ResultSetImpl { 604 private SQLITEStatement stmt; 605 private sqlite3_stmt * rs; 606 ResultSetMetaData metadata; 607 private bool closed; 608 private int currentRowIndex; 609 // private int rowCount; 610 private int[string] columnMap; 611 private bool lastIsNull; 612 private int columnCount; 613 614 private bool _last; 615 private bool _first; 616 617 // checks index, updates lastIsNull, returns column type 618 int checkIndex(int columnIndex) { 619 enforceEx!SQLException(columnIndex >= 1 && columnIndex <= columnCount, "Column index out of bounds: " ~ to!string(columnIndex)); 620 int res = sqlite3_column_type(rs, columnIndex - 1); 621 lastIsNull = (res == SQLITE_NULL); 622 return res; 623 } 624 625 void checkClosed() { 626 if (closed) 627 throw new SQLException("Result set is already closed"); 628 } 629 630 public: 631 632 void lock() { 633 stmt.lock(); 634 } 635 636 void unlock() { 637 stmt.unlock(); 638 } 639 640 this(SQLITEStatement stmt, sqlite3_stmt * rs, ResultSetMetaData metadata) { 641 this.stmt = stmt; 642 this.rs = rs; 643 this.metadata = metadata; 644 closed = false; 645 this.columnCount = sqlite3_data_count(rs); //metadata.getColumnCount(); 646 for (int i=0; i<columnCount; i++) { 647 columnMap[metadata.getColumnName(i + 1)] = i; 648 } 649 currentRowIndex = -1; 650 _first = true; 651 } 652 653 void onStatementClosed() { 654 closed = true; 655 } 656 string decodeTextBlob(ubyte[] data) { 657 char[] res = new char[data.length]; 658 foreach (i, ch; data) { 659 res[i] = cast(char)ch; 660 } 661 return to!string(res); 662 } 663 664 // ResultSet interface implementation 665 666 //Retrieves the number, types and properties of this ResultSet object's columns 667 override ResultSetMetaData getMetaData() { 668 checkClosed(); 669 lock(); 670 scope(exit) unlock(); 671 return metadata; 672 } 673 674 override void close() { 675 if (closed) 676 return; 677 checkClosed(); 678 lock(); 679 scope(exit) unlock(); 680 stmt.closeResultSet(); 681 closed = true; 682 } 683 override bool first() { 684 checkClosed(); 685 lock(); 686 scope(exit) unlock(); 687 throw new SQLException("Not implemented"); 688 } 689 override bool isFirst() { 690 checkClosed(); 691 lock(); 692 scope(exit) unlock(); 693 return _first; 694 } 695 override bool isLast() { 696 checkClosed(); 697 lock(); 698 scope(exit) unlock(); 699 return _last; 700 } 701 702 override bool next() { 703 checkClosed(); 704 lock(); 705 scope(exit) unlock(); 706 707 if (_first) { 708 _first = false; 709 //writeln("next() first time invocation, columnCount=" ~ to!string(columnCount)); 710 //return columnCount > 0; 711 } 712 713 int res = sqlite3_step(rs); 714 if (res == SQLITE_DONE) { 715 _last = true; 716 columnCount = sqlite3_data_count(rs); 717 //writeln("sqlite3_step = SQLITE_DONE columnCount=" ~ to!string(columnCount)); 718 // end of data 719 return columnCount > 0; 720 } else if (res == SQLITE_ROW) { 721 //writeln("sqlite3_step = SQLITE_ROW"); 722 // have a row 723 currentRowIndex++; 724 columnCount = sqlite3_data_count(rs); 725 return true; 726 } else { 727 enforceEx!SQLException(false, "Error #" ~ to!string(res) ~ " while reading query result: " ~ copyCString(sqlite3_errmsg(stmt.conn.getConnection()))); 728 return false; 729 } 730 } 731 732 override int findColumn(string columnName) { 733 checkClosed(); 734 lock(); 735 scope(exit) unlock(); 736 int * p = (columnName in columnMap); 737 if (!p) 738 throw new SQLException("Column " ~ columnName ~ " not found"); 739 return *p + 1; 740 } 741 742 override bool getBoolean(int columnIndex) { 743 return getLong(columnIndex) != 0; 744 } 745 override ubyte getUbyte(int columnIndex) { 746 return cast(ubyte)getLong(columnIndex); 747 } 748 override byte getByte(int columnIndex) { 749 return cast(byte)getLong(columnIndex); 750 } 751 override short getShort(int columnIndex) { 752 return cast(short)getLong(columnIndex); 753 } 754 override ushort getUshort(int columnIndex) { 755 return cast(ushort)getLong(columnIndex); 756 } 757 override int getInt(int columnIndex) { 758 return cast(int)getLong(columnIndex); 759 } 760 override uint getUint(int columnIndex) { 761 return cast(uint)getLong(columnIndex); 762 } 763 override long getLong(int columnIndex) { 764 checkClosed(); 765 checkIndex(columnIndex); 766 lock(); 767 scope(exit) unlock(); 768 auto v = sqlite3_column_int64(rs, columnIndex - 1); 769 return v; 770 } 771 override ulong getUlong(int columnIndex) { 772 return cast(ulong)getLong(columnIndex); 773 } 774 override double getDouble(int columnIndex) { 775 checkClosed(); 776 checkIndex(columnIndex); 777 lock(); 778 scope(exit) unlock(); 779 auto v = sqlite3_column_double(rs, columnIndex - 1); 780 return v; 781 } 782 override float getFloat(int columnIndex) { 783 return cast(float)getDouble(columnIndex); 784 } 785 override byte[] getBytes(int columnIndex) { 786 checkClosed(); 787 checkIndex(columnIndex); 788 lock(); 789 scope(exit) unlock(); 790 const byte * bytes = cast(const byte *)sqlite3_column_blob(rs, columnIndex - 1); 791 int len = sqlite3_column_bytes(rs, columnIndex - 1); 792 byte[] res = new byte[len]; 793 for (int i=0; i<len; i++) 794 res[i] = bytes[i]; 795 return res; 796 } 797 override ubyte[] getUbytes(int columnIndex) { 798 checkClosed(); 799 checkIndex(columnIndex); 800 lock(); 801 scope(exit) unlock(); 802 const ubyte * bytes = cast(const ubyte *)sqlite3_column_blob(rs, columnIndex - 1); 803 int len = sqlite3_column_bytes(rs, columnIndex - 1); 804 ubyte[] res = new ubyte[len]; 805 for (int i=0; i<len; i++) 806 res[i] = bytes[i]; 807 return res; 808 } 809 override string getString(int columnIndex) { 810 checkClosed(); 811 checkIndex(columnIndex); 812 lock(); 813 scope(exit) unlock(); 814 const char * bytes = cast(const char *)sqlite3_column_text(rs, columnIndex - 1); 815 int len = sqlite3_column_bytes(rs, columnIndex - 1); 816 char[] res = new char[len]; 817 for (int i=0; i<len; i++) 818 res[i] = bytes[i]; 819 return cast(string)res; 820 } 821 override DateTime getDateTime(int columnIndex) { 822 string s = getString(columnIndex); 823 DateTime dt; 824 if (s is null) 825 return dt; 826 try { 827 return DateTime.fromISOString(s); 828 } catch (Throwable e) { 829 throw new SQLException("Cannot convert string to DateTime - " ~ s); 830 } 831 } 832 override Date getDate(int columnIndex) { 833 string s = getString(columnIndex); 834 Date dt; 835 if (s is null) 836 return dt; 837 try { 838 return Date.fromISOString(s); 839 } catch (Throwable e) { 840 throw new SQLException("Cannot convert string to DateTime - " ~ s); 841 } 842 } 843 override TimeOfDay getTime(int columnIndex) { 844 string s = getString(columnIndex); 845 TimeOfDay dt; 846 if (s is null) 847 return dt; 848 try { 849 return TimeOfDay.fromISOString(s); 850 } catch (Throwable e) { 851 throw new SQLException("Cannot convert string to DateTime - " ~ s); 852 } 853 } 854 855 override Variant getVariant(int columnIndex) { 856 checkClosed(); 857 int type = checkIndex(columnIndex); 858 lock(); 859 scope(exit) unlock(); 860 Variant v = null; 861 if (lastIsNull) 862 return v; 863 switch (type) { 864 case SQLITE_INTEGER: 865 v = getLong(columnIndex); 866 break; 867 case SQLITE_FLOAT: 868 v = getDouble(columnIndex); 869 break; 870 case SQLITE3_TEXT: 871 v = getString(columnIndex); 872 break; 873 case SQLITE_BLOB: 874 v = getUbytes(columnIndex); 875 break; 876 default: 877 break; 878 } 879 return v; 880 } 881 override bool wasNull() { 882 checkClosed(); 883 lock(); 884 scope(exit) unlock(); 885 return lastIsNull; 886 } 887 override bool isNull(int columnIndex) { 888 checkClosed(); 889 lock(); 890 scope(exit) unlock(); 891 checkIndex(columnIndex); 892 return lastIsNull; 893 } 894 895 //Retrieves the Statement object that produced this ResultSet object. 896 override Statement getStatement() { 897 checkClosed(); 898 lock(); 899 scope(exit) unlock(); 900 return stmt; 901 } 902 903 //Retrieves the current row number 904 override int getRow() { 905 checkClosed(); 906 lock(); 907 scope(exit) unlock(); 908 if (currentRowIndex <0) 909 return 0; 910 return currentRowIndex + 1; 911 } 912 913 //Retrieves the fetch size for this ResultSet object. 914 override int getFetchSize() { 915 checkClosed(); 916 lock(); 917 scope(exit) unlock(); 918 return -1; 919 } 920 } 921 922 923 // sample URL: 924 // mysql://localhost:3306/DatabaseName 925 926 //String url = "jdbc:postgresql://localhost/test"; 927 //Properties props = new Properties(); 928 //props.setProperty("user","fred"); 929 //props.setProperty("password","secret"); 930 //props.setProperty("ssl","true"); 931 //Connection conn = DriverManager.getConnection(url, props); 932 class SQLITEDriver : Driver { 933 // helper function 934 public static string generateUrl(string host, ushort port, string dbname) { 935 return "postgresql://" ~ host ~ ":" ~ to!string(port) ~ "/" ~ dbname; 936 } 937 public static string[string] setUserAndPassword(string username, string password) { 938 string[string] params; 939 params["user"] = username; 940 params["password"] = password; 941 params["ssl"] = "true"; 942 return params; 943 } 944 override ddbc.core.Connection connect(string url, string[string] params) { 945 //writeln("SQLITEDriver.connect " ~ url); 946 return new SQLITEConnection(url, params); 947 } 948 } 949 950 unittest { 951 if (SQLITE_TESTS_ENABLED) { 952 953 import std.conv; 954 DataSource ds = createUnitTestSQLITEDataSource(); 955 //writeln("trying to open connection"); 956 auto conn = ds.getConnection(); 957 //writeln("connection is opened"); 958 assert(conn !is null); 959 scope(exit) conn.close(); 960 { 961 //writeln("dropping table"); 962 Statement stmt = conn.createStatement(); 963 scope(exit) stmt.close(); 964 stmt.executeUpdate("DROP TABLE IF EXISTS t1"); 965 } 966 { 967 //writeln("creating table"); 968 Statement stmt = conn.createStatement(); 969 scope(exit) stmt.close(); 970 stmt.executeUpdate("CREATE TABLE IF NOT EXISTS t1 (id INTEGER PRIMARY KEY, name VARCHAR(255) NOT NULL, flags int null)"); 971 } 972 { 973 //writeln("populating table"); 974 PreparedStatement stmt = conn.prepareStatement("INSERT INTO t1 (name) VALUES ('test1'), ('test2')"); 975 scope(exit) stmt.close(); 976 Variant id = 0; 977 assert(stmt.executeUpdate(id) == 2); 978 assert(id.get!long > 0); 979 } 980 { 981 //writeln("reading table"); 982 Statement stmt = conn.createStatement(); 983 scope(exit) stmt.close(); 984 ResultSet rs = stmt.executeQuery("SELECT id, name, flags FROM t1"); 985 assert(rs.getMetaData().getColumnCount() == 3); 986 assert(rs.getMetaData().getColumnName(1) == "id"); 987 assert(rs.getMetaData().getColumnName(2) == "name"); 988 assert(rs.getMetaData().getColumnName(3) == "flags"); 989 scope(exit) rs.close(); 990 //writeln("id" ~ "\t" ~ "name"); 991 while (rs.next()) { 992 long id = rs.getLong(1); 993 string name = rs.getString(2); 994 assert(rs.isNull(3)); 995 //writeln("" ~ to!string(id) ~ "\t" ~ name); 996 } 997 } 998 { 999 //writeln("reading table with parameter id=1"); 1000 PreparedStatement stmt = conn.prepareStatement("SELECT id, name, flags FROM t1 WHERE id = ?"); 1001 scope(exit) stmt.close(); 1002 assert(stmt.getMetaData().getColumnCount() == 3); 1003 assert(stmt.getMetaData().getColumnName(1) == "id"); 1004 assert(stmt.getMetaData().getColumnName(2) == "name"); 1005 assert(stmt.getMetaData().getColumnName(3) == "flags"); 1006 stmt.setLong(1, 1); 1007 { 1008 ResultSet rs = stmt.executeQuery(); 1009 scope(exit) rs.close(); 1010 //writeln("id" ~ "\t" ~ "name"); 1011 while (rs.next()) { 1012 long id = rs.getLong(1); 1013 string name = rs.getString(2); 1014 assert(rs.isNull(3)); 1015 //writeln("" ~ to!string(id) ~ "\t" ~ name); 1016 } 1017 } 1018 //writeln("changing parameter id=2"); 1019 stmt.setLong(1, 2); 1020 { 1021 ResultSet rs = stmt.executeQuery(); 1022 scope(exit) rs.close(); 1023 //writeln("id" ~ "\t" ~ "name"); 1024 while (rs.next()) { 1025 long id = rs.getLong(1); 1026 string name = rs.getString(2); 1027 //writeln("" ~ to!string(id) ~ "\t" ~ name); 1028 } 1029 } 1030 } 1031 } 1032 } 1033 1034 } else { // version(USE_SQLITE) 1035 version(unittest) { 1036 immutable bool SQLITE_TESTS_ENABLED = false; 1037 } 1038 }