diff --git a/.github/demos/rename.gif b/.github/demos/rename.gif index dbafd7f81b..a374103aba 100644 Binary files a/.github/demos/rename.gif and b/.github/demos/rename.gif differ diff --git a/README.md b/README.md index 3dc9d8eb68..8b04b0cc7d 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ Python data pipelines, and it keeps its state in append-only tables in your own external state database, no manifest files and no paid tier.

- Moving and renaming an incremental model: sqb plan migrates the existing table instead of rebuilding it + Moving and renaming an incremental model with sqb mv: sqb plan migrates the existing table instead of rebuilding it

-Move an incremental model to a new folder and name, and `sqb plan` migrates its table instead of -rebuilding it. +Move an incremental model to a new folder and name with `sqb mv`, and `sqb plan` migrates its table +instead of rebuilding it. ## Quick start @@ -62,9 +62,11 @@ The playground runs on local DuckDB, with no warehouse credentials. ### Change it without rebuilding everything -- **Renames keep their history.** Rename or move an incremental or snapshot model and SQLBuild +- **Renames keep their history.** [`sqb rename` and `sqb mv`](https://sqlbuild.com/docs/cli/rename/) + update every reference, and the next build [migrates](https://sqlbuild.com/docs/concepts/models/migrations/) the existing table instead of - rebuilding it. + rebuilding it. The old name keeps working through a compatibility view. + `sqb rename column:.` renames an incremental model's column in place. - **Replay on change.** When a model's SQL changes, choose how far back to reprocess, from only the new data to the last 14 days to a full rebuild, with [`replay_on_change`](https://sqlbuild.com/docs/concepts/incremental/#replay-on-change). diff --git a/website/examples/README.md b/website/examples/README.md index 519c2c1f88..0a53b6bd35 100644 --- a/website/examples/README.md +++ b/website/examples/README.md @@ -42,6 +42,20 @@ rm models/weekly_revenue.sql sqb janitor ``` +## Step 07: rename + +`waffle-shop` shows the state after the rename. Give the model its old name, build it, then rename +it back: + +```bash +cd waffle-shop +mv models/marts/daily_order_rollup.sql models/marts/daily_activity_rollup.sql +sed -i 's/daily_order_rollup/daily_activity_rollup/g' models/marts/hourly_activity_with_daily_context.sql +sqb build +sqb rename model:daily_activity_rollup daily_order_rollup +sqb plan +``` + ## Hero: move and rename `hero-shop` shows the state after the move. The model started as `models/marts/revenue.sql`: @@ -51,6 +65,9 @@ cd hero-shop mkdir -p models/marts mv models/finance/daily_revenue.sql models/marts/revenue.sql sqb build -mv models/marts/revenue.sql models/finance/daily_revenue.sql +sqb mv model:revenue models/finance/daily_revenue.sql sqb plan ``` + +`sqb rename` and `sqb mv` add `migrate_from` to the model header; the example projects leave it out, +so they can be set up again. diff --git a/website/examples/hero-shop/models/finance/daily_revenue.sql b/website/examples/hero-shop/models/finance/daily_revenue.sql index 7270016cb3..d4fe35d9fe 100644 --- a/website/examples/hero-shop/models/finance/daily_revenue.sql +++ b/website/examples/hero-shop/models/finance/daily_revenue.sql @@ -5,6 +5,14 @@ MODEL ( cursor revenue_date, cursor_type timestamp, cursor_grain day, + cursor_inputs ( + payments paid_at, + ), ); -SELECT CAST('2026-04-01' AS DATE) AS revenue_date, 2850 AS total_revenue_cents +SELECT + CAST(paid_at AS DATE) AS revenue_date, + SUM(amount_cents) AS total_revenue_cents +FROM __seed("payments") +GROUP BY + CAST(paid_at AS DATE) diff --git a/website/examples/hero-shop/seeds/payments.csv b/website/examples/hero-shop/seeds/payments.csv new file mode 100644 index 0000000000..bc4f6767ab --- /dev/null +++ b/website/examples/hero-shop/seeds/payments.csv @@ -0,0 +1,6 @@ +payment_id,paid_at,amount_cents +1,2026-04-01 09:12:00,1250 +2,2026-04-01 13:40:00,1600 +3,2026-04-02 10:05:00,900 +4,2026-04-02 18:22:00,2100 +5,2026-04-03 11:47:00,1450 diff --git a/website/examples/hero-shop/seeds/payments.yml b/website/examples/hero-shop/seeds/payments.yml new file mode 100644 index 0000000000..c5a0957ec0 --- /dev/null +++ b/website/examples/hero-shop/seeds/payments.yml @@ -0,0 +1,10 @@ +seeds: + - name: payments + description: Successful payments. + columns: + - name: payment_id + type: INTEGER + - name: paid_at + type: TIMESTAMP + - name: amount_cents + type: INTEGER diff --git a/website/public/assets/og-sqlbuild.png b/website/public/assets/og-sqlbuild.png index 503d54971b..36b0b101e9 100644 Binary files a/website/public/assets/og-sqlbuild.png and b/website/public/assets/og-sqlbuild.png differ diff --git a/website/public/index.html b/website/public/index.html index 350997eb6c..e6dc8818b3 100644 --- a/website/public/index.html +++ b/website/public/index.html @@ -18,7 +18,7 @@ - + @@ -76,13 +76,13 @@

The refactorable warehouse

+sqb planMigrations (1)└── daily_revenue migrate prod.revenue -> prod.daily_revenue ├── compatibility compatible ├── transfer physical copy, promote by staged rename └── old name prod.revenue ├── view until 2026-11-05 └── old table archived
@@ -190,7 +190,7 @@

Turn review comments into compile errors

Reorganise without breaking anything

- Declarations live next to the models that use them, and the compiler enforces who can see them. sqb scope shows what a move would break. + Declarations live next to the models that use them, and the compiler enforces who can see them. sqb scope shows what a move would break, and sqb mv moves the model's private macros, enums and constants with it.

models/marts/
@@ -322,15 +322,24 @@ 

See what the tests missed

Rename models without rebuilding history

- A renamed incremental model keeps its table instead of starting again. If the logic changed too, declare migrate_from. + sqb rename and sqb mv update every reference and keep the existing table, so an incremental model doesn't start again. The old name keeps working as a view. Renamed columns keep their history too.

-
sqb plan
+
sqb rename model:daily_activity_rollup daily_order_rollup
+Rename model  daily_activity_rollup -> daily_order_rollup
+├── models/marts/daily_order_rollup.sql  (moved from models/marts/daily_activity_rollup.sql)
+└── models/marts/hourly_activity_with_daily_context.sql
+    └── 36:19   reference __ref("daily_activity_rollup")  ->  __ref("daily_order_rollup")
+Compiled: ok, 2 files changed
+
+sqb plan
 Migrations (1)
 └── daily_order_rollup  migrate  dev.daily_activity_rollup -> dev.daily_order_rollup
     ├── compatibility  compatible
     ├── transfer  physical copy, promote by staged rename
-    └── discovery  automatic
+ └── old name dev.daily_activity_rollup + ├── view until 2026-11-05 + └── old table archived
diff --git a/website/public/styles.css b/website/public/styles.css index 179e19974f..64813f0145 100644 --- a/website/public/styles.css +++ b/website/public/styles.css @@ -742,7 +742,7 @@ footer a { margin-top: 0; } -/* Hero visual: a model is moved and renamed, then sqb plan shows the migration. */ +/* Hero visual: sqb mv moves and renames a model, then sqb plan shows the migration. */ .hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; @@ -754,7 +754,7 @@ footer a { border: 1px solid var(--panel-border); border-left: 1px solid var(--inner-line); border-radius: var(--radius); - min-height: 236px; + min-height: 300px; } .hero-visual { @@ -823,6 +823,27 @@ footer a { } } +@keyframes hv-l0 { + 0%, + 6% { + opacity: 0; + transform: translateY(3px); + } + 9%, + 92% { + opacity: 1; + transform: none; + } + 96%, + 100% { + opacity: 0; + } +} + +.hv-l0 { + animation-name: hv-l0; +} + @keyframes hv-l1 { 0%, 24% { @@ -949,6 +970,48 @@ footer a { animation-name: hv-l6; } +@keyframes hv-l7 { + 0%, + 45% { + opacity: 0; + transform: translateY(3px); + } + 48%, + 92% { + opacity: 1; + transform: none; + } + 96%, + 100% { + opacity: 0; + } +} + +.hv-l7 { + animation-name: hv-l7; +} + +@keyframes hv-l8 { + 0%, + 48% { + opacity: 0; + transform: translateY(3px); + } + 51%, + 92% { + opacity: 1; + transform: none; + } + 96%, + 100% { + opacity: 0; + } +} + +.hv-l8 { + animation-name: hv-l8; +} + @media (prefers-reduced-motion: reduce) { .hero-visual .hv-a, .hero-visual .hv-b, @@ -979,4 +1042,8 @@ footer a { .hero-visual .hv-wide { display: none; } + + .hero-visual .hv-break::after { + content: '\\\A '; + } } diff --git a/website/recordings/README.md b/website/recordings/README.md index e1c0a64dca..0b3b5a8254 100644 --- a/website/recordings/README.md +++ b/website/recordings/README.md @@ -6,7 +6,7 @@ scripts are the source; the rendered GIF, MP4 and PNG files go to `out/`, which | Demo | Shows | |------|-------| -| `rename` | A model is moved and renamed, and `sqb plan` migrates the existing table. | +| `rename` | `sqb mv` moves and renames a model, and `sqb plan` migrates the existing table. | | `contract` | A renamed column fails `sqb compile` against an enforced contract. | | `rules` | A custom rule and a missing test mock fail `sqb compile`. | | `scope` | `sqb scope --as-path` previews what a move would lose. | diff --git a/website/recordings/prepare.sh b/website/recordings/prepare.sh index f5a9ebdcdd..b3febac79a 100755 --- a/website/recordings/prepare.sh +++ b/website/recordings/prepare.sh @@ -9,11 +9,12 @@ rm -rf "$dir" mkdir -p "$here/scratch" case "$demo" in rename) - # Build the model under its old name, so the tape can move it back to models/finance. + # Build the model under its old name, so the tape can move it back to models/finance with sqb mv. cp -r "$examples/hero-shop" "$dir" cd "$dir" mkdir -p models/marts mv models/finance/daily_revenue.sql models/marts/revenue.sql + rmdir models/finance sqb build >/dev/null ;; contract) # Give daily_revenue an enforced contract, then rename one of its columns in the SELECT. diff --git a/website/recordings/tapes/rename.tape b/website/recordings/tapes/rename.tape index e93740995c..f0a3cc0cc0 100644 --- a/website/recordings/tapes/rename.tape +++ b/website/recordings/tapes/rename.tape @@ -1,5 +1,5 @@ Source tapes/common.tape -Set Height 800 +Set Height 1080 Output out/rename.gif Output out/rename.mp4 Hide @@ -10,10 +10,11 @@ Show Type "tree --noreport models" Enter Sleep 1500ms -Type "mv models/marts/revenue.sql models/finance/daily_revenue.sql" +Type "sqb mv model:revenue models/finance/daily_revenue.sql" Sleep 300ms Enter -Sleep 800ms +Wait@60s /\$\s*$/ +Sleep 1500ms Type "sqb plan" Sleep 300ms Enter