Skip to content

migrate-ts 0.20.4: offline --allow adopt-view still emits illegal CREATE OR REPLACE for a projection view with a structural column change (#239 follow-up) #240

Description

@dmealing

Follow-up to #239 (fixed in 0.20.4). The 0.20.4 fix — routing the adopt-view branch through pushViewUpdateviewReplaceIsLegal — 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)

  1. 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.
  2. Change the projection so the output columns change shape, e.g. insert a column mid-list → [a, x, b, c] (or rename cd).
  3. Run offline, no DB:
    meta migrate --slug adopt --dialect postgres --allow adopt-view
    
  4. 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:...';
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions