Skip to content

feat: fall back to Spark for collated predicate operands#4948

Open
comphead wants to merge 8 commits into
apache:mainfrom
comphead:collation
Open

feat: fall back to Spark for collated predicate operands#4948
comphead wants to merge 8 commits into
apache:mainfrom
comphead:collation

Conversation

@comphead

@comphead comphead commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Splitting #4799

Comet's native equality, ordering, and hashing compare raw UTF-8 bytes, so a predicate operand carrying a non-UTF8_BINARY collation (Spark 4+) would produce wrong answers on the native path -- for example, 'a' = 'A' under UNICODE_CI returns true in Spark but false byte-wise.

Add a ComparisonUtils.collationSupportLevel shared helper and wire it into getSupportLevel for the six binary comparison serdes plus In/InSet. CometNot's special-case rewrites for Not(EqualTo|EqualNullSafe|In) now guard on the same helper so they fall through to the generic path (and thus to Spark) when any operand is collated. The helper walks nested types via
hasNonDefaultStringCollation, so collated strings inside array/map/struct operands are caught too.

Which issue does this PR close?

Closes #.

Rationale for this change

What changes are included in this PR?

How are these changes tested?

comphead added 3 commits July 16, 2026 08:23
Comet's native equality, ordering, and hashing compare raw UTF-8 bytes,
so a predicate operand carrying a non-UTF8_BINARY collation (Spark 4+)
would produce wrong answers on the native path -- for example,
`'a' = 'A'` under UNICODE_CI returns true in Spark but false byte-wise.

Add a `ComparisonUtils.collationSupportLevel` shared helper and wire it
into `getSupportLevel` for the six binary comparison serdes plus
`In`/`InSet`. `CometNot`'s special-case rewrites for
`Not(EqualTo|EqualNullSafe|In)` now guard on the same helper so they
fall through to the generic path (and thus to Spark) when any operand
is collated. The helper walks nested types via
`hasNonDefaultStringCollation`, so collated strings inside
array/map/struct operands are caught too.
Mix CodegenDispatchFallback into CometStrToMap so that when the
input string or a delimiter carries a non-UTF8_BINARY collation
the expression is executed by Spark's own doGenCode inside the
Comet kernel rather than falling the whole projection back to
Spark. Update the str_to_map_collation.sql fixture to drop the
expect_fallback(...) assertion accordingly.
Update str_to_map_legacy_truncate.sql to drop the expect_fallback
assertions -- with CodegenDispatchFallback on CometStrToMap the
expression now runs via Spark's own doGenCode inside the Comet
kernel instead of falling the projection back to Spark.
@comphead
comphead requested a review from andygrove July 16, 2026 20:27
@andygrove

Copy link
Copy Markdown
Member

Do we need to do the same for Contains, StartsWith, EndsWith, and Like? Maybe they are already handlded correctly?

Comment on lines +113 to +114
override def getSupportLevel(expr: EqualTo): SupportLevel =
ComparisonUtils.collationSupportLevel(expr.left, expr.right)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add/update tests for predicates to demonstrate the fix?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding tests including Contains, StartsWith, EndsWith, and Like to increase regression coverage

comphead added 5 commits July 16, 2026 15:15
Extends 903a772b6 to the string-predicate serdes flagged in review.
CometLike, CometContains, CometStartsWith, and CometEndsWith now route
their `getSupportLevel` through `ComparisonUtils.collationSupportLevel`
so any non-UTF8_BINARY operand triggers a clean fallback. Previously
Contains / StartsWith / EndsWith reached the native scalar-function
bridge via `CometScalarFunction` with no collation guard, and Like's
`getSupportLevel` only rejected custom escape characters. Native
kernels compare raw UTF-8 bytes, so a case- or accent-insensitive
collation would yield wrong answers (e.g. `'Hello' LIKE '%hello%'`
returns true in Spark under UNICODE_CI but false byte-wise).

`collationSupportLevel` grows an optional `exprName` so each predicate
surfaces a fallback reason that names itself in EXPLAIN.

Expands `sql-tests/.../collation.sql` with predicate coverage: the six
binary comparisons under UTF8_LCASE and UNICODE_CI, `IN`/`NOT IN`
(scalar and `InSet`-threshold), CometNot's `NOT (=)` / `NOT (<=>)`
rewrites, `LIKE`, `Contains`, `StartsWith`, `EndsWith`, collated
strings buried in `array`/`map`/`struct` operands (exercising
`hasNonDefaultStringCollation`'s recursion), and NULL propagation
through each of the above.
Every predicate query in `collation.sql` now uses
`query expect_fallback(non-UTF8_BINARY collated operands)` instead of
plain `query`. The predicate serdes correctly return `Unsupported` for
collated operands, so the enclosing Project falls back to Spark --
which is the whole point of the fix -- but that violates the default
`CheckCoverageAndAnswer` assertion mode's "only Comet native operators"
check. `expect_fallback` still verifies the answer against Spark and
additionally pins the shared fallback-reason substring, so a regressed
guard shows up as either a wrong answer or a missing fallback.

Also drops the map test: `CometCreateMap` routes through the JVM
codegen dispatcher, so its interaction with the outer EqualTo's
collation guard is not the clean path this file is meant to pin down.
Two classes of tests never reached the predicate serdes and so could
not exercise the fix:

- NULL-literal RHS: Spark's `NullPropagation` rewrites `x <=> NULL`
  to `IsNull(x)`, folds `x = NULL` and `NULL IN (...)` to `Literal(null)`,
  etc., so the predicate serde is never asked about a collated NULL
  operand. The row `(7, NULL)` in `test_collated_predicates` still
  exercises NULL propagation through every remaining predicate query.

- UNICODE_CI + LIKE / Contains / StartsWith / EndsWith: Spark 4.0
  restricts these to `StringTypeNonCSAICollation`, which rejects
  UNICODE_CI at analysis time with a DATATYPE_MISMATCH. UTF8_LCASE
  is accepted and already covers the same fallback path.
Drop the "what" narration and shrink each remaining note to a single
sentence about *why* the case exists (NullPropagation folding,
NonCSAICollation restriction, InSet threshold, etc.). No SQL changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants