From 4c1412bdd0674de37f45f60cc070f2a1ecadca09 Mon Sep 17 00:00:00 2001
From: wforget <643348094@qq.com>
Date: Wed, 13 Aug 2025 16:58:44 +0800
Subject: [PATCH] [KYUUBI #7168] Adapt PermanentViewMarker introduced by authz
plugin in lineage plugin
### Why are the changes needed?
Fix the lineage plugin cannot capture lineage of view after integrating authz plugin.
closes #7168
### How was this patch tested?
added unit test
### Was this patch authored or co-authored using generative AI tooling?
No
Closes #7169 from wForget/KYUUBI-7168.
Closes #7168
42ac01639 [wforget] fix test
208550a3e [wforget] [KYUUBI-7168] Adapt PermanentViewMarker introduced by authz plugin in lineage plugin
Authored-by: wforget <643348094@qq.com>
Signed-off-by: wforget <643348094@qq.com>
---
extensions/spark/kyuubi-spark-authz/pom.xml | 6 ++++
.../ranger/RangerSparkExtensionSuite.scala | 29 +++++++++++++++++++
.../helper/SparkSQLLineageParseHelper.scala | 7 +++++
3 files changed, 42 insertions(+)
diff --git a/extensions/spark/kyuubi-spark-authz/pom.xml b/extensions/spark/kyuubi-spark-authz/pom.xml
index fae03aca7..49c79d496 100644
--- a/extensions/spark/kyuubi-spark-authz/pom.xml
+++ b/extensions/spark/kyuubi-spark-authz/pom.xml
@@ -391,6 +391,12 @@
test
+
+ org.apache.kyuubi
+ kyuubi-spark-lineage_${scala.binary.version}
+ ${project.version}
+ test
+
diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtensionSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtensionSuite.scala
index 4e8cddb68..1fdea0ed9 100644
--- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtensionSuite.scala
+++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtensionSuite.scala
@@ -37,6 +37,8 @@ import org.scalatest.BeforeAndAfterAll
import org.scalatest.funsuite.AnyFunSuite
import org.apache.kyuubi.Utils
+import org.apache.kyuubi.plugin.lineage.Lineage
+import org.apache.kyuubi.plugin.lineage.helper.SparkSQLLineageParseHelper
import org.apache.kyuubi.plugin.spark.authz.{AccessControlException, SparkSessionProvider}
import org.apache.kyuubi.plugin.spark.authz.MysqlContainerEnv
import org.apache.kyuubi.plugin.spark.authz.RangerTestNamespace._
@@ -1513,4 +1515,31 @@ class HiveCatalogRangerSparkExtensionSuite extends RangerSparkExtensionSuite {
}
}
}
+
+ test("Test view lineage") {
+ def extractLineage(sql: String): Lineage = {
+ val parsed = spark.sessionState.sqlParser.parsePlan(sql)
+ val qe = spark.sessionState.executePlan(parsed)
+ val analyzed = qe.analyzed
+ SparkSQLLineageParseHelper(spark).transformToLineage(0, analyzed).get
+ }
+
+ val db1 = defaultDb
+ val table1 = "table1"
+ val view1 = "view1"
+ withSingleCallEnabled {
+ withCleanTmpResources(Seq((s"$db1.$table1", "table"), (s"$db1.$view1", "view"))) {
+ doAs(admin, sql(s"CREATE TABLE IF NOT EXISTS $db1.$table1 (id int, scope int)"))
+ doAs(admin, sql(s"CREATE VIEW $db1.$view1 AS SELECT * FROM $db1.$table1"))
+
+ val lineage = doAs(
+ admin,
+ extractLineage(s"SELECT id FROM $db1.$view1 WHERE id > 1"))
+ assert(lineage.inputTables.size == 1)
+ assert(lineage.inputTables.head === s"spark_catalog.$db1.$table1")
+ assert(lineage.columnLineage.size == 1)
+ assert(lineage.columnLineage.head.originalColumns.head === s"spark_catalog.$db1.$table1.id")
+ }
+ }
+ }
}
diff --git a/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala b/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala
index effe2ae83..a6cb6934f 100644
--- a/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala
+++ b/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala
@@ -448,6 +448,13 @@ trait LineageParser {
})
}
+ // PermanentViewMarker is introduced by kyuubi authz plugin, which is a wrapper of View,
+ // so we just extract the columns lineage from its inner children (original view)
+ case pvm if pvm.nodeName == "PermanentViewMarker" =>
+ pvm.innerChildren.asInstanceOf[Seq[LogicalPlan]]
+ .map(extractColumnsLineage(_, parentColumnsLineage))
+ .reduce(mergeColumnsLineage)
+
case p: View =>
if (!p.isTempView && SparkContextHelper.getConf(
LineageConf.SKIP_PARSING_PERMANENT_VIEW_ENABLED)) {