-
Notifications
You must be signed in to change notification settings - Fork 3.4k
API: Use unsigned byte-wise comparison to fix UUID comparison bug #14500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
bodduv
wants to merge
18
commits into
apache:main
Choose a base branch
from
bodduv:uuid_comparison_bug
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
37d4fc1
API: Use unsigned byte-wise comparison to fix UUID comparison bug
bodduv 15be0e3
Let ManifestFile and ManifestEntry to be read if filter expression pa…
bodduv edd6378
Do only a single tree traversal; address comments
bodduv deb5bf8
Address comments; drop expression as a parameter of eval(); move to n…
bodduv 9af9c5a
Consolidate comparatorForIn into a static method
bodduv b625b15
Fix iff; add new lines after block; fix TestInclusiveManifestEvaluato…
bodduv ab000b3
Address comments for test files
bodduv e831236
Drop transient cached cmp
bodduv e428981
Refactor ExpressionUtil.predicate
bodduv 8c95520
Make MetricsEvalVisitor static; address doc related comments
bodduv e921039
Address comments; Bring all three metrics evaluators in sync; improve…
bodduv 7cbea58
Resolving conflicts before merge
bodduv ea4df24
Updated branch from upstream main
bodduv adec1be
Block UUID predicates in strict metrics evaluation
bodduv 0db0152
Tests: Add UUID strict metrics coverage
bodduv 6fb1881
Merge remote-tracking branch 'origin/main' into uuid_comparison_bug
bodduv 0d1df72
Updated branch from upstream master
bodduv f159dd5
Tests: Update UUID delete coverage
bodduv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,12 +24,14 @@ | |
| import java.time.temporal.ChronoUnit; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.function.Function; | ||
| import java.util.regex.Pattern; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.IntStream; | ||
| import java.util.stream.StreamSupport; | ||
| import javax.annotation.Nullable; | ||
| import org.apache.iceberg.PartitionSpec; | ||
| import org.apache.iceberg.Schema; | ||
| import org.apache.iceberg.Table; | ||
|
|
@@ -744,4 +746,125 @@ private static PartitionSpec identitySpec(Schema schema, int... ids) { | |
|
|
||
| return specBuilder.build(); | ||
| } | ||
|
|
||
| /** | ||
| * Transform UUID literals in an unbound expression to use signed comparators, if the expression | ||
| * contains UUID bounds predicates. This maintains backward compatibility with files written | ||
| * before RFC 4122/9562 compliant comparison was implemented. | ||
| * | ||
| * <p>The transformed expression contains literals with signed comparators for lt/gt/eq | ||
| * predicates. For IN predicates, the comparator information is lost when binding converts | ||
| * literals to a Set of raw values, so the evaluator must handle this separately. | ||
| * | ||
| * @param expr an unbound expression | ||
| * @return the expression with signed UUID comparators, or null if no UUID predicates are present | ||
| */ | ||
| @Nullable | ||
| public static Expression toSignedUUIDLiteral(Expression expr) { | ||
| SignedUUIDLiteralVisitor visitor = new SignedUUIDLiteralVisitor(); | ||
| Expression transformed = ExpressionVisitors.visit(expr, visitor); | ||
| return visitor.foundUUIDBoundsPredicate() ? transformed : null; | ||
| } | ||
|
|
||
| /** | ||
| * Visitor that transforms an expression to use the signed UUID comparator in all UUID literals, | ||
| * while also tracking whether any UUID bounds predicates were found. | ||
| */ | ||
| private static class SignedUUIDLiteralVisitor | ||
| extends ExpressionVisitors.ExpressionVisitor<Expression> { | ||
|
|
||
| private boolean foundUUIDBoundsPredicate = false; | ||
|
|
||
| boolean foundUUIDBoundsPredicate() { | ||
| return foundUUIDBoundsPredicate; | ||
| } | ||
|
|
||
| @Override | ||
| public Expression alwaysTrue() { | ||
| return Expressions.alwaysTrue(); | ||
| } | ||
|
|
||
| @Override | ||
| public Expression alwaysFalse() { | ||
| return Expressions.alwaysFalse(); | ||
| } | ||
|
|
||
| @Override | ||
| public Expression not(Expression result) { | ||
| return Expressions.not(result); | ||
| } | ||
|
|
||
| @Override | ||
| public Expression and(Expression leftResult, Expression rightResult) { | ||
| return Expressions.and(leftResult, rightResult); | ||
| } | ||
|
|
||
| @Override | ||
| public Expression or(Expression leftResult, Expression rightResult) { | ||
| return Expressions.or(leftResult, rightResult); | ||
| } | ||
|
|
||
| @Override | ||
| public <T> Expression predicate(BoundPredicate<T> pred) { | ||
| // Bound predicates should not be transformed - this is for unbound expressions | ||
| throw new UnsupportedOperationException( | ||
| "Cannot transform bound predicate; use unbound expressions"); | ||
| } | ||
|
|
||
| @Override | ||
| @SuppressWarnings("unchecked") | ||
| public <T> Expression predicate(UnboundPredicate<T> pred) { | ||
| UnboundTerm<T> term = pred.term(); | ||
|
|
||
| switch (pred.op()) { | ||
| case IS_NULL: | ||
| case NOT_NULL: | ||
| case IS_NAN: | ||
| case NOT_NAN: | ||
| // Unary predicates don't have literals to transform | ||
| return pred; | ||
|
|
||
| case LT: | ||
| case LT_EQ: | ||
| case GT: | ||
| case GT_EQ: | ||
| case EQ: | ||
| case NOT_EQ: | ||
| Literal<T> lit = pred.literal(); | ||
| if (lit.value() instanceof UUID) { | ||
| foundUUIDBoundsPredicate = true; | ||
| Literals.UUIDLiteral uuidLit = (Literals.UUIDLiteral) lit; | ||
| return new UnboundPredicate<>(pred.op(), term, (T) uuidLit.withSignedComparator()); | ||
| } | ||
|
|
||
| return pred; | ||
|
|
||
| case STARTS_WITH: | ||
| case NOT_STARTS_WITH: | ||
| // These operations don't apply to UUIDs, no transformation needed | ||
| return pred; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: newline - after every block. See: https://iceberg.apache.org/contribute/#block-spacing |
||
|
|
||
| case IN: | ||
| case NOT_IN: | ||
| List<Literal<T>> literals = pred.literals(); | ||
| if (!literals.isEmpty() && literals.get(0).value() instanceof UUID) { | ||
| foundUUIDBoundsPredicate = true; | ||
| List<T> transformedValues = | ||
| literals.stream() | ||
| .map( | ||
| l -> { | ||
| Literals.UUIDLiteral uuidLit = (Literals.UUIDLiteral) l; | ||
| return (T) uuidLit.withSignedComparator(); | ||
| }) | ||
| .collect(Collectors.toList()); | ||
| return new UnboundPredicate<>(pred.op(), term, transformedValues); | ||
| } | ||
|
|
||
| return pred; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: newline |
||
|
|
||
| default: | ||
| return pred; | ||
| } | ||
| } | ||
| } | ||
| } | ||
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
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
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.