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(