[CALCITE-7716] BETWEEN/range predicates on UUID literals give wrong results because RexSimplify orders bounds using java.util.UUID#compareTo (signed comparison) - #5199
Open
GoncaloCoutoDosSantos wants to merge 1 commit into
Conversation
…esults because RexSimplify orders bounds using java.util.UUID#compareTo (signed comparison)
mihaibudiu
reviewed
Aug 19, 2026
mihaibudiu
left a comment
Contributor
There was a problem hiding this comment.
this is a breaking change, both in APIs and semantics, at the very least it has to be documented in the release notes
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Jira Link
CALCITE-7716
Changes Proposed
SQL orders
UUIDvalues as unsigned 128-bit integers, butjava.util.UUID#compareTocompares the two 64-bit halves as signed longs. Since a
UUIDwas used directly asthe
Comparablevalue of aUUIDRexLiteral, every range predicate inherited thatsigned ordering.
Any bound whose most significant bit is set therefore sorts below one whose is not, so
RexSimplifysees an inverted (empty) range and folds the predicate away:Returns
FALSEbefore this change,TRUEafter.ffffffff-…has all bits set, so itshigh half is
-1as a signed long and it compares as less than00000000-…, making therange empty. The same applies to
<,<=,>and>=;IN,NOT INandIS [NOT] DISTINCT FROMwere unaffected because they compare by equality only.The fix
Introduce
org.apache.calcite.util.UuidValue, a small wrapper aroundjava.util.UUIDthat implements
ComparableusingLong.compareUnsignedon each half, and use it asboth the
RexLiteralvalue and the runtime representation of SQLUUID:RexLiteral—valueMatchesTypeand theUUIDassertion now expectUuidValue.getValueAs(UUID.class)is still supported and unwraps, so existing callers keep working.RexBuilder—makeUuidLiteralwraps into aUuidValue; aUuidValueoverload isadded and
makeLiteralaccepts either representation.JavaTypeFactoryImpl— SQLUUIDnow maps toUuidValue.classrather thanUUID.class.SqlFunctions—uuidToString,uuidToBinaryandbinaryToUuidoperate onUuidValue, plus newlt/le/gt/geoverloads so runtime comparison uses the sameunsigned ordering as planning-time simplification.
BuiltInMethod,RelJson,VariantNonNull— updated to the new type.UuidValue.fromString(wired toBuiltInMethod.UUID_FROM_STRING, used by the runtimeCAST(VARCHAR AS UUID)path) delegates to the existing lenientSqlFunctions.stringToUuidrather than
java.util.UUID.fromString, so all the spellings Calcite already accepts —optional hyphen group separators, surrounding braces — keep working, matching PostgreSQL.
Escape hatch
The new system property
calcite.uuid.unsigned.comparison(CalciteSystemProperty.UUID_UNSIGNED_COMPARISON,default
true) reverts to the oldUUID#compareToordering for anyone depending on theprevious behaviour.
Tests
SqlOperatorTest.testUuidBetweencoversBETWEEN/NOT BETWEEN,<and>across theminimum, a mid-range and the maximum UUID, and asserts that
IN,NOT INandIS [NOT] DISTINCT FROMare independent of the ordering. Expected values are derived fromUUID_UNSIGNED_COMPARISONso the test is correct under either setting../gradlew buildpasses, including the full test suite.