szehon-ho commented on code in PR #59036:
URL: https://github.com/apache/spark/pull/59036#discussion_r4331100523


##########
sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2DataFrameSessionCatalogSuite.scala:
##########
@@ -53,6 +54,25 @@ class DataSourceV2DataFrameSessionCatalogSuite
 
   override protected val catalogAndNamespace: String = ""
 
+  test("display properties survive ALTER TABLE in the session catalog") {
+    val t = "display_properties"
+    withTable(t) {
+      sql(s"CREATE TABLE $t (id bigint) USING $v2Format")
+      val table = sessionCatalog.loadTable(Identifier.of(Array("default"), t))
+        .asInstanceOf[InMemoryTable]
+      table.setDisplayProperties(util.Map.of("catalog-label", "catalog-value"))
+
+      sql(s"ALTER TABLE $t SET TBLPROPERTIES ('k' = 'v')")
+      checkAnswer(sql(s"SHOW TBLPROPERTIES $t"),
+        Seq(Row("catalog-label", "catalog-value"), Row("k", "v")))
+      checkAnswer(sql(s"DESCRIBE TABLE EXTENDED $t").where("col_name = 'Table 
Properties'"),
+        Row("Table Properties", "[catalog-label=catalog-value,k=v]", ""))

Review Comment:
   Added the `SHOW TABLE EXTENDED` assertion to the session-catalog test. It 
checks that the table is listed and its metadata omits `catalog-label`, pinning 
the documented limitation.



##########
sql/catalyst/src/test/scala/org/apache/spark/sql/connector/catalog/CatalogV2UtilSuite.scala:
##########
@@ -35,6 +37,47 @@ import org.apache.spark.sql.util.CaseInsensitiveStringMap
 
 class CatalogV2UtilSuite extends SparkFunSuite {
 
+  test("tablePropertiesForDisplay preserves properties when a mock has no 
display properties") {
+    val table = mock(classOf[Table])
+    when(table.properties()).thenReturn(util.Map.of(
+      "persisted", "stored", TableCatalog.PROP_EXTERNAL, "true"))
+
+    assert(CatalogV2Util.tablePropertiesForDisplay(
+      table.properties(), table.displayProperties(), new SQLConf) === 
Map("persisted" -> "stored"))
+  }
+
+  test("tablePropertiesForDisplay ignores reserved display keys and preserves 
stored values") {
+    val displayProperties = new util.HashMap[String, String]()
+    CatalogV2Util.TABLE_RESERVED_PROPERTIES.foreach(displayProperties.put(_, 
"display-value"))
+    displayProperties.put("persisted", "display-value")
+    displayProperties.put("catalog-label", "catalog-value")
+    val table = new DelegatingTable(new TableInfo.Builder()
+      .withProperties(util.Map.of("persisted", "stored"))
+      .withDisplayProperties(displayProperties)
+      .build(), "table")
+
+    assert(CatalogV2Util.tablePropertiesForDisplay(
+      table.properties(), table.displayProperties(), new SQLConf) ===
+      Map("persisted" -> "stored", "catalog-label" -> "catalog-value"))
+    assert(table.properties() === util.Map.of("persisted", "stored"))
+  }
+
+  test("tablePropertiesForDisplay redacts stored and display-only values") {
+    val conf = new SQLConf
+    conf.setConf(SQLConf.SQL_OPTIONS_REDACTION_PATTERN, "(?i)catalog-label".r)
+    val properties = util.Map.of("password", "stored-password", "persisted", 
"stored")
+    val displayProperties = util.Map.of(
+      "catalog-label", "catalog-value", "secret", "display-secret", 
"persisted", "display-value")
+
+    assert(CatalogV2Util.tablePropertiesForDisplay(properties, 
displayProperties, conf) === Map(
+      "password" -> "*********(redacted)",
+      "catalog-label" -> "*********(redacted)",
+      "secret" -> "*********(redacted)",
+      "persisted" -> "stored"))
+    assert(properties.get("password") === "stored-password")

Review Comment:
   All three helper tests now pass plain maps directly. Removed the mock and 
DelegatingTable setup and the redundant stored-map assertion. The redaction 
test now uses mutable HashMap inputs and compares both complete maps afterward, 
so in-place redaction would fail the input-preservation checks.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/ShowTblPropertiesSuite.scala:
##########
@@ -17,10 +17,48 @@
 
 package org.apache.spark.sql.execution.command.v2
 
+import org.apache.spark.sql.Row
 import org.apache.spark.sql.execution.command
 
 /**
  * The class contains tests for the `SHOW TBLPROPERTIES` command to check V2 
table catalogs.
  */
 class ShowTblPropertiesSuite extends command.ShowTblPropertiesSuiteBase with 
CommandSuiteBase {
+  test("display properties supplement current properties and exclude reserved 
keys") {
+    withNamespaceAndTable("ns", "table") { tbl =>
+      sql(s"CREATE TABLE $tbl (id bigint) $defaultUsing " +
+        "TBLPROPERTIES ('persisted' = 'stored')")
+      setDisplayProperties(loadTable(catalog, "ns", "table"))
+
+      checkAnswer(sql(s"SHOW TBLPROPERTIES $tbl"),
+        Seq(Row("catalog-label", "catalog-value"), Row("password", 
"*********(redacted)"),
+          Row("persisted", "stored")))
+      checkAnswer(sql(s"SHOW TBLPROPERTIES $tbl ('persisted')"), 
Row("persisted", "stored"))

Review Comment:
   Added an exact `checkAnswer` for `SHOW TBLPROPERTIES ... ('location')`, 
expecting the missing-property message even though the display map contains a 
location entry.



##########
sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2MetadataTableSuite.scala:
##########
@@ -152,8 +191,10 @@ class TestingDataSourceTableCatalog extends TableCatalog {
     throw new RuntimeException("shouldn't be called")
   override def renameTable(oldIdent: Identifier, newIdent: Identifier): Unit =
     throw new RuntimeException("shouldn't be called")
-  override def listTables(namespace: Array[String]): Array[Identifier] =
-    throw new RuntimeException("shouldn't be called")
+  override def listTables(namespace: Array[String]): Array[Identifier] = {
+    assert(namespace.toSeq == Seq("display_properties"))

Review Comment:
   Changed `listTables` to match the supported namespace and retain the 
RuntimeException guard otherwise. Updated the class Scaladoc to describe the V2 
metadata fixture and its listing scope.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/CommandSuiteBase.scala:
##########
@@ -77,4 +79,31 @@ trait CommandSuiteBase extends SharedSparkSession {
     catalogPlugin.asTableCatalog.loadTable(Identifier.of(Array(schema), table))
       .asInstanceOf[InMemoryTable]
   }
+
+  def createTableWithDisplayProperties(
+      namespace: String,
+      table: String,
+      external: Boolean = false,
+      withLocation: Boolean = false): Unit = {
+    val location = if (withLocation) "LOCATION 'file:/tmp/display-properties' 
" else ""
+    sql(s"CREATE TABLE $catalog.$namespace.$table (id bigint) $defaultUsing " +
+      s"COMMENT 'table comment' $location" +
+      "TBLPROPERTIES ('persisted' = 'stored')")
+    setDisplayProperties(loadTable(catalog, namespace, table), external)

Review Comment:
   The shared fixture now obtains the underlying table through 
`BasicInMemoryTableCatalog.liveTable` before setting display properties, so the 
setup also works for catalogs that copy on load.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/CommandSuiteBase.scala:
##########
@@ -77,4 +79,31 @@ trait CommandSuiteBase extends SharedSparkSession {
     catalogPlugin.asTableCatalog.loadTable(Identifier.of(Array(schema), table))
       .asInstanceOf[InMemoryTable]
   }
+
+  def createTableWithDisplayProperties(
+      namespace: String,
+      table: String,
+      external: Boolean = false,
+      withLocation: Boolean = false): Unit = {
+    val location = if (withLocation) "LOCATION 'file:/tmp/display-properties' 
" else ""
+    sql(s"CREATE TABLE $catalog.$namespace.$table (id bigint) $defaultUsing " +
+      s"COMMENT 'table comment' $location" +
+      "TBLPROPERTIES ('persisted' = 'stored')")
+    setDisplayProperties(loadTable(catalog, namespace, table), external)
+  }
+
+  /**
+   * Sets display-only test metadata. When external is true, also marks the 
stored table external.
+   */
+  def setDisplayProperties(table: InMemoryTable, external: Boolean = false): 
Unit = {
+    if (external) {
+      table.properties.put(TableCatalog.PROP_EXTERNAL, "true")

Review Comment:
   The fixture now uses `CREATE EXTERNAL TABLE` when requested. Removed the 
direct write to stored `external`; `setDisplayProperties` now sets only display 
entries, and its Scaladoc reflects that.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/ShowTblPropertiesSuite.scala:
##########
@@ -17,10 +17,48 @@
 
 package org.apache.spark.sql.execution.command.v2
 
+import org.apache.spark.sql.Row
 import org.apache.spark.sql.execution.command
 
 /**
  * The class contains tests for the `SHOW TBLPROPERTIES` command to check V2 
table catalogs.
  */
 class ShowTblPropertiesSuite extends command.ShowTblPropertiesSuiteBase with 
CommandSuiteBase {
+  test("display properties supplement current properties and exclude reserved 
keys") {
+    withNamespaceAndTable("ns", "table") { tbl =>
+      sql(s"CREATE TABLE $tbl (id bigint) $defaultUsing " +

Review Comment:
   Reused `createTableWithDisplayProperties("ns", "table")` in the SHOW 
TBLPROPERTIES test, consolidating its setup with the other display-property 
tests.



##########
sql/catalyst/src/test/scala/org/apache/spark/sql/connector/catalog/InMemoryBaseTable.scala:
##########
@@ -85,14 +87,22 @@ abstract class InMemoryBaseTable(
     tableColumns = newColumns
   }
 
+  override def displayProperties(): util.Map[String, String] = {
+    Option(tableDisplayProperties).getOrElse(super.displayProperties())

Review Comment:
   Wrapped the configured display map in `Collections.unmodifiableMap`, keeping 
the accessor read-only after setting or copying display properties as well as 
in its default state.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to