@rocicorp/zero 1.4.0-canary.0 → 1.4.0-canary.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/out/analyze-query/src/analyze-cli.d.ts +1 -1
  2. package/out/analyze-query/src/analyze-cli.d.ts.map +1 -1
  3. package/out/analyze-query/src/analyze-cli.js +13 -3
  4. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  5. package/out/analyze-query/src/bin-analyze.js +1 -1
  6. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  7. package/out/analyze-query/src/bin-transform.js +1 -1
  8. package/out/analyze-query/src/bin-transform.js.map +1 -1
  9. package/out/replicache/src/btree/node.d.ts +1 -1
  10. package/out/replicache/src/btree/node.d.ts.map +1 -1
  11. package/out/replicache/src/btree/node.js +34 -21
  12. package/out/replicache/src/btree/node.js.map +1 -1
  13. package/out/replicache/src/btree/write.js +1 -2
  14. package/out/replicache/src/btree/write.js.map +1 -1
  15. package/out/shared/src/btree-set.d.ts +6 -0
  16. package/out/shared/src/btree-set.d.ts.map +1 -1
  17. package/out/shared/src/btree-set.js +34 -0
  18. package/out/shared/src/btree-set.js.map +1 -1
  19. package/out/zero/package.js +1 -1
  20. package/out/zero/package.js.map +1 -1
  21. package/out/zero/src/bindings.js +1 -1
  22. package/out/zero-cache/src/config/zero-config.d.ts +18 -0
  23. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  24. package/out/zero-cache/src/config/zero-config.js +35 -3
  25. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  26. package/out/zero-cache/src/scripts/decommission.d.ts.map +1 -1
  27. package/out/zero-cache/src/scripts/decommission.js +3 -3
  28. package/out/zero-cache/src/scripts/decommission.js.map +1 -1
  29. package/out/zero-cache/src/scripts/deploy-permissions.js +1 -1
  30. package/out/zero-cache/src/scripts/deploy-permissions.js.map +1 -1
  31. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  32. package/out/zero-cache/src/server/change-streamer.js +2 -5
  33. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  34. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  35. package/out/zero-cache/src/server/main.js +6 -1
  36. package/out/zero-cache/src/server/main.js.map +1 -1
  37. package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
  38. package/out/zero-cache/src/server/reaper.js +1 -4
  39. package/out/zero-cache/src/server/reaper.js.map +1 -1
  40. package/out/zero-cache/src/server/shadow-syncer.js +35 -0
  41. package/out/zero-cache/src/server/shadow-syncer.js.map +1 -0
  42. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  43. package/out/zero-cache/src/server/syncer.js +2 -8
  44. package/out/zero-cache/src/server/syncer.js.map +1 -1
  45. package/out/zero-cache/src/server/worker-urls.d.ts +1 -0
  46. package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
  47. package/out/zero-cache/src/server/worker-urls.js +2 -1
  48. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  49. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  50. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  51. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +8 -1
  52. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  53. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +31 -18
  54. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  55. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  56. package/out/zero-cache/src/services/change-source/pg/change-source.js +44 -46
  57. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  58. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +6 -1
  59. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  60. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +62 -22
  61. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  62. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +5 -6
  64. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  65. package/out/zero-cache/src/services/change-streamer/schema/tables.js +1 -1
  66. package/out/zero-cache/src/services/run-ast.js +1 -1
  67. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js +49 -0
  68. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -0
  69. package/out/zero-cache/src/services/statz.js +3 -3
  70. package/out/zero-cache/src/services/statz.js.map +1 -1
  71. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +1 -0
  72. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  73. package/out/zero-cache/src/services/view-syncer/cvr-store.js +34 -11
  74. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  75. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +16 -1
  76. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  77. package/out/zero-cache/src/services/view-syncer/cvr.js +19 -1
  78. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  79. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  80. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  81. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +6 -0
  82. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  83. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +46 -3
  84. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  85. package/out/zero-cache/src/services/view-syncer/row-set-signature.d.ts +17 -0
  86. package/out/zero-cache/src/services/view-syncer/row-set-signature.d.ts.map +1 -0
  87. package/out/zero-cache/src/services/view-syncer/row-set-signature.js +29 -0
  88. package/out/zero-cache/src/services/view-syncer/row-set-signature.js.map +1 -0
  89. package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts +1 -0
  90. package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/view-syncer/schema/cvr.js +1 -0
  92. package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
  93. package/out/zero-cache/src/services/view-syncer/schema/init.d.ts.map +1 -1
  94. package/out/zero-cache/src/services/view-syncer/schema/init.js +5 -1
  95. package/out/zero-cache/src/services/view-syncer/schema/init.js.map +1 -1
  96. package/out/zero-cache/src/services/view-syncer/schema/types.d.ts +105 -0
  97. package/out/zero-cache/src/services/view-syncer/schema/types.d.ts.map +1 -1
  98. package/out/zero-cache/src/services/view-syncer/schema/types.js +8 -4
  99. package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
  100. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  101. package/out/zero-cache/src/services/view-syncer/view-syncer.js +2 -2
  102. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  103. package/out/zero-cache/src/types/pg.d.ts +1 -1
  104. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  105. package/out/zero-cache/src/types/pg.js +8 -2
  106. package/out/zero-cache/src/types/pg.js.map +1 -1
  107. package/out/zero-cache/src/types/timeout.d.ts +11 -0
  108. package/out/zero-cache/src/types/timeout.d.ts.map +1 -0
  109. package/out/zero-cache/src/types/timeout.js +26 -0
  110. package/out/zero-cache/src/types/timeout.js.map +1 -0
  111. package/out/zero-cache/src/workers/connection.js +5 -5
  112. package/out/zero-cache/src/workers/connection.js.map +1 -1
  113. package/out/zero-client/src/client/bindings.js +1 -1
  114. package/out/zero-client/src/client/log-options.d.ts +1 -0
  115. package/out/zero-client/src/client/log-options.d.ts.map +1 -1
  116. package/out/zero-client/src/client/log-options.js +3 -2
  117. package/out/zero-client/src/client/log-options.js.map +1 -1
  118. package/out/zero-client/src/client/options.d.ts +13 -1
  119. package/out/zero-client/src/client/options.d.ts.map +1 -1
  120. package/out/zero-client/src/client/options.js.map +1 -1
  121. package/out/zero-client/src/client/version.js +1 -1
  122. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  123. package/out/zero-client/src/client/zero.js +2 -1
  124. package/out/zero-client/src/client/zero.js.map +1 -1
  125. package/out/zero-react/src/bindings.js +1 -1
  126. package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
  127. package/out/zero-server/src/adapters/postgresjs.js +1 -1
  128. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  129. package/out/zero-solid/src/bindings.js +1 -1
  130. package/out/zero-solid/src/solid-view.js +1 -1
  131. package/out/zql/src/ivm/array-view.js +1 -1
  132. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  133. package/out/zql/src/ivm/memory-source.js +4 -4
  134. package/out/zql/src/ivm/memory-source.js.map +1 -1
  135. package/out/zql/src/ivm/operator.d.ts +1 -1
  136. package/out/zql/src/ivm/operator.d.ts.map +1 -1
  137. package/out/zql/src/ivm/operator.js +2 -4
  138. package/out/zql/src/ivm/operator.js.map +1 -1
  139. package/out/zql/src/ivm/skip-yields.d.ts +4 -0
  140. package/out/zql/src/ivm/skip-yields.d.ts.map +1 -0
  141. package/out/zql/src/ivm/skip-yields.js +33 -0
  142. package/out/zql/src/ivm/skip-yields.js.map +1 -0
  143. package/out/zql/src/ivm/view-apply-change.js +1 -1
  144. package/out/zql/src/query/query-internals.d.ts.map +1 -1
  145. package/out/zql/src/query/query-internals.js +1 -1
  146. package/out/zql/src/query/query-internals.js.map +1 -1
  147. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline-driver.js","names":["#tables","#pipelines","#lc","#snapshotter","#storage","#shardID","#logConfig","#config","#tableSpecs","#allTableNames","#costModels","#yieldThresholdMs","#advanceTime","#conflictRowsDeleted","#inspectorDelegate","#initAndResetCommon","#primaryKeys","#replicaVersion","#permissions","#getSource","#createStorage","#ensureCostModelExistsIfEnabled","#advanceContext","#hydrateContext","#resolveScalarSubqueries","#streamer","#advance","#shouldAdvanceYieldMaybeAbortAdvance","#push","#shouldYield","#startAccumulating","#stopAccumulating","#changes","#streamChanges","#streamNodes"],"sources":["../../../../../../zero-cache/src/services/view-syncer/pipeline-driver.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {deepEqual, type JSONValue} from '../../../../shared/src/json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {AST, LiteralValue} from '../../../../zero-protocol/src/ast.ts';\nimport type {ClientSchema} from '../../../../zero-protocol/src/client-schema.ts';\nimport type {Row} from '../../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../../zero-protocol/src/primary-key.ts';\nimport {buildPipeline} from '../../../../zql/src/builder/builder.ts';\nimport {\n Debug,\n runtimeDebugFlags,\n} from '../../../../zql/src/builder/debug-delegate.ts';\nimport {ChangeIndex} from '../../../../zql/src/ivm/change-index.ts';\nimport {ChangeType} from '../../../../zql/src/ivm/change-type.ts';\nimport type {Change} from '../../../../zql/src/ivm/change.ts';\nimport type {Node} from '../../../../zql/src/ivm/data.ts';\nimport {\n skipYields,\n type Input,\n type Storage,\n} from '../../../../zql/src/ivm/operator.ts';\nimport type {SourceSchema} from '../../../../zql/src/ivm/schema.ts';\nimport {\n type Source,\n type SourceChange,\n type SourceInput,\n makeSourceChangeAdd,\n makeSourceChangeEdit,\n makeSourceChangeRemove,\n} from '../../../../zql/src/ivm/source.ts';\nimport type {ConnectionCostModel} from '../../../../zql/src/planner/planner-connection.ts';\nimport {MeasurePushOperator} from '../../../../zql/src/query/measure-push-operator.ts';\nimport type {ClientGroupStorage} from '../../../../zqlite/src/database-storage.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n resolveSimpleScalarSubqueries,\n type CompanionSubquery,\n} from '../../../../zqlite/src/resolve-scalar-subqueries.ts';\nimport {createSQLiteCostModel} from '../../../../zqlite/src/sqlite-cost-model.ts';\nimport {TableSource} from '../../../../zqlite/src/table-source.ts';\nimport {\n reloadPermissionsIfChanged,\n type LoadedPermissions,\n} from '../../auth/load-permissions.ts';\nimport type {LogConfig, ZeroConfig} from '../../config/zero-config.ts';\nimport {computeZqlSpecs, mustGetTableSpec} from '../../db/lite-tables.ts';\nimport type {LiteAndZqlSpec, LiteTableSpec} from '../../db/specs.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../observability/metrics.ts';\nimport type {InspectorDelegate} from '../../server/inspector-delegate.ts';\nimport {type RowKey} from '../../types/row-key.ts';\nimport {type ShardID} from '../../types/shards.ts';\nimport {\n getSubscriptionState,\n ZERO_VERSION_COLUMN_NAME,\n} from '../replicator/schema/replication-state.ts';\nimport {checkClientSchema} from './client-schema.ts';\nimport type {Snapshotter} from './snapshotter.ts';\nimport {ResetPipelinesSignal, type SnapshotDiff} from './snapshotter.ts';\n\ntype RowOp<Op extends Omit<ChangeType, ChangeType.CHILD>> = {\n readonly type: Op;\n readonly queryID: string;\n readonly table: string;\n readonly rowKey: Row;\n readonly row: Row;\n};\n\nexport type RowAdd = RowOp<ChangeType.ADD>;\n\nexport type RowRemove = RowOp<ChangeType.REMOVE>;\n\nexport type RowEdit = RowOp<ChangeType.EDIT>;\n\nexport type RowChange = RowAdd | RowRemove | RowEdit;\n\ntype CompanionPipeline = {\n readonly input: Input;\n readonly childField: string;\n readonly resolvedValue: LiteralValue | null | undefined;\n};\n\ntype Pipeline = {\n readonly input: Input;\n readonly hydrationTimeMs: number;\n readonly transformedAst: AST;\n readonly transformationHash: string;\n readonly companions: readonly CompanionPipeline[];\n};\n\ntype QueryInfo = {\n readonly transformedAst: AST;\n readonly transformationHash: string;\n};\n\ntype AdvanceContext = {\n readonly timer: Timer;\n readonly totalHydrationTimeMs: number;\n readonly numChanges: number;\n pos: number;\n};\n\ntype HydrateContext = {\n readonly timer: Timer;\n};\n\nexport type Timer = {\n elapsedLap: () => number;\n totalElapsed: () => number;\n};\n\n/**\n * No matter how fast hydration is, advancement is given at least this long to\n * complete before doing a pipeline reset.\n */\nconst MIN_ADVANCEMENT_TIME_LIMIT_MS = 50;\n\n/**\n * Manages the state of IVM pipelines for a given ViewSyncer (i.e. client group).\n */\nexport class PipelineDriver {\n readonly #tables = new Map<string, TableSource>();\n // Query id to pipeline\n readonly #pipelines = new Map<string, Pipeline>();\n\n readonly #lc: LogContext;\n readonly #snapshotter: Snapshotter;\n readonly #storage: ClientGroupStorage;\n readonly #shardID: ShardID;\n readonly #logConfig: LogConfig;\n readonly #config: ZeroConfig | undefined;\n readonly #tableSpecs = new Map<string, LiteAndZqlSpec>();\n readonly #allTableNames = new Set<string>();\n readonly #costModels: WeakMap<Database, ConnectionCostModel> | undefined;\n readonly #yieldThresholdMs: () => number;\n #streamer: Streamer | null = null;\n #hydrateContext: HydrateContext | null = null;\n #advanceContext: AdvanceContext | null = null;\n #replicaVersion: string | null = null;\n #primaryKeys: Map<string, PrimaryKey> | null = null;\n #permissions: LoadedPermissions | null = null;\n\n readonly #advanceTime = getOrCreateHistogram('sync', 'ivm.advance-time', {\n description:\n 'Time to advance all queries for a given client group for in response to a single change.',\n unit: 's',\n });\n\n readonly #conflictRowsDeleted = getOrCreateCounter(\n 'sync',\n 'ivm.conflict-rows-deleted',\n 'Number of rows deleted because they conflicted with added row',\n );\n\n readonly #inspectorDelegate: InspectorDelegate;\n\n constructor(\n lc: LogContext,\n logConfig: LogConfig,\n snapshotter: Snapshotter,\n shardID: ShardID,\n storage: ClientGroupStorage,\n clientGroupID: string,\n inspectorDelegate: InspectorDelegate,\n yieldThresholdMs: () => number,\n enablePlanner?: boolean,\n config?: ZeroConfig,\n ) {\n this.#lc = lc.withContext('clientGroupID', clientGroupID);\n this.#snapshotter = snapshotter;\n this.#storage = storage;\n this.#shardID = shardID;\n this.#logConfig = logConfig;\n this.#config = config;\n this.#inspectorDelegate = inspectorDelegate;\n this.#costModels = enablePlanner ? new WeakMap() : undefined;\n this.#yieldThresholdMs = yieldThresholdMs;\n }\n\n /**\n * Initializes the PipelineDriver to the current head of the database.\n * Queries can then be added (i.e. hydrated) with {@link addQuery()}.\n *\n * Must only be called once.\n */\n init(clientSchema: ClientSchema) {\n assert(!this.#snapshotter.initialized(), 'Already initialized');\n this.#snapshotter.init();\n this.#initAndResetCommon(clientSchema);\n }\n\n /**\n * @returns Whether the PipelineDriver has been initialized.\n */\n initialized(): boolean {\n return this.#snapshotter.initialized();\n }\n\n /**\n * Clears the current pipelines and TableSources, returning the PipelineDriver\n * to its initial state. This should be called in response to a schema change,\n * as TableSources need to be recomputed.\n */\n reset(clientSchema: ClientSchema) {\n for (const pipeline of this.#pipelines.values()) {\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n this.#pipelines.clear();\n this.#tables.clear();\n this.#allTableNames.clear();\n this.#initAndResetCommon(clientSchema);\n }\n\n #initAndResetCommon(clientSchema: ClientSchema) {\n const {db} = this.#snapshotter.current();\n const fullTables = new Map<string, LiteTableSpec>();\n computeZqlSpecs(\n this.#lc,\n db.db,\n {includeBackfillingColumns: false},\n this.#tableSpecs,\n fullTables,\n );\n checkClientSchema(\n this.#shardID,\n clientSchema,\n this.#tableSpecs,\n fullTables,\n );\n this.#allTableNames.clear();\n for (const table of fullTables.keys()) {\n this.#allTableNames.add(table);\n }\n const primaryKeys = this.#primaryKeys ?? new Map<string, PrimaryKey>();\n this.#primaryKeys = primaryKeys;\n primaryKeys.clear();\n for (const [table, spec] of this.#tableSpecs.entries()) {\n primaryKeys.set(table, spec.tableSpec.primaryKey);\n }\n buildPrimaryKeys(clientSchema, primaryKeys);\n const {replicaVersion} = getSubscriptionState(db);\n this.#replicaVersion = replicaVersion;\n }\n\n /** @returns The replica version. The PipelineDriver must have been initialized. */\n get replicaVersion(): string {\n return must(this.#replicaVersion, 'Not yet initialized');\n }\n\n /**\n * Returns the current version of the database. This will reflect the\n * latest version change when calling {@link advance()} once the\n * iteration has begun.\n */\n currentVersion(): string {\n assert(this.initialized(), 'Not yet initialized');\n return this.#snapshotter.current().version;\n }\n\n /**\n * Returns the current upstream {app}.permissions, or `null` if none are defined.\n */\n currentPermissions(): LoadedPermissions | null {\n assert(this.initialized(), 'Not yet initialized');\n const res = reloadPermissionsIfChanged(\n this.#lc,\n this.#snapshotter.current().db,\n this.#shardID.appID,\n this.#permissions,\n this.#config,\n );\n if (res.changed) {\n this.#permissions = res.permissions;\n this.#lc.debug?.(\n 'Reloaded permissions',\n JSON.stringify(this.#permissions),\n );\n }\n return this.#permissions;\n }\n\n advanceWithoutDiff(): string {\n const {db, version} = this.#snapshotter.advanceWithoutDiff().curr;\n for (const table of this.#tables.values()) {\n table.setDB(db.db);\n }\n return version;\n }\n\n #ensureCostModelExistsIfEnabled(db: Database) {\n let existing = this.#costModels?.get(db);\n if (existing) {\n return existing;\n }\n if (this.#costModels) {\n const costModel = createSQLiteCostModel(db, this.#tableSpecs);\n this.#costModels.set(db, costModel);\n return costModel;\n }\n return undefined;\n }\n\n /**\n * Clears storage used for the pipelines. Call this when the\n * PipelineDriver will no longer be used.\n */\n destroy() {\n this.#storage.destroy();\n this.#snapshotter.destroy();\n }\n\n /** @return Map from query ID to PipelineInfo for all added queries. */\n queries(): ReadonlyMap<string, QueryInfo> {\n return this.#pipelines;\n }\n\n totalHydrationTimeMs(): number {\n let total = 0;\n for (const pipeline of this.#pipelines.values()) {\n total += pipeline.hydrationTimeMs;\n }\n return total;\n }\n\n #resolveScalarSubqueries(ast: AST): {\n ast: AST;\n companionRows: {table: string; row: Row}[];\n companions: CompanionSubquery[];\n companionInputs: Input[];\n } {\n const companionRows: {table: string; row: Row}[] = [];\n const companionInputs: Input[] = [];\n\n const executor = (\n subqueryAST: AST,\n childField: string,\n ): LiteralValue | null | undefined => {\n const input = buildPipeline(\n subqueryAST,\n {\n getSource: name => this.#getSource(name),\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput): Input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n 'scalar-subquery',\n );\n // Consume the full stream rather than using first() to avoid\n // triggering early return on Take's #initialFetch assertion.\n // The subquery AST already has limit: 1, so at most one row is produced.\n let node: Node | undefined;\n for (const n of skipYields(input.fetch({}))) {\n node ??= n;\n }\n if (!node) {\n // Keep the companion alive even with no results — it will\n // detect a future insert that creates the row.\n companionInputs.push(input);\n return undefined;\n }\n companionRows.push({table: subqueryAST.table, row: node.row as Row});\n companionInputs.push(input);\n return (node.row[childField] as LiteralValue) ?? null;\n };\n\n const {ast: resolved, companions} = resolveSimpleScalarSubqueries(\n ast,\n this.#tableSpecs,\n executor,\n );\n return {ast: resolved, companionRows, companions, companionInputs};\n }\n\n /**\n * Adds a pipeline for the query. The method will hydrate the query using the\n * driver's current snapshot of the database and return a stream of results.\n * Henceforth, updates to the query will be returned when the driver is\n * {@link advance}d. The query and its pipeline can be removed with\n * {@link removeQuery()}.\n *\n * If a query with the same queryID is already added, the existing pipeline\n * will be removed and destroyed before adding the new pipeline.\n *\n * @param timer The caller-controlled {@link Timer} used to determine the\n * final hydration time. (The caller may pause and resume the timer\n * when yielding the thread for time-slicing).\n * @return The rows from the initial hydration of the query.\n */\n *addQuery(\n transformationHash: string,\n queryID: string,\n query: AST,\n timer: Timer,\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before adding queries',\n );\n this.removeQuery(queryID);\n const debugDelegate = runtimeDebugFlags.trackRowsVended\n ? new Debug()\n : undefined;\n\n const costModel = this.#ensureCostModelExistsIfEnabled(\n this.#snapshotter.current().db.db,\n );\n\n assert(\n this.#advanceContext === null,\n 'Cannot hydrate while advance is in progress',\n );\n this.#hydrateContext = {\n timer,\n };\n try {\n const {\n ast: resolvedQuery,\n companionRows,\n companions: companionMeta,\n companionInputs,\n } = this.#resolveScalarSubqueries(query);\n\n const input = buildPipeline(\n resolvedQuery,\n {\n debug: debugDelegate,\n enableNotExists: true, // Server-side can handle NOT EXISTS\n getSource: name => this.#getSource(name),\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput, _queryID: string): Input =>\n new MeasurePushOperator(\n input,\n queryID,\n this.#inspectorDelegate,\n 'query-update-server',\n ),\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n queryID,\n costModel,\n );\n const schema = input.getSchema();\n input.setOutput({\n push: change => {\n const streamer = this.#streamer;\n assert(streamer, 'must #startAccumulating() before pushing changes');\n streamer.accumulate(queryID, schema, [change]);\n return [];\n },\n });\n\n yield* hydrateInternal(\n input,\n queryID,\n must(this.#primaryKeys),\n this.#tableSpecs,\n );\n\n for (const {table, row} of companionRows) {\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n yield {\n type: ChangeType.ADD,\n queryID,\n table,\n rowKey: getRowKey(primaryKey, row),\n row,\n } as RowChange;\n }\n\n const hydrationTimeMs = timer.totalElapsed();\n if (runtimeDebugFlags.trackRowCountsVended) {\n if (hydrationTimeMs > this.#logConfig.slowHydrateThreshold) {\n let totalRowsConsidered = 0;\n const lc = this.#lc\n .withContext('queryID', queryID)\n .withContext('hydrationTimeMs', hydrationTimeMs);\n for (const tableName of this.#tables.keys()) {\n const entries = Object.entries(\n debugDelegate?.getVendedRowCounts()[tableName] ?? {},\n );\n totalRowsConsidered += entries.reduce(\n (acc, entry) => acc + entry[1],\n 0,\n );\n lc.info?.(tableName + ' VENDED: ', entries);\n }\n lc.info?.(`Total rows considered: ${totalRowsConsidered}`);\n }\n }\n debugDelegate?.reset();\n\n // Set up live companion pipelines for reactive scalar subquery monitoring.\n const liveCompanions: CompanionPipeline[] = [];\n for (let i = 0; i < companionMeta.length; i++) {\n const meta = companionMeta[i];\n const companionInput = companionInputs[i];\n const companionSchema = companionInput.getSchema();\n const {childField, resolvedValue} = meta;\n companionInput.setOutput({\n push: (change: Change) => {\n let newValue: LiteralValue | null | undefined;\n switch (change[ChangeIndex.TYPE]) {\n case ChangeType.ADD:\n case ChangeType.EDIT:\n newValue =\n (change[ChangeIndex.NODE].row[childField] as LiteralValue) ??\n null;\n break;\n case ChangeType.REMOVE:\n newValue = undefined;\n break;\n case ChangeType.CHILD:\n return [];\n }\n if (!scalarValuesEqual(newValue, resolvedValue)) {\n throw new ResetPipelinesSignal(\n `Scalar subquery value changed for ${meta.ast.table}: ` +\n `${String(resolvedValue)} -> ${String(newValue)}`,\n 'scalar-subquery',\n );\n }\n const streamer = this.#streamer;\n assert(\n streamer,\n 'must #startAccumulating() before pushing changes',\n );\n streamer.accumulate(queryID, companionSchema, [change]);\n return [];\n },\n });\n liveCompanions.push({input: companionInput, childField, resolvedValue});\n }\n\n // Note: This hydrationTime is a wall-clock overestimate, as it does\n // not take time slicing into account. The view-syncer resets this\n // to a more precise processing-time measurement with setHydrationTime().\n this.#pipelines.set(queryID, {\n input,\n hydrationTimeMs,\n transformedAst: resolvedQuery,\n transformationHash,\n companions: liveCompanions,\n });\n } finally {\n this.#hydrateContext = null;\n }\n }\n\n /**\n * Removes the pipeline for the query. This is a no-op if the query\n * was not added.\n */\n removeQuery(queryID: string) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline) {\n this.#pipelines.delete(queryID);\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n }\n\n /**\n * Returns the value of the row with the given primary key `pk`,\n * or `undefined` if there is no such row. The pipeline must have been\n * initialized.\n */\n getRow(table: string, pk: RowKey): Row | undefined {\n assert(this.initialized(), 'Not yet initialized');\n const source = must(this.#tables.get(table));\n return source.getRow(pk as Row);\n }\n\n /**\n * Advances to the new head of the database.\n *\n * @param timer The caller-controlled {@link Timer} that will be used to\n * measure the progress of the advancement and abort with a\n * {@link ResetPipelinesSignal} if it is estimated to take longer\n * than a hydration.\n * @return The resulting row changes for all added queries. Note that the\n * `changes` must be iterated over in their entirety in order to\n * advance the database snapshot.\n */\n advance(timer: Timer): {\n version: string;\n numChanges: number;\n changes: Iterable<RowChange | 'yield'>;\n } {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before advancing',\n );\n const diff = this.#snapshotter.advance(\n this.#tableSpecs,\n this.#allTableNames,\n );\n const {prev, curr, changes} = diff;\n this.#lc.debug?.(\n `advance ${prev.version} => ${curr.version}: ${changes} changes`,\n );\n\n return {\n version: curr.version,\n numChanges: changes,\n changes: this.#advance(diff, timer, changes),\n };\n }\n\n *#advance(\n diff: SnapshotDiff,\n timer: Timer,\n numChanges: number,\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.#hydrateContext === null,\n 'Cannot advance while hydration is in progress',\n );\n const totalHydrationTimeMs = this.totalHydrationTimeMs();\n this.#advanceContext = {\n timer,\n totalHydrationTimeMs,\n numChanges,\n pos: 0,\n };\n this.#lc.info?.(\n `starting pipeline advancement of ${numChanges} changes with an ` +\n `advancement time limited based on total hydration time of ` +\n `${totalHydrationTimeMs} ms.`,\n );\n try {\n for (const {table, prevValues, nextValue} of diff) {\n // Advance progress is checked each time a row is fetched\n // from a TableSource during push processing, but some pushes\n // don't read any rows. Check progress here before processing\n // the next change.\n if (this.#shouldAdvanceYieldMaybeAbortAdvance()) {\n yield 'yield';\n }\n const start = timer.totalElapsed();\n\n let type;\n try {\n const tableSource = this.#tables.get(table);\n if (!tableSource) {\n // no pipelines read from this table, so no need to process the change\n continue;\n }\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n let editOldRow: Row | undefined = undefined;\n for (const prevValue of prevValues) {\n if (\n nextValue &&\n deepEqual(\n getRowKey(primaryKey, prevValue as Row) as JSONValue,\n getRowKey(primaryKey, nextValue as Row) as JSONValue,\n )\n ) {\n editOldRow = prevValue;\n } else {\n if (nextValue) {\n this.#conflictRowsDeleted.add(1);\n }\n yield* this.#push(\n tableSource,\n makeSourceChangeRemove(prevValue as Row),\n );\n }\n }\n if (nextValue) {\n if (editOldRow) {\n yield* this.#push(\n tableSource,\n makeSourceChangeEdit(nextValue as Row, editOldRow),\n );\n } else {\n yield* this.#push(\n tableSource,\n makeSourceChangeAdd(nextValue as Row),\n );\n }\n }\n } finally {\n this.#advanceContext.pos++;\n }\n\n const elapsed = timer.totalElapsed() - start;\n this.#advanceTime.record(elapsed / 1000, {\n table,\n type,\n });\n }\n\n // Set the new snapshot on all TableSources.\n const {curr} = diff;\n for (const table of this.#tables.values()) {\n table.setDB(curr.db.db);\n }\n this.#ensureCostModelExistsIfEnabled(curr.db.db);\n this.#lc.debug?.(`Advanced to ${curr.version}`);\n } finally {\n this.#advanceContext = null;\n }\n }\n\n /** Implements `BuilderDelegate.getSource()` */\n #getSource(tableName: string): Source {\n let source = this.#tables.get(tableName);\n if (source) {\n return source;\n }\n\n const tableSpec = mustGetTableSpec(this.#tableSpecs, tableName);\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, tableName);\n\n const {db} = this.#snapshotter.current();\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n db.db,\n tableName,\n tableSpec.zqlSpec,\n primaryKey,\n () => this.#shouldYield(),\n );\n this.#tables.set(tableName, source);\n this.#lc.debug?.(`created TableSource for ${tableName}`);\n return source;\n }\n\n #shouldYield(): boolean {\n if (this.#hydrateContext) {\n return this.#hydrateContext.timer.elapsedLap() > this.#yieldThresholdMs();\n }\n if (this.#advanceContext) {\n return this.#shouldAdvanceYieldMaybeAbortAdvance();\n }\n throw new Error('shouldYield called outside of hydration or advancement');\n }\n\n /**\n * Cancel the advancement processing, by throwing a ResetPipelinesSignal, if\n * it has taken longer than half the total hydration time to make it through\n * half of the advancement, or if processing time exceeds total hydration\n * time. This serves as both a circuit breaker for very large transactions,\n * as well as a bound on the amount of time the previous connection locks\n * the inactive WAL file (as the lock prevents WAL2 from switching to the\n * free WAL when the current one is over the size limit, which can make\n * the WAL grow continuously and compound slowness).\n * This is checked:\n * 1. before starting to process each change in an advancement is processed\n * 2. whenever a row is fetched from a TableSource during push processing\n */\n #shouldAdvanceYieldMaybeAbortAdvance(): boolean {\n const {\n pos,\n numChanges,\n timer: advanceTimer,\n totalHydrationTimeMs,\n } = must(this.#advanceContext);\n const elapsed = advanceTimer.totalElapsed();\n if (\n elapsed > MIN_ADVANCEMENT_TIME_LIMIT_MS &&\n (elapsed > totalHydrationTimeMs ||\n (elapsed > totalHydrationTimeMs / 2 && pos <= numChanges / 2))\n ) {\n throw new ResetPipelinesSignal(\n `Advancement exceeded timeout at ${pos} of ${numChanges} changes ` +\n `after ${elapsed} ms. Advancement time limited based on total ` +\n `hydration time of ${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n );\n }\n return advanceTimer.elapsedLap() > this.#yieldThresholdMs();\n }\n\n /** Implements `BuilderDelegate.createStorage()` */\n #createStorage(): Storage {\n return this.#storage.createStorage();\n }\n\n *#push(\n source: TableSource,\n change: SourceChange,\n ): Iterable<RowChange | 'yield'> {\n this.#startAccumulating();\n try {\n for (const val of source.genPush(change)) {\n if (val === 'yield') {\n yield 'yield';\n }\n for (const changeOrYield of this.#stopAccumulating().stream()) {\n yield changeOrYield;\n }\n this.#startAccumulating();\n }\n } finally {\n if (this.#streamer !== null) {\n this.#stopAccumulating();\n }\n }\n }\n\n #startAccumulating() {\n assert(this.#streamer === null, 'Streamer already started');\n this.#streamer = new Streamer(must(this.#primaryKeys), this.#tableSpecs);\n }\n\n #stopAccumulating(): Streamer {\n const streamer = this.#streamer;\n assert(streamer, 'Streamer not started');\n this.#streamer = null;\n return streamer;\n }\n}\n\nclass Streamer {\n readonly #primaryKeys: Map<string, PrimaryKey>;\n readonly #tableSpecs: Map<string, LiteAndZqlSpec>;\n\n constructor(\n primaryKeys: Map<string, PrimaryKey>,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n ) {\n this.#primaryKeys = primaryKeys;\n this.#tableSpecs = tableSpecs;\n }\n\n readonly #changes: [\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ][] = [];\n\n accumulate(\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ): this {\n this.#changes.push([queryID, schema, changes]);\n return this;\n }\n\n *stream(): Iterable<RowChange | 'yield'> {\n for (const [queryID, schema, changes] of this.#changes) {\n yield* this.#streamChanges(queryID, schema, changes);\n }\n }\n\n *#streamChanges(\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n // We do not sync rows gathered by the permissions\n // system to the client.\n if (schema.system === 'permissions') {\n return;\n }\n\n for (const change of changes) {\n if (change === 'yield') {\n yield change;\n continue;\n }\n const type = change[ChangeIndex.TYPE];\n switch (type) {\n case ChangeType.REMOVE:\n case ChangeType.ADD: {\n yield* this.#streamNodes(queryID, schema, type, () => [\n change[ChangeIndex.NODE],\n ]);\n break;\n }\n\n case ChangeType.CHILD: {\n const child = change[ChangeIndex.CHILD_DATA];\n const childSchema = must(\n schema.relationships[child.relationshipName],\n );\n\n yield* this.#streamChanges(queryID, childSchema, [child.change]);\n break;\n }\n case ChangeType.EDIT:\n yield* this.#streamNodes(queryID, schema, type, () => [\n {row: change[ChangeIndex.NODE].row, relationships: {}},\n ]);\n break;\n default:\n unreachable(change[ChangeIndex.TYPE]);\n }\n }\n }\n\n *#streamNodes(\n queryID: string,\n schema: SourceSchema,\n op: ChangeType.ADD | ChangeType.REMOVE | ChangeType.EDIT,\n nodes: () => Iterable<Node | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n const {tableName: table, system} = schema;\n\n const primaryKey = must(this.#primaryKeys.get(table));\n const spec = must(this.#tableSpecs.get(table)).tableSpec;\n\n // We do not sync rows gathered by the permissions\n // system to the client.\n if (system === 'permissions') {\n return;\n }\n\n for (const node of nodes()) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n const {relationships} = node;\n let {row} = node;\n const rowKey = getRowKey(primaryKey, row);\n if (op !== ChangeType.REMOVE) {\n const rowVersion = row[ZERO_VERSION_COLUMN_NAME];\n if (\n typeof rowVersion === 'string' &&\n rowVersion < (spec.minRowVersion ?? '00')\n ) {\n row = {...row, [ZERO_VERSION_COLUMN_NAME]: spec.minRowVersion};\n }\n }\n\n yield {\n type: op,\n queryID,\n table,\n rowKey,\n row: op === ChangeType.REMOVE ? undefined : row,\n } as RowChange;\n\n for (const [relationship, children] of Object.entries(relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n yield* this.#streamNodes(queryID, childSchema, op, children);\n }\n }\n }\n}\n\nfunction* toAdds(nodes: Iterable<Node | 'yield'>): Iterable<Change | 'yield'> {\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n yield [ChangeType.ADD, node, null];\n }\n}\n\nfunction getRowKey(cols: PrimaryKey, row: Row): RowKey {\n return Object.fromEntries(cols.map(col => [col, must(row[col])]));\n}\n\n/**\n * Core hydration logic used by {@link PipelineDriver#addQuery}, extracted to a\n * function for reuse by bin-analyze so that bin-analyze's hydration logic\n * is as close as possible to zero-cache's real hydration logic.\n */\nexport function* hydrate(\n input: Input,\n hash: string,\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n const res = input.fetch({});\n const streamer = new Streamer(\n buildPrimaryKeys(clientSchema),\n tableSpecs,\n ).accumulate(hash, input.getSchema(), toAdds(res));\n yield* streamer.stream();\n}\n\nexport function* hydrateInternal(\n input: Input,\n hash: string,\n primaryKeys: Map<string, PrimaryKey>,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n const res = input.fetch({});\n const streamer = new Streamer(primaryKeys, tableSpecs).accumulate(\n hash,\n input.getSchema(),\n toAdds(res),\n );\n yield* streamer.stream();\n}\n\nfunction buildPrimaryKeys(\n clientSchema: ClientSchema,\n primaryKeys: Map<string, PrimaryKey> = new Map<string, PrimaryKey>(),\n) {\n for (const [tableName, {primaryKey}] of Object.entries(clientSchema.tables)) {\n primaryKeys.set(tableName, primaryKey as unknown as PrimaryKey);\n }\n return primaryKeys;\n}\n\nfunction mustGetPrimaryKey(\n primaryKeys: Map<string, PrimaryKey> | null,\n table: string,\n): PrimaryKey {\n const pKeys = must(primaryKeys, 'primaryKey map must be non-null');\n\n const rv = pKeys.get(table);\n assert(\n rv,\n () =>\n // oxlint-disable-next-line typescript/restrict-template-expressions e18e/prefer-array-to-sorted\n `table '${table}' is not one of: ${[...pKeys.keys()].sort()}. ` +\n `Check the spelling and ensure that the table has a primary key.`,\n );\n return rv;\n}\n\n/**\n * Compares two scalar subquery resolved values for equality.\n * Unlike `valuesEqual` in data.ts (which treats null != null for join\n * semantics), this uses identity semantics: undefined === undefined\n * (no row matched), null === null (row matched but field was NULL).\n */\nfunction scalarValuesEqual(\n a: LiteralValue | null | undefined,\n b: LiteralValue | null | undefined,\n): boolean {\n return a === b;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pipeline-driver.js","names":["#tables","#pipelines","#rowSetSignatures","#lc","#snapshotter","#storage","#shardID","#logConfig","#config","#tableSpecs","#allTableNames","#costModels","#yieldThresholdMs","#advanceTime","#conflictRowsDeleted","#inspectorDelegate","#initAndResetCommon","#primaryKeys","#replicaVersion","#permissions","#getSource","#createStorage","#trackRowSetSignatures","#addQueryImpl","#ensureCostModelExistsIfEnabled","#advanceContext","#hydrateContext","#resolveScalarSubqueries","#streamer","#advance","#shouldAdvanceYieldMaybeAbortAdvance","#push","#shouldYield","#startAccumulating","#stopAccumulating","#changes","#streamChanges","#streamNodes"],"sources":["../../../../../../zero-cache/src/services/view-syncer/pipeline-driver.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {deepEqual, type JSONValue} from '../../../../shared/src/json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {AST, LiteralValue} from '../../../../zero-protocol/src/ast.ts';\nimport type {ClientSchema} from '../../../../zero-protocol/src/client-schema.ts';\nimport type {Row} from '../../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../../zero-protocol/src/primary-key.ts';\nimport {buildPipeline} from '../../../../zql/src/builder/builder.ts';\nimport {\n Debug,\n runtimeDebugFlags,\n} from '../../../../zql/src/builder/debug-delegate.ts';\nimport {ChangeIndex} from '../../../../zql/src/ivm/change-index.ts';\nimport {ChangeType} from '../../../../zql/src/ivm/change-type.ts';\nimport type {Change} from '../../../../zql/src/ivm/change.ts';\nimport type {Node} from '../../../../zql/src/ivm/data.ts';\nimport {\n skipYields,\n type Input,\n type Storage,\n} from '../../../../zql/src/ivm/operator.ts';\nimport type {SourceSchema} from '../../../../zql/src/ivm/schema.ts';\nimport {\n type Source,\n type SourceChange,\n type SourceInput,\n makeSourceChangeAdd,\n makeSourceChangeEdit,\n makeSourceChangeRemove,\n} from '../../../../zql/src/ivm/source.ts';\nimport type {ConnectionCostModel} from '../../../../zql/src/planner/planner-connection.ts';\nimport {MeasurePushOperator} from '../../../../zql/src/query/measure-push-operator.ts';\nimport type {ClientGroupStorage} from '../../../../zqlite/src/database-storage.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n resolveSimpleScalarSubqueries,\n type CompanionSubquery,\n} from '../../../../zqlite/src/resolve-scalar-subqueries.ts';\nimport {createSQLiteCostModel} from '../../../../zqlite/src/sqlite-cost-model.ts';\nimport {TableSource} from '../../../../zqlite/src/table-source.ts';\nimport {\n reloadPermissionsIfChanged,\n type LoadedPermissions,\n} from '../../auth/load-permissions.ts';\nimport type {LogConfig, ZeroConfig} from '../../config/zero-config.ts';\nimport {computeZqlSpecs, mustGetTableSpec} from '../../db/lite-tables.ts';\nimport type {LiteAndZqlSpec, LiteTableSpec} from '../../db/specs.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../observability/metrics.ts';\nimport type {InspectorDelegate} from '../../server/inspector-delegate.ts';\nimport {type RowKey} from '../../types/row-key.ts';\nimport {type ShardID} from '../../types/shards.ts';\nimport {\n getSubscriptionState,\n ZERO_VERSION_COLUMN_NAME,\n} from '../replicator/schema/replication-state.ts';\nimport {checkClientSchema} from './client-schema.ts';\nimport {rowIDSignatureUnit} from './row-set-signature.ts';\nimport type {Snapshotter} from './snapshotter.ts';\nimport {ResetPipelinesSignal, type SnapshotDiff} from './snapshotter.ts';\n\ntype RowOp<Op extends Omit<ChangeType, ChangeType.CHILD>> = {\n readonly type: Op;\n readonly queryID: string;\n readonly table: string;\n readonly rowKey: Row;\n readonly row: Row;\n};\n\nexport type RowAdd = RowOp<ChangeType.ADD>;\n\nexport type RowRemove = RowOp<ChangeType.REMOVE>;\n\nexport type RowEdit = RowOp<ChangeType.EDIT>;\n\nexport type RowChange = RowAdd | RowRemove | RowEdit;\n\ntype CompanionPipeline = {\n readonly input: Input;\n readonly childField: string;\n readonly resolvedValue: LiteralValue | null | undefined;\n};\n\ntype Pipeline = {\n readonly input: Input;\n readonly hydrationTimeMs: number;\n readonly transformedAst: AST;\n readonly transformationHash: string;\n readonly companions: readonly CompanionPipeline[];\n};\n\ntype QueryInfo = {\n readonly transformedAst: AST;\n readonly transformationHash: string;\n};\n\ntype AdvanceContext = {\n readonly timer: Timer;\n readonly totalHydrationTimeMs: number;\n readonly numChanges: number;\n pos: number;\n};\n\ntype HydrateContext = {\n readonly timer: Timer;\n};\n\nexport type Timer = {\n elapsedLap: () => number;\n totalElapsed: () => number;\n};\n\n/**\n * No matter how fast hydration is, advancement is given at least this long to\n * complete before doing a pipeline reset.\n */\nconst MIN_ADVANCEMENT_TIME_LIMIT_MS = 50;\n\n/**\n * Manages the state of IVM pipelines for a given ViewSyncer (i.e. client group).\n */\nexport class PipelineDriver {\n readonly #tables = new Map<string, TableSource>();\n // Query id to pipeline\n readonly #pipelines = new Map<string, Pipeline>();\n /**\n * 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 implicitly reseeds from `0n` because {@link addQuery} calls\n * {@link removeQuery} first, which deletes the entry.\n */\n readonly #rowSetSignatures = new Map<string, bigint>();\n\n readonly #lc: LogContext;\n readonly #snapshotter: Snapshotter;\n readonly #storage: ClientGroupStorage;\n readonly #shardID: ShardID;\n readonly #logConfig: LogConfig;\n readonly #config: ZeroConfig | undefined;\n readonly #tableSpecs = new Map<string, LiteAndZqlSpec>();\n readonly #allTableNames = new Set<string>();\n readonly #costModels: WeakMap<Database, ConnectionCostModel> | undefined;\n readonly #yieldThresholdMs: () => number;\n #streamer: Streamer | null = null;\n #hydrateContext: HydrateContext | null = null;\n #advanceContext: AdvanceContext | null = null;\n #replicaVersion: string | null = null;\n #primaryKeys: Map<string, PrimaryKey> | null = null;\n #permissions: LoadedPermissions | null = null;\n\n readonly #advanceTime = getOrCreateHistogram('sync', 'ivm.advance-time', {\n description:\n 'Time to advance all queries for a given client group for in response to a single change.',\n unit: 's',\n });\n\n readonly #conflictRowsDeleted = getOrCreateCounter(\n 'sync',\n 'ivm.conflict-rows-deleted',\n 'Number of rows deleted because they conflicted with added row',\n );\n\n readonly #inspectorDelegate: InspectorDelegate;\n\n constructor(\n lc: LogContext,\n logConfig: LogConfig,\n snapshotter: Snapshotter,\n shardID: ShardID,\n storage: ClientGroupStorage,\n clientGroupID: string,\n inspectorDelegate: InspectorDelegate,\n yieldThresholdMs: () => number,\n enablePlanner?: boolean,\n config?: ZeroConfig,\n ) {\n this.#lc = lc.withContext('clientGroupID', clientGroupID);\n this.#snapshotter = snapshotter;\n this.#storage = storage;\n this.#shardID = shardID;\n this.#logConfig = logConfig;\n this.#config = config;\n this.#inspectorDelegate = inspectorDelegate;\n this.#costModels = enablePlanner ? new WeakMap() : undefined;\n this.#yieldThresholdMs = yieldThresholdMs;\n }\n\n /**\n * Initializes the PipelineDriver to the current head of the database.\n * Queries can then be added (i.e. hydrated) with {@link addQuery()}.\n *\n * Must only be called once.\n */\n init(clientSchema: ClientSchema) {\n assert(!this.#snapshotter.initialized(), 'Already initialized');\n this.#snapshotter.init();\n this.#initAndResetCommon(clientSchema);\n }\n\n /**\n * @returns Whether the PipelineDriver has been initialized.\n */\n initialized(): boolean {\n return this.#snapshotter.initialized();\n }\n\n /**\n * Clears the current pipelines and TableSources, returning the PipelineDriver\n * to its initial state. This should be called in response to a schema change,\n * as TableSources need to be recomputed.\n */\n reset(clientSchema: ClientSchema) {\n for (const pipeline of this.#pipelines.values()) {\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n this.#pipelines.clear();\n this.#tables.clear();\n this.#allTableNames.clear();\n this.#rowSetSignatures.clear();\n this.#initAndResetCommon(clientSchema);\n }\n\n #initAndResetCommon(clientSchema: ClientSchema) {\n const {db} = this.#snapshotter.current();\n const fullTables = new Map<string, LiteTableSpec>();\n computeZqlSpecs(\n this.#lc,\n db.db,\n {includeBackfillingColumns: false},\n this.#tableSpecs,\n fullTables,\n );\n checkClientSchema(\n this.#shardID,\n clientSchema,\n this.#tableSpecs,\n fullTables,\n );\n this.#allTableNames.clear();\n for (const table of fullTables.keys()) {\n this.#allTableNames.add(table);\n }\n const primaryKeys = this.#primaryKeys ?? new Map<string, PrimaryKey>();\n this.#primaryKeys = primaryKeys;\n primaryKeys.clear();\n for (const [table, spec] of this.#tableSpecs.entries()) {\n primaryKeys.set(table, spec.tableSpec.primaryKey);\n }\n buildPrimaryKeys(clientSchema, primaryKeys);\n const {replicaVersion} = getSubscriptionState(db);\n this.#replicaVersion = replicaVersion;\n }\n\n /** @returns The replica version. The PipelineDriver must have been initialized. */\n get replicaVersion(): string {\n return must(this.#replicaVersion, 'Not yet initialized');\n }\n\n /**\n * Returns the current version of the database. This will reflect the\n * latest version change when calling {@link advance()} once the\n * iteration has begun.\n */\n currentVersion(): string {\n assert(this.initialized(), 'Not yet initialized');\n return this.#snapshotter.current().version;\n }\n\n /**\n * Returns the current upstream {app}.permissions, or `null` if none are defined.\n */\n currentPermissions(): LoadedPermissions | null {\n assert(this.initialized(), 'Not yet initialized');\n const res = reloadPermissionsIfChanged(\n this.#lc,\n this.#snapshotter.current().db,\n this.#shardID.appID,\n this.#permissions,\n this.#config,\n );\n if (res.changed) {\n this.#permissions = res.permissions;\n this.#lc.debug?.(\n 'Reloaded permissions',\n JSON.stringify(this.#permissions),\n );\n }\n return this.#permissions;\n }\n\n advanceWithoutDiff(): string {\n const {db, version} = this.#snapshotter.advanceWithoutDiff().curr;\n for (const table of this.#tables.values()) {\n table.setDB(db.db);\n }\n return version;\n }\n\n #ensureCostModelExistsIfEnabled(db: Database) {\n let existing = this.#costModels?.get(db);\n if (existing) {\n return existing;\n }\n if (this.#costModels) {\n const costModel = createSQLiteCostModel(db, this.#tableSpecs);\n this.#costModels.set(db, costModel);\n return costModel;\n }\n return undefined;\n }\n\n /**\n * Clears storage used for the pipelines. Call this when the\n * PipelineDriver will no longer be used.\n */\n destroy() {\n this.#storage.destroy();\n this.#snapshotter.destroy();\n }\n\n /** @return Map from query ID to PipelineInfo for all added queries. */\n queries(): ReadonlyMap<string, QueryInfo> {\n return this.#pipelines;\n }\n\n totalHydrationTimeMs(): number {\n let total = 0;\n for (const pipeline of this.#pipelines.values()) {\n total += pipeline.hydrationTimeMs;\n }\n return total;\n }\n\n #resolveScalarSubqueries(ast: AST): {\n ast: AST;\n companionRows: {table: string; row: Row}[];\n companions: CompanionSubquery[];\n companionInputs: Input[];\n } {\n const companionRows: {table: string; row: Row}[] = [];\n const companionInputs: Input[] = [];\n\n const executor = (\n subqueryAST: AST,\n childField: string,\n ): LiteralValue | null | undefined => {\n const input = buildPipeline(\n subqueryAST,\n {\n getSource: name => this.#getSource(name),\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput): Input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n 'scalar-subquery',\n );\n // Consume the full stream rather than using first() to avoid\n // triggering early return on Take's #initialFetch assertion.\n // The subquery AST already has limit: 1, so at most one row is produced.\n let node: Node | undefined;\n for (const n of skipYields(input.fetch({}))) {\n node ??= n;\n }\n if (!node) {\n // Keep the companion alive even with no results — it will\n // detect a future insert that creates the row.\n companionInputs.push(input);\n return undefined;\n }\n companionRows.push({table: subqueryAST.table, row: node.row as Row});\n companionInputs.push(input);\n return (node.row[childField] as LiteralValue) ?? null;\n };\n\n const {ast: resolved, companions} = resolveSimpleScalarSubqueries(\n ast,\n this.#tableSpecs,\n executor,\n );\n return {ast: resolved, companionRows, companions, companionInputs};\n }\n\n /**\n * Adds a pipeline for the query. The method will hydrate the query using the\n * driver's current snapshot of the database and return a stream of results.\n * Henceforth, updates to the query will be returned when the driver is\n * {@link advance}d. The query and its pipeline can be removed with\n * {@link removeQuery()}.\n *\n * If a query with the same queryID is already added, the existing pipeline\n * will be removed and destroyed before adding the new pipeline.\n *\n * @param timer The caller-controlled {@link Timer} used to determine the\n * final hydration time. (The caller may pause and resume the timer\n * when yielding the thread for time-slicing).\n * @return The rows from the initial hydration of the query.\n */\n addQuery(\n transformationHash: string,\n queryID: string,\n query: AST,\n timer: Timer,\n ): Iterable<RowChange | 'yield'> {\n return this.#trackRowSetSignatures(\n this.#addQueryImpl(transformationHash, queryID, query, timer),\n );\n }\n\n *#addQueryImpl(\n transformationHash: string,\n queryID: string,\n query: AST,\n timer: Timer,\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before adding queries',\n );\n this.removeQuery(queryID);\n const debugDelegate = runtimeDebugFlags.trackRowsVended\n ? new Debug()\n : undefined;\n\n const costModel = this.#ensureCostModelExistsIfEnabled(\n this.#snapshotter.current().db.db,\n );\n\n assert(\n this.#advanceContext === null,\n 'Cannot hydrate while advance is in progress',\n );\n this.#hydrateContext = {\n timer,\n };\n try {\n const {\n ast: resolvedQuery,\n companionRows,\n companions: companionMeta,\n companionInputs,\n } = this.#resolveScalarSubqueries(query);\n\n const input = buildPipeline(\n resolvedQuery,\n {\n debug: debugDelegate,\n enableNotExists: true, // Server-side can handle NOT EXISTS\n getSource: name => this.#getSource(name),\n createStorage: () => this.#createStorage(),\n decorateSourceInput: (input: SourceInput, _queryID: string): Input =>\n new MeasurePushOperator(\n input,\n queryID,\n this.#inspectorDelegate,\n 'query-update-server',\n ),\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n queryID,\n costModel,\n );\n const schema = input.getSchema();\n input.setOutput({\n push: change => {\n const streamer = this.#streamer;\n assert(streamer, 'must #startAccumulating() before pushing changes');\n streamer.accumulate(queryID, schema, [change]);\n return [];\n },\n });\n\n yield* hydrateInternal(\n input,\n queryID,\n must(this.#primaryKeys),\n this.#tableSpecs,\n );\n\n for (const {table, row} of companionRows) {\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n yield {\n type: ChangeType.ADD,\n queryID,\n table,\n rowKey: getRowKey(primaryKey, row),\n row,\n } as RowChange;\n }\n\n const hydrationTimeMs = timer.totalElapsed();\n if (runtimeDebugFlags.trackRowCountsVended) {\n if (hydrationTimeMs > this.#logConfig.slowHydrateThreshold) {\n let totalRowsConsidered = 0;\n const lc = this.#lc\n .withContext('queryID', queryID)\n .withContext('hydrationTimeMs', hydrationTimeMs);\n for (const tableName of this.#tables.keys()) {\n const entries = Object.entries(\n debugDelegate?.getVendedRowCounts()[tableName] ?? {},\n );\n totalRowsConsidered += entries.reduce(\n (acc, entry) => acc + entry[1],\n 0,\n );\n lc.info?.(tableName + ' VENDED: ', entries);\n }\n lc.info?.(`Total rows considered: ${totalRowsConsidered}`);\n }\n }\n debugDelegate?.reset();\n\n // Set up live companion pipelines for reactive scalar subquery monitoring.\n const liveCompanions: CompanionPipeline[] = [];\n for (let i = 0; i < companionMeta.length; i++) {\n const meta = companionMeta[i];\n const companionInput = companionInputs[i];\n const companionSchema = companionInput.getSchema();\n const {childField, resolvedValue} = meta;\n companionInput.setOutput({\n push: (change: Change) => {\n let newValue: LiteralValue | null | undefined;\n switch (change[ChangeIndex.TYPE]) {\n case ChangeType.ADD:\n case ChangeType.EDIT:\n newValue =\n (change[ChangeIndex.NODE].row[childField] as LiteralValue) ??\n null;\n break;\n case ChangeType.REMOVE:\n newValue = undefined;\n break;\n case ChangeType.CHILD:\n return [];\n }\n if (!scalarValuesEqual(newValue, resolvedValue)) {\n throw new ResetPipelinesSignal(\n `Scalar subquery value changed for ${meta.ast.table}: ` +\n `${String(resolvedValue)} -> ${String(newValue)}`,\n 'scalar-subquery',\n );\n }\n const streamer = this.#streamer;\n assert(\n streamer,\n 'must #startAccumulating() before pushing changes',\n );\n streamer.accumulate(queryID, companionSchema, [change]);\n return [];\n },\n });\n liveCompanions.push({input: companionInput, childField, resolvedValue});\n }\n\n // Note: This hydrationTime is a wall-clock overestimate, as it does\n // not take time slicing into account. The view-syncer resets this\n // to a more precise processing-time measurement with setHydrationTime().\n this.#pipelines.set(queryID, {\n input,\n hydrationTimeMs,\n transformedAst: resolvedQuery,\n transformationHash,\n companions: liveCompanions,\n });\n } finally {\n this.#hydrateContext = null;\n }\n }\n\n /**\n * Removes the pipeline for the query. This is a no-op if the query\n * was not added.\n */\n removeQuery(queryID: string) {\n const pipeline = this.#pipelines.get(queryID);\n if (pipeline) {\n this.#pipelines.delete(queryID);\n pipeline.input.destroy();\n for (const companion of pipeline.companions) {\n companion.input.destroy();\n }\n }\n this.#rowSetSignatures.delete(queryID);\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 * 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 }\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 * @return The resulting row changes for all added queries. Note that the\n * `changes` must be iterated over in their entirety in order to\n * advance the database snapshot.\n */\n advance(timer: Timer): {\n version: string;\n numChanges: number;\n changes: Iterable<RowChange | 'yield'>;\n } {\n assert(\n this.initialized(),\n 'Pipeline driver must be initialized before advancing',\n );\n const diff = this.#snapshotter.advance(\n this.#tableSpecs,\n this.#allTableNames,\n );\n const {prev, curr, changes} = diff;\n this.#lc.debug?.(\n `advance ${prev.version} => ${curr.version}: ${changes} changes`,\n );\n\n return {\n version: curr.version,\n numChanges: changes,\n changes: this.#trackRowSetSignatures(this.#advance(diff, timer, changes)),\n };\n }\n\n *#advance(\n diff: SnapshotDiff,\n timer: Timer,\n numChanges: number,\n ): Iterable<RowChange | 'yield'> {\n assert(\n this.#hydrateContext === null,\n 'Cannot advance while hydration is in progress',\n );\n const totalHydrationTimeMs = this.totalHydrationTimeMs();\n this.#advanceContext = {\n timer,\n totalHydrationTimeMs,\n numChanges,\n pos: 0,\n };\n this.#lc.info?.(\n `starting pipeline advancement of ${numChanges} changes with an ` +\n `advancement time limited based on total hydration time of ` +\n `${totalHydrationTimeMs} ms.`,\n );\n try {\n for (const {table, prevValues, nextValue} of diff) {\n // Advance progress is checked each time a row is fetched\n // from a TableSource during push processing, but some pushes\n // don't read any rows. Check progress here before processing\n // the next change.\n if (this.#shouldAdvanceYieldMaybeAbortAdvance()) {\n yield 'yield';\n }\n const start = timer.totalElapsed();\n\n let type;\n try {\n const tableSource = this.#tables.get(table);\n if (!tableSource) {\n // no pipelines read from this table, so no need to process the change\n continue;\n }\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);\n let editOldRow: Row | undefined = undefined;\n for (const prevValue of prevValues) {\n if (\n nextValue &&\n deepEqual(\n getRowKey(primaryKey, prevValue as Row) as JSONValue,\n getRowKey(primaryKey, nextValue as Row) as JSONValue,\n )\n ) {\n editOldRow = prevValue;\n } else {\n if (nextValue) {\n this.#conflictRowsDeleted.add(1);\n }\n yield* this.#push(\n tableSource,\n makeSourceChangeRemove(prevValue as Row),\n );\n }\n }\n if (nextValue) {\n if (editOldRow) {\n yield* this.#push(\n tableSource,\n makeSourceChangeEdit(nextValue as Row, editOldRow),\n );\n } else {\n yield* this.#push(\n tableSource,\n makeSourceChangeAdd(nextValue as Row),\n );\n }\n }\n } finally {\n this.#advanceContext.pos++;\n }\n\n const elapsed = timer.totalElapsed() - start;\n this.#advanceTime.record(elapsed / 1000, {\n table,\n type,\n });\n }\n\n // Set the new snapshot on all TableSources.\n const {curr} = diff;\n for (const table of this.#tables.values()) {\n table.setDB(curr.db.db);\n }\n this.#ensureCostModelExistsIfEnabled(curr.db.db);\n this.#lc.debug?.(`Advanced to ${curr.version}`);\n } finally {\n this.#advanceContext = null;\n }\n }\n\n /** Implements `BuilderDelegate.getSource()` */\n #getSource(tableName: string): Source {\n let source = this.#tables.get(tableName);\n if (source) {\n return source;\n }\n\n const tableSpec = mustGetTableSpec(this.#tableSpecs, tableName);\n const primaryKey = mustGetPrimaryKey(this.#primaryKeys, tableName);\n\n const {db} = this.#snapshotter.current();\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n db.db,\n tableName,\n tableSpec.zqlSpec,\n primaryKey,\n () => this.#shouldYield(),\n );\n this.#tables.set(tableName, source);\n this.#lc.debug?.(`created TableSource for ${tableName}`);\n return source;\n }\n\n #shouldYield(): boolean {\n if (this.#hydrateContext) {\n return this.#hydrateContext.timer.elapsedLap() > this.#yieldThresholdMs();\n }\n if (this.#advanceContext) {\n return this.#shouldAdvanceYieldMaybeAbortAdvance();\n }\n throw new Error('shouldYield called outside of hydration or advancement');\n }\n\n /**\n * Cancel the advancement processing, by throwing a ResetPipelinesSignal, if\n * it has taken longer than half the total hydration time to make it through\n * half of the advancement, or if processing time exceeds total hydration\n * time. This serves as both a circuit breaker for very large transactions,\n * as well as a bound on the amount of time the previous connection locks\n * the inactive WAL file (as the lock prevents WAL2 from switching to the\n * free WAL when the current one is over the size limit, which can make\n * the WAL grow continuously and compound slowness).\n * This is checked:\n * 1. before starting to process each change in an advancement is processed\n * 2. whenever a row is fetched from a TableSource during push processing\n */\n #shouldAdvanceYieldMaybeAbortAdvance(): boolean {\n const {\n pos,\n numChanges,\n timer: advanceTimer,\n totalHydrationTimeMs,\n } = must(this.#advanceContext);\n const elapsed = advanceTimer.totalElapsed();\n if (\n elapsed > MIN_ADVANCEMENT_TIME_LIMIT_MS &&\n (elapsed > totalHydrationTimeMs ||\n (elapsed > totalHydrationTimeMs / 2 && pos <= numChanges / 2))\n ) {\n throw new ResetPipelinesSignal(\n `Advancement exceeded timeout at ${pos} of ${numChanges} changes ` +\n `after ${elapsed} ms. Advancement time limited based on total ` +\n `hydration time of ${totalHydrationTimeMs} ms.`,\n 'advancement-timeout',\n );\n }\n return advanceTimer.elapsedLap() > this.#yieldThresholdMs();\n }\n\n /** Implements `BuilderDelegate.createStorage()` */\n #createStorage(): Storage {\n return this.#storage.createStorage();\n }\n\n *#push(\n source: TableSource,\n change: SourceChange,\n ): Iterable<RowChange | 'yield'> {\n this.#startAccumulating();\n try {\n for (const val of source.genPush(change)) {\n if (val === 'yield') {\n yield 'yield';\n }\n for (const changeOrYield of this.#stopAccumulating().stream()) {\n yield changeOrYield;\n }\n this.#startAccumulating();\n }\n } finally {\n if (this.#streamer !== null) {\n this.#stopAccumulating();\n }\n }\n }\n\n #startAccumulating() {\n assert(this.#streamer === null, 'Streamer already started');\n this.#streamer = new Streamer(must(this.#primaryKeys), this.#tableSpecs);\n }\n\n #stopAccumulating(): Streamer {\n const streamer = this.#streamer;\n assert(streamer, 'Streamer not started');\n this.#streamer = null;\n return streamer;\n }\n}\n\nclass Streamer {\n readonly #primaryKeys: Map<string, PrimaryKey>;\n readonly #tableSpecs: Map<string, LiteAndZqlSpec>;\n\n constructor(\n primaryKeys: Map<string, PrimaryKey>,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n ) {\n this.#primaryKeys = primaryKeys;\n this.#tableSpecs = tableSpecs;\n }\n\n readonly #changes: [\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ][] = [];\n\n accumulate(\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ): this {\n this.#changes.push([queryID, schema, changes]);\n return this;\n }\n\n *stream(): Iterable<RowChange | 'yield'> {\n for (const [queryID, schema, changes] of this.#changes) {\n yield* this.#streamChanges(queryID, schema, changes);\n }\n }\n\n *#streamChanges(\n queryID: string,\n schema: SourceSchema,\n changes: Iterable<Change | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n // We do not sync rows gathered by the permissions\n // system to the client.\n if (schema.system === 'permissions') {\n return;\n }\n\n for (const change of changes) {\n if (change === 'yield') {\n yield change;\n continue;\n }\n const type = change[ChangeIndex.TYPE];\n switch (type) {\n case ChangeType.REMOVE:\n case ChangeType.ADD: {\n yield* this.#streamNodes(queryID, schema, type, () => [\n change[ChangeIndex.NODE],\n ]);\n break;\n }\n\n case ChangeType.CHILD: {\n const child = change[ChangeIndex.CHILD_DATA];\n const childSchema = must(\n schema.relationships[child.relationshipName],\n );\n\n yield* this.#streamChanges(queryID, childSchema, [child.change]);\n break;\n }\n case ChangeType.EDIT:\n yield* this.#streamNodes(queryID, schema, type, () => [\n {row: change[ChangeIndex.NODE].row, relationships: {}},\n ]);\n break;\n default:\n unreachable(change[ChangeIndex.TYPE]);\n }\n }\n }\n\n *#streamNodes(\n queryID: string,\n schema: SourceSchema,\n op: ChangeType.ADD | ChangeType.REMOVE | ChangeType.EDIT,\n nodes: () => Iterable<Node | 'yield'>,\n ): Iterable<RowChange | 'yield'> {\n const {tableName: table, system} = schema;\n\n const primaryKey = must(this.#primaryKeys.get(table));\n const spec = must(this.#tableSpecs.get(table)).tableSpec;\n\n // We do not sync rows gathered by the permissions\n // system to the client.\n if (system === 'permissions') {\n return;\n }\n\n for (const node of nodes()) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n const {relationships} = node;\n let {row} = node;\n const rowKey = getRowKey(primaryKey, row);\n if (op !== ChangeType.REMOVE) {\n const rowVersion = row[ZERO_VERSION_COLUMN_NAME];\n if (\n typeof rowVersion === 'string' &&\n rowVersion < (spec.minRowVersion ?? '00')\n ) {\n row = {...row, [ZERO_VERSION_COLUMN_NAME]: spec.minRowVersion};\n }\n }\n\n yield {\n type: op,\n queryID,\n table,\n rowKey,\n row: op === ChangeType.REMOVE ? undefined : row,\n } as RowChange;\n\n for (const [relationship, children] of Object.entries(relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n yield* this.#streamNodes(queryID, childSchema, op, children);\n }\n }\n }\n}\n\nfunction* toAdds(nodes: Iterable<Node | 'yield'>): Iterable<Change | 'yield'> {\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n yield [ChangeType.ADD, node, null];\n }\n}\n\nfunction getRowKey(cols: PrimaryKey, row: Row): RowKey {\n return Object.fromEntries(cols.map(col => [col, must(row[col])]));\n}\n\n/**\n * Core hydration logic used by {@link PipelineDriver#addQuery}, extracted to a\n * function for reuse by bin-analyze so that bin-analyze's hydration logic\n * is as close as possible to zero-cache's real hydration logic.\n */\nexport function* hydrate(\n input: Input,\n hash: string,\n clientSchema: ClientSchema,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n const res = input.fetch({});\n const streamer = new Streamer(\n buildPrimaryKeys(clientSchema),\n tableSpecs,\n ).accumulate(hash, input.getSchema(), toAdds(res));\n yield* streamer.stream();\n}\n\nexport function* hydrateInternal(\n input: Input,\n hash: string,\n primaryKeys: Map<string, PrimaryKey>,\n tableSpecs: Map<string, LiteAndZqlSpec>,\n): Iterable<RowChange | 'yield'> {\n const res = input.fetch({});\n const streamer = new Streamer(primaryKeys, tableSpecs).accumulate(\n hash,\n input.getSchema(),\n toAdds(res),\n );\n yield* streamer.stream();\n}\n\nfunction buildPrimaryKeys(\n clientSchema: ClientSchema,\n primaryKeys: Map<string, PrimaryKey> = new Map<string, PrimaryKey>(),\n) {\n for (const [tableName, {primaryKey}] of Object.entries(clientSchema.tables)) {\n primaryKeys.set(tableName, primaryKey as unknown as PrimaryKey);\n }\n return primaryKeys;\n}\n\nfunction mustGetPrimaryKey(\n primaryKeys: Map<string, PrimaryKey> | null,\n table: string,\n): PrimaryKey {\n const pKeys = must(primaryKeys, 'primaryKey map must be non-null');\n\n const rv = pKeys.get(table);\n assert(\n rv,\n () =>\n // oxlint-disable-next-line typescript/restrict-template-expressions e18e/prefer-array-to-sorted\n `table '${table}' is not one of: ${[...pKeys.keys()].sort()}. ` +\n `Check the spelling and ensure that the table has a primary key.`,\n );\n return rv;\n}\n\n/**\n * Compares two scalar subquery resolved values for equality.\n * Unlike `valuesEqual` in data.ts (which treats null != null for join\n * semantics), this uses identity semantics: undefined === undefined\n * (no row matched), null === null (row matched but field was NULL).\n */\nfunction scalarValuesEqual(\n a: LiteralValue | null | undefined,\n b: LiteralValue | null | undefined,\n): boolean {\n return a === b;\n}\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { RowID } from './schema/types.ts';
2
+ /**
3
+ * The hash of a row ID used as the unit XOR'd into a query's
4
+ * {@link rowSetSignature}. Includes schema + table + rowKey, so the hash is
5
+ * unique across tables in the same query.
6
+ */
7
+ export declare function rowIDSignatureUnit(id: RowID): bigint;
8
+ /**
9
+ * Parses a hex-encoded signature back to its bigint form. Empty / undefined
10
+ * is the identity (`0n`).
11
+ */
12
+ export declare function parseSignature(hex: string | undefined | null): bigint;
13
+ /**
14
+ * Serializes a bigint signature to lowercase hex. `0n` serializes to `'0'`.
15
+ */
16
+ export declare function formatSignature(sig: bigint): string;
17
+ //# sourceMappingURL=row-set-signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-set-signature.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/row-set-signature.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,KAAK,GAAG,MAAM,CAEpD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAKrE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD"}
@@ -0,0 +1,29 @@
1
+ import { h64 } from "../../../../shared/src/hash.js";
2
+ import { rowIDString } from "../../types/row-key.js";
3
+ //#region ../zero-cache/src/services/view-syncer/row-set-signature.ts
4
+ /**
5
+ * The hash of a row ID used as the unit XOR'd into a query's
6
+ * {@link rowSetSignature}. Includes schema + table + rowKey, so the hash is
7
+ * unique across tables in the same query.
8
+ */
9
+ function rowIDSignatureUnit(id) {
10
+ return h64(rowIDString(id));
11
+ }
12
+ /**
13
+ * Parses a hex-encoded signature back to its bigint form. Empty / undefined
14
+ * is the identity (`0n`).
15
+ */
16
+ function parseSignature(hex) {
17
+ if (!hex) return 0n;
18
+ return BigInt("0x" + hex);
19
+ }
20
+ /**
21
+ * Serializes a bigint signature to lowercase hex. `0n` serializes to `'0'`.
22
+ */
23
+ function formatSignature(sig) {
24
+ return sig.toString(16);
25
+ }
26
+ //#endregion
27
+ export { formatSignature, parseSignature, rowIDSignatureUnit };
28
+
29
+ //# sourceMappingURL=row-set-signature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-set-signature.js","names":[],"sources":["../../../../../../zero-cache/src/services/view-syncer/row-set-signature.ts"],"sourcesContent":["import {h64} from '../../../../shared/src/hash.ts';\nimport {rowIDString} from '../../types/row-key.ts';\nimport type {RowID} from './schema/types.ts';\n\n/**\n * The hash of a row ID used as the unit XOR'd into a query's\n * {@link rowSetSignature}. Includes schema + table + rowKey, so the hash is\n * unique across tables in the same query.\n */\nexport function rowIDSignatureUnit(id: RowID): bigint {\n return h64(rowIDString(id));\n}\n\n/**\n * Parses a hex-encoded signature back to its bigint form. Empty / undefined\n * is the identity (`0n`).\n */\nexport function parseSignature(hex: string | undefined | null): bigint {\n if (!hex) {\n return 0n;\n }\n return BigInt('0x' + hex);\n}\n\n/**\n * Serializes a bigint signature to lowercase hex. `0n` serializes to `'0'`.\n */\nexport function formatSignature(sig: bigint): string {\n return sig.toString(16);\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,mBAAmB,IAAmB;AACpD,QAAO,IAAI,YAAY,GAAG,CAAC;;;;;;AAO7B,SAAgB,eAAe,KAAwC;AACrE,KAAI,CAAC,IACH,QAAO;AAET,QAAO,OAAO,OAAO,IAAI;;;;;AAM3B,SAAgB,gBAAgB,KAAqB;AACnD,QAAO,IAAI,SAAS,GAAG"}
@@ -34,6 +34,7 @@ export type QueriesRow = {
34
34
  transformationVersion: string | null;
35
35
  internal: boolean | null;
36
36
  deleted: boolean | null;
37
+ rowSetSignature?: string | null;
37
38
  };
38
39
  export declare function compareQueriesRows(a: QueriesRow, b: QueriesRow): number;
39
40
  export type DesiresRow = {
@@ -1 +1 @@
1
- {"version":3,"file":"cvr.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/cvr.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cvr.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/cvr.ts"],"names":[],"mappings":"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"}
@@ -67,6 +67,7 @@ CREATE TABLE ${schema(shard)}.queries (
67
67
  "transformationVersion" TEXT,
68
68
  "internal" BOOL, -- If true, no need to track / send patches
69
69
  "deleted" BOOL, -- put vs del "got" query
70
+ "rowSetSignature" TEXT, -- Hex XOR of h64([schema,table,rowKey]) for rows in this query (drift detection for Cap)
70
71
 
71
72
  PRIMARY KEY ("clientGroupID", "queryHash"),
72
73
 
@@ -1 +1 @@
1
- {"version":3,"file":"cvr.js","names":[],"sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/cvr.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {ident} from 'pg-format';\nimport type postgres from 'postgres';\nimport {\n type JSONObject,\n type JSONValue,\n stringify,\n} from '../../../../../shared/src/bigint-json.ts';\nimport type {ReadonlyJSONValue} from '../../../../../shared/src/json.ts';\nimport {stringCompare} from '../../../../../shared/src/string-compare.ts';\nimport type {ClientSchema} from '../../../../../zero-protocol/src/client-schema.ts';\nimport {normalizedKeyOrder, type RowKey} from '../../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../../types/shards.ts';\nimport type {TTLClock} from '../ttl-clock.ts';\nimport {\n type RowID,\n type RowRecord,\n versionFromString,\n versionString,\n} from './types.ts';\n\n// For readability in the sql statements.\nfunction schema(shard: ShardID) {\n return ident(cvrSchema(shard));\n}\n\nfunction createSchema(shard: ShardID) {\n return `CREATE SCHEMA IF NOT EXISTS ${schema(shard)};`;\n}\n\nexport type InstancesRow = {\n clientGroupID: string;\n version: string;\n lastActive: number;\n ttlClock: TTLClock;\n replicaVersion: string | null;\n owner: string | null;\n grantedAt: number | null;\n clientSchema: ClientSchema | null;\n profileID: string | null;\n};\n\nfunction createInstancesTable(shard: ShardID) {\n return /*sql*/ `\nCREATE TABLE ${schema(shard)}.instances (\n \"clientGroupID\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL, -- Sortable representation of CVRVersion, e.g. \"5nbqa2w:09\"\n \"lastActive\" TIMESTAMPTZ NOT NULL, -- For garbage collection\n \"ttlClock\" DOUBLE PRECISION NOT NULL DEFAULT 0, -- The ttl clock gets \"paused\" when disconnected.\n \"replicaVersion\" TEXT, -- Identifies the replica (i.e. initial-sync point) from which the CVR data comes.\n \"owner\" TEXT, -- The ID of the task / server that has been granted ownership of the CVR.\n \"grantedAt\" TIMESTAMPTZ, -- The time at which the current owner was last granted ownership (most recent connection time).\n \"clientSchema\" JSONB, -- ClientSchema of the client group\n \"profileID\" TEXT, -- Stable profile id (\"p...\"), falling back to the clientGroupID (\"cg{clientGroupID}\") for old clients\n \"deleted\" BOOL DEFAULT FALSE -- Tombstone column for deleted CVRs; instances rows are kept longer for usage stats\n);\n\n-- For garbage collection.\nCREATE INDEX instances_last_active\n ON ${schema(shard)}.instances (\"lastActive\") WHERE NOT \"deleted\";\nCREATE INDEX tombstones_last_active\n ON ${schema(shard)}.instances (\"lastActive\") WHERE \"deleted\";\n\n-- For usage stats; the composite index allows a \n-- SELECT COUNT(DISTINCT(\"profileID\")) query to be answered by\n-- an index scan without additional table lookups.\nCREATE INDEX profile_ids_last_active ON ${schema(shard)}.instances (\"lastActive\", \"profileID\")\n WHERE \"profileID\" IS NOT NULL;\n`;\n}\n\nexport function compareInstancesRows(a: InstancesRow, b: InstancesRow) {\n return stringCompare(a.clientGroupID, b.clientGroupID);\n}\n\nexport type ClientsRow = {\n clientGroupID: string;\n clientID: string;\n};\n\nfunction createClientsTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.clients (\n \"clientGroupID\" TEXT,\n \"clientID\" TEXT,\n\n PRIMARY KEY (\"clientGroupID\", \"clientID\"),\n\n CONSTRAINT fk_clients_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.instances(\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n`;\n}\nexport function compareClientsRows(a: ClientsRow, b: ClientsRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n return stringCompare(a.clientID, b.clientID);\n}\n\nexport type QueriesRow = {\n clientGroupID: string;\n queryHash: string;\n // This is the client AST _AFTER_ applying server name transformations.\n clientAST: JSONValue | null;\n queryName: string | null;\n queryArgs: readonly ReadonlyJSONValue[] | null;\n patchVersion: string | null;\n transformationHash: string | null;\n transformationVersion: string | null;\n internal: boolean | null;\n deleted: boolean | null;\n};\n\nfunction createQueriesTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.queries (\n \"clientGroupID\" TEXT,\n \"queryHash\" TEXT, -- this is the hash of the client query AST\n \"clientAST\" JSONB, -- this is nullable as custom queries will not persist an AST\n \"queryName\" TEXT, -- the name of the query if it is a custom query\n \"queryArgs\" JSON, -- the arguments of the query if it is a custom query\n \"patchVersion\" TEXT, -- NULL if only desired but not yet \"got\"\n \"transformationHash\" TEXT,\n \"transformationVersion\" TEXT,\n \"internal\" BOOL, -- If true, no need to track / send patches\n \"deleted\" BOOL, -- put vs del \"got\" query\n\n PRIMARY KEY (\"clientGroupID\", \"queryHash\"),\n\n CONSTRAINT fk_queries_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.instances(\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX queries_patch_version \n ON ${schema(shard)}.queries (\"patchVersion\" NULLS FIRST);\n`;\n}\n\nexport function compareQueriesRows(a: QueriesRow, b: QueriesRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n return stringCompare(a.queryHash, b.queryHash);\n}\n\nexport type DesiresRow = {\n clientGroupID: string;\n clientID: string;\n queryHash: string;\n patchVersion: string;\n deleted: boolean | null;\n ttl: number | null;\n inactivatedAt: TTLClock | null;\n};\n\nfunction createDesiresTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.desires (\n \"clientGroupID\" TEXT,\n \"clientID\" TEXT,\n \"queryHash\" TEXT,\n \"patchVersion\" TEXT NOT NULL,\n \"deleted\" BOOL, -- put vs del \"desired\" query\n \"ttl\" INTERVAL, -- DEPRECATED: Use ttlMs instead. Time to live for this client\n \"ttlMs\" DOUBLE PRECISION, -- Time to live in milliseconds\n \"inactivatedAt\" TIMESTAMPTZ, -- DEPRECATED: Use inactivatedAtMs instead. Time at which this row was inactivated\n \"inactivatedAtMs\" DOUBLE PRECISION, -- Time at which this row was inactivated (milliseconds since client group start)\n\n PRIMARY KEY (\"clientGroupID\", \"clientID\", \"queryHash\"),\n\n CONSTRAINT fk_desires_query\n FOREIGN KEY(\"clientGroupID\", \"queryHash\")\n REFERENCES ${ident(cvrSchema(shard))}.queries(\"clientGroupID\", \"queryHash\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX desires_patch_version\n ON ${schema(shard)}.desires (\"patchVersion\");\n\nCREATE INDEX desires_inactivated_at\n ON ${schema(shard)}.desires (\"inactivatedAt\");\n`;\n}\n\nexport function compareDesiresRows(a: DesiresRow, b: DesiresRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n const clientIDComp = stringCompare(a.clientID, b.clientID);\n if (clientIDComp !== 0) {\n return clientIDComp;\n }\n return stringCompare(a.queryHash, b.queryHash);\n}\n\nexport type RowsRow = {\n clientGroupID: string;\n schema: string;\n table: string;\n rowKey: JSONObject;\n rowVersion: string;\n patchVersion: string;\n refCounts: {[queryHash: string]: number} | null;\n};\n\nexport function rowsRowToRowID(rowsRow: RowsRow): RowID {\n return {\n schema: rowsRow.schema,\n table: rowsRow.table,\n rowKey: rowsRow.rowKey as Record<string, JSONValue>,\n };\n}\n\nexport function rowsRowToRowRecord(rowsRow: RowsRow): RowRecord {\n return {\n id: rowsRowToRowID(rowsRow),\n rowVersion: rowsRow.rowVersion,\n patchVersion: versionFromString(rowsRow.patchVersion),\n refCounts: rowsRow.refCounts,\n };\n}\n\nexport function rowRecordToRowsRow(\n clientGroupID: string,\n rowRecord: RowRecord,\n): RowsRow {\n return {\n clientGroupID,\n schema: rowRecord.id.schema,\n table: rowRecord.id.table,\n rowKey: rowRecord.id.rowKey as Record<string, JSONValue>,\n rowVersion: rowRecord.rowVersion,\n patchVersion: versionString(rowRecord.patchVersion),\n refCounts: rowRecord.refCounts,\n };\n}\n\nexport function compareRowsRows(a: RowsRow, b: RowsRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n const schemaComp = stringCompare(a.schema, b.schema);\n if (schemaComp !== 0) {\n return schemaComp;\n }\n const tableComp = stringCompare(b.table, b.table);\n if (tableComp !== 0) {\n return tableComp;\n }\n return stringCompare(\n stringifySorted(a.rowKey as RowKey),\n stringifySorted(b.rowKey as RowKey),\n );\n}\n\n/**\n * The version of the data in the `cvr.rows` table. This may lag\n * `version` in `cvr.instances` but eventually catches up, modulo\n * exceptional circumstances like a server crash.\n *\n * The `rowsVersion` is tracked in a separate table (as opposed to\n * a column in the `cvr.instances` table) so that general `cvr` updates\n * and `row` updates can be executed independently without serialization\n * conflicts.\n *\n * Note: Although `clientGroupID` logically references the same column in\n * `cvr.instances`, a FOREIGN KEY constraint must not be declared as the\n * `cvr.rows` TABLE needs to be updated without affecting the\n * `SELECT ... FOR UPDATE` lock when `cvr.instances` is updated.\n */\nexport function createRowsVersionTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.\"rowsVersion\" (\n \"clientGroupID\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL\n);\n`;\n}\n\n/**\n * CVR `rows` are updated asynchronously from the CVR metadata\n * (i.e. `instances`). The `rowsVersion` table is updated atomically with\n * updates to the `rows` data.\n */\nfunction createRowsTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.rows (\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT NOT NULL,\n \"patchVersion\" TEXT NOT NULL,\n \"refCounts\" JSONB, -- {[queryHash: string]: number}, NULL for tombstone\n\n PRIMARY KEY (\"clientGroupID\", \"schema\", \"table\", \"rowKey\"),\n\n CONSTRAINT fk_rows_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.\"rowsVersion\" (\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX row_patch_version \n ON ${schema(shard)}.rows (\"patchVersion\");\n\n-- For listing rows returned by one or more query hashes. e.g.\n-- SELECT * FROM cvr_shard.rows WHERE \"refCounts\" ?| array[...queryHashes...];\nCREATE INDEX row_ref_counts ON ${schema(shard)}.rows \n USING GIN (\"refCounts\");\n`;\n}\n\nexport type RowsVersionRow = {\n clientGroupID: string;\n version: string;\n};\n\nfunction createTables(shard: ShardID) {\n return (\n createSchema(shard) +\n createInstancesTable(shard) +\n createClientsTable(shard) +\n createQueriesTable(shard) +\n createDesiresTable(shard) +\n createRowsVersionTable(shard) +\n createRowsTable(shard)\n );\n}\n\nexport async function setupCVRTables(\n lc: LogContext,\n db: postgres.TransactionSql,\n shard: ShardID,\n) {\n lc.info?.(`Setting up CVR tables`);\n await db.unsafe(createTables(shard));\n}\n\nfunction stringifySorted(r: RowKey) {\n return stringify(normalizedKeyOrder(r));\n}\n"],"mappings":";;;;;;AAsBA,SAAS,OAAO,OAAgB;AAC9B,QAAO,MAAM,UAAU,MAAM,CAAC;;AAGhC,SAAS,aAAa,OAAgB;AACpC,QAAO,+BAA+B,OAAO,MAAM,CAAC;;AAetD,SAAS,qBAAqB,OAAgB;AAC5C,QAAe;eACF,OAAO,MAAM,CAAC;;;;;;;;;;;;;;;OAetB,OAAO,MAAM,CAAC;;OAEd,OAAO,MAAM,CAAC;;;;;0CAKqB,OAAO,MAAM,CAAC;;;;AAcxD,SAAS,mBAAmB,OAAgB;AAC1C,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;iBAQZ,OAAO,MAAM,CAAC;;;;;;AA4B/B,SAAS,mBAAmB,OAAgB;AAC1C,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;;;;;;;;;iBAgBZ,OAAO,MAAM,CAAC;;;;;;OAMxB,OAAO,MAAM,CAAC;;;AAsBrB,SAAS,mBAAmB,OAAgB;AAC1C,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;;;;;;;;iBAeZ,MAAM,UAAU,MAAM,CAAC,CAAC;;;;;;OAMlC,OAAO,MAAM,CAAC;;;OAGd,OAAO,MAAM,CAAC;;;AA0BrB,SAAgB,eAAe,SAAyB;AACtD,QAAO;EACL,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,QAAQ,QAAQ;EACjB;;AAGH,SAAgB,mBAAmB,SAA6B;AAC9D,QAAO;EACL,IAAI,eAAe,QAAQ;EAC3B,YAAY,QAAQ;EACpB,cAAc,kBAAkB,QAAQ,aAAa;EACrD,WAAW,QAAQ;EACpB;;AAGH,SAAgB,mBACd,eACA,WACS;AACT,QAAO;EACL;EACA,QAAQ,UAAU,GAAG;EACrB,OAAO,UAAU,GAAG;EACpB,QAAQ,UAAU,GAAG;EACrB,YAAY,UAAU;EACtB,cAAc,cAAc,UAAU,aAAa;EACnD,WAAW,UAAU;EACtB;;;;;;;;;;;;;;;;;AAqCH,SAAgB,uBAAuB,OAAgB;AACrD,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;;;;AAY7B,SAAS,gBAAgB,OAAgB;AACvC,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;;;;;;iBAaZ,OAAO,MAAM,CAAC;;;;;;OAMxB,OAAO,MAAM,CAAC;;;;iCAIY,OAAO,MAAM,CAAC;;;;AAU/C,SAAS,aAAa,OAAgB;AACpC,QACE,aAAa,MAAM,GACnB,qBAAqB,MAAM,GAC3B,mBAAmB,MAAM,GACzB,mBAAmB,MAAM,GACzB,mBAAmB,MAAM,GACzB,uBAAuB,MAAM,GAC7B,gBAAgB,MAAM;;AAI1B,eAAsB,eACpB,IACA,IACA,OACA;AACA,IAAG,OAAO,wBAAwB;AAClC,OAAM,GAAG,OAAO,aAAa,MAAM,CAAC"}
1
+ {"version":3,"file":"cvr.js","names":[],"sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/cvr.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {ident} from 'pg-format';\nimport type postgres from 'postgres';\nimport {\n type JSONObject,\n type JSONValue,\n stringify,\n} from '../../../../../shared/src/bigint-json.ts';\nimport type {ReadonlyJSONValue} from '../../../../../shared/src/json.ts';\nimport {stringCompare} from '../../../../../shared/src/string-compare.ts';\nimport type {ClientSchema} from '../../../../../zero-protocol/src/client-schema.ts';\nimport {normalizedKeyOrder, type RowKey} from '../../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../../types/shards.ts';\nimport type {TTLClock} from '../ttl-clock.ts';\nimport {\n type RowID,\n type RowRecord,\n versionFromString,\n versionString,\n} from './types.ts';\n\n// For readability in the sql statements.\nfunction schema(shard: ShardID) {\n return ident(cvrSchema(shard));\n}\n\nfunction createSchema(shard: ShardID) {\n return `CREATE SCHEMA IF NOT EXISTS ${schema(shard)};`;\n}\n\nexport type InstancesRow = {\n clientGroupID: string;\n version: string;\n lastActive: number;\n ttlClock: TTLClock;\n replicaVersion: string | null;\n owner: string | null;\n grantedAt: number | null;\n clientSchema: ClientSchema | null;\n profileID: string | null;\n};\n\nfunction createInstancesTable(shard: ShardID) {\n return /*sql*/ `\nCREATE TABLE ${schema(shard)}.instances (\n \"clientGroupID\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL, -- Sortable representation of CVRVersion, e.g. \"5nbqa2w:09\"\n \"lastActive\" TIMESTAMPTZ NOT NULL, -- For garbage collection\n \"ttlClock\" DOUBLE PRECISION NOT NULL DEFAULT 0, -- The ttl clock gets \"paused\" when disconnected.\n \"replicaVersion\" TEXT, -- Identifies the replica (i.e. initial-sync point) from which the CVR data comes.\n \"owner\" TEXT, -- The ID of the task / server that has been granted ownership of the CVR.\n \"grantedAt\" TIMESTAMPTZ, -- The time at which the current owner was last granted ownership (most recent connection time).\n \"clientSchema\" JSONB, -- ClientSchema of the client group\n \"profileID\" TEXT, -- Stable profile id (\"p...\"), falling back to the clientGroupID (\"cg{clientGroupID}\") for old clients\n \"deleted\" BOOL DEFAULT FALSE -- Tombstone column for deleted CVRs; instances rows are kept longer for usage stats\n);\n\n-- For garbage collection.\nCREATE INDEX instances_last_active\n ON ${schema(shard)}.instances (\"lastActive\") WHERE NOT \"deleted\";\nCREATE INDEX tombstones_last_active\n ON ${schema(shard)}.instances (\"lastActive\") WHERE \"deleted\";\n\n-- For usage stats; the composite index allows a \n-- SELECT COUNT(DISTINCT(\"profileID\")) query to be answered by\n-- an index scan without additional table lookups.\nCREATE INDEX profile_ids_last_active ON ${schema(shard)}.instances (\"lastActive\", \"profileID\")\n WHERE \"profileID\" IS NOT NULL;\n`;\n}\n\nexport function compareInstancesRows(a: InstancesRow, b: InstancesRow) {\n return stringCompare(a.clientGroupID, b.clientGroupID);\n}\n\nexport type ClientsRow = {\n clientGroupID: string;\n clientID: string;\n};\n\nfunction createClientsTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.clients (\n \"clientGroupID\" TEXT,\n \"clientID\" TEXT,\n\n PRIMARY KEY (\"clientGroupID\", \"clientID\"),\n\n CONSTRAINT fk_clients_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.instances(\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n`;\n}\nexport function compareClientsRows(a: ClientsRow, b: ClientsRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n return stringCompare(a.clientID, b.clientID);\n}\n\nexport type QueriesRow = {\n clientGroupID: string;\n queryHash: string;\n // This is the client AST _AFTER_ applying server name transformations.\n clientAST: JSONValue | null;\n queryName: string | null;\n queryArgs: readonly ReadonlyJSONValue[] | null;\n patchVersion: string | null;\n transformationHash: string | null;\n transformationVersion: string | null;\n internal: boolean | null;\n deleted: boolean | null;\n // Optional because (a) old DBs migrated to v17 will start with NULL, and\n // (b) test fixtures predate this column.\n rowSetSignature?: string | null;\n};\n\nfunction createQueriesTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.queries (\n \"clientGroupID\" TEXT,\n \"queryHash\" TEXT, -- this is the hash of the client query AST\n \"clientAST\" JSONB, -- this is nullable as custom queries will not persist an AST\n \"queryName\" TEXT, -- the name of the query if it is a custom query\n \"queryArgs\" JSON, -- the arguments of the query if it is a custom query\n \"patchVersion\" TEXT, -- NULL if only desired but not yet \"got\"\n \"transformationHash\" TEXT,\n \"transformationVersion\" TEXT,\n \"internal\" BOOL, -- If true, no need to track / send patches\n \"deleted\" BOOL, -- put vs del \"got\" query\n \"rowSetSignature\" TEXT, -- Hex XOR of h64([schema,table,rowKey]) for rows in this query (drift detection for Cap)\n\n PRIMARY KEY (\"clientGroupID\", \"queryHash\"),\n\n CONSTRAINT fk_queries_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.instances(\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX queries_patch_version \n ON ${schema(shard)}.queries (\"patchVersion\" NULLS FIRST);\n`;\n}\n\nexport function compareQueriesRows(a: QueriesRow, b: QueriesRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n return stringCompare(a.queryHash, b.queryHash);\n}\n\nexport type DesiresRow = {\n clientGroupID: string;\n clientID: string;\n queryHash: string;\n patchVersion: string;\n deleted: boolean | null;\n ttl: number | null;\n inactivatedAt: TTLClock | null;\n};\n\nfunction createDesiresTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.desires (\n \"clientGroupID\" TEXT,\n \"clientID\" TEXT,\n \"queryHash\" TEXT,\n \"patchVersion\" TEXT NOT NULL,\n \"deleted\" BOOL, -- put vs del \"desired\" query\n \"ttl\" INTERVAL, -- DEPRECATED: Use ttlMs instead. Time to live for this client\n \"ttlMs\" DOUBLE PRECISION, -- Time to live in milliseconds\n \"inactivatedAt\" TIMESTAMPTZ, -- DEPRECATED: Use inactivatedAtMs instead. Time at which this row was inactivated\n \"inactivatedAtMs\" DOUBLE PRECISION, -- Time at which this row was inactivated (milliseconds since client group start)\n\n PRIMARY KEY (\"clientGroupID\", \"clientID\", \"queryHash\"),\n\n CONSTRAINT fk_desires_query\n FOREIGN KEY(\"clientGroupID\", \"queryHash\")\n REFERENCES ${ident(cvrSchema(shard))}.queries(\"clientGroupID\", \"queryHash\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX desires_patch_version\n ON ${schema(shard)}.desires (\"patchVersion\");\n\nCREATE INDEX desires_inactivated_at\n ON ${schema(shard)}.desires (\"inactivatedAt\");\n`;\n}\n\nexport function compareDesiresRows(a: DesiresRow, b: DesiresRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n const clientIDComp = stringCompare(a.clientID, b.clientID);\n if (clientIDComp !== 0) {\n return clientIDComp;\n }\n return stringCompare(a.queryHash, b.queryHash);\n}\n\nexport type RowsRow = {\n clientGroupID: string;\n schema: string;\n table: string;\n rowKey: JSONObject;\n rowVersion: string;\n patchVersion: string;\n refCounts: {[queryHash: string]: number} | null;\n};\n\nexport function rowsRowToRowID(rowsRow: RowsRow): RowID {\n return {\n schema: rowsRow.schema,\n table: rowsRow.table,\n rowKey: rowsRow.rowKey as Record<string, JSONValue>,\n };\n}\n\nexport function rowsRowToRowRecord(rowsRow: RowsRow): RowRecord {\n return {\n id: rowsRowToRowID(rowsRow),\n rowVersion: rowsRow.rowVersion,\n patchVersion: versionFromString(rowsRow.patchVersion),\n refCounts: rowsRow.refCounts,\n };\n}\n\nexport function rowRecordToRowsRow(\n clientGroupID: string,\n rowRecord: RowRecord,\n): RowsRow {\n return {\n clientGroupID,\n schema: rowRecord.id.schema,\n table: rowRecord.id.table,\n rowKey: rowRecord.id.rowKey as Record<string, JSONValue>,\n rowVersion: rowRecord.rowVersion,\n patchVersion: versionString(rowRecord.patchVersion),\n refCounts: rowRecord.refCounts,\n };\n}\n\nexport function compareRowsRows(a: RowsRow, b: RowsRow) {\n const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);\n if (clientGroupIDComp !== 0) {\n return clientGroupIDComp;\n }\n const schemaComp = stringCompare(a.schema, b.schema);\n if (schemaComp !== 0) {\n return schemaComp;\n }\n const tableComp = stringCompare(b.table, b.table);\n if (tableComp !== 0) {\n return tableComp;\n }\n return stringCompare(\n stringifySorted(a.rowKey as RowKey),\n stringifySorted(b.rowKey as RowKey),\n );\n}\n\n/**\n * The version of the data in the `cvr.rows` table. This may lag\n * `version` in `cvr.instances` but eventually catches up, modulo\n * exceptional circumstances like a server crash.\n *\n * The `rowsVersion` is tracked in a separate table (as opposed to\n * a column in the `cvr.instances` table) so that general `cvr` updates\n * and `row` updates can be executed independently without serialization\n * conflicts.\n *\n * Note: Although `clientGroupID` logically references the same column in\n * `cvr.instances`, a FOREIGN KEY constraint must not be declared as the\n * `cvr.rows` TABLE needs to be updated without affecting the\n * `SELECT ... FOR UPDATE` lock when `cvr.instances` is updated.\n */\nexport function createRowsVersionTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.\"rowsVersion\" (\n \"clientGroupID\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL\n);\n`;\n}\n\n/**\n * CVR `rows` are updated asynchronously from the CVR metadata\n * (i.e. `instances`). The `rowsVersion` table is updated atomically with\n * updates to the `rows` data.\n */\nfunction createRowsTable(shard: ShardID) {\n return `\nCREATE TABLE ${schema(shard)}.rows (\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT NOT NULL,\n \"patchVersion\" TEXT NOT NULL,\n \"refCounts\" JSONB, -- {[queryHash: string]: number}, NULL for tombstone\n\n PRIMARY KEY (\"clientGroupID\", \"schema\", \"table\", \"rowKey\"),\n\n CONSTRAINT fk_rows_client_group\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${schema(shard)}.\"rowsVersion\" (\"clientGroupID\")\n ON DELETE CASCADE\n);\n\n-- For catchup patches.\nCREATE INDEX row_patch_version \n ON ${schema(shard)}.rows (\"patchVersion\");\n\n-- For listing rows returned by one or more query hashes. e.g.\n-- SELECT * FROM cvr_shard.rows WHERE \"refCounts\" ?| array[...queryHashes...];\nCREATE INDEX row_ref_counts ON ${schema(shard)}.rows \n USING GIN (\"refCounts\");\n`;\n}\n\nexport type RowsVersionRow = {\n clientGroupID: string;\n version: string;\n};\n\nfunction createTables(shard: ShardID) {\n return (\n createSchema(shard) +\n createInstancesTable(shard) +\n createClientsTable(shard) +\n createQueriesTable(shard) +\n createDesiresTable(shard) +\n createRowsVersionTable(shard) +\n createRowsTable(shard)\n );\n}\n\nexport async function setupCVRTables(\n lc: LogContext,\n db: postgres.TransactionSql,\n shard: ShardID,\n) {\n lc.info?.(`Setting up CVR tables`);\n await db.unsafe(createTables(shard));\n}\n\nfunction stringifySorted(r: RowKey) {\n return stringify(normalizedKeyOrder(r));\n}\n"],"mappings":";;;;;;AAsBA,SAAS,OAAO,OAAgB;AAC9B,QAAO,MAAM,UAAU,MAAM,CAAC;;AAGhC,SAAS,aAAa,OAAgB;AACpC,QAAO,+BAA+B,OAAO,MAAM,CAAC;;AAetD,SAAS,qBAAqB,OAAgB;AAC5C,QAAe;eACF,OAAO,MAAM,CAAC;;;;;;;;;;;;;;;OAetB,OAAO,MAAM,CAAC;;OAEd,OAAO,MAAM,CAAC;;;;;0CAKqB,OAAO,MAAM,CAAC;;;;AAcxD,SAAS,mBAAmB,OAAgB;AAC1C,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;iBAQZ,OAAO,MAAM,CAAC;;;;;;AA+B/B,SAAS,mBAAmB,OAAgB;AAC1C,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;iBAiBZ,OAAO,MAAM,CAAC;;;;;;OAMxB,OAAO,MAAM,CAAC;;;AAsBrB,SAAS,mBAAmB,OAAgB;AAC1C,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;;;;;;;;iBAeZ,MAAM,UAAU,MAAM,CAAC,CAAC;;;;;;OAMlC,OAAO,MAAM,CAAC;;;OAGd,OAAO,MAAM,CAAC;;;AA0BrB,SAAgB,eAAe,SAAyB;AACtD,QAAO;EACL,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,QAAQ,QAAQ;EACjB;;AAGH,SAAgB,mBAAmB,SAA6B;AAC9D,QAAO;EACL,IAAI,eAAe,QAAQ;EAC3B,YAAY,QAAQ;EACpB,cAAc,kBAAkB,QAAQ,aAAa;EACrD,WAAW,QAAQ;EACpB;;AAGH,SAAgB,mBACd,eACA,WACS;AACT,QAAO;EACL;EACA,QAAQ,UAAU,GAAG;EACrB,OAAO,UAAU,GAAG;EACpB,QAAQ,UAAU,GAAG;EACrB,YAAY,UAAU;EACtB,cAAc,cAAc,UAAU,aAAa;EACnD,WAAW,UAAU;EACtB;;;;;;;;;;;;;;;;;AAqCH,SAAgB,uBAAuB,OAAgB;AACrD,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;;;;AAY7B,SAAS,gBAAgB,OAAgB;AACvC,QAAO;eACM,OAAO,MAAM,CAAC;;;;;;;;;;;;;iBAaZ,OAAO,MAAM,CAAC;;;;;;OAMxB,OAAO,MAAM,CAAC;;;;iCAIY,OAAO,MAAM,CAAC;;;;AAU/C,SAAS,aAAa,OAAgB;AACpC,QACE,aAAa,MAAM,GACnB,qBAAqB,MAAM,GAC3B,mBAAmB,MAAM,GACzB,mBAAmB,MAAM,GACzB,mBAAmB,MAAM,GACzB,uBAAuB,MAAM,GAC7B,gBAAgB,MAAM;;AAI1B,eAAsB,eACpB,IACA,IACA,OACA;AACA,IAAG,OAAO,wBAAwB;AAClC,OAAM,GAAG,OAAO,aAAa,MAAM,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAOjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,0BAA0B,CAAC;AAGjE,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,UAAU,EACf,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAkPf"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAOjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,0BAA0B,CAAC;AAGjE,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,UAAU,EACf,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CA4Pf"}
@@ -129,6 +129,9 @@ async function initViewSyncerSchema(log, db, shard) {
129
129
  `;
130
130
  }
131
131
  };
132
+ const migratedV16ToV17 = { migrateSchema: async (_, sql) => {
133
+ await sql`ALTER TABLE ${sql(schema)}.queries ADD COLUMN "rowSetSignature" TEXT`;
134
+ } };
132
135
  const schemaVersionMigrationMap = {
133
136
  2: migrateV1toV2,
134
137
  3: migrateV2ToV3,
@@ -144,7 +147,8 @@ async function initViewSyncerSchema(log, db, shard) {
144
147
  13: migratedV12ToV13,
145
148
  14: migratedV13ToV14,
146
149
  15: migratedV14ToV15,
147
- 16: migratedV15ToV16
150
+ 16: migratedV15ToV16,
151
+ 17: migratedV16ToV17
148
152
  };
149
153
  await runSchemaMigrations(log, "view-syncer", cvrSchema(shard), db, setupMigration, schemaVersionMigrationMap);
150
154
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","names":[],"sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/init.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {PendingQuery, Row} from 'postgres';\nimport {\n runSchemaMigrations,\n type IncrementalMigrationMap,\n type Migration,\n} from '../../../db/migration.ts';\nimport type {PostgresDB} from '../../../types/pg.ts';\nimport {cvrSchema, type ShardID} from '../../../types/shards.ts';\nimport {createRowsVersionTable, setupCVRTables} from './cvr.ts';\n\nexport async function initViewSyncerSchema(\n log: LogContext,\n db: PostgresDB,\n shard: ShardID,\n): Promise<void> {\n const schema = cvrSchema(shard);\n\n const setupMigration: Migration = {\n migrateSchema: (lc, tx) => setupCVRTables(lc, tx, shard),\n minSafeVersion: 1,\n };\n\n const migrateV1toV2: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.instances ADD \"replicaVersion\" TEXT`;\n },\n };\n\n const migrateV2ToV3: Migration = {\n migrateSchema: async (_, tx) => {\n await tx.unsafe(createRowsVersionTable(shard));\n },\n\n /** Populates the cvr.rowsVersion table with versions from cvr.instances. */\n migrateData: async (lc, tx) => {\n const pending: PendingQuery<Row[]>[] = [];\n for await (const versions of tx<\n {clientGroupID: string; version: string}[]\n >`\n SELECT \"clientGroupID\", \"version\" FROM ${tx(schema)}.instances`.cursor(\n 5000,\n )) {\n for (const version of versions) {\n pending.push(\n tx`INSERT INTO ${tx(schema)}.\"rowsVersion\" ${tx(version)} \n ON CONFLICT (\"clientGroupID\")\n DO UPDATE SET ${tx(version)}`.execute(),\n );\n }\n }\n lc.info?.(`initializing rowsVersion for ${pending.length} cvrs`);\n await Promise.all(pending);\n },\n };\n\n const migrateV3ToV4: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.instances ADD \"owner\" TEXT`;\n await tx`ALTER TABLE ${tx(schema)}.instances ADD \"grantedAt\" TIMESTAMPTZ`;\n },\n };\n\n const migrateV5ToV6: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`\n ALTER TABLE ${tx(schema)}.\"rows\"\n DROP CONSTRAINT fk_rows_client_group`;\n await tx`\n ALTER TABLE ${tx(schema)}.\"rowsVersion\"\n DROP CONSTRAINT fk_rows_version_client_group`;\n },\n };\n\n const migrateV6ToV7: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.desires ADD \"expiresAt\" TIMESTAMPTZ`;\n await tx`ALTER TABLE ${tx(\n schema,\n )}.desires ADD \"inactivatedAt\" TIMESTAMPTZ`;\n await tx`ALTER TABLE ${tx(schema)}.desires ADD \"ttl\" INTERVAL`;\n\n await tx`CREATE INDEX desires_expires_at ON ${tx(\n schema,\n )}.desires (\"expiresAt\")`;\n await tx`CREATE INDEX desires_inactivated_at ON ${tx(\n schema,\n )}.desires (\"inactivatedAt\")`;\n },\n };\n\n const migrateV7ToV8: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(\n schema,\n )}.\"desires\" DROP CONSTRAINT fk_desires_client`;\n },\n };\n\n const migrateV8ToV9: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.instances ADD \"clientSchema\" JSONB`;\n },\n };\n\n const migrateV9ToV10: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.queries ADD \"queryName\" TEXT`;\n await tx`ALTER TABLE ${tx(schema)}.queries ADD \"queryArgs\" JSONB`;\n await tx`ALTER TABLE ${tx(schema)}.queries ALTER COLUMN \"clientAST\" DROP NOT NULL`;\n },\n };\n\n const migrateV10ToV11: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`DROP INDEX IF EXISTS ${tx(schema)}.desires_expires_at`;\n await tx`ALTER TABLE ${tx(schema)}.desires DROP COLUMN \"expiresAt\"`;\n await tx`DROP INDEX IF EXISTS ${tx(schema)}.client_patch_version`;\n await tx`ALTER TABLE ${tx(schema)}.clients DROP COLUMN \"patchVersion\"`;\n await tx`ALTER TABLE ${tx(schema)}.clients DROP COLUMN \"deleted\"`;\n },\n };\n\n const migratedV11ToV12: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.queries ALTER COLUMN \"queryArgs\" TYPE JSON USING \"queryArgs\"::JSON`;\n },\n };\n\n const migratedV12ToV13: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.instances ADD COLUMN \"ttlClock\" DOUBLE PRECISION NOT NULL DEFAULT 0`;\n },\n };\n\n const migratedV13ToV14: Migration = {\n migrateSchema: async (_, sql) => {\n await sql`\n CREATE INDEX instances_last_active ON ${sql(schema)}.instances (\"lastActive\");\n `;\n\n // Update / add foreign key constraints to cascade deletes.\n for (const [table, reference] of [\n ['clients', 'instances'],\n ['queries', 'instances'],\n ['rows', 'rowsVersion'],\n ] as [string, string][]) {\n const constraint = sql(`fk_${table}_client_group`);\n await sql`\n ALTER TABLE ${sql(schema)}.${sql(table)} DROP CONSTRAINT IF EXISTS ${constraint}`;\n await sql`\n ALTER TABLE ${sql(schema)}.${sql(table)} ADD CONSTRAINT ${constraint}\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${sql(schema)}.${sql(reference)} (\"clientGroupID\")\n ON DELETE CASCADE;\n `;\n }\n },\n };\n\n const migratedV14ToV15: Migration = {\n migrateSchema: async (_, sql) => {\n // Add new columns for storing inactivatedAt and ttl in milliseconds.\n // This avoids postgres.js type conversion issues with TIMESTAMPTZ and INTERVAL.\n await sql`ALTER TABLE ${sql(schema)}.desires \n ADD COLUMN \"inactivatedAtMs\" DOUBLE PRECISION`;\n await sql`ALTER TABLE ${sql(schema)}.desires \n ADD COLUMN \"ttlMs\" DOUBLE PRECISION`;\n },\n // Migrate existing data: convert TIMESTAMPTZ to milliseconds for inactivatedAt\n // and INTERVAL to milliseconds for ttl\n // Note: EXTRACT(EPOCH FROM NULL) returns NULL, so NULL values are preserved\n migrateData: async (lc, sql) => {\n lc.info?.(\n 'Migrating desires.inactivatedAt to inactivatedAtMs and ttl to ttlMs',\n );\n await sql`\n UPDATE ${sql(schema)}.desires\n SET \"inactivatedAtMs\" = EXTRACT(EPOCH FROM \"inactivatedAt\") * 1000,\n \"ttlMs\" = EXTRACT(EPOCH FROM \"ttl\") * 1000\n `;\n },\n };\n\n const migratedV15ToV16: Migration = {\n migrateSchema: async (_, sql) => {\n await sql`ALTER TABLE ${sql(schema)}.instances ADD COLUMN \"profileID\" TEXT`;\n await sql`ALTER TABLE ${sql(schema)}.instances ADD COLUMN \"deleted\" BOOL DEFAULT FALSE`;\n\n // Recreate the instances_last_active index to exclude tombstones\n await sql`\n DROP INDEX IF EXISTS ${sql(schema)}.instances_last_active`;\n await sql`\n CREATE INDEX instances_last_active ON ${sql(schema)}.instances (\"lastActive\")\n WHERE NOT \"deleted\"`;\n await sql`\n CREATE INDEX tombstones_last_active ON ${sql(schema)}.instances (\"lastActive\")\n WHERE \"deleted\"`;\n await sql`\n CREATE INDEX profile_ids_last_active ON ${sql(schema)}.instances (\"lastActive\", \"profileID\")\n WHERE \"profileID\" IS NOT NULL`;\n },\n\n // Backfill profileIDs to the `cg${clientGroupID}`, as is done for\n // client groups from old zero-clients that don't send a profileID.\n migrateData: async (lc, sql) => {\n lc.info?.('Backfilling instance.profileIDs');\n await sql`\n UPDATE ${sql(schema)}.instances\n SET \"profileID\" = 'cg' || \"clientGroupID\"\n WHERE \"profileID\" IS NULL\n `;\n },\n };\n\n const schemaVersionMigrationMap: IncrementalMigrationMap = {\n 2: migrateV1toV2,\n 3: migrateV2ToV3,\n 4: migrateV3ToV4,\n // v5 enables asynchronous row-record flushing, and thus relies on\n // the logic that updates and checks the rowsVersion table in v3.\n 5: {minSafeVersion: 3},\n 6: migrateV5ToV6,\n 7: migrateV6ToV7,\n 8: migrateV7ToV8,\n 9: migrateV8ToV9,\n // v10 adds queryName and queryArgs to the queries table to support\n // custom queries. clientAST is now optional to support migrating\n // off client queries.\n 10: migrateV9ToV10,\n // V11 removes the deprecated queries.\"expiresAt\", clients.\"patchVersion\",\n // clients.\"deleted\" columns.\n 11: migrateV10ToV11,\n 12: migratedV11ToV12,\n // V13 adds instances.\"ttlClock\"\n 13: migratedV12ToV13,\n // V14 adds an index on instances.\"lastActive\" and a FK constraint\n // from rows.\"clientGroupID\" to rowsVersion.\"clientGroupID\" for\n // garbage collection\n 14: migratedV13ToV14,\n // V15 adds desires.\"inactivatedAtTTLClock\" to store TTLClock values\n // directly as DOUBLE PRECISION, avoiding postgres.js TIMESTAMPTZ\n // type conversion issues\n 15: migratedV14ToV15,\n // V16 adds instances.\"profileID\" and a corresponding index for estimating\n // active user counts more accurately for apps that use memstore.\n 16: migratedV15ToV16,\n };\n\n await runSchemaMigrations(\n log,\n 'view-syncer',\n cvrSchema(shard),\n db,\n setupMigration,\n schemaVersionMigrationMap,\n );\n}\n"],"mappings":";;;;AAWA,eAAsB,qBACpB,KACA,IACA,OACe;CACf,MAAM,SAAS,UAAU,MAAM;CAE/B,MAAM,iBAA4B;EAChC,gBAAgB,IAAI,OAAO,eAAe,IAAI,IAAI,MAAM;EACxD,gBAAgB;EACjB;CAED,MAAM,gBAA2B,EAC/B,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAErC;CAED,MAAM,gBAA2B;EAC/B,eAAe,OAAO,GAAG,OAAO;AAC9B,SAAM,GAAG,OAAO,uBAAuB,MAAM,CAAC;;EAIhD,aAAa,OAAO,IAAI,OAAO;GAC7B,MAAM,UAAiC,EAAE;AACzC,cAAW,MAAM,YAAY,EAE5B;+CACwC,GAAG,OAAO,CAAC,YAAY,OAC9D,IACD,CACC,MAAK,MAAM,WAAW,SACpB,SAAQ,KACN,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,GAAG,QAAQ,CAAC;;+BAEtC,GAAG,QAAQ,GAAG,SAAS,CAC3C;AAGL,MAAG,OAAO,gCAAgC,QAAQ,OAAO,OAAO;AAChE,SAAM,QAAQ,IAAI,QAAQ;;EAE7B;CAED,MAAM,gBAA2B,EAC/B,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;AAClC,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAErC;CAED,MAAM,gBAA2B,EAC/B,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE;oBACM,GAAG,OAAO,CAAC;;AAEzB,QAAM,EAAE;oBACM,GAAG,OAAO,CAAC;;IAG5B;CAED,MAAM,gBAA2B,EAC/B,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;AAClC,QAAM,EAAE,eAAe,GACrB,OACD,CAAC;AACF,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;AAElC,QAAM,EAAE,sCAAsC,GAC5C,OACD,CAAC;AACF,QAAM,EAAE,0CAA0C,GAChD,OACD,CAAC;IAEL;CAED,MAAM,gBAA2B,EAC/B,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE,eAAe,GACrB,OACD,CAAC;IAEL;CAED,MAAM,gBAA2B,EAC/B,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAErC;CAED,MAAM,iBAA4B,EAChC,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;AAClC,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;AAClC,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAErC;CAED,MAAM,kBAA6B,EACjC,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC;AAC3C,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;AAClC,QAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC;AAC3C,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;AAClC,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAErC;CAED,MAAM,mBAA8B,EAClC,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAErC;CAED,MAAM,mBAA8B,EAClC,eAAe,OAAO,GAAG,OAAO;AAC9B,QAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAErC;CAED,MAAM,mBAA8B,EAClC,eAAe,OAAO,GAAG,QAAQ;AAC/B,QAAM,GAAG;gDACiC,IAAI,OAAO,CAAC;;AAItD,OAAK,MAAM,CAAC,OAAO,cAAc;GAC/B,CAAC,WAAW,YAAY;GACxB,CAAC,WAAW,YAAY;GACxB,CAAC,QAAQ,cAAc;GACxB,EAAwB;GACvB,MAAM,aAAa,IAAI,MAAM,MAAM,eAAe;AAClD,SAAM,GAAG;wBACO,IAAI,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC,6BAA6B;AACvE,SAAM,GAAG;wBACO,IAAI,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC,kBAAkB,WAAW;;yBAEtD,IAAI,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC;;;;IAKpD;CAED,MAAM,mBAA8B;EAClC,eAAe,OAAO,GAAG,QAAQ;AAG/B,SAAM,GAAG,eAAe,IAAI,OAAO,CAAC;;AAEpC,SAAM,GAAG,eAAe,IAAI,OAAO,CAAC;;;EAMtC,aAAa,OAAO,IAAI,QAAQ;AAC9B,MAAG,OACD,sEACD;AACD,SAAM,GAAG;iBACE,IAAI,OAAO,CAAC;;;;;EAK1B;CAED,MAAM,mBAA8B;EAClC,eAAe,OAAO,GAAG,QAAQ;AAC/B,SAAM,GAAG,eAAe,IAAI,OAAO,CAAC;AACpC,SAAM,GAAG,eAAe,IAAI,OAAO,CAAC;AAGpC,SAAM,GAAG;+BACgB,IAAI,OAAO,CAAC;AACrC,SAAM,GAAG;gDACiC,IAAI,OAAO,CAAC;;AAEtD,SAAM,GAAG;iDACkC,IAAI,OAAO,CAAC;;AAEvD,SAAM,GAAG;kDACmC,IAAI,OAAO,CAAC;;;EAM1D,aAAa,OAAO,IAAI,QAAQ;AAC9B,MAAG,OAAO,kCAAkC;AAC5C,SAAM,GAAG;iBACE,IAAI,OAAO,CAAC;;;;;EAK1B;CAED,MAAM,4BAAqD;EACzD,GAAG;EACH,GAAG;EACH,GAAG;EAGH,GAAG,EAAC,gBAAgB,GAAE;EACtB,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;EAIH,IAAI;EAGJ,IAAI;EACJ,IAAI;EAEJ,IAAI;EAIJ,IAAI;EAIJ,IAAI;EAGJ,IAAI;EACL;AAED,OAAM,oBACJ,KACA,eACA,UAAU,MAAM,EAChB,IACA,gBACA,0BACD"}
1
+ {"version":3,"file":"init.js","names":[],"sources":["../../../../../../../zero-cache/src/services/view-syncer/schema/init.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {PendingQuery, Row} from 'postgres';\nimport {\n runSchemaMigrations,\n type IncrementalMigrationMap,\n type Migration,\n} from '../../../db/migration.ts';\nimport type {PostgresDB} from '../../../types/pg.ts';\nimport {cvrSchema, type ShardID} from '../../../types/shards.ts';\nimport {createRowsVersionTable, setupCVRTables} from './cvr.ts';\n\nexport async function initViewSyncerSchema(\n log: LogContext,\n db: PostgresDB,\n shard: ShardID,\n): Promise<void> {\n const schema = cvrSchema(shard);\n\n const setupMigration: Migration = {\n migrateSchema: (lc, tx) => setupCVRTables(lc, tx, shard),\n minSafeVersion: 1,\n };\n\n const migrateV1toV2: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.instances ADD \"replicaVersion\" TEXT`;\n },\n };\n\n const migrateV2ToV3: Migration = {\n migrateSchema: async (_, tx) => {\n await tx.unsafe(createRowsVersionTable(shard));\n },\n\n /** Populates the cvr.rowsVersion table with versions from cvr.instances. */\n migrateData: async (lc, tx) => {\n const pending: PendingQuery<Row[]>[] = [];\n for await (const versions of tx<\n {clientGroupID: string; version: string}[]\n >`\n SELECT \"clientGroupID\", \"version\" FROM ${tx(schema)}.instances`.cursor(\n 5000,\n )) {\n for (const version of versions) {\n pending.push(\n tx`INSERT INTO ${tx(schema)}.\"rowsVersion\" ${tx(version)} \n ON CONFLICT (\"clientGroupID\")\n DO UPDATE SET ${tx(version)}`.execute(),\n );\n }\n }\n lc.info?.(`initializing rowsVersion for ${pending.length} cvrs`);\n await Promise.all(pending);\n },\n };\n\n const migrateV3ToV4: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.instances ADD \"owner\" TEXT`;\n await tx`ALTER TABLE ${tx(schema)}.instances ADD \"grantedAt\" TIMESTAMPTZ`;\n },\n };\n\n const migrateV5ToV6: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`\n ALTER TABLE ${tx(schema)}.\"rows\"\n DROP CONSTRAINT fk_rows_client_group`;\n await tx`\n ALTER TABLE ${tx(schema)}.\"rowsVersion\"\n DROP CONSTRAINT fk_rows_version_client_group`;\n },\n };\n\n const migrateV6ToV7: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.desires ADD \"expiresAt\" TIMESTAMPTZ`;\n await tx`ALTER TABLE ${tx(\n schema,\n )}.desires ADD \"inactivatedAt\" TIMESTAMPTZ`;\n await tx`ALTER TABLE ${tx(schema)}.desires ADD \"ttl\" INTERVAL`;\n\n await tx`CREATE INDEX desires_expires_at ON ${tx(\n schema,\n )}.desires (\"expiresAt\")`;\n await tx`CREATE INDEX desires_inactivated_at ON ${tx(\n schema,\n )}.desires (\"inactivatedAt\")`;\n },\n };\n\n const migrateV7ToV8: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(\n schema,\n )}.\"desires\" DROP CONSTRAINT fk_desires_client`;\n },\n };\n\n const migrateV8ToV9: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.instances ADD \"clientSchema\" JSONB`;\n },\n };\n\n const migrateV9ToV10: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.queries ADD \"queryName\" TEXT`;\n await tx`ALTER TABLE ${tx(schema)}.queries ADD \"queryArgs\" JSONB`;\n await tx`ALTER TABLE ${tx(schema)}.queries ALTER COLUMN \"clientAST\" DROP NOT NULL`;\n },\n };\n\n const migrateV10ToV11: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`DROP INDEX IF EXISTS ${tx(schema)}.desires_expires_at`;\n await tx`ALTER TABLE ${tx(schema)}.desires DROP COLUMN \"expiresAt\"`;\n await tx`DROP INDEX IF EXISTS ${tx(schema)}.client_patch_version`;\n await tx`ALTER TABLE ${tx(schema)}.clients DROP COLUMN \"patchVersion\"`;\n await tx`ALTER TABLE ${tx(schema)}.clients DROP COLUMN \"deleted\"`;\n },\n };\n\n const migratedV11ToV12: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.queries ALTER COLUMN \"queryArgs\" TYPE JSON USING \"queryArgs\"::JSON`;\n },\n };\n\n const migratedV12ToV13: Migration = {\n migrateSchema: async (_, tx) => {\n await tx`ALTER TABLE ${tx(schema)}.instances ADD COLUMN \"ttlClock\" DOUBLE PRECISION NOT NULL DEFAULT 0`;\n },\n };\n\n const migratedV13ToV14: Migration = {\n migrateSchema: async (_, sql) => {\n await sql`\n CREATE INDEX instances_last_active ON ${sql(schema)}.instances (\"lastActive\");\n `;\n\n // Update / add foreign key constraints to cascade deletes.\n for (const [table, reference] of [\n ['clients', 'instances'],\n ['queries', 'instances'],\n ['rows', 'rowsVersion'],\n ] as [string, string][]) {\n const constraint = sql(`fk_${table}_client_group`);\n await sql`\n ALTER TABLE ${sql(schema)}.${sql(table)} DROP CONSTRAINT IF EXISTS ${constraint}`;\n await sql`\n ALTER TABLE ${sql(schema)}.${sql(table)} ADD CONSTRAINT ${constraint}\n FOREIGN KEY(\"clientGroupID\")\n REFERENCES ${sql(schema)}.${sql(reference)} (\"clientGroupID\")\n ON DELETE CASCADE;\n `;\n }\n },\n };\n\n const migratedV14ToV15: Migration = {\n migrateSchema: async (_, sql) => {\n // Add new columns for storing inactivatedAt and ttl in milliseconds.\n // This avoids postgres.js type conversion issues with TIMESTAMPTZ and INTERVAL.\n await sql`ALTER TABLE ${sql(schema)}.desires \n ADD COLUMN \"inactivatedAtMs\" DOUBLE PRECISION`;\n await sql`ALTER TABLE ${sql(schema)}.desires \n ADD COLUMN \"ttlMs\" DOUBLE PRECISION`;\n },\n // Migrate existing data: convert TIMESTAMPTZ to milliseconds for inactivatedAt\n // and INTERVAL to milliseconds for ttl\n // Note: EXTRACT(EPOCH FROM NULL) returns NULL, so NULL values are preserved\n migrateData: async (lc, sql) => {\n lc.info?.(\n 'Migrating desires.inactivatedAt to inactivatedAtMs and ttl to ttlMs',\n );\n await sql`\n UPDATE ${sql(schema)}.desires\n SET \"inactivatedAtMs\" = EXTRACT(EPOCH FROM \"inactivatedAt\") * 1000,\n \"ttlMs\" = EXTRACT(EPOCH FROM \"ttl\") * 1000\n `;\n },\n };\n\n const migratedV15ToV16: Migration = {\n migrateSchema: async (_, sql) => {\n await sql`ALTER TABLE ${sql(schema)}.instances ADD COLUMN \"profileID\" TEXT`;\n await sql`ALTER TABLE ${sql(schema)}.instances ADD COLUMN \"deleted\" BOOL DEFAULT FALSE`;\n\n // Recreate the instances_last_active index to exclude tombstones\n await sql`\n DROP INDEX IF EXISTS ${sql(schema)}.instances_last_active`;\n await sql`\n CREATE INDEX instances_last_active ON ${sql(schema)}.instances (\"lastActive\")\n WHERE NOT \"deleted\"`;\n await sql`\n CREATE INDEX tombstones_last_active ON ${sql(schema)}.instances (\"lastActive\")\n WHERE \"deleted\"`;\n await sql`\n CREATE INDEX profile_ids_last_active ON ${sql(schema)}.instances (\"lastActive\", \"profileID\")\n WHERE \"profileID\" IS NOT NULL`;\n },\n\n // Backfill profileIDs to the `cg${clientGroupID}`, as is done for\n // client groups from old zero-clients that don't send a profileID.\n migrateData: async (lc, sql) => {\n lc.info?.('Backfilling instance.profileIDs');\n await sql`\n UPDATE ${sql(schema)}.instances\n SET \"profileID\" = 'cg' || \"clientGroupID\"\n WHERE \"profileID\" IS NULL\n `;\n },\n };\n\n const migratedV16ToV17: Migration = {\n migrateSchema: async (_, sql) => {\n await sql`ALTER TABLE ${sql(schema)}.queries ADD COLUMN \"rowSetSignature\" TEXT`;\n },\n };\n\n const schemaVersionMigrationMap: IncrementalMigrationMap = {\n 2: migrateV1toV2,\n 3: migrateV2ToV3,\n 4: migrateV3ToV4,\n // v5 enables asynchronous row-record flushing, and thus relies on\n // the logic that updates and checks the rowsVersion table in v3.\n 5: {minSafeVersion: 3},\n 6: migrateV5ToV6,\n 7: migrateV6ToV7,\n 8: migrateV7ToV8,\n 9: migrateV8ToV9,\n // v10 adds queryName and queryArgs to the queries table to support\n // custom queries. clientAST is now optional to support migrating\n // off client queries.\n 10: migrateV9ToV10,\n // V11 removes the deprecated queries.\"expiresAt\", clients.\"patchVersion\",\n // clients.\"deleted\" columns.\n 11: migrateV10ToV11,\n 12: migratedV11ToV12,\n // V13 adds instances.\"ttlClock\"\n 13: migratedV12ToV13,\n // V14 adds an index on instances.\"lastActive\" and a FK constraint\n // from rows.\"clientGroupID\" to rowsVersion.\"clientGroupID\" for\n // garbage collection\n 14: migratedV13ToV14,\n // V15 adds desires.\"inactivatedAtTTLClock\" to store TTLClock values\n // directly as DOUBLE PRECISION, avoiding postgres.js TIMESTAMPTZ\n // type conversion issues\n 15: migratedV14ToV15,\n // V16 adds instances.\"profileID\" and a corresponding index for estimating\n // active user counts more accurately for apps that use memstore.\n 16: migratedV15ToV16,\n // V17 adds queries.\"rowSetSignature\" — XOR'd hash of row IDs attached to\n // each query, used to detect drift on re-hydration of queries containing\n // the Cap operator.\n 17: migratedV16ToV17,\n };\n\n await runSchemaMigrations(\n log,\n 'view-syncer',\n cvrSchema(shard),\n db,\n setupMigration,\n schemaVersionMigrationMap,\n );\n}\n"],"mappings":"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"}