Follow-up to #239 (fixed in 0.20.4). The 0.20.4 fix — routing the adopt-view branch through pushViewUpdate → viewReplaceIsLegal — resolves the case where both column sets are known. But the OFFLINE (snapshot-based) meta migrate --allow adopt-view path still emits an illegal CREATE OR REPLACE VIEW for a projection view whose columns change structurally (mid-list insert or rename), because in the offline plan the expected projection view's .columns is not populated, so pushViewUpdate falls through to the : unmanaged fallback (= a non-destructive replace) instead of the viewReplaceIsLegal → drop+create decision.
Reproduces on 0.20.4.
Minimal repro (synthetic, Postgres, offline)
- A projection view
v_foo over base table t. The committed snapshot holds v_foo unfingerprinted (created before view fingerprinting) — it records the view's columns (e.g. [a, b, c]) and sql.
- Change the projection so the output columns change shape, e.g. insert a column mid-list →
[a, x, b, c] (or rename c → d).
- Run offline, no DB:
meta migrate --slug adopt --dialect postgres --allow adopt-view
- Emitted
up.sql:
CREATE OR REPLACE VIEW "v_foo" AS SELECT t.a AS a, t.x AS x, t.b AS b, t.c AS c FROM t;
COMMENT ON VIEW "v_foo" IS 'metaobjects:v1:sha256:...';
- Apply against a DB that already holds the prior
v_foo:
ERROR: 42P16: cannot change name of view column "b" to "x"
(A pure end-append change succeeds — the failure is specific to a structural, non-append column change.)
Root cause (localized)
runOfflineGenerate (cli migrate.js) builds expected views with buildProjectionViews(metadata, …) and passes them to planOffline({ …, views }).
In pushViewUpdate (migrate-ts/src/diff/index.ts):
const legal =
expected.columns !== undefined && actual.columns !== undefined
? viewReplaceIsLegal(expected.columns, actual.columns)
: unmanaged; // adoption ⇒ true ⇒ replace-view ⇒ CREATE OR REPLACE
The snapshot supplies actual.columns (it's recorded), and the change IS a projection (so the columns are knowable), yet the emitted DDL is CREATE OR REPLACE — which only happens if the ternary took the : unmanaged branch. That points to expected.columns being undefined for the offline projection view (i.e. buildProjectionViews' offline output, or planOffline's handling of it, doesn't carry the structured column list into the diff). Because expected.columns is unresolved, the #239 legality check can't run, and the adopt path defaults back to the illegal non-destructive replace.
The online/--from-db path presumably resolves the expected columns (it introspects), which is why the #239 fix is observable there but not offline.
Expected
Offline meta migrate (the primary authoring path) should make the same legal/illegal decision the online path makes: for a structural projection-view change it should emit DROP VIEW "v_foo"; CREATE VIEW "v_foo" AS … (as the down-migration and pre-fingerprinting forward migrations already do), not CREATE OR REPLACE.
Suggested fix direction
Ensure the expected projection view passed to planOffline carries its resolved .columns (populate it in buildProjectionViews, or have planOffline/pushViewUpdate resolve projection columns from metadata) so viewReplaceIsLegal(expected.columns, actual.columns) can run in the offline path. Then the #239 fix fires for offline adopt-view + structural change too.
Follow-up to #239 (fixed in 0.20.4). The 0.20.4 fix — routing the adopt-view branch through
pushViewUpdate→viewReplaceIsLegal— resolves the case where both column sets are known. But the OFFLINE (snapshot-based)meta migrate --allow adopt-viewpath still emits an illegalCREATE OR REPLACE VIEWfor a projection view whose columns change structurally (mid-list insert or rename), because in the offline plan the expected projection view's.columnsis not populated, sopushViewUpdatefalls through to the: unmanagedfallback (= a non-destructive replace) instead of theviewReplaceIsLegal→ drop+create decision.Reproduces on 0.20.4.
Minimal repro (synthetic, Postgres, offline)
v_fooover base tablet. The committed snapshot holdsv_foounfingerprinted (created before view fingerprinting) — it records the view'scolumns(e.g.[a, b, c]) andsql.[a, x, b, c](or renamec→d).up.sql:v_foo:Root cause (localized)
runOfflineGenerate(climigrate.js) builds expected views withbuildProjectionViews(metadata, …)and passes them toplanOffline({ …, views }).In
pushViewUpdate(migrate-ts/src/diff/index.ts):The snapshot supplies
actual.columns(it's recorded), and the change IS a projection (so the columns are knowable), yet the emitted DDL isCREATE OR REPLACE— which only happens if the ternary took the: unmanagedbranch. That points toexpected.columnsbeingundefinedfor the offline projection view (i.e.buildProjectionViews' offline output, orplanOffline's handling of it, doesn't carry the structured column list into the diff). Becauseexpected.columnsis unresolved, the #239 legality check can't run, and the adopt path defaults back to the illegal non-destructive replace.The online/
--from-dbpath presumably resolves the expected columns (it introspects), which is why the #239 fix is observable there but not offline.Expected
Offline
meta migrate(the primary authoring path) should make the same legal/illegal decision the online path makes: for a structural projection-view change it should emitDROP VIEW "v_foo"; CREATE VIEW "v_foo" AS …(as the down-migration and pre-fingerprinting forward migrations already do), notCREATE OR REPLACE.Suggested fix direction
Ensure the expected projection view passed to
planOfflinecarries its resolved.columns(populate it inbuildProjectionViews, or haveplanOffline/pushViewUpdateresolve projection columns from metadata) soviewReplaceIsLegal(expected.columns, actual.columns)can run in the offline path. Then the #239 fix fires for offline adopt-view + structural change too.