1 /++ 2 Managing query results. 3 4 Authors: 5 Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) 6 7 Copyright: 8 Copyright 2011-18 Nicolas Sicard. 9 10 License: 11 $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 +/ 13 module d2sqlite3.results; 14 15 import d2sqlite3.database; 16 import d2sqlite3.statement; 17 import d2sqlite3.sqlite3; 18 import d2sqlite3.internal.util; 19 20 import std.conv : to; 21 import std.exception : enforce; 22 import std.string : format; 23 import std.typecons : Nullable; 24 25 /// Set _UnlockNotify version if compiled with SqliteEnableUnlockNotify or SqliteFakeUnlockNotify 26 version (SqliteEnableUnlockNotify) 27 version = _UnlockNotify; 28 else version (SqliteFakeUnlockNotify) 29 version = _UnlockNotify; 30 31 /++ 32 An input range interface to access the rows resulting from an SQL query. 33 34 The elements of the range are `Row` structs. A `Row` is just a view of the current 35 row when iterating the results of a `ResultRange`. It becomes invalid as soon as 36 `ResultRange.popFront()` is called (it contains undefined data afterwards). Use 37 `cached` to store the content of rows past the execution of the statement. 38 39 Instances of this struct are typically returned by `Database.execute()` or 40 `Statement.execute()`. 41 +/ 42 struct ResultRange { 43 private: 44 Statement statement; 45 int state = SQLITE_DONE; 46 int colCount = 0; 47 Row current; 48 49 package(d2sqlite3): 50 this(Statement statement) { 51 if (!statement.empty) { 52 version (_UnlockNotify) 53 state = sqlite3_blocking_step(statement); 54 else 55 state = sqlite3_step(statement.handle); 56 } else 57 state = SQLITE_DONE; 58 59 enforce(state == SQLITE_ROW || state == SQLITE_DONE, 60 new SqliteException(errmsg(statement.handle), state)); 61 62 this.statement = statement; 63 colCount = sqlite3_column_count(statement.handle); 64 current = Row(statement, colCount); 65 } 66 67 version (_UnlockNotify) { 68 auto sqlite3_blocking_step(Statement statement) { 69 int rc; 70 while (SQLITE_LOCKED == (rc = sqlite3_step(statement.handle))) { 71 rc = statement.waitForUnlockNotify(); 72 if (rc != SQLITE_OK) 73 break; 74 sqlite3_reset(statement.handle); 75 } 76 return rc; 77 } 78 } 79 80 public: 81 /++ 82 Range interface. 83 +/ 84 bool empty() @property { 85 return state == SQLITE_DONE; 86 } 87 88 /// ditto 89 ref Row front() return @property { 90 assert(!empty, "no rows available"); 91 return current; 92 } 93 94 /// ditto 95 void popFront() { 96 assert(!empty, "no rows available"); 97 version (_UnlockNotify) 98 state = sqlite3_blocking_step(statement); 99 else 100 state = sqlite3_step(statement.handle); 101 current = Row(statement, colCount); 102 enforce(state == SQLITE_DONE || state == SQLITE_ROW, 103 new SqliteException(errmsg(statement.handle), state)); 104 } 105 106 /++ 107 Gets only the first value of the first row returned by the execution of the statement. 108 +/ 109 auto oneValue(T)() { 110 return front.peek!T(0); 111 } 112 /// 113 unittest { 114 auto db = Database(":memory:"); 115 db.execute("CREATE TABLE test (val INTEGER)"); 116 auto count = db.execute("SELECT count(*) FROM test").oneValue!long; 117 assert(count == 0); 118 } 119 } 120 /// 121 unittest { 122 auto db = Database(":memory:"); 123 db.run("CREATE TABLE test (i INTEGER); 124 INSERT INTO test VALUES (1); 125 INSERT INTO test VALUES (2);"); 126 127 auto results = db.execute("SELECT * FROM test"); 128 assert(!results.empty); 129 assert(results.front.peek!long(0) == 1); 130 results.popFront(); 131 assert(!results.empty); 132 assert(results.front.peek!long(0) == 2); 133 results.popFront(); 134 assert(results.empty); 135 } 136 137 /++ 138 A row returned when stepping over an SQLite prepared statement. 139 140 The data of each column can be retrieved: 141 $(UL 142 $(LI using Row as a random-access range of ColumnData.) 143 $(LI using the more direct peek functions.) 144 ) 145 146 Warning: 147 The data of the row is invalid when the next row is accessed (after a call to 148 `ResultRange.popFront()`). 149 +/ 150 struct Row { 151 import std.traits : isBoolean, isIntegral, isSomeChar, isFloatingPoint, isSomeString, isArray; 152 import std.traits : isInstanceOf, TemplateArgsOf; 153 154 private: 155 Statement statement; 156 int frontIndex = 0; 157 int backIndex = -1; 158 159 this(Statement statement, int colCount) nothrow { 160 this.statement = statement; 161 backIndex = colCount - 1; 162 } 163 164 public: 165 /// Range interface. 166 bool empty() const @property nothrow { 167 return length == 0; 168 } 169 170 /// ditto 171 ColumnData front() @property { 172 assertInitialized(); 173 return opIndex(0); 174 } 175 176 /// ditto 177 void popFront() nothrow { 178 assertInitialized(); 179 frontIndex++; 180 } 181 182 /// ditto 183 Row save() @property { 184 return this; 185 } 186 187 /// ditto 188 ColumnData back() @property { 189 assertInitialized(); 190 return opIndex(backIndex - frontIndex); 191 } 192 193 /// ditto 194 void popBack() nothrow { 195 assertInitialized(); 196 backIndex--; 197 } 198 199 /// ditto 200 size_t length() const @property nothrow { 201 return backIndex - frontIndex + 1; 202 } 203 204 /// ditto 205 ColumnData opIndex(size_t index) { 206 assertInitialized(); 207 auto i = internalIndex(index); 208 auto type = sqlite3_column_type(statement.handle, i); 209 final switch (type) { 210 case SqliteType.INTEGER: 211 return ColumnData(peek!long(index)); 212 213 case SqliteType.FLOAT: 214 return ColumnData(peek!double(index)); 215 216 case SqliteType.TEXT: 217 return ColumnData(peek!string(index)); 218 219 case SqliteType.BLOB: 220 return ColumnData(peek!(Blob, PeekMode.copy)(index)); 221 222 case SqliteType.NULL: 223 return ColumnData(null); 224 } 225 } 226 227 /// Ditto 228 ColumnData opIndex(string columnName) { 229 return opIndex(indexForName(columnName)); 230 } 231 232 /++ 233 Returns the data of a column directly. 234 235 Contrary to `opIndex`, the `peek` functions return the data directly, automatically cast to T, 236 without the overhead of using a wrapping type (`ColumnData`). 237 238 When using `peek` to retrieve an array or a string, you can use either: 239 $(UL 240 $(LI `peek!(..., PeekMode.copy)(index)`, 241 in which case the function returns a copy of the data that will outlive the step 242 to the next row, 243 or) 244 $(LI `peek!(..., PeekMode.slice)(index)`, 245 in which case a slice of SQLite's internal buffer is returned (see Warnings).) 246 ) 247 248 Params: 249 T = The type of the returned data. T must be a boolean, a built-in numeric type, a 250 string, an array or a `Nullable`. 251 $(TABLE 252 $(TR 253 $(TH Condition on T) 254 $(TH Requested database type) 255 ) 256 $(TR 257 $(TD `isIntegral!T || isBoolean!T`) 258 $(TD INTEGER) 259 ) 260 $(TR 261 $(TD `isFloatingPoint!T`) 262 $(TD FLOAT) 263 ) 264 $(TR 265 $(TD `isSomeString!T`) 266 $(TD TEXT) 267 ) 268 $(TR 269 $(TD `isArray!T`) 270 $(TD BLOB) 271 ) 272 $(TR 273 $(TD `is(T == Nullable!U, U...)`) 274 $(TD NULL or U) 275 ) 276 ) 277 278 index = The index of the column in the prepared statement or 279 the name of the column, as specified in the prepared statement 280 with an AS clause. The index of the first column is 0. 281 282 Returns: 283 A value of type T. The returned value results from SQLite's own conversion rules: 284 see $(LINK http://www.sqlite.org/c3ref/column_blob.html) and 285 $(LINK http://www.sqlite.org/lang_expr.html#castexpr). It's then converted 286 to T using `std.conv.to!T`. 287 288 Warnings: 289 When using `PeekMode.slice`, the data of the slice will be $(B invalidated) 290 when the next row is accessed. A copy of the data has to be made somehow for it to 291 outlive the next step on the same statement. 292 293 When using referring to the column by name, the names of all the columns are 294 tested each time this function is called: use 295 numeric indexing for better performance. 296 +/ 297 T peek(T)(size_t index) if (isBoolean!T || isIntegral!T || isSomeChar!T) { 298 assertInitialized(); 299 return sqlite3_column_int64(statement.handle, internalIndex(index)).to!T; 300 } 301 302 /// ditto 303 T peek(T)(size_t index) if (isFloatingPoint!T) { 304 assertInitialized(); 305 return sqlite3_column_double(statement.handle, internalIndex(index)).to!T; 306 } 307 308 /// ditto 309 T peek(T, PeekMode mode = PeekMode.copy)(size_t index) if (isSomeString!T) { 310 import core.stdc.string : strlen, memcpy; 311 312 assertInitialized(); 313 auto i = internalIndex(index); 314 auto str = cast(const(char)*) sqlite3_column_text(statement.handle, i); 315 316 if (str is null) 317 return null; 318 319 auto length = strlen(str); 320 static if (mode == PeekMode.copy) { 321 char[] text; 322 text.length = length; 323 memcpy(text.ptr, str, length); 324 return text.to!T; 325 } else static if (mode == PeekMode.slice) 326 return cast(T) str[0 .. length]; 327 else 328 static assert(false); 329 } 330 331 /// ditto 332 T peek(T, PeekMode mode = PeekMode.copy)(size_t index) 333 if (isArray!T && !isSomeString!T) { 334 assertInitialized(); 335 auto i = internalIndex(index); 336 auto ptr = sqlite3_column_blob(statement.handle, i); 337 auto length = sqlite3_column_bytes(statement.handle, i); 338 static if (mode == PeekMode.copy) { 339 import core.stdc.string : memcpy; 340 341 ubyte[] blob; 342 blob.length = length; 343 memcpy(blob.ptr, ptr, length); 344 return cast(T) blob; 345 } else static if (mode == PeekMode.slice) 346 return cast(T) ptr[0 .. length]; 347 else 348 static assert(false); 349 } 350 351 /// ditto 352 T peek(T)(size_t index) 353 if (isInstanceOf!(Nullable, T) && !isArray!(TemplateArgsOf!T[0]) 354 && !isSomeString!(TemplateArgsOf!T[0])) { 355 assertInitialized(); 356 alias U = TemplateArgsOf!T[0]; 357 if (sqlite3_column_type(statement.handle, internalIndex(index)) == SqliteType.NULL) 358 return T.init; 359 return T(peek!U(index)); 360 } 361 362 /// ditto 363 T peek(T, PeekMode mode = PeekMode.copy)(size_t index) 364 if (isInstanceOf!(Nullable, T) && (isArray!(TemplateArgsOf!T[0]) 365 || isSomeString!(TemplateArgsOf!T[0]))) { 366 assertInitialized(); 367 alias U = TemplateArgsOf!T[0]; 368 if (sqlite3_column_type(statement.handle, internalIndex(index)) == SqliteType.NULL) 369 return T.init; 370 return T(peek!(U, mode)(index)); 371 } 372 373 /// ditto 374 T peek(T)(string columnName) { 375 return peek!T(indexForName(columnName)); 376 } 377 378 /++ 379 Determines the type of the data in a particular column. 380 381 `columnType` returns the type of the actual data in that column, whereas 382 `columnDeclaredTypeName` returns the name of the type as declared in the SELECT statement. 383 384 See_Also: $(LINK http://www.sqlite.org/c3ref/column_blob.html) and 385 $(LINK http://www.sqlite.org/c3ref/column_decltype.html). 386 +/ 387 SqliteType columnType(size_t index) { 388 assertInitialized(); 389 return cast(SqliteType) sqlite3_column_type(statement.handle, internalIndex(index)); 390 } 391 /// Ditto 392 SqliteType columnType(string columnName) { 393 return columnType(indexForName(columnName)); 394 } 395 /// Ditto 396 string columnDeclaredTypeName(size_t index) { 397 assertInitialized(); 398 return sqlite3_column_decltype(statement.handle, internalIndex(index)).to!string; 399 } 400 /// Ditto 401 string columnDeclaredTypeName(string columnName) { 402 return columnDeclaredTypeName(indexForName(columnName)); 403 } 404 /// 405 unittest { 406 auto db = Database(":memory:"); 407 db.run("CREATE TABLE items (name TEXT, price REAL); 408 INSERT INTO items VALUES ('car', 20000); 409 INSERT INTO items VALUES ('air', 'free');"); 410 411 auto results = db.execute("SELECT name, price FROM items"); 412 413 auto row = results.front; 414 assert(row.columnType(0) == SqliteType.TEXT); 415 assert(row.columnType("price") == SqliteType.FLOAT); 416 assert(row.columnDeclaredTypeName(0) == "TEXT"); 417 assert(row.columnDeclaredTypeName("price") == "REAL"); 418 419 results.popFront(); 420 row = results.front; 421 assert(row.columnType(0) == SqliteType.TEXT); 422 assert(row.columnType("price") == SqliteType.TEXT); 423 assert(row.columnDeclaredTypeName(0) == "TEXT"); 424 assert(row.columnDeclaredTypeName("price") == "REAL"); 425 } 426 427 /++ 428 Determines the name of a particular column. 429 430 See_Also: $(LINK http://www.sqlite.org/c3ref/column_name.html). 431 +/ 432 string columnName(size_t index) { 433 assertInitialized(); 434 return sqlite3_column_name(statement.handle, internalIndex(index)).to!string; 435 } 436 /// 437 unittest { 438 auto db = Database(":memory:"); 439 db.run("CREATE TABLE items (name TEXT, price REAL); 440 INSERT INTO items VALUES ('car', 20000);"); 441 442 auto row = db.execute("SELECT name, price FROM items").front; 443 assert(row.columnName(1) == "price"); 444 } 445 446 version (SqliteEnableColumnMetadata) { 447 /++ 448 Determines the name of the database, table, or column that is the origin of a 449 particular result column in SELECT statement. 450 451 Warning: 452 These methods are defined only when this library is compiled with 453 `-version=SqliteEnableColumnMetadata`, and SQLite compiled with the 454 `SQLITE_ENABLE_COLUMN_METADATA` option defined. 455 456 See_Also: $(LINK http://www.sqlite.org/c3ref/column_database_name.html). 457 +/ 458 string columnDatabaseName(size_t index) { 459 assertInitialized(); 460 return sqlite3_column_database_name(statement.handle, internalIndex(index)).to!string; 461 } 462 /// Ditto 463 string columnDatabaseName(string columnName) { 464 return columnDatabaseName(indexForName(columnName)); 465 } 466 /// Ditto 467 string columnTableName(size_t index) { 468 assertInitialized(); 469 return sqlite3_column_database_name(statement.handle, internalIndex(index)).to!string; 470 } 471 /// Ditto 472 string columnTableName(string columnName) { 473 return columnTableName(indexForName(columnName)); 474 } 475 /// Ditto 476 string columnOriginName(size_t index) { 477 assertInitialized(); 478 return sqlite3_column_origin_name(statement.handle, internalIndex(index)).to!string; 479 } 480 /// Ditto 481 string columnOriginName(string columnName) { 482 return columnOriginName(indexForName(columnName)); 483 } 484 } 485 486 /++ 487 Returns a struct with field members populated from the row's data. 488 489 Neither the names of the fields nor the names of the columns are checked. The fields 490 are filled with the columns' data in order. Thus, the order of the struct members must be the 491 same as the order of the columns in the prepared statement. 492 493 SQLite's conversion rules will be used. For instance, if a string field has the same rank 494 as an INTEGER column, the field's data will be the string representation of the integer. 495 +/ 496 T as(T)() if (is(T == struct)) { 497 import std.traits : FieldTypeTuple, FieldNameTuple; 498 499 alias FieldTypes = FieldTypeTuple!T; 500 T obj; 501 foreach (i, fieldName; FieldNameTuple!T) 502 __traits(getMember, obj, fieldName) = peek!(FieldTypes[i])(i); 503 return obj; 504 } 505 /// 506 unittest { 507 struct Item { 508 int _id; 509 string name; 510 } 511 512 auto db = Database(":memory:"); 513 db.run("CREATE TABLE items (name TEXT); 514 INSERT INTO items VALUES ('Light bulb')"); 515 516 auto results = db.execute("SELECT rowid AS id, name FROM items"); 517 auto row = results.front; 518 auto thing = row.as!Item(); 519 520 assert(thing == Item(1, "Light bulb")); 521 } 522 523 private: 524 int internalIndex(size_t index) { 525 assertInitialized(); 526 auto i = index + frontIndex; 527 assert(i >= 0 && i <= backIndex, "invalid column index: %d".format(i)); 528 assert(i <= int.max, "invalid index value: %d".format(i)); 529 return cast(int) i; 530 } 531 532 int indexForName(string name) { 533 assertInitialized(); 534 assert(name.length, "column with no name"); 535 foreach (i; frontIndex .. backIndex + 1) { 536 assert(i <= int.max, "invalid index value: %d".format(i)); 537 if (sqlite3_column_name(statement.handle, cast(int) i).to!string == name) 538 return i; 539 } 540 541 assert(false, "invalid column name: '%s'".format(name)); 542 } 543 544 void assertInitialized() nothrow { 545 assert(!empty, "Accessing elements of an empty row"); 546 assert(statement.handle !is null, "operation on an empty statement"); 547 } 548 } 549 550 /// Behavior of the `Row.peek()` method for arrays/strings 551 enum PeekMode { 552 /++ 553 Return a copy of the data into a new array/string. 554 The copy is safe to use after stepping to the next row. 555 +/ 556 copy, 557 558 /++ 559 Return a slice of the data. 560 The slice can point to invalid data after stepping to the next row. 561 +/ 562 slice 563 } 564 565 /++ 566 Some data retrieved from a column. 567 +/ 568 struct ColumnData { 569 import std.traits : isBoolean, isIntegral, isNumeric, isFloatingPoint, isSomeString, isArray; 570 import std.variant : Algebraic, VariantException; 571 572 alias SqliteVariant = Algebraic!(long, double, string, Blob, typeof(null)); 573 574 private { 575 SqliteVariant _value; 576 SqliteType _type; 577 } 578 579 /++ 580 Creates a new `ColumnData` from the value. 581 +/ 582 this(T)(inout T value) inout if (isBoolean!T || isIntegral!T) { 583 _value = SqliteVariant(value.to!long); 584 _type = SqliteType.INTEGER; 585 } 586 587 /// ditto 588 this(T)(T value) if (isFloatingPoint!T) { 589 _value = SqliteVariant(value.to!double); 590 _type = SqliteType.FLOAT; 591 } 592 593 /// ditto 594 this(T)(T value) if (isSomeString!T) { 595 if (value is null) { 596 _value = SqliteVariant(null); 597 _type = SqliteType.NULL; 598 } else { 599 _value = SqliteVariant(value.to!string); 600 _type = SqliteType.TEXT; 601 } 602 } 603 604 /// ditto 605 this(T)(T value) if (isArray!T && !isSomeString!T) { 606 if (value is null) { 607 _value = SqliteVariant(null); 608 _type = SqliteType.NULL; 609 } else { 610 _value = SqliteVariant(value.to!Blob); 611 _type = SqliteType.BLOB; 612 } 613 } 614 /// ditto 615 this(T)(T value) if (is(T == typeof(null))) { 616 _value = SqliteVariant(null); 617 _type = SqliteType.NULL; 618 } 619 620 /++ 621 Returns the Sqlite type of the column. 622 +/ 623 SqliteType type() const nothrow { 624 assertInitialized(); 625 return _type; 626 } 627 628 /++ 629 Returns the data converted to T. 630 631 If the data is NULL, defaultValue is returned. 632 +/ 633 auto as(T)(T defaultValue = T.init) 634 if (isBoolean!T || isNumeric!T || isSomeString!T) { 635 assertInitialized(); 636 637 if (_type == SqliteType.NULL) 638 return defaultValue; 639 640 return _value.coerce!T; 641 } 642 643 /// ditto 644 auto as(T)(T defaultValue = T.init) if (isArray!T && !isSomeString!T) { 645 assertInitialized(); 646 647 if (_type == SqliteType.NULL) 648 return defaultValue; 649 650 Blob data; 651 try 652 data = _value.get!Blob; 653 catch (VariantException e) 654 throw new SqliteException("impossible to convert this column to a " ~ T.stringof); 655 656 return cast(T) data; 657 } 658 659 /// ditto 660 auto as(T : Nullable!U, U...)(T defaultValue = T.init) { 661 assertInitialized(); 662 663 if (_type == SqliteType.NULL) 664 return defaultValue; 665 666 return T(as!U()); 667 } 668 669 void toString(scope void delegate(const(char)[]) sink) { 670 assertInitialized(); 671 672 if (_type == SqliteType.NULL) 673 sink("null"); 674 else 675 sink(_value.toString); 676 } 677 678 private: 679 void assertInitialized() const nothrow { 680 assert(_value.hasValue, "Accessing uninitialized ColumnData"); 681 } 682 } 683 684 /++ 685 Caches all the results of a query into memory at once. 686 687 This allows to keep all the rows returned from a query accessible in any order 688 and indefinitely. 689 690 Returns: 691 A `CachedResults` struct that allows to iterate on the rows and their 692 columns with an array-like interface. 693 694 The `CachedResults` struct is equivalent to an array of 'rows', which in 695 turn can be viewed as either an array of `ColumnData` or as an associative 696 array of `ColumnData` indexed by the column names. 697 +/ 698 CachedResults cached(ResultRange results) { 699 return CachedResults(results); 700 } 701 /// 702 unittest { 703 auto db = Database(":memory:"); 704 db.run("CREATE TABLE test (msg TEXT, num FLOAT); 705 INSERT INTO test (msg, num) VALUES ('ABC', 123); 706 INSERT INTO test (msg, num) VALUES ('DEF', 456);"); 707 708 auto results = db.execute("SELECT * FROM test").cached; 709 assert(results.length == 2); 710 assert(results[0][0].as!string == "ABC"); 711 assert(results[0][1].as!int == 123); 712 assert(results[1]["msg"].as!string == "DEF"); 713 assert(results[1]["num"].as!int == 456); 714 } 715 716 /++ 717 Stores all the results of a query. 718 719 The `CachedResults` struct is equivalent to an array of 'rows', which in 720 turn can be viewed as either an array of `ColumnData` or as an associative 721 array of `ColumnData` indexed by the column names. 722 723 Unlike `ResultRange`, `CachedResults` is a random-access range of rows, and its 724 data always remain available. 725 726 See_Also: 727 `cached` for an example. 728 +/ 729 struct CachedResults { 730 import std.array : appender; 731 732 // A row of retrieved data 733 struct CachedRow { 734 ColumnData[] columns; 735 alias columns this; 736 737 size_t[string] columnIndexes; 738 739 private this(Row row, size_t[string] columnIndexes) { 740 this.columnIndexes = columnIndexes; 741 742 auto colapp = appender!(ColumnData[]); 743 foreach (i; 0 .. row.length) 744 colapp.put(row[i]); 745 columns = colapp.data; 746 } 747 748 // Returns the data at the given index in the row. 749 ColumnData opIndex(size_t index) { 750 return columns[index]; 751 } 752 753 // Returns the data at the given column. 754 ColumnData opIndex(string name) { 755 auto index = name in columnIndexes; 756 assert(index, "unknown column name: %s".format(name)); 757 return columns[*index]; 758 } 759 } 760 761 // All the rows returned by the query. 762 CachedRow[] rows; 763 alias rows this; 764 765 private size_t[string] columnIndexes; 766 767 this(ResultRange results) { 768 if (!results.empty) { 769 auto first = results.front; 770 foreach (i; 0 .. first.length) { 771 assert(i <= int.max, "invalid column index value: %d".format(i)); 772 auto name = sqlite3_column_name(results.statement.handle, cast(int) i).to!string; 773 columnIndexes[name] = i; 774 } 775 } 776 777 auto rowapp = appender!(CachedRow[]); 778 while (!results.empty) { 779 rowapp.put(CachedRow(results.front, columnIndexes)); 780 results.popFront(); 781 } 782 rows = rowapp.data; 783 } 784 }