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.
- duckstring-0.7.0/CHANGELOG.md +391 -0
- {duckstring-0.6.0rc2/src/duckstring.egg-info → duckstring-0.7.0}/PKG-INFO +17 -7
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/README.md +8 -4
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/pyproject.toml +32 -8
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/app.py +3 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/auth.py +44 -3
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/cloud.py +4 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/db.py +3 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/driver.py +492 -308
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/egress_worker.py +39 -28
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/flight_sql.py +25 -15
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/pg_wire.py +21 -14
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/poller.py +31 -20
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/__init__.py +2 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/data.py +131 -49
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/deploy.py +38 -21
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/draw.py +1 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/duck.py +2 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/metrics.py +10 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/orchestrate.py +60 -57
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/serving.py +60 -10
- duckstring-0.7.0/src/duckstring/catchment/routes/tap.py +64 -0
- duckstring-0.7.0/src/duckstring/catchment/schema/030_flock_accept.sql +5 -0
- duckstring-0.7.0/src/duckstring/catchment/schema/031_refresh.sql +83 -0
- duckstring-0.7.0/src/duckstring/catchment/schema/032_tap.sql +16 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/serving.py +42 -15
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/404.html +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next.__PAGE__.txt +2 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next._full.txt +2 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next._head.txt +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next._index.txt +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/__next._tree.txt +1 -1
- duckstring-0.7.0/src/duckstring/catchment/static/_next/static/chunks/0h6ulcnnp2foi.js +2 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._full.txt +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._head.txt +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._index.txt +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._not-found.txt +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found/__next._tree.txt +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found.html +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_not-found.txt +1 -1
- duckstring-0.7.0/src/duckstring/catchment/static/index.html +1 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/index.txt +2 -2
- duckstring-0.7.0/src/duckstring/catchment/taps.py +236 -0
- duckstring-0.7.0/src/duckstring/changes.py +122 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/__init__.py +22 -41
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/catchment.py +9 -7
- duckstring-0.7.0/src/duckstring/cli/control.py +346 -0
- duckstring-0.7.0/src/duckstring/cli/data.py +351 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/deploy.py +19 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/duck.py +10 -5
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/duct.py +16 -13
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/lineage.py +23 -15
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/pond.py +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/puddle.py +3 -1
- duckstring-0.7.0/src/duckstring/cli/refresh.py +202 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/spout.py +5 -3
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/status.py +10 -10
- duckstring-0.7.0/src/duckstring/cli/tap.py +135 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/window.py +61 -49
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/core.py +312 -20
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/dataplane.py +238 -79
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_events/src/pond.py +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_repo_activity/README.md +2 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/orders/src/pond.py +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/src/puddles.py +4 -4
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/products/src/pond.py +1 -1
- duckstring-0.7.0/src/duckstring/demo/reports/pond.toml +11 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/README.md +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/src/puddles.py +2 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/README.md +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_reports/pond.toml +4 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_category_revenue/README.md +2 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_category_revenue/src/pond.py +2 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_items/src/pond.py +2 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_sales/src/pond.py +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_stores/src/pond.py +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/transactions/src/pond.py +1 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/__init__.py +2 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/__main__.py +6 -3
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/client.py +7 -4
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/core.py +30 -4
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/dbt_executor.py +4 -4
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/executor.py +17 -13
- duckstring-0.7.0/src/duckstring/egress/attached.py +178 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/base.py +6 -1
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/destination.py +4 -3
- duckstring-0.7.0/src/duckstring/egress/ducklake.py +142 -0
- duckstring-0.7.0/src/duckstring/egress/postgres.py +55 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/__init__.py +32 -28
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/catchment.py +430 -240
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/core.py +115 -54
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/worker.py +15 -15
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/environments.py +31 -14
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/flock/__init__.py +164 -81
- duckstring-0.7.0/src/duckstring/flock/engines/athena.py +154 -0
- duckstring-0.7.0/src/duckstring/flock/engines/motherduck.py +62 -0
- duckstring-0.7.0/src/duckstring/flock/engines/remote_duckdb.py +225 -0
- duckstring-0.7.0/src/duckstring/flock/engines/sql_engine.py +206 -0
- duckstring-0.7.0/src/duckstring/flock/engines/starrocks.py +305 -0
- duckstring-0.7.0/src/duckstring/flock/translate.py +289 -0
- duckstring-0.7.0/src/duckstring/flock/verify.py +123 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/local/runner.py +8 -5
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/objects.py +75 -7
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/schema_contract.py +19 -2
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/sql_ripples.py +3 -2
- duckstring-0.7.0/src/duckstring/trickle/__init__.py +17 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0/src/duckstring.egg-info}/PKG-INFO +17 -7
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring.egg-info/SOURCES.txt +21 -16
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring.egg-info/requires.txt +9 -2
- duckstring-0.6.0rc2/CHANGELOG.md +0 -197
- duckstring-0.6.0rc2/src/duckstring/catchment/static/_next/static/chunks/09y-jd_5pt5my.js +0 -2
- duckstring-0.6.0rc2/src/duckstring/catchment/static/index.html +0 -1
- duckstring-0.6.0rc2/src/duckstring/cli/bulk.py +0 -135
- duckstring-0.6.0rc2/src/duckstring/cli/control.py +0 -227
- duckstring-0.6.0rc2/src/duckstring/cli/data.py +0 -273
- duckstring-0.6.0rc2/src/duckstring/cli/serve.py +0 -97
- duckstring-0.6.0rc2/src/duckstring/cli/trigger.py +0 -119
- duckstring-0.6.0rc2/src/duckstring/demo/reports/pond.toml +0 -7
- duckstring-0.6.0rc2/src/duckstring/egress/postgres.py +0 -170
- duckstring-0.6.0rc2/src/duckstring/flock/engines/athena.py +0 -298
- duckstring-0.6.0rc2/src/duckstring/flock/equivalence.py +0 -75
- duckstring-0.6.0rc2/src/duckstring/trickle/__init__.py +0 -71
- duckstring-0.6.0rc2/src/duckstring/trickle/acc.py +0 -122
- duckstring-0.6.0rc2/src/duckstring/trickle/agg.py +0 -184
- duckstring-0.6.0rc2/src/duckstring/trickle/builder.py +0 -1176
- duckstring-0.6.0rc2/src/duckstring/trickle/capture.py +0 -363
- duckstring-0.6.0rc2/src/duckstring/trickle/context.py +0 -53
- duckstring-0.6.0rc2/src/duckstring/trickle/io.py +0 -2547
- duckstring-0.6.0rc2/src/duckstring/trickle/lineage.py +0 -305
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/LICENSE +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/MANIFEST.in +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/setup.cfg +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/__init__.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/__main__.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/acc.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/agg.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/__init__.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/base.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/email.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/event.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/alerts/webhook.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/__init__.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/alert_worker.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/asgi.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/cloud_backends.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/cloud_deploy.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/dag.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/data_lease.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/dialback.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/ec2_launcher.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/fargate_launcher.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/launcher.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/pool_launcher.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/registry.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/relay.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/alerts.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/catchment.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/duct.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/pool.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/secrets.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/routes/view.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/001_init.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/002_ducts.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/003_pull_m.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/004_identity.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/005_pond_version_schema.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/006_refresh.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/007_catchment_key.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/008_spout.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/009_spout_state.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/010_spout_wake.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/011_spout_window.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/012_spout_node.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/013_changed_f.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/014_alert.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/016_alert_scope_major.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/017_alert_renotify.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/018_duck.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/019_lineage.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/020_column_lineage.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/021_cloud.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/022_retire_duck_size.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/023_pool_provider.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/024_serving.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/025_dbt.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/026_deploy_config.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/027_persist.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/028_persist_timing.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/schema/029_source_pins.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/secrets.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/state_sync.py +0 -0
- {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
- {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
- {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
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/03~yq9q893hmn.js +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/07lhk_q6pmm3r.js +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/0_8xp0_1~w7l7.css +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/0dbhjjzl8qfwv.js +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/0jvmviuftg5e2.css +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/0kczw6usu-y-f.js +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/11kjahy2ntf0n.js +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/_next/static/chunks/turbopack-03~mbvk_uplk_.js +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/favicon.svg +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/logo-mark.svg +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/catchment/static/logo.svg +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/_http.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/alert.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/config.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/cli/secret.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/dbt_mode.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/catalog/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/catalog/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/catalog/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/catalog/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_actors/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_actors/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_actors/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_events/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_events/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_pushes/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_pushes/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_pushes/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_repo_activity/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_repo_activity/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_stars/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_stars/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_stars/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_trending/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_trending/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/gh_trending/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/orders/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/orders/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/orders/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/priced/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/products/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/products/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/products/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/reports/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/reports/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/reports/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/revenue/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sales/src/puddles.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/dbt_project.yml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/models/orders_clean.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/models/revenue_by_product.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/models/sources.yml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/dbt/models/top_products.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_analytics/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_orders/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_orders/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_orders/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/shop_orders/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_reports/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_reports/sql/monthly_summary.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/data/tier_bands.csv +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/sql/daily_sales.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/sql/price_tiers.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/sql/sale_line.sql +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/sql_sales/src/puddles.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_category_revenue/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_items/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_items/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_priced/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_priced/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_priced/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_sales/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_sales/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_store_revenue/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_store_revenue/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_store_revenue/src/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_stores/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/tpcds_stores/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/transactions/.gitignore +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/transactions/README.md +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/demo/transactions/pond.toml +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/discover.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/duck/pool_agent.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/__init__.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/credentials.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/egress/object_store.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/engine/pond.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/flock/engines/__init__.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/keys.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/local/__init__.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/local/hydrate.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/local/project.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/pondignore.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/storage.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/trickle_builder.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring/trickle_io.py +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring.egg-info/dependency_links.txt +0 -0
- {duckstring-0.6.0rc2 → duckstring-0.7.0}/src/duckstring.egg-info/entry_points.txt +0 -0
- {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.
|
|
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<
|
|
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
|
|
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
|
|
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
|
|
329
|
-
duckstring query reports
|
|
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,
|
|
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
|
|
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
|
|
82
|
-
duckstring query reports
|
|
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,
|
|
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.
|
|
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
|
|
14
|
-
# in an allowed-to-fail job. Raise the ceiling once the suite passes on the next major's release
|
|
15
|
-
# (plans/duckdb-2
|
|
16
|
-
"duckdb>=1.5,<
|
|
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
|
|
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>=
|
|
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 (
|
|
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'")
|
|
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
|