Происхождение данных на уровне колонок¶
Provisa отслеживает происхождение данных на уровне колонок статически — оно вычисляется на основе SQL-определений и контрактов команд, без выполнения запроса. Доступны два представления: DAG для отдельного выражения и граф происхождения (provenance graph) на уровне всей федерации, охватывающий все зарегистрированные представления и материализованные представления (MV).
Обозреватель происхождения¶
Перейдите на страницу Lineage в UI (/lineage). Вставьте SQL-выражение и нажмите Build
statement graph, чтобы увидеть его DAG на уровне колонок. Нажмите Federation graph, чтобы
загрузить граф происхождения по всем MV в реестре. [tool-verified: LineagePage.tsx:28-119]
DAG уровня выражения (REQ-1160)¶
Каждая именованная выходная колонка в вашем SQL становится узлом. Построитель прослеживает её назад через все CTE, подзапросы, join и встроенные вызовы команд до исходных колонок, строя направленный граф от исходных входов к итоговым выходам.
Разобранный пример¶
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
Это выражение производит три выходные колонки. Граф для geo_u выглядит так:
orders.geo ──[enrich_grpc_set(...)]──► e.geo ──[UPPER]──► geo_u
orders.id ─╮ (taint closure)
orders.region ─╯
orders.id,orders.regionиorders.geo— узлы source (узкий входной контрактenrich_grpc_setобъявляетidиregion; полное замыкание taint-closure соединяет все объявленные входы со всеми выходами). [tool-verified:_splice_commandsin graph.py:223-242]e.embeddingиe.geo— узлы command — границаenrich_grpc_set.geo_u— узел derived, произведённый SQL-функциейUPPER.
Граница команды не непрозрачна. Поскольку enrich_grpc_set объявляет свои входные колонки
(id, region) и выходные колонки (id, embedding, geo), движок происхождения непрерывно
сращивает taint-замыкание от объявленных колонок исходного отношения к каждому выходу.
[tool-verified: _splice_commands and _input_relation in graph.py:245-271]
Виды узлов и визуальные подсказки¶
[tool-verified: LineageDag.tsx:25-29, KIND_COLOR constants; LineagePage.tsx:21-26 LEGEND]
| Вид узла | Цвет | Значение |
|---|---|---|
source |
Зелёный | Колонка базовой таблицы |
derived |
Синий | Произведена SQL-выражением (функция, оператор, CTE) |
command |
Фиолетовый | Выходная колонка зарегистрированной команды |
Дополнительные кольца на узле:
- Оранжевое кольцо — итоговая выходная колонка выражения.
- Двойная рамка — отношение колонки является материализованным представлением (снимок MV/CTAS).
- Красное кольцо — участник цикла, классифицированного как ошибка.
- Жёлтое кольцо — участник цикла, классифицированного как контур обратной связи.
[tool-verified: LineageDag.tsx:88-103 Cytoscape style selectors]
Именованные преобразования на рёбрах¶
Каждое ребро несёт исходное SQL-выражение, производящее целевую колонку, плюс список именованных
операций: SQL-функции (sql_function), арифметические и логические операторы (operator),
зарегистрированные команды (command), голые ссылки на колонки (identity) и литералы
(constant). [tool-verified: TransformOp and name_transform in graph.py:36-145]
Ребро от вызова команды отрисовывается в UI пунктирной фиолетовой линией. [tool-verified: LineageDag.tsx:122-124]
Граф на уровне всей федерации (REQ-1161)¶
Граф федерации сливает происхождение каждого зарегистрированного MV на уровне выражений в один граф
происхождения. Идентичностью узла служит relation.column — выходная колонка одного представления и
входная ссылка другого представления на ту же колонку схлопываются в один узел. Результат —
единый DAG от колонок базовых источников до каждого производного набора данных на платформе.
[tool-verified: build_federation_graph in merge.py:205-229 and qualify_outputs in
graph.py:275-299]
Используйте focus, direction и depth, чтобы сузить представление в масштабе федерации без
пересчёта графа. [tool-verified: slice_graph in merge.py:160-189]
Циклы (REQ-1161)¶
Циклы описываются, а не отвергаются. Движок происхождения обнаруживает каждый направленный цикл и
классифицирует его. [tool-verified: Cycle.classification property in merge.py:43-46]
| Классификация | Цвет рамки | Значение |
|---|---|---|
feedback |
Жёлтый | Цикл проходит через материализованный узел — легальный контур обратной связи с задержкой во времени. Снимок MV — это граница версий, которая делает его определённым. |
error |
Красный | На петле нет границы материализации — циклическое определение без устойчивого порядка вычисления. Вероятно, ошибка проектирования. |
[tool-verified: LineagePage.tsx:83-98 cycle alert rendering; merge.py:38-48]
Цикл feedback — не сбой. MV обогащения, возвращающий производную колонку обратно в собственное
исходное отношение, — допустимый шаблон, пока хотя бы один узел петли материализован: снимок
временно изолирует две половины. Цикл error требует суждения оператора: обычно он означает, что
два представления ссылаются друг на друга без снимка между ними.
API¶
Оба эндпоинта статические — они читают определения и контракты, а не данные.
POST /admin/lineage/graph¶
Возвращает DAG на уровне колонок для одного 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]
Форма ответа [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"]
}
Возвращает HTTP 422, когда SQL не удаётся разобрать. [tool-verified: lineage_router.py:51-54]
GET /admin/lineage/federation¶
Возвращает объединённый граф происхождения по всем MV в реестре.
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]
Параметры запроса [tool-verified: function signature at lineage_router.py:73-76]:
| Параметр | Значения | По умолчанию | Действие |
|---|---|---|---|
focus |
Идентификатор узла | — | Сузить ответ до подграфа вокруг этого узла |
direction |
upstream | downstream | both |
both |
В каком направлении обходить от focus |
depth |
целое число | без ограничения | Максимальное расстояние в переходах от focus |
Форма ответа та же, что и у графа выражения, с добавленным полем 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"
}
]
}
Что сломало бы переименование или удаление колонки (REQ-1484)¶
У колонки два имени, и каждое хранится своим набором артефактов.
Отображаемое имя — то, что показывают поверхности SQL и GraphQL: table_columns.alias, а при
отсутствии псевдонима — значение по умолчанию в snake_case [tool-verified: computed_sql_alias at
schema_helpers.py:317]. Представления, материализованные представления, выражения метрик,
предикаты RLS, контракты DQ, гранулярности представлений-метрик и ключи строк MV — всё это написано
против этого имени, поэтому переименование псевдонима ломает их так же надёжно, как и удаление
колонки.
Физическое имя — это table_columns.column_name, идентичность, переживающая сплошную замену
колонок при upsert таблицы. Связи, привязки глоссария, назначения тегов, колонка
водяного знака и пресеты колонок хранят именно его, поэтому они ломаются только тогда, когда колонка
удаляется.
columnDependents сообщает об обоих. Нижестоящие представления и MV берутся из среза графа
федерации по отображаемому имени колонки; артефакты, которые граф не покрывает, берутся из прямого
сканирования реестра [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 для ссылки на отображаемое имя и remove для ссылки на физическое, так
что вызывающая сторона может понять, на какую половину правки реагирует каждый артефакт.
Спрашивайте об этом до сохранения. Переименованная колонка находится по отображаемому имени, которое она ещё несёт в реестре; как только псевдоним записан, старое имя исчезло и запрос ничего не находит.
Страница Tables выполняет этот запрос автоматически, когда ожидающая правка меняет псевдоним или
сокращает набор колонок, и перечисляет найденное [tool-verified: diffEditedColumns in
provisa-ui/src/pages/tables/columnDiff.ts, dialog in TablesPage.tsx]. Предупреждение носит
рекомендательный характер: оно называет затронутые артефакты, а решает администратор. Оно не
блокирует сохранение, потому что до всех потребителей в хозяйстве не дотянуться — внешняя панель
мониторинга или клиентское приложение, запрашивающее колонку по имени, лежат за пределами знаний
реестра. По той же причине сканирование свободного SQL-текста сопоставляет колонку как токен
идентификатора, а не разрешает области видимости, из-за чего может назвать артефакт, который на
поверку колонку не использует. Для предупреждения перестраховка — безопасное направление.
Использование происхождения для управления контрактами команд¶
Поскольку taint-замыкание соединяет каждую объявленную входную колонку с каждой объявленной выходной, широта этого замыкания целиком зависит от того, что вы объявите.
Рассмотрим команду, которая принимает полную таблицу orders (id, region, amount,
customer_id, discount, notes, ...) и возвращает embedding. Если входной контракт
перечисляет все эти колонки, каждая нижестоящая колонка, использующая embedding, покажет
происхождение от них всех. Это точно, но бесполезно — трудно понять, что действительно имело
значение.
Объявите только id и text (колонки, которые модель embedding действительно читает), и конус
происхождения сузится до этих двух исходных колонок. Вывод при этом остаётся и корректным, и точным.
О механике объявления узкого входного контракта см. Команды.