This is an automated email from the ASF dual-hosted git repository. hope pushed a commit to branch release-1.4 in repository https://gitbox.apache.org/repos/asf/paimon.git
commit f509a8fe15bc7e5b7db40923f3c1d283eb498a73 Author: Zouxxyy <[email protected]> AuthorDate: Tue Mar 31 21:07:29 2026 +0800 [spark] Fix view resolution for CTE and ORDER BY ordinal (#7552) Fix PaimonViewResolver to apply early analysis rules (CTESubstitution, SubstituteUnresolvedOrdinals) to parsed view plans. These rules run in Spark's earlyBatches before the Resolution batch, so they won't re-run for plans injected by PaimonViewResolver during resolution. Without this fix: - Views with CTE (`WITH t AS ...`) fail with `TABLE_OR_VIEW_NOT_FOUND` - Views with `ORDER BY 1` (ordinal) produce incorrect results --- .../catalyst/analysis/PaimonViewResolver.scala | 8 +++-- .../paimon/spark/sql/PaimonViewTestBase.scala | 38 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/PaimonViewResolver.scala b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/PaimonViewResolver.scala index 45de6a1c76..b45463e916 100644 --- a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/PaimonViewResolver.scala +++ b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/PaimonViewResolver.scala @@ -24,7 +24,7 @@ import org.apache.paimon.spark.catalog.SupportView import org.apache.paimon.view.View import org.apache.spark.sql.SparkSession -import org.apache.spark.sql.catalyst.analysis.{GetColumnByOrdinal, UnresolvedRelation, UnresolvedTableOrView} +import org.apache.spark.sql.catalyst.analysis.{CTESubstitution, GetColumnByOrdinal, SubstituteUnresolvedOrdinals, UnresolvedRelation, UnresolvedTableOrView} import org.apache.spark.sql.catalyst.expressions.{Alias, Attribute, UpCast} import org.apache.spark.sql.catalyst.parser.ParseException import org.apache.spark.sql.catalyst.parser.extensions.{CurrentOrigin, Origin} @@ -62,6 +62,10 @@ case class PaimonViewResolver(spark: SparkSession) val parsedPlan = parseViewText(nameParts.toArray.mkString("."), view.query(SupportView.DIALECT)) + // Apply early analysis rules that won't re-run for plans injected during Resolution batch. + val earlyRules = Seq(CTESubstitution, SubstituteUnresolvedOrdinals) + val rewritten = earlyRules.foldLeft(parsedPlan)((plan, rule) => rule.apply(plan)) + val aliases = SparkTypeUtils.fromPaimonRowType(view.rowType()).fields.zipWithIndex.map { case (expected, pos) => val attr = GetColumnByOrdinal(pos, expected.dataType) @@ -69,7 +73,7 @@ case class PaimonViewResolver(spark: SparkSession) Some(expected.metadata)) } - SubqueryAlias(nameParts, Project(aliases, parsedPlan)) + SubqueryAlias(nameParts, Project(aliases, rewritten)) } private def parseViewText(name: String, viewText: String): LogicalPlan = { diff --git a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/PaimonViewTestBase.scala b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/PaimonViewTestBase.scala index b276af89b5..7c503d98a5 100644 --- a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/PaimonViewTestBase.scala +++ b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/PaimonViewTestBase.scala @@ -204,4 +204,42 @@ abstract class PaimonViewTestBase extends PaimonHiveTestBase { } } } + + test("Paimon View: create view with CTE") { + Seq(sparkCatalogName, paimonHiveCatalogName).foreach { + catalogName => + sql(s"USE $catalogName") + withDatabase("test_db") { + sql("CREATE DATABASE test_db") + sql("USE test_db") + withView("v1") { + sql(""" + |CREATE VIEW v1 AS + | WITH t(a, b, c, d) AS (SELECT 1, 2, 3, 4) + | SELECT * FROM t + |""".stripMargin) + checkAnswer(sql("SELECT * FROM v1"), Seq(Row(1, 2, 3, 4))) + } + } + } + } + + test("Paimon View: create view with ORDER BY ordinal") { + Seq(sparkCatalogName, paimonHiveCatalogName).foreach { + catalogName => + sql(s"USE $catalogName") + withDatabase("test_db") { + sql("CREATE DATABASE test_db") + sql("USE test_db") + withTable("t") { + withView("v_ord") { + sql("CREATE TABLE t (id INT, name STRING) USING paimon") + sql("INSERT INTO t VALUES (2, 'b'), (1, 'a')") + sql("CREATE VIEW v_ord AS SELECT * FROM t ORDER BY 1") + checkAnswer(sql("SELECT * FROM v_ord"), Seq(Row(1, "a"), Row(2, "b"))) + } + } + } + } + } }
