@rocicorp/zero 1.12.0-canary.2 → 1.12.0-canary.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/analyze-query/src/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +4 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +4 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +2 -1
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +5 -5
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +8 -8
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.js +2 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +4 -4
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +18 -0
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +46 -20
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +4 -3
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +5 -2
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +2 -2
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-protocol/src/inspect-down.d.ts +81 -81
- package/out/zero-protocol/src/poke.d.ts +6 -6
- package/out/zero-protocol/src/up.d.ts +60 -60
- package/out/zql/src/ivm/cap.js +2 -4
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/take.js +2 -4
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zqlite/src/database-storage.d.ts.map +1 -1
- package/out/zqlite/src/database-storage.js +38 -6
- package/out/zqlite/src/database-storage.js.map +1 -1
- package/out/zqlite/src/fetch-sql.d.ts +27 -0
- package/out/zqlite/src/fetch-sql.d.ts.map +1 -0
- package/out/zqlite/src/fetch-sql.js +112 -0
- package/out/zqlite/src/fetch-sql.js.map +1 -0
- package/out/zqlite/src/query-builder.d.ts +11 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +22 -7
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts +9 -2
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +11 -14
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-storage.js","names":["#stmts","#options","#db","#checkpoint","#maybeCheckpoint","#numWrites","#scan","#get","#set","#del","#destroy"],"sources":["../../../../zqlite/src/database-storage.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {JSONValue} from '../../shared/src/json.ts';\nimport type {Storage} from '../../zql/src/ivm/operator.ts';\nimport type {Stream} from '../../zql/src/ivm/stream.ts';\nimport type {Statement} from './db.ts';\nimport {Database} from './db.ts';\n\nexport interface ClientGroupStorage {\n /** Creates a {@link Storage} instance for a single operator. */\n createStorage(): Storage;\n\n /** Deletes all storage for the client group. */\n destroy(): void;\n}\n\ntype Statements = {\n get: Statement;\n set: Statement;\n del: Statement;\n scan: Statement;\n clear: Statement;\n clearOp: Statement;\n commit: Statement;\n begin: Statement;\n};\n\n// Exported for testing.\nexport const CREATE_STORAGE_TABLE = `\n CREATE TABLE storage (\n clientGroupID TEXT,\n op NUMBER,\n key TEXT,\n val TEXT,\n PRIMARY KEY(clientGroupID, op, key)\n )\n `;\n\nconst defaultOptions = {\n commitInterval: 5_000,\n compactionThresholdBytes: 50 * 1024 * 1024,\n};\n\nexport class DatabaseStorage {\n static create(\n lc: LogContext,\n path: string,\n options = defaultOptions,\n ): DatabaseStorage {\n // SQLite is used for ephemeral storage (i.e. similar to RAM) that can spill to\n // disk to avoid consuming too much memory. Each worker thread gets its own\n // database (file) and acts as the single reader/writer of the DB, so\n // `locking_mode` is set to `EXCLUSIVE` for performance. Similarly, since\n // durability is not important, `synchronous` is set to `OFF` for performance.\n const db = new Database(lc, path);\n db.unsafeMode(true); // Allows journal_mode = OFF\n db.pragma('locking_mode = EXCLUSIVE');\n db.pragma('foreign_keys = OFF');\n db.pragma('journal_mode = OFF');\n db.pragma('synchronous = OFF');\n db.pragma('auto_vacuum = INCREMENTAL');\n\n db.prepare(CREATE_STORAGE_TABLE).run();\n lc.debug?.(`Created DatabaseStorage backed by ${path}`);\n return new DatabaseStorage(db, options);\n }\n\n readonly #stmts: Statements;\n readonly #options: typeof defaultOptions;\n readonly #db: Database;\n #numWrites = 0;\n\n constructor(db: Database, options = defaultOptions) {\n this.#stmts = {\n get: db.prepare(`\n SELECT val FROM storage WHERE\n clientGroupID = ? AND op = ? AND key = ?\n `),\n set: db.prepare(`\n INSERT INTO storage (clientGroupID, op, key, val)\n VALUES(?, ?, ?, ?)\n ON CONFLICT(clientGroupID, op, key) \n DO \n UPDATE SET val = excluded.val\n `),\n del: db.prepare(`\n DELETE FROM storage WHERE\n clientGroupID = ? AND op = ? AND key = ?\n `),\n scan: db.prepare(`\n SELECT key, val FROM storage WHERE\n clientGroupID = ? AND op = ? AND key >= ?\n `),\n clear: db.prepare(`\n DELETE FROM storage WHERE clientGroupID = ?\n `),\n clearOp: db.prepare(`\n DELETE FROM storage WHERE clientGroupID = ? AND op = ?\n `),\n commit: db.prepare('COMMIT'),\n begin: db.prepare('BEGIN'),\n };\n this.#stmts.begin.run();\n this.#options = options;\n this.#db = db;\n }\n\n close() {\n this.#checkpoint();\n this.#db.close();\n }\n\n #get(\n cgID: string,\n opID: number,\n key: string,\n def?: JSONValue,\n ): JSONValue | undefined {\n this.#maybeCheckpoint();\n const row = this.#stmts.get.get<{val: string}>(cgID, opID, key);\n return row ? JSON.parse(row.val) : def;\n }\n\n #set(cgID: string, opID: number, key: string, val: JSONValue) {\n this.#maybeCheckpoint();\n this.#stmts.set.run(cgID, opID, key, JSON.stringify(val));\n }\n\n #del(cgID: string, opID: number, key: string) {\n this.#maybeCheckpoint();\n this.#stmts.del.run(cgID, opID, key);\n }\n\n #destroy(cgID: string, opID: number) {\n this.#maybeCheckpoint();\n this.#stmts.clearOp.run(cgID, opID);\n }\n\n /**\n * We don't need to commit every single write to the DB\n * since we're not concerned with durability.\n * Waiting on commits can be expensive, so we commit\n * every `COMMIT_INTERVAL` writes.\n */\n #maybeCheckpoint() {\n if (++this.#numWrites >= this.#options.commitInterval) {\n this.#checkpoint();\n }\n }\n\n #checkpoint() {\n this.#stmts.commit.run();\n this.#stmts.begin.run();\n this.#numWrites = 0;\n }\n\n *#scan(\n cgID: string,\n opID: number,\n opts: {prefix: string} = {prefix: ''},\n ): Stream<[string, JSONValue]> {\n const {prefix} = opts;\n for (const {key, val} of this.#stmts.scan.iterate<{\n key: string;\n val: string;\n }>(cgID, opID, prefix)) {\n if (!key.startsWith(prefix)) {\n return;\n }\n yield [key, JSON.parse(val)];\n }\n }\n\n createClientGroupStorage(cgID: string): ClientGroupStorage {\n const destroy = () => {\n this.#stmts.clear.run(cgID);\n this.#checkpoint();\n this.#db.compact(this.#options.compactionThresholdBytes);\n };\n this.#stmts.clear.run(cgID);\n\n let nextOpID = 1;\n return {\n createStorage: () => {\n const opID = nextOpID++;\n return {\n get: (key, def?) => this.#get(cgID, opID, key, def),\n set: (key, val) => this.#set(cgID, opID, key, val),\n del: key => this.#del(cgID, opID, key),\n scan: opts => this.#scan(cgID, opID, opts),\n destroy: () => this.#destroy(cgID, opID),\n };\n },\n\n destroy,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"database-storage.js","names":["#stmts","#options","#db","#checkpoint","#maybeCheckpoint","#numWrites","#scan","#get","#set","#del","#destroy"],"sources":["../../../../zqlite/src/database-storage.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {JSONValue} from '../../shared/src/json.ts';\nimport type {Storage} from '../../zql/src/ivm/operator.ts';\nimport type {Stream} from '../../zql/src/ivm/stream.ts';\nimport type {Statement} from './db.ts';\nimport {Database} from './db.ts';\n\nexport interface ClientGroupStorage {\n /** Creates a {@link Storage} instance for a single operator. */\n createStorage(): Storage;\n\n /** Deletes all storage for the client group. */\n destroy(): void;\n}\n\ntype Statements = {\n get: Statement;\n set: Statement;\n del: Statement;\n scan: Statement;\n clear: Statement;\n clearOp: Statement;\n commit: Statement;\n begin: Statement;\n};\n\n// Exported for testing.\nexport const CREATE_STORAGE_TABLE = `\n CREATE TABLE storage (\n clientGroupID TEXT,\n op NUMBER,\n key TEXT,\n val TEXT,\n PRIMARY KEY(clientGroupID, op, key)\n )\n `;\n\n/**\n * The most values each operator's storage keeps in memory. The storage is in\n * SQLite so that operator state can spill to disk, so only a few values per\n * operator are kept: profiles of the view-syncer gain as much from 64 as from\n * 1,024.\n */\nconst MAX_CACHED_ENTRIES_PER_OP = 64;\n\nconst defaultOptions = {\n commitInterval: 5_000,\n compactionThresholdBytes: 50 * 1024 * 1024,\n};\n\nexport class DatabaseStorage {\n static create(\n lc: LogContext,\n path: string,\n options = defaultOptions,\n ): DatabaseStorage {\n // SQLite is used for ephemeral storage (i.e. similar to RAM) that can spill to\n // disk to avoid consuming too much memory. Each worker thread gets its own\n // database (file) and acts as the single reader/writer of the DB, so\n // `locking_mode` is set to `EXCLUSIVE` for performance. Similarly, since\n // durability is not important, `synchronous` is set to `OFF` for performance.\n const db = new Database(lc, path);\n db.unsafeMode(true); // Allows journal_mode = OFF\n db.pragma('locking_mode = EXCLUSIVE');\n db.pragma('foreign_keys = OFF');\n db.pragma('journal_mode = OFF');\n db.pragma('synchronous = OFF');\n db.pragma('auto_vacuum = INCREMENTAL');\n\n db.prepare(CREATE_STORAGE_TABLE).run();\n lc.debug?.(`Created DatabaseStorage backed by ${path}`);\n return new DatabaseStorage(db, options);\n }\n\n readonly #stmts: Statements;\n readonly #options: typeof defaultOptions;\n readonly #db: Database;\n #numWrites = 0;\n\n constructor(db: Database, options = defaultOptions) {\n this.#stmts = {\n get: db.prepare(`\n SELECT val FROM storage WHERE\n clientGroupID = ? AND op = ? AND key = ?\n `),\n set: db.prepare(`\n INSERT INTO storage (clientGroupID, op, key, val)\n VALUES(?, ?, ?, ?)\n ON CONFLICT(clientGroupID, op, key) \n DO \n UPDATE SET val = excluded.val\n `),\n del: db.prepare(`\n DELETE FROM storage WHERE\n clientGroupID = ? AND op = ? AND key = ?\n `),\n scan: db.prepare(`\n SELECT key, val FROM storage WHERE\n clientGroupID = ? AND op = ? AND key >= ?\n `),\n clear: db.prepare(`\n DELETE FROM storage WHERE clientGroupID = ?\n `),\n clearOp: db.prepare(`\n DELETE FROM storage WHERE clientGroupID = ? AND op = ?\n `),\n commit: db.prepare('COMMIT'),\n begin: db.prepare('BEGIN'),\n };\n this.#stmts.begin.run();\n this.#options = options;\n this.#db = db;\n }\n\n close() {\n this.#checkpoint();\n this.#db.close();\n }\n\n #get(cgID: string, opID: number, key: string): JSONValue | undefined {\n this.#maybeCheckpoint();\n const row = this.#stmts.get.get<{val: string}>(cgID, opID, key);\n return row ? JSON.parse(row.val) : undefined;\n }\n\n #set(cgID: string, opID: number, key: string, val: JSONValue) {\n this.#maybeCheckpoint();\n this.#stmts.set.run(cgID, opID, key, JSON.stringify(val));\n }\n\n #del(cgID: string, opID: number, key: string) {\n this.#maybeCheckpoint();\n this.#stmts.del.run(cgID, opID, key);\n }\n\n #destroy(cgID: string, opID: number) {\n this.#maybeCheckpoint();\n this.#stmts.clearOp.run(cgID, opID);\n }\n\n /**\n * We don't need to commit every single write to the DB\n * since we're not concerned with durability.\n * Waiting on commits can be expensive, so we commit\n * every `COMMIT_INTERVAL` writes.\n */\n #maybeCheckpoint() {\n if (++this.#numWrites >= this.#options.commitInterval) {\n this.#checkpoint();\n }\n }\n\n #checkpoint() {\n this.#stmts.commit.run();\n this.#stmts.begin.run();\n this.#numWrites = 0;\n }\n\n *#scan(\n cgID: string,\n opID: number,\n opts: {prefix: string} = {prefix: ''},\n ): Stream<[string, JSONValue]> {\n const {prefix} = opts;\n for (const {key, val} of this.#stmts.scan.iterate<{\n key: string;\n val: string;\n }>(cgID, opID, prefix)) {\n if (!key.startsWith(prefix)) {\n return;\n }\n yield [key, JSON.parse(val)];\n }\n }\n\n createClientGroupStorage(cgID: string): ClientGroupStorage {\n // The caches of the group's live operator storages.\n const caches = new Set<Map<string, JSONValue | undefined>>();\n const destroy = () => {\n this.#stmts.clear.run(cgID);\n for (const cache of caches) {\n cache.clear();\n }\n this.#checkpoint();\n this.#db.compact(this.#options.compactionThresholdBytes);\n };\n this.#stmts.clear.run(cgID);\n\n let nextOpID = 1;\n return {\n createStorage: () => {\n const opID = nextOpID++;\n // The operator's most recently read and written values, `undefined`\n // for a key known to be absent. Operators read the same key again and\n // again (Cap and Take read their state on every fetch and push), so\n // this saves most of the SQLite reads. Like MemoryStorage, `get`\n // returns the stored value itself, which operators do not mutate.\n const cache = new Map<string, JSONValue | undefined>();\n caches.add(cache);\n const cacheSet = (key: string, val: JSONValue | undefined) => {\n if (cache.size >= MAX_CACHED_ENTRIES_PER_OP && !cache.has(key)) {\n cache.clear();\n }\n cache.set(key, val);\n };\n return {\n get: (key, def?) => {\n let val = cache.get(key);\n if (val === undefined && !cache.has(key)) {\n val = this.#get(cgID, opID, key);\n cacheSet(key, val);\n }\n return val === undefined ? def : val;\n },\n // The cache is updated only once the database is, so a failed write\n // leaves it consistent with the database.\n set: (key, val) => {\n this.#set(cgID, opID, key, val);\n cacheSet(key, val);\n },\n del: key => {\n this.#del(cgID, opID, key);\n cacheSet(key, undefined);\n },\n scan: opts => this.#scan(cgID, opID, opts),\n destroy: () => {\n this.#destroy(cgID, opID);\n cache.clear();\n caches.delete(cache);\n },\n };\n },\n\n destroy,\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Ordering } from '../../zero-protocol/src/ast.ts';
|
|
2
|
+
import type { SchemaValue } from '../../zero-schema/src/table-schema.ts';
|
|
3
|
+
import type { FetchRequest } from '../../zql/src/ivm/operator.ts';
|
|
4
|
+
import { type NoSubqueryCondition } from './query-builder.ts';
|
|
5
|
+
export type FetchSQL = {
|
|
6
|
+
readonly text: string;
|
|
7
|
+
readonly values: readonly unknown[];
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Builds the SELECTs of {@link buildSelectQueries} for the fetches of one
|
|
11
|
+
* connection, formatting the SQL text only once per fetch shape.
|
|
12
|
+
*
|
|
13
|
+
* Building and formatting the SQL is a fixed cost of every fetch that is
|
|
14
|
+
* comparable to running the statement for the small fetches IVM does most.
|
|
15
|
+
* The text depends only on the fetch's shape, though: which columns it
|
|
16
|
+
* constrains, how many entries each multi-constraint has, its direction,
|
|
17
|
+
* which columns of its start row are NULL, and its `req.filter`. The values
|
|
18
|
+
* are bound to `?` placeholders, so a shape's text is built once with a
|
|
19
|
+
* {@link Binding} in place of each value, and each fetch only reads its values
|
|
20
|
+
* from those.
|
|
21
|
+
*/
|
|
22
|
+
export declare class FetchSQLCache {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(tableName: string, columns: Record<string, SchemaValue>, filters: NoSubqueryCondition | undefined, order: Ordering | undefined);
|
|
25
|
+
get(req: FetchRequest): FetchSQL[];
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=fetch-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-sql.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/fetch-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gCAAgC,CAAC;AAE7D,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uCAAuC,CAAC;AAEvE,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;CACrC,CAAC;AAYF;;;;;;;;;;;;GAYG;AACH,qBAAa,aAAa;;IAOxB,YACE,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,OAAO,EAAE,mBAAmB,GAAG,SAAS,EACxC,KAAK,EAAE,QAAQ,GAAG,SAAS,EAM5B;IAED,GAAG,CAAC,GAAG,EAAE,YAAY,GAAG,QAAQ,EAAE,CAcjC;CA2GF"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { assignProperty } from "../../shared/src/objects.js";
|
|
2
|
+
import { format } from "./internal/sql.js";
|
|
3
|
+
import { Binding, buildSelectQueries, toSQLiteType } from "./query-builder.js";
|
|
4
|
+
//#region ../zqlite/src/fetch-sql.ts
|
|
5
|
+
/**
|
|
6
|
+
* More shapes than a connection is expected to fetch. Past it the cache is
|
|
7
|
+
* cleared, which bounds it if some fetch parameter that is part of the shape
|
|
8
|
+
* (a `req.filter` that is not the same object every time, say) never repeats.
|
|
9
|
+
*/
|
|
10
|
+
var MAX_SHAPES = 256;
|
|
11
|
+
var filterIDs = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
var nextFilterID = 0;
|
|
13
|
+
/**
|
|
14
|
+
* Builds the SELECTs of {@link buildSelectQueries} for the fetches of one
|
|
15
|
+
* connection, formatting the SQL text only once per fetch shape.
|
|
16
|
+
*
|
|
17
|
+
* Building and formatting the SQL is a fixed cost of every fetch that is
|
|
18
|
+
* comparable to running the statement for the small fetches IVM does most.
|
|
19
|
+
* The text depends only on the fetch's shape, though: which columns it
|
|
20
|
+
* constrains, how many entries each multi-constraint has, its direction,
|
|
21
|
+
* which columns of its start row are NULL, and its `req.filter`. The values
|
|
22
|
+
* are bound to `?` placeholders, so a shape's text is built once with a
|
|
23
|
+
* {@link Binding} in place of each value, and each fetch only reads its values
|
|
24
|
+
* from those.
|
|
25
|
+
*/
|
|
26
|
+
var FetchSQLCache = class {
|
|
27
|
+
#tableName;
|
|
28
|
+
#columns;
|
|
29
|
+
#filters;
|
|
30
|
+
#order;
|
|
31
|
+
#templates = /* @__PURE__ */ new Map();
|
|
32
|
+
constructor(tableName, columns, filters, order) {
|
|
33
|
+
this.#tableName = tableName;
|
|
34
|
+
this.#columns = columns;
|
|
35
|
+
this.#filters = filters;
|
|
36
|
+
this.#order = order;
|
|
37
|
+
}
|
|
38
|
+
get(req) {
|
|
39
|
+
const key = this.#shapeKey(req);
|
|
40
|
+
let templates = this.#templates.get(key);
|
|
41
|
+
if (templates === void 0) {
|
|
42
|
+
templates = this.#buildTemplates(req);
|
|
43
|
+
if (this.#templates.size >= MAX_SHAPES) this.#templates.clear();
|
|
44
|
+
this.#templates.set(key, templates);
|
|
45
|
+
}
|
|
46
|
+
return templates.map(({ text, values }) => ({
|
|
47
|
+
text,
|
|
48
|
+
values: values.map((v) => v instanceof Binding ? v.get(req) : v)
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
#shapeKey(req) {
|
|
52
|
+
const { constraint, multiConstraints, start, filter } = req;
|
|
53
|
+
let key = req.reverse ? "r" : "f";
|
|
54
|
+
if (constraint) for (const k in constraint) key += k + "\0";
|
|
55
|
+
key += "\0";
|
|
56
|
+
if (multiConstraints) for (const mc of multiConstraints) {
|
|
57
|
+
key += mc.length + "\0";
|
|
58
|
+
if (mc.length > 0) for (const k in mc[0]) key += k + "\0";
|
|
59
|
+
key += "\0";
|
|
60
|
+
}
|
|
61
|
+
key += "\0";
|
|
62
|
+
if (start) {
|
|
63
|
+
key += start.basis;
|
|
64
|
+
for (const [field] of this.#order ?? []) key += isNullish(start.row[field]) ? "0" : "1";
|
|
65
|
+
}
|
|
66
|
+
key += "\0";
|
|
67
|
+
if (filter) {
|
|
68
|
+
let id = filterIDs.get(filter);
|
|
69
|
+
if (id === void 0) {
|
|
70
|
+
id = nextFilterID++;
|
|
71
|
+
filterIDs.set(filter, id);
|
|
72
|
+
}
|
|
73
|
+
key += id;
|
|
74
|
+
}
|
|
75
|
+
return key;
|
|
76
|
+
}
|
|
77
|
+
#buildTemplates(req) {
|
|
78
|
+
const columns = this.#columns;
|
|
79
|
+
const binding = (get, field) => {
|
|
80
|
+
const { type } = columns[field];
|
|
81
|
+
return new Binding((req) => toSQLiteType(get(req), type));
|
|
82
|
+
};
|
|
83
|
+
let constraint;
|
|
84
|
+
if (req.constraint) {
|
|
85
|
+
const probe = {};
|
|
86
|
+
for (const k in req.constraint) assignProperty(probe, k, binding((req) => req.constraint[k], k));
|
|
87
|
+
constraint = probe;
|
|
88
|
+
}
|
|
89
|
+
const multiConstraints = req.multiConstraints?.map((mc, i) => mc.map((entry, j) => {
|
|
90
|
+
const probe = {};
|
|
91
|
+
for (const k in entry) assignProperty(probe, k, binding((req) => req.multiConstraints[i][j][k], k));
|
|
92
|
+
return probe;
|
|
93
|
+
}));
|
|
94
|
+
let start;
|
|
95
|
+
if (req.start) {
|
|
96
|
+
const row = {};
|
|
97
|
+
for (const [field] of this.#order ?? []) assignProperty(row, field, isNullish(req.start.row[field]) ? null : binding((req) => req.start.row[field], field));
|
|
98
|
+
start = {
|
|
99
|
+
basis: req.start.basis,
|
|
100
|
+
row
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return buildSelectQueries(this.#tableName, columns, constraint, this.#filters, this.#order, req.reverse, start, multiConstraints, req.filter).map((query) => format(query));
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
function isNullish(v) {
|
|
107
|
+
return v === null || v === void 0;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { FetchSQLCache };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=fetch-sql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-sql.js","names":["#tableName","#columns","#filters","#order","#templates","#shapeKey","#buildTemplates"],"sources":["../../../../zqlite/src/fetch-sql.ts"],"sourcesContent":["import {assignProperty} from '../../shared/src/objects.ts';\nimport type {Ordering} from '../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../zero-protocol/src/data.ts';\nimport type {SchemaValue} from '../../zero-schema/src/table-schema.ts';\nimport type {Constraint} from '../../zql/src/ivm/constraint.ts';\nimport type {\n FetchRequest,\n MultiConstraint,\n} from '../../zql/src/ivm/operator.ts';\nimport {format} from './internal/sql.ts';\nimport {\n Binding,\n buildSelectQueries,\n toSQLiteType,\n type NoSubqueryCondition,\n} from './query-builder.ts';\n\nexport type FetchSQL = {\n readonly text: string;\n readonly values: readonly unknown[];\n};\n\n/**\n * More shapes than a connection is expected to fetch. Past it the cache is\n * cleared, which bounds it if some fetch parameter that is part of the shape\n * (a `req.filter` that is not the same object every time, say) never repeats.\n */\nconst MAX_SHAPES = 256;\n\nconst filterIDs = new WeakMap<NoSubqueryCondition, number>();\nlet nextFilterID = 0;\n\n/**\n * Builds the SELECTs of {@link buildSelectQueries} for the fetches of one\n * connection, formatting the SQL text only once per fetch shape.\n *\n * Building and formatting the SQL is a fixed cost of every fetch that is\n * comparable to running the statement for the small fetches IVM does most.\n * The text depends only on the fetch's shape, though: which columns it\n * constrains, how many entries each multi-constraint has, its direction,\n * which columns of its start row are NULL, and its `req.filter`. The values\n * are bound to `?` placeholders, so a shape's text is built once with a\n * {@link Binding} in place of each value, and each fetch only reads its values\n * from those.\n */\nexport class FetchSQLCache {\n readonly #tableName: string;\n readonly #columns: Record<string, SchemaValue>;\n readonly #filters: NoSubqueryCondition | undefined;\n readonly #order: Ordering | undefined;\n readonly #templates = new Map<string, readonly FetchSQL[]>();\n\n constructor(\n tableName: string,\n columns: Record<string, SchemaValue>,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n ) {\n this.#tableName = tableName;\n this.#columns = columns;\n this.#filters = filters;\n this.#order = order;\n }\n\n get(req: FetchRequest): FetchSQL[] {\n const key = this.#shapeKey(req);\n let templates = this.#templates.get(key);\n if (templates === undefined) {\n templates = this.#buildTemplates(req);\n if (this.#templates.size >= MAX_SHAPES) {\n this.#templates.clear();\n }\n this.#templates.set(key, templates);\n }\n return templates.map(({text, values}) => ({\n text,\n values: values.map(v => (v instanceof Binding ? v.get(req) : v)),\n }));\n }\n\n // Column names are never empty and never contain NUL, so terminating each\n // with NUL and each list with an extra NUL keeps distinct shapes distinct.\n #shapeKey(req: FetchRequest): string {\n const {constraint, multiConstraints, start, filter} = req;\n let key = req.reverse ? 'r' : 'f';\n if (constraint) {\n for (const k in constraint) {\n key += k + '\\0';\n }\n }\n key += '\\0';\n if (multiConstraints) {\n for (const mc of multiConstraints) {\n key += mc.length + '\\0';\n if (mc.length > 0) {\n for (const k in mc[0]) {\n key += k + '\\0';\n }\n }\n key += '\\0';\n }\n }\n key += '\\0';\n if (start) {\n key += start.basis;\n // Only the order's columns of the start row are read.\n for (const [field] of this.#order ?? []) {\n key += isNullish(start.row[field]) ? '0' : '1';\n }\n }\n key += '\\0';\n if (filter) {\n let id = filterIDs.get(filter);\n if (id === undefined) {\n id = nextFilterID++;\n filterIDs.set(filter, id);\n }\n key += id;\n }\n return key;\n }\n\n #buildTemplates(req: FetchRequest): FetchSQL[] {\n const columns = this.#columns;\n const binding = (\n get: (req: FetchRequest) => unknown,\n field: string,\n ): Binding => {\n const {type} = columns[field];\n return new Binding(req => toSQLiteType(get(req), type));\n };\n\n let constraint: Constraint | undefined;\n if (req.constraint) {\n const probe: Record<string, unknown> = {};\n for (const k in req.constraint) {\n assignProperty(\n probe,\n k,\n binding(req => req.constraint![k], k),\n );\n }\n constraint = probe as Constraint;\n }\n const multiConstraints = req.multiConstraints?.map(\n (mc, i): MultiConstraint =>\n mc.map((entry, j) => {\n const probe: Record<string, unknown> = {};\n for (const k in entry) {\n assignProperty(\n probe,\n k,\n binding(req => req.multiConstraints![i][j][k], k),\n );\n }\n return probe as Row;\n }),\n );\n let start: FetchRequest['start'];\n if (req.start) {\n const row: Record<string, unknown> = {};\n for (const [field] of this.#order ?? []) {\n assignProperty(\n row,\n field,\n isNullish(req.start.row[field])\n ? null\n : binding(req => req.start!.row[field], field),\n );\n }\n start = {basis: req.start.basis, row: row as Row};\n }\n\n return buildSelectQueries(\n this.#tableName,\n columns,\n constraint,\n this.#filters,\n this.#order,\n req.reverse,\n start,\n multiConstraints,\n req.filter,\n ).map(query => format(query));\n }\n}\n\nfunction isNullish(v: unknown): boolean {\n return v === null || v === undefined;\n}\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import type { SQLQuery } from '@databases/sql';
|
|
|
2
2
|
import type { Condition, Ordering } from '../../zero-protocol/src/ast.ts';
|
|
3
3
|
import type { SchemaValue, ValueType } from '../../zero-schema/src/table-schema.ts';
|
|
4
4
|
import type { Constraint } from '../../zql/src/ivm/constraint.ts';
|
|
5
|
-
import type { MultiConstraint, Start } from '../../zql/src/ivm/operator.ts';
|
|
5
|
+
import type { FetchRequest, MultiConstraint, Start } from '../../zql/src/ivm/operator.ts';
|
|
6
6
|
/**
|
|
7
7
|
* Condition type without correlated subqueries.
|
|
8
8
|
* This matches the output of transformFilters from zql/builder/filter.ts
|
|
@@ -47,4 +47,14 @@ export declare function orderByToSQL(order: Ordering, reverse: boolean): SQLQuer
|
|
|
47
47
|
*/
|
|
48
48
|
export declare function filtersToSQL(filters: NoSubqueryCondition): SQLQuery;
|
|
49
49
|
export declare function toSQLiteType(v: unknown, type: ValueType): unknown;
|
|
50
|
+
/**
|
|
51
|
+
* A placeholder for a fetch's value that {@link buildSelectQueries} passes
|
|
52
|
+
* through to the bindings of its SELECTs, where `toSQLiteType` would convert
|
|
53
|
+
* the value itself. It lets a fetch's SQL text be built once and reused for
|
|
54
|
+
* every fetch of the same shape (see `fetch-sql.ts`).
|
|
55
|
+
*/
|
|
56
|
+
export declare class Binding {
|
|
57
|
+
readonly get: (req: FetchRequest) => unknown;
|
|
58
|
+
constructor(get: (req: FetchRequest) => unknown);
|
|
59
|
+
}
|
|
50
60
|
//# sourceMappingURL=query-builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-builder.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/query-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAE7C,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAGT,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACV,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"query-builder.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/query-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAE7C,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAGT,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACV,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EACf,KAAK,EACN,MAAM,+BAA+B,CAAC;AAGvC;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,CACvC,SAAS,EACT;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAC,CAC7B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,mBAAmB,GAAG,SAAS,EACxC,KAAK,EAAE,QAAQ,GAAG,SAAS,EAC3B,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,KAAK,EAAE,KAAK,GAAG,SAAS,EACxB,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,EACzD,YAAY,CAAC,EAAE,mBAAmB,GAAG,SAAS,GAC7C,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CA4DnC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,GAC7C,QAAQ,CAOV;AA0DD,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,cAcrC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACnC,QAAQ,CAyCV;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,CAmBxE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,QAAQ,CAuBnE;AAgGD,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAWjE;AAED;;;;;GAKG;AACH,qBAAa,OAAO;IAClB,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC;IAC7C,YAAY,GAAG,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,EAE9C;CACF"}
|
|
@@ -87,7 +87,7 @@ function constraintsRejectNull(constraint, multiConstraints, field) {
|
|
|
87
87
|
function constraintsToSQL(constraint, columns) {
|
|
88
88
|
if (!constraint) return [];
|
|
89
89
|
const constraints = [];
|
|
90
|
-
for (const [key, value] of Object.entries(constraint)) constraints.push(sql`${sql.ident(key)} = ${
|
|
90
|
+
for (const [key, value] of Object.entries(constraint)) constraints.push(sql`${sql.ident(key)} = ${toSQLiteBinding(value, columns[key].type)}`);
|
|
91
91
|
return constraints;
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
@@ -112,10 +112,10 @@ function multiConstraintToSQL(multiConstraint, columns) {
|
|
|
112
112
|
if (keys.length === 1) {
|
|
113
113
|
const key = keys[0];
|
|
114
114
|
const colType = columns[key].type;
|
|
115
|
-
return sql`${sql.ident(key)} IN (${sql.join(multiConstraint.map((c) => sql`${
|
|
115
|
+
return sql`${sql.ident(key)} IN (${sql.join(multiConstraint.map((c) => sql`${toSQLiteBinding(c[key], colType)}`), sql`,`)})`;
|
|
116
116
|
}
|
|
117
117
|
const colList = sql`(${sql.join(keys.map((k) => sql.ident(k)), sql`,`)})`;
|
|
118
|
-
const rows = multiConstraint.map((c) => sql`(${sql.join(keys.map((k) => sql`${
|
|
118
|
+
const rows = multiConstraint.map((c) => sql`(${sql.join(keys.map((k) => sql`${toSQLiteBinding(c[k], columns[k].type)}`), sql`,`)})`);
|
|
119
119
|
return sql`${colList} IN (VALUES ${sql.join(rows, sql`,`)})`;
|
|
120
120
|
}
|
|
121
121
|
function orderByToSQL(order, reverse) {
|
|
@@ -173,6 +173,21 @@ function toSQLiteType(v, type) {
|
|
|
173
173
|
case "json": return JSON.stringify(v);
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* A placeholder for a fetch's value that {@link buildSelectQueries} passes
|
|
178
|
+
* through to the bindings of its SELECTs, where `toSQLiteType` would convert
|
|
179
|
+
* the value itself. It lets a fetch's SQL text be built once and reused for
|
|
180
|
+
* every fetch of the same shape (see `fetch-sql.ts`).
|
|
181
|
+
*/
|
|
182
|
+
var Binding = class {
|
|
183
|
+
get;
|
|
184
|
+
constructor(get) {
|
|
185
|
+
this.get = get;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
function toSQLiteBinding(v, type) {
|
|
189
|
+
return v instanceof Binding ? v : toSQLiteType(v, type);
|
|
190
|
+
}
|
|
176
191
|
function nullableAwareEquality(field, value, columnType) {
|
|
177
192
|
if (value === null) return sql`${sql.ident(field)} IS NULL`;
|
|
178
193
|
return columnType.optional === true ? sql`${sql.ident(field)} IS ${value}` : sql`${sql.ident(field)} = ${value}`;
|
|
@@ -226,7 +241,7 @@ function gatherStartConstraints(start, reverse, order, columnTypes) {
|
|
|
226
241
|
const [iField, iDirection] = order[i];
|
|
227
242
|
for (let j = 0; j <= i; j++) if (j === i) {
|
|
228
243
|
const columnType = columnTypes[iField];
|
|
229
|
-
const constraintValue =
|
|
244
|
+
const constraintValue = toSQLiteBinding(from[iField] ?? null, columnType.type);
|
|
230
245
|
const operator = iDirection === "asc" ? reverse ? "<" : ">" : reverse ? ">" : "<";
|
|
231
246
|
let nullGroupExcluded = false;
|
|
232
247
|
if (i === 0) {
|
|
@@ -237,14 +252,14 @@ function gatherStartConstraints(start, reverse, order, columnTypes) {
|
|
|
237
252
|
} else {
|
|
238
253
|
const [jField] = order[j];
|
|
239
254
|
const columnType = columnTypes[jField];
|
|
240
|
-
const value =
|
|
255
|
+
const value = toSQLiteBinding(from[jField] ?? null, columnType.type);
|
|
241
256
|
group.push(nullableAwareEquality(jField, value, columnType));
|
|
242
257
|
}
|
|
243
258
|
constraints.push(sql`(${sql.join(group, sql` AND `)})`);
|
|
244
259
|
}
|
|
245
260
|
if (basis === "at") constraints.push(sql`(${sql.join(order.map(([field]) => {
|
|
246
261
|
const columnType = columnTypes[field];
|
|
247
|
-
return nullableAwareEquality(field,
|
|
262
|
+
return nullableAwareEquality(field, toSQLiteBinding(from[field] ?? null, columnType.type), columnType);
|
|
248
263
|
}), sql` AND `)})`);
|
|
249
264
|
const lexicographicStart = sql`(${sql.join(constraints, sql` OR `)})`;
|
|
250
265
|
if (leadingBound === void 0) return {
|
|
@@ -257,6 +272,6 @@ function gatherStartConstraints(start, reverse, order, columnTypes) {
|
|
|
257
272
|
};
|
|
258
273
|
}
|
|
259
274
|
//#endregion
|
|
260
|
-
export { buildSelectQueries, buildSelectQuery, toSQLiteType };
|
|
275
|
+
export { Binding, buildSelectQueries, buildSelectQuery, toSQLiteType };
|
|
261
276
|
|
|
262
277
|
//# sourceMappingURL=query-builder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-builder.js","names":[],"sources":["../../../../zqlite/src/query-builder.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport {assert} from '../../shared/src/asserts.ts';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport type {Constraint} from '../../zql/src/ivm/constraint.ts';\nimport type {MultiConstraint, Start} from '../../zql/src/ivm/operator.ts';\nimport {sql} from './internal/sql.ts';\n\n/**\n * Condition type without correlated subqueries.\n * This matches the output of transformFilters from zql/builder/filter.ts\n */\nexport type NoSubqueryCondition = Exclude<\n Condition,\n {type: 'correlatedSubquery'}\n>;\n\n/**\n * Builds the SELECTs for a fetch. Running them in order and concatenating\n * their rows gives the fetch's rows, in order.\n *\n * There is a second SELECT only when the fetch starts at a non-NULL value of a\n * nullable leading sort column and walks towards its NULL group. The first\n * SELECT then leaves that group out so that SQLite can seek it (see\n * `gatherStartConstraints`), and the second fetches the group. NULLs sort\n * before every other value, so the group follows all of the first SELECT's\n * rows, and a fetch that stops early never needs to run the second.\n */\nexport function buildSelectQueries(\n tableName: string,\n columns: Record<string, SchemaValue>,\n constraint: Constraint | undefined,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n reverse: boolean | undefined,\n start: Start | undefined,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n fetchFilters?: NoSubqueryCondition | undefined,\n): [SQLQuery] | [SQLQuery, SQLQuery] {\n const select = sql`SELECT ${sql.join(\n Object.keys(columns).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(tableName)}`;\n const leading: SQLQuery[] = constraintsToSQL(constraint, columns);\n\n if (multiConstraints) {\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n leading.push(multiConstraintToSQL(mc, columns));\n }\n }\n }\n\n const trailing: SQLQuery[] = [];\n if (filters) {\n trailing.push(filtersToSQL(filters));\n }\n\n if (fetchFilters) {\n trailing.push(filtersToSQL(fetchFilters));\n }\n\n const orderBy =\n order && order.length > 0 ? orderByToSQL(order, !!reverse) : undefined;\n const selectWhere = (constraints: SQLQuery[]) => {\n const query =\n constraints.length > 0\n ? sql`${select} WHERE ${sql.join(constraints, sql` AND `)}`\n : select;\n return orderBy ? sql`${query} ${orderBy}` : query;\n };\n\n if (!start) {\n return [selectWhere([...leading, ...trailing])];\n }\n\n assert(order !== undefined, 'start requires ordering');\n const {constraint: startConstraint, excludesNullGroup} =\n gatherStartConstraints(start, reverse, order, columns);\n const query = selectWhere([...leading, startConstraint, ...trailing]);\n const [leadingField] = order[0];\n if (\n !excludesNullGroup ||\n // The rest of the fetch would reject every row of the NULL group anyway.\n constraintsRejectNull(constraint, multiConstraints, leadingField) ||\n rejectsNull(filters, leadingField) ||\n rejectsNull(fetchFilters, leadingField)\n ) {\n return [query];\n }\n return [\n query,\n selectWhere([\n ...leading,\n sql`${sql.ident(leadingField)} IS NULL`,\n ...trailing,\n ]),\n ];\n}\n\n/**\n * {@link buildSelectQueries} for a fetch that needs only one SELECT, such as\n * one without a start row.\n */\nexport function buildSelectQuery(\n ...args: Parameters<typeof buildSelectQueries>\n): SQLQuery {\n const [query, nullGroup] = buildSelectQueries(...args);\n assert(\n nullGroup === undefined,\n 'The fetch needs a second SELECT for its NULL group; use buildSelectQueries',\n );\n return query;\n}\n\n/**\n * Whether `condition` is never true for a row whose `field` is NULL. Only a\n * comparison of `field` itself, alone or ANDed with other conditions, is\n * recognized. Every operator yields NULL for a NULL operand except `IS`,\n * `IS NOT` and `NOT IN`, which is true for an empty list.\n */\nfunction rejectsNull(\n condition: NoSubqueryCondition | undefined,\n field: string,\n): boolean {\n switch (condition?.type) {\n case undefined:\n case 'or':\n return false;\n case 'and':\n return condition.conditions.some(c =>\n rejectsNull(c as NoSubqueryCondition, field),\n );\n case 'simple': {\n const {op, left, right} = condition;\n if (left.type !== 'column' || left.name !== field) {\n return false;\n }\n switch (op) {\n case 'IS':\n return right.type === 'literal' && right.value !== null;\n case 'IS NOT':\n return right.type === 'literal' && right.value === null;\n case 'NOT IN':\n return false;\n default:\n return true;\n }\n }\n }\n}\n\n/**\n * Whether the constraints of the fetch are never true for a row whose `field`\n * is NULL. {@link constraintsToSQL} compares the field with `=` and\n * {@link multiConstraintToSQL} with `IN`, neither of which is ever true for a\n * NULL operand, so the field being constrained at all is enough.\n */\nfunction constraintsRejectNull(\n constraint: Constraint | undefined,\n multiConstraints: readonly MultiConstraint[] | undefined,\n field: string,\n): boolean {\n if (constraint && field in constraint) {\n return true;\n }\n // `multiConstraintToSQL` takes its key list from the first entry and asserts\n // that the rest match it.\n return multiConstraints?.some(mc => mc.length > 0 && field in mc[0]) === true;\n}\n\nexport function constraintsToSQL(\n constraint: Constraint | undefined,\n columns: Record<string, SchemaValue>,\n) {\n if (!constraint) {\n return [];\n }\n\n const constraints: SQLQuery[] = [];\n for (const [key, value] of Object.entries(constraint)) {\n constraints.push(\n sql`${sql.ident(key)} = ${toSQLiteType(value, columns[key].type)}`,\n );\n }\n\n return constraints;\n}\n\n/**\n * Builds a single batched IN clause from a `MultiConstraint`. All entries\n * are assumed to share the same shape (the keys of the first entry);\n * FlippedJoin derives them from the same parentKey for all children.\n *\n * Single-column form: `col IN (?, ?, ?)`\n * Compound form: `(a, b) IN (VALUES (?, ?), (?, ?), …)`\n *\n * NOTE: SQLite optimizes `col IN (literal-list)` using the column's index;\n * verified via EXPLAIN QUERY PLAN — see query-builder.test.ts.\n */\nexport function multiConstraintToSQL(\n multiConstraint: MultiConstraint,\n columns: Record<string, SchemaValue>,\n): SQLQuery {\n assert(multiConstraint.length > 0, 'multiConstraint must be non-empty');\n // All entries share the same keys; pull the column list from the first.\n const keys = Object.keys(multiConstraint[0]);\n assert(keys.length > 0, 'multiConstraint entries must have at least one key');\n // Subsequent entries must share the first entry's shape — the SQL form\n // is `(col_a, col_b, …) IN VALUES (…)`, with one binding per key per\n // entry. Heterogeneous keys would silently produce incorrect bindings.\n for (let i = 1; i < multiConstraint.length; i++) {\n const entry = multiConstraint[i];\n assert(\n Object.keys(entry).length === keys.length && keys.every(k => k in entry),\n () =>\n `multiConstraint entries must share the same keys (entry 0: [${keys.join(\n ',',\n )}], entry ${i}: [${Object.keys(entry).join(',')}])`,\n );\n }\n\n if (keys.length === 1) {\n const key = keys[0];\n const colType = columns[key].type;\n return sql`${sql.ident(key)} IN (${sql.join(\n multiConstraint.map(c => sql`${toSQLiteType(c[key], colType)}`),\n sql`,`,\n )})`;\n }\n\n // Compound: `(col_a, col_b, …) IN (VALUES (?, ?, …), …)`\n const colList = sql`(${sql.join(\n keys.map(k => sql.ident(k)),\n sql`,`,\n )})`;\n const rows = multiConstraint.map(\n c =>\n sql`(${sql.join(\n keys.map(k => sql`${toSQLiteType(c[k], columns[k].type)}`),\n sql`,`,\n )})`,\n );\n return sql`${colList} IN (VALUES ${sql.join(rows, sql`,`)})`;\n}\n\nexport function orderByToSQL(order: Ordering, reverse: boolean): SQLQuery {\n if (reverse) {\n return sql`ORDER BY ${sql.join(\n order.map(\n s =>\n sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(\n s[1] === 'asc' ? 'desc' : 'asc',\n )}`,\n ),\n sql`, `,\n )}`;\n } else {\n return sql`ORDER BY ${sql.join(\n order.map(\n s => sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(s[1])}`,\n ),\n sql`, `,\n )}`;\n }\n}\n\n/**\n * Converts filters (conditions) to SQL WHERE clause.\n * This applies all filters present in the AST for a query to the source.\n */\nexport function filtersToSQL(filters: NoSubqueryCondition): SQLQuery {\n switch (filters.type) {\n case 'simple':\n return simpleConditionToSQL(filters);\n case 'and':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` AND `,\n )})`\n : sql`TRUE`;\n case 'or':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` OR `,\n )})`\n : sql`FALSE`;\n }\n}\n\nfunction simpleConditionToSQL(filter: SimpleCondition): SQLQuery {\n const {op} = filter;\n if (op === 'IN' || op === 'NOT IN') {\n switch (filter.right.type) {\n case 'literal':\n return sql`${valuePositionToSQL(\n filter.left,\n )} ${sql.__dangerous__rawValue(\n filter.op,\n )} (SELECT value FROM json_each(${JSON.stringify(\n filter.right.value,\n )}))`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n }\n if (\n op === 'LIKE' ||\n op === 'NOT LIKE' ||\n op === 'ILIKE' ||\n op === 'NOT ILIKE'\n ) {\n return likeConditionToSQL(filter);\n }\n\n if (\n (op === 'IS' || op === 'IS NOT') &&\n filter.right.type === 'literal' &&\n filter.right.value === null\n ) {\n return sql`${valuePositionToSQL(filter.left)} ${sql.__dangerous__rawValue(\n op,\n )} NULL`;\n }\n\n return sql`${valuePositionToSQL(filter.left)} ${sql.__dangerous__rawValue(\n filter.op,\n )} ${valuePositionToSQL(filter.right)}`;\n}\n\nfunction likeConditionToSQL(filter: SimpleCondition): SQLQuery {\n const {op} = filter;\n // Mirror Postgres pattern-matching semantics:\n // * LIKE is case-sensitive. The replica connection runs with\n // `PRAGMA case_sensitive_like = ON` (see db.ts), so the bare LIKE\n // operator is case-sensitive.\n // * ILIKE is case-insensitive. We lower() both operands using the\n // Unicode-aware lower() that @rocicorp/zero-sqlite3 provides via ICU,\n // mirroring the toLowerCase() used by the in-memory IVM matcher\n // (see zql/src/builder/like.ts).\n // * Backslash is the default escape character in Postgres and in the IVM\n // matcher, but SQLite has no default, so we specify `ESCAPE '\\'`\n // explicitly. The SQL literal '\\' is a single backslash (SQLite does not\n // process backslash escapes inside string literals).\n const caseInsensitive = op === 'ILIKE' || op === 'NOT ILIKE';\n const negated = op === 'NOT LIKE' || op === 'NOT ILIKE';\n const likeOp = sql.__dangerous__rawValue(negated ? 'NOT LIKE' : 'LIKE');\n\n const left = valuePositionToSQL(filter.left);\n const right = valuePositionToSQL(filter.right);\n if (caseInsensitive) {\n return sql`lower(${left}) ${likeOp} lower(${right}) ESCAPE '\\\\'`;\n }\n return sql`${left} ${likeOp} ${right} ESCAPE '\\\\'`;\n}\n\nfunction valuePositionToSQL(value: ValuePosition): SQLQuery {\n switch (value.type) {\n case 'column':\n return sql.ident(value.name);\n case 'literal':\n return sql`${toSQLiteType(value.value, getJsType(value.value))}`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n}\n\nfunction getJsType(value: unknown): ValueType {\n if (value === null) {\n return 'null';\n }\n return typeof value === 'string'\n ? 'string'\n : typeof value === 'number'\n ? 'number'\n : typeof value === 'boolean'\n ? 'boolean'\n : 'json';\n}\n\nexport function toSQLiteType(v: unknown, type: ValueType): unknown {\n switch (type) {\n case 'boolean':\n return v === null ? null : v ? 1 : 0;\n case 'number':\n case 'string':\n case 'null':\n return v;\n case 'json':\n return JSON.stringify(v);\n }\n}\n\nfunction nullableAwareEquality(\n field: string,\n value: unknown,\n columnType: SchemaValue,\n): SQLQuery {\n if (value === null) {\n // A NULL bound value proves the column is nullable regardless of the\n // column metadata, and `=` never matches NULL — `IS` selects the NULL\n // tie-break group a cursor anchored on a NULL value needs.\n return sql`${sql.ident(field)} IS NULL`;\n }\n // Use = instead of IS for non-nullable columns to enable better\n // index usage in SQLite.\n return columnType.optional === true\n ? sql`${sql.ident(field)} IS ${value}`\n : sql`${sql.ident(field)} = ${value}`;\n}\n\nfunction nullableAwareRangeComparison(\n field: string,\n value: unknown,\n operator: '>' | '<',\n columnType: SchemaValue,\n /**\n * Whether an entailed bound ANDed in front of the disjunction already\n * excludes the column's NULL group, which a second SELECT then fetches (see\n * {@link gatherStartConstraints}).\n */\n nullGroupExcluded: boolean,\n): SQLQuery {\n if (value === null) {\n return operator === '>' ? sql`${sql.ident(field)} IS NOT NULL` : sql`FALSE`;\n }\n\n // For non-nullable columns, skip IS NULL checks to avoid breaking\n // SQLite's MULTI-INDEX OR optimization, which falls back to a full\n // table scan when any OR branch involves NULL.\n // See: https://github.com/rocicorp/mono/pull/5542\n const comparison = sql`${sql.ident(field)} ${sql.__dangerous__rawValue(\n operator,\n )} ${value}`;\n if (columnType.optional !== true) {\n return comparison;\n }\n\n // The bound is non-NULL here. NULLs sort before every non-NULL value, so\n // `>` already excludes them and needs no guard, while `<` must admit the\n // NULL group explicitly — a bare `col < ?` would silently drop NULL rows\n // from a backward walk. Unless the NULL group is excluded and fetched on its\n // own: the guard is dead then, and a NULL inside an OR is what costs SQLite\n // the MULTI-INDEX OR optimization in the first place.\n return operator === '>' || nullGroupExcluded\n ? comparison\n : sql`(${sql.ident(field)} IS NULL OR ${comparison})`;\n}\n\ntype SargableLeadingStartBound = {\n readonly bound: SQLQuery;\n /**\n * Whether `bound` excludes the column's NULL group, which belongs to the\n * rows the start walks over and must then be fetched separately.\n */\n readonly excludesNullGroup: boolean;\n};\n\nfunction sargableLeadingStartBound(\n field: string,\n value: unknown,\n operator: '>' | '<',\n columnType: SchemaValue,\n): SargableLeadingStartBound | undefined {\n // A NULL bound value proves the column is nullable regardless of the\n // column metadata, and a bare range bound is not sound there: `col >= NULL`\n // is never true, so instead of being redundant it would annihilate the\n // whole start constraint.\n if (value === null) {\n return undefined;\n }\n\n const inclusiveOperator = operator === '>' ? '>=' : '<=';\n return {\n bound: sql`${sql.ident(field)} ${sql.__dangerous__rawValue(\n inclusiveOperator,\n )} ${value}`,\n // For `>`, NULLs sort before the non-NULL bound, so `col >= value` remains\n // sound on a nullable column. For `<`, the rows before the bound include\n // the whole NULL group, which `col <= value` drops. SQLite cannot seek\n // `col IS NULL OR col <= value` as one index range, so the caller fetches\n // that group separately.\n excludesNullGroup: columnType.optional === true && operator === '<',\n };\n}\n\n/**\n * The ordering could be complex such as:\n * `ORDER BY a ASC, b DESC, c ASC`\n *\n * In those cases, we need to encode the constraints as various\n * `OR` clauses.\n *\n * E.g.,\n *\n * to get the row after (a = 1, b = 2, c = 3) would be:\n *\n * `WHERE a > 1 OR (a = 1 AND b < 2) OR (a = 1 AND b = 2 AND c > 3)`\n *\n * - after vs before flips the comparison operators.\n * - inclusive adds a final `OR` clause for the exact match.\n *\n * SQLite cannot seek an index with that disjunction, so a redundant, entailed\n * bound on the leading column is ANDed in front of it (e.g. `a >= 1 AND ...`).\n *\n * When the leading column is nullable and the walk heads towards lower values\n * (`<`), the rows the start walks over include the whole NULL group, which\n * that bound drops. `excludesNullGroup` is set then, and the group is left out\n * of the disjunction too — every row the returned constraint admits has\n * `a <= ?`, so it and `a IS NULL` partition those rows, and the caller fetches\n * the NULL group with a second SELECT.\n */\nfunction gatherStartConstraints(\n start: Start,\n reverse: boolean | undefined,\n order: Ordering,\n columnTypes: Record<string, SchemaValue>,\n): {readonly constraint: SQLQuery; readonly excludesNullGroup: boolean} {\n const constraints: SQLQuery[] = [];\n const {row: from, basis} = start;\n let leadingBound: SargableLeadingStartBound | undefined;\n\n for (let i = 0; i < order.length; i++) {\n const group: SQLQuery[] = [];\n const [iField, iDirection] = order[i];\n for (let j = 0; j <= i; j++) {\n if (j === i) {\n const columnType = columnTypes[iField];\n const constraintValue = toSQLiteType(\n from[iField] ?? null,\n columnType.type,\n );\n const operator =\n iDirection === 'asc' ? (reverse ? '<' : '>') : reverse ? '>' : '<';\n let nullGroupExcluded = false;\n if (i === 0) {\n leadingBound = sargableLeadingStartBound(\n iField,\n constraintValue,\n operator,\n columnType,\n );\n nullGroupExcluded = leadingBound?.excludesNullGroup === true;\n }\n group.push(\n nullableAwareRangeComparison(\n iField,\n constraintValue,\n operator,\n columnType,\n nullGroupExcluded,\n ),\n );\n } else {\n const [jField] = order[j];\n const columnType = columnTypes[jField];\n const value = toSQLiteType(from[jField] ?? null, columnType.type);\n group.push(nullableAwareEquality(jField, value, columnType));\n }\n }\n constraints.push(sql`(${sql.join(group, sql` AND `)})`);\n }\n\n if (basis === 'at') {\n constraints.push(\n sql`(${sql.join(\n order.map(([field]) => {\n const columnType = columnTypes[field];\n const value = toSQLiteType(from[field] ?? null, columnType.type);\n return nullableAwareEquality(field, value, columnType);\n }),\n sql` AND `,\n )})`,\n );\n }\n\n const lexicographicStart = sql`(${sql.join(constraints, sql` OR `)})`;\n if (leadingBound === undefined) {\n return {constraint: lexicographicStart, excludesNullGroup: false};\n }\n return {\n constraint: sql`(${leadingBound.bound} AND ${lexicographicStart})`,\n excludesNullGroup: leadingBound.excludesNullGroup,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"query-builder.js","names":[],"sources":["../../../../zqlite/src/query-builder.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport {assert} from '../../shared/src/asserts.ts';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport type {Constraint} from '../../zql/src/ivm/constraint.ts';\nimport type {\n FetchRequest,\n MultiConstraint,\n Start,\n} from '../../zql/src/ivm/operator.ts';\nimport {sql} from './internal/sql.ts';\n\n/**\n * Condition type without correlated subqueries.\n * This matches the output of transformFilters from zql/builder/filter.ts\n */\nexport type NoSubqueryCondition = Exclude<\n Condition,\n {type: 'correlatedSubquery'}\n>;\n\n/**\n * Builds the SELECTs for a fetch. Running them in order and concatenating\n * their rows gives the fetch's rows, in order.\n *\n * There is a second SELECT only when the fetch starts at a non-NULL value of a\n * nullable leading sort column and walks towards its NULL group. The first\n * SELECT then leaves that group out so that SQLite can seek it (see\n * `gatherStartConstraints`), and the second fetches the group. NULLs sort\n * before every other value, so the group follows all of the first SELECT's\n * rows, and a fetch that stops early never needs to run the second.\n */\nexport function buildSelectQueries(\n tableName: string,\n columns: Record<string, SchemaValue>,\n constraint: Constraint | undefined,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n reverse: boolean | undefined,\n start: Start | undefined,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n fetchFilters?: NoSubqueryCondition | undefined,\n): [SQLQuery] | [SQLQuery, SQLQuery] {\n const select = sql`SELECT ${sql.join(\n Object.keys(columns).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(tableName)}`;\n const leading: SQLQuery[] = constraintsToSQL(constraint, columns);\n\n if (multiConstraints) {\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n leading.push(multiConstraintToSQL(mc, columns));\n }\n }\n }\n\n const trailing: SQLQuery[] = [];\n if (filters) {\n trailing.push(filtersToSQL(filters));\n }\n\n if (fetchFilters) {\n trailing.push(filtersToSQL(fetchFilters));\n }\n\n const orderBy =\n order && order.length > 0 ? orderByToSQL(order, !!reverse) : undefined;\n const selectWhere = (constraints: SQLQuery[]) => {\n const query =\n constraints.length > 0\n ? sql`${select} WHERE ${sql.join(constraints, sql` AND `)}`\n : select;\n return orderBy ? sql`${query} ${orderBy}` : query;\n };\n\n if (!start) {\n return [selectWhere([...leading, ...trailing])];\n }\n\n assert(order !== undefined, 'start requires ordering');\n const {constraint: startConstraint, excludesNullGroup} =\n gatherStartConstraints(start, reverse, order, columns);\n const query = selectWhere([...leading, startConstraint, ...trailing]);\n const [leadingField] = order[0];\n if (\n !excludesNullGroup ||\n // The rest of the fetch would reject every row of the NULL group anyway.\n constraintsRejectNull(constraint, multiConstraints, leadingField) ||\n rejectsNull(filters, leadingField) ||\n rejectsNull(fetchFilters, leadingField)\n ) {\n return [query];\n }\n return [\n query,\n selectWhere([\n ...leading,\n sql`${sql.ident(leadingField)} IS NULL`,\n ...trailing,\n ]),\n ];\n}\n\n/**\n * {@link buildSelectQueries} for a fetch that needs only one SELECT, such as\n * one without a start row.\n */\nexport function buildSelectQuery(\n ...args: Parameters<typeof buildSelectQueries>\n): SQLQuery {\n const [query, nullGroup] = buildSelectQueries(...args);\n assert(\n nullGroup === undefined,\n 'The fetch needs a second SELECT for its NULL group; use buildSelectQueries',\n );\n return query;\n}\n\n/**\n * Whether `condition` is never true for a row whose `field` is NULL. Only a\n * comparison of `field` itself, alone or ANDed with other conditions, is\n * recognized. Every operator yields NULL for a NULL operand except `IS`,\n * `IS NOT` and `NOT IN`, which is true for an empty list.\n */\nfunction rejectsNull(\n condition: NoSubqueryCondition | undefined,\n field: string,\n): boolean {\n switch (condition?.type) {\n case undefined:\n case 'or':\n return false;\n case 'and':\n return condition.conditions.some(c =>\n rejectsNull(c as NoSubqueryCondition, field),\n );\n case 'simple': {\n const {op, left, right} = condition;\n if (left.type !== 'column' || left.name !== field) {\n return false;\n }\n switch (op) {\n case 'IS':\n return right.type === 'literal' && right.value !== null;\n case 'IS NOT':\n return right.type === 'literal' && right.value === null;\n case 'NOT IN':\n return false;\n default:\n return true;\n }\n }\n }\n}\n\n/**\n * Whether the constraints of the fetch are never true for a row whose `field`\n * is NULL. {@link constraintsToSQL} compares the field with `=` and\n * {@link multiConstraintToSQL} with `IN`, neither of which is ever true for a\n * NULL operand, so the field being constrained at all is enough.\n */\nfunction constraintsRejectNull(\n constraint: Constraint | undefined,\n multiConstraints: readonly MultiConstraint[] | undefined,\n field: string,\n): boolean {\n if (constraint && field in constraint) {\n return true;\n }\n // `multiConstraintToSQL` takes its key list from the first entry and asserts\n // that the rest match it.\n return multiConstraints?.some(mc => mc.length > 0 && field in mc[0]) === true;\n}\n\nexport function constraintsToSQL(\n constraint: Constraint | undefined,\n columns: Record<string, SchemaValue>,\n) {\n if (!constraint) {\n return [];\n }\n\n const constraints: SQLQuery[] = [];\n for (const [key, value] of Object.entries(constraint)) {\n constraints.push(\n sql`${sql.ident(key)} = ${toSQLiteBinding(value, columns[key].type)}`,\n );\n }\n\n return constraints;\n}\n\n/**\n * Builds a single batched IN clause from a `MultiConstraint`. All entries\n * are assumed to share the same shape (the keys of the first entry);\n * FlippedJoin derives them from the same parentKey for all children.\n *\n * Single-column form: `col IN (?, ?, ?)`\n * Compound form: `(a, b) IN (VALUES (?, ?), (?, ?), …)`\n *\n * NOTE: SQLite optimizes `col IN (literal-list)` using the column's index;\n * verified via EXPLAIN QUERY PLAN — see query-builder.test.ts.\n */\nexport function multiConstraintToSQL(\n multiConstraint: MultiConstraint,\n columns: Record<string, SchemaValue>,\n): SQLQuery {\n assert(multiConstraint.length > 0, 'multiConstraint must be non-empty');\n // All entries share the same keys; pull the column list from the first.\n const keys = Object.keys(multiConstraint[0]);\n assert(keys.length > 0, 'multiConstraint entries must have at least one key');\n // Subsequent entries must share the first entry's shape — the SQL form\n // is `(col_a, col_b, …) IN VALUES (…)`, with one binding per key per\n // entry. Heterogeneous keys would silently produce incorrect bindings.\n for (let i = 1; i < multiConstraint.length; i++) {\n const entry = multiConstraint[i];\n assert(\n Object.keys(entry).length === keys.length && keys.every(k => k in entry),\n () =>\n `multiConstraint entries must share the same keys (entry 0: [${keys.join(\n ',',\n )}], entry ${i}: [${Object.keys(entry).join(',')}])`,\n );\n }\n\n if (keys.length === 1) {\n const key = keys[0];\n const colType = columns[key].type;\n return sql`${sql.ident(key)} IN (${sql.join(\n multiConstraint.map(c => sql`${toSQLiteBinding(c[key], colType)}`),\n sql`,`,\n )})`;\n }\n\n // Compound: `(col_a, col_b, …) IN (VALUES (?, ?, …), …)`\n const colList = sql`(${sql.join(\n keys.map(k => sql.ident(k)),\n sql`,`,\n )})`;\n const rows = multiConstraint.map(\n c =>\n sql`(${sql.join(\n keys.map(k => sql`${toSQLiteBinding(c[k], columns[k].type)}`),\n sql`,`,\n )})`,\n );\n return sql`${colList} IN (VALUES ${sql.join(rows, sql`,`)})`;\n}\n\nexport function orderByToSQL(order: Ordering, reverse: boolean): SQLQuery {\n if (reverse) {\n return sql`ORDER BY ${sql.join(\n order.map(\n s =>\n sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(\n s[1] === 'asc' ? 'desc' : 'asc',\n )}`,\n ),\n sql`, `,\n )}`;\n } else {\n return sql`ORDER BY ${sql.join(\n order.map(\n s => sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(s[1])}`,\n ),\n sql`, `,\n )}`;\n }\n}\n\n/**\n * Converts filters (conditions) to SQL WHERE clause.\n * This applies all filters present in the AST for a query to the source.\n */\nexport function filtersToSQL(filters: NoSubqueryCondition): SQLQuery {\n switch (filters.type) {\n case 'simple':\n return simpleConditionToSQL(filters);\n case 'and':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` AND `,\n )})`\n : sql`TRUE`;\n case 'or':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` OR `,\n )})`\n : sql`FALSE`;\n }\n}\n\nfunction simpleConditionToSQL(filter: SimpleCondition): SQLQuery {\n const {op} = filter;\n if (op === 'IN' || op === 'NOT IN') {\n switch (filter.right.type) {\n case 'literal':\n return sql`${valuePositionToSQL(\n filter.left,\n )} ${sql.__dangerous__rawValue(\n filter.op,\n )} (SELECT value FROM json_each(${JSON.stringify(\n filter.right.value,\n )}))`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n }\n if (\n op === 'LIKE' ||\n op === 'NOT LIKE' ||\n op === 'ILIKE' ||\n op === 'NOT ILIKE'\n ) {\n return likeConditionToSQL(filter);\n }\n\n if (\n (op === 'IS' || op === 'IS NOT') &&\n filter.right.type === 'literal' &&\n filter.right.value === null\n ) {\n return sql`${valuePositionToSQL(filter.left)} ${sql.__dangerous__rawValue(\n op,\n )} NULL`;\n }\n\n return sql`${valuePositionToSQL(filter.left)} ${sql.__dangerous__rawValue(\n filter.op,\n )} ${valuePositionToSQL(filter.right)}`;\n}\n\nfunction likeConditionToSQL(filter: SimpleCondition): SQLQuery {\n const {op} = filter;\n // Mirror Postgres pattern-matching semantics:\n // * LIKE is case-sensitive. The replica connection runs with\n // `PRAGMA case_sensitive_like = ON` (see db.ts), so the bare LIKE\n // operator is case-sensitive.\n // * ILIKE is case-insensitive. We lower() both operands using the\n // Unicode-aware lower() that @rocicorp/zero-sqlite3 provides via ICU,\n // mirroring the toLowerCase() used by the in-memory IVM matcher\n // (see zql/src/builder/like.ts).\n // * Backslash is the default escape character in Postgres and in the IVM\n // matcher, but SQLite has no default, so we specify `ESCAPE '\\'`\n // explicitly. The SQL literal '\\' is a single backslash (SQLite does not\n // process backslash escapes inside string literals).\n const caseInsensitive = op === 'ILIKE' || op === 'NOT ILIKE';\n const negated = op === 'NOT LIKE' || op === 'NOT ILIKE';\n const likeOp = sql.__dangerous__rawValue(negated ? 'NOT LIKE' : 'LIKE');\n\n const left = valuePositionToSQL(filter.left);\n const right = valuePositionToSQL(filter.right);\n if (caseInsensitive) {\n return sql`lower(${left}) ${likeOp} lower(${right}) ESCAPE '\\\\'`;\n }\n return sql`${left} ${likeOp} ${right} ESCAPE '\\\\'`;\n}\n\nfunction valuePositionToSQL(value: ValuePosition): SQLQuery {\n switch (value.type) {\n case 'column':\n return sql.ident(value.name);\n case 'literal':\n return sql`${toSQLiteType(value.value, getJsType(value.value))}`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n}\n\nfunction getJsType(value: unknown): ValueType {\n if (value === null) {\n return 'null';\n }\n return typeof value === 'string'\n ? 'string'\n : typeof value === 'number'\n ? 'number'\n : typeof value === 'boolean'\n ? 'boolean'\n : 'json';\n}\n\nexport function toSQLiteType(v: unknown, type: ValueType): unknown {\n switch (type) {\n case 'boolean':\n return v === null ? null : v ? 1 : 0;\n case 'number':\n case 'string':\n case 'null':\n return v;\n case 'json':\n return JSON.stringify(v);\n }\n}\n\n/**\n * A placeholder for a fetch's value that {@link buildSelectQueries} passes\n * through to the bindings of its SELECTs, where `toSQLiteType` would convert\n * the value itself. It lets a fetch's SQL text be built once and reused for\n * every fetch of the same shape (see `fetch-sql.ts`).\n */\nexport class Binding {\n readonly get: (req: FetchRequest) => unknown;\n constructor(get: (req: FetchRequest) => unknown) {\n this.get = get;\n }\n}\n\nfunction toSQLiteBinding(v: unknown, type: ValueType): unknown {\n return v instanceof Binding ? v : toSQLiteType(v, type);\n}\n\nfunction nullableAwareEquality(\n field: string,\n value: unknown,\n columnType: SchemaValue,\n): SQLQuery {\n if (value === null) {\n // A NULL bound value proves the column is nullable regardless of the\n // column metadata, and `=` never matches NULL — `IS` selects the NULL\n // tie-break group a cursor anchored on a NULL value needs.\n return sql`${sql.ident(field)} IS NULL`;\n }\n // Use = instead of IS for non-nullable columns to enable better\n // index usage in SQLite.\n return columnType.optional === true\n ? sql`${sql.ident(field)} IS ${value}`\n : sql`${sql.ident(field)} = ${value}`;\n}\n\nfunction nullableAwareRangeComparison(\n field: string,\n value: unknown,\n operator: '>' | '<',\n columnType: SchemaValue,\n /**\n * Whether an entailed bound ANDed in front of the disjunction already\n * excludes the column's NULL group, which a second SELECT then fetches (see\n * {@link gatherStartConstraints}).\n */\n nullGroupExcluded: boolean,\n): SQLQuery {\n if (value === null) {\n return operator === '>' ? sql`${sql.ident(field)} IS NOT NULL` : sql`FALSE`;\n }\n\n // For non-nullable columns, skip IS NULL checks to avoid breaking\n // SQLite's MULTI-INDEX OR optimization, which falls back to a full\n // table scan when any OR branch involves NULL.\n // See: https://github.com/rocicorp/mono/pull/5542\n const comparison = sql`${sql.ident(field)} ${sql.__dangerous__rawValue(\n operator,\n )} ${value}`;\n if (columnType.optional !== true) {\n return comparison;\n }\n\n // The bound is non-NULL here. NULLs sort before every non-NULL value, so\n // `>` already excludes them and needs no guard, while `<` must admit the\n // NULL group explicitly — a bare `col < ?` would silently drop NULL rows\n // from a backward walk. Unless the NULL group is excluded and fetched on its\n // own: the guard is dead then, and a NULL inside an OR is what costs SQLite\n // the MULTI-INDEX OR optimization in the first place.\n return operator === '>' || nullGroupExcluded\n ? comparison\n : sql`(${sql.ident(field)} IS NULL OR ${comparison})`;\n}\n\ntype SargableLeadingStartBound = {\n readonly bound: SQLQuery;\n /**\n * Whether `bound` excludes the column's NULL group, which belongs to the\n * rows the start walks over and must then be fetched separately.\n */\n readonly excludesNullGroup: boolean;\n};\n\nfunction sargableLeadingStartBound(\n field: string,\n value: unknown,\n operator: '>' | '<',\n columnType: SchemaValue,\n): SargableLeadingStartBound | undefined {\n // A NULL bound value proves the column is nullable regardless of the\n // column metadata, and a bare range bound is not sound there: `col >= NULL`\n // is never true, so instead of being redundant it would annihilate the\n // whole start constraint.\n if (value === null) {\n return undefined;\n }\n\n const inclusiveOperator = operator === '>' ? '>=' : '<=';\n return {\n bound: sql`${sql.ident(field)} ${sql.__dangerous__rawValue(\n inclusiveOperator,\n )} ${value}`,\n // For `>`, NULLs sort before the non-NULL bound, so `col >= value` remains\n // sound on a nullable column. For `<`, the rows before the bound include\n // the whole NULL group, which `col <= value` drops. SQLite cannot seek\n // `col IS NULL OR col <= value` as one index range, so the caller fetches\n // that group separately.\n excludesNullGroup: columnType.optional === true && operator === '<',\n };\n}\n\n/**\n * The ordering could be complex such as:\n * `ORDER BY a ASC, b DESC, c ASC`\n *\n * In those cases, we need to encode the constraints as various\n * `OR` clauses.\n *\n * E.g.,\n *\n * to get the row after (a = 1, b = 2, c = 3) would be:\n *\n * `WHERE a > 1 OR (a = 1 AND b < 2) OR (a = 1 AND b = 2 AND c > 3)`\n *\n * - after vs before flips the comparison operators.\n * - inclusive adds a final `OR` clause for the exact match.\n *\n * SQLite cannot seek an index with that disjunction, so a redundant, entailed\n * bound on the leading column is ANDed in front of it (e.g. `a >= 1 AND ...`).\n *\n * When the leading column is nullable and the walk heads towards lower values\n * (`<`), the rows the start walks over include the whole NULL group, which\n * that bound drops. `excludesNullGroup` is set then, and the group is left out\n * of the disjunction too — every row the returned constraint admits has\n * `a <= ?`, so it and `a IS NULL` partition those rows, and the caller fetches\n * the NULL group with a second SELECT.\n */\nfunction gatherStartConstraints(\n start: Start,\n reverse: boolean | undefined,\n order: Ordering,\n columnTypes: Record<string, SchemaValue>,\n): {readonly constraint: SQLQuery; readonly excludesNullGroup: boolean} {\n const constraints: SQLQuery[] = [];\n const {row: from, basis} = start;\n let leadingBound: SargableLeadingStartBound | undefined;\n\n for (let i = 0; i < order.length; i++) {\n const group: SQLQuery[] = [];\n const [iField, iDirection] = order[i];\n for (let j = 0; j <= i; j++) {\n if (j === i) {\n const columnType = columnTypes[iField];\n const constraintValue = toSQLiteBinding(\n from[iField] ?? null,\n columnType.type,\n );\n const operator =\n iDirection === 'asc' ? (reverse ? '<' : '>') : reverse ? '>' : '<';\n let nullGroupExcluded = false;\n if (i === 0) {\n leadingBound = sargableLeadingStartBound(\n iField,\n constraintValue,\n operator,\n columnType,\n );\n nullGroupExcluded = leadingBound?.excludesNullGroup === true;\n }\n group.push(\n nullableAwareRangeComparison(\n iField,\n constraintValue,\n operator,\n columnType,\n nullGroupExcluded,\n ),\n );\n } else {\n const [jField] = order[j];\n const columnType = columnTypes[jField];\n const value = toSQLiteBinding(from[jField] ?? null, columnType.type);\n group.push(nullableAwareEquality(jField, value, columnType));\n }\n }\n constraints.push(sql`(${sql.join(group, sql` AND `)})`);\n }\n\n if (basis === 'at') {\n constraints.push(\n sql`(${sql.join(\n order.map(([field]) => {\n const columnType = columnTypes[field];\n const value = toSQLiteBinding(from[field] ?? null, columnType.type);\n return nullableAwareEquality(field, value, columnType);\n }),\n sql` AND `,\n )})`,\n );\n }\n\n const lexicographicStart = sql`(${sql.join(constraints, sql` OR `)})`;\n if (leadingBound === undefined) {\n return {constraint: lexicographicStart, excludesNullGroup: false};\n }\n return {\n constraint: sql`(${leadingBound.bound} AND ${lexicographicStart})`,\n excludesNullGroup: leadingBound.excludesNullGroup,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAwCA,SAAgB,mBACd,WACA,SACA,YACA,SACA,OACA,SACA,OACA,kBACA,cACmC;CACnC,MAAM,SAAS,GAAG,UAAU,IAAI,KAC9B,OAAO,KAAK,OAAO,EAAE,KAAI,MAAK,IAAI,MAAM,CAAC,CAAC,GAC1C,GAAG,GACL,EAAE,QAAQ,IAAI,MAAM,SAAS;CAC7B,MAAM,UAAsB,iBAAiB,YAAY,OAAO;CAEhE,IAAI;OACG,MAAM,MAAM,kBACf,IAAI,GAAG,SAAS,GACd,QAAQ,KAAK,qBAAqB,IAAI,OAAO,CAAC;CAAA;CAKpD,MAAM,WAAuB,CAAC;CAC9B,IAAI,SACF,SAAS,KAAK,aAAa,OAAO,CAAC;CAGrC,IAAI,cACF,SAAS,KAAK,aAAa,YAAY,CAAC;CAG1C,MAAM,UACJ,SAAS,MAAM,SAAS,IAAI,aAAa,OAAO,CAAC,CAAC,OAAO,IAAI,KAAA;CAC/D,MAAM,eAAe,gBAA4B;EAC/C,MAAM,QACJ,YAAY,SAAS,IACjB,GAAG,GAAG,OAAO,SAAS,IAAI,KAAK,aAAa,GAAG,OAAO,MACtD;EACN,OAAO,UAAU,GAAG,GAAG,MAAM,GAAG,YAAY;CAC9C;CAEA,IAAI,CAAC,OACH,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;CAGhD,OAAO,UAAU,KAAA,GAAW,yBAAyB;CACrD,MAAM,EAAC,YAAY,iBAAiB,sBAClC,uBAAuB,OAAO,SAAS,OAAO,OAAO;CACvD,MAAM,QAAQ,YAAY;EAAC,GAAG;EAAS;EAAiB,GAAG;CAAQ,CAAC;CACpE,MAAM,CAAC,gBAAgB,MAAM;CAC7B,IACE,CAAC,qBAED,sBAAsB,YAAY,kBAAkB,YAAY,KAChE,YAAY,SAAS,YAAY,KACjC,YAAY,cAAc,YAAY,GAEtC,OAAO,CAAC,KAAK;CAEf,OAAO,CACL,OACA,YAAY;EACV,GAAG;EACH,GAAG,GAAG,IAAI,MAAM,YAAY,EAAE;EAC9B,GAAG;CACL,CAAC,CACH;AACF;;;;;AAMA,SAAgB,iBACd,GAAG,MACO;CACV,MAAM,CAAC,OAAO,aAAa,mBAAmB,GAAG,IAAI;CACrD,OACE,cAAc,KAAA,GACd,4EACF;CACA,OAAO;AACT;;;;;;;AAQA,SAAS,YACP,WACA,OACS;CACT,QAAQ,WAAW,MAAnB;EACE,KAAK,KAAA;EACL,KAAK,MACH,OAAO;EACT,KAAK,OACH,OAAO,UAAU,WAAW,MAAK,MAC/B,YAAY,GAA0B,KAAK,CAC7C;EACF,KAAK,UAAU;GACb,MAAM,EAAC,IAAI,MAAM,UAAS;GAC1B,IAAI,KAAK,SAAS,YAAY,KAAK,SAAS,OAC1C,OAAO;GAET,QAAQ,IAAR;IACE,KAAK,MACH,OAAO,MAAM,SAAS,aAAa,MAAM,UAAU;IACrD,KAAK,UACH,OAAO,MAAM,SAAS,aAAa,MAAM,UAAU;IACrD,KAAK,UACH,OAAO;IACT,SACE,OAAO;GACX;EACF;CACF;AACF;;;;;;;AAQA,SAAS,sBACP,YACA,kBACA,OACS;CACT,IAAI,cAAc,SAAS,YACzB,OAAO;CAIT,OAAO,kBAAkB,MAAK,OAAM,GAAG,SAAS,KAAK,SAAS,GAAG,EAAE,MAAM;AAC3E;AAEA,SAAgB,iBACd,YACA,SACA;CACA,IAAI,CAAC,YACH,OAAO,CAAC;CAGV,MAAM,cAA0B,CAAC;CACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,GAClD,YAAY,KACV,GAAG,GAAG,IAAI,MAAM,GAAG,EAAE,KAAK,gBAAgB,OAAO,QAAQ,KAAK,IAAI,GACpE;CAGF,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,qBACd,iBACA,SACU;CACV,OAAO,gBAAgB,SAAS,GAAG,mCAAmC;CAEtE,MAAM,OAAO,OAAO,KAAK,gBAAgB,EAAE;CAC3C,OAAO,KAAK,SAAS,GAAG,oDAAoD;CAI5E,KAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;EAC/C,MAAM,QAAQ,gBAAgB;EAC9B,OACE,OAAO,KAAK,KAAK,EAAE,WAAW,KAAK,UAAU,KAAK,OAAM,MAAK,KAAK,KAAK,SAErE,+DAA+D,KAAK,KAClE,GACF,EAAE,WAAW,EAAE,KAAK,OAAO,KAAK,KAAK,EAAE,KAAK,GAAG,EAAE,GACrD;CACF;CAEA,IAAI,KAAK,WAAW,GAAG;EACrB,MAAM,MAAM,KAAK;EACjB,MAAM,UAAU,QAAQ,KAAK;EAC7B,OAAO,GAAG,GAAG,IAAI,MAAM,GAAG,EAAE,OAAO,IAAI,KACrC,gBAAgB,KAAI,MAAK,GAAG,GAAG,gBAAgB,EAAE,MAAM,OAAO,GAAG,GACjE,GAAG,GACL,EAAE;CACJ;CAGA,MAAM,UAAU,GAAG,IAAI,IAAI,KACzB,KAAK,KAAI,MAAK,IAAI,MAAM,CAAC,CAAC,GAC1B,GAAG,GACL,EAAE;CACF,MAAM,OAAO,gBAAgB,KAC3B,MACE,GAAG,IAAI,IAAI,KACT,KAAK,KAAI,MAAK,GAAG,GAAG,gBAAgB,EAAE,IAAI,QAAQ,GAAG,IAAI,GAAG,GAC5D,GAAG,GACL,EAAE,EACN;CACA,OAAO,GAAG,GAAG,QAAQ,cAAc,IAAI,KAAK,MAAM,GAAG,GAAG,EAAE;AAC5D;AAEA,SAAgB,aAAa,OAAiB,SAA4B;CACxE,IAAI,SACF,OAAO,GAAG,YAAY,IAAI,KACxB,MAAM,KACJ,MACE,GAAG,GAAG,IAAI,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,sBAC3B,EAAE,OAAO,QAAQ,SAAS,KAC5B,GACJ,GACA,GAAG,IACL;MAEA,OAAO,GAAG,YAAY,IAAI,KACxB,MAAM,KACJ,MAAK,GAAG,GAAG,IAAI,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,sBAAsB,EAAE,EAAE,GAC9D,GACA,GAAG,IACL;AAEJ;;;;;AAMA,SAAgB,aAAa,SAAwC;CACnE,QAAQ,QAAQ,MAAhB;EACE,KAAK,UACH,OAAO,qBAAqB,OAAO;EACrC,KAAK,OACH,OAAO,QAAQ,WAAW,SAAS,IAC/B,GAAG,IAAI,IAAI,KACT,QAAQ,WAAW,KAAI,cACrB,aAAa,SAAgC,CAC/C,GACA,GAAG,OACL,EAAE,KACF,GAAG;EACT,KAAK,MACH,OAAO,QAAQ,WAAW,SAAS,IAC/B,GAAG,IAAI,IAAI,KACT,QAAQ,WAAW,KAAI,cACrB,aAAa,SAAgC,CAC/C,GACA,GAAG,MACL,EAAE,KACF,GAAG;CACX;AACF;AAEA,SAAS,qBAAqB,QAAmC;CAC/D,MAAM,EAAC,OAAM;CACb,IAAI,OAAO,QAAQ,OAAO,UACxB,QAAQ,OAAO,MAAM,MAArB;EACE,KAAK,WACH,OAAO,GAAG,GAAG,mBACX,OAAO,IACT,EAAE,GAAG,IAAI,sBACP,OAAO,EACT,EAAE,gCAAgC,KAAK,UACrC,OAAO,MAAM,KACf,EAAE;EACJ,KAAK,UACH,MAAM,IAAI,MACR,6DACF;CACJ;CAEF,IACE,OAAO,UACP,OAAO,cACP,OAAO,WACP,OAAO,aAEP,OAAO,mBAAmB,MAAM;CAGlC,KACG,OAAO,QAAQ,OAAO,aACvB,OAAO,MAAM,SAAS,aACtB,OAAO,MAAM,UAAU,MAEvB,OAAO,GAAG,GAAG,mBAAmB,OAAO,IAAI,EAAE,GAAG,IAAI,sBAClD,EACF,EAAE;CAGJ,OAAO,GAAG,GAAG,mBAAmB,OAAO,IAAI,EAAE,GAAG,IAAI,sBAClD,OAAO,EACT,EAAE,GAAG,mBAAmB,OAAO,KAAK;AACtC;AAEA,SAAS,mBAAmB,QAAmC;CAC7D,MAAM,EAAC,OAAM;CAab,MAAM,kBAAkB,OAAO,WAAW,OAAO;CACjD,MAAM,UAAU,OAAO,cAAc,OAAO;CAC5C,MAAM,SAAS,IAAI,sBAAsB,UAAU,aAAa,MAAM;CAEtE,MAAM,OAAO,mBAAmB,OAAO,IAAI;CAC3C,MAAM,QAAQ,mBAAmB,OAAO,KAAK;CAC7C,IAAI,iBACF,OAAO,GAAG,SAAS,KAAK,IAAI,OAAO,SAAS,MAAM;CAEpD,OAAO,GAAG,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM;AACvC;AAEA,SAAS,mBAAmB,OAAgC;CAC1D,QAAQ,MAAM,MAAd;EACE,KAAK,UACH,OAAO,IAAI,MAAM,MAAM,IAAI;EAC7B,KAAK,WACH,OAAO,GAAG,GAAG,aAAa,MAAM,OAAO,UAAU,MAAM,KAAK,CAAC;EAC/D,KAAK,UACH,MAAM,IAAI,MACR,6DACF;CACJ;AACF;AAEA,SAAS,UAAU,OAA2B;CAC5C,IAAI,UAAU,MACZ,OAAO;CAET,OAAO,OAAO,UAAU,WACpB,WACA,OAAO,UAAU,WACf,WACA,OAAO,UAAU,YACf,YACA;AACV;AAEA,SAAgB,aAAa,GAAY,MAA0B;CACjE,QAAQ,MAAR;EACE,KAAK,WACH,OAAO,MAAM,OAAO,OAAO,IAAI,IAAI;EACrC,KAAK;EACL,KAAK;EACL,KAAK,QACH,OAAO;EACT,KAAK,QACH,OAAO,KAAK,UAAU,CAAC;CAC3B;AACF;;;;;;;AAQA,IAAa,UAAb,MAAqB;CACnB;CACA,YAAY,KAAqC;EAC/C,KAAK,MAAM;CACb;AACF;AAEA,SAAS,gBAAgB,GAAY,MAA0B;CAC7D,OAAO,aAAa,UAAU,IAAI,aAAa,GAAG,IAAI;AACxD;AAEA,SAAS,sBACP,OACA,OACA,YACU;CACV,IAAI,UAAU,MAIZ,OAAO,GAAG,GAAG,IAAI,MAAM,KAAK,EAAE;CAIhC,OAAO,WAAW,aAAa,OAC3B,GAAG,GAAG,IAAI,MAAM,KAAK,EAAE,MAAM,UAC7B,GAAG,GAAG,IAAI,MAAM,KAAK,EAAE,KAAK;AAClC;AAEA,SAAS,6BACP,OACA,OACA,UACA,YAMA,mBACU;CACV,IAAI,UAAU,MACZ,OAAO,aAAa,MAAM,GAAG,GAAG,IAAI,MAAM,KAAK,EAAE,gBAAgB,GAAG;CAOtE,MAAM,aAAa,GAAG,GAAG,IAAI,MAAM,KAAK,EAAE,GAAG,IAAI,sBAC/C,QACF,EAAE,GAAG;CACL,IAAI,WAAW,aAAa,MAC1B,OAAO;CAST,OAAO,aAAa,OAAO,oBACvB,aACA,GAAG,IAAI,IAAI,MAAM,KAAK,EAAE,cAAc,WAAW;AACvD;AAWA,SAAS,0BACP,OACA,OACA,UACA,YACuC;CAKvC,IAAI,UAAU,MACZ;CAGF,MAAM,oBAAoB,aAAa,MAAM,OAAO;CACpD,OAAO;EACL,OAAO,GAAG,GAAG,IAAI,MAAM,KAAK,EAAE,GAAG,IAAI,sBACnC,iBACF,EAAE,GAAG;EAML,mBAAmB,WAAW,aAAa,QAAQ,aAAa;CAClE;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAS,uBACP,OACA,SACA,OACA,aACsE;CACtE,MAAM,cAA0B,CAAC;CACjC,MAAM,EAAC,KAAK,MAAM,UAAS;CAC3B,IAAI;CAEJ,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,QAAoB,CAAC;EAC3B,MAAM,CAAC,QAAQ,cAAc,MAAM;EACnC,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG,KACtB,IAAI,MAAM,GAAG;GACX,MAAM,aAAa,YAAY;GAC/B,MAAM,kBAAkB,gBACtB,KAAK,WAAW,MAChB,WAAW,IACb;GACA,MAAM,WACJ,eAAe,QAAS,UAAU,MAAM,MAAO,UAAU,MAAM;GACjE,IAAI,oBAAoB;GACxB,IAAI,MAAM,GAAG;IACX,eAAe,0BACb,QACA,iBACA,UACA,UACF;IACA,oBAAoB,cAAc,sBAAsB;GAC1D;GACA,MAAM,KACJ,6BACE,QACA,iBACA,UACA,YACA,iBACF,CACF;EACF,OAAO;GACL,MAAM,CAAC,UAAU,MAAM;GACvB,MAAM,aAAa,YAAY;GAC/B,MAAM,QAAQ,gBAAgB,KAAK,WAAW,MAAM,WAAW,IAAI;GACnE,MAAM,KAAK,sBAAsB,QAAQ,OAAO,UAAU,CAAC;EAC7D;EAEF,YAAY,KAAK,GAAG,IAAI,IAAI,KAAK,OAAO,GAAG,OAAO,EAAE,EAAE;CACxD;CAEA,IAAI,UAAU,MACZ,YAAY,KACV,GAAG,IAAI,IAAI,KACT,MAAM,KAAK,CAAC,WAAW;EACrB,MAAM,aAAa,YAAY;EAE/B,OAAO,sBAAsB,OADf,gBAAgB,KAAK,UAAU,MAAM,WAAW,IAC1B,GAAO,UAAU;CACvD,CAAC,GACD,GAAG,OACL,EAAE,EACJ;CAGF,MAAM,qBAAqB,GAAG,IAAI,IAAI,KAAK,aAAa,GAAG,MAAM,EAAE;CACnE,IAAI,iBAAiB,KAAA,GACnB,OAAO;EAAC,YAAY;EAAoB,mBAAmB;CAAK;CAElE,OAAO;EACL,YAAY,GAAG,IAAI,aAAa,MAAM,OAAO,mBAAmB;EAChE,mBAAmB,aAAa;CAClC;AACF"}
|
|
@@ -129,7 +129,7 @@ export declare class TableSource implements Source {
|
|
|
129
129
|
* Returns `base` untouched when derivation is not deferred, which is what
|
|
130
130
|
* makes this a no-op for the write-through path.
|
|
131
131
|
*/
|
|
132
|
-
reconcilePendingConflicts(base: readonly Row[], next: Row | null, rowKey:
|
|
132
|
+
reconcilePendingConflicts(base: readonly Row[], next: Row | null, rowKey: SQLiteRow, uniqueKeys: readonly (readonly string[])[]): readonly Row[];
|
|
133
133
|
/**
|
|
134
134
|
* Retrieves a row from the backing DB by a unique key, or `undefined` if such a
|
|
135
135
|
* row does not exist. This is not used in the IVM pipeline but is useful
|
|
@@ -141,7 +141,14 @@ export declare class TableSource implements Source {
|
|
|
141
141
|
}
|
|
142
142
|
export declare function toSQLiteTypes(columns: readonly string[], row: Row, columnTypes: Record<string, SchemaValue>): readonly unknown[];
|
|
143
143
|
export declare function toSQLiteTypeName(type: ValueType): "INTEGER" | "NULL" | "REAL" | "TEXT";
|
|
144
|
-
|
|
144
|
+
/**
|
|
145
|
+
* A column value as read from SQLite with `safeIntegers(true)`, i.e. before
|
|
146
|
+
* {@link fromSQLiteTypes} converts it to a {@link Value}.
|
|
147
|
+
*/
|
|
148
|
+
export type SQLiteValue = bigint | number | string | null;
|
|
149
|
+
/** A row as read from SQLite, before {@link fromSQLiteTypes}. */
|
|
150
|
+
export type SQLiteRow = Readonly<Record<string, SQLiteValue>>;
|
|
151
|
+
export declare function fromSQLiteTypes(valueTypes: Record<string, SchemaValue>, row: SQLiteRow, tableName: string): Row;
|
|
145
152
|
export declare class UnsupportedValueError extends Error {
|
|
146
153
|
}
|
|
147
154
|
//# sourceMappingURL=table-source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-source.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/table-source.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"table-source.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/table-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,11 +6,12 @@ import { makeComparator } from "../../zql/src/ivm/data.js";
|
|
|
6
6
|
import { createPredicate, transformFilters } from "../../zql/src/builder/filter.js";
|
|
7
7
|
import { genPushAndWriteWithSplitEdit, generateWithOverlay, generateWithOverlayUnordered, generateWithStart } from "../../zql/src/ivm/memory-source.js";
|
|
8
8
|
import { timeSampled } from "../../otel/src/maybe-time.js";
|
|
9
|
-
import { recordIndexUsage } from "./index-usage.js";
|
|
10
9
|
import { compile, format, sql } from "./internal/sql.js";
|
|
10
|
+
import { toSQLiteType } from "./query-builder.js";
|
|
11
|
+
import { FetchSQLCache } from "./fetch-sql.js";
|
|
12
|
+
import { recordIndexUsage } from "./index-usage.js";
|
|
11
13
|
import { StatementCache } from "./internal/statement-cache.js";
|
|
12
14
|
import { NOT_OVERRIDDEN, PendingDelta, generateWithPendingDelta, generateWithPendingDeltaUnordered } from "./pending-delta.js";
|
|
13
|
-
import { buildSelectQueries, toSQLiteType } from "./query-builder.js";
|
|
14
15
|
import SQLite3Database from "@rocicorp/zero-sqlite3";
|
|
15
16
|
//#region ../zqlite/src/table-source.ts
|
|
16
17
|
var eventCount = 0;
|
|
@@ -166,9 +167,10 @@ var TableSource = class {
|
|
|
166
167
|
const transformedFilters = transformFilters(filters);
|
|
167
168
|
const unordered = sort === void 0;
|
|
168
169
|
const primaryKeySort = this.#primaryKeySort;
|
|
170
|
+
const fetchSQL = new FetchSQLCache(this.#table, this.#columns, transformedFilters.filters, sort);
|
|
169
171
|
const input = {
|
|
170
172
|
getSchema: () => schema,
|
|
171
|
-
fetch: (req) => this.#fetch(req, connection),
|
|
173
|
+
fetch: (req) => this.#fetch(req, connection, fetchSQL),
|
|
172
174
|
setOutput: (output) => {
|
|
173
175
|
connection.output = output;
|
|
174
176
|
},
|
|
@@ -200,9 +202,9 @@ var TableSource = class {
|
|
|
200
202
|
toSQLiteRow(row) {
|
|
201
203
|
return Object.fromEntries(Object.entries(row).map(([key, value]) => [key, toSQLiteType(value, this.#columns[key].type)]));
|
|
202
204
|
}
|
|
203
|
-
*#fetch(req, connection) {
|
|
205
|
+
*#fetch(req, connection, fetchSQL) {
|
|
204
206
|
const { sort, debug } = connection;
|
|
205
|
-
const rows = this.#queryRows(
|
|
207
|
+
const rows = this.#queryRows(fetchSQL.get(req), debug);
|
|
206
208
|
const overlayPredicate = mergeOverlayPredicate(connection.filters?.predicate, req.filter);
|
|
207
209
|
try {
|
|
208
210
|
if (sort) {
|
|
@@ -214,14 +216,13 @@ var TableSource = class {
|
|
|
214
216
|
}
|
|
215
217
|
}
|
|
216
218
|
/**
|
|
217
|
-
* The rows of `queries`, run one after another as
|
|
219
|
+
* The rows of `queries`, run one after another as `buildSelectQueries`
|
|
218
220
|
* requires. Each statement is run only once the previous one is exhausted,
|
|
219
221
|
* so a fetch that stops early never runs the rest.
|
|
220
222
|
*/
|
|
221
223
|
*#queryRows(queries, debug) {
|
|
222
224
|
const { cache } = this.#stmts;
|
|
223
|
-
for (const
|
|
224
|
-
const sqlAndBindings = format(query);
|
|
225
|
+
for (const sqlAndBindings of queries) {
|
|
225
226
|
const cachedStatement = cache.get(sqlAndBindings.text);
|
|
226
227
|
cachedStatement.statement.safeIntegers(true);
|
|
227
228
|
const rowIterator = cachedStatement.statement.iterate(...sqlAndBindings.values);
|
|
@@ -452,9 +453,6 @@ var TableSource = class {
|
|
|
452
453
|
}
|
|
453
454
|
return delta.getByColumns(keyCols, rowKey);
|
|
454
455
|
}
|
|
455
|
-
#requestToSQL(request, filters, order) {
|
|
456
|
-
return buildSelectQueries(this.#table, this.#columns, request.constraint, filters, order, request.reverse, request.start, request.multiConstraints, request.filter);
|
|
457
|
-
}
|
|
458
456
|
};
|
|
459
457
|
function mergeOverlayPredicate(connPredicate, reqFilter) {
|
|
460
458
|
if (!reqFilter) return connPredicate;
|
|
@@ -520,9 +518,8 @@ function fromSQLiteType(valueType, v, column, tableName) {
|
|
|
520
518
|
case "string":
|
|
521
519
|
case "null":
|
|
522
520
|
if (typeof v === "bigint") {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return Number(bi);
|
|
521
|
+
if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) throw new UnsupportedValueError(`value ${v} (in ${tableName}.${column}) is outside of supported bounds`);
|
|
522
|
+
return Number(v);
|
|
526
523
|
}
|
|
527
524
|
return v;
|
|
528
525
|
case "json": try {
|