1 /++
2 	SQLite implementation of the [arsd.database.Database] interface.
3 
4 
5 	Compile with `-version=sqlite_extended_metadata_available` if your sqlite is compiled with the `SQLITE_ENABLE_COLUMN_METADATA` C-preprocessor symbol.
6 
7 	If you enable that, you get the ability to use the queryDataObject() function with sqlite. (You can still use DataObjects, but you'll have to set up the mappings manually without the extended metadata.)
8 
9 	History:
10 		Originally written prior to July 2011 (before arsd on git).
11 
12 		Only lightly modified before then and May 2024 when it got an overhaul.
13 
14 		FIXME: `PRAGMA foreign_keys = ON` is something i wanna enable by default in here.
15 +/
16 module arsd.sqlite;
17 
18 version(static_sqlite) {} else
19 	pragma(lib, "sqlite3");
20 version(linux)
21 	pragma(lib, "dl"); // apparently sqlite3 depends on this
22 
23 public import arsd.database;
24 
25 import core.stdc.stdlib;
26 import core.exception;
27 import core.memory;
28 import std.conv;
29 import std.string;
30 import std.exception;
31 
32 /*
33 	NOTE:
34 
35 	This only works correctly on INSERTs if the user can grow the
36 	database file! This means he must have permission to write to
37 	both the file and the directory it is in.
38 
39 */
40 
41 
42 /++
43 	The Database interface provides a consistent and safe way to access sql RDBMSs.
44 
45 	Why are all the classes scope? To ensure the database connection is closed when you are done with it.
46 	The destructor cleans everything up.
47 
48 	(maybe including rolling back a transaction if one is going and it errors.... maybe, or that could bne
49 	scope(exit))
50 +/
51 Sqlite openDBAndCreateIfNotPresent(string filename, string sql, scope void delegate(Sqlite db) initialize = null){
52 	static import std.file;
53 	if(std.file.exists(filename))
54 		return new Sqlite(filename);
55 	else {
56 		auto db = new Sqlite(filename);
57 		db.exec(sql);
58 		if(initialize !is null)
59 			initialize(db);
60 		return db;
61 	}
62 }
63 
64 /*
65 import std.stdio;
66 void main() {
67 	Database db = new Sqlite("test.sqlite.db");
68 
69 	db.query("CREATE TABLE users (id integer, name text)");
70 
71 	db.query("INSERT INTO users values (?, ?)", 1, "hello");
72 
73 	foreach(line; db.query("SELECT * FROM users")) {
74 		writefln("%s %s", line[0], line["name"]);
75 	}
76 }
77 */
78 
79 /++
80 
81 +/
82 class Sqlite : Database {
83   public:
84 	/++
85 		Opens and creates the database, if desired.
86 
87 		History:
88 			The `flags` argument was ignored until July 29, 2022. (This function was originally written over 11 years ago, when sqlite3_open_v2 was not commonly supported on some distributions yet, and I didn't notice to revisit it for ages!)
89 	+/
90 	this(string filename, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) {
91 		int error = sqlite3_open_v2(toStringz(filename), &db, flags, null);
92 		if(error != SQLITE_OK)
93 			throw new DatabaseConnectionException(this.error());
94 	/+
95 		int error = sqlite3_open(toStringz(filename), &db);
96 		if(error != SQLITE_OK)
97 			throw new DatabaseException(this.error());
98 	+/
99 	}
100 
101 	~this(){
102 		if(sqlite3_close(db) != SQLITE_OK)
103 			throw new DatabaseException(error());
104 	}
105 
106 	string sysTimeToValue(SysTime s) {
107 		return "datetime('" ~ escape(s.toISOExtString()) ~ "')";
108 	}
109 
110 	// my extension for easier editing
111 	version(sqlite_extended_metadata_available) {
112 		ResultByDataObject queryDataObject(T...)(string sql, T t) {
113 			// modify sql for the best data object grabbing
114 			sql = fixupSqlForDataObjectUse(sql);
115 
116 			auto s = Statement(this, sql);
117 			foreach(i, arg; t) {
118 				s.bind(i + 1, arg);
119 			}
120 
121 			auto magic = s.execute(true); // fetch extended metadata
122 
123 			return ResultByDataObject(cast(SqliteResult) magic, magic.extendedMetadata, this);
124 		}
125 	}
126 
127 	override bool isAlive() {
128 		return true;
129 	}
130 
131 	///
132 	override void startTransaction() {
133 		query("BEGIN TRANSACTION");
134 	}
135 
136 	override ResultSet queryImpl(string sql, Variant[] args...) {
137 		auto s = Statement(this, sql);
138 		foreach(i, arg; args) {
139 			s.bind(cast(int) i + 1, arg);
140 		}
141 		return s.execute();
142 	}
143 
144 	override string escape(string sql) @system {
145 		if(sql is null)
146 			return null;
147 		char* got = sqlite3_mprintf("%q", toStringz(sql)); // FIXME: might have to be %Q, need to check this, but I think the other impls do the same as %q
148 		auto orig = got;
149 		string esc;
150 		while(*got) {
151 			esc ~= (*got);
152 			got++;
153 		}
154 
155 		sqlite3_free(orig);
156 
157 		return esc;
158 	}
159 
160 	string escapeBinaryString(const(ubyte)[] b) {
161 		return tohexsql(b);
162 	}
163 
164 	string error() @system {
165 		import core.stdc.string : strlen;
166 		char* mesg = sqlite3_errmsg(db);
167 		char[] m;
168 		sizediff_t a = strlen(mesg);
169 		m.length = a;
170 		for(int v = 0; v < a; v++)
171 			m[v] = mesg[v];
172 
173 		return assumeUnique(m);
174 	}
175 
176 	///
177 	int affectedRows(){
178 		return sqlite3_changes(db);
179 	}
180 
181 	///
182 	int lastInsertId(){
183 		return cast(int) sqlite3_last_insert_rowid(db);
184 	}
185 
186 
187 	int exec(string sql, void delegate (char[][char[]]) onEach = null) @system {
188 		char* mesg;
189 		if(sqlite3_exec(db, toStringz(sql), &callback, &onEach, &mesg) != SQLITE_OK) {
190 			import core.stdc.string : strlen;
191 			char[] m;
192 			sizediff_t a = strlen(mesg);
193 			m.length = a;
194 			for(int v = 0; v < a; v++)
195 				m[v] = mesg[v];
196 
197 			sqlite3_free(mesg);
198 			throw new SqlException("exec " ~ m.idup);
199 		}
200 
201 		return 0;
202 	}
203 /*
204 	Statement prepare(string sql){
205 		sqlite3_stmt * s;
206 		if(sqlite3_prepare_v2(db, toStringz(sql), cast(int) sql.length, &s, null) != SQLITE_OK)
207 			throw new DatabaseException("prepare " ~ error());
208 
209 		Statement a = new Statement(s);
210 
211 		return a;
212 	}
213 */
214   private:
215 	sqlite3* db;
216 }
217 
218 
219 class SqliteResult :  ResultSet {
220 	int getFieldIndex(string field) {
221 		foreach(i, n; columnNames)
222 			if(n == field)
223 				return cast(int) i;
224 		throw new Exception("no such field " ~ field);
225 	}
226 
227 	string[] fieldNames() {
228 		return columnNames;
229 	}
230 
231 	// this is a range that can offer other ranges to access it
232 	bool empty() {
233 		return position == rows.length;
234 	}
235 
236 	Row front() {
237 		Row r;
238 
239 		r.resultSet = this;
240 		if(rows.length <= position)
241 			throw new Exception("Result is empty");
242 		foreach(c; rows[position]) {
243 			if(auto t = c.peek!(immutable(ubyte)[]))
244 				r.row ~= DatabaseDatum(cast(string) *t);
245 			else if (auto d = c.peek!double)
246 				// 17 significant decimal digits are enough to not lose precision (IEEE 754 section 5.12.2)
247 				r.row ~= DatabaseDatum(format!"%.17s"(*d));
248 			else {
249 				r.row ~= DatabaseDatum(c.coerce!(string));
250 			}
251 		}
252 
253 		return r;
254 	}
255 
256 	void popFront() {
257 		position++;
258 	}
259 
260 	override size_t length() {
261 		return rows.length;
262 	}
263 
264 	this(Variant[][] rows, char[][] columnNames) {
265 		this.rows = rows;
266 		foreach(c; columnNames)
267 			this.columnNames ~= c.idup;
268 	}
269 
270     private:
271 	string[] columnNames;
272 	Variant[][] rows;
273 	int position = 0;
274 }
275 
276 
277 struct Statement {
278 	private this(Sqlite db, sqlite3_stmt * S) {
279 		this.db = db;
280 		s = S;
281 		finalized = false;
282 	}
283 
284 	Sqlite db;
285 
286 	this(Sqlite db, string sql) {
287 		// the arsd convention is zero based ?, but sqlite insists on one based. so this is stupid but still
288 		if(sql.indexOf("?0") != -1) {
289 			foreach_reverse(i; 0 .. 10)
290 				sql = sql.replace("?" ~ to!string(i), "?" ~ to!string(i + 1));
291 		}
292 
293 		this.db = db;
294 		if(sqlite3_prepare_v2(db.db, toStringz(sql), cast(int) sql.length, &s, null) != SQLITE_OK)
295 			throw new SqlException(db.error() ~ " " ~ sql);
296 	}
297 
298 	version(sqlite_extended_metadata_available)
299 		Tuple!(string, string)[string] extendedMetadata;
300 
301 	ResultSet execute(bool fetchExtendedMetadata = false) @system {
302 		bool first = true;
303 		int count;
304 		int numRows = 0;
305 		int r = 0;
306 		// FIXME: doesn't handle busy database
307 		while( SQLITE_ROW == sqlite3_step(s) ){
308 			numRows++;
309 			if(numRows >= rows.length)
310 				rows.length = rows.length + 8;
311 
312 			if(first){
313 				count = sqlite3_column_count(s);
314 
315 				columnNames.length = count;
316 				for(int a = 0; a < count; a++){
317 					import core.stdc.string : strlen;
318 					char* str = sqlite3_column_name(s, a);
319 					sizediff_t l = strlen(str);
320 					columnNames[a].length = l;
321 					for(int b = 0; b < l; b++)
322 						columnNames[a][b] = str[b];
323 
324 					version(sqlite_extended_metadata_available) {
325 					if(fetchExtendedMetadata) {
326 						string origtbl;
327 						string origcol;
328 
329 						const(char)* rofl;
330 
331 						rofl = sqlite3_column_table_name(s, a);
332 						if(rofl is null)
333 							throw new Exception("null table name pointer");
334 						while(*rofl) {
335 							origtbl ~= *rofl;
336 							rofl++;
337 						}
338 						rofl = sqlite3_column_origin_name(s, a);
339 						if(rofl is null)
340 							throw new Exception("null colum name pointer");
341 						while(*rofl) {
342 							origcol ~= *rofl;
343 							rofl++;
344 						}
345 						extendedMetadata[columnNames[a].idup] = tuple(origtbl, origcol);
346 					}
347 					}
348 				}
349 
350 				first = false;
351 			}
352 
353 
354 			rows[r].length = count;
355 
356 			for(int a = 0; a < count; a++){
357 				Variant v;
358 				final switch(sqlite3_column_type(s, a)){
359 					case SQLITE_INTEGER:
360 						v = sqlite3_column_int64(s, a);
361 					break;
362 					case SQLITE_FLOAT:
363 						v = sqlite3_column_double(s, a);
364 					break;
365 					case SQLITE3_TEXT:
366 						char* str = sqlite3_column_text(s, a);
367 						char[] st;
368 
369 						import core.stdc.string : strlen;
370 						sizediff_t l = strlen(str);
371 						st.length = l;
372 						st[] = str[0 ..l];
373 
374 						v = assumeUnique(st);
375 					break;
376 					case SQLITE_BLOB:
377 						ubyte* str = cast(ubyte*) sqlite3_column_blob(s, a);
378 						ubyte[] st;
379 
380 						int l = sqlite3_column_bytes(s, a);
381 						st.length = l;
382 						st[] = str[0 .. l];
383 
384 						v = assumeUnique(st);
385 					break;
386 					case SQLITE_NULL:
387 						string n = null;
388 						v = n;
389 					break;
390 				}
391 
392 				rows[r][a] = v;
393 			}
394 
395 			r++;
396 		}
397 
398 		rows.length = numRows;
399 		length = numRows;
400 		position = 0;
401 		executed = true;
402 		reset();
403 
404 		return new SqliteResult(rows.dup, columnNames);
405 	}
406 
407 /*
408 template extract(A, T, R...){
409 	void extract(A args, out T t, out R r){
410 		if(r.length + 1 != args.length)
411 			throw new DatabaseException("wrong places");
412 		args[0].to(t);
413 		static if(r.length)
414 			extract(args[1..$], r);
415 	}
416 }
417 */
418 /*
419 	bool next(T, R...)(out T t, out R r){
420 		if(position == length)
421 			return false;
422 
423 		extract(rows[position], t, r);
424 
425 		position++;
426 		return true;
427 	}
428 */
429 	bool step(out Variant[] row){
430 		assert(executed);
431 		if(position == length)
432 			return false;
433 
434 		row = rows[position];
435 		position++;
436 
437 		return true;
438 	}
439 
440 	bool step(out Variant[char[]] row){
441 		assert(executed);
442 		if(position == length)
443 			return false;
444 
445 		for(int a = 0; a < length; a++)
446 			row[columnNames[a].idup] = rows[position][a];
447 
448 		position++;
449 
450 		return true;
451 	}
452 
453 	void reset(){
454 		if(sqlite3_reset(s) != SQLITE_OK)
455 			throw new DatabaseException("reset " ~ db.error());
456 	}
457 
458 	void resetBindings(){
459 		sqlite3_clear_bindings(s);
460 	}
461 
462 	void resetAll(){
463 		reset;
464 		resetBindings;
465 		executed = false;
466 	}
467 
468 	int bindNameLookUp(const char[] name){
469 		int a = sqlite3_bind_parameter_index(s, toStringz(name));
470 		if(a == 0)
471 			throw new DatabaseException("bind name lookup failed " ~ db.error());
472 		return a;
473 	}
474 
475 	bool next(T, R...)(out T t, out R r){
476 		assert(executed);
477 		if(position == length)
478 			return false;
479 
480 		extract(rows[position], t, r);
481 
482 		position++;
483 		return true;
484 	}
485 
486 	template bindAll(T, R...){
487 		void bindAll(T what, R more){
488 			bindAllHelper(1, what, more);
489 		}
490 	}
491 
492 	template exec(T, R...){
493 		void exec(T what, R more){
494 			bindAllHelper(1, what, more);
495 			execute();
496 		}
497 	}
498 
499 	void bindAllHelper(A, T, R...)(A where, T what, R more){
500 		bind(where, what);
501 		static if(more.length)
502 			bindAllHelper(where + 1, more);
503 	}
504 
505 	//void bind(T)(string name, T value) {
506 		//bind(bindNameLookUp(name), value);
507 	//}
508 
509 		// This should be a template, but grrrr.
510 		void bind (const char[] name, const char[] value){ bind(bindNameLookUp(name), value); }
511 		void bind (const char[] name, int value){ bind(bindNameLookUp(name), value); }
512 		void bind (const char[] name, float value){ bind(bindNameLookUp(name), value); }
513 		void bind (const char[] name, double value){ bind(bindNameLookUp(name), value); }
514 		void bind (const char[] name, const byte[] value){ bind(bindNameLookUp(name), value); }
515 		void bind (const char[] name, const ubyte[] value){ bind(bindNameLookUp(name), value); }
516 
517 	void bind(int col, typeof(null) value){
518 		if(sqlite3_bind_null(s, col) != SQLITE_OK)
519 			throw new DatabaseException("bind " ~ db.error());
520 	}
521 	void bind(int col, const char[] value){
522 		if(sqlite3_bind_text(s, col, value.ptr is null ? "" : value.ptr, cast(int) value.length, cast(void*)-1) != SQLITE_OK)
523 			throw new DatabaseException("bind " ~ db.error());
524 	}
525 
526 	void bind(int col, float value){
527 		if(sqlite3_bind_double(s, col, value) != SQLITE_OK)
528 			throw new DatabaseException("bind " ~ db.error());
529 	}
530 
531 	void bind(int col, double value){
532 		if(sqlite3_bind_double(s, col, value) != SQLITE_OK)
533 			throw new DatabaseException("bind " ~ db.error());
534 	}
535 
536 	void bind(int col, int value){
537 		if(sqlite3_bind_int(s, col, value) != SQLITE_OK)
538 			throw new DatabaseException("bind " ~ db.error());
539 	}
540 
541 	void bind(int col, long value){
542 		if(sqlite3_bind_int64(s, col, value) != SQLITE_OK)
543 			throw new DatabaseException("bind " ~ db.error());
544 	}
545 
546 	void bind(int col, const ubyte[] value){
547 		if(value is null) {
548 			if(sqlite3_bind_null(s, col) != SQLITE_OK)
549 				throw new DatabaseException("bind " ~ db.error());
550 		} else {
551 			if(sqlite3_bind_blob(s, col, cast(void*)value.ptr, cast(int) value.length, cast(void*)-1) != SQLITE_OK)
552 				throw new DatabaseException("bind " ~ db.error());
553 		}
554 	}
555 
556 	void bind(int col, const byte[] value){
557 		if(value is null) {
558 			if(sqlite3_bind_null(s, col) != SQLITE_OK)
559 				throw new DatabaseException("bind " ~ db.error());
560 		} else {
561 			if(sqlite3_bind_blob(s, col, cast(void*)value.ptr, cast(int) value.length, cast(void*)-1) != SQLITE_OK)
562 				throw new DatabaseException("bind " ~ db.error());
563 		}
564 	}
565 
566 	void bind(int col, Variant v) {
567 		if(v.peek!long)
568 			bind(col, v.get!long);
569 		else if(v.peek!ulong)
570 			bind(col, v.get!ulong);
571 		else if(v.peek!int)
572 			bind(col, v.get!int);
573 		else if(v.peek!(const(int)))
574 			bind(col, v.get!(const(int)));
575 		else if(v.peek!bool)
576 			bind(col, v.get!bool ? 1 : 0);
577 		else if(v.peek!DateTime)
578 			bind(col, v.get!DateTime.toISOExtString());
579 		else if(v.peek!string)
580 			bind(col, v.get!string);
581 		else if(v.peek!float)
582 			bind(col, v.get!float);
583 		else if(v.peek!double)
584 			bind(col, v.get!double);
585 		else if(v.peek!(byte[]))
586 			bind(col, v.get!(byte[]));
587 		else if(v.peek!(ubyte[]))
588 			bind(col, v.get!(ubyte[]));
589 		else if(v.peek!(immutable(ubyte)[]))
590 			bind(col, v.get!(immutable(ubyte)[]));
591 		else if(v.peek!(void*) && v.get!(void*) is null)
592 			bind(col, null);
593 		else
594 			bind(col, v.coerce!string);
595 		//assert(0, v.type.toString ~ " " ~ v.coerce!string);
596 	}
597 
598 	~this(){
599 		if(!finalized)
600 			finalize();
601 	}
602 
603 	void finalize(){
604 		if(finalized)
605 			return;
606 		if(sqlite3_finalize(s) != SQLITE_OK)
607 			throw new DatabaseException("finalize " ~ db.error());
608 		finalized = true;
609 	}
610   private:
611 	Variant[][] rows;
612 	char[][]  columnNames;
613 	int length;
614 	int position;
615 	bool finalized;
616 
617 	sqlite3_stmt * s;
618 
619 	bool executed;
620 
621 }
622 
623 
624 version(sqlite_extended_metadata_available) {
625 	import std.typecons;
626 	struct ResultByDataObject {
627 		this(SqliteResult r, Tuple!(string, string)[string] mappings, Sqlite db) {
628 			result = r;
629 			this.db = db;
630 			this.mappings = mappings;
631 		}
632 
633 		Tuple!(string, string)[string] mappings;
634 
635 		ulong length() { return result.length; }
636 		bool empty() { return result.empty; }
637 		void popFront() { result.popFront(); }
638 		DataObject front() {
639 			return new DataObject(db, result.front.toAA, mappings);
640 		}
641 		// would it be good to add a new() method? would be valid even if empty
642 		// it'd just fill in the ID's at random and allow you to do the rest
643 
644 		@disable this(this) { }
645 
646 		SqliteResult result;
647 		Sqlite db;
648 	}
649 }
650 
651 
652 extern(C) int callback(void* cb, int howmany, char** text, char** columns) @system {
653 	if(cb is null)
654 		return 0;
655 
656 	void delegate(char[][char[]]) onEach = *cast(void delegate(char[][char[]])*) cb;
657 
658 	char[][char[]] row;
659 	import core.stdc.string : strlen;
660 
661 	for(int a = 0; a < howmany; a++){
662 		sizediff_t b = strlen(columns[a]);
663 		char[] buf;
664 		buf.length = b;
665 		for(int c = 0; c < b; c++)
666 			buf[c] = columns[a][c];
667 
668 		sizediff_t d = strlen(text[a]);
669 		char[] t;
670 		t.length = d;
671 		for(int c = 0; c < d; c++)
672 			t[c] = text[a][c];
673 
674 		row[buf.idup] = t;
675 	}
676 
677 	onEach(row);
678 
679 	return 0;
680 }
681 
682 extern(C) {
683 	struct sqlite3;
684 	struct sqlite3_stmt;
685 
686 	enum int SQLITE_OK = 0;
687 	enum int SQLITE_ROW = 100;
688 	enum int SQLITE_DONE = 101;
689 
690 	enum int SQLITE_INTEGER = 1; // int
691 	enum int SQLITE_FLOAT = 2;   // float
692 	enum int SQLITE3_TEXT = 3;   // char[]
693 	enum int SQLITE_BLOB = 4;    // ubyte[]
694 	enum int SQLITE_NULL = 5;    // void* = null
695 
696 	enum int SQLITE_DELETE = 9; // table name, null
697 	enum int SQLITE_INSERT = 18; // table name, null
698 	enum int SQLITE_UPDATE = 23; // table name, column name
699 
700 	enum int SQLITE_OPEN_READONLY = 0x1;
701 	enum int SQLITE_OPEN_READWRITE = 0x2;
702 	enum int SQLITE_OPEN_CREATE = 0x4;
703 	enum int SQLITE_CANTOPEN = 14;
704 
705 
706 	int sqlite3_changes(sqlite3*);
707 	int sqlite3_close(sqlite3 *);
708 	int sqlite3_exec(
709 		sqlite3*,                                  /* An open database */
710 		const(char) *sql,                           /* SQL to be evaluted */
711 		int function(void*,int,char**,char**),  /* Callback function */
712 		void *,                                    /* 1st argument to callback */
713 		char **errmsg                              /* Error msg written here */
714 	);
715 
716 	int sqlite3_open(
717 		const(char) *filename,   /* Database filename (UTF-8) */
718 		sqlite3 **ppDb          /* OUT: SQLite db handle */
719 	);
720 
721 	int sqlite3_open_v2(
722 		const char *filename,   /* Database filename (UTF-8) */
723 		sqlite3 **ppDb,         /* OUT: SQLite db handle */
724 		int flags,              /* Flags */
725 		const char *zVfs        /* Name of VFS module to use */
726 	);
727 
728 	int sqlite3_prepare_v2(
729 		sqlite3 *db,            /* Database handle */
730 		const(char) *zSql,       /* SQL statement, UTF-8 encoded */
731 		int nByte,              /* Maximum length of zSql in bytes. */
732 		sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
733 		char **pzTail     /* OUT: Pointer to unused portion of zSql */
734 	);
735 
736 	int sqlite3_finalize(sqlite3_stmt *pStmt);
737 	int sqlite3_step(sqlite3_stmt*);
738 	long sqlite3_last_insert_rowid(sqlite3*);
739 
740 	char *sqlite3_mprintf(const char*,...);
741 
742 	int sqlite3_reset(sqlite3_stmt *pStmt);
743 	int sqlite3_clear_bindings(sqlite3_stmt*);
744 	int sqlite3_bind_parameter_index(sqlite3_stmt*, const(char) *zName);
745 
746 	int sqlite3_bind_blob(sqlite3_stmt*, int, void*, int n, void*);
747 	//int sqlite3_bind_blob(sqlite3_stmt*, int, void*, int n, void(*)(void*));
748 	int sqlite3_bind_double(sqlite3_stmt*, int, double);
749 	int sqlite3_bind_int(sqlite3_stmt*, int, int);
750 	int sqlite3_bind_int64(sqlite3_stmt*, int, long);
751 	int sqlite3_bind_null(sqlite3_stmt*, int);
752 	int sqlite3_bind_text(sqlite3_stmt*, int, const(char)*, int n, void*);
753 	//int sqlite3_bind_text(sqlite3_stmt*, int, char*, int n, void(*)(void*));
754 
755 	void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
756 	int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
757 	double sqlite3_column_double(sqlite3_stmt*, int iCol);
758 	int sqlite3_column_int(sqlite3_stmt*, int iCol);
759 	long sqlite3_column_int64(sqlite3_stmt*, int iCol);
760 	char *sqlite3_column_text(sqlite3_stmt*, int iCol);
761 	int sqlite3_column_type(sqlite3_stmt*, int iCol);
762 	char *sqlite3_column_name(sqlite3_stmt*, int N);
763 
764 	int sqlite3_column_count(sqlite3_stmt *pStmt);
765 	void sqlite3_free(void*);
766 	char *sqlite3_errmsg(sqlite3*);
767 
768 	// will need these to enable support for DataObjects here
769 	const(char*) sqlite3_column_database_name(sqlite3_stmt*,int);
770 	const(char*) sqlite3_column_table_name(sqlite3_stmt*,int);
771 	const(char*) sqlite3_column_origin_name(sqlite3_stmt*,int);
772 
773 	// https://www.sqlite.org/c3ref/update_hook.html
774 	void* sqlite3_update_hook(sqlite3* db, updatehookcallback cb, void* userData); // returns the old userData
775 }
776 
777 extern(C) alias updatehookcallback = void function(void* userData, int op, char* databaseName, char* tableName, /* sqlite3_int64 */ long rowid);