This is an automated email from the ASF dual-hosted git repository.

zouxxyy 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 da8dde093d [spark] Support column aliases and comments for Paimon 
views (#7625)
da8dde093d is described below

commit da8dde093df9afb9fdf5221c5de0ab4ddcebaef4
Author: Yann Byron <[email protected]>
AuthorDate: Wed Apr 15 15:24:00 2026 +0800

    [spark] Support column aliases and comments for Paimon views (#7625)
---
 .../paimon/spark/execution/PaimonViewExec.scala    |  45 ++++++++-
 .../paimon/spark/sql/PaimonViewTestBase.scala      | 104 +++++++++++++++++++++
 2 files changed, 145 insertions(+), 4 deletions(-)

diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/execution/PaimonViewExec.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/execution/PaimonViewExec.scala
index 6e807d63ea..e7ddaf463d 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/execution/PaimonViewExec.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/execution/PaimonViewExec.scala
@@ -26,7 +26,7 @@ import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions.{Attribute, 
GenericInternalRow}
 import org.apache.spark.sql.catalyst.util.{escapeSingleQuotedString, 
quoteIfNeeded, StringUtils}
 import org.apache.spark.sql.connector.catalog.Identifier
-import org.apache.spark.sql.types.StructType
+import org.apache.spark.sql.types.{StructField, StructType}
 import org.apache.spark.unsafe.types.UTF8String
 
 import scala.collection.JavaConverters._
@@ -49,11 +49,19 @@ case class CreatePaimonViewExec(
   override def output: Seq[Attribute] = Nil
 
   override protected def run(): Seq[InternalRow] = {
-    if (columnAliases.nonEmpty || columnComments.nonEmpty || 
queryColumnNames.nonEmpty) {
+    if (queryColumnNames.nonEmpty) {
+      throw new UnsupportedOperationException("queryColumnNames is not 
supported now")
+    }
+
+    if (columnAliases.nonEmpty && columnAliases.length != 
viewSchema.fields.length) {
       throw new UnsupportedOperationException(
-        "columnAliases, columnComments and queryColumnNames are not supported 
now")
+        s"The number of column aliases (${columnAliases.length}) " +
+          s"must match the number of columns (${viewSchema.fields.length})")
     }
 
+    // Apply column aliases and comments to the view schema
+    val finalSchema = applyColumnAliasesAndComments(viewSchema, columnAliases, 
columnComments)
+
     // Note: for replace just drop then create ,this operation is non-atomic.
     if (replace) {
       catalog.dropView(ident, true)
@@ -61,7 +69,7 @@ case class CreatePaimonViewExec(
 
     catalog.createView(
       ident,
-      viewSchema,
+      finalSchema,
       queryText,
       comment.orNull,
       properties.asJava,
@@ -70,6 +78,35 @@ case class CreatePaimonViewExec(
     Nil
   }
 
+  /**
+   * Apply column aliases and comments to the view schema. If columnAliases is 
empty, the original
+   * column names are used. If columnComments is empty or a specific comment 
is None, no comment is
+   * added. The length of aliases (if non-empty) is validated before calling 
this method.
+   */
+  private def applyColumnAliasesAndComments(
+      schema: StructType,
+      aliases: Seq[String],
+      comments: Seq[Option[String]]): StructType = {
+    if (aliases.isEmpty && comments.isEmpty) {
+      return schema
+    }
+
+    val fields = schema.fields.zipWithIndex.map {
+      case (field, index) =>
+        val newName = if (aliases.nonEmpty) aliases(index) else field.name
+        val newComment = if (index < comments.length) comments(index) else None
+
+        val newField = StructField(newName, field.dataType, field.nullable, 
field.metadata)
+
+        newComment match {
+          case Some(c) => newField.withComment(c)
+          case None => newField
+        }
+    }
+
+    StructType(fields)
+  }
+
   override def simpleString(maxFields: Int): String = {
     s"CreatePaimonViewExec: $ident"
   }
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 a0c29bfcbc..f4e48f318f 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
@@ -290,4 +290,108 @@ abstract class PaimonViewTestBase extends 
PaimonHiveTestBase {
         }
     }
   }
+
+  test("Paimon View: create view with column comments") {
+    Seq(sparkCatalogName, paimonHiveCatalogName).foreach {
+      catalogName =>
+        sql(s"USE $catalogName")
+        withDatabase("test_db") {
+          sql("CREATE DATABASE test_db")
+          sql("USE test_db")
+          withTable("t") {
+            withView("v1") {
+              sql("CREATE TABLE t (id INT, name STRING) USING paimon")
+              sql("INSERT INTO t VALUES (1, 'alice'), (2, 'bob')")
+
+              // Create view with column comments
+              sql("""
+                    |CREATE VIEW v1 (
+                    |  id COMMENT 'the user id',
+                    |  name COMMENT 'the user name'
+                    |) AS SELECT * FROM t
+                    |""".stripMargin)
+
+              // Verify view works
+              checkAnswer(sql("SELECT * FROM v1"), Seq(Row(1, "alice"), Row(2, 
"bob")))
+
+              // Verify column comments via DESCRIBE
+              val descRows = sql("DESC TABLE v1").collectAsList()
+              assert(descRows.get(0).get(0).equals("id"))
+              assert(descRows.get(0).get(2).equals("the user id"))
+              assert(descRows.get(1).get(0).equals("name"))
+              assert(descRows.get(1).get(2).equals("the user name"))
+
+              // Verify column comments via SHOW CREATE TABLE
+              val showCreateRows = sql("SHOW CREATE TABLE v1").collectAsList()
+              val showCreateStr = showCreateRows.get(0).get(0).toString
+              assert(showCreateStr.contains("COMMENT 'the user id'"))
+              assert(showCreateStr.contains("COMMENT 'the user name'"))
+            }
+          }
+        }
+    }
+  }
+
+  test("Paimon View: create view with column aliases") {
+    Seq(sparkCatalogName, paimonHiveCatalogName).foreach {
+      catalogName =>
+        sql(s"USE $catalogName")
+        withDatabase("test_db") {
+          sql("CREATE DATABASE test_db")
+          sql("USE test_db")
+          withTable("t") {
+            withView("v1") {
+              sql("CREATE TABLE t (id INT, name STRING) USING paimon")
+              sql("INSERT INTO t VALUES (1, 'alice'), (2, 'bob')")
+
+              // Create view with column aliases (without comments)
+              sql("CREATE VIEW v1 (user_id, user_name) AS SELECT * FROM t")
+
+              // Verify view works
+              checkAnswer(sql("SELECT * FROM v1"), Seq(Row(1, "alice"), Row(2, 
"bob")))
+
+              // Verify column names via DESCRIBE
+              val descRows = sql("DESC TABLE v1").collectAsList()
+              assert(descRows.get(0).get(0).equals("user_id"))
+              assert(descRows.get(1).get(0).equals("user_name"))
+            }
+          }
+        }
+    }
+  }
+
+  test("Paimon View: create view with column aliases and comments") {
+    Seq(sparkCatalogName, paimonHiveCatalogName).foreach {
+      catalogName =>
+        sql(s"USE $catalogName")
+        withDatabase("test_db") {
+          sql("CREATE DATABASE test_db")
+          sql("USE test_db")
+          withTable("t") {
+            withView("v1") {
+              sql("CREATE TABLE t (id INT, name STRING) USING paimon")
+              sql("INSERT INTO t VALUES (1, 'alice'), (2, 'bob')")
+
+              // Create view with column aliases and comments
+              sql("""
+                    |CREATE VIEW v1 (
+                    |  user_id COMMENT 'the user id',
+                    |  user_name COMMENT 'the user name'
+                    |) AS SELECT * FROM t
+                    |""".stripMargin)
+
+              // Verify view works
+              checkAnswer(sql("SELECT * FROM v1"), Seq(Row(1, "alice"), Row(2, 
"bob")))
+
+              // Verify column names and comments via DESCRIBE
+              val descRows = sql("DESC TABLE v1").collectAsList()
+              assert(descRows.get(0).get(0).equals("user_id"))
+              assert(descRows.get(0).get(2).equals("the user id"))
+              assert(descRows.get(1).get(0).equals("user_name"))
+              assert(descRows.get(1).get(2).equals("the user name"))
+            }
+          }
+        }
+    }
+  }
 }

Reply via email to