UNPKG

expo-sqlite

Version:

Provides access to a database using SQLite (https://www.sqlite.org/). The database is persisted across restarts of your app.

1,144 lines (1,018 loc) 35.2 kB
// Copyright 2021 Roy T. Hashimoto. All Rights Reserved. import * as SQLite from './sqlite-constants.js'; export * from './sqlite-constants.js'; const MAX_INT64 = 0x7fffffffffffffffn; const MIN_INT64 = -0x8000000000000000n; const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; export class SQLiteError extends Error { constructor(message, code) { super(message); this.code = code; } } const async = true; /** * Builds a Javascript API from the Emscripten module. This API is still * low-level and closely corresponds to the C API exported by the module, * but differs in some specifics like throwing exceptions on errors. * @param {*} Module SQLite Emscripten module * @returns {SQLiteAPI} */ export function Factory(Module) { /** @type {SQLiteAPI} */ const sqlite3 = {}; Module.retryOps = []; const sqliteFreeAddress = Module._getSqliteFree(); // Allocate some space for 32-bit returned values. const tmp = Module._malloc(8); const tmpPtr = [tmp, tmp + 4]; // Convert a JS string to a C string. sqlite3_malloc is used to allocate // memory (use sqlite3_free to deallocate). function createUTF8(s) { if (typeof s !== 'string') return 0; const utf8 = new TextEncoder().encode(s); const zts = Module._sqlite3_malloc(utf8.byteLength + 1); Module.HEAPU8.set(utf8, zts); Module.HEAPU8[zts + utf8.byteLength] = 0; return zts; } /** * Concatenate 32-bit numbers into a 64-bit (signed) BigInt. * @param {number} lo32 * @param {number} hi32 * @returns {bigint} */ function cvt32x2ToBigInt(lo32, hi32) { return (BigInt(hi32) << 32n) | (BigInt(lo32) & 0xffffffffn); } /** * Concatenate 32-bit numbers and return as number or BigInt, depending * on the value. * @param {number} lo32 * @param {number} hi32 * @returns {number|bigint} */ const cvt32x2AsSafe = (function() { const hiMax = BigInt(Number.MAX_SAFE_INTEGER) >> 32n; const hiMin = BigInt(Number.MIN_SAFE_INTEGER) >> 32n; return function(lo32, hi32) { if (hi32 > hiMax || hi32 < hiMin) { // Can't be expressed as a Number so use BigInt. return cvt32x2ToBigInt(lo32, hi32); } else { // Combine the upper and lower 32-bit numbers. The complication is // that lo32 is a signed integer which makes manipulating its bits // a little tricky - the sign bit gets handled separately. return (hi32 * 0x100000000) + (lo32 & 0x7fffffff) - (lo32 & 0x80000000); } } })(); const databases = new Set(); function verifyDatabase(db) { if (!databases.has(db)) { throw new SQLiteError('not a database', SQLite.SQLITE_MISUSE); } } const mapStmtToDB = new Map(); function verifyStatement(stmt) { if (!mapStmtToDB.has(stmt)) { throw new SQLiteError('not a statement', SQLite.SQLITE_MISUSE); } } sqlite3.backup = (function() { const init = Module.cwrap('sqlite3_backup_init', ...decl('nsns:n')); const step = Module.cwrap('sqlite3_backup_step', ...decl('nn:n')); const finish = Module.cwrap('sqlite3_backup_finish', ...decl('n:n')); return async function(destDb, destName, srcDb, srcName) { verifyDatabase(destDb); verifyDatabase(srcDb); const backup = init(destDb, destName, srcDb, srcName); if (!backup) { check('sqlite3_backup_init', SQLite.SQLITE_ERROR, destDb); return; } step(backup, -1); const result = finish(backup); return check('sqlite3_backup_finish', result, destDb); }; })(); sqlite3.bind_collection = function(stmt, bindings) { verifyStatement(stmt); const isArray = Array.isArray(bindings); const nBindings = sqlite3.bind_parameter_count(stmt); for (let i = 1; i <= nBindings; ++i) { const key = isArray ? i - 1 : sqlite3.bind_parameter_name(stmt, i); const value = bindings[key]; if (value !== undefined) { sqlite3.bind(stmt, i, value); } } return SQLite.SQLITE_OK; }; sqlite3.bind = function(stmt, i, value) { verifyStatement(stmt); switch (typeof value) { case 'number': if (value === (value | 0)) { return sqlite3.bind_int(stmt, i, value); } else { return sqlite3.bind_double(stmt, i, value); } case 'string': return sqlite3.bind_text(stmt, i, value); default: if (value instanceof Uint8Array || Array.isArray(value)) { return sqlite3.bind_blob(stmt, i, value); } else if (value === null) { return sqlite3.bind_null(stmt, i); } else if (typeof value === 'bigint') { return sqlite3.bind_int64(stmt, i, value); } else if (value === undefined) { // Existing binding (or NULL) will be used. return SQLite.SQLITE_NOTICE; } else { console.warn('unknown binding converted to null', value); return sqlite3.bind_null(stmt, i); } } }; sqlite3.bind_blob = (function() { const fname = 'sqlite3_bind_blob'; const f = Module.cwrap(fname, ...decl('nnnnn:n')); return function(stmt, i, value) { verifyStatement(stmt); // @ts-ignore const byteLength = value.byteLength ?? value.length; const ptr = Module._sqlite3_malloc(byteLength); Module.HEAPU8.subarray(ptr).set(value); const result = f(stmt, i, ptr, byteLength, sqliteFreeAddress); return check(fname, result, mapStmtToDB.get(stmt)); }; })(); sqlite3.bind_parameter_count = (function() { const fname = 'sqlite3_bind_parameter_count'; const f = Module.cwrap(fname, ...decl('n:n')); return function(stmt) { verifyStatement(stmt); const result = f(stmt); return result; }; })(); sqlite3.bind_double = (function() { const fname = 'sqlite3_bind_double'; const f = Module.cwrap(fname, ...decl('nnn:n')); return function(stmt, i, value) { verifyStatement(stmt); const result = f(stmt, i, value); return check(fname, result, mapStmtToDB.get(stmt)); }; })(); sqlite3.bind_int = (function() { const fname = 'sqlite3_bind_int'; const f = Module.cwrap(fname, ...decl('nnn:n')); return function(stmt, i, value) { verifyStatement(stmt); if (value > 0x7fffffff || value < -0x80000000) return SQLite.SQLITE_RANGE; const result = f(stmt, i, value); return check(fname, result, mapStmtToDB.get(stmt)); }; })(); sqlite3.bind_int64 = (function() { const fname = 'sqlite3_bind_int64'; const f = Module.cwrap(fname, ...decl('nnnn:n')); return function(stmt, i, value) { verifyStatement(stmt); if (value > MAX_INT64 || value < MIN_INT64) return SQLite.SQLITE_RANGE; const lo32 = value & 0xffffffffn; const hi32 = value >> 32n; const result = f(stmt, i, Number(lo32), Number(hi32)); return check(fname, result, mapStmtToDB.get(stmt)); }; })(); sqlite3.bind_null = (function() { const fname = 'sqlite3_bind_null'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(stmt, i) { verifyStatement(stmt); const result = f(stmt, i); return check(fname, result, mapStmtToDB.get(stmt)); }; })(); sqlite3.bind_parameter_index = (function () { const fname = "sqlite3_bind_parameter_index"; const f = Module.cwrap(fname, ...decl("ns:n")); return function (stmt, name) { verifyStatement(stmt); const result = f(stmt, name); return result; }; })(); sqlite3.bind_parameter_name = (function() { const fname = 'sqlite3_bind_parameter_name'; const f = Module.cwrap(fname, ...decl('n:s')); return function(stmt, i) { verifyStatement(stmt); const result = f(stmt, i); return result; }; })(); sqlite3.bind_text = (function() { const fname = 'sqlite3_bind_text'; const f = Module.cwrap(fname, ...decl('nnnnn:n')); return function(stmt, i, value) { verifyStatement(stmt); const ptr = createUTF8(value); const result = f(stmt, i, ptr, -1, sqliteFreeAddress); return check(fname, result, mapStmtToDB.get(stmt)); }; })(); sqlite3.changes = (function() { const fname = 'sqlite3_changes'; const f = Module.cwrap(fname, ...decl('n:n')); return function(db) { verifyDatabase(db); const result = f(db); return result; }; })(); sqlite3.clear_bindings = (function() { const fname = 'sqlite3_clear_bindings'; const f = Module.cwrap(fname, ...decl('n:n')); return function(stmt) { verifyStatement(stmt); const result = f(stmt); return check(fname, result, mapStmtToDB.get(stmt)); }; })(); sqlite3.close = (function() { const fname = 'sqlite3_close'; const f = Module.cwrap(fname, ...decl('n:n'), { async }); return async function(db) { verifyDatabase(db); const result = await f(db); databases.delete(db); return check(fname, result, db); }; })(); sqlite3.column = function(stmt, iCol) { verifyStatement(stmt); const type = sqlite3.column_type(stmt, iCol); switch (type) { case SQLite.SQLITE_BLOB: return sqlite3.column_blob(stmt, iCol); case SQLite.SQLITE_FLOAT: return sqlite3.column_double(stmt, iCol); case SQLite.SQLITE_INTEGER: const lo32 = sqlite3.column_int(stmt, iCol); const hi32 = Module.getTempRet0(); return cvt32x2AsSafe(lo32, hi32); case SQLite.SQLITE_NULL: return null; case SQLite.SQLITE_TEXT: return sqlite3.column_text(stmt, iCol); default: throw new SQLiteError('unknown type', type); } }; sqlite3.column_blob = (function() { const fname = 'sqlite3_column_blob'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(stmt, iCol) { verifyStatement(stmt); const nBytes = sqlite3.column_bytes(stmt, iCol); const address = f(stmt, iCol); const result = Module.HEAPU8.subarray(address, address + nBytes); return result; }; })(); sqlite3.column_bytes = (function() { const fname = 'sqlite3_column_bytes'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(stmt, iCol) { verifyStatement(stmt); const result = f(stmt, iCol); return result; }; })(); sqlite3.column_count = (function() { const fname = 'sqlite3_column_count'; const f = Module.cwrap(fname, ...decl('n:n')); return function(stmt) { verifyStatement(stmt); const result = f(stmt); return result; }; })(); sqlite3.column_double = (function() { const fname = 'sqlite3_column_double'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(stmt, iCol) { verifyStatement(stmt); const result = f(stmt, iCol); return result; }; })(); sqlite3.column_int = (function() { // Retrieve int64 but use only the lower 32 bits. The upper 32-bits are // accessible with Module.getTempRet0(). const fname = 'sqlite3_column_int64'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(stmt, iCol) { verifyStatement(stmt); const result = f(stmt, iCol); return result; }; })(); sqlite3.column_int64 = (function() { const fname = 'sqlite3_column_int64'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(stmt, iCol) { verifyStatement(stmt); const lo32 = f(stmt, iCol); const hi32 = Module.getTempRet0(); const result = cvt32x2ToBigInt(lo32, hi32); return result; }; })(); sqlite3.column_int_safe = (function() { const fname = 'sqlite3_column_int64'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(stmt, iCol) { verifyStatement(stmt); const lo32 = f(stmt, iCol); const hi32 = Module.getTempRet0(); return cvt32x2AsSafe(lo32, hi32); }; })(); sqlite3.column_name = (function() { const fname = 'sqlite3_column_name'; const f = Module.cwrap(fname, ...decl('nn:s')); return function(stmt, iCol) { verifyStatement(stmt); const result = f(stmt, iCol); return result; }; })(); sqlite3.column_names = function(stmt) { const columns = []; const nColumns = sqlite3.column_count(stmt); for (let i = 0; i < nColumns; ++i) { columns.push(sqlite3.column_name(stmt, i)); } return columns; }; sqlite3.column_text = (function() { const fname = 'sqlite3_column_text'; const f = Module.cwrap(fname, ...decl('nn:s')); return function(stmt, iCol) { verifyStatement(stmt); const result = f(stmt, iCol); return result; }; })(); sqlite3.column_type = (function() { const fname = 'sqlite3_column_type'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(stmt, iCol) { verifyStatement(stmt); const result = f(stmt, iCol); return result; }; })(); sqlite3.create_function = function(db, zFunctionName, nArg, eTextRep, pApp, xFunc, xStep, xFinal) { verifyDatabase(db); // Convert SQLite callback arguments to JavaScript-friendly arguments. function adapt(f) { return f instanceof AsyncFunction ? (async (ctx, n, values) => f(ctx, Module.HEAP32.subarray(values / 4, values / 4 + n))) : ((ctx, n, values) => f(ctx, Module.HEAP32.subarray(values / 4, values / 4 + n))); } const result = Module.create_function( db, zFunctionName, nArg, eTextRep, pApp, xFunc && adapt(xFunc), xStep && adapt(xStep), xFinal); return check('sqlite3_create_function', result, db); }; sqlite3.data_count = (function() { const fname = 'sqlite3_data_count'; const f = Module.cwrap(fname, ...decl('n:n')); return function(stmt) { verifyStatement(stmt); const result = f(stmt); return result; }; })(); sqlite3.db_filename = (function() { const fname = 'sqlite3_db_filename'; const f = Module.cwrap(fname, ...decl('ns:s')); return function(db, schema) { verifyDatabase(db); const result = f(db, schema); return result; }; })(); sqlite3.deserialize = (function() { const fname = 'sqlite3_deserialize'; const f = Module.cwrap(fname, ...decl('nsnnnn:n')); return function(db, schema, data) { const flags = SQLite.SQLITE_DESERIALIZE_RESIZEABLE | SQLite.SQLITE_DESERIALIZE_FREEONCLOSE; verifyDatabase(db); const size = data.byteLength; const ptr = Module._sqlite3_malloc(size); Module.HEAPU8.subarray(ptr).set(data); const result = f(db, schema, ptr, size, size, flags); return check(fname, result, db); }; })(); sqlite3.exec = async function(db, sql, callback) { for await (const stmt of sqlite3.statements(db, sql)) { let columns; while (await sqlite3.step(stmt) === SQLite.SQLITE_ROW) { if (callback) { columns = columns ?? sqlite3.column_names(stmt); const row = sqlite3.row(stmt); await callback(row, columns); } } } return SQLite.SQLITE_OK; }; sqlite3.finalize = (function() { const fname = 'sqlite3_finalize'; const f = Module.cwrap(fname, ...decl('n:n'), { async }); return async function(stmt) { const result = await f(stmt); mapStmtToDB.delete(stmt) // Don't throw on error here. Typically the error has already been // thrown and finalize() is part of the cleanup. return result; }; })(); sqlite3.get_autocommit = (function() { const fname = 'sqlite3_get_autocommit'; const f = Module.cwrap(fname, ...decl('n:n')); return function(db) { const result = f(db); return result; }; })(); sqlite3.last_insert_rowid = (function() { const fname = 'sqlite3_last_insert_rowid'; const f = Module.cwrap(fname, ...decl('n:n')); return function(db) { const lo32 = f(db); const hi32 = Module.getTempRet0(); return cvt32x2AsSafe(lo32, hi32); }; })(); sqlite3.libversion = (function() { const fname = 'sqlite3_libversion'; const f = Module.cwrap(fname, ...decl(':s')); return function() { const result = f(); return result; }; })(); sqlite3.libversion_number = (function() { const fname = 'sqlite3_libversion_number'; const f = Module.cwrap(fname, ...decl(':n')); return function() { const result = f(); return result; }; })(); sqlite3.limit = (function() { const fname = 'sqlite3_limit'; const f = Module.cwrap(fname, ...decl('nnn:n')); return function(db, id, newVal) { const result = f(db, id, newVal); return result; }; })(); sqlite3.next_stmt = (function() { const fname = 'sqlite3_next_stmt'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(db, stmt) { verifyDatabase(db); const result = f(db, stmt || 0); return result; }; })(); sqlite3.open_v2 = (function() { const fname = 'sqlite3_open_v2'; const f = Module.cwrap(fname, ...decl('snnn:n'), { async }); return async function(zFilename, flags, zVfs) { flags = flags || SQLite.SQLITE_OPEN_CREATE | SQLite.SQLITE_OPEN_READWRITE; zVfs = createUTF8(zVfs); try { // Allow retry operations. const rc = await retry(() => f(zFilename, tmpPtr[0], flags, zVfs)); const db = Module.getValue(tmpPtr[0], '*'); databases.add(db); Module.ccall('RegisterExtensionFunctions', 'void', ['number'], [db]); check(fname, rc); return db; } finally { Module._sqlite3_free(zVfs); } }; })(); sqlite3.progress_handler = function(db, nProgressOps, handler, userData) { verifyDatabase(db); Module.progress_handler(db, nProgressOps, handler, userData); };; sqlite3.reset = (function() { const fname = 'sqlite3_reset'; const f = Module.cwrap(fname, ...decl('n:n'), { async }); return async function(stmt) { verifyStatement(stmt); const result = await f(stmt); return check(fname, result, mapStmtToDB.get(stmt)); }; })(); sqlite3.result = function(context, value) { switch (typeof value) { case 'number': if (value === (value | 0)) { sqlite3.result_int(context, value); } else { sqlite3.result_double(context, value); } break; case 'string': sqlite3.result_text(context, value); break; default: if (value instanceof Uint8Array || Array.isArray(value)) { sqlite3.result_blob(context, value); } else if (value === null) { sqlite3.result_null(context); } else if (typeof value === 'bigint') { return sqlite3.result_int64(context, value); } else { console.warn('unknown result converted to null', value); sqlite3.result_null(context); } break; } }; sqlite3.result_blob = (function() { const fname = 'sqlite3_result_blob'; const f = Module.cwrap(fname, ...decl('nnnn:n')); return function(context, value) { // @ts-ignore const byteLength = value.byteLength ?? value.length; const ptr = Module._sqlite3_malloc(byteLength); Module.HEAPU8.subarray(ptr).set(value); f(context, ptr, byteLength, sqliteFreeAddress); // void return }; })(); sqlite3.result_double = (function() { const fname = 'sqlite3_result_double'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(context, value) { f(context, value); // void return }; })(); sqlite3.result_int = (function() { const fname = 'sqlite3_result_int'; const f = Module.cwrap(fname, ...decl('nn:n')); return function(context, value) { f(context, value); // void return }; })(); sqlite3.result_int64 = (function() { const fname = 'sqlite3_result_int64'; const f = Module.cwrap(fname, ...decl('nnn:n')); return function(context, value) { if (value > MAX_INT64 || value < MIN_INT64) return SQLite.SQLITE_RANGE; const lo32 = value & 0xffffffffn; const hi32 = value >> 32n; f(context, Number(lo32), Number(hi32)); // void return }; })(); sqlite3.result_null = (function() { const fname = 'sqlite3_result_null'; const f = Module.cwrap(fname, ...decl('n:n')); return function(context) { f(context); // void return }; })(); sqlite3.result_text = (function() { const fname = 'sqlite3_result_text'; const f = Module.cwrap(fname, ...decl('nnnn:n')); return function(context, value) { const ptr = createUTF8(value); f(context, ptr, -1, sqliteFreeAddress); // void return }; })(); sqlite3.row = function(stmt) { const row = []; const nColumns = sqlite3.data_count(stmt); for (let i = 0; i < nColumns; ++i) { const value = sqlite3.column(stmt, i); // Copy blob if aliasing volatile WebAssembly memory. This avoids an // unnecessary copy if users monkey patch column_blob to copy. // @ts-ignore row.push(value?.buffer === Module.HEAPU8.buffer ? value.slice() : value); } return row; }; sqlite3.serialize = (function() { const fname = 'sqlite3_serialize'; const f = Module.cwrap(fname, ...decl('nsnn:n')); return function(db, schema) { verifyDatabase(db); const size = tmpPtr[0]; const flags = 0; const ptr = f(db, schema, size, flags); if (!ptr) { check(fname, SQLite.SQLITE_ERROR, db); return null; } const bufferSize = Module.getValue(size, '*'); const buffer = Module.HEAPU8.subarray(ptr, ptr + bufferSize); const result = new Uint8Array(buffer); Module._sqlite3_free(ptr); return result; }; })(); sqlite3.set_authorizer = function(db, xAuth, pApp) { verifyDatabase(db); // Convert SQLite callback arguments to JavaScript-friendly arguments. function cvtArgs(_, iAction, p3, p4, p5, p6) { return [ _, iAction, Module.UTF8ToString(p3), Module.UTF8ToString(p4), Module.UTF8ToString(p5), Module.UTF8ToString(p6) ]; }; function adapt(f) { return f instanceof AsyncFunction ? (async (_, iAction, p3, p4, p5, p6) => f(...cvtArgs(_, iAction, p3, p4, p5, p6))) : ((_, iAction, p3, p4, p5, p6) => f(...cvtArgs(_, iAction, p3, p4, p5, p6))); } const result = Module.set_authorizer(db, adapt(xAuth), pApp); return check('sqlite3_set_authorizer', result, db); };; sqlite3.sql = (function() { const fname = 'sqlite3_sql'; const f = Module.cwrap(fname, ...decl('n:s')); return function(stmt) { verifyStatement(stmt); const result = f(stmt); return result; }; })(); sqlite3.statements = function(db, sql, options = {}) { const prepare = Module.cwrap( 'sqlite3_prepare_v3', 'number', ['number', 'number', 'number', 'number', 'number', 'number'], { async: true }); return (async function*() { const onFinally = []; try { // Encode SQL string to UTF-8. const utf8 = new TextEncoder().encode(sql); // Copy encoded string to WebAssembly memory. The SQLite docs say // zero-termination is a minor optimization so add room for that. // Also add space for the statement handle and SQL tail pointer. const allocSize = utf8.byteLength - (utf8.byteLength % 4) + 12; const pzHead = Module._sqlite3_malloc(allocSize); const pzEnd = pzHead + utf8.byteLength + 1; onFinally.push(() => Module._sqlite3_free(pzHead)); Module.HEAPU8.set(utf8, pzHead); Module.HEAPU8[pzEnd - 1] = 0; // Use extra space for the statement handle and SQL tail pointer. const pStmt = pzHead + allocSize - 8; const pzTail = pzHead + allocSize - 4; // Ensure that statement handles are not leaked. let stmt; function maybeFinalize() { if (stmt && !options.unscoped) { sqlite3.finalize(stmt); } stmt = 0; } onFinally.push(maybeFinalize); // Loop over statements. Module.setValue(pzTail, pzHead, '*'); do { // Reclaim resources for the previous iteration. maybeFinalize(); // Call sqlite3_prepare_v3() for the next statement. // Allow retry operations. const zTail = Module.getValue(pzTail, '*'); const rc = await retry(() => { return prepare( db, zTail, pzEnd - pzTail, options.flags || 0, pStmt, pzTail); }); if (rc !== SQLite.SQLITE_OK) { check('sqlite3_prepare_v3', rc, db); } stmt = Module.getValue(pStmt, '*'); if (stmt) { mapStmtToDB.set(stmt, db); yield stmt; } } while (stmt); } finally { while (onFinally.length) { onFinally.pop()(); } } })(); }; sqlite3.step = (function() { const fname = 'sqlite3_step'; const f = Module.cwrap(fname, ...decl('n:n'), { async }); return async function(stmt) { verifyStatement(stmt); // Allow retry operations. const rc = await retry(() => f(stmt)); return check(fname, rc, mapStmtToDB.get(stmt), [SQLite.SQLITE_ROW, SQLite.SQLITE_DONE]); }; })(); sqlite3.commit_hook = function(db, xCommitHook) { verifyDatabase(db); Module.commit_hook(db, xCommitHook); }; sqlite3.update_hook = function(db, xUpdateHook) { verifyDatabase(db); // Convert SQLite callback arguments to JavaScript-friendly arguments. function cvtArgs(iUpdateType, dbName, tblName, lo32, hi32) { return [ iUpdateType, Module.UTF8ToString(dbName), Module.UTF8ToString(tblName), cvt32x2ToBigInt(lo32, hi32) ]; }; function adapt(f) { return f instanceof AsyncFunction ? (async (iUpdateType, dbName, tblName, lo32, hi32) => f(...cvtArgs(iUpdateType, dbName, tblName, lo32, hi32))) : ((iUpdateType, dbName, tblName, lo32, hi32) => f(...cvtArgs(iUpdateType, dbName, tblName, lo32, hi32))); } Module.update_hook(db, adapt(xUpdateHook)); };; sqlite3.value = function(pValue) { const type = sqlite3.value_type(pValue); switch (type) { case SQLite.SQLITE_BLOB: return sqlite3.value_blob(pValue); case SQLite.SQLITE_FLOAT: return sqlite3.value_double(pValue); case SQLite.SQLITE_INTEGER: const lo32 = sqlite3.value_int(pValue); const hi32 = Module.getTempRet0(); return cvt32x2AsSafe(lo32, hi32); case SQLite.SQLITE_NULL: return null; case SQLite.SQLITE_TEXT: return sqlite3.value_text(pValue); default: throw new SQLiteError('unknown type', type); } }; sqlite3.value_blob = (function() { const fname = 'sqlite3_value_blob'; const f = Module.cwrap(fname, ...decl('n:n')); return function(pValue) { const nBytes = sqlite3.value_bytes(pValue); const address = f(pValue); const result = Module.HEAPU8.subarray(address, address + nBytes); return result; }; })(); sqlite3.value_bytes = (function() { const fname = 'sqlite3_value_bytes'; const f = Module.cwrap(fname, ...decl('n:n')); return function(pValue) { const result = f(pValue); return result; }; })(); sqlite3.value_double = (function() { const fname = 'sqlite3_value_double'; const f = Module.cwrap(fname, ...decl('n:n')); return function(pValue) { const result = f(pValue); return result; }; })(); sqlite3.value_int = (function() { const fname = 'sqlite3_value_int64'; const f = Module.cwrap(fname, ...decl('n:n')); return function(pValue) { const result = f(pValue); return result; }; })(); sqlite3.value_int64 = (function() { const fname = 'sqlite3_value_int64'; const f = Module.cwrap(fname, ...decl('n:n')); return function(pValue) { const lo32 = f(pValue); const hi32 = Module.getTempRet0(); const result = cvt32x2ToBigInt(lo32, hi32); return result; }; })(); sqlite3.value_text = (function() { const fname = 'sqlite3_value_text'; const f = Module.cwrap(fname, ...decl('n:s')); return function(pValue) { const result = f(pValue); return result; }; })(); sqlite3.value_type = (function() { const fname = 'sqlite3_value_type'; const f = Module.cwrap(fname, ...decl('n:n')); return function(pValue) { const result = f(pValue); return result; }; })(); sqlite3.vfs_register = function(vfs, makeDefault) { const result = Module.vfs_register(vfs, makeDefault); return check('sqlite3_vfs_register', result); }; sqlite3.changeset_apply = (function () { const fname = 'sqlite3changeset_apply'; const f = Module.cwrap(fname, ...decl('nnnnnn:n')); return function (db, changeset) { verifyDatabase(db); const size = changeset.byteLength; const buffer = Module._sqlite3_malloc(size); Module.HEAPU8.subarray(buffer).set(changeset); const onConflict = () => { return SQLite.SQLITE_CHANGESET_REPLACE; }; const result = f(db, size, buffer, null, onConflict, null); Module._sqlite3_free(buffer); return check(fname, result, db); }; })(); sqlite3.changeset_invert = (function () { const fname = 'sqlite3changeset_invert'; const f = Module.cwrap(fname, ...decl('nnnn:n')); return function (changeset) { const inSize = changeset.byteLength; const inBuffer = Module._sqlite3_malloc(inSize); Module.HEAPU8.subarray(inBuffer).set(changeset); const outSize = tmpPtr[0]; const outBuffer = tmpPtr[1]; const result = f(inSize, inBuffer, outSize, outBuffer); Module._sqlite3_free(inBuffer); check(fname, result); const bufferSize = Module.getValue(outSize, '*'); const bufferPtr = Module.getValue(outBuffer, '*'); const buffer = Module.HEAPU8.subarray(bufferPtr, bufferPtr + bufferSize); const inverted = new Uint8Array(buffer); Module._sqlite3_free(outBuffer); return inverted; }; })(); sqlite3.session_create = (function () { const fname = 'sqlite3session_create'; const f = Module.cwrap(fname, ...decl('nsn:n')); return function (db, dbName) { verifyDatabase(db); const ptrSession = tmpPtr[0]; const result = f(db, dbName, ptrSession); check(fname, result, db); const session = Module.getValue(ptrSession, '*'); return session; }; })(); sqlite3.session_delete = (function () { const fname = 'sqlite3session_delete'; const f = Module.cwrap(fname, ...decl('n:v')); return function (session) { f(session); }; })(); sqlite3.session_enable = (function () { const fname = 'sqlite3session_enable'; const f = Module.cwrap(fname, ...decl('nn:v')); return function (session, enabled) { f(session, enabled ? 1 : 0); }; })(); sqlite3.session_attach = (function () { const fname = 'sqlite3session_attach'; const f = Module.cwrap(fname, ...decl('ns:n')); return function (session, tableName) { const result = f(session, tableName); return check(fname, result); }; })(); sqlite3.session_changeset = (function () { const fname = 'sqlite3session_changeset'; const f = Module.cwrap(fname, ...decl('nnn:n')); return function (session) { const bufferSize = tmpPtr[0]; const ptr = tmpPtr[1]; const result = f(session, bufferSize, ptr); check(fname, result); const size = Module.getValue(bufferSize, '*'); const bufferPtr = Module.getValue(ptr, '*'); const buffer = Module.HEAPU8.subarray(bufferPtr, bufferPtr + size); const changeset = new Uint8Array(buffer); Module._sqlite3_free(ptr); return changeset; }; })(); /** * Shorthand for `session_changeset` and `changeset_invert` but * in a single call without additional memory allocations. */ sqlite3.session_changeset_inverted = (function () { const fNameChangeset = 'sqlite3session_changeset'; const fNameInvert = 'sqlite3changeset_invert'; const fChangeset = Module.cwrap(fNameChangeset, ...decl('nnn:n')); const fInvert = Module.cwrap(fNameInvert, ...decl('nnnn:n')); return function (session) { const bufferSize = tmpPtr[0]; const ptr = tmpPtr[1]; const changesetResult = fChangeset(session, bufferSize, ptr); check(fNameChangeset, changesetResult); const size = Module.getValue(bufferSize, '*'); const buffer = Module.getValue(ptr, '*'); const outSize = tmpPtr[0]; const outBuffer = tmpPtr[1]; const invertResult = fInvert(size, buffer, outSize, outBuffer); Module._sqlite3_free(buffer); check(fNameInvert, invertResult); const outSizeValue = Module.getValue(outSize, '*'); const outBufferPtr = Module.getValue(outBuffer, '*'); const inverted = Module.HEAPU8.subarray(outBufferPtr, outBufferPtr + outSizeValue); const invertedArray = new Uint8Array(inverted); Module._sqlite3_free(outBuffer); return invertedArray; }; })(); function check(fname, result, db = null, allowed = [SQLite.SQLITE_OK]) { if (allowed.includes(result)) return result; let message; if (db) { const errcode = Module.ccall('sqlite3_errcode', 'number', ['number'], [db]); const errmsg = Module.ccall('sqlite3_errmsg', 'string', ['number'], [db]); message = 'Error code ' + errcode + ': ' + errmsg; } else { message = fname; } throw new SQLiteError(message, result); } // This function is used to automatically retry failed calls that // have pending retry operations that should allow the retry to // succeed. async function retry(f) { let rc; do { // Wait for all pending retry operations to complete. This is // normally empty on the first loop iteration. if (Module.retryOps.length) { await Promise.all(Module.retryOps); Module.retryOps = []; } rc = await f(); // Retry on failure with new pending retry operations. } while (rc && Module.retryOps.length); return rc; } return sqlite3; } // Helper function to use a more compact signature specification. function decl(s) { const result = []; const m = s.match(/([ns@]*):([nsv@])/); switch (m[2]) { case 'n': result.push('number'); break; case 's': result.push('string'); break; case 'v': result.push(null); break; } const args = []; for (let c of m[1]) { switch (c) { case 'n': args.push('number'); break; case 's': args.push('string'); break; } } result.push(args); return result; }