This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 64380bbf8a [flink] fix namespace for lineage from table path to
warehouse path (#7576)
64380bbf8a is described below
commit 64380bbf8a6303426d185f8704606f0a0bfa4cea
Author: jsingh-yelp <[email protected]>
AuthorDate: Fri Jun 19 10:12:17 2026 -0400
[flink] fix namespace for lineage from table path to warehouse path (#7576)
---
.../apache/paimon/flink/lineage/LineageUtils.java | 86 ++++++++++++++++++----
.../paimon/flink/lineage/PaimonLineageDataset.java | 2 +-
.../paimon/flink/lineage/LineageUtilsTest.java | 74 ++++++++++++++++---
3 files changed, 138 insertions(+), 24 deletions(-)
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/lineage/LineageUtils.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/lineage/LineageUtils.java
index 110365c76e..e9c51d489a 100644
---
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/lineage/LineageUtils.java
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/lineage/LineageUtils.java
@@ -19,6 +19,11 @@
package org.apache.paimon.flink.lineage;
import org.apache.paimon.CoreOptions;
+import org.apache.paimon.catalog.CatalogContext;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.options.CatalogOptions;
+import org.apache.paimon.table.FileStoreTable;
+import org.apache.paimon.table.FormatTable;
import org.apache.paimon.table.Table;
import org.apache.flink.api.connector.source.Boundedness;
@@ -26,28 +31,54 @@ import
org.apache.flink.streaming.api.lineage.LineageDataset;
import org.apache.flink.streaming.api.lineage.LineageVertex;
import org.apache.flink.streaming.api.lineage.SourceLineageVertex;
+import javax.annotation.Nullable;
+
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Lineage utilities for building {@link SourceLineageVertex} and {@link
LineageVertex} from a
- * Paimon table name and its physical warehouse path (namespace).
+ * Paimon table name and catalog options.
*/
public class LineageUtils {
- private static final String PAIMON_DATASET_PREFIX = "paimon://";
+ /** Default namespace when the catalog warehouse path is not available. */
+ private static final String DEFAULT_NAMESPACE = "paimon";
+
+ private static final String CATALOG_PREFIX = "catalog.";
+
+ /** Catalog option keys safe to include in lineage facets (no credentials
or secrets). */
+ private static final Set<String> CATALOG_OPTION_ALLOWLIST =
+ new HashSet<>(
+ Arrays.asList(CatalogOptions.WAREHOUSE.key(),
CatalogOptions.METASTORE.key()));
private static final Set<String> PAIMON_OPTION_KEYS =
CoreOptions.getOptions().stream().map(opt ->
opt.key()).collect(Collectors.toSet());
+ /** Extracts the {@link CatalogContext} from a table, or null if not
available. */
+ @Nullable
+ private static CatalogContext catalogContext(Table table) {
+ if (table instanceof FileStoreTable) {
+ return ((FileStoreTable)
table).catalogEnvironment().catalogContext();
+ }
+ if (table instanceof FormatTable) {
+ return ((FormatTable) table).catalogContext();
+ }
+ return null;
+ }
+
/**
- * Builds the config map for a dataset facet from a {@link Table}.
Includes filtered Paimon
- * {@link CoreOptions}, partition keys, primary keys, and the table
comment (if present).
+ * Builds the config map for a dataset facet. Includes filtered Paimon
{@link CoreOptions},
+ * partition keys, primary keys, and a safe subset of catalog-level
options (warehouse,
+ * metastore) prefixed with {@code "catalog."}.
*/
- private static Map<String, String> buildConfigMap(Table table) {
+ private static Map<String, String> buildConfigMap(
+ Table table, @Nullable CatalogContext catalogContext) {
Map<String, String> config = new HashMap<>();
config.put("partition-keys", String.join(",", table.partitionKeys()));
config.put("primary-keys", String.join(",", table.primaryKeys()));
@@ -56,16 +87,39 @@ public class LineageUtils {
.filter(e -> PAIMON_OPTION_KEYS.contains(e.getKey()))
.forEach(e -> config.put(e.getKey(), e.getValue()));
+ config.put("type", "paimon");
+
+ if (catalogContext != null) {
+ catalogContext
+ .options()
+ .toMap()
+ .forEach(
+ (k, v) -> {
+ if (CATALOG_OPTION_ALLOWLIST.contains(k)) {
+ config.put(CATALOG_PREFIX + k, v);
+ }
+ });
+ }
+
return config;
}
/**
- * Returns the lineage namespace for a Paimon table. The namespace uses
the {@code paimon://}
- * scheme followed by the table's physical warehouse path, e.g. {@code
- * "paimon://s3://my-bucket/warehouse/mydb.db/mytable"}.
+ * Returns the catalog warehouse path as the lineage namespace. Falls back
to the table path
+ * when no warehouse is configured, or {@code "paimon"} as a last resort.
*/
- public static String getNamespace(Table table) {
- return PAIMON_DATASET_PREFIX + CoreOptions.path(table.options());
+ public static String getNamespace(Table table, @Nullable CatalogContext
catalogContext) {
+ if (catalogContext != null) {
+ String warehouse =
catalogContext.options().get(CatalogOptions.WAREHOUSE);
+ if (warehouse != null) {
+ return warehouse;
+ }
+ }
+ Path path = CoreOptions.path(table.options());
+ if (path != null) {
+ return path.toString();
+ }
+ return DEFAULT_NAMESPACE;
}
/**
@@ -73,12 +127,14 @@ public class LineageUtils {
*
* @param name fully qualified table name, e.g. {@code
"paimon.mydb.mytable"}
* @param isBounded whether the source is bounded (batch) or unbounded
(streaming)
- * @param table the Paimon table (namespace is derived from its {@code
path} option)
+ * @param table the Paimon table
*/
public static SourceLineageVertex sourceLineageVertex(
String name, boolean isBounded, Table table) {
+ CatalogContext ctx = catalogContext(table);
LineageDataset dataset =
- new PaimonLineageDataset(name, getNamespace(table),
buildConfigMap(table));
+ new PaimonLineageDataset(
+ name, getNamespace(table, ctx), buildConfigMap(table,
ctx));
Boundedness boundedness =
isBounded ? Boundedness.BOUNDED :
Boundedness.CONTINUOUS_UNBOUNDED;
return new PaimonSourceLineageVertex(boundedness,
Collections.singletonList(dataset));
@@ -88,11 +144,13 @@ public class LineageUtils {
* Creates a {@link LineageVertex} for a Paimon sink table.
*
* @param name fully qualified table name, e.g. {@code
"paimon.mydb.mytable"}
- * @param table the Paimon table (namespace is derived from its {@code
path} option)
+ * @param table the Paimon table
*/
public static LineageVertex sinkLineageVertex(String name, Table table) {
+ CatalogContext ctx = catalogContext(table);
LineageDataset dataset =
- new PaimonLineageDataset(name, getNamespace(table),
buildConfigMap(table));
+ new PaimonLineageDataset(
+ name, getNamespace(table, ctx), buildConfigMap(table,
ctx));
return new PaimonSinkLineageVertex(Collections.singletonList(dataset));
}
}
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/lineage/PaimonLineageDataset.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/lineage/PaimonLineageDataset.java
index 5e99df0b2d..cdc072623b 100644
---
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/lineage/PaimonLineageDataset.java
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/lineage/PaimonLineageDataset.java
@@ -27,7 +27,7 @@ import java.util.Map;
/**
* A {@link LineageDataset} representing a Paimon table, identified by its
fully qualified name and
- * physical warehouse path as the namespace.
+ * catalog warehouse option as the namespace.
*/
public class PaimonLineageDataset implements LineageDataset {
diff --git
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/lineage/LineageUtilsTest.java
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/lineage/LineageUtilsTest.java
index 62d601ec1b..ed06e45430 100644
---
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/lineage/LineageUtilsTest.java
+++
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/lineage/LineageUtilsTest.java
@@ -19,10 +19,15 @@
package org.apache.paimon.flink.lineage;
import org.apache.paimon.CoreOptions;
+import org.apache.paimon.catalog.Catalog;
+import org.apache.paimon.catalog.CatalogContext;
+import org.apache.paimon.catalog.CatalogFactory;
import org.apache.paimon.flink.PaimonDataStreamScanProvider;
import org.apache.paimon.flink.PaimonDataStreamSinkProvider;
import org.apache.paimon.fs.Path;
import org.apache.paimon.fs.local.LocalFileIO;
+import org.apache.paimon.options.CatalogOptions;
+import org.apache.paimon.options.Options;
import org.apache.paimon.schema.Schema;
import org.apache.paimon.schema.SchemaManager;
import org.apache.paimon.table.FileStoreTable;
@@ -54,11 +59,14 @@ class LineageUtilsTest {
@TempDir java.nio.file.Path temp;
+ private Path warehouse;
private Path tablePath;
@BeforeEach
void setUp() {
- tablePath = new Path(temp.toUri().toString());
+ // mirror real Paimon layout: <warehouse>/<database>.db/<table>
+ warehouse = new Path(temp.toUri().toString());
+ tablePath = new Path(warehouse, "test_db.db/test_table");
}
private FileStoreTable createTable(
@@ -79,20 +87,50 @@ class LineageUtilsTest {
}
@Test
- void testGetNamespace() throws Exception {
+ void testGetNamespaceWithNullCatalogContext() throws Exception {
FileStoreTable table =
createTable(new HashMap<>(), Collections.emptyList(),
Arrays.asList("f0"));
+ assertThat(LineageUtils.getNamespace(table,
null)).isEqualTo(table.options().get("path"));
+ }
- String namespace = LineageUtils.getNamespace(table);
+ @Test
+ void testGetNamespaceWithCatalogContext() throws Exception {
+ FileStoreTable table =
+ createTable(new HashMap<>(), Collections.emptyList(),
Arrays.asList("f0"));
+ Options options = new Options();
+ options.set(CatalogOptions.WAREHOUSE, warehouse.toString());
+ CatalogContext ctx = CatalogContext.create(options);
- assertThat(namespace).startsWith("paimon://");
- assertThat(namespace).contains(tablePath.toString());
+ assertThat(LineageUtils.getNamespace(table,
ctx)).isEqualTo(warehouse.toString());
}
@Test
- void testSourceLineageVertexBounded() throws Exception {
+ void testGetNamespaceWithCatalogContextNoWarehouse() throws Exception {
FileStoreTable table =
createTable(new HashMap<>(), Collections.emptyList(),
Arrays.asList("f0"));
+ CatalogContext ctx = CatalogContext.create(new Options());
+ assertThat(LineageUtils.getNamespace(table,
ctx)).isEqualTo(table.options().get("path"));
+ }
+
+ @Test
+ void testSourceLineageVertexBounded() throws Exception {
+ Options catalogOptions = new Options();
+ catalogOptions.set(CatalogOptions.WAREHOUSE, warehouse.toString());
+ Catalog catalog =
CatalogFactory.createCatalog(CatalogContext.create(catalogOptions));
+ catalog.createDatabase("test_db", true);
+ catalog.createTable(
+ org.apache.paimon.catalog.Identifier.create("test_db", "src"),
+ new Schema(
+ RowType.of(new IntType(), new VarCharType(100), new
IntType()).getFields(),
+ Collections.emptyList(),
+ Arrays.asList("f0"),
+ new HashMap<>(),
+ ""),
+ false);
+ FileStoreTable table =
+ (FileStoreTable)
+ catalog.getTable(
+
org.apache.paimon.catalog.Identifier.create("test_db", "src"));
SourceLineageVertex vertex =
LineageUtils.sourceLineageVertex("paimon.db.src", true, table);
@@ -102,7 +140,8 @@ class LineageUtilsTest {
LineageDataset dataset = vertex.datasets().get(0);
assertThat(dataset.name()).isEqualTo("paimon.db.src");
- assertThat(dataset.namespace()).startsWith("paimon://");
+ assertThat(dataset.namespace()).isEqualTo(warehouse.toString());
+ catalog.close();
}
@Test
@@ -118,8 +157,23 @@ class LineageUtilsTest {
@Test
void testSinkLineageVertex() throws Exception {
+ Options catalogOptions = new Options();
+ catalogOptions.set(CatalogOptions.WAREHOUSE, warehouse.toString());
+ Catalog catalog =
CatalogFactory.createCatalog(CatalogContext.create(catalogOptions));
+ catalog.createDatabase("test_db", true);
+ catalog.createTable(
+ org.apache.paimon.catalog.Identifier.create("test_db", "sink"),
+ new Schema(
+ RowType.of(new IntType(), new VarCharType(100), new
IntType()).getFields(),
+ Collections.emptyList(),
+ Arrays.asList("f0"),
+ new HashMap<>(),
+ ""),
+ false);
FileStoreTable table =
- createTable(new HashMap<>(), Collections.emptyList(),
Arrays.asList("f0"));
+ (FileStoreTable)
+ catalog.getTable(
+
org.apache.paimon.catalog.Identifier.create("test_db", "sink"));
LineageVertex vertex =
LineageUtils.sinkLineageVertex("paimon.db.sink", table);
@@ -128,7 +182,8 @@ class LineageUtilsTest {
LineageDataset dataset = vertex.datasets().get(0);
assertThat(dataset.name()).isEqualTo("paimon.db.sink");
- assertThat(dataset.namespace()).startsWith("paimon://");
+ assertThat(dataset.namespace()).isEqualTo(warehouse.toString());
+ catalog.close();
}
@Test
@@ -144,6 +199,7 @@ class LineageUtilsTest {
DatasetConfigFacet configFacet = (DatasetConfigFacet)
facets.get("config");
Map<String, String> config = configFacet.config();
+ assertThat(config).containsEntry("type", "paimon");
assertThat(config).containsEntry("partition-keys", "f2");
assertThat(config).containsEntry("primary-keys", "f0,f2");
}