@rocicorp/zero 1.12.0-canary.3 → 1.12.0-canary.5
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/_virtual/{_@oxc-project_runtime@0.130.0/helpers → _@oxc-project_runtime@0.154.0/helpers/esm}/usingCtx.js +2 -2
- package/out/analyze-query/src/analyze-cli.d.ts.map +1 -1
- package/out/analyze-query/src/analyze-cli.js +6 -4
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/analyze-query/src/bin-transform.js +4 -3
- package/out/analyze-query/src/bin-transform.js.map +1 -1
- package/out/analyze-query/src/zql-chain.d.ts +23 -0
- package/out/analyze-query/src/zql-chain.d.ts.map +1 -0
- package/out/analyze-query/src/zql-chain.js +162 -0
- package/out/analyze-query/src/zql-chain.js.map +1 -0
- package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
- package/out/ast-to-zql/src/bin.js +4 -3
- package/out/ast-to-zql/src/bin.js.map +1 -1
- package/out/ast-to-zql/src/format.js.map +1 -1
- package/out/datadog/src/datadog-log-sink.js +5 -4
- package/out/datadog/src/datadog-log-sink.js.map +1 -1
- package/out/otel/src/log-options.js.map +1 -1
- package/out/replicache/src/btree/node.js +13 -11
- package/out/replicache/src/btree/node.js.map +1 -1
- package/out/replicache/src/btree/read.js +4 -2
- package/out/replicache/src/btree/read.js.map +1 -1
- package/out/replicache/src/btree/splice.js.map +1 -1
- package/out/replicache/src/btree/write.js +9 -5
- package/out/replicache/src/btree/write.js.map +1 -1
- package/out/replicache/src/config.js +1 -2
- package/out/replicache/src/connection-loop.js.map +1 -1
- package/out/replicache/src/dag/chunk.js +1 -1
- package/out/replicache/src/dag/gc.js.map +1 -1
- package/out/replicache/src/dag/lazy-store.js +8 -7
- package/out/replicache/src/dag/lazy-store.js.map +1 -1
- package/out/replicache/src/dag/store-impl.js +1 -1
- package/out/replicache/src/dag/store-impl.js.map +1 -1
- package/out/replicache/src/dag/visitor.js.map +1 -1
- package/out/replicache/src/db/commit.js +5 -3
- package/out/replicache/src/db/commit.js.map +1 -1
- package/out/replicache/src/db/read.js +1 -1
- package/out/replicache/src/db/read.js.map +1 -1
- package/out/replicache/src/db/rebase.js.map +1 -1
- package/out/replicache/src/db/write.js +5 -2
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/deleted-clients.js +3 -2
- package/out/replicache/src/deleted-clients.js.map +1 -1
- package/out/replicache/src/expo-sqlite.js +1 -2
- package/out/replicache/src/frozen-json.js +1 -1
- package/out/replicache/src/frozen-json.js.map +1 -1
- package/out/replicache/src/get-default-puller.js +1 -1
- package/out/replicache/src/hash.js +1 -1
- package/out/replicache/src/hash.js.map +1 -1
- package/out/replicache/src/impl.js +1 -2
- package/out/replicache/src/index-defs.js +5 -3
- package/out/replicache/src/index-defs.js.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/idb-store.js +1 -1
- package/out/replicache/src/kv/idb-store.js.map +1 -1
- package/out/replicache/src/kv/mem-fallback-store.js +1 -1
- package/out/replicache/src/kv/mem-fallback-store.js.map +1 -1
- package/out/replicache/src/kv/mem-store.js.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/op-sqlite/types.js.map +1 -1
- package/out/replicache/src/kv/read-impl.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +1 -1
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/kv/write-impl-base.js +4 -1
- package/out/replicache/src/kv/write-impl-base.js.map +1 -1
- package/out/replicache/src/kv/write-impl.js.map +1 -1
- package/out/replicache/src/log-options.js +2 -1
- package/out/replicache/src/log-options.js.map +1 -1
- package/out/replicache/src/make-idb-name.js +1 -1
- package/out/replicache/src/new-client-channel.js +9 -7
- package/out/replicache/src/new-client-channel.js.map +1 -1
- package/out/replicache/src/on-persist-channel.js +1 -1
- package/out/replicache/src/on-persist-channel.js.map +1 -1
- package/out/replicache/src/op-sqlite.js +1 -2
- package/out/replicache/src/pending-mutations.js +2 -1
- package/out/replicache/src/pending-mutations.js.map +1 -1
- package/out/replicache/src/persist/client-gc.js +2 -2
- package/out/replicache/src/persist/client-gc.js.map +1 -1
- package/out/replicache/src/persist/client-group-gc.js +2 -2
- package/out/replicache/src/persist/client-group-gc.js.map +1 -1
- package/out/replicache/src/persist/client-groups.js +1 -1
- package/out/replicache/src/persist/client-groups.js.map +1 -1
- package/out/replicache/src/persist/clients.js +1 -1
- package/out/replicache/src/persist/clients.js.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +5 -4
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/gather-mem-only-visitor.js.map +1 -1
- package/out/replicache/src/persist/gather-not-cached-visitor.js.map +1 -1
- package/out/replicache/src/persist/heartbeat.js +2 -2
- package/out/replicache/src/persist/heartbeat.js.map +1 -1
- package/out/replicache/src/persist/idb-databases-store.js +1 -1
- package/out/replicache/src/persist/idb-databases-store.js.map +1 -1
- package/out/replicache/src/persist/make-client-id.js +1 -1
- package/out/replicache/src/persist/make-client-id.js.map +1 -1
- package/out/replicache/src/persist/persist.js +3 -2
- package/out/replicache/src/persist/persist.js.map +1 -1
- package/out/replicache/src/persist/refresh.js +8 -5
- package/out/replicache/src/persist/refresh.js.map +1 -1
- package/out/replicache/src/process-scheduler.js.map +1 -1
- package/out/replicache/src/replicache-impl.js +3 -3
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/scan-iterator.js.map +1 -1
- package/out/replicache/src/scan-options.js +7 -6
- package/out/replicache/src/scan-options.js.map +1 -1
- package/out/replicache/src/sqlite.js +1 -2
- package/out/replicache/src/subscriptions.js +1 -1
- package/out/replicache/src/subscriptions.js.map +1 -1
- package/out/replicache/src/sync/diff.js +3 -1
- package/out/replicache/src/sync/diff.js.map +1 -1
- package/out/replicache/src/sync/ids.js.map +1 -1
- package/out/replicache/src/sync/patch.js +2 -4
- package/out/replicache/src/sync/patch.js.map +1 -1
- package/out/replicache/src/sync/pull.js +3 -1
- package/out/replicache/src/sync/pull.js.map +1 -1
- package/out/replicache/src/sync/push.js.map +1 -1
- package/out/replicache/src/sync/request-id.js +1 -1
- package/out/replicache/src/sync/request-id.js.map +1 -1
- package/out/replicache/src/transactions.js +1 -1
- package/out/replicache/src/transactions.js.map +1 -1
- package/out/shared/src/arrays.js +4 -1
- package/out/shared/src/arrays.js.map +1 -1
- package/out/shared/src/asserts.js +1 -1
- package/out/shared/src/bigint-json.d.ts +9 -0
- package/out/shared/src/bigint-json.d.ts.map +1 -1
- package/out/shared/src/bigint-json.js +27 -2
- package/out/shared/src/bigint-json.js.map +1 -1
- package/out/shared/src/broadcast-channel.js +1 -1
- package/out/shared/src/btree-set.js +11 -8
- package/out/shared/src/btree-set.js.map +1 -1
- package/out/shared/src/cache.js.map +1 -1
- package/out/shared/src/config.js +1 -1
- package/out/shared/src/custom-key-map.js.map +1 -1
- package/out/shared/src/custom-key-set.js.map +1 -1
- package/out/shared/src/deep-clone.js +1 -1
- package/out/shared/src/document-visible.js.map +1 -1
- package/out/shared/src/dotenv.js.map +1 -1
- package/out/shared/src/error.js +1 -1
- package/out/shared/src/error.js.map +1 -1
- package/out/shared/src/hash.js +2 -2
- package/out/shared/src/hash.js.map +1 -1
- package/out/shared/src/iterables.js.map +1 -1
- package/out/shared/src/json-schema.js.map +1 -1
- package/out/shared/src/json.js.map +1 -1
- package/out/shared/src/logging-test-utils.js +1 -1
- package/out/shared/src/logging.js +1 -1
- package/out/shared/src/logging.js.map +1 -1
- package/out/shared/src/navigator.js +1 -1
- package/out/shared/src/options.js +9 -12
- package/out/shared/src/options.js.map +1 -1
- package/out/shared/src/promise-race.js.map +1 -1
- package/out/shared/src/queue.js.map +1 -1
- package/out/shared/src/record-proxy.js.map +1 -1
- package/out/shared/src/ring-buffer.js.map +1 -1
- package/out/shared/src/size-of-value.js +7 -10
- package/out/shared/src/size-of-value.js.map +1 -1
- package/out/shared/src/sorted-entries.js.map +1 -1
- package/out/shared/src/tdigest-schema.js.map +1 -1
- package/out/shared/src/tdigest.js.map +1 -1
- package/out/shared/src/valita.js +1 -1
- package/out/shared/src/valita.js.map +1 -1
- package/out/z2s/src/compiler.js +2 -4
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/z2s/src/sql.js +1 -1
- package/out/z2s/src/sql.js.map +1 -1
- package/out/zero/package.js +211 -189
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/adapters/drizzle.js +1 -2
- package/out/zero/src/adapters/kysely.js +1 -2
- package/out/zero/src/adapters/pg.js +1 -2
- package/out/zero/src/adapters/postgresjs.js +1 -2
- package/out/zero/src/adapters/prisma.js +1 -2
- package/out/zero/src/analyze.js +1 -2
- package/out/zero/src/change-protocol/v0.js +1 -2
- package/out/zero/src/expo-sqlite.js +1 -2
- package/out/zero/src/op-sqlite.js +1 -2
- package/out/zero/src/react-native.js.map +1 -1
- package/out/zero/src/sqlite.js +1 -2
- package/out/zero/src/zero-cache-dev.js +10 -9
- package/out/zero/src/zero-cache-dev.js.map +1 -1
- package/out/zero/src/zero-out.js +2 -1
- package/out/zero/src/zero-out.js.map +1 -1
- package/out/zero-cache/src/auth/auth.js +1 -1
- package/out/zero-cache/src/auth/auth.js.map +1 -1
- package/out/zero-cache/src/auth/jwt.js +4 -1
- package/out/zero-cache/src/auth/jwt.js.map +1 -1
- package/out/zero-cache/src/auth/load-permissions.js +1 -1
- package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
- package/out/zero-cache/src/auth/read-authorizer.js +3 -2
- package/out/zero-cache/src/auth/read-authorizer.js.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.js +6 -17
- package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
- package/out/zero-cache/src/config/network.js +1 -1
- package/out/zero-cache/src/config/network.js.map +1 -1
- package/out/zero-cache/src/config/server-context.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +5 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +13 -2
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +1 -1
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
- package/out/zero-cache/src/db/create.js.map +1 -1
- package/out/zero-cache/src/db/index-predicate.js.map +1 -1
- package/out/zero-cache/src/db/lite-tables.d.ts +2 -2
- package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
- package/out/zero-cache/src/db/lite-tables.js.map +1 -1
- package/out/zero-cache/src/db/migration-lite.js +1 -1
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/db/migration.js +5 -3
- package/out/zero-cache/src/db/migration.js.map +1 -1
- package/out/zero-cache/src/db/pg-copy-binary.js +4 -3
- package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -1
- package/out/zero-cache/src/db/pg-copy.js +0 -1
- package/out/zero-cache/src/db/pg-copy.js.map +1 -1
- package/out/zero-cache/src/db/pg-to-lite.js +1 -1
- package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
- package/out/zero-cache/src/db/run-transaction.js.map +1 -1
- package/out/zero-cache/src/db/specs.js +7 -5
- package/out/zero-cache/src/db/specs.js.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
- package/out/zero-cache/src/db/statements.d.ts.map +1 -1
- package/out/zero-cache/src/db/statements.js +9 -2
- package/out/zero-cache/src/db/statements.js.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.js +5 -3
- package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
- package/out/zero-cache/src/db/warmup.js.map +1 -1
- package/out/zero-cache/src/observability/events.js.map +1 -1
- package/out/zero-cache/src/observability/log-throttle.js.map +1 -1
- package/out/zero-cache/src/observability/metrics.js.map +1 -1
- package/out/zero-cache/src/scripts/decommission.js +1 -1
- package/out/zero-cache/src/scripts/deploy-permissions.js +5 -3
- package/out/zero-cache/src/scripts/deploy-permissions.js.map +1 -1
- package/out/zero-cache/src/scripts/permissions.js.map +1 -1
- package/out/zero-cache/src/server/anonymous-otel-start.js +2 -1
- package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +1 -1
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
- package/out/zero-cache/src/server/logging.js +1 -1
- package/out/zero-cache/src/server/logging.js.map +1 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
- package/out/zero-cache/src/server/otel-start.js.map +1 -1
- package/out/zero-cache/src/server/reaper.js +2 -2
- package/out/zero-cache/src/server/reaper.js.map +1 -1
- package/out/zero-cache/src/server/replicator.js +1 -1
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +2 -1
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/runner/runtime.js +2 -1
- package/out/zero-cache/src/server/runner/runtime.js.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
- package/out/zero-cache/src/server/shadow-syncer.js +1 -1
- package/out/zero-cache/src/server/shadow-syncer.js.map +1 -1
- package/out/zero-cache/src/server/slot-keeper.js.map +1 -1
- package/out/zero-cache/src/server/syncer-assigner.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +9 -4
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-dispatcher.js +1 -1
- package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/analyze.js +2 -2
- package/out/zero-cache/src/services/analyze.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +2 -2
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/init-cleanup.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +4 -4
- 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 +4 -4
- 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-metadata.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +4 -4
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js +3 -3
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +6 -8
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/decommission.js +2 -1
- package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +9 -7
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/binary-reader.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +5 -4
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/lsn.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js +3 -3
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +13 -12
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +10 -7
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/validation.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +1 -1
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/control.js.map +1 -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.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/status.js.map +1 -1
- 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 +1 -1
- package/out/zero-cache/src/services/change-source/protocol/mod.js +1 -2
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backfill-state.js +1 -1
- package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js +2 -3
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +3 -3
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +5 -3
- 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.js +21 -21
- 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.js +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/drain-tracker.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.js +11 -10
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js +3 -3
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/replica-poller.js +2 -2
- package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/restore-admission.js +12 -1
- package/out/zero-cache/src/services/change-streamer/restore-admission.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/init.js +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js +2 -2
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +4 -6
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +3 -5
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.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 +3 -3
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +2 -2
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js +0 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -1
- package/out/zero-cache/src/services/heapz.js +2 -1
- package/out/zero-cache/src/services/heapz.js.map +1 -1
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +2 -2
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/limiter/sliding-window-limiter.js +4 -2
- package/out/zero-cache/src/services/limiter/sliding-window-limiter.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +3 -3
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +2 -2
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -1
- package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
- package/out/zero-cache/src/services/litestream/restore-progress.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +3 -2
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/plannerz.js +6 -5
- package/out/zero-cache/src/services/plannerz.js.map +1 -1
- package/out/zero-cache/src/services/profz.js +5 -3
- package/out/zero-cache/src/services/profz.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-db.js +4 -3
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +1 -1
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/drain-handler.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +4 -4
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replication-status.js +4 -3
- package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.js +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/report-schema.js +1 -1
- package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +2 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +19 -7
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/change-log.js +31 -15
- package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.js +10 -7
- package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/table-metadata.js.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js +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 +3 -3
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +1 -1
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/runner.js.map +1 -1
- package/out/zero-cache/src/services/running-state.js.map +1 -1
- package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
- package/out/zero-cache/src/services/statz.js +4 -3
- package/out/zero-cache/src/services/statz.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/active-users-gauge.js +2 -2
- package/out/zero-cache/src/services/view-syncer/active-users-gauge.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/advancement-metrics.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/batch-pacer.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +24 -3
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-schema.d.ts +15 -2
- package/out/zero-cache/src/services/view-syncer/client-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-schema.js +46 -6
- package/out/zero-cache/src/services/view-syncer/client-schema.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-purger.js +2 -2
- package/out/zero-cache/src/services/view-syncer/cvr-purger.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +11 -6
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +12 -4
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +1 -1
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +2 -2
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +6 -4
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +13 -10
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/push-cost.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/push-cutoff.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-stats.js +1 -1
- package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/cvr.js +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.js +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js +7 -1
- package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +8 -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 -6
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/ttl-clock.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 +19 -15
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/error-with-level.js.map +1 -1
- package/out/zero-cache/src/types/lexi-version.js.map +1 -1
- package/out/zero-cache/src/types/lite.js +1 -1
- package/out/zero-cache/src/types/lite.js.map +1 -1
- package/out/zero-cache/src/types/pg-data-type.js +1 -1
- package/out/zero-cache/src/types/pg-data-type.js.map +1 -1
- package/out/zero-cache/src/types/pg.js +6 -4
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-cache/src/types/poke-chunk.js +2 -2
- package/out/zero-cache/src/types/poke-chunk.js.map +1 -1
- package/out/zero-cache/src/types/processes.js +1 -1
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/profiler.js +1 -1
- package/out/zero-cache/src/types/profiler.js.map +1 -1
- package/out/zero-cache/src/types/row-key.js.map +1 -1
- package/out/zero-cache/src/types/sql.js.map +1 -1
- package/out/zero-cache/src/types/streams.js +10 -9
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/strings.js.map +1 -1
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/types/timeout.js +1 -1
- package/out/zero-cache/src/types/websocket-handoff.js +1 -1
- package/out/zero-cache/src/types/ws.js +1 -1
- package/out/zero-cache/src/workers/connection.js +5 -6
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/mutator.js.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +5 -4
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +9 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +4 -4
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/active-clients-manager.js +1 -1
- package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
- package/out/zero-client/src/client/bindings.js +11 -11
- package/out/zero-client/src/client/connection-manager.js.map +1 -1
- package/out/zero-client/src/client/connection.js +1 -1
- package/out/zero-client/src/client/connection.js.map +1 -1
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/crud-impl.js +1 -1
- package/out/zero-client/src/client/crud-impl.js.map +1 -1
- package/out/zero-client/src/client/crud.js +1 -1
- package/out/zero-client/src/client/crud.js.map +1 -1
- package/out/zero-client/src/client/custom.js +1 -1
- package/out/zero-client/src/client/custom.js.map +1 -1
- package/out/zero-client/src/client/delete-clients-manager.js.map +1 -1
- package/out/zero-client/src/client/enable-analytics.js +1 -1
- package/out/zero-client/src/client/enable-analytics.js.map +1 -1
- package/out/zero-client/src/client/error.js.map +1 -1
- package/out/zero-client/src/client/inspector/client-group.js.map +1 -1
- package/out/zero-client/src/client/inspector/client.js.map +1 -1
- package/out/zero-client/src/client/inspector/html-dialog-prompt.js +1 -1
- package/out/zero-client/src/client/inspector/html-dialog-prompt.js.map +1 -1
- package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
- package/out/zero-client/src/client/inspector/query.js.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js +1 -3
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/keys.js +2 -1
- package/out/zero-client/src/client/keys.js.map +1 -1
- package/out/zero-client/src/client/log-options.js.map +1 -1
- package/out/zero-client/src/client/make-replicache-mutators.js +5 -3
- package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
- package/out/zero-client/src/client/metrics.js +3 -5
- package/out/zero-client/src/client/metrics.js.map +1 -1
- package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/query-manager.js +2 -2
- package/out/zero-client/src/client/query-manager.js.map +1 -1
- package/out/zero-client/src/client/reload-error-handler.js +4 -3
- package/out/zero-client/src/client/reload-error-handler.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.js +19 -11
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/mod.js +23 -23
- package/out/zero-client/src/util/nanoid.js.map +1 -1
- package/out/zero-permissions/src/permissions.js +3 -2
- package/out/zero-permissions/src/permissions.js.map +1 -1
- package/out/zero-pg/src/mod.js +9 -9
- package/out/zero-protocol/src/analyze-query-result.js +1 -1
- package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
- package/out/zero-protocol/src/application-error.js.map +1 -1
- package/out/zero-protocol/src/ast.js +19 -9
- package/out/zero-protocol/src/ast.js.map +1 -1
- package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
- package/out/zero-protocol/src/client-schema.js +1 -1
- package/out/zero-protocol/src/client-schema.js.map +1 -1
- package/out/zero-protocol/src/close-connection.js +1 -1
- package/out/zero-protocol/src/close-connection.js.map +1 -1
- package/out/zero-protocol/src/connect.js +1 -1
- package/out/zero-protocol/src/connect.js.map +1 -1
- package/out/zero-protocol/src/custom-queries.js +1 -1
- package/out/zero-protocol/src/custom-queries.js.map +1 -1
- package/out/zero-protocol/src/data.js +4 -2
- package/out/zero-protocol/src/data.js.map +1 -1
- package/out/zero-protocol/src/delete-clients.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +27 -27
- package/out/zero-protocol/src/down.js.map +1 -1
- package/out/zero-protocol/src/error.js.map +1 -1
- package/out/zero-protocol/src/inspect-down.d.ts +81 -81
- package/out/zero-protocol/src/inspect-down.js +1 -1
- package/out/zero-protocol/src/inspect-down.js.map +1 -1
- package/out/zero-protocol/src/inspect-up.js +1 -1
- package/out/zero-protocol/src/inspect-up.js.map +1 -1
- package/out/zero-protocol/src/mutate-server.js +1 -1
- package/out/zero-protocol/src/mutate-server.js.map +1 -1
- package/out/zero-protocol/src/mutation-id.js.map +1 -1
- package/out/zero-protocol/src/mutation.js +1 -1
- package/out/zero-protocol/src/mutation.js.map +1 -1
- package/out/zero-protocol/src/mutations-patch.js +1 -1
- package/out/zero-protocol/src/mutations-patch.js.map +1 -1
- package/out/zero-protocol/src/ping.js +1 -1
- package/out/zero-protocol/src/ping.js.map +1 -1
- package/out/zero-protocol/src/poke.d.ts +6 -6
- package/out/zero-protocol/src/poke.js +1 -1
- package/out/zero-protocol/src/poke.js.map +1 -1
- package/out/zero-protocol/src/pong.js +1 -1
- package/out/zero-protocol/src/pong.js.map +1 -1
- package/out/zero-protocol/src/primary-key.js.map +1 -1
- package/out/zero-protocol/src/protocol-version.js +1 -0
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-protocol/src/pull.js +1 -1
- package/out/zero-protocol/src/pull.js.map +1 -1
- package/out/zero-protocol/src/push.js +1 -1
- package/out/zero-protocol/src/push.js.map +1 -1
- package/out/zero-protocol/src/queries-patch.js +1 -1
- package/out/zero-protocol/src/queries-patch.js.map +1 -1
- package/out/zero-protocol/src/query-hash-visitor.js.map +1 -1
- package/out/zero-protocol/src/query-hash.js +2 -2
- package/out/zero-protocol/src/query-server.js +1 -1
- package/out/zero-protocol/src/query-server.js.map +1 -1
- package/out/zero-protocol/src/row-patch.js.map +1 -1
- package/out/zero-protocol/src/up.d.ts +60 -60
- package/out/zero-protocol/src/up.js.map +1 -1
- package/out/zero-protocol/src/update-auth.js.map +1 -1
- package/out/zero-protocol/src/version.js.map +1 -1
- package/out/zero-react/src/bindings.js +11 -11
- package/out/zero-react/src/mod.js +5 -5
- package/out/zero-react/src/use-query.js +1 -1
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-react/src/zero-provider.d.ts +1 -1
- package/out/zero-react/src/zero-provider.d.ts.map +1 -1
- package/out/zero-react/src/zero-provider.js +2 -1
- package/out/zero-react/src/zero-provider.js.map +1 -1
- package/out/zero-react/src/zero.js +23 -23
- package/out/zero-schema/src/builder/schema-builder.js +3 -2
- package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
- package/out/zero-schema/src/builder/table-builder.js +1 -1
- package/out/zero-schema/src/builder/table-builder.js.map +1 -1
- package/out/zero-schema/src/compiled-permissions.js.map +1 -1
- package/out/zero-schema/src/name-mapper.js +7 -5
- package/out/zero-schema/src/name-mapper.js.map +1 -1
- package/out/zero-schema/src/schema-config.js +1 -1
- package/out/zero-schema/src/schema-config.js.map +1 -1
- package/out/zero-server/src/adapters/drizzle.js.map +1 -1
- package/out/zero-server/src/adapters/kysely.js.map +1 -1
- package/out/zero-server/src/adapters/pg.js.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
- package/out/zero-server/src/adapters/prisma.js.map +1 -1
- package/out/zero-server/src/closable-db-transaction.js.map +1 -1
- package/out/zero-server/src/custom.js +1 -1
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/mod.js +8 -8
- package/out/zero-server/src/process-mutations.js.map +1 -1
- package/out/zero-server/src/push-processor.js.map +1 -1
- package/out/zero-server/src/queries/process-queries.js +2 -1
- package/out/zero-server/src/queries/process-queries.js.map +1 -1
- package/out/zero-server/src/schema.js +7 -7
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-server/src/zql-database.js.map +1 -1
- package/out/zero-solid/src/bindings.js +11 -11
- package/out/zero-solid/src/mod.js +5 -5
- package/out/zero-solid/src/solid-view.js +1 -1
- package/out/zero-solid/src/solid-view.js.map +1 -1
- package/out/zero-solid/src/use-connection-state.js +2 -1
- package/out/zero-solid/src/use-connection-state.js.map +1 -1
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zero-solid/src/use-zero-online.js +2 -1
- package/out/zero-solid/src/use-zero-online.js.map +1 -1
- package/out/zero-solid/src/use-zero.js +3 -2
- package/out/zero-solid/src/use-zero.js.map +1 -1
- package/out/zero-solid/src/zero.js +23 -23
- package/out/zero-types/src/name-mapper.js.map +1 -1
- package/out/zql/src/builder/builder.js +15 -13
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/builder/correlated-predicate-pushdown.js.map +1 -1
- package/out/zql/src/builder/debug-delegate.js.map +1 -1
- package/out/zql/src/builder/like.js +0 -1
- package/out/zql/src/builder/like.js.map +1 -1
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/cap.d.ts +1 -1
- package/out/zql/src/ivm/cap.d.ts.map +1 -1
- package/out/zql/src/ivm/cap.js +10 -0
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/constraint.js +1 -1
- package/out/zql/src/ivm/constraint.js.map +1 -1
- package/out/zql/src/ivm/data.d.ts +15 -1
- package/out/zql/src/ivm/data.d.ts.map +1 -1
- package/out/zql/src/ivm/data.js +15 -1
- package/out/zql/src/ivm/data.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.js.map +1 -1
- package/out/zql/src/ivm/exists.d.ts.map +1 -1
- package/out/zql/src/ivm/exists.js +32 -22
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/fan-in.js.map +1 -1
- package/out/zql/src/ivm/fan-out.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.d.ts +5 -0
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +1 -1
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/filter-push.js.map +1 -1
- package/out/zql/src/ivm/filter.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +1 -3
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/join-index-tap.js.map +1 -1
- package/out/zql/src/ivm/join-index.js.map +1 -1
- package/out/zql/src/ivm/join-utils.js +11 -13
- package/out/zql/src/ivm/join-utils.js.map +1 -1
- package/out/zql/src/ivm/join.d.ts.map +1 -1
- package/out/zql/src/ivm/join.js +6 -3
- package/out/zql/src/ivm/join.js.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
- package/out/zql/src/ivm/memory-source.js +27 -23
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/memory-storage.js.map +1 -1
- package/out/zql/src/ivm/operator.d.ts +6 -0
- package/out/zql/src/ivm/operator.d.ts.map +1 -1
- package/out/zql/src/ivm/operator.js +2 -2
- package/out/zql/src/ivm/operator.js.map +1 -1
- package/out/zql/src/ivm/push-accumulated.js +0 -1
- package/out/zql/src/ivm/push-accumulated.js.map +1 -1
- package/out/zql/src/ivm/skip-yields.js.map +1 -1
- package/out/zql/src/ivm/skip.js.map +1 -1
- package/out/zql/src/ivm/take-gate.js.map +1 -1
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +5 -4
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +1 -1
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/union-fan-out.js.map +1 -1
- package/out/zql/src/ivm/view-apply-change.js +1 -1
- package/out/zql/src/ivm/view-apply-change.js.map +1 -1
- package/out/zql/src/mutate/crud.js +3 -2
- package/out/zql/src/mutate/crud.js.map +1 -1
- package/out/zql/src/planner/planner-builder.js +1 -1
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-debug.js.map +1 -1
- package/out/zql/src/planner/planner-fan-in.js.map +1 -1
- package/out/zql/src/planner/planner-fan-out.js.map +1 -1
- package/out/zql/src/planner/planner-filter.js.map +1 -1
- package/out/zql/src/planner/planner-graph.js +1 -5
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-join.js +1 -1
- package/out/zql/src/planner/planner-join.js.map +1 -1
- package/out/zql/src/planner/planner-source.js.map +1 -1
- package/out/zql/src/planner/planner-terminus.js.map +1 -1
- package/out/zql/src/query/create-builder.js +2 -1
- package/out/zql/src/query/create-builder.js.map +1 -1
- package/out/zql/src/query/expression.js +1 -1
- package/out/zql/src/query/expression.js.map +1 -1
- package/out/zql/src/query/measure-push-operator.js.map +1 -1
- package/out/zql/src/query/named.js +2 -1
- package/out/zql/src/query/named.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +1 -1
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-impl.js +3 -2
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-registry.js +1 -1
- package/out/zql/src/query/query-registry.js.map +1 -1
- package/out/zql/src/query/query-transitions.js +1 -1
- package/out/zql/src/query/query-transitions.js.map +1 -1
- package/out/zql/src/query/runnable-query-impl.js +3 -2
- package/out/zql/src/query/runnable-query-impl.js.map +1 -1
- package/out/zql/src/query/static-query.js.map +1 -1
- package/out/zql/src/query/ttl.js +8 -8
- package/out/zql/src/query/ttl.js.map +1 -1
- package/out/zql/src/query/validate-input.js.map +1 -1
- package/out/zqlite/src/database-storage.d.ts +27 -8
- package/out/zqlite/src/database-storage.d.ts.map +1 -1
- package/out/zqlite/src/database-storage.js +190 -53
- package/out/zqlite/src/database-storage.js.map +1 -1
- package/out/zqlite/src/db.d.ts +47 -1
- package/out/zqlite/src/db.d.ts.map +1 -1
- package/out/zqlite/src/db.js +121 -21
- package/out/zqlite/src/db.js.map +1 -1
- package/out/zqlite/src/explain-queries.js.map +1 -1
- package/out/zqlite/src/fetch-sql.js.map +1 -1
- package/out/zqlite/src/index-usage.js.map +1 -1
- package/out/zqlite/src/internal/sql-inline.js.map +1 -1
- package/out/zqlite/src/internal/sql.js.map +1 -1
- package/out/zqlite/src/internal/statement-cache.d.ts +7 -0
- package/out/zqlite/src/internal/statement-cache.d.ts.map +1 -1
- package/out/zqlite/src/internal/statement-cache.js +11 -4
- package/out/zqlite/src/internal/statement-cache.js.map +1 -1
- package/out/zqlite/src/mod.js +5 -5
- package/out/zqlite/src/pending-delta.js.map +1 -1
- package/out/zqlite/src/query-builder.js +1 -1
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/query-delegate.js.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.js +2 -5
- package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +3 -2
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts +11 -0
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +63 -26
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +37 -36
- package/out/analyze-query/src/bin-analyze.d.ts +0 -2
- package/out/analyze-query/src/bin-analyze.d.ts.map +0 -1
- package/out/analyze-query/src/bin-analyze.js +0 -283
- package/out/analyze-query/src/bin-analyze.js.map +0 -1
- package/out/otel/src/maybe-time.d.ts +0 -3
- package/out/otel/src/maybe-time.d.ts.map +0 -1
- package/out/otel/src/maybe-time.js +0 -15
- package/out/otel/src/maybe-time.js.map +0 -1
- package/out/zero/src/analyze-query.d.ts +0 -3
- package/out/zero/src/analyze-query.d.ts.map +0 -1
- package/out/zero/src/analyze-query.js +0 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud.js","names":[],"sources":["../../../../../zql/src/mutate/crud.ts"],"sourcesContent":["import type {Expand} from '../../../shared/src/expand.ts';\nimport {assignProperty, mapValues} from '../../../shared/src/objects.ts';\nimport {recordProxy} from '../../../shared/src/record-proxy.ts';\nimport type {SchemaValueToTSType} from '../../../zero-types/src/schema-value.ts';\nimport type {Schema, TableSchema} from '../../../zero-types/src/schema.ts';\nimport type {MutateCRUD} from './custom.ts';\n\nexport type SchemaCRUD<S extends Schema> = {\n [Table in keyof S['tables']]: TableCRUD<S['tables'][Table]>;\n};\n\nexport type TransactionMutate<S extends Schema> = SchemaCRUD<S>;\n\nexport type TableCRUD<S extends TableSchema> = {\n /**\n * Writes a row. If a row with the same primary key already exists this is a\n * no-op; the existing row is left unchanged. A collision on any other unique\n * constraint still throws. Non-primary-key fields that are 'optional' can be\n * omitted or set to `undefined`. Such fields will be assigned the value\n * `null` optimistically and then the default value as defined by the server.\n */\n insert: (value: InsertValue<S>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<S>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<S>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<S>) => Promise<void>;\n};\n\nexport type CRUDKind = keyof TableCRUD<TableSchema>;\n\nexport const CRUD_KINDS = ['insert', 'upsert', 'update', 'delete'] as const;\n\nexport type DeleteID<S extends TableSchema> = Expand<PrimaryKeyFields<S>>;\n\ntype PrimaryKeyFields<S extends TableSchema> = {\n [\n K in Extract<S['primaryKey'][number], keyof S['columns']>\n ]: SchemaValueToTSType<S['columns'][K]>;\n};\n\nexport type InsertValue<S extends TableSchema> = Expand<\n PrimaryKeyFields<S> & {\n [\n K in keyof S['columns'] as S['columns'][K] extends {optional: true}\n ? K\n : never\n ]?: SchemaValueToTSType<S['columns'][K]> | undefined;\n } & {\n [\n K in keyof S['columns'] as S['columns'][K] extends {optional: true}\n ? never\n : K\n ]: SchemaValueToTSType<S['columns'][K]>;\n }\n>;\n\nexport type UpsertValue<S extends TableSchema> = InsertValue<S>;\n\nexport type UpdateValue<S extends TableSchema> = Expand<\n PrimaryKeyFields<S> & {\n [K in keyof S['columns']]?:\n | SchemaValueToTSType<S['columns'][K]>\n | undefined;\n }\n>;\n\n/**\n * This is the type of the generated mutate.<name>.<verb> function.\n */\nexport type TableMutator<TS extends TableSchema> = {\n /**\n * Writes a row. If a row with the same primary key already exists this is a\n * no-op; the existing row is left unchanged. A collision on any other unique\n * constraint still throws. Non-primary-key fields that are 'optional' can be\n * omitted or set to `undefined`. Such fields will be assigned the value\n * `null` optimistically and then the default value as defined by the server.\n */\n insert: (value: InsertValue<TS>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<TS>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<TS>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<TS>) => Promise<void>;\n};\n\n/**\n * A function that executes a CRUD operation.\n * Client and server provide different implementations.\n */\nexport type CRUDExecutor = (\n table: string,\n kind: CRUDKind,\n args: unknown,\n) => Promise<void>;\n\n/**\n * Creates a MutateCRUD function from a schema and executor.\n * This is the shared implementation used by both client and server.\n *\n * @param schema - The Zero schema\n * @param executor - A function that executes CRUD operations\n * @returns A MutateCRUD function that can be called with CRUDMutateRequest objects\n */\nexport function makeCRUDMutate<\n TSchema extends Schema,\n TAddSchemaCRUD extends boolean,\n>(\n schema: TSchema,\n addSchemaCRUD: TAddSchemaCRUD,\n executor: CRUDExecutor,\n): MutateCRUD<TSchema, TAddSchemaCRUD> {\n // Create a callable function that accepts CRUDMutateRequest\n const mutate = (request: AnyCRUDMutateRequest) => {\n const {table, kind, args} = request;\n return executor(table, kind, args);\n };\n\n // Only add table properties when enableLegacyMutators is true\n if (addSchemaCRUD) {\n // Add table names as keys so the proxy can discover them\n for (const tableName of Object.keys(schema.tables)) {\n assignProperty(\n mutate as unknown as Record<string, undefined>,\n tableName,\n undefined,\n );\n }\n\n // Wrap in proxy that lazily creates and caches table CRUD objects\n return recordProxy(\n mutate as unknown as Record<string, undefined>,\n (_value, tableName) => makeTableCRUD(tableName, executor),\n ) as unknown as MutateCRUD<TSchema, TAddSchemaCRUD>;\n }\n\n return mutate as MutateCRUD<TSchema, TAddSchemaCRUD>;\n}\n\nexport function makeTransactionMutate<TSchema extends Schema>(\n schema: TSchema,\n executor: CRUDExecutor,\n): TransactionMutate<TSchema> {\n const target = mapValues(schema.tables, () => undefined);\n\n return recordProxy(target, (_value, tableName) =>\n makeTableCRUD(tableName, executor),\n ) as SchemaCRUD<TSchema>;\n}\n\n/**\n * Creates a TableCRUD object that delegates to the executor.\n */\nfunction makeTableCRUD(\n tableName: string,\n executor: CRUDExecutor,\n): TableCRUD<TableSchema> {\n return Object.fromEntries(\n CRUD_KINDS.map(kind => [\n kind,\n (value: unknown) => executor(tableName, kind, value),\n ]),\n ) as TableCRUD<TableSchema>;\n}\n\nexport type CRUDMutator<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'] & string,\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = {\n (args: TArgs): CRUDMutateRequest<TSchema, TTable, TKind, TArgs>;\n\n /**\n * Type-only phantom property to surface mutator types in a covariant position.\n */\n ['~']: Expand<CRUDMutatorTypes<TSchema, TTable, TKind, TArgs>>;\n};\n\nexport type CRUDMutatorTypes<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'] & string,\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = 'CRUDMutator' & CRUDMutateRequest<TSchema, TTable, TKind, TArgs>;\n\nexport type CRUDMutateRequest<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'],\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = {\n readonly schema: TSchema;\n readonly table: TTable;\n readonly kind: TKind;\n readonly args: TArgs;\n};\n\n// oxlint-disable-next-line no-explicit-any\nexport type AnyCRUDMutateRequest = CRUDMutateRequest<any, any, CRUDKind, any>;\n"],"mappings":";;;AA+CA,IAAa,aAAa;CAAC;CAAU;CAAU;CAAU;AAAQ;;;;;;;;;AAyFjE,SAAgB,eAId,QACA,eACA,UACqC;CAErC,MAAM,UAAU,YAAkC;EAChD,MAAM,EAAC,OAAO,MAAM,SAAQ;EAC5B,OAAO,SAAS,OAAO,MAAM,IAAI;CACnC;CAGA,IAAI,eAAe;EAEjB,KAAK,MAAM,aAAa,OAAO,KAAK,OAAO,MAAM,GAC/C,eACE,QACA,WACA,KAAA,CACF;EAIF,OAAO,YACL,SACC,QAAQ,cAAc,cAAc,WAAW,QAAQ,CAC1D;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,sBACd,QACA,UAC4B;
|
|
1
|
+
{"version":3,"file":"crud.js","names":[],"sources":["../../../../../zql/src/mutate/crud.ts"],"sourcesContent":["import type {Expand} from '../../../shared/src/expand.ts';\nimport {assignProperty, mapValues} from '../../../shared/src/objects.ts';\nimport {recordProxy} from '../../../shared/src/record-proxy.ts';\nimport type {SchemaValueToTSType} from '../../../zero-types/src/schema-value.ts';\nimport type {Schema, TableSchema} from '../../../zero-types/src/schema.ts';\nimport type {MutateCRUD} from './custom.ts';\n\nexport type SchemaCRUD<S extends Schema> = {\n [Table in keyof S['tables']]: TableCRUD<S['tables'][Table]>;\n};\n\nexport type TransactionMutate<S extends Schema> = SchemaCRUD<S>;\n\nexport type TableCRUD<S extends TableSchema> = {\n /**\n * Writes a row. If a row with the same primary key already exists this is a\n * no-op; the existing row is left unchanged. A collision on any other unique\n * constraint still throws. Non-primary-key fields that are 'optional' can be\n * omitted or set to `undefined`. Such fields will be assigned the value\n * `null` optimistically and then the default value as defined by the server.\n */\n insert: (value: InsertValue<S>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<S>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<S>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<S>) => Promise<void>;\n};\n\nexport type CRUDKind = keyof TableCRUD<TableSchema>;\n\nexport const CRUD_KINDS = ['insert', 'upsert', 'update', 'delete'] as const;\n\nexport type DeleteID<S extends TableSchema> = Expand<PrimaryKeyFields<S>>;\n\ntype PrimaryKeyFields<S extends TableSchema> = {\n [\n K in Extract<S['primaryKey'][number], keyof S['columns']>\n ]: SchemaValueToTSType<S['columns'][K]>;\n};\n\nexport type InsertValue<S extends TableSchema> = Expand<\n PrimaryKeyFields<S> & {\n [\n K in keyof S['columns'] as S['columns'][K] extends {optional: true}\n ? K\n : never\n ]?: SchemaValueToTSType<S['columns'][K]> | undefined;\n } & {\n [\n K in keyof S['columns'] as S['columns'][K] extends {optional: true}\n ? never\n : K\n ]: SchemaValueToTSType<S['columns'][K]>;\n }\n>;\n\nexport type UpsertValue<S extends TableSchema> = InsertValue<S>;\n\nexport type UpdateValue<S extends TableSchema> = Expand<\n PrimaryKeyFields<S> & {\n [K in keyof S['columns']]?:\n | SchemaValueToTSType<S['columns'][K]>\n | undefined;\n }\n>;\n\n/**\n * This is the type of the generated mutate.<name>.<verb> function.\n */\nexport type TableMutator<TS extends TableSchema> = {\n /**\n * Writes a row. If a row with the same primary key already exists this is a\n * no-op; the existing row is left unchanged. A collision on any other unique\n * constraint still throws. Non-primary-key fields that are 'optional' can be\n * omitted or set to `undefined`. Such fields will be assigned the value\n * `null` optimistically and then the default value as defined by the server.\n */\n insert: (value: InsertValue<TS>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<TS>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<TS>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<TS>) => Promise<void>;\n};\n\n/**\n * A function that executes a CRUD operation.\n * Client and server provide different implementations.\n */\nexport type CRUDExecutor = (\n table: string,\n kind: CRUDKind,\n args: unknown,\n) => Promise<void>;\n\n/**\n * Creates a MutateCRUD function from a schema and executor.\n * This is the shared implementation used by both client and server.\n *\n * @param schema - The Zero schema\n * @param executor - A function that executes CRUD operations\n * @returns A MutateCRUD function that can be called with CRUDMutateRequest objects\n */\nexport function makeCRUDMutate<\n TSchema extends Schema,\n TAddSchemaCRUD extends boolean,\n>(\n schema: TSchema,\n addSchemaCRUD: TAddSchemaCRUD,\n executor: CRUDExecutor,\n): MutateCRUD<TSchema, TAddSchemaCRUD> {\n // Create a callable function that accepts CRUDMutateRequest\n const mutate = (request: AnyCRUDMutateRequest) => {\n const {table, kind, args} = request;\n return executor(table, kind, args);\n };\n\n // Only add table properties when enableLegacyMutators is true\n if (addSchemaCRUD) {\n // Add table names as keys so the proxy can discover them\n for (const tableName of Object.keys(schema.tables)) {\n assignProperty(\n mutate as unknown as Record<string, undefined>,\n tableName,\n undefined,\n );\n }\n\n // Wrap in proxy that lazily creates and caches table CRUD objects\n return recordProxy(\n mutate as unknown as Record<string, undefined>,\n (_value, tableName) => makeTableCRUD(tableName, executor),\n ) as unknown as MutateCRUD<TSchema, TAddSchemaCRUD>;\n }\n\n return mutate as MutateCRUD<TSchema, TAddSchemaCRUD>;\n}\n\nexport function makeTransactionMutate<TSchema extends Schema>(\n schema: TSchema,\n executor: CRUDExecutor,\n): TransactionMutate<TSchema> {\n const target = mapValues(schema.tables, () => undefined);\n\n return recordProxy(target, (_value, tableName) =>\n makeTableCRUD(tableName, executor),\n ) as SchemaCRUD<TSchema>;\n}\n\n/**\n * Creates a TableCRUD object that delegates to the executor.\n */\nfunction makeTableCRUD(\n tableName: string,\n executor: CRUDExecutor,\n): TableCRUD<TableSchema> {\n return Object.fromEntries(\n CRUD_KINDS.map(kind => [\n kind,\n (value: unknown) => executor(tableName, kind, value),\n ]),\n ) as TableCRUD<TableSchema>;\n}\n\nexport type CRUDMutator<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'] & string,\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = {\n (args: TArgs): CRUDMutateRequest<TSchema, TTable, TKind, TArgs>;\n\n /**\n * Type-only phantom property to surface mutator types in a covariant position.\n */\n ['~']: Expand<CRUDMutatorTypes<TSchema, TTable, TKind, TArgs>>;\n};\n\nexport type CRUDMutatorTypes<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'] & string,\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = 'CRUDMutator' & CRUDMutateRequest<TSchema, TTable, TKind, TArgs>;\n\nexport type CRUDMutateRequest<\n TSchema extends Schema,\n TTable extends keyof TSchema['tables'],\n TKind extends keyof TableMutator<TSchema['tables'][TTable]>,\n TArgs extends Parameters<TableMutator<TSchema['tables'][TTable]>[TKind]>[0],\n> = {\n readonly schema: TSchema;\n readonly table: TTable;\n readonly kind: TKind;\n readonly args: TArgs;\n};\n\n// oxlint-disable-next-line no-explicit-any\nexport type AnyCRUDMutateRequest = CRUDMutateRequest<any, any, CRUDKind, any>;\n"],"mappings":";;;AA+CA,IAAa,aAAa;CAAC;CAAU;CAAU;CAAU;AAAQ;;;;;;;;;AAyFjE,SAAgB,eAId,QACA,eACA,UACqC;CAErC,MAAM,UAAU,YAAkC;EAChD,MAAM,EAAC,OAAO,MAAM,SAAQ;EAC5B,OAAO,SAAS,OAAO,MAAM,IAAI;CACnC;CAGA,IAAI,eAAe;EAEjB,KAAK,MAAM,aAAa,OAAO,KAAK,OAAO,MAAM,GAC/C,eACE,QACA,WACA,KAAA,CACF;EAIF,OAAO,YACL,SACC,QAAQ,cAAc,cAAc,WAAW,QAAQ,CAC1D;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,sBACd,QACA,UAC4B;CAC5B,MAAM,SAAS,UAAU,OAAO,cAAc,KAAA,CAAS;CAEvD,OAAO,YAAY,SAAS,QAAQ,cAClC,cAAc,WAAW,QAAQ,CACnC;AACF;;;;AAKA,SAAS,cACP,WACA,UACwB;CACxB,OAAO,OAAO,YACZ,WAAW,KAAI,SAAQ,CACrB,OACC,UAAmB,SAAS,WAAW,MAAM,KAAK,CACrD,CAAC,CACH;AACF"}
|
|
@@ -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';\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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"}
|
|
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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-connection.js","names":["#sort","#filters","#pushed","#unpushedFilters","#model","#baseConstraints","#baseLimit","#constraints","#perBranchFilters","#isRoot","#computeSelectivity","#output","#parentJoin","#clearCostCaches","#cachedConstraintCosts","#filtersFor","#cachedAccesses"],"sources":["../../../../../zql/src/planner/planner-connection.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {NoSubqueryCondition} from '../builder/filter.ts';\nimport {\n mergeConstraints,\n type PlannerConstraint,\n} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport type {PlannerJoin} from './planner-join.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\n\n/**\n * Represents a connection to a source (table scan).\n *\n * # Dual State Pattern\n * Like all planner nodes, PlannerConnection separates:\n * 1. immutable structure: Ordering, filters, cost model (set at construction)\n * 2. mutable state: Pinned status, constraints (mutated during planning)\n *\n * # Cost Estimation\n * The ordering and filters determine the initial cost. As planning progresses,\n * constraints from parent joins refine the cost estimate.\n *\n * # Constraint Flow\n * When a connection is pinned as the outer loop, it reveals constraints for\n * connected joins. These constraints propagate through the graph, allowing\n * other connections to update their cost estimates.\n *\n * Example:\n *\n * ```ts\n * builder.issue.whereExists('assignee', a => a.where('name', 'Alice'))\n * ```\n *\n * ```\n * [issue] [assignee]\n * | |\n * | +-- where name = 'Alice'\n * \\ /\n * \\ /\n * [join]\n * |\n * ```\n *\n * - Initial state: Both connections have no constraints, costs are unconstrained\n * - If `issue` chosen first: Reveals constraint `assignee_id` for assignee connection\n * - If `assignee` chosen first: Reveals constraint `assignee_id` for issue connection\n * - Updated costs guide the next selection\n *\n * # Pushed conditions\n * Correlated predicate pushdown copies a parent's condition on a correlation\n * column into the child (`pushed`). When the parent drives the join into the\n * child, the join binds that column to a parent row, so the parent's\n * condition implies the copy and the copy removes no rows. The connection\n * leaves such a copy out of the cost model call. Another join that binds the\n * column does not imply the copy, because its rows need not hold the parent's\n * condition. The connection also leaves every copy out of `selectivity`: a\n * semi-join always binds the column, and a flipped join gets the copy's\n * reduction through `returnedRows` instead.\n *\n * # Lifecycle\n * 1. Construct with immutable structure (ordering, filters, cost model)\n * 2. Wire to output node during graph construction\n * 3. Planning mutates pinned status and accumulates constraints\n * 4. reset() clears mutable state for replanning\n */\nexport class PlannerConnection {\n readonly kind = 'connection' as const;\n\n // ========================================================================\n // IMMUTABLE STRUCTURE (set during construction, never changes)\n // ========================================================================\n readonly #sort: Ordering;\n readonly #filters: Condition | undefined;\n readonly #pushed: ReadonlySet<SimpleCondition> | undefined;\n /** `#filters` without the pushed conditions. */\n readonly #unpushedFilters: Condition | undefined;\n readonly #model: ConnectionCostModel;\n readonly table: string;\n readonly name: string; // Human-readable name for debugging (defaults to table name)\n readonly #baseConstraints: PlannerConstraint | undefined; // Constraints from parent correlation\n readonly #baseLimit: number | undefined; // Original limit from query structure (never modified)\n readonly selectivity: number; // Fraction of rows passing filters (1.0 = no filtering)\n #output?: PlannerNode | undefined; // Set once during graph construction\n /**\n * The join from the parent into this connection's EXISTS subquery. Set once\n * during graph construction. The pushed conditions are copies of that\n * parent's conditions. Undefined for a root connection, whose parent (if\n * it is a `related` subquery) always drives it.\n */\n #parentJoin: PlannerJoin | undefined;\n\n // ========================================================================\n // MUTABLE PLANNING STATE (changes during plan search)\n // ========================================================================\n /**\n * Current limit during planning. Can be cleared (set to undefined) when a\n * parent join is flipped, indicating this connection is now in an outer loop\n * and should not be limited by EXISTS semantics.\n */\n limit: number | undefined;\n\n /**\n * Constraints accumulated from parent joins during planning.\n * Key is a path through the graph (e.g., \"0,1\" for branch pattern [0,1]).\n *\n * Undefined constraints are possible when a FO converts to UFO and only\n * a single join in the UFO is flipped - other branches report undefined.\n */\n readonly #constraints: Map<string, PlannerConstraint | undefined>;\n\n /**\n * Per-branch filters registered by `PlannerFilter` nodes (simple OR\n * branches) when the enclosing FanIn is in UFI mode. Key is the same\n * `branchPattern.join(',')` used by `#constraints`.\n *\n * These are AND-merged with `#filters` when calling the cost model so\n * the model sees the true filter that the runtime would push to the\n * source for this specific branch.\n */\n readonly #perBranchFilters: Map<string, NoSubqueryCondition> = new Map();\n\n readonly #isRoot: boolean;\n\n /**\n * Cached per-constraint costs to avoid redundant cost model calls.\n * Maps constraint key (branch pattern string) to computed cost.\n * Invalidated when constraints change.\n */\n #cachedConstraintCosts: Map<string, CostEstimate> = new Map();\n\n /**\n * How the source reads rows for each constraint key with a cached cost.\n * Cleared along with {@link #cachedConstraintCosts}.\n */\n #cachedAccesses: Map<string, ConnectionAccess> = new Map();\n\n constructor(\n table: string,\n model: ConnectionCostModel,\n sort: Ordering,\n filters: Condition | undefined,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n limit?: number,\n name?: string,\n pushed?: ReadonlySet<SimpleCondition>,\n ) {\n this.table = table;\n this.name = name ?? table;\n this.#sort = sort;\n this.#filters = filters;\n this.#pushed = pushed;\n this.#unpushedFilters = pushed\n ? withoutConjuncts(filters, c => pushed.has(c))\n : filters;\n this.#model = model;\n this.#baseConstraints = baseConstraints;\n this.#baseLimit = limit;\n this.limit = limit;\n this.#constraints = new Map();\n this.#isRoot = isRoot;\n\n this.selectivity = this.#computeSelectivity(this.#unpushedFilters);\n }\n\n setOutput(node: PlannerNode): void {\n this.#output = node;\n }\n\n get output(): PlannerNode {\n assert(this.#output !== undefined, 'Output not set');\n return this.#output;\n }\n\n setParentJoin(join: PlannerJoin): void {\n this.#parentJoin = join;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return 'connection';\n }\n\n /**\n * Constraints are uniquely identified by their path through the\n * graph.\n *\n * FO represents all sub-joins as a single path.\n * UFO represents each sub-join as a separate path.\n * The first branch in a UFO will match the path of FO so no re-set needs to happen\n * when swapping from FO to UFO.\n *\n * FO swaps to UFO when a join inside FO-FI gets flipped.\n *\n * The max of the last element of the paths is the number of\n * root branches.\n */\n propagateConstraints(\n path: number[],\n c: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n const key = path.join(',');\n this.#constraints.set(key, c);\n // Constraints changed, invalidate cost caches\n this.#clearCostCaches();\n\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'connection',\n node: this.name,\n branchPattern: path,\n constraint: c,\n from: from?.kind ?? 'unknown',\n });\n }\n\n /**\n * Register a per-branch filter for the given branch pattern. Called by\n * `PlannerFilter` (a simple OR branch) when the enclosing FanIn is in\n * UFI mode. The filter is AND-merged with `this.#filters` when the cost\n * model is invoked for this branch.\n */\n setPerBranchFilter(path: number[], filter: NoSubqueryCondition): void {\n this.#perBranchFilters.set(path.join(','), filter);\n // The cost depends on the filter, so invalidate caches.\n this.#clearCostCaches();\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n // Branch pattern specified - return cost for this specific branch\n const key = branchPattern.join(',');\n\n // Check per-constraint cache first\n let cost = this.#cachedConstraintCosts.get(key);\n if (cost !== undefined) {\n return cost;\n }\n\n // Cache miss - compute and cache\n const constraint = this.#constraints.get(key);\n // Merge base constraints with propagated constraints\n const mergedConstraint = mergeConstraints(\n this.#baseConstraints,\n constraint,\n );\n // AND the per-branch filter (registered by a PlannerFilter for a\n // simple OR branch in UFI mode) with the connection-time filter so\n // the cost model sees the same effective filter the runtime applies.\n const perBranchFilter = this.#perBranchFilters.get(key);\n const filters = andFilters(\n this.#filtersFor(mergedConstraint),\n perBranchFilter,\n );\n const {startupCost, fanout, rows, plan} = this.#model(\n this.table,\n this.#sort,\n filters,\n mergedConstraint,\n );\n const selectivity = perBranchFilter\n ? this.#computeSelectivity(\n andFilters(this.#unpushedFilters, perBranchFilter),\n )\n : this.selectivity;\n cost = {\n startupCost,\n scanEst:\n this.limit === undefined\n ? rows\n : Math.min(rows, this.limit / downstreamChildSelectivity),\n cost: 0,\n returnedRows: rows,\n selectivity,\n limit: this.limit,\n fanout,\n };\n this.#cachedConstraintCosts.set(key, cost);\n this.#cachedAccesses.set(key, {\n constraint: mergedConstraint,\n filters,\n rows,\n plan,\n });\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'connection',\n node: this.name,\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(cost),\n filters: this.#filters,\n ordering: this.#sort,\n });\n }\n\n return cost;\n }\n\n /**\n * `#filters` without the pushed conditions that `constraint` binds the\n * column of, when the parent drives the join into this connection. The\n * runtime still applies them, but they remove no rows.\n *\n * When that join is flipped, this connection is the outer loop, so every\n * pushed condition counts, even where another flipped join binds its\n * column.\n */\n #filtersFor(\n constraint: PlannerConstraint | undefined,\n ): Condition | undefined {\n const pushed = this.#pushed;\n if (\n !pushed ||\n !constraint ||\n this.#unpushedFilters === this.#filters ||\n this.#parentJoin?.type === 'flipped'\n ) {\n return this.#filters;\n }\n return withoutConjuncts(\n this.#filters,\n c =>\n pushed.has(c) &&\n c.left.type === 'column' &&\n Object.hasOwn(constraint, c.left.name),\n );\n }\n\n #computeSelectivity(filters: Condition | undefined): number {\n if (this.#baseLimit === undefined || filters === undefined) {\n return 1.0;\n }\n\n const costWithFilters = this.#model(\n this.table,\n this.#sort,\n filters,\n undefined,\n );\n const costWithoutFilters = this.#model(\n this.table,\n this.#sort,\n undefined,\n undefined,\n );\n return costWithoutFilters.rows > 0\n ? costWithFilters.rows / costWithoutFilters.rows\n : 1.0;\n }\n\n /**\n * Remove the limit from this connection.\n * Called when a parent join is flipped, making this connection part of an\n * outer loop that should produce all rows rather than stopping at the limit.\n */\n unlimit(): void {\n if (this.#isRoot) {\n // We cannot unlimit root connections\n return;\n }\n if (this.limit !== undefined) {\n this.limit = undefined;\n // Limit changes do not impact connection costs.\n // Limit is taken into account at the join level.\n // Given that, we do not need to invalidate cost caches here.\n }\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * For connections, we simply remove the limit.\n */\n propagateUnlimitFromFlippedJoin(): void {\n this.unlimit();\n }\n\n reset() {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n this.limit = this.#baseLimit;\n // Clear all cost caches\n this.#clearCostCaches();\n }\n\n #clearCostCaches(): void {\n this.#cachedConstraintCosts.clear();\n this.#cachedAccesses.clear();\n }\n\n /**\n * Capture constraint state for snapshotting.\n * Used by PlannerGraph to save/restore planning state.\n */\n captureConstraints(): Map<string, PlannerConstraint | undefined> {\n return new Map(this.#constraints);\n }\n\n /**\n * Restore constraint state from a snapshot.\n * Used by PlannerGraph to restore planning state.\n *\n * Per-branch filters are not snapshotted — they are re-derived by the\n * subsequent `propagateConstraints` pass. Clearing them here ensures\n * that any entries left over from the previous iteration don't linger\n * in case the restored plan registers a different set.\n */\n restoreConstraints(\n constraints: Map<string, PlannerConstraint | undefined>,\n ): void {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n for (const [key, value] of constraints) {\n this.#constraints.set(key, value);\n }\n // Constraints changed, invalidate cost caches\n this.#clearCostCaches();\n }\n\n get sort(): Ordering {\n return this.#sort;\n }\n\n /**\n * How the source reads rows for the current plan: one access per branch\n * pattern this connection was costed for since its constraints last\n * changed. Estimate the plan's cost first to populate them.\n */\n accesses(): Iterable<ConnectionAccess> {\n return this.#cachedAccesses.values();\n }\n\n /** Get current constraints for debugging. */\n getConstraintsForDebug(): Record<string, PlannerConstraint | undefined> {\n const record: Record<string, PlannerConstraint | undefined> = {};\n for (const [key, value] of this.#constraints) {\n record[key] = value;\n }\n return record;\n }\n\n /** Get filters for debugging. */\n getFiltersForDebug(): Condition | undefined {\n return this.#filters;\n }\n\n /** Get sort/ordering for debugging. */\n getSortForDebug(): Ordering {\n return this.#sort;\n }\n\n /** Get estimated cost for each constraint branch. */\n getConstraintCostsForDebug(): Record<string, CostEstimate> {\n const record: Record<string, CostEstimate> = {};\n for (const [key, value] of this.#cachedConstraintCosts) {\n record[key] = value;\n }\n return record;\n }\n}\n\nfunction andFilters(\n a: Condition | undefined,\n b: Condition | undefined,\n): Condition | undefined {\n return a && b ? {type: 'and', conditions: [a, b]} : (a ?? b);\n}\n\n/**\n * `c` without the simple conjuncts, including those of nested ANDs, for which\n * `remove` returns true. Returns `c` itself when it removes nothing.\n */\nfunction withoutConjuncts(\n c: Condition | undefined,\n remove: (c: SimpleCondition) => boolean,\n): Condition | undefined {\n switch (c?.type) {\n case 'simple':\n return remove(c) ? undefined : c;\n case 'and': {\n const conditions: Condition[] = [];\n for (const x of c.conditions) {\n const y = withoutConjuncts(x, remove);\n if (y !== undefined) {\n conditions.push(y);\n }\n }\n if (\n conditions.length === c.conditions.length &&\n conditions.every((x, i) => x === c.conditions[i])\n ) {\n return c;\n }\n if (conditions.length <= 1) {\n return conditions[0];\n }\n return {type: 'and', conditions};\n }\n default:\n return c;\n }\n}\n\ntype FanoutEst = {\n fanout: number;\n confidence: 'high' | 'med' | 'none';\n};\nexport type FanoutCostModel = (columns: string[]) => FanoutEst;\n\n/**\n * How a source would read the rows of a connection, as far as the cost model\n * can tell. This is used to warn about slow plans, not to choose between\n * them.\n */\nexport type AccessPlan = {\n /**\n * `search` if the source uses an index to find the matching rows. `scan`\n * if it reads the whole table (or a whole index, for its order) and tests\n * each row, stopping early only if the reader stops.\n */\n readonly access: 'search' | 'scan';\n\n /**\n * Whether the rows are sorted after they are read. `full` means that every\n * matching row is read and sorted before the first one is returned, so\n * reading fewer rows (e.g. for a limit) does not help. `partial` means that\n * only runs of rows that tie on a prefix of the ordering are sorted.\n */\n readonly sort: 'none' | 'partial' | 'full';\n\n /** The number of rows in the table, if the cost model knows it. */\n readonly tableRows: number | undefined;\n};\n\nexport type CostModelCost = {\n startupCost: number;\n rows: number;\n fanout: FanoutCostModel;\n /** Omitted by cost models that cannot tell. */\n plan?: AccessPlan | undefined;\n};\n\n/**\n * A read of a connection's source under a particular constraint, as costed\n * by the cost model.\n */\nexport type ConnectionAccess = {\n /** The columns bound to a single value by the time of the read. */\n readonly constraint: PlannerConstraint | undefined;\n readonly filters: Condition | undefined;\n /** The estimated number of rows returned. */\n readonly rows: number;\n readonly plan: AccessPlan | undefined;\n};\nexport type ConnectionCostModel = (\n table: string,\n sort: Ordering,\n filters: Condition | undefined,\n constraint: PlannerConstraint | undefined,\n) => CostModelCost;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner-connection.js","names":[],"sources":["../../../../../zql/src/planner/planner-connection.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {NoSubqueryCondition} from '../builder/filter.ts';\nimport {\n mergeConstraints,\n type PlannerConstraint,\n} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport type {PlannerJoin} from './planner-join.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\n\n/**\n * Represents a connection to a source (table scan).\n *\n * # Dual State Pattern\n * Like all planner nodes, PlannerConnection separates:\n * 1. immutable structure: Ordering, filters, cost model (set at construction)\n * 2. mutable state: Pinned status, constraints (mutated during planning)\n *\n * # Cost Estimation\n * The ordering and filters determine the initial cost. As planning progresses,\n * constraints from parent joins refine the cost estimate.\n *\n * # Constraint Flow\n * When a connection is pinned as the outer loop, it reveals constraints for\n * connected joins. These constraints propagate through the graph, allowing\n * other connections to update their cost estimates.\n *\n * Example:\n *\n * ```ts\n * builder.issue.whereExists('assignee', a => a.where('name', 'Alice'))\n * ```\n *\n * ```\n * [issue] [assignee]\n * | |\n * | +-- where name = 'Alice'\n * \\ /\n * \\ /\n * [join]\n * |\n * ```\n *\n * - Initial state: Both connections have no constraints, costs are unconstrained\n * - If `issue` chosen first: Reveals constraint `assignee_id` for assignee connection\n * - If `assignee` chosen first: Reveals constraint `assignee_id` for issue connection\n * - Updated costs guide the next selection\n *\n * # Pushed conditions\n * Correlated predicate pushdown copies a parent's condition on a correlation\n * column into the child (`pushed`). When the parent drives the join into the\n * child, the join binds that column to a parent row, so the parent's\n * condition implies the copy and the copy removes no rows. The connection\n * leaves such a copy out of the cost model call. Another join that binds the\n * column does not imply the copy, because its rows need not hold the parent's\n * condition. The connection also leaves every copy out of `selectivity`: a\n * semi-join always binds the column, and a flipped join gets the copy's\n * reduction through `returnedRows` instead.\n *\n * # Lifecycle\n * 1. Construct with immutable structure (ordering, filters, cost model)\n * 2. Wire to output node during graph construction\n * 3. Planning mutates pinned status and accumulates constraints\n * 4. reset() clears mutable state for replanning\n */\nexport class PlannerConnection {\n readonly kind = 'connection' as const;\n\n // ========================================================================\n // IMMUTABLE STRUCTURE (set during construction, never changes)\n // ========================================================================\n readonly #sort: Ordering;\n readonly #filters: Condition | undefined;\n readonly #pushed: ReadonlySet<SimpleCondition> | undefined;\n /** `#filters` without the pushed conditions. */\n readonly #unpushedFilters: Condition | undefined;\n readonly #model: ConnectionCostModel;\n readonly table: string;\n readonly name: string; // Human-readable name for debugging (defaults to table name)\n readonly #baseConstraints: PlannerConstraint | undefined; // Constraints from parent correlation\n readonly #baseLimit: number | undefined; // Original limit from query structure (never modified)\n readonly selectivity: number; // Fraction of rows passing filters (1.0 = no filtering)\n #output?: PlannerNode | undefined; // Set once during graph construction\n /**\n * The join from the parent into this connection's EXISTS subquery. Set once\n * during graph construction. The pushed conditions are copies of that\n * parent's conditions. Undefined for a root connection, whose parent (if\n * it is a `related` subquery) always drives it.\n */\n #parentJoin: PlannerJoin | undefined;\n\n // ========================================================================\n // MUTABLE PLANNING STATE (changes during plan search)\n // ========================================================================\n /**\n * Current limit during planning. Can be cleared (set to undefined) when a\n * parent join is flipped, indicating this connection is now in an outer loop\n * and should not be limited by EXISTS semantics.\n */\n limit: number | undefined;\n\n /**\n * Constraints accumulated from parent joins during planning.\n * Key is a path through the graph (e.g., \"0,1\" for branch pattern [0,1]).\n *\n * Undefined constraints are possible when a FO converts to UFO and only\n * a single join in the UFO is flipped - other branches report undefined.\n */\n readonly #constraints: Map<string, PlannerConstraint | undefined>;\n\n /**\n * Per-branch filters registered by `PlannerFilter` nodes (simple OR\n * branches) when the enclosing FanIn is in UFI mode. Key is the same\n * `branchPattern.join(',')` used by `#constraints`.\n *\n * These are AND-merged with `#filters` when calling the cost model so\n * the model sees the true filter that the runtime would push to the\n * source for this specific branch.\n */\n readonly #perBranchFilters: Map<string, NoSubqueryCondition> = new Map();\n\n readonly #isRoot: boolean;\n\n /**\n * Cached per-constraint costs to avoid redundant cost model calls.\n * Maps constraint key (branch pattern string) to computed cost.\n * Invalidated when constraints change.\n */\n #cachedConstraintCosts: Map<string, CostEstimate> = new Map();\n\n /**\n * How the source reads rows for each constraint key with a cached cost.\n * Cleared along with {@link #cachedConstraintCosts}.\n */\n #cachedAccesses: Map<string, ConnectionAccess> = new Map();\n\n constructor(\n table: string,\n model: ConnectionCostModel,\n sort: Ordering,\n filters: Condition | undefined,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n limit?: number,\n name?: string,\n pushed?: ReadonlySet<SimpleCondition>,\n ) {\n this.table = table;\n this.name = name ?? table;\n this.#sort = sort;\n this.#filters = filters;\n this.#pushed = pushed;\n this.#unpushedFilters = pushed\n ? withoutConjuncts(filters, c => pushed.has(c))\n : filters;\n this.#model = model;\n this.#baseConstraints = baseConstraints;\n this.#baseLimit = limit;\n this.limit = limit;\n this.#constraints = new Map();\n this.#isRoot = isRoot;\n\n this.selectivity = this.#computeSelectivity(this.#unpushedFilters);\n }\n\n setOutput(node: PlannerNode): void {\n this.#output = node;\n }\n\n get output(): PlannerNode {\n assert(this.#output !== undefined, 'Output not set');\n return this.#output;\n }\n\n setParentJoin(join: PlannerJoin): void {\n this.#parentJoin = join;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return 'connection';\n }\n\n /**\n * Constraints are uniquely identified by their path through the\n * graph.\n *\n * FO represents all sub-joins as a single path.\n * UFO represents each sub-join as a separate path.\n * The first branch in a UFO will match the path of FO so no re-set needs to happen\n * when swapping from FO to UFO.\n *\n * FO swaps to UFO when a join inside FO-FI gets flipped.\n *\n * The max of the last element of the paths is the number of\n * root branches.\n */\n propagateConstraints(\n path: number[],\n c: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n const key = path.join(',');\n this.#constraints.set(key, c);\n // Constraints changed, invalidate cost caches\n this.#clearCostCaches();\n\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'connection',\n node: this.name,\n branchPattern: path,\n constraint: c,\n from: from?.kind ?? 'unknown',\n });\n }\n\n /**\n * Register a per-branch filter for the given branch pattern. Called by\n * `PlannerFilter` (a simple OR branch) when the enclosing FanIn is in\n * UFI mode. The filter is AND-merged with `this.#filters` when the cost\n * model is invoked for this branch.\n */\n setPerBranchFilter(path: number[], filter: NoSubqueryCondition): void {\n this.#perBranchFilters.set(path.join(','), filter);\n // The cost depends on the filter, so invalidate caches.\n this.#clearCostCaches();\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n // Branch pattern specified - return cost for this specific branch\n const key = branchPattern.join(',');\n\n // Check per-constraint cache first\n let cost = this.#cachedConstraintCosts.get(key);\n if (cost !== undefined) {\n return cost;\n }\n\n // Cache miss - compute and cache\n const constraint = this.#constraints.get(key);\n // Merge base constraints with propagated constraints\n const mergedConstraint = mergeConstraints(\n this.#baseConstraints,\n constraint,\n );\n // AND the per-branch filter (registered by a PlannerFilter for a\n // simple OR branch in UFI mode) with the connection-time filter so\n // the cost model sees the same effective filter the runtime applies.\n const perBranchFilter = this.#perBranchFilters.get(key);\n const filters = andFilters(\n this.#filtersFor(mergedConstraint),\n perBranchFilter,\n );\n const {startupCost, fanout, rows, plan} = this.#model(\n this.table,\n this.#sort,\n filters,\n mergedConstraint,\n );\n const selectivity = perBranchFilter\n ? this.#computeSelectivity(\n andFilters(this.#unpushedFilters, perBranchFilter),\n )\n : this.selectivity;\n cost = {\n startupCost,\n scanEst:\n this.limit === undefined\n ? rows\n : Math.min(rows, this.limit / downstreamChildSelectivity),\n cost: 0,\n returnedRows: rows,\n selectivity,\n limit: this.limit,\n fanout,\n };\n this.#cachedConstraintCosts.set(key, cost);\n this.#cachedAccesses.set(key, {\n constraint: mergedConstraint,\n filters,\n rows,\n plan,\n });\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'connection',\n node: this.name,\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(cost),\n filters: this.#filters,\n ordering: this.#sort,\n });\n }\n\n return cost;\n }\n\n /**\n * `#filters` without the pushed conditions that `constraint` binds the\n * column of, when the parent drives the join into this connection. The\n * runtime still applies them, but they remove no rows.\n *\n * When that join is flipped, this connection is the outer loop, so every\n * pushed condition counts, even where another flipped join binds its\n * column.\n */\n #filtersFor(\n constraint: PlannerConstraint | undefined,\n ): Condition | undefined {\n const pushed = this.#pushed;\n if (\n !pushed ||\n !constraint ||\n this.#unpushedFilters === this.#filters ||\n this.#parentJoin?.type === 'flipped'\n ) {\n return this.#filters;\n }\n return withoutConjuncts(\n this.#filters,\n c =>\n pushed.has(c) &&\n c.left.type === 'column' &&\n Object.hasOwn(constraint, c.left.name),\n );\n }\n\n #computeSelectivity(filters: Condition | undefined): number {\n if (this.#baseLimit === undefined || filters === undefined) {\n return 1.0;\n }\n\n const costWithFilters = this.#model(\n this.table,\n this.#sort,\n filters,\n undefined,\n );\n const costWithoutFilters = this.#model(\n this.table,\n this.#sort,\n undefined,\n undefined,\n );\n return costWithoutFilters.rows > 0\n ? costWithFilters.rows / costWithoutFilters.rows\n : 1.0;\n }\n\n /**\n * Remove the limit from this connection.\n * Called when a parent join is flipped, making this connection part of an\n * outer loop that should produce all rows rather than stopping at the limit.\n */\n unlimit(): void {\n if (this.#isRoot) {\n // We cannot unlimit root connections\n return;\n }\n if (this.limit !== undefined) {\n this.limit = undefined;\n // Limit changes do not impact connection costs.\n // Limit is taken into account at the join level.\n // Given that, we do not need to invalidate cost caches here.\n }\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * For connections, we simply remove the limit.\n */\n propagateUnlimitFromFlippedJoin(): void {\n this.unlimit();\n }\n\n reset() {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n this.limit = this.#baseLimit;\n // Clear all cost caches\n this.#clearCostCaches();\n }\n\n #clearCostCaches(): void {\n this.#cachedConstraintCosts.clear();\n this.#cachedAccesses.clear();\n }\n\n /**\n * Capture constraint state for snapshotting.\n * Used by PlannerGraph to save/restore planning state.\n */\n captureConstraints(): Map<string, PlannerConstraint | undefined> {\n return new Map(this.#constraints);\n }\n\n /**\n * Restore constraint state from a snapshot.\n * Used by PlannerGraph to restore planning state.\n *\n * Per-branch filters are not snapshotted — they are re-derived by the\n * subsequent `propagateConstraints` pass. Clearing them here ensures\n * that any entries left over from the previous iteration don't linger\n * in case the restored plan registers a different set.\n */\n restoreConstraints(\n constraints: Map<string, PlannerConstraint | undefined>,\n ): void {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n for (const [key, value] of constraints) {\n this.#constraints.set(key, value);\n }\n // Constraints changed, invalidate cost caches\n this.#clearCostCaches();\n }\n\n get sort(): Ordering {\n return this.#sort;\n }\n\n /**\n * How the source reads rows for the current plan: one access per branch\n * pattern this connection was costed for since its constraints last\n * changed. Estimate the plan's cost first to populate them.\n */\n accesses(): Iterable<ConnectionAccess> {\n return this.#cachedAccesses.values();\n }\n\n /** Get current constraints for debugging. */\n getConstraintsForDebug(): Record<string, PlannerConstraint | undefined> {\n const record: Record<string, PlannerConstraint | undefined> = {};\n for (const [key, value] of this.#constraints) {\n record[key] = value;\n }\n return record;\n }\n\n /** Get filters for debugging. */\n getFiltersForDebug(): Condition | undefined {\n return this.#filters;\n }\n\n /** Get sort/ordering for debugging. */\n getSortForDebug(): Ordering {\n return this.#sort;\n }\n\n /** Get estimated cost for each constraint branch. */\n getConstraintCostsForDebug(): Record<string, CostEstimate> {\n const record: Record<string, CostEstimate> = {};\n for (const [key, value] of this.#cachedConstraintCosts) {\n record[key] = value;\n }\n return record;\n }\n}\n\nfunction andFilters(\n a: Condition | undefined,\n b: Condition | undefined,\n): Condition | undefined {\n return a && b ? {type: 'and', conditions: [a, b]} : (a ?? b);\n}\n\n/**\n * `c` without the simple conjuncts, including those of nested ANDs, for which\n * `remove` returns true. Returns `c` itself when it removes nothing.\n */\nfunction withoutConjuncts(\n c: Condition | undefined,\n remove: (c: SimpleCondition) => boolean,\n): Condition | undefined {\n switch (c?.type) {\n case 'simple':\n return remove(c) ? undefined : c;\n case 'and': {\n const conditions: Condition[] = [];\n for (const x of c.conditions) {\n const y = withoutConjuncts(x, remove);\n if (y !== undefined) {\n conditions.push(y);\n }\n }\n if (\n conditions.length === c.conditions.length &&\n conditions.every((x, i) => x === c.conditions[i])\n ) {\n return c;\n }\n if (conditions.length <= 1) {\n return conditions[0];\n }\n return {type: 'and', conditions};\n }\n default:\n return c;\n }\n}\n\ntype FanoutEst = {\n fanout: number;\n confidence: 'high' | 'med' | 'none';\n};\nexport type FanoutCostModel = (columns: string[]) => FanoutEst;\n\n/**\n * How a source would read the rows of a connection, as far as the cost model\n * can tell. This is used to warn about slow plans, not to choose between\n * them.\n */\nexport type AccessPlan = {\n /**\n * `search` if the source uses an index to find the matching rows. `scan`\n * if it reads the whole table (or a whole index, for its order) and tests\n * each row, stopping early only if the reader stops.\n */\n readonly access: 'search' | 'scan';\n\n /**\n * Whether the rows are sorted after they are read. `full` means that every\n * matching row is read and sorted before the first one is returned, so\n * reading fewer rows (e.g. for a limit) does not help. `partial` means that\n * only runs of rows that tie on a prefix of the ordering are sorted.\n */\n readonly sort: 'none' | 'partial' | 'full';\n\n /** The number of rows in the table, if the cost model knows it. */\n readonly tableRows: number | undefined;\n};\n\nexport type CostModelCost = {\n startupCost: number;\n rows: number;\n fanout: FanoutCostModel;\n /** Omitted by cost models that cannot tell. */\n plan?: AccessPlan | undefined;\n};\n\n/**\n * A read of a connection's source under a particular constraint, as costed\n * by the cost model.\n */\nexport type ConnectionAccess = {\n /** The columns bound to a single value by the time of the read. */\n readonly constraint: PlannerConstraint | undefined;\n readonly filters: Condition | undefined;\n /** The estimated number of rows returned. */\n readonly rows: number;\n readonly plan: AccessPlan | undefined;\n};\nexport type ConnectionCostModel = (\n table: string,\n sort: Ordering,\n filters: Condition | undefined,\n constraint: PlannerConstraint | undefined,\n) => CostModelCost;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import {newArray} from '../../../shared/src/arrays.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\nimport type * as v from '../../../shared/src/valita.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanState} from './planner-graph.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType:\n | 'connection'\n | 'join'\n | 'fan-out'\n | 'fan-in'\n | 'filter'\n | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n getOrInsertComputed(eventsByAttempt, attempt, newArray).push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import {newArray} from '../../../shared/src/arrays.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\nimport type * as v from '../../../shared/src/valita.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanState} from './planner-graph.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType:\n | 'connection'\n | 'join'\n | 'fan-out'\n | 'fan-in'\n | 'filter'\n | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n getOrInsertComputed(eventsByAttempt, attempt, newArray).push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-fan-in.js","names":[
|
|
1
|
+
{"version":3,"file":"planner-fan-in.js","names":[],"sources":["../../../../../zql/src/planner/planner-fan-in.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\nimport type {PlannerTerminus} from './planner-terminus.ts';\n\n/**\n * A PlannerFanIn node can either be a normal FanIn or UnionFanIn.\n *\n * These have different performance characteristics so we need to distinguish them.\n *\n * A normal FanIn only does a single fetch to FanOut, regardless of how many internal\n * branches / inputs it has.\n *\n * A UnionFanIn does a fetch per internal branch / input. This causes an exponential\n * increase in cost if many UnionFanIns are chained after on another. E.g., `(A or B) AND (C or D)`.\n *\n * To capture this cost blow-up, union fan in assigns different branch patterns to their inputs.\n *\n * Since UFI will generate a unique branch pattern per input, planner-connection will yield a higher cost\n * each time a UFI is present. planner-connection will return the sum of the costs of each unique branch pattern.\n */\nexport class PlannerFanIn {\n readonly kind = 'fan-in' as const;\n #type: 'FI' | 'UFI';\n #output?: PlannerNode | undefined;\n readonly #inputs: Exclude<PlannerNode, PlannerTerminus>[];\n\n constructor(inputs: Exclude<PlannerNode, PlannerTerminus>[]) {\n this.#type = 'FI';\n this.#inputs = inputs;\n }\n\n get type() {\n return this.#type;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return 'join';\n }\n\n setOutput(node: PlannerNode): void {\n this.#output = node;\n }\n\n get output(): PlannerNode {\n assert(this.#output !== undefined, 'Output not set');\n return this.#output;\n }\n\n reset() {\n this.#type = 'FI';\n }\n\n convertToUFI(): void {\n this.#type = 'UFI';\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * Fan-in propagates to all of its inputs.\n */\n propagateUnlimitFromFlippedJoin(): void {\n for (const input of this.#inputs) {\n if (\n 'propagateUnlimitFromFlippedJoin' in input &&\n typeof input.propagateUnlimitFromFlippedJoin === 'function'\n ) {\n (\n input as {propagateUnlimitFromFlippedJoin(): void}\n ).propagateUnlimitFromFlippedJoin();\n }\n }\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n // FanIn always sums costs of its inputs\n // But it needs to pass the correct branch pattern to each input\n let totalCost: CostEstimate = {\n returnedRows: 0,\n cost: 0,\n scanEst: 0,\n startupCost: 0,\n selectivity: 0,\n limit: undefined,\n fanout: () => {\n throw new Error('Failed to set fanout model');\n },\n };\n\n if (this.#type === 'FI') {\n // Normal FanIn: all inputs get the same branch pattern with 0 prepended\n const updatedPattern = [0, ...branchPattern];\n let maxrows = 0;\n let maxRunningCost = 0;\n let maxStartupCost = 0;\n let maxScanEst = 0;\n\n let noMatchProb = 1.0;\n for (const input of this.#inputs) {\n const cost = input.estimateCost(\n downstreamChildSelectivity,\n updatedPattern,\n planDebugger,\n );\n totalCost.fanout = cost.fanout;\n if (cost.returnedRows > maxrows) {\n maxrows = cost.returnedRows;\n }\n if (cost.cost > maxRunningCost) {\n maxRunningCost = cost.cost;\n }\n if (cost.startupCost > maxStartupCost) {\n maxStartupCost = cost.startupCost;\n }\n if (cost.scanEst > maxScanEst) {\n maxScanEst = cost.scanEst;\n }\n\n // OR branches: combine selectivities assuming independent events\n // P(A OR B) = 1 - (1-A)(1-B)\n // Track probability of NO match in any branch\n noMatchProb *= 1 - cost.selectivity;\n\n // all inputs should have the same limit.\n assert(\n totalCost.limit === undefined || cost.limit === totalCost.limit,\n 'All FanIn inputs should have the same limit',\n );\n totalCost.limit = cost.limit;\n }\n\n totalCost.returnedRows = maxrows;\n totalCost.cost = maxRunningCost;\n totalCost.selectivity = 1 - noMatchProb;\n totalCost.startupCost = maxStartupCost;\n totalCost.scanEst = maxScanEst;\n } else {\n // Union FanIn (UFI): each input gets unique branch pattern\n let i = 0;\n\n let noMatchProb = 1.0;\n for (const input of this.#inputs) {\n const updatedPattern = [i, ...branchPattern];\n const cost = input.estimateCost(\n downstreamChildSelectivity,\n updatedPattern,\n planDebugger,\n );\n totalCost.fanout = cost.fanout;\n totalCost.returnedRows += cost.returnedRows;\n totalCost.cost += cost.cost;\n totalCost.scanEst += cost.scanEst;\n totalCost.startupCost = totalCost.startupCost + cost.startupCost;\n\n // OR branches: combine selectivities assuming independent events\n // P(A OR B) = 1 - (1-A)(1-B)\n // Track probability of NO match in any branch\n noMatchProb *= 1 - cost.selectivity;\n\n // all inputs should have the same limit.\n assert(\n totalCost.limit === undefined || cost.limit === totalCost.limit,\n 'All FanIn inputs should have the same limit',\n );\n totalCost.limit = cost.limit;\n i++;\n }\n totalCost.selectivity = 1 - noMatchProb;\n }\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'fan-in',\n node: this.#type,\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(totalCost),\n });\n }\n\n return totalCost;\n }\n\n propagateConstraints(\n branchPattern: number[],\n constraint: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'fan-in',\n node: this.#type,\n branchPattern,\n constraint,\n from: from?.kind ?? 'unknown',\n });\n\n if (this.#type === 'FI') {\n const updatedPattern = [0, ...branchPattern];\n /**\n * All inputs get the same branch pattern.\n * 1. They cannot contribute differing constraints to their parent inputs because they are not flipped.\n * If they were flipped this would be of type UFI.\n * 2. All inputs need to be called because they could be pinned. If they are pinned they could have constraints\n * to send to their children.\n */\n for (const input of this.#inputs) {\n input.propagateConstraints(\n updatedPattern,\n constraint,\n this,\n planDebugger,\n );\n }\n return;\n }\n\n let i = 0;\n for (const input of this.#inputs) {\n input.propagateConstraints(\n [i, ...branchPattern],\n constraint,\n this,\n planDebugger,\n );\n i++;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-fan-out.js","names":[
|
|
1
|
+
{"version":3,"file":"planner-fan-out.js","names":[],"sources":["../../../../../zql/src/planner/planner-fan-out.ts"],"sourcesContent":["import type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\nimport type {PlannerTerminus} from './planner-terminus.ts';\n\nexport class PlannerFanOut {\n readonly kind = 'fan-out' as const;\n #type: 'FO' | 'UFO';\n readonly #outputs: PlannerNode[] = [];\n readonly #input: Exclude<PlannerNode, PlannerTerminus>;\n\n constructor(input: Exclude<PlannerNode, PlannerTerminus>) {\n this.#type = 'FO';\n this.#input = input;\n }\n\n get type() {\n return this.#type;\n }\n\n addOutput(node: PlannerNode): void {\n this.#outputs.push(node);\n }\n\n get outputs(): PlannerNode[] {\n return this.#outputs;\n }\n\n get input(): Exclude<PlannerNode, PlannerTerminus> {\n return this.#input;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return this.#input.closestJoinOrSource();\n }\n\n propagateConstraints(\n branchPattern: number[],\n constraint: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'fan-out',\n node: 'FO',\n branchPattern,\n constraint,\n from: from?.kind ?? 'unknown',\n });\n\n this.#input.propagateConstraints(\n branchPattern,\n constraint,\n this,\n planDebugger,\n );\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n const ret = this.#input.estimateCost(\n downstreamChildSelectivity,\n branchPattern,\n planDebugger,\n );\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'fan-out',\n node: 'FO',\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(ret),\n });\n }\n\n return ret;\n }\n\n convertToUFO(): void {\n this.#type = 'UFO';\n }\n\n reset(): void {\n this.#type = 'FO';\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * Fan-out propagates to its input.\n */\n propagateUnlimitFromFlippedJoin(): void {\n if (\n 'propagateUnlimitFromFlippedJoin' in this.#input &&\n typeof this.#input.propagateUnlimitFromFlippedJoin === 'function'\n ) {\n (\n this.#input as {propagateUnlimitFromFlippedJoin(): void}\n ).propagateUnlimitFromFlippedJoin();\n }\n }\n}\n"],"mappings":";;AAUA,IAAa,gBAAb,MAA2B;CACzB,OAAgB;CAChB;CACA,WAAmC,CAAC;CACpC;CAEA,YAAY,OAA8C;EACxD,KAAK,QAAQ;EACb,KAAK,SAAS;CAChB;CAEA,IAAI,OAAO;EACT,OAAO,KAAK;CACd;CAEA,UAAU,MAAyB;EACjC,KAAK,SAAS,KAAK,IAAI;CACzB;CAEA,IAAI,UAAyB;EAC3B,OAAO,KAAK;CACd;CAEA,IAAI,QAA+C;EACjD,OAAO,KAAK;CACd;CAEA,sBAAwC;EACtC,OAAO,KAAK,OAAO,oBAAoB;CACzC;CAEA,qBACE,eACA,YACA,MACA,cACM;EACN,cAAc,IAAI;GAChB,MAAM;GACN,UAAU;GACV,MAAM;GACN;GACA;GACA,MAAM,MAAM,QAAQ;EACtB,CAAC;EAED,KAAK,OAAO,qBACV,eACA,YACA,MACA,YACF;CACF;CAEA,aACE,4BACA,eACA,cACc;EACd,MAAM,MAAM,KAAK,OAAO,aACtB,4BACA,eACA,YACF;EAEA,IAAI,cACF,aAAa,IAAI;GACf,MAAM;GACN,UAAU;GACV,MAAM;GACN;GACA;GACA,cAAc,WAAW,GAAG;EAC9B,CAAC;EAGH,OAAO;CACT;CAEA,eAAqB;EACnB,KAAK,QAAQ;CACf;CAEA,QAAc;EACZ,KAAK,QAAQ;CACf;;;;;CAMA,kCAAwC;EACtC,IACE,qCAAqC,KAAK,UAC1C,OAAO,KAAK,OAAO,oCAAoC,YAEvD,KACO,OACL,gCAAgC;CAEtC;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-filter.js","names":[
|
|
1
|
+
{"version":3,"file":"planner-filter.js","names":[],"sources":["../../../../../zql/src/planner/planner-filter.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {NoSubqueryCondition} from '../builder/filter.ts';\nimport type {PlannerConnection} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\nimport type {PlannerTerminus} from './planner-terminus.ts';\n\n/**\n * Represents a simple (non-CSQ) branch of an OR in the planner graph.\n *\n * # Why this node exists\n * Without `PlannerFilter`, the planner would not see simple branches of an\n * OR at all — `processOr` historically only admitted subquery-bearing\n * branches into the graph. That meant the cost model never got to weigh\n * the per-branch filter that the runtime actually pushes down via\n * `FetchRequest.filter` for UFI/UFO branches.\n *\n * # What it does\n * - Stores the simple branch's `NoSubqueryCondition`.\n * - Forwards `propagateConstraints` to its input (typically a `PlannerFanOut`\n * leading back to a `PlannerConnection`).\n * - When the enclosing FanIn is in `UFI` mode, registers the condition at\n * the receiving `PlannerConnection` under the current `branchPattern` via\n * `connection.setPerBranchFilter(...)`. This lets the cost model see the\n * true per-branch filter at fetch time.\n * - In `FI` mode, registration is skipped — at runtime, FI/FO uses a\n * single shared scan of the source for all branches, so per-branch filters\n * would mis-credit the source scan cost.\n *\n * # Cost\n * Pass-through. The filter node itself adds no execution cost; the only\n * effect is at the connection layer (lower estimated rows).\n *\n * # Planner/runtime branch-count mismatch\n * For OR with multiple simple branches and a flipped CSQ (e.g.\n * `or(a=1, b=2, exists(...))`), the planner builds one `PlannerFilter` per\n * simple branch, but at runtime `applyFilterWithFlips` bundles all\n * non-flipped branches into a single source fetch with `(a=1 OR b=2)` —\n * one source scan, not N. In UFI mode the planner therefore models N\n * independent source scans against the runtime's 1, slightly inflating\n * the cost of OR-with-many-simples shapes. Common `or(simple, exists)`\n * shapes have only one simple branch and are unaffected.\n */\nexport class PlannerFilter {\n readonly kind = 'filter' as const;\n readonly #input: Exclude<PlannerNode, PlannerTerminus>;\n readonly #condition: NoSubqueryCondition | undefined;\n #output?: PlannerNode | undefined;\n\n constructor(\n input: Exclude<PlannerNode, PlannerTerminus>,\n condition: NoSubqueryCondition | undefined,\n ) {\n this.#input = input;\n this.#condition = condition;\n }\n\n setOutput(node: PlannerNode): void {\n this.#output = node;\n }\n\n get output(): PlannerNode {\n assert(this.#output !== undefined, 'Output not set');\n return this.#output;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return this.#input.closestJoinOrSource();\n }\n\n propagateConstraints(\n branchPattern: number[],\n constraint: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'filter',\n node: 'filter',\n branchPattern,\n constraint,\n from: from?.kind ?? 'unknown',\n });\n\n // Only register the per-branch filter when we're actually going to do\n // a per-branch fetch at runtime — i.e., when our enclosing FanIn is in\n // UFI mode. In FI mode, all branches share a single source scan, so\n // applying our filter at the source would drop rows that other\n // branches need.\n if (this.#condition && from?.kind === 'fan-in' && from.type === 'UFI') {\n const conn = findParentConnection(this.#input);\n if (conn) {\n conn.setPerBranchFilter(branchPattern, this.#condition);\n }\n }\n\n this.#input.propagateConstraints(\n branchPattern,\n constraint,\n this,\n planDebugger,\n );\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n const cost = this.#input.estimateCost(\n downstreamChildSelectivity,\n branchPattern,\n planDebugger,\n );\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'filter',\n node: 'filter',\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(cost),\n });\n }\n\n return cost;\n }\n\n /**\n * Forward unlimit propagation along the parent chain (toward the source).\n */\n propagateUnlimitFromFlippedJoin(): void {\n if (\n 'propagateUnlimitFromFlippedJoin' in this.#input &&\n typeof this.#input.propagateUnlimitFromFlippedJoin === 'function'\n ) {\n (\n this.#input as {propagateUnlimitFromFlippedJoin(): void}\n ).propagateUnlimitFromFlippedJoin();\n }\n }\n\n reset(): void {\n // No mutable structural state. Per-branch filters are re-registered on\n // every propagation pass, and PlannerConnection clears its\n // #perBranchFilters in its own reset().\n }\n\n /** For debugging. */\n getConditionForDebug(): NoSubqueryCondition | undefined {\n return this.#condition;\n }\n}\n\n/**\n * Walk the input chain (toward the source) looking for the first\n * `PlannerConnection`. The simple branch's filter is meant to land on the\n * connection that the FanOut wraps — for nested AST shapes, that may be\n * several joins above.\n */\nfunction findParentConnection(\n node: Exclude<PlannerNode, PlannerTerminus>,\n): PlannerConnection | undefined {\n switch (node.kind) {\n case 'connection':\n return node;\n case 'fan-out':\n return findParentConnection(node.input);\n case 'join':\n return findParentConnection(node.parent);\n case 'fan-in':\n // Reaching a FanIn while walking up means the simple branch sits\n // above a nested OR/UFI. The connection that matters for *this*\n // simple branch's filter is the one feeding the outer FanOut, but\n // from a nested FanIn we don't have a single input to walk through.\n // Be conservative: don't register, accept that deeply nested OR\n // shapes lose this optimization.\n return undefined;\n case 'filter':\n // Shouldn't occur in graphs built by `processOr` — a PlannerFilter's\n // input is always the FanOut it's a branch of, never another filter.\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,IAAa,gBAAb,MAA2B;CACzB,OAAgB;CAChB;CACA;CACA;CAEA,YACE,OACA,WACA;EACA,KAAK,SAAS;EACd,KAAK,aAAa;CACpB;CAEA,UAAU,MAAyB;EACjC,KAAK,UAAU;CACjB;CAEA,IAAI,SAAsB;EACxB,OAAO,KAAK,YAAY,KAAA,GAAW,gBAAgB;EACnD,OAAO,KAAK;CACd;CAEA,sBAAwC;EACtC,OAAO,KAAK,OAAO,oBAAoB;CACzC;CAEA,qBACE,eACA,YACA,MACA,cACM;EACN,cAAc,IAAI;GAChB,MAAM;GACN,UAAU;GACV,MAAM;GACN;GACA;GACA,MAAM,MAAM,QAAQ;EACtB,CAAC;EAOD,IAAI,KAAK,cAAc,MAAM,SAAS,YAAY,KAAK,SAAS,OAAO;GACrE,MAAM,OAAO,qBAAqB,KAAK,MAAM;GAC7C,IAAI,MACF,KAAK,mBAAmB,eAAe,KAAK,UAAU;EAE1D;EAEA,KAAK,OAAO,qBACV,eACA,YACA,MACA,YACF;CACF;CAEA,aACE,4BACA,eACA,cACc;EACd,MAAM,OAAO,KAAK,OAAO,aACvB,4BACA,eACA,YACF;EAEA,IAAI,cACF,aAAa,IAAI;GACf,MAAM;GACN,UAAU;GACV,MAAM;GACN;GACA;GACA,cAAc,WAAW,IAAI;EAC/B,CAAC;EAGH,OAAO;CACT;;;;CAKA,kCAAwC;EACtC,IACE,qCAAqC,KAAK,UAC1C,OAAO,KAAK,OAAO,oCAAoC,YAEvD,KACO,OACL,gCAAgC;CAEtC;CAEA,QAAc,CAId;;CAGA,uBAAwD;EACtD,OAAO,KAAK;CACd;AACF;;;;;;;AAQA,SAAS,qBACP,MAC+B;CAC/B,QAAQ,KAAK,MAAb;EACE,KAAK,cACH,OAAO;EACT,KAAK,WACH,OAAO,qBAAqB,KAAK,KAAK;EACxC,KAAK,QACH,OAAO,qBAAqB,KAAK,MAAM;EACzC,KAAK,UAOH;EACF,KAAK,UAGH;CACJ;AACF"}
|
|
@@ -302,11 +302,7 @@ function findFIAndJoins(fo) {
|
|
|
302
302
|
case "fan-out":
|
|
303
303
|
queue.push(...node.outputs);
|
|
304
304
|
break;
|
|
305
|
-
case "fan-in":
|
|
306
|
-
fi = node;
|
|
307
|
-
break;
|
|
308
|
-
case "connection": break;
|
|
309
|
-
case "terminus": break;
|
|
305
|
+
case "fan-in": fi = node;
|
|
310
306
|
}
|
|
311
307
|
}
|
|
312
308
|
return {
|