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 c81dcd5a59 [core] Support array set predicates in multivalue index 
(#9294)
c81dcd5a59 is described below

commit c81dcd5a59627e838f209ed8694189319e0e9e5f
Author: Jingsong Lee <[email protected]>
AuthorDate: Tue Aug 18 23:27:18 2026 +0800

    [core] Support array set predicates in multivalue index (#9294)
---
 docs/docs/multimodal-table/global-index.mdx        |  2 +-
 .../multimodal-table/global-index/multivalue.mdx   | 28 ++++--
 docs/docs/primary-key-table/global-index.mdx       | 20 +++--
 .../apache/paimon/fileindex/FileIndexReader.java   | 10 +++
 .../globalindex/ConstantGlobalIndexReader.java     | 12 +++
 .../paimon/globalindex/GlobalIndexReader.java      | 12 +++
 .../globalindex/OffsetGlobalIndexReader.java       | 12 +++
 .../paimon/globalindex/SortedFileMetaSelector.java | 12 +++
 .../paimon/globalindex/UnionGlobalIndexReader.java | 12 +++
 .../bitmap/MultiValueBitmapIndexReader.java        | 65 +++++++++++++-
 .../apache/paimon/predicate/ArrayContainsAll.java  | 99 ++++++++++++++++++++++
 .../org/apache/paimon/predicate/ArraysOverlap.java | 99 ++++++++++++++++++++++
 .../apache/paimon/predicate/FunctionVisitor.java   |  8 ++
 .../org/apache/paimon/predicate/LeafFunction.java  |  2 +
 .../predicate/OnlyPartitionKeyEqualVisitor.java    | 10 +++
 .../apache/paimon/predicate/PredicateBuilder.java  | 31 +++++++
 .../globalindex/GlobalIndexEvaluatorTest.java      | 58 +++++++++++++
 .../bitmap/MultiValueBitmapIndexReaderTest.java    | 40 +++++++++
 .../apache/paimon/predicate/LeafPredicateTest.java | 30 +++++++
 .../paimon/predicate/PredicateBuilderTest.java     | 41 +++++++++
 .../paimon/predicate/PredicateJsonSerdeTest.java   | 20 +++++
 .../table/source/PrimaryKeySortedIndexScan.java    | 30 +++++++
 .../table/MultiValueGlobalIndexTableTest.java      | 10 +++
 .../source/PrimaryKeySortedIndexScanTest.java      | 67 +++++++++++++++
 .../orc/filter/OrcPredicateFunctionVisitor.java    | 12 +++
 .../parquet/filter2/predicate/ParquetFilters.java  | 10 +++
 26 files changed, 731 insertions(+), 21 deletions(-)

diff --git a/docs/docs/multimodal-table/global-index.mdx 
b/docs/docs/multimodal-table/global-index.mdx
index a506a08759..ea0bd8588f 100644
--- a/docs/docs/multimodal-table/global-index.mdx
+++ b/docs/docs/multimodal-table/global-index.mdx
@@ -43,7 +43,7 @@ without full-table scans. Paimon supports multiple global 
index types:
 |---|---|---|
 | BTree | Scalar filters on numeric, string, date, and timestamp columns | 
Best when predicates are selective, such as equality, IN, range, and null 
checks. |
 | Bitmap | Enum-like dimensions and tag columns | Best for equality, IN, 
string prefix match, complement predicates, and null checks over compressed 
row-id bitmaps. |
-| Multivalue | Membership tests on arrays of supported scalar elements | Best 
for `ARRAY_CONTAINS(array_column, element)`. |
+| Multivalue | Membership tests on arrays of supported scalar elements | Best 
for `ARRAY_CONTAINS`, `ARRAYS_OVERLAP`, and `ARRAY_CONTAINS_ALL`. |
 | Vector | Top-K similarity search on embeddings | Uses ANN algorithms. Tune 
build-time and search-time options to balance recall, latency, and index size. |
 | Full-Text | Keyword search over text columns | Uses full-text scoring and 
tokenizer configuration stored with each index file. |
 | Hybrid Search | Combining multiple vector routes, multiple full-text routes, 
or vector and full-text retrieval together | Runs multiple scored routes and 
merges them with a ranker before reading rows. |
diff --git a/docs/docs/multimodal-table/global-index/multivalue.mdx 
b/docs/docs/multimodal-table/global-index/multivalue.mdx
index 484da0dcdf..200900b809 100644
--- a/docs/docs/multimodal-table/global-index/multivalue.mdx
+++ b/docs/docs/multimodal-table/global-index/multivalue.mdx
@@ -25,14 +25,18 @@ under the License.
 # Multivalue Index
 
 A Multivalue index maps every distinct non-null element of an `ARRAY` column 
to a compressed
-64-bit row-id bitmap. It accelerates element-membership predicates without 
treating the complete
-array as one scalar key.
+64-bit row-id bitmap. It accelerates `ARRAY_CONTAINS`, `ARRAYS_OVERLAP`, and
+`ARRAY_CONTAINS_ALL` predicates without treating the complete array as one 
scalar key.
 
 For membership lookup, the index has these semantics:
 
 - Duplicate elements in one array contribute the row ID only once.
 - Null arrays, empty arrays, and null elements contribute no posting. The 
index does not
   distinguish those cases and does not accelerate array `IS NULL` or `IS NOT 
NULL` predicates.
+- `ARRAYS_OVERLAP` unions the postings for its non-null literal elements.
+  `ARRAY_CONTAINS_ALL` intersects the postings for its distinct literal 
elements. A null literal
+  element cannot match; an empty contains-all literal safely falls back 
because the index cannot
+  distinguish a null array from a non-null array with no indexed elements.
 
 The indexed column must be an `ARRAY` whose element type is supported by the 
global-index key
 serializer. A Multivalue index is single-column.
@@ -77,21 +81,27 @@ coverage behavior.
 
 ## Query Through Paimon Core
 
-The Core predicate API exposes array membership directly:
+The Core predicate API exposes single-element, any-element, and all-element 
membership directly:
 
 ```java
 int tagsFieldIndex = table.rowType().getFieldIndex("tags");
-Predicate predicate = new PredicateBuilder(table.rowType())
-        .arrayContains(tagsFieldIndex, BinaryString.fromString("blue"));
-
-ReadBuilder readBuilder = table.newReadBuilder().withFilter(predicate);
+PredicateBuilder predicates = new PredicateBuilder(table.rowType());
+Predicate contains = predicates.arrayContains(tagsFieldIndex, 
BinaryString.fromString("blue"));
+Predicate overlaps = predicates.arraysOverlap(
+        tagsFieldIndex,
+        Arrays.asList(BinaryString.fromString("blue"), 
BinaryString.fromString("green")));
+Predicate containsAll = predicates.arrayContainsAll(
+        tagsFieldIndex,
+        Arrays.asList(BinaryString.fromString("blue"), 
BinaryString.fromString("green")));
+
+ReadBuilder readBuilder = table.newReadBuilder().withFilter(overlaps);
 TableScan.Plan plan = readBuilder.newScan().plan();
 ```
 
 Paimon automatically uses matching Multivalue global index files and maps 
their row IDs to Data
 Evolution splits. SQL engines use the index after their connector translates 
an array-membership
-expression to Paimon's `ARRAY_CONTAINS` predicate; that connector translation 
is separate from the
-Core index implementation.
+expression to Paimon's `ARRAY_CONTAINS`, `ARRAYS_OVERLAP`, or 
`ARRAY_CONTAINS_ALL` predicate; that
+connector translation is separate from the Core index implementation.
 
 ## Drop the Index
 
diff --git a/docs/docs/primary-key-table/global-index.mdx 
b/docs/docs/primary-key-table/global-index.mdx
index 2bd2f3886e..00361af4c7 100644
--- a/docs/docs/primary-key-table/global-index.mdx
+++ b/docs/docs/primary-key-table/global-index.mdx
@@ -65,12 +65,12 @@ See [Bitmap Index](../multimodal-table/global-index/bitmap) 
for predicate and fo
 <TabItem value="multivalue" label="Multivalue">
 
 Use Multivalue for element-membership predicates on an `ARRAY` column. It 
stores one compressed
-bitmap posting list per distinct non-null element and accelerates Paimon Core 
`ARRAY_CONTAINS`
-predicates. Null arrays, empty arrays, and null elements produce no membership 
posting; array null
-checks fall back to the ordinary data path.
+bitmap posting list per distinct non-null element and accelerates Paimon Core 
`ARRAY_CONTAINS`,
+`ARRAYS_OVERLAP`, and `ARRAY_CONTAINS_ALL` predicates. Null arrays, empty 
arrays, and null elements
+produce no membership posting; array null checks fall back to the ordinary 
data path.
 
 The Core predicate and index pushdown are available independently of SQL 
connectors. A connector
-uses this index after it translates its array-membership expression to 
Paimon's `ARRAY_CONTAINS`
+uses this index after it translates its array-membership expression to the 
corresponding Paimon
 predicate.
 
 For an append-only or Data Evolution table whose Multivalue index is built 
independently, see
@@ -323,19 +323,23 @@ become full-text searchable only after compaction 
publishes an eligible data fil
 BTree, Bitmap, and Multivalue indexes are applied automatically to 
snapshot-scoped Core batch
 scans after a supported predicate reaches Paimon. BTree and Bitmap can 
accelerate equality, `IN`,
 null checks, comparisons and ranges, complement predicates, and supported 
string predicates.
-Multivalue accelerates `ARRAY_CONTAINS(array_column, element)`.
+Multivalue accelerates `ARRAY_CONTAINS`, `ARRAYS_OVERLAP`, and 
`ARRAY_CONTAINS_ALL` predicates.
 
 The Java/Core predicate can be constructed directly:
 
 ```java
 Predicate predicate = new PredicateBuilder(rowType)
-        .arrayContains(tagsFieldIndex, BinaryString.fromString("blue"));
+        .arraysOverlap(
+                tagsFieldIndex,
+                Arrays.asList(
+                        BinaryString.fromString("blue"),
+                        BinaryString.fromString("green")));
 ReadBuilder readBuilder = table.newReadBuilder().withFilter(predicate);
 TableScan.Plan plan = readBuilder.newScan().plan();
 ```
 
-SQL engines need a connector-specific expression conversion before their 
`array_contains` or
-equivalent function reaches this Core predicate.
+SQL engines need a connector-specific expression conversion before their 
array-membership
+function reaches the corresponding Core predicate.
 
 Paimon combines indexed predicate results as follows:
 
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 1dca420976..cf4e562c8b 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
@@ -68,6 +68,16 @@ public abstract class FileIndexReader implements 
FunctionVisitor<FileIndexResult
         return REMAIN;
     }
 
+    @Override
+    public FileIndexResult visitArraysOverlap(FieldRef fieldRef, List<Object> 
literals) {
+        return REMAIN;
+    }
+
+    @Override
+    public FileIndexResult visitArrayContainsAll(FieldRef fieldRef, 
List<Object> literals) {
+        return REMAIN;
+    }
+
     @Override
     public FileIndexResult visitLike(FieldRef fieldRef, Object literal) {
         return REMAIN;
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/ConstantGlobalIndexReader.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/ConstantGlobalIndexReader.java
index dd0c87b9b7..380d4aa5e9 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/ConstantGlobalIndexReader.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/ConstantGlobalIndexReader.java
@@ -78,6 +78,18 @@ public class ConstantGlobalIndexReader implements 
GlobalIndexReader {
         return result;
     }
 
+    @Override
+    public CompletableFuture<Optional<GlobalIndexResult>> visitArraysOverlap(
+            FieldRef fieldRef, List<Object> literals) {
+        return result;
+    }
+
+    @Override
+    public CompletableFuture<Optional<GlobalIndexResult>> 
visitArrayContainsAll(
+            FieldRef fieldRef, List<Object> literals) {
+        return result;
+    }
+
     @Override
     public CompletableFuture<Optional<GlobalIndexResult>> visitLike(
             FieldRef fieldRef, Object literal) {
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexReader.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexReader.java
index 065bd8814e..f1098cf68e 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexReader.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexReader.java
@@ -47,6 +47,18 @@ public interface GlobalIndexReader
         return CompletableFuture.completedFuture(Optional.empty());
     }
 
+    @Override
+    default CompletableFuture<Optional<GlobalIndexResult>> visitArraysOverlap(
+            FieldRef fieldRef, List<Object> literals) {
+        return CompletableFuture.completedFuture(Optional.empty());
+    }
+
+    @Override
+    default CompletableFuture<Optional<GlobalIndexResult>> 
visitArrayContainsAll(
+            FieldRef fieldRef, List<Object> literals) {
+        return CompletableFuture.completedFuture(Optional.empty());
+    }
+
     @Override
     default CompletableFuture<Optional<GlobalIndexResult>> visitBetween(
             FieldRef fieldRef, Object from, Object to) {
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/OffsetGlobalIndexReader.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/OffsetGlobalIndexReader.java
index 585984dcc0..64be91a8f3 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/OffsetGlobalIndexReader.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/OffsetGlobalIndexReader.java
@@ -80,6 +80,18 @@ public class OffsetGlobalIndexReader implements 
GlobalIndexReader {
         return wrapped.visitArrayContains(fieldRef, 
literal).thenApply(this::applyOffset);
     }
 
+    @Override
+    public CompletableFuture<Optional<GlobalIndexResult>> visitArraysOverlap(
+            FieldRef fieldRef, List<Object> literals) {
+        return wrapped.visitArraysOverlap(fieldRef, 
literals).thenApply(this::applyOffset);
+    }
+
+    @Override
+    public CompletableFuture<Optional<GlobalIndexResult>> 
visitArrayContainsAll(
+            FieldRef fieldRef, List<Object> literals) {
+        return wrapped.visitArrayContainsAll(fieldRef, 
literals).thenApply(this::applyOffset);
+    }
+
     @Override
     public CompletableFuture<Optional<GlobalIndexResult>> visitLike(
             FieldRef fieldRef, Object literal) {
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedFileMetaSelector.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedFileMetaSelector.java
index b12ade7a2e..6b7e1f01a4 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedFileMetaSelector.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedFileMetaSelector.java
@@ -107,6 +107,18 @@ public class SortedFileMetaSelector implements 
FunctionVisitor<Optional<List<Glo
         return Optional.empty();
     }
 
+    @Override
+    public Optional<List<GlobalIndexIOMeta>> visitArraysOverlap(
+            FieldRef fieldRef, List<Object> literals) {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<List<GlobalIndexIOMeta>> visitArrayContainsAll(
+            FieldRef fieldRef, List<Object> literals) {
+        return Optional.empty();
+    }
+
     @Override
     public Optional<List<GlobalIndexIOMeta>> visitLike(FieldRef fieldRef, 
Object literal) {
         return Optional.of(filter(meta -> literal != null && 
!meta.onlyNulls()));
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/UnionGlobalIndexReader.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/UnionGlobalIndexReader.java
index c2316b9427..8bee57ebe8 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/UnionGlobalIndexReader.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/UnionGlobalIndexReader.java
@@ -83,6 +83,18 @@ public class UnionGlobalIndexReader implements 
GlobalIndexReader {
         return unionAsync(reader -> reader.visitArrayContains(fieldRef, 
literal));
     }
 
+    @Override
+    public CompletableFuture<Optional<GlobalIndexResult>> visitArraysOverlap(
+            FieldRef fieldRef, List<Object> literals) {
+        return unionAsync(reader -> reader.visitArraysOverlap(fieldRef, 
literals));
+    }
+
+    @Override
+    public CompletableFuture<Optional<GlobalIndexResult>> 
visitArrayContainsAll(
+            FieldRef fieldRef, List<Object> literals) {
+        return unionAsync(reader -> reader.visitArrayContainsAll(fieldRef, 
literals));
+    }
+
     @Override
     public CompletableFuture<Optional<GlobalIndexResult>> visitLike(
             FieldRef fieldRef, Object literal) {
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/MultiValueBitmapIndexReader.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/MultiValueBitmapIndexReader.java
index 3305e3afa7..f8856ba221 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/MultiValueBitmapIndexReader.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/MultiValueBitmapIndexReader.java
@@ -28,7 +28,10 @@ import org.apache.paimon.types.ArrayType;
 import org.apache.paimon.types.DataType;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
@@ -38,6 +41,7 @@ public class MultiValueBitmapIndexReader implements 
GlobalIndexReader {
 
     private final DataType elementType;
     private final boolean compatibleElementType;
+    private final KeySerializer keySerializer;
     private final LazyFilteredBitmapReader bitmapReader;
 
     MultiValueBitmapIndexReader(
@@ -48,6 +52,7 @@ public class MultiValueBitmapIndexReader implements 
GlobalIndexReader {
             long totalRowCount,
             ExecutorService executor) {
         this.elementType = elementType;
+        this.keySerializer = keySerializer;
         this.compatibleElementType =
                 files.stream()
                         .allMatch(
@@ -72,14 +77,62 @@ public class MultiValueBitmapIndexReader implements 
GlobalIndexReader {
     @Override
     public CompletableFuture<Optional<GlobalIndexResult>> visitArrayContains(
             FieldRef fieldRef, Object literal) {
-        if (!compatibleElementType
-                || !(fieldRef.type() instanceof ArrayType)
-                || !((ArrayType) 
fieldRef.type()).getElementType().equals(elementType)) {
+        if (!supports(fieldRef)) {
             return unsupported();
         }
         return bitmapReader.visitEqual(fieldRef, literal);
     }
 
+    @Override
+    public CompletableFuture<Optional<GlobalIndexResult>> visitArraysOverlap(
+            FieldRef fieldRef, List<Object> literals) {
+        if (!supports(fieldRef)) {
+            return unsupported();
+        }
+        return bitmapReader.visitIn(fieldRef, literals);
+    }
+
+    @Override
+    public CompletableFuture<Optional<GlobalIndexResult>> 
visitArrayContainsAll(
+            FieldRef fieldRef, List<Object> literals) {
+        if (!supports(fieldRef) || literals.isEmpty()) {
+            return unsupported();
+        }
+
+        Map<BitmapGlobalIndexFormat.SerializedKey, Object> distinctLiterals = 
new LinkedHashMap<>();
+        for (Object literal : literals) {
+            if (literal == null) {
+                return CompletableFuture.completedFuture(
+                        Optional.of(GlobalIndexResult.createEmpty()));
+            }
+            distinctLiterals.put(
+                    
BitmapGlobalIndexFormat.SerializedKey.fromObject(keySerializer, literal),
+                    literal);
+        }
+
+        List<CompletableFuture<Optional<GlobalIndexResult>>> futures =
+                new ArrayList<>(distinctLiterals.size());
+        for (Object literal : distinctLiterals.values()) {
+            futures.add(bitmapReader.visitEqual(fieldRef, literal));
+        }
+        return CompletableFuture.allOf(futures.toArray(new 
CompletableFuture[0]))
+                .thenApply(
+                        ignored -> {
+                            Optional<GlobalIndexResult> result = 
Optional.empty();
+                            for 
(CompletableFuture<Optional<GlobalIndexResult>> future : futures) {
+                                Optional<GlobalIndexResult> current = 
future.join();
+                                if (!current.isPresent()) {
+                                    return Optional.empty();
+                                }
+                                result =
+                                        result.isPresent()
+                                                ? 
Optional.of(result.get().and(current.get()))
+                                                : current;
+                            }
+                            return result;
+                        });
+    }
+
     @Override
     public CompletableFuture<Optional<GlobalIndexResult>> visitStartsWith(
             FieldRef fieldRef, Object literal) {
@@ -172,4 +225,10 @@ public class MultiValueBitmapIndexReader implements 
GlobalIndexReader {
     private static CompletableFuture<Optional<GlobalIndexResult>> 
unsupported() {
         return CompletableFuture.completedFuture(Optional.empty());
     }
+
+    private boolean supports(FieldRef fieldRef) {
+        return compatibleElementType
+                && fieldRef.type() instanceof ArrayType
+                && ((ArrayType) 
fieldRef.type()).getElementType().equals(elementType);
+    }
 }
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/predicate/ArrayContainsAll.java 
b/paimon-common/src/main/java/org/apache/paimon/predicate/ArrayContainsAll.java
new file mode 100644
index 0000000000..bcd598af8f
--- /dev/null
+++ 
b/paimon-common/src/main/java/org/apache/paimon/predicate/ArrayContainsAll.java
@@ -0,0 +1,99 @@
+/*
+ * 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.ArrayType;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.utils.Preconditions;
+
+import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator;
+
+import java.util.List;
+import java.util.Optional;
+
+/** A {@link LeafNAryFunction} testing whether an array contains all literal 
elements. */
+public class ArrayContainsAll extends LeafNAryFunction {
+
+    public static final String NAME = "ARRAY_CONTAINS_ALL";
+
+    public static final ArrayContainsAll INSTANCE = new ArrayContainsAll();
+
+    @JsonCreator
+    private ArrayContainsAll() {}
+
+    @Override
+    public DataType literalType(DataType fieldType) {
+        return elementType(fieldType);
+    }
+
+    @Override
+    public boolean test(DataType type, Object field, List<Object> literals) {
+        if (field == null) {
+            return false;
+        }
+        for (Object literal : literals) {
+            if (literal == null || !ArrayContains.INSTANCE.test(type, field, 
literal)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean test(
+            DataType type,
+            long rowCount,
+            Object min,
+            Object max,
+            Long nullCount,
+            List<Object> literals) {
+        if (nullCount != null && rowCount == nullCount) {
+            return false;
+        }
+        for (Object literal : literals) {
+            if (literal == null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public Optional<LeafFunction> negate() {
+        return Optional.empty();
+    }
+
+    @Override
+    public <T> T visit(FunctionVisitor<T> visitor, FieldRef fieldRef, 
List<Object> literals) {
+        return visitor.visitArrayContainsAll(fieldRef, literals);
+    }
+
+    @Override
+    public String toJson() {
+        return NAME;
+    }
+
+    static DataType elementType(DataType fieldType) {
+        Preconditions.checkArgument(
+                fieldType instanceof ArrayType,
+                "ARRAY_CONTAINS_ALL requires an ARRAY field, but field type is 
%s.",
+                fieldType);
+        return ((ArrayType) fieldType).getElementType();
+    }
+}
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/predicate/ArraysOverlap.java 
b/paimon-common/src/main/java/org/apache/paimon/predicate/ArraysOverlap.java
new file mode 100644
index 0000000000..5ff9070f2d
--- /dev/null
+++ b/paimon-common/src/main/java/org/apache/paimon/predicate/ArraysOverlap.java
@@ -0,0 +1,99 @@
+/*
+ * 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.ArrayType;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.utils.Preconditions;
+
+import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator;
+
+import java.util.List;
+import java.util.Optional;
+
+/** A {@link LeafNAryFunction} testing whether an array overlaps literal 
elements. */
+public class ArraysOverlap extends LeafNAryFunction {
+
+    public static final String NAME = "ARRAYS_OVERLAP";
+
+    public static final ArraysOverlap INSTANCE = new ArraysOverlap();
+
+    @JsonCreator
+    private ArraysOverlap() {}
+
+    @Override
+    public DataType literalType(DataType fieldType) {
+        return elementType(fieldType);
+    }
+
+    @Override
+    public boolean test(DataType type, Object field, List<Object> literals) {
+        if (field == null) {
+            return false;
+        }
+        for (Object literal : literals) {
+            if (literal != null && ArrayContains.INSTANCE.test(type, field, 
literal)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean test(
+            DataType type,
+            long rowCount,
+            Object min,
+            Object max,
+            Long nullCount,
+            List<Object> literals) {
+        if (nullCount != null && rowCount == nullCount) {
+            return false;
+        }
+        for (Object literal : literals) {
+            if (literal != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Optional<LeafFunction> negate() {
+        return Optional.empty();
+    }
+
+    @Override
+    public <T> T visit(FunctionVisitor<T> visitor, FieldRef fieldRef, 
List<Object> literals) {
+        return visitor.visitArraysOverlap(fieldRef, literals);
+    }
+
+    @Override
+    public String toJson() {
+        return NAME;
+    }
+
+    static DataType elementType(DataType fieldType) {
+        Preconditions.checkArgument(
+                fieldType instanceof ArrayType,
+                "ARRAYS_OVERLAP requires an ARRAY field, but field type is 
%s.",
+                fieldType);
+        return ((ArrayType) fieldType).getElementType();
+    }
+}
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/predicate/FunctionVisitor.java 
b/paimon-common/src/main/java/org/apache/paimon/predicate/FunctionVisitor.java
index 6216e1ce2d..6ffa97cee2 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/predicate/FunctionVisitor.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/predicate/FunctionVisitor.java
@@ -70,6 +70,14 @@ public interface FunctionVisitor<T> extends 
PredicateVisitor<T> {
         throw new UnsupportedOperationException();
     }
 
+    default T visitArraysOverlap(FieldRef fieldRef, List<Object> literals) {
+        throw new UnsupportedOperationException();
+    }
+
+    default T visitArrayContainsAll(FieldRef fieldRef, List<Object> literals) {
+        throw new UnsupportedOperationException();
+    }
+
     T visitLike(FieldRef fieldRef, Object literal);
 
     T visitLessThan(FieldRef fieldRef, Object literal);
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/predicate/LeafFunction.java 
b/paimon-common/src/main/java/org/apache/paimon/predicate/LeafFunction.java
index b4b50695e0..77326abd32 100644
--- a/paimon-common/src/main/java/org/apache/paimon/predicate/LeafFunction.java
+++ b/paimon-common/src/main/java/org/apache/paimon/predicate/LeafFunction.java
@@ -64,6 +64,8 @@ public abstract class LeafFunction implements Serializable {
             registry.put(EndsWith.NAME, EndsWith.INSTANCE);
             registry.put(Contains.NAME, Contains.INSTANCE);
             registry.put(ArrayContains.NAME, ArrayContains.INSTANCE);
+            registry.put(ArraysOverlap.NAME, ArraysOverlap.INSTANCE);
+            registry.put(ArrayContainsAll.NAME, ArrayContainsAll.INSTANCE);
             registry.put(Like.NAME, Like.INSTANCE);
             registry.put(In.NAME, In.INSTANCE);
             registry.put(NotIn.NAME, NotIn.INSTANCE);
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/predicate/OnlyPartitionKeyEqualVisitor.java
 
b/paimon-common/src/main/java/org/apache/paimon/predicate/OnlyPartitionKeyEqualVisitor.java
index 35f482924d..fb6e6e1c85 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/predicate/OnlyPartitionKeyEqualVisitor.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/predicate/OnlyPartitionKeyEqualVisitor.java
@@ -72,6 +72,16 @@ public class OnlyPartitionKeyEqualVisitor implements 
FunctionVisitor<Boolean> {
         return false;
     }
 
+    @Override
+    public Boolean visitArraysOverlap(FieldRef fieldRef, List<Object> 
literals) {
+        return false;
+    }
+
+    @Override
+    public Boolean visitArrayContainsAll(FieldRef fieldRef, List<Object> 
literals) {
+        return false;
+    }
+
     @Override
     public Boolean visitLike(FieldRef fieldRef, Object literal) {
         return false;
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 ed80a4f767..04e813cdcf 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
@@ -181,6 +181,28 @@ public class PredicateBuilder {
         return leaf(ArrayContains.INSTANCE, transform, elementLiteral);
     }
 
+    public Predicate arraysOverlap(int idx, List<?> elementLiterals) {
+        DataField field = rowType.getFields().get(idx);
+        ArraysOverlap.elementType(field.type());
+        return leaf(ArraysOverlap.INSTANCE, idx, new 
ArrayList<>(elementLiterals));
+    }
+
+    public Predicate arraysOverlap(Transform transform, List<?> 
elementLiterals) {
+        ArraysOverlap.elementType(transform.outputType());
+        return leaf(ArraysOverlap.INSTANCE, transform, new 
ArrayList<>(elementLiterals));
+    }
+
+    public Predicate arrayContainsAll(int idx, List<?> elementLiterals) {
+        DataField field = rowType.getFields().get(idx);
+        ArrayContainsAll.elementType(field.type());
+        return leaf(ArrayContainsAll.INSTANCE, idx, new 
ArrayList<>(elementLiterals));
+    }
+
+    public Predicate arrayContainsAll(Transform transform, List<?> 
elementLiterals) {
+        ArrayContainsAll.elementType(transform.outputType());
+        return leaf(ArrayContainsAll.INSTANCE, transform, new 
ArrayList<>(elementLiterals));
+    }
+
     public Predicate like(int idx, Object patternLiteral) {
         Pair<LeafBinaryFunction, Object> optimized =
                 LikeOptimization.tryOptimize(patternLiteral)
@@ -204,6 +226,15 @@ public class PredicateBuilder {
         return LeafPredicate.of(transform, function, singletonList(literal));
     }
 
+    private Predicate leaf(LeafFunction function, int idx, List<Object> 
literals) {
+        DataField field = rowType.getFields().get(idx);
+        return new LeafPredicate(function, field.type(), idx, field.name(), 
literals);
+    }
+
+    private Predicate leaf(LeafFunction function, Transform transform, 
List<Object> literals) {
+        return LeafPredicate.of(transform, function, literals);
+    }
+
     private Predicate leaf(LeafUnaryFunction function, int idx) {
         DataField field = rowType.getFields().get(idx);
         return new LeafPredicate(
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexEvaluatorTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexEvaluatorTest.java
index 8245da25f0..5bb156393d 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexEvaluatorTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexEvaluatorTest.java
@@ -800,6 +800,18 @@ class GlobalIndexEvaluatorTest {
                         return CompletableFuture.completedFuture(
                                 Optional.of(GlobalIndexResult.createEmpty()));
                     }
+
+                    @Override
+                    public CompletableFuture<Optional<GlobalIndexResult>> 
visitArraysOverlap(
+                            FieldRef fieldRef, List<Object> literals) {
+                        return 
CompletableFuture.completedFuture(Optional.of(resultOf(1, 2, 3)));
+                    }
+
+                    @Override
+                    public CompletableFuture<Optional<GlobalIndexResult>> 
visitArrayContainsAll(
+                            FieldRef fieldRef, List<Object> literals) {
+                        return 
CompletableFuture.completedFuture(Optional.of(resultOf(2)));
+                    }
                 };
         GlobalIndexEvaluator evaluator =
                 new GlobalIndexEvaluator(
@@ -814,11 +826,19 @@ class GlobalIndexEvaluatorTest {
                 evaluator.evaluate(
                         PredicateBuilder.and(
                                 builder.arrayContains(0, 1), 
builder.arrayContains(0, 2)));
+        Optional<GlobalIndexResult> overlap =
+                evaluator.evaluate(builder.arraysOverlap(0, Arrays.asList(1, 
2)));
+        Optional<GlobalIndexResult> containsAll =
+                evaluator.evaluate(builder.arrayContainsAll(0, 
Arrays.asList(1, 2)));
 
         assertThat(any).isPresent();
         assertBitmapContainsExactly(any.get().results(), 1L, 2L, 3L);
         assertThat(all).isPresent();
         assertBitmapContainsExactly(all.get().results(), 2L);
+        assertThat(overlap).isPresent();
+        assertBitmapContainsExactly(overlap.get().results(), 1L, 2L, 3L);
+        assertThat(containsAll).isPresent();
+        assertBitmapContainsExactly(containsAll.get().results(), 2L);
         evaluator.close();
     }
 
@@ -844,6 +864,18 @@ class GlobalIndexEvaluatorTest {
                             FieldRef fieldRef, Object literal) {
                         return 
CompletableFuture.completedFuture(Optional.of(resultOf(1, 3)));
                     }
+
+                    @Override
+                    public CompletableFuture<Optional<GlobalIndexResult>> 
visitArraysOverlap(
+                            FieldRef fieldRef, List<Object> literals) {
+                        return 
CompletableFuture.completedFuture(Optional.of(resultOf(1, 3)));
+                    }
+
+                    @Override
+                    public CompletableFuture<Optional<GlobalIndexResult>> 
visitArrayContainsAll(
+                            FieldRef fieldRef, List<Object> literals) {
+                        return 
CompletableFuture.completedFuture(Optional.of(resultOf(1, 3)));
+                    }
                 };
         GlobalIndexReader wrapped =
                 new UnionGlobalIndexReader(
@@ -855,6 +887,16 @@ class GlobalIndexEvaluatorTest {
 
         assertThat(result).isPresent();
         assertBitmapContainsExactly(result.get().results(), 11L, 13L);
+        Optional<GlobalIndexResult> overlapResult =
+                evaluator.evaluate(
+                        new PredicateBuilder(rowType).arraysOverlap(0, 
Arrays.asList(2)));
+        Optional<GlobalIndexResult> containsAllResult =
+                evaluator.evaluate(
+                        new PredicateBuilder(rowType).arrayContainsAll(0, 
Arrays.asList(2)));
+        assertThat(overlapResult).isPresent();
+        assertBitmapContainsExactly(overlapResult.get().results(), 11L, 13L);
+        assertThat(containsAllResult).isPresent();
+        assertBitmapContainsExactly(containsAllResult.get().results(), 11L, 
13L);
         Optional<GlobalIndexResult> constantResult =
                 new ConstantGlobalIndexReader(resultOf(4))
                         .visitArrayContains(
@@ -862,6 +904,22 @@ class GlobalIndexEvaluatorTest {
                         .join();
         assertThat(constantResult).isPresent();
         assertBitmapContainsExactly(constantResult.get().results(), 4L);
+        ConstantGlobalIndexReader constantReader = new 
ConstantGlobalIndexReader(resultOf(5));
+        FieldRef fieldRef = new FieldRef(0, "tags", 
DataTypes.ARRAY(DataTypes.INT()));
+        assertBitmapContainsExactly(
+                constantReader
+                        .visitArraysOverlap(fieldRef, Arrays.asList(2))
+                        .join()
+                        .get()
+                        .results(),
+                5L);
+        assertBitmapContainsExactly(
+                constantReader
+                        .visitArrayContainsAll(fieldRef, Arrays.asList(2))
+                        .join()
+                        .get()
+                        .results(),
+                5L);
         evaluator.close();
     }
 
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/globalindex/bitmap/MultiValueBitmapIndexReaderTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/bitmap/MultiValueBitmapIndexReaderTest.java
index a268c549f8..118d1d1f7c 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/globalindex/bitmap/MultiValueBitmapIndexReaderTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/bitmap/MultiValueBitmapIndexReaderTest.java
@@ -128,6 +128,32 @@ class MultiValueBitmapIndexReaderTest {
             assertRows(reader.visitArrayContains(fieldRef, str("B")).join(), 
0L, 3L);
             assertRows(reader.visitArrayContains(fieldRef, 
str("missing")).join());
             assertRows(reader.visitArrayContains(fieldRef, null).join());
+            assertRows(
+                    reader.visitArraysOverlap(
+                                    fieldRef,
+                                    java.util.Arrays.asList(
+                                            str("missing"), null, str("B"), 
str("B")))
+                            .join(),
+                    0L,
+                    3L);
+            assertRows(
+                    reader.visitArraysOverlap(
+                                    fieldRef, 
java.util.Arrays.asList(str("missing"), null))
+                            .join());
+            assertRows(
+                    reader.visitArrayContainsAll(
+                                    fieldRef, 
java.util.Arrays.asList(str("A"), str("B"), str("A")))
+                            .join(),
+                    0L);
+            assertRows(
+                    reader.visitArrayContainsAll(
+                                    fieldRef, 
java.util.Arrays.asList(str("A"), str("C")))
+                            .join());
+            assertRows(
+                    reader.visitArrayContainsAll(fieldRef, 
java.util.Arrays.asList(str("A"), null))
+                            .join());
+            assertThat(reader.visitArrayContainsAll(fieldRef, 
Collections.emptyList()).join())
+                    .isEmpty();
             assertThat(
                             reader.visitArrayContains(
                                             new FieldRef(
@@ -135,6 +161,20 @@ class MultiValueBitmapIndexReaderTest {
                                             1L)
                                     .join())
                     .isEmpty();
+            assertThat(
+                            reader.visitArraysOverlap(
+                                            new FieldRef(
+                                                    1, "tags", 
DataTypes.ARRAY(DataTypes.BIGINT())),
+                                            Collections.singletonList(1L))
+                                    .join())
+                    .isEmpty();
+            assertThat(
+                            reader.visitArrayContainsAll(
+                                            new FieldRef(
+                                                    1, "tags", 
DataTypes.ARRAY(DataTypes.BIGINT())),
+                                            Collections.singletonList(1L))
+                                    .join())
+                    .isEmpty();
             assertThat(reader.visitIsNull(fieldRef).join()).isEmpty();
             assertThat(reader.visitIsNotNull(fieldRef).join()).isEmpty();
 
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/predicate/LeafPredicateTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/predicate/LeafPredicateTest.java
index 7fac8ddb88..a179753b0f 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/predicate/LeafPredicateTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/predicate/LeafPredicateTest.java
@@ -93,6 +93,36 @@ class LeafPredicateTest {
                 .isTrue();
     }
 
+    @Test
+    public void testArraySetPredicateSerializationUsesElementSerializer()
+            throws IOException, ClassNotFoundException {
+        PredicateBuilder builder =
+                new 
PredicateBuilder(RowType.of(DataTypes.ARRAY(DataTypes.STRING())));
+        LeafPredicate overlap =
+                (LeafPredicate)
+                        builder.arraysOverlap(
+                                0,
+                                java.util.Arrays.asList(
+                                        BinaryString.fromString("trial"),
+                                        BinaryString.fromString("vip")));
+        LeafPredicate containsAll =
+                (LeafPredicate)
+                        builder.arrayContainsAll(
+                                0,
+                                java.util.Arrays.asList(
+                                        BinaryString.fromString("trial"),
+                                        BinaryString.fromString("vip")));
+
+        GenericRow row =
+                GenericRow.of(
+                        new GenericArray(
+                                new BinaryString[] {
+                                    BinaryString.fromString("trial"), 
BinaryString.fromString("vip")
+                                }));
+        assertThat(InstantiationUtil.clone(overlap).test(row)).isTrue();
+        assertThat(InstantiationUtil.clone(containsAll).test(row)).isTrue();
+    }
+
     private LeafPredicate create() {
         List<Object> inputs = new ArrayList<>();
         inputs.add(new FieldRef(0, "f0", DataTypes.STRING()));
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 b3f1c616b8..92250b8746 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
@@ -250,6 +250,41 @@ public class PredicateBuilderTest {
         assertThat(containsTwo.negate()).isEmpty();
     }
 
+    @Test
+    public void testArraysOverlap() {
+        PredicateBuilder builder =
+                new 
PredicateBuilder(RowType.of(DataTypes.ARRAY(DataTypes.INT())));
+        Predicate overlap = builder.arraysOverlap(0, Arrays.asList(9, null, 2, 
2));
+        Predicate noOverlap = builder.arraysOverlap(0, Arrays.asList(9, null));
+        Predicate empty = builder.arraysOverlap(0, new ArrayList<>());
+
+        GenericRow row = GenericRow.of(new GenericArray(new Integer[] {1, 
null, 2, 2}));
+        assertThat(overlap.test(row)).isTrue();
+        assertThat(noOverlap.test(row)).isFalse();
+        assertThat(empty.test(row)).isFalse();
+        assertThat(overlap.test(GenericRow.of((Object) null))).isFalse();
+        assertThat(overlap.negate()).isEmpty();
+    }
+
+    @Test
+    public void testArrayContainsAll() {
+        PredicateBuilder builder =
+                new 
PredicateBuilder(RowType.of(DataTypes.ARRAY(DataTypes.INT())));
+        Predicate containsAll = builder.arrayContainsAll(0, Arrays.asList(2, 
1, 2));
+        Predicate missing = builder.arrayContainsAll(0, Arrays.asList(1, 3));
+        Predicate containsNull = builder.arrayContainsAll(0, Arrays.asList(1, 
null));
+        Predicate empty = builder.arrayContainsAll(0, new ArrayList<>());
+
+        GenericRow row = GenericRow.of(new GenericArray(new Integer[] {1, 
null, 2, 2}));
+        assertThat(containsAll.test(row)).isTrue();
+        assertThat(missing.test(row)).isFalse();
+        assertThat(containsNull.test(row)).isFalse();
+        assertThat(empty.test(row)).isTrue();
+        assertThat(empty.test(GenericRow.of(new GenericArray(new 
Integer[0])))).isTrue();
+        assertThat(empty.test(GenericRow.of((Object) null))).isFalse();
+        assertThat(containsAll.negate()).isEmpty();
+    }
+
     @Test
     public void testArrayContainsRequiresArrayField() {
         PredicateBuilder builder = new 
PredicateBuilder(RowType.of(DataTypes.INT()));
@@ -257,6 +292,12 @@ public class PredicateBuilderTest {
         assertThatThrownBy(() -> builder.arrayContains(0, 1))
                 .isInstanceOf(IllegalArgumentException.class)
                 .hasMessageContaining("ARRAY_CONTAINS requires an ARRAY 
field");
+        assertThatThrownBy(() -> builder.arraysOverlap(0, Arrays.asList(1, 2)))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("ARRAYS_OVERLAP requires an ARRAY 
field");
+        assertThatThrownBy(() -> builder.arrayContainsAll(0, Arrays.asList(1, 
2)))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("ARRAY_CONTAINS_ALL requires an ARRAY 
field");
     }
 
     // ---- or()/and() binary tree structure tests ----
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/predicate/PredicateJsonSerdeTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/predicate/PredicateJsonSerdeTest.java
index e3814736c5..362f34f917 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/predicate/PredicateJsonSerdeTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/predicate/PredicateJsonSerdeTest.java
@@ -176,6 +176,26 @@ class PredicateJsonSerdeTest {
                         .expectJson(
                                 
"{\"kind\":\"LEAF\",\"transform\":{\"name\":\"FIELD_REF\",\"fieldRef\":{\"index\":4,\"name\":\"f4\",\"type\":{\"type\":\"ARRAY\",\"element\":\"STRING\"}}},\"function\":\"ARRAY_CONTAINS\",\"literals\":[\"vip\"]}"),
 
+                // LeafPredicate - ArraysOverlap uses the element type for 
literal serde
+                TestSpec.forPredicate(
+                                builder.arraysOverlap(
+                                        4,
+                                        Arrays.asList(
+                                                BinaryString.fromString("vip"),
+                                                
BinaryString.fromString("trial"))))
+                        .expectJson(
+                                
"{\"kind\":\"LEAF\",\"transform\":{\"name\":\"FIELD_REF\",\"fieldRef\":{\"index\":4,\"name\":\"f4\",\"type\":{\"type\":\"ARRAY\",\"element\":\"STRING\"}}},\"function\":\"ARRAYS_OVERLAP\",\"literals\":[\"vip\",\"trial\"]}"),
+
+                // LeafPredicate - ArrayContainsAll uses the element type for 
literal serde
+                TestSpec.forPredicate(
+                                builder.arrayContainsAll(
+                                        4,
+                                        Arrays.asList(
+                                                BinaryString.fromString("vip"),
+                                                
BinaryString.fromString("trial"))))
+                        .expectJson(
+                                
"{\"kind\":\"LEAF\",\"transform\":{\"name\":\"FIELD_REF\",\"fieldRef\":{\"index\":4,\"name\":\"f4\",\"type\":{\"type\":\"ARRAY\",\"element\":\"STRING\"}}},\"function\":\"ARRAY_CONTAINS_ALL\",\"literals\":[\"vip\",\"trial\"]}"),
+
                 // LeafPredicate - Between
                 TestSpec.forPredicate(builder.between(0, 3, 7))
                         .expectJson(
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScan.java
 
b/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScan.java
index d919cb85ac..cfe3fcea64 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScan.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScan.java
@@ -457,6 +457,22 @@ public final class PrimaryKeySortedIndexScan {
                     () -> reader().visitArrayContains(fieldRef, literal));
         }
 
+        @Override
+        public CompletableFuture<Optional<GlobalIndexResult>> 
visitArraysOverlap(
+                FieldRef fieldRef, List<Object> literals) {
+            return query(
+                    QueryKey.ofLiterals(QueryOperation.ARRAYS_OVERLAP, 
fieldRef, literals),
+                    () -> reader().visitArraysOverlap(fieldRef, literals));
+        }
+
+        @Override
+        public CompletableFuture<Optional<GlobalIndexResult>> 
visitArrayContainsAll(
+                FieldRef fieldRef, List<Object> literals) {
+            return query(
+                    QueryKey.ofLiterals(QueryOperation.ARRAY_CONTAINS_ALL, 
fieldRef, literals),
+                    () -> reader().visitArrayContainsAll(fieldRef, literals));
+        }
+
         @Override
         public CompletableFuture<Optional<GlobalIndexResult>> visitLike(
                 FieldRef fieldRef, Object literal) {
@@ -639,6 +655,8 @@ public final class PrimaryKeySortedIndexScan {
         ENDS_WITH,
         CONTAINS,
         ARRAY_CONTAINS,
+        ARRAYS_OVERLAP,
+        ARRAY_CONTAINS_ALL,
         LIKE,
         LESS_THAN,
         GREATER_OR_EQUAL,
@@ -739,6 +757,18 @@ public final class PrimaryKeySortedIndexScan {
             return localize(wrapped.visitArrayContains(fieldRef, literal));
         }
 
+        @Override
+        public CompletableFuture<Optional<GlobalIndexResult>> 
visitArraysOverlap(
+                FieldRef fieldRef, List<Object> literals) {
+            return localize(wrapped.visitArraysOverlap(fieldRef, literals));
+        }
+
+        @Override
+        public CompletableFuture<Optional<GlobalIndexResult>> 
visitArrayContainsAll(
+                FieldRef fieldRef, List<Object> literals) {
+            return localize(wrapped.visitArrayContainsAll(fieldRef, literals));
+        }
+
         @Override
         public CompletableFuture<Optional<GlobalIndexResult>> visitLike(
                 FieldRef fieldRef, Object literal) {
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/table/MultiValueGlobalIndexTableTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/table/MultiValueGlobalIndexTableTest.java
index ed52c8c727..90d440a98c 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/table/MultiValueGlobalIndexTableTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/table/MultiValueGlobalIndexTableTest.java
@@ -41,6 +41,7 @@ import org.apache.paimon.types.DataTypes;
 import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -82,6 +83,15 @@ public class MultiValueGlobalIndexTableTest extends 
TableTestBase {
         PredicateBuilder builder = new PredicateBuilder(table.rowType());
         Predicate containsRed = builder.arrayContains(1, RED);
         assertThat(readIds(table, containsRed)).containsExactlyInAnyOrder(1, 
5);
+        assertThat(readIds(table, builder.arraysOverlap(1, Arrays.asList(BLUE, 
9))))
+                .containsExactlyInAnyOrder(1, 2);
+        assertThat(readIds(table, builder.arrayContainsAll(1, 
Arrays.asList(RED, BLUE))))
+                .containsExactly(1);
+        assertThat(readIds(table, builder.arrayContainsAll(1, 
Arrays.asList(RED, RED))))
+                .containsExactlyInAnyOrder(1, 5);
+        assertThat(readIds(table, builder.arrayContainsAll(1, 
Arrays.asList(RED, null)))).isEmpty();
+        assertThat(readIdsWithFallback(table, builder.arrayContainsAll(1, 
Collections.emptyList())))
+                .containsExactlyInAnyOrder(1, 2, 4, 5);
         assertThat(readIdsWithFallback(table, 
builder.isNull(1))).containsExactly(3);
 
         write(table, GenericRow.of(6, array(RED)));
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScanTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScanTest.java
index d4b6d73ed9..c9c09ea7d4 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScanTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScanTest.java
@@ -420,6 +420,73 @@ class PrimaryKeySortedIndexScanTest {
         verify(reader).close();
     }
 
+    @Test
+    void testArraySetPredicatesAreCachedAndLocalized() throws IOException {
+        DataFileMeta first = dataFile("data-1", 2);
+        DataFileMeta second = dataFile("data-2", 3);
+        DataSplit split = dataSplit(11, 0, first, second);
+        PrimaryKeyIndexDefinition definition =
+                definition(
+                        7,
+                        MultiValueGlobalIndexerFactory.IDENTIFIER,
+                        PrimaryKeyIndexDefinition.Family.MULTI_VALUE);
+        IndexFileMeta mergedPayload =
+                payload(
+                        "multivalue-merged",
+                        Arrays.asList(
+                                new PrimaryKeyIndexSourceFile("data-1", 2),
+                                new PrimaryKeyIndexSourceFile("data-2", 3)),
+                        "multivalue",
+                        7,
+                        5);
+        PrimaryKeySortedIndexScan.Plan plan =
+                PrimaryKeySortedIndexScan.plan(
+                        11,
+                        Collections.singletonList(split),
+                        Collections.singletonList(definition),
+                        Collections.singletonList(payloadEntry(0, 
mergedPayload)));
+        RowType rowType = RowType.of(new DataField(7, "tags", 
DataTypes.ARRAY(DataTypes.INT())));
+        List<Object> literals = Arrays.asList(42, 43);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
+        Predicate predicate =
+                PredicateBuilder.and(
+                        builder.arraysOverlap(0, literals), 
builder.arrayContainsAll(0, literals));
+        AtomicInteger queries = new AtomicInteger();
+        GlobalIndexReader reader = mock(GlobalIndexReader.class);
+        when(reader.visitArraysOverlap(any(), eq(literals)))
+                .thenAnswer(
+                        ignored -> {
+                            queries.incrementAndGet();
+                            return completedResult(1, 2, 4);
+                        });
+        when(reader.visitArrayContainsAll(any(), eq(literals)))
+                .thenAnswer(
+                        ignored -> {
+                            queries.incrementAndGet();
+                            return completedResult(1, 4);
+                        });
+
+        PrimaryKeySortedIndexScan.EvaluatedPlan evaluated =
+                PrimaryKeySortedIndexScan.evaluate(
+                        plan,
+                        rowType,
+                        predicate,
+                        Collections.singletonList(definition),
+                        (ignoredFile, ignoredDefinition, payloads, 
totalRowCount) -> {
+                            
assertThat(payloads).containsExactly(mergedPayload);
+                            assertThat(totalRowCount).isEqualTo(5);
+                            return reader;
+                        });
+
+        assertThat(queries).hasValue(2);
+        assertThat(evaluated.files()).hasSize(2);
+        assertThat(evaluated.files().get(0).result()).isPresent();
+        
assertThat(evaluated.files().get(0).result().get().results()).containsExactly(1L);
+        assertThat(evaluated.files().get(1).result()).isPresent();
+        
assertThat(evaluated.files().get(1).result().get().results()).containsExactly(2L);
+        verify(reader).close();
+    }
+
     @Test
     void testPerFileBooleanFallbackSemantics() {
         DataSplit split = dataSplit(11, 0, dataFile("data-1", 4));
diff --git 
a/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcPredicateFunctionVisitor.java
 
b/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcPredicateFunctionVisitor.java
index b67364d33e..f72478c8a1 100644
--- 
a/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcPredicateFunctionVisitor.java
+++ 
b/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcPredicateFunctionVisitor.java
@@ -87,6 +87,18 @@ public class OrcPredicateFunctionVisitor
         return Optional.empty();
     }
 
+    @Override
+    public Optional<OrcFilters.Predicate> visitArraysOverlap(
+            FieldRef fieldRef, List<Object> literals) {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<OrcFilters.Predicate> visitArrayContainsAll(
+            FieldRef fieldRef, List<Object> literals) {
+        return Optional.empty();
+    }
+
     @Override
     public Optional<OrcFilters.Predicate> visitLike(FieldRef fieldRef, Object 
literal) {
         return Optional.empty();
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 9afb6f8981..e28b8ee437 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
@@ -204,6 +204,16 @@ public class ParquetFilters {
             throw new UnsupportedOperationException();
         }
 
+        @Override
+        public FilterPredicate visitArraysOverlap(FieldRef fieldRef, 
List<Object> literals) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public FilterPredicate visitArrayContainsAll(FieldRef fieldRef, 
List<Object> literals) {
+            throw new UnsupportedOperationException();
+        }
+
         @Override
         public FilterPredicate visitLike(FieldRef fieldRef, Object literal) {
             throw new UnsupportedOperationException();

Reply via email to