spark-test commented on code in PR #60060:
URL: https://github.com/apache/spark/pull/60060#discussion_r4471925692
##########
sql/core/src/test/scala/org/apache/spark/sql/collation/CollationTypePrecedenceSuite.scala:
##########
@@ -490,6 +490,63 @@ class CollationTypePrecedenceSuite extends
SharedSparkSession {
}
}
+ testFixedPointAndSinglePass("variant_get with a collated target type has
implicit strength") {
+ val tableName = "variant_get_tbl"
+ withTable(tableName) {
+ sql(s"CREATE TABLE $tableName (id INT, c STRING COLLATE UNICODE_CI, p
STRING, v VARIANT) " +
+ s"USING $dataSource")
+ sql(
+ s"""
+ |INSERT INTO $tableName SELECT id, 'OPEN', 'OPEN', parse_json(j)
FROM VALUES
+ | (1, '{"s": "Open", "a": ["Open"]}'),
+ | (2, '{"s": "open", "a": ["open"]}'),
+ | (3, '{"s": "closed", "a": ["closed"]}') AS t(id, j)
+ |""".stripMargin)
+
+ Seq("variant_get", "try_variant_get").foreach { fn =>
+ val lcase = s"$fn(v, '$$.s', 'STRING COLLATE UTF8_LCASE')"
+ val lcaseMap = s"$fn(v, '$$', 'MAP<STRING, STRING COLLATE
UTF8_LCASE>')"
+ val mixedStruct = s"$fn(v, '$$', 'STRUCT<s: STRING, t: STRING COLLATE
UTF8_LCASE>')"
+ checkAnswer(
+ sql(s"SELECT DISTINCT COLLATION($lcase || 'a') FROM $tableName"),
+ Row(UTF8_LCASE_COLLATION_NAME))
+
+ Seq(
+ s"$lcase = 'OPEN'",
+ s"$lcase IN ('OPEN', 'x')",
+ s"startswith($lcase, 'OP')",
+ s"array_contains($fn(v, '$$.a', 'ARRAY<STRING COLLATE UTF8_LCASE>'),
'OPEN')",
+ s"$fn(v, '$$', 'STRUCT<s: STRING COLLATE UTF8_LCASE>').s = 'OPEN'",
+ s"$lcaseMap['s'] = 'OPEN'",
+ s"element_at($lcaseMap, 's') = 'OPEN'").foreach { cond =>
+ checkAnswer(sql(s"SELECT id FROM $tableName WHERE $cond"),
Seq(Row(1), Row(2)))
+ }
+
+ // The UTF8_BINARY field of a partly collated struct stays UTF8_BINARY.
+ checkAnswer(sql(s"SELECT id FROM $tableName WHERE $mixedStruct.s =
'OPEN'"), Seq.empty)
+
+ checkAnswer(
+ sql(s"SELECT id FROM $tableName WHERE $lcase = 'Open' COLLATE
UTF8_BINARY"),
+ Seq(Row(1)))
+
+ // Like a column of the target type, it conflicts with other implicit
collations.
+ Seq(
+ s"$lcase = c",
+ s"$lcase = p",
+ s"$lcase IN (p, 'x')",
+ s"$fn(v, '$$.s', 'STRING COLLATE UTF8_BINARY') = c",
+ s"$mixedStruct.s = c").foreach { cond =>
+ assertIndeterminateCollation(sql(s"SELECT id FROM $tableName WHERE
$cond"))
+ }
+
+ // A plain STRING target keeps default strength.
Review Comment:
**Nit (P3):** This plain `STRING` case is the only check of where the new
rule stops applying, and every case above uses a UTF8_BINARY literal path
outside any default collation. Three regressions would pass the whole test:
- Replacing the recursive `hasNonDefaultStringCharOrVarcharType` check with
a top-level one, which is true for every array, map and struct, would make
`array_contains(variant_get(v, '$.a', 'ARRAY<STRING>'), c)` meet two implicit
collations instead of matching ids 1 and 2 under `c`'s UNICODE_CI.
- If the path argument's collation took part again, as it did before this
change, no case would notice, because a Default literal path never beats the
target. A collated path such as `variant_get(v, '$.s' COLLATE UNICODE, 'STRING
COLLATE UTF8_LCASE') = 'OPEN'` would catch it.
- No case runs in a view or table with a `DEFAULT COLLATION`, where an
explicit `STRING COLLATE UTF8_BINARY` target must win over the recolored
literals.
One case per boundary, under both analyzers, would pin them.
**Verification:**
- **Behavior:** An all-default ARRAY<STRING> target checked with
array_contains against a UNICODE_CI column returns the case-insensitive matches
under both analyzers.
- **Behavior:** A STRING COLLATE UTF8_LCASE target whose path argument
carries another collation compares in UTF8_LCASE under both analyzers.
- **Behavior:** Inside a view with DEFAULT COLLATION UTF8_LCASE, an explicit
STRING COLLATE UTF8_BINARY target compared with a literal matches
case-sensitively under both analyzers.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CollationTypeCoercion.scala:
##########
@@ -399,6 +401,11 @@ object CollationTypeCoercion extends SQLConfHelper {
Some(addContextToStringType(cast.dataType, castStrength))
+ // An explicitly collated target type (even UTF8_BINARY), not the path,
decides the
+ // collation, like a column of that type.
+ case v: VariantGet if
DataTypeUtils.hasNonDefaultStringCharOrVarcharType(v.dataType) =>
Review Comment:
**Non-blocking (P2):** This arm keeps collated `variant_get` join keys
non-binary, so a join such as `variant_get(a.v, '$.k', 'STRING COLLATE
UTF8_LCASE') = variant_get(b.v, '$.k', 'STRING COLLATE UTF8_LCASE')` now
compares case-insensitively. Before this change both keys were cast to
`STRING`, so the join and any runtime filter on it agreed byte for byte.
`RewriteCollationJoin` wraps only attribute pairs in `CollationKey`, so these
keys reach `InjectRuntimeFilter` as they are. That rule skips non-binary-stable
keys only on its materialized-cache path (`InjectRuntimeFilter.scala:314-315`).
Its selective-predicate path still builds `BloomFilterAggregate(XxHash64(key))`
and probes with `BloomFilterMightContain(..., XxHash64(key))` (lines 78-93).
`XxHash64` hashes the raw bytes of a non-binary string unless
`spark.sql.legacy.collationAwareHashFunctions` is set, and that flag is off by
default.
So when the default-on runtime filter applies (an application-side scan of
at least 10GB, a selectively filtered creation side under 10MB, and a shuffle
on the application side), an application row with `'Open'` whose only match is
`'open'` is filtered out before the join. For inner, left semi, left outer and
right outer joins, whether the fix holds then depends on size statistics. A
left or right outer join null-extends the preserved row instead of matching it.
Other computed collated join keys already have the same gap on the base branch.
**Recommended change:** Make the runtime bloom filter rule skip a join key
whose type is not binary-stable on the selective-predicate path, as it already
does for materialized caches, and add runtime-filter join tests with collated
variant_get keys whose values differ only by case.
**Why this works:** A key that is not binary-stable can compare equal under
its collation while hashing differently. Refusing such keys before a creation
side is chosen means no bloom filter can reject a row that the join would
match, so the join's collation-aware comparison alone decides the result.
**Scope:** Tighten runtime bloom filter key eligibility in the optimizer and
cover collated join keys in runtime-filter tests.
**Compatibility:** Runtime filter injection for binary-stable join keys,
including the existing materialized-cache handling, is unchanged.
**Risks:** Runtime bloom filters are no longer injected for joins on
non-binary collated keys, including computed collated keys that received
unsound filters on the base branch, so those joins can read and shuffle more
rows; this is a plan-only change that can be rolled back, and collation-aware
hashing could restore the pruning later.
**Constraints:** Joins on binary-stable keys, including attribute pairs that
RewriteCollationJoin wraps in CollationKey, keep their current runtime filter
behavior.
**Exclusions:**
- Collation-aware hashing for runtime bloom filters, which would keep
pruning for collated keys, is left for a follow-up.
**Success:** No runtime bloom filter is injected for an equi-join key whose
type is not binary-stable. Joins on collated variant_get keys return every
case-variant match whether or not runtime bloom filters are enabled.
**Verification:**
- **Regression:** For inner, left semi (including one built from an IN
subquery), left outer and right outer joins on two collated variant_get keys
whose values differ only by case, with runtime bloom filters forced on through
a small application-side threshold, a selective creation side and broadcast
disabled, each query returns the case-insensitive matches, an outer join
null-extends no row that has such a match, and the optimized plan contains no
bloom filter.
- **Compatibility:** Joins on binary-stable keys, including collated
attribute pairs that are rewritten to CollationKey, still receive runtime bloom
filters as on the base branch.
--
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]