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