Lineage em Nível de Coluna¶
O Provisa rastreia lineage de dados em nível de coluna de forma estática — computado a partir de definições SQL e contratos de command, sem execução necessária. Duas exibições estão disponíveis: um DAG por declaração e um grafo de proveniência abrangendo toda a federação, cobrindo todas as exibições e exibições materializadas (MVs) registradas.
O explorador de lineage¶
Navegue até Lineage na UI (/lineage). Cole uma declaração SQL e clique em Build statement
graph para ver seu DAG em nível de coluna. Clique em Federation graph para carregar o grafo de
proveniência sobre todas as MVs no registro. [tool-verified: LineagePage.tsx:28-119]
DAG em nível de declaração (REQ-1160)¶
Cada coluna de saída nomeada no seu SQL se torna um nó. O builder a rastreia de volta através de todo CTE, subconsulta, join e chamada de command inline até suas colunas de fonte, construindo um grafo direcionado das entradas de fonte até as saídas finais.
Exemplo trabalhado¶
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
Esta declaração produz três colunas de saída. O grafo para geo_u se parece com:
orders.geo ──[enrich_grpc_set(...)]──► e.geo ──[UPPER]──► geo_u
orders.id ─╮ (taint closure)
orders.region ─╯
orders.id,orders.regioneorders.geosão nós de fonte (o contrato de entrada estreito deenrich_grpc_setdeclaraideregion; o fechamento de contaminação completo conecta todas as entradas declaradas a todas as saídas). [tool-verified:_splice_commandsin graph.py:223-242]e.embeddingee.geosão nós de command — a fronteiraenrich_grpc_set.geo_ué um nó derivado produzido pela função SQLUPPER.
A fronteira do command não é opaca. Como enrich_grpc_set declara suas colunas de entrada
(id, region) e colunas de saída (id, embedding, geo), o motor de lineage emenda o
fechamento de contaminação continuamente das colunas declaradas da relação de fonte até cada saída.
[tool-verified: _splice_commands and _input_relation in graph.py:245-271]
Tipos de nó e pistas visuais¶
[tool-verified: LineageDag.tsx:25-29, KIND_COLOR constants; LineagePage.tsx:21-26 LEGEND]
| Tipo de nó | Cor | Significado |
|---|---|---|
source |
Verde | Uma coluna de tabela base |
derived |
Azul | Produzida por uma expressão SQL (função, operador, CTE) |
command |
Roxo | Uma coluna de saída de um command registrado |
Anéis adicionais em um nó:
- Anel laranja — uma coluna de saída final da declaração.
- Borda dupla — a relação da coluna é uma exibição materializada (snapshot MV/CTAS).
- Anel vermelho — membro de um ciclo classificado como erro.
- Anel amarelo — membro de um ciclo classificado como laço de realimentação.
[tool-verified: LineageDag.tsx:88-103 Cytoscape style selectors]
Transformações nomeadas nas arestas¶
Toda aresta carrega a expressão SQL bruta que produz a coluna de destino, mais uma lista de
operações nomeadas: funções SQL (sql_function), operadores aritméticos/lógicos (operator),
commands registrados (command), referências simples de coluna (identity) e literais (constant).
[tool-verified: TransformOp and name_transform in graph.py:36-145]
Uma aresta vinda de uma chamada de command é renderizada como uma linha roxa tracejada na UI. [tool-verified: LineageDag.tsx:122-124]
Grafo abrangendo toda a federação (REQ-1161)¶
O grafo de federação funde o lineage por declaração de cada MV registrada em um único grafo de proveniência.
A identidade do nó é relation.column — a coluna de saída de uma exibição e a referência de entrada de outra
exibição à mesma coluna colapsam em um só nó. O resultado é um único DAG das colunas de fonte base até
cada conjunto de dados derivado na plataforma. [tool-verified: build_federation_graph in merge.py:205-229
and qualify_outputs in graph.py:275-299]
Use focus, direction e depth para delimitar a exibição em escala de federação sem recomputar
o grafo. [tool-verified: slice_graph in merge.py:160-189]
Ciclos (REQ-1161)¶
Ciclos são descritos, não rejeitados. O motor de lineage detecta todo ciclo direcionado e o
classifica. [tool-verified: Cycle.classification property in merge.py:43-46]
| Classificação | Cor da borda | Significado |
|---|---|---|
feedback |
Amarelo | O ciclo cruza um nó materializado — um laço de realimentação legítimo e defasado no tempo. O snapshot da MV é a fronteira de versão que o torna bem definido. |
error |
Vermelho | Nenhuma fronteira de materialização no laço — uma definição circular sem ordem de avaliação estável. Provavelmente um erro de projeto. |
[tool-verified: LineagePage.tsx:83-98 cycle alert rendering; merge.py:38-48]
Um ciclo feedback não é uma falha. Uma MV de enriquecimento que realimenta uma coluna derivada em sua
própria relação de fonte é um padrão válido desde que um nó do laço seja materializado — o
snapshot isola as duas metades temporalmente. Um ciclo error exige julgamento do operador: normalmente
significa que duas exibições se referenciam mutuamente sem snapshot no meio.
API¶
Ambos os endpoints são estáticos — eles leem definições e contratos, não dados.
POST /admin/lineage/graph¶
Retorna o DAG em nível de coluna para uma única declaração 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]
Formato da resposta [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"]
}
Retorna HTTP 422 quando o SQL não pode ser analisado. [tool-verified: lineage_router.py:51-54]
GET /admin/lineage/federation¶
Retorna o grafo de proveniência fundido sobre todas as MVs no 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]
Parâmetros de consulta [tool-verified: function signature at lineage_router.py:73-76]:
| Parâmetro | Valores | Padrão | Efeito |
|---|---|---|---|
focus |
Um id de nó | — | Delimita a resposta ao subgrafo em torno deste nó |
direction |
upstream | downstream | both |
both |
Qual direção percorrer a partir de focus |
depth |
inteiro | sem limite | Distância máxima em saltos a partir de focus |
A resposta tem o mesmo formato do grafo de declaração, com um campo cycles adicionado
[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"
}
]
}
O que uma renomeação ou remoção de coluna quebraria (REQ-1484)¶
Uma coluna carrega dois nomes, e cada um é armazenado por um conjunto diferente de artefatos.
O nome exposto é o que as interfaces SQL e GraphQL mostram: table_columns.alias, recaindo
para o padrão snake_case quando nenhum alias está definido [tool-verified: computed_sql_alias at
schema_helpers.py:317]. Exibições, exibições materializadas, expressões de métrica, predicados de RLS,
contratos de DQ, grãos de exibição de métrica e chaves de linha de MV são todos escritos contra aquele nome, então
renomear um alias os quebra tão certamente quanto excluir a coluna.
O nome físico é table_columns.column_name, a identidade que sobrevive à substituição integral de
colunas feita pelo upsert da tabela. Relacionamentos, vínculos de glossário, atribuições de tag, a coluna
de watermark e presets de coluna guardam este, então eles só quebram quando a coluna é removida.
columnDependents reporta ambos. Exibições e MVs a jusante vêm de fatiar o grafo de federação no
nome exposto da coluna; os artefatos que o grafo não cobre vêm de uma varredura direta do
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 é rename para uma referência ao nome exposto e remove para uma ao nome físico, de modo que quem
chama consegue dizer a qual metade da edição cada artefato está reagindo.
Pergunte isto antes de salvar. Uma coluna renomeada é localizada pelo nome exposto que ela ainda carrega no registro; uma vez que o alias tenha sido aplicado, o nome antigo se foi e a consulta não encontra nada.
A página de Tabelas roda a consulta automaticamente quando uma edição pendente muda um alias ou encolhe o
conjunto de colunas, e lista o que encontra [tool-verified: diffEditedColumns in
provisa-ui/src/pages/tables/columnDiff.ts, dialog in TablesPage.tsx]. O aviso é consultivo:
ele nomeia os artefatos afetados e o administrador decide. Ele não bloqueia o salvamento, porque
os consumidores do patrimônio não podem ser todos alcançados — um painel externo ou uma aplicação cliente que
consulta a coluna pelo nome está além do conhecimento do registro. Pela mesma razão, varreduras sobre
texto SQL livre casam a coluna como um token identificador em vez de resolver escopo, o que pode nomear um
artefato que acaba não usando a coluna. Reportar demais é a direção segura para um aviso.
Usando lineage para governar contratos de command¶
Como o fechamento de contaminação conecta cada coluna de entrada declarada a cada coluna de saída declarada, a amplitude desse fechamento depende inteiramente do que você declara.
Considere um command que recebe uma tabela de orders completa (id, region, amount, customer_id,
discount, notes, ...) e retorna um embedding. Se o contrato de entrada listar todas essas
colunas, cada coluna a jusante que usar o embedding mostrará lineage a partir de todas elas.
Isso é preciso mas não é útil — fica difícil dizer o que realmente importou.
Declare somente id e text (as colunas que o modelo de embedding de fato lê), e o cone de
lineage se aperta para essas duas colunas de fonte. A derivação é ao mesmo tempo correta e precisa.
Veja Commands para a mecânica de declarar um contrato de entrada estreito.