Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1517,7 +1517,14 @@ public static QsNode expandBestFields(QsNode root, List<String> fields) {
private static QsNode expandNodeCrossFields(QsNode node, List<String> fields, boolean luceneMode) {
// MATCH_ALL_DOCS matches all documents regardless of field - don't expand
if (node.getType() == QsClauseType.MATCH_ALL_DOCS) {
return new QsNode(QsClauseType.MATCH_ALL_DOCS, (List<QsNode>) null);
QsNode result = new QsNode(QsClauseType.MATCH_ALL_DOCS, (List<QsNode>) null);
// Preserve occur attribute (e.g., SHOULD from "X OR *" queries)
// Without this, occur defaults to null which BE interprets as MUST,
// changing "X OR *" from matching all docs to matching only X.
if (node.getOccur() != null) {
result.setOccur(node.getOccur());
}
return result;
}

// Check if this is a leaf node (no children)
Expand Down Expand Up @@ -1598,7 +1605,11 @@ private static boolean isLeafNode(QsNode node) {
private static QsNode deepCopyWithField(QsNode node, String field, List<String> fields) {
// MATCH_ALL_DOCS matches all documents regardless of field - don't set field
if (node.getType() == QsClauseType.MATCH_ALL_DOCS) {
return new QsNode(QsClauseType.MATCH_ALL_DOCS, (List<QsNode>) null);
QsNode result = new QsNode(QsClauseType.MATCH_ALL_DOCS, (List<QsNode>) null);
if (node.getOccur() != null) {
result.setOccur(node.getOccur());
}
return result;
}
if (isLeafNode(node)) {
// If the user explicitly wrote "field:term" syntax, preserve original field
Expand Down Expand Up @@ -1645,7 +1656,11 @@ private static QsNode deepCopyWithField(QsNode node, String field, List<String>
private static QsNode setFieldOnLeaves(QsNode node, String field, List<String> fields) {
// MATCH_ALL_DOCS matches all documents regardless of field - don't set field
if (node.getType() == QsClauseType.MATCH_ALL_DOCS) {
return new QsNode(QsClauseType.MATCH_ALL_DOCS, (List<QsNode>) null);
QsNode result = new QsNode(QsClauseType.MATCH_ALL_DOCS, (List<QsNode>) null);
if (node.getOccur() != null) {
result.setOccur(node.getOccur());
}
return result;
}
if (isLeafNode(node)) {
// If the user explicitly wrote "field:term" syntax, preserve original field
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2473,4 +2473,59 @@ public void testSingleFieldMatchAllDocsLuceneMode() {
Assertions.assertEquals(1, plan.getFieldBindings().size());
Assertions.assertEquals("title", plan.getFieldBindings().get(0).getFieldName());
}

@Test
public void testMultiFieldMatchAllDocsPreservesOccurInOrQuery() {
// Test: '"Lauren Boebert" OR *' with multi-field + lucene mode + best_fields
// Bug: expandCrossFields was dropping the SHOULD occur on MATCH_ALL_DOCS nodes,
// causing BE to default to MUST, which changed the semantics from
// "phrase OR match_all" (= all docs) to "phrase AND match_all" (= only phrase matches).
String dsl = "\"Lauren Boebert\" OR *";
String options = "{\"fields\":[\"title\",\"content\"],\"type\":\"best_fields\","
+ "\"default_operator\":\"AND\",\"mode\":\"lucene\",\"minimum_should_match\":0}";

QsPlan plan = SearchDslParser.parseDsl(dsl, options);
Assertions.assertNotNull(plan);

// Root should be OCCUR_BOOLEAN
Assertions.assertEquals(QsClauseType.OCCUR_BOOLEAN, plan.getRoot().getType());

// Find the MATCH_ALL_DOCS child - it MUST have occur=SHOULD
boolean foundMatchAllWithShould = false;
for (QsNode child : plan.getRoot().getChildren()) {
if (child.getType() == QsClauseType.MATCH_ALL_DOCS) {
Assertions.assertEquals(QsOccur.SHOULD, child.getOccur(),
"MATCH_ALL_DOCS must preserve SHOULD occur after multi-field expansion");
foundMatchAllWithShould = true;
}
}
Assertions.assertTrue(foundMatchAllWithShould,
"Should contain MATCH_ALL_DOCS node with SHOULD occur");
}

@Test
public void testMultiFieldMatchAllDocsPreservesOccurWithAndOperator() {
// Test: 'Dollar AND *' with multi-field + lucene mode
// MATCH_ALL_DOCS should have occur=MUST (from AND operator)
String dsl = "Dollar AND *";
String options = "{\"fields\":[\"title\",\"content\"],\"type\":\"best_fields\","
+ "\"default_operator\":\"OR\",\"mode\":\"lucene\",\"minimum_should_match\":0}";

QsPlan plan = SearchDslParser.parseDsl(dsl, options);
Assertions.assertNotNull(plan);

Assertions.assertEquals(QsClauseType.OCCUR_BOOLEAN, plan.getRoot().getType());

// Find the MATCH_ALL_DOCS child - it MUST have occur=MUST (from AND operator)
boolean foundMatchAllWithMust = false;
for (QsNode child : plan.getRoot().getChildren()) {
if (child.getType() == QsClauseType.MATCH_ALL_DOCS) {
Assertions.assertEquals(QsOccur.MUST, child.getOccur(),
"MATCH_ALL_DOCS must preserve MUST occur after multi-field expansion");
foundMatchAllWithMust = true;
}
}
Assertions.assertTrue(foundMatchAllWithMust,
"Should contain MATCH_ALL_DOCS node with MUST occur");
}
}
Loading