@rocicorp/zero 1.11.0-canary.13 → 1.11.0-canary.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/analyze-query/src/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts +8 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.js +25 -25
- package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
- package/out/otel/src/log-options.d.ts +16 -0
- package/out/otel/src/log-options.d.ts.map +1 -1
- package/out/otel/src/log-options.js +54 -1
- package/out/otel/src/log-options.js.map +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
- package/out/replicache/src/get-kv-store-provider.js +4 -4
- package/out/replicache/src/get-kv-store-provider.js.map +1 -1
- package/out/replicache/src/kv/idb-store.d.ts +10 -1
- package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
- package/out/replicache/src/kv/idb-store.js +18 -2
- package/out/replicache/src/kv/idb-store.js.map +1 -1
- package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
- package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
- package/out/replicache/src/kv/mem-fallback-store.js +177 -0
- package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
- package/out/replicache/src/kv/op-sqlite/store.js +3 -3
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.d.ts +9 -0
- package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +86 -6
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/new-client-channel.js.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts +17 -3
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +81 -11
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
- package/out/replicache/src/persist/persist.d.ts.map +1 -1
- package/out/replicache/src/persist/persist.js +6 -3
- package/out/replicache/src/persist/persist.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts +43 -3
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +135 -17
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/storage-failure.d.ts +37 -0
- package/out/replicache/src/storage-failure.d.ts.map +1 -0
- package/out/replicache/src/storage-failure.js +31 -0
- package/out/replicache/src/storage-failure.js.map +1 -0
- package/out/shared/src/random-values.js +12 -1
- package/out/shared/src/random-values.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/zero.js +2 -1
- package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
- package/out/zero-cache/src/config/normalize.js +1 -1
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +26 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +36 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
- package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
- package/out/zero-cache/src/observability/log-throttle.js +76 -0
- package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
- package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
- package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.js +2 -0
- package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +20 -2
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +27 -15
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -0
- 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 +15 -3
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +9 -0
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +204 -18
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +11 -2
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +11 -2
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +43 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +10 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +57 -0
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +20 -6
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +66 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +154 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +7 -6
- 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-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +20 -4
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +26 -1
- 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/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +88 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js +9 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +29 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +34 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/plannerz.d.ts +159 -0
- package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
- package/out/zero-cache/src/services/plannerz.js +263 -0
- package/out/zero-cache/src/services/plannerz.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +44 -12
- package/out/zero-cache/src/services/replicator/change-processor.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 +4 -3
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
- 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.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +36 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +362 -20
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +77 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js +206 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +11 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +7 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +49 -3
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +2 -2
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +29 -0
- package/out/zero-client/src/client/options.d.ts.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +8 -23
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/mod.d.ts +1 -0
- package/out/zero-client/src/mod.d.ts.map +1 -1
- package/out/zero-client/src/mod.js +1 -0
- package/out/zero-protocol/src/down.d.ts +2 -2
- package/out/zero-react/src/zero.js +1 -0
- package/out/zero-solid/src/zero.js +1 -0
- package/out/zql/src/builder/builder.d.ts +6 -0
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +4 -3
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +13 -0
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +1 -1
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/planner/planner-builder.d.ts +12 -1
- package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
- package/out/zql/src/planner/planner-builder.js +8 -1
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.d.ts +43 -0
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +31 -4
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-graph.d.ts +10 -0
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js +14 -0
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-warnings.d.ts +83 -0
- package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
- package/out/zql/src/planner/planner-warnings.js +78 -0
- package/out/zql/src/planner/planner-warnings.js.map +1 -0
- package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
- package/out/zql/src/query/measure-push-operator.js +30 -8
- package/out/zql/src/query/measure-push-operator.js.map +1 -1
- package/out/zqlite/src/pending-delta.d.ts +110 -0
- package/out/zqlite/src/pending-delta.d.ts.map +1 -0
- package/out/zqlite/src/pending-delta.js +278 -0
- package/out/zqlite/src/pending-delta.js.map +1 -0
- package/out/zqlite/src/query-delegate.d.ts.map +1 -1
- package/out/zqlite/src/query-delegate.js +5 -1
- package/out/zqlite/src/query-delegate.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +52 -1
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js +2 -26
- package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
- package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
- package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
- package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
- package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
- package/out/zqlite/src/table-source.d.ts +68 -0
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +206 -7
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -35
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +0 -156
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +0 -15
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-source.js","names":["#tableName","#columns","#primaryKey","#primaryIndexSort","#indexes","#connections","#getPrimaryIndex","#fetch","#disconnect","#getSchema","#fetchMulti","#getOrCreateIndex","#overlay","#writeChange","#pushEpoch"],"sources":["../../../../../zql/src/ivm/memory-source.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {BTreeSet} from '../../../shared/src/btree-set.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport {once, toSorted} from '../../../shared/src/iterables.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n Condition,\n Ordering,\n OrderPart,\n} 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 {SchemaValue} from '../../../zero-types/src/schema-value.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n type NoSubqueryCondition,\n} from '../builder/filter.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Change} from './change.ts';\nimport {makeAddChange, makeEditChange, makeRemoveChange} from './change.ts';\nimport {\n constraintMatchesPrimaryKey,\n constraintMatchesRow,\n primaryKeyConstraintFromFilters,\n type Constraint,\n} from './constraint.ts';\nimport {\n compareValues,\n makeComparator,\n valuesEqual,\n type Comparator,\n type Node,\n} from './data.ts';\nimport {filterPush} from './filter-push.ts';\nimport {\n skipYields,\n type FetchRequest,\n type Input,\n type MultiConstraint,\n type Output,\n type Start,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {SourceChangeIndex} from './source-change-index.ts';\nimport type {\n Source,\n SourceChange,\n SourceChangeAdd,\n SourceChangeEdit,\n SourceChangeRemove,\n SourceInput,\n} from './source.ts';\nimport {makeSourceChangeAdd, makeSourceChangeRemove} from './source.ts';\nimport {consume, type Stream} from './stream.ts';\n\nexport type Overlay = {\n epoch: number;\n change: SourceChange;\n};\n\nexport type Overlays = {\n add: Row | undefined;\n remove: Row | undefined;\n};\n\ntype Index = {\n comparator: Comparator;\n data: BTreeSet<Row>;\n};\n\nexport type Connection = {\n input: Input;\n output: Output | undefined;\n sort?: Ordering | undefined;\n splitEditKeys: Set<string> | undefined;\n compareRows: Comparator;\n filters:\n | {\n condition: NoSubqueryCondition;\n predicate: (row: Row) => boolean;\n }\n | undefined;\n readonly debug?: DebugDelegate | undefined;\n lastPushedEpoch: number;\n};\n\n/**\n * A `MemorySource` is a source that provides data to the pipeline from an\n * in-memory data source.\n *\n * This data is kept in sorted order as downstream pipelines will always expect\n * the data they receive from `pull` to be in sorted order.\n */\nexport class MemorySource implements Source {\n readonly #tableName: string;\n readonly #columns: Record<string, SchemaValue>;\n readonly #primaryKey: PrimaryKey;\n readonly #primaryIndexSort: Ordering;\n readonly #indexes: Map<string, Index> = new Map();\n readonly #connections: Connection[] = [];\n\n #overlay: Overlay | undefined;\n #pushEpoch = 0;\n\n constructor(\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n primaryIndexData?: BTreeSet<Row>,\n ) {\n this.#tableName = tableName;\n this.#columns = columns;\n this.#primaryKey = primaryKey;\n this.#primaryIndexSort = primaryKey.map(k => [k, 'asc']);\n const comparator = makeBoundComparator(this.#primaryIndexSort);\n this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {\n comparator,\n data: primaryIndexData ?? new BTreeSet<Row>(comparator),\n });\n }\n\n get tableSchema() {\n return {\n name: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n fork() {\n const primaryIndex = this.#getPrimaryIndex();\n return new MemorySource(\n this.#tableName,\n this.#columns,\n this.#primaryKey,\n primaryIndex.data.clone(),\n );\n }\n\n get data(): BTreeSet<Row> {\n return this.#getPrimaryIndex().data;\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n system: 'client',\n relationships: {},\n isHidden: false,\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n ): SourceInput {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n const internalSort = sort ?? this.#primaryIndexSort;\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 this.#disconnect(input);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n output: undefined,\n sort: internalSort,\n splitEditKeys,\n compareRows: makeComparator(internalSort),\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(internalSort, this.#primaryKey);\n }\n this.#connections.push(connection);\n return input;\n }\n\n #disconnect(input: Input): void {\n const idx = this.#connections.findIndex(c => c.input === input);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n\n // Indexes deliberately hold no reference back to the connections that use\n // them. They used to, to support deleting an index once its last user went\n // away. That deletion is gone (see below) but the `usedBy` set outlived it,\n // so an index kept every `Connection` — and through its `input` and\n // `output`, the whole torn-down pipeline — reachable for as long as the\n // source lived.\n //\n // TODO: We used to delete unused indexes here. But in common cases like\n // navigating into issue detail pages it caused a ton of constantly\n // building and destroying indexes.\n //\n // Perhaps some intelligent LRU or something is needed here but for now,\n // the opposite extreme of keeping all indexes for the lifetime of the\n // page seems better.\n }\n\n #getPrimaryIndex(): Index {\n const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));\n assert(index, 'Primary index not found');\n return index;\n }\n\n #getOrCreateIndex(sort: Ordering): Index {\n const key = JSON.stringify(sort);\n const index = this.#indexes.get(key);\n // Future optimization could use existing index if it's the same just sorted\n // in reverse of needed.\n if (index) {\n return index;\n }\n\n const comparator = makeBoundComparator(sort);\n\n // When creating these synchronously becomes a problem, a few options:\n // 1. Allow users to specify needed indexes up front\n // 2. Create indexes in a different thread asynchronously (this would require\n // modifying the BTree to be able to be passed over structured-clone, or using\n // a different library.)\n // 3. We could even theoretically do (2) on multiple threads and then merge the\n // results!\n const rows = toSorted(this.#getPrimaryIndex().data, comparator);\n const data = BTreeSet.fromSorted(comparator, rows);\n\n const newIndex = {comparator, data};\n this.#indexes.set(key, newIndex);\n return newIndex;\n }\n\n // For unit testing that we correctly clean up indexes.\n getIndexKeys(): string[] {\n return [...this.#indexes.keys()];\n }\n\n *#fetch(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // multiConstraints is handled by driving sub-fetches off the first\n // entry's values and post-filtering matches against any remaining\n // entries. TableSource implements multi-IN natively via SQL `AND` of\n // `IN` clauses; the in-memory path is correct but not specially\n // optimized — it pays the index-seek cost per primary value, then\n // walks each result against the rest.\n if (\n req.multiConstraints &&\n req.multiConstraints.some(mc => mc.length > 0)\n ) {\n yield* this.#fetchMulti(req, conn);\n return;\n }\n const requestedSort = must(conn.sort);\n const {compareRows} = conn;\n // Avoid allocating a new closure when not reversing (the common case).\n const connectionComparator: Comparator = req.reverse\n ? (r1, r2) => compareRows(r2, r1)\n : compareRows;\n\n const reqFilter = req.filter;\n const connFilterCondition = conn.filters?.condition;\n const mergedFilterCondition: NoSubqueryCondition | undefined =\n connFilterCondition && reqFilter\n ? {type: 'and', conditions: [connFilterCondition, reqFilter]}\n : (connFilterCondition ?? reqFilter);\n const mergedFilterPredicate = mergePredicates(\n conn.filters?.predicate,\n reqFilter,\n );\n\n const pkConstraint = primaryKeyConstraintFromFilters(\n mergedFilterCondition,\n this.#primaryKey,\n );\n // The primary key constraint will be more limiting than the constraint\n // so swap out to that if it exists.\n const fetchOrPkConstraint = pkConstraint ?? req.constraint;\n\n // If there is a constraint, we need an index sorted by it first.\n const indexSort: OrderPart[] = [];\n if (fetchOrPkConstraint) {\n for (const key of Object.keys(fetchOrPkConstraint)) {\n indexSort.push([key, 'asc']);\n }\n }\n\n // For the special case of constraining by PK, we don't need to worry about\n // any requested sort since there can only be one result. Otherwise we also\n // need the index sorted by the requested sort.\n if (\n this.#primaryKey.length > 1 ||\n !fetchOrPkConstraint ||\n !constraintMatchesPrimaryKey(fetchOrPkConstraint, this.#primaryKey)\n ) {\n indexSort.push(...requestedSort);\n }\n\n const index = this.#getOrCreateIndex(indexSort);\n const {data, comparator: compare} = index;\n // Avoid allocating a new closure when not reversing (the common case).\n const indexComparator: Comparator = req.reverse\n ? (r1, r2) => compare(r2, r1)\n : compare;\n\n const startAt = req.start?.row;\n\n // If there is a constraint, we want to start our scan at the first row that\n // matches the constraint. But because the next OrderPart can be `desc`,\n // it's not true that {[constraintKey]: constraintValue} is the first\n // matching row. Because in that case, the other fields will all be\n // `undefined`, and in Zero `undefined` is always less than any other value.\n // So if the second OrderPart is descending then `undefined` values will\n // actually be the *last* row. We need a way to stay \"start at the first row\n // with this constraint value\". RowBound with the corresponding compareBound\n // comparator accomplishes this. The right thing is probably to teach the\n // btree library to support this concept.\n let scanStart: RowBound | undefined;\n\n if (fetchOrPkConstraint) {\n scanStart = {};\n for (const [key, dir] of indexSort) {\n if (hasOwn(fetchOrPkConstraint, key)) {\n scanStart[key] = fetchOrPkConstraint[key];\n } else {\n if (req.reverse) {\n scanStart[key] = dir === 'asc' ? maxValue : minValue;\n } else {\n scanStart[key] = dir === 'asc' ? minValue : maxValue;\n }\n }\n }\n } else {\n scanStart = startAt;\n }\n\n const rowsIterable = generateRows(data, scanStart, req.reverse);\n\n // Fast path: no overlay to apply, no `start`, and no connection filters.\n // This is the common shape for join child-lookups, plain scans, and source\n // fetches that sit beneath a downstream `Filter` operator. In that case the\n // whole generator stack (generateWithOverlay → generateWithStart →\n // skipYields → generateWithConstraint) reduces to: walk rows from\n // `scanStart`, wrap each as a Node, and stop once the constraint no longer\n // matches (rows are sorted by the constraint key first, so matches are\n // contiguous). Collapsing the five chained generators into one loop removes\n // a large amount of per-row generator-resume overhead on the hottest path.\n const overlayActive =\n this.#overlay && conn.lastPushedEpoch >= this.#overlay.epoch;\n if (!overlayActive && !req.start && !conn.filters && !req.filter) {\n const {constraint} = req;\n for (const row of rowsIterable) {\n if (constraint && !constraintMatchesRow(constraint, row)) {\n break;\n }\n yield {row, relationships: {}};\n }\n return;\n }\n\n const withOverlay = generateWithOverlay(\n startAt,\n pkConstraint ? once(rowsIterable) : rowsIterable,\n // use `req.constraint` here and not `fetchOrPkConstraint` since `fetchOrPkConstraint` could be the\n // primary key constraint. The primary key constraint comes from filters and is acting as a filter\n // rather than as the fetch constraint.\n req.constraint,\n this.#overlay,\n conn.lastPushedEpoch,\n // Use indexComparator, generateWithOverlayInner has a subtle dependency\n // on this. Since generateWithConstraint is done after\n // generateWithOverlay, the generator consumed by generateWithOverlayInner\n // does not end when the constraint stops matching and so the final\n // check to yield an add overlay if not yet yielded is not reached.\n // However, using the indexComparator the add overlay will be less than\n // the first row that does not match the constraint, and so any\n // not yet yielded add overlay will be yielded when the first row\n // not matching the constraint is reached.\n indexComparator,\n // `startAt` is `req.start.row`, a bound in the connection's sort order,\n // so it must be compared in that order -- not `indexComparator`, which\n // with a constraint leads with the constraint keys. The two agree only\n // when the overlay row and `startAt` share those key values; they do not\n // for `#fetchMulti`'s per-value sub-fetches, which pin one primary key\n // while still carrying the caller's connection-sort `start`. Comparing\n // there by the constraint key dropped the in-flight overlay and served\n // pre-push data. Same distinction #4926 drew for `generateWithStart`.\n connectionComparator,\n mergedFilterPredicate,\n );\n\n const withConstraint = generateWithConstraint(\n skipYields(\n generateWithStart(withOverlay, req.start, connectionComparator),\n ),\n // we use `req.constraint` and not `fetchOrPkConstraint` here because we need to\n // AND the constraint with what could have been the primary key constraint\n req.constraint,\n );\n\n yield* mergedFilterPredicate\n ? generateWithFilter(withConstraint, mergedFilterPredicate)\n : withConstraint;\n }\n\n *#fetchMulti(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // Caller (`#fetch`) guards entry on `req.multiConstraints.some(mc =>\n // mc.length > 0)`, so `multis` is guaranteed non-empty after the\n // empty-entry filter. Per the MultiConstraint contract (operator.ts),\n // entries are non-empty, share key shape, are unique, and are\n // key-compatible with `req.constraint`.\n const multis = must(req.multiConstraints).filter(mc => mc.length > 0);\n const primary = multis[0];\n const rest = multis.slice(1);\n const baseConstraint = req.constraint;\n\n // Drive sub-fetches off `primary`. Within each sub-fetch the source\n // can use its index for `primary`; we then post-filter rows by\n // checking they also match every entry in `rest`. Per the\n // MultiConstraint contract (see operator.ts), entries are unique and\n // key-compatible with `baseConstraint`, so we don't dedupe or check\n // compatibility here.\n const subStreams: Stream<Node | 'yield'>[] = primary.map(c => {\n const merged: Constraint = baseConstraint ? {...baseConstraint, ...c} : c;\n return this.#fetch(\n {...req, constraint: merged, multiConstraints: undefined},\n conn,\n );\n });\n const merged = mergeSortedStreams(\n subStreams,\n req.reverse\n ? (a, b) => conn.compareRows(b.row, a.row)\n : (a, b) => conn.compareRows(a.row, b.row),\n );\n\n if (rest.length === 0) {\n yield* merged;\n return;\n }\n\n for (const node of merged) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n let matchesAll = true;\n for (const mc of rest) {\n let any = false;\n for (const c of mc) {\n if (constraintMatchesRow(c, node.row)) {\n any = true;\n break;\n }\n }\n if (!any) {\n matchesAll = false;\n break;\n }\n }\n if (matchesAll) yield node;\n }\n }\n\n /**\n * Pushes an add for each of `rows`.\n *\n * When the source holds no rows and nothing is connected to it (loading the\n * replica at startup) there is no output to notify and no secondary index to\n * keep in step, so the primary index is built bottom-up in O(N) (plus a sort\n * when `rows` are not already in primary key order) rather than doing one\n * tree insert, and one trip through the push machinery, per row.\n *\n * `rows` may be sorted in place. Rows must have distinct primary keys.\n */\n pushAdds(rows: Row[]): void {\n const index = this.#getPrimaryIndex();\n const canBulkLoad =\n this.#connections.length === 0 &&\n this.#indexes.size === 1 &&\n index.data.size === 0;\n if (!canBulkLoad) {\n for (const row of rows) {\n consume(this.push(makeSourceChangeAdd(row)));\n }\n return;\n }\n const {comparator} = index;\n // The check is not redundant with the sort. V8's TimSort is O(N) on sorted\n // input but Hermes' sort is not adaptive: always sorting doubled the load\n // time of an already ordered 180k row replica on Android (343ms -> 672ms).\n if (!isAscending(rows, comparator)) {\n rows.sort(comparator);\n }\n index.data = BTreeSet.fromSorted(comparator, rows);\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 primaryIndex = this.#getPrimaryIndex();\n const {data} = primaryIndex;\n const exists = (row: Row) => data.has(row);\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n return genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n for (const {data} of this.#indexes.values()) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD: {\n const added = data.add(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n added,\n 'MemorySource: add must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.REMOVE: {\n const removed = data.delete(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: remove must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.EDIT: {\n // TODO: We could see if the PK (form the index tree's perspective)\n // changed and if not we could use set.\n // We cannot just do `set` with the new value since the `oldRow` might\n // not map to the same entry as the new `row` in the index btree.\n const removed = data.delete(change[SourceChangeIndex.OLD_ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: edit remove must succeed since row existence was already checked',\n );\n data.add(change[SourceChangeIndex.ROW]);\n break;\n }\n default:\n unreachable(change);\n }\n }\n }\n}\n\nfunction mergePredicates(\n connPredicate: ((row: Row) => boolean) | undefined,\n reqFilter: NoSubqueryCondition | 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* generateWithConstraint(\n it: Stream<Node>,\n constraint: Constraint | undefined,\n) {\n for (const node of it) {\n if (constraint && !constraintMatchesRow(constraint, node.row)) {\n break;\n }\n yield node;\n }\n}\n\nfunction* generateWithFilter(it: Stream<Node>, filter: (row: Row) => boolean) {\n for (const node of it) {\n if (filter(node.row)) {\n yield node;\n }\n }\n}\n\nexport function* genPushAndWriteWithSplitEdit(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n getNextEpoch: () => number,\n) {\n let shouldSplitEdit = false;\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT) {\n for (const {splitEditKeys} of connections) {\n if (splitEditKeys) {\n for (const key of splitEditKeys) {\n if (\n !valuesEqual(\n change[SourceChangeIndex.ROW][key],\n change[SourceChangeIndex.OLD_ROW][key],\n )\n ) {\n shouldSplitEdit = true;\n break;\n }\n }\n }\n }\n }\n\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT && shouldSplitEdit) {\n yield* genPushAndWrite(\n connections,\n makeSourceChangeRemove(change[SourceChangeIndex.OLD_ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n yield* genPushAndWrite(\n connections,\n makeSourceChangeAdd(change[SourceChangeIndex.ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n } else {\n yield* genPushAndWrite(\n connections,\n change,\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n }\n}\n\nfunction* genPushAndWrite(\n connections: readonly Connection[],\n change: SourceChangeAdd | SourceChangeRemove | SourceChangeEdit,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n pushEpoch: number,\n) {\n for (const x of genPush(connections, change, exists, setOverlay, pushEpoch)) {\n yield x;\n }\n writeChange(change);\n}\n\nfunction* genPush(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => void,\n pushEpoch: number,\n) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n assert(\n !exists(change[SourceChangeIndex.ROW]),\n () => `Row already exists ${stringify(change)}`,\n );\n break;\n case ChangeType.REMOVE:\n assert(\n exists(change[SourceChangeIndex.ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n case ChangeType.EDIT:\n assert(\n exists(change[SourceChangeIndex.OLD_ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n default:\n unreachable(change);\n }\n\n setOverlay({epoch: pushEpoch, change});\n try {\n for (const conn of connections) {\n const {output, filters, input} = conn;\n if (output) {\n conn.lastPushedEpoch = pushEpoch;\n const outputChange: Change =\n change[SourceChangeIndex.TYPE] === ChangeType.EDIT\n ? makeEditChange(\n {row: change[SourceChangeIndex.ROW], relationships: {}},\n {row: change[SourceChangeIndex.OLD_ROW], relationships: {}},\n )\n : change[SourceChangeIndex.TYPE] === ChangeType.ADD\n ? makeAddChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n })\n : makeRemoveChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n });\n yield* filterPush(outputChange, output, input, filters?.predicate);\n yield undefined;\n }\n }\n\n for (const conn of connections) {\n const {output, input} = conn;\n if (output?.reconcile) {\n yield* output.reconcile(input);\n yield undefined;\n }\n }\n } finally {\n setOverlay(undefined);\n }\n}\n\nexport function* generateWithStart(\n nodes: Iterable<Node | 'yield'>,\n start: Start | undefined,\n compare: (r1: Row, r2: Row) => number,\n): Stream<Node | 'yield'> {\n if (!start) {\n yield* nodes;\n return;\n }\n let started = false;\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!started) {\n if (start.basis === 'at') {\n if (compare(node.row, start.row) >= 0) {\n started = true;\n }\n } else if (start.basis === 'after') {\n if (compare(node.row, start.row) > 0) {\n started = true;\n }\n }\n }\n if (started) {\n yield node;\n }\n }\n}\n\n/**\n * Takes an iterator and overlay.\n * Splices the overlay into the iterator at the correct position.\n *\n * @param startAt - if there is a lower bound to the stream. If the lower bound of the stream\n * is above the overlay, the overlay will be skipped.\n * @param rows - the stream into which the overlay should be spliced\n * @param constraint - constraint that was applied to the rowIterator and should\n * also be applied to the overlay.\n * @param overlay - the overlay values to splice in\n * @param compare - the comparator to use to find the position for the overlay.\n * Must order rows the same way `rows` is ordered. For `MemorySource` that is\n * *index* order, which leads with the constraint keys.\n * @param startAtCompare - the comparator for `startAt`, which is a bound in the\n * *connection's* sort order. Only the same as `compare` when the stream's order\n * happens to be the connection's -- true for `TableSource` (SQL does the\n * ordering) but not for a constrained `MemorySource` fetch. Conflating the two\n * is what #4926 fixed for `generateWithStart`; this parameter is the same\n * distinction for the overlay's own `startAt` pruning.\n */\nexport function generateWithOverlay(\n startAt: Row | undefined,\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n compare: Comparator,\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[],\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n const overlays = computeOverlays(\n startAt,\n constraint,\n overlayToApply,\n startAtCompare,\n filterPredicate,\n multiConstraints,\n );\n return generateWithOverlayInner(rows, overlays, compare);\n}\n\nfunction computeOverlays(\n startAt: Row | undefined,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n // Only ever used for `overlaysForStartAt`, so this is the *connection*-order\n // comparator, not the one used to splice the overlay into the row stream.\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[],\n): Overlays {\n let overlays: Overlays = {\n add: undefined,\n remove: undefined,\n };\n switch (overlay?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlay.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: overlay.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n\n if (startAt) {\n overlays = overlaysForStartAt(overlays, startAt, startAtCompare);\n }\n\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n\n return overlays;\n}\n\nfunction applyMultiConstraintsToOverlays(\n overlays: Overlays,\n multiConstraints: readonly MultiConstraint[] | undefined,\n): Overlays {\n if (!multiConstraints) return overlays;\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n overlays = overlaysForMultiConstraint(overlays, mc);\n }\n }\n return overlays;\n}\n\nexport {overlaysForMultiConstraint as overlaysForMultiConstraintForTest};\n\nfunction overlaysForMultiConstraint(\n {add, remove}: Overlays,\n multiConstraint: MultiConstraint,\n): Overlays {\n const matchesAny = (row: Row | undefined) => {\n if (row === undefined) return false;\n for (const c of multiConstraint) {\n if (constraintMatchesRow(c, row)) return true;\n }\n return false;\n };\n return {\n add: matchesAny(add) ? add : undefined,\n remove: matchesAny(remove) ? remove : undefined,\n };\n}\n\nexport {overlaysForStartAt as overlaysForStartAtForTest};\n\nfunction overlaysForStartAt(\n {add, remove}: Overlays,\n startAt: Row,\n compare: Comparator,\n): Overlays {\n const undefinedIfBeforeStartAt = (row: Row | undefined) =>\n row === undefined || compare(row, startAt) < 0 ? undefined : row;\n return {\n add: undefinedIfBeforeStartAt(add),\n remove: undefinedIfBeforeStartAt(remove),\n };\n}\n\nexport {overlaysForConstraint as overlaysForConstraintForTest};\n\nfunction overlaysForConstraint(\n {add, remove}: Overlays,\n constraint: Constraint,\n): Overlays {\n const undefinedIfDoesntMatchConstraint = (row: Row | undefined) =>\n row === undefined || !constraintMatchesRow(constraint, row)\n ? undefined\n : row;\n\n return {\n add: undefinedIfDoesntMatchConstraint(add),\n remove: undefinedIfDoesntMatchConstraint(remove),\n };\n}\n\nfunction overlaysForFilterPredicate(\n {add, remove}: Overlays,\n filterPredicate: (row: Row) => boolean | undefined,\n): Overlays {\n const undefinedIfDoesntMatchFilter = (row: Row | undefined) =>\n row === undefined || !filterPredicate(row) ? undefined : row;\n\n return {\n add: undefinedIfDoesntMatchFilter(add),\n remove: undefinedIfDoesntMatchFilter(remove),\n };\n}\n\nexport function* generateWithOverlayInner(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n compare: (r1: Row, r2: Row) => number,\n) {\n let addOverlayYielded = false;\n let removeOverlaySkipped = false;\n for (const row of rowIterator) {\n if (!addOverlayYielded && overlays.add) {\n const cmp = compare(overlays.add, row);\n if (cmp < 0) {\n addOverlayYielded = true;\n yield {row: overlays.add, relationships: {}};\n }\n }\n\n if (!removeOverlaySkipped && overlays.remove) {\n const cmp = compare(overlays.remove, row);\n if (cmp === 0) {\n removeOverlaySkipped = true;\n continue;\n }\n }\n yield {row, relationships: {}};\n }\n\n if (!addOverlayYielded && overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n}\n\n/**\n * Like {@link generateWithOverlay} but for unordered streams.\n * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly\n * at the start, and suppresses add/new-edit rows inline by PK match.\n */\nexport function generateWithOverlayUnordered(\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n primaryKey: PrimaryKey,\n filterPredicate?: (row: Row) => boolean,\n multiConstraints?: readonly MultiConstraint[],\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n let overlays: Overlays = {add: undefined, remove: undefined};\n switch (overlayToApply?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlayToApply.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: overlayToApply.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n return generateWithOverlayInnerUnordered(rows, overlays, primaryKey);\n}\n\nexport function* generateWithOverlayInnerUnordered(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n primaryKey: PrimaryKey,\n) {\n // Eager inject: yield the add overlay at the start (row not yet in storage)\n if (overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n // Stream with inline suppress: skip the remove overlay (row still in storage)\n let removeSkipped = false;\n for (const row of rowIterator) {\n if (\n !removeSkipped &&\n overlays.remove &&\n rowMatchesPK(overlays.remove, row, primaryKey)\n ) {\n removeSkipped = true;\n continue;\n }\n yield {row, relationships: {}};\n }\n}\n\nfunction rowMatchesPK(a: Row, b: Row, primaryKey: PrimaryKey): boolean {\n for (const key of primaryKey) {\n if (!valuesEqual(a[key], b[key])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A location to begin scanning an index from. Can either be a specific value\n * or the min or max possible value for the type. This is used to start a scan\n * at the beginning of the rows matching a constraint.\n */\ntype Bound = Value | MinValue | MaxValue;\ntype RowBound = Record<string, Bound>;\nconst minValue = Symbol('min-value');\ntype MinValue = typeof minValue;\nconst maxValue = Symbol('max-value');\ntype MaxValue = typeof maxValue;\n\nfunction isAscending(rows: Row[], comparator: Comparator): boolean {\n for (let i = 1; i < rows.length; i++) {\n if (comparator(rows[i - 1], rows[i]) > 0) {\n return false;\n }\n }\n return true;\n}\n\nfunction makeBoundComparator(sort: Ordering): Comparator {\n // Pre-extract the first two keys/directions to avoid per-call array access.\n // All paths share one function literal (single SFI) so the BTree comparator call site\n // stays monomorphic across indexes with different sort orderings, preventing V8 IC deopt.\n // Even a 2-SFI split (e.g. separate len=1 path) creates a polymorphic IC that\n // measurably regresses performance, so we keep a single return body.\n const len = sort.length;\n const k0 = sort[0][0];\n const a0 = sort[0][1] === 'asc';\n const k1 = len > 1 ? sort[1][0] : '';\n const a1 = len > 1 ? sort[1][1] === 'asc' : true;\n\n return (a: RowBound, b: RowBound) => {\n const c0 = a0 ? compareBounds(a[k0], b[k0]) : compareBounds(b[k0], a[k0]);\n if (len === 1 || c0 !== 0) return c0;\n const c1 = a1 ? compareBounds(a[k1], b[k1]) : compareBounds(b[k1], a[k1]);\n if (len === 2 || c1 !== 0) return c1;\n // Hot! Do not use destructuring\n for (let i = 2; i < len; i++) {\n const cmp = compareBounds(a[sort[i][0]], b[sort[i][0]]);\n if (cmp !== 0) return sort[i][1] === 'asc' ? cmp : -cmp;\n }\n return 0;\n };\n}\n\nfunction compareBounds(a: Bound, b: Bound): number {\n if (a === b) {\n return 0;\n }\n // Use typeof to guard the Symbol sentinel checks first. This gives V8 a\n // clear type discriminant so the common non-symbol path compiles as a\n // specialised numeric/string fast-path without a Smi deopt when the\n // minValue/maxValue sentinel symbols appear.\n if (typeof a === 'symbol') {\n return a === minValue ? -1 : 1;\n }\n if (typeof b === 'symbol') {\n return b === minValue ? 1 : -1;\n }\n return compareValues(a, b);\n}\n\nfunction generateRows(\n data: BTreeSet<Row>,\n scanStart: RowBound | undefined,\n reverse: boolean | undefined,\n) {\n return reverse\n ? data.valuesFromReversed(scanStart as Row | undefined)\n : data.valuesFrom(scanStart as Row | undefined);\n}\n\nexport function stringify(change: SourceChange) {\n return JSON.stringify(change, (_, v) =>\n typeof v === 'bigint' ? v.toString() : v,\n );\n}\n\n/**\n * N-way merge of pre-sorted Node streams. Each input stream must yield\n * Nodes in `compare` order (and may interleave 'yield' values, which are\n * forwarded as-is). The merged stream yields Nodes in `compare` order.\n *\n * Implemented as a binary min-heap keyed by `compare(entry.row, ...)`, so\n * each emit costs O(log K) (K = number of streams) rather than the O(K)\n * a linear-scan-of-heads merge would. Matters when FlippedJoin chunks a\n * large `multiConstraints` IN-list into hundreds of sub-fetches.\n *\n * If the merged stream is closed early (e.g. downstream `Take` `break`s\n * after hitting its limit, prompting JS to call `.return()` on this\n * generator), the `finally` block propagates `.return()` to each\n * non-exhausted sub-iterator so the underlying sources (SQLite cursors,\n * etc.) can run their cleanup. Without this, mid-merge early-termination\n * leaves cursors open, causing later writes on the same connection to\n * fail with \"database connection is busy executing a query\".\n */\nexport function* mergeSortedStreams(\n streams: readonly Stream<Node | 'yield'>[],\n compare: (a: Node, b: Node) => number,\n): Stream<Node | 'yield'> {\n const iterators: Iterator<Node | 'yield'>[] = streams.map(s =>\n s[Symbol.iterator](),\n );\n // True while iterators[i] hasn't yet returned `done`. The finally\n // block uses this to skip already-exhausted streams when propagating\n // `.return()`.\n const active = Array.from({length: iterators.length}).fill(true) as boolean[];\n\n // Min-heap of entries; `idx` tells us which stream to refill from\n // after the entry's row is emitted.\n type Entry = {row: Node; idx: number};\n const heap: Entry[] = [];\n\n const siftUp = (start: number) => {\n let i = start;\n while (i > 0) {\n const p = (i - 1) >> 1;\n if (compare(heap[i].row, heap[p].row) >= 0) return;\n const t = heap[i];\n heap[i] = heap[p];\n heap[p] = t;\n i = p;\n }\n };\n\n const siftDown = (start: number) => {\n let i = start;\n const n = heap.length;\n while (true) {\n const l = (i << 1) + 1;\n const r = l + 1;\n let smallest = i;\n if (l < n && compare(heap[l].row, heap[smallest].row) < 0) smallest = l;\n if (r < n && compare(heap[r].row, heap[smallest].row) < 0) smallest = r;\n if (smallest === i) return;\n const t = heap[i];\n heap[i] = heap[smallest];\n heap[smallest] = t;\n i = smallest;\n }\n };\n\n // Pull the next Node from iterator `idx`, forwarding any 'yield's.\n // Returns the Node, or `undefined` once the stream is exhausted.\n const pullNext = function* (\n idx: number,\n ): IterableIterator<'yield', Node | undefined, undefined> {\n while (true) {\n const r = iterators[idx].next();\n if (r.done) {\n active[idx] = false;\n return undefined;\n }\n if (r.value === 'yield') {\n yield 'yield';\n continue;\n }\n return r.value;\n }\n };\n\n try {\n // Prime: push the first row of each non-empty stream onto the heap.\n for (let i = 0; i < iterators.length; i++) {\n const row = yield* pullNext(i);\n if (row !== undefined) {\n heap.push({row, idx: i});\n siftUp(heap.length - 1);\n }\n }\n\n while (heap.length > 0) {\n // Root is the global min across all active streams.\n const top = heap[0];\n yield top.row;\n const next = yield* pullNext(top.idx);\n if (next !== undefined) {\n // Refill root in place (top === heap[0]) and sift down. The\n // already-yielded `top.row` value is captured by the yield, so\n // mutating it here doesn't affect what was emitted.\n top.row = next;\n siftDown(0);\n } else {\n // Stream exhausted. Move tail to root and shrink. Pop returns\n // the last entry; if the heap had only one entry it was the\n // root we just yielded, so we just leave the heap empty.\n const last = must(heap.pop());\n if (heap.length > 0) {\n heap[0] = last;\n siftDown(0);\n }\n }\n }\n } finally {\n // Close any iterators that aren't already exhausted so their\n // `finally` blocks (which release cursors / cached statements) run.\n for (let i = 0; i < iterators.length; i++) {\n if (active[i]) {\n iterators[i].return?.();\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"memory-source.js","names":["#tableName","#columns","#primaryKey","#primaryIndexSort","#indexes","#connections","#getPrimaryIndex","#fetch","#disconnect","#getSchema","#fetchMulti","#getOrCreateIndex","#overlay","#writeChange","#pushEpoch"],"sources":["../../../../../zql/src/ivm/memory-source.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {BTreeSet} from '../../../shared/src/btree-set.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport {once, toSorted} from '../../../shared/src/iterables.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n Condition,\n Ordering,\n OrderPart,\n} 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 {SchemaValue} from '../../../zero-types/src/schema-value.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n type NoSubqueryCondition,\n} from '../builder/filter.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Change} from './change.ts';\nimport {makeAddChange, makeEditChange, makeRemoveChange} from './change.ts';\nimport {\n constraintMatchesPrimaryKey,\n constraintMatchesRow,\n primaryKeyConstraintFromFilters,\n type Constraint,\n} from './constraint.ts';\nimport {\n compareValues,\n makeComparator,\n valuesEqual,\n type Comparator,\n type Node,\n} from './data.ts';\nimport {filterPush} from './filter-push.ts';\nimport {\n skipYields,\n type FetchRequest,\n type Input,\n type MultiConstraint,\n type Output,\n type Start,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {SourceChangeIndex} from './source-change-index.ts';\nimport type {\n Source,\n SourceChange,\n SourceChangeAdd,\n SourceChangeEdit,\n SourceChangeRemove,\n SourceInput,\n} from './source.ts';\nimport {makeSourceChangeAdd, makeSourceChangeRemove} from './source.ts';\nimport {consume, type Stream} from './stream.ts';\n\nexport type Overlay = {\n epoch: number;\n change: SourceChange;\n};\n\nexport type Overlays = {\n add: Row | undefined;\n remove: Row | undefined;\n};\n\ntype Index = {\n comparator: Comparator;\n data: BTreeSet<Row>;\n};\n\nexport type Connection = {\n input: Input;\n output: Output | undefined;\n sort?: Ordering | undefined;\n splitEditKeys: Set<string> | undefined;\n compareRows: Comparator;\n filters:\n | {\n condition: NoSubqueryCondition;\n predicate: (row: Row) => boolean;\n }\n | undefined;\n readonly debug?: DebugDelegate | undefined;\n lastPushedEpoch: number;\n};\n\n/**\n * A `MemorySource` is a source that provides data to the pipeline from an\n * in-memory data source.\n *\n * This data is kept in sorted order as downstream pipelines will always expect\n * the data they receive from `pull` to be in sorted order.\n */\nexport class MemorySource implements Source {\n readonly #tableName: string;\n readonly #columns: Record<string, SchemaValue>;\n readonly #primaryKey: PrimaryKey;\n readonly #primaryIndexSort: Ordering;\n readonly #indexes: Map<string, Index> = new Map();\n readonly #connections: Connection[] = [];\n\n #overlay: Overlay | undefined;\n #pushEpoch = 0;\n\n constructor(\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n primaryIndexData?: BTreeSet<Row>,\n ) {\n this.#tableName = tableName;\n this.#columns = columns;\n this.#primaryKey = primaryKey;\n this.#primaryIndexSort = primaryKey.map(k => [k, 'asc']);\n const comparator = makeBoundComparator(this.#primaryIndexSort);\n this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {\n comparator,\n data: primaryIndexData ?? new BTreeSet<Row>(comparator),\n });\n }\n\n get tableSchema() {\n return {\n name: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n fork() {\n const primaryIndex = this.#getPrimaryIndex();\n return new MemorySource(\n this.#tableName,\n this.#columns,\n this.#primaryKey,\n primaryIndex.data.clone(),\n );\n }\n\n get data(): BTreeSet<Row> {\n return this.#getPrimaryIndex().data;\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n system: 'client',\n relationships: {},\n isHidden: false,\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n ): SourceInput {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n const internalSort = sort ?? this.#primaryIndexSort;\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 this.#disconnect(input);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n output: undefined,\n sort: internalSort,\n splitEditKeys,\n compareRows: makeComparator(internalSort),\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(internalSort, this.#primaryKey);\n }\n this.#connections.push(connection);\n return input;\n }\n\n #disconnect(input: Input): void {\n const idx = this.#connections.findIndex(c => c.input === input);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n\n // Indexes deliberately hold no reference back to the connections that use\n // them. They used to, to support deleting an index once its last user went\n // away. That deletion is gone (see below) but the `usedBy` set outlived it,\n // so an index kept every `Connection` — and through its `input` and\n // `output`, the whole torn-down pipeline — reachable for as long as the\n // source lived.\n //\n // TODO: We used to delete unused indexes here. But in common cases like\n // navigating into issue detail pages it caused a ton of constantly\n // building and destroying indexes.\n //\n // Perhaps some intelligent LRU or something is needed here but for now,\n // the opposite extreme of keeping all indexes for the lifetime of the\n // page seems better.\n }\n\n #getPrimaryIndex(): Index {\n const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));\n assert(index, 'Primary index not found');\n return index;\n }\n\n #getOrCreateIndex(sort: Ordering): Index {\n const key = JSON.stringify(sort);\n const index = this.#indexes.get(key);\n // Future optimization could use existing index if it's the same just sorted\n // in reverse of needed.\n if (index) {\n return index;\n }\n\n const comparator = makeBoundComparator(sort);\n\n // When creating these synchronously becomes a problem, a few options:\n // 1. Allow users to specify needed indexes up front\n // 2. Create indexes in a different thread asynchronously (this would require\n // modifying the BTree to be able to be passed over structured-clone, or using\n // a different library.)\n // 3. We could even theoretically do (2) on multiple threads and then merge the\n // results!\n const rows = toSorted(this.#getPrimaryIndex().data, comparator);\n const data = BTreeSet.fromSorted(comparator, rows);\n\n const newIndex = {comparator, data};\n this.#indexes.set(key, newIndex);\n return newIndex;\n }\n\n // For unit testing that we correctly clean up indexes.\n getIndexKeys(): string[] {\n return [...this.#indexes.keys()];\n }\n\n *#fetch(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // multiConstraints is handled by driving sub-fetches off the first\n // entry's values and post-filtering matches against any remaining\n // entries. TableSource implements multi-IN natively via SQL `AND` of\n // `IN` clauses; the in-memory path is correct but not specially\n // optimized — it pays the index-seek cost per primary value, then\n // walks each result against the rest.\n if (\n req.multiConstraints &&\n req.multiConstraints.some(mc => mc.length > 0)\n ) {\n yield* this.#fetchMulti(req, conn);\n return;\n }\n const requestedSort = must(conn.sort);\n const {compareRows} = conn;\n // Avoid allocating a new closure when not reversing (the common case).\n const connectionComparator: Comparator = req.reverse\n ? (r1, r2) => compareRows(r2, r1)\n : compareRows;\n\n const reqFilter = req.filter;\n const connFilterCondition = conn.filters?.condition;\n const mergedFilterCondition: NoSubqueryCondition | undefined =\n connFilterCondition && reqFilter\n ? {type: 'and', conditions: [connFilterCondition, reqFilter]}\n : (connFilterCondition ?? reqFilter);\n const mergedFilterPredicate = mergePredicates(\n conn.filters?.predicate,\n reqFilter,\n );\n\n const pkConstraint = primaryKeyConstraintFromFilters(\n mergedFilterCondition,\n this.#primaryKey,\n );\n // The primary key constraint will be more limiting than the constraint\n // so swap out to that if it exists.\n const fetchOrPkConstraint = pkConstraint ?? req.constraint;\n\n // If there is a constraint, we need an index sorted by it first.\n const indexSort: OrderPart[] = [];\n if (fetchOrPkConstraint) {\n for (const key of Object.keys(fetchOrPkConstraint)) {\n indexSort.push([key, 'asc']);\n }\n }\n\n // For the special case of constraining by PK, we don't need to worry about\n // any requested sort since there can only be one result. Otherwise we also\n // need the index sorted by the requested sort.\n if (\n this.#primaryKey.length > 1 ||\n !fetchOrPkConstraint ||\n !constraintMatchesPrimaryKey(fetchOrPkConstraint, this.#primaryKey)\n ) {\n indexSort.push(...requestedSort);\n }\n\n const index = this.#getOrCreateIndex(indexSort);\n const {data, comparator: compare} = index;\n // Avoid allocating a new closure when not reversing (the common case).\n const indexComparator: Comparator = req.reverse\n ? (r1, r2) => compare(r2, r1)\n : compare;\n\n const startAt = req.start?.row;\n\n // If there is a constraint, we want to start our scan at the first row that\n // matches the constraint. But because the next OrderPart can be `desc`,\n // it's not true that {[constraintKey]: constraintValue} is the first\n // matching row. Because in that case, the other fields will all be\n // `undefined`, and in Zero `undefined` is always less than any other value.\n // So if the second OrderPart is descending then `undefined` values will\n // actually be the *last* row. We need a way to stay \"start at the first row\n // with this constraint value\". RowBound with the corresponding compareBound\n // comparator accomplishes this. The right thing is probably to teach the\n // btree library to support this concept.\n let scanStart: RowBound | undefined;\n\n if (fetchOrPkConstraint) {\n scanStart = {};\n for (const [key, dir] of indexSort) {\n if (hasOwn(fetchOrPkConstraint, key)) {\n scanStart[key] = fetchOrPkConstraint[key];\n } else {\n if (req.reverse) {\n scanStart[key] = dir === 'asc' ? maxValue : minValue;\n } else {\n scanStart[key] = dir === 'asc' ? minValue : maxValue;\n }\n }\n }\n } else {\n scanStart = startAt;\n }\n\n const rowsIterable = generateRows(data, scanStart, req.reverse);\n\n // Fast path: no overlay to apply, no `start`, and no connection filters.\n // This is the common shape for join child-lookups, plain scans, and source\n // fetches that sit beneath a downstream `Filter` operator. In that case the\n // whole generator stack (generateWithOverlay → generateWithStart →\n // skipYields → generateWithConstraint) reduces to: walk rows from\n // `scanStart`, wrap each as a Node, and stop once the constraint no longer\n // matches (rows are sorted by the constraint key first, so matches are\n // contiguous). Collapsing the five chained generators into one loop removes\n // a large amount of per-row generator-resume overhead on the hottest path.\n const overlayActive =\n this.#overlay && conn.lastPushedEpoch >= this.#overlay.epoch;\n if (!overlayActive && !req.start && !conn.filters && !req.filter) {\n const {constraint} = req;\n for (const row of rowsIterable) {\n if (constraint && !constraintMatchesRow(constraint, row)) {\n break;\n }\n yield {row, relationships: {}};\n }\n return;\n }\n\n const withOverlay = generateWithOverlay(\n startAt,\n pkConstraint ? once(rowsIterable) : rowsIterable,\n // use `req.constraint` here and not `fetchOrPkConstraint` since `fetchOrPkConstraint` could be the\n // primary key constraint. The primary key constraint comes from filters and is acting as a filter\n // rather than as the fetch constraint.\n req.constraint,\n this.#overlay,\n conn.lastPushedEpoch,\n // Use indexComparator, generateWithOverlayInner has a subtle dependency\n // on this. Since generateWithConstraint is done after\n // generateWithOverlay, the generator consumed by generateWithOverlayInner\n // does not end when the constraint stops matching and so the final\n // check to yield an add overlay if not yet yielded is not reached.\n // However, using the indexComparator the add overlay will be less than\n // the first row that does not match the constraint, and so any\n // not yet yielded add overlay will be yielded when the first row\n // not matching the constraint is reached.\n indexComparator,\n // `startAt` is `req.start.row`, a bound in the connection's sort order,\n // so it must be compared in that order -- not `indexComparator`, which\n // with a constraint leads with the constraint keys. The two agree only\n // when the overlay row and `startAt` share those key values; they do not\n // for `#fetchMulti`'s per-value sub-fetches, which pin one primary key\n // while still carrying the caller's connection-sort `start`. Comparing\n // there by the constraint key dropped the in-flight overlay and served\n // pre-push data. Same distinction #4926 drew for `generateWithStart`.\n connectionComparator,\n mergedFilterPredicate,\n );\n\n const withConstraint = generateWithConstraint(\n skipYields(\n generateWithStart(withOverlay, req.start, connectionComparator),\n ),\n // we use `req.constraint` and not `fetchOrPkConstraint` here because we need to\n // AND the constraint with what could have been the primary key constraint\n req.constraint,\n );\n\n yield* mergedFilterPredicate\n ? generateWithFilter(withConstraint, mergedFilterPredicate)\n : withConstraint;\n }\n\n *#fetchMulti(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // Caller (`#fetch`) guards entry on `req.multiConstraints.some(mc =>\n // mc.length > 0)`, so `multis` is guaranteed non-empty after the\n // empty-entry filter. Per the MultiConstraint contract (operator.ts),\n // entries are non-empty, share key shape, are unique, and are\n // key-compatible with `req.constraint`.\n const multis = must(req.multiConstraints).filter(mc => mc.length > 0);\n const primary = multis[0];\n const rest = multis.slice(1);\n const baseConstraint = req.constraint;\n\n // Drive sub-fetches off `primary`. Within each sub-fetch the source\n // can use its index for `primary`; we then post-filter rows by\n // checking they also match every entry in `rest`. Per the\n // MultiConstraint contract (see operator.ts), entries are unique and\n // key-compatible with `baseConstraint`, so we don't dedupe or check\n // compatibility here.\n const subStreams: Stream<Node | 'yield'>[] = primary.map(c => {\n const merged: Constraint = baseConstraint ? {...baseConstraint, ...c} : c;\n return this.#fetch(\n {...req, constraint: merged, multiConstraints: undefined},\n conn,\n );\n });\n const merged = mergeSortedStreams(\n subStreams,\n req.reverse\n ? (a, b) => conn.compareRows(b.row, a.row)\n : (a, b) => conn.compareRows(a.row, b.row),\n );\n\n if (rest.length === 0) {\n yield* merged;\n return;\n }\n\n for (const node of merged) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n let matchesAll = true;\n for (const mc of rest) {\n let any = false;\n for (const c of mc) {\n if (constraintMatchesRow(c, node.row)) {\n any = true;\n break;\n }\n }\n if (!any) {\n matchesAll = false;\n break;\n }\n }\n if (matchesAll) yield node;\n }\n }\n\n /**\n * Pushes an add for each of `rows`.\n *\n * When the source holds no rows and nothing is connected to it (loading the\n * replica at startup) there is no output to notify and no secondary index to\n * keep in step, so the primary index is built bottom-up in O(N) (plus a sort\n * when `rows` are not already in primary key order) rather than doing one\n * tree insert, and one trip through the push machinery, per row.\n *\n * `rows` may be sorted in place. Rows must have distinct primary keys.\n */\n pushAdds(rows: Row[]): void {\n const index = this.#getPrimaryIndex();\n const canBulkLoad =\n this.#connections.length === 0 &&\n this.#indexes.size === 1 &&\n index.data.size === 0;\n if (!canBulkLoad) {\n for (const row of rows) {\n consume(this.push(makeSourceChangeAdd(row)));\n }\n return;\n }\n const {comparator} = index;\n // The check is not redundant with the sort. V8's TimSort is O(N) on sorted\n // input but Hermes' sort is not adaptive: always sorting doubled the load\n // time of an already ordered 180k row replica on Android (343ms -> 672ms).\n if (!isAscending(rows, comparator)) {\n rows.sort(comparator);\n }\n index.data = BTreeSet.fromSorted(comparator, rows);\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 primaryIndex = this.#getPrimaryIndex();\n const {data} = primaryIndex;\n const exists = (row: Row) => data.has(row);\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n return genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n for (const {data} of this.#indexes.values()) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD: {\n const added = data.add(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n added,\n 'MemorySource: add must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.REMOVE: {\n const removed = data.delete(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: remove must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.EDIT: {\n // TODO: We could see if the PK (form the index tree's perspective)\n // changed and if not we could use set.\n // We cannot just do `set` with the new value since the `oldRow` might\n // not map to the same entry as the new `row` in the index btree.\n const removed = data.delete(change[SourceChangeIndex.OLD_ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: edit remove must succeed since row existence was already checked',\n );\n data.add(change[SourceChangeIndex.ROW]);\n break;\n }\n default:\n unreachable(change);\n }\n }\n }\n}\n\nfunction mergePredicates(\n connPredicate: ((row: Row) => boolean) | undefined,\n reqFilter: NoSubqueryCondition | 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* generateWithConstraint(\n it: Stream<Node>,\n constraint: Constraint | undefined,\n) {\n for (const node of it) {\n if (constraint && !constraintMatchesRow(constraint, node.row)) {\n break;\n }\n yield node;\n }\n}\n\nfunction* generateWithFilter(it: Stream<Node>, filter: (row: Row) => boolean) {\n for (const node of it) {\n if (filter(node.row)) {\n yield node;\n }\n }\n}\n\nexport function* genPushAndWriteWithSplitEdit(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n getNextEpoch: () => number,\n) {\n let shouldSplitEdit = false;\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT) {\n for (const {splitEditKeys} of connections) {\n if (splitEditKeys) {\n for (const key of splitEditKeys) {\n if (\n !valuesEqual(\n change[SourceChangeIndex.ROW][key],\n change[SourceChangeIndex.OLD_ROW][key],\n )\n ) {\n shouldSplitEdit = true;\n break;\n }\n }\n }\n }\n }\n\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT && shouldSplitEdit) {\n yield* genPushAndWrite(\n connections,\n makeSourceChangeRemove(change[SourceChangeIndex.OLD_ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n yield* genPushAndWrite(\n connections,\n makeSourceChangeAdd(change[SourceChangeIndex.ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n } else {\n yield* genPushAndWrite(\n connections,\n change,\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n }\n}\n\nfunction* genPushAndWrite(\n connections: readonly Connection[],\n change: SourceChangeAdd | SourceChangeRemove | SourceChangeEdit,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n pushEpoch: number,\n) {\n for (const x of genPush(connections, change, exists, setOverlay, pushEpoch)) {\n yield x;\n }\n writeChange(change);\n}\n\nfunction* genPush(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => void,\n pushEpoch: number,\n) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n assert(\n !exists(change[SourceChangeIndex.ROW]),\n () => `Row already exists ${stringify(change)}`,\n );\n break;\n case ChangeType.REMOVE:\n assert(\n exists(change[SourceChangeIndex.ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n case ChangeType.EDIT:\n assert(\n exists(change[SourceChangeIndex.OLD_ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n default:\n unreachable(change);\n }\n\n setOverlay({epoch: pushEpoch, change});\n try {\n for (const conn of connections) {\n const {output, filters, input} = conn;\n if (output) {\n conn.lastPushedEpoch = pushEpoch;\n const outputChange: Change =\n change[SourceChangeIndex.TYPE] === ChangeType.EDIT\n ? makeEditChange(\n {row: change[SourceChangeIndex.ROW], relationships: {}},\n {row: change[SourceChangeIndex.OLD_ROW], relationships: {}},\n )\n : change[SourceChangeIndex.TYPE] === ChangeType.ADD\n ? makeAddChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n })\n : makeRemoveChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n });\n yield* filterPush(outputChange, output, input, filters?.predicate);\n yield undefined;\n }\n }\n\n for (const conn of connections) {\n const {output, input} = conn;\n if (output?.reconcile) {\n yield* output.reconcile(input);\n yield undefined;\n }\n }\n } finally {\n setOverlay(undefined);\n }\n}\n\nexport function* generateWithStart(\n nodes: Iterable<Node | 'yield'>,\n start: Start | undefined,\n compare: (r1: Row, r2: Row) => number,\n): Stream<Node | 'yield'> {\n if (!start) {\n yield* nodes;\n return;\n }\n let started = false;\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!started) {\n if (start.basis === 'at') {\n if (compare(node.row, start.row) >= 0) {\n started = true;\n }\n } else if (start.basis === 'after') {\n if (compare(node.row, start.row) > 0) {\n started = true;\n }\n }\n }\n if (started) {\n yield node;\n }\n }\n}\n\n/**\n * Takes an iterator and overlay.\n * Splices the overlay into the iterator at the correct position.\n *\n * @param startAt - if there is a lower bound to the stream. If the lower bound of the stream\n * is above the overlay, the overlay will be skipped.\n * @param rows - the stream into which the overlay should be spliced\n * @param constraint - constraint that was applied to the rowIterator and should\n * also be applied to the overlay.\n * @param overlay - the overlay values to splice in\n * @param compare - the comparator to use to find the position for the overlay.\n * Must order rows the same way `rows` is ordered. For `MemorySource` that is\n * *index* order, which leads with the constraint keys.\n * @param startAtCompare - the comparator for `startAt`, which is a bound in the\n * *connection's* sort order. Only the same as `compare` when the stream's order\n * happens to be the connection's -- true for `TableSource` (SQL does the\n * ordering) but not for a constrained `MemorySource` fetch. Conflating the two\n * is what #4926 fixed for `generateWithStart`; this parameter is the same\n * distinction for the overlay's own `startAt` pruning.\n */\nexport function generateWithOverlay(\n startAt: Row | undefined,\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n compare: Comparator,\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[],\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n const overlays = computeOverlays(\n startAt,\n constraint,\n overlayToApply,\n startAtCompare,\n filterPredicate,\n multiConstraints,\n );\n return generateWithOverlayInner(rows, overlays, compare);\n}\n\nfunction computeOverlays(\n startAt: Row | undefined,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n // Only ever used for `overlaysForStartAt`, so this is the *connection*-order\n // comparator, not the one used to splice the overlay into the row stream.\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[],\n): Overlays {\n let overlays: Overlays = {\n add: undefined,\n remove: undefined,\n };\n switch (overlay?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlay.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: overlay.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n\n if (startAt) {\n overlays = overlaysForStartAt(overlays, startAt, startAtCompare);\n }\n\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n\n return overlays;\n}\n\nfunction applyMultiConstraintsToOverlays(\n overlays: Overlays,\n multiConstraints: readonly MultiConstraint[] | undefined,\n): Overlays {\n if (!multiConstraints) return overlays;\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n overlays = overlaysForMultiConstraint(overlays, mc);\n }\n }\n return overlays;\n}\n\nexport {overlaysForMultiConstraint as overlaysForMultiConstraintForTest};\n\nfunction overlaysForMultiConstraint(\n {add, remove}: Overlays,\n multiConstraint: MultiConstraint,\n): Overlays {\n const matchesAny = (row: Row | undefined) => {\n if (row === undefined) return false;\n for (const c of multiConstraint) {\n if (constraintMatchesRow(c, row)) return true;\n }\n return false;\n };\n return {\n add: matchesAny(add) ? add : undefined,\n remove: matchesAny(remove) ? remove : undefined,\n };\n}\n\nexport {overlaysForStartAt as overlaysForStartAtForTest};\n\nfunction overlaysForStartAt(\n {add, remove}: Overlays,\n startAt: Row,\n compare: Comparator,\n): Overlays {\n const undefinedIfBeforeStartAt = (row: Row | undefined) =>\n row === undefined || compare(row, startAt) < 0 ? undefined : row;\n return {\n add: undefinedIfBeforeStartAt(add),\n remove: undefinedIfBeforeStartAt(remove),\n };\n}\n\nexport {overlaysForConstraint as overlaysForConstraintForTest};\n\nfunction overlaysForConstraint(\n {add, remove}: Overlays,\n constraint: Constraint,\n): Overlays {\n const undefinedIfDoesntMatchConstraint = (row: Row | undefined) =>\n row === undefined || !constraintMatchesRow(constraint, row)\n ? undefined\n : row;\n\n return {\n add: undefinedIfDoesntMatchConstraint(add),\n remove: undefinedIfDoesntMatchConstraint(remove),\n };\n}\n\nfunction overlaysForFilterPredicate(\n {add, remove}: Overlays,\n filterPredicate: (row: Row) => boolean | undefined,\n): Overlays {\n const undefinedIfDoesntMatchFilter = (row: Row | undefined) =>\n row === undefined || !filterPredicate(row) ? undefined : row;\n\n return {\n add: undefinedIfDoesntMatchFilter(add),\n remove: undefinedIfDoesntMatchFilter(remove),\n };\n}\n\nexport function* generateWithOverlayInner(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n compare: (r1: Row, r2: Row) => number,\n) {\n let addOverlayYielded = false;\n let removeOverlaySkipped = false;\n for (const row of rowIterator) {\n if (!addOverlayYielded && overlays.add) {\n const cmp = compare(overlays.add, row);\n if (cmp < 0) {\n addOverlayYielded = true;\n yield {row: overlays.add, relationships: {}};\n }\n }\n\n if (!removeOverlaySkipped && overlays.remove) {\n const cmp = compare(overlays.remove, row);\n if (cmp === 0) {\n removeOverlaySkipped = true;\n continue;\n }\n }\n yield {row, relationships: {}};\n }\n\n if (!addOverlayYielded && overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n}\n\n/**\n * Like {@link generateWithOverlay} but for unordered streams.\n * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly\n * at the start, and suppresses add/new-edit rows inline by PK match.\n */\nexport function generateWithOverlayUnordered(\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n primaryKey: PrimaryKey,\n filterPredicate?: (row: Row) => boolean,\n multiConstraints?: readonly MultiConstraint[],\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n let overlays: Overlays = {add: undefined, remove: undefined};\n switch (overlayToApply?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlayToApply.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: overlayToApply.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n return generateWithOverlayInnerUnordered(rows, overlays, primaryKey);\n}\n\nexport function* generateWithOverlayInnerUnordered(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n primaryKey: PrimaryKey,\n) {\n // Eager inject: yield the add overlay at the start (row not yet in storage)\n if (overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n // Stream with inline suppress: skip the remove overlay (row still in storage)\n let removeSkipped = false;\n for (const row of rowIterator) {\n if (\n !removeSkipped &&\n overlays.remove &&\n rowMatchesPK(overlays.remove, row, primaryKey)\n ) {\n removeSkipped = true;\n continue;\n }\n yield {row, relationships: {}};\n }\n}\n\nfunction rowMatchesPK(a: Row, b: Row, primaryKey: PrimaryKey): boolean {\n for (const key of primaryKey) {\n if (!valuesEqual(a[key], b[key])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A location to begin scanning an index from. Can either be a specific value\n * or the min or max possible value for the type. This is used to start a scan\n * at the beginning of the rows matching a constraint.\n */\nexport type Bound = Value | MinValue | MaxValue;\nexport type RowBound = Record<string, Bound>;\nexport const minValue = Symbol('min-value');\ntype MinValue = typeof minValue;\nexport const maxValue = Symbol('max-value');\ntype MaxValue = typeof maxValue;\n\nfunction isAscending(rows: Row[], comparator: Comparator): boolean {\n for (let i = 1; i < rows.length; i++) {\n if (comparator(rows[i - 1], rows[i]) > 0) {\n return false;\n }\n }\n return true;\n}\n\nexport function makeBoundComparator(sort: Ordering): Comparator {\n // Pre-extract the first two keys/directions to avoid per-call array access.\n // All paths share one function literal (single SFI) so the BTree comparator call site\n // stays monomorphic across indexes with different sort orderings, preventing V8 IC deopt.\n // Even a 2-SFI split (e.g. separate len=1 path) creates a polymorphic IC that\n // measurably regresses performance, so we keep a single return body.\n const len = sort.length;\n const k0 = sort[0][0];\n const a0 = sort[0][1] === 'asc';\n const k1 = len > 1 ? sort[1][0] : '';\n const a1 = len > 1 ? sort[1][1] === 'asc' : true;\n\n return (a: RowBound, b: RowBound) => {\n const c0 = a0 ? compareBounds(a[k0], b[k0]) : compareBounds(b[k0], a[k0]);\n if (len === 1 || c0 !== 0) return c0;\n const c1 = a1 ? compareBounds(a[k1], b[k1]) : compareBounds(b[k1], a[k1]);\n if (len === 2 || c1 !== 0) return c1;\n // Hot! Do not use destructuring\n for (let i = 2; i < len; i++) {\n const cmp = compareBounds(a[sort[i][0]], b[sort[i][0]]);\n if (cmp !== 0) return sort[i][1] === 'asc' ? cmp : -cmp;\n }\n return 0;\n };\n}\n\nfunction compareBounds(a: Bound, b: Bound): number {\n if (a === b) {\n return 0;\n }\n // Use typeof to guard the Symbol sentinel checks first. This gives V8 a\n // clear type discriminant so the common non-symbol path compiles as a\n // specialised numeric/string fast-path without a Smi deopt when the\n // minValue/maxValue sentinel symbols appear.\n if (typeof a === 'symbol') {\n return a === minValue ? -1 : 1;\n }\n if (typeof b === 'symbol') {\n return b === minValue ? 1 : -1;\n }\n return compareValues(a, b);\n}\n\nexport function generateRows(\n data: BTreeSet<Row>,\n scanStart: RowBound | undefined,\n reverse: boolean | undefined,\n) {\n return reverse\n ? data.valuesFromReversed(scanStart as Row | undefined)\n : data.valuesFrom(scanStart as Row | undefined);\n}\n\nexport function stringify(change: SourceChange) {\n return JSON.stringify(change, (_, v) =>\n typeof v === 'bigint' ? v.toString() : v,\n );\n}\n\n/**\n * N-way merge of pre-sorted Node streams. Each input stream must yield\n * Nodes in `compare` order (and may interleave 'yield' values, which are\n * forwarded as-is). The merged stream yields Nodes in `compare` order.\n *\n * Implemented as a binary min-heap keyed by `compare(entry.row, ...)`, so\n * each emit costs O(log K) (K = number of streams) rather than the O(K)\n * a linear-scan-of-heads merge would. Matters when FlippedJoin chunks a\n * large `multiConstraints` IN-list into hundreds of sub-fetches.\n *\n * If the merged stream is closed early (e.g. downstream `Take` `break`s\n * after hitting its limit, prompting JS to call `.return()` on this\n * generator), the `finally` block propagates `.return()` to each\n * non-exhausted sub-iterator so the underlying sources (SQLite cursors,\n * etc.) can run their cleanup. Without this, mid-merge early-termination\n * leaves cursors open, causing later writes on the same connection to\n * fail with \"database connection is busy executing a query\".\n */\nexport function* mergeSortedStreams(\n streams: readonly Stream<Node | 'yield'>[],\n compare: (a: Node, b: Node) => number,\n): Stream<Node | 'yield'> {\n const iterators: Iterator<Node | 'yield'>[] = streams.map(s =>\n s[Symbol.iterator](),\n );\n // True while iterators[i] hasn't yet returned `done`. The finally\n // block uses this to skip already-exhausted streams when propagating\n // `.return()`.\n const active = Array.from({length: iterators.length}).fill(true) as boolean[];\n\n // Min-heap of entries; `idx` tells us which stream to refill from\n // after the entry's row is emitted.\n type Entry = {row: Node; idx: number};\n const heap: Entry[] = [];\n\n const siftUp = (start: number) => {\n let i = start;\n while (i > 0) {\n const p = (i - 1) >> 1;\n if (compare(heap[i].row, heap[p].row) >= 0) return;\n const t = heap[i];\n heap[i] = heap[p];\n heap[p] = t;\n i = p;\n }\n };\n\n const siftDown = (start: number) => {\n let i = start;\n const n = heap.length;\n while (true) {\n const l = (i << 1) + 1;\n const r = l + 1;\n let smallest = i;\n if (l < n && compare(heap[l].row, heap[smallest].row) < 0) smallest = l;\n if (r < n && compare(heap[r].row, heap[smallest].row) < 0) smallest = r;\n if (smallest === i) return;\n const t = heap[i];\n heap[i] = heap[smallest];\n heap[smallest] = t;\n i = smallest;\n }\n };\n\n // Pull the next Node from iterator `idx`, forwarding any 'yield's.\n // Returns the Node, or `undefined` once the stream is exhausted.\n const pullNext = function* (\n idx: number,\n ): IterableIterator<'yield', Node | undefined, undefined> {\n while (true) {\n const r = iterators[idx].next();\n if (r.done) {\n active[idx] = false;\n return undefined;\n }\n if (r.value === 'yield') {\n yield 'yield';\n continue;\n }\n return r.value;\n }\n };\n\n try {\n // Prime: push the first row of each non-empty stream onto the heap.\n for (let i = 0; i < iterators.length; i++) {\n const row = yield* pullNext(i);\n if (row !== undefined) {\n heap.push({row, idx: i});\n siftUp(heap.length - 1);\n }\n }\n\n while (heap.length > 0) {\n // Root is the global min across all active streams.\n const top = heap[0];\n yield top.row;\n const next = yield* pullNext(top.idx);\n if (next !== undefined) {\n // Refill root in place (top === heap[0]) and sift down. The\n // already-yielded `top.row` value is captured by the yield, so\n // mutating it here doesn't affect what was emitted.\n top.row = next;\n siftDown(0);\n } else {\n // Stream exhausted. Move tail to root and shrink. Pop returns\n // the last entry; if the heap had only one entry it was the\n // root we just yielded, so we just leave the heap empty.\n const last = must(heap.pop());\n if (heap.length > 0) {\n heap[0] = last;\n siftDown(0);\n }\n }\n }\n } finally {\n // Close any iterators that aren't already exhausted so their\n // `finally` blocks (which release cursors / cached statements) run.\n for (let i = 0; i < iterators.length; i++) {\n if (active[i]) {\n iterators[i].return?.();\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import type { ConnectionCostModel } from './planner-connection.ts';
|
|
|
4
4
|
import type { PlannerConstraint } from './planner-constraint.ts';
|
|
5
5
|
import type { PlanDebugger } from './planner-debug.ts';
|
|
6
6
|
import { PlannerGraph } from './planner-graph.ts';
|
|
7
|
+
import { type PlanWarning, type PlanWarningThresholds } from './planner-warnings.ts';
|
|
7
8
|
export type Plans = {
|
|
8
9
|
plan: PlannerGraph;
|
|
9
10
|
subPlans: {
|
|
@@ -15,11 +16,21 @@ export type Plans = {
|
|
|
15
16
|
* a child (see `pushDownCorrelatedPredicates`).
|
|
16
17
|
*/
|
|
17
18
|
export declare function buildPlanGraph(ast: AST, model: ConnectionCostModel, isRoot: boolean, baseConstraints?: PlannerConstraint, pushed?: ReadonlySet<SimpleCondition>): Plans;
|
|
19
|
+
/**
|
|
20
|
+
* Where the planner reports the {@link PlanWarning}s for the plan it chose.
|
|
21
|
+
*/
|
|
22
|
+
export type PlanWarningSink = {
|
|
23
|
+
readonly thresholds: PlanWarningThresholds;
|
|
24
|
+
/** Called once per planned query, and only if there are warnings. */
|
|
25
|
+
report(warnings: readonly PlanWarning[]): void;
|
|
26
|
+
};
|
|
18
27
|
/**
|
|
19
28
|
* @param pushed The conditions that correlated predicate pushdown copied into
|
|
20
29
|
* a child (see `pushDownCorrelatedPredicates`). The planner counts each one
|
|
21
30
|
* only where it removes rows.
|
|
31
|
+
* @param planWarnings Receives warnings about the chosen plan. Collecting them
|
|
32
|
+
* costs extra cost model calls, so this is best left out when unused.
|
|
22
33
|
*/
|
|
23
|
-
export declare function planQuery(ast: AST, model: ConnectionCostModel, planDebugger?: PlanDebugger, lc?: LogContext, pushed?: ReadonlySet<SimpleCondition
|
|
34
|
+
export declare function planQuery(ast: AST, model: ConnectionCostModel, planDebugger?: PlanDebugger, lc?: LogContext, pushed?: ReadonlySet<SimpleCondition>, planWarnings?: PlanWarningSink): AST;
|
|
24
35
|
export declare function applyPlansToAST(ast: AST, plans: Plans): AST;
|
|
25
36
|
//# sourceMappingURL=planner-builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EACV,GAAG,EAKH,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAIrD,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"planner-builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EACV,GAAG,EAKH,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAIrD,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAIhD,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAkB/B,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;KAAC,CAAC;CAClC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,mBAAmB,EAC1B,MAAM,EAAE,OAAO,EACf,eAAe,CAAC,EAAE,iBAAiB,EACnC,MAAM,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,GACpC,KAAK,CAsDP;AAgQD;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,qEAAqE;IACrE,MAAM,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI,CAAC;CAChD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,mBAAmB,EAC1B,YAAY,CAAC,EAAE,YAAY,EAC3B,EAAE,CAAC,EAAE,UAAU,EACf,MAAM,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,EACrC,YAAY,CAAC,EAAE,eAAe,GAC7B,GAAG,CAcL;AAqCD,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,GAAG,CAyB3D"}
|
|
@@ -8,6 +8,7 @@ import { PlannerFilter } from "./planner-filter.js";
|
|
|
8
8
|
import { PlannerGraph } from "./planner-graph.js";
|
|
9
9
|
import { PlannerJoin } from "./planner-join.js";
|
|
10
10
|
import { PlannerTerminus } from "./planner-terminus.js";
|
|
11
|
+
import { collectPlanWarnings } from "./planner-warnings.js";
|
|
11
12
|
//#region ../zql/src/planner/planner-builder.ts
|
|
12
13
|
function wireOutput(from, to) {
|
|
13
14
|
switch (from.kind) {
|
|
@@ -136,10 +137,16 @@ function planRecursively(plans, planDebugger, lc) {
|
|
|
136
137
|
* @param pushed The conditions that correlated predicate pushdown copied into
|
|
137
138
|
* a child (see `pushDownCorrelatedPredicates`). The planner counts each one
|
|
138
139
|
* only where it removes rows.
|
|
140
|
+
* @param planWarnings Receives warnings about the chosen plan. Collecting them
|
|
141
|
+
* costs extra cost model calls, so this is best left out when unused.
|
|
139
142
|
*/
|
|
140
|
-
function planQuery(ast, model, planDebugger, lc, pushed) {
|
|
143
|
+
function planQuery(ast, model, planDebugger, lc, pushed, planWarnings) {
|
|
141
144
|
const plans = buildPlanGraph(ast, model, true, void 0, pushed);
|
|
142
145
|
planRecursively(plans, planDebugger, lc);
|
|
146
|
+
if (planWarnings) {
|
|
147
|
+
const warnings = collectPlanWarnings(ast.table, plans, planWarnings.thresholds);
|
|
148
|
+
if (warnings.length > 0) planWarnings.report(warnings);
|
|
149
|
+
}
|
|
143
150
|
return applyPlansToAST(ast, plans);
|
|
144
151
|
}
|
|
145
152
|
function applyToCondition(condition, flippedIds) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-builder.js","names":[],"sources":["../../../../../zql/src/planner/planner-builder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n Conjunction,\n CorrelatedSubqueryCondition,\n Disjunction,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport {planIdSymbol} from '../../../zero-protocol/src/ast.ts';\nimport {transformFilters} from '../builder/filter.ts';\nimport type {ConnectionCostModel} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {PlannerFanIn} from './planner-fan-in.ts';\nimport {PlannerFanOut} from './planner-fan-out.ts';\nimport {PlannerFilter} from './planner-filter.ts';\nimport {PlannerGraph} from './planner-graph.ts';\nimport {PlannerJoin} from './planner-join.ts';\nimport type {PlannerNode} from './planner-node.ts';\nimport {PlannerTerminus} from './planner-terminus.ts';\n\nfunction wireOutput(from: PlannerNode, to: PlannerNode): void {\n switch (from.kind) {\n case 'connection':\n case 'join':\n case 'fan-in':\n case 'filter':\n from.setOutput(to);\n break;\n case 'fan-out':\n from.addOutput(to);\n break;\n case 'terminus':\n assert(false, 'Terminus nodes cannot have outputs');\n }\n}\n\nexport type Plans = {\n plan: PlannerGraph;\n subPlans: {[key: string]: Plans};\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`).\n */\nexport function buildPlanGraph(\n ast: AST,\n model: ConnectionCostModel,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n pushed?: ReadonlySet<SimpleCondition>,\n): Plans {\n const graph = new PlannerGraph();\n let nextPlanId = 0;\n\n const source = graph.addSource(ast.table, model);\n const connection = source.connect(\n ast.orderBy ?? [],\n ast.where,\n isRoot,\n baseConstraints,\n ast.limit,\n pushed,\n );\n graph.connections.push(connection);\n\n let end: PlannerNode = connection;\n if (ast.where) {\n end = processCondition(\n ast.where,\n end,\n graph,\n model,\n ast.table,\n () => nextPlanId++,\n pushed,\n );\n }\n\n const terminus = new PlannerTerminus(end);\n wireOutput(end, terminus);\n graph.setTerminus(terminus);\n\n const subPlans: {[key: string]: Plans} = {};\n if (ast.related) {\n for (const csq of ast.related) {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const childConstraints = extractConstraint(\n csq.correlation.childField,\n csq.subquery.table,\n );\n subPlans[alias] = buildPlanGraph(\n csq.subquery,\n model,\n true,\n childConstraints,\n pushed,\n );\n }\n }\n\n return {plan: graph, subPlans};\n}\n\nfunction processCondition(\n condition: Condition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n switch (condition.type) {\n case 'simple':\n return input;\n case 'and':\n return processAnd(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'or':\n return processOr(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'correlatedSubquery':\n return processCorrelatedSubquery(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n}\n\nfunction processAnd(\n condition: Conjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n let end = input;\n for (const subCondition of condition.conditions) {\n end = processCondition(\n subCondition,\n end,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n return end;\n}\n\nfunction processOr(\n condition: Disjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n // Skip building fan structure when no branch contains a CSQ. The runtime\n // collapses such ORs to a single Filter node, so the planner has nothing\n // to choose between.\n const hasAnySubquery = condition.conditions.some(\n c => c.type === 'correlatedSubquery' || hasCorrelatedSubquery(c),\n );\n if (!hasAnySubquery) {\n return input;\n }\n\n const fanOut = new PlannerFanOut(input);\n graph.fanOuts.push(fanOut);\n wireOutput(input, fanOut);\n\n const branches: Exclude<PlannerNode, PlannerTerminus>[] = [];\n for (const subCondition of condition.conditions) {\n let branch: Exclude<PlannerNode, PlannerTerminus>;\n if (\n subCondition.type === 'correlatedSubquery' ||\n hasCorrelatedSubquery(subCondition)\n ) {\n branch = processCondition(\n subCondition,\n fanOut,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n } else {\n // Simple OR branch: wrap in a PlannerFilter so its filter can be\n // registered at the receiving connection on a per-branch basis when\n // the FanIn is in UFI mode.\n const transformed = transformFilters(subCondition).filters;\n const filter = new PlannerFilter(fanOut, transformed);\n graph.filters.push(filter);\n wireOutput(fanOut, filter);\n branch = filter;\n }\n branches.push(branch);\n fanOut.addOutput(branch);\n }\n\n const fanIn = new PlannerFanIn(branches);\n graph.fanIns.push(fanIn);\n for (const branch of branches) {\n wireOutput(branch, fanIn);\n }\n\n return fanIn;\n}\n\nfunction processCorrelatedSubquery(\n condition: CorrelatedSubqueryCondition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n const {related} = condition;\n const childTable = related.subquery.table;\n\n const childSource = graph.hasSource(childTable)\n ? graph.getSource(childTable)\n : graph.addSource(childTable, model);\n\n const childConnection = childSource.connect(\n related.subquery.orderBy ?? [],\n related.subquery.where,\n false,\n undefined, // no base constraints for EXISTS/NOT EXISTS\n condition.op === 'EXISTS' ? 1 : undefined,\n pushed,\n );\n graph.connections.push(childConnection);\n\n let childEnd: PlannerNode = childConnection;\n if (related.subquery.where) {\n childEnd = processCondition(\n related.subquery.where,\n childEnd,\n graph,\n model,\n childTable,\n getPlanId,\n pushed,\n );\n }\n\n const parentConstraint = extractConstraint(\n related.correlation.parentField,\n parentTable,\n );\n const childConstraint = extractConstraint(\n related.correlation.childField,\n childTable,\n );\n\n const planId = getPlanId();\n condition[planIdSymbol] = planId;\n\n // Determine flippability and initial type based on flip flag and operator\n const isNotExists = condition.op === 'NOT EXISTS';\n const manualFlip = condition.flip;\n\n let flippable: boolean;\n let initialType: 'semi' | 'flipped';\n\n if (isNotExists) {\n // NOT EXISTS joins can never be flipped\n flippable = false;\n initialType = 'semi';\n } else if (manualFlip === true) {\n // User explicitly requested flip=true: start flipped, don't allow planner to change\n flippable = false;\n initialType = 'flipped';\n } else if (manualFlip === false) {\n // User explicitly requested flip=false: start semi, don't allow planner to change\n flippable = false;\n initialType = 'semi';\n } else {\n // flip is undefined: planner can decide\n flippable = true;\n initialType = 'semi';\n }\n\n const join = new PlannerJoin(\n input,\n childEnd,\n parentConstraint,\n childConstraint,\n flippable,\n planId,\n initialType,\n );\n graph.joins.push(join);\n childConnection.setParentJoin(join);\n\n wireOutput(input, join);\n wireOutput(childEnd, join);\n\n return join;\n}\n\nfunction hasCorrelatedSubquery(condition: Condition): boolean {\n if (condition.type === 'correlatedSubquery') {\n return true;\n }\n if (condition.type === 'and' || condition.type === 'or') {\n return condition.conditions.some(hasCorrelatedSubquery);\n }\n // simple conditions don't contain correlated subqueries\n return false;\n}\n\nfunction extractConstraint(\n fields: readonly string[],\n _tableName: string,\n): PlannerConstraint {\n return Object.fromEntries(fields.map(field => [field, undefined]));\n}\n\nfunction planRecursively(\n plans: Plans,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n): void {\n for (const subPlan of Object.values(plans.subPlans)) {\n planRecursively(subPlan, planDebugger, lc);\n }\n plans.plan.plan(planDebugger, lc);\n}\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`). The planner counts each one\n * only where it removes rows.\n */\nexport function planQuery(\n ast: AST,\n model: ConnectionCostModel,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n pushed?: ReadonlySet<SimpleCondition>,\n): AST {\n const plans = buildPlanGraph(ast, model, true, undefined, pushed);\n planRecursively(plans, planDebugger, lc);\n return applyPlansToAST(ast, plans);\n}\n\nfunction applyToCondition(\n condition: Condition,\n flippedIds: Set<number>,\n): Condition {\n if (condition.type === 'simple') {\n return condition;\n }\n\n if (condition.type === 'correlatedSubquery') {\n const planId = (condition as unknown as Record<symbol, number>)[\n planIdSymbol\n ];\n const shouldFlip = planId !== undefined && flippedIds.has(planId);\n\n return {\n ...condition,\n flip: shouldFlip,\n related: {\n ...condition.related,\n subquery: {\n ...condition.related.subquery,\n where: condition.related.subquery.where\n ? applyToCondition(condition.related.subquery.where, flippedIds)\n : undefined,\n },\n },\n };\n }\n\n return {\n ...condition,\n conditions: condition.conditions.map(c => applyToCondition(c, flippedIds)),\n };\n}\n\nexport function applyPlansToAST(ast: AST, plans: Plans): AST {\n const flippedIds = new Set<number>();\n for (const join of plans.plan.joins) {\n if (join.type === 'flipped' && join.planId !== undefined) {\n flippedIds.add(join.planId);\n }\n }\n\n return {\n ...ast,\n where: ast.where ? applyToCondition(ast.where, flippedIds) : undefined,\n related: ast.related?.map(csq => {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const subPlan = plans.subPlans[alias];\n return {\n ...csq,\n subquery: subPlan\n ? applyPlansToAST(csq.subquery, subPlan)\n : csq.subquery,\n };\n }),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner-builder.js","names":[],"sources":["../../../../../zql/src/planner/planner-builder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n Conjunction,\n CorrelatedSubqueryCondition,\n Disjunction,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport {planIdSymbol} from '../../../zero-protocol/src/ast.ts';\nimport {transformFilters} from '../builder/filter.ts';\nimport type {ConnectionCostModel} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {PlannerFanIn} from './planner-fan-in.ts';\nimport {PlannerFanOut} from './planner-fan-out.ts';\nimport {PlannerFilter} from './planner-filter.ts';\nimport {PlannerGraph} from './planner-graph.ts';\nimport {PlannerJoin} from './planner-join.ts';\nimport type {PlannerNode} from './planner-node.ts';\nimport {PlannerTerminus} from './planner-terminus.ts';\nimport {\n collectPlanWarnings,\n type PlanWarning,\n type PlanWarningThresholds,\n} from './planner-warnings.ts';\n\nfunction wireOutput(from: PlannerNode, to: PlannerNode): void {\n switch (from.kind) {\n case 'connection':\n case 'join':\n case 'fan-in':\n case 'filter':\n from.setOutput(to);\n break;\n case 'fan-out':\n from.addOutput(to);\n break;\n case 'terminus':\n assert(false, 'Terminus nodes cannot have outputs');\n }\n}\n\nexport type Plans = {\n plan: PlannerGraph;\n subPlans: {[key: string]: Plans};\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`).\n */\nexport function buildPlanGraph(\n ast: AST,\n model: ConnectionCostModel,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n pushed?: ReadonlySet<SimpleCondition>,\n): Plans {\n const graph = new PlannerGraph();\n let nextPlanId = 0;\n\n const source = graph.addSource(ast.table, model);\n const connection = source.connect(\n ast.orderBy ?? [],\n ast.where,\n isRoot,\n baseConstraints,\n ast.limit,\n pushed,\n );\n graph.connections.push(connection);\n\n let end: PlannerNode = connection;\n if (ast.where) {\n end = processCondition(\n ast.where,\n end,\n graph,\n model,\n ast.table,\n () => nextPlanId++,\n pushed,\n );\n }\n\n const terminus = new PlannerTerminus(end);\n wireOutput(end, terminus);\n graph.setTerminus(terminus);\n\n const subPlans: {[key: string]: Plans} = {};\n if (ast.related) {\n for (const csq of ast.related) {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const childConstraints = extractConstraint(\n csq.correlation.childField,\n csq.subquery.table,\n );\n subPlans[alias] = buildPlanGraph(\n csq.subquery,\n model,\n true,\n childConstraints,\n pushed,\n );\n }\n }\n\n return {plan: graph, subPlans};\n}\n\nfunction processCondition(\n condition: Condition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n switch (condition.type) {\n case 'simple':\n return input;\n case 'and':\n return processAnd(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'or':\n return processOr(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'correlatedSubquery':\n return processCorrelatedSubquery(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n}\n\nfunction processAnd(\n condition: Conjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n let end = input;\n for (const subCondition of condition.conditions) {\n end = processCondition(\n subCondition,\n end,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n return end;\n}\n\nfunction processOr(\n condition: Disjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n // Skip building fan structure when no branch contains a CSQ. The runtime\n // collapses such ORs to a single Filter node, so the planner has nothing\n // to choose between.\n const hasAnySubquery = condition.conditions.some(\n c => c.type === 'correlatedSubquery' || hasCorrelatedSubquery(c),\n );\n if (!hasAnySubquery) {\n return input;\n }\n\n const fanOut = new PlannerFanOut(input);\n graph.fanOuts.push(fanOut);\n wireOutput(input, fanOut);\n\n const branches: Exclude<PlannerNode, PlannerTerminus>[] = [];\n for (const subCondition of condition.conditions) {\n let branch: Exclude<PlannerNode, PlannerTerminus>;\n if (\n subCondition.type === 'correlatedSubquery' ||\n hasCorrelatedSubquery(subCondition)\n ) {\n branch = processCondition(\n subCondition,\n fanOut,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n } else {\n // Simple OR branch: wrap in a PlannerFilter so its filter can be\n // registered at the receiving connection on a per-branch basis when\n // the FanIn is in UFI mode.\n const transformed = transformFilters(subCondition).filters;\n const filter = new PlannerFilter(fanOut, transformed);\n graph.filters.push(filter);\n wireOutput(fanOut, filter);\n branch = filter;\n }\n branches.push(branch);\n fanOut.addOutput(branch);\n }\n\n const fanIn = new PlannerFanIn(branches);\n graph.fanIns.push(fanIn);\n for (const branch of branches) {\n wireOutput(branch, fanIn);\n }\n\n return fanIn;\n}\n\nfunction processCorrelatedSubquery(\n condition: CorrelatedSubqueryCondition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n const {related} = condition;\n const childTable = related.subquery.table;\n\n const childSource = graph.hasSource(childTable)\n ? graph.getSource(childTable)\n : graph.addSource(childTable, model);\n\n const childConnection = childSource.connect(\n related.subquery.orderBy ?? [],\n related.subquery.where,\n false,\n undefined, // no base constraints for EXISTS/NOT EXISTS\n condition.op === 'EXISTS' ? 1 : undefined,\n pushed,\n );\n graph.connections.push(childConnection);\n\n let childEnd: PlannerNode = childConnection;\n if (related.subquery.where) {\n childEnd = processCondition(\n related.subquery.where,\n childEnd,\n graph,\n model,\n childTable,\n getPlanId,\n pushed,\n );\n }\n\n const parentConstraint = extractConstraint(\n related.correlation.parentField,\n parentTable,\n );\n const childConstraint = extractConstraint(\n related.correlation.childField,\n childTable,\n );\n\n const planId = getPlanId();\n condition[planIdSymbol] = planId;\n\n // Determine flippability and initial type based on flip flag and operator\n const isNotExists = condition.op === 'NOT EXISTS';\n const manualFlip = condition.flip;\n\n let flippable: boolean;\n let initialType: 'semi' | 'flipped';\n\n if (isNotExists) {\n // NOT EXISTS joins can never be flipped\n flippable = false;\n initialType = 'semi';\n } else if (manualFlip === true) {\n // User explicitly requested flip=true: start flipped, don't allow planner to change\n flippable = false;\n initialType = 'flipped';\n } else if (manualFlip === false) {\n // User explicitly requested flip=false: start semi, don't allow planner to change\n flippable = false;\n initialType = 'semi';\n } else {\n // flip is undefined: planner can decide\n flippable = true;\n initialType = 'semi';\n }\n\n const join = new PlannerJoin(\n input,\n childEnd,\n parentConstraint,\n childConstraint,\n flippable,\n planId,\n initialType,\n );\n graph.joins.push(join);\n childConnection.setParentJoin(join);\n\n wireOutput(input, join);\n wireOutput(childEnd, join);\n\n return join;\n}\n\nfunction hasCorrelatedSubquery(condition: Condition): boolean {\n if (condition.type === 'correlatedSubquery') {\n return true;\n }\n if (condition.type === 'and' || condition.type === 'or') {\n return condition.conditions.some(hasCorrelatedSubquery);\n }\n // simple conditions don't contain correlated subqueries\n return false;\n}\n\nfunction extractConstraint(\n fields: readonly string[],\n _tableName: string,\n): PlannerConstraint {\n return Object.fromEntries(fields.map(field => [field, undefined]));\n}\n\nfunction planRecursively(\n plans: Plans,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n): void {\n for (const subPlan of Object.values(plans.subPlans)) {\n planRecursively(subPlan, planDebugger, lc);\n }\n plans.plan.plan(planDebugger, lc);\n}\n\n/**\n * Where the planner reports the {@link PlanWarning}s for the plan it chose.\n */\nexport type PlanWarningSink = {\n readonly thresholds: PlanWarningThresholds;\n /** Called once per planned query, and only if there are warnings. */\n report(warnings: readonly PlanWarning[]): void;\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`). The planner counts each one\n * only where it removes rows.\n * @param planWarnings Receives warnings about the chosen plan. Collecting them\n * costs extra cost model calls, so this is best left out when unused.\n */\nexport function planQuery(\n ast: AST,\n model: ConnectionCostModel,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n pushed?: ReadonlySet<SimpleCondition>,\n planWarnings?: PlanWarningSink,\n): AST {\n const plans = buildPlanGraph(ast, model, true, undefined, pushed);\n planRecursively(plans, planDebugger, lc);\n if (planWarnings) {\n const warnings = collectPlanWarnings(\n ast.table,\n plans,\n planWarnings.thresholds,\n );\n if (warnings.length > 0) {\n planWarnings.report(warnings);\n }\n }\n return applyPlansToAST(ast, plans);\n}\n\nfunction applyToCondition(\n condition: Condition,\n flippedIds: Set<number>,\n): Condition {\n if (condition.type === 'simple') {\n return condition;\n }\n\n if (condition.type === 'correlatedSubquery') {\n const planId = (condition as unknown as Record<symbol, number>)[\n planIdSymbol\n ];\n const shouldFlip = planId !== undefined && flippedIds.has(planId);\n\n return {\n ...condition,\n flip: shouldFlip,\n related: {\n ...condition.related,\n subquery: {\n ...condition.related.subquery,\n where: condition.related.subquery.where\n ? applyToCondition(condition.related.subquery.where, flippedIds)\n : undefined,\n },\n },\n };\n }\n\n return {\n ...condition,\n conditions: condition.conditions.map(c => applyToCondition(c, flippedIds)),\n };\n}\n\nexport function applyPlansToAST(ast: AST, plans: Plans): AST {\n const flippedIds = new Set<number>();\n for (const join of plans.plan.joins) {\n if (join.type === 'flipped' && join.planId !== undefined) {\n flippedIds.add(join.planId);\n }\n }\n\n return {\n ...ast,\n where: ast.where ? applyToCondition(ast.where, flippedIds) : undefined,\n related: ast.related?.map(csq => {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const subPlan = plans.subPlans[alias];\n return {\n ...csq,\n subquery: subPlan\n ? applyPlansToAST(csq.subquery, subPlan)\n : csq.subquery,\n };\n }),\n };\n}\n"],"mappings":"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"}
|
|
@@ -126,6 +126,13 @@ export declare class PlannerConnection {
|
|
|
126
126
|
* in case the restored plan registers a different set.
|
|
127
127
|
*/
|
|
128
128
|
restoreConstraints(constraints: Map<string, PlannerConstraint | undefined>): void;
|
|
129
|
+
get sort(): Ordering;
|
|
130
|
+
/**
|
|
131
|
+
* How the source reads rows for the current plan: one access per branch
|
|
132
|
+
* pattern this connection was costed for since its constraints last
|
|
133
|
+
* changed. Estimate the plan's cost first to populate them.
|
|
134
|
+
*/
|
|
135
|
+
accesses(): Iterable<ConnectionAccess>;
|
|
129
136
|
/** Get current constraints for debugging. */
|
|
130
137
|
getConstraintsForDebug(): Record<string, PlannerConstraint | undefined>;
|
|
131
138
|
/** Get filters for debugging. */
|
|
@@ -140,10 +147,46 @@ type FanoutEst = {
|
|
|
140
147
|
confidence: 'high' | 'med' | 'none';
|
|
141
148
|
};
|
|
142
149
|
export type FanoutCostModel = (columns: string[]) => FanoutEst;
|
|
150
|
+
/**
|
|
151
|
+
* How a source would read the rows of a connection, as far as the cost model
|
|
152
|
+
* can tell. This is used to warn about slow plans, not to choose between
|
|
153
|
+
* them.
|
|
154
|
+
*/
|
|
155
|
+
export type AccessPlan = {
|
|
156
|
+
/**
|
|
157
|
+
* `search` if the source uses an index to find the matching rows. `scan`
|
|
158
|
+
* if it reads the whole table (or a whole index, for its order) and tests
|
|
159
|
+
* each row, stopping early only if the reader stops.
|
|
160
|
+
*/
|
|
161
|
+
readonly access: 'search' | 'scan';
|
|
162
|
+
/**
|
|
163
|
+
* Whether the rows are sorted after they are read. `full` means that every
|
|
164
|
+
* matching row is read and sorted before the first one is returned, so
|
|
165
|
+
* reading fewer rows (e.g. for a limit) does not help. `partial` means that
|
|
166
|
+
* only runs of rows that tie on a prefix of the ordering are sorted.
|
|
167
|
+
*/
|
|
168
|
+
readonly sort: 'none' | 'partial' | 'full';
|
|
169
|
+
/** The number of rows in the table, if the cost model knows it. */
|
|
170
|
+
readonly tableRows: number | undefined;
|
|
171
|
+
};
|
|
143
172
|
export type CostModelCost = {
|
|
144
173
|
startupCost: number;
|
|
145
174
|
rows: number;
|
|
146
175
|
fanout: FanoutCostModel;
|
|
176
|
+
/** Omitted by cost models that cannot tell. */
|
|
177
|
+
plan?: AccessPlan | undefined;
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* A read of a connection's source under a particular constraint, as costed
|
|
181
|
+
* by the cost model.
|
|
182
|
+
*/
|
|
183
|
+
export type ConnectionAccess = {
|
|
184
|
+
/** The columns bound to a single value by the time of the read. */
|
|
185
|
+
readonly constraint: PlannerConstraint | undefined;
|
|
186
|
+
readonly filters: Condition | undefined;
|
|
187
|
+
/** The estimated number of rows returned. */
|
|
188
|
+
readonly rows: number;
|
|
189
|
+
readonly plan: AccessPlan | undefined;
|
|
147
190
|
};
|
|
148
191
|
export type ConnectionCostModel = (table: string, sort: Ordering, filters: Condition | undefined, constraint: PlannerConstraint | undefined) => CostModelCost;
|
|
149
192
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-connection.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-connection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EACR,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBAAa,iBAAiB;;IAC5B,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAU;IAWtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAGtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAa7B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"planner-connection.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -109,6 +109,11 @@ var PlannerConnection = class {
|
|
|
109
109
|
* Invalidated when constraints change.
|
|
110
110
|
*/
|
|
111
111
|
#cachedConstraintCosts = /* @__PURE__ */ new Map();
|
|
112
|
+
/**
|
|
113
|
+
* How the source reads rows for each constraint key with a cached cost.
|
|
114
|
+
* Cleared along with {@link #cachedConstraintCosts}.
|
|
115
|
+
*/
|
|
116
|
+
#cachedAccesses = /* @__PURE__ */ new Map();
|
|
112
117
|
constructor(table, model, sort, filters, isRoot, baseConstraints, limit, name, pushed) {
|
|
113
118
|
this.table = table;
|
|
114
119
|
this.name = name ?? table;
|
|
@@ -154,7 +159,7 @@ var PlannerConnection = class {
|
|
|
154
159
|
propagateConstraints(path, c, from, planDebugger) {
|
|
155
160
|
const key = path.join(",");
|
|
156
161
|
this.#constraints.set(key, c);
|
|
157
|
-
this.#
|
|
162
|
+
this.#clearCostCaches();
|
|
158
163
|
planDebugger?.log({
|
|
159
164
|
type: "node-constraint",
|
|
160
165
|
nodeType: "connection",
|
|
@@ -172,7 +177,7 @@ var PlannerConnection = class {
|
|
|
172
177
|
*/
|
|
173
178
|
setPerBranchFilter(path, filter) {
|
|
174
179
|
this.#perBranchFilters.set(path.join(","), filter);
|
|
175
|
-
this.#
|
|
180
|
+
this.#clearCostCaches();
|
|
176
181
|
}
|
|
177
182
|
estimateCost(downstreamChildSelectivity, branchPattern, planDebugger) {
|
|
178
183
|
const key = branchPattern.join(",");
|
|
@@ -181,7 +186,8 @@ var PlannerConnection = class {
|
|
|
181
186
|
const constraint = this.#constraints.get(key);
|
|
182
187
|
const mergedConstraint = mergeConstraints(this.#baseConstraints, constraint);
|
|
183
188
|
const perBranchFilter = this.#perBranchFilters.get(key);
|
|
184
|
-
const
|
|
189
|
+
const filters = andFilters(this.#filtersFor(mergedConstraint), perBranchFilter);
|
|
190
|
+
const { startupCost, fanout, rows, plan } = this.#model(this.table, this.#sort, filters, mergedConstraint);
|
|
185
191
|
const selectivity = perBranchFilter ? this.#computeSelectivity(andFilters(this.#unpushedFilters, perBranchFilter)) : this.selectivity;
|
|
186
192
|
cost = {
|
|
187
193
|
startupCost,
|
|
@@ -193,6 +199,12 @@ var PlannerConnection = class {
|
|
|
193
199
|
fanout
|
|
194
200
|
};
|
|
195
201
|
this.#cachedConstraintCosts.set(key, cost);
|
|
202
|
+
this.#cachedAccesses.set(key, {
|
|
203
|
+
constraint: mergedConstraint,
|
|
204
|
+
filters,
|
|
205
|
+
rows,
|
|
206
|
+
plan
|
|
207
|
+
});
|
|
196
208
|
if (planDebugger) planDebugger.log({
|
|
197
209
|
type: "node-cost",
|
|
198
210
|
nodeType: "connection",
|
|
@@ -245,7 +257,11 @@ var PlannerConnection = class {
|
|
|
245
257
|
this.#constraints.clear();
|
|
246
258
|
this.#perBranchFilters.clear();
|
|
247
259
|
this.limit = this.#baseLimit;
|
|
260
|
+
this.#clearCostCaches();
|
|
261
|
+
}
|
|
262
|
+
#clearCostCaches() {
|
|
248
263
|
this.#cachedConstraintCosts.clear();
|
|
264
|
+
this.#cachedAccesses.clear();
|
|
249
265
|
}
|
|
250
266
|
/**
|
|
251
267
|
* Capture constraint state for snapshotting.
|
|
@@ -267,7 +283,18 @@ var PlannerConnection = class {
|
|
|
267
283
|
this.#constraints.clear();
|
|
268
284
|
this.#perBranchFilters.clear();
|
|
269
285
|
for (const [key, value] of constraints) this.#constraints.set(key, value);
|
|
270
|
-
this.#
|
|
286
|
+
this.#clearCostCaches();
|
|
287
|
+
}
|
|
288
|
+
get sort() {
|
|
289
|
+
return this.#sort;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* How the source reads rows for the current plan: one access per branch
|
|
293
|
+
* pattern this connection was costed for since its constraints last
|
|
294
|
+
* changed. Estimate the plan's cost first to populate them.
|
|
295
|
+
*/
|
|
296
|
+
accesses() {
|
|
297
|
+
return this.#cachedAccesses.values();
|
|
271
298
|
}
|
|
272
299
|
/** Get current constraints for debugging. */
|
|
273
300
|
getConstraintsForDebug() {
|