web: make sqlite slightly faster

This commit is contained in:
Abdullah Atta
2024-02-10 10:47:16 +05:00
parent 37eeeb1d64
commit 779f000f3e
5 changed files with 66 additions and 33 deletions

View File

@@ -36,7 +36,7 @@ export const createDialect = (name: string): Dialect => {
return {
createDriver: () =>
new WaSqliteWorkerDriver({
async: isFeatureSupported("opfs") ? false : true,
async: !isFeatureSupported("opfs"),
dbName: name
}),
createAdapter: () => new SqliteAdapter(),

View File

@@ -121,7 +121,7 @@ export function Factory(Module) {
};
sqlite3.bind = function (stmt, i, value) {
verifyStatement(stmt);
// verifyStatement(stmt);
switch (typeof value) {
case "number":
if (value === (value | 0)) {
@@ -152,14 +152,14 @@ export function Factory(Module) {
const fname = "sqlite3_bind_blob";
const f = Module.cwrap(fname, ...decl("nnnnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
// 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);
// trace(fname, result);
return check(fname, result, mapStmtToDB.get(stmt));
return check(fname, result, null, stmt);
};
})();
@@ -167,7 +167,7 @@ export function Factory(Module) {
const fname = "sqlite3_bind_parameter_count";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (stmt) {
verifyStatement(stmt);
// verifyStatement(stmt);
const result = f(stmt);
// trace(fname, result);
return result;
@@ -178,7 +178,7 @@ export function Factory(Module) {
const fname = "sqlite3_clear_bindings";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (stmt) {
verifyStatement(stmt);
// verifyStatement(stmt);
const result = f(stmt);
// trace(fname, result);
return result;
@@ -189,10 +189,10 @@ export function Factory(Module) {
const fname = "sqlite3_bind_double";
const f = Module.cwrap(fname, ...decl("nnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
// verifyStatement(stmt);
const result = f(stmt, i, value);
// trace(fname, result);
return check(fname, result, mapStmtToDB.get(stmt));
return check(fname, result, null, stmt);
};
})();
@@ -200,12 +200,12 @@ export function Factory(Module) {
const fname = "sqlite3_bind_int";
const f = Module.cwrap(fname, ...decl("nnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
// verifyStatement(stmt);
if (value > 0x7fffffff || value < -0x80000000) return SQLite.SQLITE_RANGE;
const result = f(stmt, i, value);
// trace(fname, result);
return check(fname, result, mapStmtToDB.get(stmt));
return check(fname, result, null, stmt);
};
})();
@@ -213,14 +213,14 @@ export function Factory(Module) {
const fname = "sqlite3_bind_int64";
const f = Module.cwrap(fname, ...decl("nnnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
// 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));
// trace(fname, result);
return check(fname, result, mapStmtToDB.get(stmt));
return check(fname, result, null, stmt);
};
})();
@@ -228,10 +228,10 @@ export function Factory(Module) {
const fname = "sqlite3_bind_null";
const f = Module.cwrap(fname, ...decl("nn:n"));
return function (stmt, i) {
verifyStatement(stmt);
// verifyStatement(stmt);
const result = f(stmt, i);
// trace(fname, result);
return check(fname, result, mapStmtToDB.get(stmt));
return check(fname, result, null, stmt);
};
})();
@@ -239,7 +239,7 @@ export function Factory(Module) {
const fname = "sqlite3_bind_parameter_name";
const f = Module.cwrap(fname, ...decl("n:s"));
return function (stmt, i) {
verifyStatement(stmt);
// verifyStatement(stmt);
const result = f(stmt, i);
// trace(fname, result);
return result;
@@ -250,11 +250,11 @@ export function Factory(Module) {
const fname = "sqlite3_bind_text";
const f = Module.cwrap(fname, ...decl("nnnnn:n"));
return function (stmt, i, value) {
verifyStatement(stmt);
// verifyStatement(stmt);
const ptr = createUTF8(value);
const result = f(stmt, i, ptr, -1, sqliteFreeAddress);
// trace(fname, result);
return check(fname, result, mapStmtToDB.get(stmt));
return check(fname, result, null, stmt);
};
})();
@@ -504,7 +504,7 @@ export function Factory(Module) {
}
const result = await f(stmt);
const db = mapStmtToDB.get(stmt);
// const db = mapStmtToDB.get(stmt);
mapStmtToDB.delete(stmt);
// Don't throw on error here. Typically the error has already been
@@ -594,7 +594,7 @@ export function Factory(Module) {
return async function (stmt) {
verifyStatement(stmt);
const result = await f(stmt);
return check(fname, result, mapStmtToDB.get(stmt));
return check(fname, result, null, stmt);
};
})();
@@ -737,15 +737,25 @@ export function Factory(Module) {
const fname = "sqlite3_step";
const f = Module.cwrap(fname, ...decl("n:n"), { async });
return async function (stmt) {
verifyStatement(stmt);
// verifyStatement(stmt);
const result = await f(stmt);
return check(fname, result, mapStmtToDB.get(stmt), [
return check(fname, result, null, stmt, [
SQLite.SQLITE_ROW,
SQLite.SQLITE_DONE
]);
};
})();
sqlite3.last_insert_rowid = (function () {
const fname = "sqlite3_last_insert_rowid";
const f = Module.cwrap(fname, ...decl("n:n"));
return function (db) {
verifyDatabase(db);
const result = f(db);
return result;
};
})();
// Duplicate some of the SQLite dynamic string API but without
// calling SQLite (except for memory allocation). We need some way
// to transfer Javascript strings and might as well use an API
@@ -907,9 +917,16 @@ export function Factory(Module) {
return check("sqlite3_vfs_register", result);
};
function check(fname, result, db = null, allowed = [SQLite.SQLITE_OK]) {
function check(
fname,
result,
db = null,
stmt = null,
allowed = [SQLite.SQLITE_OK]
) {
// trace(fname, result);
if (allowed.includes(result)) return result;
db = db || (stmt !== null ? mapStmtToDB.get(stmt) : null);
const message = db
? Module.ccall("sqlite3_errmsg", "string", ["number"], [db])
: fname;

View File

@@ -938,6 +938,13 @@ export interface SQLiteAPI {
*/
step(stmt: number): Promise<number>;
/**
* @see https://www.sqlite.org/c3ref/last_insert_rowid.html
* @param db database pointer
* @returns last rowid
*/
last_insert_rowid(db: number): number;
/**
* Create a new `sqlite3_str` dynamic string instance
*

View File

@@ -48,7 +48,7 @@ async function init(dbName: string, async: boolean, url?: string) {
? new IDBBatchAtomicVFS(dbName, { durability: "strict" })
: new AccessHandlePoolVFS(dbName);
if ("isReady" in vfs) await vfs.isReady;
console.log(vfs, SQLiteAsyncModule);
sqlite.vfs_register(vfs, false);
db = await sqlite.open_v2(dbName, undefined, `multipleciphers-${vfs.name}`);
}
@@ -70,15 +70,27 @@ async function prepare(sql: string) {
columns: sqlite.column_names(prepared.stmt)
};
preparedStatements.set(sql, statement);
sqlite.str_finish(str);
return statement;
}
async function run(sql: string, parameters?: SQLiteCompatibleType[]) {
async function run(
sql: string,
mode: RunMode,
parameters?: SQLiteCompatibleType[]
) {
const prepared = await prepare(sql);
if (!prepared) return [];
try {
if (parameters) sqlite.bind_collection(prepared.stmt, parameters);
// fast path for exec statements
if (mode === "exec") {
while ((await sqlite.step(prepared.stmt)) === SQLITE_ROW);
return [];
}
const rows: Record<string, SQLiteCompatibleType>[] = [];
while ((await sqlite.step(prepared.stmt)) === SQLITE_ROW) {
const row = sqlite.row(prepared.stmt);
@@ -96,7 +108,7 @@ async function run(sql: string, parameters?: SQLiteCompatibleType[]) {
sqlite
.finalize(prepared.stmt)
// ignore error (we will just prepare a new statement)
.catch(() => false)
.catch(console.error)
.finally(() => preparedStatements.delete(sql))
);
}
@@ -107,14 +119,11 @@ async function exec<R>(
sql: string,
parameters?: SQLiteCompatibleType[]
): Promise<QueryResult<R>> {
console.time(sql);
const rows = (await run(sql, parameters)) as R[];
console.timeEnd(sql);
const rows = (await run(sql, mode, parameters)) as R[];
if (mode === "query") return { rows };
const v = await run("SELECT last_insert_rowid() as id");
return {
insertId: BigInt(v[0].id as number),
insertId: BigInt(sqlite.last_insert_rowid(db)),
numAffectedRows: BigInt(sqlite.changes(db)),
rows: mode === "raw" ? rows : []
};