Propagation through a derived DAG
How does one change spread through derived tables, and how do you find out who breaks?
Single-hop compatibility (one producer, one consumer) is a solved problem. The unsolved case is one source change flowing into N derived models, each of whose schema is itself inferred from its query.
SELECT * turns a safe change into a schema change at every hop
SELECT * copies structure automatically. So an upstream additive change, which is the safest operation in the whole algebra, becomes a schema change at every downstream step, with no code change and no review.
Explicit column lists stop the propagation, but at the price of a manual edit per hop for every addition. There is no third option. The trade is automatic drift versus manual toil, and both fail at scale, in different directions.
-- Propagates every upstream change (automatic drift):
CREATE VIEW shipments_clean AS SELECT * FROM shipments;
-- Stops propagation (manual toil per added column):
CREATE VIEW shipments_clean AS
SELECT shipment_id, status, weight_kg, price_cents, occurred_at FROM shipments;Lineage and usage
Column-level lineage turns "what breaks if I drop this column?" from a meeting into a graph query. That is the real argument for lineage as infrastructure rather than documentation.
But the blast radius is the transitive closure from the changed column (everything reachable from it), intersected with actual usage. Without usage telemetry, the closure is a wild over-estimate: everything looks breaking, and you get change paralysis.
That is the less visible failure mode, and often the more expensive one. A table nobody dares to modify is not a stable interface. It is a frozen one.
SELECT * turns a safe upstream addition into a schema change at every hop. Column lineage plus usage telemetry is what makes "who breaks?" answerable.