@rocicorp/zero 1.12.0-canary.1 → 1.12.0-canary.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +18 -0
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +46 -20
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +4 -3
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +5 -2
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-protocol/src/inspect-down.d.ts +81 -81
- package/out/zero-protocol/src/poke.d.ts +6 -6
- package/out/zero-protocol/src/up.d.ts +60 -60
- package/out/zql/src/ivm/cap.js +2 -4
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/take.js +2 -4
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zqlite/src/database-storage.d.ts.map +1 -1
- package/out/zqlite/src/database-storage.js +38 -6
- package/out/zqlite/src/database-storage.js.map +1 -1
- package/out/zqlite/src/fetch-sql.d.ts +27 -0
- package/out/zqlite/src/fetch-sql.d.ts.map +1 -0
- package/out/zqlite/src/fetch-sql.js +112 -0
- package/out/zqlite/src/fetch-sql.js.map +1 -0
- package/out/zqlite/src/query-builder.d.ts +11 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +22 -7
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts +9 -2
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +11 -14
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-driver.js","names":["#tables","#pipelines","#rowSetSignatures","#changedRowSetSignatures","#lc","#snapshotter","#storage","#shardID","#logConfig","#config","#deferredWrites","#tableSpecs","#allTableNames","#costModels","#planWarningThresholds","#yieldThresholdMs","#abandoned","#pushCosts","#readCosts","#advanceTime","#conflictRowsDeleted","#deferredWritesFallbacks","#inspectorDelegate","#queryStats","#metricsDelegate","#recordAdvancePush","#initAndResetCommon","#destroyPipeline","#costBasedAdvanceIncomplete","#primaryKeys","#streamer","#replicaVersion","#permissions","#disableCorrelatedPredicatePushdown","#getSource","#createStorage","#trackRowSetSignatures","#addQueryImpl","#logQueryPipelineLifecycle","#ensureCostModelExistsIfEnabled","#advanceContext","#hydrateContext","#totalRowsRead","#resolveScalarSubqueries","#warnIgnoredScalarHints","#logPlanWarnings","#streamPushed","#removeQuery","#startCostBasedAdvance","#tablesRead","#advance","#abandon","#abandonInsteadOfReading","#reserveDeferredWrites","#checkAdvance","#cutOffQueries","#reserveSecondRow","#push","#holdPendingRows","#writeThrough","#logSlowAdvances","#recordPushCosts","#logAdvanceTimeout","#releaseDeferredWrites","#recordAdvanceStats","#shouldAdvanceYieldMaybeAbortAdvance","#queryForLog","#advancementLogFields","#shouldYield","#throwSlowCurrentChangeReset","#throwProjectedAdvancementReset","#pushedRowChanges","#input","#queryHash","#transformationHash","#queryName","#output"],"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 {getOrInsertComputed} from '../../../../shared/src/map.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {assignProperty} from '../../../../shared/src/objects.ts';\nimport {randInt} from '../../../../shared/src/rand.ts';\nimport type {Writable} from '../../../../shared/src/writable.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 throwOutput,\n type FetchRequest,\n type Input,\n type InputBase,\n type Output,\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 {Stream} from '../../../../zql/src/ivm/stream.ts';\nimport type {ConnectionCostModel} from '../../../../zql/src/planner/planner-connection.ts';\nimport type {\n PlanWarning,\n PlanWarningThresholds,\n} from '../../../../zql/src/planner/planner-warnings.ts';\nimport {MeasurePushOperator} from '../../../../zql/src/query/measure-push-operator.ts';\nimport type {\n MetricMap,\n MetricsDelegate,\n} from '../../../../zql/src/query/metrics-delegate.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 type IgnoredScalarHint,\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 {LogThrottle} from '../../observability/log-throttle.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\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 {DeferredWritesBudget} from './deferred-writes-budget.ts';\nimport {planWarningMessage} from './plan-warnings.ts';\nimport {\n PushCostEstimator,\n ReadCostEstimator,\n shouldRebuildInsteadOfReading,\n shouldStartOnRebuild,\n} from './push-cost.ts';\nimport {\n PushCutoff,\n type QueryPushProgress,\n type ShouldCutOffPush,\n} from './push-cutoff.ts';\nimport {queryShape, type QueryShape} from './query-shape.ts';\nimport type {QueryStats} from './query-stats.ts';\nimport {rowIDSignatureUnit} from './row-set-signature.ts';\nimport type {ChangeLogCounts, 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 hydrationRowCount: number;\n readonly hydrationRowsRead: number;\n readonly planWarnings: readonly PlanWarning[];\n readonly hydrationReason: PipelineHydrationReason;\n readonly pipelineRunID: string;\n readonly pipelineReadyAtMs: number;\n readonly transformedAst: AST;\n readonly originalAst: AST;\n /** The {@link queryShape} of {@link originalAst}. */\n readonly shape: QueryShape;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n readonly companions: readonly CompanionPipeline[];\n /** The tables that the pipeline and its companions read. */\n readonly tables: ReadonlySet<string>;\n};\n\nexport type QueryInfo = {\n readonly transformedAst: AST;\n readonly originalAst?: AST | undefined;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\nexport type HydrationStats = {\n /** The rows the hydration output. */\n readonly rowCount: number;\n /**\n * The rows the hydration read from the replica, including rows that were\n * then filtered out, e.g. by a filter that could not be pushed to SQLite or\n * by an EXISTS that did not match. Much greater than {@link rowCount} means\n * the query does a lot of work for the rows it returns.\n */\n readonly rowsRead: number;\n /** What the planner warned about the plan it chose for the query. */\n readonly planWarnings: readonly PlanWarning[];\n /** The {@link queryShape} of the query, computed when it was hydrated. */\n readonly shape: QueryShape;\n};\n\ntype QueryLogInfo = {\n readonly queryHash: string;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\ntype QueryPipelineLifecycleEvent =\n | 'query-pipeline-hydrate-start'\n | 'query-pipeline-hydrate-finish'\n | 'query-pipeline-hydrate-failed'\n | 'query-pipeline-hydrate-aborted'\n | 'query-pipeline-stop';\n\nexport type PipelineHydrationReason =\n | 'query-set-sync'\n | 'unchanged-query-rehydrate';\n\ntype PipelineStopReason =\n | 'replace-query'\n | 'remove-query'\n | 'abandon-advance'\n | 'reset'\n | 'destroy';\n\ntype QueryPipelineLifecycleLog = {\n readonly zeroEvent: QueryPipelineLifecycleEvent;\n readonly pipelineRunID: string;\n readonly queryHash: string;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n readonly hydrationReason?: PipelineHydrationReason | undefined;\n readonly stopReason?: PipelineStopReason | undefined;\n readonly hydrationTimeMs?: number | undefined;\n readonly hydrationRowCount?: number | undefined;\n readonly hydrationRowsRead?: number | undefined;\n readonly pipelineLifetimeMs?: number | undefined;\n /**\n * For a pipeline that an advancement abandoned (`stopReason` is\n * `abandon-advance`): why, and what the decision was made from. The\n * hydration time that these are compared with is `hydrationTimeMs`.\n */\n readonly abandonReason?: AbandonedQuery['reason'] | undefined;\n readonly estimatedPushMs?: number | undefined;\n readonly pushTimeMs?: number | undefined;\n readonly pushedChanges?: number | undefined;\n /**\n * `debug` for the events of a query that an advancement hydrates again.\n * That can be every query of a client group in every batch of changes,\n * which is far more often than anything else that is logged at `info`.\n * What is logged at `info` about those is by query shape, by the\n * {@link QueryStats}, and by client group, by the view syncer.\n */\n readonly level?: 'info' | 'debug' | undefined;\n};\n\nexport type AdvancementProgress = {\n /** Raw ChangeLog entries read, including skipped entries and the current change. */\n readonly changesScanned: number;\n /** Yielded source changes whose push processing completed. */\n readonly changesProcessed: number;\n /** Raw entries omitted by the snapshot diff. */\n readonly changesSkipped: number;\n};\n\n/**\n * A query whose pipeline was removed by a {@link CostBasedAdvance}, either\n * before the advancement's changes were pushed or partway through them\n * (`'cutoff'`). Before, it is because pushing them to the query was\n * estimated to cost more than hydrating it again (`'estimate'`), or because\n * reading them was, for the queries that were left to push them to\n * (`'read'`). The row changes it output before then have been returned by\n * the advancement. To bring the query to the advancement's\n * version, hydrate it again with {@link PipelineDriver.addQuery}, passing its\n * `originalAst`, once the advancement's changes have been iterated over in\n * their entirety.\n */\nexport type AbandonedQuery = QueryPushProgress & {\n readonly reason: 'estimate' | 'read' | 'cutoff';\n /** What pushing the advancement's changes to it was estimated to cost. */\n readonly estimatedPushMs?: number | undefined;\n readonly transformationHash: string;\n readonly transformedAst: AST;\n /** The AST the query was added with. */\n readonly originalAst: AST;\n readonly queryName?: string | undefined;\n};\n\n/**\n * Bounds the cost of an advancement query by query, by hydrating a query\n * again when that is cheaper than pushing the advancement's changes through\n * its pipeline. This is an alternative to the client group's advancement\n * time limit, which does not apply to an advancement that is given this.\n * Such an advancement is never aborted for taking too long.\n */\nexport type CostBasedAdvance = {\n /**\n * Between changes, the pipeline of each query that this says to stop\n * pushing to is removed (see {@link PushCutoff}).\n */\n readonly shouldCutOff: ShouldCutOffPush;\n /**\n * Before any change is pushed, the pipelines of the queries that are\n * estimated to be cheaper to hydrate again than to push the advancement's\n * changes to are removed (see {@link shouldStartOnRebuild}). This is how\n * many times cheaper.\n */\n readonly rebuildMargin: number;\n};\n\n/** How an estimate of a query's push time compared with the actual time. */\nexport type PushEstimate = {\n readonly queryID: string;\n readonly estimatedPushMs: number;\n readonly pushTimeMs: number;\n};\n\ntype AdvancementLogFields = AdvancementProgress & {\n readonly advancementTimeMs: number;\n readonly queryPushTimeMs: number;\n readonly outsideQueryPushTimeMs: number;\n readonly totalHydrationTimeMs: number;\n readonly changesCompleted: number;\n};\n\ntype AdvanceContext = {\n readonly timer: Timer;\n readonly progress: {changesProcessed: number};\n readonly diff: SnapshotDiff;\n /** Raw entries completed, including skipped entries but excluding an in-flight push. */\n completedChanges: number;\n readonly totalHydrationTimeMs: number;\n readonly numChanges: number;\n /**\n * The rows reserved from the {@link DeferredWritesBudget} while the\n * advancement's changes are held in memory.\n */\n reservedRows: number | undefined;\n /** The bytes the advancement has added to the budget's held bytes. */\n heldBytes: number;\n currentChangeStartMs: number | undefined;\n pos: number;\n /** The table of the change being pushed. */\n currentTable: string | undefined;\n /** The processing time of each query's pushes, by query ID. */\n readonly queryStats: Map<string, QueryAdvanceStats>;\n /**\n * How the advancement is bounded: query by query if set, and otherwise by\n * the client group's advancement time limit.\n */\n readonly costBased: CostBasedAdvanceContext | undefined;\n pushTimeMs: number;\n stage: 'diff' | 'push' | 'between';\n};\n\n/** The state of a {@link CostBasedAdvance}. */\ntype CostBasedAdvanceContext = {\n readonly cutoff: PushCutoff;\n /** The entries of the change log since the previous snapshot. */\n readonly entries: number;\n /** The estimated push times of the queries that started on push. */\n readonly estimatedPushMs: ReadonlyMap<string, number>;\n readonly abandoned: AbandonedQuery[];\n readonly estimates: PushEstimate[];\n};\n\n/**\n * Whether the rows an advancement holds in memory fit in the budget, or why\n * they do not: the bytes held, a change that needed more rows than were left\n * to reserve, or more rows held than reserved.\n */\ntype HeldRows = 'fits' | 'bytes' | 'rows' | 'row-overrun';\n\ntype QueryAdvanceStats = {\n /** The time spent processing pushes to the query. */\n timeMs: number;\n /** The number of changes pushed to the query. */\n changes: number;\n /** The position of the last change pushed to the query. */\n lastPos: number;\n /** {@link timeMs} by the table of the change pushed. */\n readonly timeMsByTable: Map<string, number>;\n};\n\n/** The most queries a log of an advancement timeout lists. */\nconst ADVANCE_TIMEOUT_LOG_MAX_QUERIES = 3;\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;\nconst MIN_PROJECTED_ADVANCEMENT_SAMPLE_CHANGES = 8;\nconst PROJECTED_ADVANCEMENT_SAMPLE_FRACTION = 0.25;\nconst MAX_PROJECTED_ADVANCEMENT_SAMPLE_CHANGES = 50;\nconst MIN_PROJECTED_ADVANCEMENT_SAMPLE_MS = 5;\nconst MIN_PROJECTED_ADVANCEMENT_CHANGES = 16;\nconst PROJECTED_ADVANCEMENT_RESET_MULTIPLIER = 1.5;\nconst LATE_ADVANCEMENT_FINISH_PROGRESS = 0.8;\n\n/**\n * The planner warns about a query each time it is planned, i.e. for every\n * client group that hydrates it, and the warnings only change when the data\n * does. So they are logged at most once per query shape per this window,\n * per process.\n */\nconst PLAN_WARNING_LOG_WINDOW_MS = 60 * 60_000;\n\n// Shared by all PipelineDrivers in the process, so that a query shape is\n// throttled across client groups.\nconst planWarningLogThrottle = new LogThrottle({\n windowMs: PLAN_WARNING_LOG_WINDOW_MS,\n});\n\n/**\n * Slow advancements of a query shape, and advancement timeouts led by it,\n * are logged at most once per this window per process.\n */\nconst SLOW_ADVANCE_LOG_WINDOW_MS = 5 * 60_000;\n\nconst slowAdvanceLogThrottle = new LogThrottle({\n windowMs: SLOW_ADVANCE_LOG_WINDOW_MS,\n});\n\nfunction randomID() {\n return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);\n}\n\nfunction projectedAdvancementTimeMs(\n elapsedMs: number,\n processedChanges: number,\n numChanges: number,\n): number | undefined {\n if (processedChanges <= 0 || numChanges <= 0) {\n return undefined;\n }\n return (elapsedMs / processedChanges) * numChanges;\n}\n\nfunction advancementResetTimeLimitMs(totalHydrationTimeMs: number): number {\n return Math.max(totalHydrationTimeMs, 1);\n}\n\nfunction minProjectedAdvancementSampleChanges(numChanges: number): number {\n return Math.max(\n MIN_PROJECTED_ADVANCEMENT_SAMPLE_CHANGES,\n Math.min(\n MAX_PROJECTED_ADVANCEMENT_SAMPLE_CHANGES,\n Math.ceil(numChanges * PROJECTED_ADVANCEMENT_SAMPLE_FRACTION),\n ),\n );\n}\n\nfunction canProjectAdvancement(\n elapsedMs: number,\n projectedTotalTimeMs: number | undefined,\n processedChanges: number,\n numChanges: number,\n): boolean {\n return (\n projectedTotalTimeMs !== undefined &&\n numChanges >= MIN_PROJECTED_ADVANCEMENT_CHANGES &&\n processedChanges >= minProjectedAdvancementSampleChanges(numChanges) &&\n elapsedMs >= MIN_PROJECTED_ADVANCEMENT_SAMPLE_MS\n );\n}\n\nfunction shouldResetProjectedAdvancement(\n elapsedMs: number,\n projectedTotalTimeMs: number,\n totalHydrationTimeMs: number,\n): boolean {\n // The time already spent is spent whether or not the advancement is reset,\n // so a reset only saves the rest of it.\n return (\n projectedTotalTimeMs - elapsedMs >\n advancementResetTimeLimitMs(totalHydrationTimeMs) *\n PROJECTED_ADVANCEMENT_RESET_MULTIPLIER\n );\n}\n\nfunction shouldFinishLateAdvancement(\n processedChanges: number,\n numChanges: number,\n): boolean {\n return (\n numChanges > 0 &&\n processedChanges / numChanges >= LATE_ADVANCEMENT_FINISH_PROGRESS\n );\n}\n\nfunction shouldResetSlowCurrentChange(\n currentChangeElapsedMs: number,\n totalHydrationTimeMs: number,\n): boolean {\n return (\n currentChangeElapsedMs > MIN_ADVANCEMENT_TIME_LIMIT_MS &&\n currentChangeElapsedMs > advancementResetTimeLimitMs(totalHydrationTimeMs)\n );\n}\n\nfunction formatAdvancementBreakdown(\n advanceContext: AdvanceContext,\n elapsed: number,\n): string {\n const {diff, pushTimeMs, stage, currentChangeStartMs, currentTable} =\n advanceContext;\n const currentPushMs =\n stage === 'push' && currentChangeStartMs !== undefined\n ? Math.max(0, elapsed - currentChangeStartMs)\n : 0;\n const totalPushMs = pushTimeMs + currentPushMs;\n const stats = diff.stats;\n const diffTimeMs = stats?.totalDiffMs ?? 0;\n const clMs = stats\n ? ` [changelog: ${stats.changeLogReadMs.toFixed(1)}ms, parse: ${stats.parseMs.toFixed(1)}ms, rows: ${stats.rowReadMs.toFixed(1)}ms]`\n : '';\n return (\n `stage: ${stage}, diff: ${diffTimeMs.toFixed(1)}ms${clMs}, ` +\n `push: ${totalPushMs.toFixed(1)}ms` +\n (currentTable ? `, table: ${currentTable}` : '')\n );\n}\n\n/**\n * Manages the state of IVM pipelines for a given ViewSyncer (i.e. client group).\n */\nexport class PipelineDriver {\n // Retain sources until reset or destroy so getRow can serve catch-up reads.\n readonly #tables = new Map<string, TableSource>();\n // Query id to pipeline\n readonly #pipelines = new Map<string, Pipeline>();\n /**\n * XOR signature of the set of rows currently attached to each active\n * query, maintained as RowChanges are yielded from {@link addQuery} and\n * {@link advance}. ADDs / REMOVEs XOR the row's unit in (XOR is\n * self-inverse, so one op serves both directions); EDITs are no-ops.\n * Hydration starts the query's entry at `0n` (the empty row set), so every\n * hydrated query has one, even if it has no rows. A hydration that does not\n * finish removes it.\n */\n readonly #rowSetSignatures = new Map<string, bigint>();\n /**\n * IDs of the queries whose {@link #rowSetSignatures} entry changed since the\n * last {@link clearChangedRowSetSignatures}, so that the CVR flush only has\n * to look at those instead of every query.\n */\n readonly #changedRowSetSignatures = new Set<string>();\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 #deferredWrites: DeferredWritesBudget | undefined;\n readonly #tableSpecs = new Map<string, LiteAndZqlSpec>();\n readonly #allTableNames = new Set<string>();\n readonly #costModels: WeakMap<Database, ConnectionCostModel> | undefined;\n readonly #planWarningThresholds: PlanWarningThresholds | undefined;\n readonly #yieldThresholdMs: () => number;\n #streamer: Streamer | null = null;\n /** The row changes of the push in progress, until #push yields them. */\n #pushedRowChanges: RowChange[] | null = null;\n #hydrateContext: HydrateContext | null = null;\n #advanceContext: AdvanceContext | null = null;\n /**\n * Whether a {@link CostBasedAdvance} has been set up and has not\n * completed. It may already have removed pipelines, but the sources only\n * move to the new snapshot when the iteration over its changes completes,\n * so nothing can be hydrated until then. If the iteration is given up on\n * or throws, they never do, and nothing can be hydrated until the\n * pipelines are {@link reset()}.\n */\n #costBasedAdvanceIncomplete = false;\n /**\n * The queries abandoned by a {@link CostBasedAdvance} that have not been\n * added again since.\n */\n readonly #abandoned = new Map<string, AbandonedQuery>();\n readonly #pushCosts = new PushCostEstimator();\n readonly #readCosts = new ReadCostEstimator();\n #replicaVersion: string | null = null;\n #primaryKeys: Map<string, PrimaryKey> | null = null;\n #permissions: LoadedPermissions | null = null;\n\n readonly #advanceTime = getOrCreateLatencyHistogram(\n 'sync',\n 'ivm.advance-time',\n 'Time to advance all queries for a given client group in response to a single change.',\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 #deferredWritesFallbacks = getOrCreateCounter(\n 'sync',\n 'ivm.deferred-writes-fallbacks',\n 'Number of advancements written through to the replica snapshot because ' +\n 'their changes did not fit in the deferred IVM writes budget, from the ' +\n 'start or partway (because of the bytes held, a change that needed ' +\n 'more rows than were left, or more rows held than reserved)',\n );\n\n readonly #inspectorDelegate: InspectorDelegate;\n readonly #queryStats: QueryStats | undefined;\n\n /**\n * Passes the pipelines' metrics on to the inspector, and accounts the time\n * spent pushing to each query to the advancement in progress.\n */\n readonly #metricsDelegate: MetricsDelegate = {\n addMetric: <K extends keyof MetricMap>(\n metric: K,\n value: number,\n ...args: MetricMap[K]\n ) => {\n this.#inspectorDelegate.addMetric(metric, value, ...args);\n if (metric === 'query-update-server') {\n this.#recordAdvancePush(args[0], value);\n }\n },\n };\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 deferredWrites?: DeferredWritesBudget,\n queryStats?: QueryStats | undefined,\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.#deferredWrites = deferredWrites;\n this.#inspectorDelegate = inspectorDelegate;\n this.#costModels = enablePlanner ? new WeakMap() : undefined;\n const planWarningThresholds = {\n rows: logConfig.planWarningRowThreshold,\n cost: logConfig.planWarningCostThreshold,\n };\n this.#planWarningThresholds =\n planWarningThresholds.rows > 0 || planWarningThresholds.cost > 0\n ? planWarningThresholds\n : undefined;\n this.#yieldThresholdMs = yieldThresholdMs;\n this.#queryStats = queryStats;\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 [queryID, pipeline] of this.#pipelines) {\n this.#pipelines.delete(queryID);\n this.#destroyPipeline(queryID, pipeline, 'reset');\n }\n this.#abandoned.clear();\n this.#costBasedAdvanceIncomplete = false;\n this.#tables.clear();\n this.#allTableNames.clear();\n this.#rowSetSignatures.clear();\n this.#changedRowSetSignatures.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 = buildRowKeys(\n clientSchema,\n this.#tableSpecs,\n this.#primaryKeys ?? undefined,\n );\n this.#primaryKeys = primaryKeys;\n this.#streamer ??= new Streamer(primaryKeys, this.#tableSpecs);\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 /**\n * Advances the snapshot to the head of the database without diffing the\n * change log, in preparation for hydrating queries at head.\n *\n * Throws a {@link ResetPipelinesSignal} if the change log records a\n * schema change since the previous snapshot. The table specs (and any\n * TableSources built from them) were computed at or before that snapshot\n * and are stale with respect to the new head, so the caller must\n * {@link reset()} before hydrating. ({@link advance()} detects this when\n * the diff encounters the RESET op; this path skips the diff and so must\n * check explicitly.)\n *\n * @param releasePreviousSnapshot Whether to release the snapshot that the\n * driver was at, which is otherwise left open until the next\n * advance (see `Snapshotter.releasePrevious()`).\n */\n advanceWithoutDiff(releasePreviousSnapshot = false): string {\n const {prev, curr} = this.#snapshotter.advanceWithoutDiff();\n if (curr.schemaChangedSince(prev.version)) {\n throw new ResetPipelinesSignal(\n `schema changed between ${prev.version} and ${curr.version}`,\n 'schema-change',\n );\n }\n for (const table of this.#tables.values()) {\n table.setDB(curr.db.db);\n }\n if (releasePreviousSnapshot) {\n this.#snapshotter.releasePrevious();\n }\n return curr.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 for (const [queryID, pipeline] of this.#pipelines) {\n this.#pipelines.delete(queryID);\n this.#destroyPipeline(queryID, pipeline, 'destroy');\n }\n this.#tables.clear();\n this.#rowSetSignatures.clear();\n this.#changedRowSetSignatures.clear();\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 /**\n * The queries whose pipelines a {@link CostBasedAdvance} removed and that\n * have not been hydrated again since, by query ID. They are not in\n * {@link queries()}. A query whose hydration is started but does not\n * finish is still one of them.\n */\n abandonedQueries(): ReadonlyMap<string, AbandonedQuery> {\n return this.#abandoned;\n }\n\n /**\n * Stats from the hydration of the pipeline for `queryID`, or `undefined` if\n * the query has no pipeline.\n */\n hydrationStats(queryID: string): HydrationStats | undefined {\n const pipeline = this.#pipelines.get(queryID);\n return pipeline\n ? {\n rowCount: pipeline.hydrationRowCount,\n rowsRead: pipeline.hydrationRowsRead,\n planWarnings: pipeline.planWarnings,\n shape: pipeline.shape,\n }\n : undefined;\n }\n\n /**\n * Logs what the planner warned about the plan it chose for `query`, at\n * most once per query shape per {@link PLAN_WARNING_LOG_WINDOW_MS}.\n */\n #logPlanWarnings(\n shape: QueryShape,\n {queryHash, transformationHash, queryName}: QueryLogInfo,\n warnings: readonly PlanWarning[],\n ): void {\n if (!this.#lc.warn) {\n return;\n }\n const suppressed = planWarningLogThrottle.admit(\n `${queryName ?? ''}:${shape.hash}`,\n );\n if (suppressed === undefined) {\n return;\n }\n const messages = warnings.map(planWarningMessage);\n this.#lc.warn(\n `Query plan warning${queryName === undefined ? '' : ` for ${queryName}`}: ` +\n messages.join(' '),\n {\n zeroEvent: 'query-plan-warning',\n queryHash,\n transformationHash,\n ...(queryName !== undefined && {queryName}),\n queryShape: shape.hash,\n warnings,\n ...(suppressed > 0 && {suppressedSinceLastLog: suppressed}),\n zql: shape.zql,\n },\n );\n }\n\n #totalRowsRead(): number {\n let total = 0;\n for (const table of this.#tables.values()) {\n total += table.rowsRead;\n }\n return total;\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 /**\n * The rows the sources hold in memory for the advancement in progress.\n * The advancement's reservation from the {@link DeferredWritesBudget}\n * bounds this.\n */\n get pendingRows(): number {\n let total = 0;\n for (const source of this.#tables.values()) {\n total += source.pendingRows;\n }\n return total;\n }\n\n /**\n * The estimated bytes of {@link pendingRows}, which are added to the\n * {@link DeferredWritesBudget}'s held bytes as the advancement goes.\n */\n get pendingBytes(): number {\n let total = 0;\n for (const source of this.#tables.values()) {\n total += source.pendingBytes;\n }\n return total;\n }\n\n #logQueryPipelineLifecycle({\n zeroEvent,\n pipelineRunID,\n queryHash,\n transformationHash,\n queryName,\n hydrationReason,\n stopReason,\n hydrationTimeMs,\n hydrationRowCount,\n hydrationRowsRead,\n pipelineLifetimeMs,\n abandonReason,\n estimatedPushMs,\n pushTimeMs,\n pushedChanges,\n level = 'info',\n }: QueryPipelineLifecycleLog): void {\n let lc = this.#lc\n .withContext('zeroEvent', zeroEvent)\n .withContext('pipelineRunID', pipelineRunID)\n .withContext('queryHash', queryHash)\n .withContext('transformationHash', transformationHash);\n if (queryName !== undefined) {\n lc = lc.withContext('queryName', queryName);\n }\n if (hydrationReason !== undefined) {\n lc = lc.withContext('hydrationReason', hydrationReason);\n }\n if (stopReason !== undefined) {\n lc = lc.withContext('stopReason', stopReason);\n }\n if (hydrationTimeMs !== undefined) {\n lc = lc.withContext('hydrationTimeMs', hydrationTimeMs);\n }\n if (hydrationRowCount !== undefined) {\n lc = lc.withContext('hydrationRowCount', hydrationRowCount);\n }\n if (hydrationRowsRead !== undefined) {\n lc = lc.withContext('hydrationRowsRead', hydrationRowsRead);\n }\n if (pipelineLifetimeMs !== undefined) {\n lc = lc.withContext('pipelineLifetimeMs', pipelineLifetimeMs);\n }\n if (abandonReason !== undefined) {\n lc = lc.withContext('abandonReason', abandonReason);\n }\n if (estimatedPushMs !== undefined) {\n lc = lc.withContext('estimatedPushMs', estimatedPushMs);\n }\n if (pushTimeMs !== undefined) {\n lc = lc.withContext('pushTimeMs', pushTimeMs);\n }\n if (pushedChanges !== undefined) {\n lc = lc.withContext('pushedChanges', pushedChanges);\n }\n lc[level]?.('query pipeline lifecycle');\n }\n\n /**\n * A `{scalar: true}` that cannot be honored degrades silently to a plain\n * EXISTS, so the author gets none of the plan they asked for and no signal\n * that they didn't. Say so, with the unique keys that were actually\n * available — the client schema knows only primary keys, so this is the only\n * place the advice can be correct.\n */\n #warnIgnoredScalarHints(queryID: string, hints: IgnoredScalarHint[]): void {\n for (const {table, uniqueKeys} of hints) {\n const keys = uniqueKeys.map(k => `(${k.join(', ')})`).join(', ');\n this.#lc.warn?.(\n `Ignoring {scalar: true} on the \"${table}\" subquery of query ` +\n `${queryID}: it does not constrain every column of any unique key ` +\n `${keys.length > 0 ? `[${keys}]` : '(none on this table)'} to a ` +\n `literal with \"=\", so it is not provably limited to one row. ` +\n `The gate runs as a plain EXISTS.`,\n );\n }\n }\n\n #disableCorrelatedPredicatePushdown(): boolean {\n return this.#config?.enableCorrelatedPredicatePushdown === false;\n }\n\n #resolveScalarSubqueries(\n ast: AST,\n tables: Set<string>,\n ): {\n ast: AST;\n companions: CompanionSubquery[];\n companionInputs: Input[];\n ignoredScalarHints: IgnoredScalarHint[];\n } {\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 enableNotExists: true, // Server-side can handle NOT EXISTS\n disableCorrelatedPredicatePushdown:\n this.#disableCorrelatedPredicatePushdown(),\n getSource: name => {\n tables.add(name);\n return this.#getSource(name);\n },\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 // Tracked before it is fetched so that a failure in this or a later\n // subquery can tear it down below. A companion with no result is kept\n // alive too: it detects a future insert that creates the row.\n companionInputs.push(input);\n return fetchScalarValue(input, childField);\n };\n\n let resolved: AST;\n let companions: CompanionSubquery[];\n let ignoredScalarHints: IgnoredScalarHint[];\n try {\n ({\n ast: resolved,\n companions,\n ignoredScalarHints,\n } = resolveSimpleScalarSubqueries(ast, this.#tableSpecs, executor));\n } catch (e) {\n for (const input of companionInputs) {\n input.destroy();\n }\n throw e;\n }\n return {\n ast: resolved,\n companions,\n companionInputs,\n ignoredScalarHints,\n };\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 queryName?: string,\n hydrationReason: PipelineHydrationReason = 'query-set-sync',\n ): Iterable<RowChange | 'yield'> {\n return this.#trackRowSetSignatures(\n this.#addQueryImpl(\n transformationHash,\n queryID,\n query,\n timer,\n queryName,\n hydrationReason,\n ),\n );\n }\n\n *#addQueryImpl(\n transformationHash: string,\n queryID: string,\n query: AST,\n timer: Timer,\n queryName?: string,\n hydrationReason: PipelineHydrationReason = 'query-set-sync',\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before adding queries',\n );\n this.removeQuery(queryID, 'replace-query');\n const pipelineRunID = randomID();\n const shape = queryShape(query);\n // A query that an advancement abandoned is being hydrated again.\n const level = this.#abandoned.has(queryID) ? 'debug' : 'info';\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-hydrate-start',\n pipelineRunID,\n queryHash: queryID,\n transformationHash,\n queryName,\n hydrationReason,\n level,\n });\n const debugDelegate = runtimeDebugFlags.trackRowsVended\n ? new Debug(true)\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 assert(\n !this.#costBasedAdvanceIncomplete,\n 'Cannot hydrate before the advance has completed',\n );\n this.#hydrateContext = {\n timer,\n };\n // Hydration has the driver to itself, so the rows read by all of its\n // tables in the meantime are the rows read by this hydration. Tables\n // added by the hydration start from zero, which this also accounts for.\n const rowsReadAtStart = this.#totalRowsRead();\n let hydrationFinished = false;\n let hydrationFailed = false;\n let hydrationRowCount = 0;\n let planWarnings: readonly PlanWarning[] = [];\n const planWarningThresholds = this.#planWarningThresholds;\n // The inputs built so far, held outside the try so that a hydration that\n // does not finish (aborted by the consumer or failed) can tear them down.\n // Only a finished hydration hands them over to #pipelines.\n let builtInputs: Input[] = [];\n const tables = new Set<string>();\n try {\n // Start from the signature of the empty row set, so that a query that\n // hydrates to no rows has one too. Without it, rowSetSignature() would\n // report \"no pipeline\" and a stale signature in the CVR would never be\n // replaced, so drift would be detected again on every rehydration. The\n // finally block removes it if the hydration does not finish.\n this.#rowSetSignatures.set(queryID, 0n);\n this.#changedRowSetSignatures.add(queryID);\n const {\n ast: resolvedQuery,\n companions: companionMeta,\n companionInputs,\n ignoredScalarHints,\n } = this.#resolveScalarSubqueries(query, tables);\n builtInputs = [...companionInputs];\n\n this.#warnIgnoredScalarHints(queryID, ignoredScalarHints);\n\n const input = buildPipeline(\n resolvedQuery,\n {\n debug: debugDelegate,\n enableNotExists: true, // Server-side can handle NOT EXISTS\n disableCorrelatedPredicatePushdown:\n this.#disableCorrelatedPredicatePushdown(),\n enablePlannerAwarePushdown:\n this.#config?.enablePlannerAwarePushdown !== false,\n getSource: name => {\n tables.add(name);\n return this.#getSource(name);\n },\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput, _queryID: string): Input =>\n new MeasurePushOperator(\n new QueryFailureLoggingOperator(\n this.#lc,\n input,\n queryID,\n transformationHash,\n queryName,\n ),\n queryID,\n this.#metricsDelegate,\n 'query-update-server',\n ),\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n planWarnings: planWarningThresholds && {\n thresholds: planWarningThresholds,\n report: warnings => {\n planWarnings = warnings;\n },\n },\n },\n queryID,\n costModel,\n );\n builtInputs.push(input);\n if (planWarnings.length > 0) {\n this.#logPlanWarnings(\n shape,\n {queryHash: queryID, transformationHash, queryName},\n planWarnings,\n );\n }\n const schema = input.getSchema();\n input.setOutput({\n push: change => this.#streamPushed(queryID, schema, change),\n });\n\n // The client runs scalar gates as plain EXISTS, so the companions are\n // hydrated like the query: their rows, and the rows behind any EXISTS\n // nested in them, are synced too. Index -1 is the query's own pipeline.\n for (let i = -1; i < companionInputs.length; i++) {\n const pipeline = i < 0 ? input : companionInputs[i];\n for (const change of must(this.#streamer).streamNodes(\n queryID,\n i < 0 ? schema : companionStreamSchema(pipeline, companionMeta[i]),\n ChangeType.ADD,\n pipeline.fetch({}),\n )) {\n if (change !== 'yield') {\n hydrationRowCount++;\n }\n yield change;\n }\n }\n\n const hydrationTimeMs = timer.totalElapsed();\n const hydrationRowsRead = this.#totalRowsRead() - rowsReadAtStart;\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 = companionStreamSchema(companionInput, meta);\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 // A change behind an EXISTS nested in the subquery leaves the\n // value alone, but the client reads those rows too.\n return this.#streamPushed(queryID, companionSchema, change);\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 return this.#streamPushed(queryID, companionSchema, change);\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 const pipelineReadyAtMs = Date.now();\n this.#pipelines.set(queryID, {\n input,\n hydrationTimeMs,\n hydrationRowCount,\n hydrationRowsRead,\n planWarnings,\n hydrationReason,\n pipelineRunID,\n pipelineReadyAtMs,\n transformedAst: resolvedQuery,\n originalAst: query,\n shape,\n transformationHash,\n ...(queryName !== undefined && {queryName}),\n companions: liveCompanions,\n tables,\n });\n this.#abandoned.delete(queryID);\n hydrationFinished = true;\n this.#queryStats?.recordHydration(\n {queryName, shape},\n {\n outcome: 'finished',\n timeMs: hydrationTimeMs,\n rowCount: hydrationRowCount,\n rowsRead: hydrationRowsRead,\n planWarnings,\n },\n );\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-hydrate-finish',\n pipelineRunID,\n queryHash: queryID,\n transformationHash,\n queryName,\n hydrationReason,\n hydrationTimeMs,\n hydrationRowCount,\n hydrationRowsRead,\n level,\n });\n } catch (e) {\n hydrationFailed = true;\n this.#queryStats?.recordHydration(\n {queryName, shape},\n {outcome: 'failed', timeMs: timer.totalElapsed()},\n );\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-hydrate-failed',\n pipelineRunID,\n queryHash: queryID,\n transformationHash,\n queryName,\n hydrationReason,\n hydrationTimeMs: timer.totalElapsed(),\n hydrationRowCount,\n });\n logQueryFailure(\n this.#lc,\n {queryHash: queryID, transformationHash, queryName},\n 'query hydration failed',\n e,\n );\n throw e;\n } finally {\n if (!hydrationFinished && !hydrationFailed) {\n this.#queryStats?.recordHydration(\n {queryName, shape},\n {outcome: 'aborted', timeMs: timer.totalElapsed()},\n );\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-hydrate-aborted',\n pipelineRunID,\n queryHash: queryID,\n transformationHash,\n queryName,\n hydrationReason,\n hydrationTimeMs: timer.totalElapsed(),\n hydrationRowCount,\n });\n }\n if (!hydrationFinished) {\n for (const input of builtInputs) {\n input.destroy();\n }\n // Rows may already have been yielded through #trackRowSetSignatures,\n // and rowSetSignature() must not report a signature for a query\n // without an active pipeline.\n this.#rowSetSignatures.delete(queryID);\n this.#changedRowSetSignatures.delete(queryID);\n }\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(\n queryID: string,\n stopReason: PipelineStopReason = 'remove-query',\n ) {\n this.#removeQuery(queryID, stopReason);\n }\n\n /**\n * @param abandoned If an advancement is abandoning the query's pipeline,\n * the record of that, which is logged.\n */\n #removeQuery(\n queryID: string,\n stopReason: PipelineStopReason,\n abandoned?: AbandonedQuery,\n ) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline) {\n this.#pipelines.delete(queryID);\n this.#destroyPipeline(queryID, pipeline, stopReason, abandoned);\n }\n this.#rowSetSignatures.delete(queryID);\n this.#changedRowSetSignatures.delete(queryID);\n if (stopReason !== 'replace-query') {\n // A query that is being hydrated again stays abandoned until that\n // succeeds (see addQuery()).\n this.#abandoned.delete(queryID);\n }\n if (stopReason === 'remove-query') {\n this.#pushCosts.forget(queryID);\n }\n }\n\n #destroyPipeline(\n queryID: string,\n pipeline: Pipeline,\n stopReason: PipelineStopReason,\n abandoned?: AbandonedQuery,\n ): void {\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-stop',\n pipelineRunID: pipeline.pipelineRunID,\n queryHash: queryID,\n transformationHash: pipeline.transformationHash,\n queryName: pipeline.queryName,\n hydrationReason: pipeline.hydrationReason,\n stopReason,\n hydrationTimeMs: pipeline.hydrationTimeMs,\n hydrationRowCount: pipeline.hydrationRowCount,\n pipelineLifetimeMs: Date.now() - pipeline.pipelineReadyAtMs,\n ...(abandoned && {\n abandonReason: abandoned.reason,\n estimatedPushMs: abandoned.estimatedPushMs,\n pushTimeMs: abandoned.pushTimeMs,\n pushedChanges: abandoned.changes,\n level: 'debug',\n }),\n });\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n\n /**\n * Current XOR signature of the row-set attached to `queryID`, or\n * `undefined` if no pipeline for the query is currently active.\n * Maintained incrementally by {@link addQuery} and {@link advance}.\n */\n rowSetSignature(queryID: string): bigint | undefined {\n return this.#rowSetSignatures.get(queryID);\n }\n\n /**\n * IDs of the queries whose {@link rowSetSignature} changed (by an ADD or\n * REMOVE from {@link addQuery} or {@link advance}) since the last call to\n * {@link clearChangedRowSetSignatures}.\n */\n changedRowSetSignatures(): ReadonlySet<string> {\n return this.#changedRowSetSignatures;\n }\n\n /**\n * Called once the {@link changedRowSetSignatures} have been persisted.\n */\n clearChangedRowSetSignatures(): void {\n this.#changedRowSetSignatures.clear();\n }\n\n /**\n * Wraps an iterable of RowChanges, XORing each row's unit hash into the\n * query's signature (ADDs and REMOVEs share the same op; EDITs are no-ops).\n * Used to intercept the yield streams from {@link addQuery} and\n * {@link advance}.\n */\n *#trackRowSetSignatures(\n changes: Iterable<RowChange | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n for (const change of changes) {\n if (change !== 'yield' && change.type !== ChangeType.EDIT) {\n const cur = this.#rowSetSignatures.get(change.queryID) ?? 0n;\n const unit = rowIDSignatureUnit({\n schema: '',\n table: change.table,\n rowKey: change.rowKey as RowKey,\n });\n this.#rowSetSignatures.set(change.queryID, cur ^ unit);\n this.#changedRowSetSignatures.add(change.queryID);\n }\n yield change;\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 * @param costBased If given, the advancement is bounded query by query\n * instead (see {@link CostBasedAdvance}).\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 * @throws A {@link ResetPipelinesSignal} if `costBased` is given and the\n * changes include a schema change, a truncation or a change of\n * the permissions. It is thrown before any change is pushed or\n * any pipeline is removed, as the caller resets the pipelines\n * either way. Without `costBased`, it is iterating over the\n * `changes` that throws it, on getting to that change.\n */\n advance(\n timer: Timer,\n costBased?: CostBasedAdvance,\n ): {\n version: string;\n numChanges: number;\n changes: Iterable<RowChange | 'yield'>;\n /** Available throughout iteration and after completion or an abort. */\n progress: () => AdvancementProgress;\n /**\n * The queries whose pipelines the {@link CostBasedAdvance} has removed\n * so far. They can be hydrated again once `changes` has been iterated\n * over.\n */\n abandoned: () => readonly AbandonedQuery[];\n /**\n * How the estimates of the {@link CostBasedAdvance} compared with the\n * actual push times, for the queries that had an estimate and were\n * pushed all of the changes. Available once `changes` has been iterated\n * over.\n */\n estimates: () => readonly PushEstimate[];\n } {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before advancing',\n );\n let diff: SnapshotDiff;\n let context: CostBasedAdvanceContext | undefined;\n if (costBased === undefined) {\n diff = this.#snapshotter.advance(\n this.#tableSpecs,\n this.#allTableNames,\n this.#tables,\n // Sources skip changes that none of this client group's pipelines can\n // observe, so a `prev` they write to diverges from other groups'.\n // #advance() overrides this if it holds the changes in memory.\n 'divergent',\n );\n } else {\n const {prev, curr} = this.#snapshotter.advanceWithoutDiff();\n // The one count of the batch's changes: it sizes the batch, the push\n // costs are estimated from it, and it says if a batch whose queries\n // are all hydrated again has anything to read from the change log.\n const counts = curr.countChangesSince(prev.version);\n // A batch with a schema change, a truncation or a change of the\n // permissions ends in a reset of the pipelines, whatever is pushed\n // before the iteration gets to it. So nothing is.\n const reset = this.#snapshotter.pendingReset(this.#tableSpecs, counts);\n if (reset) {\n throw reset;\n }\n context = this.#startCostBasedAdvance(\n costBased,\n // Hydrating a query again only makes sense, and can only be told to\n // the clients as a new version, if there is something new.\n curr.version > prev.version,\n counts,\n );\n diff = this.#snapshotter.diff(\n this.#tableSpecs,\n this.#allTableNames,\n this.#tables,\n 'divergent',\n counts,\n // The pipelines that were just removed are hydrated again from the\n // new snapshot, so the changes of the tables that only they read\n // are not needed, and are left out of what is read.\n context.abandoned.length > 0 ? this.#tablesRead() : undefined,\n );\n this.#costBasedAdvanceIncomplete = true;\n }\n const {prev, curr, changes} = diff;\n this.#lc.debug?.(\n `advance ${prev.version} => ${curr.version}: ${changes} changes`,\n );\n\n const progress = {changesProcessed: 0};\n return {\n version: curr.version,\n numChanges: changes,\n changes: this.#trackRowSetSignatures(\n this.#advance(diff, timer, changes, progress, context),\n ),\n abandoned: () => context?.abandoned ?? [],\n estimates: () => context?.estimates ?? [],\n progress: () => ({\n changesScanned: diff.changesScanned,\n changesProcessed: progress.changesProcessed,\n changesSkipped: diff.changesSkipped,\n }),\n };\n }\n\n /** The tables that the pipelines read. */\n #tablesRead(): Set<string> {\n const tables = new Set<string>();\n for (const pipeline of this.#pipelines.values()) {\n for (const table of pipeline.tables) {\n tables.add(table);\n }\n }\n return tables;\n }\n\n /**\n * Removes the pipelines of the queries that are estimated to be cheaper to\n * hydrate again than to push the advancement's changes to: first each\n * query for what pushing costs it, and then the ones that are left for\n * what reading the changes costs them together.\n *\n * @param canAbandon Whether pipelines can be removed, by this or by the\n * cutoff.\n * @param counts The rows that changed since the previous snapshot.\n */\n #startCostBasedAdvance(\n {shouldCutOff, rebuildMargin}: CostBasedAdvance,\n canAbandon: boolean,\n counts: ChangeLogCounts,\n ): CostBasedAdvanceContext {\n const changes = counts.byTable;\n const estimatedPushMs = new Map<string, number>();\n const abandoned: AbandonedQuery[] = [];\n if (canAbandon) {\n for (const [queryID, pipeline] of this.#pipelines) {\n const estimate = this.#pushCosts.estimate(\n queryID,\n pipeline.tables,\n changes,\n );\n if (estimate === undefined) {\n continue;\n }\n if (\n shouldStartOnRebuild(\n estimate,\n pipeline.hydrationTimeMs,\n rebuildMargin,\n )\n ) {\n this.#abandon(abandoned, 'estimate', pipeline, {\n queryID,\n pushTimeMs: 0,\n changes: 0,\n hydrationTimeMs: pipeline.hydrationTimeMs,\n estimatedPushMs: estimate,\n });\n } else {\n estimatedPushMs.set(queryID, estimate);\n }\n }\n this.#abandonInsteadOfReading(\n counts,\n rebuildMargin,\n estimatedPushMs,\n abandoned,\n );\n }\n return {\n cutoff: new PushCutoff(canAbandon ? shouldCutOff : () => false),\n entries: counts.total,\n estimatedPushMs,\n abandoned,\n estimates: [],\n };\n }\n\n /**\n * Removes the pipelines that are left, if reading the advancement's\n * changes and pushing them to those is estimated to cost more than\n * hydrating them again. The changes are then not read at all.\n */\n #abandonInsteadOfReading(\n counts: ChangeLogCounts,\n rebuildMargin: number,\n estimatedPushMs: Map<string, number>,\n abandoned: AbandonedQuery[],\n ): void {\n if (this.#pipelines.size === 0) {\n return;\n }\n let entriesOfTablesRead = 0;\n for (const table of this.#tablesRead()) {\n entriesOfTablesRead += counts.byTable.get(table) ?? 0;\n }\n const queries = Array.from(this.#pipelines, ([queryID, pipeline]) => ({\n queryID,\n pipeline,\n estimatedPushMs: estimatedPushMs.get(queryID),\n hydrationTimeMs: pipeline.hydrationTimeMs,\n }));\n const estimatedReadMs = this.#readCosts.estimate({\n walk: counts.total,\n // The walk is only told which tables are read if pipelines have\n // been removed (see advance()).\n parse: abandoned.length > 0 ? entriesOfTablesRead : counts.total,\n lookup: entriesOfTablesRead,\n });\n if (\n !shouldRebuildInsteadOfReading(estimatedReadMs, queries, rebuildMargin)\n ) {\n return;\n }\n // Every query that was left is hydrated again, the client group's own\n // included, which the reason of one query does not explain. At `debug`,\n // as this can be every batch of a client group.\n let pushMs = 0;\n let hydrationMs = 0;\n for (const query of queries) {\n pushMs += query.estimatedPushMs ?? 0;\n hydrationMs += query.hydrationTimeMs;\n }\n this.#lc.debug?.(\n 'Not reading a batch of changes: hydrating its queries again is estimated to cost less',\n {\n entries: counts.total,\n entriesOfTablesRead,\n estimatedReadMs,\n estimatedPushMs: pushMs,\n hydrationTimeMs: hydrationMs,\n rebuildMargin,\n queries: queries.length,\n },\n );\n for (const {queryID, pipeline, ...estimate} of queries) {\n this.#abandon(abandoned, 'read', pipeline, {\n queryID,\n pushTimeMs: 0,\n changes: 0,\n ...estimate,\n });\n }\n estimatedPushMs.clear();\n }\n\n #abandon(\n abandoned: AbandonedQuery[],\n reason: AbandonedQuery['reason'],\n pipeline: Pipeline,\n progress: QueryPushProgress & {estimatedPushMs?: number | undefined},\n ): void {\n const {transformationHash, transformedAst, originalAst, queryName} =\n pipeline;\n const query: AbandonedQuery = {\n ...progress,\n reason,\n transformationHash,\n transformedAst,\n originalAst,\n ...(queryName !== undefined && {queryName}),\n };\n this.#removeQuery(progress.queryID, 'abandon-advance', query);\n this.#abandoned.set(progress.queryID, query);\n abandoned.push(query);\n this.#queryStats?.recordAbandoned(pipeline, reason);\n }\n\n *#advance(\n diff: SnapshotDiff,\n timer: Timer,\n numChanges: number,\n progress: {changesProcessed: number},\n costBased: CostBasedAdvanceContext | undefined,\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 const advanceContext: AdvanceContext = {\n timer,\n progress,\n diff,\n completedChanges: 0,\n totalHydrationTimeMs,\n numChanges,\n reservedRows: undefined,\n heldBytes: 0,\n currentChangeStartMs: undefined,\n pos: 0,\n currentTable: undefined,\n queryStats: new Map(),\n costBased,\n pushTimeMs: 0,\n stage: 'diff',\n };\n this.#advanceContext = advanceContext;\n // The reservation is made here rather than in advance(), so that the\n // finally below is guaranteed to release it: a generator that is never\n // started never runs its finally.\n let timedOut = false;\n // Whether every change was read, as opposed to the iteration stopping\n // once no pipeline was left.\n let readToEnd = true;\n try {\n advanceContext.reservedRows = this.#reserveDeferredWrites(numChanges);\n const deferWrites = advanceContext.reservedRows !== undefined;\n if (deferWrites) {\n // `prev` is not written, so all of its reads can be shared.\n diff.setPrevWrites('none');\n }\n for (const table of this.#tables.values()) {\n table.setDeferWrites(deferWrites);\n }\n this.#lc.debug?.(\n `starting pipeline advancement of ${numChanges} changes with an ` +\n `advancement time limited based on total hydration time of ` +\n `${totalHydrationTimeMs} ms (${deferWrites ? 'deferred' : 'write-through'}).`,\n );\n for (const {\n table,\n prevValues: probedPrevValues,\n nextValue,\n rowKey,\n } of diff) {\n advanceContext.stage = 'between';\n // The diff counts every raw entry, whereas it only yields observed,\n // non-no-op changes. Match the projection's progress to its raw total,\n // but do not count the current change until its push has completed.\n advanceContext.completedChanges = diff.changesScanned - 1;\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.#checkAdvance(advanceContext)) {\n yield 'yield';\n }\n if (costBased) {\n // No push is in progress, and every row change the pipelines have\n // output has been yielded, so a pipeline can be removed cleanly.\n this.#cutOffQueries(advanceContext, costBased, diff, table);\n if (this.#pipelines.size === 0) {\n // No pipeline is left to push the changes to, and none of the\n // remaining changes resets the pipelines (see advance()), so\n // they do not have to be read.\n readToEnd = false;\n break;\n }\n }\n const start = timer.totalElapsed();\n advanceContext.currentChangeStartMs = start;\n advanceContext.currentTable = table;\n advanceContext.stage = 'push';\n\n let holds: HeldRows = 'fits';\n let pushCompleted = false;\n try {\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 pushCompleted = true;\n continue;\n }\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n // Early skip: If this change is an ADD of a row no connection can observe,\n // or an in-place EDIT where neither the old nor new row can be observed\n // and no secondary unique keys are displaced, skip immediately.\n const isUnobservableAdd =\n probedPrevValues.length === 0 &&\n nextValue !== null &&\n !tableSource.mayAcceptRow(nextValue as Row);\n const isUnobservableEdit =\n probedPrevValues.length === 1 &&\n nextValue !== null &&\n !tableSource.mayAcceptRow(nextValue as Row) &&\n !tableSource.mayAcceptRow(probedPrevValues[0] as Row) &&\n deepEqual(\n getRowKey(primaryKey, probedPrevValues[0] as Row) as JSONValue,\n rowKey as JSONValue,\n );\n if (isUnobservableAdd || isUnobservableEdit) {\n pushCompleted = true;\n continue;\n }\n\n if (\n nextValue !== null &&\n !this.#reserveSecondRow(advanceContext, rowKey, primaryKey)\n ) {\n holds = 'rows';\n }\n // The diff probed the `prev` snapshot for the rows this change\n // collides with. If the source is deferring its writes, the\n // earlier changes of this advancement are not in that snapshot,\n // so the probe has to be reconciled against them. A no-op for a\n // write-through source, which has already applied them to `prev`.\n const prevValues = tableSource.reconcilePendingConflicts(\n probedPrevValues,\n nextValue,\n rowKey as Row,\n this.#tableSpecs.get(table)?.tableSpec.uniqueKeys ?? [],\n );\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 pushCompleted = true;\n } finally {\n advanceContext.pos++;\n if (pushCompleted) {\n progress.changesProcessed++;\n advanceContext.completedChanges = diff.changesScanned;\n }\n }\n\n this.#checkAdvance(advanceContext, false);\n if (holds === 'fits') {\n holds = this.#holdPendingRows(advanceContext);\n }\n } finally {\n advanceContext.pushTimeMs += timer.totalElapsed() - start;\n advanceContext.stage = 'diff';\n advanceContext.currentChangeStartMs = undefined;\n }\n\n const elapsed = timer.totalElapsed() - start;\n this.#advanceTime.recordMs(elapsed, {\n table,\n });\n\n if (holds !== 'fits') {\n // Before the diff reads the next change from `prev`.\n yield* this.#writeThrough(advanceContext, diff, holds);\n }\n }\n\n // Include trailing skips, or an advancement that yielded no changes.\n advanceContext.completedChanges = diff.changesScanned;\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.#costBasedAdvanceIncomplete = false;\n this.#ensureCostModelExistsIfEnabled(curr.db.db);\n this.#lc.debug?.(`Advanced to ${curr.version}`);\n this.#logSlowAdvances(advanceContext);\n if (costBased) {\n this.#recordPushCosts(\n advanceContext,\n costBased,\n diff.changesScannedByTable,\n );\n // A diff with nothing to read reports no changes, and did not walk\n // the change log.\n if (readToEnd && diff.changes > 0 && diff.stats) {\n const {changeLogReadMs, parseMs, rowReadMs} = diff.stats;\n this.#readCosts.record(\n {walk: changeLogReadMs, parse: parseMs, lookup: rowReadMs},\n {\n walk: costBased.entries,\n parse: diff.changesScanned,\n lookup: diff.changesLookedUp,\n },\n );\n }\n // The sources have moved to the new snapshot, so nothing reads the\n // previous one any more. Left open until the next advancement,\n // which may be a long wait away, it would keep the replica from\n // checkpointing its write-ahead log.\n this.#snapshotter.releasePrevious();\n }\n } catch (e) {\n if (\n e instanceof ResetPipelinesSignal &&\n e.reason === 'advancement-timeout'\n ) {\n timedOut = true;\n this.#logAdvanceTimeout(advanceContext, e);\n }\n throw e;\n } finally {\n if (advanceContext.reservedRows !== undefined) {\n // An advancement that completed has moved its sources to `curr`,\n // which dropped what they held. One that was abandoned is followed by\n // a reset of the pipelines, but not right away, and what they hold\n // must not outlast its release from the budget.\n for (const table of this.#tables.values()) {\n table.discardPendingChanges();\n }\n }\n this.#releaseDeferredWrites(advanceContext);\n this.#advanceContext = null;\n this.#recordAdvanceStats(advanceContext, timedOut);\n }\n }\n\n /**\n * Checks the progress of the advancement. Returns whether it should yield.\n *\n * An advancement is bounded in one of two ways, never both. A\n * {@link CostBasedAdvance} cuts queries off one by one between changes\n * (see {@link #cutOffQueries}) and always runs to completion.\n * Without one, {@link #shouldAdvanceYieldMaybeAbortAdvance} aborts the\n * advancement with a {@link ResetPipelinesSignal} if it takes too long.\n */\n #checkAdvance(advanceContext: AdvanceContext, checkYield = true): boolean {\n if (advanceContext.costBased === undefined) {\n return this.#shouldAdvanceYieldMaybeAbortAdvance(checkYield);\n }\n return (\n checkYield && advanceContext.timer.elapsedLap() > this.#yieldThresholdMs()\n );\n }\n\n /**\n * Removes the pipelines of the queries that the cutoff says to.\n *\n * @param nextTable The table of the change that `diff` has just yielded,\n * which has not been pushed yet.\n */\n #cutOffQueries(\n {queryStats}: AdvanceContext,\n costBased: CostBasedAdvanceContext,\n diff: SnapshotDiff,\n nextTable: string,\n ): void {\n const cutOff = costBased.cutoff.takeCutOff(queryID => {\n const pipeline = this.#pipelines.get(queryID);\n const stats = queryStats.get(queryID);\n return pipeline === undefined || stats === undefined\n ? undefined\n : {\n queryID,\n pushTimeMs: stats.timeMs,\n changes: stats.changes,\n hydrationTimeMs: pipeline.hydrationTimeMs,\n };\n });\n if (cutOff.length === 0) {\n return;\n }\n // The entries that were read before the change that is yet to be pushed.\n // They are counted whether or not they were yielded, as an estimate is\n // the cost per entry times the entries counted in the change log, and\n // those include the ones that turn out to change nothing.\n const entriesByTable = new Map(diff.changesScannedByTable);\n entriesByTable.set(nextTable, (entriesByTable.get(nextTable) ?? 1) - 1);\n for (const progress of cutOff) {\n const {queryID} = progress;\n const pipeline = must(this.#pipelines.get(queryID));\n // What the pushes cost up to here is as good a sample as any.\n this.#pushCosts.record(\n queryID,\n pipeline.tables,\n queryStats.get(queryID)?.timeMsByTable,\n entriesByTable,\n );\n // Recorded here because #recordAdvanceStats only knows the queries\n // that still have a pipeline when the advancement ends.\n this.#queryStats?.recordAdvance(pipeline, {\n timeMs: progress.pushTimeMs,\n changes: progress.changes,\n timedOut: false,\n });\n this.#abandon(costBased.abandoned, 'cutoff', pipeline, {\n ...progress,\n estimatedPushMs: costBased.estimatedPushMs.get(queryID),\n });\n }\n }\n\n /**\n * Learns from a completed advancement what its changes cost the queries\n * that were pushed all of them.\n */\n #recordPushCosts(\n {queryStats}: AdvanceContext,\n {estimatedPushMs, estimates}: CostBasedAdvanceContext,\n entriesByTable: ReadonlyMap<string, number>,\n ): void {\n for (const [queryID, pipeline] of this.#pipelines) {\n const stats = queryStats.get(queryID);\n this.#pushCosts.record(\n queryID,\n pipeline.tables,\n stats?.timeMsByTable,\n entriesByTable,\n );\n const estimate = estimatedPushMs.get(queryID);\n if (estimate !== undefined) {\n estimates.push({\n queryID,\n estimatedPushMs: estimate,\n pushTimeMs: stats?.timeMs ?? 0,\n });\n }\n }\n }\n\n /** Accounts the time each query took in an advancement to its shape. */\n #recordAdvanceStats({queryStats}: AdvanceContext, timedOut: boolean): void {\n if (this.#queryStats === undefined) {\n return;\n }\n for (const [queryID, {timeMs, changes}] of queryStats) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline !== undefined) {\n this.#queryStats.recordAdvance(pipeline, {timeMs, changes, timedOut});\n }\n }\n }\n\n /**\n * Decides how the sources apply an advancement's changes: in memory if\n * they fit in the worker's {@link DeferredWritesBudget}, and otherwise\n * written through to the `prev` snapshot. Returns the number of rows\n * reserved if they are held in memory.\n */\n #reserveDeferredWrites(numChanges: number): number | undefined {\n const budget = this.#deferredWrites;\n if (!budget) {\n return undefined;\n }\n // An entry sets or removes one row, and a row that it displaces has an\n // entry of its own. Entries of tables that no pipeline of this group reads\n // are counted too: excluding them would take a scan of the entries (see\n // #reserveSecondRow for the one kind of entry that can need two rows).\n if (budget.tryReserve(numChanges)) {\n return numChanges;\n }\n this.#deferredWritesFallbacks.add(1, {stage: 'start'});\n this.#lc.debug?.(\n `writing through ${numChanges} changes: ${budget.reservedRows} rows ` +\n `of the deferred writes budget are reserved`,\n );\n return undefined;\n }\n\n /**\n * If the change log identifies the rows of a table by a key other than its\n * primary key here, an entry that sets a row can change its primary key,\n * which is two rows to a source: the old and the new. (An entry that removes\n * a row removes the one row its key identifies.) Reserves the second row for\n * such an entry, if the advancement holds its changes in memory, and\n * returns whether it fit.\n */\n #reserveSecondRow(\n advanceContext: AdvanceContext,\n rowKey: RowKey,\n primaryKey: PrimaryKey,\n ): boolean {\n if (\n advanceContext.reservedRows === undefined ||\n isKeyedBy(rowKey, primaryKey)\n ) {\n return true;\n }\n if (!must(this.#deferredWrites).tryReserveMore(1)) {\n return false;\n }\n advanceContext.reservedRows++;\n return true;\n }\n\n /**\n * Writes the changes that an advancement holds in memory through to the\n * `prev` snapshot, and the rest of its changes after them, when they no\n * longer fit (see {@link HeldRows}). The advancement then continues as if\n * it had written through from the start.\n */\n *#writeThrough(\n advanceContext: AdvanceContext,\n diff: SnapshotDiff,\n reason: Exclude<HeldRows, 'fits'>,\n ): Iterable<'yield'> {\n const budget = must(this.#deferredWrites);\n this.#deferredWritesFallbacks.add(1, {stage: 'partway', reason});\n this.#lc.debug?.(\n `writing through (${reason}) at ${advanceContext.pos} of ` +\n `${advanceContext.numChanges} changes, holding ` +\n `${advanceContext.heldBytes} of the ${budget.heldBytes} estimated ` +\n `bytes held on this worker (budget: ${budget.maxBytes})`,\n );\n // `prev` is about to be written, so the reads that its writes can affect\n // can no longer be shared.\n diff.setPrevWrites('divergent');\n for (const source of this.#tables.values()) {\n yield* source.writePendingChanges();\n }\n this.#releaseDeferredWrites(advanceContext);\n }\n\n #releaseDeferredWrites(advanceContext: AdvanceContext) {\n const {reservedRows, heldBytes} = advanceContext;\n if (reservedRows !== undefined) {\n must(this.#deferredWrites).release(reservedRows, heldBytes);\n advanceContext.reservedRows = undefined;\n advanceContext.heldBytes = 0;\n }\n }\n\n #recordAdvancePush(queryID: string, timeMs: number): void {\n const advance = this.#advanceContext;\n if (advance === null) {\n return;\n }\n const stats = getOrInsertComputed(advance.queryStats, queryID, () => ({\n timeMs: 0,\n changes: 0,\n lastPos: -1,\n timeMsByTable: new Map(),\n }));\n stats.timeMs += timeMs;\n advance.costBased?.cutoff.recordPush(queryID);\n if (stats.lastPos !== advance.pos) {\n stats.lastPos = advance.pos;\n stats.changes++;\n }\n const table = advance.currentTable;\n if (table !== undefined) {\n stats.timeMsByTable.set(\n table,\n (stats.timeMsByTable.get(table) ?? 0) + timeMs,\n );\n }\n }\n\n /**\n * The identity of the query of the pipeline for `queryID` and its\n * {@link queryShape}, for logging.\n */\n #queryForLog(queryID: string) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline === undefined) {\n return undefined;\n }\n const {transformationHash, queryName, shape} = pipeline;\n return {\n queryName,\n shape,\n fields: {\n queryHash: queryID,\n transformationHash,\n ...(queryName !== undefined && {queryName}),\n queryShape: shape.hash,\n },\n };\n }\n\n /** Compute once for an admitted log, without clocks or allocations per change. */\n #advancementLogFields({\n timer,\n diff,\n progress,\n completedChanges,\n queryStats,\n totalHydrationTimeMs,\n }: AdvanceContext): AdvancementLogFields {\n const advancementTimeMs = timer.totalElapsed();\n let queryPushTimeMs = 0;\n for (const stats of queryStats.values()) {\n queryPushTimeMs += stats.timeMs;\n }\n return {\n advancementTimeMs,\n queryPushTimeMs,\n // Time outside measured query output pushes includes upstream query\n // propagation, diff iteration, sources and consumer work/waits. This\n // approximate remainder is not a CPU or non-query-work attribution;\n // clamp it for timer precision/overlap.\n outsideQueryPushTimeMs: Math.max(0, advancementTimeMs - queryPushTimeMs),\n totalHydrationTimeMs,\n changesScanned: diff.changesScanned,\n changesCompleted: completedChanges,\n changesProcessed: progress.changesProcessed,\n changesSkipped: diff.changesSkipped,\n };\n }\n\n /**\n * Logs each query whose pushes took longer than the slow advance\n * threshold, at most once per query shape per\n * {@link SLOW_ADVANCE_LOG_WINDOW_MS}.\n */\n #logSlowAdvances(context: AdvanceContext): void {\n if (!this.#lc.warn) {\n return;\n }\n const {queryStats, numChanges} = context;\n let advancementFields: AdvancementLogFields | undefined;\n for (const [queryID, stats] of queryStats) {\n if (stats.timeMs <= this.#logConfig.slowAdvanceThreshold) {\n continue;\n }\n const query = this.#queryForLog(queryID);\n if (query === undefined) {\n continue;\n }\n const suppressed = slowAdvanceLogThrottle.admit(\n `${query.queryName ?? ''}:${query.shape.hash}`,\n );\n if (suppressed === undefined) {\n continue;\n }\n this.#lc.warn(\n `Slow query advancement${query.queryName === undefined ? '' : ` for ${query.queryName}`}: ` +\n `${Math.round(stats.timeMs)} ms for ${stats.changes} of ${numChanges} changes`,\n {\n zeroEvent: 'query-slow-advance',\n ...query.fields,\n advanceTimeMs: stats.timeMs,\n changes: stats.changes,\n advancementChanges: numChanges,\n ...(advancementFields ??= this.#advancementLogFields(context)),\n timeMsByTable: Object.fromEntries(stats.timeMsByTable),\n ...(suppressed > 0 && {suppressedSinceLastLog: suppressed}),\n zql: query.shape.zql,\n },\n );\n }\n }\n\n /**\n * Logs the queries that took the most time in an advancement that timed\n * out, since the reset that follows would otherwise not say what was slow.\n * Throttled per query shape of the slowest query, with a shared fallback\n * when the timeout happens before any query push timings are recorded.\n */\n #logAdvanceTimeout(\n context: AdvanceContext,\n reset: ResetPipelinesSignal,\n ): void {\n if (!this.#lc.warn) {\n return;\n }\n const {queryStats, numChanges} = context;\n const slowest = [...queryStats]\n .toSorted(([, a], [, b]) => b.timeMs - a.timeMs)\n .slice(0, ADVANCE_TIMEOUT_LOG_MAX_QUERIES)\n .flatMap(([queryID, stats]) => {\n const query = this.#queryForLog(queryID);\n return query ? [{query, stats}] : [];\n });\n const leadingQuery = slowest[0]?.query;\n const suppressed = slowAdvanceLogThrottle.admit(\n leadingQuery\n ? `timeout:${leadingQuery.queryName ?? ''}:${leadingQuery.shape.hash}`\n : 'timeout:outside-query-push',\n );\n if (suppressed === undefined) {\n return;\n }\n const summary = slowest\n .map(\n ({query, stats}) =>\n `${query.queryName ?? query.fields.queryHash} (${Math.round(stats.timeMs)} ms)`,\n )\n .join(', ');\n this.#lc.warn(\n `Advancement of ${numChanges} changes timed out. ` +\n (summary\n ? `The queries that took the most time: ${summary}`\n : 'No query push timings were recorded.'),\n {\n zeroEvent: 'query-advance-timeout',\n // Says where the advancement was when it timed out.\n reason: reset.message,\n advancementChanges: numChanges,\n ...this.#advancementLogFields(context),\n queries: slowest.map(({query, stats}) => ({\n ...query.fields,\n advanceTimeMs: stats.timeMs,\n changes: stats.changes,\n zql: query.shape.zql,\n })),\n ...(suppressed > 0 && {suppressedSinceLastLog: suppressed}),\n },\n );\n }\n\n /** Implements `BuilderDelegate.getSource()` */\n #getSource(tableName: string): Source {\n return getOrInsertComputed(this.#tables, tableName, tableName => {\n const tableSpec = mustGetTableSpec(this.#tableSpecs, tableName);\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, tableName);\n\n const {db} = this.#snapshotter.current();\n const source = new TableSource(\n this.#lc,\n this.#logConfig,\n db.db,\n tableName,\n tableSpec.zqlSpec,\n primaryKey,\n () => this.#shouldYield(),\n // Pipelines only read tables through their connections, and the\n // sources are moved to the next snapshot after every advancement.\n // How writes are applied is set per advancement, by #advance().\n {skipUnobservableChanges: true},\n );\n this.#lc.debug?.(`created TableSource for ${tableName}`);\n return source;\n });\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.#checkAdvance(this.#advanceContext);\n }\n throw new Error('shouldYield called outside of hydration or advancement');\n }\n\n /**\n * Cancel advancement processing when either the whole batch projects to be\n * more expensive than hydration, or the current source change alone exceeds\n * the hydration budget. The late-finish exception only applies to batch-level\n * checks; a single pathological push always resets.\n */\n #shouldAdvanceYieldMaybeAbortAdvance(checkYield = true): boolean {\n const {\n currentChangeStartMs,\n completedChanges,\n numChanges,\n timer: advanceTimer,\n totalHydrationTimeMs,\n } = must(this.#advanceContext);\n const elapsed = advanceTimer.totalElapsed();\n const currentChangeElapsedMs =\n currentChangeStartMs === undefined\n ? undefined\n : elapsed - currentChangeStartMs;\n if (\n currentChangeElapsedMs !== undefined &&\n shouldResetSlowCurrentChange(currentChangeElapsedMs, totalHydrationTimeMs)\n ) {\n this.#throwSlowCurrentChangeReset(\n completedChanges,\n numChanges,\n elapsed,\n currentChangeElapsedMs,\n totalHydrationTimeMs,\n );\n }\n const projectedTotalTimeMs = projectedAdvancementTimeMs(\n elapsed,\n completedChanges,\n numChanges,\n );\n const shouldFinish = shouldFinishLateAdvancement(\n completedChanges,\n numChanges,\n );\n if (!shouldFinish) {\n const canProject = canProjectAdvancement(\n elapsed,\n projectedTotalTimeMs,\n completedChanges,\n numChanges,\n );\n if (canProject) {\n const projectedTotal = must(projectedTotalTimeMs);\n if (\n shouldResetProjectedAdvancement(\n elapsed,\n projectedTotal,\n totalHydrationTimeMs,\n )\n ) {\n this.#throwProjectedAdvancementReset(\n completedChanges,\n numChanges,\n elapsed,\n projectedTotal,\n totalHydrationTimeMs,\n );\n }\n } else if (\n elapsed > MIN_ADVANCEMENT_TIME_LIMIT_MS &&\n elapsed > totalHydrationTimeMs\n ) {\n const advanceContext = must(this.#advanceContext);\n const breakdown = formatAdvancementBreakdown(advanceContext, elapsed);\n this.#lc.warn?.(\n `Advancement exceeded timeout at ${completedChanges} of ${numChanges} changes ` +\n `after ${elapsed.toFixed(1)} ms (${breakdown}). ` +\n `Advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms.`,\n );\n throw new ResetPipelinesSignal(\n `Advancement exceeded timeout at ${completedChanges} of ${numChanges} changes ` +\n `after ${elapsed} ms. Advancement time limited based on total ` +\n `hydration time of ${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n breakdown,\n );\n }\n }\n return checkYield && advanceTimer.elapsedLap() > this.#yieldThresholdMs();\n }\n\n /**\n * The rows an advancement holds in memory are bounded by its reservation,\n * but their width is not known in advance. So the bytes they are estimated\n * to hold are added to those held by the other advancements on this worker.\n * Says whether that takes them past the budget, or if the rows are not\n * bounded by the reservation after all.\n */\n #holdPendingRows(advanceContext: AdvanceContext): HeldRows {\n const {reservedRows, heldBytes, pos, numChanges} = advanceContext;\n if (reservedRows === undefined) {\n return 'fits';\n }\n const budget = must(this.#deferredWrites);\n const rows = this.pendingRows;\n const bytes = this.pendingBytes;\n // Recorded first, so that the bytes are released even if they do not fit.\n advanceContext.heldBytes = bytes;\n const bytesFit = budget.holdBytes(bytes - heldBytes);\n if (rows > reservedRows) {\n // The reservation is supposed to bound the rows held (see\n // DeferredWritesBudget), so this is a bug, but not one to crash on.\n budget.recordRowOverrun();\n this.#lc.error?.(\n `Advancement holds ${rows} rows at ${pos} of ${numChanges} changes, ` +\n `more than the ${reservedRows} it reserved. Writing through the rest.`,\n );\n return 'row-overrun';\n }\n return bytesFit ? 'fits' : 'bytes';\n }\n\n #throwSlowCurrentChangeReset(\n pos: number,\n numChanges: number,\n elapsed: number,\n currentChangeElapsedMs: number,\n totalHydrationTimeMs: number,\n ): never {\n const breakdown = this.#advanceContext\n ? formatAdvancementBreakdown(this.#advanceContext, elapsed)\n : undefined;\n this.#lc.warn?.(\n `Advancement slow current change at ${pos} of ${numChanges} changes ` +\n `after ${currentChangeElapsedMs.toFixed(1)} ms (${elapsed.toFixed(1)} ms total)` +\n (breakdown ? ` (${breakdown})` : '') +\n `. Advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms.`,\n );\n throw new ResetPipelinesSignal(\n `Advancement exceeded timeout processing current change at ${pos} of ` +\n `${numChanges} changes after ${currentChangeElapsedMs} ms ` +\n `(${elapsed} ms total). Advancement time limited based on total ` +\n `hydration time of ${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n breakdown,\n );\n }\n\n #throwProjectedAdvancementReset(\n pos: number,\n numChanges: number,\n elapsed: number,\n projectedTotalTimeMs: number | undefined,\n totalHydrationTimeMs: number,\n ): never {\n const breakdown = this.#advanceContext\n ? formatAdvancementBreakdown(this.#advanceContext, elapsed)\n : undefined;\n const projection =\n projectedTotalTimeMs === undefined\n ? ''\n : ` Projected total advancement time is ${projectedTotalTimeMs} ms.`;\n this.#lc.warn?.(\n `Advancement projected timeout at ${pos} of ${numChanges} changes ` +\n `after ${elapsed.toFixed(1)} ms` +\n projection +\n (breakdown ? ` (${breakdown})` : '') +\n `. Advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms.`,\n );\n throw new ResetPipelinesSignal(\n `Advancement projected to exceed hydration time at ${pos} of ` +\n `${numChanges} changes after ${elapsed} ms.` +\n projection +\n ` Advancement time limited based on total hydration time of ` +\n `${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n breakdown,\n );\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 assert(this.#pushedRowChanges === null, 'push already in progress');\n try {\n this.#pushedRowChanges = [];\n for (const val of source.genPush(change)) {\n if (val === 'yield') {\n yield 'yield';\n }\n const rowChanges = this.#pushedRowChanges;\n this.#pushedRowChanges = [];\n yield* rowChanges;\n }\n } finally {\n this.#pushedRowChanges = null;\n }\n }\n\n /**\n * Converts a change pushed out of a query pipeline into row changes during\n * the push. The relationships of a pushed node are lazy, and operators such\n * as Join compute them from the state of the push in progress: while a child\n * change is pushed to each matching parent in turn, parents not yet pushed\n * must not see it. Read after the push has moved on to the next parent, or\n * after it has finished, a node's relationships show the later state, and\n * rows that are also pushed separately are counted twice.\n */\n *#streamPushed(\n queryID: string,\n schema: SourceSchema,\n change: Change,\n ): Stream<'yield'> {\n assert(this.#pushedRowChanges, 'changes are only pushed by #push');\n for (const rowChange of must(this.#streamer).streamChange(\n queryID,\n schema,\n change,\n )) {\n if (rowChange === 'yield') {\n yield rowChange;\n continue;\n }\n // #push starts a new list after each 'yield', so add to the current one.\n must(this.#pushedRowChanges).push(rowChange);\n }\n }\n}\n\n/** Turns the output of query pipelines into the row changes sent to clients. */\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 /** The row changes for a change pushed out of the pipeline of `queryID`. */\n *streamChange(\n queryID: string,\n schema: SourceSchema,\n change: Change,\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 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 case ChangeType.CHILD: {\n const child = change[ChangeIndex.CHILD_DATA];\n yield* this.streamChange(\n queryID,\n must(schema.relationships[child.relationshipName]),\n child.change,\n );\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 /** A row change of type `op` for each of `nodes` and their descendants. */\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 // We do not sync rows gathered by the permissions\n // system to the client.\n if (system === 'permissions') {\n return;\n }\n\n const primaryKey = must(this.#primaryKeys.get(table));\n const spec = must(this.#tableSpecs.get(table)).tableSpec;\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 of Object.keys(relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n // Permissions rows are not synced, so their relationship is not read.\n if (childSchema.system !== 'permissions') {\n yield* this.streamNodes(\n queryID,\n childSchema,\n op,\n relationships[relationship](),\n );\n }\n }\n }\n }\n}\n\nclass QueryFailureLoggingOperator implements Input, Output {\n readonly #lc: LogContext;\n readonly #input: Input;\n readonly #queryHash: string;\n readonly #transformationHash: string;\n readonly #queryName: string | undefined;\n #output: Output = throwOutput;\n\n constructor(\n lc: LogContext,\n input: Input,\n queryHash: string,\n transformationHash: string,\n queryName?: string,\n ) {\n this.#lc = lc;\n this.#input = input;\n this.#queryHash = queryHash;\n this.#transformationHash = transformationHash;\n this.#queryName = queryName;\n input.setOutput(this);\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n destroy(): void {\n this.#input.destroy();\n }\n\n fetch(req: FetchRequest): Iterable<Node | 'yield'> {\n return this.#input.fetch(req);\n }\n\n *push(change: Change): Iterable<'yield'> {\n try {\n yield* this.#output.push(change, this);\n } catch (e) {\n logQueryFailure(\n this.#lc,\n {\n queryHash: this.#queryHash,\n transformationHash: this.#transformationHash,\n queryName: this.#queryName,\n },\n 'query pipeline failed',\n e,\n );\n throw e;\n }\n }\n\n *reconcile(_pusher: InputBase): Stream<'yield'> {\n if (this.#output.reconcile) {\n try {\n yield* this.#output.reconcile(this);\n } catch (e) {\n logQueryFailure(\n this.#lc,\n {\n queryHash: this.#queryHash,\n transformationHash: this.#transformationHash,\n queryName: this.#queryName,\n },\n 'query pipeline failed during reconcile',\n e,\n );\n throw e;\n }\n }\n }\n}\n\nfunction logQueryFailure(\n lc: LogContext,\n queryInfo: QueryLogInfo | undefined,\n message: string,\n error: unknown,\n): void {\n if (error instanceof ResetPipelinesSignal) {\n return;\n }\n let queryLC = lc;\n if (queryInfo) {\n queryLC = queryLC\n .withContext('queryHash', queryInfo.queryHash)\n .withContext('transformationHash', queryInfo.transformationHash);\n if (queryInfo.queryName !== undefined) {\n queryLC = queryLC.withContext('queryName', queryInfo.queryName);\n }\n }\n queryLC.error?.(message, error);\n}\n\nfunction getRowKey(cols: PrimaryKey, row: Row): RowKey {\n const rowKey: Writable<RowKey> = {};\n for (const col of cols) {\n // Not `rowKey[col] = ...`: for a column named `__proto__` that would call\n // the prototype setter instead of adding the key.\n assignProperty(rowKey, col, must(row[col]));\n }\n return rowKey;\n}\n\n/** Whether `rowKey` has exactly the columns of `primaryKey`. */\nfunction isKeyedBy(rowKey: RowKey, primaryKey: PrimaryKey): boolean {\n let cols = 0;\n for (const col in rowKey) {\n if (!primaryKey.includes(col)) {\n return false;\n }\n cols++;\n }\n return cols === primaryKey.length;\n}\n\n/**\n * Core hydration logic used by {@link PipelineDriver#addQuery}, extracted to a\n * function for reuse by the analyze-query RPC path so that analysis hydrates\n * queries the same way the view-syncer does in production. As there, the rows\n * of a table outside `clientSchema` are keyed by its key in the replica.\n */\nexport function hydrate(\n input: Input,\n hash: string,\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n return new Streamer(\n buildRowKeys(clientSchema, tableSpecs),\n tableSpecs,\n ).streamNodes(hash, input.getSchema(), ChangeType.ADD, input.fetch({}));\n}\n\n/**\n * Returns the value of `childField` in the row of `input`, the pipeline of a\n * scalar subquery: `null` if the field is `NULL`, or `undefined` if there is\n * no row. Used by {@link PipelineDriver#addQuery} and the analyze-query RPC\n * path to resolve scalar subqueries the same way.\n */\nexport function fetchScalarValue(\n input: Input,\n childField: string,\n): LiteralValue | null | undefined {\n // Consume the full stream rather than using first() to avoid\n // triggering early return on Take's #initialFetch assertion.\n // The subquery pins a unique key, 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 return undefined;\n }\n return (node.row[childField] as LiteralValue) ?? null;\n}\n\n/**\n * The schema the rows of a companion pipeline are streamed with. A companion is\n * built from the bare subquery, so its schema says `'client'` even when its\n * gate came from a permission rule. Rows read for a rule are never synced, so\n * such a companion takes the gate's system and the Streamer skips it, the rows\n * behind any EXISTS nested in it included.\n */\nfunction companionStreamSchema(\n input: Input,\n {system}: CompanionSubquery,\n): SourceSchema {\n const schema = input.getSchema();\n return system === 'permissions' ? {...schema, system} : schema;\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\n/**\n * Sets `primaryKeys` to the columns that key the synced rows of each table:\n * the table's primary key in `clientSchema`, or, for a table outside it, its\n * key in the replica.\n */\nfunction buildRowKeys(\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n primaryKeys: Map<string, PrimaryKey> = new Map<string, PrimaryKey>(),\n): Map<string, PrimaryKey> {\n primaryKeys.clear();\n for (const [table, spec] of tableSpecs.entries()) {\n primaryKeys.set(table, spec.tableSpec.primaryKey);\n }\n return buildPrimaryKeys(clientSchema, 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 e18e/prefer-array-to-sorted\n `table '${table}' is not one of: ${JSON.stringify([...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","#rowSetSignatures","#changedRowSetSignatures","#lc","#snapshotter","#storage","#shardID","#logConfig","#config","#deferredWrites","#tableSpecs","#allTableNames","#costModels","#planWarningThresholds","#yieldThresholdMs","#abandoned","#pushCosts","#readCosts","#advanceTime","#conflictRowsDeleted","#deferredWritesFallbacks","#inspectorDelegate","#queryStats","#metricsDelegate","#recordAdvancePush","#initAndResetCommon","#destroyPipeline","#costBasedAdvanceIncomplete","#primaryKeys","#streamer","#replicaVersion","#permissions","#disableCorrelatedPredicatePushdown","#getSource","#createStorage","#trackRowSetSignatures","#addQueryImpl","#logQueryPipelineLifecycle","#ensureCostModelExistsIfEnabled","#advanceContext","#hydrateContext","#totalRowsRead","#resolveScalarSubqueries","#warnIgnoredScalarHints","#logPlanWarnings","#streamPushed","#removeQuery","#startCostBasedAdvance","#tablesRead","#advance","#abandon","#abandonInsteadOfReading","#reserveDeferredWrites","#checkAdvance","#cutOffQueries","#reserveSecondRow","#push","#holdPendingRows","#writeThrough","#logSlowAdvances","#recordPushCosts","#logAdvanceTimeout","#releaseDeferredWrites","#recordAdvanceStats","#shouldAdvanceYieldMaybeAbortAdvance","#queryForLog","#advancementLogFields","#shouldYield","#throwSlowCurrentChangeReset","#throwProjectedAdvancementReset","#pushedRowChanges","#input","#queryHash","#transformationHash","#queryName","#output"],"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 {getOrInsertComputed} from '../../../../shared/src/map.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {assignProperty} from '../../../../shared/src/objects.ts';\nimport {randInt} from '../../../../shared/src/rand.ts';\nimport type {Writable} from '../../../../shared/src/writable.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 throwOutput,\n type FetchRequest,\n type Input,\n type InputBase,\n type Output,\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 {Stream} from '../../../../zql/src/ivm/stream.ts';\nimport type {ConnectionCostModel} from '../../../../zql/src/planner/planner-connection.ts';\nimport type {\n PlanWarning,\n PlanWarningThresholds,\n} from '../../../../zql/src/planner/planner-warnings.ts';\nimport {MeasurePushOperator} from '../../../../zql/src/query/measure-push-operator.ts';\nimport type {\n MetricMap,\n MetricsDelegate,\n} from '../../../../zql/src/query/metrics-delegate.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 type IgnoredScalarHint,\n} from '../../../../zqlite/src/resolve-scalar-subqueries.ts';\nimport {createSQLiteCostModel} from '../../../../zqlite/src/sqlite-cost-model.ts';\nimport {\n TableSource,\n type SQLiteRow,\n} 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 {LogThrottle} from '../../observability/log-throttle.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\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 {DeferredWritesBudget} from './deferred-writes-budget.ts';\nimport {planWarningMessage} from './plan-warnings.ts';\nimport {\n PushCostEstimator,\n ReadCostEstimator,\n shouldRebuildInsteadOfReading,\n shouldStartOnRebuild,\n} from './push-cost.ts';\nimport {\n PushCutoff,\n type QueryPushProgress,\n type ShouldCutOffPush,\n} from './push-cutoff.ts';\nimport {queryShape, type QueryShape} from './query-shape.ts';\nimport type {QueryStats} from './query-stats.ts';\nimport {rowIDSignatureUnit} from './row-set-signature.ts';\nimport type {ChangeLogCounts, 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 hydrationRowCount: number;\n readonly hydrationRowsRead: number;\n readonly planWarnings: readonly PlanWarning[];\n readonly hydrationReason: PipelineHydrationReason;\n readonly pipelineRunID: string;\n readonly pipelineReadyAtMs: number;\n readonly transformedAst: AST;\n readonly originalAst: AST;\n /** The {@link queryShape} of {@link originalAst}. */\n readonly shape: QueryShape;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n readonly companions: readonly CompanionPipeline[];\n /** The tables that the pipeline and its companions read. */\n readonly tables: ReadonlySet<string>;\n};\n\nexport type QueryInfo = {\n readonly transformedAst: AST;\n readonly originalAst?: AST | undefined;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\nexport type HydrationStats = {\n /** The rows the hydration output. */\n readonly rowCount: number;\n /**\n * The rows the hydration read from the replica, including rows that were\n * then filtered out, e.g. by a filter that could not be pushed to SQLite or\n * by an EXISTS that did not match. Much greater than {@link rowCount} means\n * the query does a lot of work for the rows it returns.\n */\n readonly rowsRead: number;\n /** What the planner warned about the plan it chose for the query. */\n readonly planWarnings: readonly PlanWarning[];\n /** The {@link queryShape} of the query, computed when it was hydrated. */\n readonly shape: QueryShape;\n};\n\ntype QueryLogInfo = {\n readonly queryHash: string;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\ntype QueryPipelineLifecycleEvent =\n | 'query-pipeline-hydrate-start'\n | 'query-pipeline-hydrate-finish'\n | 'query-pipeline-hydrate-failed'\n | 'query-pipeline-hydrate-aborted'\n | 'query-pipeline-stop';\n\nexport type PipelineHydrationReason =\n | 'query-set-sync'\n | 'unchanged-query-rehydrate';\n\ntype PipelineStopReason =\n | 'replace-query'\n | 'remove-query'\n | 'abandon-advance'\n | 'reset'\n | 'destroy';\n\ntype QueryPipelineLifecycleLog = {\n readonly zeroEvent: QueryPipelineLifecycleEvent;\n readonly pipelineRunID: string;\n readonly queryHash: string;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n readonly hydrationReason?: PipelineHydrationReason | undefined;\n readonly stopReason?: PipelineStopReason | undefined;\n readonly hydrationTimeMs?: number | undefined;\n readonly hydrationRowCount?: number | undefined;\n readonly hydrationRowsRead?: number | undefined;\n readonly pipelineLifetimeMs?: number | undefined;\n /**\n * For a pipeline that an advancement abandoned (`stopReason` is\n * `abandon-advance`): why, and what the decision was made from. The\n * hydration time that these are compared with is `hydrationTimeMs`.\n */\n readonly abandonReason?: AbandonedQuery['reason'] | undefined;\n readonly estimatedPushMs?: number | undefined;\n readonly pushTimeMs?: number | undefined;\n readonly pushedChanges?: number | undefined;\n /**\n * `debug` for the events of a query that an advancement hydrates again.\n * That can be every query of a client group in every batch of changes,\n * which is far more often than anything else that is logged at `info`.\n * What is logged at `info` about those is by query shape, by the\n * {@link QueryStats}, and by client group, by the view syncer.\n */\n readonly level?: 'info' | 'debug' | undefined;\n};\n\nexport type AdvancementProgress = {\n /** Raw ChangeLog entries read, including skipped entries and the current change. */\n readonly changesScanned: number;\n /** Yielded source changes whose push processing completed. */\n readonly changesProcessed: number;\n /** Raw entries omitted by the snapshot diff. */\n readonly changesSkipped: number;\n};\n\n/**\n * A query whose pipeline was removed by a {@link CostBasedAdvance}, either\n * before the advancement's changes were pushed or partway through them\n * (`'cutoff'`). Before, it is because pushing them to the query was\n * estimated to cost more than hydrating it again (`'estimate'`), or because\n * reading them was, for the queries that were left to push them to\n * (`'read'`). The row changes it output before then have been returned by\n * the advancement. To bring the query to the advancement's\n * version, hydrate it again with {@link PipelineDriver.addQuery}, passing its\n * `originalAst`, once the advancement's changes have been iterated over in\n * their entirety.\n */\nexport type AbandonedQuery = QueryPushProgress & {\n readonly reason: 'estimate' | 'read' | 'cutoff';\n /** What pushing the advancement's changes to it was estimated to cost. */\n readonly estimatedPushMs?: number | undefined;\n readonly transformationHash: string;\n readonly transformedAst: AST;\n /** The AST the query was added with. */\n readonly originalAst: AST;\n readonly queryName?: string | undefined;\n};\n\n/**\n * Bounds the cost of an advancement query by query, by hydrating a query\n * again when that is cheaper than pushing the advancement's changes through\n * its pipeline. This is an alternative to the client group's advancement\n * time limit, which does not apply to an advancement that is given this.\n * Such an advancement is never aborted for taking too long.\n */\nexport type CostBasedAdvance = {\n /**\n * Between changes, the pipeline of each query that this says to stop\n * pushing to is removed (see {@link PushCutoff}).\n */\n readonly shouldCutOff: ShouldCutOffPush;\n /**\n * Before any change is pushed, the pipelines of the queries that are\n * estimated to be cheaper to hydrate again than to push the advancement's\n * changes to are removed (see {@link shouldStartOnRebuild}). This is how\n * many times cheaper.\n */\n readonly rebuildMargin: number;\n};\n\n/** How an estimate of a query's push time compared with the actual time. */\nexport type PushEstimate = {\n readonly queryID: string;\n readonly estimatedPushMs: number;\n readonly pushTimeMs: number;\n};\n\ntype AdvancementLogFields = AdvancementProgress & {\n readonly advancementTimeMs: number;\n readonly queryPushTimeMs: number;\n readonly outsideQueryPushTimeMs: number;\n readonly totalHydrationTimeMs: number;\n readonly changesCompleted: number;\n};\n\ntype AdvanceContext = {\n readonly timer: Timer;\n readonly progress: {changesProcessed: number};\n readonly diff: SnapshotDiff;\n /** Raw entries completed, including skipped entries but excluding an in-flight push. */\n completedChanges: number;\n readonly totalHydrationTimeMs: number;\n readonly numChanges: number;\n /**\n * The rows reserved from the {@link DeferredWritesBudget} while the\n * advancement's changes are held in memory.\n */\n reservedRows: number | undefined;\n /** The bytes the advancement has added to the budget's held bytes. */\n heldBytes: number;\n currentChangeStartMs: number | undefined;\n pos: number;\n /** The table of the change being pushed. */\n currentTable: string | undefined;\n /** The processing time of each query's pushes, by query ID. */\n readonly queryStats: Map<string, QueryAdvanceStats>;\n /**\n * How the advancement is bounded: query by query if set, and otherwise by\n * the client group's advancement time limit.\n */\n readonly costBased: CostBasedAdvanceContext | undefined;\n pushTimeMs: number;\n stage: 'diff' | 'push' | 'between';\n};\n\n/** The state of a {@link CostBasedAdvance}. */\ntype CostBasedAdvanceContext = {\n readonly cutoff: PushCutoff;\n /** The entries of the change log since the previous snapshot. */\n readonly entries: number;\n /** The estimated push times of the queries that started on push. */\n readonly estimatedPushMs: ReadonlyMap<string, number>;\n readonly abandoned: AbandonedQuery[];\n readonly estimates: PushEstimate[];\n};\n\n/**\n * Whether the rows an advancement holds in memory fit in the budget, or why\n * they do not: the bytes held, a change that needed more rows than were left\n * to reserve, or more rows held than reserved.\n */\ntype HeldRows = 'fits' | 'bytes' | 'rows' | 'row-overrun';\n\ntype QueryAdvanceStats = {\n /** The time spent processing pushes to the query. */\n timeMs: number;\n /** The number of changes pushed to the query. */\n changes: number;\n /** The position of the last change pushed to the query. */\n lastPos: number;\n /** {@link timeMs} by the table of the change pushed. */\n readonly timeMsByTable: Map<string, number>;\n};\n\n/** The most queries a log of an advancement timeout lists. */\nconst ADVANCE_TIMEOUT_LOG_MAX_QUERIES = 3;\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;\nconst MIN_PROJECTED_ADVANCEMENT_SAMPLE_CHANGES = 8;\nconst PROJECTED_ADVANCEMENT_SAMPLE_FRACTION = 0.25;\nconst MAX_PROJECTED_ADVANCEMENT_SAMPLE_CHANGES = 50;\nconst MIN_PROJECTED_ADVANCEMENT_SAMPLE_MS = 5;\nconst MIN_PROJECTED_ADVANCEMENT_CHANGES = 16;\nconst PROJECTED_ADVANCEMENT_RESET_MULTIPLIER = 1.5;\nconst LATE_ADVANCEMENT_FINISH_PROGRESS = 0.8;\n\n/**\n * The planner warns about a query each time it is planned, i.e. for every\n * client group that hydrates it, and the warnings only change when the data\n * does. So they are logged at most once per query shape per this window,\n * per process.\n */\nconst PLAN_WARNING_LOG_WINDOW_MS = 60 * 60_000;\n\n// Shared by all PipelineDrivers in the process, so that a query shape is\n// throttled across client groups.\nconst planWarningLogThrottle = new LogThrottle({\n windowMs: PLAN_WARNING_LOG_WINDOW_MS,\n});\n\n/**\n * Slow advancements of a query shape, and advancement timeouts led by it,\n * are logged at most once per this window per process.\n */\nconst SLOW_ADVANCE_LOG_WINDOW_MS = 5 * 60_000;\n\nconst slowAdvanceLogThrottle = new LogThrottle({\n windowMs: SLOW_ADVANCE_LOG_WINDOW_MS,\n});\n\nfunction randomID() {\n return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);\n}\n\nfunction projectedAdvancementTimeMs(\n elapsedMs: number,\n processedChanges: number,\n numChanges: number,\n): number | undefined {\n if (processedChanges <= 0 || numChanges <= 0) {\n return undefined;\n }\n return (elapsedMs / processedChanges) * numChanges;\n}\n\nfunction advancementResetTimeLimitMs(totalHydrationTimeMs: number): number {\n return Math.max(totalHydrationTimeMs, 1);\n}\n\nfunction minProjectedAdvancementSampleChanges(numChanges: number): number {\n return Math.max(\n MIN_PROJECTED_ADVANCEMENT_SAMPLE_CHANGES,\n Math.min(\n MAX_PROJECTED_ADVANCEMENT_SAMPLE_CHANGES,\n Math.ceil(numChanges * PROJECTED_ADVANCEMENT_SAMPLE_FRACTION),\n ),\n );\n}\n\nfunction canProjectAdvancement(\n elapsedMs: number,\n projectedTotalTimeMs: number | undefined,\n processedChanges: number,\n numChanges: number,\n): boolean {\n return (\n projectedTotalTimeMs !== undefined &&\n numChanges >= MIN_PROJECTED_ADVANCEMENT_CHANGES &&\n processedChanges >= minProjectedAdvancementSampleChanges(numChanges) &&\n elapsedMs >= MIN_PROJECTED_ADVANCEMENT_SAMPLE_MS\n );\n}\n\nfunction shouldResetProjectedAdvancement(\n elapsedMs: number,\n projectedTotalTimeMs: number,\n totalHydrationTimeMs: number,\n): boolean {\n // The time already spent is spent whether or not the advancement is reset,\n // so a reset only saves the rest of it.\n return (\n projectedTotalTimeMs - elapsedMs >\n advancementResetTimeLimitMs(totalHydrationTimeMs) *\n PROJECTED_ADVANCEMENT_RESET_MULTIPLIER\n );\n}\n\nfunction shouldFinishLateAdvancement(\n processedChanges: number,\n numChanges: number,\n): boolean {\n return (\n numChanges > 0 &&\n processedChanges / numChanges >= LATE_ADVANCEMENT_FINISH_PROGRESS\n );\n}\n\nfunction shouldResetSlowCurrentChange(\n currentChangeElapsedMs: number,\n totalHydrationTimeMs: number,\n): boolean {\n return (\n currentChangeElapsedMs > MIN_ADVANCEMENT_TIME_LIMIT_MS &&\n currentChangeElapsedMs > advancementResetTimeLimitMs(totalHydrationTimeMs)\n );\n}\n\nfunction formatAdvancementBreakdown(\n advanceContext: AdvanceContext,\n elapsed: number,\n): string {\n const {diff, pushTimeMs, stage, currentChangeStartMs, currentTable} =\n advanceContext;\n const currentPushMs =\n stage === 'push' && currentChangeStartMs !== undefined\n ? Math.max(0, elapsed - currentChangeStartMs)\n : 0;\n const totalPushMs = pushTimeMs + currentPushMs;\n const stats = diff.stats;\n const diffTimeMs = stats?.totalDiffMs ?? 0;\n const clMs = stats\n ? ` [changelog: ${stats.changeLogReadMs.toFixed(1)}ms, parse: ${stats.parseMs.toFixed(1)}ms, rows: ${stats.rowReadMs.toFixed(1)}ms]`\n : '';\n return (\n `stage: ${stage}, diff: ${diffTimeMs.toFixed(1)}ms${clMs}, ` +\n `push: ${totalPushMs.toFixed(1)}ms` +\n (currentTable ? `, table: ${currentTable}` : '')\n );\n}\n\n/**\n * Manages the state of IVM pipelines for a given ViewSyncer (i.e. client group).\n */\nexport class PipelineDriver {\n // Retain sources until reset or destroy so getRow can serve catch-up reads.\n readonly #tables = new Map<string, TableSource>();\n // Query id to pipeline\n readonly #pipelines = new Map<string, Pipeline>();\n /**\n * XOR signature of the set of rows currently attached to each active\n * query, maintained as RowChanges are yielded from {@link addQuery} and\n * {@link advance}. ADDs / REMOVEs XOR the row's unit in (XOR is\n * self-inverse, so one op serves both directions); EDITs are no-ops.\n * Hydration starts the query's entry at `0n` (the empty row set), so every\n * hydrated query has one, even if it has no rows. A hydration that does not\n * finish removes it.\n */\n readonly #rowSetSignatures = new Map<string, bigint>();\n /**\n * IDs of the queries whose {@link #rowSetSignatures} entry changed since the\n * last {@link clearChangedRowSetSignatures}, so that the CVR flush only has\n * to look at those instead of every query.\n */\n readonly #changedRowSetSignatures = new Set<string>();\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 #deferredWrites: DeferredWritesBudget | undefined;\n readonly #tableSpecs = new Map<string, LiteAndZqlSpec>();\n readonly #allTableNames = new Set<string>();\n readonly #costModels: WeakMap<Database, ConnectionCostModel> | undefined;\n readonly #planWarningThresholds: PlanWarningThresholds | undefined;\n readonly #yieldThresholdMs: () => number;\n #streamer: Streamer | null = null;\n /** The row changes of the push in progress, until #push yields them. */\n #pushedRowChanges: RowChange[] | null = null;\n #hydrateContext: HydrateContext | null = null;\n #advanceContext: AdvanceContext | null = null;\n /**\n * Whether a {@link CostBasedAdvance} has been set up and has not\n * completed. It may already have removed pipelines, but the sources only\n * move to the new snapshot when the iteration over its changes completes,\n * so nothing can be hydrated until then. If the iteration is given up on\n * or throws, they never do, and nothing can be hydrated until the\n * pipelines are {@link reset()}.\n */\n #costBasedAdvanceIncomplete = false;\n /**\n * The queries abandoned by a {@link CostBasedAdvance} that have not been\n * added again since.\n */\n readonly #abandoned = new Map<string, AbandonedQuery>();\n readonly #pushCosts = new PushCostEstimator();\n readonly #readCosts = new ReadCostEstimator();\n #replicaVersion: string | null = null;\n #primaryKeys: Map<string, PrimaryKey> | null = null;\n #permissions: LoadedPermissions | null = null;\n\n readonly #advanceTime = getOrCreateLatencyHistogram(\n 'sync',\n 'ivm.advance-time',\n 'Time to advance all queries for a given client group in response to a single change.',\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 #deferredWritesFallbacks = getOrCreateCounter(\n 'sync',\n 'ivm.deferred-writes-fallbacks',\n 'Number of advancements written through to the replica snapshot because ' +\n 'their changes did not fit in the deferred IVM writes budget, from the ' +\n 'start or partway (because of the bytes held, a change that needed ' +\n 'more rows than were left, or more rows held than reserved)',\n );\n\n readonly #inspectorDelegate: InspectorDelegate;\n readonly #queryStats: QueryStats | undefined;\n\n /**\n * Passes the pipelines' metrics on to the inspector, and accounts the time\n * spent pushing to each query to the advancement in progress.\n */\n readonly #metricsDelegate: MetricsDelegate = {\n addMetric: <K extends keyof MetricMap>(\n metric: K,\n value: number,\n ...args: MetricMap[K]\n ) => {\n this.#inspectorDelegate.addMetric(metric, value, ...args);\n if (metric === 'query-update-server') {\n this.#recordAdvancePush(args[0], value);\n }\n },\n };\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 deferredWrites?: DeferredWritesBudget,\n queryStats?: QueryStats | undefined,\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.#deferredWrites = deferredWrites;\n this.#inspectorDelegate = inspectorDelegate;\n this.#costModels = enablePlanner ? new WeakMap() : undefined;\n const planWarningThresholds = {\n rows: logConfig.planWarningRowThreshold,\n cost: logConfig.planWarningCostThreshold,\n };\n this.#planWarningThresholds =\n planWarningThresholds.rows > 0 || planWarningThresholds.cost > 0\n ? planWarningThresholds\n : undefined;\n this.#yieldThresholdMs = yieldThresholdMs;\n this.#queryStats = queryStats;\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 [queryID, pipeline] of this.#pipelines) {\n this.#pipelines.delete(queryID);\n this.#destroyPipeline(queryID, pipeline, 'reset');\n }\n this.#abandoned.clear();\n this.#costBasedAdvanceIncomplete = false;\n this.#tables.clear();\n this.#allTableNames.clear();\n this.#rowSetSignatures.clear();\n this.#changedRowSetSignatures.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 = buildRowKeys(\n clientSchema,\n this.#tableSpecs,\n this.#primaryKeys ?? undefined,\n );\n this.#primaryKeys = primaryKeys;\n this.#streamer ??= new Streamer(primaryKeys, this.#tableSpecs);\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 /**\n * Advances the snapshot to the head of the database without diffing the\n * change log, in preparation for hydrating queries at head.\n *\n * Throws a {@link ResetPipelinesSignal} if the change log records a\n * schema change since the previous snapshot. The table specs (and any\n * TableSources built from them) were computed at or before that snapshot\n * and are stale with respect to the new head, so the caller must\n * {@link reset()} before hydrating. ({@link advance()} detects this when\n * the diff encounters the RESET op; this path skips the diff and so must\n * check explicitly.)\n *\n * @param releasePreviousSnapshot Whether to release the snapshot that the\n * driver was at, which is otherwise left open until the next\n * advance (see `Snapshotter.releasePrevious()`).\n */\n advanceWithoutDiff(releasePreviousSnapshot = false): string {\n const {prev, curr} = this.#snapshotter.advanceWithoutDiff();\n if (curr.schemaChangedSince(prev.version)) {\n throw new ResetPipelinesSignal(\n `schema changed between ${prev.version} and ${curr.version}`,\n 'schema-change',\n );\n }\n for (const table of this.#tables.values()) {\n table.setDB(curr.db.db);\n }\n if (releasePreviousSnapshot) {\n this.#snapshotter.releasePrevious();\n }\n return curr.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 for (const [queryID, pipeline] of this.#pipelines) {\n this.#pipelines.delete(queryID);\n this.#destroyPipeline(queryID, pipeline, 'destroy');\n }\n this.#tables.clear();\n this.#rowSetSignatures.clear();\n this.#changedRowSetSignatures.clear();\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 /**\n * The queries whose pipelines a {@link CostBasedAdvance} removed and that\n * have not been hydrated again since, by query ID. They are not in\n * {@link queries()}. A query whose hydration is started but does not\n * finish is still one of them.\n */\n abandonedQueries(): ReadonlyMap<string, AbandonedQuery> {\n return this.#abandoned;\n }\n\n /**\n * Stats from the hydration of the pipeline for `queryID`, or `undefined` if\n * the query has no pipeline.\n */\n hydrationStats(queryID: string): HydrationStats | undefined {\n const pipeline = this.#pipelines.get(queryID);\n return pipeline\n ? {\n rowCount: pipeline.hydrationRowCount,\n rowsRead: pipeline.hydrationRowsRead,\n planWarnings: pipeline.planWarnings,\n shape: pipeline.shape,\n }\n : undefined;\n }\n\n /**\n * Logs what the planner warned about the plan it chose for `query`, at\n * most once per query shape per {@link PLAN_WARNING_LOG_WINDOW_MS}.\n */\n #logPlanWarnings(\n shape: QueryShape,\n {queryHash, transformationHash, queryName}: QueryLogInfo,\n warnings: readonly PlanWarning[],\n ): void {\n if (!this.#lc.warn) {\n return;\n }\n const suppressed = planWarningLogThrottle.admit(\n `${queryName ?? ''}:${shape.hash}`,\n );\n if (suppressed === undefined) {\n return;\n }\n const messages = warnings.map(planWarningMessage);\n this.#lc.warn(\n `Query plan warning${queryName === undefined ? '' : ` for ${queryName}`}: ` +\n messages.join(' '),\n {\n zeroEvent: 'query-plan-warning',\n queryHash,\n transformationHash,\n ...(queryName !== undefined && {queryName}),\n queryShape: shape.hash,\n warnings,\n ...(suppressed > 0 && {suppressedSinceLastLog: suppressed}),\n zql: shape.zql,\n },\n );\n }\n\n #totalRowsRead(): number {\n let total = 0;\n for (const table of this.#tables.values()) {\n total += table.rowsRead;\n }\n return total;\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 /**\n * The rows the sources hold in memory for the advancement in progress.\n * The advancement's reservation from the {@link DeferredWritesBudget}\n * bounds this.\n */\n get pendingRows(): number {\n let total = 0;\n for (const source of this.#tables.values()) {\n total += source.pendingRows;\n }\n return total;\n }\n\n /**\n * The estimated bytes of {@link pendingRows}, which are added to the\n * {@link DeferredWritesBudget}'s held bytes as the advancement goes.\n */\n get pendingBytes(): number {\n let total = 0;\n for (const source of this.#tables.values()) {\n total += source.pendingBytes;\n }\n return total;\n }\n\n #logQueryPipelineLifecycle({\n zeroEvent,\n pipelineRunID,\n queryHash,\n transformationHash,\n queryName,\n hydrationReason,\n stopReason,\n hydrationTimeMs,\n hydrationRowCount,\n hydrationRowsRead,\n pipelineLifetimeMs,\n abandonReason,\n estimatedPushMs,\n pushTimeMs,\n pushedChanges,\n level = 'info',\n }: QueryPipelineLifecycleLog): void {\n let lc = this.#lc\n .withContext('zeroEvent', zeroEvent)\n .withContext('pipelineRunID', pipelineRunID)\n .withContext('queryHash', queryHash)\n .withContext('transformationHash', transformationHash);\n if (queryName !== undefined) {\n lc = lc.withContext('queryName', queryName);\n }\n if (hydrationReason !== undefined) {\n lc = lc.withContext('hydrationReason', hydrationReason);\n }\n if (stopReason !== undefined) {\n lc = lc.withContext('stopReason', stopReason);\n }\n if (hydrationTimeMs !== undefined) {\n lc = lc.withContext('hydrationTimeMs', hydrationTimeMs);\n }\n if (hydrationRowCount !== undefined) {\n lc = lc.withContext('hydrationRowCount', hydrationRowCount);\n }\n if (hydrationRowsRead !== undefined) {\n lc = lc.withContext('hydrationRowsRead', hydrationRowsRead);\n }\n if (pipelineLifetimeMs !== undefined) {\n lc = lc.withContext('pipelineLifetimeMs', pipelineLifetimeMs);\n }\n if (abandonReason !== undefined) {\n lc = lc.withContext('abandonReason', abandonReason);\n }\n if (estimatedPushMs !== undefined) {\n lc = lc.withContext('estimatedPushMs', estimatedPushMs);\n }\n if (pushTimeMs !== undefined) {\n lc = lc.withContext('pushTimeMs', pushTimeMs);\n }\n if (pushedChanges !== undefined) {\n lc = lc.withContext('pushedChanges', pushedChanges);\n }\n lc[level]?.('query pipeline lifecycle');\n }\n\n /**\n * A `{scalar: true}` that cannot be honored degrades silently to a plain\n * EXISTS, so the author gets none of the plan they asked for and no signal\n * that they didn't. Say so, with the unique keys that were actually\n * available — the client schema knows only primary keys, so this is the only\n * place the advice can be correct.\n */\n #warnIgnoredScalarHints(queryID: string, hints: IgnoredScalarHint[]): void {\n for (const {table, uniqueKeys} of hints) {\n const keys = uniqueKeys.map(k => `(${k.join(', ')})`).join(', ');\n this.#lc.warn?.(\n `Ignoring {scalar: true} on the \"${table}\" subquery of query ` +\n `${queryID}: it does not constrain every column of any unique key ` +\n `${keys.length > 0 ? `[${keys}]` : '(none on this table)'} to a ` +\n `literal with \"=\", so it is not provably limited to one row. ` +\n `The gate runs as a plain EXISTS.`,\n );\n }\n }\n\n #disableCorrelatedPredicatePushdown(): boolean {\n return this.#config?.enableCorrelatedPredicatePushdown === false;\n }\n\n #resolveScalarSubqueries(\n ast: AST,\n tables: Set<string>,\n ): {\n ast: AST;\n companions: CompanionSubquery[];\n companionInputs: Input[];\n ignoredScalarHints: IgnoredScalarHint[];\n } {\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 enableNotExists: true, // Server-side can handle NOT EXISTS\n disableCorrelatedPredicatePushdown:\n this.#disableCorrelatedPredicatePushdown(),\n getSource: name => {\n tables.add(name);\n return this.#getSource(name);\n },\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 // Tracked before it is fetched so that a failure in this or a later\n // subquery can tear it down below. A companion with no result is kept\n // alive too: it detects a future insert that creates the row.\n companionInputs.push(input);\n return fetchScalarValue(input, childField);\n };\n\n let resolved: AST;\n let companions: CompanionSubquery[];\n let ignoredScalarHints: IgnoredScalarHint[];\n try {\n ({\n ast: resolved,\n companions,\n ignoredScalarHints,\n } = resolveSimpleScalarSubqueries(ast, this.#tableSpecs, executor));\n } catch (e) {\n for (const input of companionInputs) {\n input.destroy();\n }\n throw e;\n }\n return {\n ast: resolved,\n companions,\n companionInputs,\n ignoredScalarHints,\n };\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 queryName?: string,\n hydrationReason: PipelineHydrationReason = 'query-set-sync',\n ): Iterable<RowChange | 'yield'> {\n return this.#trackRowSetSignatures(\n this.#addQueryImpl(\n transformationHash,\n queryID,\n query,\n timer,\n queryName,\n hydrationReason,\n ),\n );\n }\n\n *#addQueryImpl(\n transformationHash: string,\n queryID: string,\n query: AST,\n timer: Timer,\n queryName?: string,\n hydrationReason: PipelineHydrationReason = 'query-set-sync',\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before adding queries',\n );\n this.removeQuery(queryID, 'replace-query');\n const pipelineRunID = randomID();\n const shape = queryShape(query);\n // A query that an advancement abandoned is being hydrated again.\n const level = this.#abandoned.has(queryID) ? 'debug' : 'info';\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-hydrate-start',\n pipelineRunID,\n queryHash: queryID,\n transformationHash,\n queryName,\n hydrationReason,\n level,\n });\n const debugDelegate = runtimeDebugFlags.trackRowsVended\n ? new Debug(true)\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 assert(\n !this.#costBasedAdvanceIncomplete,\n 'Cannot hydrate before the advance has completed',\n );\n this.#hydrateContext = {\n timer,\n };\n // Hydration has the driver to itself, so the rows read by all of its\n // tables in the meantime are the rows read by this hydration. Tables\n // added by the hydration start from zero, which this also accounts for.\n const rowsReadAtStart = this.#totalRowsRead();\n let hydrationFinished = false;\n let hydrationFailed = false;\n let hydrationRowCount = 0;\n let planWarnings: readonly PlanWarning[] = [];\n const planWarningThresholds = this.#planWarningThresholds;\n // The inputs built so far, held outside the try so that a hydration that\n // does not finish (aborted by the consumer or failed) can tear them down.\n // Only a finished hydration hands them over to #pipelines.\n let builtInputs: Input[] = [];\n const tables = new Set<string>();\n try {\n // Start from the signature of the empty row set, so that a query that\n // hydrates to no rows has one too. Without it, rowSetSignature() would\n // report \"no pipeline\" and a stale signature in the CVR would never be\n // replaced, so drift would be detected again on every rehydration. The\n // finally block removes it if the hydration does not finish.\n this.#rowSetSignatures.set(queryID, 0n);\n this.#changedRowSetSignatures.add(queryID);\n const {\n ast: resolvedQuery,\n companions: companionMeta,\n companionInputs,\n ignoredScalarHints,\n } = this.#resolveScalarSubqueries(query, tables);\n builtInputs = [...companionInputs];\n\n this.#warnIgnoredScalarHints(queryID, ignoredScalarHints);\n\n const input = buildPipeline(\n resolvedQuery,\n {\n debug: debugDelegate,\n enableNotExists: true, // Server-side can handle NOT EXISTS\n disableCorrelatedPredicatePushdown:\n this.#disableCorrelatedPredicatePushdown(),\n enablePlannerAwarePushdown:\n this.#config?.enablePlannerAwarePushdown !== false,\n getSource: name => {\n tables.add(name);\n return this.#getSource(name);\n },\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput, _queryID: string): Input =>\n new MeasurePushOperator(\n new QueryFailureLoggingOperator(\n this.#lc,\n input,\n queryID,\n transformationHash,\n queryName,\n ),\n queryID,\n this.#metricsDelegate,\n 'query-update-server',\n ),\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n planWarnings: planWarningThresholds && {\n thresholds: planWarningThresholds,\n report: warnings => {\n planWarnings = warnings;\n },\n },\n },\n queryID,\n costModel,\n );\n builtInputs.push(input);\n if (planWarnings.length > 0) {\n this.#logPlanWarnings(\n shape,\n {queryHash: queryID, transformationHash, queryName},\n planWarnings,\n );\n }\n const schema = input.getSchema();\n input.setOutput({\n push: change => this.#streamPushed(queryID, schema, change),\n });\n\n // The client runs scalar gates as plain EXISTS, so the companions are\n // hydrated like the query: their rows, and the rows behind any EXISTS\n // nested in them, are synced too. Index -1 is the query's own pipeline.\n for (let i = -1; i < companionInputs.length; i++) {\n const pipeline = i < 0 ? input : companionInputs[i];\n for (const change of must(this.#streamer).streamNodes(\n queryID,\n i < 0 ? schema : companionStreamSchema(pipeline, companionMeta[i]),\n ChangeType.ADD,\n pipeline.fetch({}),\n )) {\n if (change !== 'yield') {\n hydrationRowCount++;\n }\n yield change;\n }\n }\n\n const hydrationTimeMs = timer.totalElapsed();\n const hydrationRowsRead = this.#totalRowsRead() - rowsReadAtStart;\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 = companionStreamSchema(companionInput, meta);\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 // A change behind an EXISTS nested in the subquery leaves the\n // value alone, but the client reads those rows too.\n return this.#streamPushed(queryID, companionSchema, change);\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 return this.#streamPushed(queryID, companionSchema, change);\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 const pipelineReadyAtMs = Date.now();\n this.#pipelines.set(queryID, {\n input,\n hydrationTimeMs,\n hydrationRowCount,\n hydrationRowsRead,\n planWarnings,\n hydrationReason,\n pipelineRunID,\n pipelineReadyAtMs,\n transformedAst: resolvedQuery,\n originalAst: query,\n shape,\n transformationHash,\n ...(queryName !== undefined && {queryName}),\n companions: liveCompanions,\n tables,\n });\n this.#abandoned.delete(queryID);\n hydrationFinished = true;\n this.#queryStats?.recordHydration(\n {queryName, shape},\n {\n outcome: 'finished',\n timeMs: hydrationTimeMs,\n rowCount: hydrationRowCount,\n rowsRead: hydrationRowsRead,\n planWarnings,\n },\n );\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-hydrate-finish',\n pipelineRunID,\n queryHash: queryID,\n transformationHash,\n queryName,\n hydrationReason,\n hydrationTimeMs,\n hydrationRowCount,\n hydrationRowsRead,\n level,\n });\n } catch (e) {\n hydrationFailed = true;\n this.#queryStats?.recordHydration(\n {queryName, shape},\n {outcome: 'failed', timeMs: timer.totalElapsed()},\n );\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-hydrate-failed',\n pipelineRunID,\n queryHash: queryID,\n transformationHash,\n queryName,\n hydrationReason,\n hydrationTimeMs: timer.totalElapsed(),\n hydrationRowCount,\n });\n logQueryFailure(\n this.#lc,\n {queryHash: queryID, transformationHash, queryName},\n 'query hydration failed',\n e,\n );\n throw e;\n } finally {\n if (!hydrationFinished && !hydrationFailed) {\n this.#queryStats?.recordHydration(\n {queryName, shape},\n {outcome: 'aborted', timeMs: timer.totalElapsed()},\n );\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-hydrate-aborted',\n pipelineRunID,\n queryHash: queryID,\n transformationHash,\n queryName,\n hydrationReason,\n hydrationTimeMs: timer.totalElapsed(),\n hydrationRowCount,\n });\n }\n if (!hydrationFinished) {\n for (const input of builtInputs) {\n input.destroy();\n }\n // Rows may already have been yielded through #trackRowSetSignatures,\n // and rowSetSignature() must not report a signature for a query\n // without an active pipeline.\n this.#rowSetSignatures.delete(queryID);\n this.#changedRowSetSignatures.delete(queryID);\n }\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(\n queryID: string,\n stopReason: PipelineStopReason = 'remove-query',\n ) {\n this.#removeQuery(queryID, stopReason);\n }\n\n /**\n * @param abandoned If an advancement is abandoning the query's pipeline,\n * the record of that, which is logged.\n */\n #removeQuery(\n queryID: string,\n stopReason: PipelineStopReason,\n abandoned?: AbandonedQuery,\n ) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline) {\n this.#pipelines.delete(queryID);\n this.#destroyPipeline(queryID, pipeline, stopReason, abandoned);\n }\n this.#rowSetSignatures.delete(queryID);\n this.#changedRowSetSignatures.delete(queryID);\n if (stopReason !== 'replace-query') {\n // A query that is being hydrated again stays abandoned until that\n // succeeds (see addQuery()).\n this.#abandoned.delete(queryID);\n }\n if (stopReason === 'remove-query') {\n this.#pushCosts.forget(queryID);\n }\n }\n\n #destroyPipeline(\n queryID: string,\n pipeline: Pipeline,\n stopReason: PipelineStopReason,\n abandoned?: AbandonedQuery,\n ): void {\n this.#logQueryPipelineLifecycle({\n zeroEvent: 'query-pipeline-stop',\n pipelineRunID: pipeline.pipelineRunID,\n queryHash: queryID,\n transformationHash: pipeline.transformationHash,\n queryName: pipeline.queryName,\n hydrationReason: pipeline.hydrationReason,\n stopReason,\n hydrationTimeMs: pipeline.hydrationTimeMs,\n hydrationRowCount: pipeline.hydrationRowCount,\n pipelineLifetimeMs: Date.now() - pipeline.pipelineReadyAtMs,\n ...(abandoned && {\n abandonReason: abandoned.reason,\n estimatedPushMs: abandoned.estimatedPushMs,\n pushTimeMs: abandoned.pushTimeMs,\n pushedChanges: abandoned.changes,\n level: 'debug',\n }),\n });\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n\n /**\n * Current XOR signature of the row-set attached to `queryID`, or\n * `undefined` if no pipeline for the query is currently active.\n * Maintained incrementally by {@link addQuery} and {@link advance}.\n */\n rowSetSignature(queryID: string): bigint | undefined {\n return this.#rowSetSignatures.get(queryID);\n }\n\n /**\n * IDs of the queries whose {@link rowSetSignature} changed (by an ADD or\n * REMOVE from {@link addQuery} or {@link advance}) since the last call to\n * {@link clearChangedRowSetSignatures}.\n */\n changedRowSetSignatures(): ReadonlySet<string> {\n return this.#changedRowSetSignatures;\n }\n\n /**\n * Called once the {@link changedRowSetSignatures} have been persisted.\n */\n clearChangedRowSetSignatures(): void {\n this.#changedRowSetSignatures.clear();\n }\n\n /**\n * Wraps an iterable of RowChanges, XORing each row's unit hash into the\n * query's signature (ADDs and REMOVEs share the same op; EDITs are no-ops).\n * Used to intercept the yield streams from {@link addQuery} and\n * {@link advance}.\n */\n *#trackRowSetSignatures(\n changes: Iterable<RowChange | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n for (const change of changes) {\n if (change !== 'yield' && change.type !== ChangeType.EDIT) {\n const cur = this.#rowSetSignatures.get(change.queryID) ?? 0n;\n const unit = rowIDSignatureUnit({\n schema: '',\n table: change.table,\n rowKey: change.rowKey as RowKey,\n });\n this.#rowSetSignatures.set(change.queryID, cur ^ unit);\n this.#changedRowSetSignatures.add(change.queryID);\n }\n yield change;\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 * @param costBased If given, the advancement is bounded query by query\n * instead (see {@link CostBasedAdvance}).\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 * @throws A {@link ResetPipelinesSignal} if `costBased` is given and the\n * changes include a schema change, a truncation or a change of\n * the permissions. It is thrown before any change is pushed or\n * any pipeline is removed, as the caller resets the pipelines\n * either way. Without `costBased`, it is iterating over the\n * `changes` that throws it, on getting to that change.\n */\n advance(\n timer: Timer,\n costBased?: CostBasedAdvance,\n ): {\n version: string;\n numChanges: number;\n changes: Iterable<RowChange | 'yield'>;\n /** Available throughout iteration and after completion or an abort. */\n progress: () => AdvancementProgress;\n /**\n * The queries whose pipelines the {@link CostBasedAdvance} has removed\n * so far. They can be hydrated again once `changes` has been iterated\n * over.\n */\n abandoned: () => readonly AbandonedQuery[];\n /**\n * How the estimates of the {@link CostBasedAdvance} compared with the\n * actual push times, for the queries that had an estimate and were\n * pushed all of the changes. Available once `changes` has been iterated\n * over.\n */\n estimates: () => readonly PushEstimate[];\n } {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before advancing',\n );\n let diff: SnapshotDiff;\n let context: CostBasedAdvanceContext | undefined;\n if (costBased === undefined) {\n diff = this.#snapshotter.advance(\n this.#tableSpecs,\n this.#allTableNames,\n this.#tables,\n // Sources skip changes that none of this client group's pipelines can\n // observe, so a `prev` they write to diverges from other groups'.\n // #advance() overrides this if it holds the changes in memory.\n 'divergent',\n );\n } else {\n const {prev, curr} = this.#snapshotter.advanceWithoutDiff();\n // The one count of the batch's changes: it sizes the batch, the push\n // costs are estimated from it, and it says if a batch whose queries\n // are all hydrated again has anything to read from the change log.\n const counts = curr.countChangesSince(prev.version);\n // A batch with a schema change, a truncation or a change of the\n // permissions ends in a reset of the pipelines, whatever is pushed\n // before the iteration gets to it. So nothing is.\n const reset = this.#snapshotter.pendingReset(this.#tableSpecs, counts);\n if (reset) {\n throw reset;\n }\n context = this.#startCostBasedAdvance(\n costBased,\n // Hydrating a query again only makes sense, and can only be told to\n // the clients as a new version, if there is something new.\n curr.version > prev.version,\n counts,\n );\n diff = this.#snapshotter.diff(\n this.#tableSpecs,\n this.#allTableNames,\n this.#tables,\n 'divergent',\n counts,\n // The pipelines that were just removed are hydrated again from the\n // new snapshot, so the changes of the tables that only they read\n // are not needed, and are left out of what is read.\n context.abandoned.length > 0 ? this.#tablesRead() : undefined,\n );\n this.#costBasedAdvanceIncomplete = true;\n }\n const {prev, curr, changes} = diff;\n this.#lc.debug?.(\n `advance ${prev.version} => ${curr.version}: ${changes} changes`,\n );\n\n const progress = {changesProcessed: 0};\n return {\n version: curr.version,\n numChanges: changes,\n changes: this.#trackRowSetSignatures(\n this.#advance(diff, timer, changes, progress, context),\n ),\n abandoned: () => context?.abandoned ?? [],\n estimates: () => context?.estimates ?? [],\n progress: () => ({\n changesScanned: diff.changesScanned,\n changesProcessed: progress.changesProcessed,\n changesSkipped: diff.changesSkipped,\n }),\n };\n }\n\n /** The tables that the pipelines read. */\n #tablesRead(): Set<string> {\n const tables = new Set<string>();\n for (const pipeline of this.#pipelines.values()) {\n for (const table of pipeline.tables) {\n tables.add(table);\n }\n }\n return tables;\n }\n\n /**\n * Removes the pipelines of the queries that are estimated to be cheaper to\n * hydrate again than to push the advancement's changes to: first each\n * query for what pushing costs it, and then the ones that are left for\n * what reading the changes costs them together.\n *\n * @param canAbandon Whether pipelines can be removed, by this or by the\n * cutoff.\n * @param counts The rows that changed since the previous snapshot.\n */\n #startCostBasedAdvance(\n {shouldCutOff, rebuildMargin}: CostBasedAdvance,\n canAbandon: boolean,\n counts: ChangeLogCounts,\n ): CostBasedAdvanceContext {\n const changes = counts.byTable;\n const estimatedPushMs = new Map<string, number>();\n const abandoned: AbandonedQuery[] = [];\n if (canAbandon) {\n for (const [queryID, pipeline] of this.#pipelines) {\n const estimate = this.#pushCosts.estimate(\n queryID,\n pipeline.tables,\n changes,\n );\n if (estimate === undefined) {\n continue;\n }\n if (\n shouldStartOnRebuild(\n estimate,\n pipeline.hydrationTimeMs,\n rebuildMargin,\n )\n ) {\n this.#abandon(abandoned, 'estimate', pipeline, {\n queryID,\n pushTimeMs: 0,\n changes: 0,\n hydrationTimeMs: pipeline.hydrationTimeMs,\n estimatedPushMs: estimate,\n });\n } else {\n estimatedPushMs.set(queryID, estimate);\n }\n }\n this.#abandonInsteadOfReading(\n counts,\n rebuildMargin,\n estimatedPushMs,\n abandoned,\n );\n }\n return {\n cutoff: new PushCutoff(canAbandon ? shouldCutOff : () => false),\n entries: counts.total,\n estimatedPushMs,\n abandoned,\n estimates: [],\n };\n }\n\n /**\n * Removes the pipelines that are left, if reading the advancement's\n * changes and pushing them to those is estimated to cost more than\n * hydrating them again. The changes are then not read at all.\n */\n #abandonInsteadOfReading(\n counts: ChangeLogCounts,\n rebuildMargin: number,\n estimatedPushMs: Map<string, number>,\n abandoned: AbandonedQuery[],\n ): void {\n if (this.#pipelines.size === 0) {\n return;\n }\n let entriesOfTablesRead = 0;\n for (const table of this.#tablesRead()) {\n entriesOfTablesRead += counts.byTable.get(table) ?? 0;\n }\n const queries = Array.from(this.#pipelines, ([queryID, pipeline]) => ({\n queryID,\n pipeline,\n estimatedPushMs: estimatedPushMs.get(queryID),\n hydrationTimeMs: pipeline.hydrationTimeMs,\n }));\n const estimatedReadMs = this.#readCosts.estimate({\n walk: counts.total,\n // The walk is only told which tables are read if pipelines have\n // been removed (see advance()).\n parse: abandoned.length > 0 ? entriesOfTablesRead : counts.total,\n lookup: entriesOfTablesRead,\n });\n if (\n !shouldRebuildInsteadOfReading(estimatedReadMs, queries, rebuildMargin)\n ) {\n return;\n }\n // Every query that was left is hydrated again, the client group's own\n // included, which the reason of one query does not explain. At `debug`,\n // as this can be every batch of a client group.\n let pushMs = 0;\n let hydrationMs = 0;\n for (const query of queries) {\n pushMs += query.estimatedPushMs ?? 0;\n hydrationMs += query.hydrationTimeMs;\n }\n this.#lc.debug?.(\n 'Not reading a batch of changes: hydrating its queries again is estimated to cost less',\n {\n entries: counts.total,\n entriesOfTablesRead,\n estimatedReadMs,\n estimatedPushMs: pushMs,\n hydrationTimeMs: hydrationMs,\n rebuildMargin,\n queries: queries.length,\n },\n );\n for (const {queryID, pipeline, ...estimate} of queries) {\n this.#abandon(abandoned, 'read', pipeline, {\n queryID,\n pushTimeMs: 0,\n changes: 0,\n ...estimate,\n });\n }\n estimatedPushMs.clear();\n }\n\n #abandon(\n abandoned: AbandonedQuery[],\n reason: AbandonedQuery['reason'],\n pipeline: Pipeline,\n progress: QueryPushProgress & {estimatedPushMs?: number | undefined},\n ): void {\n const {transformationHash, transformedAst, originalAst, queryName} =\n pipeline;\n const query: AbandonedQuery = {\n ...progress,\n reason,\n transformationHash,\n transformedAst,\n originalAst,\n ...(queryName !== undefined && {queryName}),\n };\n this.#removeQuery(progress.queryID, 'abandon-advance', query);\n this.#abandoned.set(progress.queryID, query);\n abandoned.push(query);\n this.#queryStats?.recordAbandoned(pipeline, reason);\n }\n\n *#advance(\n diff: SnapshotDiff,\n timer: Timer,\n numChanges: number,\n progress: {changesProcessed: number},\n costBased: CostBasedAdvanceContext | undefined,\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 const advanceContext: AdvanceContext = {\n timer,\n progress,\n diff,\n completedChanges: 0,\n totalHydrationTimeMs,\n numChanges,\n reservedRows: undefined,\n heldBytes: 0,\n currentChangeStartMs: undefined,\n pos: 0,\n currentTable: undefined,\n queryStats: new Map(),\n costBased,\n pushTimeMs: 0,\n stage: 'diff',\n };\n this.#advanceContext = advanceContext;\n // The reservation is made here rather than in advance(), so that the\n // finally below is guaranteed to release it: a generator that is never\n // started never runs its finally.\n let timedOut = false;\n // Whether every change was read, as opposed to the iteration stopping\n // once no pipeline was left.\n let readToEnd = true;\n try {\n advanceContext.reservedRows = this.#reserveDeferredWrites(numChanges);\n const deferWrites = advanceContext.reservedRows !== undefined;\n if (deferWrites) {\n // `prev` is not written, so all of its reads can be shared.\n diff.setPrevWrites('none');\n }\n for (const table of this.#tables.values()) {\n table.setDeferWrites(deferWrites);\n }\n this.#lc.debug?.(\n `starting pipeline advancement of ${numChanges} changes with an ` +\n `advancement time limited based on total hydration time of ` +\n `${totalHydrationTimeMs} ms (${deferWrites ? 'deferred' : 'write-through'}).`,\n );\n for (const {\n table,\n prevValues: probedPrevValues,\n nextValue,\n rowKey,\n } of diff) {\n advanceContext.stage = 'between';\n // The diff counts every raw entry, whereas it only yields observed,\n // non-no-op changes. Match the projection's progress to its raw total,\n // but do not count the current change until its push has completed.\n advanceContext.completedChanges = diff.changesScanned - 1;\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.#checkAdvance(advanceContext)) {\n yield 'yield';\n }\n if (costBased) {\n // No push is in progress, and every row change the pipelines have\n // output has been yielded, so a pipeline can be removed cleanly.\n this.#cutOffQueries(advanceContext, costBased, diff, table);\n if (this.#pipelines.size === 0) {\n // No pipeline is left to push the changes to, and none of the\n // remaining changes resets the pipelines (see advance()), so\n // they do not have to be read.\n readToEnd = false;\n break;\n }\n }\n const start = timer.totalElapsed();\n advanceContext.currentChangeStartMs = start;\n advanceContext.currentTable = table;\n advanceContext.stage = 'push';\n\n let holds: HeldRows = 'fits';\n let pushCompleted = false;\n try {\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 pushCompleted = true;\n continue;\n }\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n // Early skip: If this change is an ADD of a row no connection can observe,\n // or an in-place EDIT where neither the old nor new row can be observed\n // and no secondary unique keys are displaced, skip immediately.\n const isUnobservableAdd =\n probedPrevValues.length === 0 &&\n nextValue !== null &&\n !tableSource.mayAcceptRow(nextValue as Row);\n const isUnobservableEdit =\n probedPrevValues.length === 1 &&\n nextValue !== null &&\n !tableSource.mayAcceptRow(nextValue as Row) &&\n !tableSource.mayAcceptRow(probedPrevValues[0] as Row) &&\n deepEqual(\n getRowKey(primaryKey, probedPrevValues[0] as Row) as JSONValue,\n rowKey as JSONValue,\n );\n if (isUnobservableAdd || isUnobservableEdit) {\n pushCompleted = true;\n continue;\n }\n\n if (\n nextValue !== null &&\n !this.#reserveSecondRow(advanceContext, rowKey, primaryKey)\n ) {\n holds = 'rows';\n }\n // The diff probed the `prev` snapshot for the rows this change\n // collides with. If the source is deferring its writes, the\n // earlier changes of this advancement are not in that snapshot,\n // so the probe has to be reconciled against them. A no-op for a\n // write-through source, which has already applied them to `prev`.\n const prevValues = tableSource.reconcilePendingConflicts(\n probedPrevValues,\n nextValue,\n rowKey as SQLiteRow,\n this.#tableSpecs.get(table)?.tableSpec.uniqueKeys ?? [],\n );\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 pushCompleted = true;\n } finally {\n advanceContext.pos++;\n if (pushCompleted) {\n progress.changesProcessed++;\n advanceContext.completedChanges = diff.changesScanned;\n }\n }\n\n this.#checkAdvance(advanceContext, false);\n if (holds === 'fits') {\n holds = this.#holdPendingRows(advanceContext);\n }\n } finally {\n advanceContext.pushTimeMs += timer.totalElapsed() - start;\n advanceContext.stage = 'diff';\n advanceContext.currentChangeStartMs = undefined;\n }\n\n const elapsed = timer.totalElapsed() - start;\n this.#advanceTime.recordMs(elapsed, {\n table,\n });\n\n if (holds !== 'fits') {\n // Before the diff reads the next change from `prev`.\n yield* this.#writeThrough(advanceContext, diff, holds);\n }\n }\n\n // Include trailing skips, or an advancement that yielded no changes.\n advanceContext.completedChanges = diff.changesScanned;\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.#costBasedAdvanceIncomplete = false;\n this.#ensureCostModelExistsIfEnabled(curr.db.db);\n this.#lc.debug?.(`Advanced to ${curr.version}`);\n this.#logSlowAdvances(advanceContext);\n if (costBased) {\n this.#recordPushCosts(\n advanceContext,\n costBased,\n diff.changesScannedByTable,\n );\n // A diff with nothing to read reports no changes, and did not walk\n // the change log.\n if (readToEnd && diff.changes > 0 && diff.stats) {\n const {changeLogReadMs, parseMs, rowReadMs} = diff.stats;\n this.#readCosts.record(\n {walk: changeLogReadMs, parse: parseMs, lookup: rowReadMs},\n {\n walk: costBased.entries,\n parse: diff.changesScanned,\n lookup: diff.changesLookedUp,\n },\n );\n }\n // The sources have moved to the new snapshot, so nothing reads the\n // previous one any more. Left open until the next advancement,\n // which may be a long wait away, it would keep the replica from\n // checkpointing its write-ahead log.\n this.#snapshotter.releasePrevious();\n }\n } catch (e) {\n if (\n e instanceof ResetPipelinesSignal &&\n e.reason === 'advancement-timeout'\n ) {\n timedOut = true;\n this.#logAdvanceTimeout(advanceContext, e);\n }\n throw e;\n } finally {\n if (advanceContext.reservedRows !== undefined) {\n // An advancement that completed has moved its sources to `curr`,\n // which dropped what they held. One that was abandoned is followed by\n // a reset of the pipelines, but not right away, and what they hold\n // must not outlast its release from the budget.\n for (const table of this.#tables.values()) {\n table.discardPendingChanges();\n }\n }\n this.#releaseDeferredWrites(advanceContext);\n this.#advanceContext = null;\n this.#recordAdvanceStats(advanceContext, timedOut);\n }\n }\n\n /**\n * Checks the progress of the advancement. Returns whether it should yield.\n *\n * An advancement is bounded in one of two ways, never both. A\n * {@link CostBasedAdvance} cuts queries off one by one between changes\n * (see {@link #cutOffQueries}) and always runs to completion.\n * Without one, {@link #shouldAdvanceYieldMaybeAbortAdvance} aborts the\n * advancement with a {@link ResetPipelinesSignal} if it takes too long.\n */\n #checkAdvance(advanceContext: AdvanceContext, checkYield = true): boolean {\n if (advanceContext.costBased === undefined) {\n return this.#shouldAdvanceYieldMaybeAbortAdvance(checkYield);\n }\n return (\n checkYield && advanceContext.timer.elapsedLap() > this.#yieldThresholdMs()\n );\n }\n\n /**\n * Removes the pipelines of the queries that the cutoff says to.\n *\n * @param nextTable The table of the change that `diff` has just yielded,\n * which has not been pushed yet.\n */\n #cutOffQueries(\n {queryStats}: AdvanceContext,\n costBased: CostBasedAdvanceContext,\n diff: SnapshotDiff,\n nextTable: string,\n ): void {\n const cutOff = costBased.cutoff.takeCutOff(queryID => {\n const pipeline = this.#pipelines.get(queryID);\n const stats = queryStats.get(queryID);\n return pipeline === undefined || stats === undefined\n ? undefined\n : {\n queryID,\n pushTimeMs: stats.timeMs,\n changes: stats.changes,\n hydrationTimeMs: pipeline.hydrationTimeMs,\n };\n });\n if (cutOff.length === 0) {\n return;\n }\n // The entries that were read before the change that is yet to be pushed.\n // They are counted whether or not they were yielded, as an estimate is\n // the cost per entry times the entries counted in the change log, and\n // those include the ones that turn out to change nothing.\n const entriesByTable = new Map(diff.changesScannedByTable);\n entriesByTable.set(nextTable, (entriesByTable.get(nextTable) ?? 1) - 1);\n for (const progress of cutOff) {\n const {queryID} = progress;\n const pipeline = must(this.#pipelines.get(queryID));\n // What the pushes cost up to here is as good a sample as any.\n this.#pushCosts.record(\n queryID,\n pipeline.tables,\n queryStats.get(queryID)?.timeMsByTable,\n entriesByTable,\n );\n // Recorded here because #recordAdvanceStats only knows the queries\n // that still have a pipeline when the advancement ends.\n this.#queryStats?.recordAdvance(pipeline, {\n timeMs: progress.pushTimeMs,\n changes: progress.changes,\n timedOut: false,\n });\n this.#abandon(costBased.abandoned, 'cutoff', pipeline, {\n ...progress,\n estimatedPushMs: costBased.estimatedPushMs.get(queryID),\n });\n }\n }\n\n /**\n * Learns from a completed advancement what its changes cost the queries\n * that were pushed all of them.\n */\n #recordPushCosts(\n {queryStats}: AdvanceContext,\n {estimatedPushMs, estimates}: CostBasedAdvanceContext,\n entriesByTable: ReadonlyMap<string, number>,\n ): void {\n for (const [queryID, pipeline] of this.#pipelines) {\n const stats = queryStats.get(queryID);\n this.#pushCosts.record(\n queryID,\n pipeline.tables,\n stats?.timeMsByTable,\n entriesByTable,\n );\n const estimate = estimatedPushMs.get(queryID);\n if (estimate !== undefined) {\n estimates.push({\n queryID,\n estimatedPushMs: estimate,\n pushTimeMs: stats?.timeMs ?? 0,\n });\n }\n }\n }\n\n /** Accounts the time each query took in an advancement to its shape. */\n #recordAdvanceStats({queryStats}: AdvanceContext, timedOut: boolean): void {\n if (this.#queryStats === undefined) {\n return;\n }\n for (const [queryID, {timeMs, changes}] of queryStats) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline !== undefined) {\n this.#queryStats.recordAdvance(pipeline, {timeMs, changes, timedOut});\n }\n }\n }\n\n /**\n * Decides how the sources apply an advancement's changes: in memory if\n * they fit in the worker's {@link DeferredWritesBudget}, and otherwise\n * written through to the `prev` snapshot. Returns the number of rows\n * reserved if they are held in memory.\n */\n #reserveDeferredWrites(numChanges: number): number | undefined {\n const budget = this.#deferredWrites;\n if (!budget) {\n return undefined;\n }\n // An entry sets or removes one row, and a row that it displaces has an\n // entry of its own. Entries of tables that no pipeline of this group reads\n // are counted too: excluding them would take a scan of the entries (see\n // #reserveSecondRow for the one kind of entry that can need two rows).\n if (budget.tryReserve(numChanges)) {\n return numChanges;\n }\n this.#deferredWritesFallbacks.add(1, {stage: 'start'});\n this.#lc.debug?.(\n `writing through ${numChanges} changes: ${budget.reservedRows} rows ` +\n `of the deferred writes budget are reserved`,\n );\n return undefined;\n }\n\n /**\n * If the change log identifies the rows of a table by a key other than its\n * primary key here, an entry that sets a row can change its primary key,\n * which is two rows to a source: the old and the new. (An entry that removes\n * a row removes the one row its key identifies.) Reserves the second row for\n * such an entry, if the advancement holds its changes in memory, and\n * returns whether it fit.\n */\n #reserveSecondRow(\n advanceContext: AdvanceContext,\n rowKey: RowKey,\n primaryKey: PrimaryKey,\n ): boolean {\n if (\n advanceContext.reservedRows === undefined ||\n isKeyedBy(rowKey, primaryKey)\n ) {\n return true;\n }\n if (!must(this.#deferredWrites).tryReserveMore(1)) {\n return false;\n }\n advanceContext.reservedRows++;\n return true;\n }\n\n /**\n * Writes the changes that an advancement holds in memory through to the\n * `prev` snapshot, and the rest of its changes after them, when they no\n * longer fit (see {@link HeldRows}). The advancement then continues as if\n * it had written through from the start.\n */\n *#writeThrough(\n advanceContext: AdvanceContext,\n diff: SnapshotDiff,\n reason: Exclude<HeldRows, 'fits'>,\n ): Iterable<'yield'> {\n const budget = must(this.#deferredWrites);\n this.#deferredWritesFallbacks.add(1, {stage: 'partway', reason});\n this.#lc.debug?.(\n `writing through (${reason}) at ${advanceContext.pos} of ` +\n `${advanceContext.numChanges} changes, holding ` +\n `${advanceContext.heldBytes} of the ${budget.heldBytes} estimated ` +\n `bytes held on this worker (budget: ${budget.maxBytes})`,\n );\n // `prev` is about to be written, so the reads that its writes can affect\n // can no longer be shared.\n diff.setPrevWrites('divergent');\n for (const source of this.#tables.values()) {\n yield* source.writePendingChanges();\n }\n this.#releaseDeferredWrites(advanceContext);\n }\n\n #releaseDeferredWrites(advanceContext: AdvanceContext) {\n const {reservedRows, heldBytes} = advanceContext;\n if (reservedRows !== undefined) {\n must(this.#deferredWrites).release(reservedRows, heldBytes);\n advanceContext.reservedRows = undefined;\n advanceContext.heldBytes = 0;\n }\n }\n\n #recordAdvancePush(queryID: string, timeMs: number): void {\n const advance = this.#advanceContext;\n if (advance === null) {\n return;\n }\n const stats = getOrInsertComputed(advance.queryStats, queryID, () => ({\n timeMs: 0,\n changes: 0,\n lastPos: -1,\n timeMsByTable: new Map(),\n }));\n stats.timeMs += timeMs;\n advance.costBased?.cutoff.recordPush(queryID);\n if (stats.lastPos !== advance.pos) {\n stats.lastPos = advance.pos;\n stats.changes++;\n }\n const table = advance.currentTable;\n if (table !== undefined) {\n stats.timeMsByTable.set(\n table,\n (stats.timeMsByTable.get(table) ?? 0) + timeMs,\n );\n }\n }\n\n /**\n * The identity of the query of the pipeline for `queryID` and its\n * {@link queryShape}, for logging.\n */\n #queryForLog(queryID: string) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline === undefined) {\n return undefined;\n }\n const {transformationHash, queryName, shape} = pipeline;\n return {\n queryName,\n shape,\n fields: {\n queryHash: queryID,\n transformationHash,\n ...(queryName !== undefined && {queryName}),\n queryShape: shape.hash,\n },\n };\n }\n\n /** Compute once for an admitted log, without clocks or allocations per change. */\n #advancementLogFields({\n timer,\n diff,\n progress,\n completedChanges,\n queryStats,\n totalHydrationTimeMs,\n }: AdvanceContext): AdvancementLogFields {\n const advancementTimeMs = timer.totalElapsed();\n let queryPushTimeMs = 0;\n for (const stats of queryStats.values()) {\n queryPushTimeMs += stats.timeMs;\n }\n return {\n advancementTimeMs,\n queryPushTimeMs,\n // Time outside measured query output pushes includes upstream query\n // propagation, diff iteration, sources and consumer work/waits. This\n // approximate remainder is not a CPU or non-query-work attribution;\n // clamp it for timer precision/overlap.\n outsideQueryPushTimeMs: Math.max(0, advancementTimeMs - queryPushTimeMs),\n totalHydrationTimeMs,\n changesScanned: diff.changesScanned,\n changesCompleted: completedChanges,\n changesProcessed: progress.changesProcessed,\n changesSkipped: diff.changesSkipped,\n };\n }\n\n /**\n * Logs each query whose pushes took longer than the slow advance\n * threshold, at most once per query shape per\n * {@link SLOW_ADVANCE_LOG_WINDOW_MS}.\n */\n #logSlowAdvances(context: AdvanceContext): void {\n if (!this.#lc.warn) {\n return;\n }\n const {queryStats, numChanges} = context;\n let advancementFields: AdvancementLogFields | undefined;\n for (const [queryID, stats] of queryStats) {\n if (stats.timeMs <= this.#logConfig.slowAdvanceThreshold) {\n continue;\n }\n const query = this.#queryForLog(queryID);\n if (query === undefined) {\n continue;\n }\n const suppressed = slowAdvanceLogThrottle.admit(\n `${query.queryName ?? ''}:${query.shape.hash}`,\n );\n if (suppressed === undefined) {\n continue;\n }\n this.#lc.warn(\n `Slow query advancement${query.queryName === undefined ? '' : ` for ${query.queryName}`}: ` +\n `${Math.round(stats.timeMs)} ms for ${stats.changes} of ${numChanges} changes`,\n {\n zeroEvent: 'query-slow-advance',\n ...query.fields,\n advanceTimeMs: stats.timeMs,\n changes: stats.changes,\n advancementChanges: numChanges,\n ...(advancementFields ??= this.#advancementLogFields(context)),\n timeMsByTable: Object.fromEntries(stats.timeMsByTable),\n ...(suppressed > 0 && {suppressedSinceLastLog: suppressed}),\n zql: query.shape.zql,\n },\n );\n }\n }\n\n /**\n * Logs the queries that took the most time in an advancement that timed\n * out, since the reset that follows would otherwise not say what was slow.\n * Throttled per query shape of the slowest query, with a shared fallback\n * when the timeout happens before any query push timings are recorded.\n */\n #logAdvanceTimeout(\n context: AdvanceContext,\n reset: ResetPipelinesSignal,\n ): void {\n if (!this.#lc.warn) {\n return;\n }\n const {queryStats, numChanges} = context;\n const slowest = [...queryStats]\n .toSorted(([, a], [, b]) => b.timeMs - a.timeMs)\n .slice(0, ADVANCE_TIMEOUT_LOG_MAX_QUERIES)\n .flatMap(([queryID, stats]) => {\n const query = this.#queryForLog(queryID);\n return query ? [{query, stats}] : [];\n });\n const leadingQuery = slowest[0]?.query;\n const suppressed = slowAdvanceLogThrottle.admit(\n leadingQuery\n ? `timeout:${leadingQuery.queryName ?? ''}:${leadingQuery.shape.hash}`\n : 'timeout:outside-query-push',\n );\n if (suppressed === undefined) {\n return;\n }\n const summary = slowest\n .map(\n ({query, stats}) =>\n `${query.queryName ?? query.fields.queryHash} (${Math.round(stats.timeMs)} ms)`,\n )\n .join(', ');\n this.#lc.warn(\n `Advancement of ${numChanges} changes timed out. ` +\n (summary\n ? `The queries that took the most time: ${summary}`\n : 'No query push timings were recorded.'),\n {\n zeroEvent: 'query-advance-timeout',\n // Says where the advancement was when it timed out.\n reason: reset.message,\n advancementChanges: numChanges,\n ...this.#advancementLogFields(context),\n queries: slowest.map(({query, stats}) => ({\n ...query.fields,\n advanceTimeMs: stats.timeMs,\n changes: stats.changes,\n zql: query.shape.zql,\n })),\n ...(suppressed > 0 && {suppressedSinceLastLog: suppressed}),\n },\n );\n }\n\n /** Implements `BuilderDelegate.getSource()` */\n #getSource(tableName: string): Source {\n return getOrInsertComputed(this.#tables, tableName, tableName => {\n const tableSpec = mustGetTableSpec(this.#tableSpecs, tableName);\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, tableName);\n\n const {db} = this.#snapshotter.current();\n const source = new TableSource(\n this.#lc,\n this.#logConfig,\n db.db,\n tableName,\n tableSpec.zqlSpec,\n primaryKey,\n () => this.#shouldYield(),\n // Pipelines only read tables through their connections, and the\n // sources are moved to the next snapshot after every advancement.\n // How writes are applied is set per advancement, by #advance().\n {skipUnobservableChanges: true},\n );\n this.#lc.debug?.(`created TableSource for ${tableName}`);\n return source;\n });\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.#checkAdvance(this.#advanceContext);\n }\n throw new Error('shouldYield called outside of hydration or advancement');\n }\n\n /**\n * Cancel advancement processing when either the whole batch projects to be\n * more expensive than hydration, or the current source change alone exceeds\n * the hydration budget. The late-finish exception only applies to batch-level\n * checks; a single pathological push always resets.\n */\n #shouldAdvanceYieldMaybeAbortAdvance(checkYield = true): boolean {\n const {\n currentChangeStartMs,\n completedChanges,\n numChanges,\n timer: advanceTimer,\n totalHydrationTimeMs,\n } = must(this.#advanceContext);\n const elapsed = advanceTimer.totalElapsed();\n const currentChangeElapsedMs =\n currentChangeStartMs === undefined\n ? undefined\n : elapsed - currentChangeStartMs;\n if (\n currentChangeElapsedMs !== undefined &&\n shouldResetSlowCurrentChange(currentChangeElapsedMs, totalHydrationTimeMs)\n ) {\n this.#throwSlowCurrentChangeReset(\n completedChanges,\n numChanges,\n elapsed,\n currentChangeElapsedMs,\n totalHydrationTimeMs,\n );\n }\n const projectedTotalTimeMs = projectedAdvancementTimeMs(\n elapsed,\n completedChanges,\n numChanges,\n );\n const shouldFinish = shouldFinishLateAdvancement(\n completedChanges,\n numChanges,\n );\n if (!shouldFinish) {\n const canProject = canProjectAdvancement(\n elapsed,\n projectedTotalTimeMs,\n completedChanges,\n numChanges,\n );\n if (canProject) {\n const projectedTotal = must(projectedTotalTimeMs);\n if (\n shouldResetProjectedAdvancement(\n elapsed,\n projectedTotal,\n totalHydrationTimeMs,\n )\n ) {\n this.#throwProjectedAdvancementReset(\n completedChanges,\n numChanges,\n elapsed,\n projectedTotal,\n totalHydrationTimeMs,\n );\n }\n } else if (\n elapsed > MIN_ADVANCEMENT_TIME_LIMIT_MS &&\n elapsed > totalHydrationTimeMs\n ) {\n const advanceContext = must(this.#advanceContext);\n const breakdown = formatAdvancementBreakdown(advanceContext, elapsed);\n this.#lc.warn?.(\n `Advancement exceeded timeout at ${completedChanges} of ${numChanges} changes ` +\n `after ${elapsed.toFixed(1)} ms (${breakdown}). ` +\n `Advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms.`,\n );\n throw new ResetPipelinesSignal(\n `Advancement exceeded timeout at ${completedChanges} of ${numChanges} changes ` +\n `after ${elapsed} ms. Advancement time limited based on total ` +\n `hydration time of ${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n breakdown,\n );\n }\n }\n return checkYield && advanceTimer.elapsedLap() > this.#yieldThresholdMs();\n }\n\n /**\n * The rows an advancement holds in memory are bounded by its reservation,\n * but their width is not known in advance. So the bytes they are estimated\n * to hold are added to those held by the other advancements on this worker.\n * Says whether that takes them past the budget, or if the rows are not\n * bounded by the reservation after all.\n */\n #holdPendingRows(advanceContext: AdvanceContext): HeldRows {\n const {reservedRows, heldBytes, pos, numChanges} = advanceContext;\n if (reservedRows === undefined) {\n return 'fits';\n }\n const budget = must(this.#deferredWrites);\n const rows = this.pendingRows;\n const bytes = this.pendingBytes;\n // Recorded first, so that the bytes are released even if they do not fit.\n advanceContext.heldBytes = bytes;\n const bytesFit = budget.holdBytes(bytes - heldBytes);\n if (rows > reservedRows) {\n // The reservation is supposed to bound the rows held (see\n // DeferredWritesBudget), so this is a bug, but not one to crash on.\n budget.recordRowOverrun();\n this.#lc.error?.(\n `Advancement holds ${rows} rows at ${pos} of ${numChanges} changes, ` +\n `more than the ${reservedRows} it reserved. Writing through the rest.`,\n );\n return 'row-overrun';\n }\n return bytesFit ? 'fits' : 'bytes';\n }\n\n #throwSlowCurrentChangeReset(\n pos: number,\n numChanges: number,\n elapsed: number,\n currentChangeElapsedMs: number,\n totalHydrationTimeMs: number,\n ): never {\n const breakdown = this.#advanceContext\n ? formatAdvancementBreakdown(this.#advanceContext, elapsed)\n : undefined;\n this.#lc.warn?.(\n `Advancement slow current change at ${pos} of ${numChanges} changes ` +\n `after ${currentChangeElapsedMs.toFixed(1)} ms (${elapsed.toFixed(1)} ms total)` +\n (breakdown ? ` (${breakdown})` : '') +\n `. Advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms.`,\n );\n throw new ResetPipelinesSignal(\n `Advancement exceeded timeout processing current change at ${pos} of ` +\n `${numChanges} changes after ${currentChangeElapsedMs} ms ` +\n `(${elapsed} ms total). Advancement time limited based on total ` +\n `hydration time of ${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n breakdown,\n );\n }\n\n #throwProjectedAdvancementReset(\n pos: number,\n numChanges: number,\n elapsed: number,\n projectedTotalTimeMs: number | undefined,\n totalHydrationTimeMs: number,\n ): never {\n const breakdown = this.#advanceContext\n ? formatAdvancementBreakdown(this.#advanceContext, elapsed)\n : undefined;\n const projection =\n projectedTotalTimeMs === undefined\n ? ''\n : ` Projected total advancement time is ${projectedTotalTimeMs} ms.`;\n this.#lc.warn?.(\n `Advancement projected timeout at ${pos} of ${numChanges} changes ` +\n `after ${elapsed.toFixed(1)} ms` +\n projection +\n (breakdown ? ` (${breakdown})` : '') +\n `. Advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms.`,\n );\n throw new ResetPipelinesSignal(\n `Advancement projected to exceed hydration time at ${pos} of ` +\n `${numChanges} changes after ${elapsed} ms.` +\n projection +\n ` Advancement time limited based on total hydration time of ` +\n `${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n breakdown,\n );\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 assert(this.#pushedRowChanges === null, 'push already in progress');\n try {\n this.#pushedRowChanges = [];\n for (const val of source.genPush(change)) {\n if (val === 'yield') {\n yield 'yield';\n }\n const rowChanges = this.#pushedRowChanges;\n this.#pushedRowChanges = [];\n yield* rowChanges;\n }\n } finally {\n this.#pushedRowChanges = null;\n }\n }\n\n /**\n * Converts a change pushed out of a query pipeline into row changes during\n * the push. The relationships of a pushed node are lazy, and operators such\n * as Join compute them from the state of the push in progress: while a child\n * change is pushed to each matching parent in turn, parents not yet pushed\n * must not see it. Read after the push has moved on to the next parent, or\n * after it has finished, a node's relationships show the later state, and\n * rows that are also pushed separately are counted twice.\n */\n *#streamPushed(\n queryID: string,\n schema: SourceSchema,\n change: Change,\n ): Stream<'yield'> {\n assert(this.#pushedRowChanges, 'changes are only pushed by #push');\n for (const rowChange of must(this.#streamer).streamChange(\n queryID,\n schema,\n change,\n )) {\n if (rowChange === 'yield') {\n yield rowChange;\n continue;\n }\n // #push starts a new list after each 'yield', so add to the current one.\n must(this.#pushedRowChanges).push(rowChange);\n }\n }\n}\n\n/** Turns the output of query pipelines into the row changes sent to clients. */\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 /** The row changes for a change pushed out of the pipeline of `queryID`. */\n *streamChange(\n queryID: string,\n schema: SourceSchema,\n change: Change,\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 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 case ChangeType.CHILD: {\n const child = change[ChangeIndex.CHILD_DATA];\n yield* this.streamChange(\n queryID,\n must(schema.relationships[child.relationshipName]),\n child.change,\n );\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 /** A row change of type `op` for each of `nodes` and their descendants. */\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 // We do not sync rows gathered by the permissions\n // system to the client.\n if (system === 'permissions') {\n return;\n }\n\n const primaryKey = must(this.#primaryKeys.get(table));\n const spec = must(this.#tableSpecs.get(table)).tableSpec;\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 of Object.keys(relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n // Permissions rows are not synced, so their relationship is not read.\n if (childSchema.system !== 'permissions') {\n yield* this.streamNodes(\n queryID,\n childSchema,\n op,\n relationships[relationship](),\n );\n }\n }\n }\n }\n}\n\nclass QueryFailureLoggingOperator implements Input, Output {\n readonly #lc: LogContext;\n readonly #input: Input;\n readonly #queryHash: string;\n readonly #transformationHash: string;\n readonly #queryName: string | undefined;\n #output: Output = throwOutput;\n\n constructor(\n lc: LogContext,\n input: Input,\n queryHash: string,\n transformationHash: string,\n queryName?: string,\n ) {\n this.#lc = lc;\n this.#input = input;\n this.#queryHash = queryHash;\n this.#transformationHash = transformationHash;\n this.#queryName = queryName;\n input.setOutput(this);\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n destroy(): void {\n this.#input.destroy();\n }\n\n fetch(req: FetchRequest): Iterable<Node | 'yield'> {\n return this.#input.fetch(req);\n }\n\n *push(change: Change): Iterable<'yield'> {\n try {\n yield* this.#output.push(change, this);\n } catch (e) {\n logQueryFailure(\n this.#lc,\n {\n queryHash: this.#queryHash,\n transformationHash: this.#transformationHash,\n queryName: this.#queryName,\n },\n 'query pipeline failed',\n e,\n );\n throw e;\n }\n }\n\n *reconcile(_pusher: InputBase): Stream<'yield'> {\n if (this.#output.reconcile) {\n try {\n yield* this.#output.reconcile(this);\n } catch (e) {\n logQueryFailure(\n this.#lc,\n {\n queryHash: this.#queryHash,\n transformationHash: this.#transformationHash,\n queryName: this.#queryName,\n },\n 'query pipeline failed during reconcile',\n e,\n );\n throw e;\n }\n }\n }\n}\n\nfunction logQueryFailure(\n lc: LogContext,\n queryInfo: QueryLogInfo | undefined,\n message: string,\n error: unknown,\n): void {\n if (error instanceof ResetPipelinesSignal) {\n return;\n }\n let queryLC = lc;\n if (queryInfo) {\n queryLC = queryLC\n .withContext('queryHash', queryInfo.queryHash)\n .withContext('transformationHash', queryInfo.transformationHash);\n if (queryInfo.queryName !== undefined) {\n queryLC = queryLC.withContext('queryName', queryInfo.queryName);\n }\n }\n queryLC.error?.(message, error);\n}\n\nfunction getRowKey(cols: PrimaryKey, row: Row): RowKey {\n const rowKey: Writable<RowKey> = {};\n for (const col of cols) {\n // Not `rowKey[col] = ...`: for a column named `__proto__` that would call\n // the prototype setter instead of adding the key.\n assignProperty(rowKey, col, must(row[col]));\n }\n return rowKey;\n}\n\n/** Whether `rowKey` has exactly the columns of `primaryKey`. */\nfunction isKeyedBy(rowKey: RowKey, primaryKey: PrimaryKey): boolean {\n let cols = 0;\n for (const col in rowKey) {\n if (!primaryKey.includes(col)) {\n return false;\n }\n cols++;\n }\n return cols === primaryKey.length;\n}\n\n/**\n * Core hydration logic used by {@link PipelineDriver#addQuery}, extracted to a\n * function for reuse by the analyze-query RPC path so that analysis hydrates\n * queries the same way the view-syncer does in production. As there, the rows\n * of a table outside `clientSchema` are keyed by its key in the replica.\n */\nexport function hydrate(\n input: Input,\n hash: string,\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n return new Streamer(\n buildRowKeys(clientSchema, tableSpecs),\n tableSpecs,\n ).streamNodes(hash, input.getSchema(), ChangeType.ADD, input.fetch({}));\n}\n\n/**\n * Returns the value of `childField` in the row of `input`, the pipeline of a\n * scalar subquery: `null` if the field is `NULL`, or `undefined` if there is\n * no row. Used by {@link PipelineDriver#addQuery} and the analyze-query RPC\n * path to resolve scalar subqueries the same way.\n */\nexport function fetchScalarValue(\n input: Input,\n childField: string,\n): LiteralValue | null | undefined {\n // Consume the full stream rather than using first() to avoid\n // triggering early return on Take's #initialFetch assertion.\n // The subquery pins a unique key, 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 return undefined;\n }\n return (node.row[childField] as LiteralValue) ?? null;\n}\n\n/**\n * The schema the rows of a companion pipeline are streamed with. A companion is\n * built from the bare subquery, so its schema says `'client'` even when its\n * gate came from a permission rule. Rows read for a rule are never synced, so\n * such a companion takes the gate's system and the Streamer skips it, the rows\n * behind any EXISTS nested in it included.\n */\nfunction companionStreamSchema(\n input: Input,\n {system}: CompanionSubquery,\n): SourceSchema {\n const schema = input.getSchema();\n return system === 'permissions' ? {...schema, system} : schema;\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\n/**\n * Sets `primaryKeys` to the columns that key the synced rows of each table:\n * the table's primary key in `clientSchema`, or, for a table outside it, its\n * key in the replica.\n */\nfunction buildRowKeys(\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n primaryKeys: Map<string, PrimaryKey> = new Map<string, PrimaryKey>(),\n): Map<string, PrimaryKey> {\n primaryKeys.clear();\n for (const [table, spec] of tableSpecs.entries()) {\n primaryKeys.set(table, spec.tableSpec.primaryKey);\n }\n return buildPrimaryKeys(clientSchema, 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 e18e/prefer-array-to-sorted\n `table '${table}' is not one of: ${JSON.stringify([...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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"}
|