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