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


##########
sql/catalyst/src/main/scala/org/apache/spark/sql/connector/catalog/CatalogV2Util.scala:
##########
@@ -677,6 +677,12 @@ private[sql] object CatalogV2Util {
     builder
   }
 
+  /** Returns non-reserved table properties with additional display-only 
metadata. */
+  def tablePropertiesForDisplay(table: Table): Map[String, String] = {
+    (table.displayProperties().asScala.toMap ++ table.properties().asScala)

Review Comment:
   Added an explicit non-null map/keys/values contract to 
Table.displayProperties() and the TableInfo builder parameter. This uses the 
documented-contract approach rather than silently dropping invalid entries.



##########
sql/catalyst/src/main/java/org/apache/spark/sql/connector/catalog/Table.java:
##########
@@ -95,6 +95,31 @@ default Map<String, String> properties() {
     return Collections.emptyMap();
   }
 
+  /**
+   * Returns additional, display-only table properties.
+   *
+   * <p>Spark adds these entries to the non-reserved properties displayed by 
V2 table metadata
+   * commands and {@code Catalog.getTableProperties}. Entries in {@link 
#properties()} take
+   * precedence, and reserved keys such as {@link TableCatalog#PROP_EXTERNAL} 
and
+   * {@link TableCatalog#PROP_LOCATION} are ignored in this map. Table type 
and other reserved
+   * metadata are read from {@code properties()}.
+   *
+   * <p>{@code ALTER TABLE ... SET/UNSET TBLPROPERTIES} only changes {@code 
properties()}.

Review Comment:
   Reworded this as guidance for catalog implementations: Spark forwards 
SET/UNSET to alterTable without consulting the display map. The precedence 
paragraph now also states that, after a stored property is removed, Spark 
displays the entry for that key if it remains in the display map.



##########
sql/catalyst/src/main/java/org/apache/spark/sql/connector/catalog/TableInfo.java:
##########
@@ -59,19 +62,50 @@ public Map<String, String> properties() {
     return properties;
   }
 
+  /**
+   * Returns the additional display-only properties described by {@link 
Table#displayProperties()}.
+   *
+   * <p>These properties describe metadata returned by a catalog. Spark leaves 
them empty when

Review Comment:
   Removed the duplicated creation-method list from the builder documentation; 
it now lives only on the getter. I kept the scope to the current Spark 
behavior: these are returned metadata, and Spark leaves them empty on 
creation/staging calls. Since TableInfo is internal for now, I would prefer not 
to add a contract for non-Spark callers supplying this field on those paths.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DescribeTableExec.scala:
##########
@@ -198,13 +198,12 @@ case class DescribeTableExec(
     CatalogV2Util.TABLE_RESERVED_PROPERTIES
       .filterNot(_ == TableCatalog.PROP_EXTERNAL)
       .foreach(propKey => {
-        if (table.properties.containsKey(propKey)) {
-          rows += toCatalystRow(propKey.capitalize, 
table.properties.get(propKey), "")
+        if (tableProperties.containsKey(propKey)) {
+          rows += toCatalystRow(propKey.capitalize, 
tableProperties.get(propKey), "")
         }
       })
     val properties =
-      conf.redactOptions(table.properties.asScala.toMap).toList
-        .filter(kv => !CatalogV2Util.TABLE_RESERVED_PROPERTIES.contains(kv._1))
+      conf.redactOptions(CatalogV2Util.tablePropertiesForDisplay(table)).toList

Review Comment:
   Changed the helper to accept the stored and display property maps. DESCRIBE 
TABLE EXTENDED and SHOW TABLE EXTENDED now pass the stored map already fetched 
for the reserved metadata rows, so each reads properties() only once.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/connector/catalog/CatalogV2Util.scala:
##########
@@ -677,6 +677,12 @@ private[sql] object CatalogV2Util {
     builder
   }
 
+  /** Returns non-reserved table properties with additional display-only 
metadata. */
+  def tablePropertiesForDisplay(table: Table): Map[String, String] = {

Review Comment:
   Moved redaction into tablePropertiesForDisplay with an explicit SQLConf 
parameter and removed the three caller-side wrappers. Added a helper-level test 
for stored and display-only secrets, the SQL redaction pattern, stored-value 
precedence, and unchanged input maps.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/ShowCreateTableSuite.scala:
##########
@@ -33,6 +35,28 @@ class ShowCreateTableSuite extends 
command.ShowCreateTableSuiteBase with Command
 
   override def fullName: String = s"$catalog.$ns.$table"
 
+  test("show create table excludes display-only properties") {
+    withNamespaceAndTable(ns, table) { t =>
+      sql(s"CREATE TABLE $t (id bigint) $defaultUsing " +
+        "COMMENT 'table comment' LOCATION 'file:/tmp/display-properties' " +
+        "TBLPROPERTIES ('persisted' = 'stored')")
+      loadTable(catalog, ns, table).setDisplayProperties(Map(

Review Comment:
   SHOW CREATE TABLE now uses the shared fixture, including all of its reserved 
display keys and password entry, and the unused imports are removed. The setter 
documentation also states that external = true updates the stored properties.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/DescribeTableSuite.scala:
##########
@@ -220,6 +220,35 @@ class DescribeTableSuite extends 
command.DescribeTableSuiteBase
     }
   }
 
+  gridTest("display properties preserve table type and reserved metadata")(

Review Comment:
   Moved table creation into createTableWithDisplayProperties in 
CommandSuiteBase and reused it across DESCRIBE, SHOW TABLE EXTENDED, and SHOW 
CREATE TABLE. Reduced both grids to (false, false) and (true, true).



##########
sql/catalyst/src/test/scala/org/apache/spark/sql/connector/catalog/CatalogSuite.scala:
##########
@@ -77,6 +77,56 @@ class CatalogSuite extends SparkFunSuite {
     assert(loaded.getClass == catalog.getClass)
   }
 
+  test("TableInfo and DelegatingTable carry display-only properties 
separately") {
+    val displayProperties = new util.HashMap[String, String]()
+    displayProperties.put("catalog-label", "catalog-value")
+    val info = new TableInfo.Builder()
+      .withProperties(util.Map.of("persisted", "stored"))
+      .withDisplayProperties(displayProperties)
+      .build()
+    displayProperties.put("catalog-label", "changed")
+    val table = new DelegatingTable(info, "table")
+
+    assert(table.displayProperties() === util.Map.of("catalog-label", 
"catalog-value"))
+    assert(table.properties() === util.Map.of("persisted", "stored"))
+    intercept[UnsupportedOperationException] {
+      table.displayProperties().put("catalog-label", "changed")
+    }
+    val defaultTable = new DelegatingTable(new TableInfo.Builder().build(), 
"default")
+    assert(defaultTable.displayProperties().isEmpty)
+  }
+
+  gridTest("display properties survive copies, pinning and alterations")(

Review Comment:
   Renamed the grid cases after the two catalog classes and added a comment 
explaining that the row-level catalog always copies on load, while the option 
enables this for InMemoryTableCatalog.



##########
sql/catalyst/src/test/scala/org/apache/spark/sql/connector/catalog/InMemoryTableCatalog.scala:
##########
@@ -168,7 +168,8 @@ class BasicInMemoryTableCatalog extends TableCatalog {
   }
 
   override def createTable(ident: Identifier, tableInfo: TableInfo): Table = {
-    createTable(ident, tableInfo.columns(), tableInfo.partitions(), 
tableInfo.properties(),
+    createTable(

Review Comment:
   Restored the original argument wrapping in this method.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DescribeTableExec.scala:
##########
@@ -189,7 +188,8 @@ case class DescribeTableExec(
     rows += toCatalystRow("# Detailed Table Information", "", "")
     addIdentifierRows(rows, catalogName, identifier, entityLabel = "Table")
 
-    val tableType = if 
(table.properties().containsKey(TableCatalog.PROP_EXTERNAL)) {
+    val tableProperties = table.properties()
+    val tableType = if 
(tableProperties.containsKey(TableCatalog.PROP_EXTERNAL)) {

Review Comment:
   Agreed this is pre-existing and should be fixed separately by honoring 
table_type before the external fallback. Leaving that behavior unchanged in 
this PR. I have prepared the follow-up JIRA draft, but have not filed it yet.



-- 
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