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 522e2ea104 [core] Reject mixed hybrid route builder settings (#8336)
522e2ea104 is described below

commit 522e2ea104e7f395c911be122204d6ddf680bd07
Author: QuakeWang <[email protected]>
AuthorDate: Wed Jun 24 09:11:27 2026 +0800

    [core] Reject mixed hybrid route builder settings (#8336)
    
    `HybridSearchRoute.Builder` previously inferred the route type from
    whether a full-text query was set. If callers mixed vector settings and
    full-text settings on the same builder, the builder silently produced a
    full-text route and discarded the vector configuration.
    
    This change tracks the builder route type explicitly and rejects mixed
    vector/full-text route settings.
---
 .../apache/paimon/predicate/HybridSearchRoute.java | 22 ++++++++++-
 .../apache/paimon/predicate/FullTextQueryTest.java | 45 ++++++++++++++++++++++
 2 files changed, 65 insertions(+), 2 deletions(-)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/predicate/HybridSearchRoute.java
 
b/paimon-common/src/main/java/org/apache/paimon/predicate/HybridSearchRoute.java
index b0746635bf..ada1a58b31 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/predicate/HybridSearchRoute.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/predicate/HybridSearchRoute.java
@@ -196,11 +196,13 @@ public class HybridSearchRoute implements Serializable {
         private String fieldName;
         private float[] vector;
         private FullTextQuery fullTextQuery;
+        private RouteType routeType;
         private int limit;
         private float weight = 1.0f;
         private Map<String, String> options = new HashMap<>();
 
         public Builder vectorColumn(String fieldName) {
+            setRouteType(RouteType.VECTOR);
             this.fieldName = fieldName;
             return this;
         }
@@ -210,6 +212,7 @@ public class HybridSearchRoute implements Serializable {
         }
 
         public Builder queryVector(float[] vector) {
+            setRouteType(RouteType.VECTOR);
             this.vector = vector;
             return this;
         }
@@ -219,11 +222,26 @@ public class HybridSearchRoute implements Serializable {
         }
 
         public Builder query(String queryJson) {
-            this.fullTextQuery = FullTextQuery.fromJson(queryJson);
+            checkRouteType(RouteType.FULL_TEXT);
+            FullTextQuery fullTextQuery = FullTextQuery.fromJson(queryJson);
+            this.routeType = RouteType.FULL_TEXT;
+            this.fullTextQuery = fullTextQuery;
             this.fieldName = fullTextQuery.columns().get(0);
             return this;
         }
 
+        private void setRouteType(RouteType routeType) {
+            checkRouteType(routeType);
+            this.routeType = routeType;
+        }
+
+        private void checkRouteType(RouteType routeType) {
+            if (this.routeType != null && this.routeType != routeType) {
+                throw new IllegalArgumentException(
+                        "Cannot mix vector and full-text hybrid route 
settings");
+            }
+        }
+
         public Builder limit(int limit) {
             this.limit = limit;
             return this;
@@ -247,7 +265,7 @@ public class HybridSearchRoute implements Serializable {
         }
 
         public HybridSearchRoute build() {
-            if (fullTextQuery != null) {
+            if (routeType == RouteType.FULL_TEXT) {
                 checkFullTextOptions(options);
                 return new HybridSearchRoute(
                         RouteType.FULL_TEXT,
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/predicate/FullTextQueryTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/predicate/FullTextQueryTest.java
index ff8ad91b76..75271e6ac6 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/predicate/FullTextQueryTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/predicate/FullTextQueryTest.java
@@ -200,6 +200,51 @@ public class FullTextQueryTest {
                 .hasMessageContaining("Full-text hybrid route options are not 
supported yet");
     }
 
+    @Test
+    public void testHybridRouteBuilderRejectsMixedRouteTypes() {
+        assertThatThrownBy(
+                        () ->
+                                HybridSearchRoute.builder()
+                                        .vectorColumn("embedding")
+                                        .queryVector(new float[] {1.0f, 2.0f})
+                                        .query(
+                                                
"{\"match\":{\"column\":\"content\","
+                                                        + "\"terms\":\"paimon 
lake\"}}")
+                                        .limit(10)
+                                        .build())
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Cannot mix vector and full-text hybrid 
route settings");
+
+        assertThatThrownBy(
+                        () ->
+                                HybridSearchRoute.builder()
+                                        .query(
+                                                
"{\"match\":{\"column\":\"content\","
+                                                        + "\"terms\":\"paimon 
lake\"}}")
+                                        .vectorColumn("embedding")
+                                        .limit(10)
+                                        .build())
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Cannot mix vector and full-text hybrid 
route settings");
+    }
+
+    @Test
+    public void 
testHybridRouteBuilderKeepsRouteUnsetAfterInvalidFullTextQuery() {
+        HybridSearchRoute.Builder builder = HybridSearchRoute.builder();
+
+        assertThatThrownBy(() -> builder.query("{\"match\":{"))
+                .isInstanceOf(IllegalArgumentException.class);
+
+        HybridSearchRoute route =
+                builder.vectorColumn("embedding")
+                        .queryVector(new float[] {1.0f, 2.0f})
+                        .limit(10)
+                        .build();
+
+        assertThat(route.isVector()).isTrue();
+        assertThat(route.fieldName()).isEqualTo("embedding");
+    }
+
     @Test
     public void testHybridRouteRejectsNonFiniteWeight() {
         assertThatThrownBy(

Reply via email to