@rocicorp/zero 1.10.0-canary.23 → 1.10.0-canary.24

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 (175) hide show
  1. package/out/analyze-query/src/bin-analyze.js +8 -7
  2. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  3. package/out/replicache/src/btree/node.d.ts.map +1 -1
  4. package/out/replicache/src/btree/node.js +3 -6
  5. package/out/replicache/src/btree/node.js.map +1 -1
  6. package/out/replicache/src/dag/gc.d.ts.map +1 -1
  7. package/out/replicache/src/dag/gc.js +6 -10
  8. package/out/replicache/src/dag/gc.js.map +1 -1
  9. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  10. package/out/replicache/src/kv/sqlite-store.js +24 -2
  11. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  12. package/out/replicache/src/persist/persist.js +1 -1
  13. package/out/shared/src/arrays.d.ts +5 -0
  14. package/out/shared/src/arrays.d.ts.map +1 -1
  15. package/out/shared/src/arrays.js +8 -10
  16. package/out/shared/src/arrays.js.map +1 -1
  17. package/out/shared/src/map.d.ts +19 -2
  18. package/out/shared/src/map.d.ts.map +1 -1
  19. package/out/shared/src/map.js +18 -4
  20. package/out/shared/src/map.js.map +1 -1
  21. package/out/z2s/src/compiler.js +1 -1
  22. package/out/z2s/src/sql.d.ts.map +1 -1
  23. package/out/z2s/src/sql.js +2 -2
  24. package/out/z2s/src/sql.js.map +1 -1
  25. package/out/zero/package.js +1 -1
  26. package/out/zero/package.js.map +1 -1
  27. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  28. package/out/zero-cache/src/auth/write-authorizer.js +1 -0
  29. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  30. package/out/zero-cache/src/config/zero-config.d.ts +15 -0
  31. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  32. package/out/zero-cache/src/config/zero-config.js +35 -0
  33. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  34. package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
  35. package/out/zero-cache/src/db/lite-tables.js +3 -4
  36. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  37. package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
  38. package/out/zero-cache/src/observability/metrics.js +2 -7
  39. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  40. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  41. package/out/zero-cache/src/server/inspector-delegate.js +2 -8
  42. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  43. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  44. package/out/zero-cache/src/server/syncer.js +3 -1
  45. package/out/zero-cache/src/server/syncer.js.map +1 -1
  46. package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/analyze.js +8 -7
  48. package/out/zero-cache/src/services/analyze.js.map +1 -1
  49. package/out/zero-cache/src/services/mutagen/pusher.d.ts +32 -32
  50. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  51. package/out/zero-cache/src/services/mutagen/pusher.js +3 -5
  52. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  53. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  54. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +15 -9
  55. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  56. package/out/zero-cache/src/services/view-syncer/query-covering.d.ts.map +1 -1
  57. package/out/zero-cache/src/services/view-syncer/query-covering.js +2 -6
  58. package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
  59. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.d.ts +95 -0
  60. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.d.ts.map +1 -0
  61. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js +155 -0
  62. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js.map +1 -0
  63. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +38 -3
  64. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  65. package/out/zero-cache/src/services/view-syncer/snapshotter.js +110 -27
  66. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  67. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  68. package/out/zero-cache/src/services/view-syncer/view-syncer.js +4 -13
  69. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  70. package/out/zero-cache/src/types/row-key.d.ts.map +1 -1
  71. package/out/zero-cache/src/types/row-key.js +5 -5
  72. package/out/zero-cache/src/types/row-key.js.map +1 -1
  73. package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
  74. package/out/zero-client/src/client/query-manager.js +2 -5
  75. package/out/zero-client/src/client/query-manager.js.map +1 -1
  76. package/out/zero-client/src/client/version.js +1 -1
  77. package/out/zero-protocol/src/analyze-query-result.d.ts +5 -5
  78. package/out/zero-protocol/src/analyze-query-result.d.ts.map +1 -1
  79. package/out/zero-protocol/src/analyze-query-result.js +1 -1
  80. package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
  81. package/out/zero-protocol/src/ast.d.ts.map +1 -1
  82. package/out/zero-protocol/src/ast.js +13 -14
  83. package/out/zero-protocol/src/ast.js.map +1 -1
  84. package/out/zero-protocol/src/down.d.ts +2 -2
  85. package/out/zero-protocol/src/inspect-down.d.ts +6 -6
  86. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  87. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  88. package/out/zero-protocol/src/query-hash.d.ts.map +1 -1
  89. package/out/zero-protocol/src/query-hash.js +2 -5
  90. package/out/zero-protocol/src/query-hash.js.map +1 -1
  91. package/out/zql/src/builder/builder.d.ts +19 -0
  92. package/out/zql/src/builder/builder.d.ts.map +1 -1
  93. package/out/zql/src/builder/builder.js +28 -11
  94. package/out/zql/src/builder/builder.js.map +1 -1
  95. package/out/zql/src/builder/correlated-predicate-pushdown.d.ts +43 -0
  96. package/out/zql/src/builder/correlated-predicate-pushdown.d.ts.map +1 -0
  97. package/out/zql/src/builder/correlated-predicate-pushdown.js +161 -0
  98. package/out/zql/src/builder/correlated-predicate-pushdown.js.map +1 -0
  99. package/out/zql/src/ivm/connection-index.d.ts +68 -0
  100. package/out/zql/src/ivm/connection-index.d.ts.map +1 -0
  101. package/out/zql/src/ivm/connection-index.js +149 -0
  102. package/out/zql/src/ivm/connection-index.js.map +1 -0
  103. package/out/zql/src/ivm/exists.js +1 -1
  104. package/out/zql/src/ivm/filter-operators.d.ts +3 -2
  105. package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
  106. package/out/zql/src/ivm/filter-operators.js +19 -4
  107. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  108. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  109. package/out/zql/src/ivm/memory-source.js +17 -4
  110. package/out/zql/src/ivm/memory-source.js.map +1 -1
  111. package/out/zql/src/ivm/operator.d.ts +40 -0
  112. package/out/zql/src/ivm/operator.d.ts.map +1 -1
  113. package/out/zql/src/ivm/operator.js.map +1 -1
  114. package/out/zql/src/planner/planner-builder.d.ts +12 -3
  115. package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
  116. package/out/zql/src/planner/planner-builder.js +40 -20
  117. package/out/zql/src/planner/planner-builder.js.map +1 -1
  118. package/out/zql/src/planner/planner-connection.d.ts +28 -2
  119. package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
  120. package/out/zql/src/planner/planner-connection.js +108 -8
  121. package/out/zql/src/planner/planner-connection.js.map +1 -1
  122. package/out/zql/src/planner/planner-debug.d.ts +1 -1
  123. package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
  124. package/out/zql/src/planner/planner-debug.js +3 -8
  125. package/out/zql/src/planner/planner-debug.js.map +1 -1
  126. package/out/zql/src/planner/planner-fan-out.d.ts +1 -0
  127. package/out/zql/src/planner/planner-fan-out.d.ts.map +1 -1
  128. package/out/zql/src/planner/planner-fan-out.js +3 -0
  129. package/out/zql/src/planner/planner-fan-out.js.map +1 -1
  130. package/out/zql/src/planner/planner-filter.d.ts +59 -0
  131. package/out/zql/src/planner/planner-filter.d.ts.map +1 -0
  132. package/out/zql/src/planner/planner-filter.js +116 -0
  133. package/out/zql/src/planner/planner-filter.js.map +1 -0
  134. package/out/zql/src/planner/planner-graph.d.ts +2 -0
  135. package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
  136. package/out/zql/src/planner/planner-graph.js +2 -0
  137. package/out/zql/src/planner/planner-graph.js.map +1 -1
  138. package/out/zql/src/planner/planner-join.d.ts +2 -0
  139. package/out/zql/src/planner/planner-join.d.ts.map +1 -1
  140. package/out/zql/src/planner/planner-join.js +7 -0
  141. package/out/zql/src/planner/planner-join.js.map +1 -1
  142. package/out/zql/src/planner/planner-node.d.ts +2 -1
  143. package/out/zql/src/planner/planner-node.d.ts.map +1 -1
  144. package/out/zql/src/planner/planner-node.js.map +1 -1
  145. package/out/zql/src/planner/planner-source.d.ts +2 -2
  146. package/out/zql/src/planner/planner-source.d.ts.map +1 -1
  147. package/out/zql/src/planner/planner-source.js +2 -2
  148. package/out/zql/src/planner/planner-source.js.map +1 -1
  149. package/out/zql/src/query/query-impl.d.ts.map +1 -1
  150. package/out/zql/src/query/query-impl.js +6 -23
  151. package/out/zql/src/query/query-impl.js.map +1 -1
  152. package/out/zql/src/query/query-transitions.d.ts.map +1 -1
  153. package/out/zql/src/query/query-transitions.js +2 -5
  154. package/out/zql/src/query/query-transitions.js.map +1 -1
  155. package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
  156. package/out/zql/src/query/runnable-query-impl.js +2 -10
  157. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  158. package/out/zqlite/src/query-builder.d.ts +1 -1
  159. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  160. package/out/zqlite/src/query-builder.js +2 -1
  161. package/out/zqlite/src/query-builder.js.map +1 -1
  162. package/out/zqlite/src/query-delegate.d.ts.map +1 -1
  163. package/out/zqlite/src/query-delegate.js +5 -6
  164. package/out/zqlite/src/query-delegate.js.map +1 -1
  165. package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
  166. package/out/zqlite/src/sqlite-cost-model.js +2 -30
  167. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  168. package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
  169. package/out/zqlite/src/sqlite-stat-fanout.js +3 -5
  170. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  171. package/out/zqlite/src/table-source.d.ts +14 -2
  172. package/out/zqlite/src/table-source.d.ts.map +1 -1
  173. package/out/zqlite/src/table-source.js +25 -12
  174. package/out/zqlite/src/table-source.js.map +1 -1
  175. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { deepEqual } from "../../../../shared/src/json.js";
2
+ import { getOrInsertComputed, newMap } from "../../../../shared/src/map.js";
2
3
  import { normalizeAST } from "../../../../zero-protocol/src/ast.js";
3
4
  //#region ../zero-cache/src/services/view-syncer/query-covering.ts
4
5
  var QueryCoveringIndex = class {
@@ -11,12 +12,7 @@ var QueryCoveringIndex = class {
11
12
  this.remove(queryID);
12
13
  const normalizedAst = normalizeAST(query.transformedAst);
13
14
  const root = rootKey(normalizedAst);
14
- let queries = this.#byRoot.get(root);
15
- if (!queries) {
16
- queries = /* @__PURE__ */ new Map();
17
- this.#byRoot.set(root, queries);
18
- }
19
- queries.set(queryID, {
15
+ getOrInsertComputed(this.#byRoot, root, newMap).set(queryID, {
20
16
  ...query,
21
17
  normalizedAst
22
18
  });
@@ -1 +1 @@
1
- {"version":3,"file":"query-covering.js","names":["#byRoot","#queryIDToRoot"],"sources":["../../../../../../zero-cache/src/services/view-syncer/query-covering.ts"],"sourcesContent":["import {\n deepEqual,\n type ReadonlyJSONValue,\n} from '../../../../shared/src/json.ts';\nimport {\n normalizeAST,\n type AST,\n type Condition,\n type CorrelatedSubquery,\n type CorrelatedSubqueryCondition,\n type LiteralValue,\n type NormalizedAST,\n type SimpleCondition,\n} from '../../../../zero-protocol/src/ast.ts';\n\nexport type RunningQuery = {\n readonly transformedAst: AST;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\nexport type CoveringQuery = {\n readonly queryID: string;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\ntype NonNullScalarLiteralValue = string | number | boolean;\ntype IndexedRunningQuery = RunningQuery & {\n readonly normalizedAst: NormalizedAST;\n};\n\n/**\n * Returns true when every row that can be produced by `covered` is also\n * produced by `covering`.\n *\n * This is intentionally conservative: unsupported cases return false rather\n * than guessing. It is used for shadow logging only.\n */\nexport function isQueryCoveredBy(covered: AST, covering: AST): boolean {\n return astCoveredBy(normalizeAST(covered), normalizeAST(covering));\n}\n\nexport function findCoveringQuery(\n coveredQueryID: string,\n coveredAst: AST,\n runningQueries: ReadonlyMap<string, RunningQuery>,\n): CoveringQuery | undefined {\n return new QueryCoveringIndex(runningQueries).findCoveringQuery(\n coveredQueryID,\n coveredAst,\n );\n}\n\nexport class QueryCoveringIndex {\n readonly #byRoot = new Map<string, Map<string, IndexedRunningQuery>>();\n readonly #queryIDToRoot = new Map<string, string>();\n\n constructor(runningQueries?: ReadonlyMap<string, RunningQuery>) {\n if (runningQueries) {\n for (const [queryID, query] of runningQueries) {\n this.add(queryID, query);\n }\n }\n }\n\n add(queryID: string, query: RunningQuery): void {\n this.remove(queryID);\n\n const normalizedAst = normalizeAST(query.transformedAst);\n const root = rootKey(normalizedAst);\n let queries = this.#byRoot.get(root);\n if (!queries) {\n queries = new Map();\n this.#byRoot.set(root, queries);\n }\n queries.set(queryID, {...query, normalizedAst});\n this.#queryIDToRoot.set(queryID, root);\n }\n\n remove(queryID: string): void {\n const root = this.#queryIDToRoot.get(queryID);\n if (root === undefined) {\n return;\n }\n\n this.#queryIDToRoot.delete(queryID);\n const queries = this.#byRoot.get(root);\n if (!queries) {\n return;\n }\n\n queries.delete(queryID);\n if (queries.size === 0) {\n this.#byRoot.delete(root);\n }\n }\n\n findCoveringQuery(\n coveredQueryID: string,\n coveredAst: AST,\n ): CoveringQuery | undefined {\n const normalizedCoveredAst = normalizeAST(coveredAst);\n const queries = this.#byRoot.get(rootKey(normalizedCoveredAst));\n if (!queries) {\n return undefined;\n }\n\n for (const [queryID, query] of queries) {\n if (queryID === coveredQueryID) {\n continue;\n }\n if (astCoveredBy(normalizedCoveredAst, query.normalizedAst)) {\n return {\n queryID,\n transformationHash: query.transformationHash,\n ...(query.queryName !== undefined && {queryName: query.queryName}),\n };\n }\n }\n return undefined;\n }\n}\n\nfunction rootKey(ast: NormalizedAST): string {\n return JSON.stringify([ast.schema, ast.table, ast.alias]);\n}\n\nfunction astCoveredBy(\n covered: NormalizedAST,\n covering: NormalizedAST,\n): boolean {\n return (\n covered.schema === covering.schema &&\n covered.table === covering.table &&\n covered.alias === covering.alias &&\n conditionImplies(covered.where, covering.where) &&\n relatedCoveredBy(covered.related, covering.related) &&\n boundsCoveredBy(covered, covering)\n );\n}\n\nfunction boundsCoveredBy(\n covered: NormalizedAST,\n covering: NormalizedAST,\n): boolean {\n if (covering.limit === undefined) {\n if (covering.start === undefined) {\n return true;\n }\n return (\n jsonEqual(covered.start, covering.start) &&\n jsonEqual(covered.orderBy, covering.orderBy)\n );\n }\n\n if (covered.limit === undefined || covering.limit < covered.limit) {\n return false;\n }\n\n // A limited broader query does not necessarily contain a limited narrower\n // query. The ordered input to the limit must be equivalent.\n return (\n conditionEquivalent(covered.where, covering.where) &&\n jsonEqual(covered.start, covering.start) &&\n jsonEqual(covered.orderBy, covering.orderBy)\n );\n}\n\nfunction relatedCoveredBy(\n covered: readonly CorrelatedSubquery[] | undefined,\n covering: readonly CorrelatedSubquery[] | undefined,\n): boolean {\n if (covered === undefined || covered.length === 0) {\n return true;\n }\n if (covering === undefined) {\n return false;\n }\n return covered.every(coveredRelated =>\n covering.some(\n coveringRelated =>\n sameRelatedEdge(coveredRelated, coveringRelated) &&\n astCoveredBy(\n coveredRelated.subquery as NormalizedAST,\n coveringRelated.subquery as NormalizedAST,\n ),\n ),\n );\n}\n\nfunction conditionEquivalent(\n a: Condition | undefined,\n b: Condition | undefined,\n): boolean {\n return conditionImplies(a, b) && conditionImplies(b, a);\n}\n\nfunction conditionImplies(\n covered: Condition | undefined,\n covering: Condition | undefined,\n): boolean {\n if (covering === undefined) {\n return true;\n }\n if (covered === undefined) {\n return false;\n }\n if (jsonEqual(covered, covering)) {\n return true;\n }\n\n if (covered.type === 'or') {\n return covered.conditions.every(c => conditionImplies(c, covering));\n }\n if (covering.type === 'or') {\n return covering.conditions.some(c => conditionImplies(covered, c));\n }\n if (covering.type === 'and') {\n return covering.conditions.every(c => conditionImplies(covered, c));\n }\n if (covered.type === 'and') {\n return covered.conditions.some(c => conditionImplies(c, covering));\n }\n if (covered.type === 'simple' && covering.type === 'simple') {\n return simpleConditionImplies(covered, covering);\n }\n if (\n covered.type === 'correlatedSubquery' &&\n covering.type === 'correlatedSubquery'\n ) {\n return correlatedConditionImplies(covered, covering);\n }\n return false;\n}\n\nfunction correlatedConditionImplies(\n covered: CorrelatedSubqueryCondition,\n covering: CorrelatedSubqueryCondition,\n): boolean {\n if (\n covered.op !== covering.op ||\n covered.scalar !== covering.scalar ||\n !sameRelatedEdge(covered.related, covering.related)\n ) {\n return false;\n }\n\n if (covered.op === 'EXISTS') {\n return astCoveredBy(\n covered.related.subquery as NormalizedAST,\n covering.related.subquery as NormalizedAST,\n );\n }\n\n return astCoveredBy(\n covering.related.subquery as NormalizedAST,\n covered.related.subquery as NormalizedAST,\n );\n}\n\nfunction sameRelatedEdge(\n a: CorrelatedSubquery,\n b: CorrelatedSubquery,\n): boolean {\n return (\n jsonEqual(a.correlation, b.correlation) &&\n a.hidden === b.hidden &&\n a.system === b.system &&\n a.subquery.alias === b.subquery.alias\n );\n}\n\nfunction simpleConditionImplies(\n covered: SimpleCondition,\n covering: SimpleCondition,\n): boolean {\n const coveredParts = columnLiteralParts(covered);\n const coveringParts = columnLiteralParts(covering);\n if (!coveredParts || !coveringParts) {\n return false;\n }\n if (coveredParts.column !== coveringParts.column) {\n return false;\n }\n\n const {op: coveredOp, value: coveredValue} = coveredParts;\n const {op: coveringOp, value: coveringValue} = coveringParts;\n\n if (isEqualityOp(coveredOp) && isNonNullScalarLiteralValue(coveredValue)) {\n return equalityImplies(coveredValue, coveringOp, coveringValue);\n }\n\n if (\n coveredOp === 'IN' &&\n coveringOp === 'IN' &&\n Array.isArray(coveredValue) &&\n Array.isArray(coveringValue)\n ) {\n return coveredValue.every(v => literalArrayIncludes(coveringValue, v));\n }\n\n if (isNumericOrderOp(coveredOp) && isNumericOrderOp(coveringOp)) {\n return orderConditionImplies(\n coveredOp,\n coveredValue,\n coveringOp,\n coveringValue,\n );\n }\n\n return false;\n}\n\nfunction equalityImplies(\n value: NonNullScalarLiteralValue,\n coveringOp: SimpleCondition['op'],\n coveringValue: LiteralValue,\n): boolean {\n switch (coveringOp) {\n case '=':\n case 'IS':\n return jsonEqual(value, coveringValue);\n case '!=':\n case 'IS NOT':\n return !jsonEqual(value, coveringValue);\n case 'IN':\n return (\n Array.isArray(coveringValue) &&\n literalArrayIncludes(coveringValue, value)\n );\n case '<':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value < coveringValue\n );\n case '<=':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value <= coveringValue\n );\n case '>':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value > coveringValue\n );\n case '>=':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value >= coveringValue\n );\n case 'NOT IN':\n case 'LIKE':\n case 'NOT LIKE':\n case 'ILIKE':\n case 'NOT ILIKE':\n return false;\n }\n}\n\nfunction orderConditionImplies(\n coveredOp: '<' | '>' | '<=' | '>=',\n coveredValue: LiteralValue,\n coveringOp: '<' | '>' | '<=' | '>=',\n coveringValue: LiteralValue,\n): boolean {\n if (typeof coveredValue !== 'number' || typeof coveringValue !== 'number') {\n return false;\n }\n\n switch (coveredOp) {\n case '>':\n return (\n (coveringOp === '>' && coveredValue >= coveringValue) ||\n (coveringOp === '>=' && coveredValue >= coveringValue)\n );\n case '>=':\n return (\n (coveringOp === '>' && coveredValue > coveringValue) ||\n (coveringOp === '>=' && coveredValue >= coveringValue)\n );\n case '<':\n return (\n (coveringOp === '<' && coveredValue <= coveringValue) ||\n (coveringOp === '<=' && coveredValue <= coveringValue)\n );\n case '<=':\n return (\n (coveringOp === '<' && coveredValue < coveringValue) ||\n (coveringOp === '<=' && coveredValue <= coveringValue)\n );\n }\n}\n\nfunction columnLiteralParts(condition: SimpleCondition):\n | {\n readonly column: string;\n readonly op: SimpleCondition['op'];\n readonly value: LiteralValue;\n }\n | undefined {\n if (condition.left.type !== 'column' || condition.right.type !== 'literal') {\n return undefined;\n }\n return {\n column: condition.left.name,\n op: condition.op,\n value: condition.right.value,\n };\n}\n\nfunction isEqualityOp(op: SimpleCondition['op']): op is '=' | 'IS' {\n return op === '=' || op === 'IS';\n}\n\nfunction isNumericOrderOp(\n op: SimpleCondition['op'],\n): op is '<' | '>' | '<=' | '>=' {\n return op === '<' || op === '>' || op === '<=' || op === '>=';\n}\n\nfunction isNonNullScalarLiteralValue(\n value: LiteralValue,\n): value is NonNullScalarLiteralValue {\n return value !== null && !Array.isArray(value);\n}\n\nfunction literalArrayIncludes(\n values: ReadonlyArray<string | number | boolean>,\n value: string | number | boolean | null,\n): boolean {\n return values.some(v => jsonEqual(v, value));\n}\n\nfunction jsonEqual(a: unknown, b: unknown): boolean {\n return deepEqual(\n a as ReadonlyJSONValue | undefined,\n b as ReadonlyJSONValue | undefined,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"query-covering.js","names":["#byRoot","#queryIDToRoot"],"sources":["../../../../../../zero-cache/src/services/view-syncer/query-covering.ts"],"sourcesContent":["import {\n deepEqual,\n type ReadonlyJSONValue,\n} from '../../../../shared/src/json.ts';\nimport {getOrInsertComputed, newMap} from '../../../../shared/src/map.ts';\nimport {\n normalizeAST,\n type AST,\n type Condition,\n type CorrelatedSubquery,\n type CorrelatedSubqueryCondition,\n type LiteralValue,\n type NormalizedAST,\n type SimpleCondition,\n} from '../../../../zero-protocol/src/ast.ts';\n\nexport type RunningQuery = {\n readonly transformedAst: AST;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\nexport type CoveringQuery = {\n readonly queryID: string;\n readonly transformationHash: string;\n readonly queryName?: string | undefined;\n};\n\ntype NonNullScalarLiteralValue = string | number | boolean;\ntype IndexedRunningQuery = RunningQuery & {\n readonly normalizedAst: NormalizedAST;\n};\n\n/**\n * Returns true when every row that can be produced by `covered` is also\n * produced by `covering`.\n *\n * This is intentionally conservative: unsupported cases return false rather\n * than guessing. It is used for shadow logging only.\n */\nexport function isQueryCoveredBy(covered: AST, covering: AST): boolean {\n return astCoveredBy(normalizeAST(covered), normalizeAST(covering));\n}\n\nexport function findCoveringQuery(\n coveredQueryID: string,\n coveredAst: AST,\n runningQueries: ReadonlyMap<string, RunningQuery>,\n): CoveringQuery | undefined {\n return new QueryCoveringIndex(runningQueries).findCoveringQuery(\n coveredQueryID,\n coveredAst,\n );\n}\n\nexport class QueryCoveringIndex {\n readonly #byRoot = new Map<string, Map<string, IndexedRunningQuery>>();\n readonly #queryIDToRoot = new Map<string, string>();\n\n constructor(runningQueries?: ReadonlyMap<string, RunningQuery>) {\n if (runningQueries) {\n for (const [queryID, query] of runningQueries) {\n this.add(queryID, query);\n }\n }\n }\n\n add(queryID: string, query: RunningQuery): void {\n this.remove(queryID);\n\n const normalizedAst = normalizeAST(query.transformedAst);\n const root = rootKey(normalizedAst);\n getOrInsertComputed(this.#byRoot, root, newMap).set(queryID, {\n ...query,\n normalizedAst,\n });\n this.#queryIDToRoot.set(queryID, root);\n }\n\n remove(queryID: string): void {\n const root = this.#queryIDToRoot.get(queryID);\n if (root === undefined) {\n return;\n }\n\n this.#queryIDToRoot.delete(queryID);\n const queries = this.#byRoot.get(root);\n if (!queries) {\n return;\n }\n\n queries.delete(queryID);\n if (queries.size === 0) {\n this.#byRoot.delete(root);\n }\n }\n\n findCoveringQuery(\n coveredQueryID: string,\n coveredAst: AST,\n ): CoveringQuery | undefined {\n const normalizedCoveredAst = normalizeAST(coveredAst);\n const queries = this.#byRoot.get(rootKey(normalizedCoveredAst));\n if (!queries) {\n return undefined;\n }\n\n for (const [queryID, query] of queries) {\n if (queryID === coveredQueryID) {\n continue;\n }\n if (astCoveredBy(normalizedCoveredAst, query.normalizedAst)) {\n return {\n queryID,\n transformationHash: query.transformationHash,\n ...(query.queryName !== undefined && {queryName: query.queryName}),\n };\n }\n }\n return undefined;\n }\n}\n\nfunction rootKey(ast: NormalizedAST): string {\n return JSON.stringify([ast.schema, ast.table, ast.alias]);\n}\n\nfunction astCoveredBy(\n covered: NormalizedAST,\n covering: NormalizedAST,\n): boolean {\n return (\n covered.schema === covering.schema &&\n covered.table === covering.table &&\n covered.alias === covering.alias &&\n conditionImplies(covered.where, covering.where) &&\n relatedCoveredBy(covered.related, covering.related) &&\n boundsCoveredBy(covered, covering)\n );\n}\n\nfunction boundsCoveredBy(\n covered: NormalizedAST,\n covering: NormalizedAST,\n): boolean {\n if (covering.limit === undefined) {\n if (covering.start === undefined) {\n return true;\n }\n return (\n jsonEqual(covered.start, covering.start) &&\n jsonEqual(covered.orderBy, covering.orderBy)\n );\n }\n\n if (covered.limit === undefined || covering.limit < covered.limit) {\n return false;\n }\n\n // A limited broader query does not necessarily contain a limited narrower\n // query. The ordered input to the limit must be equivalent.\n return (\n conditionEquivalent(covered.where, covering.where) &&\n jsonEqual(covered.start, covering.start) &&\n jsonEqual(covered.orderBy, covering.orderBy)\n );\n}\n\nfunction relatedCoveredBy(\n covered: readonly CorrelatedSubquery[] | undefined,\n covering: readonly CorrelatedSubquery[] | undefined,\n): boolean {\n if (covered === undefined || covered.length === 0) {\n return true;\n }\n if (covering === undefined) {\n return false;\n }\n return covered.every(coveredRelated =>\n covering.some(\n coveringRelated =>\n sameRelatedEdge(coveredRelated, coveringRelated) &&\n astCoveredBy(\n coveredRelated.subquery as NormalizedAST,\n coveringRelated.subquery as NormalizedAST,\n ),\n ),\n );\n}\n\nfunction conditionEquivalent(\n a: Condition | undefined,\n b: Condition | undefined,\n): boolean {\n return conditionImplies(a, b) && conditionImplies(b, a);\n}\n\nfunction conditionImplies(\n covered: Condition | undefined,\n covering: Condition | undefined,\n): boolean {\n if (covering === undefined) {\n return true;\n }\n if (covered === undefined) {\n return false;\n }\n if (jsonEqual(covered, covering)) {\n return true;\n }\n\n if (covered.type === 'or') {\n return covered.conditions.every(c => conditionImplies(c, covering));\n }\n if (covering.type === 'or') {\n return covering.conditions.some(c => conditionImplies(covered, c));\n }\n if (covering.type === 'and') {\n return covering.conditions.every(c => conditionImplies(covered, c));\n }\n if (covered.type === 'and') {\n return covered.conditions.some(c => conditionImplies(c, covering));\n }\n if (covered.type === 'simple' && covering.type === 'simple') {\n return simpleConditionImplies(covered, covering);\n }\n if (\n covered.type === 'correlatedSubquery' &&\n covering.type === 'correlatedSubquery'\n ) {\n return correlatedConditionImplies(covered, covering);\n }\n return false;\n}\n\nfunction correlatedConditionImplies(\n covered: CorrelatedSubqueryCondition,\n covering: CorrelatedSubqueryCondition,\n): boolean {\n if (\n covered.op !== covering.op ||\n covered.scalar !== covering.scalar ||\n !sameRelatedEdge(covered.related, covering.related)\n ) {\n return false;\n }\n\n if (covered.op === 'EXISTS') {\n return astCoveredBy(\n covered.related.subquery as NormalizedAST,\n covering.related.subquery as NormalizedAST,\n );\n }\n\n return astCoveredBy(\n covering.related.subquery as NormalizedAST,\n covered.related.subquery as NormalizedAST,\n );\n}\n\nfunction sameRelatedEdge(\n a: CorrelatedSubquery,\n b: CorrelatedSubquery,\n): boolean {\n return (\n jsonEqual(a.correlation, b.correlation) &&\n a.hidden === b.hidden &&\n a.system === b.system &&\n a.subquery.alias === b.subquery.alias\n );\n}\n\nfunction simpleConditionImplies(\n covered: SimpleCondition,\n covering: SimpleCondition,\n): boolean {\n const coveredParts = columnLiteralParts(covered);\n const coveringParts = columnLiteralParts(covering);\n if (!coveredParts || !coveringParts) {\n return false;\n }\n if (coveredParts.column !== coveringParts.column) {\n return false;\n }\n\n const {op: coveredOp, value: coveredValue} = coveredParts;\n const {op: coveringOp, value: coveringValue} = coveringParts;\n\n if (isEqualityOp(coveredOp) && isNonNullScalarLiteralValue(coveredValue)) {\n return equalityImplies(coveredValue, coveringOp, coveringValue);\n }\n\n if (\n coveredOp === 'IN' &&\n coveringOp === 'IN' &&\n Array.isArray(coveredValue) &&\n Array.isArray(coveringValue)\n ) {\n return coveredValue.every(v => literalArrayIncludes(coveringValue, v));\n }\n\n if (isNumericOrderOp(coveredOp) && isNumericOrderOp(coveringOp)) {\n return orderConditionImplies(\n coveredOp,\n coveredValue,\n coveringOp,\n coveringValue,\n );\n }\n\n return false;\n}\n\nfunction equalityImplies(\n value: NonNullScalarLiteralValue,\n coveringOp: SimpleCondition['op'],\n coveringValue: LiteralValue,\n): boolean {\n switch (coveringOp) {\n case '=':\n case 'IS':\n return jsonEqual(value, coveringValue);\n case '!=':\n case 'IS NOT':\n return !jsonEqual(value, coveringValue);\n case 'IN':\n return (\n Array.isArray(coveringValue) &&\n literalArrayIncludes(coveringValue, value)\n );\n case '<':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value < coveringValue\n );\n case '<=':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value <= coveringValue\n );\n case '>':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value > coveringValue\n );\n case '>=':\n return (\n typeof value === 'number' &&\n typeof coveringValue === 'number' &&\n value >= coveringValue\n );\n case 'NOT IN':\n case 'LIKE':\n case 'NOT LIKE':\n case 'ILIKE':\n case 'NOT ILIKE':\n return false;\n }\n}\n\nfunction orderConditionImplies(\n coveredOp: '<' | '>' | '<=' | '>=',\n coveredValue: LiteralValue,\n coveringOp: '<' | '>' | '<=' | '>=',\n coveringValue: LiteralValue,\n): boolean {\n if (typeof coveredValue !== 'number' || typeof coveringValue !== 'number') {\n return false;\n }\n\n switch (coveredOp) {\n case '>':\n return (\n (coveringOp === '>' && coveredValue >= coveringValue) ||\n (coveringOp === '>=' && coveredValue >= coveringValue)\n );\n case '>=':\n return (\n (coveringOp === '>' && coveredValue > coveringValue) ||\n (coveringOp === '>=' && coveredValue >= coveringValue)\n );\n case '<':\n return (\n (coveringOp === '<' && coveredValue <= coveringValue) ||\n (coveringOp === '<=' && coveredValue <= coveringValue)\n );\n case '<=':\n return (\n (coveringOp === '<' && coveredValue < coveringValue) ||\n (coveringOp === '<=' && coveredValue <= coveringValue)\n );\n }\n}\n\nfunction columnLiteralParts(condition: SimpleCondition):\n | {\n readonly column: string;\n readonly op: SimpleCondition['op'];\n readonly value: LiteralValue;\n }\n | undefined {\n if (condition.left.type !== 'column' || condition.right.type !== 'literal') {\n return undefined;\n }\n return {\n column: condition.left.name,\n op: condition.op,\n value: condition.right.value,\n };\n}\n\nfunction isEqualityOp(op: SimpleCondition['op']): op is '=' | 'IS' {\n return op === '=' || op === 'IS';\n}\n\nfunction isNumericOrderOp(\n op: SimpleCondition['op'],\n): op is '<' | '>' | '<=' | '>=' {\n return op === '<' || op === '>' || op === '<=' || op === '>=';\n}\n\nfunction isNonNullScalarLiteralValue(\n value: LiteralValue,\n): value is NonNullScalarLiteralValue {\n return value !== null && !Array.isArray(value);\n}\n\nfunction literalArrayIncludes(\n values: ReadonlyArray<string | number | boolean>,\n value: string | number | boolean | null,\n): boolean {\n return values.some(v => jsonEqual(v, value));\n}\n\nfunction jsonEqual(a: unknown, b: unknown): boolean {\n return deepEqual(\n a as ReadonlyJSONValue | undefined,\n b as ReadonlyJSONValue | undefined,\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The default maximum number of entries retained by a {@link SnapshotRowCache}.
3
+ *
4
+ * Each entry is a raw SQLite row (or the list of rows conflicting with
5
+ * a row on its unique keys) read while computing a snapshot diff. A
6
+ * replicated transaction of N rows produces up to 2N entries (the new value
7
+ * and the previous value(s) of each row), so the cache should be large enough
8
+ * to hold the largest transactions that are commonly replicated in order for
9
+ * all of the client groups on a worker to share the reads.
10
+ */
11
+ export declare const DEFAULT_MAX_SNAPSHOT_ROW_CACHE_ENTRIES = 50000;
12
+ /**
13
+ * How a statement's results are read: a single row (`Statement.get()`) or
14
+ * all rows (`Statement.all()`).
15
+ */
16
+ export type ReadMode = 'get' | 'all';
17
+ /**
18
+ * The maximum number of distinct SQL statements interned by a
19
+ * {@link SnapshotRowCache} before it is cleared. There is one per table and
20
+ * lookup shape, so this is only reached after many schema changes.
21
+ */
22
+ export declare const MAX_INTERNED_STATEMENTS = 10000;
23
+ /**
24
+ * A worker-wide, bounded cache of the rows that a `Snapshotter` reads from
25
+ * replica snapshots when computing the diff between two versions of the
26
+ * replica.
27
+ *
28
+ * Every client group (i.e. view-syncer) on a sync worker computes its own
29
+ * diff from the shared `_zero.changeLog2`, and each change in the diff costs
30
+ * two SQLite reads: the new value of the row (from the `curr` snapshot) and
31
+ * the previous value(s) that it replaces (from the `prev` snapshot). The
32
+ * reads for a given change are identical for every client group that
33
+ * traverses it, so a replicated transaction of N rows costs N × 2 × (client
34
+ * groups) reads per worker, most of which are redundant. This cache lets the
35
+ * client groups on a worker share those reads.
36
+ *
37
+ * ### Keys
38
+ *
39
+ * Entries are keyed by the exact SQL statement and arguments of the read
40
+ * (which encodes the table, the selected columns and the lookup keys), plus
41
+ * a `tag` supplied by the caller that pins the result to a version of the
42
+ * replica:
43
+ *
44
+ * - The new value of a row is the row as of the `stateVersion` recorded in
45
+ * its change log entry (its `_0_version` is asserted to equal that
46
+ * version), and is tagged with that `stateVersion`. It is thus shareable
47
+ * by all client groups regardless of the snapshots they are advancing
48
+ * between.
49
+ * - Previous values are read from the `prev` snapshot at its version, and
50
+ * are tagged with that version (and, when the result can depend on the
51
+ * changes applied to the snapshot before the read, the `curr` version
52
+ * as well). See `Diff` in `snapshotter.ts` for the details.
53
+ *
54
+ * Because every key includes the version(s) that the result depends on,
55
+ * entries never go stale and are never invalidated; they are only evicted.
56
+ *
57
+ * ### Eviction
58
+ *
59
+ * The cache is bounded by entry count and evicts in insertion (FIFO) order
60
+ * rather than by recency. Diffs are traversed in change log order by every
61
+ * client group, so the entries that will not be needed again are those that
62
+ * were inserted the earliest (all groups have moved past them), while the
63
+ * most recently inserted entries are the ones that the trailing groups have
64
+ * yet to reach. Refreshing an entry on a hit would evict exactly the wrong
65
+ * entries when a trailing group catches up.
66
+ *
67
+ * ### Sharing
68
+ *
69
+ * Cached values are shared by reference across client groups and must
70
+ * therefore never be mutated by callers.
71
+ */
72
+ export declare class SnapshotRowCache {
73
+ #private;
74
+ constructor(maxEntries?: number);
75
+ get size(): number;
76
+ get maxEntries(): number;
77
+ stats(): {
78
+ hits: number;
79
+ misses: number;
80
+ size: number;
81
+ };
82
+ /**
83
+ * Returns the cached result of the read identified by `tag`, `sql`, `mode`
84
+ * and `args`, or performs the read via `read()`, caching and returning its
85
+ * result. `undefined` results (i.e. a missing row) are not cached.
86
+ *
87
+ * `mode` is part of the key because the same statement can be read both
88
+ * ways: for a table whose only unique key is its primary key, `getRow()`
89
+ * and `getRows()` produce identical SQL, args and tags, but the former
90
+ * caches a row and the latter an array of rows.
91
+ */
92
+ getOrRead<T>(tag: string, sql: string, mode: ReadMode, args: unknown[], read: () => T): T;
93
+ clear(): void;
94
+ }
95
+ //# sourceMappingURL=snapshot-row-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-row-cache.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/snapshot-row-cache.ts"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AACH,eAAO,MAAM,sCAAsC,QAAS,CAAC;AAwB7D;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC;AAErC;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAK9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,qBAAa,gBAAgB;;gBAmBf,UAAU,SAAyC;IAI/D,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,KAAK,IAAI;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC;IAIrD;;;;;;;;;OASG;IACH,SAAS,CAAC,CAAC,EACT,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,OAAO,EAAE,EACf,IAAI,EAAE,MAAM,CAAC,GACZ,CAAC;IAsBJ,KAAK,IAAI,IAAI;CA0Bd"}
@@ -0,0 +1,155 @@
1
+ import { getOrInsert } from "../../../../shared/src/map.js";
2
+ import { must } from "../../../../shared/src/must.js";
3
+ import { stringify } from "../../../../shared/src/bigint-json.js";
4
+ import { getOrCreateCounter } from "../../observability/metrics.js";
5
+ import { RingBuffer } from "../../../../shared/src/ring-buffer.js";
6
+ //#region ../zero-cache/src/services/view-syncer/snapshot-row-cache.ts
7
+ /**
8
+ * The default maximum number of entries retained by a {@link SnapshotRowCache}.
9
+ *
10
+ * Each entry is a raw SQLite row (or the list of rows conflicting with
11
+ * a row on its unique keys) read while computing a snapshot diff. A
12
+ * replicated transaction of N rows produces up to 2N entries (the new value
13
+ * and the previous value(s) of each row), so the cache should be large enough
14
+ * to hold the largest transactions that are commonly replicated in order for
15
+ * all of the client groups on a worker to share the reads.
16
+ */
17
+ var DEFAULT_MAX_SNAPSHOT_ROW_CACHE_ENTRIES = 5e4;
18
+ /**
19
+ * Serializes a lookup key value (i.e. a SQLite-bound parameter). Numbers and
20
+ * bigints serialize identically, as row keys parsed from the change log are
21
+ * JSON numbers whereas the values read from SQLite are bigints; both address
22
+ * the same row. Strings are prefixed to distinguish them from numbers.
23
+ */
24
+ function serialize(v) {
25
+ switch (typeof v) {
26
+ case "string": return "s" + v;
27
+ case "number":
28
+ case "bigint": return String(v);
29
+ case "boolean": return v ? "true" : "false";
30
+ case "object": return v === null ? "null" : "o" + stringify(v);
31
+ default: return "u" + String(v);
32
+ }
33
+ }
34
+ var HIT = { result: "hit" };
35
+ var MISS = { result: "miss" };
36
+ /**
37
+ * A worker-wide, bounded cache of the rows that a `Snapshotter` reads from
38
+ * replica snapshots when computing the diff between two versions of the
39
+ * replica.
40
+ *
41
+ * Every client group (i.e. view-syncer) on a sync worker computes its own
42
+ * diff from the shared `_zero.changeLog2`, and each change in the diff costs
43
+ * two SQLite reads: the new value of the row (from the `curr` snapshot) and
44
+ * the previous value(s) that it replaces (from the `prev` snapshot). The
45
+ * reads for a given change are identical for every client group that
46
+ * traverses it, so a replicated transaction of N rows costs N × 2 × (client
47
+ * groups) reads per worker, most of which are redundant. This cache lets the
48
+ * client groups on a worker share those reads.
49
+ *
50
+ * ### Keys
51
+ *
52
+ * Entries are keyed by the exact SQL statement and arguments of the read
53
+ * (which encodes the table, the selected columns and the lookup keys), plus
54
+ * a `tag` supplied by the caller that pins the result to a version of the
55
+ * replica:
56
+ *
57
+ * - The new value of a row is the row as of the `stateVersion` recorded in
58
+ * its change log entry (its `_0_version` is asserted to equal that
59
+ * version), and is tagged with that `stateVersion`. It is thus shareable
60
+ * by all client groups regardless of the snapshots they are advancing
61
+ * between.
62
+ * - Previous values are read from the `prev` snapshot at its version, and
63
+ * are tagged with that version (and, when the result can depend on the
64
+ * changes applied to the snapshot before the read, the `curr` version
65
+ * as well). See `Diff` in `snapshotter.ts` for the details.
66
+ *
67
+ * Because every key includes the version(s) that the result depends on,
68
+ * entries never go stale and are never invalidated; they are only evicted.
69
+ *
70
+ * ### Eviction
71
+ *
72
+ * The cache is bounded by entry count and evicts in insertion (FIFO) order
73
+ * rather than by recency. Diffs are traversed in change log order by every
74
+ * client group, so the entries that will not be needed again are those that
75
+ * were inserted the earliest (all groups have moved past them), while the
76
+ * most recently inserted entries are the ones that the trailing groups have
77
+ * yet to reach. Refreshing an entry on a hit would evict exactly the wrong
78
+ * entries when a trailing group catches up.
79
+ *
80
+ * ### Sharing
81
+ *
82
+ * Cached values are shared by reference across client groups and must
83
+ * therefore never be mutated by callers.
84
+ */
85
+ var SnapshotRowCache = class {
86
+ #maxEntries;
87
+ #entries = /* @__PURE__ */ new Map();
88
+ #keys = new RingBuffer();
89
+ #sqlIDs = /* @__PURE__ */ new Map();
90
+ #reads = getOrCreateCounter("sync", "ivm.snapshot-row-reads", "Row reads performed while computing snapshot diffs for IVM, labeled by whether the read was served by the worker-wide snapshot row cache.");
91
+ #hits = 0;
92
+ #misses = 0;
93
+ constructor(maxEntries = DEFAULT_MAX_SNAPSHOT_ROW_CACHE_ENTRIES) {
94
+ this.#maxEntries = Math.max(0, maxEntries);
95
+ }
96
+ get size() {
97
+ return this.#entries.size;
98
+ }
99
+ get maxEntries() {
100
+ return this.#maxEntries;
101
+ }
102
+ stats() {
103
+ return {
104
+ hits: this.#hits,
105
+ misses: this.#misses,
106
+ size: this.#entries.size
107
+ };
108
+ }
109
+ /**
110
+ * Returns the cached result of the read identified by `tag`, `sql`, `mode`
111
+ * and `args`, or performs the read via `read()`, caching and returning its
112
+ * result. `undefined` results (i.e. a missing row) are not cached.
113
+ *
114
+ * `mode` is part of the key because the same statement can be read both
115
+ * ways: for a table whose only unique key is its primary key, `getRow()`
116
+ * and `getRows()` produce identical SQL, args and tags, but the former
117
+ * caches a row and the latter an array of rows.
118
+ */
119
+ getOrRead(tag, sql, mode, args, read) {
120
+ const key = this.#key(tag, sql, mode, args);
121
+ const cached = this.#entries.get(key);
122
+ if (cached !== void 0) {
123
+ this.#hits++;
124
+ this.#reads.add(1, HIT);
125
+ return cached;
126
+ }
127
+ this.#misses++;
128
+ this.#reads.add(1, MISS);
129
+ const value = read();
130
+ if (value !== void 0 && this.#maxEntries > 0) {
131
+ this.#entries.set(key, value);
132
+ this.#keys.push(key);
133
+ if (this.#keys.size > this.#maxEntries) this.#entries.delete(must(this.#keys.shift()));
134
+ }
135
+ return value;
136
+ }
137
+ clear() {
138
+ this.#entries.clear();
139
+ this.#keys.clear();
140
+ this.#sqlIDs.clear();
141
+ }
142
+ #key(tag, sql, mode, args) {
143
+ if (this.#sqlIDs.size >= 1e4 && !this.#sqlIDs.has(sql)) this.clear();
144
+ let key = `${tag}\0${mode}${getOrInsert(this.#sqlIDs, sql, this.#sqlIDs.size)}`;
145
+ for (const arg of args) {
146
+ const serialized = serialize(arg);
147
+ key += `\0${serialized.length}:${serialized}`;
148
+ }
149
+ return key;
150
+ }
151
+ };
152
+ //#endregion
153
+ export { SnapshotRowCache };
154
+
155
+ //# sourceMappingURL=snapshot-row-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-row-cache.js","names":["#maxEntries","#entries","#keys","#sqlIDs","#reads","#hits","#misses","#key"],"sources":["../../../../../../zero-cache/src/services/view-syncer/snapshot-row-cache.ts"],"sourcesContent":["import {stringify} from '../../../../shared/src/bigint-json.ts';\nimport {getOrInsert} from '../../../../shared/src/map.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {RingBuffer} from '../../../../shared/src/ring-buffer.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\n\n/**\n * The default maximum number of entries retained by a {@link SnapshotRowCache}.\n *\n * Each entry is a raw SQLite row (or the list of rows conflicting with\n * a row on its unique keys) read while computing a snapshot diff. A\n * replicated transaction of N rows produces up to 2N entries (the new value\n * and the previous value(s) of each row), so the cache should be large enough\n * to hold the largest transactions that are commonly replicated in order for\n * all of the client groups on a worker to share the reads.\n */\nexport const DEFAULT_MAX_SNAPSHOT_ROW_CACHE_ENTRIES = 50_000;\n\n/**\n * Serializes a lookup key value (i.e. a SQLite-bound parameter). Numbers and\n * bigints serialize identically, as row keys parsed from the change log are\n * JSON numbers whereas the values read from SQLite are bigints; both address\n * the same row. Strings are prefixed to distinguish them from numbers.\n */\nfunction serialize(v: unknown): string {\n switch (typeof v) {\n case 'string':\n return 's' + v;\n case 'number':\n case 'bigint':\n return String(v);\n case 'boolean':\n return v ? 'true' : 'false';\n case 'object':\n return v === null ? 'null' : 'o' + stringify(v);\n default:\n return 'u' + String(v);\n }\n}\n\n/**\n * How a statement's results are read: a single row (`Statement.get()`) or\n * all rows (`Statement.all()`).\n */\nexport type ReadMode = 'get' | 'all';\n\n/**\n * The maximum number of distinct SQL statements interned by a\n * {@link SnapshotRowCache} before it is cleared. There is one per table and\n * lookup shape, so this is only reached after many schema changes.\n */\nexport const MAX_INTERNED_STATEMENTS = 10_000;\n\nconst HIT = {result: 'hit'} as const;\nconst MISS = {result: 'miss'} as const;\n\n/**\n * A worker-wide, bounded cache of the rows that a `Snapshotter` reads from\n * replica snapshots when computing the diff between two versions of the\n * replica.\n *\n * Every client group (i.e. view-syncer) on a sync worker computes its own\n * diff from the shared `_zero.changeLog2`, and each change in the diff costs\n * two SQLite reads: the new value of the row (from the `curr` snapshot) and\n * the previous value(s) that it replaces (from the `prev` snapshot). The\n * reads for a given change are identical for every client group that\n * traverses it, so a replicated transaction of N rows costs N × 2 × (client\n * groups) reads per worker, most of which are redundant. This cache lets the\n * client groups on a worker share those reads.\n *\n * ### Keys\n *\n * Entries are keyed by the exact SQL statement and arguments of the read\n * (which encodes the table, the selected columns and the lookup keys), plus\n * a `tag` supplied by the caller that pins the result to a version of the\n * replica:\n *\n * - The new value of a row is the row as of the `stateVersion` recorded in\n * its change log entry (its `_0_version` is asserted to equal that\n * version), and is tagged with that `stateVersion`. It is thus shareable\n * by all client groups regardless of the snapshots they are advancing\n * between.\n * - Previous values are read from the `prev` snapshot at its version, and\n * are tagged with that version (and, when the result can depend on the\n * changes applied to the snapshot before the read, the `curr` version\n * as well). See `Diff` in `snapshotter.ts` for the details.\n *\n * Because every key includes the version(s) that the result depends on,\n * entries never go stale and are never invalidated; they are only evicted.\n *\n * ### Eviction\n *\n * The cache is bounded by entry count and evicts in insertion (FIFO) order\n * rather than by recency. Diffs are traversed in change log order by every\n * client group, so the entries that will not be needed again are those that\n * were inserted the earliest (all groups have moved past them), while the\n * most recently inserted entries are the ones that the trailing groups have\n * yet to reach. Refreshing an entry on a hit would evict exactly the wrong\n * entries when a trailing group catches up.\n *\n * ### Sharing\n *\n * Cached values are shared by reference across client groups and must\n * therefore never be mutated by callers.\n */\nexport class SnapshotRowCache {\n readonly #maxEntries: number;\n readonly #entries = new Map<string, unknown>();\n // The keys of #entries in insertion order, so that the oldest can be\n // evicted in O(1). (Finding the oldest key with a fresh Map iterator\n // instead costs O(evictions) per insert, as V8 leaves deleted entries in\n // the table until it is compacted and every new iterator walks over them.)\n readonly #keys = new RingBuffer<string>();\n // Interns the SQL text of each distinct read so that entry keys\n // do not repeat the (long) column list of every statement.\n readonly #sqlIDs = new Map<string, number>();\n readonly #reads = getOrCreateCounter(\n 'sync',\n 'ivm.snapshot-row-reads',\n 'Row reads performed while computing snapshot diffs for IVM, labeled by whether the read was served by the worker-wide snapshot row cache.',\n );\n #hits = 0;\n #misses = 0;\n\n constructor(maxEntries = DEFAULT_MAX_SNAPSHOT_ROW_CACHE_ENTRIES) {\n this.#maxEntries = Math.max(0, maxEntries);\n }\n\n get size(): number {\n return this.#entries.size;\n }\n\n get maxEntries(): number {\n return this.#maxEntries;\n }\n\n stats(): {hits: number; misses: number; size: number} {\n return {hits: this.#hits, misses: this.#misses, size: this.#entries.size};\n }\n\n /**\n * Returns the cached result of the read identified by `tag`, `sql`, `mode`\n * and `args`, or performs the read via `read()`, caching and returning its\n * result. `undefined` results (i.e. a missing row) are not cached.\n *\n * `mode` is part of the key because the same statement can be read both\n * ways: for a table whose only unique key is its primary key, `getRow()`\n * and `getRows()` produce identical SQL, args and tags, but the former\n * caches a row and the latter an array of rows.\n */\n getOrRead<T>(\n tag: string,\n sql: string,\n mode: ReadMode,\n args: unknown[],\n read: () => T,\n ): T {\n const key = this.#key(tag, sql, mode, args);\n const cached = this.#entries.get(key);\n if (cached !== undefined) {\n this.#hits++;\n this.#reads.add(1, HIT);\n return cached as T;\n }\n this.#misses++;\n this.#reads.add(1, MISS);\n const value = read();\n if (value !== undefined && this.#maxEntries > 0) {\n // `key` is not in #entries (this is a miss), so it is always added.\n this.#entries.set(key, value);\n this.#keys.push(key);\n if (this.#keys.size > this.#maxEntries) {\n this.#entries.delete(must(this.#keys.shift()));\n }\n }\n return value;\n }\n\n clear(): void {\n this.#entries.clear();\n this.#keys.clear();\n this.#sqlIDs.clear();\n }\n\n #key(tag: string, sql: string, mode: ReadMode, args: unknown[]): string {\n if (\n this.#sqlIDs.size >= MAX_INTERNED_STATEMENTS &&\n !this.#sqlIDs.has(sql)\n ) {\n // Statements are only added (e.g. when schema changes produce new\n // column lists), so bound them by starting over. The entries refer to\n // statements by ID and are cleared with them.\n this.clear();\n }\n const sqlID = getOrInsert(this.#sqlIDs, sql, this.#sqlIDs.size);\n let key = `${tag}\\0${mode}${sqlID}`;\n // Include each value's length so separators inside string values cannot\n // make different argument arrays produce the same key.\n for (const arg of args) {\n const serialized = serialize(arg);\n key += `\\0${serialized.length}:${serialized}`;\n }\n return key;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,yCAAyC;;;;;;;AAQtD,SAAS,UAAU,GAAoB;CACrC,QAAQ,OAAO,GAAf;EACE,KAAK,UACH,OAAO,MAAM;EACf,KAAK;EACL,KAAK,UACH,OAAO,OAAO,CAAC;EACjB,KAAK,WACH,OAAO,IAAI,SAAS;EACtB,KAAK,UACH,OAAO,MAAM,OAAO,SAAS,MAAM,UAAU,CAAC;EAChD,SACE,OAAO,MAAM,OAAO,CAAC;CACzB;AACF;AAeA,IAAM,MAAM,EAAC,QAAQ,MAAK;AAC1B,IAAM,OAAO,EAAC,QAAQ,OAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmD5B,IAAa,mBAAb,MAA8B;CAC5B;CACA,2BAAoB,IAAI,IAAqB;CAK7C,QAAiB,IAAI,WAAmB;CAGxC,0BAAmB,IAAI,IAAoB;CAC3C,SAAkB,mBAChB,QACA,0BACA,2IACF;CACA,QAAQ;CACR,UAAU;CAEV,YAAY,aAAa,wCAAwC;EAC/D,KAAKA,cAAc,KAAK,IAAI,GAAG,UAAU;CAC3C;CAEA,IAAI,OAAe;EACjB,OAAO,KAAKC,SAAS;CACvB;CAEA,IAAI,aAAqB;EACvB,OAAO,KAAKD;CACd;CAEA,QAAsD;EACpD,OAAO;GAAC,MAAM,KAAKK;GAAO,QAAQ,KAAKC;GAAS,MAAM,KAAKL,SAAS;EAAI;CAC1E;;;;;;;;;;;CAYA,UACE,KACA,KACA,MACA,MACA,MACG;EACH,MAAM,MAAM,KAAKM,KAAK,KAAK,KAAK,MAAM,IAAI;EAC1C,MAAM,SAAS,KAAKN,SAAS,IAAI,GAAG;EACpC,IAAI,WAAW,KAAA,GAAW;GACxB,KAAKI;GACL,KAAKD,OAAO,IAAI,GAAG,GAAG;GACtB,OAAO;EACT;EACA,KAAKE;EACL,KAAKF,OAAO,IAAI,GAAG,IAAI;EACvB,MAAM,QAAQ,KAAK;EACnB,IAAI,UAAU,KAAA,KAAa,KAAKJ,cAAc,GAAG;GAE/C,KAAKC,SAAS,IAAI,KAAK,KAAK;GAC5B,KAAKC,MAAM,KAAK,GAAG;GACnB,IAAI,KAAKA,MAAM,OAAO,KAAKF,aACzB,KAAKC,SAAS,OAAO,KAAK,KAAKC,MAAM,MAAM,CAAC,CAAC;EAEjD;EACA,OAAO;CACT;CAEA,QAAc;EACZ,KAAKD,SAAS,MAAM;EACpB,KAAKC,MAAM,MAAM;EACjB,KAAKC,QAAQ,MAAM;CACrB;CAEA,KAAK,KAAa,KAAa,MAAgB,MAAyB;EACtE,IACE,KAAKA,QAAQ,QAAA,OACb,CAAC,KAAKA,QAAQ,IAAI,GAAG,GAKrB,KAAK,MAAM;EAGb,IAAI,MAAM,GAAG,IAAI,IAAI,OADP,YAAY,KAAKA,SAAS,KAAK,KAAKA,QAAQ,IAC9B;EAG5B,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,aAAa,UAAU,GAAG;GAChC,OAAO,KAAK,WAAW,OAAO,GAAG;EACnC;EACA,OAAO;CACT;AACF"}
@@ -6,6 +6,7 @@ import type { LiteAndZqlSpec, LiteTableSpecWithKeysAndVersion } from '../../db/s
6
6
  import { StatementRunner } from '../../db/statements.ts';
7
7
  import { type RowKey, type RowValue } from '../../types/row-key.ts';
8
8
  import type { AppID } from '../../types/shards.ts';
9
+ import type { SnapshotRowCache } from './snapshot-row-cache.ts';
9
10
  /**
10
11
  * A `Snapshotter` manages the progression of database snapshots for a
11
12
  * ViewSyncer.
@@ -67,7 +68,13 @@ import type { AppID } from '../../types/shards.ts';
67
68
  */
68
69
  export declare class Snapshotter {
69
70
  #private;
70
- constructor(lc: LogContext, dbFile: string, { appID }: AppID, pageCacheSizeKib?: number);
71
+ /**
72
+ * @param rowCache An optional worker-wide {@link SnapshotRowCache} through
73
+ * which the row reads performed when iterating over a
74
+ * {@link SnapshotDiff} are shared with the other Snapshotters
75
+ * (i.e. client groups) on the worker.
76
+ */
77
+ constructor(lc: LogContext, dbFile: string, { appID }: AppID, pageCacheSizeKib?: number, rowCache?: SnapshotRowCache);
71
78
  /**
72
79
  * Initializes the snapshot to the current head of the database. This must be
73
80
  * only be called once. The state of whether a Snapshotter has been initialized
@@ -187,12 +194,40 @@ declare class Snapshot {
187
194
  readonly version: string;
188
195
  constructor(db: StatementRunner, appID: string);
189
196
  numChangesSince(prevVersion: string): any;
197
+ /**
198
+ * Whether any table-wide op (i.e. a RESET or TRUNCATE) was logged after
199
+ * `prevVersion`. Iterating over a diff ending at this snapshot will
200
+ * abort with a {@link ResetPipelinesSignal} if so.
201
+ */
202
+ hasTableWideOpSince(prevVersion: string): boolean;
190
203
  changesSince(prevVersion: string): {
191
204
  changes: IterableIterator<unknown>;
192
205
  cleanup: () => void;
193
206
  };
194
- getRow(table: LiteTableSpecWithKeysAndVersion, rowKey: JSONValue): any;
195
- getRows(table: LiteTableSpecWithKeysAndVersion, keys: PrimaryKey[], row: RowValue): any[];
207
+ /**
208
+ * Reads the row with the given `rowKey`, or `undefined` if it does not
209
+ * exist.
210
+ *
211
+ * @param cache An optional {@link SnapshotRowCache} through which to
212
+ * perform the read, along with the `tag` that identifies the
213
+ * version(s) of the replica that the result depends on.
214
+ */
215
+ getRow(table: LiteTableSpecWithKeysAndVersion, rowKey: JSONValue, cache?: SnapshotRowCache, tag?: string): any;
216
+ /**
217
+ * Reads the rows that conflict with `row` on any of the given unique
218
+ * `keys` (which includes the row with the same primary key, if any).
219
+ *
220
+ * @param cache An optional {@link SnapshotRowCache} through which to
221
+ * perform the read, along with the `tag` that identifies the
222
+ * version(s) of the replica that the result depends on.
223
+ */
224
+ getRows(table: LiteTableSpecWithKeysAndVersion, keys: PrimaryKey[], row: RowValue, cache?: SnapshotRowCache, tag?: string): any[];
225
+ /**
226
+ * Whether the connection has been reset to a newer snapshot (i.e. via
227
+ * {@link resetToHead()}), after which reads no longer reflect
228
+ * {@link version}.
229
+ */
230
+ get reset(): boolean;
196
231
  resetToHead(): Snapshot;
197
232
  }
198
233
  export declare class InvalidDiffError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"snapshotter.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/snapshotter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"snapshotter.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/snapshotter.ts"],"names":[],"mappings":"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"}