mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
web: make sqlite slightly faster
This commit is contained in:
@@ -49,8 +49,8 @@ async function initializeDatabase(persistence: DatabasePersistence) {
|
|||||||
database.setup({
|
database.setup({
|
||||||
sqliteOptions: {
|
sqliteOptions: {
|
||||||
dialect: createDialect,
|
dialect: createDialect,
|
||||||
...(isFeatureSupported("opfs")
|
...(IS_DESKTOP_APP || isFeatureSupported("opfs")
|
||||||
? { journalMode: "WAL" }
|
? { journalMode: "WAL", lockingMode: "exclusive" }
|
||||||
: {
|
: {
|
||||||
journalMode: "MEMORY",
|
journalMode: "MEMORY",
|
||||||
lockingMode: "exclusive"
|
lockingMode: "exclusive"
|
||||||
@@ -58,7 +58,7 @@ async function initializeDatabase(persistence: DatabasePersistence) {
|
|||||||
tempStore: "memory",
|
tempStore: "memory",
|
||||||
synchronous: "normal",
|
synchronous: "normal",
|
||||||
pageSize: 8192,
|
pageSize: 8192,
|
||||||
cacheSize: -16000,
|
cacheSize: -32000,
|
||||||
password: databaseKey
|
password: databaseKey
|
||||||
},
|
},
|
||||||
storage: storage,
|
storage: storage,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const createDialect = (name: string): Dialect => {
|
|||||||
return {
|
return {
|
||||||
createDriver: () =>
|
createDriver: () =>
|
||||||
new WaSqliteWorkerDriver({
|
new WaSqliteWorkerDriver({
|
||||||
async: isFeatureSupported("opfs") ? false : true,
|
async: !isFeatureSupported("opfs"),
|
||||||
dbName: name
|
dbName: name
|
||||||
}),
|
}),
|
||||||
createAdapter: () => new SqliteAdapter(),
|
createAdapter: () => new SqliteAdapter(),
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export function Factory(Module) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sqlite3.bind = function (stmt, i, value) {
|
sqlite3.bind = function (stmt, i, value) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
case "number":
|
case "number":
|
||||||
if (value === (value | 0)) {
|
if (value === (value | 0)) {
|
||||||
@@ -152,14 +152,14 @@ export function Factory(Module) {
|
|||||||
const fname = "sqlite3_bind_blob";
|
const fname = "sqlite3_bind_blob";
|
||||||
const f = Module.cwrap(fname, ...decl("nnnnn:n"));
|
const f = Module.cwrap(fname, ...decl("nnnnn:n"));
|
||||||
return function (stmt, i, value) {
|
return function (stmt, i, value) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const byteLength = value.byteLength ?? value.length;
|
const byteLength = value.byteLength ?? value.length;
|
||||||
const ptr = Module._sqlite3_malloc(byteLength);
|
const ptr = Module._sqlite3_malloc(byteLength);
|
||||||
Module.HEAPU8.subarray(ptr).set(value);
|
Module.HEAPU8.subarray(ptr).set(value);
|
||||||
const result = f(stmt, i, ptr, byteLength, sqliteFreeAddress);
|
const result = f(stmt, i, ptr, byteLength, sqliteFreeAddress);
|
||||||
// trace(fname, result);
|
// 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 fname = "sqlite3_bind_parameter_count";
|
||||||
const f = Module.cwrap(fname, ...decl("n:n"));
|
const f = Module.cwrap(fname, ...decl("n:n"));
|
||||||
return function (stmt) {
|
return function (stmt) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
const result = f(stmt);
|
const result = f(stmt);
|
||||||
// trace(fname, result);
|
// trace(fname, result);
|
||||||
return result;
|
return result;
|
||||||
@@ -178,7 +178,7 @@ export function Factory(Module) {
|
|||||||
const fname = "sqlite3_clear_bindings";
|
const fname = "sqlite3_clear_bindings";
|
||||||
const f = Module.cwrap(fname, ...decl("n:n"));
|
const f = Module.cwrap(fname, ...decl("n:n"));
|
||||||
return function (stmt) {
|
return function (stmt) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
const result = f(stmt);
|
const result = f(stmt);
|
||||||
// trace(fname, result);
|
// trace(fname, result);
|
||||||
return result;
|
return result;
|
||||||
@@ -189,10 +189,10 @@ export function Factory(Module) {
|
|||||||
const fname = "sqlite3_bind_double";
|
const fname = "sqlite3_bind_double";
|
||||||
const f = Module.cwrap(fname, ...decl("nnn:n"));
|
const f = Module.cwrap(fname, ...decl("nnn:n"));
|
||||||
return function (stmt, i, value) {
|
return function (stmt, i, value) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
const result = f(stmt, i, value);
|
const result = f(stmt, i, value);
|
||||||
// trace(fname, result);
|
// 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 fname = "sqlite3_bind_int";
|
||||||
const f = Module.cwrap(fname, ...decl("nnn:n"));
|
const f = Module.cwrap(fname, ...decl("nnn:n"));
|
||||||
return function (stmt, i, value) {
|
return function (stmt, i, value) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
if (value > 0x7fffffff || value < -0x80000000) return SQLite.SQLITE_RANGE;
|
if (value > 0x7fffffff || value < -0x80000000) return SQLite.SQLITE_RANGE;
|
||||||
|
|
||||||
const result = f(stmt, i, value);
|
const result = f(stmt, i, value);
|
||||||
// trace(fname, result);
|
// 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 fname = "sqlite3_bind_int64";
|
||||||
const f = Module.cwrap(fname, ...decl("nnnn:n"));
|
const f = Module.cwrap(fname, ...decl("nnnn:n"));
|
||||||
return function (stmt, i, value) {
|
return function (stmt, i, value) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
if (value > MAX_INT64 || value < MIN_INT64) return SQLite.SQLITE_RANGE;
|
if (value > MAX_INT64 || value < MIN_INT64) return SQLite.SQLITE_RANGE;
|
||||||
|
|
||||||
const lo32 = value & 0xffffffffn;
|
const lo32 = value & 0xffffffffn;
|
||||||
const hi32 = value >> 32n;
|
const hi32 = value >> 32n;
|
||||||
const result = f(stmt, i, Number(lo32), Number(hi32));
|
const result = f(stmt, i, Number(lo32), Number(hi32));
|
||||||
// trace(fname, result);
|
// 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 fname = "sqlite3_bind_null";
|
||||||
const f = Module.cwrap(fname, ...decl("nn:n"));
|
const f = Module.cwrap(fname, ...decl("nn:n"));
|
||||||
return function (stmt, i) {
|
return function (stmt, i) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
const result = f(stmt, i);
|
const result = f(stmt, i);
|
||||||
// trace(fname, result);
|
// 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 fname = "sqlite3_bind_parameter_name";
|
||||||
const f = Module.cwrap(fname, ...decl("n:s"));
|
const f = Module.cwrap(fname, ...decl("n:s"));
|
||||||
return function (stmt, i) {
|
return function (stmt, i) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
const result = f(stmt, i);
|
const result = f(stmt, i);
|
||||||
// trace(fname, result);
|
// trace(fname, result);
|
||||||
return result;
|
return result;
|
||||||
@@ -250,11 +250,11 @@ export function Factory(Module) {
|
|||||||
const fname = "sqlite3_bind_text";
|
const fname = "sqlite3_bind_text";
|
||||||
const f = Module.cwrap(fname, ...decl("nnnnn:n"));
|
const f = Module.cwrap(fname, ...decl("nnnnn:n"));
|
||||||
return function (stmt, i, value) {
|
return function (stmt, i, value) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
const ptr = createUTF8(value);
|
const ptr = createUTF8(value);
|
||||||
const result = f(stmt, i, ptr, -1, sqliteFreeAddress);
|
const result = f(stmt, i, ptr, -1, sqliteFreeAddress);
|
||||||
// trace(fname, result);
|
// 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 result = await f(stmt);
|
||||||
|
|
||||||
const db = mapStmtToDB.get(stmt);
|
// const db = mapStmtToDB.get(stmt);
|
||||||
mapStmtToDB.delete(stmt);
|
mapStmtToDB.delete(stmt);
|
||||||
|
|
||||||
// Don't throw on error here. Typically the error has already been
|
// Don't throw on error here. Typically the error has already been
|
||||||
@@ -594,7 +594,7 @@ export function Factory(Module) {
|
|||||||
return async function (stmt) {
|
return async function (stmt) {
|
||||||
verifyStatement(stmt);
|
verifyStatement(stmt);
|
||||||
const result = await f(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 fname = "sqlite3_step";
|
||||||
const f = Module.cwrap(fname, ...decl("n:n"), { async });
|
const f = Module.cwrap(fname, ...decl("n:n"), { async });
|
||||||
return async function (stmt) {
|
return async function (stmt) {
|
||||||
verifyStatement(stmt);
|
// verifyStatement(stmt);
|
||||||
const result = await f(stmt);
|
const result = await f(stmt);
|
||||||
return check(fname, result, mapStmtToDB.get(stmt), [
|
return check(fname, result, null, stmt, [
|
||||||
SQLite.SQLITE_ROW,
|
SQLite.SQLITE_ROW,
|
||||||
SQLite.SQLITE_DONE
|
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
|
// Duplicate some of the SQLite dynamic string API but without
|
||||||
// calling SQLite (except for memory allocation). We need some way
|
// calling SQLite (except for memory allocation). We need some way
|
||||||
// to transfer Javascript strings and might as well use an API
|
// 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);
|
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);
|
// trace(fname, result);
|
||||||
if (allowed.includes(result)) return result;
|
if (allowed.includes(result)) return result;
|
||||||
|
db = db || (stmt !== null ? mapStmtToDB.get(stmt) : null);
|
||||||
const message = db
|
const message = db
|
||||||
? Module.ccall("sqlite3_errmsg", "string", ["number"], [db])
|
? Module.ccall("sqlite3_errmsg", "string", ["number"], [db])
|
||||||
: fname;
|
: fname;
|
||||||
|
|||||||
@@ -938,6 +938,13 @@ export interface SQLiteAPI {
|
|||||||
*/
|
*/
|
||||||
step(stmt: number): Promise<number>;
|
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
|
* Create a new `sqlite3_str` dynamic string instance
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ async function init(dbName: string, async: boolean, url?: string) {
|
|||||||
? new IDBBatchAtomicVFS(dbName, { durability: "strict" })
|
? new IDBBatchAtomicVFS(dbName, { durability: "strict" })
|
||||||
: new AccessHandlePoolVFS(dbName);
|
: new AccessHandlePoolVFS(dbName);
|
||||||
if ("isReady" in vfs) await vfs.isReady;
|
if ("isReady" in vfs) await vfs.isReady;
|
||||||
console.log(vfs, SQLiteAsyncModule);
|
|
||||||
sqlite.vfs_register(vfs, false);
|
sqlite.vfs_register(vfs, false);
|
||||||
db = await sqlite.open_v2(dbName, undefined, `multipleciphers-${vfs.name}`);
|
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)
|
columns: sqlite.column_names(prepared.stmt)
|
||||||
};
|
};
|
||||||
preparedStatements.set(sql, statement);
|
preparedStatements.set(sql, statement);
|
||||||
|
|
||||||
|
sqlite.str_finish(str);
|
||||||
return statement;
|
return statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run(sql: string, parameters?: SQLiteCompatibleType[]) {
|
async function run(
|
||||||
|
sql: string,
|
||||||
|
mode: RunMode,
|
||||||
|
parameters?: SQLiteCompatibleType[]
|
||||||
|
) {
|
||||||
const prepared = await prepare(sql);
|
const prepared = await prepare(sql);
|
||||||
if (!prepared) return [];
|
if (!prepared) return [];
|
||||||
try {
|
try {
|
||||||
if (parameters) sqlite.bind_collection(prepared.stmt, parameters);
|
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>[] = [];
|
const rows: Record<string, SQLiteCompatibleType>[] = [];
|
||||||
while ((await sqlite.step(prepared.stmt)) === SQLITE_ROW) {
|
while ((await sqlite.step(prepared.stmt)) === SQLITE_ROW) {
|
||||||
const row = sqlite.row(prepared.stmt);
|
const row = sqlite.row(prepared.stmt);
|
||||||
@@ -96,7 +108,7 @@ async function run(sql: string, parameters?: SQLiteCompatibleType[]) {
|
|||||||
sqlite
|
sqlite
|
||||||
.finalize(prepared.stmt)
|
.finalize(prepared.stmt)
|
||||||
// ignore error (we will just prepare a new statement)
|
// ignore error (we will just prepare a new statement)
|
||||||
.catch(() => false)
|
.catch(console.error)
|
||||||
.finally(() => preparedStatements.delete(sql))
|
.finally(() => preparedStatements.delete(sql))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -107,14 +119,11 @@ async function exec<R>(
|
|||||||
sql: string,
|
sql: string,
|
||||||
parameters?: SQLiteCompatibleType[]
|
parameters?: SQLiteCompatibleType[]
|
||||||
): Promise<QueryResult<R>> {
|
): Promise<QueryResult<R>> {
|
||||||
console.time(sql);
|
const rows = (await run(sql, mode, parameters)) as R[];
|
||||||
const rows = (await run(sql, parameters)) as R[];
|
|
||||||
console.timeEnd(sql);
|
|
||||||
if (mode === "query") return { rows };
|
if (mode === "query") return { rows };
|
||||||
|
|
||||||
const v = await run("SELECT last_insert_rowid() as id");
|
|
||||||
return {
|
return {
|
||||||
insertId: BigInt(v[0].id as number),
|
insertId: BigInt(sqlite.last_insert_rowid(db)),
|
||||||
numAffectedRows: BigInt(sqlite.changes(db)),
|
numAffectedRows: BigInt(sqlite.changes(db)),
|
||||||
rows: mode === "raw" ? rows : []
|
rows: mode === "raw" ? rows : []
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user