Skip to content

feat(ddl): first-class CREATE VIEW / DROP VIEW / MATERIALIZED VIEW - #161

Merged
productdevbook merged 1 commit into
mainfrom
feat/create-view
May 19, 2026
Merged

feat(ddl): first-class CREATE VIEW / DROP VIEW / MATERIALIZED VIEW#161
productdevbook merged 1 commit into
mainfrom
feat/create-view

Conversation

@productdevbook

Copy link
Copy Markdown
Owner

Summary

  • Surface standard CREATE VIEW, DROP VIEW, PG-only MATERIALIZED VIEW + REFRESH MATERIALIZED VIEW via the schema-builder DSL — no more unsafeRawExpr for view DDL.
  • Embedded SELECT bodies route through the full compile() pipeline so plugins (multi-tenant, soft-delete, CASL) apply automatically; covered by audit23.
  • Dialect divergence is handled at print time: .orReplace() works on PG/MySQL, .orAlter() works on MSSQL (emits CREATE OR ALTER VIEW, 2016 SP1+), SQLite refuses both with a pointer at DROP VIEW IF EXISTS + CREATE VIEW.

New surface

  • db.schema.createView(name).asSelect(query) — all four dialects.
  • .orReplace() / .orAlter() — mutually exclusive; the two forms aren't interchangeable so we expose them as distinct methods rather than fold MSSQL into orReplace.
  • .materialized() + .withData(false) — PG only; emits CREATE MATERIALIZED VIEW … WITH NO DATA.
  • .temporary(), .ifNotExists(), .columns(...) — standard knobs.
  • db.schema.refreshMaterializedView(name).concurrently().withData(false) — PG only.
  • db.schema.dropView(name).materialized().ifExists() — PG DROP MATERIALIZED VIEW IF EXISTS.

Why two methods for OR REPLACE / OR ALTER?

A unified .orReplace() that silently rewrote to MSSQL's CREATE OR ALTER would obscure dialect-portability mistakes (the two forms behave subtly differently — OR ALTER requires the view's signature to be unchanged on MSSQL, OR REPLACE allows column-list changes within PG's restrictions). Keeping them as distinct builder methods makes the dialect requirement explicit at the call site.

Coordination with #160

Agent A's EXCLUDE constraints PR (#160) landed first while this work was in flight. Both touch src/ast/ddl-nodes.ts, src/dialect/features.ts, src/printer/ddl.ts, and docs/recipes.md — rebased onto main and the auto-merge resolved cleanly without manual intervention.

Test plan

  • pnpm fmt && pnpm lint && pnpm typecheck — all green.
  • pnpm vitest run — 2247 passed / 58 skipped (+18 new tests in test/ddl/views.test.ts).
  • Cross-dialect CREATE VIEW (all four), CREATE OR REPLACE (PG/MySQL), CREATE OR ALTER (MSSQL), SQLite refusal + helpful message.
  • CREATE MATERIALIZED VIEW + WITH NO DATA + REFRESH (CONCURRENTLY) + DROP MATERIALIZED VIEW — PG only, other dialects refuse.
  • PGlite roundtrip: create regular VIEW + select rows, create MATERIALIZED VIEW + insert + refresh + verify count change.
  • Param-bound view body: outer compiled query's params array carries the inner SELECT's bound values (DDL print pipeline regression guard).
  • Existing audit10 / audit23 / DDL schema-builder tests untouched and passing.

🤖 Generated with Claude Code

Surface the standard `CREATE VIEW`, `DROP VIEW`, and PG-only
`MATERIALIZED VIEW` + `REFRESH MATERIALIZED VIEW` forms via the
schema-builder DSL with full dialect-aware rewriting. The embedded
SELECT body routes through the same `compile()` pipeline as top-level
queries so registered plugins (multi-tenant, soft-delete, CASL) apply
automatically — see audit23 for the regression test.

Highlights:
  * `.createView(name).asSelect(query)` — all four dialects
  * `.orReplace()` — PG / MySQL emit `CREATE OR REPLACE VIEW`
  * `.orAlter()`   — MSSQL emits `CREATE OR ALTER VIEW` (2016 SP1+)
  * `.materialized()` + `.withData(false)` — PG only
  * `db.schema.refreshMaterializedView(name).concurrently()` — PG only
  * `.dropView(name).materialized().ifExists()` — PG DROP MV form

`orReplace` and `orAlter` are deliberately separate methods rather than
a single API that quietly swaps to MSSQL's spelling. The two forms
aren't interchangeable (MSSQL has no `OR REPLACE`; PG/MySQL have no
`OR ALTER`) and folding them would obscure dialect-portability
mistakes. SQLite has neither and the printer refuses with a pointer at
the DROP+CREATE workaround.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@productdevbook
productdevbook merged commit c8009ce into main May 19, 2026
1 check passed
@productdevbook
productdevbook deleted the feat/create-view branch May 19, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant