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/>.
18 module iv
.sq3
/*is aliced*/;
19 pragma(lib
, "sqlite3");
21 //version = sq3_debug_stmtlist;
25 public import etc
.c
.sqlite3
;
27 import std
.range
.primitives
;
29 private import std
.internal
.cstring
: tempCString
;
32 ////////////////////////////////////////////////////////////////////////////////
33 mixin(NewExceptionClass
!("SQLiteException", "Exception"));
35 class SQLiteErr
: SQLiteException
{
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));
42 if (rc
== SQLITE_OK
) {
43 super("SQLite ERROR: no error!", file
, line
, next
);
45 import std
.exception
: assumeUnique
;
46 import std
.string
: fromStringz
;
48 super(sqlite3_errstr(sqlite3_extended_errcode(db)).fromStringz
.assumeUnique
, file
, line
, next
);
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 () {
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!
85 static struct DBInfo
{
88 usize onCloseSize
= 0;
89 char *onClose
= null; // 0-terminated
90 DBStatement
.Data
*stmthead
= null;
91 DBStatement
.Data
*stmttail
= null;
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
;
107 if (dd.st
!is null) {
108 sqlite3_reset(dd.st
);
109 sqlite3_clear_bindings(dd.st
);
110 sqlite3_finalize(dd.st
);
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 {
131 import core
.stdc
.stdlib
: free
;
132 if (dbi
.db !is null) {
134 if (dbi
.onClose
!is null) {
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
);
143 import core
.stdc
.stdio
: stderr
, fprintf
;
144 fprintf(stderr
, "exec:===\n%s\n===\n", dbi
.onClose
);
148 sqlite3_close_v2(dbi
.db);
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");
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
;
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) {
189 import core
.stdc
.stdlib
: malloc
, free
;
190 import std
.internal
.cstring
;
191 bool allowCreate
= false, allowWrite
= false;
192 if (schema
!is null) {
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");
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
) {
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
;
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); }
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
;
254 immutable ubyte b
= *p
++;
255 if (b
< 128) continue;
257 (b
&0xe0) == 0xc0 ?
1 :
258 (b
&0xf0) == 0xe0 ?
2 :
259 (b
&0xf8) == 0xe8 ?
3 :
261 if (!blen
) return false;
262 if (len
< blen
) return false;
265 immutable ubyte b1
= *p
++;
266 if ((b1
&0xc0) != 0x80) return false;
273 // ////////////////////////////////////////////////////////////////////////// //
274 struct DBFieldIndex
{
289 string
toString () const pure nothrow @trusted @nogc {
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";
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[];
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~"'");
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
);
330 import core
.exception
: onOutOfMemoryErrorNoGC
;
331 onOutOfMemoryErrorNoGC();
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
); }
338 data
.prev
= dbi
.stmttail
;
339 if (dbi
.stmttail
!is null) dbi
.stmttail
.next
= data
; else dbi
.stmthead
= data
;
342 scope(failure
) { data
.st
= null; DBStatement
.decref(data
); }
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");
362 sqlite3_reset(data
.st
);
363 sqlite3_clear_bindings(data
.st
);
368 if (!valid
) throw new SQLiteException("cannot execute invalid statement");
369 if (data
.stepIndex
!= 0) throw new SQLiteException("can't doAll on busy statement");
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
));
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))) {
390 if (isUTF8ValidSQ3(value
[])) {
391 rc
= sqlite3_bind_text(data
.st
, idx
, value
.ptr
, cast(int)value
.length
, /*SQLITE_STATIC*/SQLITE_TRANSIENT
);
393 rc
= sqlite3_bind_blob(data
.st
, idx
, value
.ptr
, cast(int)value
.length
, /*SQLITE_STATIC*/SQLITE_TRANSIENT
);
396 rc
= sqlite3_bind_text(data
.st
, idx
, value
.ptr
, cast(int)value
.length
, /*SQLITE_STATIC*/SQLITE_TRANSIENT
);
400 if (isUTF8ValidSQ3(value
[])) {
401 rc
= sqlite3_bind_text(data
.st
, idx
, value
.ptr
, cast(int)value
.length
, SQLITE_TRANSIENT
);
403 rc
= sqlite3_bind_blob(data
.st
, idx
, value
.ptr
, cast(int)value
.length
, SQLITE_TRANSIENT
);
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
);
413 rc
= sqlite3_bind_int64(data
.st
, idx
, cast(ulong)value
);
416 static assert(0, "WTF?!");
418 sq3check(data
.owner
.db, rc
);
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");
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
);
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");
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
);
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");
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
);
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");
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
);
492 immutable int rc
= sqlite3_bind_int64(data
.st
, idx
, cast(ulong)v
);
494 sq3check(data
.owner
.db, rc
);
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");
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
);
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
);
523 private this (DBStatement
.Data
* adata
) nothrow @trusted @nogc {
525 DBStatement
.incref(data____
);
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
);
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
~"'");
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
);
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");
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
;
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))) {
585 } else static if (is(ElementEncodingType
!T
== immutable(char))) {
586 return res
[0..len
].idup
;
588 return res
[0..len
].dup
;
592 T
to(T
) (const(char)[] name
) { return this.to
!T(fieldIndex____(name
)); }
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____
;
612 private this (ref DBStatement astat
) {
614 DBStatement
.incref(data
);
616 assert(data
.stepIndex
== 0);
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");
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
) {
642 if (rc
!= SQLITE_ROW
) {
644 sq3check(data
.owner
.db, rc
);
649 auto index_ () pure const nothrow @nogc { return (data
.stepIndex
> 0 ? data
.stepIndex
-1 : 0); }
651 private DBStatement
.Data
* data
;
652 } // end of DBRowRange
655 static void incref (Data
* data
) nothrow @nogc @trusted {
656 if (data
!is null) ++data
.refcount
;
659 static void decref (Data
* data
) nothrow @trusted {
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
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
;
681 static void decrowref (Data
* data
) nothrow @trusted {
683 if (--data
.rowcount
== 0) {
685 if (data
.st
!is null) {
686 sqlite3_reset(data
.st
);
687 sqlite3_clear_bindings(data
.st
);
696 uint rowcount
= 0; // number of row structs using this statement
698 sqlite3_stmt
* st
= null;
701 Database
.DBInfo
* owner
= null;