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