Copilot commented on code in PR #57312:
URL: https://github.com/apache/doris/pull/57312#discussion_r2460228062
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParser.java:
##########
@@ -107,11 +126,271 @@ public void
syntaxError(org.antlr.v4.runtime.Recognizer<?, ?> recognizer,
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+.*");
Review Comment:
Multiple regex matches on the same string can be inefficient. Consider using
a single compiled Pattern or combining these patterns into one regex:
`Pattern.matches(\"^NOT\\s+.*|.*\\s+(AND|OR|NOT)\\s+.*\", upper)` to improve
performance for frequently called code.
##########
fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParserTest.java:
##########
@@ -272,4 +272,309 @@ public void testQuotedFieldNames() {
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
Review Comment:
The comment is misleading. The test expects an exception not because
`\"foo\"` is invalid DSL in general, but because with an empty default field,
the expansion fails. Clarify that the exception occurs due to the empty default
field preventing proper DSL expansion.
```suggestion
// This should throw an exception because with an empty default
field, the parser cannot expand "foo" into a valid fielded query
```
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParser.java:
##########
@@ -107,11 +126,271 @@ public void
syntaxError(org.antlr.v4.runtime.Recognizer<?, ?> recognizer,
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'");
+ }
Review Comment:
[nitpick] The null/empty check and trimming logic is duplicated. Consider
extracting the trimming and null handling: `String trimmed = (operator == null)
? \"\" : operator.trim(); if (trimmed.isEmpty()) return \"or\";` This reduces
redundancy and makes the intent clearer.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SearchDslParser.java:
##########
@@ -107,11 +126,271 @@ public void
syntaxError(org.antlr.v4.runtime.Recognizer<?, ?> recognizer,
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())) {
Review Comment:
[nitpick] The operator matching logic for AND, OR, and NOT (lines 269-317)
contains duplicated patterns. Consider extracting a helper method
`matchOperator(String remaining, String operator, int length)` to reduce code
duplication and improve maintainability.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]