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 }