This is an automated email from the ASF dual-hosted git repository.

airborne 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 ef415d2990e [feature](search) add another two params for search 
(#57312)
ef415d2990e is described below

commit ef415d2990ef4ee98170c735899c48d1245d8a43
Author: Jack <[email protected]>
AuthorDate: Sat Oct 25 19:47:02 2025 +0800

    [feature](search) add another two params for search (#57312)
    
    ### What problem does this PR solve?
    
    Problem Summary:
    
    * Extend `search()` to accept **1–3 arguments**:
    
      1. `query`: the original search DSL string (unchanged).
    2. `default_field` *(optional)*: field name applied to unqualified terms
    in `query`.
    3. `default_operator` *(optional)*: boolean operator for multi-term
    queries; accepts `"and"` or `"or"` (case-insensitive).
    
      * If omitted or empty, the operator defaults to **`or`**.
    * Parser updates:
    
    * When `default_field` is provided, unqualified terms in `query` are
    automatically rewritten to `default_field:term`.
    * `default_operator` is validated and normalized; invalid values produce
    a clear error.
    
    ### Example
    
    **Before** (must qualify every term or rely on engine defaulting rules):
    
    ```sql
    SELECT *
    FROM t
    WHERE search('title:payment title:timeout')
    ```
    
    **After** (use `default_field = "title"` and `default_operator =
    "and"`):
    
    ```sql
    SELECT *
    FROM t
    WHERE search('payment timeout', 'title', 'and');
    ```
    
    This evaluates as if the query were `title:payment AND title:timeout`.
---
 .../trees/expressions/functions/scalar/Search.java |  46 +++-
 .../functions/scalar/SearchDslParser.java          | 283 ++++++++++++++++++-
 .../functions/scalar/SearchDslParserTest.java      | 305 +++++++++++++++++++++
 3 files changed, 629 insertions(+), 5 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Search.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Search.java
index 515ab6e37ec..f89af38cc22 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Search.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Search.java
@@ -33,12 +33,23 @@ import java.util.List;
 /**
  * ScalarFunction 'search' - simplified architecture similar to MultiMatch.
  * Handles DSL parsing and generates SearchPredicate during translation.
+ * <p>
+ * Supports 1-3 parameters:
+ * - search(dsl_string): Traditional usage
+ * - search(dsl_string, default_field): Simplified syntax with default field
+ * - search(dsl_string, default_field, default_operator): Full control over 
expansion
  */
 public class Search extends ScalarFunction
         implements ExplicitlyCastableSignature, AlwaysNotNullable {
 
     public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
-            
FunctionSignature.ret(BooleanType.INSTANCE).varArgs(StringType.INSTANCE)
+            // Original signature: search(dsl_string)
+            
FunctionSignature.ret(BooleanType.INSTANCE).args(StringType.INSTANCE),
+            // With default field: search(dsl_string, default_field)
+            
FunctionSignature.ret(BooleanType.INSTANCE).args(StringType.INSTANCE, 
StringType.INSTANCE),
+            // With default field and operator: search(dsl_string, 
default_field, default_operator)
+            
FunctionSignature.ret(BooleanType.INSTANCE).args(StringType.INSTANCE, 
StringType.INSTANCE,
+                    StringType.INSTANCE)
     );
 
     public Search(Expression... varArgs) {
@@ -51,7 +62,8 @@ public class Search extends ScalarFunction
 
     @Override
     public Search withChildren(List<Expression> children) {
-        Preconditions.checkArgument(children.size() >= 1);
+        Preconditions.checkArgument(children.size() >= 1 && children.size() <= 
3,
+                "search() requires 1-3 arguments");
         return new Search(getFunctionParams(children));
     }
 
@@ -76,13 +88,41 @@ public class Search extends ScalarFunction
         return dslArg.toString();
     }
 
+    /**
+     * Get default field from second argument (optional)
+     */
+    public String getDefaultField() {
+        if (children().size() < 2) {
+            return null;
+        }
+        Expression fieldArg = child(1);
+        if (fieldArg instanceof StringLikeLiteral) {
+            return ((StringLikeLiteral) fieldArg).getStringValue();
+        }
+        return fieldArg.toString();
+    }
+
+    /**
+     * Get default operator from third argument (optional)
+     */
+    public String getDefaultOperator() {
+        if (children().size() < 3) {
+            return null;
+        }
+        Expression operatorArg = child(2);
+        if (operatorArg instanceof StringLikeLiteral) {
+            return ((StringLikeLiteral) operatorArg).getStringValue();
+        }
+        return operatorArg.toString();
+    }
+
     /**
      * Get parsed DSL plan - deferred to translation phase
      * This will be handled by SearchPredicate during 
ExpressionTranslator.visitSearch()
      */
     public SearchDslParser.QsPlan getQsPlan() {
         // Lazy evaluation will be handled in SearchPredicate
-        return SearchDslParser.parseDsl(getDslString());
+        return SearchDslParser.parseDsl(getDslString(), getDefaultField(), 
getDefaultOperator());
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParser.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParser.java
index e9c79acf9dc..8dfd9febb68 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParser.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParser.java
@@ -61,13 +61,32 @@ public class SearchDslParser {
      * Parse DSL string and return intermediate representation
      */
     public static QsPlan parseDsl(String dsl) {
+        return parseDsl(dsl, null, null);
+    }
+
+    /**
+     * Parse DSL string with default field and operator support
+     *
+     * @param dsl DSL query string
+     * @param defaultField Default field name when DSL doesn't specify field 
(optional)
+     * @param defaultOperator Default operator ("and" or "or") for multi-term 
queries (optional, defaults to "or")
+     * @return Parsed QsPlan
+     */
+    public static QsPlan parseDsl(String dsl, String defaultField, String 
defaultOperator) {
         if (dsl == null || dsl.trim().isEmpty()) {
             return new QsPlan(new QsNode(QsClauseType.TERM, "error", 
"empty_dsl"), new ArrayList<>());
         }
 
+        // Expand simplified DSL if default field is provided
+        String expandedDsl = dsl;
+        if (defaultField != null && !defaultField.trim().isEmpty()) {
+            expandedDsl = expandSimplifiedDsl(dsl.trim(), defaultField.trim(),
+                    normalizeDefaultOperator(defaultOperator));
+        }
+
         try {
             // Create ANTLR lexer and parser
-            SearchLexer lexer = new SearchLexer(new ANTLRInputStream(dsl));
+            SearchLexer lexer = new SearchLexer(new 
ANTLRInputStream(expandedDsl));
             CommonTokenStream tokens = new CommonTokenStream(lexer);
             SearchParser parser = new SearchParser(tokens);
 
@@ -107,11 +126,271 @@ public class SearchDslParser {
             return new QsPlan(root, bindings);
 
         } catch (Exception e) {
-            LOG.error("Failed to parse search DSL: '{}'", dsl, e);
+            LOG.error("Failed to parse search DSL: '{}' (expanded: '{}')", 
dsl, expandedDsl, e);
             throw new RuntimeException("Invalid search DSL syntax: " + dsl + 
". Error: " + e.getMessage(), e);
         }
     }
 
+    /**
+     * Normalize default operator to lowercase "and" or "or"
+     */
+    private static String normalizeDefaultOperator(String operator) {
+        if (operator == null || operator.trim().isEmpty()) {
+            return "or";  // Default to OR
+        }
+        String normalized = operator.trim().toLowerCase();
+        if ("and".equals(normalized) || "or".equals(normalized)) {
+            return normalized;
+        }
+        throw new IllegalArgumentException("Invalid default operator: " + 
operator
+                + ". Must be 'and' or 'or'");
+    }
+
+    /**
+     * Expand simplified DSL to full DSL format
+     * <p>
+     * Examples:
+     * - "foo bar" + field="tags" + operator="and" → "tags:ALL(foo bar)"
+     * - "foo* bar*" + field="tags" + operator="and" → "tags:foo* AND 
tags:bar*"
+     * - "foo OR bar" + field="tags" → "tags:foo OR tags:bar"
+     * - "EXACT(foo bar)" + field="tags" → "tags:EXACT(foo bar)"
+     *
+     * @param dsl Simple DSL string
+     * @param defaultField Default field name
+     * @param defaultOperator "and" or "or"
+     * @return Expanded full DSL
+     */
+    private static String expandSimplifiedDsl(String dsl, String defaultField, 
String defaultOperator) {
+        // 1. If DSL already contains field names (colon), return as-is
+        if (containsFieldReference(dsl)) {
+            return dsl;
+        }
+
+        // 2. Check if DSL starts with a function keyword (EXACT, ANY, ALL, IN)
+        if (startsWithFunction(dsl)) {
+            return defaultField + ":" + dsl;
+        }
+
+        // 3. Check for explicit boolean operators in DSL
+        if (containsExplicitOperators(dsl)) {
+            return addFieldPrefixToOperatorExpression(dsl, defaultField);
+        }
+
+        // 4. Tokenize and analyze terms
+        List<String> terms = tokenizeDsl(dsl);
+        if (terms.isEmpty()) {
+            return defaultField + ":" + dsl;
+        }
+
+        // 5. Single term - simple case
+        if (terms.size() == 1) {
+            return defaultField + ":" + terms.get(0);
+        }
+
+        // 6. Multiple terms - check for wildcards
+        boolean hasWildcard = 
terms.stream().anyMatch(SearchDslParser::containsWildcard);
+
+        if (hasWildcard) {
+            // Wildcards cannot be tokenized - must create separate field 
queries
+            String operator = "and".equals(defaultOperator) ? " AND " : " OR ";
+            return terms.stream()
+                    .map(term -> defaultField + ":" + term)
+                    .collect(java.util.stream.Collectors.joining(operator));
+        } else {
+            // Regular multi-term query - use ANY/ALL
+            String clauseType = "and".equals(defaultOperator) ? "ALL" : "ANY";
+            return defaultField + ":" + clauseType + "(" + dsl + ")";
+        }
+    }
+
+    /**
+     * Check if DSL contains field references (has colon not in quoted strings)
+     */
+    private static boolean containsFieldReference(String dsl) {
+        boolean inQuotes = false;
+        boolean inRegex = false;
+        for (int i = 0; i < dsl.length(); i++) {
+            char c = dsl.charAt(i);
+            if (c == '"' && (i == 0 || dsl.charAt(i - 1) != '\\')) {
+                inQuotes = !inQuotes;
+            } else if (c == '/' && !inQuotes) {
+                inRegex = !inRegex;
+            } else if (c == ':' && !inQuotes && !inRegex) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if DSL starts with function keywords
+     */
+    private static boolean startsWithFunction(String dsl) {
+        String upper = dsl.toUpperCase();
+        return upper.startsWith("EXACT(")
+                || upper.startsWith("ANY(")
+                || upper.startsWith("ALL(")
+                || upper.startsWith("IN(");
+    }
+
+    /**
+     * Check if DSL contains explicit boolean operators (AND/OR/NOT)
+     */
+    private static boolean containsExplicitOperators(String dsl) {
+        // Look for standalone AND/OR/NOT keywords (not part of field names)
+        String upper = dsl.toUpperCase();
+        return upper.matches(".*\\s+(AND|OR)\\s+.*")
+                || upper.matches("^NOT\\s+.*")
+                || upper.matches(".*\\s+NOT\\s+.*");
+    }
+
+    /**
+     * Add field prefix to expressions with explicit operators
+     * Example: "foo AND bar" → "field:foo AND field:bar"
+     */
+    private static String addFieldPrefixToOperatorExpression(String dsl, 
String defaultField) {
+        StringBuilder result = new StringBuilder();
+        StringBuilder currentTerm = new StringBuilder();
+        int i = 0;
+
+        while (i < dsl.length()) {
+            // Skip whitespace
+            while (i < dsl.length() && Character.isWhitespace(dsl.charAt(i))) {
+                i++;
+            }
+            if (i >= dsl.length()) {
+                break;
+            }
+
+            // Try to match operators
+            String remaining = dsl.substring(i);
+            String upperRemaining = remaining.toUpperCase();
+
+            if (upperRemaining.startsWith("AND ") || 
upperRemaining.startsWith("AND\t")
+                    || (upperRemaining.equals("AND") && i + 3 >= 
dsl.length())) {
+                // Found AND operator
+                if (currentTerm.length() > 0) {
+                    if (result.length() > 0) {
+                        result.append(" ");
+                    }
+                    
result.append(defaultField).append(":").append(currentTerm.toString().trim());
+                    currentTerm.setLength(0);
+                }
+                if (result.length() > 0) {
+                    result.append(" ");
+                }
+                result.append(dsl.substring(i, i + 3)); // Preserve original 
case
+                i += 3;
+                continue;
+            } else if (upperRemaining.startsWith("OR ") || 
upperRemaining.startsWith("OR\t")
+                    || (upperRemaining.equals("OR") && i + 2 >= dsl.length())) 
{
+                // Found OR operator
+                if (currentTerm.length() > 0) {
+                    if (result.length() > 0) {
+                        result.append(" ");
+                    }
+                    
result.append(defaultField).append(":").append(currentTerm.toString().trim());
+                    currentTerm.setLength(0);
+                }
+                if (result.length() > 0) {
+                    result.append(" ");
+                }
+                result.append(dsl.substring(i, i + 2)); // Preserve original 
case
+                i += 2;
+                continue;
+            } else if (upperRemaining.startsWith("NOT ") || 
upperRemaining.startsWith("NOT\t")
+                    || (upperRemaining.equals("NOT") && i + 3 >= 
dsl.length())) {
+                // Found NOT operator
+                if (currentTerm.length() > 0) {
+                    if (result.length() > 0) {
+                        result.append(" ");
+                    }
+                    
result.append(defaultField).append(":").append(currentTerm.toString().trim());
+                    currentTerm.setLength(0);
+                }
+                if (result.length() > 0) {
+                    result.append(" ");
+                }
+                result.append(dsl.substring(i, i + 3)); // Preserve original 
case
+                i += 3;
+                continue;
+            }
+
+            // Not an operator, accumulate term
+            currentTerm.append(dsl.charAt(i));
+            i++;
+        }
+
+        // Add last term
+        if (currentTerm.length() > 0) {
+            if (result.length() > 0) {
+                result.append(" ");
+            }
+            
result.append(defaultField).append(":").append(currentTerm.toString().trim());
+        }
+
+        return result.toString().trim();
+    }
+
+    /**
+     * Tokenize DSL into terms (split by whitespace, respecting quotes and 
functions)
+     */
+    private static List<String> tokenizeDsl(String dsl) {
+        List<String> terms = new ArrayList<>();
+        StringBuilder currentTerm = new StringBuilder();
+        boolean inQuotes = false;
+        boolean inParens = false;
+        int parenDepth = 0;
+
+        for (int i = 0; i < dsl.length(); i++) {
+            char c = dsl.charAt(i);
+
+            if (c == '"' && (i == 0 || dsl.charAt(i - 1) != '\\')) {
+                inQuotes = !inQuotes;
+                currentTerm.append(c);
+            } else if (c == '(' && !inQuotes) {
+                parenDepth++;
+                inParens = true;
+                currentTerm.append(c);
+            } else if (c == ')' && !inQuotes) {
+                parenDepth--;
+                if (parenDepth == 0) {
+                    inParens = false;
+                }
+                currentTerm.append(c);
+            } else if (Character.isWhitespace(c) && !inQuotes && !inParens) {
+                // End of term
+                if (currentTerm.length() > 0) {
+                    terms.add(currentTerm.toString());
+                    currentTerm = new StringBuilder();
+                }
+            } else {
+                currentTerm.append(c);
+            }
+        }
+
+        // Add last term
+        if (currentTerm.length() > 0) {
+            terms.add(currentTerm.toString());
+        }
+
+        return terms;
+    }
+
+    /**
+     * Check if a term contains wildcard characters (* or ?)
+     */
+    private static boolean containsWildcard(String term) {
+        // Ignore wildcards in quoted strings or regex
+        if (term.startsWith("\"") && term.endsWith("\"")) {
+            return false;
+        }
+        if (term.startsWith("/") && term.endsWith("/")) {
+            return false;
+        }
+        return term.contains("*") || term.contains("?");
+    }
+
     /**
      * Clause types supported
      */
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 66e57e77fcc..eb1bf3f5d3a 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
@@ -272,4 +272,309 @@ public class SearchDslParserTest {
         Assertions.assertEquals(1, plan.fieldBindings.size());
         Assertions.assertEquals("field name", 
plan.fieldBindings.get(0).fieldName);
     }
+
+    // ============ Tests for Default Field and Operator Support ============
+
+    @Test
+    public void testDefaultFieldWithSimpleTerm() {
+        // Test: "foo" + field="tags" → "tags:foo"
+        String dsl = "foo";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", null);
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.TERM, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("foo", plan.root.value);
+        Assertions.assertEquals(1, plan.fieldBindings.size());
+        Assertions.assertEquals("tags", plan.fieldBindings.get(0).fieldName);
+    }
+
+    @Test
+    public void testDefaultFieldWithMultiTermAnd() {
+        // Test: "foo bar" + field="tags" + operator="and" → "tags:ALL(foo 
bar)"
+        String dsl = "foo bar";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "and");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.ALL, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("foo bar", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithMultiTermOr() {
+        // Test: "foo bar" + field="tags" + operator="or" → "tags:ANY(foo bar)"
+        String dsl = "foo bar";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "or");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.ANY, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("foo bar", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithMultiTermDefaultOr() {
+        // Test: "foo bar" + field="tags" (no operator, defaults to OR) → 
"tags:ANY(foo bar)"
+        String dsl = "foo bar";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", null);
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.ANY, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("foo bar", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithWildcardSingleTerm() {
+        // Test: "foo*" + field="tags" → "tags:foo*"
+        String dsl = "foo*";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", null);
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.PREFIX, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("foo*", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithWildcardMultiTermAnd() {
+        // Test: "foo* bar*" + field="tags" + operator="and" → "tags:foo* AND 
tags:bar*"
+        String dsl = "foo* bar*";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "and");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.AND, plan.root.type);
+        Assertions.assertEquals(2, plan.root.children.size());
+
+        QsNode firstChild = plan.root.children.get(0);
+        Assertions.assertEquals(QsClauseType.PREFIX, firstChild.type);
+        Assertions.assertEquals("tags", firstChild.field);
+        Assertions.assertEquals("foo*", firstChild.value);
+
+        QsNode secondChild = plan.root.children.get(1);
+        Assertions.assertEquals(QsClauseType.PREFIX, secondChild.type);
+        Assertions.assertEquals("tags", secondChild.field);
+        Assertions.assertEquals("bar*", secondChild.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithWildcardMultiTermOr() {
+        // Test: "foo* bar*" + field="tags" + operator="or" → "tags:foo* OR 
tags:bar*"
+        String dsl = "foo* bar*";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "or");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.OR, plan.root.type);
+        Assertions.assertEquals(2, plan.root.children.size());
+    }
+
+    @Test
+    public void testDefaultFieldWithExplicitOperatorOverride() {
+        // Test: "foo OR bar" + field="tags" + operator="and" → explicit OR 
takes precedence
+        String dsl = "foo OR bar";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "and");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.OR, plan.root.type);
+        Assertions.assertEquals(2, plan.root.children.size());
+
+        QsNode firstChild = plan.root.children.get(0);
+        Assertions.assertEquals("tags", firstChild.field);
+        Assertions.assertEquals("foo", firstChild.value);
+
+        QsNode secondChild = plan.root.children.get(1);
+        Assertions.assertEquals("tags", secondChild.field);
+        Assertions.assertEquals("bar", secondChild.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithExplicitAndOperator() {
+        // Test: "foo AND bar" + field="tags" + operator="or" → explicit AND 
takes precedence
+        String dsl = "foo AND bar";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "or");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.AND, plan.root.type);
+        Assertions.assertEquals(2, plan.root.children.size());
+    }
+
+    @Test
+    public void testDefaultFieldWithExactFunction() {
+        // Test: "EXACT(foo bar)" + field="tags" → "tags:EXACT(foo bar)"
+        String dsl = "EXACT(foo bar)";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", null);
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.EXACT, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("foo bar", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithAnyFunction() {
+        // Test: "ANY(foo bar)" + field="tags" → "tags:ANY(foo bar)"
+        String dsl = "ANY(foo bar)";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", null);
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.ANY, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("foo bar", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithAllFunction() {
+        // Test: "ALL(foo bar)" + field="tags" → "tags:ALL(foo bar)"
+        String dsl = "ALL(foo bar)";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", null);
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.ALL, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("foo bar", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldIgnoredWhenDslHasFieldReference() {
+        // Test: DSL with field reference should ignore default field
+        String dsl = "title:hello";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "and");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.TERM, plan.root.type);
+        Assertions.assertEquals("title", plan.root.field); // Should be 
"title", not "tags"
+        Assertions.assertEquals("hello", plan.root.value);
+    }
+
+    @Test
+    public void testInvalidDefaultOperator() {
+        // Test: invalid operator should throw exception
+        String dsl = "foo bar";
+
+        IllegalArgumentException exception = 
Assertions.assertThrows(IllegalArgumentException.class, () -> {
+            SearchDslParser.parseDsl(dsl, "tags", "invalid");
+        });
+
+        Assertions.assertTrue(exception.getMessage().contains("Invalid default 
operator"));
+        Assertions.assertTrue(exception.getMessage().contains("Must be 'and' 
or 'or'"));
+    }
+
+    @Test
+    public void testDefaultOperatorCaseInsensitive() {
+        // Test: operator should be case-insensitive
+        String dsl = "foo bar";
+
+        // Test "AND"
+        QsPlan plan1 = SearchDslParser.parseDsl(dsl, "tags", "AND");
+        Assertions.assertEquals(QsClauseType.ALL, plan1.root.type);
+
+        // Test "Or"
+        QsPlan plan2 = SearchDslParser.parseDsl(dsl, "tags", "Or");
+        Assertions.assertEquals(QsClauseType.ANY, plan2.root.type);
+
+        // Test "aNd"
+        QsPlan plan3 = SearchDslParser.parseDsl(dsl, "tags", "aNd");
+        Assertions.assertEquals(QsClauseType.ALL, plan3.root.type);
+    }
+
+    @Test
+    public void testDefaultFieldWithComplexWildcard() {
+        // Test: "*foo*" (middle wildcard) + field="tags"
+        String dsl = "*foo*";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", null);
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.WILDCARD, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("*foo*", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithMixedWildcards() {
+        // Test: "foo* *bar baz" (mixed wildcards and regular terms) + 
field="tags" + operator="and"
+        String dsl = "foo* bar baz";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "and");
+
+        Assertions.assertNotNull(plan);
+        // Should create AND query because it contains wildcards
+        Assertions.assertEquals(QsClauseType.AND, plan.root.type);
+        Assertions.assertEquals(3, plan.root.children.size());
+    }
+
+    @Test
+    public void testDefaultFieldWithQuotedPhrase() {
+        // Test: quoted phrase should be treated as PHRASE
+        String dsl = "\"hello world\"";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "and");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.PHRASE, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("hello world", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithNotOperator() {
+        // Test: "NOT foo" + field="tags" → "NOT tags:foo"
+        String dsl = "NOT foo";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", null);
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.NOT, plan.root.type);
+        Assertions.assertEquals(1, plan.root.children.size());
+
+        QsNode child = plan.root.children.get(0);
+        Assertions.assertEquals(QsClauseType.TERM, child.type);
+        Assertions.assertEquals("tags", child.field);
+        Assertions.assertEquals("foo", child.value);
+    }
+
+    @Test
+    public void testDefaultFieldWithEmptyString() {
+        // Test: empty default field should not expand DSL, causing parse error
+        // for incomplete DSL like "foo" (no field specified)
+        String dsl = "foo";
+
+        // This should throw an exception because "foo" alone is not valid DSL
+        RuntimeException exception = 
Assertions.assertThrows(RuntimeException.class, () -> {
+            SearchDslParser.parseDsl(dsl, "", "and");
+        });
+
+        Assertions.assertTrue(exception.getMessage().contains("Invalid search 
DSL syntax"));
+    }
+
+    @Test
+    public void testDefaultFieldWithNullOperator() {
+        // Test: null operator should default to OR
+        String dsl = "foo bar";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", null);
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.ANY, plan.root.type); // Defaults 
to OR/ANY
+    }
+
+    @Test
+    public void testDefaultFieldWithSingleWildcardTerm() {
+        // Test: single term with wildcard should not use ANY/ALL
+        String dsl = "f?o";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "and");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(QsClauseType.WILDCARD, plan.root.type);
+        Assertions.assertEquals("tags", plan.root.field);
+        Assertions.assertEquals("f?o", plan.root.value);
+    }
+
+    @Test
+    public void testDefaultFieldPreservesFieldBindings() {
+        // Test: field bindings should be correctly generated with default 
field
+        String dsl = "foo bar";
+        QsPlan plan = SearchDslParser.parseDsl(dsl, "tags", "and");
+
+        Assertions.assertNotNull(plan);
+        Assertions.assertEquals(1, plan.fieldBindings.size());
+        Assertions.assertEquals("tags", plan.fieldBindings.get(0).fieldName);
+        Assertions.assertEquals(0, plan.fieldBindings.get(0).slotIndex);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to