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