OpenLineage dataset identity
A dataset in OpenLineage is identified by the pair (namespace, name). Two events carrying the same pair refer to the same node in the lineage graph.
The most common silent failure in cross-tool lineage is one physical table showing up as two differently-named nodes: Hop writes orders, dbt reads analytics.staging.orders, and the graph never joins them. This page documents exactly what Hop emits, so a second producer can match it.
Treat these rules as an API. Changing them re-keys existing lineage graphs, which is a breaking change — see Versioning.
Two namespaces, never conflate them
There are two distinct namespace concepts, and they are not interchangeable:
| Concept | What it identifies | Source in Hop |
|---|---|---|
Job namespace | The producer of the run — the Hop installation or environment |
|
Dataset namespace | The physical data source a dataset lives in — a database, a filesystem | Derived from the dataset’s location, see below |
| The job namespace ( |
File datasets
File (FILE_IO) datasets have two naming modes, selected by HOP_LINEAGE_OPENLINEAGE_FILE_SPEC_NAMING (default N).
Legacy mode (default)
-
namespace = the Hop job namespace (
HOP_LINEAGE_OPENLINEAGE_NAMESPACE, defaulthop) -
name = the last path segment of the file URI, with the full URI preserved in the
dataSourcedataset facet
This predates the contract and uses the job namespace as the dataset namespace. It does not follow the OpenLineage naming specification and will not reconcile a file across engines or tools. It remains the default only so that existing lineage graphs are not silently re-keyed.
Spec mode (HOP_LINEAGE_OPENLINEAGE_FILE_SPEC_NAMING=Y)
Follows the OpenLineage naming specification, derived by the shared engine helper LineageFileIdentity so that the sink and the Beam/Spark engines agree on one identity for the same physical file.
| VFS scheme | namespace | name |
|---|---|---|
|
| absolute path ( |
|
| object key, no leading slash |
|
| object key |
|
| path |
|
| path |
other ( |
| path |
URI user info (credentials) is dropped from the namespace, so no secret reaches the lineage graph. The exception is Azure, whose authority carries the container rather than a credential.
Switching a deployment from legacy to spec mode re-keys its file datasets. That is why it is a flag rather than a straight change.
Relational datasets — the join key for other tools
This is the identity another producer has to match to land on the same node as a Hop write.
Dataset namespace
{scheme}://{host}[:{port}] -
schemeis derived from the Hop database type, not from the raw JDBC sub-protocol — the two differ (PostgreSQL ispostgresqlin JDBC butpostgresin the OpenLineage naming spec). Known types map as below; any other type falls back to its lowercased Hop type id. -
hostis taken from the connection, trimmed and lowercased. -
portis the connection’s port, or the database’s default port when the connection leaves it blank. It is omitted only when neither is known.
| Hop database type | Dataset namespace |
|---|---|
PostgreSQL |
|
MySQL, MariaDB |
|
Redshift |
|
Oracle |
|
MS SQL Server (both drivers) |
|
DB2 |
|
anything else |
|
A connection with no host — an embedded or file-backed database — has no namespace to key on, and no relational lineage event is emitted for it.
| Known gap: Snowflake and BigQuery. The OpenLineage naming specification names these by account ( |
Dataset name
{database}.{schema}.{table} -
Only the segments the database actually has are included, joined with
.; a database with no catalog level yields{schema}.{table}. -
Segments are emitted verbatim: trimmed, but never re-cased and never quoted. No surrounding quotes, no trailing semicolon.
-
A table recovered by parsing SQL carries no catalog segment (SQL cannot name one) and may carry no schema either. Blank segments are filled from the connection’s own catalog (
DatabaseMeta.getDatabaseName()) and preferred schema, so a read ofschema.tableand a write ofcatalog.schema.tableresolve to one identity.
| Known gap: identifier casing. The naming specification asks for the name as the database itself stores it in its catalog — upper case for Snowflake, lower case for PostgreSQL — unless the identifier was quoted. Hop emits what the transform was configured with, so a Table Output configured |
Worked example
A Hop pipeline writes analytics.staging.orders in PostgreSQL on db:5432; a dbt model then reads it to build analytics.marts.daily_orders. Both must emit the handoff table as:
namespace: postgres://db:5432
name: analytics.staging.orders The graph query "what fed analytics.marts.daily_orders?" then traverses Hop → dbt through one shared node, with nothing having to infer that two differently-named nodes are the same table. If such a guess is required, the identities are not aligned.
Run identity
Dataset identity answers "is this the same table". Run identity answers "is this run a child of the Hop run". The run id this sink emits is:
-
runId = the Hop log channel id of the executing pipeline or workflow (
LineageContext.getLogChannelId()). File and relational I/O are correlated to the parent pipeline run via thepipelineLogChannelIdcontext attribute.
OpenLineage types runId as a UUID. Hop log channel ids are already UUID strings, so the emitted value is that id verbatim. Only the synthetic fallback used when no log channel id is available — never part of a real cross-tool stitch — is derived deterministically from the job name.
A downstream producer that wants to attach as a child of the Hop run propagates this value as the OpenLineage ParentRunFacet run.runId, with job.namespace set to the Hop job namespace and job.name to the Hop workflow or pipeline name.
Versioning
This is contract v3.
-
v3 documents the relational identity as implemented by the engine’s
RELATIONAL_IOemitter: the namespace scheme is derived from the Hop database type rather than the JDBC sub-protocol, and the two known gaps above (Snowflake/BigQuery namespaces, identifier casing) are stated rather than assumed. -
v2 added the OpenLineage-spec file naming mode, opt-in via
HOP_LINEAGE_OPENLINEAGE_FILE_SPEC_NAMING, keeping legacy file naming as the default.
Any change to the rules on this page is breaking: it must bump the version here and be reflected in every producer that stitches into Hop’s graph.