mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 00:03:00 +00:00
* feat: capture managed-materialize output schema as asset metadata (#2a) After a managed `// materialize` run, capture the producer's output schema via a DESCRIBE folded into the existing one-row summary read (no extra round-trip) and persist it in a new versioned `materialized_asset_schema` sidecar table. This is the producer-side capture that pipeline parity gap #2b (save-time consumer-ref contract enforcement) will read back. - materialized_asset_schema sidecar (asset-level grain), versioned: a new version row is inserted only when the captured column set changes. - output_schema column added to the materialize summary codegen. - worker extracts + records the schema on a successful materialize. - /assets/asset_schemas read endpoint exposing the evolution history. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: address CI review on schema capture (partition col, order, status gate) - exclude the synthetic `_wm_partition` column from the captured schema for partitioned assets, so the recorded contract is the producer's logical output, not Windmill's storage detail (claude/cubic P1). - make the captured column list explicitly ordered (`row_number()` over the DESCRIBE + `list(... ORDER BY)`), so the `list()` aggregate can't reorder columns and spuriously bump the schema version (cubic P2). - gate the API `record_materialization` schema upsert on a `Materialized` status, so a failed/running write (or a client attaching a schema to one) can't advance the schema history (cubic P2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: address Codex review (manual-mode schema gate + auth contract docs) - gate output_schema extraction on the managed (`Some((Some(_), _))`) path so a `// materialize manual` run — whose result is the user's own query output — can't persist a caller-shaped `output_schema` into materialized_asset_schema (Codex P2). Verified e2e: a manual run returning a fabricated `output_schema:[{injected,EVIL}]` records the partition but writes no schema version, while the managed path still captures normally. - document the authorization contract on the new public `record_asset_schema` and `list_asset_schemas` helpers: they perform no access control (mirroring the materialized_partition siblings) and require callers to pass a workspace-authorized executor (Codex P1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(frontend): schema-history tab on the ducklake asset node (#2a) Adds a "Schema" tab to DucklakeAssetPanel surfacing the captured output-schema versions persisted by the materialize run. Master-detail (mirrors the History tab): the version list (newest first, newest auto-selected) shows column count + snapshot + capture time; selecting a version renders its column/type table. Reads the GET /assets/asset_schemas endpoint via raw fetch, matching the sibling PartitionStatusGrid convention (these materialization endpoints are not in the generated client). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: schema tab is strategy-aware (history vs fixed schema) Only a whole-table `replace` producer (CREATE OR REPLACE) can change columns run-to-run; `append`/`merge`/partitioned writes INSERT into a fixed-schema table, so their schema is pinned at first materialize and the "history" framing is degenerate (always one version). - backend: surface the managed `materialize_strategy` (`replace`/`append`/ `merge`) on the asset-graph runnable node, alongside the existing `partition_kind` (same parse-from-annotation path). - frontend: the pipeline page derives `schemaCanEvolve` for the selected asset from its write-producer (`replace` && not partitioned) and threads it to the Schema tab. Evolvable → master-detail version history; fixed → a single current-schema table with a short "schema is fixed" note. Unknown defaults to evolvable so real history is never hidden. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: schemaCanEvolve fails open on unknown producer strategy Previously a producer present but missing `materialize_strategy` (e.g. a draft-overlay runnable, synthesized without the field) fell through to canEvolve=false, hiding captured history behind the fixed-schema view — contradicting the "unknown defaults to evolvable" intent. Now the fixed view shows only when *every* producer is a known insert-style write (append/merge, or partitioned replace); any producer with unknown (missing) strategy is treated as evolvable, so real history is never hidden. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
38 lines
2.0 KiB
SQL
38 lines
2.0 KiB
SQL
-- Captured output schema of a managed `// materialize` asset (gap #2a).
|
|
-- After a managed materialize commits, the worker DESCRIBEs the written table
|
|
-- and records its column list here as asset-level metadata. Schema is a
|
|
-- property of the asset/table, not of a partition slice, so it lives in its own
|
|
-- table keyed by (workspace, asset_kind, asset_path) rather than as a column on
|
|
-- materialized_partition (which would duplicate the identical schema across
|
|
-- every partition row). This is the producer-side capture that #2b (save-time
|
|
-- consumer-ref contract enforcement) reads back.
|
|
--
|
|
-- Versioning across re-materializations: a new `version` row is inserted only
|
|
-- when the captured column set differs from the latest stored version for the
|
|
-- asset; an unchanged re-materialize re-affirms the latest row in place. So the
|
|
-- table is a compact schema-evolution history and MAX(version) is the current
|
|
-- contract.
|
|
CREATE TABLE IF NOT EXISTS materialized_asset_schema (
|
|
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
asset_kind ASSET_KIND NOT NULL,
|
|
asset_path VARCHAR(255) NOT NULL,
|
|
-- Monotonic per (workspace, asset_kind, asset_path), starting at 1; only
|
|
-- bumped when the schema actually changes.
|
|
version BIGINT NOT NULL,
|
|
-- The captured columns, ordered as the table presents them:
|
|
-- [{"name": "...", "type": "..."}, ...].
|
|
columns JSONB NOT NULL,
|
|
-- DuckLake snapshot the schema was captured from (NULL for non-ducklake /
|
|
-- substrates without snapshots).
|
|
snapshot_id BIGINT,
|
|
job_id UUID,
|
|
captured_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (workspace_id, asset_kind, asset_path, version)
|
|
);
|
|
|
|
-- Default privileges (migration 20250205131523) only apply to objects created
|
|
-- by the role that set them, so grant explicitly — the API reads/writes this
|
|
-- table as the invoking role (same fix as script_trigger in 20260619112847).
|
|
GRANT ALL ON materialized_asset_schema TO windmill_user;
|
|
GRANT ALL ON materialized_asset_schema TO windmill_admin;
|