This is an automated email from the ASF dual-hosted git repository.
eldenmoon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new c914dc3cefa [refactor](variant) normalize nested search predicate
field resolution (#61548)
c914dc3cefa is described below
commit c914dc3cefad1b8a1121c12f7152ad8cf5a44503
Author: lihangyu <[email protected]>
AuthorDate: Mon Mar 23 11:33:56 2026 +0800
[refactor](variant) normalize nested search predicate field resolution
(#61548)
Problem Summary:
Nested search predicates were parsed inconsistently across code paths.
Queries inside `NESTED(path, ...)` had to repeat the full nested prefix,
unsupported nested forms were validated late, and normalized field
bindings could diverge from the field paths pushed down to thrift.
This change centralizes nested field path construction and normalizes
child predicates against the active nested path during parsing. It
applies the same validation rules in standard and lucene modes, rejects
unsupported nested forms earlier, and keeps normalized field bindings
aligned with generated thrift search params. The added FE tests cover
standard mode, lucene mode, invalid nested syntax, and thrift
serialization of normalized nested fields.
Normalize FE handling of nested search predicates for Variant search
DSL. Fields inside `NESTED(path, ...)` must now be written relative to
the nested path, and unsupported forms such as absolute nested field
references, bare queries, nested `NESTED(...)`, and non-top-level
`NESTED` clauses now fail with explicit syntax errors.
- Test: Not run in this session (message-only amend; the code change
adds FE test coverage)
- Behavior changed: Yes (nested predicates now require relative field
references inside `NESTED(path, ...)`)
- Does this need documentation: No
---
be/src/exec/common/variant_util.cpp | 5 -
.../org/apache/doris/analysis/SearchDslParser.java | 199 ++++++++-------------
.../apache/doris/analysis/SearchPredicateTest.java | 29 +++
.../functions/scalar/SearchDslParserTest.java | 100 +++++++++--
4 files changed, 198 insertions(+), 135 deletions(-)
diff --git a/be/src/exec/common/variant_util.cpp
b/be/src/exec/common/variant_util.cpp
index 5a0a978ece8..dd0c15a0025 100644
--- a/be/src/exec/common/variant_util.cpp
+++ b/be/src/exec/common/variant_util.cpp
@@ -988,11 +988,6 @@ Status VariantCompactionUtil::check_path_stats(const
std::vector<RowsetSharedPtr
return Status::OK();
}
}
- for (const auto& column : output->tablet_schema()->columns()) {
- if (!column->is_variant_type()) {
- continue;
- }
- }
std::unordered_map<int32_t, PathToNoneNullValues>
original_uid_to_path_stats;
for (const auto& rs : intputs) {
RETURN_IF_ERROR(aggregate_path_to_stats(rs,
&original_uid_to_path_stats));
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/analysis/SearchDslParser.java
b/fe/fe-core/src/main/java/org/apache/doris/analysis/SearchDslParser.java
index 832ec5a37f3..d31c52c9155 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/SearchDslParser.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SearchDslParser.java
@@ -277,6 +277,37 @@ public class SearchDslParser {
}
}
+ private static String buildFieldPath(SearchParser.FieldPathContext ctx) {
+ if (ctx == null) {
+ throw new RuntimeException("Invalid field query: missing field
path");
+ }
+
+ StringBuilder fullPath = new StringBuilder();
+ List<SearchParser.FieldSegmentContext> segments = ctx.fieldSegment();
+ for (int i = 0; i < segments.size(); i++) {
+ if (i > 0) {
+ fullPath.append('.');
+ }
+ String segment = segments.get(i).getText();
+ if (segment.startsWith("\"") && segment.endsWith("\"")) {
+ segment = segment.substring(1, segment.length() - 1);
+ }
+ fullPath.append(segment);
+ }
+ return fullPath.toString();
+ }
+
+ private static String normalizeNestedFieldPath(String fieldPath, @Nullable
String nestedPath) {
+ if (nestedPath == null || nestedPath.isEmpty()) {
+ return fieldPath;
+ }
+ if (fieldPath.equals(nestedPath) || fieldPath.startsWith(nestedPath +
".")) {
+ throw new SearchDslSyntaxException("Fields in NESTED predicates
must be relative to nested path: "
+ + nestedPath + ", but got: " + fieldPath);
+ }
+ return nestedPath + "." + fieldPath;
+ }
+
/**
* Collect all field names from an AST node recursively.
* @param node The AST node to collect from
@@ -472,6 +503,7 @@ public class SearchDslParser {
// Build AST using first field as placeholder for bare queries,
with default operator
QsAstBuilder visitor = new QsAstBuilder(fields.get(0),
defaultOperator);
QsNode root = visitor.visit(tree);
+ validateNestedTopLevelOnly(root);
// Apply multi-field expansion based on type
QsNode expandedRoot;
@@ -563,6 +595,7 @@ public class SearchDslParser {
// Use constructor with override to avoid mutating shared options
object (thread-safety)
QsLuceneModeAstBuilder visitor = new
QsLuceneModeAstBuilder(effectiveOptions, fields.get(0));
QsNode root = visitor.visit(tree);
+ validateNestedTopLevelOnly(root);
// In ES query_string, both best_fields and cross_fields use
per-clause expansion
// (each clause is independently expanded across fields). The
difference is only
@@ -646,6 +679,8 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure NOT
query rewriting)
private final Set<String> fieldNames = new LinkedHashSet<>();
// Context stack to track current field name during parsing
private String currentFieldName = null;
+ // Current nested path when visiting NESTED(path, predicates)
+ private String currentNestedPath = null;
// Default field for bare queries (without field: prefix)
private final String defaultField;
// Default operator for implicit conjunction (space-separated terms):
"AND" or "OR"
@@ -822,6 +857,9 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure NOT
query rewriting)
@Override
public QsNode visitBareQuery(SearchParser.BareQueryContext ctx) {
+ if (currentNestedPath != null && (currentFieldName == null ||
currentFieldName.isEmpty())) {
+ throw new SearchDslSyntaxException("Bare queries are not
supported inside NESTED predicates");
+ }
// Use currentFieldName if inside a field group context (set by
visitFieldGroupQuery),
// otherwise fall back to the configured defaultField.
String effectiveField = (currentFieldName != null &&
!currentFieldName.isEmpty())
@@ -858,60 +896,29 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure
NOT query rewriting)
if (ctx.NESTED_PATH() == null) {
throw new RuntimeException("Invalid NESTED clause: missing
path");
}
- String nestedPath = ctx.NESTED_PATH().getText();
- QsNode innerQuery = visit(ctx.clause());
- if (innerQuery == null) {
- throw new RuntimeException("Invalid NESTED clause: missing
inner query");
+ if (currentNestedPath != null) {
+ throw new SearchDslSyntaxException("Nested NESTED() is not
supported");
}
-
- validateNestedFieldPaths(innerQuery, nestedPath);
-
- QsNode node = new QsNode(QsClauseType.NESTED,
Collections.singletonList(innerQuery));
- node.nestedPath = nestedPath;
- return node;
- }
-
- private void validateNestedFieldPaths(QsNode node, String nestedPath) {
- if (node == null) {
- return;
- }
- if (node.type == QsClauseType.NESTED) {
- throw new RuntimeException("Nested NESTED() is not supported:
" + nestedPath);
- }
- if (node.field != null && !node.field.startsWith(nestedPath +
".")) {
- throw new RuntimeException("Fields in NESTED query must start
with nested path: "
- + nestedPath + ", but got: " + node.field);
- }
- if (node.children != null) {
- for (QsNode child : node.children) {
- validateNestedFieldPaths(child, nestedPath);
+ String nestedPath = ctx.NESTED_PATH().getText();
+ String previousNestedPath = currentNestedPath;
+ currentNestedPath = nestedPath;
+ try {
+ QsNode innerQuery = visit(ctx.clause());
+ if (innerQuery == null) {
+ throw new RuntimeException("Invalid NESTED clause: missing
inner query");
}
+
+ QsNode node = new QsNode(QsClauseType.NESTED,
Collections.singletonList(innerQuery));
+ node.nestedPath = nestedPath;
+ return node;
+ } finally {
+ currentNestedPath = previousNestedPath;
}
}
@Override
public QsNode visitFieldQuery(SearchParser.FieldQueryContext ctx) {
- if (ctx.fieldPath() == null) {
- throw new RuntimeException("Invalid field query: missing field
path");
- }
-
- // Build complete field path from segments (support
field.subcolumn syntax)
- StringBuilder fullPath = new StringBuilder();
- List<SearchParser.FieldSegmentContext> segments =
ctx.fieldPath().fieldSegment();
-
- for (int i = 0; i < segments.size(); i++) {
- if (i > 0) {
- fullPath.append('.');
- }
- String segment = segments.get(i).getText();
- // Remove quotes if present
- if (segment.startsWith("\"") && segment.endsWith("\"")) {
- segment = segment.substring(1, segment.length() - 1);
- }
- fullPath.append(segment);
- }
-
- String fieldPath = fullPath.toString();
+ String fieldPath =
normalizeNestedFieldPath(buildFieldPath(ctx.fieldPath()), currentNestedPath);
fieldNames.add(fieldPath);
// Set current field context before visiting search value
@@ -941,21 +948,7 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure
NOT query rewriting)
throw new SearchDslSyntaxException("Invalid field group query:
missing field path");
}
- // Build complete field path from segments (support
field.subcolumn syntax)
- StringBuilder fullPath = new StringBuilder();
- List<SearchParser.FieldSegmentContext> segments =
ctx.fieldPath().fieldSegment();
- for (int i = 0; i < segments.size(); i++) {
- if (i > 0) {
- fullPath.append('.');
- }
- String segment = segments.get(i).getText();
- if (segment.startsWith("\"") && segment.endsWith("\"")) {
- segment = segment.substring(1, segment.length() - 1);
- }
- fullPath.append(segment);
- }
-
- String fieldPath = fullPath.toString();
+ String fieldPath =
normalizeNestedFieldPath(buildFieldPath(ctx.fieldPath()), currentNestedPath);
fieldNames.add(fieldPath);
// Set field group context so bare terms inside use this field
@@ -2075,6 +2068,7 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure
NOT query rewriting)
private final Set<String> fieldNames = new LinkedHashSet<>();
private final SearchOptions options;
private String currentFieldName = null;
+ private String currentNestedPath = null;
// Override for default field - used in multi-field mode to avoid
mutating options
private final String overrideDefaultField;
private int nestingLevel = 0;
@@ -2301,6 +2295,8 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure
NOT query rewriting)
} finally {
nestingLevel--;
}
+ } else if (atomCtx.nestedQuery() != null) {
+ node = visit(atomCtx.nestedQuery());
} else if (atomCtx.fieldGroupQuery() != null) {
// Field group query (e.g., title:(rock OR jazz))
node = visit(atomCtx.fieldGroupQuery());
@@ -2464,6 +2460,9 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure
NOT query rewriting)
@Override
public QsNode visitBareQuery(SearchParser.BareQueryContext ctx) {
+ if (currentNestedPath != null && (currentFieldName == null ||
currentFieldName.isEmpty())) {
+ throw new SearchDslSyntaxException("Bare queries are not
supported inside NESTED predicates");
+ }
// Use currentFieldName if inside a field group context (set by
visitFieldGroupQuery),
// otherwise fall back to the effective default field.
String defaultField = getEffectiveDefaultField();
@@ -2501,55 +2500,29 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure
NOT query rewriting)
if (ctx.NESTED_PATH() == null) {
throw new RuntimeException("Invalid NESTED clause: missing
path");
}
- String nestedPath = ctx.NESTED_PATH().getText();
- QsNode innerQuery = visit(ctx.clause());
- if (innerQuery == null) {
- throw new RuntimeException("Invalid NESTED clause: missing
inner query");
- }
-
- validateNestedFieldPaths(innerQuery, nestedPath);
-
- QsNode node = new QsNode(QsClauseType.NESTED,
Collections.singletonList(innerQuery));
- node.nestedPath = nestedPath;
- return node;
- }
-
- private void validateNestedFieldPaths(QsNode node, String nestedPath) {
- if (node == null) {
- return;
- }
- if (node.type == QsClauseType.NESTED) {
- throw new RuntimeException("Nested NESTED() is not supported:
" + nestedPath);
+ if (currentNestedPath != null) {
+ throw new SearchDslSyntaxException("Nested NESTED() is not
supported");
}
- if (node.field != null && !node.field.startsWith(nestedPath +
".")) {
- throw new RuntimeException("Fields in NESTED query must start
with nested path: "
- + nestedPath + ", but got: " + node.field);
- }
- if (node.children != null) {
- for (QsNode child : node.children) {
- validateNestedFieldPaths(child, nestedPath);
+ String nestedPath = ctx.NESTED_PATH().getText();
+ String previousNestedPath = currentNestedPath;
+ currentNestedPath = nestedPath;
+ try {
+ QsNode innerQuery = visit(ctx.clause());
+ if (innerQuery == null) {
+ throw new RuntimeException("Invalid NESTED clause: missing
inner query");
}
+
+ QsNode node = new QsNode(QsClauseType.NESTED,
Collections.singletonList(innerQuery));
+ node.nestedPath = nestedPath;
+ return node;
+ } finally {
+ currentNestedPath = previousNestedPath;
}
}
@Override
public QsNode visitFieldQuery(SearchParser.FieldQueryContext ctx) {
- // Build complete field path
- StringBuilder fullPath = new StringBuilder();
- List<SearchParser.FieldSegmentContext> segments =
ctx.fieldPath().fieldSegment();
-
- for (int i = 0; i < segments.size(); i++) {
- if (i > 0) {
- fullPath.append('.');
- }
- String segment = segments.get(i).getText();
- if (segment.startsWith("\"") && segment.endsWith("\"")) {
- segment = segment.substring(1, segment.length() - 1);
- }
- fullPath.append(segment);
- }
-
- String fieldPath = fullPath.toString();
+ String fieldPath =
normalizeNestedFieldPath(buildFieldPath(ctx.fieldPath()), currentNestedPath);
fieldNames.add(fieldPath);
String previousFieldName = currentFieldName;
@@ -2571,21 +2544,7 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure
NOT query rewriting)
throw new SearchDslSyntaxException("Invalid field group query:
missing field path");
}
- // Build complete field path from segments (support
field.subcolumn syntax)
- StringBuilder fullPath = new StringBuilder();
- List<SearchParser.FieldSegmentContext> segments =
ctx.fieldPath().fieldSegment();
- for (int i = 0; i < segments.size(); i++) {
- if (i > 0) {
- fullPath.append('.');
- }
- String segment = segments.get(i).getText();
- if (segment.startsWith("\"") && segment.endsWith("\"")) {
- segment = segment.substring(1, segment.length() - 1);
- }
- fullPath.append(segment);
- }
-
- String fieldPath = fullPath.toString();
+ String fieldPath =
normalizeNestedFieldPath(buildFieldPath(ctx.fieldPath()), currentNestedPath);
fieldNames.add(fieldPath);
// Set field group context so bare terms inside use this field
@@ -2724,7 +2683,7 @@ MATCH_ALL_DOCS, // Matches all documents (used for pure
NOT query rewriting)
return;
}
if (node.type == QsClauseType.NESTED && !isRoot) {
- throw new RuntimeException("NESTED clause must be evaluated at top
level");
+ throw new SearchDslSyntaxException("NESTED clause must be
evaluated at top level");
}
if (node.children == null || node.children.isEmpty()) {
return;
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/analysis/SearchPredicateTest.java
b/fe/fe-core/src/test/java/org/apache/doris/analysis/SearchPredicateTest.java
index 8a5602c3317..c1e82b894e6 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/analysis/SearchPredicateTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/analysis/SearchPredicateTest.java
@@ -153,6 +153,35 @@ public class SearchPredicateTest {
Assertions.assertEquals("content",
param.field_bindings.get(1).field_name);
}
+ @Test
+ public void testNestedRelativeFieldsAreNormalizedBeforeThrift() {
+ String dsl = "NESTED(data.items, msg:hello AND meta.channel:action)";
+ SearchDslParser.QsPlan plan = SearchDslParser.parseDsl(dsl,
"{\"mode\":\"standard\"}");
+ List<Expr> children = Arrays.asList(createTestSlotRef("data"),
createTestSlotRef("data"));
+
+ SearchPredicate predicate = new SearchPredicate(dsl, plan, children,
true);
+
+ TExprNode thriftNode = new TExprNode();
+ predicate.accept(ExprToThriftVisitor.INSTANCE, thriftNode);
+
+ TSearchParam param = thriftNode.search_param;
+ Assertions.assertNotNull(param);
+ Assertions.assertEquals("NESTED", param.root.clause_type);
+ Assertions.assertEquals("data.items", param.root.nested_path);
+ Assertions.assertEquals(1, param.root.children.size());
+ Assertions.assertEquals("AND", param.root.children.get(0).clause_type);
+ Assertions.assertEquals("data.items.msg",
param.root.children.get(0).children.get(0).field_name);
+ Assertions.assertEquals("data.items.meta.channel",
param.root.children.get(0).children.get(1).field_name);
+
+ Assertions.assertEquals(2, param.field_bindings.size());
+ Assertions.assertEquals("data.items.msg",
param.field_bindings.get(0).field_name);
+ Assertions.assertEquals("data",
param.field_bindings.get(0).parent_field_name);
+ Assertions.assertEquals("items.msg",
param.field_bindings.get(0).subcolumn_path);
+ Assertions.assertEquals("data.items.meta.channel",
param.field_bindings.get(1).field_name);
+ Assertions.assertEquals("data",
param.field_bindings.get(1).parent_field_name);
+ Assertions.assertEquals("items.meta.channel",
param.field_bindings.get(1).subcolumn_path);
+ }
+
@Test
public void testClone() {
String dsl = "title:hello";
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParserTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParserTest.java
index c5f228cf118..6dc16a1da7a 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParserTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParserTest.java
@@ -2531,7 +2531,7 @@ public class SearchDslParserTest {
@Test
public void testNestedQuerySimple() {
- String dsl = "NESTED(data, data.msg:hello)";
+ String dsl = "NESTED(data, msg:hello)";
QsPlan plan = SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
Assertions.assertNotNull(plan);
@@ -2545,7 +2545,7 @@ public class SearchDslParserTest {
@Test
public void testNestedQueryAnd() {
- String dsl = "NESTED(data, data.msg:hello AND data.title:news)";
+ String dsl = "NESTED(data, msg:hello AND title:news)";
QsPlan plan = SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
Assertions.assertNotNull(plan);
@@ -2558,29 +2558,109 @@ public class SearchDslParserTest {
}
@Test
- public void testNestedQueryFieldValidation() {
- String dsl = "NESTED(data, other.msg:hello)";
+ public void testNestedQueryAbsolutePathRejected() {
+ String dsl = "NESTED(data, data.msg:hello)";
RuntimeException exception =
Assertions.assertThrows(RuntimeException.class, () -> {
SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
});
- Assertions.assertTrue(exception.getMessage().contains("Fields in
NESTED query must start with nested path"));
+ Assertions.assertTrue(exception.getMessage().contains("Fields in
NESTED predicates must be relative"));
}
@Test
public void testNestedQueryPathWithDot() {
- String dsl = "NESTED(data.items, data.items.msg:hello)";
+ String dsl = "NESTED(data.items, meta.channel:action)";
QsPlan plan = SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
Assertions.assertNotNull(plan);
Assertions.assertEquals(QsClauseType.NESTED, plan.getRoot().getType());
Assertions.assertEquals("data.items", plan.getRoot().getNestedPath());
Assertions.assertTrue(plan.getFieldBindings().stream()
- .anyMatch(b -> "data.items.msg".equals(b.getFieldName())));
+ .anyMatch(b ->
"data.items.meta.channel".equals(b.getFieldName())));
+ }
+
+ @Test
+ public void testNestedQuerySimpleLuceneMode() {
+ String dsl = "NESTED(data, msg:hello)";
+ QsPlan plan = SearchDslParser.parseDsl(dsl,
+
"{\"mode\":\"lucene\",\"default_operator\":\"AND\",\"minimum_should_match\":0}");
+
+ Assertions.assertNotNull(plan);
+ Assertions.assertEquals(QsClauseType.NESTED, plan.getRoot().getType());
+ Assertions.assertEquals("data", plan.getRoot().getNestedPath());
+ Assertions.assertEquals(1, plan.getRoot().getChildren().size());
+ Assertions.assertEquals(QsClauseType.TERM,
plan.getRoot().getChildren().get(0).getType());
+ Assertions.assertEquals("data.msg",
plan.getRoot().getChildren().get(0).getField());
+ Assertions.assertTrue(plan.getFieldBindings().stream().anyMatch(b ->
"data.msg".equals(b.getFieldName())));
+ }
+
+ @Test
+ public void testNestedQueryAndLuceneMode() {
+ String dsl = "NESTED(data, msg:hello AND title:news)";
+ QsPlan plan = SearchDslParser.parseDsl(dsl,
+
"{\"mode\":\"lucene\",\"default_operator\":\"AND\",\"minimum_should_match\":0}");
+
+ Assertions.assertNotNull(plan);
+ Assertions.assertEquals(QsClauseType.NESTED, plan.getRoot().getType());
+ Assertions.assertEquals("data", plan.getRoot().getNestedPath());
+ Assertions.assertEquals(1, plan.getRoot().getChildren().size());
+ Assertions.assertEquals(QsClauseType.OCCUR_BOOLEAN,
plan.getRoot().getChildren().get(0).getType());
+ Assertions.assertTrue(plan.getFieldBindings().stream().anyMatch(b ->
"data.msg".equals(b.getFieldName())));
+ Assertions.assertTrue(plan.getFieldBindings().stream().anyMatch(b ->
"data.title".equals(b.getFieldName())));
+ }
+
+ @Test
+ public void testNestedQueryDescendantFieldLuceneMode() {
+ String dsl = "NESTED(data.items, input.display_text:selforigin)";
+ QsPlan plan = SearchDslParser.parseDsl(dsl,
+
"{\"mode\":\"lucene\",\"default_operator\":\"AND\",\"minimum_should_match\":0}");
+
+ Assertions.assertNotNull(plan);
+ Assertions.assertEquals(QsClauseType.NESTED, plan.getRoot().getType());
+ Assertions.assertEquals("data.items", plan.getRoot().getNestedPath());
+ Assertions.assertTrue(plan.getFieldBindings().stream()
+ .anyMatch(b ->
"data.items.input.display_text".equals(b.getFieldName())));
+ }
+
+ @Test
+ public void testNestedQueryMustBeTopLevelInAndLuceneMode() {
+ String dsl = "title:hello AND NESTED(data, msg:hello)";
+ RuntimeException exception =
Assertions.assertThrows(RuntimeException.class, () -> {
+ SearchDslParser.parseDsl(dsl,
+
"{\"mode\":\"lucene\",\"default_operator\":\"AND\",\"minimum_should_match\":0}");
+ });
+ Assertions.assertTrue(exception.getMessage().contains("NESTED clause
must be evaluated at top level"));
+ }
+
+ @Test
+ public void testNestedQueryMixedRelativeAndAbsoluteRejected() {
+ String dsl = "NESTED(data.items, msg:hello AND data.items.title:news)";
+ RuntimeException exception =
Assertions.assertThrows(RuntimeException.class, () -> {
+ SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
+ });
+ Assertions.assertTrue(exception.getMessage().contains("Fields in
NESTED predicates must be relative"));
+ }
+
+ @Test
+ public void testNestedQueryBareQueryRejected() {
+ String dsl = "NESTED(data.items, hello)";
+ RuntimeException exception =
Assertions.assertThrows(RuntimeException.class, () -> {
+ SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
+ });
+ Assertions.assertTrue(exception.getMessage().contains("Bare queries
are not supported inside NESTED predicates"));
+ }
+
+ @Test
+ public void testNestedQueryNestedNestedRejected() {
+ String dsl = "NESTED(data, NESTED(data.items, msg:hello))";
+ RuntimeException exception =
Assertions.assertThrows(RuntimeException.class, () -> {
+ SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
+ });
+ Assertions.assertTrue(exception.getMessage().contains("Nested NESTED()
is not supported"));
}
@Test
public void testNestedQueryMustBeTopLevelInAnd() {
- String dsl = "title:hello AND NESTED(data, data.msg:hello)";
+ String dsl = "title:hello AND NESTED(data, msg:hello)";
RuntimeException exception =
Assertions.assertThrows(RuntimeException.class, () -> {
SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
});
@@ -2589,7 +2669,7 @@ public class SearchDslParserTest {
@Test
public void testNestedQueryMustBeTopLevelInOr() {
- String dsl = "NESTED(data, data.msg:hello) OR title:hello";
+ String dsl = "NESTED(data, msg:hello) OR title:hello";
RuntimeException exception =
Assertions.assertThrows(RuntimeException.class, () -> {
SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
});
@@ -2598,7 +2678,7 @@ public class SearchDslParserTest {
@Test
public void testNestedQueryMustBeTopLevelInNot() {
- String dsl = "NOT NESTED(data, data.msg:hello)";
+ String dsl = "NOT NESTED(data, msg:hello)";
RuntimeException exception =
Assertions.assertThrows(RuntimeException.class, () -> {
SearchDslParser.parseDsl(dsl, "{\"mode\":\"standard\"}");
});
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]