This is an automated email from the ASF dual-hosted git repository.
JingsongLi 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 bdd6e415d3 [parquet] Push down StartsWith as Parquet binary range
(#9594)
bdd6e415d3 is described below
commit bdd6e415d3313f503522322e8ae58a9c469ab239
Author: sanshi <[email protected]>
AuthorDate: Thu Sep 3 22:08:14 2026 +0800
[parquet] Push down StartsWith as Parquet binary range (#9594)
---
.../parquet/filter2/predicate/ParquetFilters.java | 30 ++++++++++++++++-
.../paimon/format/parquet/ParquetFiltersTest.java | 38 ++++++++++++++++++++++
2 files changed, 67 insertions(+), 1 deletion(-)
diff --git
a/paimon-format/src/main/java/org/apache/parquet/filter2/predicate/ParquetFilters.java
b/paimon-format/src/main/java/org/apache/parquet/filter2/predicate/ParquetFilters.java
index e28b8ee437..57d27578a1 100644
---
a/paimon-format/src/main/java/org/apache/parquet/filter2/predicate/ParquetFilters.java
+++
b/paimon-format/src/main/java/org/apache/parquet/filter2/predicate/ParquetFilters.java
@@ -186,7 +186,21 @@ public class ParquetFilters {
@Override
public FilterPredicate visitStartsWith(FieldRef fieldRef, Object
literal) {
- throw new UnsupportedOperationException();
+ Operators.Column<?> column = toParquetColumn(fieldRef);
+ if (!(column instanceof Operators.BinaryColumn)) {
+ throw new UnsupportedOperationException();
+ }
+
+ Binary prefix = (Binary) toParquetObject(literal, fieldRef);
+ if (prefix.length() == 0) {
+ throw new UnsupportedOperationException();
+ }
+
+ FilterPredicate lower = FilterApi.gtEq((Operators.BinaryColumn)
column, prefix);
+ Binary upper = nextBinary(prefix);
+ return upper == null
+ ? lower
+ : FilterApi.and(lower,
FilterApi.lt((Operators.BinaryColumn) column, upper));
}
@Override
@@ -418,6 +432,20 @@ public class ParquetFilters {
throw new UnsupportedOperationException();
}
+ /** Returns the smallest binary value strictly greater than all values
with this prefix. */
+ @Nullable
+ private Binary nextBinary(Binary prefix) {
+ byte[] bytes = prefix.getBytes();
+ for (int i = bytes.length - 1; i >= 0; i--) {
+ int value = bytes[i] & 0xff;
+ if (value != 0xff) {
+ bytes[i] = (byte) (value + 1);
+ return Binary.fromConstantByteArray(bytes, 0, i + 1);
+ }
+ }
+ return null;
+ }
+
private Decimal normalizeDecimal(Decimal decimal, DecimalType
fieldType) {
try {
BigDecimal normalized =
diff --git
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetFiltersTest.java
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetFiltersTest.java
index e6f4c44260..fd677c3849 100644
---
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetFiltersTest.java
+++
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetFiltersTest.java
@@ -215,6 +215,44 @@ class ParquetFiltersTest {
true);
}
+ @Test
+ public void testStartsWithIsPushedToParquet() {
+ RowType rowType =
+ new RowType(
+ Collections.singletonList(new DataField(0, "string1",
new VarCharType())));
+ MessageType schema =
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+ PredicateBuilder builder = new PredicateBuilder(rowType);
+
+ FilterCompat.Filter filter =
+ ParquetFilters.convert(
+ Collections.singletonList(builder.startsWith(0,
"abc")), schema, true);
+ assertThat(filter).isInstanceOf(FilterPredicateCompat.class);
+ FilterPredicate parquetPredicate = ((FilterPredicateCompat)
filter).getFilterPredicate();
+ assertThat(parquetPredicate)
+ .isEqualTo(
+ FilterApi.and(
+ FilterApi.gtEq(
+ FilterApi.binaryColumn("string1"),
+ Binary.fromString("abc")),
+ FilterApi.lt(
+ FilterApi.binaryColumn("string1"),
+ Binary.fromString("abd"))));
+ }
+
+ @Test
+ public void testEndsWithIsNotPushedToParquet() {
+ RowType rowType =
+ new RowType(
+ Collections.singletonList(new DataField(0, "string1",
new VarCharType())));
+ MessageType schema =
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+ PredicateBuilder builder = new PredicateBuilder(rowType);
+
+ FilterCompat.Filter filter =
+ ParquetFilters.convert(
+ Collections.singletonList(builder.endsWith(0, "abc")),
schema, true);
+ assertThat(filter).isEqualTo(FilterCompat.NOOP);
+ }
+
@Test
public void testInFilterLong() {
RowType rowType =