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);