test_sqlite3_error.js (4331B)
1 import * as os from "os"; 2 import * as tart from "tart"; 3 4 const dbPath = `/tmp/qtart-sqlite3-error-${Date.now()}?.sqlite3`; 5 let db; 6 let stmt; 7 8 function assert(condition, message) { 9 if (!condition) { 10 throw new Error(message); 11 } 12 } 13 14 function expectSqlite3Error(fn, code, messagePart) { 15 try { 16 fn(); 17 } catch (error) { 18 assert(error instanceof tart.Sqlite3Error, 19 `expected Sqlite3Error, got ${error?.constructor?.name}`); 20 assert(error instanceof Error, "Sqlite3Error must inherit from Error"); 21 assert(error.name === "Sqlite3Error", 22 `unexpected error name: ${error.name}`); 23 assert(error.code === code, 24 `expected ${code}, got ${error.code}`); 25 assert(Number.isInteger(error.errno) && error.errno !== 0, 26 `unexpected SQLite error number: ${error.errno}`); 27 assert(error.message.includes(messagePart), 28 `unexpected SQLite error message: ${error.message}`); 29 return; 30 } 31 throw new Error(`expected ${code} Sqlite3Error`); 32 } 33 34 function expectTypeError(fn, messagePart) { 35 try { 36 fn(); 37 } catch (error) { 38 assert(error instanceof TypeError, 39 `expected TypeError, got ${error?.constructor?.name}`); 40 assert(error.message.includes(messagePart), 41 `unexpected TypeError message: ${error.message}`); 42 return; 43 } 44 throw new Error("expected TypeError"); 45 } 46 47 try { 48 assert(tart.apiVersion === 1, 49 `expected Qtart API version 1, got ${tart.apiVersion}`); 50 db = tart.sqlite3Open(dbPath); 51 tart.sqlite3Exec(db, "CREATE TABLE entries (value TEXT UNIQUE)"); 52 tart.sqlite3Exec(db, "INSERT INTO entries VALUES ('one')"); 53 54 expectSqlite3Error( 55 () => tart.sqlite3Prepare(db, "SELECT value FROM missing_table"), 56 "SQLITE_ERROR", 57 "no such table: missing_table", 58 ); 59 expectSqlite3Error( 60 () => tart.sqlite3Exec(db, "INSERT INTO entries VALUES ('one')"), 61 "SQLITE_CONSTRAINT_UNIQUE", 62 "UNIQUE constraint failed: entries.value", 63 ); 64 65 stmt = tart.sqlite3Prepare(db, "SELECT value FROM entries"); 66 const row = tart.sqlite3StmtGetFirst(stmt); 67 if (row?.value !== "one") { 68 throw Error(`unexpected first row: ${JSON.stringify(row)}`); 69 } 70 71 // Changing into WAL fails while another statement on the connection is 72 // still active. sqlite3StmtGetFirst must reset its SELECT before returning. 73 tart.sqlite3Exec(db, "PRAGMA journal_mode = WAL"); 74 75 // Explicit close must never create a zombie connection. A direct caller 76 // that leaves a statement live gets SQLITE_BUSY and can still use and 77 // finalize the statement before retrying the close. 78 expectSqlite3Error( 79 () => tart.sqlite3Close(db), 80 "SQLITE_BUSY", 81 "unable to close", 82 ); 83 assert(tart.sqlite3StmtGetFirst(stmt)?.value === "one", 84 "statement must remain usable after SQLITE_BUSY from close"); 85 tart.sqlite3Finalize(stmt); 86 tart.sqlite3Finalize(stmt); 87 expectTypeError( 88 () => tart.sqlite3StmtGetFirst(stmt), 89 "invalid sqlite3 statement handle", 90 ); 91 stmt = undefined; 92 tart.sqlite3Close(db); 93 db = undefined; 94 95 expectTypeError( 96 () => tart.sqlite3Open(dbPath, { readonly: "yes" }), 97 "option 'readonly' must be a boolean", 98 ); 99 100 db = tart.sqlite3Open(dbPath, { readonly: true }); 101 stmt = tart.sqlite3Prepare(db, "SELECT value FROM entries"); 102 assert(tart.sqlite3StmtGetFirst(stmt)?.value === "one", 103 "read-only connection must read existing records"); 104 tart.sqlite3Finalize(stmt); 105 stmt = undefined; 106 expectSqlite3Error( 107 () => tart.sqlite3Exec(db, "INSERT INTO entries VALUES ('two')"), 108 "SQLITE_READONLY", 109 "readonly", 110 ); 111 tart.sqlite3Close(db); 112 db = undefined; 113 114 db = tart.sqlite3Open(dbPath, { immutable: true }); 115 stmt = tart.sqlite3Prepare(db, "SELECT value FROM entries"); 116 assert(tart.sqlite3StmtGetFirst(stmt)?.value === "one", 117 "immutable connection must preserve URI-special path characters"); 118 tart.sqlite3Finalize(stmt); 119 stmt = undefined; 120 expectSqlite3Error( 121 () => tart.sqlite3Exec(db, "INSERT INTO entries VALUES ('two')"), 122 "SQLITE_READONLY", 123 "readonly", 124 ); 125 tart.sqlite3Close(db); 126 db = undefined; 127 } finally { 128 if (stmt !== undefined) { 129 tart.sqlite3Finalize(stmt); 130 } 131 if (db !== undefined) { 132 tart.sqlite3Close(db); 133 } 134 os.remove(dbPath); 135 os.remove(`${dbPath}-wal`); 136 os.remove(`${dbPath}-shm`); 137 }