Derivazione a livello di colonna¶
Provisa traccia la derivazione dei dati a livello di colonna in modo statico — calcolata dalle definizioni SQL e dai contratti dei comandi, senza bisogno di alcuna esecuzione. Sono disponibili due viste: un DAG per singola istruzione e un grafo di provenienza esteso a tutta la federazione, che copre tutte le viste e le viste materializzate (MV) registrate.
L'esploratore della derivazione¶
Aprire Lineage nella UI (/lineage). Incollare un'istruzione SQL e fare clic su Build
statement graph per vederne il DAG a livello di colonna. Fare clic su Federation graph per
caricare il grafo di provenienza su ogni MV del registro. [tool-verified: LineagePage.tsx:28-119]
DAG a livello di istruzione (REQ-1160)¶
Ogni colonna di output nominata nel SQL diventa un nodo. Il costruttore la ripercorre a ritroso attraverso ogni CTE, sotto-query, join e chiamata inline a comando fino alle sue colonne di origine, costruendo un grafo orientato dagli input di origine agli output finali.
Esempio svolto¶
SELECT o.id, e.embedding, upper(e.geo) AS geo_u
FROM orders o
JOIN enrich_grpc_set('main.public.orders') e ON o.id = e.id
Questa istruzione produce tre colonne di output. Il grafo di geo_u si presenta così:
orders.geo ──[enrich_grpc_set(...)]──► e.geo ──[UPPER]──► geo_u
orders.id ─╮ (taint closure)
orders.region ─╯
orders.id,orders.regioneorders.geosono nodi source (il contratto di input ristretto dienrich_grpc_setdichiaraideregion; la chiusura per contaminazione completa collega tutti gli input dichiarati a tutti gli output). [tool-verified:_splice_commandsin graph.py:223-242]e.embeddingede.geosono nodi command — il confine dienrich_grpc_set.geo_uè un nodo derived prodotto dalla funzione SQLUPPER.
Il confine del comando non è opaco. Poiché enrich_grpc_set dichiara le proprie colonne di
input (id, region) e di output (id, embedding, geo), il motore di derivazione salda la
chiusura per contaminazione senza interruzioni, dalle colonne dichiarate della relazione di origine
fino a ciascun output. [tool-verified: _splice_commands and _input_relation in graph.py:245-271]
Tipi di nodo e segnali visivi¶
[tool-verified: LineageDag.tsx:25-29, KIND_COLOR constants; LineagePage.tsx:21-26 LEGEND]
| Tipo di nodo | Colore | Significato |
|---|---|---|
source |
Verde | Una colonna di tabella di base |
derived |
Blu | Prodotta da un'espressione SQL (funzione, operatore, CTE) |
command |
Viola | Una colonna di output di un comando registrato |
Anelli aggiuntivi su un nodo:
- Anello arancione — una colonna di output finale dell'istruzione.
- Bordo doppio — la relazione della colonna è una vista materializzata (snapshot MV/CTAS).
- Anello rosso — membro di un ciclo classificato come errore.
- Anello giallo — membro di un ciclo classificato come anello di retroazione.
[tool-verified: LineageDag.tsx:88-103 Cytoscape style selectors]
Trasformazioni nominate sugli archi¶
Ogni arco porta con sé l'espressione SQL grezza che produce la colonna di destinazione, più un
elenco di operazioni nominate: funzioni SQL (sql_function), operatori aritmetici e logici
(operator), comandi registrati (command), riferimenti diretti a colonna (identity) e letterali
(constant). [tool-verified: TransformOp and name_transform in graph.py:36-145]
Un arco che nasce da una chiamata a comando viene disegnato nella UI come una linea viola tratteggiata. [tool-verified: LineageDag.tsx:122-124]
Grafo esteso alla federazione (REQ-1161)¶
Il grafo di federazione unisce la derivazione per istruzione di ogni MV registrata in un unico grafo
di provenienza. L'identità di un nodo è relation.column — la colonna di output di una vista e il
riferimento in input di un'altra vista alla stessa colonna collassano in un unico nodo. Il risultato
è un solo DAG che va dalle colonne di origine di base a ogni dataset derivato della piattaforma.
[tool-verified: build_federation_graph in merge.py:205-229
and qualify_outputs in graph.py:275-299]
Usare focus, direction e depth per delimitare la vista su scala di federazione senza
ricalcolare il grafo. [tool-verified: slice_graph in merge.py:160-189]
Cicli (REQ-1161)¶
I cicli vengono descritti, non rifiutati. Il motore di derivazione rileva ogni ciclo orientato e lo
classifica. [tool-verified: Cycle.classification property in merge.py:43-46]
| Classificazione | Colore del bordo | Significato |
|---|---|---|
feedback |
Giallo | Il ciclo attraversa un nodo materializzato — un anello di retroazione legittimo e sfasato nel tempo. Lo snapshot della MV è il confine di versione che lo rende ben definito. |
error |
Rosso | Nessun confine di materializzazione sull'anello — una definizione circolare senza un ordine di valutazione stabile. Probabilmente un errore di progettazione. |
[tool-verified: LineagePage.tsx:83-98 cycle alert rendering; merge.py:38-48]
Un ciclo feedback non è un guasto. Una MV di arricchimento che riporta una colonna derivata nella
propria relazione di origine è uno schema valido finché almeno un nodo dell'anello è materializzato
— lo snapshot isola temporalmente le due metà. Un ciclo error richiede il giudizio di chi opera:
di solito significa che due viste si riferiscono l'una all'altra senza alcuno snapshot in mezzo.
API¶
Entrambi gli endpoint sono statici — leggono definizioni e contratti, non dati.
POST /admin/lineage/graph¶
Restituisce il DAG a livello di colonna di una singola istruzione SQL.
POST /admin/lineage/graph
Content-Type: application/json
{
"sql": "SELECT o.id, e.embedding FROM orders o JOIN enrich_grpc_set('main.public.orders') e ON o.id = e.id",
"dialect": "postgres"
}
[tool-verified: lineage_graph endpoint at lineage_router.py:45-54, LineageGraphRequest model at
lineage_router.py:29-31]
Forma della risposta [tool-verified: LineageGraph.to_dict in graph.py:82-105]:
{
"nodes": [
{"id": "orders.id", "column": "id", "relation": "orders", "kind": "source", "materialized": false}
],
"edges": [
{
"source": "orders.id",
"target": "e.id",
"transform": "enrich_grpc_set(...)",
"ops": [{"name": "enrich_grpc_set", "kind": "command"}]
}
],
"outputs": ["id", "embedding"]
}
Restituisce HTTP 422 quando il SQL non può essere analizzato. [tool-verified: lineage_router.py:51-54]
GET /admin/lineage/federation¶
Restituisce il grafo di provenienza unito su tutte le MV del registro.
GET /admin/lineage/federation
GET /admin/lineage/federation?focus=orders.id&direction=downstream&depth=3
[tool-verified: federation_graph endpoint at lineage_router.py:73-98]
Parametri di query [tool-verified: function signature at lineage_router.py:73-76]:
| Parametro | Valori | Default | Effetto |
|---|---|---|---|
focus |
Un id di nodo | — | Delimita la risposta al sotto-grafo attorno a questo nodo |
direction |
upstream | downstream | both |
both |
In quale direzione percorrere il grafo a partire da focus |
depth |
intero | illimitato | Distanza massima in salti da focus |
La risposta ha la stessa forma del grafo di istruzione, con l'aggiunta di un campo cycles
[tool-verified: MergedGraph.to_dict in merge.py:60-64]:
{
"nodes": [...],
"edges": [...],
"outputs": [...],
"cycles": [
{
"nodes": ["orders.region", "enriched_orders.region"],
"has_materialization_boundary": true,
"classification": "feedback"
}
]
}
Che cosa romperebbe la ridenominazione o la rimozione di una colonna (REQ-1484)¶
Una colonna porta due nomi, e ciascuno è memorizzato da un insieme diverso di artefatti.
Il nome esposto è quello che mostrano le superfici SQL e GraphQL: table_columns.alias, con
ripiego sul valore predefinito in snake_case quando non è impostato alcun alias [tool-verified: computed_sql_alias at
schema_helpers.py:317]. Viste, viste materializzate, espressioni di metrica, predicati RLS,
contratti DQ, granularità delle viste di metrica e chiavi di riga delle MV sono tutti scritti su
quel nome, quindi rinominare un alias li rompe con la stessa certezza con cui li romperebbe
eliminare la colonna.
Il nome fisico è table_columns.column_name, l'identità che sopravvive alla sostituzione in
blocco delle colonne durante l'upsert della tabella. Relazioni, collegamenti del
glossario, assegnazioni di tag, la colonna di watermark e i preset di colonna
memorizzano questo, quindi si rompono solo quando la colonna viene rimossa.
columnDependents riporta entrambi. Le viste e le MV a valle provengono dal ritaglio del grafo di
federazione sul nome esposto della colonna; gli artefatti che quel grafo non copre provengono da una
scansione diretta del registro [tool-verified: graph_dependents in provisa/lineage/dependents.py, registry scans in
provisa/api/admin/column_dependents.py].
query {
columnDependents(tableId: "42", renamed: ["order_total"], removed: ["legacy_code"]) {
columnName
dependents { kind name detail breaksOn }
}
}
breaksOn vale rename per un riferimento al nome esposto e remove per uno al nome fisico, così
chi chiama può capire a quale metà della modifica sta reagendo ciascun artefatto.
Questa domanda va posta prima del salvataggio. Una colonna rinominata viene individuata tramite il nome esposto che porta ancora nel registro; una volta che l'alias è stato applicato, il vecchio nome non c'è più e la query non trova nulla.
La pagina Tabelle esegue la query automaticamente quando una modifica in sospeso cambia un alias o
riduce l'insieme delle colonne, ed elenca ciò che trova [tool-verified: diffEditedColumns in
provisa-ui/src/pages/tables/columnDiff.ts, dialog in TablesPage.tsx]. L'avviso è consultivo:
nomina gli artefatti interessati e la decisione spetta all'amministratore. Non blocca il
salvataggio, perché non è possibile raggiungere tutti i consumatori del patrimonio informativo — una
dashboard esterna o un'applicazione client che interroga la colonna per nome è fuori dalla
conoscenza del registro. Per lo stesso motivo, le scansioni sul testo SQL libero riconoscono la
colonna come token identificatore anziché risolverne l'ambito, e questo può nominare un artefatto
che poi non usa affatto la colonna. Per un avviso, l'eccesso di segnalazioni è la direzione sicura.
Usare la derivazione per governare i contratti dei comandi¶
Poiché la chiusura per contaminazione collega ogni colonna di input dichiarata a ogni colonna di output dichiarata, l'ampiezza di quella chiusura dipende interamente da ciò che si dichiara.
Si consideri un comando che prende un'intera tabella orders (id, region, amount,
customer_id, discount, notes, ...) e restituisce un embedding. Se il contratto di input
elenca tutte quelle colonne, ogni colonna a valle che usa l'embedding mostrerà una derivazione da
tutte quante. È accurato ma non utile — diventa difficile capire che cosa abbia contato davvero.
Dichiarando solo id e text (le colonne che il modello di embedding legge realmente), il cono di
derivazione si restringe a quelle due colonne di origine. La derivazione risulta insieme corretta e
precisa.
Vedere Comandi per la meccanica della dichiarazione di un contratto di input ristretto.