sq3: turned off debug output
[iv.d.git] / sq3.d
blob435df018b9651977319303c86ee1458402629ca8
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 (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 import std.exception : assumeUnique;
42 import std.string : fromStringz;
43 code = rc;
44 super(sqlite3_errstr(rc).fromStringz.assumeUnique, file, line, next);
49 private void sqcheck (int rc, string file=__FILE__, usize line=__LINE__) {
50 //pragma(inline, true);
51 if (rc != SQLITE_OK) throw new SQLiteErr(rc, file, line);
55 ////////////////////////////////////////////////////////////////////////////////
56 shared static this () {
57 if (sqlite3_initialize() != SQLITE_OK) throw new Error("can't initialize SQLite");
60 shared static ~this () {
61 sqlite3_shutdown();
65 // use this in `to` to avoid copying
66 public alias SQLStringc = const(char)[];
67 // use this in `to` to avoid copying
68 public alias SQ3Blob = const(char)[];
71 ////////////////////////////////////////////////////////////////////////////////
72 // WARNING! don't forget to finalize ALL prepared statements!
73 struct Database {
74 private:
75 static struct DBInfo {
76 sqlite3* db = null;
77 uint rc = 0;
78 usize onCloseSize = 0;
79 char *onClose = null; // 0-terminated
80 DBStatement.Data *stmthead = null;
81 DBStatement.Data *stmttail = null;
84 DBInfo* dbi = null;
86 private:
87 // caller should perform all necessary checks
88 void clearStatements () nothrow @trusted {
89 while (dbi.stmthead !is null) {
90 version(sq3_debug_stmtlist) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "clearStatements(%p): p=%p\n", dbi, dbi.stmthead); }
91 DBStatement.Data *dd = dbi.stmthead;
92 dbi.stmthead = dd.next;
93 dd.owner = null;
94 dd.prev = null;
95 dd.next = null;
96 dd.stepIndex = 0;
97 if (dd.st !is null) {
98 sqlite3_reset(dd.st);
99 sqlite3_clear_bindings(dd.st);
100 sqlite3_finalize(dd.st);
101 dd.st = null;
104 dbi.stmttail = null;
107 public:
108 // `null` schema means "open as R/O"
109 // non-null, but empty schema means "open as r/w"
110 // non-empty scheme means "create is absend"
111 this (const(char)[] name, const(char)[] schema=null) { open(name, schema); }
112 ~this () nothrow @trusted { close(); }
114 this (this) nothrow @trusted @nogc { if (dbi !is null) ++dbi.rc; }
116 @property bool isOpen () const pure nothrow @safe @nogc { return (dbi !is null && dbi.db !is null); }
118 void close () nothrow @trusted {
119 if (dbi !is null) {
120 if (--dbi.rc == 0) {
121 import core.stdc.stdlib : free;
122 if (dbi.db !is null) {
123 clearStatements();
124 if (dbi.onClose !is null) {
125 char* errmsg;
126 auto rc = sqlite3_exec(dbi.db, dbi.onClose, null, null, &errmsg);
127 if (rc != SQLITE_OK) {
128 import core.stdc.stdio : stderr, fprintf;
129 fprintf(stderr, "SQLITE ERROR: %s\n", errmsg);
130 sqlite3_free(errmsg);
132 version(none) {
133 import core.stdc.stdio : stderr, fprintf;
134 fprintf(stderr, "exec:===\n%s\n===\n", dbi.onClose);
136 free(dbi.onClose);
138 sqlite3_close_v2(dbi.db);
140 free(dbi);
142 dbi = null;
146 void appendOnClose (const(char)[] stmts) {
147 if (!isOpen) throw new SQLiteException("database is not opened");
148 while (stmts.length && stmts[0] <= 32) stmts = stmts[1..$];
149 while (stmts.length && stmts[$-1] <= 32) stmts = stmts[0..$-1];
150 if (stmts.length == 0) return;
151 import core.stdc.stdlib : realloc;
152 //FIXME: overflow. don't do it.
153 usize nsz = dbi.onCloseSize+stmts.length;
154 if (nsz+1 <= dbi.onCloseSize) throw new SQLiteException("out of memory for OnClose");
155 char *np = cast(char *)realloc(dbi.onClose, nsz+1);
156 if (np is null) throw new SQLiteException("out of memory for OnClose");
157 dbi.onClose = np;
158 np[dbi.onCloseSize..dbi.onCloseSize+stmts.length] = stmts[];
159 dbi.onCloseSize += stmts.length;
160 np[dbi.onCloseSize] = 0;
163 void setOnClose (const(char)[] stmts) {
164 if (!isOpen) throw new SQLiteException("database is not opened");
165 if (dbi.onClose !is null) {
166 import core.stdc.stdlib : free;
167 free(dbi.onClose);
168 dbi.onClose = null;
169 dbi.onCloseSize = 0;
171 appendOnClose(stmts);
174 // `null` schema means "open as R/O"
175 // non-null, but empty schema means "open as r/w"
176 // non-empty scheme means "create is absend"
177 void open (const(char)[] name, const(char)[] schema=null) {
178 close();
179 import core.stdc.stdlib : malloc, free;
180 import std.internal.cstring;
181 bool allowCreate = false, allowWrite = false;
182 if (schema !is null) {
183 allowWrite = true;
184 while (schema.length && schema[0] <= ' ') schema = schema[1..$];
185 allowCreate = (schema.length != 0);
187 dbi = cast(DBInfo *)malloc(DBInfo.sizeof);
188 if (!dbi) throw new Error("out of memory");
189 *dbi = DBInfo.init;
190 dbi.rc = 1;
191 immutable int rc = sqlite3_open_v2(name.tempCString, &dbi.db, (allowWrite ? SQLITE_OPEN_READWRITE : SQLITE_OPEN_READONLY)|(allowCreate ? SQLITE_OPEN_CREATE : 0), null);
192 if (rc != SQLITE_OK) {
193 free(dbi);
194 dbi = null;
195 sqcheck(rc);
197 scope(failure) { close(); }
198 if (allowCreate) execute(schema);
201 ulong lastRowId () nothrow @trusted { return (isOpen ? sqlite3_last_insert_rowid(dbi.db) : 0); }
203 // execute one or more SQL statements
204 // SQLite will take care of splitting
205 void execute (const(char)[] ops) {
206 if (!isOpen) throw new SQLiteException("database is not opened");
207 import std.internal.cstring;
208 char* errmsg;
209 auto rc = sqlite3_exec(dbi.db, ops.tempCString, null, null, &errmsg);
210 if (rc != SQLITE_OK) {
211 import core.stdc.stdio : stderr, fprintf;
212 fprintf(stderr, "SQLITE ERROR: %s\n", errmsg);
213 sqlite3_free(errmsg);
214 sqcheck(rc);
218 // create prepared SQL statement
219 DBStatement statement (const(char)[] stmtstr) {
220 if (!isOpen) throw new SQLiteException("database is not opened");
221 return DBStatement(dbi, stmtstr);
224 sqlite3* getHandle () nothrow @nogc @trusted { return (isOpen ? dbi.db : null); }
226 extern(C) {
227 alias UserFn = void function (sqlite3_context *ctx, int argc, sqlite3_value **argv);
230 void createFunction (const(char)[] name, int argc, UserFn xFunc, bool deterministic=true) {
231 import std.internal.cstring : tempCString;
232 if (!isOpen) throw new SQLiteException("database is not opened");
233 int rc = sqlite3_create_function(dbi.db, name.tempCString, argc, SQLITE_UTF8|(deterministic ? SQLITE_DETERMINISTIC : 0), null, xFunc, null, null);
234 if (rc != SQLITE_OK) sqcheck(rc);
239 // ////////////////////////////////////////////////////////////////////////// //
240 public static bool isUTF8ValidSQ3 (const(char)[] str) pure nothrow @trusted @nogc {
241 usize len = str.length;
242 immutable(ubyte)* p = cast(immutable(ubyte)*)str.ptr;
243 while (len--) {
244 immutable ubyte b = *p++;
245 if (b < 128) continue;
246 ubyte blen =
247 (b&0xe0) == 0xc0 ? 1 :
248 (b&0xf0) == 0xe0 ? 2 :
249 (b&0xf8) == 0xe8 ? 3 :
250 0; // no overlongs
251 if (!blen) return false;
252 if (len < blen) return false;
253 len -= blen;
254 while (blen--) {
255 immutable ubyte b1 = *p++;
256 if ((b1&0xc0) != 0x80) return false;
259 return true;
263 // ////////////////////////////////////////////////////////////////////////// //
264 struct DBFieldIndex {
265 uint idx;
268 struct DBFieldType {
269 enum {
270 Unknown,
271 Integer,
272 Float,
273 Text,
274 Blob,
275 Null,
277 uint idx;
279 string toString () const pure nothrow @trusted @nogc {
280 switch (idx) {
281 case Unknown: return "Unknown";
282 case Integer: return "Integer";
283 case Float: return "Float";
284 case Text: return "Text";
285 case Blob: return "Blob";
286 case Null: return "Null";
287 default: break;
289 return "Invalid";
293 struct DBStatement {
294 private:
295 enum FieldIndexMixin = `
296 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
297 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
298 char[257] fldname = 0;
299 if (name.length > 255) throw new SQLiteException("field name too long");
300 if (name[0] == ':' || name[0] == '?' || name[0] == '@') {
301 fldname[0..name.length] = name[];
302 } else {
303 fldname[0] = ':';
304 fldname[1..name.length+1] = name[];
306 immutable idx = sqlite3_bind_parameter_index(data.st, fldname.ptr);
307 if (idx < 1) throw new SQLiteException("invalid field name: '"~name.idup~"'");
310 public:
311 this (this) nothrow @trusted @nogc { this.incref(data); }
312 ~this () nothrow @trusted { this.decref(data); data = null; }
314 private this (Database.DBInfo *dbi, const(char)[] stmtstr) {
315 if (dbi is null || dbi.db is null) throw new SQLiteException("database is not opened");
316 if (stmtstr.length > int.max/4) throw new SQLiteException("statement too big");
317 import core.stdc.stdlib : malloc;
318 data = cast(Data*)malloc(Data.sizeof);
319 if (data is null) {
320 import core.exception : onOutOfMemoryErrorNoGC;
321 onOutOfMemoryErrorNoGC();
323 *data = Data.init;
324 data.refcount = 1;
325 // register in the owner list
326 version(sq3_debug_stmtlist) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "new stmt(%p): p=%p\n", dbi, data); }
327 data.owner = dbi;
328 data.prev = dbi.stmttail;
329 if (dbi.stmttail !is null) dbi.stmttail.next = data; else dbi.stmthead = data;
330 dbi.stmttail = data;
331 // done registering
332 scope(failure) { data.st = null; DBStatement.decref(data); }
333 const(char)* e;
334 sqcheck(sqlite3_prepare_v2(dbi.db, stmtstr.ptr, cast(int)stmtstr.length, &data.st, &e));
337 @property bool valid () nothrow @trusted @nogc { return (data !is null && data.st !is null); }
339 void close () nothrow @trusted { if (data !is null) this.decref(data); data = null; }
341 @property auto range () {
342 if (!valid) throw new SQLiteException("cannot get range from invalid statement");
343 //if (st is null) throw new SQLiteException("statement is not prepared");
344 if (data.stepIndex != 0) throw new SQLiteException("can't get range from busy statement");
345 return DBRowRange(this);
348 void reset () nothrow @trusted {
349 //if (data.stepIndex != 0) throw new SQLiteException("can't reset busy statement");
350 if (valid) {
351 data.stepIndex = 0;
352 sqlite3_reset(data.st);
353 sqlite3_clear_bindings(data.st);
357 void doAll () {
358 if (!valid) throw new SQLiteException("cannot execute invalid statement");
359 if (data.stepIndex != 0) throw new SQLiteException("can't doAll on busy statement");
360 scope(exit) reset();
361 for (;;) {
362 auto rc = sqlite3_step(data.st);
363 if (rc == SQLITE_DONE) break;
364 if (rc != SQLITE_ROW) sqcheck(rc);
368 ref DBStatement bind(T) (uint idx, T value) if ((isNarrowString!T && is(ElementEncodingType!T : char)) || isIntegral!T) {
369 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
370 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
371 if (idx < 1 || idx > sqlite3_bind_parameter_count(data.st)) {
372 import std.conv : to;
373 throw new SQLiteException("invalid field index: "~to!string(idx));
375 int rc;
376 static if (isNarrowString!T) {
377 if (value.length > int.max) throw new SQLiteException("value too big");
378 static if (is(ElementEncodingType!T == immutable(char))) {
379 version(none) {
380 if (isUTF8ValidSQ3(value[])) {
381 rc = sqlite3_bind_text(data.st, idx, value.ptr, cast(int)value.length, /*SQLITE_STATIC*/SQLITE_TRANSIENT);
382 } else {
383 rc = sqlite3_bind_blob(data.st, idx, value.ptr, cast(int)value.length, /*SQLITE_STATIC*/SQLITE_TRANSIENT);
385 } else {
386 rc = sqlite3_bind_text(data.st, idx, value.ptr, cast(int)value.length, /*SQLITE_STATIC*/SQLITE_TRANSIENT);
388 } else {
389 version(none) {
390 if (isUTF8ValidSQ3(value[])) {
391 rc = sqlite3_bind_text(data.st, idx, value.ptr, cast(int)value.length, SQLITE_TRANSIENT);
392 } else {
393 rc = sqlite3_bind_blob(data.st, idx, value.ptr, cast(int)value.length, SQLITE_TRANSIENT);
395 } else {
396 rc = sqlite3_bind_text(data.st, idx, value.ptr, cast(int)value.length, SQLITE_TRANSIENT);
399 } else static if (isIntegral!T) {
400 static if (isSigned!T) {
401 rc = sqlite3_bind_int64(data.st, idx, cast(long)value);
402 } else {
403 rc = sqlite3_bind_int64(data.st, idx, cast(ulong)value);
405 } else {
406 static assert(0, "WTF?!");
408 sqcheck(rc);
409 return this;
412 ref DBStatement bind(T) (const(char)[] name, T value) if ((isNarrowString!T && is(ElementEncodingType!T : char)) || isIntegral!T) {
413 mixin(FieldIndexMixin);
414 return bind!T(idx, value);
418 ref DBStatement bindText (uint idx, const(void)[] text, bool transient=true) {
419 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
420 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
421 if (idx < 1) {
422 import std.conv : to;
423 throw new SQLiteException("invalid field index: "~to!string(idx));
425 immutable int rc = sqlite3_bind_text(data.st, idx, cast(const(char)*)text.ptr, cast(int)text.length, (transient ? SQLITE_TRANSIENT : SQLITE_STATIC));
426 sqcheck(rc);
427 return this;
430 ref DBStatement bindText (const(char)[] name, const(void)[] text, bool transient=true) {
431 mixin(FieldIndexMixin);
432 return bindText(cast(uint)idx, text, transient);
436 ref DBStatement bindBlob (uint idx, const(void)[] blob, bool transient=true) {
437 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
438 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
439 if (idx < 1) {
440 import std.conv : to;
441 throw new SQLiteException("invalid field index: "~to!string(idx));
443 immutable int rc = sqlite3_bind_blob(data.st, idx, blob.ptr, cast(int)blob.length, (transient ? SQLITE_TRANSIENT : SQLITE_STATIC));
444 sqcheck(rc);
445 return this;
448 ref DBStatement bindBlob (const(char)[] name, const(void)[] blob, bool transient=true) {
449 mixin(FieldIndexMixin);
450 return bindBlob(cast(uint)idx, blob, transient);
454 ref DBStatement bindNull (uint idx) {
455 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
456 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
457 if (idx < 1) {
458 import std.conv : to;
459 throw new SQLiteException("invalid field index: "~to!string(idx));
461 immutable int rc = sqlite3_bind_null(data.st, idx);
462 sqcheck(rc);
463 return this;
466 ref DBStatement bindNull (const(char)[] name) {
467 mixin(FieldIndexMixin);
468 return bindNull(cast(uint)idx);
472 ref DBStatement bindInt(T) (uint idx, T v) if (isIntegral!T) {
473 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
474 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
475 if (idx < 1) {
476 import std.conv : to;
477 throw new SQLiteException("invalid field index: "~to!string(idx));
479 static if (isSigned!T) {
480 immutable int rc = sqlite3_bind_int64(data.st, idx, cast(long)v);
481 } else {
482 immutable int rc = sqlite3_bind_int64(data.st, idx, cast(ulong)v);
484 sqcheck(rc);
485 return this;
488 ref DBStatement bindInt(T) (const(char)[] name, T v) if (isIntegral!T) {
489 mixin(FieldIndexMixin);
490 return bindInt(cast(uint)idx, v);
494 ref DBStatement bindFloat(T) (uint idx, T v) if (is(T == float) || is(T == double)) {
495 if (!valid) throw new SQLiteException("cannot bind in invalid statement");
496 if (data.stepIndex != 0) throw new SQLiteException("can't bind on busy statement");
497 if (idx < 1) {
498 import std.conv : to;
499 throw new SQLiteException("invalid field index: "~to!string(idx));
501 rc = sqlite3_bind_double(data.st, idx, cast(double)v);
502 sqcheck(rc);
503 return this;
506 ref DBStatement bindFloat(T) (const(char)[] name, T v) if (is(T == float) || is(T == double)) {
507 mixin(FieldIndexMixin);
508 return bindFloat(cast(uint)idx, v);
511 private:
512 struct DBRow {
513 private this (DBStatement.Data* adata) nothrow @trusted @nogc {
514 data____ = adata;
515 DBStatement.incref(data____);
516 ++data____.rowcount;
519 this (this) nothrow @trusted @nogc { DBStatement.incref(data____); ++data____.rowcount; }
521 ~this () nothrow @trusted {
522 DBStatement.decrowref(data____);
523 DBStatement.decref(data____);
526 int fieldIndex____ (const(char)[] name) {
527 if (name.length > 0) {
528 foreach (immutable int idx; 0..sqlite3_data_count(data____.st)) {
529 import core.stdc.string : memcmp, strlen;
530 auto n = sqlite3_column_name(data____.st, idx);
531 if (n !is null) {
532 auto len = strlen(n);
533 if (len == name.length && memcmp(n, name.ptr, len) == 0) return idx;
537 throw new SQLiteException("invalid field name: '"~name.idup~"'");
540 T to(T) (uint idx)
541 if ((isNarrowString!T && is(ElementEncodingType!T : char)) || isIntegral!T || is(T : DBFieldIndex) || is(T : DBFieldType) ||
542 is(T == float) || is(T == double))
544 if (data____.stepIndex == 0) throw new SQLiteException("can't get row field of completed statement");
545 if (idx >= sqlite3_data_count(data____.st)) throw new SQLiteException("invalid result index");
546 static if (is(T : DBFieldIndex)) {
547 return DBFieldIndex(idx);
548 } else static if (is(T : DBFieldType)) {
549 switch (sqlite3_column_type(data____.st, idx)) {
550 case SQLITE_INTEGER: return DBFieldType(DBFieldType.Integer);
551 case SQLITE_FLOAT: return DBFieldType(DBFieldType.Float);
552 case SQLITE3_TEXT: return DBFieldType(DBFieldType.Text);
553 case SQLITE_BLOB: return DBFieldType(DBFieldType.Blob);
554 case SQLITE_NULL: return DBFieldType(DBFieldType.Null);
555 default: break;
557 return DBFieldType(DBFieldType.Unknown);
558 } else static if (isIntegral!T) {
559 auto res = sqlite3_column_int64(data____.st, idx);
560 if (res < T.min || res > T.max) throw new SQLiteException("integral overflow");
561 return cast(T)res;
562 } else static if (is(T == double)) {
563 auto res = sqlite3_column_double(data____.st, idx);
564 return cast(double)res;
565 } else static if (is(T == float)) {
566 auto res = sqlite3_column_double(data____.st, idx);
567 return cast(float)res;
568 } else {
569 auto len = sqlite3_column_bytes(data____.st, idx);
570 if (len < 0) throw new SQLiteException("invalid result");
571 const(char)* res = cast(const(char)*)sqlite3_column_blob(data____.st, idx);
572 if (len == 0) res = ""; else if (res is null) throw new SQLiteException("invalid result");
573 static if (is(ElementEncodingType!T == const(char))) {
574 return res[0..len];
575 } else static if (is(ElementEncodingType!T == immutable(char))) {
576 return res[0..len].idup;
577 } else {
578 return res[0..len].dup;
582 T to(T) (const(char)[] name) { return this.to!T(fieldIndex____(name)); }
584 template opIndex() {
585 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); }
586 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); }
587 alias opIndex = opIndexImpl;
590 template opDispatch(string name) {
591 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); }
592 alias opDispatch = opDispatchImpl;
595 auto index_ () pure const nothrow @nogc { return (data____.stepIndex > 0 ? data____.stepIndex-1 : 0); }
597 private DBStatement.Data* data____;
598 } // end of DBRow
600 private:
601 struct DBRowRange {
602 private this (ref DBStatement astat) {
603 data = astat.data;
604 DBStatement.incref(data);
605 ++data.rowcount;
606 assert(data.stepIndex == 0);
607 data.stepIndex = 1;
608 popFront();
611 this (this) nothrow @trusted @nogc { DBStatement.incref(data); ++data.rowcount; }
613 ~this () nothrow @trusted {
614 DBStatement.decrowref(data);
615 DBStatement.decref(data);
618 @property bool empty () const pure nothrow @nogc { return (data.stepIndex == 0); }
620 @property auto front () {
621 if (data.stepIndex == 0) throw new SQLiteException("can't get front element of completed statement");
622 return DBRow(data);
625 void popFront () {
626 if (data.stepIndex == 0) throw new SQLiteException("can't pop element of completed statement");
627 auto rc = sqlite3_step(data.st);
628 if (rc == SQLITE_DONE) {
629 data.stepIndex = 0;
630 return;
632 if (rc != SQLITE_ROW) {
633 data.stepIndex = 0;
634 sqcheck(rc);
636 ++data.stepIndex;
639 auto index_ () pure const nothrow @nogc { return (data.stepIndex > 0 ? data.stepIndex-1 : 0); }
641 private DBStatement.Data* data;
642 } // end of DBRowRange
644 private:
645 static void incref (Data* data) nothrow @nogc @trusted {
646 if (data !is null) ++data.refcount;
649 static void decref (Data* data) nothrow @trusted {
650 if (data !is null) {
651 if (--data.refcount == 0) {
652 import core.stdc.stdlib : free;
653 if (data.st !is null) {
654 sqlite3_reset(data.st);
655 sqlite3_clear_bindings(data.st);
656 sqlite3_finalize(data.st);
658 // unregister from the owner list
659 if (data.owner) {
660 version(sq3_debug_stmtlist) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "removing stmt(%p): p=%p\n", data.owner, data); }
661 if (data.prev) data.prev.next = data.next; else data.owner.stmthead = data.next;
662 if (data.next) data.next.prev = data.prev; else data.owner.stmttail = data.prev;
663 data.owner = null;
665 free(data);
666 data = null;
671 static void decrowref (Data* data) nothrow @trusted {
672 if (data !is null) {
673 if (--data.rowcount == 0) {
674 data.stepIndex = 0;
675 if (data.st !is null) {
676 sqlite3_reset(data.st);
677 sqlite3_clear_bindings(data.st);
683 private:
684 static struct Data {
685 uint refcount = 0;
686 uint rowcount = 0; // number of row structs using this statement
687 uint stepIndex = 0;
688 sqlite3_stmt* st = null;
689 Data* prev = null;
690 Data* next = null;
691 Database.DBInfo* owner = null;
693 Data* data;