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