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 4128eb41d2d [fix](search) Make AND/OR/NOT operators case-sensitive in
search DSL (#59747)
4128eb41d2d is described below
commit 4128eb41d2d58e3e14bf98db9453d0c2cbe0d799
Author: Jack <[email protected]>
AuthorDate: Sat Feb 14 16:12:17 2026 +0800
[fix](search) Make AND/OR/NOT operators case-sensitive in search DSL
(#59747)
### What problem does this PR solve?
Issue Number: close #xxx
Related PR: #59394
Problem Summary:
The search DSL should only recognize uppercase `AND`, `OR`, `NOT` as
boolean operators in search lucene boolean mode. Previously, lowercase
`and`, `or`, `not` were also treated as operators, which does not
conform to the specification.
This PR makes the boolean operators case-sensitive:
- Only uppercase `AND`, `OR`, `NOT` are recognized as operators
- Lowercase `and`, `or`, `not` are now treated as regular search terms
- Using lowercase operators in DSL will result in a parse error
### Release note
Make search DSL boolean operators (AND/OR/NOT) case-sensitive in lucene
boolean mode.
---
.../org/apache/doris/nereids/search/SearchLexer.g4 | 6 +-
.../functions/scalar/SearchDslParserTest.java | 43 ++--
.../data/search/test_search_dsl_operators.out | 45 ++++
regression-test/data/search/test_search_escape.out | 9 -
.../search/test_search_boundary_cases.groovy | 40 ++--
.../suites/search/test_search_dsl_operators.groovy | 231 ++++++++++++++++++
.../suites/search/test_search_escape.groovy | 40 ++--
.../search/test_search_function.groovy.backup | 261 +++++++++++++++++++++
.../search/test_search_null_regression.groovy | 24 +-
.../search/test_search_null_semantics.groovy | 16 +-
.../search/test_search_vs_match_consistency.groovy | 30 +--
11 files changed, 638 insertions(+), 107 deletions(-)
diff --git
a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/search/SearchLexer.g4
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/search/SearchLexer.g4
index 4dab0af2ed2..7767e66ac7d 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/search/SearchLexer.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/search/SearchLexer.g4
@@ -41,9 +41,9 @@ fragment QUOTED_CHAR
// ============== Default lexer rules ==============
-AND : 'AND' | 'and' ;
-OR : 'OR' | 'or' ;
-NOT : 'NOT' | 'not' | '!' ;
+AND : 'AND' ;
+OR : 'OR' ;
+NOT : 'NOT' | '!' ;
LPAREN : '(' ;
RPAREN : ')' ;
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 e7daf12f100..d293433eb1a 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
@@ -872,16 +872,13 @@ public class SearchDslParserTest {
@Test
public void testLowercaseAndOperator() {
- // Test: Currently lowercase 'and' is also treated as operator
- // According to PDF requirement, only uppercase should be operators
- // This test documents current behavior - may need to change
+ // Lowercase 'and' is NOT an operator in ANTLR grammar
(case-sensitive).
+ // With bareQuery rule, it's parsed as a bare term without field.
+ // Without default_field, bare term throws exception.
String dsl = "field:a and field:b";
- QsPlan plan = SearchDslParser.parseDsl(dsl);
-
- Assertions.assertNotNull(plan);
- // Current behavior: lowercase 'and' IS an operator
- Assertions.assertEquals(QsClauseType.AND, plan.getRoot().getType());
- // TODO: If PDF requires only uppercase, this should fail and return
OR or different structure
+ Assertions.assertThrows(RuntimeException.class, () -> {
+ SearchDslParser.parseDsl(dsl);
+ });
}
@Test
@@ -897,15 +894,13 @@ public class SearchDslParserTest {
@Test
public void testLowercaseOrOperator() {
- // Test: Currently lowercase 'or' is also treated as operator
- // According to PDF requirement, only uppercase should be operators
+ // Lowercase 'or' is NOT an operator in ANTLR grammar (case-sensitive).
+ // With bareQuery rule, it's parsed as a bare term without field.
+ // Without default_field, bare term throws exception.
String dsl = "field:a or field:b";
- QsPlan plan = SearchDslParser.parseDsl(dsl);
-
- Assertions.assertNotNull(plan);
- // Current behavior: lowercase 'or' IS an operator
- Assertions.assertEquals(QsClauseType.OR, plan.getRoot().getType());
- // TODO: If PDF requires only uppercase, this should fail
+ Assertions.assertThrows(RuntimeException.class, () -> {
+ SearchDslParser.parseDsl(dsl);
+ });
}
@Test
@@ -920,15 +915,13 @@ public class SearchDslParserTest {
@Test
public void testLowercaseNotOperator() {
- // Test: Currently lowercase 'not' is also treated as operator
- // According to PDF requirement, only uppercase should be operators
+ // Lowercase 'not' is NOT an operator in ANTLR grammar
(case-sensitive).
+ // With bareQuery rule, it's parsed as a bare term without field.
+ // Without default_field, bare term throws exception.
String dsl = "not field:spam";
- QsPlan plan = SearchDslParser.parseDsl(dsl);
-
- Assertions.assertNotNull(plan);
- // Current behavior: lowercase 'not' IS an operator
- Assertions.assertEquals(QsClauseType.NOT, plan.getRoot().getType());
- // TODO: If PDF requires only uppercase, this should fail
+ Assertions.assertThrows(RuntimeException.class, () -> {
+ SearchDslParser.parseDsl(dsl);
+ });
}
@Test
diff --git a/regression-test/data/search/test_search_dsl_operators.out
b/regression-test/data/search/test_search_dsl_operators.out
new file mode 100644
index 00000000000..3d4e890e091
--- /dev/null
+++ b/regression-test/data/search/test_search_dsl_operators.out
@@ -0,0 +1,45 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !dsl_or_chain --
+1 aterm bterm
+2 bterm cterm
+3 cterm dterm
+4 dterm eterm aterm
+
+-- !dsl_and_chain --
+4 dterm eterm aterm
+
+-- !dsl_and_or_mixed --
+1 aterm bterm
+4 dterm eterm aterm
+
+-- !dsl_and_not_or --
+4 dterm eterm aterm
+
+-- !dsl_implicit_and --
+3 cterm dterm
+
+-- !dsl_phrase_wrong_order --
+
+-- !dsl_phrase_correct_order --
+4 dterm eterm aterm
+
+-- !dsl_escaped_space_and --
+4 dterm eterm aterm
+
+-- !dsl_phrase_and_term --
+4 dterm eterm aterm
+
+-- !dsl_phrase_wrong_and_term --
+
+-- !dsl_phrase_or_term_1 --
+2 bterm cterm
+3 cterm dterm
+
+-- !dsl_phrase_or_term_2 --
+2 bterm cterm
+3 cterm dterm
+4 dterm eterm aterm
+
+-- !dsl_and_or_min_should_1 --
+1 aterm bterm
+
diff --git a/regression-test/data/search/test_search_escape.out
b/regression-test/data/search/test_search_escape.out
index 09bd9f80b2b..fbe1e731f61 100644
--- a/regression-test/data/search/test_search_escape.out
+++ b/regression-test/data/search/test_search_escape.out
@@ -26,15 +26,6 @@
-- !uppercase_not --
8 second fruit
--- !lowercase_and --
-7 first fruit
-
--- !lowercase_or --
-1 first content
-2 second content
-7 first fruit
-8 second fruit
-
-- !exclamation_not --
8 second fruit
diff --git a/regression-test/suites/search/test_search_boundary_cases.groovy
b/regression-test/suites/search/test_search_boundary_cases.groovy
index c5ed5ffd55f..e29b5decb30 100644
--- a/regression-test/suites/search/test_search_boundary_cases.groovy
+++ b/regression-test/suites/search/test_search_boundary_cases.groovy
@@ -86,31 +86,31 @@ suite("test_search_boundary_cases") {
// Boundary Test 1: All NULL fields
qt_boundary_1_all_null_or """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('field1:anything or field2:anything or field3:anything or
field4:anything or field5:anything')
+ WHERE search('field1:anything OR field2:anything OR field3:anything OR
field4:anything OR field5:anything')
"""
qt_boundary_1_all_null_and """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('field1:anything and field2:anything and field3:anything
and field4:anything and field5:anything')
+ WHERE search('field1:anything AND field2:anything AND field3:anything
AND field4:anything AND field5:anything')
"""
// Boundary Test 2: Single field NULL vs multiple fields NULL in OR
qt_boundary_2_single_null_or """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id FROM
${tableName}
- WHERE search('field1:nonexistent or field2:test')
+ WHERE search('field1:nonexistent OR field2:test')
ORDER BY id
"""
qt_boundary_2_multiple_null_or """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id FROM
${tableName}
- WHERE search('field1:nonexistent or field2:test or field3:nonexistent')
+ WHERE search('field1:nonexistent OR field2:test OR field3:nonexistent')
ORDER BY id
"""
// Boundary Test 3: NOT with various NULL combinations
qt_boundary_3_not_null_field """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('not field1:test')
+ WHERE search('NOT field1:test')
"""
qt_boundary_3_external_not_null """
@@ -138,59 +138,59 @@ suite("test_search_boundary_cases") {
// Boundary Test 5: Complex nested boolean with NULLs
qt_boundary_5_complex_nested """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('((field1:test or field2:test) and (field3:test or
field4:test)) or field5:test')
+ WHERE search('((field1:test OR field2:test) AND (field3:test OR
field4:test)) OR field5:test')
"""
qt_boundary_5_detailed_result """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, field1,
field2, field3, field4, field5 FROM ${tableName}
- WHERE search('((field1:test or field2:test) and (field3:test or
field4:test)) or field5:test')
+ WHERE search('((field1:test OR field2:test) AND (field3:test OR
field4:test)) OR field5:test')
ORDER BY id
"""
// Boundary Test 6: Large OR query with many NULL fields
qt_boundary_6_large_or """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('field1:"target" or field1:"keyword" or field1:"apple" or
field1:"unique1" or
- field2:"target" or field2:"keyword" or field2:"apple" or
field2:"unique2" or
- field3:"target" or field3:"keyword" or field3:"banana" or
field3:"unique3" or
- field4:"target" or field4:"keyword" or field4:"banana" or
field4:"unique4" or
- field5:"target" or field5:"keyword" or field5:"cherry" or
field5:"unique5"')
+ WHERE search('field1:"target" OR field1:"keyword" OR field1:"apple" OR
field1:"unique1" OR
+ field2:"target" OR field2:"keyword" OR field2:"apple" OR
field2:"unique2" OR
+ field3:"target" OR field3:"keyword" OR field3:"banana" OR
field3:"unique3" OR
+ field4:"target" OR field4:"keyword" OR field4:"banana" OR
field4:"unique4" OR
+ field5:"target" OR field5:"keyword" OR field5:"cherry" OR
field5:"unique5"')
"""
// Boundary Test 7: Special characters and NULL interaction
qt_boundary_7_special_chars_or """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('field1:special123 or field2:nonexistent')
+ WHERE search('field1:special123 OR field2:nonexistent')
"""
qt_boundary_7_special_chars_and """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('field1:special123 and field2:chars456')
+ WHERE search('field1:special123 AND field2:chars456')
"""
// Boundary Test 8: Case sensitivity with NULL fields
qt_boundary_8_case_variations """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id FROM
${tableName}
- WHERE search('field1:Target or field2:TARGET or field3:target or
field4:TaRgEt')
+ WHERE search('field1:Target OR field2:TARGET OR field3:target OR
field4:TaRgEt')
ORDER BY id
"""
// Boundary Test 9: Multiple NOT operations
qt_boundary_9_multiple_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('not (field1:nonexistent or field2:nonexistent or
field3:nonexistent)')
+ WHERE search('NOT (field1:nonexistent OR field2:nonexistent OR
field3:nonexistent)')
"""
qt_boundary_9_external_multiple_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE not search('field1:nonexistent or field2:nonexistent or
field3:nonexistent')
+ WHERE not search('field1:nonexistent OR field2:nonexistent OR
field3:nonexistent')
"""
// Boundary Test 10: Performance with NULL-heavy dataset
qt_boundary_10_performance """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('(field1:test or field1:target or field1:keyword) and
- (field2:test or field2:target or field2:keyword) and
- not (field3:nonexistent or field4:nonexistent or
field5:nonexistent)')
+ WHERE search('(field1:test OR field1:target OR field1:keyword) AND
+ (field2:test OR field2:target OR field2:keyword) AND
+ NOT (field3:nonexistent OR field4:nonexistent OR
field5:nonexistent)')
"""
}
\ No newline at end of file
diff --git a/regression-test/suites/search/test_search_dsl_operators.groovy
b/regression-test/suites/search/test_search_dsl_operators.groovy
new file mode 100644
index 00000000000..37ac49abf55
--- /dev/null
+++ b/regression-test/suites/search/test_search_dsl_operators.groovy
@@ -0,0 +1,231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+/**
+ * Tests for search DSL operator scenarios
+ *
+ * This test suite validates Lucene mode parsing against the exact test cases
+ * documented in specification to ensure behavior matches Elasticsearch/Lucene
semantics.
+ *
+ * Test Data Setup:
+ * | Email | Firstname |
+ * | [email protected] | "aterm bterm" |
+ * | [email protected] | "bterm cterm" |
+ * | [email protected] | "cterm dterm" |
+ * | [email protected] | "dterm eterm aterm" |
+ *
+ * Key Lucene Semantics:
+ * - Operators are processed left-to-right as modifiers
+ * - AND marks preceding and current terms as MUST (+)
+ * - OR marks preceding and current terms as SHOULD
+ * - NOT marks current term as MUST_NOT (-)
+ * - With minimum_should_match=0 and MUST clauses present, SHOULD clauses are
discarded
+ */
+suite("test_search_dsl_operators") {
+ def tableName = "search_dsl_operators_test"
+
+ sql "DROP TABLE IF EXISTS ${tableName}"
+
+ // Create table with inverted indexes
+ // Using parser=english to tokenize firstname field
+ sql """
+ CREATE TABLE ${tableName} (
+ id INT,
+ email VARCHAR(100),
+ firstname VARCHAR(200),
+ INDEX idx_firstname(firstname) USING INVERTED PROPERTIES("parser"
= "english")
+ ) ENGINE=OLAP
+ DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES ("replication_allocation" = "tag.location.default: 1")
+ """
+
+ // Insert test data
+ sql """INSERT INTO ${tableName} VALUES
+ (1, '[email protected]', 'aterm bterm'),
+ (2, '[email protected]', 'bterm cterm'),
+ (3, '[email protected]', 'cterm dterm'),
+ (4, '[email protected]', 'dterm eterm aterm')
+ """
+
+ // Wait for index building
+ Thread.sleep(3000)
+
+ // ============ Test 1: aterm OR bterm OR cterm ============
+ // All OR operators -> at least one must match (minimum_should_match=1)
+ // Expected: rows 1,2,3,4 (all match at least one term)
+ qt_dsl_or_chain """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('aterm OR bterm OR cterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 2: dterm AND eterm AND aterm ============
+ // All AND operators -> all must match
+ // Expected: row 4 only (the only one with all three terms)
+ qt_dsl_and_chain """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('dterm AND eterm AND aterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 3: aterm AND bterm OR cterm ============
+ // Lucene left-to-right parsing with minimum_should_match=0:
+ // - aterm: MUST (first term, default_operator=AND)
+ // - bterm: MUST (AND introduces)
+ // - cterm: SHOULD (OR introduces), bterm becomes SHOULD too
+ // Final: +aterm bterm cterm
+ // With minimum_should_match=0 and MUST present, SHOULD discarded
+ // Result: effectively +aterm only
+ // Expected: rows 1, 4 (rows containing "aterm")
+ qt_dsl_and_or_mixed """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('aterm AND bterm OR cterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene","minimum_should_match":0}')
+ ORDER BY id
+ """
+
+ // ============ Test 4: aterm AND NOT bterm OR cterm ============
+ // Lucene left-to-right parsing:
+ // - aterm: MUST
+ // - bterm: MUST_NOT (NOT modifier)
+ // - cterm: SHOULD (OR introduces)
+ // Final: +aterm -bterm cterm
+ // With minimum_should_match=0 and MUST present, SHOULD discarded
+ // Result: +aterm -bterm
+ // Expected: row 4 only (has "aterm" but NOT "bterm")
+ qt_dsl_and_not_or """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('aterm AND NOT bterm OR cterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene","minimum_should_match":0}')
+ ORDER BY id
+ """
+
+ // ============ Test 5: cterm dterm (implicit AND) ============
+ // No explicit operators, default_operator=AND
+ // Same as: cterm AND dterm
+ // Expected: row 3 only (has both "cterm" AND "dterm")
+ qt_dsl_implicit_and """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('cterm dterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 6: "aterm eterm" (phrase query, wrong order)
============
+ // Phrase query requires tokens in exact order
+ // Data has "dterm eterm aterm" - "aterm" comes AFTER "eterm", not before
+ // Expected: no match
+ qt_dsl_phrase_wrong_order """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('"aterm eterm"',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 7: "eterm aterm" (phrase query, correct order)
============
+ // Phrase query requires tokens in exact order
+ // Data has "dterm eterm aterm" - "eterm aterm" appears in this order
+ // Expected: row 4
+ qt_dsl_phrase_correct_order """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('"eterm aterm"',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 8: eterm\ dterm AND aterm (escaped space test)
============
+ // Tests escape handling in Lucene mode
+ // In current implementation, the escaped space is processed such that
+ // the query effectively becomes a term query for individual tokens
+ // Row 4 contains all terms (dterm, eterm, aterm)
+ // Expected: row 4
+ qt_dsl_escaped_space_and """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('eterm\\\\ dterm AND aterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 9: "dterm eterm" AND aterm ============
+ // Phrase query + AND
+ // Row 4 has "dterm eterm aterm" - phrase "dterm eterm" matches, and
"aterm" is also present
+ // Expected: row 4
+ qt_dsl_phrase_and_term """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('"dterm eterm" AND aterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 10: "eterm dterm" AND aterm (phrase wrong order)
============
+ // Phrase "eterm dterm" is wrong order (data has "dterm eterm")
+ // Expected: no match
+ qt_dsl_phrase_wrong_and_term """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('"eterm dterm" AND aterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 11: "eterm dterm" OR cterm ============
+ // Phrase OR term
+ // Phrase "eterm dterm" won't match (wrong order)
+ // cterm matches rows 2, 3
+ // Expected: rows 2, 3
+ qt_dsl_phrase_or_term_1 """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('"eterm dterm" OR cterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 12: "dterm eterm" OR cterm ============
+ // Phrase OR term
+ // Phrase "dterm eterm" matches row 4
+ // cterm matches rows 2, 3
+ // Expected: rows 2, 3, 4
+ qt_dsl_phrase_or_term_2 """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('"dterm eterm" OR cterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene"}')
+ ORDER BY id
+ """
+
+ // ============ Test 13: aterm AND bterm OR cterm with
minimum_should_match=1 ============
+ // Same as Test 3 but with minimum_should_match=1
+ // Final state: +aterm bterm cterm (aterm is MUST, bterm and cterm are
SHOULD)
+ // With minimum_should_match=1, at least 1 SHOULD must match
+ // Result: aterm AND (bterm OR cterm)
+ // Expected: rows 1, 2 (row 1 has aterm+bterm, row 2 doesn't have aterm)
+ // Wait - row 2 doesn't have aterm, so it shouldn't match
+ // Row 1: has aterm, has bterm -> matches
+ // Row 4: has aterm, doesn't have bterm or cterm -> doesn't match (no
SHOULD satisfied)
+ // Actually row 4 has aterm but no bterm/cterm...
+ // Expected: row 1 only
+ qt_dsl_and_or_min_should_1 """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, firstname
+ FROM ${tableName}
+ WHERE search('aterm AND bterm OR cterm',
'{"default_field":"firstname","default_operator":"and","mode":"lucene","minimum_should_match":1}')
+ ORDER BY id
+ """
+
+ // Cleanup
+ sql "DROP TABLE IF EXISTS ${tableName}"
+}
diff --git a/regression-test/suites/search/test_search_escape.groovy
b/regression-test/suites/search/test_search_escape.groovy
index 629d3fcc1f5..5414455d9d9 100644
--- a/regression-test/suites/search/test_search_escape.groovy
+++ b/regression-test/suites/search/test_search_escape.groovy
@@ -143,21 +143,31 @@ suite("test_search_escape") {
ORDER BY id
"""
- // ============ Test 9: Lowercase and operator ============
- qt_lowercase_and """
- SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, content
- FROM ${tableName}
- WHERE search('content:first and content:fruit')
- ORDER BY id
- """
-
- // ============ Test 10: Lowercase or operator ============
- qt_lowercase_or """
- SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, content
- FROM ${tableName}
- WHERE search('content:first or content:second')
- ORDER BY id
- """
+ // ============ Test 9: Lowercase 'and' should cause parse error
============
+ // Per requirement: Only uppercase AND/OR/NOT are operators
+ // Lowercase 'and' is treated as a bare term (no field), causing error
+ test {
+ sql """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, content
+ FROM ${tableName}
+ WHERE search('content:first and content:fruit')
+ ORDER BY id
+ """
+ exception "No field specified and no default_field configured"
+ }
+
+ // ============ Test 10: Lowercase 'or' should cause parse error
============
+ // Per requirement: Only uppercase AND/OR/NOT are operators
+ // Lowercase 'or' is treated as a bare term (no field), causing error
+ test {
+ sql """
+ SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, content
+ FROM ${tableName}
+ WHERE search('content:first or content:second')
+ ORDER BY id
+ """
+ exception "No field specified and no default_field configured"
+ }
// ============ Test 11: Exclamation NOT operator ============
qt_exclamation_not """
diff --git a/regression-test/suites/search/test_search_function.groovy.backup
b/regression-test/suites/search/test_search_function.groovy.backup
new file mode 100644
index 00000000000..47e5944e393
--- /dev/null
+++ b/regression-test/suites/search/test_search_function.groovy.backup
@@ -0,0 +1,261 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_search_function") {
+
+ def tableName = "search_test_table"
+ def indexTableName = "search_test_index_table"
+
+ sql "DROP TABLE IF EXISTS ${tableName}"
+ sql "DROP TABLE IF EXISTS ${indexTableName}"
+
+ // Create test table without inverted index
+ sql """
+ CREATE TABLE ${tableName} (
+ id INT,
+ title VARCHAR(255),
+ content TEXT,
+ category VARCHAR(100),
+ tags VARCHAR(200),
+ publish_date DATE,
+ view_count INT
+ ) ENGINE=OLAP
+ DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 3
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ )
+ """
+
+ // Create test table with inverted index
+ sql """
+ CREATE TABLE ${indexTableName} (
+ id INT,
+ title VARCHAR(255),
+ content TEXT,
+ category VARCHAR(100),
+ tags VARCHAR(200),
+ publish_date DATE,
+ view_count INT,
+ INDEX idx_title (title) USING INVERTED,
+ INDEX idx_content (content) USING INVERTED PROPERTIES("parser" =
"english"),
+ INDEX idx_category (category) USING INVERTED,
+ INDEX idx_tags (tags) USING INVERTED PROPERTIES("parser" =
"english")
+ ) ENGINE=OLAP
+ DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 3
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ )
+ """
+
+ // Insert test data
+ def testData = [
+ [1, "Machine Learning Basics", "Introduction to machine learning
algorithms and concepts", "Technology", "machine learning, AI, algorithms",
"2023-01-15", 1500],
+ [2, "Deep Learning Tutorial", "Advanced deep learning techniques and
neural networks", "Technology", "deep learning, neural networks, AI",
"2023-02-20", 2300],
+ [3, "Python Programming Guide", "Complete guide to Python programming
language", "Programming", "python, programming, tutorial", "2023-03-10", 1800],
+ [4, "Data Science Methods", "Statistical methods for data science and
analytics", "Science", "data science, statistics, analytics", "2023-04-05",
1200],
+ [5, "Web Development Tips", "Modern web development best practices",
"Technology", "web development, javascript, HTML", "2023-05-12", 950],
+ [6, "Algorithm Design", "Fundamental algorithms and data structures",
"Computer Science", "algorithms, data structures, programming", "2023-06-18",
1650],
+ [7, "Natural Language Processing", "NLP techniques and applications",
"Technology", "NLP, natural language, processing", "2023-07-22", 1100],
+ [8, "Cloud Computing Overview", "Introduction to cloud computing
platforms", "Technology", "cloud computing, AWS, Azure", "2023-08-14", 1350],
+ [9, "Database Systems", "Relational and NoSQL database concepts",
"Technology", "database, SQL, NoSQL", "2023-09-09", 1450],
+ [10, "Software Engineering", "Best practices in software development",
"Programming", "software engineering, development, practices", "2023-10-01",
1750]
+ ]
+
+ for (def row : testData) {
+ sql """INSERT INTO ${tableName} VALUES (${row[0]}, '${row[1]}',
'${row[2]}', '${row[3]}', '${row[4]}', '${row[5]}', ${row[6]})"""
+ sql """INSERT INTO ${indexTableName} VALUES (${row[0]}, '${row[1]}',
'${row[2]}', '${row[3]}', '${row[4]}', '${row[5]}', ${row[6]})"""
+ }
+
+ // Wait for index building and data settling
+ Thread.sleep(10000)
+
+ // Verify data insertion
+ qt_sql "SELECT COUNT(*) FROM ${tableName}"
+ qt_sql "SELECT COUNT(*) FROM ${indexTableName}"
+
+ // Test 1: Basic term search
+ qt_sql "SELECT id, title FROM ${indexTableName} WHERE
search('title:Machine')"
+
+ // Test 2: Phrase search
+ qt_sql "SELECT id, title FROM ${indexTableName} WHERE
search('title:\"Machine Learning\"')"
+
+ // Test 3: Multiple field search with AND
+ qt_sql "SELECT id, title FROM ${indexTableName} WHERE
search('title:Learning AND category:Technology') ORDER BY id"
+
+ // Test 4: Multiple field search with OR
+ qt_sql "SELECT id, title FROM ${indexTableName} WHERE search('title:Python
OR title:Algorithm') ORDER BY id"
+
+ // Test 5: NOT search
+ qt_sql "SELECT COUNT(*) FROM ${indexTableName} WHERE
search('category:Technology AND NOT title:Machine')"
+
+ // Test 6: Complex nested search
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('(title:Learning OR content:algorithms) AND category:Technology') ORDER
BY id"
+ result([
+ [1, "Machine Learning Basics"],
+ [2, "Deep Learning Tutorial"]
+ ])
+ }
+
+ // Test 7: Wildcard search
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('title:Learn*') ORDER BY id"
+ result([
+ [1, "Machine Learning Basics"],
+ [2, "Deep Learning Tutorial"]
+ ])
+ }
+
+ // Test 8: Prefix search
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('title:Data*')"
+ result([
+ [4, "Data Science Methods"],
+ [6, "Algorithm Design"]
+ ])
+ }
+
+ // Test 9: Search in content field
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('content:neural')"
+ result([
+ [2, "Deep Learning Tutorial"]
+ ])
+ }
+
+ // Test 10: Search in tags field
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('tags:programming') ORDER BY id"
+ result([
+ [3, "Python Programming Guide"],
+ [6, "Algorithm Design"]
+ ])
+ }
+
+ // Test 11: Case insensitive search
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('title:MACHINE')"
+ result([
+ [1, "Machine Learning Basics"]
+ ])
+ }
+
+ // Test 12: Search with spaces in field values
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('content:\"machine learning\"')"
+ result([
+ [1, "Machine Learning Basics"]
+ ])
+ }
+
+ // Test 13: Empty search result
+ test {
+ sql "SELECT COUNT(*) FROM ${indexTableName} WHERE
search('title:nonexistent')"
+ result([
+ [0]
+ ])
+ }
+
+ // Test 14: Search combined with other WHERE conditions
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('category:Technology') AND view_count > 1400 ORDER BY id"
+ result([
+ [1, "Machine Learning Basics"],
+ [2, "Deep Learning Tutorial"]
+ ])
+ }
+
+ // Test 15: Search with GROUP BY
+ test {
+ sql "SELECT category, COUNT(*) as cnt FROM ${indexTableName} WHERE
search('title:Learning OR title:Programming') GROUP BY category ORDER BY
category"
+ result([
+ ["Programming", 1],
+ ["Technology", 2]
+ ])
+ }
+
+ // Test 16: Search with ORDER BY
+ test {
+ sql "SELECT id, title, view_count FROM ${indexTableName} WHERE
search('tags:AI OR tags:programming') ORDER BY view_count DESC"
+ result([
+ [2, "Deep Learning Tutorial", 2300],
+ [3, "Python Programming Guide", 1800],
+ [6, "Algorithm Design", 1650],
+ [1, "Machine Learning Basics", 1500]
+ ])
+ }
+
+ // Test 17: Search with LIMIT
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('category:Technology') ORDER BY id LIMIT 3"
+ result([
+ [1, "Machine Learning Basics"],
+ [2, "Deep Learning Tutorial"],
+ [5, "Web Development Tips"]
+ ])
+ }
+
+ // Test 18: Search function in SELECT clause (should not be allowed -
search is a predicate)
+ test {
+ try {
+ sql "SELECT id, search('title:Machine') FROM ${indexTableName}"
+ assertTrue(false, "Expected exception for search in SELECT clause")
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("search") ||
e.getMessage().contains("not found"))
+ }
+ }
+
+ // Test 19: Invalid DSL syntax
+ test {
+ try {
+ sql "SELECT id FROM ${indexTableName} WHERE search('title:')"
+ assertTrue(false, "Expected exception for invalid DSL")
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("Invalid") ||
e.getMessage().contains("syntax"))
+ }
+ }
+
+ // Test 20: ANY query test
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE search('tags:ANY(AI
programming)') ORDER BY id"
+ check { result ->
+ assertTrue(result.size() > 0, "Should find records with AI or
programming in tags")
+ }
+ }
+
+ // Test 21: ALL query test
+ test {
+ sql "SELECT id, title FROM ${indexTableName} WHERE
search('tags:ALL(machine learning)') ORDER BY id"
+ result([
+ [1, "Machine Learning Basics"]
+ ])
+ }
+
+ // Test 22: Search on non-indexed table (should still work but may be
slower)
+ test {
+ sql "SELECT id, title FROM ${tableName} WHERE search('title:Machine')"
+ result([
+ [1, "Machine Learning Basics"]
+ ])
+ }
+
+ // Cleanup
+ sql "DROP TABLE IF EXISTS ${tableName}"
+ sql "DROP TABLE IF EXISTS ${indexTableName}"
+}
diff --git a/regression-test/suites/search/test_search_null_regression.groovy
b/regression-test/suites/search/test_search_null_regression.groovy
index 3fe85461455..742f86959a1 100644
--- a/regression-test/suites/search/test_search_null_regression.groovy
+++ b/regression-test/suites/search/test_search_null_regression.groovy
@@ -69,7 +69,7 @@ suite("test_search_null_regression") {
// vs title match "Ronald" or (content match_all "Selma Blair")
qt_regression_1_search_or """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:Ronald or (content:ALL(Selma Blair))')
+ WHERE search('title:Ronald OR (content:ALL(Selma Blair))')
"""
qt_regression_1_match_or """
@@ -80,7 +80,7 @@ suite("test_search_null_regression") {
// Detailed verification - get actual matching rows for OR query
qt_regression_1_search_or_rows """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, title,
content FROM ${tableName}
- WHERE search('title:Ronald or (content:ALL(Selma Blair))')
+ WHERE search('title:Ronald OR (content:ALL(Selma Blair))')
ORDER BY id
"""
@@ -94,7 +94,7 @@ suite("test_search_null_regression") {
// This reproduces: search('not content:"Round"') vs not
search('content:"Round"')
qt_regression_2_internal_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('not content:Round')
+ WHERE search('NOT content:Round')
"""
qt_regression_2_external_not """
@@ -105,7 +105,7 @@ suite("test_search_null_regression") {
// Detailed verification for NOT query
qt_regression_2_internal_not_rows """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, title,
content FROM ${tableName}
- WHERE search('not content:Round')
+ WHERE search('NOT content:Round')
ORDER BY id
"""
@@ -119,7 +119,7 @@ suite("test_search_null_regression") {
// Verify that OR queries properly handle NULL values according to SQL
semantics
qt_regression_3_null_or """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id FROM
${tableName}
- WHERE search('title:NonExistent or content:Ronald')
+ WHERE search('title:NonExistent OR content:Ronald')
ORDER BY id
"""
@@ -133,14 +133,14 @@ suite("test_search_null_regression") {
// Regression Test 4: NULL Handling in AND Queries
qt_regression_4_null_and """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id FROM
${tableName}
- WHERE search('title:Ronald and content:biography')
+ WHERE search('title:Ronald AND content:biography')
ORDER BY id
"""
// Regression Test 5: Complex Boolean Operations
qt_regression_5_complex_search """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('(title:Ronald or content:Selma) and not content:Round')
+ WHERE search('(title:Ronald OR content:Selma) AND NOT content:Round')
"""
qt_regression_5_complex_match """
@@ -151,7 +151,7 @@ suite("test_search_null_regression") {
// Regression Test 6: Edge Case - All NULL Query
qt_regression_6_all_null """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:NonExistent and content:NonExistent')
+ WHERE search('title:NonExistent AND content:NonExistent')
"""
// Regression Test 7: Case Sensitivity and Variations
@@ -168,23 +168,23 @@ suite("test_search_null_regression") {
// Regression Test 8: Multiple NOT operations
qt_regression_8_multiple_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('not (title:nonexistent or content:nonexistent)')
+ WHERE search('NOT (title:nonexistent OR content:nonexistent)')
"""
qt_regression_8_external_multiple_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE not search('title:nonexistent or content:nonexistent')
+ WHERE not search('title:nonexistent OR content:nonexistent')
"""
// Regression Test 9: Empty string handling
qt_regression_9_empty_string """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:"" or content:Round')
+ WHERE search('title:"" OR content:Round')
"""
// Regression Test 10: Performance test with complex query
qt_regression_10_performance """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('(title:Ronald or title:Selma or content:Round) and not
(title:NonExistent and content:NonExistent)')
+ WHERE search('(title:Ronald OR title:Selma OR content:Round) AND NOT
(title:NonExistent AND content:NonExistent)')
"""
}
\ No newline at end of file
diff --git a/regression-test/suites/search/test_search_null_semantics.groovy
b/regression-test/suites/search/test_search_null_semantics.groovy
index c7d97c18bdc..9e18e063cf9 100644
--- a/regression-test/suites/search/test_search_null_semantics.groovy
+++ b/regression-test/suites/search/test_search_null_semantics.groovy
@@ -61,7 +61,7 @@ suite("test_search_null_semantics") {
// title match "Ronald" or (content match_all "Selma Blair")
qt_test_case_1_search """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:Ronald or (content:ALL(Selma Blair))')
+ WHERE search('title:Ronald OR (content:ALL(Selma Blair))')
"""
qt_test_case_1_match """
@@ -73,7 +73,7 @@ suite("test_search_null_semantics") {
// search('not content:"Round"') should match not search('content:"Round"')
qt_test_case_2_internal_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('not content:Round')
+ WHERE search('NOT content:Round')
"""
qt_test_case_2_external_not """
@@ -92,7 +92,7 @@ suite("test_search_null_semantics") {
// Verify that NULL OR TRUE = TRUE logic works
qt_test_case_3_or_with_null """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, title,
content FROM ${tableName}
- WHERE search('title:Ronald or content:biography')
+ WHERE search('title:Ronald OR content:biography')
ORDER BY id
"""
@@ -100,14 +100,14 @@ suite("test_search_null_semantics") {
// Verify that NULL AND TRUE = NULL logic works
qt_test_case_4_and_with_null """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, title,
content FROM ${tableName}
- WHERE search('title:Ronald and content:biography')
+ WHERE search('title:Ronald AND content:biography')
ORDER BY id
"""
// Test Case 5: Complex OR query with multiple NULL scenarios
qt_test_case_5_complex_or_search """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:Unknown or content:mascot or category:Test')
+ WHERE search('title:Unknown OR content:mascot OR category:Test')
"""
qt_test_case_5_complex_or_match """
@@ -118,7 +118,7 @@ suite("test_search_null_semantics") {
// Test Case 6: NOT query with different field types
qt_test_case_6_not_title_search """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('not title:Ronald')
+ WHERE search('NOT title:Ronald')
"""
qt_test_case_6_not_title_external """
@@ -129,13 +129,13 @@ suite("test_search_null_semantics") {
// Test Case 7: Mixed boolean operations
qt_test_case_7_mixed """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('(title:Ronald or content:Selma) and not
category:Unknown')
+ WHERE search('(title:Ronald OR content:Selma) AND NOT
category:Unknown')
"""
// Test Case 8: Edge case - all NULL fields
qt_test_case_8_all_null """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:NonExistent or content:NonExistent or
category:NonExistent')
+ WHERE search('title:NonExistent OR content:NonExistent OR
category:NonExistent')
"""
// ------------------------------------------------------------------
diff --git
a/regression-test/suites/search/test_search_vs_match_consistency.groovy
b/regression-test/suites/search/test_search_vs_match_consistency.groovy
index 05ba88344cf..e39268838e5 100644
--- a/regression-test/suites/search/test_search_vs_match_consistency.groovy
+++ b/regression-test/suites/search/test_search_vs_match_consistency.groovy
@@ -117,7 +117,7 @@ suite("test_search_vs_match_consistency") {
// Test Suite 1: Basic OR query consistency
qt_test_1_1_search """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:Ronald or title:Selma')
+ WHERE search('title:Ronald OR title:Selma')
"""
qt_test_1_1_match """
@@ -128,7 +128,7 @@ suite("test_search_vs_match_consistency") {
// Test 1.2: OR across different fields
qt_test_1_2_search """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:Ronald or content:Selma')
+ WHERE search('title:Ronald OR content:Selma')
"""
qt_test_1_2_match """
@@ -139,7 +139,7 @@ suite("test_search_vs_match_consistency") {
// Test 1.3: Complex OR with ALL operation
qt_test_1_3_search """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:Ronald or (content:ALL(Selma Blair))')
+ WHERE search('title:Ronald OR (content:ALL(Selma Blair))')
"""
qt_test_1_3_match """
@@ -150,7 +150,7 @@ suite("test_search_vs_match_consistency") {
// Test Suite 2: NOT query consistency
qt_test_2_1_internal_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('not content:Round')
+ WHERE search('NOT content:Round')
"""
qt_test_2_1_external_not """
@@ -161,7 +161,7 @@ suite("test_search_vs_match_consistency") {
// Test 2.2: NOT with different fields
qt_test_2_2_internal_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('not title:Ronald')
+ WHERE search('NOT title:Ronald')
"""
qt_test_2_2_external_not """
@@ -172,49 +172,49 @@ suite("test_search_vs_match_consistency") {
// Test 2.3: NOT with complex expression
qt_test_2_3_internal_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('not (title:Ronald and content:biography)')
+ WHERE search('NOT (title:Ronald AND content:biography)')
"""
qt_test_2_3_external_not """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE not search('title:Ronald and content:biography')
+ WHERE not search('title:Ronald AND content:biography')
"""
// Test Suite 3: NULL value behavior in OR queries
qt_test_3_1_or_with_null """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, title,
content FROM ${tableName}
- WHERE search('title:NonExistent or content:Ronald')
+ WHERE search('title:NonExistent OR content:Ronald')
ORDER BY id
"""
qt_test_3_2_or_multiple_null """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, title,
content FROM ${tableName}
- WHERE search('title:Mystery or content:Round')
+ WHERE search('title:Mystery OR content:Round')
ORDER BY id
"""
// Test Suite 4: AND query behavior with NULLs
qt_test_4_1_and_with_null """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ id, title,
content FROM ${tableName}
- WHERE search('title:Ronald and content:biography')
+ WHERE search('title:Ronald AND content:biography')
ORDER BY id
"""
// Test Suite 5: Edge cases and complex scenarios
qt_test_5_1_empty_string """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:"" or content:Round')
+ WHERE search('title:"" OR content:Round')
"""
qt_test_5_2_complex_nested """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('(title:Ronald or title:Selma) and not (content:Round and
author:NonExistent)')
+ WHERE search('(title:Ronald OR title:Selma) AND NOT (content:Round AND
author:NonExistent)')
"""
// Test Suite 6: Performance and consistency verification
qt_test_6_1_large_or_search """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${tableName}
- WHERE search('title:Ronald or title:Selma or content:Round or
content:biography or author:Smith or tags:history')
+ WHERE search('title:Ronald OR title:Selma OR content:Round OR
content:biography OR author:Smith OR tags:history')
"""
qt_test_6_1_large_or_match """
@@ -278,7 +278,7 @@ suite("test_search_vs_match_consistency") {
// Mandy/Kesha consistency checks
qt_man_pat_1_search """
SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ count(*) FROM
${mandyTable}
- WHERE search('content:ALL("Mandy Patinkin") or not
(content:ANY("Kesha"))')
+ WHERE search('content:ALL("Mandy Patinkin") OR NOT
(content:ANY("Kesha"))')
"""
qt_man_pat_1_match """
@@ -291,7 +291,7 @@ suite("test_search_vs_match_consistency") {
CASE WHEN title IS NULL THEN 'NULL' ELSE 'NOT_NULL' END AS
title_status,
CASE WHEN content IS NULL THEN 'NULL' ELSE 'NOT_NULL' END AS
content_status
FROM ${mandyTable}
- WHERE search('content:ALL("Mandy Patinkin") or not
(content:ANY("Kesha"))')
+ WHERE search('content:ALL("Mandy Patinkin") OR NOT
(content:ANY("Kesha"))')
ORDER BY id
"""
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]