mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-24 15:39:44 +01:00
980 lines
28 KiB
JavaScript
980 lines
28 KiB
JavaScript
/*
|
|
This file is part of the Notesnook project (https://notesnook.com/)
|
|
|
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import * as SQLite from "./sqlite-constants.js";
|
|
export * from "./sqlite-constants.js";
|
|
|
|
const MAX_INT64 = 0x7fffffffffffffffn;
|
|
const MIN_INT64 = -0x8000000000000000n;
|
|
|
|
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
|
|
*/
|
|
export function Factory(Module) {
|
|
/** @type {SQLiteAPI} */
|
|
const sqlite3 = {};
|
|
|
|
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 n = Module.lengthBytesUTF8(s);
|
|
const zts = Module._sqlite3_malloc(n + 1);
|
|
Module.stringToUTF8(s, zts, n + 1);
|
|
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.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);
|
|
// trace(fname, result);
|
|
return check(fname, result, null, 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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
return check(fname, result, null, 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);
|
|
// trace(fname, result);
|
|
return check(fname, result, null, 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));
|
|
// trace(fname, result);
|
|
return check(fname, result, null, 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);
|
|
// trace(fname, result);
|
|
return check(fname, result, null, stmt);
|
|
};
|
|
})();
|
|
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
return check(fname, result, null, 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);
|
|
// trace(fname, result);
|
|
return result;
|
|
};
|
|
})();
|
|
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
return result;
|
|
};
|
|
})();
|
|
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
return result;
|
|
};
|
|
})();
|
|
|
|
sqlite3.create_function = function (
|
|
db,
|
|
zFunctionName,
|
|
nArg,
|
|
eTextRep,
|
|
pApp,
|
|
xFunc,
|
|
xStep,
|
|
xFinal
|
|
) {
|
|
verifyDatabase(db);
|
|
if (xFunc && !xStep && !xFinal) {
|
|
const result = Module.createFunction(
|
|
db,
|
|
zFunctionName,
|
|
nArg,
|
|
eTextRep,
|
|
pApp,
|
|
xFunc
|
|
);
|
|
return check("sqlite3_create_function", result, db);
|
|
}
|
|
|
|
if (!xFunc && xStep && xFinal) {
|
|
const result = Module.createAggregate(
|
|
db,
|
|
zFunctionName,
|
|
nArg,
|
|
eTextRep,
|
|
pApp,
|
|
xStep,
|
|
xFinal
|
|
);
|
|
return check("sqlite3_create_function", result, db);
|
|
}
|
|
|
|
throw new SQLiteError("invalid function combination", SQLite.SQLITE_MISUSE);
|
|
};
|
|
|
|
sqlite3.create_module = function (db, zName, module, appData) {
|
|
verifyDatabase(db);
|
|
const result = Module.createModule(db, zName, module, appData);
|
|
return check("sqlite3_create_module", 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);
|
|
// trace(fname, result);
|
|
return result;
|
|
};
|
|
})();
|
|
|
|
sqlite3.declare_vtab = (function () {
|
|
const fname = "sqlite3_declare_vtab";
|
|
const f = Module.cwrap(fname, ...decl("ns:n"));
|
|
return function (pVTab, zSQL) {
|
|
const result = f(pVTab, zSQL);
|
|
return check("sqlite3_declare_vtab", result);
|
|
};
|
|
})();
|
|
|
|
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) {
|
|
if (!mapStmtToDB.has(stmt)) {
|
|
return SQLite.SQLITE_MISUSE;
|
|
}
|
|
const result = await f(stmt);
|
|
|
|
// const db = mapStmtToDB.get(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.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.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);
|
|
const result = await f(zFilename, tmpPtr[0], flags, zVfs);
|
|
|
|
const db = Module.getValue(tmpPtr[0], "*");
|
|
databases.add(db);
|
|
Module._sqlite3_free(zVfs);
|
|
|
|
Module.ccall("RegisterExtensionFunctions", "void", ["number"], [db]);
|
|
Module.ccall("sqlite3Fts5BetterTrigramInit", "void", ["number"], [db]);
|
|
Module.ccall("sqlite3Fts5HtmlInit", "void", ["number"], [db]);
|
|
check(fname, result);
|
|
return db;
|
|
};
|
|
})();
|
|
|
|
sqlite3.prepare_v2 = (function () {
|
|
const fname = "sqlite3_prepare_v2";
|
|
const f = Module.cwrap(fname, ...decl("nnnnn:n"), { async });
|
|
return async function (db, sql) {
|
|
const result = await f(db, sql, -1, tmpPtr[0], tmpPtr[1]);
|
|
check(fname, result, db);
|
|
|
|
const stmt = Module.getValue(tmpPtr[0], "*");
|
|
if (stmt) {
|
|
mapStmtToDB.set(stmt, db);
|
|
return { stmt, sql: Module.getValue(tmpPtr[1], "*") };
|
|
}
|
|
return null;
|
|
};
|
|
})();
|
|
|
|
sqlite3.progress_handler = function (db, nProgressOps, handler, userData) {
|
|
verifyDatabase(db);
|
|
Module.progressHandler(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, null, 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.set_authorizer = function (db, authFunction, userData) {
|
|
verifyDatabase(db);
|
|
const result = Module.setAuthorizer(db, authFunction, userData);
|
|
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);
|
|
// trace(fname, result);
|
|
return result;
|
|
};
|
|
})();
|
|
|
|
sqlite3.statements = function (db, sql) {
|
|
return (async function* () {
|
|
const str = sqlite3.str_new(db, sql);
|
|
let prepared = { stmt: null, sql: sqlite3.str_value(str) };
|
|
try {
|
|
while ((prepared = await sqlite3.prepare_v2(db, prepared.sql))) {
|
|
// console.log(sqlite3.sql(prepared.stmt));
|
|
yield prepared.stmt;
|
|
sqlite3.finalize(prepared.stmt);
|
|
prepared.stmt = null;
|
|
}
|
|
} finally {
|
|
if (prepared?.stmt) {
|
|
sqlite3.finalize(prepared.stmt);
|
|
}
|
|
sqlite3.str_finish(str);
|
|
}
|
|
})();
|
|
};
|
|
|
|
sqlite3.step = (function () {
|
|
const fname = "sqlite3_step";
|
|
const f = Module.cwrap(fname, ...decl("n:n"), { async });
|
|
return async function (stmt) {
|
|
// verifyStatement(stmt);
|
|
const result = await f(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
|
|
// that mimics the SQLite API.
|
|
let stringId = 0;
|
|
const strings = new Map();
|
|
|
|
sqlite3.str_new = function (db, s = "") {
|
|
const sBytes = Module.lengthBytesUTF8(s);
|
|
const str = stringId++ & 0xffffffff;
|
|
const data = {
|
|
offset: Module._sqlite3_malloc(sBytes + 1),
|
|
bytes: sBytes
|
|
};
|
|
strings.set(str, data);
|
|
Module.stringToUTF8(s, data.offset, data.bytes + 1);
|
|
return str;
|
|
};
|
|
|
|
sqlite3.str_appendall = function (str, s) {
|
|
if (!strings.has(str)) {
|
|
throw new SQLiteError("not a string", SQLite.SQLITE_MISUSE);
|
|
}
|
|
const data = strings.get(str);
|
|
|
|
const sBytes = Module.lengthBytesUTF8(s);
|
|
const newBytes = data.bytes + sBytes;
|
|
const newOffset = Module._sqlite3_malloc(newBytes + 1);
|
|
const newArray = Module.HEAPU8.subarray(
|
|
newOffset,
|
|
newOffset + newBytes + 1
|
|
);
|
|
newArray.set(Module.HEAPU8.subarray(data.offset, data.offset + data.bytes));
|
|
Module.stringToUTF8(s, newOffset + data.bytes, sBytes + 1);
|
|
|
|
Module._sqlite3_free(data.offset);
|
|
data.offset = newOffset;
|
|
data.bytes = newBytes;
|
|
strings.set(str, data);
|
|
};
|
|
|
|
sqlite3.str_finish = function (str) {
|
|
if (!strings.has(str)) {
|
|
throw new SQLiteError("not a string", SQLite.SQLITE_MISUSE);
|
|
}
|
|
const data = strings.get(str);
|
|
strings.delete(str);
|
|
Module._sqlite3_free(data.offset);
|
|
};
|
|
|
|
sqlite3.str_value = function (str) {
|
|
if (!strings.has(str)) {
|
|
throw new SQLiteError("not a string", SQLite.SQLITE_MISUSE);
|
|
}
|
|
return strings.get(str).offset;
|
|
};
|
|
|
|
sqlite3.user_data = function (context) {
|
|
return Module.getFunctionUserData(context);
|
|
};
|
|
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
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);
|
|
// trace(fname, result);
|
|
return result;
|
|
};
|
|
})();
|
|
|
|
sqlite3.vfs_register = function (vfs, makeDefault) {
|
|
const result = Module.registerVFS(vfs, makeDefault);
|
|
return check("sqlite3_vfs_register", result);
|
|
};
|
|
|
|
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;
|
|
throw new SQLiteError(message, result);
|
|
}
|
|
|
|
return sqlite3;
|
|
}
|
|
|
|
function trace(...args) {
|
|
// const date = new Date();
|
|
// const t = date.getHours().toString().padStart(2, '0') + ':' +
|
|
// date.getMinutes().toString().padStart(2, '0') + ':' +
|
|
// date.getSeconds().toString().padStart(2, '0') + '.' +
|
|
// date.getMilliseconds().toString().padStart(3, '0');
|
|
// console.debug(t, ...args);
|
|
}
|
|
|
|
// 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;
|
|
}
|