spark-test commented on code in PR #59235:
URL: https://github.com/apache/spark/pull/59235#discussion_r4394927386


##########
sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowViewsSuiteBase.scala:
##########
@@ -64,4 +79,72 @@ trait ShowViewsSuiteBase extends QueryTest with 
DDLCommandTestUtils {
         s"isTemporary must be false for persistent view rows: $r"))
     }
   }
+
+  test("AS JSON returns a single json_metadata column") {
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_schema AS SELECT 1 AS x")
+    val df = sql(s"SHOW VIEWS IN $catalog.$namespace AS JSON")
+    assert(df.schema.length == 1)
+    assert(df.schema.head.name == "json_metadata")
+  }
+
+  test("AS JSON lists user-created views") {
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_a AS SELECT 1 AS x")
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_b AS SELECT 2 AS x")
+    val views = showViewsAsJson(s"SHOW VIEWS IN $catalog.$namespace AS JSON")
+    val names = viewNames(views)
+    assert(names.contains("v_json_a"), s"v_json_a missing: $names")
+    assert(names.contains("v_json_b"), s"v_json_b missing: $names")
+    assert(names.distinct.length == names.length, s"duplicate entries: $names")
+    // Scope to the two views this test created: this suite also runs under 
v1, where a
+    // leftover temp view (empty namespace, isTemporary=true) would otherwise 
fail the asserts.
+    // Uses a literal set, not `names` (which is derived from `views` itself 
and would match
+    // everything, silently disabling this scoping).
+    val createdNames = Set("v_json_a", "v_json_b")
+    val created = views.filter(v => createdNames.contains((v \ 
"viewName").extract[String]))
+    created.foreach { v =>
+      assert((v \ "namespace").extract[Seq[String]] == Seq(namespace))
+      assert(!(v \ "isTemporary").extract[Boolean])
+    }
+  }
+
+  test("AS JSON applies the LIKE pattern") {
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_match AS SELECT 1 AS x")
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_skip AS SELECT 1 AS x")
+    val names = viewNames(
+      showViewsAsJson(s"SHOW VIEWS IN $catalog.$namespace LIKE 'v_json_match' 
AS JSON"))
+    assert(names == Seq("v_json_match"), s"pattern did not filter: $names")
+  }
+
+  test("AS JSON excludes non-view table entries") {
+    val table = s"$catalog.$namespace.t_json_not_a_view"
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_only AS SELECT 1 AS x")
+    withTable(table) {
+      sql(s"CREATE TABLE $table (x INT) USING parquet")
+      val names = viewNames(showViewsAsJson(s"SHOW VIEWS IN 
$catalog.$namespace AS JSON"))
+      assert(names.contains("v_json_only"), s"v_json_only missing: $names")
+      assert(!names.contains("t_json_not_a_view"), s"non-view leaked: $names")
+    }
+  }
+
+  test("AS JSON emits an empty array when no view matches") {
+    val rows = sql(s"SHOW VIEWS IN $catalog.$namespace LIKE 
'v_json_no_such_view' AS JSON")
+      .collect()
+    assert(rows.length == 1)
+    // Asserted literally so that a rename of the `views` key cannot pass 
unnoticed.
+    assert(rows.head.getString(0) == """{"views":[]}""")
+  }
+
+  test("AS JSON agrees with the tabular output") {
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_parity AS SELECT 1 AS x")
+    val expected = sql(s"SHOW VIEWS IN $catalog.$namespace").collect()
+      .map(r => (r.getString(0), r.getString(1), r.getBoolean(2))).toSet
+    // mkString(".") matches QuotingUtils.quoted only because these namespaces 
are single-part;

Review Comment:
   **Nit (P3):** `QuotingUtils.quoted` is 
`namespace.map(quoteIfNeeded).mkString(".")`, and `quoteIfNeeded` adds 
backticks only to parts that do not match `^[a-zA-Z_][a-zA-Z0-9_]*`. The 
dot-join therefore equals the tabular `namespace` column whenever no part needs 
quoting, whatever the number of parts: `[ns1, ns2]` renders as `ns1.ns2` both 
ways, while a single-part `my-db` renders as `` `my-db` `` in the tabular 
output. As written, the comment would steer someone away from reusing this 
check for nested v2 namespaces, where it works, and implies that any 
single-part name is safe, which it is not. Rewording the first line keeps the 
second line's caveat accurate:
   
   ```suggestion
       // mkString(".") matches QuotingUtils.quoted only because no namespace 
part needs quoting;
   ```
   
   **Verification:**
   - **Inspection:** The comment's stated condition agrees with 
QuotingUtils.quoted: namespaces whose parts all need no quoting compare equal 
regardless of how many parts they have, and any part that needs backtick 
quoting breaks the comparison.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowViewsSuiteBase.scala:
##########
@@ -64,4 +79,72 @@ trait ShowViewsSuiteBase extends QueryTest with 
DDLCommandTestUtils {
         s"isTemporary must be false for persistent view rows: $r"))
     }
   }
+
+  test("AS JSON returns a single json_metadata column") {
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_schema AS SELECT 1 AS x")
+    val df = sql(s"SHOW VIEWS IN $catalog.$namespace AS JSON")
+    assert(df.schema.length == 1)
+    assert(df.schema.head.name == "json_metadata")
+  }
+
+  test("AS JSON lists user-created views") {
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_a AS SELECT 1 AS x")
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_b AS SELECT 2 AS x")
+    val views = showViewsAsJson(s"SHOW VIEWS IN $catalog.$namespace AS JSON")
+    val names = viewNames(views)
+    assert(names.contains("v_json_a"), s"v_json_a missing: $names")
+    assert(names.contains("v_json_b"), s"v_json_b missing: $names")
+    assert(names.distinct.length == names.length, s"duplicate entries: $names")
+    // Scope to the two views this test created: this suite also runs under 
v1, where a
+    // leftover temp view (empty namespace, isTemporary=true) would otherwise 
fail the asserts.
+    // Uses a literal set, not `names` (which is derived from `views` itself 
and would match
+    // everything, silently disabling this scoping).
+    val createdNames = Set("v_json_a", "v_json_b")
+    val created = views.filter(v => createdNames.contains((v \ 
"viewName").extract[String]))
+    created.foreach { v =>
+      assert((v \ "namespace").extract[Seq[String]] == Seq(namespace))
+      assert(!(v \ "isTemporary").extract[Boolean])
+    }
+  }
+
+  test("AS JSON applies the LIKE pattern") {
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_match AS SELECT 1 AS x")
+    sql(s"CREATE VIEW $catalog.$namespace.v_json_skip AS SELECT 1 AS x")
+    val names = viewNames(
+      showViewsAsJson(s"SHOW VIEWS IN $catalog.$namespace LIKE 'v_json_match' 
AS JSON"))

Review Comment:
   **Nit (P3):** Both AS JSON pattern tests use exact view names 
(`'v_json_match'` here and `'v_json_no_such_view'` below). On a `ViewCatalog` 
catalog, `ShowViewsJsonCommand` filters with its own 
`StringUtils.filterPattern` call (`ShowViewsJsonCommand.scala:87`). An exact 
name selects the same views under the documented pattern semantics (`*` 
wildcard, `|` alternation, case-insensitive match) as under plain name 
equality. So if that call were reduced to `ident.name == p`, both tests would 
still pass in the v2 suite, while `LIKE 'v_json_m*'` would silently return 
`{"views":[]}`. The session-catalog branch hands the pattern to 
`SessionCatalog.listViews`, whose wildcard handling `show-views.sql` already 
covers, so the gap is in the v2 copy. A pattern that mixes a case variant, a 
wildcard, and an alternation keeps the expected `Seq("v_json_match")` in both 
suites and fails under that regression:
   
   ```suggestion
         showViewsAsJson(s"SHOW VIEWS IN $catalog.$namespace LIKE 
'V_JSON_M*|no_such_view' AS JSON"))
   ```
   
   **Verification:**
   - **Regression:** On both a session-catalog namespace and a ViewCatalog 
namespace, SHOW VIEWS ... LIKE <pattern> AS JSON with a pattern that combines a 
wildcard, a case variant and an alternation lists the matching view and 
excludes the non-matching one, and the assertion fails when the ViewCatalog 
filter is replaced by plain name equality.



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