Skip to content

Commit 77c9f31

Browse files
authored
Pluggable graph interface for Hoptimator topology + Mermaid renderer (#222)
* Add pipeline graph SPI + Mermaid renderer Introduces the visualization framework consumed by the `!graph` command in a follow-up commit: - `com.linkedin.hoptimator.graph` (in hoptimator-api): GraphNode, GraphEdge, GraphTarget, PipelineGraph data model + GraphProvider and GraphRenderer SPIs. - New `hoptimator-graph` module hosting the Mermaid renderer plus CronHumanizer (renders cron expressions in trigger labels as English via cron-utils CronDescriptor, falling through to the raw cron string on parse failure). The renderer is registered via META-INF/services and discovered by GraphService (added in the next commit). * Add K8s graph provider with SQL identifier resolution Hooks the graph SPI from the previous commit up to a working data path: - GraphService (hoptimator-jdbc): the dispatch entry point. Walks Calcite's schema tree to resolve a user-typed identifier (TWO_LEVEL.NAME or THREE_LEVEL.SCHEMA.NAME) to a GraphTarget, loads GraphProvider and GraphRenderer impls via ServiceLoader, and dispatches. - PipelineGraphBuilder + K8sGraphProvider (hoptimator-k8s): direction-aware traversal of Pipeline / TableTrigger / View references that produces a PipelineGraph rooted at the target. - LogicalTable detection: HoptimatorJdbcSchema.isLogical() lazily walks its downstream Calcite connection to spot any sub-schema tagged with the new LogicalSchemaMarker, so the JDBC layer can surface LogicalTable targets without baking driver-specific URL prefixes or class names into the adapter. LogicalTableSchema implements the marker. isLogical() lives on HoptimatorJdbcSchema rather than the Database SPI to keep Database a thin K8s-CRD surface free of planner concerns. - GraphService resolves identifiers to MaterializedView (view), LogicalTable (marker hit), or Resource (everything else with a HoptimatorJdbcSchema backing) targets; failures throw SQLException with the offending segment in the message rather than silently building a degenerate graph. * Add `!graph` CLI command + integration tests Surfaces the visualization framework as a single Sqlline command: - `!graph <identifier> [depth]` in hoptimator-cli renders the resolved target as Mermaid. Depth defaults to a reasonable bound and negative depths error. The command auto-detects target kind via the resolver in the previous commit, so users do not need separate `!graph table` / `!graph view` / `!graph logical` variants. - Quidem-driven integration scripts under hoptimator-k8s, hoptimator-logical, and hoptimator-mysql (k8s-graph.id, k8s-trigger-graph.id, logical-graph.id, mysql-graph.id) that exercise the resolver + Mermaid renderer end-to-end against the test catalogs each module already ships. Driven by the new graph-aware harness in QuidemTestBase. - Sample logicaldb.yaml updated to expose a logical table that the integration scripts can exercise. * Refactor to remove heavy K8s .list() calls * Add documentation and address agent pr review comments * Add TODO * Clean up suppressions
1 parent 8e7e1fb commit 77c9f31

50 files changed

Lines changed: 4617 additions & 85 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

deploy/samples/logicaldb.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ metadata:
2121
name: logical-offline
2222
spec:
2323
url: jdbc:logical://nearline=ads-database;offline=ads-catalog-database
24-
schema: LOGICAL_OFFLINE
24+
schema: LOGICAL-OFFLINE
2525
dialect: Calcite

docs/extending/index.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ both — pick the layer that matches what you're doing.
66

77
## Pick the right surface
88

9-
| You want to… | What you'll write |
10-
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
11-
| Connect a new external system to the catalog (Kafka, Venice, MySQL, your-system). | A JDBC adapter + `TableTemplate` / `JobTemplate`. See [Data sources](data-sources.md). |
12-
| Send Hoptimator-generated specs somewhere other than Kubernetes. | A `Deployer` + `DeployerProvider`. See [Deployers](deployers.md). |
13-
| Reject SQL or YAML that's invalid in your environment before it deploys. | A `Validator` + `ValidatorProvider`. See [Validators](validators.md). |
14-
| Pull configuration values from somewhere other than `hoptimator-configmap`. | A `ConfigProvider`. See [Config providers](config-providers.md). |
15-
| Customize what gets deployed for an existing system. | Just a `TableTemplate` or `JobTemplate` — no Java needed. See [Templates and configuration](../kubernetes/templates.md). |
9+
| You want to… | What you'll write |
10+
|-----------------------------------------------------------------------------------------------------| ------------------------------------------------------------------ |
11+
| Connect a new external system to the catalog (Kafka, Venice, MySQL, your-system). | A JDBC adapter + `TableTemplate` / `JobTemplate`. See [Data sources](data-sources.md). |
12+
| Send Hoptimator-generated specs somewhere other than Kubernetes. | A `Deployer` + `DeployerProvider`. See [Deployers](deployers.md). |
13+
| Reject SQL or YAML that's invalid in your environment before it deploys. | A `Validator` + `ValidatorProvider`. See [Validators](validators.md). |
14+
| Pull configuration values from somewhere other than `hoptimator-configmap`. | A `ConfigProvider`. See [Config providers](config-providers.md). |
15+
| Build a dependency graph from some backing store (e.g. K8s). | A `GraphProvider`. The K8s-backed default ships in `hoptimator-k8s`. |
16+
| Render the dependency graph in a format other than Mermaid (DOT, JSON, an interactive web view, …). | A `GraphRenderer`. The Mermaid default ships in `hoptimator-graph`. |
17+
| Customize what gets deployed for an existing system. | Just a `TableTemplate` or `JobTemplate` — no Java needed. See [Templates and configuration](../kubernetes/templates.md). |
1618

1719
## How extensions are loaded
1820

19-
All four extension points are loaded via Java's `ServiceLoader`. To register
21+
All extension points are loaded via Java's `ServiceLoader`. To register
2022
an implementation, drop a service file under
2123
`src/main/resources/META-INF/services/` named after the SPI interface:
2224

@@ -26,6 +28,8 @@ META-INF/services/com.linkedin.hoptimator.ValidatorProvider
2628
META-INF/services/com.linkedin.hoptimator.ConfigProvider
2729
META-INF/services/com.linkedin.hoptimator.ConnectorProvider
2830
META-INF/services/com.linkedin.hoptimator.CatalogProvider
31+
META-INF/services/com.linkedin.hoptimator.graph.GraphProvider
32+
META-INF/services/com.linkedin.hoptimator.graph.GraphRenderer
2933
```
3034

3135
Each file contains the fully qualified class name(s) of your
@@ -66,6 +70,22 @@ mutation, and the SQL/YAML is rejected if a validator returns errors.
6670
Common uses: naming conventions, schema compatibility, ACL checks. See
6771
[Validators](validators.md).
6872

73+
### "I want to visualize what's deployed differently"
74+
75+
The `!graph` CLI command (see
76+
[SQL CLI → !graph](../user-guide/sql-cli.md#graph-identifier---depth-n))
77+
goes through two SPIs: `GraphProvider` builds the typed
78+
`PipelineGraph` from some backing store, and `GraphRenderer` serializes
79+
it to a string. The bundled defaults are a K8s-backed
80+
`K8sGraphProvider` (in `hoptimator-k8s`) and a Mermaid `MermaidRenderer`
81+
(in `hoptimator-graph`).
82+
83+
Add a `GraphRenderer` to support a new output format (e.g. DOT for
84+
graphviz, a JSON shape for a web UI). Add a `GraphProvider` if the
85+
pipeline state lives somewhere other than Kubernetes — the K8s
86+
implementation is the reference. Both register via `META-INF/services`
87+
like every other SPI here.
88+
6989
## Register, then test
7090

7191
After dropping a service file and a class, the standard verification path

docs/getting-started/architecture.md

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -142,24 +142,25 @@ the pipeline.
142142

143143
The repo is split into focused modules. The ones you'll touch most often:
144144

145-
| Module | Role |
146-
| --------------------------------- | -------------------------------------------------------------------- |
147-
| `hoptimator-api` | The interfaces. `Deployer`, `Engine`, `Connector`, `View`, etc. |
148-
| `hoptimator-jdbc` | Calcite-based JDBC driver. Catalog, parser, planner integration. |
149-
| `hoptimator-jdbc-driver` | Lightweight wrapper that exposes the driver to standard JDBC code. |
150-
| `hoptimator-cli` | The `./hoptimator` SQL CLI (sqlline + custom commands). |
151-
| `hoptimator-mcp-server` | MCP server that wraps the JDBC driver for AI agents and IDEs. |
152-
| `hoptimator-util` | Planner rules, deployment service, template engine. |
153-
| `hoptimator-k8s` | Default Deployers, the catalog/operator glue, all CRDs. |
154-
| `hoptimator-operator` | The reconciler loop and its main entry point. |
155-
| `hoptimator-flink-runner` | The runtime that executes Flink SQL jobs produced by the planner. |
156-
| `hoptimator-flink-adapter` | Flink-side adapter for catalog awareness. |
157-
| `hoptimator-kafka` / `-kafka-controller` | Kafka catalog and controller integration. |
158-
| `hoptimator-venice` | Venice catalog adapter. |
159-
| `hoptimator-mysql` | MySQL catalog adapter. |
160-
| `hoptimator-logical` | LogicalTable support — one logical entity, multiple physical tiers. |
161-
| `hoptimator-demodb` | In-memory demo source used by the quickstart. |
162-
| `hoptimator-avro` | Avro schema utilities used by the catalog/connectors. |
145+
| Module | Role |
146+
|------------------------------------------|---------------------------------------------------------------------|
147+
| `hoptimator-api` | The interfaces. `Deployer`, `Engine`, `Connector`, `View`, etc. |
148+
| `hoptimator-jdbc` | Calcite-based JDBC driver. Catalog, parser, planner integration. |
149+
| `hoptimator-jdbc-driver` | Lightweight wrapper that exposes the driver to standard JDBC code. |
150+
| `hoptimator-cli` | The `./hoptimator` SQL CLI (sqlline + custom commands). |
151+
| `hoptimator-mcp-server` | MCP server that wraps the JDBC driver for AI agents and IDEs. |
152+
| `hoptimator-util` | Planner rules, deployment service, template engine. |
153+
| `hoptimator-k8s` | Default Deployers, the catalog/operator glue, all CRDs. |
154+
| `hoptimator-operator` | The reconciler loop and its main entry point. |
155+
| `hoptimator-flink-runner` | The runtime that executes Flink SQL jobs produced by the planner. |
156+
| `hoptimator-flink-adapter` | Flink-side adapter for catalog awareness. |
157+
| `hoptimator-kafka` / `-kafka-controller` | Kafka catalog and controller integration. |
158+
| `hoptimator-venice` | Venice catalog adapter. |
159+
| `hoptimator-mysql` | MySQL catalog adapter. |
160+
| `hoptimator-logical` | LogicalTable support — one logical entity, multiple physical tiers. |
161+
| `hoptimator-graph` | Pipeline graph renderers. Ships the Mermaid backend for `!graph`. |
162+
| `hoptimator-demodb` | In-memory demo source used by the quickstart. |
163+
| `hoptimator-avro` | Avro schema utilities used by the catalog/connectors. |
163164

164165
A handful of modules (`hoptimator-catalog`, `hoptimator-models`,
165166
`hoptimator-planner`) are in the tree but marked for deletion; new
@@ -179,6 +180,11 @@ contributions should not target them.
179180
`DeployerProvider`. Kubernetes is the default but not a hard requirement;
180181
the bundled `K8sSourceDeployer` and `K8sJobDeployer` are themselves
181182
examples. Anything that knows how to materialize a spec will do.
183+
- **A new visualization format or graph backend**: implement `GraphRenderer`
184+
for a new output format (DOT, JSON, an interactive HTML view, …) or
185+
`GraphProvider` to build the dependency graph from somewhere other than
186+
Kubernetes. The bundled Mermaid renderer (`hoptimator-graph`) and K8s
187+
provider (`hoptimator-k8s`) are reference impls.
182188
- **Different cluster configuration**: usually a `ConfigProvider` change
183189
rather than code.
184190

docs/getting-started/concepts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ right bootstrap servers and topic name. Connectors are produced by the
9999
catalog adapter for each database, embedded in the YAML that
100100
[TableTemplates](#tabletemplates-and-jobtemplates) and
101101
[JobTemplates](#tabletemplates-and-jobtemplates) emit, and can be customized
102-
via [hints](#hints).
102+
via [hints](#configuration-and-hints).
103103

104104
Connectors do not require an `Engine` to function. The typical flow is:
105105
Hoptimator generates a `FlinkSessionJob` (or similar) with the connector

docs/index.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ Start with **[Getting started](getting-started/index.md)**:
2323
See the **[User guide](user-guide/index.md)**:
2424

2525
- [SQL CLI](user-guide/sql-cli.md) — sqlline-based interactive shell with
26-
`!pipeline`, `!specify`, `!resolve` for inspecting plans before they deploy.
26+
`!pipeline`, `!specify`, `!resolve` for inspecting plans before they
27+
deploy, and `!graph` for visualizing what's already running.
2728
- [JDBC driver](user-guide/jdbc.md)`jdbc:hoptimator://` for Java apps,
2829
with full connection-property reference.
2930
- [MCP server](user-guide/mcp-server.md) — Model Context Protocol server
@@ -64,6 +65,8 @@ See **[Extending Hoptimator](extending/index.md)**:
6465
via `Validator` and `ValidatorProvider`.
6566
- [Config providers](extending/config-providers.md) — custom
6667
`ConfigProvider` SPI.
68+
- [Pipeline graph SPIs](extending/index.md)`GraphProvider` (alternate backing store) and
69+
`GraphRenderer` (alternate output format).
6770
- [Templates and configuration](kubernetes/templates.md) — authoring
6871
`TableTemplate` and `JobTemplate` (lives in the Kubernetes guide).
6972

docs/user-guide/sql-cli.md

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ See [JDBC driver](jdbc.md) for the full URL syntax.
3333
## Built-in commands
3434

3535
Standard sqlline commands all work (`!help`, `!quit`, `!run`, `!record`,
36-
…) along with the catalog-introspection ones below. Hoptimator adds the
37-
last four for inspecting plans and pipelines.
36+
…) along with the catalog-introspection ones below. Hoptimator also adds
37+
commands to inspect plans, pipelines, and the deployed graph.
3838

3939
| Command | What it does |
4040
| ------------- | ----------------------------------------------------------------------------- |
@@ -44,9 +44,11 @@ last four for inspecting plans and pipelines.
4444
| `!resolve` | Print the schema and source/sink connector configs Hoptimator would use for a table. |
4545
| `!pipeline` | Print the auto-generated pipeline SQL for a SELECT or CREATE MATERIALIZED VIEW statement. |
4646
| `!specify` | Print every Kubernetes spec the statement would deploy. The dry-run for `CREATE MATERIALIZED VIEW`. |
47+
| `!graph` | Render the deployed dependency graph rooted at an identifier as a Mermaid diagram. |
4748

48-
`!resolve`, `!pipeline`, and `!specify` do not modify any state. Use them to
49-
sanity-check a plan before you let the JDBC driver actually deploy it.
49+
`!resolve`, `!pipeline`, `!specify`, and `!graph` do not modify any state.
50+
Use them to sanity-check a plan before you let the JDBC driver actually
51+
deploy it (and to inspect what's already running).
5052

5153
### `!resolve <schema.table>`
5254

@@ -115,6 +117,61 @@ If you'd `kubectl apply` the output, you'd get the same result as actually
115117
running the `CREATE MATERIALIZED VIEW`. This is the safest way to review what
116118
a statement will do before you run it.
117119

120+
### `!graph <identifier> [--depth N]`
121+
122+
```sql
123+
0: Hoptimator> !graph ADS.AUDIENCE
124+
flowchart LR
125+
subgraph n0["Materialized View"]
126+
n1[/"ads-audience
127+
kind: SqlJob
128+
engine: Flink
129+
mode: Streaming"/]
130+
end
131+
n2[("ADS.PAGE_VIEWS")]
132+
n3[("PROFILE.MEMBERS")]
133+
n4[("ADS.AUDIENCE")]
134+
n2 --> n1
135+
n3 --> n1
136+
n1 --> n4
137+
```
138+
Rendered:
139+
```mermaid
140+
flowchart LR
141+
subgraph n0["Materialized View"]
142+
n1[/"ads-audience
143+
kind: SqlJob
144+
engine: Flink
145+
mode: Streaming"/]
146+
end
147+
n2[("ADS.PAGE_VIEWS")]
148+
n3[("PROFILE.MEMBERS")]
149+
n4[("ADS.AUDIENCE")]
150+
n2 --> n1
151+
n3 --> n1
152+
n1 --> n4
153+
```
154+
155+
Renders the deployed dependency graph rooted at `<identifier>` as Mermaid.
156+
Identifier resolution runs against Calcite's catalog, so the same names you use in SQL work here:
157+
158+
- A materialized view (`ADS.AUDIENCE`) renders the view's compiled pipeline
159+
with its direct sources and sink.
160+
- A logical table (`LOGICAL.events`) renders the inter-tier pipelines,
161+
any owned triggers, and the per-tier physical resources grouped into
162+
tier subgraphs.
163+
- A physical resource (`KAFKA.events`) traverses the depends-on dependency
164+
index up to `--depth` hops in each direction — pipelines that read or
165+
write it, and recursively their other endpoints.
166+
167+
`--depth N` only applies to physical-resource targets; view and logical-table
168+
graphs are intentionally single-hop ("what this view does," not the full
169+
upstream chain). For the chain, run `!graph` on a source identifier.
170+
171+
Rendering backends are pluggable. Mermaid is the default and the only one
172+
shipped today; additional renderers can register via the
173+
`GraphRenderer` SPI — see [Extending Hoptimator](../extending/index.md).
174+
118175
## Running SQL
119176

120177
Hoptimator supports the SQL surface described in
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.linkedin.hoptimator.graph;
2+
3+
import java.util.Objects;
4+
5+
6+
/**
7+
* A directed edge in a pipeline visualization graph. Edges are equal when their endpoints and
8+
* type match — two edges of different types between the same nodes are distinct (e.g. an
9+
* {@code ownerOf} relationship coexists with a {@code dependsOnSink} relationship).
10+
*/
11+
public final class GraphEdge {
12+
13+
public enum Type {
14+
/** {@code metadata.ownerReferences} cascade — drives subgraph membership, not arrows. */
15+
OWNER_OF,
16+
/** Resource → pipeline (or job) edge derived from the {@code depends-on} annotation. */
17+
DEPENDS_ON_SOURCE,
18+
/** Pipeline (or job) → resource edge derived from the {@code depends-on} annotation. */
19+
DEPENDS_ON_SINK,
20+
/** Trigger → job/pipeline; rendered as a dotted line. */
21+
TRIGGERS
22+
}
23+
24+
private final GraphNode from;
25+
private final GraphNode to;
26+
private final Type type;
27+
28+
public GraphEdge(GraphNode from, GraphNode to, Type type) {
29+
this.from = Objects.requireNonNull(from, "from");
30+
this.to = Objects.requireNonNull(to, "to");
31+
this.type = Objects.requireNonNull(type, "type");
32+
}
33+
34+
public GraphNode from() {
35+
return from;
36+
}
37+
38+
public GraphNode to() {
39+
return to;
40+
}
41+
42+
public Type type() {
43+
return type;
44+
}
45+
46+
@Override
47+
public boolean equals(Object o) {
48+
if (!(o instanceof GraphEdge)) {
49+
return false;
50+
}
51+
GraphEdge other = (GraphEdge) o;
52+
return type == other.type && from.equals(other.from) && to.equals(other.to);
53+
}
54+
55+
@Override
56+
public int hashCode() {
57+
return Objects.hash(from, to, type);
58+
}
59+
60+
@Override
61+
public String toString() {
62+
return from.id() + " --" + type + "--> " + to.id();
63+
}
64+
}

0 commit comments

Comments
 (0)