This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 11ae6ef981 [core] Optimize between in Between
11ae6ef981 is described below
commit 11ae6ef9818c154a6c5556d3aea2ba7e7893758c
Author: JingsongLi <[email protected]>
AuthorDate: Fri Feb 13 16:31:36 2026 +0800
[core] Optimize between in Between
---
.../bitmap/RangeBitmapIndexBenchmark.java | 4 +-
.../apache/paimon/fileindex/FileIndexReader.java | 10 +
.../bsi/BitSliceIndexBitmapFileIndex.java | 17 +-
.../bsi/BitSliceIndexBitmapFileIndexFactory.java | 2 +-
.../rangebitmap/RangeBitmapFileIndex.java | 10 +
.../java/org/apache/paimon/predicate/Between.java | 166 ++++++++++
.../apache/paimon/predicate/PredicateBuilder.java | 6 +-
.../org/apache/paimon/utils/PredicateUtils.java | 190 -----------
.../bsi/BitSliceIndexBitmapFileIndexTest.java | 6 +-
.../org/apache/paimon/predicate/BetweenTest.java | 364 +++++++++++++++++++++
.../paimon/predicate/PredicateBuilderTest.java | 6 +-
.../apache/paimon/utils/PredicateUtilsTest.java | 207 ------------
.../paimon/table/source/ReadBuilderImpl.java | 2 -
13 files changed, 577 insertions(+), 413 deletions(-)
diff --git
a/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/bitmap/RangeBitmapIndexBenchmark.java
b/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/bitmap/RangeBitmapIndexBenchmark.java
index 6787f8dabf..11b0a5d088 100644
---
a/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/bitmap/RangeBitmapIndexBenchmark.java
+++
b/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/bitmap/RangeBitmapIndexBenchmark.java
@@ -166,7 +166,7 @@ public class RangeBitmapIndexBenchmark {
Random random = new Random();
FileIndexWriter bsiWriter =
- new BitSliceIndexBitmapFileIndex(DataTypes.INT(), new
Options()).createWriter();
+ new
BitSliceIndexBitmapFileIndex(DataTypes.INT()).createWriter();
Options bitmapOptions = new Options();
bitmapOptions.setInteger(BitmapFileIndex.VERSION,
BitmapFileIndex.VERSION_2);
@@ -219,7 +219,7 @@ public class RangeBitmapIndexBenchmark {
LocalFileIO.LocalSeekableInputStream localSeekableInputStream =
new LocalFileIO.LocalSeekableInputStream(file);
FileIndexReader reader =
- new BitSliceIndexBitmapFileIndex(DataTypes.INT(), options)
+ new BitSliceIndexBitmapFileIndex(DataTypes.INT())
.createReader(localSeekableInputStream, 0, 0);
FileIndexResult result = function.apply(reader, fieldRef);
((BitmapIndexResult) result).get();
diff --git
a/paimon-common/src/main/java/org/apache/paimon/fileindex/FileIndexReader.java
b/paimon-common/src/main/java/org/apache/paimon/fileindex/FileIndexReader.java
index 3b73e62d2b..cfb460f36e 100644
---
a/paimon-common/src/main/java/org/apache/paimon/fileindex/FileIndexReader.java
+++
b/paimon-common/src/main/java/org/apache/paimon/fileindex/FileIndexReader.java
@@ -135,4 +135,14 @@ public abstract class FileIndexReader implements
FunctionVisitor<FileIndexResult
public FileIndexResult visitNonFieldLeaf(LeafPredicate predicate) {
return REMAIN;
}
+
+ @Override
+ public FileIndexResult visitBetween(FieldRef fieldRef, Object from, Object
to) {
+ return REMAIN;
+ }
+
+ @Override
+ public FileIndexResult visitNotBetween(FieldRef fieldRef, Object from,
Object to) {
+ return REMAIN;
+ }
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndex.java
b/paimon-common/src/main/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndex.java
index df6b8d897c..680008a736 100644
---
a/paimon-common/src/main/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndex.java
+++
b/paimon-common/src/main/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndex.java
@@ -26,7 +26,6 @@ import org.apache.paimon.fileindex.FileIndexWriter;
import org.apache.paimon.fileindex.FileIndexer;
import org.apache.paimon.fileindex.bitmap.BitmapIndexResult;
import org.apache.paimon.fs.SeekableInputStream;
-import org.apache.paimon.options.Options;
import org.apache.paimon.predicate.FieldRef;
import org.apache.paimon.types.BigIntType;
import org.apache.paimon.types.DataType;
@@ -59,7 +58,7 @@ public class BitSliceIndexBitmapFileIndex implements
FileIndexer {
private final DataType dataType;
- public BitSliceIndexBitmapFileIndex(DataType dataType, Options options) {
+ public BitSliceIndexBitmapFileIndex(DataType dataType) {
this.dataType = dataType;
}
@@ -290,7 +289,7 @@ public class BitSliceIndexBitmapFileIndex implements
FileIndexer {
}
@Override
- public FileIndexResult visitLessOrEqual(FieldRef fieldRef, Object
literal) {
+ public BitmapIndexResult visitLessOrEqual(FieldRef fieldRef, Object
literal) {
return new BitmapIndexResult(
() -> {
Long value = valueMapper.apply(literal);
@@ -317,7 +316,7 @@ public class BitSliceIndexBitmapFileIndex implements
FileIndexer {
}
@Override
- public FileIndexResult visitGreaterOrEqual(FieldRef fieldRef, Object
literal) {
+ public BitmapIndexResult visitGreaterOrEqual(FieldRef fieldRef, Object
literal) {
return new BitmapIndexResult(
() -> {
Long value = valueMapper.apply(literal);
@@ -329,6 +328,16 @@ public class BitSliceIndexBitmapFileIndex implements
FileIndexer {
}
});
}
+
+ @Override
+ public FileIndexResult visitBetween(FieldRef fieldRef, Object from,
Object to) {
+ return new BitmapIndexResult(
+ () -> {
+ RoaringBitmap32 gte = visitGreaterOrEqual(fieldRef,
from).get();
+ RoaringBitmap32 lte = visitLessOrEqual(fieldRef,
to).get();
+ return RoaringBitmap32.and(gte, lte);
+ });
+ }
}
public static Function<Object, Long> getValueMapper(DataType dataType) {
diff --git
a/paimon-common/src/main/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndexFactory.java
b/paimon-common/src/main/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndexFactory.java
index aa7a92b785..c6d182ce01 100644
---
a/paimon-common/src/main/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndexFactory.java
+++
b/paimon-common/src/main/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndexFactory.java
@@ -35,6 +35,6 @@ public class BitSliceIndexBitmapFileIndexFactory implements
FileIndexerFactory {
@Override
public FileIndexer create(DataType dataType, Options options) {
- return new BitSliceIndexBitmapFileIndex(dataType, options);
+ return new BitSliceIndexBitmapFileIndex(dataType);
}
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/fileindex/rangebitmap/RangeBitmapFileIndex.java
b/paimon-common/src/main/java/org/apache/paimon/fileindex/rangebitmap/RangeBitmapFileIndex.java
index 1482e24f13..8b3ef92f22 100644
---
a/paimon-common/src/main/java/org/apache/paimon/fileindex/rangebitmap/RangeBitmapFileIndex.java
+++
b/paimon-common/src/main/java/org/apache/paimon/fileindex/rangebitmap/RangeBitmapFileIndex.java
@@ -153,6 +153,16 @@ public class RangeBitmapFileIndex implements FileIndexer {
return new BitmapIndexResult(() ->
bitmap.gte(converter.apply(literal)));
}
+ @Override
+ public FileIndexResult visitBetween(FieldRef fieldRef, Object from,
Object to) {
+ return new BitmapIndexResult(
+ () -> {
+ RoaringBitmap32 gte =
bitmap.gte(converter.apply(from));
+ RoaringBitmap32 lte = bitmap.lte(converter.apply(to));
+ return RoaringBitmap32.and(gte, lte);
+ });
+ }
+
@Override
public FileIndexResult visitTopN(TopN topN, FileIndexResult result) {
RoaringBitmap32 foundSet =
diff --git
a/paimon-common/src/main/java/org/apache/paimon/predicate/Between.java
b/paimon-common/src/main/java/org/apache/paimon/predicate/Between.java
index c9124c14b4..30d317b224 100644
--- a/paimon-common/src/main/java/org/apache/paimon/predicate/Between.java
+++ b/paimon-common/src/main/java/org/apache/paimon/predicate/Between.java
@@ -22,10 +22,15 @@ import org.apache.paimon.types.DataType;
import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import static org.apache.paimon.predicate.CompareUtils.compareLiteral;
+import static org.apache.paimon.predicate.PredicateBuilder.splitAnd;
/** The {@link LeafFunction} to eval between. */
public class Between extends LeafTernaryFunction {
@@ -72,4 +77,165 @@ public class Between extends LeafTernaryFunction {
public String toJson() {
return NAME;
}
+
+ /**
+ * Optimize predicates by converting LessOrEqual and GreaterOrEqual to
Between for the same
+ * field.
+ */
+ public static List<Predicate> optimize(List<Predicate> predicates) {
+ // First, split all AND predicates to get leaf predicates
+ List<Predicate> leafPredicates = new ArrayList<>();
+ for (Predicate predicate : predicates) {
+ leafPredicates.addAll(splitAnd(predicate));
+ }
+
+ // Group leaf predicates by their FieldTransform
+ Map<FieldTransform, List<LeafPredicate>> predicatesByField = new
HashMap<>();
+ List<Predicate> otherPredicates = new ArrayList<>();
+
+ for (Predicate predicate : leafPredicates) {
+ if (predicate instanceof LeafPredicate) {
+ LeafPredicate leafPredicate = (LeafPredicate) predicate;
+ if (leafPredicate.transform() instanceof FieldTransform) {
+ FieldTransform fieldTransform = (FieldTransform)
leafPredicate.transform();
+ predicatesByField
+ .computeIfAbsent(fieldTransform, k -> new
ArrayList<>())
+ .add(leafPredicate);
+ } else {
+ otherPredicates.add(predicate);
+ }
+ } else {
+ otherPredicates.add(predicate);
+ }
+ }
+
+ List<Predicate> result = new ArrayList<>(otherPredicates);
+ for (Map.Entry<FieldTransform, List<LeafPredicate>> entry :
predicatesByField.entrySet()) {
+ FieldTransform field = entry.getKey();
+ List<LeafPredicate> fieldPredicates = entry.getValue();
+ fieldPredicates = mergeLessAndGreaterToBetween(field,
fieldPredicates);
+ fieldPredicates = mergeMultipleBetweens(field, fieldPredicates);
+ result.addAll(fieldPredicates);
+ }
+
+ // return input predicate if nothing changed
+ return result.size() == predicates.size() ? predicates : result;
+ }
+
+ private static List<LeafPredicate> mergeLessAndGreaterToBetween(
+ FieldTransform field, List<LeafPredicate> predicates) {
+ List<LeafPredicate> result = new ArrayList<>();
+ DataType type = field.outputType();
+ LeafPredicate lessOrEqual = null;
+ LeafPredicate greaterOrEqual = null;
+
+ for (LeafPredicate leafPredicate : predicates) {
+ if (leafPredicate.function() == LessOrEqual.INSTANCE) {
+ if (lessOrEqual == null) {
+ lessOrEqual = leafPredicate;
+ } else {
+ lessOrEqual =
+ compareLiteral(
+ type,
+
lessOrEqual.literals().get(0),
+
leafPredicate.literals().get(0))
+ < 0
+ ? lessOrEqual
+ : leafPredicate;
+ }
+ } else if (leafPredicate.function() == GreaterOrEqual.INSTANCE) {
+ if (greaterOrEqual == null) {
+ greaterOrEqual = leafPredicate;
+ } else {
+ greaterOrEqual =
+ compareLiteral(
+ type,
+
greaterOrEqual.literals().get(0),
+
leafPredicate.literals().get(0))
+ > 0
+ ? greaterOrEqual
+ : leafPredicate;
+ }
+ } else {
+ result.add(leafPredicate);
+ }
+ }
+
+ // If we have both LessOrEqual and GreaterOrEqual, convert to Between
+ if (lessOrEqual != null && greaterOrEqual != null) {
+ // Determine which is the lower bound and which is the upper bound
+ Object lowerBound = greaterOrEqual.literals().get(0);
+ Object upperBound = lessOrEqual.literals().get(0);
+ if (compareLiteral(type, lowerBound, upperBound) >= 0) {
+ // No valid intersection, keep all original predicates
+ result.add(lessOrEqual);
+ result.add(greaterOrEqual);
+ } else {
+ // convert to Between
+ LeafPredicate betweenPredicate =
+ LeafPredicate.of(
+ field, Between.INSTANCE,
Arrays.asList(lowerBound, upperBound));
+ result.add(betweenPredicate);
+ }
+ } else {
+ // Add LessOrEqual and GreaterOrEqual to remaining if no pair found
+ if (lessOrEqual != null) {
+ result.add(lessOrEqual);
+ }
+ if (greaterOrEqual != null) {
+ result.add(greaterOrEqual);
+ }
+ }
+
+ return result;
+ }
+
+ private static List<LeafPredicate> mergeMultipleBetweens(
+ FieldTransform field, List<LeafPredicate> predicates) {
+ List<LeafPredicate> results = new ArrayList<>();
+ List<LeafPredicate> betweens = new ArrayList<>();
+ for (LeafPredicate predicate : predicates) {
+ if (predicate.function() == Between.INSTANCE) {
+ betweens.add(predicate);
+ } else {
+ results.add(predicate);
+ }
+ }
+ if (betweens.isEmpty() || betweens.size() == 1) {
+ return predicates;
+ }
+
+ DataType fieldType = field.outputType();
+
+ // Find the maximum lower bound and minimum upper bound
+ Object maxLower = null;
+ Object minUpper = null;
+
+ for (LeafPredicate between : betweens) {
+ Object lower = between.literals().get(0);
+ Object upper = between.literals().get(1);
+
+ if (maxLower == null || compareLiteral(fieldType, lower, maxLower)
> 0) {
+ maxLower = lower;
+ }
+ if (minUpper == null || compareLiteral(fieldType, upper, minUpper)
< 0) {
+ minUpper = upper;
+ }
+ }
+
+ // Check if intersection is valid
+ if (maxLower != null
+ && minUpper != null
+ && compareLiteral(fieldType, maxLower, minUpper) <= 0) {
+ // Valid intersection, create a single Between predicate
+ LeafPredicate mergedBetween =
+ LeafPredicate.of(field, Between.INSTANCE,
Arrays.asList(maxLower, minUpper));
+ results.add(mergedBetween);
+ } else {
+ // No valid intersection, keep all original predicates unchanged
+ results.addAll(betweens);
+ }
+
+ return results;
+ }
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/predicate/PredicateBuilder.java
b/paimon-common/src/main/java/org/apache/paimon/predicate/PredicateBuilder.java
index c25764193d..79c26fc351 100644
---
a/paimon-common/src/main/java/org/apache/paimon/predicate/PredicateBuilder.java
+++
b/paimon-common/src/main/java/org/apache/paimon/predicate/PredicateBuilder.java
@@ -254,7 +254,11 @@ public class PredicateBuilder {
if (predicates.size() == 1) {
return predicates.get(0);
}
- return predicates.stream()
+
+ // Optimize by converting LessOrEqual and GreaterOrEqual to Between
for same field
+ List<Predicate> optimized = Between.optimize(predicates);
+
+ return optimized.stream()
.reduce((a, b) -> new CompoundPredicate(And.INSTANCE,
Arrays.asList(a, b)))
.get();
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/utils/PredicateUtils.java
b/paimon-common/src/main/java/org/apache/paimon/utils/PredicateUtils.java
deleted file mode 100644
index 49964185f4..0000000000
--- a/paimon-common/src/main/java/org/apache/paimon/utils/PredicateUtils.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.paimon.utils;
-
-import org.apache.paimon.predicate.Between;
-import org.apache.paimon.predicate.CompareUtils;
-import org.apache.paimon.predicate.CompoundPredicate;
-import org.apache.paimon.predicate.GreaterOrEqual;
-import org.apache.paimon.predicate.LeafPredicate;
-import org.apache.paimon.predicate.LessOrEqual;
-import org.apache.paimon.predicate.Or;
-import org.apache.paimon.predicate.Predicate;
-import org.apache.paimon.predicate.PredicateBuilder;
-import org.apache.paimon.predicate.Transform;
-
-import javax.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-
-/** Utils for {@link Predicate}. */
-public class PredicateUtils {
-
- /**
- * Try to rewrite possible {@code GREATER_OR_EQUAL} and {@code
LESS_OR_EQUAL} predicates to
- * {@code BETWEEN} leaf predicate. This method will recursively try to
rewrite the children
- * predicates of an {@code AND}, for example: {@code OR(a >= 1, AND(b >=
1, b <= 2))} will be
- * rewritten to {@code OR(a >= 1, BETWEEN(b, 1, 2))}.
- */
- public static Predicate tryRewriteBetweenPredicate(@Nullable Predicate
filter) {
- if (filter == null || filter instanceof LeafPredicate) {
- return filter;
- }
- CompoundPredicate compoundPredicate = (CompoundPredicate) filter;
- boolean isOr = compoundPredicate.function() instanceof Or;
-
- Map<Transform, List<LeafPredicate>> leavesByTransform = new
HashMap<>();
- List<Predicate> resultChildren = new ArrayList<>();
- // Flatten the children predicates of an AND
- // For example, for AND(b >= 1, AND(a >= 1, b <= 2)), we will get [a
>= 1, b >= 1, b <= 2]
- // After flattening, all children will be either LeafPredicate or
ORPredicate
- List<Predicate> effectiveChildren =
- isOr ? compoundPredicate.children() :
flattenChildren(compoundPredicate.children());
- for (Predicate child : effectiveChildren) {
- if (child instanceof LeafPredicate) {
- leavesByTransform
- .computeIfAbsent(
- ((LeafPredicate) child).transform(), k -> new
ArrayList<>())
- .add((LeafPredicate) child);
- } else {
- resultChildren.add(tryRewriteBetweenPredicate(child));
- }
- }
-
- for (Map.Entry<Transform, List<LeafPredicate>> leaves :
leavesByTransform.entrySet()) {
- if (isOr) {
- resultChildren.addAll(leaves.getValue());
- continue;
- }
-
- Transform transform = leaves.getKey();
-
- // for children predicates of an AND, we only need to reserve
- // the largest GREATER_OR_EQUAL and the smallest LESS_OR_EQUAL
- // For example, for AND(a >= 1, a >= 2, a <= 3, a <= 4), we only
need to reserve a >= 2
- // and a <= 3
- LeafPredicate gtePredicate = null;
- LeafPredicate ltePredicate = null;
- for (LeafPredicate leaf : leaves.getValue()) {
- if (leaf.function() instanceof GreaterOrEqual) {
- if (gtePredicate == null
- || CompareUtils.compareLiteral(
- transform.outputType(),
- leaf.literals().get(0),
- gtePredicate.literals().get(0))
- > 0) {
- gtePredicate = leaf;
- }
- } else if (leaf.function() instanceof LessOrEqual) {
- if (ltePredicate == null
- || CompareUtils.compareLiteral(
- transform.outputType(),
- leaf.literals().get(0),
- ltePredicate.literals().get(0))
- < 0) {
- ltePredicate = leaf;
- }
- } else {
- resultChildren.add(leaf);
- }
- }
-
- boolean converted = false;
- if (gtePredicate != null && ltePredicate != null) {
- Optional<Predicate> betweenLeaf =
convertToBetweenLeaf(gtePredicate, ltePredicate);
- if (betweenLeaf.isPresent()) {
- converted = true;
- resultChildren.add(betweenLeaf.get());
- }
- }
- if (!converted) {
- if (gtePredicate != null) {
- resultChildren.add(gtePredicate);
- }
- if (ltePredicate != null) {
- resultChildren.add(ltePredicate);
- }
- }
- }
-
- return isOr ? PredicateBuilder.or(resultChildren) :
PredicateBuilder.and(resultChildren);
- }
-
- private static List<Predicate> flattenChildren(List<Predicate> children) {
- List<Predicate> result = new ArrayList<>();
- for (Predicate child : children) {
- if (child instanceof LeafPredicate) {
- result.add(child);
- } else {
- CompoundPredicate compoundPredicate = (CompoundPredicate)
child;
- if (compoundPredicate.function() instanceof Or) {
- result.add(child);
- } else {
-
result.addAll(flattenChildren(compoundPredicate.children()));
- }
- }
- }
- return result;
- }
-
- /**
- * Convert child predicates of an AND to a BETWEEN leaf predicate. Return
`Optional.empty()` if
- * not possible.
- */
- public static Optional<Predicate> convertToBetweenLeaf(
- Predicate leftChild, Predicate rightChild) {
- if (leftChild instanceof LeafPredicate && rightChild instanceof
LeafPredicate) {
- LeafPredicate left = (LeafPredicate) leftChild;
- LeafPredicate right = (LeafPredicate) rightChild;
- if (Objects.equals(left.transform(), right.transform())) {
- if (left.function() instanceof GreaterOrEqual
- && right.function() instanceof LessOrEqual) {
- return createBetweenLeaf(left, right);
- } else if (left.function() instanceof LessOrEqual
- && right.function() instanceof GreaterOrEqual) {
- return createBetweenLeaf(right, left);
- }
- }
- }
-
- return Optional.empty();
- }
-
- private static Optional<Predicate> createBetweenLeaf(
- LeafPredicate gtePredicate, LeafPredicate ltePredicate) {
- // gtePredicate and ltePredicate should have the same transform
- Transform transform = gtePredicate.transform();
- Object lbLiteral = gtePredicate.literals().get(0);
- Object ubLiteral = ltePredicate.literals().get(0);
-
- if (CompareUtils.compareLiteral(transform.outputType(), lbLiteral,
ubLiteral) > 0) {
- return Optional.empty();
- }
-
- return Optional.of(
- new LeafPredicate(
- transform, Between.INSTANCE, Arrays.asList(lbLiteral,
ubLiteral)));
- }
-}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndexTest.java
b/paimon-common/src/test/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndexTest.java
index cd73f2b2a2..b55e2e77e1 100644
---
a/paimon-common/src/test/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndexTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/fileindex/bsi/BitSliceIndexBitmapFileIndexTest.java
@@ -39,7 +39,7 @@ public class BitSliceIndexBitmapFileIndexTest {
public void testBitSliceIndexMix() {
IntType intType = new IntType();
FieldRef fieldRef = new FieldRef(0, "", intType);
- BitSliceIndexBitmapFileIndex bsiFileIndex = new
BitSliceIndexBitmapFileIndex(intType, null);
+ BitSliceIndexBitmapFileIndex bsiFileIndex = new
BitSliceIndexBitmapFileIndex(intType);
FileIndexWriter writer = bsiFileIndex.createWriter();
Object[] arr = {1, 2, null, -2, -2, -1, null, 2, 0, 5, null};
@@ -111,7 +111,7 @@ public class BitSliceIndexBitmapFileIndexTest {
public void testBitSliceIndexPositiveOnly() {
IntType intType = new IntType();
FieldRef fieldRef = new FieldRef(0, "", intType);
- BitSliceIndexBitmapFileIndex bsiFileIndex = new
BitSliceIndexBitmapFileIndex(intType, null);
+ BitSliceIndexBitmapFileIndex bsiFileIndex = new
BitSliceIndexBitmapFileIndex(intType);
FileIndexWriter writer = bsiFileIndex.createWriter();
Object[] arr = {0, 1, null, 3, 4, 5, 6, 0, null};
@@ -183,7 +183,7 @@ public class BitSliceIndexBitmapFileIndexTest {
public void testBitSliceIndexNegativeOnly() {
IntType intType = new IntType();
FieldRef fieldRef = new FieldRef(0, "", intType);
- BitSliceIndexBitmapFileIndex bsiFileIndex = new
BitSliceIndexBitmapFileIndex(intType, null);
+ BitSliceIndexBitmapFileIndex bsiFileIndex = new
BitSliceIndexBitmapFileIndex(intType);
FileIndexWriter writer = bsiFileIndex.createWriter();
Object[] arr = {null, -1, null, -3, -4, -5, -6, -1, null};
diff --git
a/paimon-common/src/test/java/org/apache/paimon/predicate/BetweenTest.java
b/paimon-common/src/test/java/org/apache/paimon/predicate/BetweenTest.java
new file mode 100644
index 0000000000..f5692d85f5
--- /dev/null
+++ b/paimon-common/src/test/java/org/apache/paimon/predicate/BetweenTest.java
@@ -0,0 +1,364 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.predicate;
+
+import org.apache.paimon.types.IntType;
+import org.apache.paimon.types.RowType;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class BetweenTest {
+
+ @Test
+ public void testOneLessOrEqualNotRewrite() {
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+ Predicate lte = builder.lessOrEqual(0, 10);
+ Predicate isNotNull = builder.isNotNull(0);
+
+ Predicate result = PredicateBuilder.and(isNotNull, lte);
+
+ assertThat(result).isInstanceOf(CompoundPredicate.class);
+ CompoundPredicate compoundResult = (CompoundPredicate) result;
+ assertThat(compoundResult.function()).isInstanceOf(And.class);
+ assertThat(compoundResult.children()).hasSize(2);
+
+ assertThat(compoundResult.children().get(1)).isEqualTo(lte);
+ assertThat(compoundResult.children().get(0)).isEqualTo(isNotNull);
+ }
+
+ @Test
+ public void testTryRewriteBetweenPredicateBasic() {
+ // Test basic case: AND(a>=1, a<=10, a is not null) should be
rewritten to BETWEEN
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+ Predicate gte = builder.greaterOrEqual(0, 1);
+ Predicate lte = builder.lessOrEqual(0, 10);
+ Predicate isNotNull = builder.isNotNull(0);
+
+ Predicate result = PredicateBuilder.and(gte, isNotNull, lte);
+
+ assertThat(result).isInstanceOf(CompoundPredicate.class);
+ CompoundPredicate compoundResult = (CompoundPredicate) result;
+ assertThat(compoundResult.function()).isInstanceOf(And.class);
+ assertThat(compoundResult.children()).hasSize(2);
+
+ Predicate betweenChild = compoundResult.children().get(1);
+ assertThat(betweenChild).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) betweenChild;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(1, 10);
+
+ Predicate notNullChild = compoundResult.children().get(0);
+ assertThat(notNullChild).isInstanceOf(LeafPredicate.class);
+ assertThat(notNullChild.toString()).contains("IsNotNull");
+ }
+
+ @Test
+ public void testTryRewriteBetweenPredicateRecursive() {
+ // Test recursive case: OR(b>=1, AND(a>=1, a<=10, a is not null))
should rewrite nested AND
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType(), new IntType()));
+
+ Predicate gteB = builder.greaterOrEqual(1, 1);
+ Predicate gteA = builder.greaterOrEqual(0, 1);
+ Predicate lteA = builder.lessOrEqual(0, 10);
+ Predicate isNotNullA = builder.isNotNull(0);
+ Predicate andPredicate = PredicateBuilder.and(gteA, isNotNullA, lteA);
+ Predicate result = PredicateBuilder.or(gteB, andPredicate);
+
+ assertThat(result).isInstanceOf(CompoundPredicate.class);
+ CompoundPredicate compoundResult = (CompoundPredicate) result;
+ assertThat(compoundResult.function()).isInstanceOf(Or.class);
+ assertThat(compoundResult.children()).hasSize(2);
+
+ Predicate secondChild = compoundResult.children().get(0);
+ assertThat(secondChild).isInstanceOf(LeafPredicate.class);
+ assertThat(secondChild.toString()).contains("GreaterOrEqual");
+
+ Predicate firstChild = compoundResult.children().get(1);
+ assertThat(firstChild).isInstanceOf(CompoundPredicate.class);
+ CompoundPredicate innerAnd = (CompoundPredicate) firstChild;
+ assertThat(innerAnd.function()).isInstanceOf(And.class);
+ assertThat(innerAnd.children()).hasSize(2);
+
+ Predicate betweenCandidate = innerAnd.children().get(1);
+ assertThat(betweenCandidate).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) betweenCandidate;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(1, 10);
+ }
+
+ /**
+ * Test this complicated scenario.
+ *
+ * <pre>{@code
+ * AND
+ * / | \
+ * OR AND a>=1
+ * /| || \
+ * / | / | \
+ * a>=1 a<=2 OR AND a>=2
+ * / | | \
+ * / | | \
+ * a>=1 b<2 b>=1 a<=10
+ *
+ * }</pre>
+ */
+ @Test
+ public void testAnExtremeComplicatedPredicate() {
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType(), new IntType()));
+ Predicate l3p1 = builder.greaterOrEqual(0, 1);
+ Predicate l3p2 = builder.lessThan(1, 2);
+ Predicate l3p3 = builder.greaterOrEqual(1, 1);
+ Predicate l3p4 = builder.lessOrEqual(0, 10);
+ Predicate l2p1 = builder.greaterOrEqual(0, 1);
+ Predicate l2p2 = builder.lessOrEqual(1, 2);
+ Predicate l2p3 = PredicateBuilder.or(l3p1, l3p2);
+ Predicate l2p4 = PredicateBuilder.and(l3p3, l3p4);
+ Predicate l2p5 = builder.greaterOrEqual(0, 2);
+ Predicate l1p1 = PredicateBuilder.or(l2p1, l2p2);
+ Predicate l1p2 = PredicateBuilder.and(l2p3, l2p4, l2p5);
+ Predicate l1p3 = builder.greaterOrEqual(0, 1);
+ Predicate result = PredicateBuilder.and(l1p1, l1p2, l1p3);
+
+ assertThat(result).isInstanceOf(CompoundPredicate.class);
+
+ CompoundPredicate compoundResult = (CompoundPredicate) result;
+ assertThat(compoundResult.function()).isInstanceOf(And.class);
+
+ // directly check the toString
+ String resultString = compoundResult.toString();
+ assertThat(resultString).contains("Between(f0, [2, 10])");
+ }
+
+ @Test
+ public void testTryRewriteBetweenPredicateIntersection() {
+ // Test intersection case: AND(a>=1, a<=10, a>=2, a<=7) should use
intersection (2, 7)
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate gte1 = builder.greaterOrEqual(0, 1);
+ Predicate lte10 = builder.lessOrEqual(0, 10);
+ Predicate gte2 = builder.greaterOrEqual(0, 2);
+ Predicate lte7 = builder.lessOrEqual(0, 7);
+
+ Predicate result =
+ PredicateBuilder.and(
+ PredicateBuilder.and(gte1, lte10),
PredicateBuilder.and(gte2, lte7));
+ assertThat(result).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) result;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(2, 7);
+
+ result = PredicateBuilder.and(gte1, lte10, gte2, lte7);
+ assertThat(result).isInstanceOf(LeafPredicate.class);
+ betweenLeaf = (LeafPredicate) result;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(2, 7);
+ }
+
+ @Test
+ public void testTryRewriteBetweenPredicateDifferentColumns() {
+ // Test different columns case: AND(a>=1, b<=10) should not be
rewritten
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType(), new IntType()));
+
+ Predicate gteA = builder.greaterOrEqual(0, 1);
+ Predicate lteB = builder.lessOrEqual(1, 10);
+ Predicate result = PredicateBuilder.and(gteA, lteB);
+
+ assertThat(result).isInstanceOf(CompoundPredicate.class);
+ CompoundPredicate compoundResult = (CompoundPredicate) result;
+ assertThat(compoundResult.function()).isInstanceOf(And.class);
+ assertThat(compoundResult.children()).hasSize(2);
+ assertThat(compoundResult.children().stream().map(Predicate::toString))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList("GreaterOrEqual(f0, 1)",
"LessOrEqual(f1, 10)"));
+ }
+
+ @Test
+ public void testTryRewriteBetweenPredicateInvalidRange() {
+ // Test invalid range case: AND(a>=10, a<=1) should not be rewritten
to BETWEEN
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate gte = builder.greaterOrEqual(0, 10);
+ Predicate lte = builder.lessOrEqual(0, 1);
+ Predicate result = PredicateBuilder.and(gte, lte);
+
+ assertThat(result).isInstanceOf(CompoundPredicate.class);
+ CompoundPredicate compoundResult = (CompoundPredicate) result;
+ assertThat(compoundResult.function()).isInstanceOf(And.class);
+ assertThat(compoundResult.children()).hasSize(2);
+ assertThat(compoundResult.children().stream().map(Predicate::toString))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList("GreaterOrEqual(f0, 10)",
"LessOrEqual(f0, 1)"));
+ }
+
+ @Test
+ public void testMergeMultipleBetweensValidIntersection() {
+ // Test merging multiple Between predicates with valid intersection
+ // BETWEEN 1 AND 10 AND BETWEEN 5 AND 15 -> BETWEEN 5 AND 10
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate between1 = builder.between(0, 1, 10);
+ Predicate between2 = builder.between(0, 5, 15);
+ Predicate result = PredicateBuilder.and(between1, between2);
+
+ assertThat(result).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) result;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(5, 10);
+ }
+
+ @Test
+ public void testMergeMultipleBetweensNoIntersection() {
+ // Test merging multiple Between predicates with no valid intersection
+ // BETWEEN 1 AND 5 AND BETWEEN 10 AND 15 -> keep both
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate between1 = builder.between(0, 1, 5);
+ Predicate between2 = builder.between(0, 10, 15);
+ Predicate result = PredicateBuilder.and(between1, between2);
+
+ assertThat(result).isInstanceOf(CompoundPredicate.class);
+ CompoundPredicate compoundResult = (CompoundPredicate) result;
+ assertThat(compoundResult.function()).isInstanceOf(And.class);
+ assertThat(compoundResult.children()).hasSize(2);
+ }
+
+ @Test
+ public void testMergeMultipleBetweensTouchingIntersection() {
+ // Test merging multiple Between predicates with touching intersection
+ // BETWEEN 1 AND 10 AND BETWEEN 10 AND 20 -> BETWEEN 10 AND 10
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate between1 = builder.between(0, 1, 10);
+ Predicate between2 = builder.between(0, 10, 20);
+ Predicate result = PredicateBuilder.and(between1, between2);
+
+ assertThat(result).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) result;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(10, 10);
+ }
+
+ @Test
+ public void testMergeThreeBetweens() {
+ // Test merging three Between predicates
+ // BETWEEN 1 AND 20 AND BETWEEN 5 AND 15 AND BETWEEN 8 AND 12 ->
BETWEEN 8 AND 12
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate between1 = builder.between(0, 1, 20);
+ Predicate between2 = builder.between(0, 5, 15);
+ Predicate between3 = builder.between(0, 8, 12);
+ Predicate result = PredicateBuilder.and(between1, between2, between3);
+
+ assertThat(result).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) result;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(8, 12);
+ }
+
+ @Test
+ public void testSingleBetweenNotModified() {
+ // Test that a single Between predicate is not modified
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate between = builder.between(0, 1, 10);
+ Predicate result = PredicateBuilder.and(between);
+
+ assertThat(result).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) result;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(1, 10);
+ }
+
+ @Test
+ public void testBetweenWithOtherPredicates() {
+ // Test Between predicate with other predicates
+ // BETWEEN 1 AND 10 AND isNotNull AND Equal
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate between = builder.between(0, 1, 10);
+ Predicate isNotNull = builder.isNotNull(0);
+ Predicate equal = builder.equal(0, 5);
+ Predicate result = PredicateBuilder.and(between, isNotNull, equal);
+
+ List<Predicate> predicates = PredicateBuilder.splitAnd(result);
+ assertThat(predicates).hasSize(3);
+ }
+
+ @Test
+ public void testMergeBetweensFromLessAndGreater() {
+ // Test that Between predicates created from LessOrEqual and
GreaterOrEqual are merged
+ // (a>=1 AND a<=10) AND (a>=2 AND a<=7) -> BETWEEN 2 AND 7
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate gte1 = builder.greaterOrEqual(0, 1);
+ Predicate lte10 = builder.lessOrEqual(0, 10);
+ Predicate gte2 = builder.greaterOrEqual(0, 2);
+ Predicate lte7 = builder.lessOrEqual(0, 7);
+
+ Predicate and1 = PredicateBuilder.and(gte1, lte10);
+ Predicate and2 = PredicateBuilder.and(gte2, lte7);
+ Predicate result = PredicateBuilder.and(and1, and2);
+
+ assertThat(result).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) result;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(2, 7);
+ }
+
+ @Test
+ public void testMultipleLessOrEqualKeepSmallest() {
+ // Test that when there are multiple LessOrEqual, only the smallest is
kept
+ // a<=10 AND a<=5 AND a>=1 -> BETWEEN 1 AND 5
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate lte10 = builder.lessOrEqual(0, 10);
+ Predicate lte5 = builder.lessOrEqual(0, 5);
+ Predicate gte1 = builder.greaterOrEqual(0, 1);
+
+ Predicate result = PredicateBuilder.and(lte10, lte5, gte1);
+
+ assertThat(result).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) result;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(1, 5);
+ }
+
+ @Test
+ public void testMultipleGreaterOrEqualKeepLargest() {
+ // Test that when there are multiple GreaterOrEqual, only the largest
is kept
+ // a>=1 AND a>=5 AND a<=10 -> BETWEEN 5 AND 10
+ PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
+
+ Predicate gte1 = builder.greaterOrEqual(0, 1);
+ Predicate gte5 = builder.greaterOrEqual(0, 5);
+ Predicate lte10 = builder.lessOrEqual(0, 10);
+
+ Predicate result = PredicateBuilder.and(gte1, gte5, lte10);
+
+ assertThat(result).isInstanceOf(LeafPredicate.class);
+ LeafPredicate betweenLeaf = (LeafPredicate) result;
+ assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
+ assertThat(betweenLeaf.literals()).containsExactly(5, 10);
+ }
+}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/predicate/PredicateBuilderTest.java
b/paimon-common/src/test/java/org/apache/paimon/predicate/PredicateBuilderTest.java
index b6b72fd259..8318990ab4 100644
---
a/paimon-common/src/test/java/org/apache/paimon/predicate/PredicateBuilderTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/predicate/PredicateBuilderTest.java
@@ -115,10 +115,10 @@ public class PredicateBuilderTest {
.isEqualTo(
Arrays.asList(
child1,
- builder.isNull(3),
- builder.isNull(4),
+ child3,
builder.isNull(5),
- child3));
+ builder.isNull(4),
+ builder.isNull(3)));
}
@Test
diff --git
a/paimon-common/src/test/java/org/apache/paimon/utils/PredicateUtilsTest.java
b/paimon-common/src/test/java/org/apache/paimon/utils/PredicateUtilsTest.java
deleted file mode 100644
index ca299500ba..0000000000
---
a/paimon-common/src/test/java/org/apache/paimon/utils/PredicateUtilsTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.paimon.utils;
-
-import org.apache.paimon.predicate.And;
-import org.apache.paimon.predicate.Between;
-import org.apache.paimon.predicate.CompoundPredicate;
-import org.apache.paimon.predicate.LeafPredicate;
-import org.apache.paimon.predicate.Or;
-import org.apache.paimon.predicate.Predicate;
-import org.apache.paimon.predicate.PredicateBuilder;
-import org.apache.paimon.types.IntType;
-import org.apache.paimon.types.RowType;
-
-import org.junit.jupiter.api.Test;
-
-import java.util.Arrays;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/** Test for {@link PredicateUtils}. */
-public class PredicateUtilsTest {
-
- @Test
- public void testTryRewriteBetweenPredicateBasic() {
- // Test basic case: AND(a>=1, a<=10, a is not null) should be
rewritten to BETWEEN
- PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
- Predicate gte = builder.greaterOrEqual(0, 1);
- Predicate lte = builder.lessOrEqual(0, 10);
- Predicate isNotNull = builder.isNotNull(0);
-
- Predicate andPredicate = PredicateBuilder.and(gte, isNotNull, lte);
- Predicate result =
PredicateUtils.tryRewriteBetweenPredicate(andPredicate);
-
- assertThat(result).isInstanceOf(CompoundPredicate.class);
- CompoundPredicate compoundResult = (CompoundPredicate) result;
- assertThat(compoundResult.function()).isInstanceOf(And.class);
- assertThat(compoundResult.children()).hasSize(2);
-
- Predicate betweenChild = compoundResult.children().get(1);
- assertThat(betweenChild).isInstanceOf(LeafPredicate.class);
- LeafPredicate betweenLeaf = (LeafPredicate) betweenChild;
- assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
- assertThat(betweenLeaf.literals()).containsExactly(1, 10);
-
- Predicate notNullChild = compoundResult.children().get(0);
- assertThat(notNullChild).isInstanceOf(LeafPredicate.class);
- assertThat(notNullChild.toString()).contains("IsNotNull");
- }
-
- @Test
- public void testTryRewriteBetweenPredicateRecursive() {
- // Test recursive case: OR(b>=1, AND(a>=1, a<=10, a is not null))
should rewrite nested AND
- PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType(), new IntType()));
-
- Predicate gteB = builder.greaterOrEqual(1, 1);
- Predicate gteA = builder.greaterOrEqual(0, 1);
- Predicate lteA = builder.lessOrEqual(0, 10);
- Predicate isNotNullA = builder.isNotNull(0);
- Predicate andPredicate = PredicateBuilder.and(gteA, isNotNullA, lteA);
- Predicate orPredicate = PredicateBuilder.or(gteB, andPredicate);
-
- Predicate result =
PredicateUtils.tryRewriteBetweenPredicate(orPredicate);
-
- assertThat(result).isInstanceOf(CompoundPredicate.class);
- CompoundPredicate compoundResult = (CompoundPredicate) result;
- assertThat(compoundResult.function()).isInstanceOf(Or.class);
- assertThat(compoundResult.children()).hasSize(2);
-
- Predicate secondChild = compoundResult.children().get(1);
- assertThat(secondChild).isInstanceOf(LeafPredicate.class);
- assertThat(secondChild.toString()).contains("GreaterOrEqual");
-
- Predicate firstChild = compoundResult.children().get(0);
- assertThat(firstChild).isInstanceOf(CompoundPredicate.class);
- CompoundPredicate innerAnd = (CompoundPredicate) firstChild;
- assertThat(innerAnd.function()).isInstanceOf(And.class);
- assertThat(innerAnd.children()).hasSize(2);
-
- Predicate betweenCandidate = innerAnd.children().get(1);
- assertThat(betweenCandidate).isInstanceOf(LeafPredicate.class);
- LeafPredicate betweenLeaf = (LeafPredicate) betweenCandidate;
- assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
- assertThat(betweenLeaf.literals()).containsExactly(1, 10);
- }
-
- /**
- * Test this complicated scenario.
- *
- * <pre>{@code
- * AND
- * / | \
- * OR AND a>=1
- * /| || \
- * / | / | \
- * a>=1 a<=2 OR AND a>=2
- * / | | \
- * / | | \
- * a>=1 b<2 b>=1 a<=10
- *
- * }</pre>
- */
- @Test
- public void testAnExtremeComplicatedPredicate() {
- PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType(), new IntType()));
- Predicate l3p1 = builder.greaterOrEqual(0, 1);
- Predicate l3p2 = builder.lessThan(1, 2);
- Predicate l3p3 = builder.greaterOrEqual(1, 1);
- Predicate l3p4 = builder.lessOrEqual(0, 10);
- Predicate l2p1 = builder.greaterOrEqual(0, 1);
- Predicate l2p2 = builder.lessOrEqual(1, 2);
- Predicate l2p3 = PredicateBuilder.or(l3p1, l3p2);
- Predicate l2p4 = PredicateBuilder.and(l3p3, l3p4);
- Predicate l2p5 = builder.greaterOrEqual(0, 2);
- Predicate l1p1 = PredicateBuilder.or(l2p1, l2p2);
- Predicate l1p2 = PredicateBuilder.and(l2p3, l2p4, l2p5);
- Predicate l1p3 = builder.greaterOrEqual(0, 1);
- Predicate root = PredicateBuilder.and(l1p1, l1p2, l1p3);
-
- Predicate result = PredicateUtils.tryRewriteBetweenPredicate(root);
- assertThat(result).isInstanceOf(CompoundPredicate.class);
-
- CompoundPredicate compoundResult = (CompoundPredicate) result;
- assertThat(compoundResult.function()).isInstanceOf(And.class);
-
- // directly check the toString
- String resultString = compoundResult.toString();
- assertThat(resultString).contains("Between(f0, [2, 10])");
- }
-
- @Test
- public void testTryRewriteBetweenPredicateIntersection() {
- // Test intersection case: AND(a>=1, a<=10, a>=2, a<=7) should use
intersection (2, 7)
- PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
-
- Predicate gte1 = builder.greaterOrEqual(0, 1);
- Predicate lte10 = builder.lessOrEqual(0, 10);
- Predicate gte2 = builder.greaterOrEqual(0, 2);
- Predicate lte7 = builder.lessOrEqual(0, 7);
-
- Predicate predicate =
- PredicateBuilder.and(
- PredicateBuilder.and(gte1, lte10),
PredicateBuilder.and(gte2, lte7));
- Predicate result =
PredicateUtils.tryRewriteBetweenPredicate(predicate);
-
- assertThat(result).isInstanceOf(LeafPredicate.class);
- LeafPredicate betweenLeaf = (LeafPredicate) result;
- assertThat(betweenLeaf.function()).isInstanceOf(Between.class);
- assertThat(betweenLeaf.literals()).containsExactly(2, 7);
- }
-
- @Test
- public void testTryRewriteBetweenPredicateDifferentColumns() {
- // Test different columns case: AND(a>=1, b<=10) should not be
rewritten
- PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType(), new IntType()));
-
- Predicate gteA = builder.greaterOrEqual(0, 1);
- Predicate lteB = builder.lessOrEqual(1, 10);
- Predicate predicate = PredicateBuilder.and(gteA, lteB);
-
- Predicate result =
PredicateUtils.tryRewriteBetweenPredicate(predicate);
-
- assertThat(result).isInstanceOf(CompoundPredicate.class);
- CompoundPredicate compoundResult = (CompoundPredicate) result;
- assertThat(compoundResult.function()).isInstanceOf(And.class);
- assertThat(compoundResult.children()).hasSize(2);
- assertThat(compoundResult.children().stream().map(Predicate::toString))
- .containsExactlyInAnyOrderElementsOf(
- Arrays.asList("GreaterOrEqual(f0, 1)",
"LessOrEqual(f1, 10)"));
- }
-
- @Test
- public void testTryRewriteBetweenPredicateInvalidRange() {
- // Test invalid range case: AND(a>=10, a<=1) should not be rewritten
to BETWEEN
- PredicateBuilder builder = new PredicateBuilder(RowType.of(new
IntType()));
-
- Predicate gte = builder.greaterOrEqual(0, 10);
- Predicate lte = builder.lessOrEqual(0, 1);
- Predicate predicate = PredicateBuilder.and(gte, lte);
-
- Predicate result =
PredicateUtils.tryRewriteBetweenPredicate(predicate);
-
- assertThat(result).isInstanceOf(CompoundPredicate.class);
- CompoundPredicate compoundResult = (CompoundPredicate) result;
- assertThat(compoundResult.function()).isInstanceOf(And.class);
- assertThat(compoundResult.children()).hasSize(2);
- assertThat(compoundResult.children().stream().map(Predicate::toString))
- .containsExactlyInAnyOrderElementsOf(
- Arrays.asList("GreaterOrEqual(f0, 10)",
"LessOrEqual(f0, 1)"));
- }
-}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilderImpl.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilderImpl.java
index f8cf39d175..f4f529dc4c 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilderImpl.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/ReadBuilderImpl.java
@@ -27,7 +27,6 @@ import org.apache.paimon.predicate.VectorSearch;
import org.apache.paimon.table.InnerTable;
import org.apache.paimon.types.RowType;
import org.apache.paimon.utils.Filter;
-import org.apache.paimon.utils.PredicateUtils;
import org.apache.paimon.utils.Range;
import javax.annotation.Nullable;
@@ -91,7 +90,6 @@ public class ReadBuilderImpl implements ReadBuilder {
} else {
this.filter = PredicateBuilder.and(this.filter, filter);
}
- this.filter = PredicateUtils.tryRewriteBetweenPredicate(this.filter);
return this;
}