@rocicorp/zero 1.11.0-canary.0 → 1.11.0-canary.2
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/replicache/src/btree/read.d.ts +12 -1
- package/out/replicache/src/btree/read.d.ts.map +1 -1
- package/out/replicache/src/btree/read.js +24 -12
- package/out/replicache/src/btree/read.js.map +1 -1
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +10 -0
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/transactions.d.ts.map +1 -1
- package/out/replicache/src/transactions.js +1 -1
- package/out/replicache/src/transactions.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 +17 -2
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +86 -26
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +6 -2
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +2 -39
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.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/change-streamer/change-streamer.d.ts +6 -6
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +8 -3
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +13 -1
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +104 -0
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts +70 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.js +101 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +23 -5
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +31 -6
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js +5 -4
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +30 -5
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +6 -6
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +6 -11
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +2 -2
- package/out/zero-cache/src/workers/replicator.d.ts +1 -0
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +4 -2
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-client/src/client/context.d.ts +15 -0
- package/out/zero-client/src/client/context.d.ts.map +1 -1
- package/out/zero-client/src/client/context.js +44 -0
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js +2 -2
- package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-rep.js +2 -1
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +50 -13
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/analyze-query-result.d.ts +5 -5
- package/out/zero-protocol/src/analyze-query-result.d.ts.map +1 -1
- package/out/zero-protocol/src/analyze-query-result.js +1 -1
- package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +30 -30
- package/out/zero-protocol/src/inspect-down.d.ts +6 -6
- package/out/zero-protocol/src/protocol-version.d.ts +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +24 -5
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-solid/src/use-query.js +4 -1
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +16 -10
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +5 -3
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.d.ts +44 -0
- package/out/zql/src/ivm/deferred-input.d.ts.map +1 -0
- package/out/zql/src/ivm/deferred-input.js +88 -0
- package/out/zql/src/ivm/deferred-input.js.map +1 -0
- package/out/zql/src/ivm/filter-operators.d.ts +3 -2
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +19 -4
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +22 -14
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/operator.d.ts +40 -0
- package/out/zql/src/ivm/operator.d.ts.map +1 -1
- package/out/zql/src/ivm/operator.js.map +1 -1
- package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
- package/out/zql/src/planner/planner-builder.js +14 -4
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.d.ts +13 -0
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +42 -7
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-debug.d.ts +1 -1
- package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
- package/out/zql/src/planner/planner-debug.js.map +1 -1
- package/out/zql/src/planner/planner-fan-out.d.ts +1 -0
- package/out/zql/src/planner/planner-fan-out.d.ts.map +1 -1
- package/out/zql/src/planner/planner-fan-out.js +3 -0
- package/out/zql/src/planner/planner-fan-out.js.map +1 -1
- package/out/zql/src/planner/planner-filter.d.ts +59 -0
- package/out/zql/src/planner/planner-filter.d.ts.map +1 -0
- package/out/zql/src/planner/planner-filter.js +116 -0
- package/out/zql/src/planner/planner-filter.js.map +1 -0
- package/out/zql/src/planner/planner-graph.d.ts +2 -0
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js +2 -0
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-join.d.ts +2 -0
- package/out/zql/src/planner/planner-join.d.ts.map +1 -1
- package/out/zql/src/planner/planner-join.js +7 -0
- package/out/zql/src/planner/planner-join.js.map +1 -1
- package/out/zql/src/planner/planner-node.d.ts +2 -1
- package/out/zql/src/planner/planner-node.d.ts.map +1 -1
- package/out/zql/src/planner/planner-node.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts +6 -0
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +73 -10
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-delegate.d.ts +22 -0
- package/out/zql/src/query/query-delegate.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +12 -11
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js +383 -60
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-transitions.d.ts +171 -0
- package/out/zql/src/query/query-transitions.d.ts.map +1 -0
- package/out/zql/src/query/query-transitions.js +207 -0
- package/out/zql/src/query/query-transitions.js.map +1 -0
- package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.js +21 -1
- package/out/zql/src/query/runnable-query-impl.js.map +1 -1
- package/out/zqlite/src/query-builder.d.ts +1 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +2 -1
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +2 -30
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +10 -3
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,8 +2,28 @@ import { tableAST } from "../../../zero-protocol/src/ast.js";
|
|
|
2
2
|
import { defaultFormat } from "../../../zero-types/src/format.js";
|
|
3
3
|
import { QueryImpl } from "./query-impl.js";
|
|
4
4
|
//#region ../zql/src/query/runnable-query-impl.ts
|
|
5
|
+
/**
|
|
6
|
+
* Runnable roots are interned per delegate *and* schema -- a query is only
|
|
7
|
+
* interchangeable with another if it would run against the same delegate. See
|
|
8
|
+
* `query-transitions.ts` for why roots need to be shared at all.
|
|
9
|
+
*/
|
|
10
|
+
var rootsByDelegate = /* @__PURE__ */ new WeakMap();
|
|
5
11
|
function newRunnableQuery(delegate, schema, table) {
|
|
6
|
-
|
|
12
|
+
let bySchema = rootsByDelegate.get(delegate);
|
|
13
|
+
if (!bySchema) {
|
|
14
|
+
bySchema = /* @__PURE__ */ new WeakMap();
|
|
15
|
+
rootsByDelegate.set(delegate, bySchema);
|
|
16
|
+
}
|
|
17
|
+
let roots = bySchema.get(schema);
|
|
18
|
+
if (!roots) {
|
|
19
|
+
roots = /* @__PURE__ */ new Map();
|
|
20
|
+
bySchema.set(schema, roots);
|
|
21
|
+
}
|
|
22
|
+
const existing = roots.get(table)?.deref();
|
|
23
|
+
if (existing) return existing;
|
|
24
|
+
const created = new RunnableQueryImpl(delegate, schema, table, tableAST(table), defaultFormat, void 0);
|
|
25
|
+
roots.set(table, new WeakRef(created));
|
|
26
|
+
return created;
|
|
7
27
|
}
|
|
8
28
|
var RunnableQueryImpl = class RunnableQueryImpl extends QueryImpl {
|
|
9
29
|
#delegate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runnable-query-impl.js","names":["#delegate"],"sources":["../../../../../zql/src/query/runnable-query-impl.ts"],"sourcesContent":["import {\n tableAST,\n type NormalizedAST,\n type System,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {defaultFormat} from '../ivm/default-format.ts';\nimport type {Format, ViewFactory} from '../ivm/view.ts';\nimport type {CustomQueryID} from './named.ts';\nimport type {QueryDelegate} from './query-delegate.ts';\nimport {QueryImpl} from './query-impl.ts';\nimport type {\n HumanReadable,\n MaterializeOptions,\n PreloadOptions,\n PullRow,\n Query,\n RunOptions,\n} from './query.ts';\nimport type {TTL} from './ttl.ts';\nimport type {TypedView} from './typed-view.ts';\n\nexport function newRunnableQuery<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n>(\n delegate: QueryDelegate,\n schema: TSchema,\n table: TTable,\n): Query<TTable, TSchema> {\n return new RunnableQueryImpl(\n delegate,\n schema,\n table,\n tableAST(table),\n defaultFormat,\n undefined,\n );\n}\n\nexport class RunnableQueryImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn = PullRow<TTable, TSchema>,\n>\n extends QueryImpl<TTable, TSchema, TReturn>\n implements Query<TTable, TSchema, TReturn>\n{\n readonly #delegate: QueryDelegate;\n\n constructor(\n delegate: QueryDelegate,\n schema: TSchema,\n tableName: TTable,\n ast: NormalizedAST = tableAST(tableName),\n format: Format = defaultFormat,\n system: System = 'client',\n customQueryID?: CustomQueryID,\n currentJunction?: string,\n ) {\n super(\n schema,\n tableName,\n ast,\n format,\n system,\n customQueryID,\n currentJunction,\n (tableName, ast, format, customQueryID, currentJunction) =>\n new RunnableQueryImpl(\n delegate,\n schema,\n tableName,\n ast,\n format,\n system,\n customQueryID,\n currentJunction,\n ),\n );\n this.#delegate = delegate;\n }\n\n override run(options?: RunOptions): Promise<HumanReadable<TReturn>> {\n // The type arguments on the delegate calls in this class are written out\n // because tsc 7.0.2 fails to infer them in some program layouts (the\n // packages/zero declaration build, apps/zero-throughput): inference\n // collapses TSchema to its constraint and the call misreports as a\n // TS2345 assignability error. Explicit arguments are inert where\n // inference works and correct where it does not.\n return this.#delegate.run<TTable, TSchema, TReturn>(this, options);\n }\n\n override preload(options?: PreloadOptions): {\n cleanup: () => void;\n complete: Promise<void>;\n } {\n return this.#delegate.preload<TTable, TSchema, TReturn>(this, options);\n }\n\n override materialize(ttl?: TTL): TypedView<HumanReadable<TReturn>>;\n override materialize<T>(\n factory: ViewFactory<TTable, TSchema, TReturn, T>,\n ttl?: TTL,\n ): T;\n override materialize<T>(\n factory?: unknown,\n ttl?: unknown,\n ): T | TypedView<HumanReadable<TReturn>> {\n let actualFactory: ViewFactory<TTable, TSchema, TReturn, T> | undefined;\n let options: MaterializeOptions | undefined;\n\n if (typeof factory === 'function') {\n actualFactory = factory as ViewFactory<TTable, TSchema, TReturn, T>;\n options = {ttl: ttl as TTL | undefined};\n } else {\n actualFactory = undefined;\n options = {ttl: factory as TTL | undefined};\n }\n\n return this.#delegate.materialize<TTable, TSchema, TReturn, T>(\n this,\n actualFactory,\n options,\n );\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"runnable-query-impl.js","names":["#delegate"],"sources":["../../../../../zql/src/query/runnable-query-impl.ts"],"sourcesContent":["import {\n tableAST,\n type NormalizedAST,\n type System,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {defaultFormat} from '../ivm/default-format.ts';\nimport type {Format, ViewFactory} from '../ivm/view.ts';\nimport type {CustomQueryID} from './named.ts';\nimport type {QueryDelegate} from './query-delegate.ts';\nimport {QueryImpl} from './query-impl.ts';\nimport type {\n HumanReadable,\n MaterializeOptions,\n PreloadOptions,\n PullRow,\n Query,\n RunOptions,\n} from './query.ts';\nimport type {TTL} from './ttl.ts';\nimport type {TypedView} from './typed-view.ts';\n\n/**\n * Runnable roots are interned per delegate *and* schema -- a query is only\n * interchangeable with another if it would run against the same delegate. See\n * `query-transitions.ts` for why roots need to be shared at all.\n */\nconst rootsByDelegate = new WeakMap<\n QueryDelegate,\n // oxlint-disable-next-line no-explicit-any\n WeakMap<Schema, Map<string, WeakRef<RunnableQueryImpl<any, any, any>>>>\n>();\n\nexport function newRunnableQuery<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n>(\n delegate: QueryDelegate,\n schema: TSchema,\n table: TTable,\n): Query<TTable, TSchema> {\n let bySchema = rootsByDelegate.get(delegate);\n if (!bySchema) {\n bySchema = new WeakMap();\n rootsByDelegate.set(delegate, bySchema);\n }\n let roots = bySchema.get(schema);\n if (!roots) {\n roots = new Map();\n bySchema.set(schema, roots);\n }\n\n const existing = roots.get(table)?.deref();\n if (existing) {\n return existing as RunnableQueryImpl<TTable, TSchema>;\n }\n\n const created = new RunnableQueryImpl<TTable, TSchema>(\n delegate,\n schema,\n table,\n tableAST(table),\n defaultFormat,\n undefined,\n );\n roots.set(table, new WeakRef(created));\n return created;\n}\n\nexport class RunnableQueryImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn = PullRow<TTable, TSchema>,\n>\n extends QueryImpl<TTable, TSchema, TReturn>\n implements Query<TTable, TSchema, TReturn>\n{\n readonly #delegate: QueryDelegate;\n\n constructor(\n delegate: QueryDelegate,\n schema: TSchema,\n tableName: TTable,\n ast: NormalizedAST = tableAST(tableName),\n format: Format = defaultFormat,\n system: System = 'client',\n customQueryID?: CustomQueryID,\n currentJunction?: string,\n ) {\n super(\n schema,\n tableName,\n ast,\n format,\n system,\n customQueryID,\n currentJunction,\n (tableName, ast, format, customQueryID, currentJunction) =>\n new RunnableQueryImpl(\n delegate,\n schema,\n tableName,\n ast,\n format,\n system,\n customQueryID,\n currentJunction,\n ),\n );\n this.#delegate = delegate;\n }\n\n override run(options?: RunOptions): Promise<HumanReadable<TReturn>> {\n // The type arguments on the delegate calls in this class are written out\n // because tsc 7.0.2 fails to infer them in some program layouts (the\n // packages/zero declaration build, apps/zero-throughput): inference\n // collapses TSchema to its constraint and the call misreports as a\n // TS2345 assignability error. Explicit arguments are inert where\n // inference works and correct where it does not.\n return this.#delegate.run<TTable, TSchema, TReturn>(this, options);\n }\n\n override preload(options?: PreloadOptions): {\n cleanup: () => void;\n complete: Promise<void>;\n } {\n return this.#delegate.preload<TTable, TSchema, TReturn>(this, options);\n }\n\n override materialize(ttl?: TTL): TypedView<HumanReadable<TReturn>>;\n override materialize<T>(\n factory: ViewFactory<TTable, TSchema, TReturn, T>,\n ttl?: TTL,\n ): T;\n override materialize<T>(\n factory?: unknown,\n ttl?: unknown,\n ): T | TypedView<HumanReadable<TReturn>> {\n let actualFactory: ViewFactory<TTable, TSchema, TReturn, T> | undefined;\n let options: MaterializeOptions | undefined;\n\n if (typeof factory === 'function') {\n actualFactory = factory as ViewFactory<TTable, TSchema, TReturn, T>;\n options = {ttl: ttl as TTL | undefined};\n } else {\n actualFactory = undefined;\n options = {ttl: factory as TTL | undefined};\n }\n\n return this.#delegate.materialize<TTable, TSchema, TReturn, T>(\n this,\n actualFactory,\n options,\n );\n }\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAM,kCAAkB,IAAI,QAI1B;AAEF,SAAgB,iBAId,UACA,QACA,OACwB;CACxB,IAAI,WAAW,gBAAgB,IAAI,QAAQ;CAC3C,IAAI,CAAC,UAAU;EACb,2BAAW,IAAI,QAAQ;EACvB,gBAAgB,IAAI,UAAU,QAAQ;CACxC;CACA,IAAI,QAAQ,SAAS,IAAI,MAAM;CAC/B,IAAI,CAAC,OAAO;EACV,wBAAQ,IAAI,IAAI;EAChB,SAAS,IAAI,QAAQ,KAAK;CAC5B;CAEA,MAAM,WAAW,MAAM,IAAI,KAAK,GAAG,MAAM;CACzC,IAAI,UACF,OAAO;CAGT,MAAM,UAAU,IAAI,kBAClB,UACA,QACA,OACA,SAAS,KAAK,GACd,eACA,KAAA,CACF;CACA,MAAM,IAAI,OAAO,IAAI,QAAQ,OAAO,CAAC;CACrC,OAAO;AACT;AAEA,IAAa,oBAAb,MAAa,0BAKH,UAEV;CACE;CAEA,YACE,UACA,QACA,WACA,MAAqB,SAAS,SAAS,GACvC,SAAiB,eACjB,SAAiB,UACjB,eACA,iBACA;EACA,MACE,QACA,WACA,KACA,QACA,QACA,eACA,kBACC,WAAW,KAAK,QAAQ,eAAe,oBACtC,IAAI,kBACF,UACA,QACA,WACA,KACA,QACA,QACA,eACA,eACF,CACJ;EACA,KAAKA,YAAY;CACnB;CAEA,IAAa,SAAuD;EAOlE,OAAO,KAAKA,UAAU,IAA8B,MAAM,OAAO;CACnE;CAEA,QAAiB,SAGf;EACA,OAAO,KAAKA,UAAU,QAAkC,MAAM,OAAO;CACvE;CAOA,YACE,SACA,KACuC;EACvC,IAAI;EACJ,IAAI;EAEJ,IAAI,OAAO,YAAY,YAAY;GACjC,gBAAgB;GAChB,UAAU,EAAM,IAAsB;EACxC,OAAO;GACL,gBAAgB,KAAA;GAChB,UAAU,EAAC,KAAK,QAA0B;EAC5C;EAEA,OAAO,KAAKA,UAAU,YACpB,MACA,eACA,OACF;CACF;AACF"}
|
|
@@ -10,7 +10,7 @@ import type { MultiConstraint, Start } from '../../zql/src/ivm/operator.ts';
|
|
|
10
10
|
export type NoSubqueryCondition = Exclude<Condition, {
|
|
11
11
|
type: 'correlatedSubquery';
|
|
12
12
|
}>;
|
|
13
|
-
export declare function buildSelectQuery(tableName: string, columns: Record<string, SchemaValue>, constraint: Constraint | undefined, filters: NoSubqueryCondition | undefined, order: Ordering | undefined, reverse: boolean | undefined, start: Start | undefined, multiConstraints?: readonly MultiConstraint[] | undefined): SQLQuery;
|
|
13
|
+
export declare function buildSelectQuery(tableName: string, columns: Record<string, SchemaValue>, constraint: Constraint | undefined, filters: NoSubqueryCondition | undefined, order: Ordering | undefined, reverse: boolean | undefined, start: Start | undefined, multiConstraints?: readonly MultiConstraint[] | undefined, fetchFilters?: NoSubqueryCondition | undefined): SQLQuery;
|
|
14
14
|
export declare function constraintsToSQL(constraint: Constraint | undefined, columns: Record<string, SchemaValue>): SQLQuery[];
|
|
15
15
|
/**
|
|
16
16
|
* Builds a single batched IN clause from a `MultiConstraint`. All entries
|
|
@@ -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,wBAAgB,gBAAgB,CAC9B,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,
|
|
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,wBAAgB,gBAAgB,CAC9B,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,YAqC/C;AAED,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,7 +1,7 @@
|
|
|
1
1
|
import { assert } from "../../shared/src/asserts.js";
|
|
2
2
|
import { sql } from "./internal/sql.js";
|
|
3
3
|
//#region ../zqlite/src/query-builder.ts
|
|
4
|
-
function buildSelectQuery(tableName, columns, constraint, filters, order, reverse, start, multiConstraints) {
|
|
4
|
+
function buildSelectQuery(tableName, columns, constraint, filters, order, reverse, start, multiConstraints, fetchFilters) {
|
|
5
5
|
let query = sql`SELECT ${sql.join(Object.keys(columns).map((c) => sql.ident(c)), sql`,`)} FROM ${sql.ident(tableName)}`;
|
|
6
6
|
const constraints = constraintsToSQL(constraint, columns);
|
|
7
7
|
if (multiConstraints) {
|
|
@@ -12,6 +12,7 @@ function buildSelectQuery(tableName, columns, constraint, filters, order, revers
|
|
|
12
12
|
constraints.push(gatherStartConstraints(start, reverse, order, columns));
|
|
13
13
|
}
|
|
14
14
|
if (filters) constraints.push(filtersToSQL(filters));
|
|
15
|
+
if (fetchFilters) constraints.push(filtersToSQL(fetchFilters));
|
|
15
16
|
if (constraints.length > 0) query = sql`${query} WHERE ${sql.join(constraints, sql` AND `)}`;
|
|
16
17
|
if (order && order.length > 0) return sql`${query} ${orderByToSQL(order, !!reverse)}`;
|
|
17
18
|
return query;
|
|
@@ -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\nexport function buildSelectQuery(\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) {\n let query = sql`SELECT ${sql.join(\n Object.keys(columns).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(tableName)}`;\n const constraints: SQLQuery[] = constraintsToSQL(constraint, columns);\n\n if (multiConstraints) {\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n constraints.push(multiConstraintToSQL(mc, columns));\n }\n }\n }\n\n if (start) {\n assert(order !== undefined, 'start requires ordering');\n constraints.push(gatherStartConstraints(start, reverse, order, columns));\n }\n\n if (filters) {\n constraints.push(filtersToSQL(filters));\n }\n\n if (constraints.length > 0) {\n query = sql`${query} WHERE ${sql.join(constraints, sql` AND `)}`;\n }\n\n if (order && order.length > 0) {\n return sql`${query} ${orderByToSQL(order, !!reverse)}`;\n }\n return query;\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): 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.\n return operator === '>'\n ? comparison\n : sql`(${sql.ident(field)} IS NULL OR ${comparison})`;\n}\n\nfunction sargableLeadingStartBound(\n field: string,\n value: unknown,\n operator: '>' | '<',\n columnType: SchemaValue,\n): SQLQuery | 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. A nullable column also cannot use a `<` bound,\n // because the start constraint must retain the NULL group. For `>`, NULLs\n // sort before the non-NULL bound, so `col >= value` remains sound.\n if (value === null || (columnType.optional === true && operator === '<')) {\n return undefined;\n }\n\n const inclusiveOperator = operator === '>' ? '>=' : '<=';\n return sql`${sql.ident(field)} ${sql.__dangerous__rawValue(\n inclusiveOperator,\n )} ${value}`;\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 */\nfunction gatherStartConstraints(\n start: Start,\n reverse: boolean | undefined,\n order: Ordering,\n columnTypes: Record<string, SchemaValue>,\n): SQLQuery {\n const constraints: SQLQuery[] = [];\n const {row: from, basis} = start;\n let leadingBound: SQLQuery | 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 if (i === 0) {\n leadingBound = sargableLeadingStartBound(\n iField,\n constraintValue,\n operator,\n columnType,\n );\n }\n group.push(\n nullableAwareRangeComparison(\n iField,\n constraintValue,\n operator,\n columnType,\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 return leadingBound === undefined\n ? lexicographicStart\n : sql`(${leadingBound} AND ${lexicographicStart})`;\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 {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\nexport function buildSelectQuery(\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) {\n let query = sql`SELECT ${sql.join(\n Object.keys(columns).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(tableName)}`;\n const constraints: SQLQuery[] = constraintsToSQL(constraint, columns);\n\n if (multiConstraints) {\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n constraints.push(multiConstraintToSQL(mc, columns));\n }\n }\n }\n\n if (start) {\n assert(order !== undefined, 'start requires ordering');\n constraints.push(gatherStartConstraints(start, reverse, order, columns));\n }\n\n if (filters) {\n constraints.push(filtersToSQL(filters));\n }\n\n if (fetchFilters) {\n constraints.push(filtersToSQL(fetchFilters));\n }\n\n if (constraints.length > 0) {\n query = sql`${query} WHERE ${sql.join(constraints, sql` AND `)}`;\n }\n\n if (order && order.length > 0) {\n return sql`${query} ${orderByToSQL(order, !!reverse)}`;\n }\n return query;\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): 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.\n return operator === '>'\n ? comparison\n : sql`(${sql.ident(field)} IS NULL OR ${comparison})`;\n}\n\nfunction sargableLeadingStartBound(\n field: string,\n value: unknown,\n operator: '>' | '<',\n columnType: SchemaValue,\n): SQLQuery | 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. A nullable column also cannot use a `<` bound,\n // because the start constraint must retain the NULL group. For `>`, NULLs\n // sort before the non-NULL bound, so `col >= value` remains sound.\n if (value === null || (columnType.optional === true && operator === '<')) {\n return undefined;\n }\n\n const inclusiveOperator = operator === '>' ? '>=' : '<=';\n return sql`${sql.ident(field)} ${sql.__dangerous__rawValue(\n inclusiveOperator,\n )} ${value}`;\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 */\nfunction gatherStartConstraints(\n start: Start,\n reverse: boolean | undefined,\n order: Ordering,\n columnTypes: Record<string, SchemaValue>,\n): SQLQuery {\n const constraints: SQLQuery[] = [];\n const {row: from, basis} = start;\n let leadingBound: SQLQuery | 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 if (i === 0) {\n leadingBound = sargableLeadingStartBound(\n iField,\n constraintValue,\n operator,\n columnType,\n );\n }\n group.push(\n nullableAwareRangeComparison(\n iField,\n constraintValue,\n operator,\n columnType,\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 return leadingBound === undefined\n ? lexicographicStart\n : sql`(${leadingBound} AND ${lexicographicStart})`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-cost-model.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/sqlite-cost-model.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"sqlite-cost-model.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/sqlite-cost-model.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,sCAAsC,CAAC;AAEtE,OAAO,KAAK,EACV,mBAAmB,EAEpB,MAAM,6CAA6C,CAAC;AAErD,OAAO,KAAK,EAAC,QAAQ,EAAY,MAAM,SAAS,CAAC;AAmBjD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,QAAQ,EACZ,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAC,CAAC,GAC9D,mBAAmB,CA+DrB;AAmFD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK9C"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assert } from "../../shared/src/asserts.js";
|
|
2
2
|
import { must } from "../../shared/src/must.js";
|
|
3
|
+
import { transformFilters } from "../../zql/src/builder/filter.js";
|
|
3
4
|
import { buildSelectQuery } from "./query-builder.js";
|
|
4
5
|
import { compileInline } from "./internal/sql-inline.js";
|
|
5
6
|
import { SQLiteStatFanout } from "./sqlite-stat-fanout.js";
|
|
@@ -17,7 +18,7 @@ import SQLite3Database from "@rocicorp/zero-sqlite3";
|
|
|
17
18
|
function createSQLiteCostModel(db, tableSpecs) {
|
|
18
19
|
const fanoutEstimator = new SQLiteStatFanout(db);
|
|
19
20
|
return (tableName, sort, filters, constraint) => {
|
|
20
|
-
const noSubqueryFilters =
|
|
21
|
+
const noSubqueryFilters = transformFilters(filters).filters;
|
|
21
22
|
const { zqlSpec } = must(tableSpecs.get(tableName));
|
|
22
23
|
const sql = compileInline(buildSelectQuery(tableName, zqlSpec, constraint, noSubqueryFilters, sort, void 0, void 0));
|
|
23
24
|
const loops = getScanstatusLoops(db.prepare(sql));
|
|
@@ -26,35 +27,6 @@ function createSQLiteCostModel(db, tableSpecs) {
|
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
|
-
* Removes correlated subqueries from conditions.
|
|
30
|
-
* The cost model estimates cost without correlated subqueries since
|
|
31
|
-
* they can't be included in the scanstatus query.
|
|
32
|
-
*/
|
|
33
|
-
function removeCorrelatedSubqueries(condition) {
|
|
34
|
-
switch (condition.type) {
|
|
35
|
-
case "correlatedSubquery": return;
|
|
36
|
-
case "simple": return condition;
|
|
37
|
-
case "and": {
|
|
38
|
-
const filtered = condition.conditions.map((c) => removeCorrelatedSubqueries(c)).filter((c) => c !== void 0);
|
|
39
|
-
if (filtered.length === 0) return void 0;
|
|
40
|
-
if (filtered.length === 1) return filtered[0];
|
|
41
|
-
return {
|
|
42
|
-
type: "and",
|
|
43
|
-
conditions: filtered
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
case "or": {
|
|
47
|
-
const filtered = condition.conditions.map((c) => removeCorrelatedSubqueries(c)).filter((c) => c !== void 0);
|
|
48
|
-
if (filtered.length === 0) return void 0;
|
|
49
|
-
if (filtered.length === 1) return filtered[0];
|
|
50
|
-
return {
|
|
51
|
-
type: "or",
|
|
52
|
-
conditions: filtered
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
30
|
* Gets scanstatus loop information from a prepared statement.
|
|
59
31
|
* Iterates through all query elements and extracts loop statistics.
|
|
60
32
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-cost-model.js","names":[],"sources":["../../../../zqlite/src/sqlite-cost-model.ts"],"sourcesContent":["import SQLite3Database from '@rocicorp/zero-sqlite3';\nimport {assert} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {Condition, Ordering} from '../../zero-protocol/src/ast.ts';\nimport type {SchemaValue} from '../../zero-types/src/schema-value.ts';\nimport type {\n ConnectionCostModel,\n CostModelCost,\n} from '../../zql/src/planner/planner-connection.ts';\nimport type {PlannerConstraint} from '../../zql/src/planner/planner-constraint.ts';\nimport type {Database, Statement} from './db.ts';\nimport {compileInline} from './internal/sql-inline.ts';\nimport {buildSelectQuery
|
|
1
|
+
{"version":3,"file":"sqlite-cost-model.js","names":[],"sources":["../../../../zqlite/src/sqlite-cost-model.ts"],"sourcesContent":["import SQLite3Database from '@rocicorp/zero-sqlite3';\nimport {assert} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {Condition, Ordering} from '../../zero-protocol/src/ast.ts';\nimport type {SchemaValue} from '../../zero-types/src/schema-value.ts';\nimport {transformFilters} from '../../zql/src/builder/filter.ts';\nimport type {\n ConnectionCostModel,\n CostModelCost,\n} from '../../zql/src/planner/planner-connection.ts';\nimport type {PlannerConstraint} from '../../zql/src/planner/planner-constraint.ts';\nimport type {Database, Statement} from './db.ts';\nimport {compileInline} from './internal/sql-inline.ts';\nimport {buildSelectQuery} from './query-builder.ts';\nimport {SQLiteStatFanout} from './sqlite-stat-fanout.ts';\n\n/**\n * Loop information returned by SQLite's scanstatus API.\n */\ninterface ScanstatusLoop {\n /** Unique identifier for this loop */\n selectId: number;\n /** Parent loop ID, or 0 for root loops */\n parentId: number;\n /** Estimated rows emitted per turn of parent loop */\n est: number;\n /** EXPLAIN text for this loop to determine: b-tree vs list subquery */\n explain: string;\n}\n\n/**\n * Creates a SQLite-based cost model for query planning.\n * Uses SQLite's scanstatus API to estimate query costs based on the actual\n * SQLite query planner's analysis.\n *\n * @param db Database instance for preparing statements\n * @param tableSpecs Map of table names to their table specs with ZQL schemas\n * @returns ConnectionCostModel function for use with the planner\n */\nexport function createSQLiteCostModel(\n db: Database,\n tableSpecs: Map<string, {zqlSpec: Record<string, SchemaValue>}>,\n): ConnectionCostModel {\n const fanoutEstimator = new SQLiteStatFanout(db);\n return (\n tableName: string,\n sort: Ordering,\n filters: Condition | undefined,\n constraint: PlannerConstraint | undefined,\n ): CostModelCost => {\n // Transform filters to remove correlated subqueries.\n //\n // We use `transformFilters` (the same function the runtime uses to\n // decide what to push to the source at connection time). It returns:\n // - For AND: the non-CSQ conjuncts (a *superset* of the original).\n // - For OR with any CSQ branch: `undefined` (the OR-with-CSQ is not\n // pushable, and matches a strict superset of any single branch).\n //\n // This matches what the runtime actually applies at the source. A\n // previous implementation here had wrong OR semantics — it dropped the\n // CSQ branch and returned the simple branch alone, which is a strict\n // *subset* of the original OR's match set, causing the cost model to\n // under-estimate scan rows for OR-with-CSQ shapes and biasing the\n // planner toward semi-join plans. See the comment in\n // `packages/zql/src/builder/filter.ts:transformFilters`.\n const noSubqueryFilters = transformFilters(filters).filters;\n\n // Build the SQL query using the same logic as actual queries\n const {zqlSpec} = must(tableSpecs.get(tableName));\n\n const query = buildSelectQuery(\n tableName,\n zqlSpec,\n constraint,\n noSubqueryFilters,\n sort,\n undefined, // reverse is undefined here\n undefined, // start is undefined here\n );\n\n // Use compileInline to inline actual values into the SQL for cost estimation.\n // This allows SQLite's query planner to see real values and make better decisions\n // about index usage and query plans. This is safe here because it's only used for\n // cost estimation, not for executing user-facing queries (which use parameterized\n // queries via the standard compile() function).\n const sql = compileInline(query);\n\n // Prepare statement to get scanstatus information\n const stmt = db.prepare(sql);\n\n // Get scanstatus loops from the prepared statement\n const loops = getScanstatusLoops(stmt);\n\n // Scanstatus should always be available - if we get no loops, something is wrong\n assert(\n loops.length > 0,\n `Expected scanstatus to return at least one loop for query: ${sql}`,\n );\n\n const ret = estimateCost(loops, (columns: string[]) =>\n fanoutEstimator.getFanout(tableName, columns),\n );\n\n return ret;\n };\n}\n\n/**\n * Gets scanstatus loop information from a prepared statement.\n * Iterates through all query elements and extracts loop statistics.\n *\n * Uses SQLITE_SCANSTAT_COMPLEX flag (1) to get all loops including sorting operations.\n *\n * @param stmt Prepared statement to get scanstatus from\n * @returns Array of loop information, or empty array if scanstatus unavailable\n */\nfunction getScanstatusLoops(stmt: Statement): ScanstatusLoop[] {\n const loops: ScanstatusLoop[] = [];\n\n // Iterate through query elements by incrementing idx until we get undefined\n // which indicates we've reached the end\n for (let idx = 0; ; idx++) {\n const selectId = stmt.scanStatus(\n idx,\n SQLite3Database.SQLITE_SCANSTAT_SELECTID,\n 1,\n );\n\n if (selectId === undefined) {\n break;\n }\n\n loops.push({\n selectId: must(selectId),\n parentId: must(\n stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_PARENTID, 1),\n ),\n explain: must(\n stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_EXPLAIN, 1),\n ),\n est: must(stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_EST, 1)),\n });\n }\n\n return loops.sort((a, b) => a.selectId - b.selectId);\n}\n\n/**\n * Estimates the cost of a query based on scanstats from sqlite3_stmt_scanstatus_v2\n */\nfunction estimateCost(\n scanstats: ScanstatusLoop[],\n fanout: CostModelCost['fanout'],\n): CostModelCost {\n // Sort by selectId to process in execution order\n const sorted = scanstats.toSorted((a, b) => a.selectId - b.selectId);\n\n let totalRows = 0;\n let totalCost = 0;\n\n // Identify if there are multiple top-level (parentId=0) operations\n // If so, the first is typically the scan, and subsequent ones are sorts\n const topLevelOps = sorted.filter(s => s.parentId === 0);\n\n // We only consider top level ops since ZQL queries are single-table when hitting SQLite.\n // We do have a nested op in the case of `WHERE x IN (:arg)` but it is negligible\n // assuming :arg is small.\n let firstLoop = true;\n for (const op of topLevelOps) {\n if (firstLoop) {\n // First top-level op is the main scan\n // and determines the total number of rows output.\n totalRows = op.est;\n firstLoop = false;\n } else {\n if (op.explain.includes('ORDER BY')) {\n totalCost += btreeCost(totalRows);\n }\n }\n }\n\n return {\n rows: totalRows,\n startupCost: totalCost,\n fanout,\n };\n}\n\nexport function btreeCost(rows: number): number {\n // B-Tree construction is ~O(n log n) so we estimate the cost as such.\n // We divide the cost by 10 because sorting in SQLite is ~10x faster\n // than bringing the data into JS and sorting there.\n return (rows * Math.log2(rows)) / 10;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,SAAgB,sBACd,IACA,YACqB;CACrB,MAAM,kBAAkB,IAAI,iBAAiB,EAAE;CAC/C,QACE,WACA,MACA,SACA,eACkB;EAgBlB,MAAM,oBAAoB,iBAAiB,OAAO,EAAE;EAGpD,MAAM,EAAC,YAAW,KAAK,WAAW,IAAI,SAAS,CAAC;EAiBhD,MAAM,MAAM,cAfE,iBACZ,WACA,SACA,YACA,mBACA,MACA,KAAA,GACA,KAAA,CAQwB,CAAK;EAM/B,MAAM,QAAQ,mBAHD,GAAG,QAAQ,GAGS,CAAI;EAGrC,OACE,MAAM,SAAS,GACf,8DAA8D,KAChE;EAMA,OAJY,aAAa,QAAQ,YAC/B,gBAAgB,UAAU,WAAW,OAAO,CAGvC;CACT;AACF;;;;;;;;;;AAWA,SAAS,mBAAmB,MAAmC;CAC7D,MAAM,QAA0B,CAAC;CAIjC,KAAK,IAAI,MAAM,IAAK,OAAO;EACzB,MAAM,WAAW,KAAK,WACpB,KACA,gBAAgB,0BAChB,CACF;EAEA,IAAI,aAAa,KAAA,GACf;EAGF,MAAM,KAAK;GACT,UAAU,KAAK,QAAQ;GACvB,UAAU,KACR,KAAK,WAAW,KAAK,gBAAgB,0BAA0B,CAAC,CAClE;GACA,SAAS,KACP,KAAK,WAAW,KAAK,gBAAgB,yBAAyB,CAAC,CACjE;GACA,KAAK,KAAK,KAAK,WAAW,KAAK,gBAAgB,qBAAqB,CAAC,CAAC;EACxE,CAAC;CACH;CAEA,OAAO,MAAM,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACrD;;;;AAKA,SAAS,aACP,WACA,QACe;CAEf,MAAM,SAAS,UAAU,UAAU,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CAEnE,IAAI,YAAY;CAChB,IAAI,YAAY;CAIhB,MAAM,cAAc,OAAO,QAAO,MAAK,EAAE,aAAa,CAAC;CAKvD,IAAI,YAAY;CAChB,KAAK,MAAM,MAAM,aACf,IAAI,WAAW;EAGb,YAAY,GAAG;EACf,YAAY;CACd,OACE,IAAI,GAAG,QAAQ,SAAS,UAAU,GAChC,aAAa,UAAU,SAAS;CAKtC,OAAO;EACL,MAAM;EACN,aAAa;EACb;CACF;AACF;AAEA,SAAgB,UAAU,MAAsB;CAI9C,OAAQ,OAAO,KAAK,KAAK,IAAI,IAAK;AACpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-source.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/table-source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,+BAA+B,CAAC;AAK7D,OAAO,KAAK,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAC,GAAG,EAAQ,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACV,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"table-source.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/table-source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,+BAA+B,CAAC;AAK7D,OAAO,KAAK,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAC,GAAG,EAAQ,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACV,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,yCAAyC,CAAC;AAmB3E,OAAO,EACL,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,6BAA6B,CAAC;AAExD,OAAO,KAAK,EAAC,QAAQ,EAAY,MAAM,SAAS,CAAC;AAoBjD;;;;;;;;;;;;;GAaG;AACH,qBAAa,WAAY,YAAW,MAAM;;IAexC;;;;;OAKG;IACH,YACE,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,WAAW,gBAAc,EAe1B;IAED,IAAI,WAAW;QAEX,IAAI;QACJ,OAAO;QACP,UAAU;MAEb;IAED;;;OAGG;IACH,KAAK,CAAC,EAAE,EAAE,QAAQ,QAEjB;IA0FD,OAAO,CACL,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,OAAO,CAAC,EAAE,SAAS,EACnB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAC3B,KAAK,CAAC,EAAE,aAAa,eA4CtB;IAED,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAOzB;IAkIA,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAM3C;IAEA,OAAO,CAAC,MAAM,EAAE,YAAY,iDAgB5B;IAqFD;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,SAAS,CAanC;CAmBF;AA4CD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACvC,SAAS,OAAO,EAAE,CAEpB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,wCAa/C;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACvC,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,GAChB,GAAG,CAaL;AAwCD,qBAAa,qBAAsB,SAAQ,KAAK;CAAG"}
|
|
@@ -142,12 +142,13 @@ var TableSource = class {
|
|
|
142
142
|
const cachedStatement = this.#stmts.cache.get(sqlAndBindings.text);
|
|
143
143
|
cachedStatement.statement.safeIntegers(true);
|
|
144
144
|
const rowIterator = cachedStatement.statement.iterate(...sqlAndBindings.values);
|
|
145
|
+
const overlayPredicate = mergeOverlayPredicate(connection.filters?.predicate, req.filter);
|
|
145
146
|
try {
|
|
146
147
|
debug?.initQuery(this.#table, sqlAndBindings.text);
|
|
147
148
|
if (sort) {
|
|
148
149
|
const comparator = makeComparator(sort, req.reverse);
|
|
149
|
-
yield* generateWithStart(generateWithYields(generateWithOverlay(req.start?.row, this.#mapFromSQLiteTypes(this.#columns, rowIterator, sqlAndBindings.text, debug), req.constraint, this.#overlay, connection.lastPushedEpoch, comparator, comparator,
|
|
150
|
-
} else yield* generateWithYields(generateWithOverlayUnordered(this.#mapFromSQLiteTypes(this.#columns, rowIterator, sqlAndBindings.text, debug), req.constraint, this.#overlay, connection.lastPushedEpoch, this.#primaryKey,
|
|
150
|
+
yield* generateWithStart(generateWithYields(generateWithOverlay(req.start?.row, this.#mapFromSQLiteTypes(this.#columns, rowIterator, sqlAndBindings.text, debug), req.constraint, this.#overlay, connection.lastPushedEpoch, comparator, comparator, overlayPredicate, req.multiConstraints), this.#shouldYield), req.start, comparator);
|
|
151
|
+
} else yield* generateWithYields(generateWithOverlayUnordered(this.#mapFromSQLiteTypes(this.#columns, rowIterator, sqlAndBindings.text, debug), req.constraint, this.#overlay, connection.lastPushedEpoch, this.#primaryKey, overlayPredicate, req.multiConstraints), this.#shouldYield);
|
|
151
152
|
} finally {
|
|
152
153
|
rowIterator.return?.();
|
|
153
154
|
if (debug) {
|
|
@@ -235,9 +236,15 @@ var TableSource = class {
|
|
|
235
236
|
return row;
|
|
236
237
|
}
|
|
237
238
|
#requestToSQL(request, filters, order) {
|
|
238
|
-
return buildSelectQuery(this.#table, this.#columns, request.constraint, filters, order, request.reverse, request.start, request.multiConstraints);
|
|
239
|
+
return buildSelectQuery(this.#table, this.#columns, request.constraint, filters, order, request.reverse, request.start, request.multiConstraints, request.filter);
|
|
239
240
|
}
|
|
240
241
|
};
|
|
242
|
+
function mergeOverlayPredicate(connPredicate, reqFilter) {
|
|
243
|
+
if (!reqFilter) return connPredicate;
|
|
244
|
+
const reqPredicate = createPredicate(reqFilter);
|
|
245
|
+
if (!connPredicate) return reqPredicate;
|
|
246
|
+
return (row) => connPredicate(row) && reqPredicate(row);
|
|
247
|
+
}
|
|
241
248
|
function getUniqueIndexes(db, tableName) {
|
|
242
249
|
const sqlAndBindings = format(sql`
|
|
243
250
|
SELECT idx.name, json_group_array(col.name) as columnsJSON
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-source.js","names":["#dbCache","#connections","#table","#columns","#uniqueIndexes","#primaryKey","#logConfig","#lc","#shouldYield","#stmts","#getStatementsFor","#allColumns","#fetch","#getSchema","#requestToSQL","#mapFromSQLiteTypes","#overlay","#writeChange","#pushEpoch","#getRowStmtCache","#getRowStmt"],"sources":["../../../../zqlite/src/table-source.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport type {LogContext} from '@rocicorp/logger';\nimport SQLite3Database from '@rocicorp/zero-sqlite3';\nimport type {LogConfig} from '../../otel/src/log-options.ts';\nimport {timeSampled} from '../../otel/src/maybe-time.ts';\nimport {assert, unreachable} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {Writable} from '../../shared/src/writable.ts';\nimport type {Condition, Ordering} from '../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../zero-protocol/src/primary-key.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport type {DebugDelegate} from '../../zql/src/builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n} from '../../zql/src/builder/filter.ts';\nimport {ChangeType} from '../../zql/src/ivm/change-type.ts';\nimport {makeComparator, type Node} from '../../zql/src/ivm/data.ts';\nimport {\n generateWithOverlay,\n generateWithOverlayUnordered,\n generateWithStart,\n genPushAndWriteWithSplitEdit,\n type Connection,\n type Overlay,\n} from '../../zql/src/ivm/memory-source.ts';\nimport {type FetchRequest} from '../../zql/src/ivm/operator.ts';\nimport type {SourceSchema} from '../../zql/src/ivm/schema.ts';\nimport {SourceChangeIndex} from '../../zql/src/ivm/source-change-index.ts';\nimport {\n type Source,\n type SourceChange,\n type SourceInput,\n} from '../../zql/src/ivm/source.ts';\nimport type {Stream} from '../../zql/src/ivm/stream.ts';\nimport {assertOrderingIncludesPK} from '../../zql/src/query/complete-ordering.ts';\nimport type {Database, Statement} from './db.ts';\nimport {compile, format, sql} from './internal/sql.ts';\nimport {StatementCache} from './internal/statement-cache.ts';\nimport {\n buildSelectQuery,\n toSQLiteType,\n type NoSubqueryCondition,\n} from './query-builder.ts';\n\ntype Statements = {\n readonly cache: StatementCache;\n readonly insert: Statement;\n readonly delete: Statement;\n readonly update: Statement | undefined;\n readonly checkExists: Statement;\n readonly getExisting: Statement;\n};\n\nlet eventCount = 0;\n\n/**\n * A source that is backed by a SQLite table.\n *\n * Values are written to the backing table _after_ being vended by the source.\n *\n * This ordering of events is to ensure self joins function properly. That is,\n * we can't reveal a value to an output before it has been pushed to that output.\n *\n * The code is fairly straightforward except for:\n * 1. Dealing with a `fetch` that has a basis of `before`.\n * 2. Dealing with compound orders that have differing directions (a ASC, b DESC, c ASC)\n *\n * See comments in relevant functions for more details.\n */\nexport class TableSource implements Source {\n readonly #dbCache = new WeakMap<Database, Statements>();\n readonly #connections: Connection[] = [];\n readonly #table: string;\n readonly #columns: Record<string, SchemaValue>;\n // Maps sorted columns JSON string (e.g. '[\"a\",\"b\"]) to Set of columns.\n readonly #uniqueIndexes: Map<string, Set<string>>;\n readonly #primaryKey: PrimaryKey;\n readonly #logConfig: LogConfig;\n readonly #lc: LogContext;\n readonly #shouldYield: () => boolean;\n #stmts: Statements;\n #overlay?: Overlay | undefined;\n #pushEpoch = 0;\n\n /**\n * @param shouldYield a function called after each row is read from the database,\n * which should return true if the source should yield the special 'yield' value\n * to yield control back to the caller at the end of the pipeline. Can\n * also throw an error to abort the pipeline processing.\n */\n constructor(\n logContext: LogContext,\n logConfig: LogConfig,\n db: Database,\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n shouldYield = () => false,\n ) {\n this.#lc = logContext;\n this.#logConfig = logConfig;\n this.#table = tableName;\n this.#columns = columns;\n this.#uniqueIndexes = getUniqueIndexes(db, tableName);\n this.#primaryKey = primaryKey;\n this.#stmts = this.#getStatementsFor(db);\n this.#shouldYield = shouldYield;\n\n assert(\n this.#uniqueIndexes.has(JSON.stringify(primaryKey.toSorted())),\n `primary key ${primaryKey} does not have a UNIQUE index`,\n );\n }\n\n get tableSchema() {\n return {\n name: this.#table,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n /**\n * Sets the db (snapshot) to use, to facilitate the Snapshotter leapfrog\n * algorithm for concurrent traversal of historic timelines.\n */\n setDB(db: Database) {\n this.#stmts = this.#getStatementsFor(db);\n }\n\n #getStatementsFor(db: Database) {\n const cached = this.#dbCache.get(db);\n if (cached) {\n return cached;\n }\n\n const stmts = {\n cache: new StatementCache(db),\n insert: db.prepare(\n compile(\n sql`INSERT INTO ${sql.ident(this.#table)} (${sql.join(\n Object.keys(this.#columns).map(c => sql.ident(c)),\n ', ',\n )}) VALUES (${sql.__dangerous__rawValue(\n Array.from({length: Object.keys(this.#columns).length})\n .fill('?')\n .join(','),\n )})`,\n ),\n ),\n delete: db.prepare(\n compile(\n sql`DELETE FROM ${sql.ident(this.#table)} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n ),\n // If all the columns are part of the primary key, we cannot use UPDATE.\n update:\n Object.keys(this.#columns).length > this.#primaryKey.length\n ? db.prepare(\n compile(\n sql`UPDATE ${sql.ident(this.#table)} SET ${sql.join(\n nonPrimaryKeys(this.#columns, this.#primaryKey).map(\n c => sql`${sql.ident(c)}=?`,\n ),\n ',',\n )} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n )\n : undefined,\n checkExists: db.prepare(\n compile(\n sql`SELECT 1 AS \"exists\" FROM ${sql.ident(\n this.#table,\n )} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )} LIMIT 1`,\n ),\n ),\n getExisting: db.prepare(\n compile(\n sql`SELECT * FROM ${sql.ident(this.#table)} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n ),\n };\n this.#dbCache.set(db, stmts);\n return stmts;\n }\n\n get #allColumns() {\n return sql.join(\n Object.keys(this.#columns).map(c => sql.ident(c)),\n sql`,`,\n );\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#table,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n relationships: {},\n isHidden: false,\n system: 'client',\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n debug?: DebugDelegate,\n ) {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n // PK comparator is used for source-level overlay matching (remove by PK\n // equality) even when no ordering is requested.\n const primaryKeySort: Ordering = this.#primaryKey.map(k => [k, 'asc']);\n\n const input: SourceInput = {\n getSchema: () => schema,\n fetch: req => this.#fetch(req, connection),\n setOutput: output => {\n connection.output = output;\n },\n destroy: () => {\n const idx = this.#connections.indexOf(connection);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n debug,\n output: undefined,\n sort,\n splitEditKeys,\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n compareRows: sort ? makeComparator(sort) : makeComparator(primaryKeySort),\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(sort, this.#primaryKey);\n }\n\n this.#connections.push(connection);\n return input;\n }\n\n toSQLiteRow(row: Row): Row {\n return Object.fromEntries(\n Object.entries(row).map(([key, value]) => [\n key,\n toSQLiteType(value, this.#columns[key].type),\n ]),\n ) as Row;\n }\n\n *#fetch(req: FetchRequest, connection: Connection): Stream<Node | 'yield'> {\n const {sort, debug} = connection;\n\n const query = this.#requestToSQL(req, connection.filters?.condition, sort);\n const sqlAndBindings = format(query);\n\n const cachedStatement = this.#stmts.cache.get(sqlAndBindings.text);\n cachedStatement.statement.safeIntegers(true);\n const rowIterator = cachedStatement.statement.iterate<Row>(\n ...sqlAndBindings.values,\n );\n try {\n debug?.initQuery(this.#table, sqlAndBindings.text);\n\n if (sort) {\n const comparator = makeComparator(sort, req.reverse);\n yield* generateWithStart(\n generateWithYields(\n generateWithOverlay(\n req.start?.row,\n this.#mapFromSQLiteTypes(\n this.#columns,\n rowIterator,\n sqlAndBindings.text,\n debug,\n ),\n req.constraint,\n this.#overlay,\n connection.lastPushedEpoch,\n comparator,\n // SQL does the ordering and constraining, so the row stream is\n // already in the connection's sort order: the splice comparator\n // and the `startAt` comparator coincide here.\n comparator,\n connection.filters?.predicate,\n req.multiConstraints,\n ),\n this.#shouldYield,\n ),\n req.start,\n comparator,\n );\n } else {\n yield* generateWithYields(\n generateWithOverlayUnordered(\n this.#mapFromSQLiteTypes(\n this.#columns,\n rowIterator,\n sqlAndBindings.text,\n debug,\n ),\n req.constraint,\n this.#overlay,\n connection.lastPushedEpoch,\n this.#primaryKey,\n connection.filters?.predicate,\n req.multiConstraints,\n ),\n this.#shouldYield,\n );\n }\n } finally {\n // Ensure the SQLite iterate() is closed.\n rowIterator.return?.();\n if (debug) {\n let totalNvisit = 0;\n const planLines: string[] = [];\n for (let i = 0; ; i++) {\n const nvisit = cachedStatement.statement.scanStatus(\n i,\n SQLite3Database.SQLITE_SCANSTAT_NVISIT,\n 1,\n );\n if (nvisit === undefined) {\n break;\n }\n totalNvisit += Number(nvisit);\n const explain = cachedStatement.statement.scanStatus(\n i,\n SQLite3Database.SQLITE_SCANSTAT_EXPLAIN,\n 1,\n );\n if (typeof explain === 'string' && explain.length > 0) {\n planLines.push(explain);\n }\n }\n if (totalNvisit !== 0) {\n debug.recordNVisit(this.#table, sqlAndBindings.text, totalNvisit);\n }\n if (planLines.length > 0) {\n debug.recordExplain(this.#table, sqlAndBindings.text, planLines);\n }\n cachedStatement.statement.scanStatusReset();\n }\n this.#stmts.cache.return(cachedStatement);\n }\n }\n\n *#mapFromSQLiteTypes(\n valueTypes: Record<string, SchemaValue>,\n rowIterator: IterableIterator<Row>,\n query: string,\n debug: DebugDelegate | undefined,\n ): IterableIterator<Row> {\n let result;\n do {\n result = timeSampled(\n this.#lc,\n ++eventCount,\n this.#logConfig.ivmSampling,\n () => rowIterator.next(),\n this.#logConfig.slowRowThreshold,\n () =>\n `table-source.next took too long for ${query}. Are you missing an index?`,\n );\n if (result.done) {\n break;\n }\n const row = fromSQLiteTypes(valueTypes, result.value, this.#table);\n debug?.rowVended(this.#table, query, row);\n yield row;\n } while (!result.done);\n }\n\n *push(change: SourceChange): Stream<'yield'> {\n for (const result of this.genPush(change)) {\n if (result === 'yield') {\n yield result;\n }\n }\n }\n\n *genPush(change: SourceChange) {\n const exists = (row: Row) =>\n this.#stmts.checkExists.get<{exists: number} | undefined>(\n ...toSQLiteTypes(this.#primaryKey, row, this.#columns),\n )?.exists === 1;\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n\n yield* genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n this.#stmts.insert.run(\n ...toSQLiteTypes(\n // TODO(arv): Compute this once!\n Object.keys(this.#columns),\n change[SourceChangeIndex.ROW],\n this.#columns,\n ),\n );\n break;\n case ChangeType.REMOVE:\n this.#stmts.delete.run(\n ...toSQLiteTypes(\n this.#primaryKey,\n change[SourceChangeIndex.ROW],\n this.#columns,\n ),\n );\n break;\n case ChangeType.EDIT: {\n // If the PK is the same, use UPDATE.\n if (\n canUseUpdate(\n change[SourceChangeIndex.OLD_ROW],\n change[SourceChangeIndex.ROW],\n this.#columns,\n this.#primaryKey,\n )\n ) {\n const mergedRow = {\n ...change[SourceChangeIndex.OLD_ROW],\n ...change[SourceChangeIndex.ROW],\n };\n const params = [\n ...nonPrimaryValues(this.#columns, this.#primaryKey, mergedRow),\n ...toSQLiteTypes(this.#primaryKey, mergedRow, this.#columns),\n ];\n must(this.#stmts.update).run(params);\n } else {\n this.#stmts.delete.run(\n ...toSQLiteTypes(\n this.#primaryKey,\n change[SourceChangeIndex.OLD_ROW],\n this.#columns,\n ),\n );\n this.#stmts.insert.run(\n ...toSQLiteTypes(\n Object.keys(this.#columns),\n change[SourceChangeIndex.ROW],\n this.#columns,\n ),\n );\n }\n\n break;\n }\n default:\n unreachable(change);\n }\n }\n\n #getRowStmtCache = new Map<string, string>();\n\n #getRowStmt(keyCols: string[]): string {\n const keyString = JSON.stringify(keyCols);\n let stmt = this.#getRowStmtCache.get(keyString);\n if (!stmt) {\n stmt = compile(\n sql`SELECT ${this.#allColumns} FROM ${sql.ident(\n this.#table,\n )} WHERE ${sql.join(\n keyCols.map(k => sql`${sql.ident(k)}=?`),\n sql` AND`,\n )}`,\n );\n this.#getRowStmtCache.set(keyString, stmt);\n }\n return stmt;\n }\n\n /**\n * Retrieves a row from the backing DB by a unique key, or `undefined` if such a\n * row does not exist. This is not used in the IVM pipeline but is useful\n * for retrieving data that is consistent with the state (and type\n * semantics) of the pipeline. Note that this key may not necessarily correspond\n * to the `primaryKey` with which this TableSource.\n */\n getRow(rowKey: Row): Row | undefined {\n const keyCols = Object.keys(rowKey);\n\n const stmt = this.#getRowStmt(keyCols);\n const row = this.#stmts.cache.use(stmt, cached =>\n cached.statement\n .safeIntegers(true)\n .get<Row>(...toSQLiteTypes(keyCols, rowKey, this.#columns)),\n );\n if (row) {\n return fromSQLiteTypes(this.#columns, row, this.#table);\n }\n return row;\n }\n\n #requestToSQL(\n request: FetchRequest,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n ): SQLQuery {\n return buildSelectQuery(\n this.#table,\n this.#columns,\n request.constraint,\n filters,\n order,\n request.reverse,\n request.start,\n request.multiConstraints,\n );\n }\n}\n\nfunction getUniqueIndexes(\n db: Database,\n tableName: string,\n): Map<string, Set<string>> {\n const sqlAndBindings = format(\n sql`\n SELECT idx.name, json_group_array(col.name) as columnsJSON\n FROM sqlite_master as idx\n JOIN pragma_index_list(idx.tbl_name) AS info ON info.name = idx.name\n JOIN pragma_index_info(idx.name) as col\n WHERE idx.tbl_name = ${tableName} AND\n idx.type = 'index' AND \n info.\"unique\" != 0 AND\n info.partial = 0\n GROUP BY idx.name\n ORDER BY idx.name`,\n );\n const stmt = db.prepare(sqlAndBindings.text);\n const indexes = stmt.all<{columnsJSON: string}>(...sqlAndBindings.values);\n return new Map(\n indexes.map(({columnsJSON}) => {\n const columns = JSON.parse(columnsJSON);\n const set = new Set<string>(columns);\n return [JSON.stringify(columns.sort()), set];\n }),\n );\n}\n\nexport function toSQLiteTypes(\n columns: readonly string[],\n row: Row,\n columnTypes: Record<string, SchemaValue>,\n): readonly unknown[] {\n return columns.map(col => toSQLiteType(row[col], columnTypes[col].type));\n}\n\nexport function toSQLiteTypeName(type: ValueType) {\n switch (type) {\n case 'boolean':\n return 'INTEGER';\n case 'number':\n return 'REAL';\n case 'string':\n return 'TEXT';\n case 'null':\n return 'NULL';\n case 'json':\n return 'TEXT';\n }\n}\n\nexport function fromSQLiteTypes(\n valueTypes: Record<string, SchemaValue>,\n row: Row,\n tableName: string,\n): Row {\n const newRow: Writable<Row> = {};\n for (const key of Object.keys(row)) {\n const valueType = valueTypes[key];\n if (valueType === undefined) {\n const columnList = Object.keys(valueTypes).sort().join(', ');\n throw new Error(\n `Invalid column \"${key}\" for table \"${tableName}\". Synced columns include ${columnList}`,\n );\n }\n newRow[key] = fromSQLiteType(valueType.type, row[key], key, tableName);\n }\n return newRow;\n}\n\nfunction fromSQLiteType(\n valueType: ValueType,\n v: Value,\n column: string,\n tableName: string,\n): Value {\n if (v === null) {\n return null;\n }\n switch (valueType) {\n case 'boolean':\n return !!v;\n case 'number':\n case 'string':\n case 'null':\n if (typeof v === 'bigint') {\n if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {\n throw new UnsupportedValueError(\n `value ${v} (in ${tableName}.${column}) is outside of supported bounds`,\n );\n }\n return Number(v);\n }\n return v;\n case 'json':\n try {\n return JSON.parse(v as string);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n throw new UnsupportedValueError(\n `Failed to parse JSON for ${tableName}.${column}: ${errorMessage}`,\n {cause: error},\n );\n }\n }\n}\n\nexport class UnsupportedValueError extends Error {}\n\nfunction canUseUpdate(\n oldRow: Row,\n row: Row,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n): boolean {\n for (const pk of primaryKey) {\n if (oldRow[pk] !== row[pk]) {\n return false;\n }\n }\n return Object.keys(columns).length > primaryKey.length;\n}\n\nfunction nonPrimaryValues(\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n row: Row,\n): Iterable<unknown> {\n return nonPrimaryKeys(columns, primaryKey).map(c =>\n toSQLiteType(row[c], columns[c].type),\n );\n}\n\nfunction nonPrimaryKeys(\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n) {\n return Object.keys(columns).filter(c => !primaryKey.includes(c));\n}\n\nfunction* generateWithYields(stream: Stream<Node>, shouldYield: () => boolean) {\n for (const n of stream) {\n if (shouldYield()) {\n yield 'yield';\n }\n yield n;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"table-source.js","names":["#dbCache","#connections","#table","#columns","#uniqueIndexes","#primaryKey","#logConfig","#lc","#shouldYield","#stmts","#getStatementsFor","#allColumns","#fetch","#getSchema","#requestToSQL","#mapFromSQLiteTypes","#overlay","#writeChange","#pushEpoch","#getRowStmtCache","#getRowStmt"],"sources":["../../../../zqlite/src/table-source.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport type {LogContext} from '@rocicorp/logger';\nimport SQLite3Database from '@rocicorp/zero-sqlite3';\nimport type {LogConfig} from '../../otel/src/log-options.ts';\nimport {timeSampled} from '../../otel/src/maybe-time.ts';\nimport {assert, unreachable} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {Writable} from '../../shared/src/writable.ts';\nimport type {Condition, Ordering} from '../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../zero-protocol/src/primary-key.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport type {DebugDelegate} from '../../zql/src/builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n type NoSubqueryCondition as StrictNoSubqueryCondition,\n} from '../../zql/src/builder/filter.ts';\nimport {ChangeType} from '../../zql/src/ivm/change-type.ts';\nimport {makeComparator, type Node} from '../../zql/src/ivm/data.ts';\nimport {\n generateWithOverlay,\n generateWithOverlayUnordered,\n generateWithStart,\n genPushAndWriteWithSplitEdit,\n type Connection,\n type Overlay,\n} from '../../zql/src/ivm/memory-source.ts';\nimport {type FetchRequest} from '../../zql/src/ivm/operator.ts';\nimport type {SourceSchema} from '../../zql/src/ivm/schema.ts';\nimport {SourceChangeIndex} from '../../zql/src/ivm/source-change-index.ts';\nimport {\n type Source,\n type SourceChange,\n type SourceInput,\n} from '../../zql/src/ivm/source.ts';\nimport type {Stream} from '../../zql/src/ivm/stream.ts';\nimport {assertOrderingIncludesPK} from '../../zql/src/query/complete-ordering.ts';\nimport type {Database, Statement} from './db.ts';\nimport {compile, format, sql} from './internal/sql.ts';\nimport {StatementCache} from './internal/statement-cache.ts';\nimport {\n buildSelectQuery,\n toSQLiteType,\n type NoSubqueryCondition,\n} from './query-builder.ts';\n\ntype Statements = {\n readonly cache: StatementCache;\n readonly insert: Statement;\n readonly delete: Statement;\n readonly update: Statement | undefined;\n readonly checkExists: Statement;\n readonly getExisting: Statement;\n};\n\nlet eventCount = 0;\n\n/**\n * A source that is backed by a SQLite table.\n *\n * Values are written to the backing table _after_ being vended by the source.\n *\n * This ordering of events is to ensure self joins function properly. That is,\n * we can't reveal a value to an output before it has been pushed to that output.\n *\n * The code is fairly straightforward except for:\n * 1. Dealing with a `fetch` that has a basis of `before`.\n * 2. Dealing with compound orders that have differing directions (a ASC, b DESC, c ASC)\n *\n * See comments in relevant functions for more details.\n */\nexport class TableSource implements Source {\n readonly #dbCache = new WeakMap<Database, Statements>();\n readonly #connections: Connection[] = [];\n readonly #table: string;\n readonly #columns: Record<string, SchemaValue>;\n // Maps sorted columns JSON string (e.g. '[\"a\",\"b\"]) to Set of columns.\n readonly #uniqueIndexes: Map<string, Set<string>>;\n readonly #primaryKey: PrimaryKey;\n readonly #logConfig: LogConfig;\n readonly #lc: LogContext;\n readonly #shouldYield: () => boolean;\n #stmts: Statements;\n #overlay?: Overlay | undefined;\n #pushEpoch = 0;\n\n /**\n * @param shouldYield a function called after each row is read from the database,\n * which should return true if the source should yield the special 'yield' value\n * to yield control back to the caller at the end of the pipeline. Can\n * also throw an error to abort the pipeline processing.\n */\n constructor(\n logContext: LogContext,\n logConfig: LogConfig,\n db: Database,\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n shouldYield = () => false,\n ) {\n this.#lc = logContext;\n this.#logConfig = logConfig;\n this.#table = tableName;\n this.#columns = columns;\n this.#uniqueIndexes = getUniqueIndexes(db, tableName);\n this.#primaryKey = primaryKey;\n this.#stmts = this.#getStatementsFor(db);\n this.#shouldYield = shouldYield;\n\n assert(\n this.#uniqueIndexes.has(JSON.stringify(primaryKey.toSorted())),\n `primary key ${primaryKey} does not have a UNIQUE index`,\n );\n }\n\n get tableSchema() {\n return {\n name: this.#table,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n /**\n * Sets the db (snapshot) to use, to facilitate the Snapshotter leapfrog\n * algorithm for concurrent traversal of historic timelines.\n */\n setDB(db: Database) {\n this.#stmts = this.#getStatementsFor(db);\n }\n\n #getStatementsFor(db: Database) {\n const cached = this.#dbCache.get(db);\n if (cached) {\n return cached;\n }\n\n const stmts = {\n cache: new StatementCache(db),\n insert: db.prepare(\n compile(\n sql`INSERT INTO ${sql.ident(this.#table)} (${sql.join(\n Object.keys(this.#columns).map(c => sql.ident(c)),\n ', ',\n )}) VALUES (${sql.__dangerous__rawValue(\n Array.from({length: Object.keys(this.#columns).length})\n .fill('?')\n .join(','),\n )})`,\n ),\n ),\n delete: db.prepare(\n compile(\n sql`DELETE FROM ${sql.ident(this.#table)} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n ),\n // If all the columns are part of the primary key, we cannot use UPDATE.\n update:\n Object.keys(this.#columns).length > this.#primaryKey.length\n ? db.prepare(\n compile(\n sql`UPDATE ${sql.ident(this.#table)} SET ${sql.join(\n nonPrimaryKeys(this.#columns, this.#primaryKey).map(\n c => sql`${sql.ident(c)}=?`,\n ),\n ',',\n )} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n )\n : undefined,\n checkExists: db.prepare(\n compile(\n sql`SELECT 1 AS \"exists\" FROM ${sql.ident(\n this.#table,\n )} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )} LIMIT 1`,\n ),\n ),\n getExisting: db.prepare(\n compile(\n sql`SELECT * FROM ${sql.ident(this.#table)} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n ),\n };\n this.#dbCache.set(db, stmts);\n return stmts;\n }\n\n get #allColumns() {\n return sql.join(\n Object.keys(this.#columns).map(c => sql.ident(c)),\n sql`,`,\n );\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#table,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n relationships: {},\n isHidden: false,\n system: 'client',\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n debug?: DebugDelegate,\n ) {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n // PK comparator is used for source-level overlay matching (remove by PK\n // equality) even when no ordering is requested.\n const primaryKeySort: Ordering = this.#primaryKey.map(k => [k, 'asc']);\n\n const input: SourceInput = {\n getSchema: () => schema,\n fetch: req => this.#fetch(req, connection),\n setOutput: output => {\n connection.output = output;\n },\n destroy: () => {\n const idx = this.#connections.indexOf(connection);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n debug,\n output: undefined,\n sort,\n splitEditKeys,\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n compareRows: sort ? makeComparator(sort) : makeComparator(primaryKeySort),\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(sort, this.#primaryKey);\n }\n\n this.#connections.push(connection);\n return input;\n }\n\n toSQLiteRow(row: Row): Row {\n return Object.fromEntries(\n Object.entries(row).map(([key, value]) => [\n key,\n toSQLiteType(value, this.#columns[key].type),\n ]),\n ) as Row;\n }\n\n *#fetch(req: FetchRequest, connection: Connection): Stream<Node | 'yield'> {\n const {sort, debug} = connection;\n\n const query = this.#requestToSQL(req, connection.filters?.condition, sort);\n const sqlAndBindings = format(query);\n\n const cachedStatement = this.#stmts.cache.get(sqlAndBindings.text);\n cachedStatement.statement.safeIntegers(true);\n const rowIterator = cachedStatement.statement.iterate<Row>(\n ...sqlAndBindings.values,\n );\n const overlayPredicate = mergeOverlayPredicate(\n connection.filters?.predicate,\n req.filter,\n );\n try {\n debug?.initQuery(this.#table, sqlAndBindings.text);\n\n if (sort) {\n const comparator = makeComparator(sort, req.reverse);\n yield* generateWithStart(\n generateWithYields(\n generateWithOverlay(\n req.start?.row,\n this.#mapFromSQLiteTypes(\n this.#columns,\n rowIterator,\n sqlAndBindings.text,\n debug,\n ),\n req.constraint,\n this.#overlay,\n connection.lastPushedEpoch,\n comparator,\n // SQL does the ordering and constraining, so the row stream is\n // already in the connection's sort order: the splice comparator\n // and the `startAt` comparator coincide here.\n comparator,\n overlayPredicate,\n req.multiConstraints,\n ),\n this.#shouldYield,\n ),\n req.start,\n comparator,\n );\n } else {\n yield* generateWithYields(\n generateWithOverlayUnordered(\n this.#mapFromSQLiteTypes(\n this.#columns,\n rowIterator,\n sqlAndBindings.text,\n debug,\n ),\n req.constraint,\n this.#overlay,\n connection.lastPushedEpoch,\n this.#primaryKey,\n overlayPredicate,\n req.multiConstraints,\n ),\n this.#shouldYield,\n );\n }\n } finally {\n // Ensure the SQLite iterate() is closed.\n rowIterator.return?.();\n if (debug) {\n let totalNvisit = 0;\n const planLines: string[] = [];\n for (let i = 0; ; i++) {\n const nvisit = cachedStatement.statement.scanStatus(\n i,\n SQLite3Database.SQLITE_SCANSTAT_NVISIT,\n 1,\n );\n if (nvisit === undefined) {\n break;\n }\n totalNvisit += Number(nvisit);\n const explain = cachedStatement.statement.scanStatus(\n i,\n SQLite3Database.SQLITE_SCANSTAT_EXPLAIN,\n 1,\n );\n if (typeof explain === 'string' && explain.length > 0) {\n planLines.push(explain);\n }\n }\n if (totalNvisit !== 0) {\n debug.recordNVisit(this.#table, sqlAndBindings.text, totalNvisit);\n }\n if (planLines.length > 0) {\n debug.recordExplain(this.#table, sqlAndBindings.text, planLines);\n }\n cachedStatement.statement.scanStatusReset();\n }\n this.#stmts.cache.return(cachedStatement);\n }\n }\n\n *#mapFromSQLiteTypes(\n valueTypes: Record<string, SchemaValue>,\n rowIterator: IterableIterator<Row>,\n query: string,\n debug: DebugDelegate | undefined,\n ): IterableIterator<Row> {\n let result;\n do {\n result = timeSampled(\n this.#lc,\n ++eventCount,\n this.#logConfig.ivmSampling,\n () => rowIterator.next(),\n this.#logConfig.slowRowThreshold,\n () =>\n `table-source.next took too long for ${query}. Are you missing an index?`,\n );\n if (result.done) {\n break;\n }\n const row = fromSQLiteTypes(valueTypes, result.value, this.#table);\n debug?.rowVended(this.#table, query, row);\n yield row;\n } while (!result.done);\n }\n\n *push(change: SourceChange): Stream<'yield'> {\n for (const result of this.genPush(change)) {\n if (result === 'yield') {\n yield result;\n }\n }\n }\n\n *genPush(change: SourceChange) {\n const exists = (row: Row) =>\n this.#stmts.checkExists.get<{exists: number} | undefined>(\n ...toSQLiteTypes(this.#primaryKey, row, this.#columns),\n )?.exists === 1;\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n\n yield* genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n this.#stmts.insert.run(\n ...toSQLiteTypes(\n // TODO(arv): Compute this once!\n Object.keys(this.#columns),\n change[SourceChangeIndex.ROW],\n this.#columns,\n ),\n );\n break;\n case ChangeType.REMOVE:\n this.#stmts.delete.run(\n ...toSQLiteTypes(\n this.#primaryKey,\n change[SourceChangeIndex.ROW],\n this.#columns,\n ),\n );\n break;\n case ChangeType.EDIT: {\n // If the PK is the same, use UPDATE.\n if (\n canUseUpdate(\n change[SourceChangeIndex.OLD_ROW],\n change[SourceChangeIndex.ROW],\n this.#columns,\n this.#primaryKey,\n )\n ) {\n const mergedRow = {\n ...change[SourceChangeIndex.OLD_ROW],\n ...change[SourceChangeIndex.ROW],\n };\n const params = [\n ...nonPrimaryValues(this.#columns, this.#primaryKey, mergedRow),\n ...toSQLiteTypes(this.#primaryKey, mergedRow, this.#columns),\n ];\n must(this.#stmts.update).run(params);\n } else {\n this.#stmts.delete.run(\n ...toSQLiteTypes(\n this.#primaryKey,\n change[SourceChangeIndex.OLD_ROW],\n this.#columns,\n ),\n );\n this.#stmts.insert.run(\n ...toSQLiteTypes(\n Object.keys(this.#columns),\n change[SourceChangeIndex.ROW],\n this.#columns,\n ),\n );\n }\n\n break;\n }\n default:\n unreachable(change);\n }\n }\n\n #getRowStmtCache = new Map<string, string>();\n\n #getRowStmt(keyCols: string[]): string {\n const keyString = JSON.stringify(keyCols);\n let stmt = this.#getRowStmtCache.get(keyString);\n if (!stmt) {\n stmt = compile(\n sql`SELECT ${this.#allColumns} FROM ${sql.ident(\n this.#table,\n )} WHERE ${sql.join(\n keyCols.map(k => sql`${sql.ident(k)}=?`),\n sql` AND`,\n )}`,\n );\n this.#getRowStmtCache.set(keyString, stmt);\n }\n return stmt;\n }\n\n /**\n * Retrieves a row from the backing DB by a unique key, or `undefined` if such a\n * row does not exist. This is not used in the IVM pipeline but is useful\n * for retrieving data that is consistent with the state (and type\n * semantics) of the pipeline. Note that this key may not necessarily correspond\n * to the `primaryKey` with which this TableSource.\n */\n getRow(rowKey: Row): Row | undefined {\n const keyCols = Object.keys(rowKey);\n\n const stmt = this.#getRowStmt(keyCols);\n const row = this.#stmts.cache.use(stmt, cached =>\n cached.statement\n .safeIntegers(true)\n .get<Row>(...toSQLiteTypes(keyCols, rowKey, this.#columns)),\n );\n if (row) {\n return fromSQLiteTypes(this.#columns, row, this.#table);\n }\n return row;\n }\n\n #requestToSQL(\n request: FetchRequest,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n ): SQLQuery {\n return buildSelectQuery(\n this.#table,\n this.#columns,\n request.constraint,\n filters,\n order,\n request.reverse,\n request.start,\n request.multiConstraints,\n request.filter,\n );\n }\n}\n\nfunction mergeOverlayPredicate(\n connPredicate: ((row: Row) => boolean) | undefined,\n reqFilter: StrictNoSubqueryCondition | undefined,\n): ((row: Row) => boolean) | undefined {\n if (!reqFilter) {\n return connPredicate;\n }\n const reqPredicate = createPredicate(reqFilter);\n if (!connPredicate) {\n return reqPredicate;\n }\n return row => connPredicate(row) && reqPredicate(row);\n}\n\nfunction getUniqueIndexes(\n db: Database,\n tableName: string,\n): Map<string, Set<string>> {\n const sqlAndBindings = format(\n sql`\n SELECT idx.name, json_group_array(col.name) as columnsJSON\n FROM sqlite_master as idx\n JOIN pragma_index_list(idx.tbl_name) AS info ON info.name = idx.name\n JOIN pragma_index_info(idx.name) as col\n WHERE idx.tbl_name = ${tableName} AND\n idx.type = 'index' AND \n info.\"unique\" != 0 AND\n info.partial = 0\n GROUP BY idx.name\n ORDER BY idx.name`,\n );\n const stmt = db.prepare(sqlAndBindings.text);\n const indexes = stmt.all<{columnsJSON: string}>(...sqlAndBindings.values);\n return new Map(\n indexes.map(({columnsJSON}) => {\n const columns = JSON.parse(columnsJSON);\n const set = new Set<string>(columns);\n return [JSON.stringify(columns.sort()), set];\n }),\n );\n}\n\nexport function toSQLiteTypes(\n columns: readonly string[],\n row: Row,\n columnTypes: Record<string, SchemaValue>,\n): readonly unknown[] {\n return columns.map(col => toSQLiteType(row[col], columnTypes[col].type));\n}\n\nexport function toSQLiteTypeName(type: ValueType) {\n switch (type) {\n case 'boolean':\n return 'INTEGER';\n case 'number':\n return 'REAL';\n case 'string':\n return 'TEXT';\n case 'null':\n return 'NULL';\n case 'json':\n return 'TEXT';\n }\n}\n\nexport function fromSQLiteTypes(\n valueTypes: Record<string, SchemaValue>,\n row: Row,\n tableName: string,\n): Row {\n const newRow: Writable<Row> = {};\n for (const key of Object.keys(row)) {\n const valueType = valueTypes[key];\n if (valueType === undefined) {\n const columnList = Object.keys(valueTypes).sort().join(', ');\n throw new Error(\n `Invalid column \"${key}\" for table \"${tableName}\". Synced columns include ${columnList}`,\n );\n }\n newRow[key] = fromSQLiteType(valueType.type, row[key], key, tableName);\n }\n return newRow;\n}\n\nfunction fromSQLiteType(\n valueType: ValueType,\n v: Value,\n column: string,\n tableName: string,\n): Value {\n if (v === null) {\n return null;\n }\n switch (valueType) {\n case 'boolean':\n return !!v;\n case 'number':\n case 'string':\n case 'null':\n if (typeof v === 'bigint') {\n if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {\n throw new UnsupportedValueError(\n `value ${v} (in ${tableName}.${column}) is outside of supported bounds`,\n );\n }\n return Number(v);\n }\n return v;\n case 'json':\n try {\n return JSON.parse(v as string);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n throw new UnsupportedValueError(\n `Failed to parse JSON for ${tableName}.${column}: ${errorMessage}`,\n {cause: error},\n );\n }\n }\n}\n\nexport class UnsupportedValueError extends Error {}\n\nfunction canUseUpdate(\n oldRow: Row,\n row: Row,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n): boolean {\n for (const pk of primaryKey) {\n if (oldRow[pk] !== row[pk]) {\n return false;\n }\n }\n return Object.keys(columns).length > primaryKey.length;\n}\n\nfunction nonPrimaryValues(\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n row: Row,\n): Iterable<unknown> {\n return nonPrimaryKeys(columns, primaryKey).map(c =>\n toSQLiteType(row[c], columns[c].type),\n );\n}\n\nfunction nonPrimaryKeys(\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n) {\n return Object.keys(columns).filter(c => !primaryKey.includes(c));\n}\n\nfunction* generateWithYields(stream: Stream<Node>, shouldYield: () => boolean) {\n for (const n of stream) {\n if (shouldYield()) {\n yield 'yield';\n }\n yield n;\n }\n}\n"],"mappings":"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"}
|