@rocicorp/zero 1.11.0-canary.13 → 1.11.0-canary.14

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 (249) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/ast-to-zql/src/ast-to-zql.d.ts +8 -1
  4. package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
  5. package/out/ast-to-zql/src/ast-to-zql.js +25 -25
  6. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  7. package/out/otel/src/log-options.d.ts +16 -0
  8. package/out/otel/src/log-options.d.ts.map +1 -1
  9. package/out/otel/src/log-options.js +54 -1
  10. package/out/otel/src/log-options.js.map +1 -1
  11. package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
  12. package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
  13. package/out/replicache/src/get-kv-store-provider.js +4 -4
  14. package/out/replicache/src/get-kv-store-provider.js.map +1 -1
  15. package/out/replicache/src/kv/idb-store.d.ts +10 -1
  16. package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
  17. package/out/replicache/src/kv/idb-store.js +18 -2
  18. package/out/replicache/src/kv/idb-store.js.map +1 -1
  19. package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
  20. package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
  21. package/out/replicache/src/kv/mem-fallback-store.js +177 -0
  22. package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
  23. package/out/replicache/src/kv/op-sqlite/store.js +3 -3
  24. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  25. package/out/replicache/src/kv/sqlite-store.d.ts +9 -0
  26. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  27. package/out/replicache/src/kv/sqlite-store.js +86 -6
  28. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  29. package/out/replicache/src/new-client-channel.js.map +1 -1
  30. package/out/replicache/src/persist/collect-idb-databases.d.ts +17 -3
  31. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  32. package/out/replicache/src/persist/collect-idb-databases.js +81 -11
  33. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  34. package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
  35. package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
  36. package/out/replicache/src/persist/persist.d.ts.map +1 -1
  37. package/out/replicache/src/persist/persist.js +6 -3
  38. package/out/replicache/src/persist/persist.js.map +1 -1
  39. package/out/replicache/src/replicache-impl.d.ts +43 -3
  40. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  41. package/out/replicache/src/replicache-impl.js +135 -17
  42. package/out/replicache/src/replicache-impl.js.map +1 -1
  43. package/out/replicache/src/storage-failure.d.ts +37 -0
  44. package/out/replicache/src/storage-failure.d.ts.map +1 -0
  45. package/out/replicache/src/storage-failure.js +31 -0
  46. package/out/replicache/src/storage-failure.js.map +1 -0
  47. package/out/shared/src/random-values.js +12 -1
  48. package/out/shared/src/random-values.js.map +1 -1
  49. package/out/zero/package.js +1 -1
  50. package/out/zero/package.js.map +1 -1
  51. package/out/zero/src/zero.js +2 -1
  52. package/out/zero-cache/src/config/zero-config.d.ts +26 -0
  53. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  54. package/out/zero-cache/src/config/zero-config.js +36 -0
  55. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  56. package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
  57. package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
  58. package/out/zero-cache/src/observability/log-throttle.js +76 -0
  59. package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
  60. package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
  61. package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
  62. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  63. package/out/zero-cache/src/server/runner/zero-dispatcher.js +2 -0
  64. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  65. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  66. package/out/zero-cache/src/server/syncer.js +20 -2
  67. package/out/zero-cache/src/server/syncer.js.map +1 -1
  68. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  69. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +27 -15
  70. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  71. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -0
  72. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  73. package/out/zero-cache/src/services/change-source/common/replica-schema.js +15 -3
  74. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  75. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  76. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  77. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +9 -0
  78. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  79. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +204 -18
  80. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  81. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +11 -2
  82. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  83. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +11 -2
  84. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  85. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
  86. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
  87. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
  88. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
  89. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +43 -0
  90. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/change-source/protocol/current/data.js +10 -1
  92. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  93. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +57 -0
  94. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  95. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +20 -6
  96. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
  97. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
  98. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  99. package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
  100. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +66 -0
  101. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
  102. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +154 -0
  103. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
  104. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
  105. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
  106. package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
  107. package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
  108. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +7 -6
  109. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  110. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  111. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +20 -4
  112. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  113. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +26 -1
  114. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  115. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  116. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  117. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  118. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  119. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +88 -0
  120. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
  121. package/out/zero-cache/src/services/change-streamer/subscribe.js +9 -1
  122. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
  123. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +29 -0
  124. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  125. package/out/zero-cache/src/services/change-streamer/subscriber.js +34 -0
  126. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  127. package/out/zero-cache/src/services/plannerz.d.ts +159 -0
  128. package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
  129. package/out/zero-cache/src/services/plannerz.js +263 -0
  130. package/out/zero-cache/src/services/plannerz.js.map +1 -0
  131. package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
  132. package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
  133. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  134. package/out/zero-cache/src/services/replicator/change-processor.js +44 -12
  135. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  136. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  137. package/out/zero-cache/src/services/replicator/incremental-sync.js +4 -3
  138. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  139. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
  140. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
  141. package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
  142. package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
  143. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
  144. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
  145. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  146. package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
  147. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  148. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
  149. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
  150. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
  151. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
  152. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  153. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +36 -1
  154. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  155. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +362 -20
  156. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  157. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
  158. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
  159. package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
  160. package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
  161. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
  162. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
  163. package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
  164. package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
  165. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +77 -0
  166. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
  167. package/out/zero-cache/src/services/view-syncer/query-stats.js +206 -0
  168. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
  169. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +11 -1
  170. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  171. package/out/zero-cache/src/services/view-syncer/snapshotter.js +7 -1
  172. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  173. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  174. package/out/zero-cache/src/services/view-syncer/view-syncer.js +49 -3
  175. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  176. package/out/zero-cache/src/workers/connection.js +3 -3
  177. package/out/zero-cache/src/workers/connection.js.map +1 -1
  178. package/out/zero-cache/src/workers/syncer.js +2 -2
  179. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  180. package/out/zero-client/src/client/options.d.ts +29 -0
  181. package/out/zero-client/src/client/options.d.ts.map +1 -1
  182. package/out/zero-client/src/client/options.js.map +1 -1
  183. package/out/zero-client/src/client/version.js +1 -1
  184. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  185. package/out/zero-client/src/client/zero.js +8 -23
  186. package/out/zero-client/src/client/zero.js.map +1 -1
  187. package/out/zero-client/src/mod.d.ts +1 -0
  188. package/out/zero-client/src/mod.d.ts.map +1 -1
  189. package/out/zero-client/src/mod.js +1 -0
  190. package/out/zero-protocol/src/down.d.ts +2 -2
  191. package/out/zero-react/src/zero.js +1 -0
  192. package/out/zero-solid/src/zero.js +1 -0
  193. package/out/zql/src/builder/builder.d.ts +6 -0
  194. package/out/zql/src/builder/builder.d.ts.map +1 -1
  195. package/out/zql/src/builder/builder.js +4 -3
  196. package/out/zql/src/builder/builder.js.map +1 -1
  197. package/out/zql/src/ivm/memory-source.d.ts +13 -0
  198. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  199. package/out/zql/src/ivm/memory-source.js +1 -1
  200. package/out/zql/src/ivm/memory-source.js.map +1 -1
  201. package/out/zql/src/planner/planner-builder.d.ts +12 -1
  202. package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
  203. package/out/zql/src/planner/planner-builder.js +8 -1
  204. package/out/zql/src/planner/planner-builder.js.map +1 -1
  205. package/out/zql/src/planner/planner-connection.d.ts +43 -0
  206. package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
  207. package/out/zql/src/planner/planner-connection.js +31 -4
  208. package/out/zql/src/planner/planner-connection.js.map +1 -1
  209. package/out/zql/src/planner/planner-graph.d.ts +10 -0
  210. package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
  211. package/out/zql/src/planner/planner-graph.js +14 -0
  212. package/out/zql/src/planner/planner-graph.js.map +1 -1
  213. package/out/zql/src/planner/planner-warnings.d.ts +83 -0
  214. package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
  215. package/out/zql/src/planner/planner-warnings.js +78 -0
  216. package/out/zql/src/planner/planner-warnings.js.map +1 -0
  217. package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
  218. package/out/zql/src/query/measure-push-operator.js +30 -8
  219. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  220. package/out/zqlite/src/pending-delta.d.ts +110 -0
  221. package/out/zqlite/src/pending-delta.d.ts.map +1 -0
  222. package/out/zqlite/src/pending-delta.js +278 -0
  223. package/out/zqlite/src/pending-delta.js.map +1 -0
  224. package/out/zqlite/src/query-delegate.d.ts.map +1 -1
  225. package/out/zqlite/src/query-delegate.js +5 -1
  226. package/out/zqlite/src/query-delegate.js.map +1 -1
  227. package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
  228. package/out/zqlite/src/sqlite-cost-model.js +52 -1
  229. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  230. package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
  231. package/out/zqlite/src/sqlite-stat-fanout.js +2 -26
  232. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  233. package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
  234. package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
  235. package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
  236. package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
  237. package/out/zqlite/src/table-source.d.ts +68 -0
  238. package/out/zqlite/src/table-source.d.ts.map +1 -1
  239. package/out/zqlite/src/table-source.js +206 -7
  240. package/out/zqlite/src/table-source.js.map +1 -1
  241. package/package.json +1 -1
  242. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -35
  243. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
  244. package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
  245. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
  246. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +0 -156
  247. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +0 -1
  248. package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +0 -15
  249. package/out/zero-cache/src/services/change-source/pg/backfill-resume.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"memory-source.js","names":["#tableName","#columns","#primaryKey","#primaryIndexSort","#indexes","#connections","#getPrimaryIndex","#fetch","#disconnect","#getSchema","#fetchMulti","#getOrCreateIndex","#overlay","#writeChange","#pushEpoch"],"sources":["../../../../../zql/src/ivm/memory-source.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {BTreeSet} from '../../../shared/src/btree-set.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport {once, toSorted} from '../../../shared/src/iterables.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n Condition,\n Ordering,\n OrderPart,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue} from '../../../zero-types/src/schema-value.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n type NoSubqueryCondition,\n} from '../builder/filter.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Change} from './change.ts';\nimport {makeAddChange, makeEditChange, makeRemoveChange} from './change.ts';\nimport {\n constraintMatchesPrimaryKey,\n constraintMatchesRow,\n primaryKeyConstraintFromFilters,\n type Constraint,\n} from './constraint.ts';\nimport {\n compareValues,\n makeComparator,\n valuesEqual,\n type Comparator,\n type Node,\n} from './data.ts';\nimport {filterPush} from './filter-push.ts';\nimport {\n skipYields,\n type FetchRequest,\n type Input,\n type MultiConstraint,\n type Output,\n type Start,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {SourceChangeIndex} from './source-change-index.ts';\nimport type {\n Source,\n SourceChange,\n SourceChangeAdd,\n SourceChangeEdit,\n SourceChangeRemove,\n SourceInput,\n} from './source.ts';\nimport {makeSourceChangeAdd, makeSourceChangeRemove} from './source.ts';\nimport {consume, type Stream} from './stream.ts';\n\nexport type Overlay = {\n epoch: number;\n change: SourceChange;\n};\n\nexport type Overlays = {\n add: Row | undefined;\n remove: Row | undefined;\n};\n\ntype Index = {\n comparator: Comparator;\n data: BTreeSet<Row>;\n};\n\nexport type Connection = {\n input: Input;\n output: Output | undefined;\n sort?: Ordering | undefined;\n splitEditKeys: Set<string> | undefined;\n compareRows: Comparator;\n filters:\n | {\n condition: NoSubqueryCondition;\n predicate: (row: Row) => boolean;\n }\n | undefined;\n readonly debug?: DebugDelegate | undefined;\n lastPushedEpoch: number;\n};\n\n/**\n * A `MemorySource` is a source that provides data to the pipeline from an\n * in-memory data source.\n *\n * This data is kept in sorted order as downstream pipelines will always expect\n * the data they receive from `pull` to be in sorted order.\n */\nexport class MemorySource implements Source {\n readonly #tableName: string;\n readonly #columns: Record<string, SchemaValue>;\n readonly #primaryKey: PrimaryKey;\n readonly #primaryIndexSort: Ordering;\n readonly #indexes: Map<string, Index> = new Map();\n readonly #connections: Connection[] = [];\n\n #overlay: Overlay | undefined;\n #pushEpoch = 0;\n\n constructor(\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n primaryIndexData?: BTreeSet<Row>,\n ) {\n this.#tableName = tableName;\n this.#columns = columns;\n this.#primaryKey = primaryKey;\n this.#primaryIndexSort = primaryKey.map(k => [k, 'asc']);\n const comparator = makeBoundComparator(this.#primaryIndexSort);\n this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {\n comparator,\n data: primaryIndexData ?? new BTreeSet<Row>(comparator),\n });\n }\n\n get tableSchema() {\n return {\n name: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n fork() {\n const primaryIndex = this.#getPrimaryIndex();\n return new MemorySource(\n this.#tableName,\n this.#columns,\n this.#primaryKey,\n primaryIndex.data.clone(),\n );\n }\n\n get data(): BTreeSet<Row> {\n return this.#getPrimaryIndex().data;\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n system: 'client',\n relationships: {},\n isHidden: false,\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n ): SourceInput {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n const internalSort = sort ?? this.#primaryIndexSort;\n\n const input: SourceInput = {\n getSchema: () => schema,\n fetch: req => this.#fetch(req, connection),\n setOutput: output => {\n connection.output = output;\n },\n destroy: () => {\n this.#disconnect(input);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n output: undefined,\n sort: internalSort,\n splitEditKeys,\n compareRows: makeComparator(internalSort),\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(internalSort, this.#primaryKey);\n }\n this.#connections.push(connection);\n return input;\n }\n\n #disconnect(input: Input): void {\n const idx = this.#connections.findIndex(c => c.input === input);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n\n // Indexes deliberately hold no reference back to the connections that use\n // them. They used to, to support deleting an index once its last user went\n // away. That deletion is gone (see below) but the `usedBy` set outlived it,\n // so an index kept every `Connection` — and through its `input` and\n // `output`, the whole torn-down pipeline — reachable for as long as the\n // source lived.\n //\n // TODO: We used to delete unused indexes here. But in common cases like\n // navigating into issue detail pages it caused a ton of constantly\n // building and destroying indexes.\n //\n // Perhaps some intelligent LRU or something is needed here but for now,\n // the opposite extreme of keeping all indexes for the lifetime of the\n // page seems better.\n }\n\n #getPrimaryIndex(): Index {\n const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));\n assert(index, 'Primary index not found');\n return index;\n }\n\n #getOrCreateIndex(sort: Ordering): Index {\n const key = JSON.stringify(sort);\n const index = this.#indexes.get(key);\n // Future optimization could use existing index if it's the same just sorted\n // in reverse of needed.\n if (index) {\n return index;\n }\n\n const comparator = makeBoundComparator(sort);\n\n // When creating these synchronously becomes a problem, a few options:\n // 1. Allow users to specify needed indexes up front\n // 2. Create indexes in a different thread asynchronously (this would require\n // modifying the BTree to be able to be passed over structured-clone, or using\n // a different library.)\n // 3. We could even theoretically do (2) on multiple threads and then merge the\n // results!\n const rows = toSorted(this.#getPrimaryIndex().data, comparator);\n const data = BTreeSet.fromSorted(comparator, rows);\n\n const newIndex = {comparator, data};\n this.#indexes.set(key, newIndex);\n return newIndex;\n }\n\n // For unit testing that we correctly clean up indexes.\n getIndexKeys(): string[] {\n return [...this.#indexes.keys()];\n }\n\n *#fetch(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // multiConstraints is handled by driving sub-fetches off the first\n // entry's values and post-filtering matches against any remaining\n // entries. TableSource implements multi-IN natively via SQL `AND` of\n // `IN` clauses; the in-memory path is correct but not specially\n // optimized — it pays the index-seek cost per primary value, then\n // walks each result against the rest.\n if (\n req.multiConstraints &&\n req.multiConstraints.some(mc => mc.length > 0)\n ) {\n yield* this.#fetchMulti(req, conn);\n return;\n }\n const requestedSort = must(conn.sort);\n const {compareRows} = conn;\n // Avoid allocating a new closure when not reversing (the common case).\n const connectionComparator: Comparator = req.reverse\n ? (r1, r2) => compareRows(r2, r1)\n : compareRows;\n\n const reqFilter = req.filter;\n const connFilterCondition = conn.filters?.condition;\n const mergedFilterCondition: NoSubqueryCondition | undefined =\n connFilterCondition && reqFilter\n ? {type: 'and', conditions: [connFilterCondition, reqFilter]}\n : (connFilterCondition ?? reqFilter);\n const mergedFilterPredicate = mergePredicates(\n conn.filters?.predicate,\n reqFilter,\n );\n\n const pkConstraint = primaryKeyConstraintFromFilters(\n mergedFilterCondition,\n this.#primaryKey,\n );\n // The primary key constraint will be more limiting than the constraint\n // so swap out to that if it exists.\n const fetchOrPkConstraint = pkConstraint ?? req.constraint;\n\n // If there is a constraint, we need an index sorted by it first.\n const indexSort: OrderPart[] = [];\n if (fetchOrPkConstraint) {\n for (const key of Object.keys(fetchOrPkConstraint)) {\n indexSort.push([key, 'asc']);\n }\n }\n\n // For the special case of constraining by PK, we don't need to worry about\n // any requested sort since there can only be one result. Otherwise we also\n // need the index sorted by the requested sort.\n if (\n this.#primaryKey.length > 1 ||\n !fetchOrPkConstraint ||\n !constraintMatchesPrimaryKey(fetchOrPkConstraint, this.#primaryKey)\n ) {\n indexSort.push(...requestedSort);\n }\n\n const index = this.#getOrCreateIndex(indexSort);\n const {data, comparator: compare} = index;\n // Avoid allocating a new closure when not reversing (the common case).\n const indexComparator: Comparator = req.reverse\n ? (r1, r2) => compare(r2, r1)\n : compare;\n\n const startAt = req.start?.row;\n\n // If there is a constraint, we want to start our scan at the first row that\n // matches the constraint. But because the next OrderPart can be `desc`,\n // it's not true that {[constraintKey]: constraintValue} is the first\n // matching row. Because in that case, the other fields will all be\n // `undefined`, and in Zero `undefined` is always less than any other value.\n // So if the second OrderPart is descending then `undefined` values will\n // actually be the *last* row. We need a way to stay \"start at the first row\n // with this constraint value\". RowBound with the corresponding compareBound\n // comparator accomplishes this. The right thing is probably to teach the\n // btree library to support this concept.\n let scanStart: RowBound | undefined;\n\n if (fetchOrPkConstraint) {\n scanStart = {};\n for (const [key, dir] of indexSort) {\n if (hasOwn(fetchOrPkConstraint, key)) {\n scanStart[key] = fetchOrPkConstraint[key];\n } else {\n if (req.reverse) {\n scanStart[key] = dir === 'asc' ? maxValue : minValue;\n } else {\n scanStart[key] = dir === 'asc' ? minValue : maxValue;\n }\n }\n }\n } else {\n scanStart = startAt;\n }\n\n const rowsIterable = generateRows(data, scanStart, req.reverse);\n\n // Fast path: no overlay to apply, no `start`, and no connection filters.\n // This is the common shape for join child-lookups, plain scans, and source\n // fetches that sit beneath a downstream `Filter` operator. In that case the\n // whole generator stack (generateWithOverlay → generateWithStart →\n // skipYields → generateWithConstraint) reduces to: walk rows from\n // `scanStart`, wrap each as a Node, and stop once the constraint no longer\n // matches (rows are sorted by the constraint key first, so matches are\n // contiguous). Collapsing the five chained generators into one loop removes\n // a large amount of per-row generator-resume overhead on the hottest path.\n const overlayActive =\n this.#overlay && conn.lastPushedEpoch >= this.#overlay.epoch;\n if (!overlayActive && !req.start && !conn.filters && !req.filter) {\n const {constraint} = req;\n for (const row of rowsIterable) {\n if (constraint && !constraintMatchesRow(constraint, row)) {\n break;\n }\n yield {row, relationships: {}};\n }\n return;\n }\n\n const withOverlay = generateWithOverlay(\n startAt,\n pkConstraint ? once(rowsIterable) : rowsIterable,\n // use `req.constraint` here and not `fetchOrPkConstraint` since `fetchOrPkConstraint` could be the\n // primary key constraint. The primary key constraint comes from filters and is acting as a filter\n // rather than as the fetch constraint.\n req.constraint,\n this.#overlay,\n conn.lastPushedEpoch,\n // Use indexComparator, generateWithOverlayInner has a subtle dependency\n // on this. Since generateWithConstraint is done after\n // generateWithOverlay, the generator consumed by generateWithOverlayInner\n // does not end when the constraint stops matching and so the final\n // check to yield an add overlay if not yet yielded is not reached.\n // However, using the indexComparator the add overlay will be less than\n // the first row that does not match the constraint, and so any\n // not yet yielded add overlay will be yielded when the first row\n // not matching the constraint is reached.\n indexComparator,\n // `startAt` is `req.start.row`, a bound in the connection's sort order,\n // so it must be compared in that order -- not `indexComparator`, which\n // with a constraint leads with the constraint keys. The two agree only\n // when the overlay row and `startAt` share those key values; they do not\n // for `#fetchMulti`'s per-value sub-fetches, which pin one primary key\n // while still carrying the caller's connection-sort `start`. Comparing\n // there by the constraint key dropped the in-flight overlay and served\n // pre-push data. Same distinction #4926 drew for `generateWithStart`.\n connectionComparator,\n mergedFilterPredicate,\n );\n\n const withConstraint = generateWithConstraint(\n skipYields(\n generateWithStart(withOverlay, req.start, connectionComparator),\n ),\n // we use `req.constraint` and not `fetchOrPkConstraint` here because we need to\n // AND the constraint with what could have been the primary key constraint\n req.constraint,\n );\n\n yield* mergedFilterPredicate\n ? generateWithFilter(withConstraint, mergedFilterPredicate)\n : withConstraint;\n }\n\n *#fetchMulti(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // Caller (`#fetch`) guards entry on `req.multiConstraints.some(mc =>\n // mc.length > 0)`, so `multis` is guaranteed non-empty after the\n // empty-entry filter. Per the MultiConstraint contract (operator.ts),\n // entries are non-empty, share key shape, are unique, and are\n // key-compatible with `req.constraint`.\n const multis = must(req.multiConstraints).filter(mc => mc.length > 0);\n const primary = multis[0];\n const rest = multis.slice(1);\n const baseConstraint = req.constraint;\n\n // Drive sub-fetches off `primary`. Within each sub-fetch the source\n // can use its index for `primary`; we then post-filter rows by\n // checking they also match every entry in `rest`. Per the\n // MultiConstraint contract (see operator.ts), entries are unique and\n // key-compatible with `baseConstraint`, so we don't dedupe or check\n // compatibility here.\n const subStreams: Stream<Node | 'yield'>[] = primary.map(c => {\n const merged: Constraint = baseConstraint ? {...baseConstraint, ...c} : c;\n return this.#fetch(\n {...req, constraint: merged, multiConstraints: undefined},\n conn,\n );\n });\n const merged = mergeSortedStreams(\n subStreams,\n req.reverse\n ? (a, b) => conn.compareRows(b.row, a.row)\n : (a, b) => conn.compareRows(a.row, b.row),\n );\n\n if (rest.length === 0) {\n yield* merged;\n return;\n }\n\n for (const node of merged) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n let matchesAll = true;\n for (const mc of rest) {\n let any = false;\n for (const c of mc) {\n if (constraintMatchesRow(c, node.row)) {\n any = true;\n break;\n }\n }\n if (!any) {\n matchesAll = false;\n break;\n }\n }\n if (matchesAll) yield node;\n }\n }\n\n /**\n * Pushes an add for each of `rows`.\n *\n * When the source holds no rows and nothing is connected to it (loading the\n * replica at startup) there is no output to notify and no secondary index to\n * keep in step, so the primary index is built bottom-up in O(N) (plus a sort\n * when `rows` are not already in primary key order) rather than doing one\n * tree insert, and one trip through the push machinery, per row.\n *\n * `rows` may be sorted in place. Rows must have distinct primary keys.\n */\n pushAdds(rows: Row[]): void {\n const index = this.#getPrimaryIndex();\n const canBulkLoad =\n this.#connections.length === 0 &&\n this.#indexes.size === 1 &&\n index.data.size === 0;\n if (!canBulkLoad) {\n for (const row of rows) {\n consume(this.push(makeSourceChangeAdd(row)));\n }\n return;\n }\n const {comparator} = index;\n // The check is not redundant with the sort. V8's TimSort is O(N) on sorted\n // input but Hermes' sort is not adaptive: always sorting doubled the load\n // time of an already ordered 180k row replica on Android (343ms -> 672ms).\n if (!isAscending(rows, comparator)) {\n rows.sort(comparator);\n }\n index.data = BTreeSet.fromSorted(comparator, rows);\n }\n\n *push(change: SourceChange): Stream<'yield'> {\n for (const result of this.genPush(change)) {\n if (result === 'yield') {\n yield result;\n }\n }\n }\n\n genPush(change: SourceChange) {\n const primaryIndex = this.#getPrimaryIndex();\n const {data} = primaryIndex;\n const exists = (row: Row) => data.has(row);\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n return genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n for (const {data} of this.#indexes.values()) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD: {\n const added = data.add(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n added,\n 'MemorySource: add must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.REMOVE: {\n const removed = data.delete(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: remove must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.EDIT: {\n // TODO: We could see if the PK (form the index tree's perspective)\n // changed and if not we could use set.\n // We cannot just do `set` with the new value since the `oldRow` might\n // not map to the same entry as the new `row` in the index btree.\n const removed = data.delete(change[SourceChangeIndex.OLD_ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: edit remove must succeed since row existence was already checked',\n );\n data.add(change[SourceChangeIndex.ROW]);\n break;\n }\n default:\n unreachable(change);\n }\n }\n }\n}\n\nfunction mergePredicates(\n connPredicate: ((row: Row) => boolean) | undefined,\n reqFilter: NoSubqueryCondition | undefined,\n): ((row: Row) => boolean) | undefined {\n if (!reqFilter) {\n return connPredicate;\n }\n const reqPredicate = createPredicate(reqFilter);\n if (!connPredicate) {\n return reqPredicate;\n }\n return row => connPredicate(row) && reqPredicate(row);\n}\n\nfunction* generateWithConstraint(\n it: Stream<Node>,\n constraint: Constraint | undefined,\n) {\n for (const node of it) {\n if (constraint && !constraintMatchesRow(constraint, node.row)) {\n break;\n }\n yield node;\n }\n}\n\nfunction* generateWithFilter(it: Stream<Node>, filter: (row: Row) => boolean) {\n for (const node of it) {\n if (filter(node.row)) {\n yield node;\n }\n }\n}\n\nexport function* genPushAndWriteWithSplitEdit(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n getNextEpoch: () => number,\n) {\n let shouldSplitEdit = false;\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT) {\n for (const {splitEditKeys} of connections) {\n if (splitEditKeys) {\n for (const key of splitEditKeys) {\n if (\n !valuesEqual(\n change[SourceChangeIndex.ROW][key],\n change[SourceChangeIndex.OLD_ROW][key],\n )\n ) {\n shouldSplitEdit = true;\n break;\n }\n }\n }\n }\n }\n\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT && shouldSplitEdit) {\n yield* genPushAndWrite(\n connections,\n makeSourceChangeRemove(change[SourceChangeIndex.OLD_ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n yield* genPushAndWrite(\n connections,\n makeSourceChangeAdd(change[SourceChangeIndex.ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n } else {\n yield* genPushAndWrite(\n connections,\n change,\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n }\n}\n\nfunction* genPushAndWrite(\n connections: readonly Connection[],\n change: SourceChangeAdd | SourceChangeRemove | SourceChangeEdit,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n pushEpoch: number,\n) {\n for (const x of genPush(connections, change, exists, setOverlay, pushEpoch)) {\n yield x;\n }\n writeChange(change);\n}\n\nfunction* genPush(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => void,\n pushEpoch: number,\n) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n assert(\n !exists(change[SourceChangeIndex.ROW]),\n () => `Row already exists ${stringify(change)}`,\n );\n break;\n case ChangeType.REMOVE:\n assert(\n exists(change[SourceChangeIndex.ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n case ChangeType.EDIT:\n assert(\n exists(change[SourceChangeIndex.OLD_ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n default:\n unreachable(change);\n }\n\n setOverlay({epoch: pushEpoch, change});\n try {\n for (const conn of connections) {\n const {output, filters, input} = conn;\n if (output) {\n conn.lastPushedEpoch = pushEpoch;\n const outputChange: Change =\n change[SourceChangeIndex.TYPE] === ChangeType.EDIT\n ? makeEditChange(\n {row: change[SourceChangeIndex.ROW], relationships: {}},\n {row: change[SourceChangeIndex.OLD_ROW], relationships: {}},\n )\n : change[SourceChangeIndex.TYPE] === ChangeType.ADD\n ? makeAddChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n })\n : makeRemoveChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n });\n yield* filterPush(outputChange, output, input, filters?.predicate);\n yield undefined;\n }\n }\n\n for (const conn of connections) {\n const {output, input} = conn;\n if (output?.reconcile) {\n yield* output.reconcile(input);\n yield undefined;\n }\n }\n } finally {\n setOverlay(undefined);\n }\n}\n\nexport function* generateWithStart(\n nodes: Iterable<Node | 'yield'>,\n start: Start | undefined,\n compare: (r1: Row, r2: Row) => number,\n): Stream<Node | 'yield'> {\n if (!start) {\n yield* nodes;\n return;\n }\n let started = false;\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!started) {\n if (start.basis === 'at') {\n if (compare(node.row, start.row) >= 0) {\n started = true;\n }\n } else if (start.basis === 'after') {\n if (compare(node.row, start.row) > 0) {\n started = true;\n }\n }\n }\n if (started) {\n yield node;\n }\n }\n}\n\n/**\n * Takes an iterator and overlay.\n * Splices the overlay into the iterator at the correct position.\n *\n * @param startAt - if there is a lower bound to the stream. If the lower bound of the stream\n * is above the overlay, the overlay will be skipped.\n * @param rows - the stream into which the overlay should be spliced\n * @param constraint - constraint that was applied to the rowIterator and should\n * also be applied to the overlay.\n * @param overlay - the overlay values to splice in\n * @param compare - the comparator to use to find the position for the overlay.\n * Must order rows the same way `rows` is ordered. For `MemorySource` that is\n * *index* order, which leads with the constraint keys.\n * @param startAtCompare - the comparator for `startAt`, which is a bound in the\n * *connection's* sort order. Only the same as `compare` when the stream's order\n * happens to be the connection's -- true for `TableSource` (SQL does the\n * ordering) but not for a constrained `MemorySource` fetch. Conflating the two\n * is what #4926 fixed for `generateWithStart`; this parameter is the same\n * distinction for the overlay's own `startAt` pruning.\n */\nexport function generateWithOverlay(\n startAt: Row | undefined,\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n compare: Comparator,\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[],\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n const overlays = computeOverlays(\n startAt,\n constraint,\n overlayToApply,\n startAtCompare,\n filterPredicate,\n multiConstraints,\n );\n return generateWithOverlayInner(rows, overlays, compare);\n}\n\nfunction computeOverlays(\n startAt: Row | undefined,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n // Only ever used for `overlaysForStartAt`, so this is the *connection*-order\n // comparator, not the one used to splice the overlay into the row stream.\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[],\n): Overlays {\n let overlays: Overlays = {\n add: undefined,\n remove: undefined,\n };\n switch (overlay?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlay.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: overlay.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n\n if (startAt) {\n overlays = overlaysForStartAt(overlays, startAt, startAtCompare);\n }\n\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n\n return overlays;\n}\n\nfunction applyMultiConstraintsToOverlays(\n overlays: Overlays,\n multiConstraints: readonly MultiConstraint[] | undefined,\n): Overlays {\n if (!multiConstraints) return overlays;\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n overlays = overlaysForMultiConstraint(overlays, mc);\n }\n }\n return overlays;\n}\n\nexport {overlaysForMultiConstraint as overlaysForMultiConstraintForTest};\n\nfunction overlaysForMultiConstraint(\n {add, remove}: Overlays,\n multiConstraint: MultiConstraint,\n): Overlays {\n const matchesAny = (row: Row | undefined) => {\n if (row === undefined) return false;\n for (const c of multiConstraint) {\n if (constraintMatchesRow(c, row)) return true;\n }\n return false;\n };\n return {\n add: matchesAny(add) ? add : undefined,\n remove: matchesAny(remove) ? remove : undefined,\n };\n}\n\nexport {overlaysForStartAt as overlaysForStartAtForTest};\n\nfunction overlaysForStartAt(\n {add, remove}: Overlays,\n startAt: Row,\n compare: Comparator,\n): Overlays {\n const undefinedIfBeforeStartAt = (row: Row | undefined) =>\n row === undefined || compare(row, startAt) < 0 ? undefined : row;\n return {\n add: undefinedIfBeforeStartAt(add),\n remove: undefinedIfBeforeStartAt(remove),\n };\n}\n\nexport {overlaysForConstraint as overlaysForConstraintForTest};\n\nfunction overlaysForConstraint(\n {add, remove}: Overlays,\n constraint: Constraint,\n): Overlays {\n const undefinedIfDoesntMatchConstraint = (row: Row | undefined) =>\n row === undefined || !constraintMatchesRow(constraint, row)\n ? undefined\n : row;\n\n return {\n add: undefinedIfDoesntMatchConstraint(add),\n remove: undefinedIfDoesntMatchConstraint(remove),\n };\n}\n\nfunction overlaysForFilterPredicate(\n {add, remove}: Overlays,\n filterPredicate: (row: Row) => boolean | undefined,\n): Overlays {\n const undefinedIfDoesntMatchFilter = (row: Row | undefined) =>\n row === undefined || !filterPredicate(row) ? undefined : row;\n\n return {\n add: undefinedIfDoesntMatchFilter(add),\n remove: undefinedIfDoesntMatchFilter(remove),\n };\n}\n\nexport function* generateWithOverlayInner(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n compare: (r1: Row, r2: Row) => number,\n) {\n let addOverlayYielded = false;\n let removeOverlaySkipped = false;\n for (const row of rowIterator) {\n if (!addOverlayYielded && overlays.add) {\n const cmp = compare(overlays.add, row);\n if (cmp < 0) {\n addOverlayYielded = true;\n yield {row: overlays.add, relationships: {}};\n }\n }\n\n if (!removeOverlaySkipped && overlays.remove) {\n const cmp = compare(overlays.remove, row);\n if (cmp === 0) {\n removeOverlaySkipped = true;\n continue;\n }\n }\n yield {row, relationships: {}};\n }\n\n if (!addOverlayYielded && overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n}\n\n/**\n * Like {@link generateWithOverlay} but for unordered streams.\n * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly\n * at the start, and suppresses add/new-edit rows inline by PK match.\n */\nexport function generateWithOverlayUnordered(\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n primaryKey: PrimaryKey,\n filterPredicate?: (row: Row) => boolean,\n multiConstraints?: readonly MultiConstraint[],\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n let overlays: Overlays = {add: undefined, remove: undefined};\n switch (overlayToApply?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlayToApply.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: overlayToApply.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n return generateWithOverlayInnerUnordered(rows, overlays, primaryKey);\n}\n\nexport function* generateWithOverlayInnerUnordered(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n primaryKey: PrimaryKey,\n) {\n // Eager inject: yield the add overlay at the start (row not yet in storage)\n if (overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n // Stream with inline suppress: skip the remove overlay (row still in storage)\n let removeSkipped = false;\n for (const row of rowIterator) {\n if (\n !removeSkipped &&\n overlays.remove &&\n rowMatchesPK(overlays.remove, row, primaryKey)\n ) {\n removeSkipped = true;\n continue;\n }\n yield {row, relationships: {}};\n }\n}\n\nfunction rowMatchesPK(a: Row, b: Row, primaryKey: PrimaryKey): boolean {\n for (const key of primaryKey) {\n if (!valuesEqual(a[key], b[key])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A location to begin scanning an index from. Can either be a specific value\n * or the min or max possible value for the type. This is used to start a scan\n * at the beginning of the rows matching a constraint.\n */\ntype Bound = Value | MinValue | MaxValue;\ntype RowBound = Record<string, Bound>;\nconst minValue = Symbol('min-value');\ntype MinValue = typeof minValue;\nconst maxValue = Symbol('max-value');\ntype MaxValue = typeof maxValue;\n\nfunction isAscending(rows: Row[], comparator: Comparator): boolean {\n for (let i = 1; i < rows.length; i++) {\n if (comparator(rows[i - 1], rows[i]) > 0) {\n return false;\n }\n }\n return true;\n}\n\nfunction makeBoundComparator(sort: Ordering): Comparator {\n // Pre-extract the first two keys/directions to avoid per-call array access.\n // All paths share one function literal (single SFI) so the BTree comparator call site\n // stays monomorphic across indexes with different sort orderings, preventing V8 IC deopt.\n // Even a 2-SFI split (e.g. separate len=1 path) creates a polymorphic IC that\n // measurably regresses performance, so we keep a single return body.\n const len = sort.length;\n const k0 = sort[0][0];\n const a0 = sort[0][1] === 'asc';\n const k1 = len > 1 ? sort[1][0] : '';\n const a1 = len > 1 ? sort[1][1] === 'asc' : true;\n\n return (a: RowBound, b: RowBound) => {\n const c0 = a0 ? compareBounds(a[k0], b[k0]) : compareBounds(b[k0], a[k0]);\n if (len === 1 || c0 !== 0) return c0;\n const c1 = a1 ? compareBounds(a[k1], b[k1]) : compareBounds(b[k1], a[k1]);\n if (len === 2 || c1 !== 0) return c1;\n // Hot! Do not use destructuring\n for (let i = 2; i < len; i++) {\n const cmp = compareBounds(a[sort[i][0]], b[sort[i][0]]);\n if (cmp !== 0) return sort[i][1] === 'asc' ? cmp : -cmp;\n }\n return 0;\n };\n}\n\nfunction compareBounds(a: Bound, b: Bound): number {\n if (a === b) {\n return 0;\n }\n // Use typeof to guard the Symbol sentinel checks first. This gives V8 a\n // clear type discriminant so the common non-symbol path compiles as a\n // specialised numeric/string fast-path without a Smi deopt when the\n // minValue/maxValue sentinel symbols appear.\n if (typeof a === 'symbol') {\n return a === minValue ? -1 : 1;\n }\n if (typeof b === 'symbol') {\n return b === minValue ? 1 : -1;\n }\n return compareValues(a, b);\n}\n\nfunction generateRows(\n data: BTreeSet<Row>,\n scanStart: RowBound | undefined,\n reverse: boolean | undefined,\n) {\n return reverse\n ? data.valuesFromReversed(scanStart as Row | undefined)\n : data.valuesFrom(scanStart as Row | undefined);\n}\n\nexport function stringify(change: SourceChange) {\n return JSON.stringify(change, (_, v) =>\n typeof v === 'bigint' ? v.toString() : v,\n );\n}\n\n/**\n * N-way merge of pre-sorted Node streams. Each input stream must yield\n * Nodes in `compare` order (and may interleave 'yield' values, which are\n * forwarded as-is). The merged stream yields Nodes in `compare` order.\n *\n * Implemented as a binary min-heap keyed by `compare(entry.row, ...)`, so\n * each emit costs O(log K) (K = number of streams) rather than the O(K)\n * a linear-scan-of-heads merge would. Matters when FlippedJoin chunks a\n * large `multiConstraints` IN-list into hundreds of sub-fetches.\n *\n * If the merged stream is closed early (e.g. downstream `Take` `break`s\n * after hitting its limit, prompting JS to call `.return()` on this\n * generator), the `finally` block propagates `.return()` to each\n * non-exhausted sub-iterator so the underlying sources (SQLite cursors,\n * etc.) can run their cleanup. Without this, mid-merge early-termination\n * leaves cursors open, causing later writes on the same connection to\n * fail with \"database connection is busy executing a query\".\n */\nexport function* mergeSortedStreams(\n streams: readonly Stream<Node | 'yield'>[],\n compare: (a: Node, b: Node) => number,\n): Stream<Node | 'yield'> {\n const iterators: Iterator<Node | 'yield'>[] = streams.map(s =>\n s[Symbol.iterator](),\n );\n // True while iterators[i] hasn't yet returned `done`. The finally\n // block uses this to skip already-exhausted streams when propagating\n // `.return()`.\n const active = Array.from({length: iterators.length}).fill(true) as boolean[];\n\n // Min-heap of entries; `idx` tells us which stream to refill from\n // after the entry's row is emitted.\n type Entry = {row: Node; idx: number};\n const heap: Entry[] = [];\n\n const siftUp = (start: number) => {\n let i = start;\n while (i > 0) {\n const p = (i - 1) >> 1;\n if (compare(heap[i].row, heap[p].row) >= 0) return;\n const t = heap[i];\n heap[i] = heap[p];\n heap[p] = t;\n i = p;\n }\n };\n\n const siftDown = (start: number) => {\n let i = start;\n const n = heap.length;\n while (true) {\n const l = (i << 1) + 1;\n const r = l + 1;\n let smallest = i;\n if (l < n && compare(heap[l].row, heap[smallest].row) < 0) smallest = l;\n if (r < n && compare(heap[r].row, heap[smallest].row) < 0) smallest = r;\n if (smallest === i) return;\n const t = heap[i];\n heap[i] = heap[smallest];\n heap[smallest] = t;\n i = smallest;\n }\n };\n\n // Pull the next Node from iterator `idx`, forwarding any 'yield's.\n // Returns the Node, or `undefined` once the stream is exhausted.\n const pullNext = function* (\n idx: number,\n ): IterableIterator<'yield', Node | undefined, undefined> {\n while (true) {\n const r = iterators[idx].next();\n if (r.done) {\n active[idx] = false;\n return undefined;\n }\n if (r.value === 'yield') {\n yield 'yield';\n continue;\n }\n return r.value;\n }\n };\n\n try {\n // Prime: push the first row of each non-empty stream onto the heap.\n for (let i = 0; i < iterators.length; i++) {\n const row = yield* pullNext(i);\n if (row !== undefined) {\n heap.push({row, idx: i});\n siftUp(heap.length - 1);\n }\n }\n\n while (heap.length > 0) {\n // Root is the global min across all active streams.\n const top = heap[0];\n yield top.row;\n const next = yield* pullNext(top.idx);\n if (next !== undefined) {\n // Refill root in place (top === heap[0]) and sift down. The\n // already-yielded `top.row` value is captured by the yield, so\n // mutating it here doesn't affect what was emitted.\n top.row = next;\n siftDown(0);\n } else {\n // Stream exhausted. Move tail to root and shrink. Pop returns\n // the last entry; if the heap had only one entry it was the\n // root we just yielded, so we just leave the heap empty.\n const last = must(heap.pop());\n if (heap.length > 0) {\n heap[0] = last;\n siftDown(0);\n }\n }\n }\n } finally {\n // Close any iterators that aren't already exhausted so their\n // `finally` blocks (which release cursors / cached statements) run.\n for (let i = 0; i < iterators.length; i++) {\n if (active[i]) {\n iterators[i].return?.();\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"memory-source.js","names":["#tableName","#columns","#primaryKey","#primaryIndexSort","#indexes","#connections","#getPrimaryIndex","#fetch","#disconnect","#getSchema","#fetchMulti","#getOrCreateIndex","#overlay","#writeChange","#pushEpoch"],"sources":["../../../../../zql/src/ivm/memory-source.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {BTreeSet} from '../../../shared/src/btree-set.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport {once, toSorted} from '../../../shared/src/iterables.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n Condition,\n Ordering,\n OrderPart,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue} from '../../../zero-types/src/schema-value.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n type NoSubqueryCondition,\n} from '../builder/filter.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Change} from './change.ts';\nimport {makeAddChange, makeEditChange, makeRemoveChange} from './change.ts';\nimport {\n constraintMatchesPrimaryKey,\n constraintMatchesRow,\n primaryKeyConstraintFromFilters,\n type Constraint,\n} from './constraint.ts';\nimport {\n compareValues,\n makeComparator,\n valuesEqual,\n type Comparator,\n type Node,\n} from './data.ts';\nimport {filterPush} from './filter-push.ts';\nimport {\n skipYields,\n type FetchRequest,\n type Input,\n type MultiConstraint,\n type Output,\n type Start,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {SourceChangeIndex} from './source-change-index.ts';\nimport type {\n Source,\n SourceChange,\n SourceChangeAdd,\n SourceChangeEdit,\n SourceChangeRemove,\n SourceInput,\n} from './source.ts';\nimport {makeSourceChangeAdd, makeSourceChangeRemove} from './source.ts';\nimport {consume, type Stream} from './stream.ts';\n\nexport type Overlay = {\n epoch: number;\n change: SourceChange;\n};\n\nexport type Overlays = {\n add: Row | undefined;\n remove: Row | undefined;\n};\n\ntype Index = {\n comparator: Comparator;\n data: BTreeSet<Row>;\n};\n\nexport type Connection = {\n input: Input;\n output: Output | undefined;\n sort?: Ordering | undefined;\n splitEditKeys: Set<string> | undefined;\n compareRows: Comparator;\n filters:\n | {\n condition: NoSubqueryCondition;\n predicate: (row: Row) => boolean;\n }\n | undefined;\n readonly debug?: DebugDelegate | undefined;\n lastPushedEpoch: number;\n};\n\n/**\n * A `MemorySource` is a source that provides data to the pipeline from an\n * in-memory data source.\n *\n * This data is kept in sorted order as downstream pipelines will always expect\n * the data they receive from `pull` to be in sorted order.\n */\nexport class MemorySource implements Source {\n readonly #tableName: string;\n readonly #columns: Record<string, SchemaValue>;\n readonly #primaryKey: PrimaryKey;\n readonly #primaryIndexSort: Ordering;\n readonly #indexes: Map<string, Index> = new Map();\n readonly #connections: Connection[] = [];\n\n #overlay: Overlay | undefined;\n #pushEpoch = 0;\n\n constructor(\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n primaryIndexData?: BTreeSet<Row>,\n ) {\n this.#tableName = tableName;\n this.#columns = columns;\n this.#primaryKey = primaryKey;\n this.#primaryIndexSort = primaryKey.map(k => [k, 'asc']);\n const comparator = makeBoundComparator(this.#primaryIndexSort);\n this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {\n comparator,\n data: primaryIndexData ?? new BTreeSet<Row>(comparator),\n });\n }\n\n get tableSchema() {\n return {\n name: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n fork() {\n const primaryIndex = this.#getPrimaryIndex();\n return new MemorySource(\n this.#tableName,\n this.#columns,\n this.#primaryKey,\n primaryIndex.data.clone(),\n );\n }\n\n get data(): BTreeSet<Row> {\n return this.#getPrimaryIndex().data;\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n system: 'client',\n relationships: {},\n isHidden: false,\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n ): SourceInput {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n const internalSort = sort ?? this.#primaryIndexSort;\n\n const input: SourceInput = {\n getSchema: () => schema,\n fetch: req => this.#fetch(req, connection),\n setOutput: output => {\n connection.output = output;\n },\n destroy: () => {\n this.#disconnect(input);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n output: undefined,\n sort: internalSort,\n splitEditKeys,\n compareRows: makeComparator(internalSort),\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(internalSort, this.#primaryKey);\n }\n this.#connections.push(connection);\n return input;\n }\n\n #disconnect(input: Input): void {\n const idx = this.#connections.findIndex(c => c.input === input);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n\n // Indexes deliberately hold no reference back to the connections that use\n // them. They used to, to support deleting an index once its last user went\n // away. That deletion is gone (see below) but the `usedBy` set outlived it,\n // so an index kept every `Connection` — and through its `input` and\n // `output`, the whole torn-down pipeline — reachable for as long as the\n // source lived.\n //\n // TODO: We used to delete unused indexes here. But in common cases like\n // navigating into issue detail pages it caused a ton of constantly\n // building and destroying indexes.\n //\n // Perhaps some intelligent LRU or something is needed here but for now,\n // the opposite extreme of keeping all indexes for the lifetime of the\n // page seems better.\n }\n\n #getPrimaryIndex(): Index {\n const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));\n assert(index, 'Primary index not found');\n return index;\n }\n\n #getOrCreateIndex(sort: Ordering): Index {\n const key = JSON.stringify(sort);\n const index = this.#indexes.get(key);\n // Future optimization could use existing index if it's the same just sorted\n // in reverse of needed.\n if (index) {\n return index;\n }\n\n const comparator = makeBoundComparator(sort);\n\n // When creating these synchronously becomes a problem, a few options:\n // 1. Allow users to specify needed indexes up front\n // 2. Create indexes in a different thread asynchronously (this would require\n // modifying the BTree to be able to be passed over structured-clone, or using\n // a different library.)\n // 3. We could even theoretically do (2) on multiple threads and then merge the\n // results!\n const rows = toSorted(this.#getPrimaryIndex().data, comparator);\n const data = BTreeSet.fromSorted(comparator, rows);\n\n const newIndex = {comparator, data};\n this.#indexes.set(key, newIndex);\n return newIndex;\n }\n\n // For unit testing that we correctly clean up indexes.\n getIndexKeys(): string[] {\n return [...this.#indexes.keys()];\n }\n\n *#fetch(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // multiConstraints is handled by driving sub-fetches off the first\n // entry's values and post-filtering matches against any remaining\n // entries. TableSource implements multi-IN natively via SQL `AND` of\n // `IN` clauses; the in-memory path is correct but not specially\n // optimized — it pays the index-seek cost per primary value, then\n // walks each result against the rest.\n if (\n req.multiConstraints &&\n req.multiConstraints.some(mc => mc.length > 0)\n ) {\n yield* this.#fetchMulti(req, conn);\n return;\n }\n const requestedSort = must(conn.sort);\n const {compareRows} = conn;\n // Avoid allocating a new closure when not reversing (the common case).\n const connectionComparator: Comparator = req.reverse\n ? (r1, r2) => compareRows(r2, r1)\n : compareRows;\n\n const reqFilter = req.filter;\n const connFilterCondition = conn.filters?.condition;\n const mergedFilterCondition: NoSubqueryCondition | undefined =\n connFilterCondition && reqFilter\n ? {type: 'and', conditions: [connFilterCondition, reqFilter]}\n : (connFilterCondition ?? reqFilter);\n const mergedFilterPredicate = mergePredicates(\n conn.filters?.predicate,\n reqFilter,\n );\n\n const pkConstraint = primaryKeyConstraintFromFilters(\n mergedFilterCondition,\n this.#primaryKey,\n );\n // The primary key constraint will be more limiting than the constraint\n // so swap out to that if it exists.\n const fetchOrPkConstraint = pkConstraint ?? req.constraint;\n\n // If there is a constraint, we need an index sorted by it first.\n const indexSort: OrderPart[] = [];\n if (fetchOrPkConstraint) {\n for (const key of Object.keys(fetchOrPkConstraint)) {\n indexSort.push([key, 'asc']);\n }\n }\n\n // For the special case of constraining by PK, we don't need to worry about\n // any requested sort since there can only be one result. Otherwise we also\n // need the index sorted by the requested sort.\n if (\n this.#primaryKey.length > 1 ||\n !fetchOrPkConstraint ||\n !constraintMatchesPrimaryKey(fetchOrPkConstraint, this.#primaryKey)\n ) {\n indexSort.push(...requestedSort);\n }\n\n const index = this.#getOrCreateIndex(indexSort);\n const {data, comparator: compare} = index;\n // Avoid allocating a new closure when not reversing (the common case).\n const indexComparator: Comparator = req.reverse\n ? (r1, r2) => compare(r2, r1)\n : compare;\n\n const startAt = req.start?.row;\n\n // If there is a constraint, we want to start our scan at the first row that\n // matches the constraint. But because the next OrderPart can be `desc`,\n // it's not true that {[constraintKey]: constraintValue} is the first\n // matching row. Because in that case, the other fields will all be\n // `undefined`, and in Zero `undefined` is always less than any other value.\n // So if the second OrderPart is descending then `undefined` values will\n // actually be the *last* row. We need a way to stay \"start at the first row\n // with this constraint value\". RowBound with the corresponding compareBound\n // comparator accomplishes this. The right thing is probably to teach the\n // btree library to support this concept.\n let scanStart: RowBound | undefined;\n\n if (fetchOrPkConstraint) {\n scanStart = {};\n for (const [key, dir] of indexSort) {\n if (hasOwn(fetchOrPkConstraint, key)) {\n scanStart[key] = fetchOrPkConstraint[key];\n } else {\n if (req.reverse) {\n scanStart[key] = dir === 'asc' ? maxValue : minValue;\n } else {\n scanStart[key] = dir === 'asc' ? minValue : maxValue;\n }\n }\n }\n } else {\n scanStart = startAt;\n }\n\n const rowsIterable = generateRows(data, scanStart, req.reverse);\n\n // Fast path: no overlay to apply, no `start`, and no connection filters.\n // This is the common shape for join child-lookups, plain scans, and source\n // fetches that sit beneath a downstream `Filter` operator. In that case the\n // whole generator stack (generateWithOverlay → generateWithStart →\n // skipYields → generateWithConstraint) reduces to: walk rows from\n // `scanStart`, wrap each as a Node, and stop once the constraint no longer\n // matches (rows are sorted by the constraint key first, so matches are\n // contiguous). Collapsing the five chained generators into one loop removes\n // a large amount of per-row generator-resume overhead on the hottest path.\n const overlayActive =\n this.#overlay && conn.lastPushedEpoch >= this.#overlay.epoch;\n if (!overlayActive && !req.start && !conn.filters && !req.filter) {\n const {constraint} = req;\n for (const row of rowsIterable) {\n if (constraint && !constraintMatchesRow(constraint, row)) {\n break;\n }\n yield {row, relationships: {}};\n }\n return;\n }\n\n const withOverlay = generateWithOverlay(\n startAt,\n pkConstraint ? once(rowsIterable) : rowsIterable,\n // use `req.constraint` here and not `fetchOrPkConstraint` since `fetchOrPkConstraint` could be the\n // primary key constraint. The primary key constraint comes from filters and is acting as a filter\n // rather than as the fetch constraint.\n req.constraint,\n this.#overlay,\n conn.lastPushedEpoch,\n // Use indexComparator, generateWithOverlayInner has a subtle dependency\n // on this. Since generateWithConstraint is done after\n // generateWithOverlay, the generator consumed by generateWithOverlayInner\n // does not end when the constraint stops matching and so the final\n // check to yield an add overlay if not yet yielded is not reached.\n // However, using the indexComparator the add overlay will be less than\n // the first row that does not match the constraint, and so any\n // not yet yielded add overlay will be yielded when the first row\n // not matching the constraint is reached.\n indexComparator,\n // `startAt` is `req.start.row`, a bound in the connection's sort order,\n // so it must be compared in that order -- not `indexComparator`, which\n // with a constraint leads with the constraint keys. The two agree only\n // when the overlay row and `startAt` share those key values; they do not\n // for `#fetchMulti`'s per-value sub-fetches, which pin one primary key\n // while still carrying the caller's connection-sort `start`. Comparing\n // there by the constraint key dropped the in-flight overlay and served\n // pre-push data. Same distinction #4926 drew for `generateWithStart`.\n connectionComparator,\n mergedFilterPredicate,\n );\n\n const withConstraint = generateWithConstraint(\n skipYields(\n generateWithStart(withOverlay, req.start, connectionComparator),\n ),\n // we use `req.constraint` and not `fetchOrPkConstraint` here because we need to\n // AND the constraint with what could have been the primary key constraint\n req.constraint,\n );\n\n yield* mergedFilterPredicate\n ? generateWithFilter(withConstraint, mergedFilterPredicate)\n : withConstraint;\n }\n\n *#fetchMulti(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // Caller (`#fetch`) guards entry on `req.multiConstraints.some(mc =>\n // mc.length > 0)`, so `multis` is guaranteed non-empty after the\n // empty-entry filter. Per the MultiConstraint contract (operator.ts),\n // entries are non-empty, share key shape, are unique, and are\n // key-compatible with `req.constraint`.\n const multis = must(req.multiConstraints).filter(mc => mc.length > 0);\n const primary = multis[0];\n const rest = multis.slice(1);\n const baseConstraint = req.constraint;\n\n // Drive sub-fetches off `primary`. Within each sub-fetch the source\n // can use its index for `primary`; we then post-filter rows by\n // checking they also match every entry in `rest`. Per the\n // MultiConstraint contract (see operator.ts), entries are unique and\n // key-compatible with `baseConstraint`, so we don't dedupe or check\n // compatibility here.\n const subStreams: Stream<Node | 'yield'>[] = primary.map(c => {\n const merged: Constraint = baseConstraint ? {...baseConstraint, ...c} : c;\n return this.#fetch(\n {...req, constraint: merged, multiConstraints: undefined},\n conn,\n );\n });\n const merged = mergeSortedStreams(\n subStreams,\n req.reverse\n ? (a, b) => conn.compareRows(b.row, a.row)\n : (a, b) => conn.compareRows(a.row, b.row),\n );\n\n if (rest.length === 0) {\n yield* merged;\n return;\n }\n\n for (const node of merged) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n let matchesAll = true;\n for (const mc of rest) {\n let any = false;\n for (const c of mc) {\n if (constraintMatchesRow(c, node.row)) {\n any = true;\n break;\n }\n }\n if (!any) {\n matchesAll = false;\n break;\n }\n }\n if (matchesAll) yield node;\n }\n }\n\n /**\n * Pushes an add for each of `rows`.\n *\n * When the source holds no rows and nothing is connected to it (loading the\n * replica at startup) there is no output to notify and no secondary index to\n * keep in step, so the primary index is built bottom-up in O(N) (plus a sort\n * when `rows` are not already in primary key order) rather than doing one\n * tree insert, and one trip through the push machinery, per row.\n *\n * `rows` may be sorted in place. Rows must have distinct primary keys.\n */\n pushAdds(rows: Row[]): void {\n const index = this.#getPrimaryIndex();\n const canBulkLoad =\n this.#connections.length === 0 &&\n this.#indexes.size === 1 &&\n index.data.size === 0;\n if (!canBulkLoad) {\n for (const row of rows) {\n consume(this.push(makeSourceChangeAdd(row)));\n }\n return;\n }\n const {comparator} = index;\n // The check is not redundant with the sort. V8's TimSort is O(N) on sorted\n // input but Hermes' sort is not adaptive: always sorting doubled the load\n // time of an already ordered 180k row replica on Android (343ms -> 672ms).\n if (!isAscending(rows, comparator)) {\n rows.sort(comparator);\n }\n index.data = BTreeSet.fromSorted(comparator, rows);\n }\n\n *push(change: SourceChange): Stream<'yield'> {\n for (const result of this.genPush(change)) {\n if (result === 'yield') {\n yield result;\n }\n }\n }\n\n genPush(change: SourceChange) {\n const primaryIndex = this.#getPrimaryIndex();\n const {data} = primaryIndex;\n const exists = (row: Row) => data.has(row);\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n return genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n for (const {data} of this.#indexes.values()) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD: {\n const added = data.add(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n added,\n 'MemorySource: add must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.REMOVE: {\n const removed = data.delete(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: remove must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.EDIT: {\n // TODO: We could see if the PK (form the index tree's perspective)\n // changed and if not we could use set.\n // We cannot just do `set` with the new value since the `oldRow` might\n // not map to the same entry as the new `row` in the index btree.\n const removed = data.delete(change[SourceChangeIndex.OLD_ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: edit remove must succeed since row existence was already checked',\n );\n data.add(change[SourceChangeIndex.ROW]);\n break;\n }\n default:\n unreachable(change);\n }\n }\n }\n}\n\nfunction mergePredicates(\n connPredicate: ((row: Row) => boolean) | undefined,\n reqFilter: NoSubqueryCondition | undefined,\n): ((row: Row) => boolean) | undefined {\n if (!reqFilter) {\n return connPredicate;\n }\n const reqPredicate = createPredicate(reqFilter);\n if (!connPredicate) {\n return reqPredicate;\n }\n return row => connPredicate(row) && reqPredicate(row);\n}\n\nfunction* generateWithConstraint(\n it: Stream<Node>,\n constraint: Constraint | undefined,\n) {\n for (const node of it) {\n if (constraint && !constraintMatchesRow(constraint, node.row)) {\n break;\n }\n yield node;\n }\n}\n\nfunction* generateWithFilter(it: Stream<Node>, filter: (row: Row) => boolean) {\n for (const node of it) {\n if (filter(node.row)) {\n yield node;\n }\n }\n}\n\nexport function* genPushAndWriteWithSplitEdit(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n getNextEpoch: () => number,\n) {\n let shouldSplitEdit = false;\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT) {\n for (const {splitEditKeys} of connections) {\n if (splitEditKeys) {\n for (const key of splitEditKeys) {\n if (\n !valuesEqual(\n change[SourceChangeIndex.ROW][key],\n change[SourceChangeIndex.OLD_ROW][key],\n )\n ) {\n shouldSplitEdit = true;\n break;\n }\n }\n }\n }\n }\n\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT && shouldSplitEdit) {\n yield* genPushAndWrite(\n connections,\n makeSourceChangeRemove(change[SourceChangeIndex.OLD_ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n yield* genPushAndWrite(\n connections,\n makeSourceChangeAdd(change[SourceChangeIndex.ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n } else {\n yield* genPushAndWrite(\n connections,\n change,\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n }\n}\n\nfunction* genPushAndWrite(\n connections: readonly Connection[],\n change: SourceChangeAdd | SourceChangeRemove | SourceChangeEdit,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n pushEpoch: number,\n) {\n for (const x of genPush(connections, change, exists, setOverlay, pushEpoch)) {\n yield x;\n }\n writeChange(change);\n}\n\nfunction* genPush(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => void,\n pushEpoch: number,\n) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n assert(\n !exists(change[SourceChangeIndex.ROW]),\n () => `Row already exists ${stringify(change)}`,\n );\n break;\n case ChangeType.REMOVE:\n assert(\n exists(change[SourceChangeIndex.ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n case ChangeType.EDIT:\n assert(\n exists(change[SourceChangeIndex.OLD_ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n default:\n unreachable(change);\n }\n\n setOverlay({epoch: pushEpoch, change});\n try {\n for (const conn of connections) {\n const {output, filters, input} = conn;\n if (output) {\n conn.lastPushedEpoch = pushEpoch;\n const outputChange: Change =\n change[SourceChangeIndex.TYPE] === ChangeType.EDIT\n ? makeEditChange(\n {row: change[SourceChangeIndex.ROW], relationships: {}},\n {row: change[SourceChangeIndex.OLD_ROW], relationships: {}},\n )\n : change[SourceChangeIndex.TYPE] === ChangeType.ADD\n ? makeAddChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n })\n : makeRemoveChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n });\n yield* filterPush(outputChange, output, input, filters?.predicate);\n yield undefined;\n }\n }\n\n for (const conn of connections) {\n const {output, input} = conn;\n if (output?.reconcile) {\n yield* output.reconcile(input);\n yield undefined;\n }\n }\n } finally {\n setOverlay(undefined);\n }\n}\n\nexport function* generateWithStart(\n nodes: Iterable<Node | 'yield'>,\n start: Start | undefined,\n compare: (r1: Row, r2: Row) => number,\n): Stream<Node | 'yield'> {\n if (!start) {\n yield* nodes;\n return;\n }\n let started = false;\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!started) {\n if (start.basis === 'at') {\n if (compare(node.row, start.row) >= 0) {\n started = true;\n }\n } else if (start.basis === 'after') {\n if (compare(node.row, start.row) > 0) {\n started = true;\n }\n }\n }\n if (started) {\n yield node;\n }\n }\n}\n\n/**\n * Takes an iterator and overlay.\n * Splices the overlay into the iterator at the correct position.\n *\n * @param startAt - if there is a lower bound to the stream. If the lower bound of the stream\n * is above the overlay, the overlay will be skipped.\n * @param rows - the stream into which the overlay should be spliced\n * @param constraint - constraint that was applied to the rowIterator and should\n * also be applied to the overlay.\n * @param overlay - the overlay values to splice in\n * @param compare - the comparator to use to find the position for the overlay.\n * Must order rows the same way `rows` is ordered. For `MemorySource` that is\n * *index* order, which leads with the constraint keys.\n * @param startAtCompare - the comparator for `startAt`, which is a bound in the\n * *connection's* sort order. Only the same as `compare` when the stream's order\n * happens to be the connection's -- true for `TableSource` (SQL does the\n * ordering) but not for a constrained `MemorySource` fetch. Conflating the two\n * is what #4926 fixed for `generateWithStart`; this parameter is the same\n * distinction for the overlay's own `startAt` pruning.\n */\nexport function generateWithOverlay(\n startAt: Row | undefined,\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n compare: Comparator,\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[],\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n const overlays = computeOverlays(\n startAt,\n constraint,\n overlayToApply,\n startAtCompare,\n filterPredicate,\n multiConstraints,\n );\n return generateWithOverlayInner(rows, overlays, compare);\n}\n\nfunction computeOverlays(\n startAt: Row | undefined,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n // Only ever used for `overlaysForStartAt`, so this is the *connection*-order\n // comparator, not the one used to splice the overlay into the row stream.\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[],\n): Overlays {\n let overlays: Overlays = {\n add: undefined,\n remove: undefined,\n };\n switch (overlay?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlay.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: overlay.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n\n if (startAt) {\n overlays = overlaysForStartAt(overlays, startAt, startAtCompare);\n }\n\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n\n return overlays;\n}\n\nfunction applyMultiConstraintsToOverlays(\n overlays: Overlays,\n multiConstraints: readonly MultiConstraint[] | undefined,\n): Overlays {\n if (!multiConstraints) return overlays;\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n overlays = overlaysForMultiConstraint(overlays, mc);\n }\n }\n return overlays;\n}\n\nexport {overlaysForMultiConstraint as overlaysForMultiConstraintForTest};\n\nfunction overlaysForMultiConstraint(\n {add, remove}: Overlays,\n multiConstraint: MultiConstraint,\n): Overlays {\n const matchesAny = (row: Row | undefined) => {\n if (row === undefined) return false;\n for (const c of multiConstraint) {\n if (constraintMatchesRow(c, row)) return true;\n }\n return false;\n };\n return {\n add: matchesAny(add) ? add : undefined,\n remove: matchesAny(remove) ? remove : undefined,\n };\n}\n\nexport {overlaysForStartAt as overlaysForStartAtForTest};\n\nfunction overlaysForStartAt(\n {add, remove}: Overlays,\n startAt: Row,\n compare: Comparator,\n): Overlays {\n const undefinedIfBeforeStartAt = (row: Row | undefined) =>\n row === undefined || compare(row, startAt) < 0 ? undefined : row;\n return {\n add: undefinedIfBeforeStartAt(add),\n remove: undefinedIfBeforeStartAt(remove),\n };\n}\n\nexport {overlaysForConstraint as overlaysForConstraintForTest};\n\nfunction overlaysForConstraint(\n {add, remove}: Overlays,\n constraint: Constraint,\n): Overlays {\n const undefinedIfDoesntMatchConstraint = (row: Row | undefined) =>\n row === undefined || !constraintMatchesRow(constraint, row)\n ? undefined\n : row;\n\n return {\n add: undefinedIfDoesntMatchConstraint(add),\n remove: undefinedIfDoesntMatchConstraint(remove),\n };\n}\n\nfunction overlaysForFilterPredicate(\n {add, remove}: Overlays,\n filterPredicate: (row: Row) => boolean | undefined,\n): Overlays {\n const undefinedIfDoesntMatchFilter = (row: Row | undefined) =>\n row === undefined || !filterPredicate(row) ? undefined : row;\n\n return {\n add: undefinedIfDoesntMatchFilter(add),\n remove: undefinedIfDoesntMatchFilter(remove),\n };\n}\n\nexport function* generateWithOverlayInner(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n compare: (r1: Row, r2: Row) => number,\n) {\n let addOverlayYielded = false;\n let removeOverlaySkipped = false;\n for (const row of rowIterator) {\n if (!addOverlayYielded && overlays.add) {\n const cmp = compare(overlays.add, row);\n if (cmp < 0) {\n addOverlayYielded = true;\n yield {row: overlays.add, relationships: {}};\n }\n }\n\n if (!removeOverlaySkipped && overlays.remove) {\n const cmp = compare(overlays.remove, row);\n if (cmp === 0) {\n removeOverlaySkipped = true;\n continue;\n }\n }\n yield {row, relationships: {}};\n }\n\n if (!addOverlayYielded && overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n}\n\n/**\n * Like {@link generateWithOverlay} but for unordered streams.\n * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly\n * at the start, and suppresses add/new-edit rows inline by PK match.\n */\nexport function generateWithOverlayUnordered(\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n primaryKey: PrimaryKey,\n filterPredicate?: (row: Row) => boolean,\n multiConstraints?: readonly MultiConstraint[],\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n let overlays: Overlays = {add: undefined, remove: undefined};\n switch (overlayToApply?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlayToApply.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: overlayToApply.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n return generateWithOverlayInnerUnordered(rows, overlays, primaryKey);\n}\n\nexport function* generateWithOverlayInnerUnordered(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n primaryKey: PrimaryKey,\n) {\n // Eager inject: yield the add overlay at the start (row not yet in storage)\n if (overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n // Stream with inline suppress: skip the remove overlay (row still in storage)\n let removeSkipped = false;\n for (const row of rowIterator) {\n if (\n !removeSkipped &&\n overlays.remove &&\n rowMatchesPK(overlays.remove, row, primaryKey)\n ) {\n removeSkipped = true;\n continue;\n }\n yield {row, relationships: {}};\n }\n}\n\nfunction rowMatchesPK(a: Row, b: Row, primaryKey: PrimaryKey): boolean {\n for (const key of primaryKey) {\n if (!valuesEqual(a[key], b[key])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A location to begin scanning an index from. Can either be a specific value\n * or the min or max possible value for the type. This is used to start a scan\n * at the beginning of the rows matching a constraint.\n */\nexport type Bound = Value | MinValue | MaxValue;\nexport type RowBound = Record<string, Bound>;\nexport const minValue = Symbol('min-value');\ntype MinValue = typeof minValue;\nexport const maxValue = Symbol('max-value');\ntype MaxValue = typeof maxValue;\n\nfunction isAscending(rows: Row[], comparator: Comparator): boolean {\n for (let i = 1; i < rows.length; i++) {\n if (comparator(rows[i - 1], rows[i]) > 0) {\n return false;\n }\n }\n return true;\n}\n\nexport function makeBoundComparator(sort: Ordering): Comparator {\n // Pre-extract the first two keys/directions to avoid per-call array access.\n // All paths share one function literal (single SFI) so the BTree comparator call site\n // stays monomorphic across indexes with different sort orderings, preventing V8 IC deopt.\n // Even a 2-SFI split (e.g. separate len=1 path) creates a polymorphic IC that\n // measurably regresses performance, so we keep a single return body.\n const len = sort.length;\n const k0 = sort[0][0];\n const a0 = sort[0][1] === 'asc';\n const k1 = len > 1 ? sort[1][0] : '';\n const a1 = len > 1 ? sort[1][1] === 'asc' : true;\n\n return (a: RowBound, b: RowBound) => {\n const c0 = a0 ? compareBounds(a[k0], b[k0]) : compareBounds(b[k0], a[k0]);\n if (len === 1 || c0 !== 0) return c0;\n const c1 = a1 ? compareBounds(a[k1], b[k1]) : compareBounds(b[k1], a[k1]);\n if (len === 2 || c1 !== 0) return c1;\n // Hot! Do not use destructuring\n for (let i = 2; i < len; i++) {\n const cmp = compareBounds(a[sort[i][0]], b[sort[i][0]]);\n if (cmp !== 0) return sort[i][1] === 'asc' ? cmp : -cmp;\n }\n return 0;\n };\n}\n\nfunction compareBounds(a: Bound, b: Bound): number {\n if (a === b) {\n return 0;\n }\n // Use typeof to guard the Symbol sentinel checks first. This gives V8 a\n // clear type discriminant so the common non-symbol path compiles as a\n // specialised numeric/string fast-path without a Smi deopt when the\n // minValue/maxValue sentinel symbols appear.\n if (typeof a === 'symbol') {\n return a === minValue ? -1 : 1;\n }\n if (typeof b === 'symbol') {\n return b === minValue ? 1 : -1;\n }\n return compareValues(a, b);\n}\n\nexport function generateRows(\n data: BTreeSet<Row>,\n scanStart: RowBound | undefined,\n reverse: boolean | undefined,\n) {\n return reverse\n ? data.valuesFromReversed(scanStart as Row | undefined)\n : data.valuesFrom(scanStart as Row | undefined);\n}\n\nexport function stringify(change: SourceChange) {\n return JSON.stringify(change, (_, v) =>\n typeof v === 'bigint' ? v.toString() : v,\n );\n}\n\n/**\n * N-way merge of pre-sorted Node streams. Each input stream must yield\n * Nodes in `compare` order (and may interleave 'yield' values, which are\n * forwarded as-is). The merged stream yields Nodes in `compare` order.\n *\n * Implemented as a binary min-heap keyed by `compare(entry.row, ...)`, so\n * each emit costs O(log K) (K = number of streams) rather than the O(K)\n * a linear-scan-of-heads merge would. Matters when FlippedJoin chunks a\n * large `multiConstraints` IN-list into hundreds of sub-fetches.\n *\n * If the merged stream is closed early (e.g. downstream `Take` `break`s\n * after hitting its limit, prompting JS to call `.return()` on this\n * generator), the `finally` block propagates `.return()` to each\n * non-exhausted sub-iterator so the underlying sources (SQLite cursors,\n * etc.) can run their cleanup. Without this, mid-merge early-termination\n * leaves cursors open, causing later writes on the same connection to\n * fail with \"database connection is busy executing a query\".\n */\nexport function* mergeSortedStreams(\n streams: readonly Stream<Node | 'yield'>[],\n compare: (a: Node, b: Node) => number,\n): Stream<Node | 'yield'> {\n const iterators: Iterator<Node | 'yield'>[] = streams.map(s =>\n s[Symbol.iterator](),\n );\n // True while iterators[i] hasn't yet returned `done`. The finally\n // block uses this to skip already-exhausted streams when propagating\n // `.return()`.\n const active = Array.from({length: iterators.length}).fill(true) as boolean[];\n\n // Min-heap of entries; `idx` tells us which stream to refill from\n // after the entry's row is emitted.\n type Entry = {row: Node; idx: number};\n const heap: Entry[] = [];\n\n const siftUp = (start: number) => {\n let i = start;\n while (i > 0) {\n const p = (i - 1) >> 1;\n if (compare(heap[i].row, heap[p].row) >= 0) return;\n const t = heap[i];\n heap[i] = heap[p];\n heap[p] = t;\n i = p;\n }\n };\n\n const siftDown = (start: number) => {\n let i = start;\n const n = heap.length;\n while (true) {\n const l = (i << 1) + 1;\n const r = l + 1;\n let smallest = i;\n if (l < n && compare(heap[l].row, heap[smallest].row) < 0) smallest = l;\n if (r < n && compare(heap[r].row, heap[smallest].row) < 0) smallest = r;\n if (smallest === i) return;\n const t = heap[i];\n heap[i] = heap[smallest];\n heap[smallest] = t;\n i = smallest;\n }\n };\n\n // Pull the next Node from iterator `idx`, forwarding any 'yield's.\n // Returns the Node, or `undefined` once the stream is exhausted.\n const pullNext = function* (\n idx: number,\n ): IterableIterator<'yield', Node | undefined, undefined> {\n while (true) {\n const r = iterators[idx].next();\n if (r.done) {\n active[idx] = false;\n return undefined;\n }\n if (r.value === 'yield') {\n yield 'yield';\n continue;\n }\n return r.value;\n }\n };\n\n try {\n // Prime: push the first row of each non-empty stream onto the heap.\n for (let i = 0; i < iterators.length; i++) {\n const row = yield* pullNext(i);\n if (row !== undefined) {\n heap.push({row, idx: i});\n siftUp(heap.length - 1);\n }\n }\n\n while (heap.length > 0) {\n // Root is the global min across all active streams.\n const top = heap[0];\n yield top.row;\n const next = yield* pullNext(top.idx);\n if (next !== undefined) {\n // Refill root in place (top === heap[0]) and sift down. The\n // already-yielded `top.row` value is captured by the yield, so\n // mutating it here doesn't affect what was emitted.\n top.row = next;\n siftDown(0);\n } else {\n // Stream exhausted. Move tail to root and shrink. Pop returns\n // the last entry; if the heap had only one entry it was the\n // root we just yielded, so we just leave the heap empty.\n const last = must(heap.pop());\n if (heap.length > 0) {\n heap[0] = last;\n siftDown(0);\n }\n }\n }\n } finally {\n // Close any iterators that aren't already exhausted so their\n // `finally` blocks (which release cursors / cached statements) run.\n for (let i = 0; i < iterators.length; i++) {\n if (active[i]) {\n iterators[i].return?.();\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -4,6 +4,7 @@ import type { ConnectionCostModel } from './planner-connection.ts';
4
4
  import type { PlannerConstraint } from './planner-constraint.ts';
5
5
  import type { PlanDebugger } from './planner-debug.ts';
6
6
  import { PlannerGraph } from './planner-graph.ts';
7
+ import { type PlanWarning, type PlanWarningThresholds } from './planner-warnings.ts';
7
8
  export type Plans = {
8
9
  plan: PlannerGraph;
9
10
  subPlans: {
@@ -15,11 +16,21 @@ export type Plans = {
15
16
  * a child (see `pushDownCorrelatedPredicates`).
16
17
  */
17
18
  export declare function buildPlanGraph(ast: AST, model: ConnectionCostModel, isRoot: boolean, baseConstraints?: PlannerConstraint, pushed?: ReadonlySet<SimpleCondition>): Plans;
19
+ /**
20
+ * Where the planner reports the {@link PlanWarning}s for the plan it chose.
21
+ */
22
+ export type PlanWarningSink = {
23
+ readonly thresholds: PlanWarningThresholds;
24
+ /** Called once per planned query, and only if there are warnings. */
25
+ report(warnings: readonly PlanWarning[]): void;
26
+ };
18
27
  /**
19
28
  * @param pushed The conditions that correlated predicate pushdown copied into
20
29
  * a child (see `pushDownCorrelatedPredicates`). The planner counts each one
21
30
  * only where it removes rows.
31
+ * @param planWarnings Receives warnings about the chosen plan. Collecting them
32
+ * costs extra cost model calls, so this is best left out when unused.
22
33
  */
23
- export declare function planQuery(ast: AST, model: ConnectionCostModel, planDebugger?: PlanDebugger, lc?: LogContext, pushed?: ReadonlySet<SimpleCondition>): AST;
34
+ export declare function planQuery(ast: AST, model: ConnectionCostModel, planDebugger?: PlanDebugger, lc?: LogContext, pushed?: ReadonlySet<SimpleCondition>, planWarnings?: PlanWarningSink): AST;
24
35
  export declare function applyPlansToAST(ast: AST, plans: Plans): AST;
25
36
  //# sourceMappingURL=planner-builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"planner-builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EACV,GAAG,EAKH,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAIrD,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAqBhD,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;KAAC,CAAC;CAClC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,mBAAmB,EAC1B,MAAM,EAAE,OAAO,EACf,eAAe,CAAC,EAAE,iBAAiB,EACnC,MAAM,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,GACpC,KAAK,CAsDP;AAgQD;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,mBAAmB,EAC1B,YAAY,CAAC,EAAE,YAAY,EAC3B,EAAE,CAAC,EAAE,UAAU,EACf,MAAM,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,GACpC,GAAG,CAIL;AAqCD,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,GAAG,CAyB3D"}
1
+ {"version":3,"file":"planner-builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EACV,GAAG,EAKH,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAIrD,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAIhD,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAkB/B,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;KAAC,CAAC;CAClC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,mBAAmB,EAC1B,MAAM,EAAE,OAAO,EACf,eAAe,CAAC,EAAE,iBAAiB,EACnC,MAAM,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,GACpC,KAAK,CAsDP;AAgQD;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,qEAAqE;IACrE,MAAM,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI,CAAC;CAChD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,mBAAmB,EAC1B,YAAY,CAAC,EAAE,YAAY,EAC3B,EAAE,CAAC,EAAE,UAAU,EACf,MAAM,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,EACrC,YAAY,CAAC,EAAE,eAAe,GAC7B,GAAG,CAcL;AAqCD,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,GAAG,CAyB3D"}
@@ -8,6 +8,7 @@ import { PlannerFilter } from "./planner-filter.js";
8
8
  import { PlannerGraph } from "./planner-graph.js";
9
9
  import { PlannerJoin } from "./planner-join.js";
10
10
  import { PlannerTerminus } from "./planner-terminus.js";
11
+ import { collectPlanWarnings } from "./planner-warnings.js";
11
12
  //#region ../zql/src/planner/planner-builder.ts
12
13
  function wireOutput(from, to) {
13
14
  switch (from.kind) {
@@ -136,10 +137,16 @@ function planRecursively(plans, planDebugger, lc) {
136
137
  * @param pushed The conditions that correlated predicate pushdown copied into
137
138
  * a child (see `pushDownCorrelatedPredicates`). The planner counts each one
138
139
  * only where it removes rows.
140
+ * @param planWarnings Receives warnings about the chosen plan. Collecting them
141
+ * costs extra cost model calls, so this is best left out when unused.
139
142
  */
140
- function planQuery(ast, model, planDebugger, lc, pushed) {
143
+ function planQuery(ast, model, planDebugger, lc, pushed, planWarnings) {
141
144
  const plans = buildPlanGraph(ast, model, true, void 0, pushed);
142
145
  planRecursively(plans, planDebugger, lc);
146
+ if (planWarnings) {
147
+ const warnings = collectPlanWarnings(ast.table, plans, planWarnings.thresholds);
148
+ if (warnings.length > 0) planWarnings.report(warnings);
149
+ }
143
150
  return applyPlansToAST(ast, plans);
144
151
  }
145
152
  function applyToCondition(condition, flippedIds) {
@@ -1 +1 @@
1
- {"version":3,"file":"planner-builder.js","names":[],"sources":["../../../../../zql/src/planner/planner-builder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n Conjunction,\n CorrelatedSubqueryCondition,\n Disjunction,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport {planIdSymbol} from '../../../zero-protocol/src/ast.ts';\nimport {transformFilters} from '../builder/filter.ts';\nimport type {ConnectionCostModel} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {PlannerFanIn} from './planner-fan-in.ts';\nimport {PlannerFanOut} from './planner-fan-out.ts';\nimport {PlannerFilter} from './planner-filter.ts';\nimport {PlannerGraph} from './planner-graph.ts';\nimport {PlannerJoin} from './planner-join.ts';\nimport type {PlannerNode} from './planner-node.ts';\nimport {PlannerTerminus} from './planner-terminus.ts';\n\nfunction wireOutput(from: PlannerNode, to: PlannerNode): void {\n switch (from.kind) {\n case 'connection':\n case 'join':\n case 'fan-in':\n case 'filter':\n from.setOutput(to);\n break;\n case 'fan-out':\n from.addOutput(to);\n break;\n case 'terminus':\n assert(false, 'Terminus nodes cannot have outputs');\n }\n}\n\nexport type Plans = {\n plan: PlannerGraph;\n subPlans: {[key: string]: Plans};\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`).\n */\nexport function buildPlanGraph(\n ast: AST,\n model: ConnectionCostModel,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n pushed?: ReadonlySet<SimpleCondition>,\n): Plans {\n const graph = new PlannerGraph();\n let nextPlanId = 0;\n\n const source = graph.addSource(ast.table, model);\n const connection = source.connect(\n ast.orderBy ?? [],\n ast.where,\n isRoot,\n baseConstraints,\n ast.limit,\n pushed,\n );\n graph.connections.push(connection);\n\n let end: PlannerNode = connection;\n if (ast.where) {\n end = processCondition(\n ast.where,\n end,\n graph,\n model,\n ast.table,\n () => nextPlanId++,\n pushed,\n );\n }\n\n const terminus = new PlannerTerminus(end);\n wireOutput(end, terminus);\n graph.setTerminus(terminus);\n\n const subPlans: {[key: string]: Plans} = {};\n if (ast.related) {\n for (const csq of ast.related) {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const childConstraints = extractConstraint(\n csq.correlation.childField,\n csq.subquery.table,\n );\n subPlans[alias] = buildPlanGraph(\n csq.subquery,\n model,\n true,\n childConstraints,\n pushed,\n );\n }\n }\n\n return {plan: graph, subPlans};\n}\n\nfunction processCondition(\n condition: Condition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n switch (condition.type) {\n case 'simple':\n return input;\n case 'and':\n return processAnd(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'or':\n return processOr(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'correlatedSubquery':\n return processCorrelatedSubquery(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n}\n\nfunction processAnd(\n condition: Conjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n let end = input;\n for (const subCondition of condition.conditions) {\n end = processCondition(\n subCondition,\n end,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n return end;\n}\n\nfunction processOr(\n condition: Disjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n // Skip building fan structure when no branch contains a CSQ. The runtime\n // collapses such ORs to a single Filter node, so the planner has nothing\n // to choose between.\n const hasAnySubquery = condition.conditions.some(\n c => c.type === 'correlatedSubquery' || hasCorrelatedSubquery(c),\n );\n if (!hasAnySubquery) {\n return input;\n }\n\n const fanOut = new PlannerFanOut(input);\n graph.fanOuts.push(fanOut);\n wireOutput(input, fanOut);\n\n const branches: Exclude<PlannerNode, PlannerTerminus>[] = [];\n for (const subCondition of condition.conditions) {\n let branch: Exclude<PlannerNode, PlannerTerminus>;\n if (\n subCondition.type === 'correlatedSubquery' ||\n hasCorrelatedSubquery(subCondition)\n ) {\n branch = processCondition(\n subCondition,\n fanOut,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n } else {\n // Simple OR branch: wrap in a PlannerFilter so its filter can be\n // registered at the receiving connection on a per-branch basis when\n // the FanIn is in UFI mode.\n const transformed = transformFilters(subCondition).filters;\n const filter = new PlannerFilter(fanOut, transformed);\n graph.filters.push(filter);\n wireOutput(fanOut, filter);\n branch = filter;\n }\n branches.push(branch);\n fanOut.addOutput(branch);\n }\n\n const fanIn = new PlannerFanIn(branches);\n graph.fanIns.push(fanIn);\n for (const branch of branches) {\n wireOutput(branch, fanIn);\n }\n\n return fanIn;\n}\n\nfunction processCorrelatedSubquery(\n condition: CorrelatedSubqueryCondition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n const {related} = condition;\n const childTable = related.subquery.table;\n\n const childSource = graph.hasSource(childTable)\n ? graph.getSource(childTable)\n : graph.addSource(childTable, model);\n\n const childConnection = childSource.connect(\n related.subquery.orderBy ?? [],\n related.subquery.where,\n false,\n undefined, // no base constraints for EXISTS/NOT EXISTS\n condition.op === 'EXISTS' ? 1 : undefined,\n pushed,\n );\n graph.connections.push(childConnection);\n\n let childEnd: PlannerNode = childConnection;\n if (related.subquery.where) {\n childEnd = processCondition(\n related.subquery.where,\n childEnd,\n graph,\n model,\n childTable,\n getPlanId,\n pushed,\n );\n }\n\n const parentConstraint = extractConstraint(\n related.correlation.parentField,\n parentTable,\n );\n const childConstraint = extractConstraint(\n related.correlation.childField,\n childTable,\n );\n\n const planId = getPlanId();\n condition[planIdSymbol] = planId;\n\n // Determine flippability and initial type based on flip flag and operator\n const isNotExists = condition.op === 'NOT EXISTS';\n const manualFlip = condition.flip;\n\n let flippable: boolean;\n let initialType: 'semi' | 'flipped';\n\n if (isNotExists) {\n // NOT EXISTS joins can never be flipped\n flippable = false;\n initialType = 'semi';\n } else if (manualFlip === true) {\n // User explicitly requested flip=true: start flipped, don't allow planner to change\n flippable = false;\n initialType = 'flipped';\n } else if (manualFlip === false) {\n // User explicitly requested flip=false: start semi, don't allow planner to change\n flippable = false;\n initialType = 'semi';\n } else {\n // flip is undefined: planner can decide\n flippable = true;\n initialType = 'semi';\n }\n\n const join = new PlannerJoin(\n input,\n childEnd,\n parentConstraint,\n childConstraint,\n flippable,\n planId,\n initialType,\n );\n graph.joins.push(join);\n childConnection.setParentJoin(join);\n\n wireOutput(input, join);\n wireOutput(childEnd, join);\n\n return join;\n}\n\nfunction hasCorrelatedSubquery(condition: Condition): boolean {\n if (condition.type === 'correlatedSubquery') {\n return true;\n }\n if (condition.type === 'and' || condition.type === 'or') {\n return condition.conditions.some(hasCorrelatedSubquery);\n }\n // simple conditions don't contain correlated subqueries\n return false;\n}\n\nfunction extractConstraint(\n fields: readonly string[],\n _tableName: string,\n): PlannerConstraint {\n return Object.fromEntries(fields.map(field => [field, undefined]));\n}\n\nfunction planRecursively(\n plans: Plans,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n): void {\n for (const subPlan of Object.values(plans.subPlans)) {\n planRecursively(subPlan, planDebugger, lc);\n }\n plans.plan.plan(planDebugger, lc);\n}\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`). The planner counts each one\n * only where it removes rows.\n */\nexport function planQuery(\n ast: AST,\n model: ConnectionCostModel,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n pushed?: ReadonlySet<SimpleCondition>,\n): AST {\n const plans = buildPlanGraph(ast, model, true, undefined, pushed);\n planRecursively(plans, planDebugger, lc);\n return applyPlansToAST(ast, plans);\n}\n\nfunction applyToCondition(\n condition: Condition,\n flippedIds: Set<number>,\n): Condition {\n if (condition.type === 'simple') {\n return condition;\n }\n\n if (condition.type === 'correlatedSubquery') {\n const planId = (condition as unknown as Record<symbol, number>)[\n planIdSymbol\n ];\n const shouldFlip = planId !== undefined && flippedIds.has(planId);\n\n return {\n ...condition,\n flip: shouldFlip,\n related: {\n ...condition.related,\n subquery: {\n ...condition.related.subquery,\n where: condition.related.subquery.where\n ? applyToCondition(condition.related.subquery.where, flippedIds)\n : undefined,\n },\n },\n };\n }\n\n return {\n ...condition,\n conditions: condition.conditions.map(c => applyToCondition(c, flippedIds)),\n };\n}\n\nexport function applyPlansToAST(ast: AST, plans: Plans): AST {\n const flippedIds = new Set<number>();\n for (const join of plans.plan.joins) {\n if (join.type === 'flipped' && join.planId !== undefined) {\n flippedIds.add(join.planId);\n }\n }\n\n return {\n ...ast,\n where: ast.where ? applyToCondition(ast.where, flippedIds) : undefined,\n related: ast.related?.map(csq => {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const subPlan = plans.subPlans[alias];\n return {\n ...csq,\n subquery: subPlan\n ? applyPlansToAST(csq.subquery, subPlan)\n : csq.subquery,\n };\n }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"planner-builder.js","names":[],"sources":["../../../../../zql/src/planner/planner-builder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n Conjunction,\n CorrelatedSubqueryCondition,\n Disjunction,\n SimpleCondition,\n} from '../../../zero-protocol/src/ast.ts';\nimport {planIdSymbol} from '../../../zero-protocol/src/ast.ts';\nimport {transformFilters} from '../builder/filter.ts';\nimport type {ConnectionCostModel} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {PlannerFanIn} from './planner-fan-in.ts';\nimport {PlannerFanOut} from './planner-fan-out.ts';\nimport {PlannerFilter} from './planner-filter.ts';\nimport {PlannerGraph} from './planner-graph.ts';\nimport {PlannerJoin} from './planner-join.ts';\nimport type {PlannerNode} from './planner-node.ts';\nimport {PlannerTerminus} from './planner-terminus.ts';\nimport {\n collectPlanWarnings,\n type PlanWarning,\n type PlanWarningThresholds,\n} from './planner-warnings.ts';\n\nfunction wireOutput(from: PlannerNode, to: PlannerNode): void {\n switch (from.kind) {\n case 'connection':\n case 'join':\n case 'fan-in':\n case 'filter':\n from.setOutput(to);\n break;\n case 'fan-out':\n from.addOutput(to);\n break;\n case 'terminus':\n assert(false, 'Terminus nodes cannot have outputs');\n }\n}\n\nexport type Plans = {\n plan: PlannerGraph;\n subPlans: {[key: string]: Plans};\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`).\n */\nexport function buildPlanGraph(\n ast: AST,\n model: ConnectionCostModel,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n pushed?: ReadonlySet<SimpleCondition>,\n): Plans {\n const graph = new PlannerGraph();\n let nextPlanId = 0;\n\n const source = graph.addSource(ast.table, model);\n const connection = source.connect(\n ast.orderBy ?? [],\n ast.where,\n isRoot,\n baseConstraints,\n ast.limit,\n pushed,\n );\n graph.connections.push(connection);\n\n let end: PlannerNode = connection;\n if (ast.where) {\n end = processCondition(\n ast.where,\n end,\n graph,\n model,\n ast.table,\n () => nextPlanId++,\n pushed,\n );\n }\n\n const terminus = new PlannerTerminus(end);\n wireOutput(end, terminus);\n graph.setTerminus(terminus);\n\n const subPlans: {[key: string]: Plans} = {};\n if (ast.related) {\n for (const csq of ast.related) {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const childConstraints = extractConstraint(\n csq.correlation.childField,\n csq.subquery.table,\n );\n subPlans[alias] = buildPlanGraph(\n csq.subquery,\n model,\n true,\n childConstraints,\n pushed,\n );\n }\n }\n\n return {plan: graph, subPlans};\n}\n\nfunction processCondition(\n condition: Condition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n switch (condition.type) {\n case 'simple':\n return input;\n case 'and':\n return processAnd(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'or':\n return processOr(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n case 'correlatedSubquery':\n return processCorrelatedSubquery(\n condition,\n input,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n}\n\nfunction processAnd(\n condition: Conjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n let end = input;\n for (const subCondition of condition.conditions) {\n end = processCondition(\n subCondition,\n end,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n }\n return end;\n}\n\nfunction processOr(\n condition: Disjunction,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n // Skip building fan structure when no branch contains a CSQ. The runtime\n // collapses such ORs to a single Filter node, so the planner has nothing\n // to choose between.\n const hasAnySubquery = condition.conditions.some(\n c => c.type === 'correlatedSubquery' || hasCorrelatedSubquery(c),\n );\n if (!hasAnySubquery) {\n return input;\n }\n\n const fanOut = new PlannerFanOut(input);\n graph.fanOuts.push(fanOut);\n wireOutput(input, fanOut);\n\n const branches: Exclude<PlannerNode, PlannerTerminus>[] = [];\n for (const subCondition of condition.conditions) {\n let branch: Exclude<PlannerNode, PlannerTerminus>;\n if (\n subCondition.type === 'correlatedSubquery' ||\n hasCorrelatedSubquery(subCondition)\n ) {\n branch = processCondition(\n subCondition,\n fanOut,\n graph,\n model,\n parentTable,\n getPlanId,\n pushed,\n );\n } else {\n // Simple OR branch: wrap in a PlannerFilter so its filter can be\n // registered at the receiving connection on a per-branch basis when\n // the FanIn is in UFI mode.\n const transformed = transformFilters(subCondition).filters;\n const filter = new PlannerFilter(fanOut, transformed);\n graph.filters.push(filter);\n wireOutput(fanOut, filter);\n branch = filter;\n }\n branches.push(branch);\n fanOut.addOutput(branch);\n }\n\n const fanIn = new PlannerFanIn(branches);\n graph.fanIns.push(fanIn);\n for (const branch of branches) {\n wireOutput(branch, fanIn);\n }\n\n return fanIn;\n}\n\nfunction processCorrelatedSubquery(\n condition: CorrelatedSubqueryCondition,\n input: Exclude<PlannerNode, PlannerTerminus>,\n graph: PlannerGraph,\n model: ConnectionCostModel,\n parentTable: string,\n getPlanId: () => number,\n pushed: ReadonlySet<SimpleCondition> | undefined,\n): Exclude<PlannerNode, PlannerTerminus> {\n const {related} = condition;\n const childTable = related.subquery.table;\n\n const childSource = graph.hasSource(childTable)\n ? graph.getSource(childTable)\n : graph.addSource(childTable, model);\n\n const childConnection = childSource.connect(\n related.subquery.orderBy ?? [],\n related.subquery.where,\n false,\n undefined, // no base constraints for EXISTS/NOT EXISTS\n condition.op === 'EXISTS' ? 1 : undefined,\n pushed,\n );\n graph.connections.push(childConnection);\n\n let childEnd: PlannerNode = childConnection;\n if (related.subquery.where) {\n childEnd = processCondition(\n related.subquery.where,\n childEnd,\n graph,\n model,\n childTable,\n getPlanId,\n pushed,\n );\n }\n\n const parentConstraint = extractConstraint(\n related.correlation.parentField,\n parentTable,\n );\n const childConstraint = extractConstraint(\n related.correlation.childField,\n childTable,\n );\n\n const planId = getPlanId();\n condition[planIdSymbol] = planId;\n\n // Determine flippability and initial type based on flip flag and operator\n const isNotExists = condition.op === 'NOT EXISTS';\n const manualFlip = condition.flip;\n\n let flippable: boolean;\n let initialType: 'semi' | 'flipped';\n\n if (isNotExists) {\n // NOT EXISTS joins can never be flipped\n flippable = false;\n initialType = 'semi';\n } else if (manualFlip === true) {\n // User explicitly requested flip=true: start flipped, don't allow planner to change\n flippable = false;\n initialType = 'flipped';\n } else if (manualFlip === false) {\n // User explicitly requested flip=false: start semi, don't allow planner to change\n flippable = false;\n initialType = 'semi';\n } else {\n // flip is undefined: planner can decide\n flippable = true;\n initialType = 'semi';\n }\n\n const join = new PlannerJoin(\n input,\n childEnd,\n parentConstraint,\n childConstraint,\n flippable,\n planId,\n initialType,\n );\n graph.joins.push(join);\n childConnection.setParentJoin(join);\n\n wireOutput(input, join);\n wireOutput(childEnd, join);\n\n return join;\n}\n\nfunction hasCorrelatedSubquery(condition: Condition): boolean {\n if (condition.type === 'correlatedSubquery') {\n return true;\n }\n if (condition.type === 'and' || condition.type === 'or') {\n return condition.conditions.some(hasCorrelatedSubquery);\n }\n // simple conditions don't contain correlated subqueries\n return false;\n}\n\nfunction extractConstraint(\n fields: readonly string[],\n _tableName: string,\n): PlannerConstraint {\n return Object.fromEntries(fields.map(field => [field, undefined]));\n}\n\nfunction planRecursively(\n plans: Plans,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n): void {\n for (const subPlan of Object.values(plans.subPlans)) {\n planRecursively(subPlan, planDebugger, lc);\n }\n plans.plan.plan(planDebugger, lc);\n}\n\n/**\n * Where the planner reports the {@link PlanWarning}s for the plan it chose.\n */\nexport type PlanWarningSink = {\n readonly thresholds: PlanWarningThresholds;\n /** Called once per planned query, and only if there are warnings. */\n report(warnings: readonly PlanWarning[]): void;\n};\n\n/**\n * @param pushed The conditions that correlated predicate pushdown copied into\n * a child (see `pushDownCorrelatedPredicates`). The planner counts each one\n * only where it removes rows.\n * @param planWarnings Receives warnings about the chosen plan. Collecting them\n * costs extra cost model calls, so this is best left out when unused.\n */\nexport function planQuery(\n ast: AST,\n model: ConnectionCostModel,\n planDebugger?: PlanDebugger,\n lc?: LogContext,\n pushed?: ReadonlySet<SimpleCondition>,\n planWarnings?: PlanWarningSink,\n): AST {\n const plans = buildPlanGraph(ast, model, true, undefined, pushed);\n planRecursively(plans, planDebugger, lc);\n if (planWarnings) {\n const warnings = collectPlanWarnings(\n ast.table,\n plans,\n planWarnings.thresholds,\n );\n if (warnings.length > 0) {\n planWarnings.report(warnings);\n }\n }\n return applyPlansToAST(ast, plans);\n}\n\nfunction applyToCondition(\n condition: Condition,\n flippedIds: Set<number>,\n): Condition {\n if (condition.type === 'simple') {\n return condition;\n }\n\n if (condition.type === 'correlatedSubquery') {\n const planId = (condition as unknown as Record<symbol, number>)[\n planIdSymbol\n ];\n const shouldFlip = planId !== undefined && flippedIds.has(planId);\n\n return {\n ...condition,\n flip: shouldFlip,\n related: {\n ...condition.related,\n subquery: {\n ...condition.related.subquery,\n where: condition.related.subquery.where\n ? applyToCondition(condition.related.subquery.where, flippedIds)\n : undefined,\n },\n },\n };\n }\n\n return {\n ...condition,\n conditions: condition.conditions.map(c => applyToCondition(c, flippedIds)),\n };\n}\n\nexport function applyPlansToAST(ast: AST, plans: Plans): AST {\n const flippedIds = new Set<number>();\n for (const join of plans.plan.joins) {\n if (join.type === 'flipped' && join.planId !== undefined) {\n flippedIds.add(join.planId);\n }\n }\n\n return {\n ...ast,\n where: ast.where ? applyToCondition(ast.where, flippedIds) : undefined,\n related: ast.related?.map(csq => {\n const alias = must(\n csq.subquery.alias,\n 'Related subquery must have alias',\n );\n const subPlan = plans.subPlans[alias];\n return {\n ...csq,\n subquery: subPlan\n ? applyPlansToAST(csq.subquery, subPlan)\n : csq.subquery,\n };\n }),\n };\n}\n"],"mappings":"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"}
@@ -126,6 +126,13 @@ export declare class PlannerConnection {
126
126
  * in case the restored plan registers a different set.
127
127
  */
128
128
  restoreConstraints(constraints: Map<string, PlannerConstraint | undefined>): void;
129
+ get sort(): Ordering;
130
+ /**
131
+ * How the source reads rows for the current plan: one access per branch
132
+ * pattern this connection was costed for since its constraints last
133
+ * changed. Estimate the plan's cost first to populate them.
134
+ */
135
+ accesses(): Iterable<ConnectionAccess>;
129
136
  /** Get current constraints for debugging. */
130
137
  getConstraintsForDebug(): Record<string, PlannerConstraint | undefined>;
131
138
  /** Get filters for debugging. */
@@ -140,10 +147,46 @@ type FanoutEst = {
140
147
  confidence: 'high' | 'med' | 'none';
141
148
  };
142
149
  export type FanoutCostModel = (columns: string[]) => FanoutEst;
150
+ /**
151
+ * How a source would read the rows of a connection, as far as the cost model
152
+ * can tell. This is used to warn about slow plans, not to choose between
153
+ * them.
154
+ */
155
+ export type AccessPlan = {
156
+ /**
157
+ * `search` if the source uses an index to find the matching rows. `scan`
158
+ * if it reads the whole table (or a whole index, for its order) and tests
159
+ * each row, stopping early only if the reader stops.
160
+ */
161
+ readonly access: 'search' | 'scan';
162
+ /**
163
+ * Whether the rows are sorted after they are read. `full` means that every
164
+ * matching row is read and sorted before the first one is returned, so
165
+ * reading fewer rows (e.g. for a limit) does not help. `partial` means that
166
+ * only runs of rows that tie on a prefix of the ordering are sorted.
167
+ */
168
+ readonly sort: 'none' | 'partial' | 'full';
169
+ /** The number of rows in the table, if the cost model knows it. */
170
+ readonly tableRows: number | undefined;
171
+ };
143
172
  export type CostModelCost = {
144
173
  startupCost: number;
145
174
  rows: number;
146
175
  fanout: FanoutCostModel;
176
+ /** Omitted by cost models that cannot tell. */
177
+ plan?: AccessPlan | undefined;
178
+ };
179
+ /**
180
+ * A read of a connection's source under a particular constraint, as costed
181
+ * by the cost model.
182
+ */
183
+ export type ConnectionAccess = {
184
+ /** The columns bound to a single value by the time of the read. */
185
+ readonly constraint: PlannerConstraint | undefined;
186
+ readonly filters: Condition | undefined;
187
+ /** The estimated number of rows returned. */
188
+ readonly rows: number;
189
+ readonly plan: AccessPlan | undefined;
147
190
  };
148
191
  export type ConnectionCostModel = (table: string, sort: Ordering, filters: Condition | undefined, constraint: PlannerConstraint | undefined) => CostModelCost;
149
192
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"planner-connection.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-connection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"planner-connection.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-connection.ts"],"names":[],"mappings":"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"}
@@ -109,6 +109,11 @@ var PlannerConnection = class {
109
109
  * Invalidated when constraints change.
110
110
  */
111
111
  #cachedConstraintCosts = /* @__PURE__ */ new Map();
112
+ /**
113
+ * How the source reads rows for each constraint key with a cached cost.
114
+ * Cleared along with {@link #cachedConstraintCosts}.
115
+ */
116
+ #cachedAccesses = /* @__PURE__ */ new Map();
112
117
  constructor(table, model, sort, filters, isRoot, baseConstraints, limit, name, pushed) {
113
118
  this.table = table;
114
119
  this.name = name ?? table;
@@ -154,7 +159,7 @@ var PlannerConnection = class {
154
159
  propagateConstraints(path, c, from, planDebugger) {
155
160
  const key = path.join(",");
156
161
  this.#constraints.set(key, c);
157
- this.#cachedConstraintCosts.clear();
162
+ this.#clearCostCaches();
158
163
  planDebugger?.log({
159
164
  type: "node-constraint",
160
165
  nodeType: "connection",
@@ -172,7 +177,7 @@ var PlannerConnection = class {
172
177
  */
173
178
  setPerBranchFilter(path, filter) {
174
179
  this.#perBranchFilters.set(path.join(","), filter);
175
- this.#cachedConstraintCosts.clear();
180
+ this.#clearCostCaches();
176
181
  }
177
182
  estimateCost(downstreamChildSelectivity, branchPattern, planDebugger) {
178
183
  const key = branchPattern.join(",");
@@ -181,7 +186,8 @@ var PlannerConnection = class {
181
186
  const constraint = this.#constraints.get(key);
182
187
  const mergedConstraint = mergeConstraints(this.#baseConstraints, constraint);
183
188
  const perBranchFilter = this.#perBranchFilters.get(key);
184
- const { startupCost, fanout, rows } = this.#model(this.table, this.#sort, andFilters(this.#filtersFor(mergedConstraint), perBranchFilter), mergedConstraint);
189
+ const filters = andFilters(this.#filtersFor(mergedConstraint), perBranchFilter);
190
+ const { startupCost, fanout, rows, plan } = this.#model(this.table, this.#sort, filters, mergedConstraint);
185
191
  const selectivity = perBranchFilter ? this.#computeSelectivity(andFilters(this.#unpushedFilters, perBranchFilter)) : this.selectivity;
186
192
  cost = {
187
193
  startupCost,
@@ -193,6 +199,12 @@ var PlannerConnection = class {
193
199
  fanout
194
200
  };
195
201
  this.#cachedConstraintCosts.set(key, cost);
202
+ this.#cachedAccesses.set(key, {
203
+ constraint: mergedConstraint,
204
+ filters,
205
+ rows,
206
+ plan
207
+ });
196
208
  if (planDebugger) planDebugger.log({
197
209
  type: "node-cost",
198
210
  nodeType: "connection",
@@ -245,7 +257,11 @@ var PlannerConnection = class {
245
257
  this.#constraints.clear();
246
258
  this.#perBranchFilters.clear();
247
259
  this.limit = this.#baseLimit;
260
+ this.#clearCostCaches();
261
+ }
262
+ #clearCostCaches() {
248
263
  this.#cachedConstraintCosts.clear();
264
+ this.#cachedAccesses.clear();
249
265
  }
250
266
  /**
251
267
  * Capture constraint state for snapshotting.
@@ -267,7 +283,18 @@ var PlannerConnection = class {
267
283
  this.#constraints.clear();
268
284
  this.#perBranchFilters.clear();
269
285
  for (const [key, value] of constraints) this.#constraints.set(key, value);
270
- this.#cachedConstraintCosts.clear();
286
+ this.#clearCostCaches();
287
+ }
288
+ get sort() {
289
+ return this.#sort;
290
+ }
291
+ /**
292
+ * How the source reads rows for the current plan: one access per branch
293
+ * pattern this connection was costed for since its constraints last
294
+ * changed. Estimate the plan's cost first to populate them.
295
+ */
296
+ accesses() {
297
+ return this.#cachedAccesses.values();
271
298
  }
272
299
  /** Get current constraints for debugging. */
273
300
  getConstraintsForDebug() {