sq3: better (i hope) error reporting
[iv.d.git] / sq3.d
blob80551fb9473666891f42d8dafe913e7f26772666
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // sqlite3 helpers
18 module iv.sq3 /*is aliced*/;
19 pragma(lib, "sqlite3");
21 //version = sq3_debug_stmtlist;
23 import iv.alice;
25 public import etc.c.sqlite3;
26 import std.traits;
27 import std.range.primitives;
29 private import std.internal.cstring : tempCString;
32 ////////////////////////////////////////////////////////////////////////////////
33 mixin(NewExceptionClass!("SQLiteException", "Exception"));
35 class SQLiteErr : SQLiteException {
36 int code;
38 this (sqlite3* db, int rc, string file=__FILE__, usize line=__LINE__, Throwable next=null) @trusted nothrow {
39 //import core.stdc.stdio : stderr, fprintf;
40 //fprintf(stderr, "SQLITE ERROR: %s\n", sqlite3_errstr(rc));
41 code = rc;
42 if (rc == SQLITE_OK) {
43 super("SQLite ERROR: no error!", file, line, next);
44 } else {
45 import std.exception : assumeUnique;
46 import std.string : fromStringz;
47 if (db) {
48 super(sqlite3_errstr(sqlite3_extended_errcode(db)).fromStringz.assumeUnique, file, line, next);
49 } else {
50 super(sqlite3_errstr(rc).fromStringz.assumeUnique, file, line, next);
57 public void sq3check (sqlite3* db, int rc, string file=__FILE__, usize line=__LINE__) {
58 //pragma(inline, true);
59 if (rc != SQLITE_OK) throw new SQLiteErr(db, rc, file, line);
63 ////////////////////////////////////////////////////////////////////////////////
64 shared static this () {
65 if (sqlite3_initialize() != SQLITE_OK) throw new Error("can't initialize SQLite");
68 shared static ~this () {
69 sqlite3_shutdown();
73 // use this in `to` to avoid copying
74 public alias SQLStringc = const(char)[];
75 // use this in `to` to avoid copying
76 public alias SQ3Blob = const(char)[];
77 // use this in `to` to avoid copying
78 public alias SQ3Text = const(char)[];
81 ////////////////////////////////////////////////////////////////////////////////
82 // WARNING! don't forget to finalize ALL prepared statements!
83 struct Database {
84 private:
85 static struct DBInfo {
86 sqlite3* db = null;
87 uint rc = 0;
88 usize onCloseSize = 0;
89 char *onClose = null; // 0-terminated
90 DBStatement.Data *stmthead = null;
91 DBStatement.Data *stmttail = null;
94 DBInfo* dbi = null;
96 private:
97 // caller should perform all necessary checks
98 void clearStatements () nothrow @trusted {
99 while (dbi.stmthead !is null) {
100 version(sq3_debug_stmtlist) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "clearStatements(%p): p=%p\n", dbi, dbi.stmthead); }
101 DBStatement.Data *dd = dbi.stmthead;
102 dbi.stmthead = dd.next;
103 dd.owner = null;
104 dd.prev = null;
105 dd.next = null;
106 dd.stepIndex = 0;
107 if (dd.st !is null) {
108 sqlite3_reset(dd.st);
109 sqlite3_clear_bindings(dd.st);
110 sqlite3_finalize(dd.st);
111 dd.st = null;
114 dbi.stmttail = null;
117 public:
118 // `null` schema means "open as R/O"
119 // non-null, but empty schema means "open as r/w"
120 // non-empty scheme means "create is absend"
121 this (const(char)[] name, const(char)[] schema=null) { open(name, schema); }
122 ~this () nothrow @trusted { close(); }
124 this (this) nothrow @trusted @nogc { if (dbi !is null) ++dbi.rc; }
126 @property bool isOpen () const pure nothrow @safe @nogc { return (dbi !is null && dbi.db !is null); }
128 void close () nothrow @trusted {
129 if (dbi !is null) {
130 if (--dbi.rc == 0) {
131 import core.stdc.stdlib : free;
132 if (dbi.db !is null) {
133 clearStatements();
134 if (dbi.onClose !is null) {
135 char* errmsg;
136 auto rc = sqlite3_exec(dbi.db, dbi.onClose, null, null, &errmsg);
137 if (rc != SQLITE_OK) {
138 import core.stdc.stdio : stderr, fprintf;
139 fprintf(stderr, "SQLITE ERROR: %s\n", errmsg);
140 sqlite3_free(errmsg);
142 version(none) {
143 import core.stdc.stdio : stderr, fprintf;
144 fprintf(stderr, "exec:===\n%s\n===\n", dbi.onClose);
146 free(dbi.onClose);
148 sqlite3_close_v2(dbi.db);
150 free(dbi);
152 dbi = null;
156 void appendOnClose (const(char)[] stmts) {
157 if (!isOpen) throw new SQLiteException("database is not opened");
158 while (stmts.length && stmts[0] <= 32) stmts = stmts[1..$];
159 while (stmts.length && stmts[$-1] <= 32) stmts = stmts[0..$-1];
160 if (stmts.length == 0) return;
161 import core.stdc.stdlib : realloc;
162 //FIXME: overflow. don't do it.
163 usize nsz = dbi.onCloseSize+stmts.length;
164 if (nsz+1 <= dbi.onCloseSize) throw new SQLiteException("out of memory for OnClose");
165 char *np = cast(char *)realloc(dbi.onClose, nsz+1);
166 if (np is null) throw new SQLiteException("out of memory for OnClose");
167 dbi.onClose = np;
168 np[dbi.onCloseSize..dbi.onCloseSize+stmts.length] = stmts[];
169 dbi.onCloseSize += stmts.length;
170 np[dbi.onCloseSize] = 0;
173 void setOnClose (const(char)[] stmts) {
174 if (!isOpen) throw new SQLiteException("database is not opened");
175 if (dbi.onClose !is null) {
176 import core.stdc.stdlib : free;
177 free(dbi.onClose);
178 dbi.onClose = null;
179 dbi.onCloseSize = 0;
181 appendOnClose(stmts);
184 // `null` schema means "open as R/O"
185 // non-null, but empty schema means "open as r/w"
186 // non-empty scheme means "create is absend"
187 void open (const(char)[] name, const(char)[] schema=null) {
188 close();
189 import core.stdc.stdlib : malloc, free;
190 import std.internal.cstring;
191 bool allowCreate = false, allowWrite = false;
192 if (schema !is null) {
193 allowWrite = true;
194 while (schema.length && schema[0] <= ' ') schema = schema[1..$];
195 allowCreate = (schema.length != 0);
197 dbi = cast(DBInfo *)malloc(DBInfo.sizeof);
198 if (!dbi) throw new Error("out of memory");
199 *dbi = DBInfo.init;
200 dbi.rc = 1;
201 immutable int rc = sqlite3_open_v2(name.tempCString, &dbi.db, (allowWrite ? SQLITE_OPEN_READWRITE : SQLITE_OPEN_READONLY)|(allowCreate ? SQLITE_OPEN_CREATE : 0), null);
202 if (rc != SQLITE_OK) {
203 free(dbi);
204 dbi = null;
205 sq3check(null, rc);
207 scope(failure) { close(); }
208 if (allowCreate) execute(schema);
211 ulong lastRowId () nothrow @trusted { return (isOpen ? sqlite3_last_insert_rowid(dbi.db) : 0); }
213 // execute one or more SQL statements
214 // SQLite will take care of splitting
215 void execute (const(char)[] ops) {
216 if (!isOpen) throw new SQLiteException("database is not opened");
217 import std.internal.cstring;
218 //char* errmsg;
219 immutable int rc = sqlite3_exec(dbi.db, ops.tempCString, null, null, null/*&errmsg*/);
220 if (rc != SQLITE_OK) {
221 //import core.stdc.stdio : stderr, fprintf;
222 //fprintf(stderr, "SQLITE ERROR: %s\n", errmsg);
223 //sqlite3_free(errmsg);
224 sq3check(dbi.db, rc);
228 // create prepared SQL statement
229 DBStatement statement (const(char)[] stmtstr) {
230 if (!isOpen) throw new SQLiteException("database is not opened");
231 return DBStatement(dbi, stmtstr);
234 sqlite3* getHandle () nothrow @nogc @trusted { return (isOpen ? dbi.db : null); }
236 extern(C) {
237 alias UserFn = void function (sqlite3_context *ctx, int argc, sqlite3_value **argv);
240 void createFunction (const(char)[] name, int argc, UserFn xFunc, bool deterministic=true) {
241 import std.internal.cstring : tempCString;
242 if (!isOpen) throw new SQLiteException("database is not opened");
243 immutable int rc = sqlite3_create_function(dbi.db, name.tempCString, argc, SQLITE_UTF8|(deterministic ? SQLITE_DETERMINISTIC : 0), null, xFunc, null, null);
244 sq3check(dbi.db, rc);
249 // ////////////////////////////////////////////////////////////////////////// //
250 public static bool isUTF8ValidSQ3 (const(char)[] str) pure nothrow @trusted @nogc {
251 usize len = str.length;
252 immutable(ubyte)* p = cast(immutable(ubyte)*)str.ptr;
253 while (len--) {
254 immutable ubyte b = *p++;
255 if (b < 128) continue;
256 ubyte blen =
257 (b&0xe0) == 0xc0 ? 1 :
258 (b&0xf0) == 0xe0 ? 2 :
259 (b&0xf8) == 0xe8 ? 3 :
260 0; // no overlongs
261 if (!blen) return false;
262 if (len < blen) return false;
263 len -= blen;
264 while (blen--) {
265 immutable ubyte b1 = *p++;
266 if ((b1&0xc0) != 0x80) return false;
269 return true;
273 // ////////////////////////////////////////////////////////////////////////// //
274 struct DBFieldIndex {
275 uint idx;
278 struct DBFieldType {
279 enum {
280 Unknown,
281 Integer,
282 Float,
283 Text,
284 Blob,
285 Null,
287 uint idx;
289 string toString () const pure nothrow @trusted @nogc {
290 switch (idx) {
291 case Unknown: return "Unknown";
292 case Integer: return "Integer";
293 case Float: return "Float";
294 case Text: return "Text";
295 case Blob: return "Blob";
296 case Null: return "Null";
297 default: break;
299 return "Invalid";
303 struct DBStatement {
304 private:
305 enum FieldIndexMixin = `
306 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
307 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
308 char[257] fldname = 0;
309 if (name.length > 255) throw new SQLiteException("field name too long");
310 if (name[0] == ':' || name[0] == '?' || name[0] == '@') {
311 fldname[0..name.length] = name[];
312 } else {
313 fldname[0] = ':';
314 fldname[1..name.length+1] = name[];
316 immutable idx = sqlite3_bind_parameter_index(data.st, fldname.ptr);
317 if (idx < 1) throw new SQLiteException("invalid field name: '"~name.idup~"'");
320 public:
321 this (this) nothrow @trusted @nogc { this.incref(data); }
322 ~this () nothrow @trusted { this.decref(data); data = null; }
324 private this (Database.DBInfo *dbi, const(char)[] stmtstr) {
325 if (dbi is null || dbi.db is null) throw new SQLiteException("database is not opened");
326 if (stmtstr.length > int.max/4) throw new SQLiteException("statement too big");
327 import core.stdc.stdlib : malloc;
328 data = cast(Data*)malloc(Data.sizeof);
329 if (data is null) {
330 import core.exception : onOutOfMemoryErrorNoGC;
331 onOutOfMemoryErrorNoGC();
333 *data = Data.init;
334 data.refcount = 1;
335 // register in the owner list
336 version(sq3_debug_stmtlist) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "new stmt(%p): p=%p\n", dbi, data); }
337 data.owner = dbi;
338 data.prev = dbi.stmttail;
339 if (dbi.stmttail !is null) dbi.stmttail.next = data; else dbi.stmthead = data;
340 dbi.stmttail = data;
341 // done registering
342 scope(failure) { data.st = null; DBStatement.decref(data); }
343 const(char)* e;
344 sq3check(dbi.db, sqlite3_prepare_v2(dbi.db, stmtstr.ptr, cast(int)stmtstr.length, &data.st, &e));
347 @property bool valid () nothrow @trusted @nogc { return (data !is null && data.st !is null); }
349 void close () nothrow @trusted { if (data !is null) this.decref(data); data = null; }
351 @property auto range () {
352 if (!valid) throw new SQLiteException("cannot get range from invalid statement");
353 //if (st is null) throw new SQLiteException("statement is not prepared");
354 if (data.stepIndex != 0) throw new SQLiteException("can't get range from busy statement");
355 return DBRowRange(this);
358 void reset () nothrow @trusted {
359 //if (data.stepIndex != 0) throw new SQLiteException("can't reset busy statement");
360 if (valid) {
361 data.stepIndex = 0;
362 sqlite3_reset(data.st);
363 sqlite3_clear_bindings(data.st);
367 void doAll () {
368 if (!valid) throw new SQLiteException("cannot execute invalid statement");
369 if (data.stepIndex != 0) throw new SQLiteException("can't doAll on busy statement");
370 scope(exit) reset();
371 for (;;) {
372 auto rc = sqlite3_step(data.st);
373 if (rc == SQLITE_DONE) break;
374 if (rc != SQLITE_ROW) sq3check(data.owner.db, rc);
378 ref DBStatement bind(T) (uint idx, T value) if ((isNarrowString!T && is(ElementEncodingType!T : char)) || isIntegral!T) {
379 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
380 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
381 if (idx < 1 || idx > sqlite3_bind_parameter_count(data.st)) {
382 import std.conv : to;
383 throw new SQLiteException("invalid field index: "~to!string(idx));
385 int rc;
386 static if (isNarrowString!T) {
387 if (value.length > int.max) throw new SQLiteException("value too big");
388 static if (is(ElementEncodingType!T == immutable(char))) {
389 version(none) {
390 if (isUTF8ValidSQ3(value[])) {
391 rc = sqlite3_bind_text(data.st, idx, value.ptr, cast(int)value.length, /*SQLITE_STATIC*/SQLITE_TRANSIENT);
392 } else {
393 rc = sqlite3_bind_blob(data.st, idx, value.ptr, cast(int)value.length, /*SQLITE_STATIC*/SQLITE_TRANSIENT);
395 } else {
396 rc = sqlite3_bind_text(data.st, idx, value.ptr, cast(int)value.length, /*SQLITE_STATIC*/SQLITE_TRANSIENT);
398 } else {
399 version(none) {
400 if (isUTF8ValidSQ3(value[])) {
401 rc = sqlite3_bind_text(data.st, idx, value.ptr, cast(int)value.length, SQLITE_TRANSIENT);
402 } else {
403 rc = sqlite3_bind_blob(data.st, idx, value.ptr, cast(int)value.length, SQLITE_TRANSIENT);
405 } else {
406 rc = sqlite3_bind_text(data.st, idx, value.ptr, cast(int)value.length, SQLITE_TRANSIENT);
409 } else static if (isIntegral!T) {
410 static if (isSigned!T) {
411 rc = sqlite3_bind_int64(data.st, idx, cast(long)value);
412 } else {
413 rc = sqlite3_bind_int64(data.st, idx, cast(ulong)value);
415 } else {
416 static assert(0, "WTF?!");
418 sq3check(data.owner.db, rc);
419 return this;
422 ref DBStatement bind(T) (const(char)[] name, T value) if ((isNarrowString!T && is(ElementEncodingType!T : char)) || isIntegral!T) {
423 mixin(FieldIndexMixin);
424 return bind!T(idx, value);
428 ref DBStatement bindText (uint idx, const(void)[] text, bool transient=true) {
429 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
430 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
431 if (idx < 1) {
432 import std.conv : to;
433 throw new SQLiteException("invalid field index: "~to!string(idx));
435 immutable int rc = sqlite3_bind_text(data.st, idx, cast(const(char)*)text.ptr, cast(int)text.length, (transient ? SQLITE_TRANSIENT : SQLITE_STATIC));
436 sq3check(data.owner.db, rc);
437 return this;
440 ref DBStatement bindText (const(char)[] name, const(void)[] text, bool transient=true) {
441 mixin(FieldIndexMixin);
442 return bindText(cast(uint)idx, text, transient);
446 ref DBStatement bindBlob (uint idx, const(void)[] blob, bool transient=true) {
447 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
448 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
449 if (idx < 1) {
450 import std.conv : to;
451 throw new SQLiteException("invalid field index: "~to!string(idx));
453 immutable int rc = sqlite3_bind_blob(data.st, idx, blob.ptr, cast(int)blob.length, (transient ? SQLITE_TRANSIENT : SQLITE_STATIC));
454 sq3check(data.owner.db, rc);
455 return this;
458 ref DBStatement bindBlob (const(char)[] name, const(void)[] blob, bool transient=true) {
459 mixin(FieldIndexMixin);
460 return bindBlob(cast(uint)idx, blob, transient);
464 ref DBStatement bindNull (uint idx) {
465 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
466 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
467 if (idx < 1) {
468 import std.conv : to;
469 throw new SQLiteException("invalid field index: "~to!string(idx));
471 immutable int rc = sqlite3_bind_null(data.st, idx);
472 sq3check(data.owner.db, rc);
473 return this;
476 ref DBStatement bindNull (const(char)[] name) {
477 mixin(FieldIndexMixin);
478 return bindNull(cast(uint)idx);
482 ref DBStatement bindInt(T) (uint idx, T v) if (isIntegral!T) {
483 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
484 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
485 if (idx < 1) {
486 import std.conv : to;
487 throw new SQLiteException("invalid field index: "~to!string(idx));
489 static if (isSigned!T) {
490 immutable int rc = sqlite3_bind_int64(data.st, idx, cast(long)v);
491 } else {
492 immutable int rc = sqlite3_bind_int64(data.st, idx, cast(ulong)v);
494 sq3check(data.owner.db, rc);
495 return this;
498 ref DBStatement bindInt(T) (const(char)[] name, T v) if (isIntegral!T) {
499 mixin(FieldIndexMixin);
500 return bindInt(cast(uint)idx, v);
504 ref DBStatement bindFloat(T) (uint idx, T v) if (is(T == float) || is(T == double)) {
505 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
506 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
507 if (idx < 1) {
508 import std.conv : to;
509 throw new SQLiteException("invalid field index: "~to!string(idx));
511 rc = sqlite3_bind_double(data.st, idx, cast(double)v);
512 sq3check(data.owner.db, rc);
513 return this;
516 ref DBStatement bindFloat(T) (const(char)[] name, T v) if (is(T == float) || is(T == double)) {
517 mixin(FieldIndexMixin);
518 return bindFloat(cast(uint)idx, v);
521 private:
522 struct DBRow {
523 private this (DBStatement.Data* adata) nothrow @trusted @nogc {
524 data____ = adata;
525 DBStatement.incref(data____);
526 ++data____.rowcount;
529 this (this) nothrow @trusted @nogc { DBStatement.incref(data____); ++data____.rowcount; }
531 ~this () nothrow @trusted {
532 DBStatement.decrowref(data____);
533 DBStatement.decref(data____);
536 int fieldIndex____ (const(char)[] name) {
537 if (name.length > 0) {
538 foreach (immutable int idx; 0..sqlite3_data_count(data____.st)) {
539 import core.stdc.string : memcmp, strlen;
540 auto n = sqlite3_column_name(data____.st, idx);
541 if (n !is null) {
542 auto len = strlen(n);
543 if (len == name.length && memcmp(n, name.ptr, len) == 0) return idx;
547 throw new SQLiteException("invalid field name: '"~name.idup~"'");
550 T to(T) (uint idx)
551 if ((isNarrowString!T && is(ElementEncodingType!T : char)) || isIntegral!T || is(T : DBFieldIndex) || is(T : DBFieldType) ||
552 is(T == float) || is(T == double))
554 if (data____.stepIndex == 0) throw new SQLiteException("can't get row field of completed statement");
555 if (idx >= sqlite3_data_count(data____.st)) throw new SQLiteException("invalid result index");
556 static if (is(T : DBFieldIndex)) {
557 return DBFieldIndex(idx);
558 } else static if (is(T : DBFieldType)) {
559 switch (sqlite3_column_type(data____.st, idx)) {
560 case SQLITE_INTEGER: return DBFieldType(DBFieldType.Integer);
561 case SQLITE_FLOAT: return DBFieldType(DBFieldType.Float);
562 case SQLITE3_TEXT: return DBFieldType(DBFieldType.Text);
563 case SQLITE_BLOB: return DBFieldType(DBFieldType.Blob);
564 case SQLITE_NULL: return DBFieldType(DBFieldType.Null);
565 default: break;
567 return DBFieldType(DBFieldType.Unknown);
568 } else static if (isIntegral!T) {
569 auto res = sqlite3_column_int64(data____.st, idx);
570 if (res < T.min || res > T.max) throw new SQLiteException("integral overflow");
571 return cast(T)res;
572 } else static if (is(T == double)) {
573 auto res = sqlite3_column_double(data____.st, idx);
574 return cast(double)res;
575 } else static if (is(T == float)) {
576 auto res = sqlite3_column_double(data____.st, idx);
577 return cast(float)res;
578 } else {
579 auto len = sqlite3_column_bytes(data____.st, idx);
580 if (len < 0) throw new SQLiteException("invalid result");
581 const(char)* res = cast(const(char)*)sqlite3_column_blob(data____.st, idx);
582 if (len == 0) res = ""; else if (res is null) throw new SQLiteException("invalid result");
583 static if (is(ElementEncodingType!T == const(char))) {
584 return res[0..len];
585 } else static if (is(ElementEncodingType!T == immutable(char))) {
586 return res[0..len].idup;
587 } else {
588 return res[0..len].dup;
592 T to(T) (const(char)[] name) { return this.to!T(fieldIndex____(name)); }
594 template opIndex() {
595 T opIndexImpl(T) (uint idx) if ((isNarrowString!T && is(ElementEncodingType!T : char)) || isIntegral!T || is(T : DBFieldIndex) || is(T : DBFieldType)) { return this.to!T(idx); }
596 T opIndexImpl(T) (const(char)[] name) if ((isNarrowString!T && is(ElementEncodingType!T : char)) || isIntegral!T || is(T : DBFieldIndex) || is(T : DBFieldType)) { return this.to!T(name); }
597 alias opIndex = opIndexImpl;
600 template opDispatch(string name) {
601 T opDispatchImpl(T=const(char)[]) () if ((isNarrowString!T && is(ElementEncodingType!T : char)) || isIntegral!T || is(T : DBFieldIndex) || is(T : DBFieldType)) { return this.to!T(name); }
602 alias opDispatch = opDispatchImpl;
605 auto index_ () pure const nothrow @nogc { return (data____.stepIndex > 0 ? data____.stepIndex-1 : 0); }
607 private DBStatement.Data* data____;
608 } // end of DBRow
610 private:
611 struct DBRowRange {
612 private this (ref DBStatement astat) {
613 data = astat.data;
614 DBStatement.incref(data);
615 ++data.rowcount;
616 assert(data.stepIndex == 0);
617 data.stepIndex = 1;
618 popFront();
621 this (this) nothrow @trusted @nogc { DBStatement.incref(data); ++data.rowcount; }
623 ~this () nothrow @trusted {
624 DBStatement.decrowref(data);
625 DBStatement.decref(data);
628 @property bool empty () const pure nothrow @nogc { return (data.stepIndex == 0); }
630 @property auto front () {
631 if (data.stepIndex == 0) throw new SQLiteException("can't get front element of completed statement");
632 return DBRow(data);
635 void popFront () {
636 if (data.stepIndex == 0) throw new SQLiteException("can't pop element of completed statement");
637 auto rc = sqlite3_step(data.st);
638 if (rc == SQLITE_DONE) {
639 data.stepIndex = 0;
640 return;
642 if (rc != SQLITE_ROW) {
643 data.stepIndex = 0;
644 sq3check(data.owner.db, rc);
646 ++data.stepIndex;
649 auto index_ () pure const nothrow @nogc { return (data.stepIndex > 0 ? data.stepIndex-1 : 0); }
651 private DBStatement.Data* data;
652 } // end of DBRowRange
654 private:
655 static void incref (Data* data) nothrow @nogc @trusted {
656 if (data !is null) ++data.refcount;
659 static void decref (Data* data) nothrow @trusted {
660 if (data !is null) {
661 if (--data.refcount == 0) {
662 import core.stdc.stdlib : free;
663 if (data.st !is null) {
664 sqlite3_reset(data.st);
665 sqlite3_clear_bindings(data.st);
666 sqlite3_finalize(data.st);
668 // unregister from the owner list
669 if (data.owner) {
670 version(sq3_debug_stmtlist) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "removing stmt(%p): p=%p\n", data.owner, data); }
671 if (data.prev) data.prev.next = data.next; else data.owner.stmthead = data.next;
672 if (data.next) data.next.prev = data.prev; else data.owner.stmttail = data.prev;
673 data.owner = null;
675 free(data);
676 data = null;
681 static void decrowref (Data* data) nothrow @trusted {
682 if (data !is null) {
683 if (--data.rowcount == 0) {
684 data.stepIndex = 0;
685 if (data.st !is null) {
686 sqlite3_reset(data.st);
687 sqlite3_clear_bindings(data.st);
693 private:
694 static struct Data {
695 uint refcount = 0;
696 uint rowcount = 0; // number of row structs using this statement
697 uint stepIndex = 0;
698 sqlite3_stmt* st = null;
699 Data* prev = null;
700 Data* next = null;
701 Database.DBInfo* owner = null;
703 Data* data;