Fixes #97: SQLite is now async, optimized, tests

This commit is contained in:
Zef Hemel
2022-10-21 10:00:43 +02:00
parent b9da9b7965
commit c1a78e0105
65 changed files with 329 additions and 142 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{
"worker.js": "data:application/javascript;base64,KCgpID0+IHsgdmFyIG1vZD0oKCk9PntmdW5jdGlvbiBjKHIpe3IoKS5jYXRjaChlPT57Y29uc29sZS5lcnJvcigiQ2F1Z2h0IGVycm9yIixlLm1lc3NhZ2UpfSl9dmFyIGE9Y2xhc3N7Y29uc3RydWN0b3IoZSxuPSEwKXt0aGlzLnByaW50PW4sdGhpcy5jYWxsYmFjaz1lfWxvZyguLi5lKXt0aGlzLnB1c2goImxvZyIsZSl9d2FybiguLi5lKXt0aGlzLnB1c2goIndhcm4iLGUpfWVycm9yKC4uLmUpe3RoaXMucHVzaCgiZXJyb3IiLGUpfWluZm8oLi4uZSl7dGhpcy5wdXNoKCJpbmZvIixlKX1wdXNoKGUsbil7dGhpcy5jYWxsYmFjayhlLHRoaXMubG9nTWVzc2FnZShuKSksdGhpcy5wcmludCYmY29uc29sZVtlXSguLi5uKX1sb2dNZXNzYWdlKGUpe2xldCBuPVtdO2ZvcihsZXQgdCBvZiBlKXN3aXRjaCh0eXBlb2YgdCl7Y2FzZSJzdHJpbmciOmNhc2UibnVtYmVyIjpuLnB1c2goIiIrdCk7YnJlYWs7Y2FzZSJ1bmRlZmluZWQiOm4ucHVzaCgidW5kZWZpbmVkIik7YnJlYWs7ZGVmYXVsdDp0cnl7bGV0IG89SlNPTi5zdHJpbmdpZnkodCxudWxsLDIpO28ubGVuZ3RoPjUwMCYmKG89by5zdWJzdHJpbmcoMCw1MDApKyIuLi4iKSxuLnB1c2gobyl9Y2F0Y2h7bi5wdXNoKCJbY2lyY3VsYXIgb2JqZWN0XSIpfX1yZXR1cm4gbi5qb2luKCIgIil9fTt0eXBlb2YgRGVubz4idSImJihzZWxmLkRlbm89e2FyZ3M6W10sYnVpbGQ6e2FyY2g6Ing4Nl82NCJ9LGVudjp7Z2V0KCl7fX19KTt2YXIgZD1uZXcgTWFwLGk9bmV3IE1hcDtmdW5jdGlvbiBzKHIpe3R5cGVvZiB3aW5kb3c8InUiJiZ3aW5kb3cucGFyZW50IT09d2luZG93P3dpbmRvdy5wYXJlbnQucG9zdE1lc3NhZ2UociwiKiIpOnNlbGYucG9zdE1lc3NhZ2Uocil9dmFyIGw9MDtzZWxmLnN5c2NhbGw9YXN5bmMociwuLi5lKT0+YXdhaXQgbmV3IFByb21pc2UoKG4sdCk9PntsKyssaS5zZXQobCx7cmVzb2x2ZTpuLHJlamVjdDp0fSkscyh7dHlwZToic3lzY2FsbCIsaWQ6bCxuYW1lOnIsYXJnczplfSl9KTt2YXIgdT1uZXcgTWFwO3NlbGYucmVxdWlyZT1yPT57bGV0IGU9dS5nZXQocik7aWYoIWUpdGhyb3cgbmV3IEVycm9yKGBEeW5hbWljYWxseSBpbXBvcnRpbmcgbm9uLXByZWxvYWRlZCBsaWJyYXJ5ICR7cn1gKTtyZXR1cm4gZX07c2VsZi5jb25zb2xlPW5ldyBhKChyLGUpPT57cyh7dHlwZToibG9nIixsZXZlbDpyLG1lc3NhZ2U6ZX0pfSwhMSk7ZnVuY3Rpb24gZyhyKXtyZXR1cm5gcmV0dXJuICgke3J9KVsiZGVmYXVsdCJdYH1zZWxmLmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLHI9PntjKGFzeW5jKCk9PntsZXQgZT1yLmRhdGE7c3dpdGNoKGUudHlwZSl7Y2FzZSJsb2FkIjp7bGV0IG49bmV3IEZ1bmN0aW9uKGcoZS5jb2RlKSk7ZC5zZXQoZS5uYW1lLG4oKSkscyh7dHlwZToiaW5pdGVkIixuYW1lOmUubmFtZX0pfWJyZWFrO2Nhc2UibG9hZC1kZXBlbmRlbmN5Ijp7bGV0IHQ9bmV3IEZ1bmN0aW9uKGByZXR1cm4gJHtlLmNvZGV9YCkoKTt1LnNldChlLm5hbWUsdCkscyh7dHlwZToiZGVwZW5kZW5jeS1pbml0ZWQiLG5hbWU6ZS5uYW1lfSl9YnJlYWs7Y2FzZSJpbnZva2UiOntsZXQgbj1kLmdldChlLm5hbWUpO2lmKCFuKXRocm93IG5ldyBFcnJvcihgRnVuY3Rpb24gbm90IGxvYWRlZDogJHtlLm5hbWV9YCk7dHJ5e2xldCB0PWF3YWl0IFByb21pc2UucmVzb2x2ZShuKC4uLmUuYXJnc3x8W10pKTtzKHt0eXBlOiJyZXN1bHQiLGlkOmUuaWQscmVzdWx0OnR9KX1jYXRjaCh0KXtzKHt0eXBlOiJyZXN1bHQiLGlkOmUuaWQsZXJyb3I6dC5tZXNzYWdlLHN0YWNrOnQuc3RhY2t9KX19YnJlYWs7Y2FzZSJzeXNjYWxsLXJlc3BvbnNlIjp7bGV0IG49ZS5pZCx0PWkuZ2V0KG4pO2lmKCF0KXRocm93IGNvbnNvbGUubG9nKCJDdXJyZW50IG91dHN0YW5kaW5nIHJlcXVlc3RzIixpLCJsb29raW5nIHVwIixuKSxFcnJvcigiSW52YWxpZCByZXF1ZXN0IGlkIik7aS5kZWxldGUobiksZS5lcnJvcj90LnJlamVjdChuZXcgRXJyb3IoZS5lcnJvcikpOnQucmVzb2x2ZShlLnJlc3VsdCl9YnJlYWt9fSl9KTt9KSgpOwogcmV0dXJuIG1vZDt9KSgp"
"worker.js": "data:application/javascript;base64,KCgpID0+IHsgdmFyIG1vZD0oKCk9PntmdW5jdGlvbiBjKHMpe3MoKS5jYXRjaChlPT57Y29uc29sZS5lcnJvcigiQ2F1Z2h0IGVycm9yIixlLm1lc3NhZ2UpfSl9dmFyIGE9Y2xhc3N7Y29uc3RydWN0b3IoZSxuPSEwKXt0aGlzLnByaW50PW4sdGhpcy5jYWxsYmFjaz1lfWxvZyguLi5lKXt0aGlzLnB1c2goImxvZyIsZSl9d2FybiguLi5lKXt0aGlzLnB1c2goIndhcm4iLGUpfWVycm9yKC4uLmUpe3RoaXMucHVzaCgiZXJyb3IiLGUpfWluZm8oLi4uZSl7dGhpcy5wdXNoKCJpbmZvIixlKX1wdXNoKGUsbil7dGhpcy5jYWxsYmFjayhlLHRoaXMubG9nTWVzc2FnZShuKSksdGhpcy5wcmludCYmY29uc29sZVtlXSguLi5uKX1sb2dNZXNzYWdlKGUpe2xldCBuPVtdO2ZvcihsZXQgciBvZiBlKXN3aXRjaCh0eXBlb2Ygcil7Y2FzZSJzdHJpbmciOmNhc2UibnVtYmVyIjpuLnB1c2goIiIrcik7YnJlYWs7Y2FzZSJ1bmRlZmluZWQiOm4ucHVzaCgidW5kZWZpbmVkIik7YnJlYWs7ZGVmYXVsdDp0cnl7bGV0IG89SlNPTi5zdHJpbmdpZnkocixudWxsLDIpO28ubGVuZ3RoPjUwMCYmKG89by5zdWJzdHJpbmcoMCw1MDApKyIuLi4iKSxuLnB1c2gobyl9Y2F0Y2h7bi5wdXNoKCJbY2lyY3VsYXIgb2JqZWN0XSIpfX1yZXR1cm4gbi5qb2luKCIgIil9fTt0eXBlb2YgRGVubz4idSImJihzZWxmLkRlbm89e2FyZ3M6W10sYnVpbGQ6e2FyY2g6Ing4Nl82NCJ9LGVudjp7Z2V0KCl7fX19KTt2YXIgZD1uZXcgTWFwLGk9bmV3IE1hcDtmdW5jdGlvbiB0KHMpe3R5cGVvZiB3aW5kb3c8InUiJiZ3aW5kb3cucGFyZW50IT09d2luZG93P3dpbmRvdy5wYXJlbnQucG9zdE1lc3NhZ2UocywiKiIpOnNlbGYucG9zdE1lc3NhZ2Uocyl9dmFyIGw9MDtzZWxmLnN5c2NhbGw9YXN5bmMocywuLi5lKT0+YXdhaXQgbmV3IFByb21pc2UoKG4scik9PntsKyssaS5zZXQobCx7cmVzb2x2ZTpuLHJlamVjdDpyfSksdCh7dHlwZToic3lzY2FsbCIsaWQ6bCxuYW1lOnMsYXJnczplfSl9KTt2YXIgdT1uZXcgTWFwO3NlbGYucmVxdWlyZT1zPT57bGV0IGU9dS5nZXQocyk7aWYoIWUpdGhyb3cgbmV3IEVycm9yKGBEeW5hbWljYWxseSBpbXBvcnRpbmcgbm9uLXByZWxvYWRlZCBsaWJyYXJ5ICR7c31gKTtyZXR1cm4gZX07c2VsZi5jb25zb2xlPW5ldyBhKChzLGUpPT57dCh7dHlwZToibG9nIixsZXZlbDpzLG1lc3NhZ2U6ZX0pfSwhMSk7ZnVuY3Rpb24gZyhzKXtyZXR1cm5gcmV0dXJuICgke3N9KVsiZGVmYXVsdCJdYH1zZWxmLmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLHM9PntjKGFzeW5jKCk9PntsZXQgZT1zLmRhdGE7c3dpdGNoKGUudHlwZSl7Y2FzZSJsb2FkIjp7bGV0IG49bmV3IEZ1bmN0aW9uKGcoZS5jb2RlKSk7ZC5zZXQoZS5uYW1lLG4oKSksdCh7dHlwZToiaW5pdGVkIixuYW1lOmUubmFtZX0pfWJyZWFrO2Nhc2UibG9hZC1kZXBlbmRlbmN5Ijp7bGV0IHI9bmV3IEZ1bmN0aW9uKGByZXR1cm4gJHtlLmNvZGV9YCkoKTt1LnNldChlLm5hbWUsciksdCh7dHlwZToiZGVwZW5kZW5jeS1pbml0ZWQiLG5hbWU6ZS5uYW1lfSl9YnJlYWs7Y2FzZSJpbnZva2UiOntsZXQgbj1kLmdldChlLm5hbWUpO2lmKCFuKXRocm93IG5ldyBFcnJvcihgRnVuY3Rpb24gbm90IGxvYWRlZDogJHtlLm5hbWV9YCk7dHJ5e2xldCByPWF3YWl0IFByb21pc2UucmVzb2x2ZShuKC4uLmUuYXJnc3x8W10pKTt0KHt0eXBlOiJyZXN1bHQiLGlkOmUuaWQscmVzdWx0OnJ9KX1jYXRjaChyKXt0KHt0eXBlOiJyZXN1bHQiLGlkOmUuaWQsZXJyb3I6ci5tZXNzYWdlLHN0YWNrOnIuc3RhY2t9KX19YnJlYWs7Y2FzZSJzeXNjYWxsLXJlc3BvbnNlIjp7bGV0IG49ZS5pZCxyPWkuZ2V0KG4pO2lmKCFyKXRocm93IGNvbnNvbGUubG9nKCJDdXJyZW50IG91dHN0YW5kaW5nIHJlcXVlc3RzIixpLCJsb29raW5nIHVwIixuKSxFcnJvcigiSW52YWxpZCByZXF1ZXN0IGlkIik7aS5kZWxldGUobiksZS5lcnJvcj9yLnJlamVjdChuZXcgRXJyb3IoZS5lcnJvcikpOnIucmVzb2x2ZShlLnJlc3VsdCl9YnJlYWt9fSl9KTt9KSgpOwogcmV0dXJuIG1vZDt9KSgp"
}
+19
View File
@@ -1,5 +1,7 @@
import { AssetBundle } from "./asset_bundle/bundle.ts";
import { compile } from "./compile.ts";
console.log("Generating sandbox worker...");
const bundlePath =
new URL("./environments/worker_bundle.json", import.meta.url).pathname;
const workerPath =
@@ -15,4 +17,21 @@ Deno.writeTextFile(
);
console.log(`Wrote updated bundle to ${bundlePath}`);
console.log("Now generating SQLite worker...");
const sqliteBundlePath =
new URL("./sqlite/worker_bundle.json", import.meta.url).pathname;
const sqliteWorkerPath =
new URL("./sqlite/worker.ts", import.meta.url).pathname;
const sqliteWorkerCode = await compile(sqliteWorkerPath);
const sqliteAssetBundle = new AssetBundle();
sqliteAssetBundle.writeTextFileSync("worker.js", sqliteWorkerCode);
Deno.writeTextFile(
sqliteBundlePath,
JSON.stringify(sqliteAssetBundle.toJSON(), null, 2),
);
console.log(`Wrote updated bundle to ${sqliteBundlePath}`);
Deno.exit(0);
+16
View File
@@ -0,0 +1,16 @@
import { AsyncSQLite } from "./async_sqlite.ts";
import { assertEquals } from "../../test_deps.ts";
Deno.test("Async SQLite test", async () => {
const db = new AsyncSQLite(":memory:");
await db.init();
await db.execute("CREATE TABLE test (name TEXT)");
await db.execute("INSERT INTO test (name) VALUES (?)", "test");
await db.execute("INSERT INTO test (name) VALUES (?)", "test 2");
assertEquals(await db.query("SELECT * FROM test ORDER BY name"), [{
name: "test",
}, {
name: "test 2",
}]);
db.stop();
});
+70
View File
@@ -0,0 +1,70 @@
import { AssetBundle } from "../asset_bundle/bundle.ts";
import workerBundleJson from "./worker_bundle.json" assert { type: "json" };
const workerBundle = new AssetBundle(workerBundleJson);
export class AsyncSQLite {
worker: Worker;
requestId = 0;
outstandingRequests = new Map<
number,
{ resolve: (val: any) => void; reject: (error: Error) => void }
>();
constructor(readonly dbPath: string) {
const workerHref = URL.createObjectURL(
new Blob([
workerBundle.readFileSync("worker.js"),
], {
type: "application/javascript",
}),
);
this.worker = new Worker(
workerHref,
{
type: "module",
},
);
this.worker.addEventListener("message", (event: MessageEvent) => {
const { data } = event;
// console.log("Got data back", data);
const { id, result, error } = data;
const req = this.outstandingRequests.get(id);
if (!req) {
console.error("Invalid request id", id);
return;
}
if (result !== undefined) {
req.resolve(result);
} else if (error) {
req.reject(new Error(error));
}
this.outstandingRequests.delete(id);
});
}
private request(message: Record<string, any>): Promise<any> {
this.requestId++;
return new Promise((resolve, reject) => {
this.outstandingRequests.set(this.requestId, { resolve, reject });
// console.log("Sending request", message);
this.worker.postMessage({ ...message, id: this.requestId });
});
}
init(): Promise<void> {
return this.request({ type: "init", dbPath: this.dbPath });
}
execute(query: string, ...params: any[]): Promise<number> {
return this.request({ type: "execute", query, params });
}
query(query: string, ...params: any[]): Promise<any[]> {
return this.request({ type: "query", query, params });
}
stop() {
this.worker.terminate();
}
}
@@ -11,6 +11,3 @@ export type {
Row,
RowObject,
} from "./src/query.ts";
import { compile } from "./build/sqlite.js";
await compile();
+61
View File
@@ -0,0 +1,61 @@
// This file is never loaded directly, it's loaded via a bundle. Run `deno task generate` to update.
import { DB } from "./deno-sqlite/mod.ts";
let db: DB | undefined;
import { compile } from "./deno-sqlite/build/sqlite.js";
const ready = compile();
globalThis.addEventListener("message", (event: MessageEvent) => {
const { data } = event;
// console.log("Got message", data);
ready.then(() => {
switch (data.type) {
case "init": {
try {
db = new DB(data.dbPath);
} catch (e: any) {
// console.error("Error!!!", e, data);
respondError(data.id, e);
break;
}
respond(data.id, true);
break;
}
case "execute": {
if (!db) {
respondError(data.id, new Error("Not initialized"));
break;
}
try {
db.query(data.query, data.params);
respond(data.id, db.changes);
} catch (e: any) {
respondError(data.id, e);
}
break;
}
case "query": {
if (!db) {
respondError(data.id, new Error("Not initialized"));
break;
}
try {
const result = db.queryEntries(data.query, data.params);
respond(data.id, result);
} catch (e: any) {
respondError(data.id, e);
}
break;
}
}
}).catch(console.error);
});
function respond(id: number, result: any) {
globalThis.postMessage({ id, result });
}
function respondError(id: number, error: Error) {
globalThis.postMessage({ id, error: error.message });
}
File diff suppressed because one or more lines are too long
+11 -16
View File
@@ -1,48 +1,43 @@
import { SQLite } from "../../server/deps.ts";
import { AsyncSQLite } from "../../plugos/sqlite/async_sqlite.ts";
import { SysCallMapping } from "../system.ts";
import { asyncExecute, asyncQuery } from "./store.deno.ts";
export function ensureFTSTable(
db: SQLite,
export async function ensureFTSTable(
db: AsyncSQLite,
tableName: string,
) {
const result = db.query(
const result = await db.query(
`SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
[tableName],
tableName,
);
if (result.length === 0) {
asyncExecute(
db,
await db.execute(
`CREATE VIRTUAL TABLE ${tableName} USING fts5(key, value);`,
);
console.log(`Created fts5 table ${tableName}`);
}
return Promise.resolve();
}
export function fullTextSearchSyscalls(
db: SQLite,
db: AsyncSQLite,
tableName: string,
): SysCallMapping {
return {
"fulltext.index": async (_ctx, key: string, value: string) => {
await asyncExecute(db, `DELETE FROM ${tableName} WHERE key = ?`, key);
await asyncExecute(
db,
await db.execute(`DELETE FROM ${tableName} WHERE key = ?`, key);
await db.execute(
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)`,
key,
value,
);
},
"fulltext.delete": async (_ctx, key: string) => {
await asyncExecute(db, `DELETE FROM ${tableName} WHERE key = ?`, key);
await db.execute(`DELETE FROM ${tableName} WHERE key = ?`, key);
},
"fulltext.search": async (_ctx, phrase: string, limit: number) => {
console.log("Got search query", phrase);
return (
await asyncQuery<any>(
db,
await db.query(
`SELECT key, rank FROM ${tableName} WHERE value MATCH ? ORDER BY key, rank LIMIT ?`,
phrase,
limit,
+5 -3
View File
@@ -1,11 +1,12 @@
import { assertEquals } from "../../test_deps.ts";
import { SQLite } from "../../server/deps.ts";
import { createSandbox } from "../environments/deno_sandbox.ts";
import { System } from "../system.ts";
import { ensureTable, storeSyscalls } from "./store.deno.ts";
import { AsyncSQLite } from "../sqlite/async_sqlite.ts";
Deno.test("Test store", async () => {
const db = new SQLite(":memory:");
const db = new AsyncSQLite(":memory:");
await db.init();
await ensureTable(db, "test_table");
const system = new System("server");
const syscalls = storeSyscalls(db, "test_table");
@@ -100,7 +101,8 @@ Deno.test("Test store", async () => {
});
allRoberts = await syscalls["store.query"](dummyCtx, {});
// console.log("All Roberts", allRoberts);
assertEquals(allRoberts.length, 2);
db.close();
db.stop();
});
+34 -53
View File
@@ -1,4 +1,4 @@
import { SQLite } from "../../server/deps.ts";
import { AsyncSQLite } from "../sqlite/async_sqlite.ts";
import { SysCallMapping } from "../system.ts";
export type Item = {
@@ -12,18 +12,17 @@ export type KV = {
value: any;
};
export function ensureTable(db: SQLite, tableName: string) {
const result = db.query(
export async function ensureTable(db: AsyncSQLite, tableName: string) {
const result = await db.query(
`SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
[tableName],
tableName,
);
if (result.length === 0) {
db.execute(
await db.execute(
`CREATE TABLE ${tableName} (key STRING PRIMARY KEY, value TEXT);`,
);
console.log(`Created table ${tableName}`);
}
return Promise.resolve();
}
export type Query = {
@@ -72,68 +71,53 @@ export function queryToSql(
};
}
export function asyncQuery<T extends Record<string, unknown>>(
db: SQLite,
query: string,
...params: any[]
): Promise<T[]> {
// console.log("Querying", query, params);
return Promise.resolve(db.queryEntries(query, params));
}
export function asyncExecute(
db: SQLite,
query: string,
...params: any[]
): Promise<number> {
// console.log("Exdecting", query, params);
db.query(query, params);
return Promise.resolve(db.changes);
}
export function storeSyscalls(
db: SQLite,
db: AsyncSQLite,
tableName: string,
): SysCallMapping {
const apiObj: SysCallMapping = {
"store.delete": async (_ctx, key: string) => {
await asyncExecute(db, `DELETE FROM ${tableName} WHERE key = ?`, key);
await db.execute(`DELETE FROM ${tableName} WHERE key = ?`, key);
},
"store.deletePrefix": async (_ctx, prefix: string) => {
await asyncExecute(
db,
await db.execute(
`DELETE FROM ${tableName} WHERE key LIKE ?`,
`${prefix}%`,
);
},
"store.deleteQuery": async (_ctx, query: Query) => {
const { sql, params } = queryToSql(query);
await asyncExecute(db, `DELETE FROM ${tableName} ${sql}`, ...params);
await db.execute(`DELETE FROM ${tableName} ${sql}`, ...params);
},
"store.deleteAll": async () => {
await asyncExecute(db, `DELETE FROM ${tableName}`);
await db.execute(`DELETE FROM ${tableName}`);
},
"store.set": async (_ctx, key: string, value: any) => {
await asyncExecute(
db,
`UPDATE ${tableName} SET value = ? WHERE key = ?`,
JSON.stringify(value),
await db.execute(
`INSERT INTO ${tableName}
(key, value)
VALUES (?, ?)
ON CONFLICT(key)
DO UPDATE SET value=excluded.value`,
key,
JSON.stringify(value),
);
if (db.changes === 0) {
await asyncExecute(
db,
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)`,
key,
JSON.stringify(value),
);
}
},
// TODO: Optimize
"store.batchSet": async (ctx, kvs: KV[]) => {
for (const { key, value } of kvs) {
await apiObj["store.set"](ctx, key, value);
"store.batchSet": async (_ctx, kvs: KV[]) => {
if (kvs.length === 0) {
return;
}
const values = kvs.flatMap((
kv,
) => [kv.key, JSON.stringify(kv.value)]);
await db.execute(
`INSERT INTO ${tableName}
(key, value)
VALUES ${kvs.map((_) => "(?, ?)").join(",")}
ON CONFLICT(key)
DO UPDATE SET value=excluded.value`,
...values,
);
},
"store.batchDelete": async (ctx, keys: string[]) => {
for (const key of keys) {
@@ -141,8 +125,7 @@ export function storeSyscalls(
}
},
"store.get": async (_ctx, key: string): Promise<any | null> => {
const result = await asyncQuery<Item>(
db,
const result = await db.query(
`SELECT value FROM ${tableName} WHERE key = ?`,
key,
);
@@ -154,8 +137,7 @@ export function storeSyscalls(
},
"store.queryPrefix": async (_ctx, prefix: string) => {
return (
await asyncQuery<Item>(
db,
await db.query(
`SELECT key, value FROM ${tableName} WHERE key LIKE ?`,
`${prefix}%`,
)
@@ -167,8 +149,7 @@ export function storeSyscalls(
"store.query": async (_ctx, query: Query) => {
const { sql, params } = queryToSql(query);
return (
await asyncQuery<Item>(
db,
await db.query(
`SELECT key, value FROM ${tableName} ${sql}`,
...params,
)