@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.
Files changed (76) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/zero/package.js +1 -1
  4. package/out/zero/package.js.map +1 -1
  5. package/out/zero-cache/src/config/zero-config.d.ts +4 -0
  6. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  7. package/out/zero-cache/src/config/zero-config.js +4 -0
  8. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  9. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  10. package/out/zero-cache/src/server/change-streamer.js +2 -1
  11. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  12. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  13. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  14. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -1
  15. package/out/zero-cache/src/services/change-source/pg/change-source-init.js +1 -1
  16. package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
  17. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +1 -0
  18. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  19. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +5 -5
  20. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  21. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +1 -1
  22. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  23. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +2 -2
  24. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  25. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +1 -1
  26. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  27. package/out/zero-cache/src/services/change-source/pg/schema/init.js +8 -8
  28. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  29. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +1 -1
  30. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
  31. package/out/zero-cache/src/services/change-source/pg/schema/published.js +2 -1
  32. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  33. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +2 -2
  34. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  35. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +4 -4
  36. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  37. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -2
  38. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  39. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +18 -0
  40. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
  41. package/out/zero-cache/src/services/view-syncer/client-handler.js +46 -20
  42. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  43. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  45. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +4 -3
  46. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/view-syncer/snapshotter.js +5 -2
  48. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  49. package/out/zero-cache/src/workers/connection.js +3 -3
  50. package/out/zero-cache/src/workers/connection.js.map +1 -1
  51. package/out/zero-cache/src/workers/syncer.js +2 -2
  52. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  53. package/out/zero-client/src/client/version.js +1 -1
  54. package/out/zero-protocol/src/inspect-down.d.ts +81 -81
  55. package/out/zero-protocol/src/poke.d.ts +6 -6
  56. package/out/zero-protocol/src/up.d.ts +60 -60
  57. package/out/zql/src/ivm/cap.js +2 -4
  58. package/out/zql/src/ivm/cap.js.map +1 -1
  59. package/out/zql/src/ivm/take.js +2 -4
  60. package/out/zql/src/ivm/take.js.map +1 -1
  61. package/out/zqlite/src/database-storage.d.ts.map +1 -1
  62. package/out/zqlite/src/database-storage.js +38 -6
  63. package/out/zqlite/src/database-storage.js.map +1 -1
  64. package/out/zqlite/src/fetch-sql.d.ts +27 -0
  65. package/out/zqlite/src/fetch-sql.d.ts.map +1 -0
  66. package/out/zqlite/src/fetch-sql.js +112 -0
  67. package/out/zqlite/src/fetch-sql.js.map +1 -0
  68. package/out/zqlite/src/query-builder.d.ts +11 -1
  69. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  70. package/out/zqlite/src/query-builder.js +22 -7
  71. package/out/zqlite/src/query-builder.js.map +1 -1
  72. package/out/zqlite/src/table-source.d.ts +9 -2
  73. package/out/zqlite/src/table-source.d.ts.map +1 -1
  74. package/out/zqlite/src/table-source.js +11 -14
  75. package/out/zqlite/src/table-source.js.map +1 -1
  76. 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,EAAC,eAAe,EAAE,KAAK,EAAC,MAAM,+BAA+B,CAAC;AAG1E;;;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"}
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)} = ${toSQLiteType(value, columns[key].type)}`);
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`${toSQLiteType(c[key], colType)}`), 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`${toSQLiteType(c[k], columns[k].type)}`), 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 = toSQLiteType(from[iField] ?? null, columnType.type);
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 = toSQLiteType(from[jField] ?? null, columnType.type);
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, toSQLiteType(from[field] ?? null, columnType.type), columnType);
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":"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"}
@@ -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: Row, uniqueKeys: readonly (readonly string[])[]): readonly Row[];
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
- export declare function fromSQLiteTypes(valueTypes: Record<string, SchemaValue>, row: Row, tableName: string): Row;
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":"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"}
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(this.#requestToSQL(req, connection.filters?.condition, sort), debug);
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 {@link buildSelectQueries}
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 query of queries) {
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
- const bi = v;
524
- if (bi > Number.MAX_SAFE_INTEGER || bi < Number.MIN_SAFE_INTEGER) throw new UnsupportedValueError(`value ${bi} (in ${tableName}.${column}) is outside of supported bounds`);
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 {