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 DatabaseException(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 	///
128 	override void startTransaction() {
129 		query("BEGIN TRANSACTION");
130 	}
131 
132 	override ResultSet queryImpl(string sql, Variant[] args...) {
133 		auto s = Statement(this, sql);
134 		foreach(i, arg; args) {
135 			s.bind(cast(int) i + 1, arg);
136 		}
137 		return s.execute();
138 	}
139 
140 	override string escape(string sql) @system {
141 		if(sql is null)
142 			return null;
143 		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
144 		auto orig = got;
145 		string esc;
146 		while(*got) {
147 			esc ~= (*got);
148 			got++;
149 		}
150 
151 		sqlite3_free(orig);
152 
153 		return esc;
154 	}
155 
156 	string escapeBinaryString(const(ubyte)[] b) {
157 		return tohexsql(b);
158 	}
159 
160 	string error() @system {
161 		import core.stdc.string : strlen;
162 		char* mesg = sqlite3_errmsg(db);
163 		char[] m;
164 		sizediff_t a = strlen(mesg);
165 		m.length = a;
166 		for(int v = 0; v < a; v++)
167 			m[v] = mesg[v];
168 
169 		return assumeUnique(m);
170 	}
171 
172 	///
173 	int affectedRows(){
174 		return sqlite3_changes(db);
175 	}
176 
177 	///
178 	int lastInsertId(){
179 		return cast(int) sqlite3_last_insert_rowid(db);
180 	}
181 
182 
183 	int exec(string sql, void delegate (char[][char[]]) onEach = null) @system {
184 		char* mesg;
185 		if(sqlite3_exec(db, toStringz(sql), &callback, &onEach, &mesg) != SQLITE_OK) {
186 			import core.stdc.string : strlen;
187 			char[] m;
188 			sizediff_t a = strlen(mesg);
189 			m.length = a;
190 			for(int v = 0; v < a; v++)
191 				m[v] = mesg[v];
192 
193 			sqlite3_free(mesg);
194 			throw new DatabaseException("exec " ~ m.idup);
195 		}
196 
197 		return 0;
198 	}
199 /*
200 	Statement prepare(string sql){
201 		sqlite3_stmt * s;
202 		if(sqlite3_prepare_v2(db, toStringz(sql), cast(int) sql.length, &s, null) != SQLITE_OK)
203 			throw new DatabaseException("prepare " ~ error());
204 
205 		Statement a = new Statement(s);
206 
207 		return a;
208 	}
209 */
210   private:
211 	sqlite3* db;
212 }
213 
214 
215 class SqliteResult :  ResultSet {
216 	int getFieldIndex(string field) {
217 		foreach(i, n; columnNames)
218 			if(n == field)
219 				return cast(int) i;
220 		throw new Exception("no such field " ~ field);
221 	}
222 
223 	string[] fieldNames() {
224 		return columnNames;
225 	}
226 
227 	// this is a range that can offer other ranges to access it
228 	bool empty() {
229 		return position == rows.length;
230 	}
231 
232 	Row front() {
233 		Row r;
234 
235 		r.resultSet = this;
236 		if(rows.length <= position)
237 			throw new Exception("Result is empty");
238 		foreach(c; rows[position]) {
239 			if(auto t = c.peek!(immutable(ubyte)[]))
240 				r.row ~= DatabaseDatum(cast(string) *t);
241 			else if (auto d = c.peek!double)
242 				// 17 significant decimal digits are enough to not lose precision (IEEE 754 section 5.12.2)
243 				r.row ~= DatabaseDatum(format!"%.17s"(*d));
244 			else
245 				r.row ~= DatabaseDatum(c.coerce!(string));
246 		}
247 
248 		return r;
249 	}
250 
251 	void popFront() {
252 		position++;
253 	}
254 
255 	override size_t length() {
256 		return rows.length;
257 	}
258 
259 	this(Variant[][] rows, char[][] columnNames) {
260 		this.rows = rows;
261 		foreach(c; columnNames)
262 			this.columnNames ~= c.idup;
263 	}
264 
265     private:
266 	string[] columnNames;
267 	Variant[][] rows;
268 	int position = 0;
269 }
270 
271 
272 struct Statement {
273 	private this(Sqlite db, sqlite3_stmt * S) {
274 		this.db = db;
275 		s = S;
276 		finalized = false;
277 	}
278 
279 	Sqlite db;
280 
281 	this(Sqlite db, string sql) {
282 		// the arsd convention is zero based ?, but sqlite insists on one based. so this is stupid but still
283 		if(sql.indexOf("?0") != -1) {
284 			foreach_reverse(i; 0 .. 10)
285 				sql = sql.replace("?" ~ to!string(i), "?" ~ to!string(i + 1));
286 		}
287 
288 		this.db = db;
289 		if(sqlite3_prepare_v2(db.db, toStringz(sql), cast(int) sql.length, &s, null) != SQLITE_OK)
290 			throw new DatabaseException(db.error() ~ " " ~ sql);
291 	}
292 
293 	version(sqlite_extended_metadata_available)
294 		Tuple!(string, string)[string] extendedMetadata;
295 
296 	ResultSet execute(bool fetchExtendedMetadata = false) @system {
297 		bool first = true;
298 		int count;
299 		int numRows = 0;
300 		int r = 0;
301 		// FIXME: doesn't handle busy database
302 		while( SQLITE_ROW == sqlite3_step(s) ){
303 			numRows++;
304 			if(numRows >= rows.length)
305 				rows.length = rows.length + 8;
306 
307 			if(first){
308 				count = sqlite3_column_count(s);
309 
310 				columnNames.length = count;
311 				for(int a = 0; a < count; a++){
312 					import core.stdc.string : strlen;
313 					char* str = sqlite3_column_name(s, a);
314 					sizediff_t l = strlen(str);
315 					columnNames[a].length = l;
316 					for(int b = 0; b < l; b++)
317 						columnNames[a][b] = str[b];
318 
319 					version(sqlite_extended_metadata_available) {
320 					if(fetchExtendedMetadata) {
321 						string origtbl;
322 						string origcol;
323 
324 						const(char)* rofl;
325 
326 						rofl = sqlite3_column_table_name(s, a);
327 						if(rofl is null)
328 							throw new Exception("null table name pointer");
329 						while(*rofl) {
330 							origtbl ~= *rofl;
331 							rofl++;
332 						}
333 						rofl = sqlite3_column_origin_name(s, a);
334 						if(rofl is null)
335 							throw new Exception("null colum name pointer");
336 						while(*rofl) {
337 							origcol ~= *rofl;
338 							rofl++;
339 						}
340 						extendedMetadata[columnNames[a].idup] = tuple(origtbl, origcol);
341 					}
342 					}
343 				}
344 
345 				first = false;
346 			}
347 
348 
349 			rows[r].length = count;
350 
351 			for(int a = 0; a < count; a++){
352 				Variant v;
353 				final switch(sqlite3_column_type(s, a)){
354 					case SQLITE_INTEGER:
355 						v = sqlite3_column_int64(s, a);
356 					break;
357 					case SQLITE_FLOAT:
358 						v = sqlite3_column_double(s, a);
359 					break;
360 					case SQLITE3_TEXT:
361 						char* str = sqlite3_column_text(s, a);
362 						char[] st;
363 
364 						import core.stdc.string : strlen;
365 						sizediff_t l = strlen(str);
366 						st.length = l;
367 						st[] = str[0 ..l];
368 
369 						v = assumeUnique(st);
370 					break;
371 					case SQLITE_BLOB:
372 						ubyte* str = cast(ubyte*) sqlite3_column_blob(s, a);
373 						ubyte[] st;
374 
375 						int l = sqlite3_column_bytes(s, a);
376 						st.length = l;
377 						st[] = str[0 .. l];
378 
379 						v = assumeUnique(st);
380 					break;
381 					case SQLITE_NULL:
382 						string n = null;
383 						v = n;
384 					break;
385 				}
386 
387 				rows[r][a] = v;
388 			}
389 
390 			r++;
391 		}
392 
393 		rows.length = numRows;
394 		length = numRows;
395 		position = 0;
396 		executed = true;
397 		reset();
398 
399 		return new SqliteResult(rows.dup, columnNames);
400 	}
401 
402 /*
403 template extract(A, T, R...){
404 	void extract(A args, out T t, out R r){
405 		if(r.length + 1 != args.length)
406 			throw new DatabaseException("wrong places");
407 		args[0].to(t);
408 		static if(r.length)
409 			extract(args[1..$], r);
410 	}
411 }
412 */
413 /*
414 	bool next(T, R...)(out T t, out R r){
415 		if(position == length)
416 			return false;
417 
418 		extract(rows[position], t, r);
419 
420 		position++;
421 		return true;
422 	}
423 */
424 	bool step(out Variant[] row){
425 		assert(executed);
426 		if(position == length)
427 			return false;
428 
429 		row = rows[position];
430 		position++;
431 
432 		return true;
433 	}
434 
435 	bool step(out Variant[char[]] row){
436 		assert(executed);
437 		if(position == length)
438 			return false;
439 
440 		for(int a = 0; a < length; a++)
441 			row[columnNames[a].idup] = rows[position][a];
442 
443 		position++;
444 
445 		return true;
446 	}
447 
448 	void reset(){
449 		if(sqlite3_reset(s) != SQLITE_OK)
450 			throw new DatabaseException("reset " ~ db.error());
451 	}
452 
453 	void resetBindings(){
454 		sqlite3_clear_bindings(s);
455 	}
456 
457 	void resetAll(){
458 		reset;
459 		resetBindings;
460 		executed = false;
461 	}
462 
463 	int bindNameLookUp(const char[] name){
464 		int a = sqlite3_bind_parameter_index(s, toStringz(name));
465 		if(a == 0)
466 			throw new DatabaseException("bind name lookup failed " ~ db.error());
467 		return a;
468 	}
469 
470 	bool next(T, R...)(out T t, out R r){
471 		assert(executed);
472 		if(position == length)
473 			return false;
474 
475 		extract(rows[position], t, r);
476 
477 		position++;
478 		return true;
479 	}
480 
481 	template bindAll(T, R...){
482 		void bindAll(T what, R more){
483 			bindAllHelper(1, what, more);
484 		}
485 	}
486 
487 	template exec(T, R...){
488 		void exec(T what, R more){
489 			bindAllHelper(1, what, more);
490 			execute();
491 		}
492 	}
493 
494 	void bindAllHelper(A, T, R...)(A where, T what, R more){
495 		bind(where, what);
496 		static if(more.length)
497 			bindAllHelper(where + 1, more);
498 	}
499 
500 	//void bind(T)(string name, T value) {
501 		//bind(bindNameLookUp(name), value);
502 	//}
503 
504 		// This should be a template, but grrrr.
505 		void bind (const char[] name, const char[] value){ bind(bindNameLookUp(name), value); }
506 		void bind (const char[] name, int value){ bind(bindNameLookUp(name), value); }
507 		void bind (const char[] name, float value){ bind(bindNameLookUp(name), value); }
508 		void bind (const char[] name, double value){ bind(bindNameLookUp(name), value); }
509 		void bind (const char[] name, const byte[] value){ bind(bindNameLookUp(name), value); }
510 		void bind (const char[] name, const ubyte[] value){ bind(bindNameLookUp(name), value); }
511 
512 	void bind(int col, typeof(null) value){
513 		if(sqlite3_bind_null(s, col) != SQLITE_OK)
514 			throw new DatabaseException("bind " ~ db.error());
515 	}
516 	void bind(int col, const char[] value){
517 		if(sqlite3_bind_text(s, col, value.ptr is null ? "" : value.ptr, cast(int) value.length, cast(void*)-1) != SQLITE_OK)
518 			throw new DatabaseException("bind " ~ db.error());
519 	}
520 
521 	void bind(int col, float value){
522 		if(sqlite3_bind_double(s, col, value) != SQLITE_OK)
523 			throw new DatabaseException("bind " ~ db.error());
524 	}
525 
526 	void bind(int col, double 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, int value){
532 		if(sqlite3_bind_int(s, col, value) != SQLITE_OK)
533 			throw new DatabaseException("bind " ~ db.error());
534 	}
535 
536 	void bind(int col, long value){
537 		if(sqlite3_bind_int64(s, col, value) != SQLITE_OK)
538 			throw new DatabaseException("bind " ~ db.error());
539 	}
540 
541 	void bind(int col, const ubyte[] value){
542 		if(value is null) {
543 			if(sqlite3_bind_null(s, col) != SQLITE_OK)
544 				throw new DatabaseException("bind " ~ db.error());
545 		} else {
546 			if(sqlite3_bind_blob(s, col, cast(void*)value.ptr, cast(int) value.length, cast(void*)-1) != SQLITE_OK)
547 				throw new DatabaseException("bind " ~ db.error());
548 		}
549 	}
550 
551 	void bind(int col, const byte[] value){
552 		if(value is null) {
553 			if(sqlite3_bind_null(s, col) != SQLITE_OK)
554 				throw new DatabaseException("bind " ~ db.error());
555 		} else {
556 			if(sqlite3_bind_blob(s, col, cast(void*)value.ptr, cast(int) value.length, cast(void*)-1) != SQLITE_OK)
557 				throw new DatabaseException("bind " ~ db.error());
558 		}
559 	}
560 
561 	void bind(int col, Variant v) {
562 		if(v.peek!long)
563 			bind(col, v.get!long);
564 		else if(v.peek!ulong)
565 			bind(col, v.get!ulong);
566 		else if(v.peek!int)
567 			bind(col, v.get!int);
568 		else if(v.peek!(const(int)))
569 			bind(col, v.get!(const(int)));
570 		else if(v.peek!bool)
571 			bind(col, v.get!bool ? 1 : 0);
572 		else if(v.peek!DateTime)
573 			bind(col, v.get!DateTime.toISOExtString());
574 		else if(v.peek!string)
575 			bind(col, v.get!string);
576 		else if(v.peek!float)
577 			bind(col, v.get!float);
578 		else if(v.peek!double)
579 			bind(col, v.get!double);
580 		else if(v.peek!(byte[]))
581 			bind(col, v.get!(byte[]));
582 		else if(v.peek!(ubyte[]))
583 			bind(col, v.get!(ubyte[]));
584 		else if(v.peek!(immutable(ubyte)[]))
585 			bind(col, v.get!(immutable(ubyte)[]));
586 		else if(v.peek!(void*) && v.get!(void*) is null)
587 			bind(col, null);
588 		else
589 			bind(col, v.coerce!string);
590 		//assert(0, v.type.toString ~ " " ~ v.coerce!string);
591 	}
592 
593 	~this(){
594 		if(!finalized)
595 			finalize();
596 	}
597 
598 	void finalize(){
599 		if(finalized)
600 			return;
601 		if(sqlite3_finalize(s) != SQLITE_OK)
602 			throw new DatabaseException("finalize " ~ db.error());
603 		finalized = true;
604 	}
605   private:
606 	Variant[][] rows;
607 	char[][]  columnNames;
608 	int length;
609 	int position;
610 	bool finalized;
611 
612 	sqlite3_stmt * s;
613 
614 	bool executed;
615 
616 }
617 
618 
619 version(sqlite_extended_metadata_available) {
620 	import std.typecons;
621 	struct ResultByDataObject {
622 		this(SqliteResult r, Tuple!(string, string)[string] mappings, Sqlite db) {
623 			result = r;
624 			this.db = db;
625 			this.mappings = mappings;
626 		}
627 
628 		Tuple!(string, string)[string] mappings;
629 
630 		ulong length() { return result.length; }
631 		bool empty() { return result.empty; }
632 		void popFront() { result.popFront(); }
633 		DataObject front() {
634 			return new DataObject(db, result.front.toAA, mappings);
635 		}
636 		// would it be good to add a new() method? would be valid even if empty
637 		// it'd just fill in the ID's at random and allow you to do the rest
638 
639 		@disable this(this) { }
640 
641 		SqliteResult result;
642 		Sqlite db;
643 	}
644 }
645 
646 
647 extern(C) int callback(void* cb, int howmany, char** text, char** columns) @system {
648 	if(cb is null)
649 		return 0;
650 
651 	void delegate(char[][char[]]) onEach = *cast(void delegate(char[][char[]])*) cb;
652 
653 	char[][char[]] row;
654 	import core.stdc.string : strlen;
655 
656 	for(int a = 0; a < howmany; a++){
657 		sizediff_t b = strlen(columns[a]);
658 		char[] buf;
659 		buf.length = b;
660 		for(int c = 0; c < b; c++)
661 			buf[c] = columns[a][c];
662 
663 		sizediff_t d = strlen(text[a]);
664 		char[] t;
665 		t.length = d;
666 		for(int c = 0; c < d; c++)
667 			t[c] = text[a][c];
668 
669 		row[buf.idup] = t;
670 	}
671 
672 	onEach(row);
673 
674 	return 0;
675 }
676 
677 extern(C) {
678 	struct sqlite3;
679 	struct sqlite3_stmt;
680 
681 	enum int SQLITE_OK = 0;
682 	enum int SQLITE_ROW = 100;
683 	enum int SQLITE_DONE = 101;
684 
685 	enum int SQLITE_INTEGER = 1; // int
686 	enum int SQLITE_FLOAT = 2;   // float
687 	enum int SQLITE3_TEXT = 3;   // char[]
688 	enum int SQLITE_BLOB = 4;    // ubyte[]
689 	enum int SQLITE_NULL = 5;    // void* = null
690 
691 	enum int SQLITE_DELETE = 9; // table name, null
692 	enum int SQLITE_INSERT = 18; // table name, null
693 	enum int SQLITE_UPDATE = 23; // table name, column name
694 
695 	enum int SQLITE_OPEN_READONLY = 0x1;
696 	enum int SQLITE_OPEN_READWRITE = 0x2;
697 	enum int SQLITE_OPEN_CREATE = 0x4;
698 	enum int SQLITE_CANTOPEN = 14;
699 
700 
701 	int sqlite3_changes(sqlite3*);
702 	int sqlite3_close(sqlite3 *);
703 	int sqlite3_exec(
704 		sqlite3*,                                  /* An open database */
705 		const(char) *sql,                           /* SQL to be evaluted */
706 		int function(void*,int,char**,char**),  /* Callback function */
707 		void *,                                    /* 1st argument to callback */
708 		char **errmsg                              /* Error msg written here */
709 	);
710 
711 	int sqlite3_open(
712 		const(char) *filename,   /* Database filename (UTF-8) */
713 		sqlite3 **ppDb          /* OUT: SQLite db handle */
714 	);
715 
716 	int sqlite3_open_v2(
717 		const char *filename,   /* Database filename (UTF-8) */
718 		sqlite3 **ppDb,         /* OUT: SQLite db handle */
719 		int flags,              /* Flags */
720 		const char *zVfs        /* Name of VFS module to use */
721 	);
722 
723 	int sqlite3_prepare_v2(
724 		sqlite3 *db,            /* Database handle */
725 		const(char) *zSql,       /* SQL statement, UTF-8 encoded */
726 		int nByte,              /* Maximum length of zSql in bytes. */
727 		sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
728 		char **pzTail     /* OUT: Pointer to unused portion of zSql */
729 	);
730 
731 	int sqlite3_finalize(sqlite3_stmt *pStmt);
732 	int sqlite3_step(sqlite3_stmt*);
733 	long sqlite3_last_insert_rowid(sqlite3*);
734 
735 	char *sqlite3_mprintf(const char*,...);
736 
737 	int sqlite3_reset(sqlite3_stmt *pStmt);
738 	int sqlite3_clear_bindings(sqlite3_stmt*);
739 	int sqlite3_bind_parameter_index(sqlite3_stmt*, const(char) *zName);
740 
741 	int sqlite3_bind_blob(sqlite3_stmt*, int, void*, int n, void*);
742 	//int sqlite3_bind_blob(sqlite3_stmt*, int, void*, int n, void(*)(void*));
743 	int sqlite3_bind_double(sqlite3_stmt*, int, double);
744 	int sqlite3_bind_int(sqlite3_stmt*, int, int);
745 	int sqlite3_bind_int64(sqlite3_stmt*, int, long);
746 	int sqlite3_bind_null(sqlite3_stmt*, int);
747 	int sqlite3_bind_text(sqlite3_stmt*, int, const(char)*, int n, void*);
748 	//int sqlite3_bind_text(sqlite3_stmt*, int, char*, int n, void(*)(void*));
749 
750 	void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
751 	int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
752 	double sqlite3_column_double(sqlite3_stmt*, int iCol);
753 	int sqlite3_column_int(sqlite3_stmt*, int iCol);
754 	long sqlite3_column_int64(sqlite3_stmt*, int iCol);
755 	char *sqlite3_column_text(sqlite3_stmt*, int iCol);
756 	int sqlite3_column_type(sqlite3_stmt*, int iCol);
757 	char *sqlite3_column_name(sqlite3_stmt*, int N);
758 
759 	int sqlite3_column_count(sqlite3_stmt *pStmt);
760 	void sqlite3_free(void*);
761 	char *sqlite3_errmsg(sqlite3*);
762 
763 	// will need these to enable support for DataObjects here
764 	const(char*) sqlite3_column_database_name(sqlite3_stmt*,int);
765 	const(char*) sqlite3_column_table_name(sqlite3_stmt*,int);
766 	const(char*) sqlite3_column_origin_name(sqlite3_stmt*,int);
767 
768 	// https://www.sqlite.org/c3ref/update_hook.html
769 	void* sqlite3_update_hook(sqlite3* db, updatehookcallback cb, void* userData); // returns the old userData
770 }
771 
772 extern(C) alias updatehookcallback = void function(void* userData, int op, char* databaseName, char* tableName, /* sqlite3_int64 */ long rowid);