Skip to content

feat(schema): drag a DB/table into the data pane for a lineage graph#41

Merged
BorisTyshkevich merged 4 commits into
mainfrom
feat/schema-graph
Jun 25, 2026
Merged

feat(schema): drag a DB/table into the data pane for a lineage graph#41
BorisTyshkevich merged 4 commits into
mainfrom
feat/schema-graph

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

What

Drag a database or table row from the schema sidebar onto the results pane to render a graph of ClickHouse object relationships — not generic foreign keys, but engine-specific lineage:

  • materialized viewsfeeds from sources, writes to the target
  • viewsreads their sources
  • dictionariesdict from a source table (or an external leaf for non-CH sources)
  • Distributed / Buffer / Merge — engine-arg references to backing tables

Drag a DB → whole-DB lineage (isolated tables with no relationships are pruned so the lineage is the focus); drag a table → its 1-hop neighbourhood. Click any node to re-centre on it; Expand for a fullscreen pan/zoom view (same controls as the pipeline graph). Nodes are kind-coloured with a legend; edges are coloured + labelled by relationship.

How discovery works (structured-first, parse-fallback)

The helpful system.tables columns are build-dependent, so the loader prefers structured columns (dependencies_table / loading_dependencies_* / system.dictionaries.source) when populated, and otherwise lets ClickHouse parse the SQL via EXPLAIN AST (query sources) plus light regex on create_table_query (TO target) and engine_full (Distributed/Buffer/Merge args). EXPLAIN AST's TableIdentifier lines are cross-checked against real objects, dropping CTE/alias false-positives.

This was validated empirically: on otel (Altinity-antalya CH 26.3.10) a real MV has no target_* column and empty dependencies_* — a pure-structured approach would render nothing there, so the EXPLAIN AST + create-query parsing path is required for the builds we deploy to.

Verified live

Deployed the branch build to otel and dragged bentoclick onto the results pane:

dashboards_raw —feeds→ dashboards_mv —writes→ dashboards_tok —reads→ dashboards (the writes target came from parsing TO, the feeds/reads from EXPLAIN AST — the parse-fallback path). Click-to-expand on dashboards_mv correctly re-focused to its 1-hop neighbourhood. Kind-colouring (purple MV, teal views, blue dictionaries) and the legend render correctly in both Chrome and Firefox (verified color-mix resolves, not the dark base).

Layers / tests

  • src/core/schema-graph.js (NEW, pure, 100%-covered) — all parsers + buildSchemaGraph
  • src/net/ch-client.jsloadSchemaLineage (scope query + per-view/MV EXPLAIN AST)
  • src/core/dot-layout.js — pass node kind + edge label through the dagre seam
  • src/ui/explain-graph.js — extracted renderGraphSvg; buildSchemaSvg; generalized fullscreen overlay; renderSchemaGraph + legend + click-to-expand
  • src/ui/results.js, schema.js, editor.js, app.js — render dispatch, second SCHEMA_GRAPH_MIME drag payload, results-region drop target + showSchemaGraph action
  • styles.css — kind-coloured nodes/edges + legend (node-kind rules scoped under .explain-graph so the kind fill beats the base — this was a real bug caught in live testing)
  • tests — schema-graph unit, e2e harness + spec, plus results/app/schema/ch-client/explain-graph specs. 852 tests pass; per-file coverage gate holds.

Notes for review

  • Isolated-node pruning on DB focus is a product decision: a DB with 22 tables but only 8 in lineage rendered a useless wide strip of singletons, so degree-0 nodes are dropped when there is lineage (a DB with no relationships still shows its tables). Easy to revert if you'd rather see everything.
  • Column cards inside nodes (à la play.clickhouse.com/schema), an insert heat-map, and refreshable-MV badges are intentionally deferred to fast-follow (see the plan).
  • e2e (tests/e2e/schema-graph.spec.js) runs in CI / locally — Playwright browsers can't launch in the dev sandbox (SIGTRAP), which also affects the pre-existing pipeline spec; the Firefox render path was instead verified against the live harness via the agent browser.

🤖 Generated with Claude Code

BorisTyshkevich and others added 4 commits June 25, 2026 20:26
Drag a database or table row from the schema sidebar onto the results pane
to render a graph of ClickHouse object relationships — not generic FKs, but
engine-specific lineage: materialized views (feeds from sources, writes to
target), views (reads sources), dictionaries (dict from source), and
Distributed/Buffer/Merge engine refs. Drag a DB → whole-DB lineage (isolated
tables pruned); drag a table → 1-hop neighbourhood; click a node to re-centre;
Expand for fullscreen pan/zoom. Nodes are kind-coloured with a legend; edges
coloured + labelled by relationship.

Discovery is structured-first, parse-fallback: prefer dependencies_table /
loading_dependencies_* / dictionaries.source when populated, else let
ClickHouse parse the SQL via EXPLAIN AST (query sources) + light regex on
create_table_query (TO target) and engine_full (Distributed/Buffer/Merge).
This keeps it working on older deployed builds (verified live on otel
CH 26.3.10, where target_* is absent and dependencies_* can be empty).

- src/core/schema-graph.js (NEW, pure, 100%): objectKind, parseAstTables,
  parseMvTarget, parseDictSource, parseEngineRef, buildSchemaGraph
- src/net/ch-client.js: loadSchemaLineage (scope query + EXPLAIN AST per view/MV)
- src/core/dot-layout.js: pass node kind + edge label through dagre layout
- src/ui/explain-graph.js: extract renderGraphSvg; buildSchemaSvg; generalize
  the fullscreen overlay; renderSchemaGraph + legend + click-to-expand
- src/ui/results.js: render r.schemaGraph + toolbar (Schema · <focus> + Expand)
- src/ui/schema.js + editor.js: second SCHEMA_GRAPH_MIME drag payload on rows
- src/ui/app.js: results-region drop target + showSchemaGraph action
- styles.css: kind-coloured nodes/edges + legend (scoped so kind fill wins)
- tests: schema-graph unit + e2e harness/spec; results/app/schema/ch-client/
  explain-graph specs; per-file coverage gate holds (852 tests)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBS74oUsXarGkCRQKEFLu
…regex

Two findings from the code-review pass on src/core/schema-graph.js:

- The addEdge dedup key joined fields with literal NUL bytes (`'\x00'`), which
  made git treat the whole file as binary (no diffs, breaks review tooling).
  Replaced with JSON.stringify([from,to,kind]) — collision-proof and text.
- new RegExp(ref.regex) for a Merge engine could throw on a pathological
  pattern, violating the module's documented "never a throw" contract. Wrapped
  in try/catch (an invalid pattern just yields no merge edges) + a test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBS74oUsXarGkCRQKEFLu
… run

showSchemaGraph now renders a spinner placeholder (reusing the query "starting"
pattern) before awaiting the lineage queries — system.* plus an EXPLAIN AST per
view/MV, which is noticeable on large databases — then swaps in the graph when
they resolve. The Expand button is hidden until the graph has loaded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBS74oUsXarGkCRQKEFLu
Reworks the inline schema-graph interaction so clicking is the primary gesture:

- Normal (default) cursor over the graph data area instead of the grab hand;
  nodes keep the pointer cursor.
- Drag-to-pan now requires ⌘/Ctrl held (attachPanZoom gains a modifierPan
  option), so a plain click lands on a node rather than grabbing the canvas.
  The pipeline graph and the fullscreen overlay keep plain drag-to-pan.
- Clicking an object runs SHOW CREATE for it and drops the formatted DDL into
  the editor (reuses the insertCreate action — same as a shift-click in the
  schema tree), replacing the previous click-to-expand. External
  dictionary-source leaves are inert.

Verified live on otel: cursor default, plain drag no-ops, ⌘-drag pans, and
clicking claude_otel.mv_api_latency_by_model wrote its CREATE DDL to the editor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBS74oUsXarGkCRQKEFLu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant