@rocicorp/zero 1.3.0 → 1.4.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/out/analyze-query/src/analyze-cli.d.ts +24 -0
  2. package/out/analyze-query/src/analyze-cli.d.ts.map +1 -0
  3. package/out/analyze-query/src/analyze-cli.js +279 -0
  4. package/out/analyze-query/src/analyze-cli.js.map +1 -0
  5. package/out/analyze-query/src/bin-analyze.js +6 -6
  6. package/out/analyze-query/src/bin-transform.js +2 -2
  7. package/out/ast-to-zql/src/bin.js +1 -1
  8. package/out/shared/src/logging.d.ts.map +1 -1
  9. package/out/shared/src/logging.js +1 -1
  10. package/out/shared/src/logging.js.map +1 -1
  11. package/out/shared/src/options.d.ts.map +1 -1
  12. package/out/shared/src/options.js +1 -1
  13. package/out/shared/src/options.js.map +1 -1
  14. package/out/z2s/src/compiler.d.ts.map +1 -1
  15. package/out/z2s/src/compiler.js +4 -1
  16. package/out/z2s/src/compiler.js.map +1 -1
  17. package/out/z2s/src/sql.d.ts.map +1 -1
  18. package/out/z2s/src/sql.js +1 -0
  19. package/out/z2s/src/sql.js.map +1 -1
  20. package/out/zero/package.js +95 -89
  21. package/out/zero/package.js.map +1 -1
  22. package/out/zero/src/analyze.d.ts +2 -0
  23. package/out/zero/src/analyze.d.ts.map +1 -0
  24. package/out/zero/src/analyze.js +2 -0
  25. package/out/zero/src/zero-cache-dev.js +1 -1
  26. package/out/zero/src/zero-cache-dev.js.map +1 -1
  27. package/out/zero/src/zero-out.js +1 -1
  28. package/out/zero-cache/src/auth/auth.d.ts.map +1 -1
  29. package/out/zero-cache/src/auth/auth.js.map +1 -1
  30. package/out/zero-cache/src/auth/load-permissions.js +2 -2
  31. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  32. package/out/zero-cache/src/auth/write-authorizer.js +5 -14
  33. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  34. package/out/zero-cache/src/config/network.d.ts +1 -1
  35. package/out/zero-cache/src/config/network.d.ts.map +1 -1
  36. package/out/zero-cache/src/config/network.js +1 -1
  37. package/out/zero-cache/src/config/network.js.map +1 -1
  38. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  39. package/out/zero-cache/src/config/normalize.js.map +1 -1
  40. package/out/zero-cache/src/config/zero-config.d.ts +5 -0
  41. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  42. package/out/zero-cache/src/config/zero-config.js +16 -3
  43. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  44. package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
  45. package/out/zero-cache/src/db/lite-tables.js +3 -3
  46. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  47. package/out/zero-cache/src/db/transaction-pool.d.ts +43 -40
  48. package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
  49. package/out/zero-cache/src/db/transaction-pool.js +76 -56
  50. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  51. package/out/zero-cache/src/observability/events.d.ts.map +1 -1
  52. package/out/zero-cache/src/observability/events.js +1 -1
  53. package/out/zero-cache/src/observability/events.js.map +1 -1
  54. package/out/zero-cache/src/scripts/decommission.js +1 -1
  55. package/out/zero-cache/src/scripts/deploy-permissions.js +2 -2
  56. package/out/zero-cache/src/scripts/permissions.js +1 -1
  57. package/out/zero-cache/src/server/anonymous-otel-start.d.ts.map +1 -1
  58. package/out/zero-cache/src/server/anonymous-otel-start.js +4 -4
  59. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  60. package/out/zero-cache/src/server/change-streamer.d.ts +1 -1
  61. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  62. package/out/zero-cache/src/server/change-streamer.js +27 -12
  63. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  64. package/out/zero-cache/src/server/logging.d.ts +1 -3
  65. package/out/zero-cache/src/server/logging.d.ts.map +1 -1
  66. package/out/zero-cache/src/server/logging.js +6 -3
  67. package/out/zero-cache/src/server/logging.js.map +1 -1
  68. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  69. package/out/zero-cache/src/server/main.js +26 -26
  70. package/out/zero-cache/src/server/main.js.map +1 -1
  71. package/out/zero-cache/src/server/mutator.js +4 -2
  72. package/out/zero-cache/src/server/mutator.js.map +1 -1
  73. package/out/zero-cache/src/server/otel-log-sink.d.ts.map +1 -1
  74. package/out/zero-cache/src/server/otel-log-sink.js +0 -2
  75. package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
  76. package/out/zero-cache/src/server/otel-start.d.ts +1 -1
  77. package/out/zero-cache/src/server/otel-start.d.ts.map +1 -1
  78. package/out/zero-cache/src/server/otel-start.js +7 -3
  79. package/out/zero-cache/src/server/otel-start.js.map +1 -1
  80. package/out/zero-cache/src/server/reaper.js +6 -6
  81. package/out/zero-cache/src/server/reaper.js.map +1 -1
  82. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  83. package/out/zero-cache/src/server/replicator.js +5 -3
  84. package/out/zero-cache/src/server/replicator.js.map +1 -1
  85. package/out/zero-cache/src/server/runner/run-worker.js +2 -2
  86. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  87. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  88. package/out/zero-cache/src/server/syncer.js +13 -12
  89. package/out/zero-cache/src/server/syncer.js.map +1 -1
  90. package/out/zero-cache/src/server/worker-dispatcher.js +1 -1
  91. package/out/zero-cache/src/services/analyze.js +1 -1
  92. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +1 -1
  93. package/out/zero-cache/src/services/change-source/common/replica-schema.js +1 -1
  94. package/out/zero-cache/src/services/change-source/custom/change-source.js +4 -4
  95. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  96. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +4 -1
  97. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  98. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  99. package/out/zero-cache/src/services/change-source/pg/change-source.js +19 -23
  100. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  101. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +58 -3
  102. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  103. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +209 -52
  104. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  105. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +2 -2
  106. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +24 -15
  107. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  108. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +35 -58
  109. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  110. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  111. package/out/zero-cache/src/services/change-source/pg/schema/init.js +2 -2
  112. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  113. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +1 -2
  114. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
  115. package/out/zero-cache/src/services/change-source/pg/schema/published.js +15 -18
  116. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  117. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +1 -1
  118. package/out/zero-cache/src/services/change-source/protocol/current/data.js +1 -1
  119. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +1 -1
  120. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +1 -1
  121. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  122. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +4 -4
  123. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  124. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +5 -1
  125. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  126. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +10 -7
  127. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  128. package/out/zero-cache/src/services/change-streamer/replica-monitor.js +2 -2
  129. package/out/zero-cache/src/services/change-streamer/storer.d.ts +19 -2
  130. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  131. package/out/zero-cache/src/services/change-streamer/storer.js +70 -6
  132. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  133. package/out/zero-cache/src/services/heapz.d.ts.map +1 -1
  134. package/out/zero-cache/src/services/heapz.js +1 -1
  135. package/out/zero-cache/src/services/heapz.js.map +1 -1
  136. package/out/zero-cache/src/services/life-cycle.d.ts +2 -1
  137. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  138. package/out/zero-cache/src/services/life-cycle.js +10 -7
  139. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  140. package/out/zero-cache/src/services/litestream/commands.d.ts +15 -4
  141. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  142. package/out/zero-cache/src/services/litestream/commands.js +40 -34
  143. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  144. package/out/zero-cache/src/services/mutagen/mutagen.js +3 -3
  145. package/out/zero-cache/src/services/mutagen/pusher.d.ts +28 -28
  146. package/out/zero-cache/src/services/replicator/change-processor.js +2 -2
  147. package/out/zero-cache/src/services/replicator/incremental-sync.js +1 -1
  148. package/out/zero-cache/src/services/replicator/schema/replication-state.js +1 -1
  149. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  150. package/out/zero-cache/src/services/replicator/write-worker.js +3 -3
  151. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  152. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  153. package/out/zero-cache/src/services/run-ast.js +2 -2
  154. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  155. package/out/zero-cache/src/services/statz.d.ts.map +1 -1
  156. package/out/zero-cache/src/services/statz.js +3 -3
  157. package/out/zero-cache/src/services/statz.js.map +1 -1
  158. package/out/zero-cache/src/services/view-syncer/active-users-gauge.js +1 -1
  159. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +2 -2
  160. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.map +1 -1
  161. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  162. package/out/zero-cache/src/services/view-syncer/cvr-purger.js +1 -1
  163. package/out/zero-cache/src/services/view-syncer/cvr-store.js +3 -3
  164. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  165. package/out/zero-cache/src/services/view-syncer/cvr.js +1 -1
  166. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +2 -2
  167. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +6 -16
  168. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  169. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +30 -38
  170. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  171. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
  172. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +4 -4
  173. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  174. package/out/zero-cache/src/services/view-syncer/snapshotter.js +2 -2
  175. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  176. package/out/zero-cache/src/services/view-syncer/view-syncer.js +6 -6
  177. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  178. package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
  179. package/out/zero-cache/src/types/profiler.js.map +1 -1
  180. package/out/zero-cache/src/types/row-key.d.ts.map +1 -1
  181. package/out/zero-cache/src/types/row-key.js.map +1 -1
  182. package/out/zero-cache/src/types/streams.d.ts +1 -1
  183. package/out/zero-cache/src/types/streams.d.ts.map +1 -1
  184. package/out/zero-cache/src/types/streams.js.map +1 -1
  185. package/out/zero-cache/src/types/websocket-handoff.d.ts +1 -1
  186. package/out/zero-cache/src/types/websocket-handoff.d.ts.map +1 -1
  187. package/out/zero-cache/src/types/websocket-handoff.js +1 -1
  188. package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
  189. package/out/zero-cache/src/workers/connection.d.ts +1 -1
  190. package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
  191. package/out/zero-cache/src/workers/connection.js +2 -2
  192. package/out/zero-cache/src/workers/connection.js.map +1 -1
  193. package/out/zero-cache/src/workers/mutator.js.map +1 -1
  194. package/out/zero-cache/src/workers/syncer.d.ts +1 -1
  195. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  196. package/out/zero-cache/src/workers/syncer.js +3 -3
  197. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  198. package/out/zero-client/src/client/crud-impl.d.ts.map +1 -1
  199. package/out/zero-client/src/client/crud-impl.js +4 -13
  200. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  201. package/out/zero-client/src/client/inspector/inspector.d.ts +24 -0
  202. package/out/zero-client/src/client/inspector/inspector.d.ts.map +1 -1
  203. package/out/zero-client/src/client/inspector/inspector.js +28 -0
  204. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  205. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts +9 -0
  206. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
  207. package/out/zero-client/src/client/inspector/lazy-inspector.js +28 -1
  208. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  209. package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
  210. package/out/zero-client/src/client/ivm-branch.js +4 -13
  211. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  212. package/out/zero-client/src/client/version.js +1 -1
  213. package/out/zero-protocol/src/error.d.ts.map +1 -1
  214. package/out/zero-protocol/src/error.js +1 -1
  215. package/out/zero-protocol/src/error.js.map +1 -1
  216. package/out/zero-solid/src/solid-view.d.ts.map +1 -1
  217. package/out/zero-solid/src/solid-view.js +13 -13
  218. package/out/zero-solid/src/solid-view.js.map +1 -1
  219. package/out/zql/src/builder/builder.d.ts.map +1 -1
  220. package/out/zql/src/builder/builder.js.map +1 -1
  221. package/out/zql/src/ivm/array-view.d.ts.map +1 -1
  222. package/out/zql/src/ivm/array-view.js +26 -1
  223. package/out/zql/src/ivm/array-view.js.map +1 -1
  224. package/out/zql/src/ivm/change-index-enum.d.ts +9 -0
  225. package/out/zql/src/ivm/change-index-enum.d.ts.map +1 -0
  226. package/out/zql/src/ivm/change-index.d.ts +5 -0
  227. package/out/zql/src/ivm/change-index.d.ts.map +1 -0
  228. package/out/zql/src/ivm/change-type-enum.d.ts +9 -0
  229. package/out/zql/src/ivm/change-type-enum.d.ts.map +1 -0
  230. package/out/zql/src/ivm/change-type.d.ts +5 -0
  231. package/out/zql/src/ivm/change-type.d.ts.map +1 -0
  232. package/out/zql/src/ivm/change.d.ts +20 -22
  233. package/out/zql/src/ivm/change.d.ts.map +1 -1
  234. package/out/zql/src/ivm/change.js +33 -0
  235. package/out/zql/src/ivm/change.js.map +1 -0
  236. package/out/zql/src/ivm/exists.d.ts.map +1 -1
  237. package/out/zql/src/ivm/exists.js +27 -38
  238. package/out/zql/src/ivm/exists.js.map +1 -1
  239. package/out/zql/src/ivm/fan-in.d.ts +3 -2
  240. package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
  241. package/out/zql/src/ivm/fan-in.js.map +1 -1
  242. package/out/zql/src/ivm/fan-out.d.ts +1 -1
  243. package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
  244. package/out/zql/src/ivm/fan-out.js +1 -1
  245. package/out/zql/src/ivm/fan-out.js.map +1 -1
  246. package/out/zql/src/ivm/filter-operators.d.ts +3 -3
  247. package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
  248. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  249. package/out/zql/src/ivm/filter-push.d.ts.map +1 -1
  250. package/out/zql/src/ivm/filter-push.js +7 -7
  251. package/out/zql/src/ivm/filter-push.js.map +1 -1
  252. package/out/zql/src/ivm/filter.d.ts +1 -1
  253. package/out/zql/src/ivm/filter.d.ts.map +1 -1
  254. package/out/zql/src/ivm/filter.js.map +1 -1
  255. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  256. package/out/zql/src/ivm/flipped-join.js +49 -58
  257. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  258. package/out/zql/src/ivm/join-utils.d.ts +2 -6
  259. package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
  260. package/out/zql/src/ivm/join-utils.js +25 -25
  261. package/out/zql/src/ivm/join-utils.js.map +1 -1
  262. package/out/zql/src/ivm/join.d.ts.map +1 -1
  263. package/out/zql/src/ivm/join.js +32 -51
  264. package/out/zql/src/ivm/join.js.map +1 -1
  265. package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
  266. package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts.map +1 -1
  267. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +5 -10
  268. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  269. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  270. package/out/zql/src/ivm/memory-source.js +51 -59
  271. package/out/zql/src/ivm/memory-source.js.map +1 -1
  272. package/out/zql/src/ivm/push-accumulated.d.ts +3 -2
  273. package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
  274. package/out/zql/src/ivm/push-accumulated.js +98 -122
  275. package/out/zql/src/ivm/push-accumulated.js.map +1 -1
  276. package/out/zql/src/ivm/skip.d.ts +1 -1
  277. package/out/zql/src/ivm/skip.d.ts.map +1 -1
  278. package/out/zql/src/ivm/skip.js +2 -2
  279. package/out/zql/src/ivm/skip.js.map +1 -1
  280. package/out/zql/src/ivm/source-change-index-enum.d.ts +7 -0
  281. package/out/zql/src/ivm/source-change-index-enum.d.ts.map +1 -0
  282. package/out/zql/src/ivm/source-change-index.d.ts +5 -0
  283. package/out/zql/src/ivm/source-change-index.d.ts.map +1 -0
  284. package/out/zql/src/ivm/source.d.ts +11 -13
  285. package/out/zql/src/ivm/source.d.ts.map +1 -1
  286. package/out/zql/src/ivm/source.js +26 -0
  287. package/out/zql/src/ivm/source.js.map +1 -0
  288. package/out/zql/src/ivm/take.d.ts.map +1 -1
  289. package/out/zql/src/ivm/take.js +27 -50
  290. package/out/zql/src/ivm/take.js.map +1 -1
  291. package/out/zql/src/ivm/union-fan-in.d.ts +2 -1
  292. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  293. package/out/zql/src/ivm/union-fan-in.js +3 -3
  294. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  295. package/out/zql/src/ivm/union-fan-out.d.ts.map +1 -1
  296. package/out/zql/src/ivm/union-fan-out.js +1 -1
  297. package/out/zql/src/ivm/union-fan-out.js.map +1 -1
  298. package/out/zql/src/planner/planner-debug.d.ts +2 -2
  299. package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
  300. package/out/zql/src/planner/planner-debug.js.map +1 -1
  301. package/out/zql/src/planner/planner-graph.d.ts +1 -1
  302. package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
  303. package/out/zql/src/planner/planner-graph.js.map +1 -1
  304. package/out/zqlite/src/internal/sql-inline.d.ts.map +1 -1
  305. package/out/zqlite/src/internal/sql-inline.js.map +1 -1
  306. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  307. package/out/zqlite/src/query-builder.js.map +1 -1
  308. package/out/zqlite/src/table-source.d.ts.map +1 -1
  309. package/out/zqlite/src/table-source.js +11 -11
  310. package/out/zqlite/src/table-source.js.map +1 -1
  311. package/package.json +99 -93
@@ -1,9 +1,9 @@
1
1
  import type * as v from '../../../shared/src/valita.ts';
2
- import type { Condition, Ordering } from '../../../zero-protocol/src/ast.ts';
3
2
  import type { attemptStartEventJSONSchema, bestPlanSelectedEventJSONSchema, connectionSelectedEventJSONSchema, nodeConstraintEventJSONSchema, PlanDebugEventJSON, planFailedEventJSONSchema } from '../../../zero-protocol/src/analyze-query-result.ts';
3
+ import type { Condition, Ordering } from '../../../zero-protocol/src/ast.ts';
4
4
  import type { PlannerConstraint } from './planner-constraint.ts';
5
- import type { CostEstimate, JoinType } from './planner-node.ts';
6
5
  import type { PlanState } from './planner-graph.ts';
6
+ import type { CostEstimate, JoinType } from './planner-node.ts';
7
7
  /**
8
8
  * Structured debug events emitted during query planning.
9
9
  * These events can be accumulated, printed, or analyzed to understand
@@ -1 +1 @@
1
- {"version":3,"file":"planner-debug.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-debug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACxD,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAET,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,2BAA2B,EAC3B,+BAA+B,EAC/B,iCAAiC,EACjC,6BAA6B,EAC7B,kBAAkB,EAClB,yBAAyB,EAC1B,MAAM,oDAAoD,CAAC;AAC5D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAElD;;;;GAIG;AAEH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAE5E;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,kBAAkB,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,EAAE,OAAO,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC;QAC3D,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;KAC/D,CAAC,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,iCAAiC,CACzC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,KAAK,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC;QAC3D,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;KAC/D,CAAC,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,eAAe,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,QAAQ,CAAC;KAChB,CAAC,CAAC;IAEH,YAAY,EAAE,SAAS,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CACzC,OAAO,+BAA+B,CACvC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,0BAA0B,EAAE,MAAM,CAAC;IACnC,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAC3C,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACjC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEhF;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,iBAAiB,GACjB,oBAAoB,GACpB,uBAAuB,GACvB,0BAA0B,GAC1B,iBAAiB,GACjB,eAAe,GACf,qBAAqB,GACrB,aAAa,GACb,mBAAmB,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACtD,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,CAAM;IACvC,OAAO,CAAC,cAAc,CAAK;IAE3B,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAehC;;OAEG;IACH,SAAS,CAAC,CAAC,SAAS,cAAc,CAAC,MAAM,CAAC,EACxC,IAAI,EAAE,CAAC,GACN,OAAO,CAAC,cAAc,EAAE;QAAC,IAAI,EAAE,CAAC,CAAA;KAAC,CAAC,EAAE;IAOvC;;OAEG;IACH,MAAM,IAAI,MAAM;CAGjB;AA2QD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,cAAc,EAAE,GACvB,kBAAkB,EAAE,CAEtB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,kBAAkB,EAAE,GAAG,cAAc,EAAE,GAC9C,MAAM,CAyDR"}
1
+ {"version":3,"file":"planner-debug.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-debug.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import type * as v from '../../../shared/src/valita.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\nimport type {PlanState} from './planner-graph.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType: 'connection' | 'join' | 'fan-out' | 'fan-in' | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n let attemptEvents = eventsByAttempt.get(attempt);\n if (!attemptEvents) {\n attemptEvents = [];\n eventsByAttempt.set(attempt, attemptEvents);\n }\n attemptEvents.push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import type * as v from '../../../shared/src/valita.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanState} from './planner-graph.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType: 'connection' | 'join' | 'fan-out' | 'fan-in' | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n let attemptEvents = eventsByAttempt.get(attempt);\n if (!attemptEvents) {\n attemptEvents = [];\n eventsByAttempt.set(attempt, attemptEvents);\n }\n attemptEvents.push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import type { LogContext } from '@rocicorp/logger';
2
- import type { PlanDebugger } from './planner-debug.ts';
3
2
  import type { PlannerConnection } from './planner-connection.ts';
4
3
  import type { PlannerConstraint } from './planner-constraint.ts';
4
+ import type { PlanDebugger } from './planner-debug.ts';
5
5
  import type { PlannerFanIn } from './planner-fan-in.ts';
6
6
  import type { PlannerFanOut } from './planner-fan-out.ts';
7
7
  import type { PlannerJoin } from './planner-join.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"planner-graph.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAC,aAAa,EAAE,KAAK,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,WAAW,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC,CAAC,CAAC;IAChD,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC,CAAC,CAAC;IACzC,OAAO,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAA;KAAC,CAAC,CAAC;IACrC,MAAM,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAA;KAAC,CAAC,CAAC;IACpC,qBAAqB,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC,CAAC;CAC1E,CAAC;AAkBF,qBAAa,YAAY;;IAQvB,KAAK,EAAE,WAAW,EAAE,CAAM;IAC1B,OAAO,EAAE,aAAa,EAAE,CAAM;IAC9B,MAAM,EAAE,YAAY,EAAE,CAAM;IAC5B,WAAW,EAAE,iBAAiB,EAAE,CAAM;IAEtC;;;;;OAKG;IACH,kBAAkB;IAOlB;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,aAAa;IAUlE;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa;IAMtC;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIhC;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAI5C;;;;OAIG;IACH,oBAAoB,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI;IAQvD;;;OAGG;IACH,YAAY,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,MAAM;IAKjD;;;;;;;;OAQG;IACH,uBAAuB,IAAI,SAAS;IAYpC;;;;;;;;OAQG;IACH,uBAAuB,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAsF/C;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,YAAY,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,IAAI;CA+HzD"}
1
+ {"version":3,"file":"planner-graph.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAC,aAAa,EAAE,KAAK,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,WAAW,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC,CAAC,CAAC;IAChD,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC,CAAC,CAAC;IACzC,OAAO,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAA;KAAC,CAAC,CAAC;IACrC,MAAM,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAA;KAAC,CAAC,CAAC;IACpC,qBAAqB,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC,CAAC;CAC1E,CAAC;AAkBF,qBAAa,YAAY;;IAQvB,KAAK,EAAE,WAAW,EAAE,CAAM;IAC1B,OAAO,EAAE,aAAa,EAAE,CAAM;IAC9B,MAAM,EAAE,YAAY,EAAE,CAAM;IAC5B,WAAW,EAAE,iBAAiB,EAAE,CAAM;IAEtC;;;;;OAKG;IACH,kBAAkB;IAOlB;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,aAAa;IAUlE;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa;IAMtC;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIhC;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAI5C;;;;OAIG;IACH,oBAAoB,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI;IAQvD;;;OAGG;IACH,YAAY,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,MAAM;IAKjD;;;;;;;;OAQG;IACH,uBAAuB,IAAI,SAAS;IAYpC;;;;;;;;OAQG;IACH,uBAAuB,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAsF/C;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,YAAY,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,IAAI;CA+HzD"}
@@ -1 +1 @@
1
- {"version":3,"file":"planner-graph.js","names":["#sources","#terminus","#validateSnapshotShape","#restoreConnections","#restoreJoins","#restoreFanNodes"],"sources":["../../../../../zql/src/planner/planner-graph.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport type {PlannerConnection} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlannerFanIn} from './planner-fan-in.ts';\nimport type {PlannerFanOut} from './planner-fan-out.ts';\nimport type {PlannerJoin} from './planner-join.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {PlannerNode} from './planner-node.ts';\nimport {PlannerSource, type ConnectionCostModel} from './planner-source.ts';\nimport type {PlannerTerminus} from './planner-terminus.ts';\n\n/**\n * Captured state of a plan for comparison and restoration.\n */\nexport type PlanState = {\n connections: Array<{limit: number | undefined}>;\n joins: Array<{type: 'semi' | 'flipped'}>;\n fanOuts: Array<{type: 'FO' | 'UFO'}>;\n fanIns: Array<{type: 'FI' | 'UFI'}>;\n connectionConstraints: Array<Map<string, PlannerConstraint | undefined>>;\n};\n\n/**\n * Maximum number of flippable joins to attempt exhaustive enumeration.\n * With n flippable joins, we explore 2^n plans.\n * 10 joins = 1024 plans (~100-200ms), 12 joins = 4096 plans (~400ms - 1 second)\n */\nconst MAX_FLIPPABLE_JOINS = 9;\n\n/**\n * Cached information about FanOut→FanIn relationships.\n * Computed once during planning to avoid redundant BFS traversals.\n */\ntype FOFIInfo = {\n fi: PlannerFanIn | undefined;\n joinsBetween: PlannerJoin[];\n};\n\nexport class PlannerGraph {\n // Sources indexed by table name\n readonly #sources = new Map<string, PlannerSource>();\n\n // The final output node where constraint propagation starts\n #terminus: PlannerTerminus | undefined = undefined;\n\n // Collections of nodes with mutable planning state\n joins: PlannerJoin[] = [];\n fanOuts: PlannerFanOut[] = [];\n fanIns: PlannerFanIn[] = [];\n connections: PlannerConnection[] = [];\n\n /**\n * Reset all planning state back to initial values for another planning pass.\n * Resets only mutable planning state - graph structure is unchanged.\n *\n * This allows replanning the same query graph with different strategies.\n */\n resetPlanningState() {\n for (const j of this.joins) j.reset();\n for (const fo of this.fanOuts) fo.reset();\n for (const fi of this.fanIns) fi.reset();\n for (const c of this.connections) c.reset();\n }\n\n /**\n * Create and register a source (table) in the graph.\n */\n addSource(name: string, model: ConnectionCostModel): PlannerSource {\n assert(\n !this.#sources.has(name),\n `Source ${name} already exists in the graph`,\n );\n const source = new PlannerSource(name, model);\n this.#sources.set(name, source);\n return source;\n }\n\n /**\n * Get a source by table name.\n */\n getSource(name: string): PlannerSource {\n const source = this.#sources.get(name);\n assert(source !== undefined, `Source ${name} not found in the graph`);\n return source;\n }\n\n /**\n * Check if a source exists by table name.\n */\n hasSource(name: string): boolean {\n return this.#sources.has(name);\n }\n\n /**\n * Set the terminus (final output) node of the graph.\n * Constraint propagation starts from this node.\n */\n setTerminus(terminus: PlannerTerminus): void {\n this.#terminus = terminus;\n }\n\n /**\n * Initiate constraint propagation from the terminus node.\n * This sends constraints up through the graph to update\n * connection cost estimates.\n */\n propagateConstraints(planDebugger?: PlanDebugger): void {\n assert(\n this.#terminus !== undefined,\n 'Cannot propagate constraints without a terminus node',\n );\n this.#terminus.propagateConstraints(planDebugger);\n }\n\n /**\n * Calculate total cost of the current plan.\n * Total cost includes both startup cost (one-time, e.g., sorting) and running cost.\n */\n getTotalCost(planDebugger?: PlanDebugger): number {\n const estimate = must(this.#terminus).estimateCost(planDebugger);\n return estimate.cost + estimate.startupCost;\n }\n\n /**\n * Capture a lightweight snapshot of the current planning state.\n * Used for backtracking during multi-start greedy search.\n *\n * Captures mutable state including pinned flags, join types, and\n * constraint maps to avoid needing repropagation on restore.\n *\n * @returns A snapshot that can be restored via restorePlanningSnapshot()\n */\n capturePlanningSnapshot(): PlanState {\n return {\n connections: this.connections.map(c => ({\n limit: c.limit,\n })),\n joins: this.joins.map(j => ({type: j.type})),\n fanOuts: this.fanOuts.map(fo => ({type: fo.type})),\n fanIns: this.fanIns.map(fi => ({type: fi.type})),\n connectionConstraints: this.connections.map(c => c.captureConstraints()),\n };\n }\n\n /**\n * Restore planning state from a previously captured snapshot.\n * Used for backtracking when a planning attempt fails.\n *\n * Restores pinned flags, join types, and constraint maps, eliminating\n * the need for repropagation.\n *\n * @param state - Snapshot created by capturePlanningSnapshot()\n */\n restorePlanningSnapshot(state: PlanState): void {\n this.#validateSnapshotShape(state);\n this.#restoreConnections(state);\n this.#restoreJoins(state);\n this.#restoreFanNodes(state);\n }\n\n /**\n * Validate that snapshot shape matches current graph structure.\n */\n #validateSnapshotShape(state: PlanState): void {\n assert(\n this.connections.length === state.connections.length,\n 'Plan state mismatch: connections',\n );\n assert(\n this.joins.length === state.joins.length,\n 'Plan state mismatch: joins',\n );\n assert(\n this.fanOuts.length === state.fanOuts.length,\n 'Plan state mismatch: fanOuts',\n );\n assert(\n this.fanIns.length === state.fanIns.length,\n 'Plan state mismatch: fanIns',\n );\n assert(\n this.connections.length === state.connectionConstraints.length,\n 'Plan state mismatch: connectionConstraints',\n );\n }\n\n /**\n * Restore connection pinned flags, limits, and constraint maps.\n */\n #restoreConnections(state: PlanState): void {\n for (let i = 0; i < this.connections.length; i++) {\n this.connections[i].limit = state.connections[i].limit;\n this.connections[i].restoreConstraints(state.connectionConstraints[i]);\n }\n }\n\n /**\n * Restore join types and pinned flags.\n */\n #restoreJoins(state: PlanState): void {\n for (let i = 0; i < this.joins.length; i++) {\n const join = this.joins[i];\n const targetState = state.joins[i];\n\n // Reset to initial state first\n join.reset();\n\n // Apply target state\n if (targetState.type === 'flipped' && join.type !== 'flipped') {\n join.flip();\n }\n assert(\n targetState.type === join.type,\n 'join is not in the correct state after reset',\n );\n }\n }\n\n /**\n * Restore FanOut and FanIn types.\n */\n #restoreFanNodes(state: PlanState): void {\n for (let i = 0; i < this.fanOuts.length; i++) {\n const fo = this.fanOuts[i];\n const targetType = state.fanOuts[i].type;\n if (targetType === 'UFO' && fo.type === 'FO') {\n fo.convertToUFO();\n }\n }\n\n for (let i = 0; i < this.fanIns.length; i++) {\n const fi = this.fanIns[i];\n const targetType = state.fanIns[i].type;\n if (targetType === 'UFI' && fi.type === 'FI') {\n fi.convertToUFI();\n }\n }\n }\n\n /**\n * Main planning algorithm using exhaustive join flip enumeration.\n *\n * Enumerates all possible flip patterns for flippable joins (2^n for n flippable joins).\n * Each pattern represents a different query execution plan. We evaluate the cost of each\n * plan and select the one with the lowest cost.\n *\n * Connections are used only for cost estimation - the flip patterns determine the plan.\n * FanOut/FanIn states (FO/UFO and FI/UFI) are automatically derived from join flip states.\n *\n * @param planDebugger - Optional debugger to receive structured events during planning\n * @param lc - Optional logger for warnings\n */\n plan(planDebugger?: PlanDebugger, lc?: LogContext): void {\n // Get all flippable joins\n const flippableJoins = this.joins.filter(j => j.isFlippable());\n\n // Too many flippable joins - skip optimization and run as-is\n if (flippableJoins.length > MAX_FLIPPABLE_JOINS) {\n lc?.warn?.(\n `Query has ${flippableJoins.length} EXISTS checks which would require ` +\n `${2 ** flippableJoins.length} plan evaluations. Skipping optimization.`,\n );\n return;\n }\n\n // Build FO→FI cache once to avoid redundant BFS traversals in each iteration\n const fofiCache = buildFOFICache(this);\n\n const numPatterns =\n flippableJoins.length === 0 ? 0 : 2 ** flippableJoins.length;\n let bestCost = Infinity;\n let bestPlan: PlanState | undefined = undefined;\n let bestAttemptNumber = -1;\n\n // Enumerate all flip patterns\n for (let pattern = 0; pattern < numPatterns; pattern++) {\n // Reset to initial state\n this.resetPlanningState();\n\n if (planDebugger) {\n planDebugger.log({\n type: 'attempt-start',\n attemptNumber: pattern,\n totalAttempts: numPatterns,\n });\n }\n\n // Apply flip pattern (treat pattern as bitmask)\n // Bit i set to 1 means flip join i\n for (let i = 0; i < flippableJoins.length; i++) {\n if (pattern & (1 << i)) {\n flippableJoins[i].flip();\n }\n }\n\n // Derive FO/UFO and FI/UFI states from join flip states\n checkAndConvertFOFI(fofiCache);\n\n // Propagate unlimiting for flipped joins\n propagateUnlimitForFlippedJoins(this);\n\n // Propagate constraints through the graph\n this.propagateConstraints(planDebugger);\n\n if (planDebugger) {\n planDebugger.log({\n type: 'constraints-propagated',\n attemptNumber: pattern,\n connectionConstraints: this.connections.map(c => {\n const constraintCosts = c.getConstraintCostsForDebug();\n const constraintCostsWithoutFanout: Record<\n string,\n Omit<(typeof constraintCosts)[string], 'fanout'>\n > = {};\n for (const [key, cost] of Object.entries(constraintCosts)) {\n constraintCostsWithoutFanout[key] = omitFanout(cost);\n }\n return {\n connection: c.name,\n constraints: c.getConstraintsForDebug(),\n constraintCosts: constraintCostsWithoutFanout,\n };\n }),\n });\n }\n\n // Evaluate this plan\n const totalCost = this.getTotalCost(planDebugger);\n\n if (planDebugger) {\n planDebugger.log({\n type: 'plan-complete',\n attemptNumber: pattern,\n totalCost,\n flipPattern: pattern, // Bitmask of which joins are flipped\n planSnapshot: this.capturePlanningSnapshot(),\n joinStates: this.joins.map(j => {\n const info = j.getDebugInfo();\n return {\n join: info.name,\n type: info.type,\n };\n }),\n });\n }\n\n // Track best plan\n if (totalCost < bestCost) {\n bestCost = totalCost;\n bestPlan = this.capturePlanningSnapshot();\n bestAttemptNumber = pattern;\n }\n }\n\n // Restore best plan\n if (bestPlan) {\n this.restorePlanningSnapshot(bestPlan);\n // Propagate constraints to ensure all derived state is consistent\n this.propagateConstraints(planDebugger);\n\n if (planDebugger) {\n planDebugger.log({\n type: 'best-plan-selected',\n bestAttemptNumber,\n totalCost: bestCost,\n flipPattern: bestAttemptNumber, // The best attempt number is also the flip pattern\n joinStates: this.joins.map(j => ({\n join: j.getName(),\n type: j.type,\n })),\n });\n }\n } else {\n assert(\n numPatterns === 0,\n 'no plan was found but flippable joins did exist!',\n );\n }\n }\n}\n\n/**\n * Build cache of FO→FI relationships and joins between them.\n * Called once at the start of planning to avoid redundant BFS traversals.\n */\nfunction buildFOFICache(graph: PlannerGraph): Map<PlannerFanOut, FOFIInfo> {\n const cache = new Map<PlannerFanOut, FOFIInfo>();\n\n for (const fo of graph.fanOuts) {\n const info = findFIAndJoins(fo);\n cache.set(fo, info);\n }\n\n return cache;\n}\n\n/**\n * Check if any joins downstream of a FanOut (before reaching FanIn) are flipped.\n * If so, convert the FO to UFO and the FI to UFI.\n *\n * This must be called after join flipping and before propagateConstraints.\n */\nfunction checkAndConvertFOFI(fofiCache: Map<PlannerFanOut, FOFIInfo>): void {\n for (const [fo, info] of fofiCache) {\n const hasFlippedJoin = info.joinsBetween.some(j => j.type === 'flipped');\n if (info.fi && hasFlippedJoin) {\n fo.convertToUFO();\n info.fi.convertToUFI();\n }\n }\n}\n\n/**\n * Traverse from a FanOut through its outputs to find the corresponding FanIn\n * and collect all joins along the way.\n */\nfunction findFIAndJoins(fo: PlannerFanOut): FOFIInfo {\n const joinsBetween: PlannerJoin[] = [];\n let fi: PlannerFanIn | undefined = undefined;\n\n // BFS through FO outputs to find FI and collect joins\n const queue: PlannerNode[] = [...fo.outputs];\n const visited = new Set<PlannerNode>();\n\n while (queue.length > 0) {\n const node = must(queue.shift());\n if (visited.has(node)) continue;\n visited.add(node);\n\n switch (node.kind) {\n case 'join':\n joinsBetween.push(node);\n queue.push(node.output);\n break;\n case 'fan-out':\n // Nested FO - traverse its outputs\n queue.push(...node.outputs);\n break;\n case 'fan-in':\n // Found the FI - this is the boundary, don't traverse further\n fi = node;\n break;\n case 'connection':\n // Shouldn't happen in a well-formed graph\n break;\n case 'terminus':\n // Reached the end without finding FI\n break;\n }\n }\n\n return {fi, joinsBetween};\n}\n\n/**\n * Propagate unlimiting to all flipped joins in the graph.\n * When a join is flipped, its child becomes the outer loop and should no longer\n * be limited by EXISTS semantics.\n *\n * This must be called after join flipping and before propagateConstraints.\n */\nfunction propagateUnlimitForFlippedJoins(graph: PlannerGraph): void {\n for (const join of graph.joins) {\n if (join.type === 'flipped') {\n join.propagateUnlimit();\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"planner-graph.js","names":["#sources","#terminus","#validateSnapshotShape","#restoreConnections","#restoreJoins","#restoreFanNodes"],"sources":["../../../../../zql/src/planner/planner-graph.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {PlannerConnection} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport type {PlannerFanIn} from './planner-fan-in.ts';\nimport type {PlannerFanOut} from './planner-fan-out.ts';\nimport type {PlannerJoin} from './planner-join.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {PlannerNode} from './planner-node.ts';\nimport {PlannerSource, type ConnectionCostModel} from './planner-source.ts';\nimport type {PlannerTerminus} from './planner-terminus.ts';\n\n/**\n * Captured state of a plan for comparison and restoration.\n */\nexport type PlanState = {\n connections: Array<{limit: number | undefined}>;\n joins: Array<{type: 'semi' | 'flipped'}>;\n fanOuts: Array<{type: 'FO' | 'UFO'}>;\n fanIns: Array<{type: 'FI' | 'UFI'}>;\n connectionConstraints: Array<Map<string, PlannerConstraint | undefined>>;\n};\n\n/**\n * Maximum number of flippable joins to attempt exhaustive enumeration.\n * With n flippable joins, we explore 2^n plans.\n * 10 joins = 1024 plans (~100-200ms), 12 joins = 4096 plans (~400ms - 1 second)\n */\nconst MAX_FLIPPABLE_JOINS = 9;\n\n/**\n * Cached information about FanOut→FanIn relationships.\n * Computed once during planning to avoid redundant BFS traversals.\n */\ntype FOFIInfo = {\n fi: PlannerFanIn | undefined;\n joinsBetween: PlannerJoin[];\n};\n\nexport class PlannerGraph {\n // Sources indexed by table name\n readonly #sources = new Map<string, PlannerSource>();\n\n // The final output node where constraint propagation starts\n #terminus: PlannerTerminus | undefined = undefined;\n\n // Collections of nodes with mutable planning state\n joins: PlannerJoin[] = [];\n fanOuts: PlannerFanOut[] = [];\n fanIns: PlannerFanIn[] = [];\n connections: PlannerConnection[] = [];\n\n /**\n * Reset all planning state back to initial values for another planning pass.\n * Resets only mutable planning state - graph structure is unchanged.\n *\n * This allows replanning the same query graph with different strategies.\n */\n resetPlanningState() {\n for (const j of this.joins) j.reset();\n for (const fo of this.fanOuts) fo.reset();\n for (const fi of this.fanIns) fi.reset();\n for (const c of this.connections) c.reset();\n }\n\n /**\n * Create and register a source (table) in the graph.\n */\n addSource(name: string, model: ConnectionCostModel): PlannerSource {\n assert(\n !this.#sources.has(name),\n `Source ${name} already exists in the graph`,\n );\n const source = new PlannerSource(name, model);\n this.#sources.set(name, source);\n return source;\n }\n\n /**\n * Get a source by table name.\n */\n getSource(name: string): PlannerSource {\n const source = this.#sources.get(name);\n assert(source !== undefined, `Source ${name} not found in the graph`);\n return source;\n }\n\n /**\n * Check if a source exists by table name.\n */\n hasSource(name: string): boolean {\n return this.#sources.has(name);\n }\n\n /**\n * Set the terminus (final output) node of the graph.\n * Constraint propagation starts from this node.\n */\n setTerminus(terminus: PlannerTerminus): void {\n this.#terminus = terminus;\n }\n\n /**\n * Initiate constraint propagation from the terminus node.\n * This sends constraints up through the graph to update\n * connection cost estimates.\n */\n propagateConstraints(planDebugger?: PlanDebugger): void {\n assert(\n this.#terminus !== undefined,\n 'Cannot propagate constraints without a terminus node',\n );\n this.#terminus.propagateConstraints(planDebugger);\n }\n\n /**\n * Calculate total cost of the current plan.\n * Total cost includes both startup cost (one-time, e.g., sorting) and running cost.\n */\n getTotalCost(planDebugger?: PlanDebugger): number {\n const estimate = must(this.#terminus).estimateCost(planDebugger);\n return estimate.cost + estimate.startupCost;\n }\n\n /**\n * Capture a lightweight snapshot of the current planning state.\n * Used for backtracking during multi-start greedy search.\n *\n * Captures mutable state including pinned flags, join types, and\n * constraint maps to avoid needing repropagation on restore.\n *\n * @returns A snapshot that can be restored via restorePlanningSnapshot()\n */\n capturePlanningSnapshot(): PlanState {\n return {\n connections: this.connections.map(c => ({\n limit: c.limit,\n })),\n joins: this.joins.map(j => ({type: j.type})),\n fanOuts: this.fanOuts.map(fo => ({type: fo.type})),\n fanIns: this.fanIns.map(fi => ({type: fi.type})),\n connectionConstraints: this.connections.map(c => c.captureConstraints()),\n };\n }\n\n /**\n * Restore planning state from a previously captured snapshot.\n * Used for backtracking when a planning attempt fails.\n *\n * Restores pinned flags, join types, and constraint maps, eliminating\n * the need for repropagation.\n *\n * @param state - Snapshot created by capturePlanningSnapshot()\n */\n restorePlanningSnapshot(state: PlanState): void {\n this.#validateSnapshotShape(state);\n this.#restoreConnections(state);\n this.#restoreJoins(state);\n this.#restoreFanNodes(state);\n }\n\n /**\n * Validate that snapshot shape matches current graph structure.\n */\n #validateSnapshotShape(state: PlanState): void {\n assert(\n this.connections.length === state.connections.length,\n 'Plan state mismatch: connections',\n );\n assert(\n this.joins.length === state.joins.length,\n 'Plan state mismatch: joins',\n );\n assert(\n this.fanOuts.length === state.fanOuts.length,\n 'Plan state mismatch: fanOuts',\n );\n assert(\n this.fanIns.length === state.fanIns.length,\n 'Plan state mismatch: fanIns',\n );\n assert(\n this.connections.length === state.connectionConstraints.length,\n 'Plan state mismatch: connectionConstraints',\n );\n }\n\n /**\n * Restore connection pinned flags, limits, and constraint maps.\n */\n #restoreConnections(state: PlanState): void {\n for (let i = 0; i < this.connections.length; i++) {\n this.connections[i].limit = state.connections[i].limit;\n this.connections[i].restoreConstraints(state.connectionConstraints[i]);\n }\n }\n\n /**\n * Restore join types and pinned flags.\n */\n #restoreJoins(state: PlanState): void {\n for (let i = 0; i < this.joins.length; i++) {\n const join = this.joins[i];\n const targetState = state.joins[i];\n\n // Reset to initial state first\n join.reset();\n\n // Apply target state\n if (targetState.type === 'flipped' && join.type !== 'flipped') {\n join.flip();\n }\n assert(\n targetState.type === join.type,\n 'join is not in the correct state after reset',\n );\n }\n }\n\n /**\n * Restore FanOut and FanIn types.\n */\n #restoreFanNodes(state: PlanState): void {\n for (let i = 0; i < this.fanOuts.length; i++) {\n const fo = this.fanOuts[i];\n const targetType = state.fanOuts[i].type;\n if (targetType === 'UFO' && fo.type === 'FO') {\n fo.convertToUFO();\n }\n }\n\n for (let i = 0; i < this.fanIns.length; i++) {\n const fi = this.fanIns[i];\n const targetType = state.fanIns[i].type;\n if (targetType === 'UFI' && fi.type === 'FI') {\n fi.convertToUFI();\n }\n }\n }\n\n /**\n * Main planning algorithm using exhaustive join flip enumeration.\n *\n * Enumerates all possible flip patterns for flippable joins (2^n for n flippable joins).\n * Each pattern represents a different query execution plan. We evaluate the cost of each\n * plan and select the one with the lowest cost.\n *\n * Connections are used only for cost estimation - the flip patterns determine the plan.\n * FanOut/FanIn states (FO/UFO and FI/UFI) are automatically derived from join flip states.\n *\n * @param planDebugger - Optional debugger to receive structured events during planning\n * @param lc - Optional logger for warnings\n */\n plan(planDebugger?: PlanDebugger, lc?: LogContext): void {\n // Get all flippable joins\n const flippableJoins = this.joins.filter(j => j.isFlippable());\n\n // Too many flippable joins - skip optimization and run as-is\n if (flippableJoins.length > MAX_FLIPPABLE_JOINS) {\n lc?.warn?.(\n `Query has ${flippableJoins.length} EXISTS checks which would require ` +\n `${2 ** flippableJoins.length} plan evaluations. Skipping optimization.`,\n );\n return;\n }\n\n // Build FO→FI cache once to avoid redundant BFS traversals in each iteration\n const fofiCache = buildFOFICache(this);\n\n const numPatterns =\n flippableJoins.length === 0 ? 0 : 2 ** flippableJoins.length;\n let bestCost = Infinity;\n let bestPlan: PlanState | undefined = undefined;\n let bestAttemptNumber = -1;\n\n // Enumerate all flip patterns\n for (let pattern = 0; pattern < numPatterns; pattern++) {\n // Reset to initial state\n this.resetPlanningState();\n\n if (planDebugger) {\n planDebugger.log({\n type: 'attempt-start',\n attemptNumber: pattern,\n totalAttempts: numPatterns,\n });\n }\n\n // Apply flip pattern (treat pattern as bitmask)\n // Bit i set to 1 means flip join i\n for (let i = 0; i < flippableJoins.length; i++) {\n if (pattern & (1 << i)) {\n flippableJoins[i].flip();\n }\n }\n\n // Derive FO/UFO and FI/UFI states from join flip states\n checkAndConvertFOFI(fofiCache);\n\n // Propagate unlimiting for flipped joins\n propagateUnlimitForFlippedJoins(this);\n\n // Propagate constraints through the graph\n this.propagateConstraints(planDebugger);\n\n if (planDebugger) {\n planDebugger.log({\n type: 'constraints-propagated',\n attemptNumber: pattern,\n connectionConstraints: this.connections.map(c => {\n const constraintCosts = c.getConstraintCostsForDebug();\n const constraintCostsWithoutFanout: Record<\n string,\n Omit<(typeof constraintCosts)[string], 'fanout'>\n > = {};\n for (const [key, cost] of Object.entries(constraintCosts)) {\n constraintCostsWithoutFanout[key] = omitFanout(cost);\n }\n return {\n connection: c.name,\n constraints: c.getConstraintsForDebug(),\n constraintCosts: constraintCostsWithoutFanout,\n };\n }),\n });\n }\n\n // Evaluate this plan\n const totalCost = this.getTotalCost(planDebugger);\n\n if (planDebugger) {\n planDebugger.log({\n type: 'plan-complete',\n attemptNumber: pattern,\n totalCost,\n flipPattern: pattern, // Bitmask of which joins are flipped\n planSnapshot: this.capturePlanningSnapshot(),\n joinStates: this.joins.map(j => {\n const info = j.getDebugInfo();\n return {\n join: info.name,\n type: info.type,\n };\n }),\n });\n }\n\n // Track best plan\n if (totalCost < bestCost) {\n bestCost = totalCost;\n bestPlan = this.capturePlanningSnapshot();\n bestAttemptNumber = pattern;\n }\n }\n\n // Restore best plan\n if (bestPlan) {\n this.restorePlanningSnapshot(bestPlan);\n // Propagate constraints to ensure all derived state is consistent\n this.propagateConstraints(planDebugger);\n\n if (planDebugger) {\n planDebugger.log({\n type: 'best-plan-selected',\n bestAttemptNumber,\n totalCost: bestCost,\n flipPattern: bestAttemptNumber, // The best attempt number is also the flip pattern\n joinStates: this.joins.map(j => ({\n join: j.getName(),\n type: j.type,\n })),\n });\n }\n } else {\n assert(\n numPatterns === 0,\n 'no plan was found but flippable joins did exist!',\n );\n }\n }\n}\n\n/**\n * Build cache of FO→FI relationships and joins between them.\n * Called once at the start of planning to avoid redundant BFS traversals.\n */\nfunction buildFOFICache(graph: PlannerGraph): Map<PlannerFanOut, FOFIInfo> {\n const cache = new Map<PlannerFanOut, FOFIInfo>();\n\n for (const fo of graph.fanOuts) {\n const info = findFIAndJoins(fo);\n cache.set(fo, info);\n }\n\n return cache;\n}\n\n/**\n * Check if any joins downstream of a FanOut (before reaching FanIn) are flipped.\n * If so, convert the FO to UFO and the FI to UFI.\n *\n * This must be called after join flipping and before propagateConstraints.\n */\nfunction checkAndConvertFOFI(fofiCache: Map<PlannerFanOut, FOFIInfo>): void {\n for (const [fo, info] of fofiCache) {\n const hasFlippedJoin = info.joinsBetween.some(j => j.type === 'flipped');\n if (info.fi && hasFlippedJoin) {\n fo.convertToUFO();\n info.fi.convertToUFI();\n }\n }\n}\n\n/**\n * Traverse from a FanOut through its outputs to find the corresponding FanIn\n * and collect all joins along the way.\n */\nfunction findFIAndJoins(fo: PlannerFanOut): FOFIInfo {\n const joinsBetween: PlannerJoin[] = [];\n let fi: PlannerFanIn | undefined = undefined;\n\n // BFS through FO outputs to find FI and collect joins\n const queue: PlannerNode[] = [...fo.outputs];\n const visited = new Set<PlannerNode>();\n\n while (queue.length > 0) {\n const node = must(queue.shift());\n if (visited.has(node)) continue;\n visited.add(node);\n\n switch (node.kind) {\n case 'join':\n joinsBetween.push(node);\n queue.push(node.output);\n break;\n case 'fan-out':\n // Nested FO - traverse its outputs\n queue.push(...node.outputs);\n break;\n case 'fan-in':\n // Found the FI - this is the boundary, don't traverse further\n fi = node;\n break;\n case 'connection':\n // Shouldn't happen in a well-formed graph\n break;\n case 'terminus':\n // Reached the end without finding FI\n break;\n }\n }\n\n return {fi, joinsBetween};\n}\n\n/**\n * Propagate unlimiting to all flipped joins in the graph.\n * When a join is flipped, its child becomes the outer loop and should no longer\n * be limited by EXISTS semantics.\n *\n * This must be called after join flipping and before propagateConstraints.\n */\nfunction propagateUnlimitForFlippedJoins(graph: PlannerGraph): void {\n for (const join of graph.joins) {\n if (join.type === 'flipped') {\n join.propagateUnlimit();\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sql-inline.d.ts","sourceRoot":"","sources":["../../../../../zqlite/src/internal/sql-inline.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAoE7C;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAEnD"}
1
+ {"version":3,"file":"sql-inline.d.ts","sourceRoot":"","sources":["../../../../../zqlite/src/internal/sql-inline.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAmE7C;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAEnD"}
@@ -1 +1 @@
1
- {"version":3,"file":"sql-inline.js","names":[],"sources":["../../../../../zqlite/src/internal/sql-inline.ts"],"sourcesContent":["import type {FormatConfig} from '@databases/sql';\nimport type {SQLQuery} from '@databases/sql';\nimport {escapeSQLiteIdentifier} from '@databases/escape-identifier';\n\n/**\n * Escapes a SQLite string value by doubling single quotes.\n * SQLite uses single quotes for string literals and doubles them for escaping.\n */\nfunction escapeSQLiteString(str: string): string {\n return `'${str.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Formats a value for inline inclusion in SQL (not as a placeholder).\n *\n * WARNING: This should ONLY be used for cost estimation in the query planner,\n * not for user-facing queries. All production queries must use parameterized\n * statements to prevent SQL injection.\n *\n * @param value The value to inline into SQL\n * @returns SQL literal representation of the value\n */\nfunction inlineValue(value: unknown): string {\n if (value === null) {\n return 'NULL';\n }\n if (typeof value === 'string') {\n return escapeSQLiteString(value);\n }\n if (typeof value === 'number') {\n return String(value);\n }\n if (typeof value === 'boolean') {\n // SQLite uses 1 and 0 for booleans\n return value ? '1' : '0';\n }\n if (Array.isArray(value)) {\n // For arrays, use JSON representation (same as query-builder.ts does for IN clauses)\n return escapeSQLiteString(JSON.stringify(value));\n }\n // For objects/other JSON types\n return escapeSQLiteString(JSON.stringify(value));\n}\n\n/**\n * Format configuration that inlines values directly into SQL instead of using placeholders.\n *\n * This is used ONLY for cost estimation in the SQLite cost model, where we want SQLite's\n * query planner to see actual values to make better decisions about index usage and query plans.\n *\n * Production code must use the standard parameterized format from sql.ts.\n */\nconst sqliteInlineFormat: FormatConfig = {\n escapeIdentifier: str => escapeSQLiteIdentifier(str),\n formatValue: value =>\n // undefined is our signal to use a placeholder\n // IMPORTANT. Changing this will break the planner as it will assume `NULL`\n // for constraints!\n value === undefined\n ? {\n placeholder: '?',\n value,\n }\n : {\n placeholder: inlineValue(value),\n value: undefined, // No binding needed since value is inlined\n },\n};\n\n/**\n * Compiles a SQL query with values inlined directly into the SQL string.\n *\n * WARNING: This should ONLY be used for cost estimation in the query planner.\n * Never use this for user-facing queries - always use the standard `compile()`\n * function from sql.ts which uses parameterized queries.\n *\n * @param sql The SQL query to compile\n * @returns SQL string with values inlined\n */\nexport function compileInline(sql: SQLQuery): string {\n return sql.format(sqliteInlineFormat).text;\n}\n"],"mappings":";;;;;;AAQA,SAAS,mBAAmB,KAAqB;AAC/C,QAAO,IAAI,IAAI,QAAQ,MAAM,KAAK,CAAC;;;;;;;;;;;;AAarC,SAAS,YAAY,OAAwB;AAC3C,KAAI,UAAU,KACZ,QAAO;AAET,KAAI,OAAO,UAAU,SACnB,QAAO,mBAAmB,MAAM;AAElC,KAAI,OAAO,UAAU,SACnB,QAAO,OAAO,MAAM;AAEtB,KAAI,OAAO,UAAU,UAEnB,QAAO,QAAQ,MAAM;AAEvB,KAAI,MAAM,QAAQ,MAAM,CAEtB,QAAO,mBAAmB,KAAK,UAAU,MAAM,CAAC;AAGlD,QAAO,mBAAmB,KAAK,UAAU,MAAM,CAAC;;;;;;;;;;AAWlD,IAAM,qBAAmC;CACvC,mBAAkB,QAAO,uBAAuB,IAAI;CACpD,cAAa,UAIX,UAAU,KAAA,IACN;EACE,aAAa;EACb;EACD,GACD;EACE,aAAa,YAAY,MAAM;EAC/B,OAAO,KAAA;EACR;CACR;;;;;;;;;;;AAYD,SAAgB,cAAc,KAAuB;AACnD,QAAO,IAAI,OAAO,mBAAmB,CAAC"}
1
+ {"version":3,"file":"sql-inline.js","names":[],"sources":["../../../../../zqlite/src/internal/sql-inline.ts"],"sourcesContent":["import {escapeSQLiteIdentifier} from '@databases/escape-identifier';\nimport type {FormatConfig} from '@databases/sql';\nimport type {SQLQuery} from '@databases/sql';\n\n/**\n * Escapes a SQLite string value by doubling single quotes.\n * SQLite uses single quotes for string literals and doubles them for escaping.\n */\nfunction escapeSQLiteString(str: string): string {\n return `'${str.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Formats a value for inline inclusion in SQL (not as a placeholder).\n *\n * WARNING: This should ONLY be used for cost estimation in the query planner,\n * not for user-facing queries. All production queries must use parameterized\n * statements to prevent SQL injection.\n *\n * @param value The value to inline into SQL\n * @returns SQL literal representation of the value\n */\nfunction inlineValue(value: unknown): string {\n if (value === null) {\n return 'NULL';\n }\n if (typeof value === 'string') {\n return escapeSQLiteString(value);\n }\n if (typeof value === 'number') {\n return String(value);\n }\n if (typeof value === 'boolean') {\n // SQLite uses 1 and 0 for booleans\n return value ? '1' : '0';\n }\n if (Array.isArray(value)) {\n // For arrays, use JSON representation (same as query-builder.ts does for IN clauses)\n return escapeSQLiteString(JSON.stringify(value));\n }\n // For objects/other JSON types\n return escapeSQLiteString(JSON.stringify(value));\n}\n\n/**\n * Format configuration that inlines values directly into SQL instead of using placeholders.\n *\n * This is used ONLY for cost estimation in the SQLite cost model, where we want SQLite's\n * query planner to see actual values to make better decisions about index usage and query plans.\n *\n * Production code must use the standard parameterized format from sql.ts.\n */\nconst sqliteInlineFormat: FormatConfig = {\n escapeIdentifier: str => escapeSQLiteIdentifier(str),\n formatValue: value =>\n // undefined is our signal to use a placeholder\n // IMPORTANT. Changing this will break the planner as it will assume `NULL`\n // for constraints!\n value === undefined\n ? {\n placeholder: '?',\n value,\n }\n : {\n placeholder: inlineValue(value),\n value: undefined, // No binding needed since value is inlined\n },\n};\n\n/**\n * Compiles a SQL query with values inlined directly into the SQL string.\n *\n * WARNING: This should ONLY be used for cost estimation in the query planner.\n * Never use this for user-facing queries - always use the standard `compile()`\n * function from sql.ts which uses parameterized queries.\n *\n * @param sql The SQL query to compile\n * @returns SQL string with values inlined\n */\nexport function compileInline(sql: SQLQuery): string {\n return sql.format(sqliteInlineFormat).text;\n}\n"],"mappings":";;;;;;AAQA,SAAS,mBAAmB,KAAqB;AAC/C,QAAO,IAAI,IAAI,QAAQ,MAAM,KAAK,CAAC;;;;;;;;;;;;AAarC,SAAS,YAAY,OAAwB;AAC3C,KAAI,UAAU,KACZ,QAAO;AAET,KAAI,OAAO,UAAU,SACnB,QAAO,mBAAmB,MAAM;AAElC,KAAI,OAAO,UAAU,SACnB,QAAO,OAAO,MAAM;AAEtB,KAAI,OAAO,UAAU,UAEnB,QAAO,QAAQ,MAAM;AAEvB,KAAI,MAAM,QAAQ,MAAM,CAEtB,QAAO,mBAAmB,KAAK,UAAU,MAAM,CAAC;AAGlD,QAAO,mBAAmB,KAAK,UAAU,MAAM,CAAC;;;;;;;;;;AAWlD,IAAM,qBAAmC;CACvC,mBAAkB,QAAO,uBAAuB,IAAI;CACpD,cAAa,UAIX,UAAU,KAAA,IACN;EACE,aAAa;EACb;EACD,GACD;EACE,aAAa,YAAY,MAAM;EAC/B,OAAO,KAAA;EACR;CACR;;;;;;;;;;;AAYD,SAAgB,cAAc,KAAuB;AACnD,QAAO,IAAI,OAAO,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"query-builder.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/query-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAGT,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACV,MAAM,uCAAuC,CAAC;AAG/C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,+BAA+B,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,CACvC,SAAS,EACT;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAC,CAC7B,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,mBAAmB,GAAG,SAAS,EACxC,KAAK,EAAE,QAAQ,GAAG,SAAS,EAC3B,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,KAAK,EAAE,KAAK,GAAG,SAAS,YAyBzB;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,cAcrC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,CAmBxE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,QAAQ,CAuBnE;AAyDD,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAWjE"}
1
+ {"version":3,"file":"query-builder.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/query-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAE7C,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAGT,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACV,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,+BAA+B,CAAC;AAGzD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,CACvC,SAAS,EACT;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAC,CAC7B,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,mBAAmB,GAAG,SAAS,EACxC,KAAK,EAAE,QAAQ,GAAG,SAAS,EAC3B,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,KAAK,EAAE,KAAK,GAAG,SAAS,YAyBzB;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,cAcrC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,CAmBxE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,QAAQ,CAuBnE;AAyDD,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAWjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"query-builder.js","names":[],"sources":["../../../../zqlite/src/query-builder.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport {assert} from '../../shared/src/asserts.ts';\nimport {sql} from './internal/sql.ts';\nimport type {Constraint} from '../../zql/src/ivm/constraint.ts';\nimport type {Start} from '../../zql/src/ivm/operator.ts';\n\n/**\n * Condition type without correlated subqueries.\n * This matches the output of transformFilters from zql/builder/filter.ts\n */\nexport type NoSubqueryCondition = Exclude<\n Condition,\n {type: 'correlatedSubquery'}\n>;\n\nexport function buildSelectQuery(\n tableName: string,\n columns: Record<string, SchemaValue>,\n constraint: Constraint | undefined,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n reverse: boolean | undefined,\n start: Start | undefined,\n) {\n let query = sql`SELECT ${sql.join(\n Object.keys(columns).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(tableName)}`;\n const constraints: SQLQuery[] = constraintsToSQL(constraint, columns);\n\n if (start) {\n assert(order !== undefined, 'start requires ordering');\n constraints.push(gatherStartConstraints(start, reverse, order, columns));\n }\n\n if (filters) {\n constraints.push(filtersToSQL(filters));\n }\n\n if (constraints.length > 0) {\n query = sql`${query} WHERE ${sql.join(constraints, sql` AND `)}`;\n }\n\n if (order && order.length > 0) {\n return sql`${query} ${orderByToSQL(order, !!reverse)}`;\n }\n return query;\n}\n\nexport function constraintsToSQL(\n constraint: Constraint | undefined,\n columns: Record<string, SchemaValue>,\n) {\n if (!constraint) {\n return [];\n }\n\n const constraints: SQLQuery[] = [];\n for (const [key, value] of Object.entries(constraint)) {\n constraints.push(\n sql`${sql.ident(key)} = ${toSQLiteType(value, columns[key].type)}`,\n );\n }\n\n return constraints;\n}\n\nexport function orderByToSQL(order: Ordering, reverse: boolean): SQLQuery {\n if (reverse) {\n return sql`ORDER BY ${sql.join(\n order.map(\n s =>\n sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(\n s[1] === 'asc' ? 'desc' : 'asc',\n )}`,\n ),\n sql`, `,\n )}`;\n } else {\n return sql`ORDER BY ${sql.join(\n order.map(\n s => sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(s[1])}`,\n ),\n sql`, `,\n )}`;\n }\n}\n\n/**\n * Converts filters (conditions) to SQL WHERE clause.\n * This applies all filters present in the AST for a query to the source.\n */\nexport function filtersToSQL(filters: NoSubqueryCondition): SQLQuery {\n switch (filters.type) {\n case 'simple':\n return simpleConditionToSQL(filters);\n case 'and':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` AND `,\n )})`\n : sql`TRUE`;\n case 'or':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` OR `,\n )})`\n : sql`FALSE`;\n }\n}\n\nfunction simpleConditionToSQL(filter: SimpleCondition): SQLQuery {\n const {op} = filter;\n if (op === 'IN' || op === 'NOT IN') {\n switch (filter.right.type) {\n case 'literal':\n return sql`${valuePositionToSQL(\n filter.left,\n )} ${sql.__dangerous__rawValue(\n filter.op,\n )} (SELECT value FROM json_each(${JSON.stringify(\n filter.right.value,\n )}))`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n }\n return sql`${valuePositionToSQL(filter.left)} ${sql.__dangerous__rawValue(\n // SQLite's LIKE operator is case-insensitive by default, so we\n // convert ILIKE to LIKE and NOT ILIKE to NOT LIKE.\n filter.op === 'ILIKE'\n ? 'LIKE'\n : filter.op === 'NOT ILIKE'\n ? 'NOT LIKE'\n : filter.op,\n )} ${valuePositionToSQL(filter.right)}`;\n}\n\nfunction valuePositionToSQL(value: ValuePosition): SQLQuery {\n switch (value.type) {\n case 'column':\n return sql.ident(value.name);\n case 'literal':\n return sql`${toSQLiteType(value.value, getJsType(value.value))}`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n}\n\nfunction getJsType(value: unknown): ValueType {\n if (value === null) {\n return 'null';\n }\n return typeof value === 'string'\n ? 'string'\n : typeof value === 'number'\n ? 'number'\n : typeof value === 'boolean'\n ? 'boolean'\n : 'json';\n}\n\nexport function toSQLiteType(v: unknown, type: ValueType): unknown {\n switch (type) {\n case 'boolean':\n return v === null ? null : v ? 1 : 0;\n case 'number':\n case 'string':\n case 'null':\n return v;\n case 'json':\n return JSON.stringify(v);\n }\n}\n\n/**\n * The ordering could be complex such as:\n * `ORDER BY a ASC, b DESC, c ASC`\n *\n * In those cases, we need to encode the constraints as various\n * `OR` clauses.\n *\n * E.g.,\n *\n * to get the row after (a = 1, b = 2, c = 3) would be:\n *\n * `WHERE a > 1 OR (a = 1 AND b < 2) OR (a = 1 AND b = 2 AND c > 3)`\n *\n * - after vs before flips the comparison operators.\n * - inclusive adds a final `OR` clause for the exact match.\n */\nfunction gatherStartConstraints(\n start: Start,\n reverse: boolean | undefined,\n order: Ordering,\n columnTypes: Record<string, SchemaValue>,\n): SQLQuery {\n const constraints: SQLQuery[] = [];\n const {row: from, basis} = start;\n\n for (let i = 0; i < order.length; i++) {\n const group: SQLQuery[] = [];\n const [iField, iDirection] = order[i];\n for (let j = 0; j <= i; j++) {\n if (j === i) {\n const constraintValue = toSQLiteType(\n from[iField],\n columnTypes[iField].type,\n );\n if (iDirection === 'asc') {\n if (!reverse) {\n group.push(\n sql`(${constraintValue} IS NULL OR ${sql.ident(iField)} > ${constraintValue})`,\n );\n } else {\n reverse satisfies true;\n group.push(\n sql`(${sql.ident(iField)} IS NULL OR ${sql.ident(iField)} < ${constraintValue})`,\n );\n }\n } else {\n iDirection satisfies 'desc';\n if (!reverse) {\n group.push(\n sql`(${sql.ident(iField)} IS NULL OR ${sql.ident(iField)} < ${constraintValue})`,\n );\n } else {\n reverse satisfies true;\n group.push(\n sql`(${constraintValue} IS NULL OR ${sql.ident(iField)} > ${constraintValue})`,\n );\n }\n }\n } else {\n const [jField] = order[j];\n group.push(\n sql`${sql.ident(jField)} IS ${toSQLiteType(\n from[jField],\n columnTypes[jField].type,\n )}`,\n );\n }\n }\n constraints.push(sql`(${sql.join(group, sql` AND `)})`);\n }\n\n if (basis === 'at') {\n constraints.push(\n sql`(${sql.join(\n order.map(\n s =>\n sql`${sql.ident(s[0])} IS ${toSQLiteType(\n from[s[0]],\n columnTypes[s[0]].type,\n )}`,\n ),\n sql` AND `,\n )})`,\n );\n }\n\n return sql`(${sql.join(constraints, sql` OR `)})`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"query-builder.js","names":[],"sources":["../../../../zqlite/src/query-builder.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport {assert} from '../../shared/src/asserts.ts';\nimport type {\n Condition,\n Ordering,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport type {Constraint} from '../../zql/src/ivm/constraint.ts';\nimport type {Start} from '../../zql/src/ivm/operator.ts';\nimport {sql} from './internal/sql.ts';\n\n/**\n * Condition type without correlated subqueries.\n * This matches the output of transformFilters from zql/builder/filter.ts\n */\nexport type NoSubqueryCondition = Exclude<\n Condition,\n {type: 'correlatedSubquery'}\n>;\n\nexport function buildSelectQuery(\n tableName: string,\n columns: Record<string, SchemaValue>,\n constraint: Constraint | undefined,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n reverse: boolean | undefined,\n start: Start | undefined,\n) {\n let query = sql`SELECT ${sql.join(\n Object.keys(columns).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(tableName)}`;\n const constraints: SQLQuery[] = constraintsToSQL(constraint, columns);\n\n if (start) {\n assert(order !== undefined, 'start requires ordering');\n constraints.push(gatherStartConstraints(start, reverse, order, columns));\n }\n\n if (filters) {\n constraints.push(filtersToSQL(filters));\n }\n\n if (constraints.length > 0) {\n query = sql`${query} WHERE ${sql.join(constraints, sql` AND `)}`;\n }\n\n if (order && order.length > 0) {\n return sql`${query} ${orderByToSQL(order, !!reverse)}`;\n }\n return query;\n}\n\nexport function constraintsToSQL(\n constraint: Constraint | undefined,\n columns: Record<string, SchemaValue>,\n) {\n if (!constraint) {\n return [];\n }\n\n const constraints: SQLQuery[] = [];\n for (const [key, value] of Object.entries(constraint)) {\n constraints.push(\n sql`${sql.ident(key)} = ${toSQLiteType(value, columns[key].type)}`,\n );\n }\n\n return constraints;\n}\n\nexport function orderByToSQL(order: Ordering, reverse: boolean): SQLQuery {\n if (reverse) {\n return sql`ORDER BY ${sql.join(\n order.map(\n s =>\n sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(\n s[1] === 'asc' ? 'desc' : 'asc',\n )}`,\n ),\n sql`, `,\n )}`;\n } else {\n return sql`ORDER BY ${sql.join(\n order.map(\n s => sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(s[1])}`,\n ),\n sql`, `,\n )}`;\n }\n}\n\n/**\n * Converts filters (conditions) to SQL WHERE clause.\n * This applies all filters present in the AST for a query to the source.\n */\nexport function filtersToSQL(filters: NoSubqueryCondition): SQLQuery {\n switch (filters.type) {\n case 'simple':\n return simpleConditionToSQL(filters);\n case 'and':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` AND `,\n )})`\n : sql`TRUE`;\n case 'or':\n return filters.conditions.length > 0\n ? sql`(${sql.join(\n filters.conditions.map(condition =>\n filtersToSQL(condition as NoSubqueryCondition),\n ),\n sql` OR `,\n )})`\n : sql`FALSE`;\n }\n}\n\nfunction simpleConditionToSQL(filter: SimpleCondition): SQLQuery {\n const {op} = filter;\n if (op === 'IN' || op === 'NOT IN') {\n switch (filter.right.type) {\n case 'literal':\n return sql`${valuePositionToSQL(\n filter.left,\n )} ${sql.__dangerous__rawValue(\n filter.op,\n )} (SELECT value FROM json_each(${JSON.stringify(\n filter.right.value,\n )}))`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n }\n return sql`${valuePositionToSQL(filter.left)} ${sql.__dangerous__rawValue(\n // SQLite's LIKE operator is case-insensitive by default, so we\n // convert ILIKE to LIKE and NOT ILIKE to NOT LIKE.\n filter.op === 'ILIKE'\n ? 'LIKE'\n : filter.op === 'NOT ILIKE'\n ? 'NOT LIKE'\n : filter.op,\n )} ${valuePositionToSQL(filter.right)}`;\n}\n\nfunction valuePositionToSQL(value: ValuePosition): SQLQuery {\n switch (value.type) {\n case 'column':\n return sql.ident(value.name);\n case 'literal':\n return sql`${toSQLiteType(value.value, getJsType(value.value))}`;\n case 'static':\n throw new Error(\n 'Static parameters must be replaced before conversion to SQL',\n );\n }\n}\n\nfunction getJsType(value: unknown): ValueType {\n if (value === null) {\n return 'null';\n }\n return typeof value === 'string'\n ? 'string'\n : typeof value === 'number'\n ? 'number'\n : typeof value === 'boolean'\n ? 'boolean'\n : 'json';\n}\n\nexport function toSQLiteType(v: unknown, type: ValueType): unknown {\n switch (type) {\n case 'boolean':\n return v === null ? null : v ? 1 : 0;\n case 'number':\n case 'string':\n case 'null':\n return v;\n case 'json':\n return JSON.stringify(v);\n }\n}\n\n/**\n * The ordering could be complex such as:\n * `ORDER BY a ASC, b DESC, c ASC`\n *\n * In those cases, we need to encode the constraints as various\n * `OR` clauses.\n *\n * E.g.,\n *\n * to get the row after (a = 1, b = 2, c = 3) would be:\n *\n * `WHERE a > 1 OR (a = 1 AND b < 2) OR (a = 1 AND b = 2 AND c > 3)`\n *\n * - after vs before flips the comparison operators.\n * - inclusive adds a final `OR` clause for the exact match.\n */\nfunction gatherStartConstraints(\n start: Start,\n reverse: boolean | undefined,\n order: Ordering,\n columnTypes: Record<string, SchemaValue>,\n): SQLQuery {\n const constraints: SQLQuery[] = [];\n const {row: from, basis} = start;\n\n for (let i = 0; i < order.length; i++) {\n const group: SQLQuery[] = [];\n const [iField, iDirection] = order[i];\n for (let j = 0; j <= i; j++) {\n if (j === i) {\n const constraintValue = toSQLiteType(\n from[iField],\n columnTypes[iField].type,\n );\n if (iDirection === 'asc') {\n if (!reverse) {\n group.push(\n sql`(${constraintValue} IS NULL OR ${sql.ident(iField)} > ${constraintValue})`,\n );\n } else {\n reverse satisfies true;\n group.push(\n sql`(${sql.ident(iField)} IS NULL OR ${sql.ident(iField)} < ${constraintValue})`,\n );\n }\n } else {\n iDirection satisfies 'desc';\n if (!reverse) {\n group.push(\n sql`(${sql.ident(iField)} IS NULL OR ${sql.ident(iField)} < ${constraintValue})`,\n );\n } else {\n reverse satisfies true;\n group.push(\n sql`(${constraintValue} IS NULL OR ${sql.ident(iField)} > ${constraintValue})`,\n );\n }\n }\n } else {\n const [jField] = order[j];\n group.push(\n sql`${sql.ident(jField)} IS ${toSQLiteType(\n from[jField],\n columnTypes[jField].type,\n )}`,\n );\n }\n }\n constraints.push(sql`(${sql.join(group, sql` AND `)})`);\n }\n\n if (basis === 'at') {\n constraints.push(\n sql`(${sql.join(\n order.map(\n s =>\n sql`${sql.ident(s[0])} IS ${toSQLiteType(\n from[s[0]],\n columnTypes[s[0]].type,\n )}`,\n ),\n sql` AND `,\n )})`,\n );\n }\n\n return sql`(${sql.join(constraints, sql` OR `)})`;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"table-source.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/table-source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,+BAA+B,CAAC;AAK7D,OAAO,KAAK,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAC,GAAG,EAAQ,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACV,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,yCAAyC,CAAC;AAgB3E,OAAO,EACL,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,6BAA6B,CAAC;AAExD,OAAO,KAAK,EAAC,QAAQ,EAAY,MAAM,SAAS,CAAC;AAoBjD;;;;;;;;;;;;;GAaG;AACH,qBAAa,WAAY,YAAW,MAAM;;IAexC;;;;;OAKG;gBAED,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,WAAW,gBAAc;IAiB3B,IAAI,WAAW;;;;MAMd;IAED;;;OAGG;IACH,KAAK,CAAC,EAAE,EAAE,QAAQ;IA4FlB,OAAO,CACL,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,OAAO,CAAC,EAAE,SAAS,EACnB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAC3B,KAAK,CAAC,EAAE,aAAa;IA8CvB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IAoHzB,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC;IAQ3C,OAAO,CAAC,MAAM,EAAE,YAAY;IA4F7B;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,SAAS;CA8BrC;AA6BD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACvC,SAAS,OAAO,EAAE,CAEpB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,wCAa/C;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACvC,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,GAChB,GAAG,CAaL;AAwCD,qBAAa,qBAAsB,SAAQ,KAAK;CAAG"}
1
+ {"version":3,"file":"table-source.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/table-source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,+BAA+B,CAAC;AAK7D,OAAO,KAAK,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAC,GAAG,EAAQ,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACV,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,yCAAyC,CAAC;AAkB3E,OAAO,EACL,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,6BAA6B,CAAC;AAExD,OAAO,KAAK,EAAC,QAAQ,EAAY,MAAM,SAAS,CAAC;AAoBjD;;;;;;;;;;;;;GAaG;AACH,qBAAa,WAAY,YAAW,MAAM;;IAexC;;;;;OAKG;gBAED,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,WAAW,gBAAc;IAiB3B,IAAI,WAAW;;;;MAMd;IAED;;;OAGG;IACH,KAAK,CAAC,EAAE,EAAE,QAAQ;IA4FlB,OAAO,CACL,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,OAAO,CAAC,EAAE,SAAS,EACnB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAC3B,KAAK,CAAC,EAAE,aAAa;IA8CvB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IAoHzB,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC;IAQ3C,OAAO,CAAC,MAAM,EAAE,YAAY;IAqG7B;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,SAAS;CA8BrC;AA6BD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACvC,SAAS,OAAO,EAAE,CAEpB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,wCAa/C;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACvC,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,GAChB,GAAG,CAaL;AAwCD,qBAAa,qBAAsB,SAAQ,KAAK;CAAG"}
@@ -184,24 +184,24 @@ var TableSource = class {
184
184
  yield* genPushAndWriteWithSplitEdit(this.#connections, change, exists, setOverlay, writeChange, () => ++this.#pushEpoch);
185
185
  }
186
186
  #writeChange(change) {
187
- switch (change.type) {
188
- case "add":
189
- this.#stmts.insert.run(...toSQLiteTypes(Object.keys(this.#columns), change.row, this.#columns));
187
+ switch (change[0]) {
188
+ case 0:
189
+ this.#stmts.insert.run(...toSQLiteTypes(Object.keys(this.#columns), change[1], this.#columns));
190
190
  break;
191
- case "remove":
192
- this.#stmts.delete.run(...toSQLiteTypes(this.#primaryKey, change.row, this.#columns));
191
+ case 1:
192
+ this.#stmts.delete.run(...toSQLiteTypes(this.#primaryKey, change[1], this.#columns));
193
193
  break;
194
- case "edit":
195
- if (canUseUpdate(change.oldRow, change.row, this.#columns, this.#primaryKey)) {
194
+ case 2:
195
+ if (canUseUpdate(change[2], change[1], this.#columns, this.#primaryKey)) {
196
196
  const mergedRow = {
197
- ...change.oldRow,
198
- ...change.row
197
+ ...change[2],
198
+ ...change[1]
199
199
  };
200
200
  const params = [...nonPrimaryValues(this.#columns, this.#primaryKey, mergedRow), ...toSQLiteTypes(this.#primaryKey, mergedRow, this.#columns)];
201
201
  must(this.#stmts.update).run(params);
202
202
  } else {
203
- this.#stmts.delete.run(...toSQLiteTypes(this.#primaryKey, change.oldRow, this.#columns));
204
- this.#stmts.insert.run(...toSQLiteTypes(Object.keys(this.#columns), change.row, this.#columns));
203
+ this.#stmts.delete.run(...toSQLiteTypes(this.#primaryKey, change[2], this.#columns));
204
+ this.#stmts.insert.run(...toSQLiteTypes(Object.keys(this.#columns), change[1], this.#columns));
205
205
  }
206
206
  break;
207
207
  default: unreachable(change);
@@ -1 +1 @@
1
- {"version":3,"file":"table-source.js","names":["#dbCache","#connections","#table","#columns","#uniqueIndexes","#primaryKey","#logConfig","#lc","#shouldYield","#stmts","#getStatementsFor","#allColumns","#fetch","#getSchema","#requestToSQL","#mapFromSQLiteTypes","#overlay","#writeChange","#pushEpoch","#getRowStmtCache","#getRowStmt"],"sources":["../../../../zqlite/src/table-source.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport type {LogContext} from '@rocicorp/logger';\nimport SQLite3Database from '@rocicorp/zero-sqlite3';\nimport type {LogConfig} from '../../otel/src/log-options.ts';\nimport {timeSampled} from '../../otel/src/maybe-time.ts';\nimport {assert, unreachable} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {Writable} from '../../shared/src/writable.ts';\nimport type {Condition, Ordering} 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 {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport type {DebugDelegate} from '../../zql/src/builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n} from '../../zql/src/builder/filter.ts';\nimport {makeComparator, type Node} from '../../zql/src/ivm/data.ts';\nimport {\n generateWithOverlay,\n generateWithOverlayUnordered,\n generateWithStart,\n genPushAndWriteWithSplitEdit,\n type Connection,\n type Overlay,\n} from '../../zql/src/ivm/memory-source.ts';\nimport {type FetchRequest} from '../../zql/src/ivm/operator.ts';\nimport type {SourceSchema} from '../../zql/src/ivm/schema.ts';\nimport {\n type Source,\n type SourceChange,\n type SourceInput,\n} from '../../zql/src/ivm/source.ts';\nimport type {Stream} from '../../zql/src/ivm/stream.ts';\nimport {assertOrderingIncludesPK} from '../../zql/src/query/complete-ordering.ts';\nimport type {Database, Statement} from './db.ts';\nimport {compile, format, sql} from './internal/sql.ts';\nimport {StatementCache} from './internal/statement-cache.ts';\nimport {\n buildSelectQuery,\n toSQLiteType,\n type NoSubqueryCondition,\n} from './query-builder.ts';\n\ntype Statements = {\n readonly cache: StatementCache;\n readonly insert: Statement;\n readonly delete: Statement;\n readonly update: Statement | undefined;\n readonly checkExists: Statement;\n readonly getExisting: Statement;\n};\n\nlet eventCount = 0;\n\n/**\n * A source that is backed by a SQLite table.\n *\n * Values are written to the backing table _after_ being vended by the source.\n *\n * This ordering of events is to ensure self joins function properly. That is,\n * we can't reveal a value to an output before it has been pushed to that output.\n *\n * The code is fairly straightforward except for:\n * 1. Dealing with a `fetch` that has a basis of `before`.\n * 2. Dealing with compound orders that have differing directions (a ASC, b DESC, c ASC)\n *\n * See comments in relevant functions for more details.\n */\nexport class TableSource implements Source {\n readonly #dbCache = new WeakMap<Database, Statements>();\n readonly #connections: Connection[] = [];\n readonly #table: string;\n readonly #columns: Record<string, SchemaValue>;\n // Maps sorted columns JSON string (e.g. '[\"a\",\"b\"]) to Set of columns.\n readonly #uniqueIndexes: Map<string, Set<string>>;\n readonly #primaryKey: PrimaryKey;\n readonly #logConfig: LogConfig;\n readonly #lc: LogContext;\n readonly #shouldYield: () => boolean;\n #stmts: Statements;\n #overlay?: Overlay | undefined;\n #pushEpoch = 0;\n\n /**\n * @param shouldYield a function called after each row is read from the database,\n * which should return true if the source should yield the special 'yield' value\n * to yield control back to the caller at the end of the pipeline. Can\n * also throw an error to abort the pipeline processing.\n */\n constructor(\n logContext: LogContext,\n logConfig: LogConfig,\n db: Database,\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n shouldYield = () => false,\n ) {\n this.#lc = logContext;\n this.#logConfig = logConfig;\n this.#table = tableName;\n this.#columns = columns;\n this.#uniqueIndexes = getUniqueIndexes(db, tableName);\n this.#primaryKey = primaryKey;\n this.#stmts = this.#getStatementsFor(db);\n this.#shouldYield = shouldYield;\n\n assert(\n this.#uniqueIndexes.has(JSON.stringify(primaryKey.toSorted())),\n `primary key ${primaryKey} does not have a UNIQUE index`,\n );\n }\n\n get tableSchema() {\n return {\n name: this.#table,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n /**\n * Sets the db (snapshot) to use, to facilitate the Snapshotter leapfrog\n * algorithm for concurrent traversal of historic timelines.\n */\n setDB(db: Database) {\n this.#stmts = this.#getStatementsFor(db);\n }\n\n #getStatementsFor(db: Database) {\n const cached = this.#dbCache.get(db);\n if (cached) {\n return cached;\n }\n\n const stmts = {\n cache: new StatementCache(db),\n insert: db.prepare(\n compile(\n sql`INSERT INTO ${sql.ident(this.#table)} (${sql.join(\n Object.keys(this.#columns).map(c => sql.ident(c)),\n ', ',\n )}) VALUES (${sql.__dangerous__rawValue(\n Array.from({length: Object.keys(this.#columns).length})\n .fill('?')\n .join(','),\n )})`,\n ),\n ),\n delete: db.prepare(\n compile(\n sql`DELETE FROM ${sql.ident(this.#table)} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n ),\n // If all the columns are part of the primary key, we cannot use UPDATE.\n update:\n Object.keys(this.#columns).length > this.#primaryKey.length\n ? db.prepare(\n compile(\n sql`UPDATE ${sql.ident(this.#table)} SET ${sql.join(\n nonPrimaryKeys(this.#columns, this.#primaryKey).map(\n c => sql`${sql.ident(c)}=?`,\n ),\n ',',\n )} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n )\n : undefined,\n checkExists: db.prepare(\n compile(\n sql`SELECT 1 AS \"exists\" FROM ${sql.ident(\n this.#table,\n )} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )} LIMIT 1`,\n ),\n ),\n getExisting: db.prepare(\n compile(\n sql`SELECT * FROM ${sql.ident(this.#table)} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n ),\n };\n this.#dbCache.set(db, stmts);\n return stmts;\n }\n\n get #allColumns() {\n return sql.join(\n Object.keys(this.#columns).map(c => sql.ident(c)),\n sql`,`,\n );\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#table,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n relationships: {},\n isHidden: false,\n system: 'client',\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n debug?: DebugDelegate,\n ) {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n // PK comparator is used for source-level overlay matching (remove by PK\n // equality) even when no ordering is requested.\n const primaryKeySort: Ordering = this.#primaryKey.map(k => [k, 'asc']);\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 const idx = this.#connections.indexOf(connection);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n debug,\n output: undefined,\n sort,\n splitEditKeys,\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n compareRows: sort ? makeComparator(sort) : makeComparator(primaryKeySort),\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(sort, this.#primaryKey);\n }\n\n this.#connections.push(connection);\n return input;\n }\n\n toSQLiteRow(row: Row): Row {\n return Object.fromEntries(\n Object.entries(row).map(([key, value]) => [\n key,\n toSQLiteType(value, this.#columns[key].type),\n ]),\n ) as Row;\n }\n\n *#fetch(req: FetchRequest, connection: Connection): Stream<Node | 'yield'> {\n const {sort, debug} = connection;\n\n const query = this.#requestToSQL(req, connection.filters?.condition, sort);\n const sqlAndBindings = format(query);\n\n const cachedStatement = this.#stmts.cache.get(sqlAndBindings.text);\n cachedStatement.statement.safeIntegers(true);\n const rowIterator = cachedStatement.statement.iterate<Row>(\n ...sqlAndBindings.values,\n );\n try {\n debug?.initQuery(this.#table, sqlAndBindings.text);\n\n if (sort) {\n const comparator = makeComparator(sort, req.reverse);\n yield* generateWithStart(\n generateWithYields(\n generateWithOverlay(\n req.start?.row,\n this.#mapFromSQLiteTypes(\n this.#columns,\n rowIterator,\n sqlAndBindings.text,\n debug,\n ),\n req.constraint,\n this.#overlay,\n connection.lastPushedEpoch,\n comparator,\n connection.filters?.predicate,\n ),\n this.#shouldYield,\n ),\n req.start,\n comparator,\n );\n } else {\n yield* generateWithYields(\n generateWithOverlayUnordered(\n this.#mapFromSQLiteTypes(\n this.#columns,\n rowIterator,\n sqlAndBindings.text,\n debug,\n ),\n req.constraint,\n this.#overlay,\n connection.lastPushedEpoch,\n this.#primaryKey,\n connection.filters?.predicate,\n ),\n this.#shouldYield,\n );\n }\n } finally {\n // Ensure the SQLite iterate() is closed.\n rowIterator.return?.();\n if (debug) {\n let totalNvisit = 0;\n let i = 0;\n while (true) {\n const nvisit = cachedStatement.statement.scanStatus(\n i++,\n SQLite3Database.SQLITE_SCANSTAT_NVISIT,\n 1,\n );\n if (nvisit === undefined) {\n break;\n }\n totalNvisit += Number(nvisit);\n }\n if (totalNvisit !== 0) {\n debug.recordNVisit(this.#table, sqlAndBindings.text, totalNvisit);\n }\n cachedStatement.statement.scanStatusReset();\n }\n this.#stmts.cache.return(cachedStatement);\n }\n }\n\n *#mapFromSQLiteTypes(\n valueTypes: Record<string, SchemaValue>,\n rowIterator: IterableIterator<Row>,\n query: string,\n debug: DebugDelegate | undefined,\n ): IterableIterator<Row> {\n let result;\n do {\n result = timeSampled(\n this.#lc,\n ++eventCount,\n this.#logConfig.ivmSampling,\n () => rowIterator.next(),\n this.#logConfig.slowRowThreshold,\n () =>\n `table-source.next took too long for ${query}. Are you missing an index?`,\n );\n if (result.done) {\n break;\n }\n const row = fromSQLiteTypes(valueTypes, result.value, this.#table);\n debug?.rowVended(this.#table, query, row);\n yield row;\n } while (!result.done);\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 exists = (row: Row) =>\n this.#stmts.checkExists.get<{exists: number} | undefined>(\n ...toSQLiteTypes(this.#primaryKey, row, this.#columns),\n )?.exists === 1;\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n\n yield* genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n switch (change.type) {\n case 'add':\n this.#stmts.insert.run(\n ...toSQLiteTypes(\n Object.keys(this.#columns),\n change.row,\n this.#columns,\n ),\n );\n break;\n case 'remove':\n this.#stmts.delete.run(\n ...toSQLiteTypes(this.#primaryKey, change.row, this.#columns),\n );\n break;\n case 'edit': {\n // If the PK is the same, use UPDATE.\n if (\n canUseUpdate(\n change.oldRow,\n change.row,\n this.#columns,\n this.#primaryKey,\n )\n ) {\n const mergedRow = {\n ...change.oldRow,\n ...change.row,\n };\n const params = [\n ...nonPrimaryValues(this.#columns, this.#primaryKey, mergedRow),\n ...toSQLiteTypes(this.#primaryKey, mergedRow, this.#columns),\n ];\n must(this.#stmts.update).run(params);\n } else {\n this.#stmts.delete.run(\n ...toSQLiteTypes(this.#primaryKey, change.oldRow, this.#columns),\n );\n this.#stmts.insert.run(\n ...toSQLiteTypes(\n Object.keys(this.#columns),\n change.row,\n this.#columns,\n ),\n );\n }\n\n break;\n }\n default:\n unreachable(change);\n }\n }\n\n #getRowStmtCache = new Map<string, string>();\n\n #getRowStmt(keyCols: string[]): string {\n const keyString = JSON.stringify(keyCols);\n let stmt = this.#getRowStmtCache.get(keyString);\n if (!stmt) {\n stmt = compile(\n sql`SELECT ${this.#allColumns} FROM ${sql.ident(\n this.#table,\n )} WHERE ${sql.join(\n keyCols.map(k => sql`${sql.ident(k)}=?`),\n sql` AND`,\n )}`,\n );\n this.#getRowStmtCache.set(keyString, stmt);\n }\n return stmt;\n }\n\n /**\n * Retrieves a row from the backing DB by a unique key, or `undefined` if such a\n * row does not exist. This is not used in the IVM pipeline but is useful\n * for retrieving data that is consistent with the state (and type\n * semantics) of the pipeline. Note that this key may not necessarily correspond\n * to the `primaryKey` with which this TableSource.\n */\n getRow(rowKey: Row): Row | undefined {\n const keyCols = Object.keys(rowKey);\n\n const stmt = this.#getRowStmt(keyCols);\n const row = this.#stmts.cache.use(stmt, cached =>\n cached.statement\n .safeIntegers(true)\n .get<Row>(...toSQLiteTypes(keyCols, rowKey, this.#columns)),\n );\n if (row) {\n return fromSQLiteTypes(this.#columns, row, this.#table);\n }\n return row;\n }\n\n #requestToSQL(\n request: FetchRequest,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n ): SQLQuery {\n return buildSelectQuery(\n this.#table,\n this.#columns,\n request.constraint,\n filters,\n order,\n request.reverse,\n request.start,\n );\n }\n}\n\nfunction getUniqueIndexes(\n db: Database,\n tableName: string,\n): Map<string, Set<string>> {\n const sqlAndBindings = format(\n sql`\n SELECT idx.name, json_group_array(col.name) as columnsJSON\n FROM sqlite_master as idx\n JOIN pragma_index_list(idx.tbl_name) AS info ON info.name = idx.name\n JOIN pragma_index_info(idx.name) as col\n WHERE idx.tbl_name = ${tableName} AND\n idx.type = 'index' AND \n info.\"unique\" != 0\n GROUP BY idx.name\n ORDER BY idx.name`,\n );\n const stmt = db.prepare(sqlAndBindings.text);\n const indexes = stmt.all<{columnsJSON: string}>(...sqlAndBindings.values);\n return new Map(\n indexes.map(({columnsJSON}) => {\n const columns = JSON.parse(columnsJSON);\n const set = new Set<string>(columns);\n return [JSON.stringify(columns.sort()), set];\n }),\n );\n}\n\nexport function toSQLiteTypes(\n columns: readonly string[],\n row: Row,\n columnTypes: Record<string, SchemaValue>,\n): readonly unknown[] {\n return columns.map(col => toSQLiteType(row[col], columnTypes[col].type));\n}\n\nexport function toSQLiteTypeName(type: ValueType) {\n switch (type) {\n case 'boolean':\n return 'INTEGER';\n case 'number':\n return 'REAL';\n case 'string':\n return 'TEXT';\n case 'null':\n return 'NULL';\n case 'json':\n return 'TEXT';\n }\n}\n\nexport function fromSQLiteTypes(\n valueTypes: Record<string, SchemaValue>,\n row: Row,\n tableName: string,\n): Row {\n const newRow: Writable<Row> = {};\n for (const key of Object.keys(row)) {\n const valueType = valueTypes[key];\n if (valueType === undefined) {\n const columnList = Object.keys(valueTypes).sort().join(', ');\n throw new Error(\n `Invalid column \"${key}\" for table \"${tableName}\". Synced columns include ${columnList}`,\n );\n }\n newRow[key] = fromSQLiteType(valueType.type, row[key], key, tableName);\n }\n return newRow;\n}\n\nfunction fromSQLiteType(\n valueType: ValueType,\n v: Value,\n column: string,\n tableName: string,\n): Value {\n if (v === null) {\n return null;\n }\n switch (valueType) {\n case 'boolean':\n return !!v;\n case 'number':\n case 'string':\n case 'null':\n if (typeof v === 'bigint') {\n if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {\n throw new UnsupportedValueError(\n `value ${v} (in ${tableName}.${column}) is outside of supported bounds`,\n );\n }\n return Number(v);\n }\n return v;\n case 'json':\n try {\n return JSON.parse(v as string);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n throw new UnsupportedValueError(\n `Failed to parse JSON for ${tableName}.${column}: ${errorMessage}`,\n {cause: error},\n );\n }\n }\n}\n\nexport class UnsupportedValueError extends Error {}\n\nfunction canUseUpdate(\n oldRow: Row,\n row: Row,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n): boolean {\n for (const pk of primaryKey) {\n if (oldRow[pk] !== row[pk]) {\n return false;\n }\n }\n return Object.keys(columns).length > primaryKey.length;\n}\n\nfunction nonPrimaryValues(\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n row: Row,\n): Iterable<unknown> {\n return nonPrimaryKeys(columns, primaryKey).map(c =>\n toSQLiteType(row[c], columns[c].type),\n );\n}\n\nfunction nonPrimaryKeys(\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n) {\n return Object.keys(columns).filter(c => !primaryKey.includes(c));\n}\n\nfunction* generateWithYields(stream: Stream<Node>, shouldYield: () => boolean) {\n for (const n of stream) {\n if (shouldYield()) {\n yield 'yield';\n }\n yield n;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"table-source.js","names":["#dbCache","#connections","#table","#columns","#uniqueIndexes","#primaryKey","#logConfig","#lc","#shouldYield","#stmts","#getStatementsFor","#allColumns","#fetch","#getSchema","#requestToSQL","#mapFromSQLiteTypes","#overlay","#writeChange","#pushEpoch","#getRowStmtCache","#getRowStmt"],"sources":["../../../../zqlite/src/table-source.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport type {LogContext} from '@rocicorp/logger';\nimport SQLite3Database from '@rocicorp/zero-sqlite3';\nimport type {LogConfig} from '../../otel/src/log-options.ts';\nimport {timeSampled} from '../../otel/src/maybe-time.ts';\nimport {assert, unreachable} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {Writable} from '../../shared/src/writable.ts';\nimport type {Condition, Ordering} 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 {\n SchemaValue,\n ValueType,\n} from '../../zero-schema/src/table-schema.ts';\nimport type {DebugDelegate} from '../../zql/src/builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n} from '../../zql/src/builder/filter.ts';\nimport {ChangeType} from '../../zql/src/ivm/change-type.ts';\nimport {makeComparator, type Node} from '../../zql/src/ivm/data.ts';\nimport {\n generateWithOverlay,\n generateWithOverlayUnordered,\n generateWithStart,\n genPushAndWriteWithSplitEdit,\n type Connection,\n type Overlay,\n} from '../../zql/src/ivm/memory-source.ts';\nimport {type FetchRequest} from '../../zql/src/ivm/operator.ts';\nimport type {SourceSchema} from '../../zql/src/ivm/schema.ts';\nimport {SourceChangeIndex} from '../../zql/src/ivm/source-change-index.ts';\nimport {\n type Source,\n type SourceChange,\n type SourceInput,\n} from '../../zql/src/ivm/source.ts';\nimport type {Stream} from '../../zql/src/ivm/stream.ts';\nimport {assertOrderingIncludesPK} from '../../zql/src/query/complete-ordering.ts';\nimport type {Database, Statement} from './db.ts';\nimport {compile, format, sql} from './internal/sql.ts';\nimport {StatementCache} from './internal/statement-cache.ts';\nimport {\n buildSelectQuery,\n toSQLiteType,\n type NoSubqueryCondition,\n} from './query-builder.ts';\n\ntype Statements = {\n readonly cache: StatementCache;\n readonly insert: Statement;\n readonly delete: Statement;\n readonly update: Statement | undefined;\n readonly checkExists: Statement;\n readonly getExisting: Statement;\n};\n\nlet eventCount = 0;\n\n/**\n * A source that is backed by a SQLite table.\n *\n * Values are written to the backing table _after_ being vended by the source.\n *\n * This ordering of events is to ensure self joins function properly. That is,\n * we can't reveal a value to an output before it has been pushed to that output.\n *\n * The code is fairly straightforward except for:\n * 1. Dealing with a `fetch` that has a basis of `before`.\n * 2. Dealing with compound orders that have differing directions (a ASC, b DESC, c ASC)\n *\n * See comments in relevant functions for more details.\n */\nexport class TableSource implements Source {\n readonly #dbCache = new WeakMap<Database, Statements>();\n readonly #connections: Connection[] = [];\n readonly #table: string;\n readonly #columns: Record<string, SchemaValue>;\n // Maps sorted columns JSON string (e.g. '[\"a\",\"b\"]) to Set of columns.\n readonly #uniqueIndexes: Map<string, Set<string>>;\n readonly #primaryKey: PrimaryKey;\n readonly #logConfig: LogConfig;\n readonly #lc: LogContext;\n readonly #shouldYield: () => boolean;\n #stmts: Statements;\n #overlay?: Overlay | undefined;\n #pushEpoch = 0;\n\n /**\n * @param shouldYield a function called after each row is read from the database,\n * which should return true if the source should yield the special 'yield' value\n * to yield control back to the caller at the end of the pipeline. Can\n * also throw an error to abort the pipeline processing.\n */\n constructor(\n logContext: LogContext,\n logConfig: LogConfig,\n db: Database,\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n shouldYield = () => false,\n ) {\n this.#lc = logContext;\n this.#logConfig = logConfig;\n this.#table = tableName;\n this.#columns = columns;\n this.#uniqueIndexes = getUniqueIndexes(db, tableName);\n this.#primaryKey = primaryKey;\n this.#stmts = this.#getStatementsFor(db);\n this.#shouldYield = shouldYield;\n\n assert(\n this.#uniqueIndexes.has(JSON.stringify(primaryKey.toSorted())),\n `primary key ${primaryKey} does not have a UNIQUE index`,\n );\n }\n\n get tableSchema() {\n return {\n name: this.#table,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n /**\n * Sets the db (snapshot) to use, to facilitate the Snapshotter leapfrog\n * algorithm for concurrent traversal of historic timelines.\n */\n setDB(db: Database) {\n this.#stmts = this.#getStatementsFor(db);\n }\n\n #getStatementsFor(db: Database) {\n const cached = this.#dbCache.get(db);\n if (cached) {\n return cached;\n }\n\n const stmts = {\n cache: new StatementCache(db),\n insert: db.prepare(\n compile(\n sql`INSERT INTO ${sql.ident(this.#table)} (${sql.join(\n Object.keys(this.#columns).map(c => sql.ident(c)),\n ', ',\n )}) VALUES (${sql.__dangerous__rawValue(\n Array.from({length: Object.keys(this.#columns).length})\n .fill('?')\n .join(','),\n )})`,\n ),\n ),\n delete: db.prepare(\n compile(\n sql`DELETE FROM ${sql.ident(this.#table)} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n ),\n // If all the columns are part of the primary key, we cannot use UPDATE.\n update:\n Object.keys(this.#columns).length > this.#primaryKey.length\n ? db.prepare(\n compile(\n sql`UPDATE ${sql.ident(this.#table)} SET ${sql.join(\n nonPrimaryKeys(this.#columns, this.#primaryKey).map(\n c => sql`${sql.ident(c)}=?`,\n ),\n ',',\n )} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n )\n : undefined,\n checkExists: db.prepare(\n compile(\n sql`SELECT 1 AS \"exists\" FROM ${sql.ident(\n this.#table,\n )} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )} LIMIT 1`,\n ),\n ),\n getExisting: db.prepare(\n compile(\n sql`SELECT * FROM ${sql.ident(this.#table)} WHERE ${sql.join(\n this.#primaryKey.map(k => sql`${sql.ident(k)}=?`),\n ' AND ',\n )}`,\n ),\n ),\n };\n this.#dbCache.set(db, stmts);\n return stmts;\n }\n\n get #allColumns() {\n return sql.join(\n Object.keys(this.#columns).map(c => sql.ident(c)),\n sql`,`,\n );\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#table,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n relationships: {},\n isHidden: false,\n system: 'client',\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n debug?: DebugDelegate,\n ) {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n // PK comparator is used for source-level overlay matching (remove by PK\n // equality) even when no ordering is requested.\n const primaryKeySort: Ordering = this.#primaryKey.map(k => [k, 'asc']);\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 const idx = this.#connections.indexOf(connection);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n debug,\n output: undefined,\n sort,\n splitEditKeys,\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n compareRows: sort ? makeComparator(sort) : makeComparator(primaryKeySort),\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(sort, this.#primaryKey);\n }\n\n this.#connections.push(connection);\n return input;\n }\n\n toSQLiteRow(row: Row): Row {\n return Object.fromEntries(\n Object.entries(row).map(([key, value]) => [\n key,\n toSQLiteType(value, this.#columns[key].type),\n ]),\n ) as Row;\n }\n\n *#fetch(req: FetchRequest, connection: Connection): Stream<Node | 'yield'> {\n const {sort, debug} = connection;\n\n const query = this.#requestToSQL(req, connection.filters?.condition, sort);\n const sqlAndBindings = format(query);\n\n const cachedStatement = this.#stmts.cache.get(sqlAndBindings.text);\n cachedStatement.statement.safeIntegers(true);\n const rowIterator = cachedStatement.statement.iterate<Row>(\n ...sqlAndBindings.values,\n );\n try {\n debug?.initQuery(this.#table, sqlAndBindings.text);\n\n if (sort) {\n const comparator = makeComparator(sort, req.reverse);\n yield* generateWithStart(\n generateWithYields(\n generateWithOverlay(\n req.start?.row,\n this.#mapFromSQLiteTypes(\n this.#columns,\n rowIterator,\n sqlAndBindings.text,\n debug,\n ),\n req.constraint,\n this.#overlay,\n connection.lastPushedEpoch,\n comparator,\n connection.filters?.predicate,\n ),\n this.#shouldYield,\n ),\n req.start,\n comparator,\n );\n } else {\n yield* generateWithYields(\n generateWithOverlayUnordered(\n this.#mapFromSQLiteTypes(\n this.#columns,\n rowIterator,\n sqlAndBindings.text,\n debug,\n ),\n req.constraint,\n this.#overlay,\n connection.lastPushedEpoch,\n this.#primaryKey,\n connection.filters?.predicate,\n ),\n this.#shouldYield,\n );\n }\n } finally {\n // Ensure the SQLite iterate() is closed.\n rowIterator.return?.();\n if (debug) {\n let totalNvisit = 0;\n let i = 0;\n while (true) {\n const nvisit = cachedStatement.statement.scanStatus(\n i++,\n SQLite3Database.SQLITE_SCANSTAT_NVISIT,\n 1,\n );\n if (nvisit === undefined) {\n break;\n }\n totalNvisit += Number(nvisit);\n }\n if (totalNvisit !== 0) {\n debug.recordNVisit(this.#table, sqlAndBindings.text, totalNvisit);\n }\n cachedStatement.statement.scanStatusReset();\n }\n this.#stmts.cache.return(cachedStatement);\n }\n }\n\n *#mapFromSQLiteTypes(\n valueTypes: Record<string, SchemaValue>,\n rowIterator: IterableIterator<Row>,\n query: string,\n debug: DebugDelegate | undefined,\n ): IterableIterator<Row> {\n let result;\n do {\n result = timeSampled(\n this.#lc,\n ++eventCount,\n this.#logConfig.ivmSampling,\n () => rowIterator.next(),\n this.#logConfig.slowRowThreshold,\n () =>\n `table-source.next took too long for ${query}. Are you missing an index?`,\n );\n if (result.done) {\n break;\n }\n const row = fromSQLiteTypes(valueTypes, result.value, this.#table);\n debug?.rowVended(this.#table, query, row);\n yield row;\n } while (!result.done);\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 exists = (row: Row) =>\n this.#stmts.checkExists.get<{exists: number} | undefined>(\n ...toSQLiteTypes(this.#primaryKey, row, this.#columns),\n )?.exists === 1;\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n\n yield* genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n this.#stmts.insert.run(\n ...toSQLiteTypes(\n // TODO(arv): Compute this once!\n Object.keys(this.#columns),\n change[SourceChangeIndex.ROW],\n this.#columns,\n ),\n );\n break;\n case ChangeType.REMOVE:\n this.#stmts.delete.run(\n ...toSQLiteTypes(\n this.#primaryKey,\n change[SourceChangeIndex.ROW],\n this.#columns,\n ),\n );\n break;\n case ChangeType.EDIT: {\n // If the PK is the same, use UPDATE.\n if (\n canUseUpdate(\n change[SourceChangeIndex.OLD_ROW],\n change[SourceChangeIndex.ROW],\n this.#columns,\n this.#primaryKey,\n )\n ) {\n const mergedRow = {\n ...change[SourceChangeIndex.OLD_ROW],\n ...change[SourceChangeIndex.ROW],\n };\n const params = [\n ...nonPrimaryValues(this.#columns, this.#primaryKey, mergedRow),\n ...toSQLiteTypes(this.#primaryKey, mergedRow, this.#columns),\n ];\n must(this.#stmts.update).run(params);\n } else {\n this.#stmts.delete.run(\n ...toSQLiteTypes(\n this.#primaryKey,\n change[SourceChangeIndex.OLD_ROW],\n this.#columns,\n ),\n );\n this.#stmts.insert.run(\n ...toSQLiteTypes(\n Object.keys(this.#columns),\n change[SourceChangeIndex.ROW],\n this.#columns,\n ),\n );\n }\n\n break;\n }\n default:\n unreachable(change);\n }\n }\n\n #getRowStmtCache = new Map<string, string>();\n\n #getRowStmt(keyCols: string[]): string {\n const keyString = JSON.stringify(keyCols);\n let stmt = this.#getRowStmtCache.get(keyString);\n if (!stmt) {\n stmt = compile(\n sql`SELECT ${this.#allColumns} FROM ${sql.ident(\n this.#table,\n )} WHERE ${sql.join(\n keyCols.map(k => sql`${sql.ident(k)}=?`),\n sql` AND`,\n )}`,\n );\n this.#getRowStmtCache.set(keyString, stmt);\n }\n return stmt;\n }\n\n /**\n * Retrieves a row from the backing DB by a unique key, or `undefined` if such a\n * row does not exist. This is not used in the IVM pipeline but is useful\n * for retrieving data that is consistent with the state (and type\n * semantics) of the pipeline. Note that this key may not necessarily correspond\n * to the `primaryKey` with which this TableSource.\n */\n getRow(rowKey: Row): Row | undefined {\n const keyCols = Object.keys(rowKey);\n\n const stmt = this.#getRowStmt(keyCols);\n const row = this.#stmts.cache.use(stmt, cached =>\n cached.statement\n .safeIntegers(true)\n .get<Row>(...toSQLiteTypes(keyCols, rowKey, this.#columns)),\n );\n if (row) {\n return fromSQLiteTypes(this.#columns, row, this.#table);\n }\n return row;\n }\n\n #requestToSQL(\n request: FetchRequest,\n filters: NoSubqueryCondition | undefined,\n order: Ordering | undefined,\n ): SQLQuery {\n return buildSelectQuery(\n this.#table,\n this.#columns,\n request.constraint,\n filters,\n order,\n request.reverse,\n request.start,\n );\n }\n}\n\nfunction getUniqueIndexes(\n db: Database,\n tableName: string,\n): Map<string, Set<string>> {\n const sqlAndBindings = format(\n sql`\n SELECT idx.name, json_group_array(col.name) as columnsJSON\n FROM sqlite_master as idx\n JOIN pragma_index_list(idx.tbl_name) AS info ON info.name = idx.name\n JOIN pragma_index_info(idx.name) as col\n WHERE idx.tbl_name = ${tableName} AND\n idx.type = 'index' AND \n info.\"unique\" != 0\n GROUP BY idx.name\n ORDER BY idx.name`,\n );\n const stmt = db.prepare(sqlAndBindings.text);\n const indexes = stmt.all<{columnsJSON: string}>(...sqlAndBindings.values);\n return new Map(\n indexes.map(({columnsJSON}) => {\n const columns = JSON.parse(columnsJSON);\n const set = new Set<string>(columns);\n return [JSON.stringify(columns.sort()), set];\n }),\n );\n}\n\nexport function toSQLiteTypes(\n columns: readonly string[],\n row: Row,\n columnTypes: Record<string, SchemaValue>,\n): readonly unknown[] {\n return columns.map(col => toSQLiteType(row[col], columnTypes[col].type));\n}\n\nexport function toSQLiteTypeName(type: ValueType) {\n switch (type) {\n case 'boolean':\n return 'INTEGER';\n case 'number':\n return 'REAL';\n case 'string':\n return 'TEXT';\n case 'null':\n return 'NULL';\n case 'json':\n return 'TEXT';\n }\n}\n\nexport function fromSQLiteTypes(\n valueTypes: Record<string, SchemaValue>,\n row: Row,\n tableName: string,\n): Row {\n const newRow: Writable<Row> = {};\n for (const key of Object.keys(row)) {\n const valueType = valueTypes[key];\n if (valueType === undefined) {\n const columnList = Object.keys(valueTypes).sort().join(', ');\n throw new Error(\n `Invalid column \"${key}\" for table \"${tableName}\". Synced columns include ${columnList}`,\n );\n }\n newRow[key] = fromSQLiteType(valueType.type, row[key], key, tableName);\n }\n return newRow;\n}\n\nfunction fromSQLiteType(\n valueType: ValueType,\n v: Value,\n column: string,\n tableName: string,\n): Value {\n if (v === null) {\n return null;\n }\n switch (valueType) {\n case 'boolean':\n return !!v;\n case 'number':\n case 'string':\n case 'null':\n if (typeof v === 'bigint') {\n if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {\n throw new UnsupportedValueError(\n `value ${v} (in ${tableName}.${column}) is outside of supported bounds`,\n );\n }\n return Number(v);\n }\n return v;\n case 'json':\n try {\n return JSON.parse(v as string);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n throw new UnsupportedValueError(\n `Failed to parse JSON for ${tableName}.${column}: ${errorMessage}`,\n {cause: error},\n );\n }\n }\n}\n\nexport class UnsupportedValueError extends Error {}\n\nfunction canUseUpdate(\n oldRow: Row,\n row: Row,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n): boolean {\n for (const pk of primaryKey) {\n if (oldRow[pk] !== row[pk]) {\n return false;\n }\n }\n return Object.keys(columns).length > primaryKey.length;\n}\n\nfunction nonPrimaryValues(\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n row: Row,\n): Iterable<unknown> {\n return nonPrimaryKeys(columns, primaryKey).map(c =>\n toSQLiteType(row[c], columns[c].type),\n );\n}\n\nfunction nonPrimaryKeys(\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n) {\n return Object.keys(columns).filter(c => !primaryKey.includes(c));\n}\n\nfunction* generateWithYields(stream: Stream<Node>, shouldYield: () => boolean) {\n for (const n of stream) {\n if (shouldYield()) {\n yield 'yield';\n }\n yield n;\n }\n}\n"],"mappings":"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"}