feat: init
This commit is contained in:
136
apps/web/core/local-db/worker/db.ts
Normal file
136
apps/web/core/local-db/worker/db.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import * as Comlink from "comlink";
|
||||
import { OPFSCoopSyncVFS as MyVFS } from "./wa-sqlite/src/OPFSCoopSyncVFS";
|
||||
import * as SQLite from "./wa-sqlite/src/sqlite-api";
|
||||
import SQLiteESMFactory from "./wa-sqlite/src/wa-sqlite.mjs";
|
||||
|
||||
type TQueryProps = {
|
||||
sql: string;
|
||||
rowMode: string;
|
||||
returnValue: string;
|
||||
bind: any[];
|
||||
};
|
||||
const mergeToObject = (columns: string[], row: any[]) => {
|
||||
const obj: any = {};
|
||||
columns.forEach((column, index) => {
|
||||
obj[column] = row[index];
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
interface SQLiteInstance {
|
||||
db: unknown;
|
||||
exec: (sql: string) => Promise<unknown[]>;
|
||||
}
|
||||
|
||||
export class DBClass {
|
||||
private instance: SQLiteInstance = {} as SQLiteInstance;
|
||||
private sqlite3: any;
|
||||
private tp: Promise<any>[] = [];
|
||||
private tpResolver: any = [];
|
||||
async init(dbName: string) {
|
||||
if (!dbName || typeof dbName !== "string") {
|
||||
throw new Error("Invalid database name");
|
||||
}
|
||||
|
||||
try {
|
||||
const m = await SQLiteESMFactory();
|
||||
this.sqlite3 = SQLite.Factory(m);
|
||||
const vfs = await MyVFS.create("plane", m);
|
||||
this.sqlite3.vfs_register(vfs, true);
|
||||
// Fallback in rare cases where the database is not initialized in time
|
||||
const p = new Promise((resolve) => setTimeout(() => resolve(false), 2000));
|
||||
const dbPromise = this.sqlite3.open_v2(
|
||||
`${dbName}.sqlite3`,
|
||||
this.sqlite3.OPEN_READWRITE | this.sqlite3.OPEN_CREATE,
|
||||
"plane"
|
||||
);
|
||||
|
||||
const db = await Promise.any([dbPromise, p]);
|
||||
|
||||
if (!db) {
|
||||
throw new Error("Failed to initialize in time");
|
||||
}
|
||||
|
||||
this.instance.db = db;
|
||||
this.instance.exec = async (sql: string) => {
|
||||
const rows: any[] = [];
|
||||
await this.sqlite3.exec(db, sql, (row: any[], columns: string[]) => {
|
||||
rows.push(mergeToObject(columns, row));
|
||||
});
|
||||
|
||||
return rows;
|
||||
};
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to initialize database: ${(error as any)?.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
runQuery(sql: string) {
|
||||
return this.instance?.exec?.(sql);
|
||||
}
|
||||
|
||||
async exec(props: string | TQueryProps) {
|
||||
// @todo this will fail if the transaction is started any other way
|
||||
// eg: BEGIN, OR BEGIN TRANSACTION
|
||||
if (props === "BEGIN;") {
|
||||
let promiseToAwait;
|
||||
if (this.tp.length > 0) {
|
||||
promiseToAwait = this.tp.shift();
|
||||
}
|
||||
const p = new Promise((resolve, reject) => {
|
||||
this.tpResolver.push({ resolve, reject });
|
||||
});
|
||||
this.tp.push(p);
|
||||
|
||||
if (promiseToAwait) {
|
||||
await promiseToAwait;
|
||||
}
|
||||
}
|
||||
let sql: string, bind: any[];
|
||||
if (typeof props === "string") {
|
||||
sql = props;
|
||||
} else {
|
||||
({ sql, bind } = props);
|
||||
if (bind) {
|
||||
for await (const stmt of this.sqlite3.statements(this.instance.db, sql)) {
|
||||
bind.forEach((b, i) => {
|
||||
this.sqlite3.bind(stmt, i + 1, b);
|
||||
});
|
||||
|
||||
const rows = [];
|
||||
|
||||
do {
|
||||
const columns = await this.sqlite3.column_names(stmt);
|
||||
const row = await this.sqlite3.row(stmt);
|
||||
rows.push(mergeToObject(columns, row));
|
||||
} while ((await this.sqlite3.step(stmt)) === SQLite.SQLITE_ROW);
|
||||
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sql === "COMMIT;" && this.tp) {
|
||||
await this.instance?.exec?.(sql);
|
||||
if (this.tp.length > 0) {
|
||||
const { resolve } = this.tpResolver.shift();
|
||||
resolve();
|
||||
}
|
||||
return;
|
||||
}
|
||||
return await this.instance?.exec?.(sql);
|
||||
}
|
||||
async close() {
|
||||
try {
|
||||
if (!this.instance.db) {
|
||||
return;
|
||||
}
|
||||
await this.sqlite3.close(this.instance.db);
|
||||
// Clear instance to prevent usage after closing
|
||||
this.instance = {} as SQLiteInstance;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to close database: ${(error as any)?.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Comlink.expose(DBClass);
|
||||
508
apps/web/core/local-db/worker/wa-sqlite/src/FacadeVFS.js
Normal file
508
apps/web/core/local-db/worker/wa-sqlite/src/FacadeVFS.js
Normal file
@@ -0,0 +1,508 @@
|
||||
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
|
||||
import * as VFS from './VFS.js';
|
||||
|
||||
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
||||
|
||||
// Convenience base class for a JavaScript VFS.
|
||||
// The raw xOpen, xRead, etc. function signatures receive only C primitives
|
||||
// which aren't easy to work with. This class provides corresponding calls
|
||||
// like jOpen, jRead, etc., which receive JavaScript-friendlier arguments
|
||||
// such as string, Uint8Array, and DataView.
|
||||
export class FacadeVFS extends VFS.Base {
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {object} module
|
||||
*/
|
||||
constructor(name, module) {
|
||||
super(name, module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to indicate which methods are asynchronous.
|
||||
* @param {string} methodName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAsyncMethod(methodName) {
|
||||
// The input argument is a string like "xOpen", so convert to "jOpen".
|
||||
// Then check if the method exists and is async.
|
||||
const jMethodName = `j${methodName.slice(1)}`;
|
||||
return this[jMethodName] instanceof AsyncFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filename for a file id for use by mixins.
|
||||
* @param {number} pFile
|
||||
* @returns {string}
|
||||
*/
|
||||
getFilename(pFile) {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string?} filename
|
||||
* @param {number} pFile
|
||||
* @param {number} flags
|
||||
* @param {DataView} pOutFlags
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jOpen(filename, pFile, flags, pOutFlags) {
|
||||
return VFS.SQLITE_CANTOPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filename
|
||||
* @param {number} syncDir
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jDelete(filename, syncDir) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filename
|
||||
* @param {number} flags
|
||||
* @param {DataView} pResOut
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jAccess(filename, flags, pResOut) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filename
|
||||
* @param {Uint8Array} zOut
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jFullPathname(filename, zOut) {
|
||||
// Copy the filename to the output buffer.
|
||||
const { read, written } = new TextEncoder().encodeInto(filename, zOut);
|
||||
if (read < filename.length) return VFS.SQLITE_IOERR;
|
||||
if (written >= zOut.length) return VFS.SQLITE_IOERR;
|
||||
zOut[written] = 0;
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} zBuf
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jGetLastError(zBuf) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jClose(pFile) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {Uint8Array} pData
|
||||
* @param {number} iOffset
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jRead(pFile, pData, iOffset) {
|
||||
pData.fill(0);
|
||||
return VFS.SQLITE_IOERR_SHORT_READ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {Uint8Array} pData
|
||||
* @param {number} iOffset
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jWrite(pFile, pData, iOffset) {
|
||||
return VFS.SQLITE_IOERR_WRITE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} size
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jTruncate(pFile, size) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} flags
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jSync(pFile, flags) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {DataView} pSize
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jFileSize(pFile, pSize) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} lockType
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jLock(pFile, lockType) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} lockType
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jUnlock(pFile, lockType) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {DataView} pResOut
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jCheckReservedLock(pFile, pResOut) {
|
||||
pResOut.setInt32(0, 0, true);
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} op
|
||||
* @param {DataView} pArg
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jFileControl(pFile, op, pArg) {
|
||||
return VFS.SQLITE_NOTFOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jSectorSize(pFile) {
|
||||
return super.xSectorSize(pFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jDeviceCharacteristics(pFile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} zName
|
||||
* @param {number} pFile
|
||||
* @param {number} flags
|
||||
* @param {number} pOutFlags
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xOpen(pVfs, zName, pFile, flags, pOutFlags) {
|
||||
const filename = this.#decodeFilename(zName, flags);
|
||||
const pOutFlagsView = this.#makeTypedDataView('Int32', pOutFlags);
|
||||
this['log']?.('jOpen', filename, pFile, '0x' + flags.toString(16));
|
||||
return this.jOpen(filename, pFile, flags, pOutFlagsView);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} zName
|
||||
* @param {number} syncDir
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xDelete(pVfs, zName, syncDir) {
|
||||
const filename = this._module.UTF8ToString(zName);
|
||||
this['log']?.('jDelete', filename, syncDir);
|
||||
return this.jDelete(filename, syncDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} zName
|
||||
* @param {number} flags
|
||||
* @param {number} pResOut
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xAccess(pVfs, zName, flags, pResOut) {
|
||||
const filename = this._module.UTF8ToString(zName);
|
||||
const pResOutView = this.#makeTypedDataView('Int32', pResOut);
|
||||
this['log']?.('jAccess', filename, flags);
|
||||
return this.jAccess(filename, flags, pResOutView);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} zName
|
||||
* @param {number} nOut
|
||||
* @param {number} zOut
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xFullPathname(pVfs, zName, nOut, zOut) {
|
||||
const filename = this._module.UTF8ToString(zName);
|
||||
const zOutArray = this._module.HEAPU8.subarray(zOut, zOut + nOut);
|
||||
this['log']?.('jFullPathname', filename, nOut);
|
||||
return this.jFullPathname(filename, zOutArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} nBuf
|
||||
* @param {number} zBuf
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xGetLastError(pVfs, nBuf, zBuf) {
|
||||
const zBufArray = this._module.HEAPU8.subarray(zBuf, zBuf + nBuf);
|
||||
this['log']?.('jGetLastError', nBuf);
|
||||
return this.jGetLastError(zBufArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xClose(pFile) {
|
||||
this['log']?.('jClose', pFile);
|
||||
return this.jClose(pFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} pData
|
||||
* @param {number} iAmt
|
||||
* @param {number} iOffsetLo
|
||||
* @param {number} iOffsetHi
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xRead(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
|
||||
const pDataArray = this.#makeDataArray(pData, iAmt);
|
||||
const iOffset = delegalize(iOffsetLo, iOffsetHi);
|
||||
this['log']?.('jRead', pFile, iAmt, iOffset);
|
||||
return this.jRead(pFile, pDataArray, iOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} pData
|
||||
* @param {number} iAmt
|
||||
* @param {number} iOffsetLo
|
||||
* @param {number} iOffsetHi
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xWrite(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
|
||||
const pDataArray = this.#makeDataArray(pData, iAmt);
|
||||
const iOffset = delegalize(iOffsetLo, iOffsetHi);
|
||||
this['log']?.('jWrite', pFile, pDataArray, iOffset);
|
||||
return this.jWrite(pFile, pDataArray, iOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} sizeLo
|
||||
* @param {number} sizeHi
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xTruncate(pFile, sizeLo, sizeHi) {
|
||||
const size = delegalize(sizeLo, sizeHi);
|
||||
this['log']?.('jTruncate', pFile, size);
|
||||
return this.jTruncate(pFile, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} flags
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xSync(pFile, flags) {
|
||||
this['log']?.('jSync', pFile, flags);
|
||||
return this.jSync(pFile, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} pFile
|
||||
* @param {number} pSize
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xFileSize(pFile, pSize) {
|
||||
const pSizeView = this.#makeTypedDataView('BigInt64', pSize);
|
||||
this['log']?.('jFileSize', pFile);
|
||||
return this.jFileSize(pFile, pSizeView);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} lockType
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xLock(pFile, lockType) {
|
||||
this['log']?.('jLock', pFile, lockType);
|
||||
return this.jLock(pFile, lockType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} lockType
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xUnlock(pFile, lockType) {
|
||||
this['log']?.('jUnlock', pFile, lockType);
|
||||
return this.jUnlock(pFile, lockType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} pResOut
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xCheckReservedLock(pFile, pResOut) {
|
||||
const pResOutView = this.#makeTypedDataView('Int32', pResOut);
|
||||
this['log']?.('jCheckReservedLock', pFile);
|
||||
return this.jCheckReservedLock(pFile, pResOutView);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} op
|
||||
* @param {number} pArg
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xFileControl(pFile, op, pArg) {
|
||||
const pArgView = new DataView(
|
||||
this._module.HEAPU8.buffer,
|
||||
this._module.HEAPU8.byteOffset + pArg);
|
||||
this['log']?.('jFileControl', pFile, op, pArgView);
|
||||
return this.jFileControl(pFile, op, pArgView);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xSectorSize(pFile) {
|
||||
this['log']?.('jSectorSize', pFile);
|
||||
return this.jSectorSize(pFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xDeviceCharacteristics(pFile) {
|
||||
this['log']?.('jDeviceCharacteristics', pFile);
|
||||
return this.jDeviceCharacteristics(pFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapped DataView for pointer arguments.
|
||||
* Pointers to a single value are passed using DataView. A Proxy
|
||||
* wrapper prevents use of incorrect type or endianness.
|
||||
* @param {'Int32'|'BigInt64'} type
|
||||
* @param {number} byteOffset
|
||||
* @returns {DataView}
|
||||
*/
|
||||
#makeTypedDataView(type, byteOffset) {
|
||||
const byteLength = type === 'Int32' ? 4 : 8;
|
||||
const getter = `get${type}`;
|
||||
const setter = `set${type}`;
|
||||
const makeDataView = () => new DataView(
|
||||
this._module.HEAPU8.buffer,
|
||||
this._module.HEAPU8.byteOffset + byteOffset,
|
||||
byteLength);
|
||||
let dataView = makeDataView();
|
||||
return new Proxy(dataView, {
|
||||
get(_, prop) {
|
||||
if (dataView.buffer.byteLength === 0) {
|
||||
// WebAssembly memory resize detached the buffer.
|
||||
dataView = makeDataView();
|
||||
}
|
||||
if (prop === getter) {
|
||||
return function(byteOffset, littleEndian) {
|
||||
if (!littleEndian) throw new Error('must be little endian');
|
||||
return dataView[prop](byteOffset, littleEndian);
|
||||
}
|
||||
}
|
||||
if (prop === setter) {
|
||||
return function(byteOffset, value, littleEndian) {
|
||||
if (!littleEndian) throw new Error('must be little endian');
|
||||
return dataView[prop](byteOffset, value, littleEndian);
|
||||
}
|
||||
}
|
||||
if (typeof prop === 'string' && (prop.match(/^(get)|(set)/))) {
|
||||
throw new Error('invalid type');
|
||||
}
|
||||
const result = dataView[prop];
|
||||
return typeof result === 'function' ? result.bind(dataView) : result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} byteOffset
|
||||
* @param {number} byteLength
|
||||
*/
|
||||
#makeDataArray(byteOffset, byteLength) {
|
||||
let target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);
|
||||
return new Proxy(target, {
|
||||
get: (_, prop, receiver) => {
|
||||
if (target.buffer.byteLength === 0) {
|
||||
// WebAssembly memory resize detached the buffer.
|
||||
target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);
|
||||
}
|
||||
const result = target[prop];
|
||||
return typeof result === 'function' ? result.bind(target) : result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#decodeFilename(zName, flags) {
|
||||
if (flags & VFS.SQLITE_OPEN_URI) {
|
||||
// The first null-terminated string is the URI path. Subsequent
|
||||
// strings are query parameter keys and values.
|
||||
// https://www.sqlite.org/c3ref/open.html#urifilenamesinsqlite3open
|
||||
let pName = zName;
|
||||
let state = 1;
|
||||
const charCodes = [];
|
||||
while (state) {
|
||||
const charCode = this._module.HEAPU8[pName++];
|
||||
if (charCode) {
|
||||
charCodes.push(charCode);
|
||||
} else {
|
||||
if (!this._module.HEAPU8[pName]) state = null;
|
||||
switch (state) {
|
||||
case 1: // path
|
||||
charCodes.push('?'.charCodeAt(0));
|
||||
state = 2;
|
||||
break;
|
||||
case 2: // key
|
||||
charCodes.push('='.charCodeAt(0));
|
||||
state = 3;
|
||||
break;
|
||||
case 3: // value
|
||||
charCodes.push('&'.charCodeAt(0));
|
||||
state = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new TextDecoder().decode(new Uint8Array(charCodes));
|
||||
}
|
||||
return zName ? this._module.UTF8ToString(zName) : null;
|
||||
}
|
||||
}
|
||||
|
||||
// Emscripten "legalizes" 64-bit integer arguments by passing them as
|
||||
// two 32-bit signed integers.
|
||||
function delegalize(lo32, hi32) {
|
||||
return (hi32 * 0x100000000) + lo32 + (lo32 < 0 ? 2**32 : 0);
|
||||
}
|
||||
592
apps/web/core/local-db/worker/wa-sqlite/src/OPFSCoopSyncVFS.js
Normal file
592
apps/web/core/local-db/worker/wa-sqlite/src/OPFSCoopSyncVFS.js
Normal file
@@ -0,0 +1,592 @@
|
||||
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
|
||||
import { FacadeVFS } from "./FacadeVFS.js";
|
||||
import * as VFS from "./VFS.js";
|
||||
|
||||
const DEFAULT_TEMPORARY_FILES = 10;
|
||||
const LOCK_NOTIFY_INTERVAL = 1000;
|
||||
|
||||
const DB_RELATED_FILE_SUFFIXES = ["", "-journal", "-wal"];
|
||||
|
||||
const finalizationRegistry = new FinalizationRegistry((releaser) => releaser());
|
||||
|
||||
class File {
|
||||
/** @type {string} */ path;
|
||||
/** @type {number} */ flags;
|
||||
/** @type {FileSystemSyncAccessHandle} */ accessHandle;
|
||||
|
||||
/** @type {PersistentFile?} */ persistentFile;
|
||||
|
||||
constructor(path, flags) {
|
||||
this.path = path;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
class PersistentFile {
|
||||
/** @type {FileSystemFileHandle} */ fileHandle;
|
||||
/** @type {FileSystemSyncAccessHandle} */ accessHandle = null;
|
||||
|
||||
// The following properties are for main database files.
|
||||
|
||||
/** @type {boolean} */ isLockBusy = false;
|
||||
/** @type {boolean} */ isFileLocked = false;
|
||||
/** @type {boolean} */ isRequestInProgress = false;
|
||||
/** @type {function} */ handleLockReleaser = null;
|
||||
|
||||
/** @type {BroadcastChannel} */ handleRequestChannel;
|
||||
/** @type {boolean} */ isHandleRequested = false;
|
||||
|
||||
constructor(fileHandle) {
|
||||
this.fileHandle = fileHandle;
|
||||
}
|
||||
}
|
||||
|
||||
export class OPFSCoopSyncVFS extends FacadeVFS {
|
||||
/** @type {Map<number, File>} */ mapIdToFile = new Map();
|
||||
|
||||
lastError = null;
|
||||
log = null; //function(...args) { console.log(`[${contextName}]`, ...args) };
|
||||
|
||||
/** @type {Map<string, PersistentFile>} */ persistentFiles = new Map();
|
||||
/** @type {Map<string, FileSystemSyncAccessHandle>} */ boundAccessHandles = new Map();
|
||||
/** @type {Set<FileSystemSyncAccessHandle>} */ unboundAccessHandles = new Set();
|
||||
/** @type {Set<string>} */ accessiblePaths = new Set();
|
||||
releaser = null;
|
||||
|
||||
static async create(name, module) {
|
||||
const vfs = new OPFSCoopSyncVFS(name, module);
|
||||
await Promise.all([vfs.isReady(), vfs.#initialize(DEFAULT_TEMPORARY_FILES)]);
|
||||
return vfs;
|
||||
}
|
||||
|
||||
constructor(name, module) {
|
||||
super(name, module);
|
||||
}
|
||||
|
||||
async #initialize(nTemporaryFiles) {
|
||||
// Delete temporary directories no longer in use.
|
||||
const root = await navigator.storage.getDirectory();
|
||||
// @ts-ignore
|
||||
for await (const entry of root.values()) {
|
||||
if (entry.kind === "directory" && entry.name.startsWith(".ahp-")) {
|
||||
// A lock with the same name as the directory protects it from
|
||||
// being deleted.
|
||||
await navigator.locks.request(entry.name, { ifAvailable: true }, async (lock) => {
|
||||
if (lock) {
|
||||
this.log?.(`Deleting temporary directory ${entry.name}`);
|
||||
await root.removeEntry(entry.name, { recursive: true });
|
||||
} else {
|
||||
this.log?.(`Temporary directory ${entry.name} is in use`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create our temporary directory.
|
||||
const tmpDirName = `.ahp-${Math.random().toString(36).slice(2)}`;
|
||||
this.releaser = await new Promise((resolve) => {
|
||||
navigator.locks.request(tmpDirName, () => {
|
||||
return new Promise((release) => {
|
||||
resolve(release);
|
||||
});
|
||||
});
|
||||
});
|
||||
finalizationRegistry.register(this, this.releaser);
|
||||
const tmpDir = await root.getDirectoryHandle(tmpDirName, { create: true });
|
||||
|
||||
// Populate temporary directory.
|
||||
for (let i = 0; i < nTemporaryFiles; i++) {
|
||||
const tmpFile = await tmpDir.getFileHandle(`${i}.tmp`, { create: true });
|
||||
const tmpAccessHandle = await tmpFile.createSyncAccessHandle();
|
||||
this.unboundAccessHandles.add(tmpAccessHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string?} zName
|
||||
* @param {number} fileId
|
||||
* @param {number} flags
|
||||
* @param {DataView} pOutFlags
|
||||
* @returns {number}
|
||||
*/
|
||||
jOpen(zName, fileId, flags, pOutFlags) {
|
||||
try {
|
||||
const url = new URL(zName || Math.random().toString(36).slice(2), "file://");
|
||||
const path = url.pathname;
|
||||
|
||||
if (flags & VFS.SQLITE_OPEN_MAIN_DB) {
|
||||
const persistentFile = this.persistentFiles.get(path);
|
||||
if (persistentFile?.isRequestInProgress) {
|
||||
// Should not reach here unless SQLite itself retries an open.
|
||||
// Otherwise, asynchronous operations started on a previous
|
||||
// open try should have completed.
|
||||
return VFS.SQLITE_BUSY;
|
||||
} else if (!persistentFile) {
|
||||
// This is the usual starting point for opening a database.
|
||||
// Register a Promise that resolves when the database and related
|
||||
// files are ready to be used.
|
||||
this.log?.(`creating persistent file for ${path}`);
|
||||
const create = !!(flags & VFS.SQLITE_OPEN_CREATE);
|
||||
this._module.retryOps.push(
|
||||
(async () => {
|
||||
try {
|
||||
// Get the path directory handle.
|
||||
let dirHandle = await navigator.storage.getDirectory();
|
||||
const directories = path.split("/").filter((d) => d);
|
||||
const filename = directories.pop();
|
||||
for (const directory of directories) {
|
||||
dirHandle = await dirHandle.getDirectoryHandle(directory, { create });
|
||||
}
|
||||
|
||||
// Get file handles for the database and related files,
|
||||
// and create persistent file instances.
|
||||
for (const suffix of DB_RELATED_FILE_SUFFIXES) {
|
||||
const fileHandle = await dirHandle.getFileHandle(filename + suffix, { create });
|
||||
await this.#createPersistentFile(fileHandle);
|
||||
}
|
||||
|
||||
// Get access handles for the files.
|
||||
const file = new File(path, flags);
|
||||
file.persistentFile = this.persistentFiles.get(path);
|
||||
await this.#requestAccessHandle(file);
|
||||
} catch (e) {
|
||||
// Use an invalid persistent file to signal this error
|
||||
// for the retried open.
|
||||
const persistentFile = new PersistentFile(null);
|
||||
this.persistentFiles.set(path, persistentFile);
|
||||
console.error(e);
|
||||
}
|
||||
})()
|
||||
);
|
||||
return VFS.SQLITE_BUSY;
|
||||
} else if (!persistentFile.fileHandle) {
|
||||
// The asynchronous open operation failed.
|
||||
this.persistentFiles.delete(path);
|
||||
return VFS.SQLITE_CANTOPEN;
|
||||
} else if (!persistentFile.accessHandle) {
|
||||
// This branch is reached if the database was previously opened
|
||||
// and closed.
|
||||
this._module.retryOps.push(
|
||||
(async () => {
|
||||
const file = new File(path, flags);
|
||||
file.persistentFile = this.persistentFiles.get(path);
|
||||
await this.#requestAccessHandle(file);
|
||||
})()
|
||||
);
|
||||
return VFS.SQLITE_BUSY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.accessiblePaths.has(path) && !(flags & VFS.SQLITE_OPEN_CREATE)) {
|
||||
throw new Error(`File ${path} not found`);
|
||||
}
|
||||
|
||||
const file = new File(path, flags);
|
||||
this.mapIdToFile.set(fileId, file);
|
||||
|
||||
if (this.persistentFiles.has(path)) {
|
||||
file.persistentFile = this.persistentFiles.get(path);
|
||||
} else if (this.boundAccessHandles.has(path)) {
|
||||
// This temporary file was previously created and closed. Reopen
|
||||
// the same access handle.
|
||||
file.accessHandle = this.boundAccessHandles.get(path);
|
||||
} else if (this.unboundAccessHandles.size) {
|
||||
// Associate an unbound access handle to this file.
|
||||
file.accessHandle = this.unboundAccessHandles.values().next().value;
|
||||
file.accessHandle.truncate(0);
|
||||
this.unboundAccessHandles.delete(file.accessHandle);
|
||||
this.boundAccessHandles.set(path, file.accessHandle);
|
||||
}
|
||||
this.accessiblePaths.add(path);
|
||||
|
||||
pOutFlags.setInt32(0, flags, true);
|
||||
return VFS.SQLITE_OK;
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_CANTOPEN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} zName
|
||||
* @param {number} syncDir
|
||||
* @returns {number}
|
||||
*/
|
||||
jDelete(zName, syncDir) {
|
||||
try {
|
||||
const url = new URL(zName, "file://");
|
||||
const path = url.pathname;
|
||||
if (this.persistentFiles.has(path)) {
|
||||
const persistentFile = this.persistentFiles.get(path);
|
||||
persistentFile.accessHandle.truncate(0);
|
||||
} else {
|
||||
this.boundAccessHandles.get(path)?.truncate(0);
|
||||
}
|
||||
this.accessiblePaths.delete(path);
|
||||
return VFS.SQLITE_OK;
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_IOERR_DELETE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} zName
|
||||
* @param {number} flags
|
||||
* @param {DataView} pResOut
|
||||
* @returns {number}
|
||||
*/
|
||||
jAccess(zName, flags, pResOut) {
|
||||
try {
|
||||
const url = new URL(zName, "file://");
|
||||
const path = url.pathname;
|
||||
pResOut.setInt32(0, this.accessiblePaths.has(path) ? 1 : 0, true);
|
||||
return VFS.SQLITE_OK;
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_IOERR_ACCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @returns {number}
|
||||
*/
|
||||
jClose(fileId) {
|
||||
try {
|
||||
const file = this.mapIdToFile.get(fileId);
|
||||
this.mapIdToFile.delete(fileId);
|
||||
|
||||
if (file?.flags & VFS.SQLITE_OPEN_MAIN_DB) {
|
||||
if (file.persistentFile?.handleLockReleaser) {
|
||||
this.#releaseAccessHandle(file);
|
||||
}
|
||||
} else if (file?.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
|
||||
file.accessHandle.truncate(0);
|
||||
this.accessiblePaths.delete(file.path);
|
||||
if (!this.persistentFiles.has(file.path)) {
|
||||
this.boundAccessHandles.delete(file.path);
|
||||
this.unboundAccessHandles.add(file.accessHandle);
|
||||
}
|
||||
}
|
||||
return VFS.SQLITE_OK;
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_IOERR_CLOSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {Uint8Array} pData
|
||||
* @param {number} iOffset
|
||||
* @returns {number}
|
||||
*/
|
||||
jRead(fileId, pData, iOffset) {
|
||||
try {
|
||||
const file = this.mapIdToFile.get(fileId);
|
||||
|
||||
// On Chrome (at least), passing pData to accessHandle.read() is
|
||||
// an error because pData is a Proxy of a Uint8Array. Calling
|
||||
// subarray() produces a real Uint8Array and that works.
|
||||
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
|
||||
const bytesRead = accessHandle.read(pData.subarray(), { at: iOffset });
|
||||
|
||||
// Opening a database file performs one read without a xLock call.
|
||||
if (file.flags & VFS.SQLITE_OPEN_MAIN_DB && !file.persistentFile.isFileLocked) {
|
||||
this.#releaseAccessHandle(file);
|
||||
}
|
||||
|
||||
if (bytesRead < pData.byteLength) {
|
||||
pData.fill(0, bytesRead);
|
||||
return VFS.SQLITE_IOERR_SHORT_READ;
|
||||
}
|
||||
return VFS.SQLITE_OK;
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_IOERR_READ;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {Uint8Array} pData
|
||||
* @param {number} iOffset
|
||||
* @returns {number}
|
||||
*/
|
||||
jWrite(fileId, pData, iOffset) {
|
||||
try {
|
||||
const file = this.mapIdToFile.get(fileId);
|
||||
|
||||
// On Chrome (at least), passing pData to accessHandle.write() is
|
||||
// an error because pData is a Proxy of a Uint8Array. Calling
|
||||
// subarray() produces a real Uint8Array and that works.
|
||||
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
|
||||
const nBytes = accessHandle.write(pData.subarray(), { at: iOffset });
|
||||
if (nBytes !== pData.byteLength) throw new Error("short write");
|
||||
return VFS.SQLITE_OK;
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_IOERR_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {number} iSize
|
||||
* @returns {number}
|
||||
*/
|
||||
jTruncate(fileId, iSize) {
|
||||
try {
|
||||
const file = this.mapIdToFile.get(fileId);
|
||||
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
|
||||
accessHandle.truncate(iSize);
|
||||
return VFS.SQLITE_OK;
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_IOERR_TRUNCATE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {number} flags
|
||||
* @returns {number}
|
||||
*/
|
||||
jSync(fileId, flags) {
|
||||
try {
|
||||
const file = this.mapIdToFile.get(fileId);
|
||||
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
|
||||
accessHandle.flush();
|
||||
return VFS.SQLITE_OK;
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_IOERR_FSYNC;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {DataView} pSize64
|
||||
* @returns {number}
|
||||
*/
|
||||
jFileSize(fileId, pSize64) {
|
||||
try {
|
||||
const file = this.mapIdToFile.get(fileId);
|
||||
const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
|
||||
const size = accessHandle.getSize();
|
||||
pSize64.setBigInt64(0, BigInt(size), true);
|
||||
return VFS.SQLITE_OK;
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_IOERR_FSTAT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {number} lockType
|
||||
* @returns {number}
|
||||
*/
|
||||
jLock(fileId, lockType) {
|
||||
const file = this.mapIdToFile.get(fileId);
|
||||
if (file.persistentFile.isRequestInProgress) {
|
||||
file.persistentFile.isLockBusy = true;
|
||||
return VFS.SQLITE_BUSY;
|
||||
}
|
||||
|
||||
file.persistentFile.isFileLocked = true;
|
||||
if (!file.persistentFile.handleLockReleaser) {
|
||||
// Start listening for notifications from other connections.
|
||||
// This is before we actually get access handles, but waiting to
|
||||
// listen until then allows a race condition where notifications
|
||||
// are missed.
|
||||
file.persistentFile.handleRequestChannel.onmessage = () => {
|
||||
this.log?.(`received notification for ${file.path}`);
|
||||
if (file.persistentFile.isFileLocked) {
|
||||
// We're still using the access handle, so mark it to be
|
||||
// released when we're done.
|
||||
file.persistentFile.isHandleRequested = true;
|
||||
} else {
|
||||
// Release the access handles immediately.
|
||||
this.#releaseAccessHandle(file);
|
||||
}
|
||||
file.persistentFile.handleRequestChannel.onmessage = null;
|
||||
};
|
||||
|
||||
this.#requestAccessHandle(file);
|
||||
this.log?.("returning SQLITE_BUSY");
|
||||
file.persistentFile.isLockBusy = true;
|
||||
return VFS.SQLITE_BUSY;
|
||||
}
|
||||
file.persistentFile.isLockBusy = false;
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {number} lockType
|
||||
* @returns {number}
|
||||
*/
|
||||
jUnlock(fileId, lockType) {
|
||||
const file = this.mapIdToFile.get(fileId);
|
||||
if (lockType === VFS.SQLITE_LOCK_NONE) {
|
||||
// Don't change any state if this unlock is because xLock returned
|
||||
// SQLITE_BUSY.
|
||||
if (!file.persistentFile.isLockBusy) {
|
||||
if (file.persistentFile.isHandleRequested) {
|
||||
// Another connection wants the access handle.
|
||||
this.#releaseAccessHandle(file);
|
||||
this.isHandleRequested = false;
|
||||
}
|
||||
file.persistentFile.isFileLocked = false;
|
||||
}
|
||||
}
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {number} op
|
||||
* @param {DataView} pArg
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
jFileControl(fileId, op, pArg) {
|
||||
try {
|
||||
const file = this.mapIdToFile.get(fileId);
|
||||
switch (op) {
|
||||
case VFS.SQLITE_FCNTL_PRAGMA:
|
||||
const key = extractString(pArg, 4);
|
||||
const value = extractString(pArg, 8);
|
||||
this.log?.("xFileControl", file.path, "PRAGMA", key, value);
|
||||
switch (key.toLowerCase()) {
|
||||
case "journal_mode":
|
||||
if (value && !["off", "memory", "delete", "wal"].includes(value.toLowerCase())) {
|
||||
throw new Error('journal_mode must be "off", "memory", "delete", or "wal"');
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
this.lastError = e;
|
||||
return VFS.SQLITE_IOERR;
|
||||
}
|
||||
return VFS.SQLITE_NOTFOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} zBuf
|
||||
* @returns
|
||||
*/
|
||||
jGetLastError(zBuf) {
|
||||
if (this.lastError) {
|
||||
console.error(this.lastError);
|
||||
const outputArray = zBuf.subarray(0, zBuf.byteLength - 1);
|
||||
const { written } = new TextEncoder().encodeInto(this.lastError.message, outputArray);
|
||||
zBuf[written] = 0;
|
||||
}
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FileSystemFileHandle} fileHandle
|
||||
* @returns {Promise<PersistentFile>}
|
||||
*/
|
||||
async #createPersistentFile(fileHandle) {
|
||||
const persistentFile = new PersistentFile(fileHandle);
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const relativePath = await root.resolve(fileHandle);
|
||||
const path = `/${relativePath.join("/")}`;
|
||||
persistentFile.handleRequestChannel = new BroadcastChannel(`ahp:${path}`);
|
||||
this.persistentFiles.set(path, persistentFile);
|
||||
|
||||
const f = await fileHandle.getFile();
|
||||
if (f.size) {
|
||||
this.accessiblePaths.add(path);
|
||||
}
|
||||
return persistentFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {File} file
|
||||
*/
|
||||
#requestAccessHandle(file) {
|
||||
console.assert(!file.persistentFile.handleLockReleaser);
|
||||
if (!file.persistentFile.isRequestInProgress) {
|
||||
file.persistentFile.isRequestInProgress = true;
|
||||
this._module.retryOps.push(
|
||||
(async () => {
|
||||
// Acquire the Web Lock.
|
||||
file.persistentFile.handleLockReleaser = await this.#acquireLock(file.persistentFile);
|
||||
|
||||
// Get access handles for the database and releated files in parallel.
|
||||
this.log?.(`creating access handles for ${file.path}`);
|
||||
await Promise.all(
|
||||
DB_RELATED_FILE_SUFFIXES.map(async (suffix) => {
|
||||
const persistentFile = this.persistentFiles.get(file.path + suffix);
|
||||
if (persistentFile) {
|
||||
persistentFile.accessHandle = await persistentFile.fileHandle.createSyncAccessHandle();
|
||||
}
|
||||
})
|
||||
);
|
||||
file.persistentFile.isRequestInProgress = false;
|
||||
})()
|
||||
);
|
||||
return this._module.retryOps.at(-1);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {File} file
|
||||
*/
|
||||
async #releaseAccessHandle(file) {
|
||||
DB_RELATED_FILE_SUFFIXES.forEach(async (suffix) => {
|
||||
const persistentFile = this.persistentFiles.get(file.path + suffix);
|
||||
if (persistentFile) {
|
||||
persistentFile.accessHandle?.close();
|
||||
persistentFile.accessHandle = null;
|
||||
}
|
||||
});
|
||||
this.log?.(`access handles closed for ${file.path}`);
|
||||
|
||||
file.persistentFile.handleLockReleaser?.();
|
||||
file.persistentFile.handleLockReleaser = null;
|
||||
this.log?.(`lock released for ${file.path}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PersistentFile} persistentFile
|
||||
* @returns {Promise<function>} lock releaser
|
||||
*/
|
||||
#acquireLock(persistentFile) {
|
||||
return new Promise((resolve) => {
|
||||
// Tell other connections we want the access handle.
|
||||
const lockName = persistentFile.handleRequestChannel.name;
|
||||
const notify = () => {
|
||||
this.log?.(`notifying for ${lockName}`);
|
||||
persistentFile.handleRequestChannel.postMessage(null);
|
||||
};
|
||||
const notifyId = setInterval(notify, LOCK_NOTIFY_INTERVAL);
|
||||
setTimeout(notify);
|
||||
|
||||
this.log?.(`lock requested: ${lockName}`);
|
||||
navigator.locks.request(lockName, (lock) => {
|
||||
// We have the lock. Stop asking other connections for it.
|
||||
this.log?.(`lock acquired: ${lockName}`, lock);
|
||||
clearInterval(notifyId);
|
||||
return new Promise(resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function extractString(dataView, offset) {
|
||||
const p = dataView.getUint32(offset, true);
|
||||
if (p) {
|
||||
const chars = new Uint8Array(dataView.buffer, p);
|
||||
return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
222
apps/web/core/local-db/worker/wa-sqlite/src/VFS.js
Normal file
222
apps/web/core/local-db/worker/wa-sqlite/src/VFS.js
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
|
||||
import * as VFS from './sqlite-constants.js';
|
||||
export * from './sqlite-constants.js';
|
||||
|
||||
const DEFAULT_SECTOR_SIZE = 512;
|
||||
|
||||
// Base class for a VFS.
|
||||
export class Base {
|
||||
name;
|
||||
mxPathname = 64;
|
||||
_module;
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {object} module
|
||||
*/
|
||||
constructor(name, module) {
|
||||
this.name = name;
|
||||
this._module = module;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {void|Promise<void>}
|
||||
*/
|
||||
close() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean|Promise<boolean>}
|
||||
*/
|
||||
isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload in subclasses to indicate which methods are asynchronous.
|
||||
* @param {string} methodName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAsyncMethod(methodName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} zName
|
||||
* @param {number} pFile
|
||||
* @param {number} flags
|
||||
* @param {number} pOutFlags
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xOpen(pVfs, zName, pFile, flags, pOutFlags) {
|
||||
return VFS.SQLITE_CANTOPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} zName
|
||||
* @param {number} syncDir
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xDelete(pVfs, zName, syncDir) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} zName
|
||||
* @param {number} flags
|
||||
* @param {number} pResOut
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xAccess(pVfs, zName, flags, pResOut) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} zName
|
||||
* @param {number} nOut
|
||||
* @param {number} zOut
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xFullPathname(pVfs, zName, nOut, zOut) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pVfs
|
||||
* @param {number} nBuf
|
||||
* @param {number} zBuf
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xGetLastError(pVfs, nBuf, zBuf) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xClose(pFile) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} pData
|
||||
* @param {number} iAmt
|
||||
* @param {number} iOffsetLo
|
||||
* @param {number} iOffsetHi
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xRead(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} pData
|
||||
* @param {number} iAmt
|
||||
* @param {number} iOffsetLo
|
||||
* @param {number} iOffsetHi
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xWrite(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} sizeLo
|
||||
* @param {number} sizeHi
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xTruncate(pFile, sizeLo, sizeHi) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} flags
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xSync(pFile, flags) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} pFile
|
||||
* @param {number} pSize
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xFileSize(pFile, pSize) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} lockType
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xLock(pFile, lockType) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} lockType
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xUnlock(pFile, lockType) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} pResOut
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xCheckReservedLock(pFile, pResOut) {
|
||||
return VFS.SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @param {number} op
|
||||
* @param {number} pArg
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xFileControl(pFile, op, pArg) {
|
||||
return VFS.SQLITE_NOTFOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xSectorSize(pFile) {
|
||||
return DEFAULT_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pFile
|
||||
* @returns {number|Promise<number>}
|
||||
*/
|
||||
xDeviceCharacteristics(pFile) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export const FILE_TYPE_MASK = [
|
||||
VFS.SQLITE_OPEN_MAIN_DB,
|
||||
VFS.SQLITE_OPEN_MAIN_JOURNAL,
|
||||
VFS.SQLITE_OPEN_TEMP_DB,
|
||||
VFS.SQLITE_OPEN_TEMP_JOURNAL,
|
||||
VFS.SQLITE_OPEN_TRANSIENT_DB,
|
||||
VFS.SQLITE_OPEN_SUBJOURNAL,
|
||||
VFS.SQLITE_OPEN_SUPER_JOURNAL,
|
||||
VFS.SQLITE_OPEN_WAL
|
||||
].reduce((mask, element) => mask | element);
|
||||
899
apps/web/core/local-db/worker/wa-sqlite/src/sqlite-api.js
Normal file
899
apps/web/core/local-db/worker/wa-sqlite/src/sqlite-api.js
Normal file
@@ -0,0 +1,899 @@
|
||||
// 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.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_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_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.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.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);
|
||||
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.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.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);
|
||||
};
|
||||
|
||||
function check(fname, result, db = null, allowed = [SQLite.SQLITE_OK]) {
|
||||
if (allowed.includes(result)) return result;
|
||||
const message = db ? Module.ccall("sqlite3_errmsg", "string", ["number"], [db]) : 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;
|
||||
}
|
||||
275
apps/web/core/local-db/worker/wa-sqlite/src/sqlite-constants.js
Normal file
275
apps/web/core/local-db/worker/wa-sqlite/src/sqlite-constants.js
Normal file
@@ -0,0 +1,275 @@
|
||||
// Primary result codes.
|
||||
// https://www.sqlite.org/rescode.html
|
||||
export const SQLITE_OK = 0;
|
||||
export const SQLITE_ERROR = 1;
|
||||
export const SQLITE_INTERNAL = 2;
|
||||
export const SQLITE_PERM = 3;
|
||||
export const SQLITE_ABORT = 4;
|
||||
export const SQLITE_BUSY = 5;
|
||||
export const SQLITE_LOCKED = 6;
|
||||
export const SQLITE_NOMEM = 7;
|
||||
export const SQLITE_READONLY = 8;
|
||||
export const SQLITE_INTERRUPT = 9;
|
||||
export const SQLITE_IOERR = 10;
|
||||
export const SQLITE_CORRUPT = 11;
|
||||
export const SQLITE_NOTFOUND = 12;
|
||||
export const SQLITE_FULL = 13;
|
||||
export const SQLITE_CANTOPEN = 14;
|
||||
export const SQLITE_PROTOCOL = 15;
|
||||
export const SQLITE_EMPTY = 16;
|
||||
export const SQLITE_SCHEMA = 17;
|
||||
export const SQLITE_TOOBIG = 18;
|
||||
export const SQLITE_CONSTRAINT = 19;
|
||||
export const SQLITE_MISMATCH = 20;
|
||||
export const SQLITE_MISUSE = 21;
|
||||
export const SQLITE_NOLFS = 22;
|
||||
export const SQLITE_AUTH = 23;
|
||||
export const SQLITE_FORMAT = 24;
|
||||
export const SQLITE_RANGE = 25;
|
||||
export const SQLITE_NOTADB = 26;
|
||||
export const SQLITE_NOTICE = 27;
|
||||
export const SQLITE_WARNING = 28;
|
||||
export const SQLITE_ROW = 100;
|
||||
export const SQLITE_DONE = 101;
|
||||
|
||||
// Extended error codes.
|
||||
export const SQLITE_IOERR_ACCESS = 3338;
|
||||
export const SQLITE_IOERR_CHECKRESERVEDLOCK = 3594;
|
||||
export const SQLITE_IOERR_CLOSE = 4106;
|
||||
export const SQLITE_IOERR_DATA = 8202;
|
||||
export const SQLITE_IOERR_DELETE = 2570;
|
||||
export const SQLITE_IOERR_DELETE_NOENT = 5898;
|
||||
export const SQLITE_IOERR_DIR_FSYNC = 1290;
|
||||
export const SQLITE_IOERR_FSTAT = 1802;
|
||||
export const SQLITE_IOERR_FSYNC = 1034;
|
||||
export const SQLITE_IOERR_GETTEMPPATH = 6410;
|
||||
export const SQLITE_IOERR_LOCK = 3850;
|
||||
export const SQLITE_IOERR_NOMEM = 3082;
|
||||
export const SQLITE_IOERR_READ = 266;
|
||||
export const SQLITE_IOERR_RDLOCK = 2314;
|
||||
export const SQLITE_IOERR_SEEK = 5642;
|
||||
export const SQLITE_IOERR_SHORT_READ = 522;
|
||||
export const SQLITE_IOERR_TRUNCATE = 1546;
|
||||
export const SQLITE_IOERR_UNLOCK = 2058;
|
||||
export const SQLITE_IOERR_VNODE = 6922;
|
||||
export const SQLITE_IOERR_WRITE = 778;
|
||||
export const SQLITE_IOERR_BEGIN_ATOMIC = 7434;
|
||||
export const SQLITE_IOERR_COMMIT_ATOMIC = 7690;
|
||||
export const SQLITE_IOERR_ROLLBACK_ATOMIC = 7946;
|
||||
|
||||
// Other extended result codes.
|
||||
export const SQLITE_CONSTRAINT_CHECK = 275;
|
||||
export const SQLITE_CONSTRAINT_COMMITHOOK = 531;
|
||||
export const SQLITE_CONSTRAINT_FOREIGNKEY = 787;
|
||||
export const SQLITE_CONSTRAINT_FUNCTION = 1043;
|
||||
export const SQLITE_CONSTRAINT_NOTNULL = 1299;
|
||||
export const SQLITE_CONSTRAINT_PINNED = 2835;
|
||||
export const SQLITE_CONSTRAINT_PRIMARYKEY = 1555;
|
||||
export const SQLITE_CONSTRAINT_ROWID = 2579;
|
||||
export const SQLITE_CONSTRAINT_TRIGGER = 1811;
|
||||
export const SQLITE_CONSTRAINT_UNIQUE = 2067;
|
||||
export const SQLITE_CONSTRAINT_VTAB = 2323;
|
||||
|
||||
// Open flags.
|
||||
// https://www.sqlite.org/c3ref/c_open_autoproxy.html
|
||||
export const SQLITE_OPEN_READONLY = 0x00000001;
|
||||
export const SQLITE_OPEN_READWRITE = 0x00000002;
|
||||
export const SQLITE_OPEN_CREATE = 0x00000004;
|
||||
export const SQLITE_OPEN_DELETEONCLOSE = 0x00000008;
|
||||
export const SQLITE_OPEN_EXCLUSIVE = 0x00000010;
|
||||
export const SQLITE_OPEN_AUTOPROXY = 0x00000020;
|
||||
export const SQLITE_OPEN_URI = 0x00000040;
|
||||
export const SQLITE_OPEN_MEMORY = 0x00000080;
|
||||
export const SQLITE_OPEN_MAIN_DB = 0x00000100;
|
||||
export const SQLITE_OPEN_TEMP_DB = 0x00000200;
|
||||
export const SQLITE_OPEN_TRANSIENT_DB = 0x00000400;
|
||||
export const SQLITE_OPEN_MAIN_JOURNAL = 0x00000800;
|
||||
export const SQLITE_OPEN_TEMP_JOURNAL = 0x00001000;
|
||||
export const SQLITE_OPEN_SUBJOURNAL = 0x00002000;
|
||||
export const SQLITE_OPEN_SUPER_JOURNAL = 0x00004000;
|
||||
export const SQLITE_OPEN_NOMUTEX = 0x00008000;
|
||||
export const SQLITE_OPEN_FULLMUTEX = 0x00010000;
|
||||
export const SQLITE_OPEN_SHAREDCACHE = 0x00020000;
|
||||
export const SQLITE_OPEN_PRIVATECACHE = 0x00040000;
|
||||
export const SQLITE_OPEN_WAL = 0x00080000;
|
||||
export const SQLITE_OPEN_NOFOLLOW = 0x01000000;
|
||||
|
||||
// Locking levels.
|
||||
// https://www.sqlite.org/c3ref/c_lock_exclusive.html
|
||||
export const SQLITE_LOCK_NONE = 0;
|
||||
export const SQLITE_LOCK_SHARED = 1;
|
||||
export const SQLITE_LOCK_RESERVED = 2;
|
||||
export const SQLITE_LOCK_PENDING = 3;
|
||||
export const SQLITE_LOCK_EXCLUSIVE = 4;
|
||||
|
||||
// Device characteristics.
|
||||
// https://www.sqlite.org/c3ref/c_iocap_atomic.html
|
||||
export const SQLITE_IOCAP_ATOMIC = 0x00000001;
|
||||
export const SQLITE_IOCAP_ATOMIC512 = 0x00000002;
|
||||
export const SQLITE_IOCAP_ATOMIC1K = 0x00000004;
|
||||
export const SQLITE_IOCAP_ATOMIC2K = 0x00000008;
|
||||
export const SQLITE_IOCAP_ATOMIC4K = 0x00000010;
|
||||
export const SQLITE_IOCAP_ATOMIC8K = 0x00000020;
|
||||
export const SQLITE_IOCAP_ATOMIC16K = 0x00000040;
|
||||
export const SQLITE_IOCAP_ATOMIC32K = 0x00000080;
|
||||
export const SQLITE_IOCAP_ATOMIC64K = 0x00000100;
|
||||
export const SQLITE_IOCAP_SAFE_APPEND = 0x00000200;
|
||||
export const SQLITE_IOCAP_SEQUENTIAL = 0x00000400;
|
||||
export const SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 0x00000800;
|
||||
export const SQLITE_IOCAP_POWERSAFE_OVERWRITE = 0x00001000;
|
||||
export const SQLITE_IOCAP_IMMUTABLE = 0x00002000;
|
||||
export const SQLITE_IOCAP_BATCH_ATOMIC = 0x00004000;
|
||||
|
||||
// xAccess flags.
|
||||
// https://www.sqlite.org/c3ref/c_access_exists.html
|
||||
export const SQLITE_ACCESS_EXISTS = 0;
|
||||
export const SQLITE_ACCESS_READWRITE = 1;
|
||||
export const SQLITE_ACCESS_READ = 2;
|
||||
|
||||
// File control opcodes
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite
|
||||
export const SQLITE_FCNTL_LOCKSTATE = 1;
|
||||
export const SQLITE_FCNTL_GET_LOCKPROXYFILE = 2;
|
||||
export const SQLITE_FCNTL_SET_LOCKPROXYFILE = 3;
|
||||
export const SQLITE_FCNTL_LAST_ERRNO = 4;
|
||||
export const SQLITE_FCNTL_SIZE_HINT = 5;
|
||||
export const SQLITE_FCNTL_CHUNK_SIZE = 6;
|
||||
export const SQLITE_FCNTL_FILE_POINTER = 7;
|
||||
export const SQLITE_FCNTL_SYNC_OMITTED = 8;
|
||||
export const SQLITE_FCNTL_WIN32_AV_RETRY = 9;
|
||||
export const SQLITE_FCNTL_PERSIST_WAL = 10;
|
||||
export const SQLITE_FCNTL_OVERWRITE = 11;
|
||||
export const SQLITE_FCNTL_VFSNAME = 12;
|
||||
export const SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13;
|
||||
export const SQLITE_FCNTL_PRAGMA = 14;
|
||||
export const SQLITE_FCNTL_BUSYHANDLER = 15;
|
||||
export const SQLITE_FCNTL_TEMPFILENAME = 16;
|
||||
export const SQLITE_FCNTL_MMAP_SIZE = 18;
|
||||
export const SQLITE_FCNTL_TRACE = 19;
|
||||
export const SQLITE_FCNTL_HAS_MOVED = 20;
|
||||
export const SQLITE_FCNTL_SYNC = 21;
|
||||
export const SQLITE_FCNTL_COMMIT_PHASETWO = 22;
|
||||
export const SQLITE_FCNTL_WIN32_SET_HANDLE = 23;
|
||||
export const SQLITE_FCNTL_WAL_BLOCK = 24;
|
||||
export const SQLITE_FCNTL_ZIPVFS = 25;
|
||||
export const SQLITE_FCNTL_RBU = 26;
|
||||
export const SQLITE_FCNTL_VFS_POINTER = 27;
|
||||
export const SQLITE_FCNTL_JOURNAL_POINTER = 28;
|
||||
export const SQLITE_FCNTL_WIN32_GET_HANDLE = 29;
|
||||
export const SQLITE_FCNTL_PDB = 30;
|
||||
export const SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31;
|
||||
export const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32;
|
||||
export const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33;
|
||||
export const SQLITE_FCNTL_LOCK_TIMEOUT = 34;
|
||||
export const SQLITE_FCNTL_DATA_VERSION = 35;
|
||||
export const SQLITE_FCNTL_SIZE_LIMIT = 36;
|
||||
export const SQLITE_FCNTL_CKPT_DONE = 37;
|
||||
export const SQLITE_FCNTL_RESERVE_BYTES = 38;
|
||||
export const SQLITE_FCNTL_CKPT_START = 39;
|
||||
|
||||
// Fundamental datatypes.
|
||||
// https://www.sqlite.org/c3ref/c_blob.html
|
||||
export const SQLITE_INTEGER = 1;
|
||||
export const SQLITE_FLOAT = 2;
|
||||
export const SQLITE_TEXT = 3;
|
||||
export const SQLITE_BLOB = 4;
|
||||
export const SQLITE_NULL = 5;
|
||||
|
||||
// Special destructor behavior.
|
||||
// https://www.sqlite.org/c3ref/c_static.html
|
||||
export const SQLITE_STATIC = 0;
|
||||
export const SQLITE_TRANSIENT = -1;
|
||||
|
||||
// Text encodings.
|
||||
// https://sqlite.org/c3ref/c_any.html
|
||||
export const SQLITE_UTF8 = 1; /* IMP: R-37514-35566 */
|
||||
export const SQLITE_UTF16LE = 2; /* IMP: R-03371-37637 */
|
||||
export const SQLITE_UTF16BE = 3; /* IMP: R-51971-34154 */
|
||||
export const SQLITE_UTF16 = 4; /* Use native byte order */
|
||||
|
||||
// Module constraint ops.
|
||||
export const SQLITE_INDEX_CONSTRAINT_EQ = 2;
|
||||
export const SQLITE_INDEX_CONSTRAINT_GT = 4;
|
||||
export const SQLITE_INDEX_CONSTRAINT_LE = 8;
|
||||
export const SQLITE_INDEX_CONSTRAINT_LT = 16;
|
||||
export const SQLITE_INDEX_CONSTRAINT_GE = 32;
|
||||
export const SQLITE_INDEX_CONSTRAINT_MATCH = 64;
|
||||
export const SQLITE_INDEX_CONSTRAINT_LIKE = 65;
|
||||
export const SQLITE_INDEX_CONSTRAINT_GLOB = 66;
|
||||
export const SQLITE_INDEX_CONSTRAINT_REGEXP = 67;
|
||||
export const SQLITE_INDEX_CONSTRAINT_NE = 68;
|
||||
export const SQLITE_INDEX_CONSTRAINT_ISNOT = 69;
|
||||
export const SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70;
|
||||
export const SQLITE_INDEX_CONSTRAINT_ISNULL = 71;
|
||||
export const SQLITE_INDEX_CONSTRAINT_IS = 72;
|
||||
export const SQLITE_INDEX_CONSTRAINT_FUNCTION = 150;
|
||||
export const SQLITE_INDEX_SCAN_UNIQUE = 1; /* Scan visits at most = 1 row */
|
||||
|
||||
// Function flags
|
||||
export const SQLITE_DETERMINISTIC = 0x000000800;
|
||||
export const SQLITE_DIRECTONLY = 0x000080000;
|
||||
export const SQLITE_SUBTYPE = 0x000100000;
|
||||
export const SQLITE_INNOCUOUS = 0x000200000;
|
||||
|
||||
// Sync flags
|
||||
export const SQLITE_SYNC_NORMAL = 0x00002;
|
||||
export const SQLITE_SYNC_FULL = 0x00003;
|
||||
export const SQLITE_SYNC_DATAONLY = 0x00010;
|
||||
|
||||
// Authorizer action codes
|
||||
export const SQLITE_CREATE_INDEX = 1;
|
||||
export const SQLITE_CREATE_TABLE = 2;
|
||||
export const SQLITE_CREATE_TEMP_INDEX = 3;
|
||||
export const SQLITE_CREATE_TEMP_TABLE = 4;
|
||||
export const SQLITE_CREATE_TEMP_TRIGGER = 5;
|
||||
export const SQLITE_CREATE_TEMP_VIEW = 6;
|
||||
export const SQLITE_CREATE_TRIGGER = 7;
|
||||
export const SQLITE_CREATE_VIEW = 8;
|
||||
export const SQLITE_DELETE = 9;
|
||||
export const SQLITE_DROP_INDEX = 10;
|
||||
export const SQLITE_DROP_TABLE = 11;
|
||||
export const SQLITE_DROP_TEMP_INDEX = 12;
|
||||
export const SQLITE_DROP_TEMP_TABLE = 13;
|
||||
export const SQLITE_DROP_TEMP_TRIGGER = 14;
|
||||
export const SQLITE_DROP_TEMP_VIEW = 15;
|
||||
export const SQLITE_DROP_TRIGGER = 16;
|
||||
export const SQLITE_DROP_VIEW = 17;
|
||||
export const SQLITE_INSERT = 18;
|
||||
export const SQLITE_PRAGMA = 19;
|
||||
export const SQLITE_READ = 20;
|
||||
export const SQLITE_SELECT = 21;
|
||||
export const SQLITE_TRANSACTION = 22;
|
||||
export const SQLITE_UPDATE = 23;
|
||||
export const SQLITE_ATTACH = 24;
|
||||
export const SQLITE_DETACH = 25;
|
||||
export const SQLITE_ALTER_TABLE = 26;
|
||||
export const SQLITE_REINDEX = 27;
|
||||
export const SQLITE_ANALYZE = 28;
|
||||
export const SQLITE_CREATE_VTABLE = 29;
|
||||
export const SQLITE_DROP_VTABLE = 30;
|
||||
export const SQLITE_FUNCTION = 31;
|
||||
export const SQLITE_SAVEPOINT = 32;
|
||||
export const SQLITE_COPY = 0;
|
||||
export const SQLITE_RECURSIVE = 33;
|
||||
|
||||
// Authorizer return codes
|
||||
export const SQLITE_DENY = 1;
|
||||
export const SQLITE_IGNORE = 2;
|
||||
|
||||
// Limit categories
|
||||
export const SQLITE_LIMIT_LENGTH = 0;
|
||||
export const SQLITE_LIMIT_SQL_LENGTH = 1;
|
||||
export const SQLITE_LIMIT_COLUMN = 2;
|
||||
export const SQLITE_LIMIT_EXPR_DEPTH = 3;
|
||||
export const SQLITE_LIMIT_COMPOUND_SELECT = 4;
|
||||
export const SQLITE_LIMIT_VDBE_OP = 5;
|
||||
export const SQLITE_LIMIT_FUNCTION_ARG = 6;
|
||||
export const SQLITE_LIMIT_ATTACHED = 7;
|
||||
export const SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8;
|
||||
export const SQLITE_LIMIT_VARIABLE_NUMBER = 9;
|
||||
export const SQLITE_LIMIT_TRIGGER_DEPTH = 10;
|
||||
export const SQLITE_LIMIT_WORKER_THREADS = 11;
|
||||
|
||||
export const SQLITE_PREPARE_PERSISTENT = 0x01;
|
||||
export const SQLITE_PREPARE_NORMALIZED = 0x02;
|
||||
export const SQLITE_PREPARE_NO_VTAB = 0x04;
|
||||
62
apps/web/core/local-db/worker/wa-sqlite/src/types/globals.d.ts
vendored
Normal file
62
apps/web/core/local-db/worker/wa-sqlite/src/types/globals.d.ts
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable no-var */
|
||||
|
||||
declare namespace Asyncify {
|
||||
function handleAsync(f: () => Promise<any>);
|
||||
}
|
||||
|
||||
declare function UTF8ToString(ptr: number): string;
|
||||
declare function lengthBytesUTF8(s: string): number;
|
||||
declare function stringToUTF8(s: string, p: number, n: number);
|
||||
declare function ccall(name: string, returns: string, args: Array<any>, options?: object): any;
|
||||
declare function getValue(ptr: number, type: string): number;
|
||||
declare function setValue(ptr: number, value: number, type: string): number;
|
||||
declare function mergeInto(library: object, methods: object): void;
|
||||
|
||||
declare var HEAPU8: Uint8Array;
|
||||
declare var HEAPU32: Uint32Array;
|
||||
declare var LibraryManager;
|
||||
declare var Module;
|
||||
declare var _vfsAccess;
|
||||
declare var _vfsCheckReservedLock;
|
||||
declare var _vfsClose;
|
||||
declare var _vfsDelete;
|
||||
declare var _vfsDeviceCharacteristics;
|
||||
declare var _vfsFileControl;
|
||||
declare var _vfsFileSize;
|
||||
declare var _vfsLock;
|
||||
declare var _vfsOpen;
|
||||
declare var _vfsRead;
|
||||
declare var _vfsSectorSize;
|
||||
declare var _vfsSync;
|
||||
declare var _vfsTruncate;
|
||||
declare var _vfsUnlock;
|
||||
declare var _vfsWrite;
|
||||
|
||||
declare var _jsFunc;
|
||||
declare var _jsStep;
|
||||
declare var _jsFinal;
|
||||
|
||||
declare var _modStruct;
|
||||
declare var _modCreate;
|
||||
declare var _modConnect;
|
||||
declare var _modBestIndex;
|
||||
declare var _modDisconnect;
|
||||
declare var _modDestroy;
|
||||
declare var _modOpen;
|
||||
declare var _modClose;
|
||||
declare var _modFilter;
|
||||
declare var _modNext;
|
||||
declare var _modEof;
|
||||
declare var _modColumn;
|
||||
declare var _modRowid;
|
||||
declare var _modUpdate;
|
||||
declare var _modBegin;
|
||||
declare var _modSync;
|
||||
declare var _modCommit;
|
||||
declare var _modRollback;
|
||||
declare var _modFindFunction;
|
||||
declare var _modRename;
|
||||
|
||||
declare var _jsAuth;
|
||||
|
||||
declare var _jsProgress;
|
||||
1295
apps/web/core/local-db/worker/wa-sqlite/src/types/index.d.ts
vendored
Normal file
1295
apps/web/core/local-db/worker/wa-sqlite/src/types/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16
apps/web/core/local-db/worker/wa-sqlite/src/wa-sqlite.mjs
Normal file
16
apps/web/core/local-db/worker/wa-sqlite/src/wa-sqlite.mjs
Normal file
File diff suppressed because one or more lines are too long
BIN
apps/web/core/local-db/worker/wa-sqlite/src/wa-sqlite.wasm
Executable file
BIN
apps/web/core/local-db/worker/wa-sqlite/src/wa-sqlite.wasm
Executable file
Binary file not shown.
Reference in New Issue
Block a user