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
JavaScript
// 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;
}