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     Throws:
634         VariantException if the value cannot be converted
635         to the desired type.
636     +/
637     auto as(T)(T defaultValue = T.init)
638             if (isBoolean!T || isNumeric!T || isSomeString!T) {
639         assertInitialized();
640 
641         if (_type == SqliteType.NULL)
642             return defaultValue;
643 
644         return _value.coerce!T;
645     }
646 
647     /// ditto
648     auto as(T)(T defaultValue = T.init) if (isArray!T && !isSomeString!T) {
649         assertInitialized();
650 
651         if (_type == SqliteType.NULL)
652             return defaultValue;
653 
654         Blob data = _value.get!Blob;
655         return cast(T) data;
656     }
657 
658     /// ditto
659     auto as(T : Nullable!U, U...)(T defaultValue = T.init) {
660         assertInitialized();
661 
662         if (_type == SqliteType.NULL)
663             return defaultValue;
664 
665         return T(as!U());
666     }
667 
668     void toString(scope void delegate(const(char)[]) sink) {
669         assertInitialized();
670 
671         if (_type == SqliteType.NULL)
672             sink("null");
673         else
674             sink(_value.toString);
675     }
676 
677 private:
678     void assertInitialized() const nothrow {
679         assert(_value.hasValue, "Accessing uninitialized ColumnData");
680     }
681 }
682 
683 /++
684 Caches all the results of a query into memory at once.
685 
686 This allows to keep all the rows returned from a query accessible in any order
687 and indefinitely.
688 
689 Returns:
690     A `CachedResults` struct that allows to iterate on the rows and their
691     columns with an array-like interface.
692 
693     The `CachedResults` struct is equivalent to an array of 'rows', which in
694     turn can be viewed as either an array of `ColumnData` or as an associative
695     array of `ColumnData` indexed by the column names.
696 +/
697 CachedResults cached(ResultRange results) {
698     return CachedResults(results);
699 }
700 ///
701 unittest {
702     auto db = Database(":memory:");
703     db.run("CREATE TABLE test (msg TEXT, num FLOAT);
704             INSERT INTO test (msg, num) VALUES ('ABC', 123);
705             INSERT INTO test (msg, num) VALUES ('DEF', 456);");
706 
707     auto results = db.execute("SELECT * FROM test").cached;
708     assert(results.length == 2);
709     assert(results[0][0].as!string == "ABC");
710     assert(results[0][1].as!int == 123);
711     assert(results[1]["msg"].as!string == "DEF");
712     assert(results[1]["num"].as!int == 456);
713 }
714 
715 /++
716 Stores all the results of a query.
717 
718 The `CachedResults` struct is equivalent to an array of 'rows', which in
719 turn can be viewed as either an array of `ColumnData` or as an associative
720 array of `ColumnData` indexed by the column names.
721 
722 Unlike `ResultRange`, `CachedResults` is a random-access range of rows, and its
723 data always remain available.
724 
725 See_Also:
726     `cached` for an example.
727 +/
728 struct CachedResults {
729     import std.array : appender;
730 
731     // A row of retrieved data
732     struct CachedRow {
733         ColumnData[] columns;
734         alias columns this;
735 
736         size_t[string] columnIndexes;
737 
738         private this(Row row, size_t[string] columnIndexes) {
739             this.columnIndexes = columnIndexes;
740 
741             auto colapp = appender!(ColumnData[]);
742             foreach (i; 0 .. row.length)
743                 colapp.put(row[i]);
744             columns = colapp.data;
745         }
746 
747         // Returns the data at the given index in the row.
748         ColumnData opIndex(size_t index) {
749             return columns[index];
750         }
751 
752         // Returns the data at the given column.
753         ColumnData opIndex(string name) {
754             auto index = name in columnIndexes;
755             assert(index, "unknown column name: %s".format(name));
756             return columns[*index];
757         }
758     }
759 
760     // All the rows returned by the query.
761     CachedRow[] rows;
762     alias rows this;
763 
764     private size_t[string] columnIndexes;
765 
766     this(ResultRange results) {
767         if (!results.empty) {
768             auto first = results.front;
769             foreach (i; 0 .. first.length) {
770                 assert(i <= int.max, "invalid column index value: %d".format(i));
771                 auto name = sqlite3_column_name(results.statement.handle, cast(int) i).to!string;
772                 columnIndexes[name] = i;
773             }
774         }
775 
776         auto rowapp = appender!(CachedRow[]);
777         while (!results.empty) {
778             rowapp.put(CachedRow(results.front, columnIndexes));
779             results.popFront();
780         }
781         rows = rowapp.data;
782     }
783 }