@rocicorp/zero 1.3.0 → 1.4.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/analyze-query/src/analyze-cli.d.ts +24 -0
- package/out/analyze-query/src/analyze-cli.d.ts.map +1 -0
- package/out/analyze-query/src/analyze-cli.js +279 -0
- package/out/analyze-query/src/analyze-cli.js.map +1 -0
- package/out/analyze-query/src/bin-analyze.js +6 -6
- package/out/analyze-query/src/bin-transform.js +2 -2
- package/out/ast-to-zql/src/bin.js +1 -1
- package/out/shared/src/logging.d.ts.map +1 -1
- package/out/shared/src/logging.js +1 -1
- package/out/shared/src/logging.js.map +1 -1
- package/out/shared/src/options.d.ts.map +1 -1
- package/out/shared/src/options.js +1 -1
- package/out/shared/src/options.js.map +1 -1
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +4 -1
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/z2s/src/sql.d.ts.map +1 -1
- package/out/z2s/src/sql.js +1 -0
- package/out/z2s/src/sql.js.map +1 -1
- package/out/zero/package.js +95 -89
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/analyze.d.ts +2 -0
- package/out/zero/src/analyze.d.ts.map +1 -0
- package/out/zero/src/analyze.js +2 -0
- package/out/zero/src/zero-cache-dev.js +1 -1
- package/out/zero/src/zero-cache-dev.js.map +1 -1
- package/out/zero/src/zero-out.js +1 -1
- package/out/zero-cache/src/auth/auth.d.ts.map +1 -1
- package/out/zero-cache/src/auth/auth.js.map +1 -1
- package/out/zero-cache/src/auth/load-permissions.js +2 -2
- package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.js +5 -14
- package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
- package/out/zero-cache/src/config/network.d.ts +1 -1
- package/out/zero-cache/src/config/network.d.ts.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/normalize.d.ts.map +1 -1
- package/out/zero-cache/src/config/normalize.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 +16 -3
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
- package/out/zero-cache/src/db/lite-tables.js +3 -3
- package/out/zero-cache/src/db/lite-tables.js.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.d.ts +43 -40
- package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.js +76 -56
- package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
- package/out/zero-cache/src/observability/events.d.ts.map +1 -1
- package/out/zero-cache/src/observability/events.js +1 -1
- package/out/zero-cache/src/observability/events.js.map +1 -1
- package/out/zero-cache/src/scripts/decommission.js +1 -1
- package/out/zero-cache/src/scripts/deploy-permissions.js +2 -2
- package/out/zero-cache/src/scripts/permissions.js +1 -1
- package/out/zero-cache/src/server/anonymous-otel-start.d.ts.map +1 -1
- package/out/zero-cache/src/server/anonymous-otel-start.js +4 -4
- package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +27 -12
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/logging.d.ts +1 -3
- package/out/zero-cache/src/server/logging.d.ts.map +1 -1
- package/out/zero-cache/src/server/logging.js +6 -3
- package/out/zero-cache/src/server/logging.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +26 -26
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/mutator.js +4 -2
- package/out/zero-cache/src/server/mutator.js.map +1 -1
- package/out/zero-cache/src/server/otel-log-sink.d.ts.map +1 -1
- package/out/zero-cache/src/server/otel-log-sink.js +0 -2
- package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
- package/out/zero-cache/src/server/otel-start.d.ts +1 -1
- package/out/zero-cache/src/server/otel-start.d.ts.map +1 -1
- package/out/zero-cache/src/server/otel-start.js +7 -3
- package/out/zero-cache/src/server/otel-start.js.map +1 -1
- package/out/zero-cache/src/server/reaper.js +6 -6
- package/out/zero-cache/src/server/reaper.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +5 -3
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +2 -2
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +13 -12
- 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/services/analyze.js +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +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-stream.js +4 -1
- 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.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +19 -23
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +58 -3
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +209 -52
- 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/stream.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +24 -15
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +35 -58
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +1 -2
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.js +15 -18
- package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +4 -4
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +5 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +10 -7
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/replica-monitor.js +2 -2
- package/out/zero-cache/src/services/change-streamer/storer.d.ts +19 -2
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +70 -6
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/heapz.d.ts.map +1 -1
- package/out/zero-cache/src/services/heapz.js +1 -1
- package/out/zero-cache/src/services/heapz.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts +2 -1
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +10 -7
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts +15 -4
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +40 -34
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +3 -3
- package/out/zero-cache/src/services/mutagen/pusher.d.ts +28 -28
- package/out/zero-cache/src/services/replicator/change-processor.js +2 -2
- package/out/zero-cache/src/services/replicator/incremental-sync.js +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.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.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +2 -2
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/statz.d.ts.map +1 -1
- package/out/zero-cache/src/services/statz.js +3 -3
- package/out/zero-cache/src/services/statz.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/active-users-gauge.js +1 -1
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +2 -2
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.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 +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +3 -3
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +2 -2
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +6 -16
- 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 +30 -38
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js +4 -4
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +2 -2
- 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 +6 -6
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
- package/out/zero-cache/src/types/profiler.js.map +1 -1
- package/out/zero-cache/src/types/row-key.d.ts.map +1 -1
- package/out/zero-cache/src/types/row-key.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +1 -1
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/websocket-handoff.d.ts +1 -1
- package/out/zero-cache/src/types/websocket-handoff.d.ts.map +1 -1
- package/out/zero-cache/src/types/websocket-handoff.js +1 -1
- package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts +1 -1
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +2 -2
- 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/syncer.d.ts +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +3 -3
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/crud-impl.d.ts.map +1 -1
- package/out/zero-client/src/client/crud-impl.js +4 -13
- package/out/zero-client/src/client/crud-impl.js.map +1 -1
- package/out/zero-client/src/client/inspector/inspector.d.ts +24 -0
- package/out/zero-client/src/client/inspector/inspector.d.ts.map +1 -1
- package/out/zero-client/src/client/inspector/inspector.js +28 -0
- package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.d.ts +9 -0
- package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js +28 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
- package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js +4 -13
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-protocol/src/error.d.ts.map +1 -1
- package/out/zero-protocol/src/error.js +1 -1
- package/out/zero-protocol/src/error.js.map +1 -1
- package/out/zero-solid/src/solid-view.d.ts.map +1 -1
- package/out/zero-solid/src/solid-view.js +13 -13
- package/out/zero-solid/src/solid-view.js.map +1 -1
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +26 -1
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/change-index-enum.d.ts +9 -0
- package/out/zql/src/ivm/change-index-enum.d.ts.map +1 -0
- package/out/zql/src/ivm/change-index.d.ts +5 -0
- package/out/zql/src/ivm/change-index.d.ts.map +1 -0
- package/out/zql/src/ivm/change-type-enum.d.ts +9 -0
- package/out/zql/src/ivm/change-type-enum.d.ts.map +1 -0
- package/out/zql/src/ivm/change-type.d.ts +5 -0
- package/out/zql/src/ivm/change-type.d.ts.map +1 -0
- package/out/zql/src/ivm/change.d.ts +20 -22
- package/out/zql/src/ivm/change.d.ts.map +1 -1
- package/out/zql/src/ivm/change.js +33 -0
- package/out/zql/src/ivm/change.js.map +1 -0
- package/out/zql/src/ivm/exists.d.ts.map +1 -1
- package/out/zql/src/ivm/exists.js +27 -38
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/fan-in.d.ts +3 -2
- package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-in.js.map +1 -1
- package/out/zql/src/ivm/fan-out.d.ts +1 -1
- package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-out.js +1 -1
- package/out/zql/src/ivm/fan-out.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.d.ts +3 -3
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/filter-push.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-push.js +7 -7
- package/out/zql/src/ivm/filter-push.js.map +1 -1
- package/out/zql/src/ivm/filter.d.ts +1 -1
- package/out/zql/src/ivm/filter.d.ts.map +1 -1
- package/out/zql/src/ivm/filter.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +49 -58
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/join-utils.d.ts +2 -6
- package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
- package/out/zql/src/ivm/join-utils.js +25 -25
- 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 +32 -51
- package/out/zql/src/ivm/join.js.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +5 -10
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +51 -59
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/push-accumulated.d.ts +3 -2
- package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
- package/out/zql/src/ivm/push-accumulated.js +98 -122
- package/out/zql/src/ivm/push-accumulated.js.map +1 -1
- package/out/zql/src/ivm/skip.d.ts +1 -1
- package/out/zql/src/ivm/skip.d.ts.map +1 -1
- package/out/zql/src/ivm/skip.js +2 -2
- package/out/zql/src/ivm/skip.js.map +1 -1
- package/out/zql/src/ivm/source-change-index-enum.d.ts +7 -0
- package/out/zql/src/ivm/source-change-index-enum.d.ts.map +1 -0
- package/out/zql/src/ivm/source-change-index.d.ts +5 -0
- package/out/zql/src/ivm/source-change-index.d.ts.map +1 -0
- package/out/zql/src/ivm/source.d.ts +11 -13
- package/out/zql/src/ivm/source.d.ts.map +1 -1
- package/out/zql/src/ivm/source.js +26 -0
- package/out/zql/src/ivm/source.js.map +1 -0
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +27 -50
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts +2 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +3 -3
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/union-fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-out.js +1 -1
- package/out/zql/src/ivm/union-fan-out.js.map +1 -1
- package/out/zql/src/planner/planner-debug.d.ts +2 -2
- package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
- package/out/zql/src/planner/planner-debug.js.map +1 -1
- package/out/zql/src/planner/planner-graph.d.ts +1 -1
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zqlite/src/internal/sql-inline.d.ts.map +1 -1
- package/out/zqlite/src/internal/sql-inline.js.map +1 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +11 -11
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +99 -93
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-driver.js","names":["#tables","#pipelines","#lc","#snapshotter","#storage","#shardID","#logConfig","#config","#tableSpecs","#allTableNames","#costModels","#yieldThresholdMs","#advanceTime","#conflictRowsDeleted","#inspectorDelegate","#initAndResetCommon","#primaryKeys","#replicaVersion","#permissions","#getSource","#createStorage","#ensureCostModelExistsIfEnabled","#advanceContext","#hydrateContext","#resolveScalarSubqueries","#streamer","#advance","#shouldAdvanceYieldMaybeAbortAdvance","#push","#shouldYield","#startAccumulating","#stopAccumulating","#changes","#streamChanges","#streamNodes"],"sources":["../../../../../../zero-cache/src/services/view-syncer/pipeline-driver.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {deepEqual, type JSONValue} from '../../../../shared/src/json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {AST, LiteralValue} from '../../../../zero-protocol/src/ast.ts';\nimport type {ClientSchema} from '../../../../zero-protocol/src/client-schema.ts';\nimport type {Row} from '../../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../../zero-protocol/src/primary-key.ts';\nimport {buildPipeline} from '../../../../zql/src/builder/builder.ts';\nimport {\n Debug,\n runtimeDebugFlags,\n} from '../../../../zql/src/builder/debug-delegate.ts';\nimport type {Change} from '../../../../zql/src/ivm/change.ts';\nimport type {Node} from '../../../../zql/src/ivm/data.ts';\nimport {\n skipYields,\n type Input,\n type Storage,\n} from '../../../../zql/src/ivm/operator.ts';\nimport type {SourceSchema} from '../../../../zql/src/ivm/schema.ts';\nimport type {\n Source,\n SourceChange,\n SourceInput,\n} from '../../../../zql/src/ivm/source.ts';\nimport type {ConnectionCostModel} from '../../../../zql/src/planner/planner-connection.ts';\nimport {MeasurePushOperator} from '../../../../zql/src/query/measure-push-operator.ts';\nimport type {ClientGroupStorage} from '../../../../zqlite/src/database-storage.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n resolveSimpleScalarSubqueries,\n type CompanionSubquery,\n} from '../../../../zqlite/src/resolve-scalar-subqueries.ts';\nimport {createSQLiteCostModel} from '../../../../zqlite/src/sqlite-cost-model.ts';\nimport {TableSource} from '../../../../zqlite/src/table-source.ts';\nimport {\n reloadPermissionsIfChanged,\n type LoadedPermissions,\n} from '../../auth/load-permissions.ts';\nimport type {LogConfig, ZeroConfig} from '../../config/zero-config.ts';\nimport {computeZqlSpecs, mustGetTableSpec} from '../../db/lite-tables.ts';\nimport type {LiteAndZqlSpec, LiteTableSpec} from '../../db/specs.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../observability/metrics.ts';\nimport type {InspectorDelegate} from '../../server/inspector-delegate.ts';\nimport {type RowKey} from '../../types/row-key.ts';\nimport {type ShardID} from '../../types/shards.ts';\nimport {\n getSubscriptionState,\n ZERO_VERSION_COLUMN_NAME,\n} from '../replicator/schema/replication-state.ts';\nimport {checkClientSchema} from './client-schema.ts';\nimport type {Snapshotter} from './snapshotter.ts';\nimport {ResetPipelinesSignal, type SnapshotDiff} from './snapshotter.ts';\n\nexport type RowAdd = {\n readonly type: 'add';\n readonly queryID: string;\n readonly table: string;\n readonly rowKey: Row;\n readonly row: Row;\n};\n\nexport type RowRemove = {\n readonly type: 'remove';\n readonly queryID: string;\n readonly table: string;\n readonly rowKey: Row;\n readonly row: undefined;\n};\n\nexport type RowEdit = {\n readonly type: 'edit';\n readonly queryID: string;\n readonly table: string;\n readonly rowKey: Row;\n readonly row: Row;\n};\n\nexport type RowChange = RowAdd | RowRemove | RowEdit;\n\ntype CompanionPipeline = {\n readonly input: Input;\n readonly childField: string;\n readonly resolvedValue: LiteralValue | null | undefined;\n};\n\ntype Pipeline = {\n readonly input: Input;\n readonly hydrationTimeMs: number;\n readonly transformedAst: AST;\n readonly transformationHash: string;\n readonly companions: readonly CompanionPipeline[];\n};\n\ntype QueryInfo = {\n readonly transformedAst: AST;\n readonly transformationHash: string;\n};\n\ntype AdvanceContext = {\n readonly timer: Timer;\n readonly totalHydrationTimeMs: number;\n readonly numChanges: number;\n pos: number;\n};\n\ntype HydrateContext = {\n readonly timer: Timer;\n};\n\nexport type Timer = {\n elapsedLap: () => number;\n totalElapsed: () => number;\n};\n\n/**\n * No matter how fast hydration is, advancement is given at least this long to\n * complete before doing a pipeline reset.\n */\nconst MIN_ADVANCEMENT_TIME_LIMIT_MS = 50;\n\n/**\n * Manages the state of IVM pipelines for a given ViewSyncer (i.e. client group).\n */\nexport class PipelineDriver {\n readonly #tables = new Map<string, TableSource>();\n // Query id to pipeline\n readonly #pipelines = new Map<string, Pipeline>();\n\n readonly #lc: LogContext;\n readonly #snapshotter: Snapshotter;\n readonly #storage: ClientGroupStorage;\n readonly #shardID: ShardID;\n readonly #logConfig: LogConfig;\n readonly #config: ZeroConfig | undefined;\n readonly #tableSpecs = new Map<string, LiteAndZqlSpec>();\n readonly #allTableNames = new Set<string>();\n readonly #costModels: WeakMap<Database, ConnectionCostModel> | undefined;\n readonly #yieldThresholdMs: () => number;\n #streamer: Streamer | null = null;\n #hydrateContext: HydrateContext | null = null;\n #advanceContext: AdvanceContext | null = null;\n #replicaVersion: string | null = null;\n #primaryKeys: Map<string, PrimaryKey> | null = null;\n #permissions: LoadedPermissions | null = null;\n\n readonly #advanceTime = getOrCreateHistogram('sync', 'ivm.advance-time', {\n description:\n 'Time to advance all queries for a given client group for in response to a single change.',\n unit: 's',\n });\n\n readonly #conflictRowsDeleted = getOrCreateCounter(\n 'sync',\n 'ivm.conflict-rows-deleted',\n 'Number of rows deleted because they conflicted with added row',\n );\n\n readonly #inspectorDelegate: InspectorDelegate;\n\n constructor(\n lc: LogContext,\n logConfig: LogConfig,\n snapshotter: Snapshotter,\n shardID: ShardID,\n storage: ClientGroupStorage,\n clientGroupID: string,\n inspectorDelegate: InspectorDelegate,\n yieldThresholdMs: () => number,\n enablePlanner?: boolean,\n config?: ZeroConfig,\n ) {\n this.#lc = lc.withContext('clientGroupID', clientGroupID);\n this.#snapshotter = snapshotter;\n this.#storage = storage;\n this.#shardID = shardID;\n this.#logConfig = logConfig;\n this.#config = config;\n this.#inspectorDelegate = inspectorDelegate;\n this.#costModels = enablePlanner ? new WeakMap() : undefined;\n this.#yieldThresholdMs = yieldThresholdMs;\n }\n\n /**\n * Initializes the PipelineDriver to the current head of the database.\n * Queries can then be added (i.e. hydrated) with {@link addQuery()}.\n *\n * Must only be called once.\n */\n init(clientSchema: ClientSchema) {\n assert(!this.#snapshotter.initialized(), 'Already initialized');\n this.#snapshotter.init();\n this.#initAndResetCommon(clientSchema);\n }\n\n /**\n * @returns Whether the PipelineDriver has been initialized.\n */\n initialized(): boolean {\n return this.#snapshotter.initialized();\n }\n\n /**\n * Clears the current pipelines and TableSources, returning the PipelineDriver\n * to its initial state. This should be called in response to a schema change,\n * as TableSources need to be recomputed.\n */\n reset(clientSchema: ClientSchema) {\n for (const pipeline of this.#pipelines.values()) {\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n this.#pipelines.clear();\n this.#tables.clear();\n this.#allTableNames.clear();\n this.#initAndResetCommon(clientSchema);\n }\n\n #initAndResetCommon(clientSchema: ClientSchema) {\n const {db} = this.#snapshotter.current();\n const fullTables = new Map<string, LiteTableSpec>();\n computeZqlSpecs(\n this.#lc,\n db.db,\n {includeBackfillingColumns: false},\n this.#tableSpecs,\n fullTables,\n );\n checkClientSchema(\n this.#shardID,\n clientSchema,\n this.#tableSpecs,\n fullTables,\n );\n this.#allTableNames.clear();\n for (const table of fullTables.keys()) {\n this.#allTableNames.add(table);\n }\n const primaryKeys = this.#primaryKeys ?? new Map<string, PrimaryKey>();\n this.#primaryKeys = primaryKeys;\n primaryKeys.clear();\n for (const [table, spec] of this.#tableSpecs.entries()) {\n primaryKeys.set(table, spec.tableSpec.primaryKey);\n }\n buildPrimaryKeys(clientSchema, primaryKeys);\n const {replicaVersion} = getSubscriptionState(db);\n this.#replicaVersion = replicaVersion;\n }\n\n /** @returns The replica version. The PipelineDriver must have been initialized. */\n get replicaVersion(): string {\n return must(this.#replicaVersion, 'Not yet initialized');\n }\n\n /**\n * Returns the current version of the database. This will reflect the\n * latest version change when calling {@link advance()} once the\n * iteration has begun.\n */\n currentVersion(): string {\n assert(this.initialized(), 'Not yet initialized');\n return this.#snapshotter.current().version;\n }\n\n /**\n * Returns the current upstream {app}.permissions, or `null` if none are defined.\n */\n currentPermissions(): LoadedPermissions | null {\n assert(this.initialized(), 'Not yet initialized');\n const res = reloadPermissionsIfChanged(\n this.#lc,\n this.#snapshotter.current().db,\n this.#shardID.appID,\n this.#permissions,\n this.#config,\n );\n if (res.changed) {\n this.#permissions = res.permissions;\n this.#lc.debug?.(\n 'Reloaded permissions',\n JSON.stringify(this.#permissions),\n );\n }\n return this.#permissions;\n }\n\n advanceWithoutDiff(): string {\n const {db, version} = this.#snapshotter.advanceWithoutDiff().curr;\n for (const table of this.#tables.values()) {\n table.setDB(db.db);\n }\n return version;\n }\n\n #ensureCostModelExistsIfEnabled(db: Database) {\n let existing = this.#costModels?.get(db);\n if (existing) {\n return existing;\n }\n if (this.#costModels) {\n const costModel = createSQLiteCostModel(db, this.#tableSpecs);\n this.#costModels.set(db, costModel);\n return costModel;\n }\n return undefined;\n }\n\n /**\n * Clears storage used for the pipelines. Call this when the\n * PipelineDriver will no longer be used.\n */\n destroy() {\n this.#storage.destroy();\n this.#snapshotter.destroy();\n }\n\n /** @return Map from query ID to PipelineInfo for all added queries. */\n queries(): ReadonlyMap<string, QueryInfo> {\n return this.#pipelines;\n }\n\n totalHydrationTimeMs(): number {\n let total = 0;\n for (const pipeline of this.#pipelines.values()) {\n total += pipeline.hydrationTimeMs;\n }\n return total;\n }\n\n #resolveScalarSubqueries(ast: AST): {\n ast: AST;\n companionRows: {table: string; row: Row}[];\n companions: CompanionSubquery[];\n companionInputs: Input[];\n } {\n const companionRows: {table: string; row: Row}[] = [];\n const companionInputs: Input[] = [];\n\n const executor = (\n subqueryAST: AST,\n childField: string,\n ): LiteralValue | null | undefined => {\n const input = buildPipeline(\n subqueryAST,\n {\n getSource: name => this.#getSource(name),\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput): Input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n 'scalar-subquery',\n );\n // Consume the full stream rather than using first() to avoid\n // triggering early return on Take's #initialFetch assertion.\n // The subquery AST already has limit: 1, so at most one row is produced.\n let node: Node | undefined;\n for (const n of skipYields(input.fetch({}))) {\n node ??= n;\n }\n if (!node) {\n // Keep the companion alive even with no results — it will\n // detect a future insert that creates the row.\n companionInputs.push(input);\n return undefined;\n }\n companionRows.push({table: subqueryAST.table, row: node.row as Row});\n companionInputs.push(input);\n return (node.row[childField] as LiteralValue) ?? null;\n };\n\n const {ast: resolved, companions} = resolveSimpleScalarSubqueries(\n ast,\n this.#tableSpecs,\n executor,\n );\n return {ast: resolved, companionRows, companions, companionInputs};\n }\n\n /**\n * Adds a pipeline for the query. The method will hydrate the query using the\n * driver's current snapshot of the database and return a stream of results.\n * Henceforth, updates to the query will be returned when the driver is\n * {@link advance}d. The query and its pipeline can be removed with\n * {@link removeQuery()}.\n *\n * If a query with the same queryID is already added, the existing pipeline\n * will be removed and destroyed before adding the new pipeline.\n *\n * @param timer The caller-controlled {@link Timer} used to determine the\n * final hydration time. (The caller may pause and resume the timer\n * when yielding the thread for time-slicing).\n * @return The rows from the initial hydration of the query.\n */\n *addQuery(\n transformationHash: string,\n queryID: string,\n query: AST,\n timer: Timer,\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before adding queries',\n );\n this.removeQuery(queryID);\n const debugDelegate = runtimeDebugFlags.trackRowsVended\n ? new Debug()\n : undefined;\n\n const costModel = this.#ensureCostModelExistsIfEnabled(\n this.#snapshotter.current().db.db,\n );\n\n assert(\n this.#advanceContext === null,\n 'Cannot hydrate while advance is in progress',\n );\n this.#hydrateContext = {\n timer,\n };\n try {\n const {\n ast: resolvedQuery,\n companionRows,\n companions: companionMeta,\n companionInputs,\n } = this.#resolveScalarSubqueries(query);\n\n const input = buildPipeline(\n resolvedQuery,\n {\n debug: debugDelegate,\n enableNotExists: true, // Server-side can handle NOT EXISTS\n getSource: name => this.#getSource(name),\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput, _queryID: string): Input =>\n new MeasurePushOperator(\n input,\n queryID,\n this.#inspectorDelegate,\n 'query-update-server',\n ),\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n queryID,\n costModel,\n );\n const schema = input.getSchema();\n input.setOutput({\n push: change => {\n const streamer = this.#streamer;\n assert(streamer, 'must #startAccumulating() before pushing changes');\n streamer.accumulate(queryID, schema, [change]);\n return [];\n },\n });\n\n yield* hydrateInternal(\n input,\n queryID,\n must(this.#primaryKeys),\n this.#tableSpecs,\n );\n\n for (const {table, row} of companionRows) {\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n yield {\n type: 'add',\n queryID,\n table,\n rowKey: getRowKey(primaryKey, row),\n row,\n } as RowChange;\n }\n\n const hydrationTimeMs = timer.totalElapsed();\n if (runtimeDebugFlags.trackRowCountsVended) {\n if (hydrationTimeMs > this.#logConfig.slowHydrateThreshold) {\n let totalRowsConsidered = 0;\n const lc = this.#lc\n .withContext('queryID', queryID)\n .withContext('hydrationTimeMs', hydrationTimeMs);\n for (const tableName of this.#tables.keys()) {\n const entries = Object.entries(\n debugDelegate?.getVendedRowCounts()[tableName] ?? {},\n );\n totalRowsConsidered += entries.reduce(\n (acc, entry) => acc + entry[1],\n 0,\n );\n lc.info?.(tableName + ' VENDED: ', entries);\n }\n lc.info?.(`Total rows considered: ${totalRowsConsidered}`);\n }\n }\n debugDelegate?.reset();\n\n // Set up live companion pipelines for reactive scalar subquery monitoring.\n const liveCompanions: CompanionPipeline[] = [];\n for (let i = 0; i < companionMeta.length; i++) {\n const meta = companionMeta[i];\n const companionInput = companionInputs[i];\n const companionSchema = companionInput.getSchema();\n const {childField, resolvedValue} = meta;\n companionInput.setOutput({\n push: (change: Change) => {\n let newValue: LiteralValue | null | undefined;\n switch (change.type) {\n case 'add':\n case 'edit':\n newValue =\n (change.node.row[childField] as LiteralValue) ?? null;\n break;\n case 'remove':\n newValue = undefined;\n break;\n case 'child':\n return [];\n }\n if (!scalarValuesEqual(newValue, resolvedValue)) {\n throw new ResetPipelinesSignal(\n `Scalar subquery value changed for ${meta.ast.table}: ` +\n `${String(resolvedValue)} -> ${String(newValue)}`,\n 'scalar-subquery',\n );\n }\n const streamer = this.#streamer;\n assert(\n streamer,\n 'must #startAccumulating() before pushing changes',\n );\n streamer.accumulate(queryID, companionSchema, [change]);\n return [];\n },\n });\n liveCompanions.push({input: companionInput, childField, resolvedValue});\n }\n\n // Note: This hydrationTime is a wall-clock overestimate, as it does\n // not take time slicing into account. The view-syncer resets this\n // to a more precise processing-time measurement with setHydrationTime().\n this.#pipelines.set(queryID, {\n input,\n hydrationTimeMs,\n transformedAst: resolvedQuery,\n transformationHash,\n companions: liveCompanions,\n });\n } finally {\n this.#hydrateContext = null;\n }\n }\n\n /**\n * Removes the pipeline for the query. This is a no-op if the query\n * was not added.\n */\n removeQuery(queryID: string) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline) {\n this.#pipelines.delete(queryID);\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n }\n\n /**\n * Returns the value of the row with the given primary key `pk`,\n * or `undefined` if there is no such row. The pipeline must have been\n * initialized.\n */\n getRow(table: string, pk: RowKey): Row | undefined {\n assert(this.initialized(), 'Not yet initialized');\n const source = must(this.#tables.get(table));\n return source.getRow(pk as Row);\n }\n\n /**\n * Advances to the new head of the database.\n *\n * @param timer The caller-controlled {@link Timer} that will be used to\n * measure the progress of the advancement and abort with a\n * {@link ResetPipelinesSignal} if it is estimated to take longer\n * than a hydration.\n * @return The resulting row changes for all added queries. Note that the\n * `changes` must be iterated over in their entirety in order to\n * advance the database snapshot.\n */\n advance(timer: Timer): {\n version: string;\n numChanges: number;\n changes: Iterable<RowChange | 'yield'>;\n } {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before advancing',\n );\n const diff = this.#snapshotter.advance(\n this.#tableSpecs,\n this.#allTableNames,\n );\n const {prev, curr, changes} = diff;\n this.#lc.debug?.(\n `advance ${prev.version} => ${curr.version}: ${changes} changes`,\n );\n\n return {\n version: curr.version,\n numChanges: changes,\n changes: this.#advance(diff, timer, changes),\n };\n }\n\n *#advance(\n diff: SnapshotDiff,\n timer: Timer,\n numChanges: number,\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.#hydrateContext === null,\n 'Cannot advance while hydration is in progress',\n );\n const totalHydrationTimeMs = this.totalHydrationTimeMs();\n this.#advanceContext = {\n timer,\n totalHydrationTimeMs,\n numChanges,\n pos: 0,\n };\n this.#lc.info?.(\n `starting pipeline advancement of ${numChanges} changes with an ` +\n `advancement time limited based on total hydration time of ` +\n `${totalHydrationTimeMs} ms.`,\n );\n try {\n for (const {table, prevValues, nextValue} of diff) {\n // Advance progress is checked each time a row is fetched\n // from a TableSource during push processing, but some pushes\n // don't read any rows. Check progress here before processing\n // the next change.\n if (this.#shouldAdvanceYieldMaybeAbortAdvance()) {\n yield 'yield';\n }\n const start = timer.totalElapsed();\n\n let type;\n try {\n const tableSource = this.#tables.get(table);\n if (!tableSource) {\n // no pipelines read from this table, so no need to process the change\n continue;\n }\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n let editOldRow: Row | undefined = undefined;\n for (const prevValue of prevValues) {\n if (\n nextValue &&\n deepEqual(\n getRowKey(primaryKey, prevValue as Row) as JSONValue,\n getRowKey(primaryKey, nextValue as Row) as JSONValue,\n )\n ) {\n editOldRow = prevValue;\n } else {\n if (nextValue) {\n this.#conflictRowsDeleted.add(1);\n }\n yield* this.#push(tableSource, {\n type: 'remove',\n row: prevValue,\n });\n }\n }\n if (nextValue) {\n if (editOldRow) {\n yield* this.#push(tableSource, {\n type: 'edit',\n row: nextValue,\n oldRow: editOldRow,\n });\n } else {\n yield* this.#push(tableSource, {\n type: 'add',\n row: nextValue,\n });\n }\n }\n } finally {\n this.#advanceContext.pos++;\n }\n\n const elapsed = timer.totalElapsed() - start;\n this.#advanceTime.record(elapsed / 1000, {\n table,\n type,\n });\n }\n\n // Set the new snapshot on all TableSources.\n const {curr} = diff;\n for (const table of this.#tables.values()) {\n table.setDB(curr.db.db);\n }\n this.#ensureCostModelExistsIfEnabled(curr.db.db);\n this.#lc.debug?.(`Advanced to ${curr.version}`);\n } finally {\n this.#advanceContext = null;\n }\n }\n\n /** Implements `BuilderDelegate.getSource()` */\n #getSource(tableName: string): Source {\n let source = this.#tables.get(tableName);\n if (source) {\n return source;\n }\n\n const tableSpec = mustGetTableSpec(this.#tableSpecs, tableName);\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, tableName);\n\n const {db} = this.#snapshotter.current();\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n db.db,\n tableName,\n tableSpec.zqlSpec,\n primaryKey,\n () => this.#shouldYield(),\n );\n this.#tables.set(tableName, source);\n this.#lc.debug?.(`created TableSource for ${tableName}`);\n return source;\n }\n\n #shouldYield(): boolean {\n if (this.#hydrateContext) {\n return this.#hydrateContext.timer.elapsedLap() > this.#yieldThresholdMs();\n }\n if (this.#advanceContext) {\n return this.#shouldAdvanceYieldMaybeAbortAdvance();\n }\n throw new Error('shouldYield called outside of hydration or advancement');\n }\n\n /**\n * Cancel the advancement processing, by throwing a ResetPipelinesSignal, if\n * it has taken longer than half the total hydration time to make it through\n * half of the advancement, or if processing time exceeds total hydration\n * time. This serves as both a circuit breaker for very large transactions,\n * as well as a bound on the amount of time the previous connection locks\n * the inactive WAL file (as the lock prevents WAL2 from switching to the\n * free WAL when the current one is over the size limit, which can make\n * the WAL grow continuously and compound slowness).\n * This is checked:\n * 1. before starting to process each change in an advancement is processed\n * 2. whenever a row is fetched from a TableSource during push processing\n */\n #shouldAdvanceYieldMaybeAbortAdvance(): boolean {\n const {\n pos,\n numChanges,\n timer: advanceTimer,\n totalHydrationTimeMs,\n } = must(this.#advanceContext);\n const elapsed = advanceTimer.totalElapsed();\n if (\n elapsed > MIN_ADVANCEMENT_TIME_LIMIT_MS &&\n (elapsed > totalHydrationTimeMs ||\n (elapsed > totalHydrationTimeMs / 2 && pos <= numChanges / 2))\n ) {\n throw new ResetPipelinesSignal(\n `Advancement exceeded timeout at ${pos} of ${numChanges} changes ` +\n `after ${elapsed} ms. Advancement time limited based on total ` +\n `hydration time of ${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n );\n }\n return advanceTimer.elapsedLap() > this.#yieldThresholdMs();\n }\n\n /** Implements `BuilderDelegate.createStorage()` */\n #createStorage(): Storage {\n return this.#storage.createStorage();\n }\n\n *#push(\n source: TableSource,\n change: SourceChange,\n ): Iterable<RowChange | 'yield'> {\n this.#startAccumulating();\n try {\n for (const val of source.genPush(change)) {\n if (val === 'yield') {\n yield 'yield';\n }\n for (const changeOrYield of this.#stopAccumulating().stream()) {\n yield changeOrYield;\n }\n this.#startAccumulating();\n }\n } finally {\n if (this.#streamer !== null) {\n this.#stopAccumulating();\n }\n }\n }\n\n #startAccumulating() {\n assert(this.#streamer === null, 'Streamer already started');\n this.#streamer = new Streamer(must(this.#primaryKeys), this.#tableSpecs);\n }\n\n #stopAccumulating(): Streamer {\n const streamer = this.#streamer;\n assert(streamer, 'Streamer not started');\n this.#streamer = null;\n return streamer;\n }\n}\n\nclass Streamer {\n readonly #primaryKeys: Map<string, PrimaryKey>;\n readonly #tableSpecs: Map<string, LiteAndZqlSpec>;\n\n constructor(\n primaryKeys: Map<string, PrimaryKey>,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n ) {\n this.#primaryKeys = primaryKeys;\n this.#tableSpecs = tableSpecs;\n }\n\n readonly #changes: [\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ][] = [];\n\n accumulate(\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ): this {\n this.#changes.push([queryID, schema, changes]);\n return this;\n }\n\n *stream(): Iterable<RowChange | 'yield'> {\n for (const [queryID, schema, changes] of this.#changes) {\n yield* this.#streamChanges(queryID, schema, changes);\n }\n }\n\n *#streamChanges(\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n // We do not sync rows gathered by the permissions\n // system to the client.\n if (schema.system === 'permissions') {\n return;\n }\n\n for (const change of changes) {\n if (change === 'yield') {\n yield change;\n continue;\n }\n const {type} = change;\n\n switch (type) {\n case 'add':\n case 'remove': {\n yield* this.#streamNodes(queryID, schema, type, () => [change.node]);\n break;\n }\n case 'child': {\n const {child} = change;\n const childSchema = must(\n schema.relationships[child.relationshipName],\n );\n\n yield* this.#streamChanges(queryID, childSchema, [child.change]);\n break;\n }\n case 'edit':\n yield* this.#streamNodes(queryID, schema, type, () => [\n {row: change.node.row, relationships: {}},\n ]);\n break;\n default:\n unreachable(type);\n }\n }\n }\n\n *#streamNodes(\n queryID: string,\n schema: SourceSchema,\n op: 'add' | 'remove' | 'edit',\n nodes: () => Iterable<Node | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n const {tableName: table, system} = schema;\n\n const primaryKey = must(this.#primaryKeys.get(table));\n const spec = must(this.#tableSpecs.get(table)).tableSpec;\n\n // We do not sync rows gathered by the permissions\n // system to the client.\n if (system === 'permissions') {\n return;\n }\n\n for (const node of nodes()) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n const {relationships} = node;\n let {row} = node;\n const rowKey = getRowKey(primaryKey, row);\n if (op !== 'remove') {\n const rowVersion = row[ZERO_VERSION_COLUMN_NAME];\n if (\n typeof rowVersion === 'string' &&\n rowVersion < (spec.minRowVersion ?? '00')\n ) {\n row = {...row, [ZERO_VERSION_COLUMN_NAME]: spec.minRowVersion};\n }\n }\n\n yield {\n type: op,\n queryID,\n table,\n rowKey,\n row: op === 'remove' ? undefined : row,\n } as RowChange;\n\n for (const [relationship, children] of Object.entries(relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n yield* this.#streamNodes(queryID, childSchema, op, children);\n }\n }\n }\n}\n\nfunction* toAdds(nodes: Iterable<Node | 'yield'>): Iterable<Change | 'yield'> {\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n yield {type: 'add', node};\n }\n}\n\nfunction getRowKey(cols: PrimaryKey, row: Row): RowKey {\n return Object.fromEntries(cols.map(col => [col, must(row[col])]));\n}\n\n/**\n * Core hydration logic used by {@link PipelineDriver#addQuery}, extracted to a\n * function for reuse by bin-analyze so that bin-analyze's hydration logic\n * is as close as possible to zero-cache's real hydration logic.\n */\nexport function* hydrate(\n input: Input,\n hash: string,\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n const res = input.fetch({});\n const streamer = new Streamer(\n buildPrimaryKeys(clientSchema),\n tableSpecs,\n ).accumulate(hash, input.getSchema(), toAdds(res));\n yield* streamer.stream();\n}\n\nexport function* hydrateInternal(\n input: Input,\n hash: string,\n primaryKeys: Map<string, PrimaryKey>,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n const res = input.fetch({});\n const streamer = new Streamer(primaryKeys, tableSpecs).accumulate(\n hash,\n input.getSchema(),\n toAdds(res),\n );\n yield* streamer.stream();\n}\n\nfunction buildPrimaryKeys(\n clientSchema: ClientSchema,\n primaryKeys: Map<string, PrimaryKey> = new Map<string, PrimaryKey>(),\n) {\n for (const [tableName, {primaryKey}] of Object.entries(clientSchema.tables)) {\n primaryKeys.set(tableName, primaryKey as unknown as PrimaryKey);\n }\n return primaryKeys;\n}\n\nfunction mustGetPrimaryKey(\n primaryKeys: Map<string, PrimaryKey> | null,\n table: string,\n): PrimaryKey {\n const pKeys = must(primaryKeys, 'primaryKey map must be non-null');\n\n const rv = pKeys.get(table);\n assert(\n rv,\n () =>\n // oxlint-disable-next-line typescript/restrict-template-expressions e18e/prefer-array-to-sorted\n `table '${table}' is not one of: ${[...pKeys.keys()].sort()}. ` +\n `Check the spelling and ensure that the table has a primary key.`,\n );\n return rv;\n}\n\n/**\n * Compares two scalar subquery resolved values for equality.\n * Unlike `valuesEqual` in data.ts (which treats null != null for join\n * semantics), this uses identity semantics: undefined === undefined\n * (no row matched), null === null (row matched but field was NULL).\n */\nfunction scalarValuesEqual(\n a: LiteralValue | null | undefined,\n b: LiteralValue | null | undefined,\n): boolean {\n return a === b;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pipeline-driver.js","names":["#tables","#pipelines","#lc","#snapshotter","#storage","#shardID","#logConfig","#config","#tableSpecs","#allTableNames","#costModels","#yieldThresholdMs","#advanceTime","#conflictRowsDeleted","#inspectorDelegate","#initAndResetCommon","#primaryKeys","#replicaVersion","#permissions","#getSource","#createStorage","#ensureCostModelExistsIfEnabled","#advanceContext","#hydrateContext","#resolveScalarSubqueries","#streamer","#advance","#shouldAdvanceYieldMaybeAbortAdvance","#push","#shouldYield","#startAccumulating","#stopAccumulating","#changes","#streamChanges","#streamNodes"],"sources":["../../../../../../zero-cache/src/services/view-syncer/pipeline-driver.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {deepEqual, type JSONValue} from '../../../../shared/src/json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {AST, LiteralValue} from '../../../../zero-protocol/src/ast.ts';\nimport type {ClientSchema} from '../../../../zero-protocol/src/client-schema.ts';\nimport type {Row} from '../../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../../zero-protocol/src/primary-key.ts';\nimport {buildPipeline} from '../../../../zql/src/builder/builder.ts';\nimport {\n Debug,\n runtimeDebugFlags,\n} from '../../../../zql/src/builder/debug-delegate.ts';\nimport {ChangeIndex} from '../../../../zql/src/ivm/change-index.ts';\nimport {ChangeType} from '../../../../zql/src/ivm/change-type.ts';\nimport type {Change} from '../../../../zql/src/ivm/change.ts';\nimport type {Node} from '../../../../zql/src/ivm/data.ts';\nimport {\n skipYields,\n type Input,\n type Storage,\n} from '../../../../zql/src/ivm/operator.ts';\nimport type {SourceSchema} from '../../../../zql/src/ivm/schema.ts';\nimport {\n type Source,\n type SourceChange,\n type SourceInput,\n makeSourceChangeAdd,\n makeSourceChangeEdit,\n makeSourceChangeRemove,\n} from '../../../../zql/src/ivm/source.ts';\nimport type {ConnectionCostModel} from '../../../../zql/src/planner/planner-connection.ts';\nimport {MeasurePushOperator} from '../../../../zql/src/query/measure-push-operator.ts';\nimport type {ClientGroupStorage} from '../../../../zqlite/src/database-storage.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n resolveSimpleScalarSubqueries,\n type CompanionSubquery,\n} from '../../../../zqlite/src/resolve-scalar-subqueries.ts';\nimport {createSQLiteCostModel} from '../../../../zqlite/src/sqlite-cost-model.ts';\nimport {TableSource} from '../../../../zqlite/src/table-source.ts';\nimport {\n reloadPermissionsIfChanged,\n type LoadedPermissions,\n} from '../../auth/load-permissions.ts';\nimport type {LogConfig, ZeroConfig} from '../../config/zero-config.ts';\nimport {computeZqlSpecs, mustGetTableSpec} from '../../db/lite-tables.ts';\nimport type {LiteAndZqlSpec, LiteTableSpec} from '../../db/specs.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../observability/metrics.ts';\nimport type {InspectorDelegate} from '../../server/inspector-delegate.ts';\nimport {type RowKey} from '../../types/row-key.ts';\nimport {type ShardID} from '../../types/shards.ts';\nimport {\n getSubscriptionState,\n ZERO_VERSION_COLUMN_NAME,\n} from '../replicator/schema/replication-state.ts';\nimport {checkClientSchema} from './client-schema.ts';\nimport type {Snapshotter} from './snapshotter.ts';\nimport {ResetPipelinesSignal, type SnapshotDiff} from './snapshotter.ts';\n\ntype RowOp<Op extends Omit<ChangeType, ChangeType.CHILD>> = {\n readonly type: Op;\n readonly queryID: string;\n readonly table: string;\n readonly rowKey: Row;\n readonly row: Row;\n};\n\nexport type RowAdd = RowOp<ChangeType.ADD>;\n\nexport type RowRemove = RowOp<ChangeType.REMOVE>;\n\nexport type RowEdit = RowOp<ChangeType.EDIT>;\n\nexport type RowChange = RowAdd | RowRemove | RowEdit;\n\ntype CompanionPipeline = {\n readonly input: Input;\n readonly childField: string;\n readonly resolvedValue: LiteralValue | null | undefined;\n};\n\ntype Pipeline = {\n readonly input: Input;\n readonly hydrationTimeMs: number;\n readonly transformedAst: AST;\n readonly transformationHash: string;\n readonly companions: readonly CompanionPipeline[];\n};\n\ntype QueryInfo = {\n readonly transformedAst: AST;\n readonly transformationHash: string;\n};\n\ntype AdvanceContext = {\n readonly timer: Timer;\n readonly totalHydrationTimeMs: number;\n readonly numChanges: number;\n pos: number;\n};\n\ntype HydrateContext = {\n readonly timer: Timer;\n};\n\nexport type Timer = {\n elapsedLap: () => number;\n totalElapsed: () => number;\n};\n\n/**\n * No matter how fast hydration is, advancement is given at least this long to\n * complete before doing a pipeline reset.\n */\nconst MIN_ADVANCEMENT_TIME_LIMIT_MS = 50;\n\n/**\n * Manages the state of IVM pipelines for a given ViewSyncer (i.e. client group).\n */\nexport class PipelineDriver {\n readonly #tables = new Map<string, TableSource>();\n // Query id to pipeline\n readonly #pipelines = new Map<string, Pipeline>();\n\n readonly #lc: LogContext;\n readonly #snapshotter: Snapshotter;\n readonly #storage: ClientGroupStorage;\n readonly #shardID: ShardID;\n readonly #logConfig: LogConfig;\n readonly #config: ZeroConfig | undefined;\n readonly #tableSpecs = new Map<string, LiteAndZqlSpec>();\n readonly #allTableNames = new Set<string>();\n readonly #costModels: WeakMap<Database, ConnectionCostModel> | undefined;\n readonly #yieldThresholdMs: () => number;\n #streamer: Streamer | null = null;\n #hydrateContext: HydrateContext | null = null;\n #advanceContext: AdvanceContext | null = null;\n #replicaVersion: string | null = null;\n #primaryKeys: Map<string, PrimaryKey> | null = null;\n #permissions: LoadedPermissions | null = null;\n\n readonly #advanceTime = getOrCreateHistogram('sync', 'ivm.advance-time', {\n description:\n 'Time to advance all queries for a given client group for in response to a single change.',\n unit: 's',\n });\n\n readonly #conflictRowsDeleted = getOrCreateCounter(\n 'sync',\n 'ivm.conflict-rows-deleted',\n 'Number of rows deleted because they conflicted with added row',\n );\n\n readonly #inspectorDelegate: InspectorDelegate;\n\n constructor(\n lc: LogContext,\n logConfig: LogConfig,\n snapshotter: Snapshotter,\n shardID: ShardID,\n storage: ClientGroupStorage,\n clientGroupID: string,\n inspectorDelegate: InspectorDelegate,\n yieldThresholdMs: () => number,\n enablePlanner?: boolean,\n config?: ZeroConfig,\n ) {\n this.#lc = lc.withContext('clientGroupID', clientGroupID);\n this.#snapshotter = snapshotter;\n this.#storage = storage;\n this.#shardID = shardID;\n this.#logConfig = logConfig;\n this.#config = config;\n this.#inspectorDelegate = inspectorDelegate;\n this.#costModels = enablePlanner ? new WeakMap() : undefined;\n this.#yieldThresholdMs = yieldThresholdMs;\n }\n\n /**\n * Initializes the PipelineDriver to the current head of the database.\n * Queries can then be added (i.e. hydrated) with {@link addQuery()}.\n *\n * Must only be called once.\n */\n init(clientSchema: ClientSchema) {\n assert(!this.#snapshotter.initialized(), 'Already initialized');\n this.#snapshotter.init();\n this.#initAndResetCommon(clientSchema);\n }\n\n /**\n * @returns Whether the PipelineDriver has been initialized.\n */\n initialized(): boolean {\n return this.#snapshotter.initialized();\n }\n\n /**\n * Clears the current pipelines and TableSources, returning the PipelineDriver\n * to its initial state. This should be called in response to a schema change,\n * as TableSources need to be recomputed.\n */\n reset(clientSchema: ClientSchema) {\n for (const pipeline of this.#pipelines.values()) {\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n this.#pipelines.clear();\n this.#tables.clear();\n this.#allTableNames.clear();\n this.#initAndResetCommon(clientSchema);\n }\n\n #initAndResetCommon(clientSchema: ClientSchema) {\n const {db} = this.#snapshotter.current();\n const fullTables = new Map<string, LiteTableSpec>();\n computeZqlSpecs(\n this.#lc,\n db.db,\n {includeBackfillingColumns: false},\n this.#tableSpecs,\n fullTables,\n );\n checkClientSchema(\n this.#shardID,\n clientSchema,\n this.#tableSpecs,\n fullTables,\n );\n this.#allTableNames.clear();\n for (const table of fullTables.keys()) {\n this.#allTableNames.add(table);\n }\n const primaryKeys = this.#primaryKeys ?? new Map<string, PrimaryKey>();\n this.#primaryKeys = primaryKeys;\n primaryKeys.clear();\n for (const [table, spec] of this.#tableSpecs.entries()) {\n primaryKeys.set(table, spec.tableSpec.primaryKey);\n }\n buildPrimaryKeys(clientSchema, primaryKeys);\n const {replicaVersion} = getSubscriptionState(db);\n this.#replicaVersion = replicaVersion;\n }\n\n /** @returns The replica version. The PipelineDriver must have been initialized. */\n get replicaVersion(): string {\n return must(this.#replicaVersion, 'Not yet initialized');\n }\n\n /**\n * Returns the current version of the database. This will reflect the\n * latest version change when calling {@link advance()} once the\n * iteration has begun.\n */\n currentVersion(): string {\n assert(this.initialized(), 'Not yet initialized');\n return this.#snapshotter.current().version;\n }\n\n /**\n * Returns the current upstream {app}.permissions, or `null` if none are defined.\n */\n currentPermissions(): LoadedPermissions | null {\n assert(this.initialized(), 'Not yet initialized');\n const res = reloadPermissionsIfChanged(\n this.#lc,\n this.#snapshotter.current().db,\n this.#shardID.appID,\n this.#permissions,\n this.#config,\n );\n if (res.changed) {\n this.#permissions = res.permissions;\n this.#lc.debug?.(\n 'Reloaded permissions',\n JSON.stringify(this.#permissions),\n );\n }\n return this.#permissions;\n }\n\n advanceWithoutDiff(): string {\n const {db, version} = this.#snapshotter.advanceWithoutDiff().curr;\n for (const table of this.#tables.values()) {\n table.setDB(db.db);\n }\n return version;\n }\n\n #ensureCostModelExistsIfEnabled(db: Database) {\n let existing = this.#costModels?.get(db);\n if (existing) {\n return existing;\n }\n if (this.#costModels) {\n const costModel = createSQLiteCostModel(db, this.#tableSpecs);\n this.#costModels.set(db, costModel);\n return costModel;\n }\n return undefined;\n }\n\n /**\n * Clears storage used for the pipelines. Call this when the\n * PipelineDriver will no longer be used.\n */\n destroy() {\n this.#storage.destroy();\n this.#snapshotter.destroy();\n }\n\n /** @return Map from query ID to PipelineInfo for all added queries. */\n queries(): ReadonlyMap<string, QueryInfo> {\n return this.#pipelines;\n }\n\n totalHydrationTimeMs(): number {\n let total = 0;\n for (const pipeline of this.#pipelines.values()) {\n total += pipeline.hydrationTimeMs;\n }\n return total;\n }\n\n #resolveScalarSubqueries(ast: AST): {\n ast: AST;\n companionRows: {table: string; row: Row}[];\n companions: CompanionSubquery[];\n companionInputs: Input[];\n } {\n const companionRows: {table: string; row: Row}[] = [];\n const companionInputs: Input[] = [];\n\n const executor = (\n subqueryAST: AST,\n childField: string,\n ): LiteralValue | null | undefined => {\n const input = buildPipeline(\n subqueryAST,\n {\n getSource: name => this.#getSource(name),\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput): Input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n 'scalar-subquery',\n );\n // Consume the full stream rather than using first() to avoid\n // triggering early return on Take's #initialFetch assertion.\n // The subquery AST already has limit: 1, so at most one row is produced.\n let node: Node | undefined;\n for (const n of skipYields(input.fetch({}))) {\n node ??= n;\n }\n if (!node) {\n // Keep the companion alive even with no results — it will\n // detect a future insert that creates the row.\n companionInputs.push(input);\n return undefined;\n }\n companionRows.push({table: subqueryAST.table, row: node.row as Row});\n companionInputs.push(input);\n return (node.row[childField] as LiteralValue) ?? null;\n };\n\n const {ast: resolved, companions} = resolveSimpleScalarSubqueries(\n ast,\n this.#tableSpecs,\n executor,\n );\n return {ast: resolved, companionRows, companions, companionInputs};\n }\n\n /**\n * Adds a pipeline for the query. The method will hydrate the query using the\n * driver's current snapshot of the database and return a stream of results.\n * Henceforth, updates to the query will be returned when the driver is\n * {@link advance}d. The query and its pipeline can be removed with\n * {@link removeQuery()}.\n *\n * If a query with the same queryID is already added, the existing pipeline\n * will be removed and destroyed before adding the new pipeline.\n *\n * @param timer The caller-controlled {@link Timer} used to determine the\n * final hydration time. (The caller may pause and resume the timer\n * when yielding the thread for time-slicing).\n * @return The rows from the initial hydration of the query.\n */\n *addQuery(\n transformationHash: string,\n queryID: string,\n query: AST,\n timer: Timer,\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before adding queries',\n );\n this.removeQuery(queryID);\n const debugDelegate = runtimeDebugFlags.trackRowsVended\n ? new Debug()\n : undefined;\n\n const costModel = this.#ensureCostModelExistsIfEnabled(\n this.#snapshotter.current().db.db,\n );\n\n assert(\n this.#advanceContext === null,\n 'Cannot hydrate while advance is in progress',\n );\n this.#hydrateContext = {\n timer,\n };\n try {\n const {\n ast: resolvedQuery,\n companionRows,\n companions: companionMeta,\n companionInputs,\n } = this.#resolveScalarSubqueries(query);\n\n const input = buildPipeline(\n resolvedQuery,\n {\n debug: debugDelegate,\n enableNotExists: true, // Server-side can handle NOT EXISTS\n getSource: name => this.#getSource(name),\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput, _queryID: string): Input =>\n new MeasurePushOperator(\n input,\n queryID,\n this.#inspectorDelegate,\n 'query-update-server',\n ),\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n queryID,\n costModel,\n );\n const schema = input.getSchema();\n input.setOutput({\n push: change => {\n const streamer = this.#streamer;\n assert(streamer, 'must #startAccumulating() before pushing changes');\n streamer.accumulate(queryID, schema, [change]);\n return [];\n },\n });\n\n yield* hydrateInternal(\n input,\n queryID,\n must(this.#primaryKeys),\n this.#tableSpecs,\n );\n\n for (const {table, row} of companionRows) {\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n yield {\n type: ChangeType.ADD,\n queryID,\n table,\n rowKey: getRowKey(primaryKey, row),\n row,\n } as RowChange;\n }\n\n const hydrationTimeMs = timer.totalElapsed();\n if (runtimeDebugFlags.trackRowCountsVended) {\n if (hydrationTimeMs > this.#logConfig.slowHydrateThreshold) {\n let totalRowsConsidered = 0;\n const lc = this.#lc\n .withContext('queryID', queryID)\n .withContext('hydrationTimeMs', hydrationTimeMs);\n for (const tableName of this.#tables.keys()) {\n const entries = Object.entries(\n debugDelegate?.getVendedRowCounts()[tableName] ?? {},\n );\n totalRowsConsidered += entries.reduce(\n (acc, entry) => acc + entry[1],\n 0,\n );\n lc.info?.(tableName + ' VENDED: ', entries);\n }\n lc.info?.(`Total rows considered: ${totalRowsConsidered}`);\n }\n }\n debugDelegate?.reset();\n\n // Set up live companion pipelines for reactive scalar subquery monitoring.\n const liveCompanions: CompanionPipeline[] = [];\n for (let i = 0; i < companionMeta.length; i++) {\n const meta = companionMeta[i];\n const companionInput = companionInputs[i];\n const companionSchema = companionInput.getSchema();\n const {childField, resolvedValue} = meta;\n companionInput.setOutput({\n push: (change: Change) => {\n let newValue: LiteralValue | null | undefined;\n switch (change[ChangeIndex.TYPE]) {\n case ChangeType.ADD:\n case ChangeType.EDIT:\n newValue =\n (change[ChangeIndex.NODE].row[childField] as LiteralValue) ??\n null;\n break;\n case ChangeType.REMOVE:\n newValue = undefined;\n break;\n case ChangeType.CHILD:\n return [];\n }\n if (!scalarValuesEqual(newValue, resolvedValue)) {\n throw new ResetPipelinesSignal(\n `Scalar subquery value changed for ${meta.ast.table}: ` +\n `${String(resolvedValue)} -> ${String(newValue)}`,\n 'scalar-subquery',\n );\n }\n const streamer = this.#streamer;\n assert(\n streamer,\n 'must #startAccumulating() before pushing changes',\n );\n streamer.accumulate(queryID, companionSchema, [change]);\n return [];\n },\n });\n liveCompanions.push({input: companionInput, childField, resolvedValue});\n }\n\n // Note: This hydrationTime is a wall-clock overestimate, as it does\n // not take time slicing into account. The view-syncer resets this\n // to a more precise processing-time measurement with setHydrationTime().\n this.#pipelines.set(queryID, {\n input,\n hydrationTimeMs,\n transformedAst: resolvedQuery,\n transformationHash,\n companions: liveCompanions,\n });\n } finally {\n this.#hydrateContext = null;\n }\n }\n\n /**\n * Removes the pipeline for the query. This is a no-op if the query\n * was not added.\n */\n removeQuery(queryID: string) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline) {\n this.#pipelines.delete(queryID);\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n }\n\n /**\n * Returns the value of the row with the given primary key `pk`,\n * or `undefined` if there is no such row. The pipeline must have been\n * initialized.\n */\n getRow(table: string, pk: RowKey): Row | undefined {\n assert(this.initialized(), 'Not yet initialized');\n const source = must(this.#tables.get(table));\n return source.getRow(pk as Row);\n }\n\n /**\n * Advances to the new head of the database.\n *\n * @param timer The caller-controlled {@link Timer} that will be used to\n * measure the progress of the advancement and abort with a\n * {@link ResetPipelinesSignal} if it is estimated to take longer\n * than a hydration.\n * @return The resulting row changes for all added queries. Note that the\n * `changes` must be iterated over in their entirety in order to\n * advance the database snapshot.\n */\n advance(timer: Timer): {\n version: string;\n numChanges: number;\n changes: Iterable<RowChange | 'yield'>;\n } {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before advancing',\n );\n const diff = this.#snapshotter.advance(\n this.#tableSpecs,\n this.#allTableNames,\n );\n const {prev, curr, changes} = diff;\n this.#lc.debug?.(\n `advance ${prev.version} => ${curr.version}: ${changes} changes`,\n );\n\n return {\n version: curr.version,\n numChanges: changes,\n changes: this.#advance(diff, timer, changes),\n };\n }\n\n *#advance(\n diff: SnapshotDiff,\n timer: Timer,\n numChanges: number,\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.#hydrateContext === null,\n 'Cannot advance while hydration is in progress',\n );\n const totalHydrationTimeMs = this.totalHydrationTimeMs();\n this.#advanceContext = {\n timer,\n totalHydrationTimeMs,\n numChanges,\n pos: 0,\n };\n this.#lc.info?.(\n `starting pipeline advancement of ${numChanges} changes with an ` +\n `advancement time limited based on total hydration time of ` +\n `${totalHydrationTimeMs} ms.`,\n );\n try {\n for (const {table, prevValues, nextValue} of diff) {\n // Advance progress is checked each time a row is fetched\n // from a TableSource during push processing, but some pushes\n // don't read any rows. Check progress here before processing\n // the next change.\n if (this.#shouldAdvanceYieldMaybeAbortAdvance()) {\n yield 'yield';\n }\n const start = timer.totalElapsed();\n\n let type;\n try {\n const tableSource = this.#tables.get(table);\n if (!tableSource) {\n // no pipelines read from this table, so no need to process the change\n continue;\n }\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n let editOldRow: Row | undefined = undefined;\n for (const prevValue of prevValues) {\n if (\n nextValue &&\n deepEqual(\n getRowKey(primaryKey, prevValue as Row) as JSONValue,\n getRowKey(primaryKey, nextValue as Row) as JSONValue,\n )\n ) {\n editOldRow = prevValue;\n } else {\n if (nextValue) {\n this.#conflictRowsDeleted.add(1);\n }\n yield* this.#push(\n tableSource,\n makeSourceChangeRemove(prevValue as Row),\n );\n }\n }\n if (nextValue) {\n if (editOldRow) {\n yield* this.#push(\n tableSource,\n makeSourceChangeEdit(nextValue as Row, editOldRow),\n );\n } else {\n yield* this.#push(\n tableSource,\n makeSourceChangeAdd(nextValue as Row),\n );\n }\n }\n } finally {\n this.#advanceContext.pos++;\n }\n\n const elapsed = timer.totalElapsed() - start;\n this.#advanceTime.record(elapsed / 1000, {\n table,\n type,\n });\n }\n\n // Set the new snapshot on all TableSources.\n const {curr} = diff;\n for (const table of this.#tables.values()) {\n table.setDB(curr.db.db);\n }\n this.#ensureCostModelExistsIfEnabled(curr.db.db);\n this.#lc.debug?.(`Advanced to ${curr.version}`);\n } finally {\n this.#advanceContext = null;\n }\n }\n\n /** Implements `BuilderDelegate.getSource()` */\n #getSource(tableName: string): Source {\n let source = this.#tables.get(tableName);\n if (source) {\n return source;\n }\n\n const tableSpec = mustGetTableSpec(this.#tableSpecs, tableName);\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, tableName);\n\n const {db} = this.#snapshotter.current();\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n db.db,\n tableName,\n tableSpec.zqlSpec,\n primaryKey,\n () => this.#shouldYield(),\n );\n this.#tables.set(tableName, source);\n this.#lc.debug?.(`created TableSource for ${tableName}`);\n return source;\n }\n\n #shouldYield(): boolean {\n if (this.#hydrateContext) {\n return this.#hydrateContext.timer.elapsedLap() > this.#yieldThresholdMs();\n }\n if (this.#advanceContext) {\n return this.#shouldAdvanceYieldMaybeAbortAdvance();\n }\n throw new Error('shouldYield called outside of hydration or advancement');\n }\n\n /**\n * Cancel the advancement processing, by throwing a ResetPipelinesSignal, if\n * it has taken longer than half the total hydration time to make it through\n * half of the advancement, or if processing time exceeds total hydration\n * time. This serves as both a circuit breaker for very large transactions,\n * as well as a bound on the amount of time the previous connection locks\n * the inactive WAL file (as the lock prevents WAL2 from switching to the\n * free WAL when the current one is over the size limit, which can make\n * the WAL grow continuously and compound slowness).\n * This is checked:\n * 1. before starting to process each change in an advancement is processed\n * 2. whenever a row is fetched from a TableSource during push processing\n */\n #shouldAdvanceYieldMaybeAbortAdvance(): boolean {\n const {\n pos,\n numChanges,\n timer: advanceTimer,\n totalHydrationTimeMs,\n } = must(this.#advanceContext);\n const elapsed = advanceTimer.totalElapsed();\n if (\n elapsed > MIN_ADVANCEMENT_TIME_LIMIT_MS &&\n (elapsed > totalHydrationTimeMs ||\n (elapsed > totalHydrationTimeMs / 2 && pos <= numChanges / 2))\n ) {\n throw new ResetPipelinesSignal(\n `Advancement exceeded timeout at ${pos} of ${numChanges} changes ` +\n `after ${elapsed} ms. Advancement time limited based on total ` +\n `hydration time of ${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n );\n }\n return advanceTimer.elapsedLap() > this.#yieldThresholdMs();\n }\n\n /** Implements `BuilderDelegate.createStorage()` */\n #createStorage(): Storage {\n return this.#storage.createStorage();\n }\n\n *#push(\n source: TableSource,\n change: SourceChange,\n ): Iterable<RowChange | 'yield'> {\n this.#startAccumulating();\n try {\n for (const val of source.genPush(change)) {\n if (val === 'yield') {\n yield 'yield';\n }\n for (const changeOrYield of this.#stopAccumulating().stream()) {\n yield changeOrYield;\n }\n this.#startAccumulating();\n }\n } finally {\n if (this.#streamer !== null) {\n this.#stopAccumulating();\n }\n }\n }\n\n #startAccumulating() {\n assert(this.#streamer === null, 'Streamer already started');\n this.#streamer = new Streamer(must(this.#primaryKeys), this.#tableSpecs);\n }\n\n #stopAccumulating(): Streamer {\n const streamer = this.#streamer;\n assert(streamer, 'Streamer not started');\n this.#streamer = null;\n return streamer;\n }\n}\n\nclass Streamer {\n readonly #primaryKeys: Map<string, PrimaryKey>;\n readonly #tableSpecs: Map<string, LiteAndZqlSpec>;\n\n constructor(\n primaryKeys: Map<string, PrimaryKey>,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n ) {\n this.#primaryKeys = primaryKeys;\n this.#tableSpecs = tableSpecs;\n }\n\n readonly #changes: [\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ][] = [];\n\n accumulate(\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ): this {\n this.#changes.push([queryID, schema, changes]);\n return this;\n }\n\n *stream(): Iterable<RowChange | 'yield'> {\n for (const [queryID, schema, changes] of this.#changes) {\n yield* this.#streamChanges(queryID, schema, changes);\n }\n }\n\n *#streamChanges(\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n // We do not sync rows gathered by the permissions\n // system to the client.\n if (schema.system === 'permissions') {\n return;\n }\n\n for (const change of changes) {\n if (change === 'yield') {\n yield change;\n continue;\n }\n const type = change[ChangeIndex.TYPE];\n switch (type) {\n case ChangeType.REMOVE:\n case ChangeType.ADD: {\n yield* this.#streamNodes(queryID, schema, type, () => [\n change[ChangeIndex.NODE],\n ]);\n break;\n }\n\n case ChangeType.CHILD: {\n const child = change[ChangeIndex.CHILD_DATA];\n const childSchema = must(\n schema.relationships[child.relationshipName],\n );\n\n yield* this.#streamChanges(queryID, childSchema, [child.change]);\n break;\n }\n case ChangeType.EDIT:\n yield* this.#streamNodes(queryID, schema, type, () => [\n {row: change[ChangeIndex.NODE].row, relationships: {}},\n ]);\n break;\n default:\n unreachable(change[ChangeIndex.TYPE]);\n }\n }\n }\n\n *#streamNodes(\n queryID: string,\n schema: SourceSchema,\n op: ChangeType.ADD | ChangeType.REMOVE | ChangeType.EDIT,\n nodes: () => Iterable<Node | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n const {tableName: table, system} = schema;\n\n const primaryKey = must(this.#primaryKeys.get(table));\n const spec = must(this.#tableSpecs.get(table)).tableSpec;\n\n // We do not sync rows gathered by the permissions\n // system to the client.\n if (system === 'permissions') {\n return;\n }\n\n for (const node of nodes()) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n const {relationships} = node;\n let {row} = node;\n const rowKey = getRowKey(primaryKey, row);\n if (op !== ChangeType.REMOVE) {\n const rowVersion = row[ZERO_VERSION_COLUMN_NAME];\n if (\n typeof rowVersion === 'string' &&\n rowVersion < (spec.minRowVersion ?? '00')\n ) {\n row = {...row, [ZERO_VERSION_COLUMN_NAME]: spec.minRowVersion};\n }\n }\n\n yield {\n type: op,\n queryID,\n table,\n rowKey,\n row: op === ChangeType.REMOVE ? undefined : row,\n } as RowChange;\n\n for (const [relationship, children] of Object.entries(relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n yield* this.#streamNodes(queryID, childSchema, op, children);\n }\n }\n }\n}\n\nfunction* toAdds(nodes: Iterable<Node | 'yield'>): Iterable<Change | 'yield'> {\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n yield [ChangeType.ADD, node, null];\n }\n}\n\nfunction getRowKey(cols: PrimaryKey, row: Row): RowKey {\n return Object.fromEntries(cols.map(col => [col, must(row[col])]));\n}\n\n/**\n * Core hydration logic used by {@link PipelineDriver#addQuery}, extracted to a\n * function for reuse by bin-analyze so that bin-analyze's hydration logic\n * is as close as possible to zero-cache's real hydration logic.\n */\nexport function* hydrate(\n input: Input,\n hash: string,\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n const res = input.fetch({});\n const streamer = new Streamer(\n buildPrimaryKeys(clientSchema),\n tableSpecs,\n ).accumulate(hash, input.getSchema(), toAdds(res));\n yield* streamer.stream();\n}\n\nexport function* hydrateInternal(\n input: Input,\n hash: string,\n primaryKeys: Map<string, PrimaryKey>,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n const res = input.fetch({});\n const streamer = new Streamer(primaryKeys, tableSpecs).accumulate(\n hash,\n input.getSchema(),\n toAdds(res),\n );\n yield* streamer.stream();\n}\n\nfunction buildPrimaryKeys(\n clientSchema: ClientSchema,\n primaryKeys: Map<string, PrimaryKey> = new Map<string, PrimaryKey>(),\n) {\n for (const [tableName, {primaryKey}] of Object.entries(clientSchema.tables)) {\n primaryKeys.set(tableName, primaryKey as unknown as PrimaryKey);\n }\n return primaryKeys;\n}\n\nfunction mustGetPrimaryKey(\n primaryKeys: Map<string, PrimaryKey> | null,\n table: string,\n): PrimaryKey {\n const pKeys = must(primaryKeys, 'primaryKey map must be non-null');\n\n const rv = pKeys.get(table);\n assert(\n rv,\n () =>\n // oxlint-disable-next-line typescript/restrict-template-expressions e18e/prefer-array-to-sorted\n `table '${table}' is not one of: ${[...pKeys.keys()].sort()}. ` +\n `Check the spelling and ensure that the table has a primary key.`,\n );\n return rv;\n}\n\n/**\n * Compares two scalar subquery resolved values for equality.\n * Unlike `valuesEqual` in data.ts (which treats null != null for join\n * semantics), this uses identity semantics: undefined === undefined\n * (no row matched), null === null (row matched but field was NULL).\n */\nfunction scalarValuesEqual(\n a: LiteralValue | null | undefined,\n b: LiteralValue | null | undefined,\n): boolean {\n return a === b;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"row-record-cache.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAE,GAAG,EAAC,MAAM,UAAU,CAAC;AAYhD,OAAO,EAAC,KAAK,UAAU,EAAE,KAAK,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAe,KAAK,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,UAAU,CAAC;AAC1C,OAAO,EAEL,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"row-record-cache.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAE,GAAG,EAAC,MAAM,UAAU,CAAC;AAYhD,OAAO,EAAC,KAAK,UAAU,EAAE,KAAK,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAe,KAAK,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,UAAU,CAAC;AAC1C,OAAO,EAEL,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,KAAK,EACV,KAAK,SAAS,EAGf,MAAM,mBAAmB,CAAC;AAK3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,qBAAa,cAAc;;gBAgCvB,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,EACjC,yBAAyB,SAAM,EAC/B,YAAY,oBAAa;IAW3B,oBAAoB,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM;IA+D5D,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAIvD;;;;;;;;;;;;;;OAcG;IACG,KAAK,CACT,UAAU,EAAE,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC,EACxC,WAAW,EAAE,UAAU,EACvB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,MAAM,CAAC;IAuElB,iBAAiB;IAIjB;;;OAGG;IACH,OAAO,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtC,KAAK;IAOE,iBAAiB,CACtB,EAAE,EAAE,UAAU,EACd,YAAY,EAAE,kBAAkB,EAChC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,UAAU,EACnB,kBAAkB,GAAE,MAAM,EAAO,GAChC,cAAc,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC;IAmD7C,iBAAiB,CACf,EAAE,EAAE,mBAAmB,EACvB,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC,EACxC,IAAI,EAAE,aAAa,GAAG,OAAO,EAC7B,EAAE,yDAAW,GACZ,YAAY,CAAC,GAAG,EAAE,CAAC,EAAE;CAiEzB"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { promiseVoid } from "../../../../shared/src/resolved-promises.js";
|
|
2
2
|
import { must } from "../../../../shared/src/must.js";
|
|
3
|
-
import { startAsyncSpan } from "../../../../otel/src/span.js";
|
|
4
3
|
import { cvrSchema } from "../../types/shards.js";
|
|
5
|
-
import "
|
|
4
|
+
import { startAsyncSpan } from "../../../../otel/src/span.js";
|
|
5
|
+
import { getOrCreateCounter, getOrCreateHistogram } from "../../observability/metrics.js";
|
|
6
6
|
import { rowIDString } from "../../types/row-key.js";
|
|
7
|
+
import "../../types/pg.js";
|
|
7
8
|
import { READONLY, READ_COMMITTED } from "../../db/mode-enum.js";
|
|
8
9
|
import { runTx } from "../../db/run-transaction.js";
|
|
9
|
-
import { getOrCreateCounter, getOrCreateHistogram } from "../../observability/metrics.js";
|
|
10
10
|
import { CustomKeyMap } from "../../../../shared/src/custom-key-map.js";
|
|
11
11
|
import { TransactionPool } from "../../db/transaction-pool.js";
|
|
12
12
|
import { cmpVersions, versionString, versionToNullableCookie } from "./schema/types.js";
|
|
@@ -217,7 +217,7 @@ var RowRecordCache = class {
|
|
|
217
217
|
lc.debug?.(`scanning row patches for clients from ${start}`);
|
|
218
218
|
await this.flushed(lc);
|
|
219
219
|
const flushMs = Date.now() - startMs;
|
|
220
|
-
const reader = new TransactionPool(lc, READONLY).run(this.#db);
|
|
220
|
+
const reader = new TransactionPool(lc, { mode: READONLY }).run(this.#db);
|
|
221
221
|
try {
|
|
222
222
|
await reader.processReadTask((tx) => checkVersion(tx, this.#schema, this.#cvrID, current));
|
|
223
223
|
const { query } = await reader.processReadTask((tx) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"row-record-cache.js","names":["#lc","#db","#schema","#cvrID","#failService","#deferredRowFlushThreshold","#setTimeout","#pending","#cvrFlushTime","#cvrRowsFlushed","#ensureLoaded","#cache","#cvr","#pendingRowsVersion","#flushing","#flush","#flushedRowsVersion","#recordAsyncFlushStats"],"sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Resolver, resolver} from '@rocicorp/resolver';\nimport type {PendingQuery, Row} from 'postgres';\nimport {startAsyncSpan} from '../../../../otel/src/span.ts';\nimport {CustomKeyMap} from '../../../../shared/src/custom-key-map.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {runTx} from '../../db/run-transaction.ts';\nimport {TransactionPool} from '../../db/transaction-pool.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../observability/metrics.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../../types/pg.ts';\nimport {rowIDString} from '../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {checkVersion, type CVRFlushStats} from './cvr-store.ts';\nimport type {CVRSnapshot} from './cvr.ts';\nimport {\n rowRecordToRowsRow,\n type RowsRow,\n rowsRowToRowRecord,\n} from './schema/cvr.ts';\nimport {tracer} from './tracer.ts';\nimport {\n cmpVersions,\n type CVRVersion,\n type NullableCVRVersion,\n type RowID,\n type RowRecord,\n versionString,\n versionToNullableCookie,\n} from './schema/types.ts';\n\nconst FLUSH_TYPE_ATTRIBUTE = 'flush.type';\n\n/**\n * The RowRecordCache is an in-memory cache of the `cvr.rows` tables that\n * operates as both a write-through and write-back cache.\n *\n * For \"small\" CVR updates (i.e. zero or small numbers of rows) the\n * RowRecordCache operates as write-through, executing commits in\n * {@link executeRowUpdates()} before they are {@link apply}-ed to the\n * in-memory state.\n *\n * For \"large\" CVR updates (i.e. with many rows), the cache switches to a\n * write-back mode of operation, in which {@link executeRowUpdates()} is a\n * no-op, and {@link apply()} initiates a background task to flush the pending\n * row changes to the store. This allows the client poke to be completed and\n * committed on the client without waiting for the heavyweight operation of\n * committing the row records to the CVR store.\n *\n * Note that when the cache is in write-back mode, all updates become\n * write-back (i.e. asynchronously flushed) until the pending update queue is\n * fully flushed. This is required because updates must be applied in version\n * order. As with all pending work systems in zero-cache, multiple pending\n * updates are coalesced to reduce buildup of work.\n *\n * ### High level consistency\n *\n * Note that the above caching scheme only applies to the row data in `cvr.rows`\n * and corresponding `cvr.rowsVersion` tables. CVR metadata and query\n * information, on the other hand, are always committed before completing the\n * client poke. In this manner, the difference between the `version` column in\n * `cvr.instances` and the analogous column in `cvr.rowsVersion` determines\n * whether the data in the store is consistent, or whether it is awaiting a\n * pending update.\n *\n * The logic in {@link CVRStore#load()} takes this into account by loading both\n * the `cvr.instances` version and the `cvr.rowsVersion` version and checking\n * if they are in sync, waiting for a configurable delay until they are.\n *\n * ### Eventual conversion\n *\n * In the event of a continual stream of mutations (e.g. an animation-style\n * app), it is conceivable that the row record data be continually behind\n * the CVR metadata. In order to effect eventual convergence, a new view-syncer\n * signals the current view-syncer to stop updating by writing new `owner`\n * information to the `cvr.instances` row. This effectively stops the mutation\n * processing (in {@link CVRStore.#checkVersionAndOwnership}) so that the row\n * data can eventually catch up, allowing the new view-syncer to take over.\n *\n * Of course, there is the pathological situation in which a view-syncer\n * process crashes before the pending row updates are flushed. In this case,\n * the wait timeout will elapse and the CVR considered invalid.\n */\nexport class RowRecordCache {\n // The state in the #cache is always in sync with the CVR metadata\n // (i.e. cvr.instances). It may contain information that has not yet\n // been flushed to cvr.rows.\n #cache: Promise<CustomKeyMap<RowID, RowRecord>> | undefined;\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #cvrID: string;\n readonly #failService: (e: unknown) => void;\n readonly #deferredRowFlushThreshold: number;\n readonly #setTimeout: typeof setTimeout;\n\n // Write-back cache state.\n readonly #pending = new CustomKeyMap<RowID, RowRecord | null>(rowIDString);\n #pendingRowsVersion: CVRVersion | null = null;\n #flushedRowsVersion: CVRVersion | null = null;\n #flushing: Resolver<void> | null = null;\n\n readonly #cvrFlushTime = getOrCreateHistogram('sync', 'cvr.flush-time', {\n description:\n 'Time to flush a CVR transaction. This includes both synchronous ' +\n 'and asynchronous flushes, distinguished by the flush.type attribute',\n unit: 's',\n });\n readonly #cvrRowsFlushed = getOrCreateCounter(\n 'sync',\n 'cvr.rows-flushed',\n 'Number of (changed) rows flushed to a CVR',\n );\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n cvrID: string,\n failService: (e: unknown) => void,\n deferredRowFlushThreshold = 100,\n setTimeoutFn = setTimeout,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#cvrID = cvrID;\n this.#failService = failService;\n this.#deferredRowFlushThreshold = deferredRowFlushThreshold;\n this.#setTimeout = setTimeoutFn;\n }\n\n recordSyncFlushStats(stats: CVRFlushStats, elapsedMs: number) {\n this.#cvrFlushTime.record(elapsedMs / 1000, {\n [FLUSH_TYPE_ATTRIBUTE]: 'sync',\n });\n if (stats.rowsDeferred === 0) {\n this.#cvrRowsFlushed.add(stats.rows);\n }\n }\n\n #recordAsyncFlushStats(rows: number, elapsedMs: number) {\n this.#cvrFlushTime.record(elapsedMs / 1000, {\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n });\n this.#cvrRowsFlushed.add(rows);\n }\n\n #cvr(table: string) {\n return this.#db(`${this.#schema}.${table}`);\n }\n\n async #ensureLoaded(): Promise<CustomKeyMap<RowID, RowRecord>> {\n if (this.#cache) {\n return this.#cache;\n }\n const start = Date.now();\n const r = resolver<CustomKeyMap<RowID, RowRecord>>();\n r.promise.catch(() => {});\n // Set this.#cache immediately (before await) so that only one db\n // query is made even if there are multiple callers.\n this.#cache = r.promise;\n try {\n const cache = await startAsyncSpan(\n tracer,\n 'RowRecordCache.load',\n async span => {\n const cache: CustomKeyMap<RowID, RowRecord> = new CustomKeyMap(\n rowIDString,\n );\n for await (const rows of this.#db<RowsRow[]>`\n SELECT * FROM ${this.#cvr(`rows`)}\n WHERE \"clientGroupID\" = ${this.#cvrID} AND \"refCounts\" IS NOT NULL`\n // TODO(arv): Arbitrary page size\n .cursor(5000)) {\n for (const row of rows) {\n const rowRecord = rowsRowToRowRecord(row);\n cache.set(rowRecord.id, rowRecord);\n }\n }\n span.setAttribute('rows', cache.size);\n return cache;\n },\n );\n this.#lc.info?.(\n `Loaded ${cache.size} row records in ${Date.now() - start} ms`,\n );\n r.resolve(cache);\n return this.#cache;\n } catch (e) {\n r.reject(e); // Make sure the error is reflected in the cached promise\n throw e;\n }\n }\n\n getRowRecords(): Promise<ReadonlyMap<RowID, RowRecord>> {\n return this.#ensureLoaded();\n }\n\n /**\n * Applies the `rowRecords` corresponding to the `rowsVersion`\n * to the cache, indicating whether the corresponding updates\n * (generated by {@link executeRowUpdates}) were `flushed`.\n *\n * If `flushed` is false, the RowRecordCache will flush the records\n * asynchronously.\n *\n * Note that `apply()` indicates that the CVR metadata associated with\n * the `rowRecords` was successfully committed, which essentially means\n * that this process has the unconditional right (and responsibility) of\n * following up with a flush of the `rowRecords`. In particular, the\n * commit of row records are not conditioned on the version or ownership\n * columns of the `cvr.instances` row.\n */\n async apply(\n rowRecords: Map<RowID, RowRecord | null>,\n rowsVersion: CVRVersion,\n flushed: boolean,\n ): Promise<number> {\n const cache = await this.#ensureLoaded();\n for (const [id, row] of rowRecords.entries()) {\n if (row === null || row.refCounts === null) {\n cache.delete(id);\n } else {\n cache.set(id, row);\n }\n if (!flushed) {\n this.#pending.set(id, row);\n }\n }\n this.#pendingRowsVersion = rowsVersion;\n // Initiate a flush if not already flushing.\n if (!flushed && this.#flushing === null) {\n this.#flushing = resolver();\n // The #flush() method handles propagating errors to #failService.\n // Attach a rejection handler to this promise to avoid unhandled\n // rejections.\n this.#flushing.promise.catch(() => {});\n this.#setTimeout(() => this.#flush(), 0);\n }\n return cache.size;\n }\n\n async #flush() {\n const flushing = must(this.#flushing);\n try {\n while (this.#pendingRowsVersion !== this.#flushedRowsVersion) {\n const start = performance.now();\n\n const {rows, rowsVersion} = await runTx(\n this.#db,\n tx => {\n // Note: This code block is synchronous, guaranteeing that the\n // #pendingRowsVersion is consistent with the #pending rows.\n const rows = this.#pending.size;\n const rowsVersion = must(this.#pendingRowsVersion);\n // Awaiting all of the individual statements incurs too much\n // overhead. Instead, just catch and log exception(s); the outer\n // transaction will properly fail.\n void Promise.all(\n this.executeRowUpdates(tx, rowsVersion, this.#pending, 'force'),\n ).catch(e => this.#lc.error?.(`error flushing cvr rows`, e));\n\n this.#pending.clear();\n return {rows, rowsVersion};\n },\n {mode: Mode.READ_COMMITTED},\n );\n const elapsed = performance.now() - start;\n this.#lc.info?.(\n `flushed ${rows} rows@${versionString(rowsVersion)} (${elapsed} ms)`,\n );\n this.#recordAsyncFlushStats(rows, elapsed);\n this.#flushedRowsVersion = rowsVersion;\n // Note: apply() may have called while the transaction was committing,\n // which will result in looping to commit the next #pendingRowsVersion.\n }\n this.#lc.info?.(\n `up to date rows@${versionToNullableCookie(this.#flushedRowsVersion)}`,\n );\n flushing.resolve();\n this.#flushing = null;\n } catch (e) {\n this.#lc.info?.(`row record flush failed`, e);\n flushing.reject(e);\n this.#failService(e);\n }\n }\n\n hasPendingUpdates() {\n return this.#flushing !== null;\n }\n\n /**\n * Returns a promise that resolves when all outstanding row-records\n * have been committed.\n */\n flushed(lc: LogContext): Promise<void> {\n if (this.#flushing) {\n lc.debug?.('awaiting pending row flush');\n return this.#flushing.promise;\n }\n return promiseVoid;\n }\n\n clear() {\n // Note: Only the #cache is cleared. #pending updates, on the other hand,\n // comprise canonical (i.e. already flushed) data and must be flushed\n // even if the snapshot of the present state (the #cache) is cleared.\n this.#cache = undefined;\n }\n\n async *catchupRowPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n excludeQueryHashes: string[] = [],\n ): AsyncGenerator<RowsRow[], void, undefined> {\n if (cmpVersions(afterVersion, upToCVR.version) >= 0) {\n return;\n }\n\n const startMs = Date.now();\n const start = afterVersion ? versionString(afterVersion) : '';\n const end = versionString(upToCVR.version);\n lc.debug?.(`scanning row patches for clients from ${start}`);\n\n // Before accessing the CVR db, pending row records must be flushed.\n // Note that because catchupRowPatches() is called from within the\n // view syncer lock, this flush is guaranteed to complete since no\n // new CVR updates can happen while the lock is held.\n await this.flushed(lc);\n const flushMs = Date.now() - startMs;\n\n const reader = new TransactionPool(lc, Mode.READONLY).run(this.#db);\n try {\n // Verify that we are reading the right version of the CVR.\n await reader.processReadTask(tx =>\n checkVersion(tx, this.#schema, this.#cvrID, current),\n );\n\n const {query} = await reader.processReadTask(tx => {\n const query =\n excludeQueryHashes.length === 0\n ? tx<RowsRow[]>`SELECT * FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`\n : // Exclude rows that were already sent as part of query hydration.\n tx<RowsRow[]>`SELECT * FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}\n AND (\"refCounts\" IS NULL OR NOT \"refCounts\" ?| ${excludeQueryHashes})`;\n return {query};\n });\n\n yield* query.cursor(10000);\n } finally {\n reader.setDone();\n }\n\n const totalMs = Date.now() - startMs;\n lc.info?.(\n `finished row catchup (flush: ${flushMs} ms, total: ${totalMs} ms)`,\n );\n }\n\n executeRowUpdates(\n tx: PostgresTransaction,\n version: CVRVersion,\n rowUpdates: Map<RowID, RowRecord | null>,\n mode: 'allow-defer' | 'force',\n lc = this.#lc,\n ): PendingQuery<Row[]>[] {\n if (\n mode === 'allow-defer' &&\n // defer if pending rows are being flushed\n (this.#flushing !== null ||\n // or if the new batch is above the limit.\n rowUpdates.size > this.#deferredRowFlushThreshold)\n ) {\n return [];\n }\n const rowsVersion = {\n clientGroupID: this.#cvrID,\n version: versionString(version),\n };\n const pending: PendingQuery<Row[]>[] = [\n tx`INSERT INTO ${this.#cvr('rowsVersion')} ${tx(rowsVersion)}\n ON CONFLICT (\"clientGroupID\") \n DO UPDATE SET ${tx(rowsVersion)}`,\n ];\n\n const rowRecordRows: RowsRow[] = [];\n for (const [id, row] of rowUpdates.entries()) {\n if (row === null) {\n pending.push(\n tx`\n DELETE FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"schema\" = ${id.schema}\n AND \"table\" = ${id.table}\n AND \"rowKey\" = ${id.rowKey}\n `,\n );\n } else {\n rowRecordRows.push(rowRecordToRowsRow(this.#cvrID, row));\n }\n }\n if (rowRecordRows.length) {\n pending.push(\n tx`\n INSERT INTO ${this.#cvr('rows')}(\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n ) SELECT\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n FROM json_to_recordset(${rowRecordRows}) AS x(\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT,\n \"patchVersion\" TEXT,\n \"refCounts\" JSONB\n ) ON CONFLICT (\"clientGroupID\", \"schema\", \"table\", \"rowKey\")\n DO UPDATE SET \"rowVersion\" = excluded.\"rowVersion\",\n \"patchVersion\" = excluded.\"patchVersion\",\n \"refCounts\" = excluded.\"refCounts\"\n `,\n );\n lc.info?.(\n `flushing ${rowUpdates.size} rows (${rowRecordRows.length} inserts, ${\n rowUpdates.size - rowRecordRows.length\n } deletes)`,\n );\n }\n return pending;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"row-record-cache.js","names":["#lc","#db","#schema","#cvrID","#failService","#deferredRowFlushThreshold","#setTimeout","#pending","#cvrFlushTime","#cvrRowsFlushed","#ensureLoaded","#cache","#cvr","#pendingRowsVersion","#flushing","#flush","#flushedRowsVersion","#recordAsyncFlushStats"],"sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Resolver, resolver} from '@rocicorp/resolver';\nimport type {PendingQuery, Row} from 'postgres';\nimport {startAsyncSpan} from '../../../../otel/src/span.ts';\nimport {CustomKeyMap} from '../../../../shared/src/custom-key-map.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {runTx} from '../../db/run-transaction.ts';\nimport {TransactionPool} from '../../db/transaction-pool.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../observability/metrics.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../../types/pg.ts';\nimport {rowIDString} from '../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {checkVersion, type CVRFlushStats} from './cvr-store.ts';\nimport type {CVRSnapshot} from './cvr.ts';\nimport {\n rowRecordToRowsRow,\n type RowsRow,\n rowsRowToRowRecord,\n} from './schema/cvr.ts';\nimport {\n cmpVersions,\n type CVRVersion,\n type NullableCVRVersion,\n type RowID,\n type RowRecord,\n versionString,\n versionToNullableCookie,\n} from './schema/types.ts';\nimport {tracer} from './tracer.ts';\n\nconst FLUSH_TYPE_ATTRIBUTE = 'flush.type';\n\n/**\n * The RowRecordCache is an in-memory cache of the `cvr.rows` tables that\n * operates as both a write-through and write-back cache.\n *\n * For \"small\" CVR updates (i.e. zero or small numbers of rows) the\n * RowRecordCache operates as write-through, executing commits in\n * {@link executeRowUpdates()} before they are {@link apply}-ed to the\n * in-memory state.\n *\n * For \"large\" CVR updates (i.e. with many rows), the cache switches to a\n * write-back mode of operation, in which {@link executeRowUpdates()} is a\n * no-op, and {@link apply()} initiates a background task to flush the pending\n * row changes to the store. This allows the client poke to be completed and\n * committed on the client without waiting for the heavyweight operation of\n * committing the row records to the CVR store.\n *\n * Note that when the cache is in write-back mode, all updates become\n * write-back (i.e. asynchronously flushed) until the pending update queue is\n * fully flushed. This is required because updates must be applied in version\n * order. As with all pending work systems in zero-cache, multiple pending\n * updates are coalesced to reduce buildup of work.\n *\n * ### High level consistency\n *\n * Note that the above caching scheme only applies to the row data in `cvr.rows`\n * and corresponding `cvr.rowsVersion` tables. CVR metadata and query\n * information, on the other hand, are always committed before completing the\n * client poke. In this manner, the difference between the `version` column in\n * `cvr.instances` and the analogous column in `cvr.rowsVersion` determines\n * whether the data in the store is consistent, or whether it is awaiting a\n * pending update.\n *\n * The logic in {@link CVRStore#load()} takes this into account by loading both\n * the `cvr.instances` version and the `cvr.rowsVersion` version and checking\n * if they are in sync, waiting for a configurable delay until they are.\n *\n * ### Eventual conversion\n *\n * In the event of a continual stream of mutations (e.g. an animation-style\n * app), it is conceivable that the row record data be continually behind\n * the CVR metadata. In order to effect eventual convergence, a new view-syncer\n * signals the current view-syncer to stop updating by writing new `owner`\n * information to the `cvr.instances` row. This effectively stops the mutation\n * processing (in {@link CVRStore.#checkVersionAndOwnership}) so that the row\n * data can eventually catch up, allowing the new view-syncer to take over.\n *\n * Of course, there is the pathological situation in which a view-syncer\n * process crashes before the pending row updates are flushed. In this case,\n * the wait timeout will elapse and the CVR considered invalid.\n */\nexport class RowRecordCache {\n // The state in the #cache is always in sync with the CVR metadata\n // (i.e. cvr.instances). It may contain information that has not yet\n // been flushed to cvr.rows.\n #cache: Promise<CustomKeyMap<RowID, RowRecord>> | undefined;\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #cvrID: string;\n readonly #failService: (e: unknown) => void;\n readonly #deferredRowFlushThreshold: number;\n readonly #setTimeout: typeof setTimeout;\n\n // Write-back cache state.\n readonly #pending = new CustomKeyMap<RowID, RowRecord | null>(rowIDString);\n #pendingRowsVersion: CVRVersion | null = null;\n #flushedRowsVersion: CVRVersion | null = null;\n #flushing: Resolver<void> | null = null;\n\n readonly #cvrFlushTime = getOrCreateHistogram('sync', 'cvr.flush-time', {\n description:\n 'Time to flush a CVR transaction. This includes both synchronous ' +\n 'and asynchronous flushes, distinguished by the flush.type attribute',\n unit: 's',\n });\n readonly #cvrRowsFlushed = getOrCreateCounter(\n 'sync',\n 'cvr.rows-flushed',\n 'Number of (changed) rows flushed to a CVR',\n );\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n cvrID: string,\n failService: (e: unknown) => void,\n deferredRowFlushThreshold = 100,\n setTimeoutFn = setTimeout,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#cvrID = cvrID;\n this.#failService = failService;\n this.#deferredRowFlushThreshold = deferredRowFlushThreshold;\n this.#setTimeout = setTimeoutFn;\n }\n\n recordSyncFlushStats(stats: CVRFlushStats, elapsedMs: number) {\n this.#cvrFlushTime.record(elapsedMs / 1000, {\n [FLUSH_TYPE_ATTRIBUTE]: 'sync',\n });\n if (stats.rowsDeferred === 0) {\n this.#cvrRowsFlushed.add(stats.rows);\n }\n }\n\n #recordAsyncFlushStats(rows: number, elapsedMs: number) {\n this.#cvrFlushTime.record(elapsedMs / 1000, {\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n });\n this.#cvrRowsFlushed.add(rows);\n }\n\n #cvr(table: string) {\n return this.#db(`${this.#schema}.${table}`);\n }\n\n async #ensureLoaded(): Promise<CustomKeyMap<RowID, RowRecord>> {\n if (this.#cache) {\n return this.#cache;\n }\n const start = Date.now();\n const r = resolver<CustomKeyMap<RowID, RowRecord>>();\n r.promise.catch(() => {});\n // Set this.#cache immediately (before await) so that only one db\n // query is made even if there are multiple callers.\n this.#cache = r.promise;\n try {\n const cache = await startAsyncSpan(\n tracer,\n 'RowRecordCache.load',\n async span => {\n const cache: CustomKeyMap<RowID, RowRecord> = new CustomKeyMap(\n rowIDString,\n );\n for await (const rows of this.#db<RowsRow[]>`\n SELECT * FROM ${this.#cvr(`rows`)}\n WHERE \"clientGroupID\" = ${this.#cvrID} AND \"refCounts\" IS NOT NULL`\n // TODO(arv): Arbitrary page size\n .cursor(5000)) {\n for (const row of rows) {\n const rowRecord = rowsRowToRowRecord(row);\n cache.set(rowRecord.id, rowRecord);\n }\n }\n span.setAttribute('rows', cache.size);\n return cache;\n },\n );\n this.#lc.info?.(\n `Loaded ${cache.size} row records in ${Date.now() - start} ms`,\n );\n r.resolve(cache);\n return this.#cache;\n } catch (e) {\n r.reject(e); // Make sure the error is reflected in the cached promise\n throw e;\n }\n }\n\n getRowRecords(): Promise<ReadonlyMap<RowID, RowRecord>> {\n return this.#ensureLoaded();\n }\n\n /**\n * Applies the `rowRecords` corresponding to the `rowsVersion`\n * to the cache, indicating whether the corresponding updates\n * (generated by {@link executeRowUpdates}) were `flushed`.\n *\n * If `flushed` is false, the RowRecordCache will flush the records\n * asynchronously.\n *\n * Note that `apply()` indicates that the CVR metadata associated with\n * the `rowRecords` was successfully committed, which essentially means\n * that this process has the unconditional right (and responsibility) of\n * following up with a flush of the `rowRecords`. In particular, the\n * commit of row records are not conditioned on the version or ownership\n * columns of the `cvr.instances` row.\n */\n async apply(\n rowRecords: Map<RowID, RowRecord | null>,\n rowsVersion: CVRVersion,\n flushed: boolean,\n ): Promise<number> {\n const cache = await this.#ensureLoaded();\n for (const [id, row] of rowRecords.entries()) {\n if (row === null || row.refCounts === null) {\n cache.delete(id);\n } else {\n cache.set(id, row);\n }\n if (!flushed) {\n this.#pending.set(id, row);\n }\n }\n this.#pendingRowsVersion = rowsVersion;\n // Initiate a flush if not already flushing.\n if (!flushed && this.#flushing === null) {\n this.#flushing = resolver();\n // The #flush() method handles propagating errors to #failService.\n // Attach a rejection handler to this promise to avoid unhandled\n // rejections.\n this.#flushing.promise.catch(() => {});\n this.#setTimeout(() => this.#flush(), 0);\n }\n return cache.size;\n }\n\n async #flush() {\n const flushing = must(this.#flushing);\n try {\n while (this.#pendingRowsVersion !== this.#flushedRowsVersion) {\n const start = performance.now();\n\n const {rows, rowsVersion} = await runTx(\n this.#db,\n tx => {\n // Note: This code block is synchronous, guaranteeing that the\n // #pendingRowsVersion is consistent with the #pending rows.\n const rows = this.#pending.size;\n const rowsVersion = must(this.#pendingRowsVersion);\n // Awaiting all of the individual statements incurs too much\n // overhead. Instead, just catch and log exception(s); the outer\n // transaction will properly fail.\n void Promise.all(\n this.executeRowUpdates(tx, rowsVersion, this.#pending, 'force'),\n ).catch(e => this.#lc.error?.(`error flushing cvr rows`, e));\n\n this.#pending.clear();\n return {rows, rowsVersion};\n },\n {mode: Mode.READ_COMMITTED},\n );\n const elapsed = performance.now() - start;\n this.#lc.info?.(\n `flushed ${rows} rows@${versionString(rowsVersion)} (${elapsed} ms)`,\n );\n this.#recordAsyncFlushStats(rows, elapsed);\n this.#flushedRowsVersion = rowsVersion;\n // Note: apply() may have called while the transaction was committing,\n // which will result in looping to commit the next #pendingRowsVersion.\n }\n this.#lc.info?.(\n `up to date rows@${versionToNullableCookie(this.#flushedRowsVersion)}`,\n );\n flushing.resolve();\n this.#flushing = null;\n } catch (e) {\n this.#lc.info?.(`row record flush failed`, e);\n flushing.reject(e);\n this.#failService(e);\n }\n }\n\n hasPendingUpdates() {\n return this.#flushing !== null;\n }\n\n /**\n * Returns a promise that resolves when all outstanding row-records\n * have been committed.\n */\n flushed(lc: LogContext): Promise<void> {\n if (this.#flushing) {\n lc.debug?.('awaiting pending row flush');\n return this.#flushing.promise;\n }\n return promiseVoid;\n }\n\n clear() {\n // Note: Only the #cache is cleared. #pending updates, on the other hand,\n // comprise canonical (i.e. already flushed) data and must be flushed\n // even if the snapshot of the present state (the #cache) is cleared.\n this.#cache = undefined;\n }\n\n async *catchupRowPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n excludeQueryHashes: string[] = [],\n ): AsyncGenerator<RowsRow[], void, undefined> {\n if (cmpVersions(afterVersion, upToCVR.version) >= 0) {\n return;\n }\n\n const startMs = Date.now();\n const start = afterVersion ? versionString(afterVersion) : '';\n const end = versionString(upToCVR.version);\n lc.debug?.(`scanning row patches for clients from ${start}`);\n\n // Before accessing the CVR db, pending row records must be flushed.\n // Note that because catchupRowPatches() is called from within the\n // view syncer lock, this flush is guaranteed to complete since no\n // new CVR updates can happen while the lock is held.\n await this.flushed(lc);\n const flushMs = Date.now() - startMs;\n\n const reader = new TransactionPool(lc, {mode: Mode.READONLY}).run(this.#db);\n try {\n // Verify that we are reading the right version of the CVR.\n await reader.processReadTask(tx =>\n checkVersion(tx, this.#schema, this.#cvrID, current),\n );\n\n const {query} = await reader.processReadTask(tx => {\n const query =\n excludeQueryHashes.length === 0\n ? tx<RowsRow[]>`SELECT * FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`\n : // Exclude rows that were already sent as part of query hydration.\n tx<RowsRow[]>`SELECT * FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}\n AND (\"refCounts\" IS NULL OR NOT \"refCounts\" ?| ${excludeQueryHashes})`;\n return {query};\n });\n\n yield* query.cursor(10000);\n } finally {\n reader.setDone();\n }\n\n const totalMs = Date.now() - startMs;\n lc.info?.(\n `finished row catchup (flush: ${flushMs} ms, total: ${totalMs} ms)`,\n );\n }\n\n executeRowUpdates(\n tx: PostgresTransaction,\n version: CVRVersion,\n rowUpdates: Map<RowID, RowRecord | null>,\n mode: 'allow-defer' | 'force',\n lc = this.#lc,\n ): PendingQuery<Row[]>[] {\n if (\n mode === 'allow-defer' &&\n // defer if pending rows are being flushed\n (this.#flushing !== null ||\n // or if the new batch is above the limit.\n rowUpdates.size > this.#deferredRowFlushThreshold)\n ) {\n return [];\n }\n const rowsVersion = {\n clientGroupID: this.#cvrID,\n version: versionString(version),\n };\n const pending: PendingQuery<Row[]>[] = [\n tx`INSERT INTO ${this.#cvr('rowsVersion')} ${tx(rowsVersion)}\n ON CONFLICT (\"clientGroupID\") \n DO UPDATE SET ${tx(rowsVersion)}`,\n ];\n\n const rowRecordRows: RowsRow[] = [];\n for (const [id, row] of rowUpdates.entries()) {\n if (row === null) {\n pending.push(\n tx`\n DELETE FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"schema\" = ${id.schema}\n AND \"table\" = ${id.table}\n AND \"rowKey\" = ${id.rowKey}\n `,\n );\n } else {\n rowRecordRows.push(rowRecordToRowsRow(this.#cvrID, row));\n }\n }\n if (rowRecordRows.length) {\n pending.push(\n tx`\n INSERT INTO ${this.#cvr('rows')}(\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n ) SELECT\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n FROM json_to_recordset(${rowRecordRows}) AS x(\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT,\n \"patchVersion\" TEXT,\n \"refCounts\" JSONB\n ) ON CONFLICT (\"clientGroupID\", \"schema\", \"table\", \"rowKey\")\n DO UPDATE SET \"rowVersion\" = excluded.\"rowVersion\",\n \"patchVersion\" = excluded.\"patchVersion\",\n \"refCounts\" = excluded.\"refCounts\"\n `,\n );\n lc.info?.(\n `flushing ${rowUpdates.size} rows (${rowRecordRows.length} inserts, ${\n rowUpdates.size - rowRecordRows.length\n } deletes)`,\n );\n }\n return pending;\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,11 +3,11 @@ import { parse } from "../../../../shared/src/valita.js";
|
|
|
3
3
|
import { stringify } from "../../../../shared/src/bigint-json.js";
|
|
4
4
|
import { Database } from "../../../../zqlite/src/db.js";
|
|
5
5
|
import { fromSQLiteTypes } from "../../../../zqlite/src/table-source.js";
|
|
6
|
-
import
|
|
6
|
+
import "../replicator/schema/constants.js";
|
|
7
7
|
import { normalizedKeyOrder } from "../../types/row-key.js";
|
|
8
8
|
import { changeLogEntrySchema } from "../replicator/schema/change-log.js";
|
|
9
|
-
import "../replicator/schema/constants.js";
|
|
10
9
|
import { getReplicationState } from "../replicator/schema/replication-state.js";
|
|
10
|
+
import { StatementRunner } from "../../db/statements.js";
|
|
11
11
|
import { id } from "../../types/sql.js";
|
|
12
12
|
//#region ../zero-cache/src/services/view-syncer/snapshotter.ts
|
|
13
13
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-syncer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/view-syncer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAcjD,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,yDAAyD,CAAC;AACzG,OAAO,KAAK,EAEV,qBAAqB,EACtB,MAAM,0CAA0C,CAAC;AAElD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,iDAAiD,CAAC;AAC1F,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,uCAAuC,CAAC;AAQtE,OAAO,KAAK,EAEV,gBAAgB,EACjB,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,8CAA8C,CAAC;
|
|
1
|
+
{"version":3,"file":"view-syncer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/view-syncer.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,14 +6,14 @@ import { ProtocolError, isProtocolError } from "../../../../zero-protocol/src/er
|
|
|
6
6
|
import { MAX_TTL_MS, clampTTL } from "../../../../zql/src/query/ttl.js";
|
|
7
7
|
import { stringify } from "../../../../shared/src/bigint-json.js";
|
|
8
8
|
import { manualSpan, startAsyncSpan, startSpan } from "../../../../otel/src/span.js";
|
|
9
|
-
import { randInt } from "../../../../shared/src/rand.js";
|
|
10
|
-
import { rowIDString } from "../../types/row-key.js";
|
|
11
9
|
import { ZERO_VERSION_COLUMN_NAME } from "../replicator/schema/constants.js";
|
|
12
|
-
import "../replicator/schema/replication-state.js";
|
|
13
10
|
import { transformAndHashQuery } from "../../auth/read-authorizer.js";
|
|
14
11
|
import { getOrCreateCounter, getOrCreateHistogram, getOrCreateUpDownCounter } from "../../observability/metrics.js";
|
|
12
|
+
import { rowIDString } from "../../types/row-key.js";
|
|
13
|
+
import "../replicator/schema/replication-state.js";
|
|
15
14
|
import { ResetPipelinesSignal } from "./snapshotter.js";
|
|
16
15
|
import "./pipeline-driver.js";
|
|
16
|
+
import { randInt } from "../../../../shared/src/rand.js";
|
|
17
17
|
import { Subscription } from "../../types/subscription.js";
|
|
18
18
|
import { CustomKeyMap } from "../../../../shared/src/custom-key-map.js";
|
|
19
19
|
import { ttlClockAsNumber, ttlClockFromNumber } from "./ttl-clock.js";
|
|
@@ -956,14 +956,14 @@ var ViewSyncerService = class {
|
|
|
956
956
|
parsedRow.contents = contents;
|
|
957
957
|
};
|
|
958
958
|
switch (type) {
|
|
959
|
-
case
|
|
959
|
+
case 0:
|
|
960
960
|
updateVersion(row);
|
|
961
961
|
parsedRow.refCounts[queryID]++;
|
|
962
962
|
break;
|
|
963
|
-
case
|
|
963
|
+
case 2:
|
|
964
964
|
updateVersion(row);
|
|
965
965
|
break;
|
|
966
|
-
case
|
|
966
|
+
case 1:
|
|
967
967
|
parsedRow.refCounts[queryID]--;
|
|
968
968
|
break;
|
|
969
969
|
default: unreachable(type);
|