Skip to content

[UUID 4a] CAST to and from the logical UUID type - #19181

Merged
xiangfu0 merged 2 commits into
apache:masterfrom
xiangfu0:uuid-split/04a-cast
Aug 7, 2026
Merged

[UUID 4a] CAST to and from the logical UUID type#19181
xiangfu0 merged 2 commits into
apache:masterfrom
xiangfu0:uuid-split/04a-cast

Conversation

@xiangfu0

@xiangfu0 xiangfu0 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

What

CAST support for the logical UUID type, in both directions, single- and multi-value.

  • CAST(<string|bytes> AS UUID) parses into the 16-byte stored form
  • a UUID result read as a string renders the canonical dashed form rather than the hex a BYTES column produces

Why both directions

These are genuinely distinct and both are needed:

They live in different switches in CastTransformFunction (one dispatches on the source type, the other on the result type). Removing either produces IllegalStateException: Cannot cast from SV UUID to STRING.

Where the conversions live

ArrayCopyUtils.copyToUuid() / copyFromUuid(), SV and MV, placed with their src/dest neighbours. They mirror the existing copyFromTimestamp — a logical type parsed into, or rendered from, its stored representation.

Testing

CastTransformFunctionTest covers both directions, the round trip through BYTES, and rejection of invalid literals, invalid byte lengths and MV sources. BaseTransformFunctionTest gains a UUID single-value column and the matching assertion branch, which the other UUID transform tests build on.

About this PR

Split out of #18872 to keep that one to predicate evaluation. Depends only on UuidUtils (#18869) and the UUID stored type (#18870), both already on master — it does not depend on #18872 and can merge independently.

Part of the #18140 UUID split.

Adds CAST support for the logical UUID type, in both directions and for single-
and multi-value columns:

- CAST(<string|bytes> AS UUID) parses the value into the 16-byte stored form
- a UUID result read as a string renders the canonical dashed form rather than
  the hex a BYTES column would produce

The conversions live in ArrayCopyUtils as copyToUuid / copyFromUuid, next to
copyFromTimestamp which they mirror -- a logical type parsed into, or rendered
from, its stored representation.

Note the two directions are distinct and both are needed: apache#19165 handles a UUID
*source* cast to STRING, while this handles rendering a UUID *result*, e.g.
reading CAST(x AS UUID) as a string.

Split out of apache#18872 to keep that PR to predicate evaluation. Depends only on
UuidUtils (apache#18869) and the UUID stored type (apache#18870), both already on master.
xiangfu0 added a commit to xiangfu0/pinot that referenced this pull request Aug 7, 2026
Adds UUID handling to the predicate evaluators, so =, !=, IN, NOT IN and range
predicates work against a UUID column on both the raw and the dictionary path.

UUID follows the pattern TIMESTAMP already uses: a logical type whose stored
type does the work. The literal is parsed to its 16-byte stored form once, when
the evaluator is built, and from there the existing BYTES evaluators apply --
no per-value conversion in the scan loop.

The dictionary path needs no UUID branch: Dictionary#getStoredValue returns hex
for a UUID column and indexOf(String) hex-decodes, so the existing String-keyed
lookup is already correct. PredicateUtils renders the literal to that hex form
for those String-typed lookup APIs.

Split into apache#19181 (CAST), apache#19182 (bloom filter pruning) and apache#19183 (transform
functions); this PR is now just the predicate evaluators.
@codecov-commenter

codecov-commenter commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 43.42105% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.62%. Comparing base (d3604a5) to head (1ab6eaf).
⚠️ Report is 9 commits behind head on master.

Files with missing lines Patch % Lines
...ator/transform/function/CastTransformFunction.java 50.00% 20 Missing and 2 partials ⚠️
...ava/org/apache/pinot/spi/utils/ArrayCopyUtils.java 30.00% 21 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             master   #19181   +/-   ##
=========================================
  Coverage     66.62%   66.62%           
  Complexity     1423     1423           
=========================================
  Files          3443     3443           
  Lines        218577   218633   +56     
  Branches      34792    34795    +3     
=========================================
+ Hits         145624   145675   +51     
- Misses        61218    61232   +14     
+ Partials      11735    11726    -9     
Flag Coverage Δ
custom-integration1 100.00% <ø> (ø)
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 66.62% <43.42%> (+<0.01%) ⬆️
temurin 66.62% <43.42%> (+<0.01%) ⬆️
unittests 66.62% <43.42%> (+<0.01%) ⬆️
unittests1 57.16% <43.42%> (+0.03%) ⬆️
unittests2 38.91% <0.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

xiangfu0 added a commit to xiangfu0/pinot that referenced this pull request Aug 7, 2026
Adds UUID handling to the predicate evaluators, so =, !=, IN, NOT IN and range
predicates work against a UUID column on both the raw and the dictionary path.

UUID follows the pattern TIMESTAMP already uses: a logical type whose stored
type does the work. The literal is parsed to its 16-byte stored form once, when
the evaluator is built, and from there the existing BYTES evaluators apply --
no per-value conversion in the scan loop.

The dictionary path needs no UUID branch: Dictionary#getStoredValue returns hex
for a UUID column and indexOf(String) hex-decodes, so the existing String-keyed
lookup is already correct. PredicateUtils renders the literal to that hex form
for those String-typed lookup APIs.

Split into apache#19181 (CAST), apache#19182 (bloom filter pruning) and apache#19183 (transform
functions); this PR is now just the predicate evaluators.
xiangfu0 added a commit to xiangfu0/pinot that referenced this pull request Aug 7, 2026
Three transform functions did not handle a UUID operand:

- BinaryOperatorTransformFunction compared UUID columns via the STRING path,
  so the comparison ran over the rendered form instead of the stored bytes.
  The fix is generic -- it routes any BYTES-stored logical type through the
  bytes comparison -- and is not UUID-specific.
- CaseTransformFunction returned UUID branches through the string path, so a
  bare STRING literal branch was not coerced to UUID bytes.
- InTransformFunction parsed the IN list as hex, which rejects the canonical
  dashed form users actually write.

Split out of apache#18872. Stacked on apache#19181 -- both touch BaseTransformFunctionTest,
whose UUID column and assertion branch these tests build on.
Comment thread pinot-spi/src/main/java/org/apache/pinot/spi/utils/ArrayCopyUtils.java Outdated
@Jackie-Jiang Jackie-Jiang added the feature New functionality label Aug 7, 2026
@xiangfu0
xiangfu0 merged commit 690ac21 into apache:master Aug 7, 2026
12 checks passed
@xiangfu0
xiangfu0 deleted the uuid-split/04a-cast branch August 7, 2026 21:03
xiangfu0 added a commit to pinot-contrib/pinot-docs that referenced this pull request Aug 7, 2026
Documents the CAST behavior added by apache/pinot#19181.

- Adds UUID to the CAST target types.
- Documents string and 16-byte inputs, canonical output, validation, and
SV/MV support.

Co-authored-by: Xiang Fu <xiangfu@Xiang-mac-mtv-2.local>
@xiangfu0

xiangfu0 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Documentation follow-up: pinot-contrib/pinot-docs#968 (merged).

xiangfu0 added a commit to xiangfu0/pinot that referenced this pull request Aug 7, 2026
Three transform functions did not handle a UUID operand:

- BinaryOperatorTransformFunction compared UUID columns via the STRING path,
  so the comparison ran over the rendered form instead of the stored bytes.
  The fix is generic -- it routes any BYTES-stored logical type through the
  bytes comparison -- and is not UUID-specific.
- CaseTransformFunction returned UUID branches through the string path, so a
  bare STRING literal branch was not coerced to UUID bytes.
- InTransformFunction parsed the IN list as hex, which rejects the canonical
  dashed form users actually write.

Split out of apache#18872. Stacked on apache#19181 -- both touch BaseTransformFunctionTest,
whose UUID column and assertion branch these tests build on.
xiangfu0 added a commit to xiangfu0/pinot that referenced this pull request Aug 8, 2026
Add UUID transform coverage and stored-byte handling for CASE and IN.

- CASE validates bare UUID branches as fixed-width hex and initializes the bytes result buffer for null-aware evaluation; canonical dashed values require CAST(... AS UUID).
- IN parses all-literal UUID lists to the 16-byte stored form so dashed and dashless spellings compare by value.
- Binary comparisons already dispatch through BYTES stored types; add dictionary/raw regression coverage and improve diagnostics and comparator documentation.

Split out of apache#18872. Prerequisite UUID changes apache#19181 and apache#19182 are now on master.
xiangfu0 added a commit to xiangfu0/pinot that referenced this pull request Aug 8, 2026
Add UUID transform regression coverage and stored-byte handling for CASE.

- CASE validates bare UUID branches as fixed-width hex and initializes the bytes result buffer for null-aware evaluation; canonical dashed values require CAST(... AS UUID).\n- IN already uses the BYTES stored path; cover uppercase hex, explicit UUID casts, and rejection of bare canonical strings.\n- Binary comparisons already dispatch through BYTES stored types; add dictionary/raw regression coverage and improve diagnostics and comparator documentation.

Split out of apache#18872. Prerequisite UUID changes apache#19181 and apache#19182 are now on master.
xiangfu0 added a commit to xiangfu0/pinot that referenced this pull request Aug 8, 2026
Add UUID transform regression coverage and stored-byte handling for CASE.

- CASE validates bare UUID branches as fixed-width hex and initializes the bytes result buffer for null-aware evaluation; canonical dashed values require CAST(... AS UUID).
- IN already uses the BYTES stored path; cover uppercase hex, explicit UUID casts, and rejection of bare canonical strings.
- Binary comparisons already dispatch through BYTES stored types; add dictionary/raw regression coverage and improve diagnostics and comparator documentation.

Split out of apache#18872. Prerequisite UUID changes apache#19181 and apache#19182 are now on master.
xiangfu0 added a commit to xiangfu0/pinot that referenced this pull request Aug 8, 2026
Add UUID transform regression coverage and stored-byte handling for CASE.

- CASE validates bare UUID branches as fixed-width hex and initializes the bytes result buffer for null-aware evaluation; canonical dashed values require CAST(... AS UUID).
- IN already uses the BYTES stored path; cover uppercase hex, explicit UUID casts, and rejection of bare canonical strings.
- Binary comparisons already dispatch through BYTES stored types; add dictionary/raw regression coverage and improve diagnostics and comparator documentation.

Split out of apache#18872. Prerequisite UUID changes apache#19181 and apache#19182 are now on master.
xiangfu0 added a commit to xiangfu0/pinot that referenced this pull request Aug 8, 2026
Adds UUID handling to the predicate evaluators, so =, !=, IN, NOT IN and range
predicates work against a UUID column on both the raw and the dictionary path.

UUID follows the pattern TIMESTAMP already uses: a logical type whose stored
type does the work. The literal is parsed to its 16-byte stored form once, when
the evaluator is built, and from there the existing BYTES evaluators apply --
no per-value conversion in the scan loop.

The dictionary path needs no UUID branch: Dictionary#getStoredValue returns hex
for a UUID column and indexOf(String) hex-decodes, so the existing String-keyed
lookup is already correct. PredicateUtils renders the literal to that hex form
for those String-typed lookup APIs.

Split into apache#19181 (CAST), apache#19182 (bloom filter pruning) and apache#19183 (transform
functions); this PR is now just the predicate evaluators.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants