duckstring 0.6.0rc2__tar.gz → 0.7.0__tar.gz

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 (306) hide show
  1. duckstring-0.7.0/CHANGELOG.md +391 -0
  2. {duckstring-0.6.0rc2/src/duckstring.egg-info → duckstring-0.7.0}/PKG-INFO +17 -7
  3. {duckstring-0.6.0rc2 → duckstring-0.7.0}/README.md +8 -4
  4. {duckstring-0.6.0rc2 → duckstring-0.7.0}/pyproject.toml +32 -8
  5. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/app.py +3 -2
  6. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/auth.py +44 -3
  7. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/cloud.py +4 -2
  8. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/db.py +3 -1
  9. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/driver.py +492 -308
  10. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/egress_worker.py +39 -28
  11. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/flight_sql.py +25 -15
  12. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/pg_wire.py +21 -14
  13. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/poller.py +31 -20
  14. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/__init__.py +2 -0
  15. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/data.py +131 -49
  16. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/deploy.py +38 -21
  17. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/draw.py +1 -2
  18. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/duck.py +2 -2
  19. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/metrics.py +10 -0
  20. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/orchestrate.py +60 -57
  21. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/serving.py +60 -10
  22. duckstring-0.7.0/src/duckstring/catchment/routes/tap.py +64 -0
  23. duckstring-0.7.0/src/duckstring/catchment/schema/030_flock_accept.sql +5 -0
  24. duckstring-0.7.0/src/duckstring/catchment/schema/031_refresh.sql +83 -0
  25. duckstring-0.7.0/src/duckstring/catchment/schema/032_tap.sql +16 -0
  26. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/serving.py +42 -15
  27. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/404.html +1 -1
  28. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next.__PAGE__.txt +2 -2
  29. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next._full.txt +2 -2
  30. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next._head.txt +1 -1
  31. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next._index.txt +1 -1
  32. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next._tree.txt +1 -1
  33. duckstring-0.7.0/src/duckstring/catchment/static/_next/static/chunks/0h6ulcnnp2foi.js +2 -0
  34. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._full.txt +1 -1
  35. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._head.txt +1 -1
  36. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._index.txt +1 -1
  37. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
  38. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._not-found.txt +1 -1
  39. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._tree.txt +1 -1
  40. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found.html +1 -1
  41. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found.txt +1 -1
  42. duckstring-0.7.0/src/duckstring/catchment/static/index.html +1 -0
  43. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/index.txt +2 -2
  44. duckstring-0.7.0/src/duckstring/catchment/taps.py +236 -0
  45. duckstring-0.7.0/src/duckstring/changes.py +122 -0
  46. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/__init__.py +22 -41
  47. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/catchment.py +9 -7
  48. duckstring-0.7.0/src/duckstring/cli/control.py +346 -0
  49. duckstring-0.7.0/src/duckstring/cli/data.py +351 -0
  50. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/deploy.py +19 -2
  51. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/duck.py +10 -5
  52. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/duct.py +16 -13
  53. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/lineage.py +23 -15
  54. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/pond.py +1 -1
  55. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/puddle.py +3 -1
  56. duckstring-0.7.0/src/duckstring/cli/refresh.py +202 -0
  57. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/spout.py +5 -3
  58. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/status.py +10 -10
  59. duckstring-0.7.0/src/duckstring/cli/tap.py +135 -0
  60. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/window.py +61 -49
  61. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/core.py +312 -20
  62. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/dataplane.py +238 -79
  63. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_events/src/pond.py +1 -1
  64. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_repo_activity/README.md +2 -2
  65. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/orders/src/pond.py +1 -1
  66. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/src/puddles.py +4 -4
  67. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/products/src/pond.py +1 -1
  68. duckstring-0.7.0/src/duckstring/demo/reports/pond.toml +11 -0
  69. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/README.md +1 -1
  70. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/src/puddles.py +2 -2
  71. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/README.md +1 -1
  72. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_reports/pond.toml +4 -0
  73. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_category_revenue/README.md +2 -2
  74. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_category_revenue/src/pond.py +2 -2
  75. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_items/src/pond.py +2 -2
  76. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_sales/src/pond.py +1 -1
  77. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_stores/src/pond.py +1 -1
  78. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/transactions/src/pond.py +1 -1
  79. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/__init__.py +2 -2
  80. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/__main__.py +6 -3
  81. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/client.py +7 -4
  82. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/core.py +30 -4
  83. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/dbt_executor.py +4 -4
  84. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/executor.py +17 -13
  85. duckstring-0.7.0/src/duckstring/egress/attached.py +178 -0
  86. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/base.py +6 -1
  87. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/destination.py +4 -3
  88. duckstring-0.7.0/src/duckstring/egress/ducklake.py +142 -0
  89. duckstring-0.7.0/src/duckstring/egress/postgres.py +55 -0
  90. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/__init__.py +32 -28
  91. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/catchment.py +430 -240
  92. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/core.py +115 -54
  93. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/worker.py +15 -15
  94. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/environments.py +31 -14
  95. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/flock/__init__.py +164 -81
  96. duckstring-0.7.0/src/duckstring/flock/engines/athena.py +154 -0
  97. duckstring-0.7.0/src/duckstring/flock/engines/motherduck.py +62 -0
  98. duckstring-0.7.0/src/duckstring/flock/engines/remote_duckdb.py +225 -0
  99. duckstring-0.7.0/src/duckstring/flock/engines/sql_engine.py +206 -0
  100. duckstring-0.7.0/src/duckstring/flock/engines/starrocks.py +305 -0
  101. duckstring-0.7.0/src/duckstring/flock/translate.py +289 -0
  102. duckstring-0.7.0/src/duckstring/flock/verify.py +123 -0
  103. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/local/runner.py +8 -5
  104. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/objects.py +75 -7
  105. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/schema_contract.py +19 -2
  106. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/sql_ripples.py +3 -2
  107. duckstring-0.7.0/src/duckstring/trickle/__init__.py +17 -0
  108. {duckstring-0.6.0rc2 → duckstring-0.7.0/src/duckstring.egg-info}/PKG-INFO +17 -7
  109. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring.egg-info/SOURCES.txt +21 -16
  110. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring.egg-info/requires.txt +9 -2
  111. duckstring-0.6.0rc2/CHANGELOG.md +0 -197
  112. duckstring-0.6.0rc2/src/duckstring/catchment/static/_next/static/chunks/09y-jd_5pt5my.js +0 -2
  113. duckstring-0.6.0rc2/src/duckstring/catchment/static/index.html +0 -1
  114. duckstring-0.6.0rc2/src/duckstring/cli/bulk.py +0 -135
  115. duckstring-0.6.0rc2/src/duckstring/cli/control.py +0 -227
  116. duckstring-0.6.0rc2/src/duckstring/cli/data.py +0 -273
  117. duckstring-0.6.0rc2/src/duckstring/cli/serve.py +0 -97
  118. duckstring-0.6.0rc2/src/duckstring/cli/trigger.py +0 -119
  119. duckstring-0.6.0rc2/src/duckstring/demo/reports/pond.toml +0 -7
  120. duckstring-0.6.0rc2/src/duckstring/egress/postgres.py +0 -170
  121. duckstring-0.6.0rc2/src/duckstring/flock/engines/athena.py +0 -298
  122. duckstring-0.6.0rc2/src/duckstring/flock/equivalence.py +0 -75
  123. duckstring-0.6.0rc2/src/duckstring/trickle/__init__.py +0 -71
  124. duckstring-0.6.0rc2/src/duckstring/trickle/acc.py +0 -122
  125. duckstring-0.6.0rc2/src/duckstring/trickle/agg.py +0 -184
  126. duckstring-0.6.0rc2/src/duckstring/trickle/builder.py +0 -1176
  127. duckstring-0.6.0rc2/src/duckstring/trickle/capture.py +0 -363
  128. duckstring-0.6.0rc2/src/duckstring/trickle/context.py +0 -53
  129. duckstring-0.6.0rc2/src/duckstring/trickle/io.py +0 -2547
  130. duckstring-0.6.0rc2/src/duckstring/trickle/lineage.py +0 -305
  131. {duckstring-0.6.0rc2 → duckstring-0.7.0}/LICENSE +0 -0
  132. {duckstring-0.6.0rc2 → duckstring-0.7.0}/MANIFEST.in +0 -0
  133. {duckstring-0.6.0rc2 → duckstring-0.7.0}/setup.cfg +0 -0
  134. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/__init__.py +0 -0
  135. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/__main__.py +0 -0
  136. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/acc.py +0 -0
  137. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/agg.py +0 -0
  138. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/__init__.py +0 -0
  139. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/base.py +0 -0
  140. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/email.py +0 -0
  141. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/event.py +0 -0
  142. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/webhook.py +0 -0
  143. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/__init__.py +0 -0
  144. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/alert_worker.py +0 -0
  145. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/asgi.py +0 -0
  146. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/cloud_backends.py +0 -0
  147. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/cloud_deploy.py +0 -0
  148. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/dag.py +0 -0
  149. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/data_lease.py +0 -0
  150. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/dialback.py +0 -0
  151. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/ec2_launcher.py +0 -0
  152. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/fargate_launcher.py +0 -0
  153. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/launcher.py +0 -0
  154. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/pool_launcher.py +0 -0
  155. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/registry.py +0 -0
  156. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/relay.py +0 -0
  157. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/alerts.py +0 -0
  158. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/catchment.py +0 -0
  159. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/duct.py +0 -0
  160. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/pool.py +0 -0
  161. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/secrets.py +0 -0
  162. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/view.py +0 -0
  163. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/001_init.sql +0 -0
  164. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/002_ducts.sql +0 -0
  165. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/003_pull_m.sql +0 -0
  166. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/004_identity.sql +0 -0
  167. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/005_pond_version_schema.sql +0 -0
  168. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/006_refresh.sql +0 -0
  169. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/007_catchment_key.sql +0 -0
  170. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/008_spout.sql +0 -0
  171. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/009_spout_state.sql +0 -0
  172. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/010_spout_wake.sql +0 -0
  173. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/011_spout_window.sql +0 -0
  174. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/012_spout_node.sql +0 -0
  175. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/013_changed_f.sql +0 -0
  176. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/014_alert.sql +0 -0
  177. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/016_alert_scope_major.sql +0 -0
  178. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/017_alert_renotify.sql +0 -0
  179. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/018_duck.sql +0 -0
  180. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/019_lineage.sql +0 -0
  181. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/020_column_lineage.sql +0 -0
  182. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/021_cloud.sql +0 -0
  183. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/022_retire_duck_size.sql +0 -0
  184. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/023_pool_provider.sql +0 -0
  185. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/024_serving.sql +0 -0
  186. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/025_dbt.sql +0 -0
  187. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/026_deploy_config.sql +0 -0
  188. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/027_persist.sql +0 -0
  189. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/028_persist_timing.sql +0 -0
  190. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/029_source_pins.sql +0 -0
  191. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/secrets.py +0 -0
  192. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/state_sync.py +0 -0
  193. {duckstring-0.6.0rc2/src/duckstring/catchment/static/_next/static/yh7itwhhijhMjJK_wed06 → duckstring-0.7.0/src/duckstring/catchment/static/_next/static/FqgYlcTGJfIEhRPz1Nc4p}/_buildManifest.js +0 -0
  194. {duckstring-0.6.0rc2/src/duckstring/catchment/static/_next/static/yh7itwhhijhMjJK_wed06 → duckstring-0.7.0/src/duckstring/catchment/static/_next/static/FqgYlcTGJfIEhRPz1Nc4p}/_clientMiddlewareManifest.js +0 -0
  195. {duckstring-0.6.0rc2/src/duckstring/catchment/static/_next/static/yh7itwhhijhMjJK_wed06 → duckstring-0.7.0/src/duckstring/catchment/static/_next/static/FqgYlcTGJfIEhRPz1Nc4p}/_ssgManifest.js +0 -0
  196. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/03~yq9q893hmn.js +0 -0
  197. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/07lhk_q6pmm3r.js +0 -0
  198. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/0_8xp0_1~w7l7.css +0 -0
  199. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/0dbhjjzl8qfwv.js +0 -0
  200. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/0jvmviuftg5e2.css +0 -0
  201. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/0kczw6usu-y-f.js +0 -0
  202. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/11kjahy2ntf0n.js +0 -0
  203. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/turbopack-03~mbvk_uplk_.js +0 -0
  204. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/favicon.svg +0 -0
  205. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/logo-mark.svg +0 -0
  206. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/logo.svg +0 -0
  207. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/_http.py +0 -0
  208. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/alert.py +0 -0
  209. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/config.py +0 -0
  210. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/secret.py +0 -0
  211. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/dbt_mode.py +0 -0
  212. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/catalog/.gitignore +0 -0
  213. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/catalog/README.md +0 -0
  214. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/catalog/pond.toml +0 -0
  215. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/catalog/src/pond.py +0 -0
  216. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_actors/README.md +0 -0
  217. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_actors/pond.toml +0 -0
  218. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_actors/src/pond.py +0 -0
  219. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_events/README.md +0 -0
  220. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_events/pond.toml +0 -0
  221. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_pushes/README.md +0 -0
  222. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_pushes/pond.toml +0 -0
  223. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_pushes/src/pond.py +0 -0
  224. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_repo_activity/pond.toml +0 -0
  225. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_repo_activity/src/pond.py +0 -0
  226. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_stars/README.md +0 -0
  227. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_stars/pond.toml +0 -0
  228. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_stars/src/pond.py +0 -0
  229. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_trending/README.md +0 -0
  230. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_trending/pond.toml +0 -0
  231. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_trending/src/pond.py +0 -0
  232. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/orders/.gitignore +0 -0
  233. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/orders/README.md +0 -0
  234. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/orders/pond.toml +0 -0
  235. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/.gitignore +0 -0
  236. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/README.md +0 -0
  237. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/pond.toml +0 -0
  238. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/src/pond.py +0 -0
  239. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/products/.gitignore +0 -0
  240. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/products/README.md +0 -0
  241. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/products/pond.toml +0 -0
  242. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/reports/.gitignore +0 -0
  243. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/reports/README.md +0 -0
  244. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/reports/src/pond.py +0 -0
  245. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/.gitignore +0 -0
  246. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/pond.toml +0 -0
  247. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/src/pond.py +0 -0
  248. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/.gitignore +0 -0
  249. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/README.md +0 -0
  250. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/pond.toml +0 -0
  251. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/src/pond.py +0 -0
  252. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/src/puddles.py +0 -0
  253. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/.gitignore +0 -0
  254. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/dbt_project.yml +0 -0
  255. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/models/orders_clean.sql +0 -0
  256. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/models/revenue_by_product.sql +0 -0
  257. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/models/sources.yml +0 -0
  258. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/models/top_products.sql +0 -0
  259. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/pond.toml +0 -0
  260. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_orders/.gitignore +0 -0
  261. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_orders/README.md +0 -0
  262. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_orders/pond.toml +0 -0
  263. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_orders/src/pond.py +0 -0
  264. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_reports/README.md +0 -0
  265. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_reports/sql/monthly_summary.sql +0 -0
  266. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/README.md +0 -0
  267. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/data/tier_bands.csv +0 -0
  268. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/pond.toml +0 -0
  269. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/sql/daily_sales.sql +0 -0
  270. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/sql/price_tiers.sql +0 -0
  271. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/sql/sale_line.sql +0 -0
  272. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/src/puddles.py +0 -0
  273. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_category_revenue/pond.toml +0 -0
  274. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_items/README.md +0 -0
  275. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_items/pond.toml +0 -0
  276. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_priced/README.md +0 -0
  277. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_priced/pond.toml +0 -0
  278. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_priced/src/pond.py +0 -0
  279. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_sales/README.md +0 -0
  280. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_sales/pond.toml +0 -0
  281. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_store_revenue/README.md +0 -0
  282. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_store_revenue/pond.toml +0 -0
  283. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_store_revenue/src/pond.py +0 -0
  284. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_stores/README.md +0 -0
  285. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_stores/pond.toml +0 -0
  286. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/transactions/.gitignore +0 -0
  287. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/transactions/README.md +0 -0
  288. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/transactions/pond.toml +0 -0
  289. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/discover.py +0 -0
  290. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/pool_agent.py +0 -0
  291. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/__init__.py +0 -0
  292. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/credentials.py +0 -0
  293. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/object_store.py +0 -0
  294. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/pond.py +0 -0
  295. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/flock/engines/__init__.py +0 -0
  296. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/keys.py +0 -0
  297. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/local/__init__.py +0 -0
  298. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/local/hydrate.py +0 -0
  299. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/local/project.py +0 -0
  300. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/pondignore.py +0 -0
  301. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/storage.py +0 -0
  302. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/trickle_builder.py +0 -0
  303. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/trickle_io.py +0 -0
  304. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring.egg-info/dependency_links.txt +0 -0
  305. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring.egg-info/entry_points.txt +0 -0
  306. {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring.egg-info/top_level.txt +0 -0
@@ -0,0 +1,391 @@
1
+ # Changelog
2
+
3
+ Notable changes per release. Versions before 0.5.0 are recorded in the git history and the `v*` tags.
4
+
5
+ ## 0.7.0 — 2026-10-22
6
+
7
+ A single trigger, Refresh, replaces Tap, Wave, Pulse and Tide, and a Tap is now an app's access point to a
8
+ Pond. Also DuckDB 2.0 support and Spouts that deliver to a DuckLake. The old trigger commands and routes are
9
+ removed with no compatibility period, so read each section's Upgrading notes. Needs `duckstring-trickle` 0.2.1.
10
+
11
+ ### One trigger: Refresh
12
+
13
+ Tap, Wave, Pulse and Tide are replaced by a single trigger, **Refresh**, which asks for a Pond's data to be at
14
+ least as fresh as now. A recurring Refresh repeats it:
15
+
16
+ - `duckstring refresh add reports --every 1d`: a day after the later of the last request and the last refresh of
17
+ the data (the old Tide).
18
+ - `duckstring refresh add reports --every 1d --start 06:00 [--on MON,TUE,WED,THU,FRI] [--until T]`: at set times,
19
+ on a window's grid, whatever else refreshes the Pond. New. A Catchment that was down asks once, for the latest
20
+ occurrence, when it returns.
21
+ - `duckstring refresh add reports --continuous` (or `--every 0s`): as fresh as the pipeline allows, paced to its
22
+ slowest step (the old Wave).
23
+
24
+ `duckstring refresh now reports` refreshes once (the old Pulse). A Pond can have any number of scheduled Refreshes and
25
+ one `--every` or continuous Refresh; `refresh ls` lists them with when each next asks, and `refresh rm` removes
26
+ one. In the web UI, a Pond's trigger buttons became a Refresh section (Refresh Now, and Recurring Refresh for
27
+ Continuous, Every N and schedules), and its recurring Refreshes and Taps are listed in a strip at the bottom of its
28
+ box on the canvas.
29
+
30
+ Upgrading:
31
+
32
+ - **Existing Waves become continuous Refreshes and Tides become `--every` Refreshes** (migration `031`).
33
+ - **`POST /api/ponds/{name}/refresh` changed meaning.** It used to mark a Pond for a rebuild; it now refreshes the
34
+ Pond (at `demand` level). The rebuild flag moved to `POST /api/ponds/{name}/rebuild`, and the command to
35
+ `duckstring control rebuild` (and the batch operation `refresh` to `rebuild`).
36
+ Scripts that called `/refresh` for a rebuild need updating.
37
+ - **Pull is gone from the engine.** A Tap's "anything newer" request no longer exists: a Refresh always asks for
38
+ data as of now. Wake now runs once if the Sources are fresher at that moment, rather than waiting for them.
39
+ - `duckstring trigger window POND add|list|remove` is now `duckstring window add|ls|rm POND`.
40
+ - **The old commands and routes are removed, with no compatibility period**: `duckstring trigger …`, `control
41
+ refresh`, `do --refresh`, and `POST /tap`, `/pulse`, `/wave`, `/tide` and `/untrigger`. Scripts need the new ones.
42
+ - **Both ends of a duct need this release.** A Draw sends no requests to an upstream Catchment older than Refresh
43
+ (there, `/refresh` would mark the Pond for a rebuild) and logs that it needs upgrading.
44
+ - **`catchment open --tap-on-get` is removed** (it went with Tap, and fired on every reader, including the web
45
+ UI's data viewer). An open Pond stays open. Taps, below, replace it.
46
+ - `/api/status` lists each Pond's `refreshes` (with `next_due`), `demand_f` and `rebuild_pending`, in place of
47
+ `trigger`, `has_pull`, `target_f` and `refresh_pending`.
48
+ - The Orchestration Theory page was removed from the docs; the Scheduling guide and the `refresh` and `window`
49
+ CLI reference cover what it said that users need.
50
+
51
+ ### Taps: freshness that follows usage
52
+
53
+ A **Tap** is an access point on a Pond for one app: `duckstring refresh tap add reports --name dashboard --fresh-within
54
+ 5m`. It has its own key, shown once, which reads the Pond's exposed tables (as a read-level key does) over HTTP
55
+ (`/api/query`, `/api/serve/query`), the Postgres wire or Arrow Flight, and is refused everywhere else. A query through it sends the Pond a Refresh when the newest request on the Pond, or the
56
+ data, is at least `--fresh-within` old (the `--every` clock, checked per query; `0`, the default, refreshes on every
57
+ query unless a request is already waiting), so the pipeline keeps up while the app is in use and goes quiet when it
58
+ isn't. A query is always answered at once from what is published. Queries with any other key never send demand.
59
+
60
+ - `refresh tap ls|rm|rotate POND`; the routes `/api/ponds/{name}/taps` (full). The web UI lists a Pond's Taps at the
61
+ foot of its Refresh section, adds them in a modal, and shows a Tap's details, Rotate and Delete when clicked.
62
+ - `/metrics` gains `duckstring_tap_queries_total` and `duckstring_tap_refreshes_total`; `/api/status` lists each
63
+ Pond's `taps` (never keys).
64
+ - **`GET /api/ponds/{name}/freshness`** says how fresh a Pond's data is: `freshness` (the point in time it
65
+ reflects), `changed` (when its output last changed), `refreshing`, and when each table and Object last changed. A
66
+ Tap's key can call it for its own Pond, and `Catchment.freshness()` wraps it. It replaces a route of the same name
67
+ that the HTTP reference described as the Pond's freshness but that listed a Trickle table's run freshnesses for
68
+ the data viewer; that list moved to `/api/ponds/{name}/tables/{table}/runs`.
69
+ - The Python `Catchment` client takes a Tap's key as `api_key`, and then needs no `pond`.
70
+ - The demo `reports` exposes `monthly_summary` (`[serve] tables`), so a Tap on it works out of the box.
71
+ - Migration `032` (`pond_tap`).
72
+
73
+ ### The CLI, regrouped
74
+
75
+ The top level goes from 21 commands to 13: `status`, `query`, `get`, `refresh`, `window`, `control`, `data`, `pond`,
76
+ `catchment`, `duck`, `spout`, `secret` and `alert`. Tables and Objects are named `POND.NAME` everywhere, as in the
77
+ Python API. No old forms are kept:
78
+
79
+ | Before | Now |
80
+ |---|---|
81
+ | `query POND [TABLE] [--sql SQL]` | `query POND.TABLE`, or `query SQL [--pond POND]` |
82
+ | `serve query SQL` | `query SQL` (with `--csv`, `--json` and `--parquet` too) |
83
+ | `get POND TABLE [--path]`, `get-object POND NAME [--out]` | `get POND.NAME [--out]` |
84
+ | `objects POND`, `serve status` | `data ls [POND]` (with each table's exposure and last change) |
85
+ | `delete-table POND TABLE`, `delete-object POND NAME` | `data delete POND.NAME` |
86
+ | `serve expose POND TABLE` | `data expose POND.TABLE` |
87
+ | `lineage [POND] [--table T]`, `trace POND.TABLE` | `data lineage [POND[.TABLE]]`, `data trace POND.TABLE` |
88
+ | `serve promote POND` | `pond promote POND` |
89
+ | `puddle ls\|show\|query` | `pond puddle ls\|show\|query` |
90
+ | `catchment open\|close` | `catchment duct open\|close` |
91
+ | `do PONDS --kill …` | `control kill PONDS …`: every `control` verb takes several Ponds and `--all`, `--tree`, `--between`, `--downstream`; `--wipe` is `control wipe`, `--remove` is `pond remove` |
92
+
93
+ `POST /api/serve/query` takes a `format` (`csv`, `json` or `parquet`) to return a file.
94
+
95
+ ### Orchestration
96
+
97
+ - **Requests that arrive faster than the pipeline can serve them are paced to its slowest step** (the throttle),
98
+ always on, with no setting. A step that receives two requests during one run is held there, and one request goes
99
+ upstream each time it starts a new run.
100
+ - **A Refresh no longer refreshes an optional Source after its first build.** Demand propagates only to a Pond's
101
+ required Sources: a Pond never waits for an optional Source, so asking it ran it for nothing. An optional Source
102
+ that has never run still gets one request, so its Sink has output to read, and a Pond whose Sources are all
103
+ optional still asks every one of them. If an optional Source needs a cadence of its own, give it its own Refresh.
104
+ - **A repeat request for the freshness a Pond is already running is ignored.** It used to start a second
105
+ Run at the same freshness, which the no-change skip then completed as a pass, ending the Run in flight
106
+ early. A duct re-sending its downstream's request could cause it.
107
+ - **A join's Sources read together under saturation.** When some of a Pond's Sources are throttled and others
108
+ aren't, its requests reach the faster Sources only once the throttled ones start their next run, so a join
109
+ isn't held back by a fast Source that read early.
110
+ - **A Pond already throttled through another Refresh's Source still sends its first request.** It used to hold
111
+ that request until its own first completion, which the request itself was needed for.
112
+ - **Sleep keeps other Ponds' demand.** Sleeping a Pond (or a path with `--upstream`) clears its own demand, but a
113
+ pending request from another Pond it was serving is now sent to it again; before, that request stranded.
114
+ - **Demand is no longer lost at a halted Source.** A Pond that stops being blocked (after Clear, a recovery or a
115
+ redeploy) re-sends its newest unmet request upstream. Before, a request waiting on a Source whose Run had failed
116
+ or been killed stranded after Clear until something else demanded the path.
117
+
118
+ ### Redeploys and contract failures
119
+
120
+ - **A redeploy takes effect while a recurring Refresh keeps the Pond busy.** A warm Duck used to keep running the
121
+ code it started with until it went idle, which a continuous Refresh never let it do, so a fix redeploy changed
122
+ nothing. A Duck now says which version it runs when it asks for work; a Duck on a replaced version finishes the
123
+ Runs it holds and exits, and a Duck on the new version picks up the next Run.
124
+ - **A Run in flight across a redeploy is recorded against the version that started it.** Its events were recorded
125
+ against the new version, which the database refused, and the Duck retried the refused event forever: the Pond
126
+ showed as running for good, and nothing it did afterwards was recorded.
127
+ - **A contract violation fails the Pond.** The Duck reported the Run's last Ripple as a success before it found the
128
+ output broke the version's contract, so the Catchment counted the Run done and dropped the failure that followed:
129
+ the Pond never showed as failed, and its Sinks ran on output that was never published. A Duck from before this
130
+ release still reports the old way, so redeploy Ducks with the Catchment.
131
+
132
+ ### DuckDB 2.0
133
+
134
+ Duckstring and Trickle support DuckDB 2.0 as well as 1.5 (`duckdb>=1.5,<3`). DuckDB 2.0 needs Python 3.11 or
135
+ newer, so on Python 3.10 DuckDB stays at 1.5.
136
+
137
+ Upgrading:
138
+
139
+ - **Ripple code may need a change.** In DuckDB 2.0 a query run with `execute()` holds its connection until its rows
140
+ are read to the end, so `pond.con.execute(...).fetchone()` followed by another statement fails, and relations
141
+ no longer have `fetchone()`. Read with `fetchall()`. A Pond whose lock keeps DuckDB 1.5 stays on 1.5 while the
142
+ Catchment runs 2.0, so Ponds can move over one at a time.
143
+ - A column that is NULL in every row and has no other type (`SELECT NULL AS note`) is published as `INTEGER`, as
144
+ DuckDB 1.5 typed it. DuckDB 2.0 would give it its own NULL type, which refuses later values.
145
+ - Tables written with `if_changed=True` republish once after the upgrade, since change fingerprints are tied to
146
+ the DuckDB version.
147
+ - A Pond database written by DuckDB 2.0 can't be opened by 1.5. A Pond moved back to 1.5 rebuilds its database
148
+ from what it published, as it would after losing it.
149
+ - On DuckDB 2.0 an integer `//` or `%` by zero raises. A Flock job on StarRocks now fails in the same place and
150
+ runs locally, rather than publishing NULL.
151
+ - Trickle 0.2.1 is required.
152
+
153
+ ### DuckLake Spouts
154
+
155
+ A Spout can deliver to a [DuckLake](https://ducklake.select):
156
+ `duckstring spout add reports --to 'ducklake:sqlite:/srv/lake/meta.sqlite?data_path=s3://lakehouse/files/'`. The
157
+ destination is DuckDB's own attach string for the lake (SQLite, Postgres or DuckDB catalogs), with `data_path`,
158
+ `schema` and the data path's credentials as options. Any table can be delivered: a merge Trickle by its primary
159
+ key, an append Trickle's new rows, a plain table replaced whole. A run's tables go in one transaction, so the
160
+ lake gains one snapshot per delivery, authored `duckstring`. Delivery is exactly-once. Duckstring never
161
+ migrates a lake's catalog, and leaves compaction and snapshot expiry to the lake's owner.
162
+
163
+ Also for Spouts:
164
+
165
+ - `spout resync` now re-delivers in full to Postgres too. Before, a destination that keeps its own record of
166
+ what it has received (Postgres) treated a resync as already delivered.
167
+ - A Postgres table first filled by a full load no longer gains a `_duckstring_f` column that later deliveries
168
+ left empty, and a column a table gains is added to the destination table.
169
+ - A Spout delivering all of a Pond's tables no longer delivers a merge Trickle's changelog as a table of its own.
170
+
171
+ ### Contract checks on remote Ducks
172
+
173
+ A merge or append Trickle with an integer `SUM` column failed its version contract on the first run of a fresh
174
+ Duck (every run on Fargate, EC2 or a pool, after its idle shutdown). `SUM` of an integer is `HUGEINT`, which
175
+ Parquet stores as `DOUBLE`, so a Duck that rebuilt its database from what it had published held `DOUBLE` where the
176
+ contract had recorded `HUGEINT`. The check now compares types as published. Before this release, contract
177
+ failures found at that point were dropped without a trace, which hid it.
178
+
179
+ ### Documentation and Playground
180
+
181
+ The Concepts and Guides have screenshots and recordings of the web UI and the Playground, and the landing page
182
+ leads with the Playground. The Playground has a `chain` example (`?example=chain`), the 1 s → 3 s → 1 s chain the
183
+ Orchestration page uses.
184
+
185
+ ## 0.6.1 — 2026-10-08
186
+
187
+ ### Performance
188
+
189
+ - **Clustering uses a rank-Hilbert order.** Interleaved `cluster_by` columns are now mapped onto a
190
+ Hilbert curve instead of a Morton (Z-order) curve, which on TPC-DS reads 20 to 40% fewer rows for a
191
+ filter on one clustered column at the same cost. Each column's rank is found by binary search over
192
+ quantile boundaries computed in one pass (approximate on tables over ten million rows), replacing
193
+ window functions that sorted every row once per column: on 288 million rows the old encoding filled
194
+ 60 GiB of spill and failed, and the new one adds a few seconds to the sort. Equal values now always
195
+ share a bucket, so the order is deterministic for tables under ten million rows (above that, the
196
+ approximate boundaries can shift slightly between rewrites). `cluster_bits` is rounded up to a
197
+ multiple of the number of columns. A table already clustered is reordered at its next compaction.
198
+
199
+ ## 0.6.0 — 2026-10-05
200
+
201
+ SQL Ripples, clustering, Pond environments and a simpler data plane. The Iceberg plane is gone: every
202
+ Catchment now publishes versioned Parquet, which pins each run's Source reads and costs a third of the
203
+ export time. See "Upgrading" at the end.
204
+
205
+ ### Building Ponds
206
+
207
+ - **SQL Ripples**: a Ripple can be one `SELECT` in a file, declared in `pond.toml` under
208
+ `[ripples.NAME]` with its `parents`, the Source tables it `reads`, and how it writes (`overwrite`,
209
+ `merge` or `append`). Nothing is inferred from the SQL; each query is checked against its declaration
210
+ at deploy, so a forgotten parent is refused instead of silently reading the previous run's table. SQL
211
+ and Python Ripples mix freely (`@ripple(parents=...)` now takes names), and column lineage is
212
+ recorded for SQL Ripples too. `duckstring pond demo --sql` shows the default pipeline in SQL.
213
+ - **Static tables**: files shipped with a Pond's code, declared under `[static.NAME]`, are read-only
214
+ tables every Ripple can query.
215
+ - **Pond environments**: a Pond with a `pyproject.toml` and `uv.lock` runs in its own environment, built
216
+ at deploy on the Catchment and by cloud and Pool Ducks on their own machines. A missing or stale lock,
217
+ or code that won't import, now refuses the deploy with the reason (an import error used to register no
218
+ Ripples).
219
+ - **`.pondignore`**: chooses which files a deploy uploads; by default `puddles/`, `.env` files, hidden
220
+ directories and caches stay behind.
221
+ - Names containing dots can be referenced with backticks (``sales.`daily.v2` ``).
222
+ - A merge Trickle's name is a view over its current state, so plain SQL in later Ripples reads what
223
+ `read_table` returns.
224
+
225
+ ### Performance
226
+
227
+ - **Clustering**: `cluster_by` on `merge_table`, the builder's `.merge()`, `write_table` and SQL Ripples
228
+ orders a table for reads that filter on those columns. One column sorts; two or more are interleaved
229
+ with a rank-Morton order (each column's quantile ranks, bit-interleaved), so every listed column
230
+ prunes, skewed ones included. A merge table's compacted base is now ordered by its primary key by
231
+ default instead of by freshness, which no read could use.
232
+ - A merge table's warm tier is read where it's published instead of being copied into a fresh Duck's
233
+ database, and is no longer also re-published in full on every export.
234
+
235
+ ### Data plane
236
+
237
+ - **Versioned overwrite tables**: each run publishes a plain table as a new immutable version, and a
238
+ Sink run reads the version its Source had published when the run started, even if the Source publishes
239
+ again meanwhile. Superseded versions are kept only while a running Sink still reads them.
240
+ - **The Iceberg plane is removed**, with `pyiceberg`, `DUCKSTRING_DATA_PLANE` and
241
+ `DUCKSTRING_ICEBERG_KEEP_SNAPSHOTS`. It cost about three times the export time, more than twice the
242
+ memory and over three times the disk of the Parquet plane, for interop no external engine could use.
243
+ - A registry DuckDB can't open (written by a newer DuckDB, or corrupt) is set aside and rebuilt from
244
+ published state instead of stopping the Duck.
245
+
246
+ ### Cloud and egress
247
+
248
+ - Flock settings and engine credentials travel with each run, so Fargate and EC2 Ducks can use the
249
+ Flock; they received neither before.
250
+ - The **Duck image is published** to GHCR for amd64 and arm64, tagged with the version, so cloud Ducks
251
+ need no image of your own unless Ponds need system libraries.
252
+ - The S3 Spout now honours `?endpoint=` and `DUCKSTRING_S3_ENDPOINT`, so it works with MinIO, R2 and
253
+ Ceph; it used to reach only AWS. Tested against MinIO in CI.
254
+ - A failed Spout delivery is retried when its source next publishes, and a Spout or alert destination
255
+ can be a single `${env:...}` or `${secret:...}` reference.
256
+ - A Pond Draw records its upstream's real version, so a `[sources]` pin above `{major}.0.0` deploys.
257
+ - A Duck that lost its local publish restores it from the durable layer before rebuilding, so Ponds on
258
+ the same machine never read it incomplete; the durable copy no longer loses warm bands after such a
259
+ rebuild.
260
+
261
+ ### Correctness and operability
262
+
263
+ - **DuckDB is bounded** to the tested range, `>=1.5,<2`, and CI tests the floor and the DuckDB 2.0
264
+ pre-release. A change to or from DuckDB 2.0's `VARIANT` type counts as breaking in the version
265
+ contract.
266
+ - `catchment init` and `start` warn when an open Catchment binds beyond loopback, since a deploy runs code
267
+ on the machine.
268
+ - Querying a missing table says the Pond hasn't run yet, or lists the tables it has.
269
+ - A Pool agent stopped with SIGTERM stops its Ducks.
270
+ - A Fargate or EC2 Duck shut down for being idle is replaced on the Pond's next run. Before, the Catchment
271
+ kept the departed Duck's record, launched nothing, and failed the run as "Lost contact" three minutes
272
+ later.
273
+ - An EC2 Duck's instance terminates when the Duck exits cleanly, instead of running, and billing, after
274
+ the Duck has gone. A Duck that crashes leaves its instance up so its console output can be read.
275
+ - SQL Ripples that read a Source work on the DuckDB 2.0 pre-release.
276
+ - A Catchment without API keys could answer a request with 401 under load. The Catchment's database
277
+ connection is shared by its threads, and the key check read it without the lock the engine holds,
278
+ so it sometimes saw a key that wasn't there. Every user of the connection now takes that lock, and
279
+ the key check is cached.
280
+ - A Duck that has heard nothing from its Catchment for an hour while idle exits, so a Duck left behind by
281
+ a Catchment that was deleted or stopped hard doesn't run, and bill, indefinitely. Set
282
+ `DUCKSTRING_DUCK_ORPHAN_MINUTES` on the Catchment to change the limit, or `0` to turn it off. Pool
283
+ machines aren't affected.
284
+
285
+ ### Documentation
286
+
287
+ The documentation is rewritten: Quickstart, Concepts, task-led Guides and a full Reference (Python API,
288
+ `pond.toml`, every CLI command group, the HTTP API, formats and environment variables), with the README and
289
+ landing page repositioned around Duckstring as a data engineering platform for DuckDB.
290
+
291
+ ### Upgrading
292
+
293
+ - **Published layout**: plain tables are now written as `{table}__v/{version}.parquet`. Readers still read
294
+ a Source's old single `{table}.parquet` until it publishes again, so Ponds can be upgraded in any order.
295
+ - **Leftover Iceberg files**: a data directory published by the Iceberg plane keeps a `catalog.json` and a
296
+ `pond/` directory that nothing reads now. Delete them; on a Catchment using local-first publish with an
297
+ S3 data root, the Persist mirror would otherwise upload them.
298
+ - **`DUCKSTRING_DATA_PLANE`** is no longer read; remove it from the environment.
299
+ - **DuckDB 2.0** isn't supported yet: installing Duckstring keeps DuckDB below 2, including in Pond
300
+ environments that lock a newer one.
301
+ - The Catchment database migrates itself on start (one new column).
302
+
303
+ ## 0.5.0 — 2026-08-03
304
+
305
+ The cloud release: a Catchment can now run its Ducks on AWS, keep its data on S3, and serve that data
306
+ over standard wire protocols. Everything below is opt-in — a Catchment with no cloud configuration
307
+ behaves exactly as it did in 0.4.0, and a `pond.toml` asking for remote compute still runs anywhere.
308
+
309
+ ### Cloud compute
310
+
311
+ - **Duck launchers**: a Pond's Duck runs on the Catchment's own box (default), on **Fargate** (the
312
+ default remote backend — serverless containers, task-role IAM), or on **EC2** (the escape hatch, for
313
+ sizes and GPUs Fargate does not offer). Built-in **S/M/L/XL preset pools** resolve with zero setup.
314
+ - **Duck Pools**: named remote-compute pools (`duckstring duck pool add`), with per-Pond compute
315
+ declared in `pond.toml` and overridable operationally (`duckstring duck set`).
316
+ - **The Pool agent**: one shared machine per named Pool hosting many co-resident Ducks.
317
+ - **Auto-relay**: a Catchment on a laptop behind NAT can run cloud Ducks with no manual tunnel — the
318
+ first remote spawn provisions a small always-reachable box and holds a reverse tunnel to it.
319
+ - **Remote failures are observable**: boot output is teed to the console (EC2) / CloudWatch (Fargate)
320
+ and the tail is attached to the Pond's failure, so a Duck that dies before dialling back still says
321
+ why. Spawn-failure reasons (missing image, missing AMI, bad IAM) surface on the Pond rather than as a
322
+ generic crash.
323
+ - Per-provider **startup grace** so a cold machine is not judged by the steady-state silence window.
324
+
325
+ ### Data plane on S3
326
+
327
+ - A Catchment's data root can be an object store (`duckstring catchment settings --data-root s3://…`),
328
+ with an S3-compatible **endpoint override** for MinIO/Ceph/R2.
329
+ - **Switch, adopt, or migrate** an existing plane, with a background copy and live progress.
330
+ - **Local-first publish + async Persist**: a run publishes locally and mirrors durably in the
331
+ background, so compute never waits on the object store; `persisted_f` is the durable watermark and
332
+ freshness gating is Pool-aware.
333
+ - **S3-resident state**: a merge base is read as a view rather than hydrated, and retention pruning is
334
+ floor-anchored rather than inferred from absence.
335
+
336
+ ### The Flock — over-envelope compute
337
+
338
+ A comprehensive `pond.trickle(...)` recompute that exceeds the Duck's memory envelope can be dispatched
339
+ to a serverless engine (**Athena** ships first), while the Duck keeps merge/diff/publish.
340
+
341
+ **DuckDB is the authority.** Dispatch decides *where* work runs, never *what* is published, enforced
342
+ structurally: an engine-owned allow-list of expressions proven equivalent on both engines (division and
343
+ CAST are excluded — the two engines genuinely disagree), a `conform` step that casts the engine's result
344
+ to DuckDB's own schema and rejects a differing column set, and degradation to local compute on any
345
+ failure. Dispatch counters ride `/metrics`, since a silently degrading Flock is otherwise invisible.
346
+
347
+ ### Data serving
348
+
349
+ - A sandboxed, warm, read-only query surface over published data: catalog = catchment, schema = pond.
350
+ - **Postgres wire** and **Arrow Flight SQL** adapters, plus `/api/serve` and a CLI.
351
+ - A **Catalog UI** unifying the query surface, with role-governed access.
352
+
353
+ ### Lineage
354
+
355
+ - Table-level (observed per run), column-level (static per version, with a sqlglot upgrade for SQL
356
+ outputs), and row-level temporal provenance (`duckstring trace`).
357
+ - **OpenLineage** events emitted on run completion for catalog integration.
358
+
359
+ ### dbt-mode Ponds
360
+
361
+ Deploy a dbt project as a Pond with no `@ripple` code: each dbt model becomes a Ripple with its own
362
+ freshness, failure and retry tracking, and `ref()` becomes the intra-Pond graph. Opt in with the
363
+ `duckstring[dbt]` extra.
364
+
365
+ ### Correctness and operability
366
+
367
+ - **Version contract**: a lossless widening of a column type is accepted (a data-dependent branch could
368
+ legitimately wedge a live Pond permanently); `reset-contract` is the escape hatch for a genuine
369
+ narrowing; contract failures carry a dedicated sub-reason end-to-end.
370
+ - **Reads reject a stale local publish** the Catchment knows is behind, rather than serving it.
371
+ - Pond Runs the Pond has moved past are closed rather than stranded at `running`.
372
+ - A Pool machine that disappears is detected and relaunched, instead of wedging every Pond on it.
373
+ - The **serving sandbox** no longer spills into the shared system temp — DuckDB exempts its temp
374
+ directory from the external-access lock, which on Linux exposed everything under `/tmp` to a
375
+ read-level user.
376
+ - An idle Duck backs off instead of polling ten times a second.
377
+ - Opt-in **re-notify cadence** for alert channels while a failure or freshness episode persists.
378
+ - Incremental object-store egress (`mode=append`) mirrors the published collection per delivery.
379
+
380
+ ### Testing
381
+
382
+ - The object-store data plane runs against a **real S3 API** in CI (MinIO, which rejects unsigned
383
+ requests — the class of bug moto cannot catch).
384
+ - Real-data demo sets: **TPC-DS** and **GHArchive** (the latter offline against a committed fixture).
385
+ - Python matrix, the dbt extra, real-Postgres egress, and frontend tests in CI.
386
+
387
+ ### Documentation
388
+
389
+ - A full **AWS guide** (`guides/cloud.md`): the cloud-enable gate, the three IAM roles, security-group
390
+ asymmetry, the AMI Python constraint, the build-your-own-image policy, and the Flock's authority rules.
391
+ - New guides for lineage, dbt-mode, and querying data.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: duckstring
3
- Version: 0.6.0rc2
3
+ Version: 0.7.0
4
4
  Summary: An open source data engineering platform for DuckDB: versioned transformations, pull orchestration, incremental processing and a built-in catalog.
5
5
  Author-email: Duckstring <dev@duckstring.com>
6
6
  License: Apache License
@@ -212,7 +212,8 @@ Project-URL: Playground, https://playground.duckstring.com
212
212
  Requires-Python: >=3.10
213
213
  Description-Content-Type: text/markdown
214
214
  License-File: LICENSE
215
- Requires-Dist: duckdb<2,>=1.5
215
+ Requires-Dist: duckdb<3,>=1.5
216
+ Requires-Dist: duckstring-trickle<0.3,>=0.2.1
216
217
  Requires-Dist: rich>=13.7
217
218
  Requires-Dist: typer>=0.12
218
219
  Requires-Dist: fastapi>=0.111
@@ -232,6 +233,10 @@ Requires-Dist: sqlglot>=23; extra == "lineage"
232
233
  Provides-Extra: aws
233
234
  Requires-Dist: s3fs>=2023.1; extra == "aws"
234
235
  Requires-Dist: boto3>=1.34; extra == "aws"
236
+ Requires-Dist: sqlglot<31,>=30.21; extra == "aws"
237
+ Provides-Extra: starrocks
238
+ Requires-Dist: pymysql>=1.1; extra == "starrocks"
239
+ Requires-Dist: sqlglot<31,>=30.21; extra == "starrocks"
235
240
  Provides-Extra: dev
236
241
  Requires-Dist: pytest>=8.0; extra == "dev"
237
242
  Requires-Dist: moto[server]>=5.0; extra == "dev"
@@ -239,10 +244,11 @@ Requires-Dist: pytest-timeout>=0.5; extra == "dev"
239
244
  Requires-Dist: ruff>=0.3; extra == "dev"
240
245
  Requires-Dist: mypy>=1.8; extra == "dev"
241
246
  Requires-Dist: dbt-duckdb>=1.9; extra == "dev"
242
- Requires-Dist: sqlglot>=23; extra == "dev"
247
+ Requires-Dist: sqlglot<31,>=30.21; extra == "dev"
243
248
  Requires-Dist: s3fs>=2023.1; extra == "dev"
244
249
  Requires-Dist: boto3>=1.34; extra == "dev"
245
250
  Requires-Dist: pg8000>=1.31; extra == "dev"
251
+ Requires-Dist: pymysql>=1.1; extra == "dev"
246
252
  Dynamic: license-file
247
253
 
248
254
  # Duckstring
@@ -274,9 +280,13 @@ The pipeline follows from those declarations, so it never has to be drawn by han
274
280
  Schedules are set on the Ponds whose output is actually used, at the end of the pipeline. Duckstring works back from there, running each upstream Pond only as often as something downstream needs it, so a path nobody consumes never runs. A daily job is one command:
275
281
 
276
282
  ```bash
277
- duckstring trigger tide reports 1d
283
+ duckstring refresh add reports --every 1d
278
284
  ```
279
285
 
286
+ Data can also stay fresh just because it's being used: a [Tap](https://docs.duckstring.com/guides/serving_apps_with_taps) gives an app its own key, and its queries refresh the Pond when the data is older than you allow, so nothing runs while the app is idle.
287
+
288
+ The [Playground](https://playground.duckstring.com) runs the demo pipeline in your browser, with a short guided tour of how it's scheduled.
289
+
280
290
  ## Incremental processing
281
291
 
282
292
  A *Trickle* stores a table's changes as a Z-set: rows weighted +1 when added and -1 when removed. Joins and aggregations written with the Trickle builder recompute only the keys that changed since the last run, and handle updates and deletes correctly across any shape of join:
@@ -325,8 +335,8 @@ This starts a local Catchment, with its web UI at http://127.0.0.1:7474. Leave i
325
335
  mkdir demo && cd demo
326
336
  duckstring pond demo # transactions, products → sales → reports
327
337
  duckstring pond deploy --all --yes
328
- duckstring trigger pulse reports # run the pipeline once, from the end
329
- duckstring query reports monthly_summary
338
+ duckstring refresh now reports # run the pipeline once, from the end
339
+ duckstring query reports.monthly_summary
330
340
  ```
331
341
 
332
342
  The [Quickstart](https://docs.duckstring.com/quickstart) goes through the same steps with explanations.
@@ -343,7 +353,7 @@ The [Quickstart](https://docs.duckstring.com/quickstart) goes through the same s
343
353
 
344
354
  ## Documentation
345
355
 
346
- [docs.duckstring.com](https://docs.duckstring.com) has the concepts, task guides, and a reference for the CLI, Python API, `pond.toml` and HTTP API. To try pull orchestration without installing anything, use the [Playground](https://playground.duckstring.com).
356
+ [docs.duckstring.com](https://docs.duckstring.com) has the concepts, task guides, and a reference for the CLI, Python API, `pond.toml` and HTTP API. To try pull orchestration without installing anything, the [Playground](https://playground.duckstring.com) simulates it in the browser, with a guided tour.
347
357
 
348
358
  ## Status
349
359
 
@@ -27,9 +27,13 @@ The pipeline follows from those declarations, so it never has to be drawn by han
27
27
  Schedules are set on the Ponds whose output is actually used, at the end of the pipeline. Duckstring works back from there, running each upstream Pond only as often as something downstream needs it, so a path nobody consumes never runs. A daily job is one command:
28
28
 
29
29
  ```bash
30
- duckstring trigger tide reports 1d
30
+ duckstring refresh add reports --every 1d
31
31
  ```
32
32
 
33
+ Data can also stay fresh just because it's being used: a [Tap](https://docs.duckstring.com/guides/serving_apps_with_taps) gives an app its own key, and its queries refresh the Pond when the data is older than you allow, so nothing runs while the app is idle.
34
+
35
+ The [Playground](https://playground.duckstring.com) runs the demo pipeline in your browser, with a short guided tour of how it's scheduled.
36
+
33
37
  ## Incremental processing
34
38
 
35
39
  A *Trickle* stores a table's changes as a Z-set: rows weighted +1 when added and -1 when removed. Joins and aggregations written with the Trickle builder recompute only the keys that changed since the last run, and handle updates and deletes correctly across any shape of join:
@@ -78,8 +82,8 @@ This starts a local Catchment, with its web UI at http://127.0.0.1:7474. Leave i
78
82
  mkdir demo && cd demo
79
83
  duckstring pond demo # transactions, products → sales → reports
80
84
  duckstring pond deploy --all --yes
81
- duckstring trigger pulse reports # run the pipeline once, from the end
82
- duckstring query reports monthly_summary
85
+ duckstring refresh now reports # run the pipeline once, from the end
86
+ duckstring query reports.monthly_summary
83
87
  ```
84
88
 
85
89
  The [Quickstart](https://docs.duckstring.com/quickstart) goes through the same steps with explanations.
@@ -96,7 +100,7 @@ The [Quickstart](https://docs.duckstring.com/quickstart) goes through the same s
96
100
 
97
101
  ## Documentation
98
102
 
99
- [docs.duckstring.com](https://docs.duckstring.com) has the concepts, task guides, and a reference for the CLI, Python API, `pond.toml` and HTTP API. To try pull orchestration without installing anything, use the [Playground](https://playground.duckstring.com).
103
+ [docs.duckstring.com](https://docs.duckstring.com) has the concepts, task guides, and a reference for the CLI, Python API, `pond.toml` and HTTP API. To try pull orchestration without installing anything, the [Playground](https://playground.duckstring.com) simulates it in the browser, with a guided tour.
100
104
 
101
105
  ## Status
102
106
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "duckstring"
3
- version = "0.6.0rc2"
3
+ version = "0.7.0"
4
4
  description = "An open source data engineering platform for DuckDB: versioned transformations, pull orchestration, incremental processing and a built-in catalog."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -10,10 +10,13 @@ authors = [
10
10
  ]
11
11
 
12
12
  dependencies = [
13
- # The tested range: CI runs the suite on the floor and the latest release, and against the pre-release
14
- # in an allowed-to-fail job. Raise the ceiling once the suite passes on the next major's release
15
- # (plans/duckdb-2-ready.md). A Pond environment gets this Duckstring, so these bounds hold there too.
16
- "duckdb>=1.5,<2",
13
+ # The tested range: CI runs the suite on the floor (1.5, an LTS) and the latest release, and against the
14
+ # pre-release in an allowed-to-fail job. Raise the ceiling once the suite passes on the next major's release
15
+ # (plans/duckdb-2.md). A Pond environment gets this Duckstring, so these bounds hold there too.
16
+ "duckdb>=1.5,<3",
17
+ # The incremental engine (packages/trickle), released separately. A compatible range: while it's 0.x a
18
+ # minor release can change its API or storage format, so Duckstring moves to one deliberately.
19
+ "duckstring-trickle>=0.2.1,<0.3",
17
20
  "rich>=13.7",
18
21
  "typer>=0.12",
19
22
  "fastapi>=0.111",
@@ -48,10 +51,19 @@ lineage = [
48
51
  ]
49
52
  # AWS / cloud (plans/cloud-config.md): the object-store data plane + egress (s3fs — fsspec's small-file
50
53
  # work; DuckDB does the bulk Parquet I/O over httpfs on its own), and the remote-compute launchers
51
- # (EC2/Fargate/relay) + the Athena Flock engine (boto3). Opt-in — a local/offline Catchment needs none.
54
+ # (EC2/Fargate/relay) + the Athena Flock engine (boto3, and sqlglot to translate its jobs). Opt-in: a
55
+ # local/offline Catchment needs none.
52
56
  aws = [
53
57
  "s3fs>=2023.1",
54
58
  "boto3>=1.34",
59
+ "sqlglot>=30.21,<31",
60
+ ]
61
+ # The StarRocks Flock engine (plans/flock-coverage.md): the MySQL-protocol client, and sqlglot to translate
62
+ # jobs. sqlglot is pinned to one major because what an engine is trusted with is measured against its
63
+ # translations: re-run tests/test_flock_starrocks_conformance.py before widening it.
64
+ starrocks = [
65
+ "pymysql>=1.1",
66
+ "sqlglot>=30.21,<31",
55
67
  ]
56
68
  dev = [
57
69
  "pytest>=8.0",
@@ -61,10 +73,11 @@ dev = [
61
73
  "ruff>=0.3",
62
74
  "mypy>=1.8",
63
75
  "dbt-duckdb>=1.9",
64
- "sqlglot>=23",
76
+ "sqlglot>=30.21,<31",
65
77
  "s3fs>=2023.1",
66
78
  "boto3>=1.34",
67
79
  "pg8000>=1.31",
80
+ "pymysql>=1.1",
68
81
  ]
69
82
 
70
83
  [tool.setuptools]
@@ -77,6 +90,12 @@ package-dir = { "" = "src" }
77
90
  [tool.setuptools.packages.find]
78
91
  where = ["src"]
79
92
 
93
+ [tool.uv.workspace]
94
+ members = ["packages/trickle"]
95
+
96
+ [tool.uv.sources]
97
+ duckstring-trickle = { workspace = true }
98
+
80
99
  [project.urls]
81
100
  Homepage = "https://duckstring.com"
82
101
  Documentation = "https://docs.duckstring.com"
@@ -88,7 +107,7 @@ duckstring = "duckstring.cli:main"
88
107
  ds = "duckstring.cli:main"
89
108
 
90
109
  [tool.pytest.ini_options]
91
- testpaths = ["tests"]
110
+ testpaths = ["tests", "packages/trickle/tests"]
92
111
  # Some suites share fixtures by importing `tests.<module>`; put the repo root on the path so a bare
93
112
  # `pytest` (what CI runs) resolves them the same as `python -m pytest`.
94
113
  pythonpath = ["."]
@@ -104,3 +123,8 @@ extend-exclude = ["experiment"]
104
123
  [tool.ruff.lint]
105
124
  select = ["E", "F", "I", "B"]
106
125
  ignore = ["B008"] # typer.Option/Argument in defaults is intentional
126
+
127
+ [tool.ruff.lint.isort]
128
+ # Explicit, because ruff versions disagree on modules that only resolve through an alias: newer ones class
129
+ # duckstring.trickle.* (now packages/trickle, aliased) as third-party. `trickle` itself is third-party here.
130
+ known-first-party = ["duckstring"]
@@ -9,6 +9,7 @@ from pathlib import Path
9
9
  from fastapi import FastAPI
10
10
  from fastapi.staticfiles import StaticFiles
11
11
 
12
+ from ..trickle_io import first_row
12
13
  from . import auth
13
14
  from .db import connect, ensure_identity, migrate
14
15
  from .driver import Driver
@@ -19,7 +20,7 @@ _STATIC_DIR = Path(__file__).parent / "static"
19
20
 
20
21
 
21
22
  async def _scheduler(driver: Driver) -> None:
22
- """Drive clock processes (Tide deadlines, window boundaries, Wave-on-idle) at next_wake."""
23
+ """Drive clock processes (recurring Refreshes, window boundaries) at next_wake."""
23
24
  while True:
24
25
  nw = driver.next_wake()
25
26
  now = datetime.now(timezone.utc)
@@ -205,7 +206,7 @@ def create_app(
205
206
  from ..storage import get_storage
206
207
  from .data_lease import acquire_lease
207
208
 
208
- cid = con.execute("SELECT value FROM catchment_meta WHERE key = 'id'").fetchone()
209
+ cid = first_row(con.execute("SELECT value FROM catchment_meta WHERE key = 'id'"))
209
210
  owner_id = cid[0] if cid else "unknown"
210
211
  lease_store = get_storage(data_root)
211
212
  acquire_lease(lease_store, owner_id) # raises LeaseConflict → the server does not start