This is an automated email from the ASF dual-hosted git repository. jark pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/fluss.git
commit 74ab1f130a00fceb880dcf28c94e68fe5dc062ce Author: Jark Wu <j...@apache.org> AuthorDate: Wed Aug 6 10:55:05 2025 +0800 [common] Support statistic-based Predicate interface --- LICENSE | 27 ++++ .../main/java/com/alibaba/fluss/predicate/And.java | 15 +++ .../com/alibaba/fluss/predicate/CompareUtils.java | 10 +- .../alibaba/fluss/predicate/CompoundPredicate.java | 16 +++ .../java/com/alibaba/fluss/predicate/Contains.java | 2 + .../java/com/alibaba/fluss/predicate/EndsWith.java | 2 + .../java/com/alibaba/fluss/predicate/Equal.java | 2 + .../java/com/alibaba/fluss/predicate/FieldRef.java | 2 +- .../alibaba/fluss/predicate/GreaterOrEqual.java | 2 + .../com/alibaba/fluss/predicate/GreaterThan.java | 2 + .../main/java/com/alibaba/fluss/predicate/In.java | 2 +- .../com/alibaba/fluss/predicate/IsNotNull.java | 2 + .../java/com/alibaba/fluss/predicate/IsNull.java | 2 + .../com/alibaba/fluss/predicate/LeafFunction.java | 27 +++- .../com/alibaba/fluss/predicate/LeafPredicate.java | 21 ++- .../alibaba/fluss/predicate/LeafUnaryFunction.java | 2 +- .../com/alibaba/fluss/predicate/LessOrEqual.java | 2 + .../java/com/alibaba/fluss/predicate/LessThan.java | 2 + .../java/com/alibaba/fluss/predicate/NotEqual.java | 4 + .../java/com/alibaba/fluss/predicate/NotIn.java | 5 +- .../predicate/NullFalseLeafBinaryFunction.java | 2 +- .../main/java/com/alibaba/fluss/predicate/Or.java | 17 ++- .../com/alibaba/fluss/predicate/Predicate.java | 16 ++- .../alibaba/fluss/predicate/PredicateBuilder.java | 47 ++++--- .../alibaba/fluss/predicate/PredicateVisitor.java | 9 +- .../com/alibaba/fluss/predicate/StartsWith.java | 2 + .../fluss/predicate/UnsupportedExpression.java | 3 + .../main/java/com/alibaba/fluss/types/RowType.java | 20 +-- .../com/alibaba/fluss/utils/BinaryStringUtils.java | 10 +- .../fluss/predicate/PredicateBuilderTest.java | 45 +++++-- .../com/alibaba/fluss/predicate/PredicateTest.java | 150 ++++++++++++++++++++- 31 files changed, 399 insertions(+), 71 deletions(-) diff --git a/LICENSE b/LICENSE index 7342382ec..5c274b510 100644 --- a/LICENSE +++ b/LICENSE @@ -359,6 +359,33 @@ Apache Kafka ./fluss-server/src/main/java/com/alibaba/fluss/server/utils/timer/TimingWheel.java Apache Paimon +./fluss-common/src/main/java/com/alibaba/fluss/predicate/And.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/CompareUtils.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/CompoundPredicate.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/Contains.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/EndsWith.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/Equal.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/FieldRef.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/FunctionVisitor.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterOrEqual.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterThan.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/In.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNotNull.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNull.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafFunction.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafPredicate.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafUnaryFunction.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/LessOrEqual.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/LessThan.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/NotEqual.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/NotIn.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/NullFalseLeafBinaryFunction.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/Or.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/PartitionPredicateVisitor.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/Predicate.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateBuilder.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateVisitor.java +./fluss-common/src/main/java/com/alibaba/fluss/predicate/StartsWith.java ./fluss-common/src/main/java/com/alibaba/fluss/row/encode/paimon/PaimonBinaryRowWriter.java ./fluss-flink/fluss-flink-common/src/main/java/com/alibaba/fluss/flink/lakehouse/paimon/reader/PaimonSnapshotScanner.java diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/And.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/And.java index 204bb7e3d..59f79cd69 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/And.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/And.java @@ -46,6 +46,21 @@ public class And extends CompoundPredicate.Function { return true; } + @Override + public boolean test( + long rowCount, + InternalRow minValues, + InternalRow maxValues, + Long[] nullCounts, + List<Predicate> children) { + for (Predicate child : children) { + if (!child.test(rowCount, minValues, maxValues, nullCounts)) { + return false; + } + } + return true; + } + @Override public Optional<Predicate> negate(List<Predicate> children) { List<Predicate> negatedChildren = new ArrayList<>(); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/CompareUtils.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/CompareUtils.java index 50380abf8..68b97a2ef 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/CompareUtils.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/CompareUtils.java @@ -17,7 +17,6 @@ package com.alibaba.fluss.predicate; -import com.alibaba.fluss.row.BinaryString; import com.alibaba.fluss.types.DataType; import static java.lang.Math.min; @@ -30,16 +29,9 @@ import static java.lang.Math.min; public class CompareUtils { private CompareUtils() {} + @SuppressWarnings("unchecked") public static int compareLiteral(DataType type, Object v1, Object v2) { if (v1 instanceof Comparable) { - // because BinaryString can not serialize so v1 or v2 may be BinaryString convert to - // String for compare - if (v1 instanceof BinaryString) { - v1 = ((BinaryString) v1).toString(); - } - if (v2 instanceof BinaryString) { - v2 = ((BinaryString) v2).toString(); - } return ((Comparable<Object>) v1).compareTo(v2); } else if (v1 instanceof byte[]) { return compare((byte[]) v1, (byte[]) v2); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/CompoundPredicate.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/CompoundPredicate.java index 449598f2f..f38599326 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/CompoundPredicate.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/CompoundPredicate.java @@ -34,6 +34,7 @@ import java.util.Optional; */ public class CompoundPredicate implements Predicate { + private static final long serialVersionUID = 1L; private final Function function; private final List<Predicate> children; @@ -55,6 +56,12 @@ public class CompoundPredicate implements Predicate { return function.test(row, children); } + @Override + public boolean test( + long rowCount, InternalRow minValues, InternalRow maxValues, Long[] nullCounts) { + return function.test(rowCount, minValues, maxValues, nullCounts, children); + } + @Override public Optional<Predicate> negate() { return function.negate(children); @@ -87,8 +94,17 @@ public class CompoundPredicate implements Predicate { /** Evaluate the predicate result based on multiple {@link Predicate}s. */ public abstract static class Function implements Serializable { + private static final long serialVersionUID = 1L; + public abstract boolean test(InternalRow row, List<Predicate> children); + public abstract boolean test( + long rowCount, + InternalRow minValues, + InternalRow maxValues, + Long[] nullCounts, + List<Predicate> children); + public abstract Optional<Predicate> negate(List<Predicate> children); public abstract <T> T visit(FunctionVisitor<T> visitor, List<T> children); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/Contains.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/Contains.java index cfdaf0a06..5c166813d 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/Contains.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/Contains.java @@ -29,6 +29,8 @@ import java.util.Optional; /** A {@link NullFalseLeafBinaryFunction} to evaluate {@code filter like '%abc%'}. */ public class Contains extends NullFalseLeafBinaryFunction { + private static final long serialVersionUID = 1L; + public static final Contains INSTANCE = new Contains(); private Contains() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/EndsWith.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/EndsWith.java index a6af4c2e1..38f4a67f8 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/EndsWith.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/EndsWith.java @@ -32,6 +32,8 @@ import java.util.Optional; */ public class EndsWith extends NullFalseLeafBinaryFunction { + private static final long serialVersionUID = 1L; + public static final EndsWith INSTANCE = new EndsWith(); private EndsWith() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/Equal.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/Equal.java index 26b181749..b7fbf776a 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/Equal.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/Equal.java @@ -31,6 +31,8 @@ import static com.alibaba.fluss.predicate.CompareUtils.compareLiteral; /** A {@link NullFalseLeafBinaryFunction} to eval equal. */ public class Equal extends NullFalseLeafBinaryFunction { + private static final long serialVersionUID = 1L; + public static final Equal INSTANCE = new Equal(); private Equal() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/FieldRef.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/FieldRef.java index d98dc92ba..b9e179ddc 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/FieldRef.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/FieldRef.java @@ -29,7 +29,7 @@ import java.util.Objects; /** A reference to a field in an input. */ public class FieldRef implements Serializable { - private static final long serialVersionUID = 4982103776651292199L; + private static final long serialVersionUID = 1L; private final int index; private final String name; diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterOrEqual.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterOrEqual.java index 0a915355f..6f3ff9d85 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterOrEqual.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterOrEqual.java @@ -31,6 +31,8 @@ import static com.alibaba.fluss.predicate.CompareUtils.compareLiteral; /** A {@link NullFalseLeafBinaryFunction} to eval greater or equal. */ public class GreaterOrEqual extends NullFalseLeafBinaryFunction { + private static final long serialVersionUID = 1L; + public static final GreaterOrEqual INSTANCE = new GreaterOrEqual(); private GreaterOrEqual() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterThan.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterThan.java index f92c84c9b..9f088a773 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterThan.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/GreaterThan.java @@ -31,6 +31,8 @@ import static com.alibaba.fluss.predicate.CompareUtils.compareLiteral; /** A {@link LeafFunction} to eval greater. */ public class GreaterThan extends NullFalseLeafBinaryFunction { + private static final long serialVersionUID = 1L; + public static final GreaterThan INSTANCE = new GreaterThan(); private GreaterThan() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/In.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/In.java index 672a1c797..6697750ce 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/In.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/In.java @@ -31,7 +31,7 @@ import static com.alibaba.fluss.predicate.CompareUtils.compareLiteral; /** A {@link LeafFunction} to eval in. */ public class In extends LeafFunction { - private static final long serialVersionUID = -9115697441080586485L; + private static final long serialVersionUID = 1L; public static final In INSTANCE = new In(); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNotNull.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNotNull.java index 6249306b5..7facb9834 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNotNull.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNotNull.java @@ -29,6 +29,8 @@ import java.util.Optional; /** A {@link NullFalseLeafBinaryFunction} to eval is not null. */ public class IsNotNull extends LeafUnaryFunction { + private static final long serialVersionUID = 1L; + public static final IsNotNull INSTANCE = new IsNotNull(); private IsNotNull() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNull.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNull.java index d97c8cb43..126df7872 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNull.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/IsNull.java @@ -29,6 +29,8 @@ import java.util.Optional; /** A {@link NullFalseLeafBinaryFunction} to eval is null. */ public class IsNull extends LeafUnaryFunction { + private static final long serialVersionUID = 1L; + public static final IsNull INSTANCE = new IsNull(); private IsNull() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafFunction.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafFunction.java index a897c902e..e2b00a3aa 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafFunction.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafFunction.java @@ -30,8 +30,29 @@ import java.util.Optional; /** Function to test a field with literals. */ public abstract class LeafFunction implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * Tests whether a field satisfies the condition based on the provided literals. + * + * @param type the data type of the field being tested + * @param field the value of the field to test + * @param literals the list of literals to test against the field + * @return true if the field satisfies the condition, false otherwise + */ public abstract boolean test(DataType type, Object field, List<Object> literals); + /** + * Tests whether a set of rows satisfies the condition based on the provided statistics. + * + * @param type the data type of the field being tested + * @param rowCount the total number of rows + * @param min the minimum value of the field in the rows + * @param max the maximum value of the field in the rows + * @param nullCount the number of null values in the field, or null if unknown + * @param literals the literals to test against the field + * @return true if there is any row satisfies the condition, false otherwise + */ public abstract boolean test( DataType type, long rowCount, @@ -42,6 +63,9 @@ public abstract class LeafFunction implements Serializable { public abstract Optional<LeafFunction> negate(); + public abstract <T> T visit( + FunctionVisitor<T> visitor, FieldRef fieldRef, List<Object> literals); + @Override public int hashCode() { return this.getClass().getName().hashCode(); @@ -55,9 +79,6 @@ public abstract class LeafFunction implements Serializable { return o != null && getClass() == o.getClass(); } - public abstract <T> T visit( - FunctionVisitor<T> visitor, FieldRef fieldRef, List<Object> literals); - @Override public String toString() { return getClass().getSimpleName(); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafPredicate.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafPredicate.java index 343e6c0f3..58f6f7963 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafPredicate.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafPredicate.java @@ -34,14 +34,14 @@ import java.util.Optional; /** Leaf node of a {@link Predicate} tree. Compares a field in the row with literals. */ public class LeafPredicate implements Predicate { - private static final long serialVersionUID = -9033842253303772188L; + private static final long serialVersionUID = 1L; private final LeafFunction function; private final DataType type; private final int fieldIndex; private final String fieldName; - private List<Object> literals; + private final List<Object> literals; public LeafPredicate( LeafFunction function, @@ -89,6 +89,23 @@ public class LeafPredicate implements Predicate { return function.test(type, get(row, fieldIndex, type), literals); } + @Override + public boolean test( + long rowCount, InternalRow minValues, InternalRow maxValues, Long[] nullCounts) { + Object min = get(minValues, fieldIndex, type); + Object max = get(maxValues, fieldIndex, type); + Long nullCount = nullCounts != null ? nullCounts[fieldIndex] : null; + if (nullCount == null || rowCount != nullCount) { + // not all null + // min or max is null + // unknown stats + if (min == null || max == null) { + return true; + } + } + return function.test(type, rowCount, min, max, nullCount, literals); + } + @Override public Optional<Predicate> negate() { return function.negate() diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafUnaryFunction.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafUnaryFunction.java index b10fe40ff..e03710aa0 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafUnaryFunction.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LeafUnaryFunction.java @@ -28,7 +28,7 @@ import java.util.List; /** Function to test a field. */ public abstract class LeafUnaryFunction extends LeafFunction { - private static final long serialVersionUID = -155104972966998013L; + private static final long serialVersionUID = 1L; public abstract boolean test(DataType type, Object value); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LessOrEqual.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LessOrEqual.java index 45dd3fda6..019ba9c31 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LessOrEqual.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LessOrEqual.java @@ -31,6 +31,8 @@ import static com.alibaba.fluss.predicate.CompareUtils.compareLiteral; /** A {@link NullFalseLeafBinaryFunction} to eval less or equal. */ public class LessOrEqual extends NullFalseLeafBinaryFunction { + private static final long serialVersionUID = 1L; + public static final LessOrEqual INSTANCE = new LessOrEqual(); private LessOrEqual() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LessThan.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LessThan.java index ed1ee70c3..a8ef1f9c8 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/LessThan.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/LessThan.java @@ -31,6 +31,8 @@ import static com.alibaba.fluss.predicate.CompareUtils.compareLiteral; /** A {@link NullFalseLeafBinaryFunction} to eval less or equal. */ public class LessThan extends NullFalseLeafBinaryFunction { + private static final long serialVersionUID = 1L; + public static final LessThan INSTANCE = new LessThan(); private LessThan() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/NotEqual.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/NotEqual.java index bca203a22..0ad0f0dc5 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/NotEqual.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/NotEqual.java @@ -31,6 +31,8 @@ import static com.alibaba.fluss.predicate.CompareUtils.compareLiteral; /** A {@link NullFalseLeafBinaryFunction} to eval not equal. */ public class NotEqual extends NullFalseLeafBinaryFunction { + private static final long serialVersionUID = 1L; + public static final NotEqual INSTANCE = new NotEqual(); private NotEqual() {} @@ -43,6 +45,8 @@ public class NotEqual extends NullFalseLeafBinaryFunction { @Override public boolean test( DataType type, long rowCount, Object min, Object max, Long nullCount, Object literal) { + // ony when max == min == literal, the result is false, + // otherwise, the row set MAY contain the literal. return compareLiteral(type, literal, min) != 0 || compareLiteral(type, literal, max) != 0; } diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/NotIn.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/NotIn.java index 3bad7e56e..efd2788f1 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/NotIn.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/NotIn.java @@ -31,7 +31,7 @@ import static com.alibaba.fluss.predicate.CompareUtils.compareLiteral; /** A {@link LeafFunction} to eval not in. */ public class NotIn extends LeafFunction { - private static final long serialVersionUID = 8953845894700582887L; + private static final long serialVersionUID = 1L; public static final NotIn INSTANCE = new NotIn(); private NotIn() {} @@ -62,6 +62,9 @@ public class NotIn extends LeafFunction { } for (Object literal : literals) { if (literal == null + // only if max == min == literal, the row set are all IN the literal, return + // false; other cases, the row set MAY contain elements NOT IN the literal, + // return true || (compareLiteral(type, literal, min) == 0 && compareLiteral(type, literal, max) == 0)) { return false; diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/NullFalseLeafBinaryFunction.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/NullFalseLeafBinaryFunction.java index 2dfdf22d4..da170834e 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/NullFalseLeafBinaryFunction.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/NullFalseLeafBinaryFunction.java @@ -28,7 +28,7 @@ import java.util.List; /** Function to test a field with a literal. */ public abstract class NullFalseLeafBinaryFunction extends LeafFunction { - private static final long serialVersionUID = 5617091663961558170L; + private static final long serialVersionUID = 1L; public abstract boolean test(DataType type, Object field, Object literal); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/Or.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/Or.java index 744717435..3b8ae43d2 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/Or.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/Or.java @@ -30,7 +30,7 @@ import java.util.Optional; /** A {@link CompoundPredicate.Function} to eval or. */ public class Or extends CompoundPredicate.Function { - private static final long serialVersionUID = -2110346319473699418L; + private static final long serialVersionUID = 1L; public static final Or INSTANCE = new Or(); @@ -46,6 +46,21 @@ public class Or extends CompoundPredicate.Function { return false; } + @Override + public boolean test( + long rowCount, + InternalRow minValues, + InternalRow maxValues, + Long[] nullCounts, + List<Predicate> children) { + for (Predicate child : children) { + if (child.test(rowCount, minValues, maxValues, nullCounts)) { + return true; + } + } + return false; + } + @Override public Optional<Predicate> negate(List<Predicate> children) { List<Predicate> negatedChildren = new ArrayList<>(); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/Predicate.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/Predicate.java index 7083a33d5..ecb65d1be 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/Predicate.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/Predicate.java @@ -17,6 +17,7 @@ package com.alibaba.fluss.predicate; +import com.alibaba.fluss.annotation.PublicEvolving; import com.alibaba.fluss.row.InternalRow; import java.io.Serializable; @@ -30,19 +31,26 @@ import java.util.Optional; * Predicate which returns Boolean and provides testing by stats. * * @see PredicateBuilder - * @since 0.4.0 + * @since 0.8 */ +@PublicEvolving public interface Predicate extends Serializable { /** - * Now only support test based on the specific input row. Todo: boolean test(long rowCount, - * InternalRow minValues, InternalRow maxValues, InternalArray nullCounts); Test based on the - * specific input row. + * Test based on the specific input row. * * @return return true when hit, false when not hit. */ boolean test(InternalRow row); + /** + * Test based on the statistical information to determine whether a hit is possible. + * + * @return return true is likely to hit (there may also be false positives), return false is + * absolutely not possible to hit. + */ + boolean test(long rowCount, InternalRow minValues, InternalRow maxValues, Long[] nullCounts); + /** @return the negation predicate of this predicate if possible. */ Optional<Predicate> negate(); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateBuilder.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateBuilder.java index 975954c2a..525c0e996 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateBuilder.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateBuilder.java @@ -17,6 +17,7 @@ package com.alibaba.fluss.predicate; +import com.alibaba.fluss.annotation.PublicEvolving; import com.alibaba.fluss.row.BinaryString; import com.alibaba.fluss.row.Decimal; import com.alibaba.fluss.row.TimestampLtz; @@ -60,10 +61,14 @@ import static java.util.Collections.singletonList; /** * A utility class to create {@link Predicate} object for common filter conditions. * - * @since 0.4.0 + * @since 0.8 */ +@PublicEvolving public class PredicateBuilder { + private static final LocalDate EPOCH_DAY = + Instant.ofEpochSecond(0).atOffset(ZoneOffset.UTC).toLocalDate(); + private final RowType rowType; private final List<String> fieldNames; @@ -168,7 +173,7 @@ public class PredicateBuilder { public static Predicate and(List<Predicate> predicates) { checkArgument( - predicates.size() > 0, + !predicates.isEmpty(), "There must be at least 1 inner predicate to construct an AND predicate"); if (predicates.size() == 1) { return predicates.get(0); @@ -199,8 +204,11 @@ public class PredicateBuilder { public static Predicate or(List<Predicate> predicates) { checkArgument( - predicates.size() > 0, + !predicates.isEmpty(), "There must be at least 1 inner predicate to construct an OR predicate"); + if (predicates.size() == 1) { + return predicates.get(0); + } return predicates.stream() .reduce((a, b) -> new CompoundPredicate(Or.INSTANCE, Arrays.asList(a, b))) .get(); @@ -280,9 +288,7 @@ public class PredicateBuilder { throw new UnsupportedOperationException( "Unexpected date literal of class " + o.getClass().getName()); } - LocalDate epochDay = - Instant.ofEpochSecond(0).atOffset(ZoneOffset.UTC).toLocalDate(); - return (int) ChronoUnit.DAYS.between(epochDay, localDate); + return (int) ChronoUnit.DAYS.between(EPOCH_DAY, localDate); case TIME_WITHOUT_TIME_ZONE: LocalTime localTime; if (o instanceof java.sql.Time) { @@ -409,10 +415,20 @@ public class PredicateBuilder { .collect(Collectors.toList()); } + /** + * Creates a {@link Predicate} that represents a condition where partition fields are equal to + * the specified partition values. + * + * @param partitionSpec A map containing partition field names as keys and their corresponding + * values as strings. + * @param rowType The {@link RowType} describing the schema of the row, including field names + * and types. + * @return A {@link Predicate} representing the equality conditions for the partition fields, or + * {@code null} if no conditions are specified. + */ @Nullable - public static Predicate partition( - Map<String, String> map, RowType rowType, String defaultPartValue) { - Map<String, Object> internalValues = convertSpecToInternal(map, rowType, defaultPartValue); + public static Predicate partition(Map<String, String> partitionSpec, RowType rowType) { + Map<String, Object> internalValues = convertSpecToInternal(partitionSpec, rowType); List<String> fieldNames = rowType.getFieldNames(); Predicate predicate = null; PredicateBuilder builder = new PredicateBuilder(rowType); @@ -430,24 +446,21 @@ public class PredicateBuilder { return predicate; } - public static Predicate partitions( - List<Map<String, String>> partitions, RowType rowType, String defaultPartValue) { + public static Predicate partitions(List<Map<String, String>> partitions, RowType rowType) { return PredicateBuilder.or( partitions.stream() - .map(p -> PredicateBuilder.partition(p, rowType, defaultPartValue)) + .map(p -> PredicateBuilder.partition(p, rowType)) .toArray(Predicate[]::new)); } public static Map<String, Object> convertSpecToInternal( - Map<String, String> spec, RowType partType, String defaultPartValue) { + Map<String, String> spec, RowType partType) { Map<String, Object> partValues = new LinkedHashMap<>(); for (Map.Entry<String, String> entry : spec.entrySet()) { partValues.put( entry.getKey(), - defaultPartValue.equals(entry.getValue()) - ? null - : TypeUtils.castFromString( - entry.getValue(), partType.getField(entry.getKey()).getType())); + TypeUtils.castFromString( + entry.getValue(), partType.getField(entry.getKey()).getType())); } return partValues; } diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateVisitor.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateVisitor.java index 3e20e9c92..fb6e854d2 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateVisitor.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/PredicateVisitor.java @@ -21,7 +21,14 @@ package com.alibaba.fluss.predicate; * Software Foundation (ASF) under the Apache License, Version 2.0. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. */ -/** A visitor to visit {@link Predicate}. */ +import com.alibaba.fluss.annotation.PublicEvolving; + +/** + * A visitor to visit {@link Predicate}. + * + * @since 0.8 + */ +@PublicEvolving public interface PredicateVisitor<T> { T visit(LeafPredicate predicate); diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/StartsWith.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/StartsWith.java index e3baa41fa..88205c156 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/StartsWith.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/StartsWith.java @@ -32,6 +32,8 @@ import java.util.Optional; */ public class StartsWith extends NullFalseLeafBinaryFunction { + private static final long serialVersionUID = 1L; + public static final StartsWith INSTANCE = new StartsWith(); private StartsWith() {} diff --git a/fluss-common/src/main/java/com/alibaba/fluss/predicate/UnsupportedExpression.java b/fluss-common/src/main/java/com/alibaba/fluss/predicate/UnsupportedExpression.java index 74b50f2f4..f0eddff5a 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/predicate/UnsupportedExpression.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/predicate/UnsupportedExpression.java @@ -19,6 +19,9 @@ package com.alibaba.fluss.predicate; /** Encounter an unsupported expression, the caller can choose to ignore this filter branch. */ public class UnsupportedExpression extends RuntimeException { + + private static final long serialVersionUID = 1L; + public UnsupportedExpression(String message) { super(message); } diff --git a/fluss-common/src/main/java/com/alibaba/fluss/types/RowType.java b/fluss-common/src/main/java/com/alibaba/fluss/types/RowType.java index 840d0a86b..da215e165 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/types/RowType.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/types/RowType.java @@ -67,6 +67,16 @@ public final class RowType extends DataType { return fields.stream().map(DataField::getName).collect(Collectors.toList()); } + public DataField getField(String fieldName) { + for (DataField field : fields) { + if (field.getName().equals(fieldName)) { + return field; + } + } + + throw new RuntimeException("Cannot find field: " + fieldName); + } + public DataType getTypeAt(int i) { return fields.get(i).getType(); } @@ -255,14 +265,4 @@ public final class RowType extends DataType { return new RowType(isNullable, fields); } } - - public DataField getField(String fieldName) { - for (DataField field : fields) { - if (field.getName().equals(fieldName)) { - return field; - } - } - - throw new RuntimeException("Cannot find field: " + fieldName); - } } diff --git a/fluss-common/src/main/java/com/alibaba/fluss/utils/BinaryStringUtils.java b/fluss-common/src/main/java/com/alibaba/fluss/utils/BinaryStringUtils.java index 78c792920..70967c362 100644 --- a/fluss-common/src/main/java/com/alibaba/fluss/utils/BinaryStringUtils.java +++ b/fluss-common/src/main/java/com/alibaba/fluss/utils/BinaryStringUtils.java @@ -70,15 +70,15 @@ public class BinaryStringUtils { return date; } - /** Used by {@code CAST(x as TIMESTAMPNTZ)}. */ + /** Used by {@code CAST(x as TIMESTAMP_NTZ)}. */ public static TimestampNtz toTimestampNtz(BinaryString input, int precision) throws DateTimeException { return DateTimeUtils.parseTimestampData(input.toString(), precision); } - /** Used by {@code CAST(x as TIMESTAMPLTZ)}. */ - public static TimestampLtz toTimestampltz(BinaryString input, int precision, TimeZone timeZone) - throws DateTimeException { - return DateTimeUtils.parseTimestampData(input.toString(), precision, timeZone); + /** Used by {@code CAST(x as TIMESTAMP_LTZ)}. */ + public static TimestampLtz toTimestampLtz( + BinaryString input, int precision, TimeZone localTimeZone) throws DateTimeException { + return DateTimeUtils.parseTimestampData(input.toString(), precision, localTimeZone); } } diff --git a/fluss-common/src/test/java/com/alibaba/fluss/predicate/PredicateBuilderTest.java b/fluss-common/src/test/java/com/alibaba/fluss/predicate/PredicateBuilderTest.java index acb193fe3..a47275bc6 100644 --- a/fluss-common/src/test/java/com/alibaba/fluss/predicate/PredicateBuilderTest.java +++ b/fluss-common/src/test/java/com/alibaba/fluss/predicate/PredicateBuilderTest.java @@ -17,14 +17,16 @@ package com.alibaba.fluss.predicate; -import com.alibaba.fluss.row.GenericRow; import com.alibaba.fluss.types.IntType; import com.alibaba.fluss.types.RowType; import org.junit.jupiter.api.Test; +import javax.annotation.Nullable; + import java.util.Arrays; +import static com.alibaba.fluss.testutils.DataTestUtils.row; import static org.assertj.core.api.Assertions.assertThat; /** Tests for {@link PredicateBuilder}. */ @@ -35,11 +37,17 @@ public class PredicateBuilderTest { PredicateBuilder builder = new PredicateBuilder(RowType.of(new IntType())); Predicate predicate = builder.between(0, 1, 3); - assertThat(predicate.test(GenericRow.of(1))).isEqualTo(true); - assertThat(predicate.test(GenericRow.of(2))).isEqualTo(true); - assertThat(predicate.test(GenericRow.of(3))).isEqualTo(true); - assertThat(predicate.test(GenericRow.of(4))).isEqualTo(false); - assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + assertThat(predicate.test(row(1))).isEqualTo(true); + assertThat(predicate.test(row(2))).isEqualTo(true); + assertThat(predicate.test(row(3))).isEqualTo(true); + assertThat(predicate.test(row(4))).isEqualTo(false); + assertThat(predicate.test(row((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 2, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 0, 2, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -47,11 +55,17 @@ public class PredicateBuilderTest { PredicateBuilder builder = new PredicateBuilder(RowType.of(new IntType())); Predicate predicate = builder.between(0, 1, null); - assertThat(predicate.test(GenericRow.of(1))).isEqualTo(false); - assertThat(predicate.test(GenericRow.of(2))).isEqualTo(false); - assertThat(predicate.test(GenericRow.of(3))).isEqualTo(false); - assertThat(predicate.test(GenericRow.of(4))).isEqualTo(false); - assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + assertThat(predicate.test(row(1))).isEqualTo(false); + assertThat(predicate.test(row(2))).isEqualTo(false); + assertThat(predicate.test(row(3))).isEqualTo(false); + assertThat(predicate.test(row(4))).isEqualTo(false); + assertThat(predicate.test(row((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 2, 5, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 0, 2, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -82,4 +96,13 @@ public class PredicateBuilderTest { builder.isNull(5), child3)); } + + static boolean test( + Predicate predicate, + long rowCount, + @Nullable Object min, + @Nullable Object max, + @Nullable Long nullCount) { + return predicate.test(rowCount, row(min), row(max), new Long[] {nullCount}); + } } diff --git a/fluss-common/src/test/java/com/alibaba/fluss/predicate/PredicateTest.java b/fluss-common/src/test/java/com/alibaba/fluss/predicate/PredicateTest.java index 427fec8a8..f5e1e30aa 100644 --- a/fluss-common/src/test/java/com/alibaba/fluss/predicate/PredicateTest.java +++ b/fluss-common/src/test/java/com/alibaba/fluss/predicate/PredicateTest.java @@ -28,7 +28,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static com.alibaba.fluss.predicate.PredicateBuilderTest.test; import static com.alibaba.fluss.row.BinaryString.fromString; +import static com.alibaba.fluss.testutils.DataTestUtils.row; import static org.assertj.core.api.Assertions.assertThat; /** Test for {@link Predicate}s. */ @@ -43,6 +45,11 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(5))).isEqualTo(true); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 0, 6, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); + assertThat(predicate.negate().orElse(null)).isEqualTo(builder.notEqual(0, 5)); } @@ -51,6 +58,10 @@ public class PredicateTest { PredicateBuilder builder = new PredicateBuilder(RowType.of(new IntType())); Predicate predicate = builder.equal(0, null); + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(false); + // null not equal to null + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); + assertThat(predicate.test(GenericRow.of(4))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); } @@ -64,6 +75,12 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(5))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 0, 6, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, 5, 5, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); + assertThat(predicate.negate().orElse(null)).isEqualTo(builder.equal(0, 5)); } @@ -74,6 +91,9 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(4))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -86,6 +106,12 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(6))).isEqualTo(true); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + assertThat(test(predicate, 3, 0, 4, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 0, 6, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, 6, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); + assertThat(predicate.negate().orElse(null)).isEqualTo(builder.lessOrEqual(0, 5)); } @@ -96,6 +122,9 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(4))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 1, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -108,6 +137,12 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(6))).isEqualTo(true); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + assertThat(test(predicate, 3, 0, 4, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 0, 6, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, 6, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); + assertThat(predicate.negate().orElse(null)).isEqualTo(builder.lessThan(0, 5)); } @@ -118,6 +153,9 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(4))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 1, 0, 4, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -130,6 +168,12 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(6))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 5, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 4, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, 3, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); + assertThat(predicate.negate().orElse(null)).isEqualTo(builder.greaterOrEqual(0, 5)); } @@ -140,6 +184,9 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(4))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 1, 3, 5, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -152,6 +199,12 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(6))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 5, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 4, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, 3, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); + assertThat(predicate.negate().orElse(null)).isEqualTo(builder.greaterThan(0, 5)); } @@ -162,6 +215,9 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(4))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 1, 3, 5, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -172,6 +228,11 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(4))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 5, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 5, 7, 1L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(true); + assertThat(predicate.negate().orElse(null)).isEqualTo(builder.isNotNull(0)); } @@ -183,6 +244,11 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(4))).isEqualTo(true); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 5, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 5, 7, 1L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); + assertThat(predicate.negate().orElse(null)).isEqualTo(builder.isNull(0)); } @@ -196,6 +262,10 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(2))).isEqualTo(false); assertThat(predicate.test(GenericRow.of(3))).isEqualTo(true); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -208,6 +278,10 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(2))).isEqualTo(false); assertThat(predicate.test(GenericRow.of(3))).isEqualTo(true); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -220,6 +294,13 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(2))).isEqualTo(true); assertThat(predicate.test(GenericRow.of(3))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 1, 1, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 3, 3, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 1, 3, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -232,6 +313,13 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(2))).isEqualTo(false); assertThat(predicate.test(GenericRow.of(3))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 1, 1, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 3, 3, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 1, 3, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -239,8 +327,13 @@ public class PredicateTest { PredicateBuilder builder = new PredicateBuilder(RowType.of(new StringType())); Predicate predicate = builder.endsWith(0, ("bcc")); GenericRow row = GenericRow.of(fromString("aabbcc")); - assertThat(predicate.test(row)).isEqualTo(true); + + GenericRow max = row(fromString("aaba")); + GenericRow min = row(fromString("aabb")); + Long[] nullCounts = {null}; + assertThat(predicate.test(10, min, max, nullCounts)).isEqualTo(true); + assertThat(predicate.test(10, min, max, new Long[] {10L})).isEqualTo(false); } @Test @@ -248,8 +341,17 @@ public class PredicateTest { PredicateBuilder builder = new PredicateBuilder(RowType.of(new StringType())); Predicate predicate = builder.startsWith(0, ("aab")); GenericRow row = GenericRow.of(fromString("aabbcc")); - assertThat(predicate.test(row)).isEqualTo(true); + + GenericRow aaab = row(fromString("aaab")); + GenericRow aaba = row(fromString("aaba")); + GenericRow bbaa = row(fromString("bbaa")); + GenericRow ccbb = row(fromString("ccbb")); + Long[] nullCounts = {null}; + assertThat(predicate.test(10, aaab, aaba, nullCounts)).isEqualTo(true); + assertThat(predicate.test(10, aaba, bbaa, nullCounts)).isEqualTo(true); + assertThat(predicate.test(10, bbaa, ccbb, nullCounts)).isEqualTo(false); + assertThat(predicate.test(10, aaab, aaba, new Long[] {10L})).isEqualTo(false); } @Test @@ -258,9 +360,14 @@ public class PredicateTest { Predicate predicate = builder.contains(0, ("def")); GenericRow row1 = GenericRow.of(fromString("aabbdefcc")); GenericRow row2 = GenericRow.of(fromString("aabbdcefcc")); - assertThat(predicate.test(row1)).isEqualTo(true); assertThat(predicate.test(row2)).isEqualTo(false); + + GenericRow aaab = row(fromString("aaab")); + GenericRow aaba = row(fromString("aaba")); + Long[] nullCounts = {null}; + assertThat(predicate.test(10, aaab, aaba, nullCounts)).isEqualTo(true); + assertThat(predicate.test(10, aaab, aaba, new Long[] {10L})).isEqualTo(false); } @Test @@ -279,6 +386,11 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(2))).isEqualTo(false); assertThat(predicate.test(GenericRow.of(3))).isEqualTo(true); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 29, 32, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -298,6 +410,11 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(2))).isEqualTo(false); assertThat(predicate.test(GenericRow.of(3))).isEqualTo(true); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 29, 32, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -316,6 +433,14 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(2))).isEqualTo(true); assertThat(predicate.test(GenericRow.of(3))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 1, 1, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 3, 3, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 1, 3, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(true); + assertThat(test(predicate, 3, 29, 32, 0L)).isEqualTo(true); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -335,6 +460,14 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(2))).isEqualTo(false); assertThat(predicate.test(GenericRow.of(3))).isEqualTo(false); assertThat(predicate.test(GenericRow.of((Object) null))).isEqualTo(false); + + assertThat(test(predicate, 3, 1, 1, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 3, 3, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 1, 3, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 0, 5, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 6, 7, 0L)).isEqualTo(false); + assertThat(test(predicate, 3, 29, 32, 0L)).isEqualTo(false); + assertThat(test(predicate, 1, null, null, 1L)).isEqualTo(false); } @Test @@ -347,6 +480,10 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(3, 5))).isEqualTo(true); assertThat(predicate.test(GenericRow.of(null, 5))).isEqualTo(false); + assertThat(predicate.test(3, row(3, 4), row(6, 6), new Long[] {0L, 0L})).isEqualTo(true); + assertThat(predicate.test(3, row(3, 6), row(6, 8), new Long[] {0L, 0L})).isEqualTo(false); + assertThat(predicate.test(3, row(6, 4), row(7, 6), new Long[] {0L, 0L})).isEqualTo(false); + assertThat(predicate.negate().orElse(null)) .isEqualTo(PredicateBuilder.or(builder.notEqual(0, 3), builder.notEqual(1, 5))); } @@ -361,6 +498,10 @@ public class PredicateTest { assertThat(predicate.test(GenericRow.of(3, 5))).isEqualTo(true); assertThat(predicate.test(GenericRow.of(null, 5))).isEqualTo(true); + assertThat(predicate.test(3, row(3, 4), row(6, 6), new Long[] {0L, 0L})).isEqualTo(true); + assertThat(predicate.test(3, row(3, 6), row(6, 8), new Long[] {0L, 0L})).isEqualTo(true); + assertThat(predicate.test(3, row(6, 8), row(7, 10), new Long[] {0L, 0L})).isEqualTo(false); + assertThat(predicate.negate().orElse(null)) .isEqualTo(PredicateBuilder.and(builder.notEqual(0, 3), builder.notEqual(1, 5))); } @@ -369,6 +510,9 @@ public class PredicateTest { public void testUnknownStats() { PredicateBuilder builder = new PredicateBuilder(RowType.of(new IntType())); Predicate predicate = builder.equal(0, 5); + + assertThat(test(predicate, 3, null, null, null)).isEqualTo(true); + assertThat(test(predicate, 3, null, null, 3L)).isEqualTo(false); } @Test