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 a3d1080e87d [test](search) add more regression case (#56704)
a3d1080e87d is described below

commit a3d1080e87d5d7f09e3414172c6c0529cc3ca25a
Author: Jack <[email protected]>
AuthorDate: Sun Oct 5 21:10:20 2025 +0800

    [test](search) add more regression case (#56704)
---
 .../test_complex_or_null_semantics.groovy          | 240 +++++++++++++++++++++
 .../test_cross_field_or_with_null.groovy           | 192 +++++++++++++++++
 .../test_search_or_null_semantics.groovy           | 195 +++++++++++++++++
 3 files changed, 627 insertions(+)

diff --git 
a/regression-test/suites/inverted_index_p0/test_complex_or_null_semantics.groovy
 
b/regression-test/suites/inverted_index_p0/test_complex_or_null_semantics.groovy
new file mode 100644
index 00000000000..d922a00160e
--- /dev/null
+++ 
b/regression-test/suites/inverted_index_p0/test_complex_or_null_semantics.groovy
@@ -0,0 +1,240 @@
+// 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_complex_or_null_semantics") {
+    // This regression test verifies the fix for complex nested OR queries 
with NULL values
+    // Based on the real-world bug where MATCH and SEARCH returned different 
results
+
+    def tableName = "test_complex_or_null_table"
+
+    sql "DROP TABLE IF EXISTS ${tableName}"
+
+    sql """
+        CREATE TABLE ${tableName} (
+            id INT,
+            title TEXT,
+            content TEXT,
+            author TEXT,
+            category TEXT,
+            INDEX idx_title (title) USING INVERTED PROPERTIES("parser" = 
"english"),
+            INDEX idx_content (content) USING INVERTED PROPERTIES("parser" = 
"english"),
+            INDEX idx_author (author) USING INVERTED PROPERTIES("parser" = 
"english"),
+            INDEX idx_category (category) USING INVERTED
+        ) ENGINE=OLAP
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 3
+        PROPERTIES (
+            "replication_allocation" = "tag.location.default: 1"
+        )
+    """
+
+    // Insert test data simulating Wikipedia-like articles
+    sql """ INSERT INTO ${tableName} VALUES
+        -- Rows 1-15: title = "Philosophy", content = NULL
+        (1, 'Philosophy', NULL, 'Author A', 'Education'),
+        (2, 'Philosophy', NULL, 'Author B', 'Science'),
+        (3, 'Philosophy', NULL, 'Author C', 'History'),
+        (4, 'Philosophy', NULL, 'Author D', 'Culture'),
+        (5, 'Philosophy', NULL, 'Author E', 'Education'),
+        (6, 'Philosophy', NULL, 'Author F', 'Science'),
+        (7, 'Philosophy', NULL, 'Author G', 'History'),
+        (8, 'Philosophy', NULL, 'Author H', 'Education'),
+        (9, 'Philosophy', NULL, 'Author I', 'Culture'),
+        (10, 'Philosophy', NULL, 'Author J', 'Education'),
+        (11, 'Philosophy', NULL, 'Author K', 'Science'),
+        (12, 'Philosophy', NULL, 'Author L', 'History'),
+        (13, 'Philosophy', NULL, 'Author M', 'Education'),
+        (14, 'Philosophy', NULL, 'Author N', 'Culture'),
+        (15, 'Philosophy', NULL, 'Author O', 'Education'),
+        -- Row 16: Different article
+        (16, 'Technology News', 'Disney+ Hotstar streaming platform', 'Tech 
Writer', 'Technology'),
+        -- Rows 17-30: Various other articles with content
+        (17, 'Science Article', 'President of the United States speech', 
'Political Writer', 'Politics'),
+        (18, 'History Piece', 'List of presidents of India', 'Historian', 
'History'),
+        (19, 'Movie Review', 'Braveheart film analysis', 'Critic', 
'Entertainment'),
+        (20, 'Tech Article', 'University of Southern California research', 
'Academic', 'Education'),
+        (21, 'Biography', 'Elliot Page interview', 'Entertainment Writer', 
'Celebrity'),
+        (22, 'History Document', 'Archduke Franz Ferdinand of Austria', 
'Historian', 'History'),
+        (23, 'Film Review', 'Renée Zellweger performance', 'Critic', 'Movies'),
+        (24, 'Sports', 'Randy Savage wrestling career', 'Sports Writer', 
'Sports'),
+        (25, 'Random', 'Cindy story', 'Random Writer', 'General'),
+        (26, 'Wildlife', 'Bindi Irwin conservation work', 'Nature Writer', 
'Environment'),
+        (27, 'Job Portal', 'Indeed job listings', 'HR Writer', 'Business'),
+        (28, 'Short Story', 'Boy adventure tale', 'Fiction Writer', 
'Literature'),
+        (29, 'Generic', 'Article about of preposition', 'Grammar Writer', 
'Language'),
+        (30, 'Misc', 'Random content', 'Anonymous', 'Misc')
+    """
+
+    sql "SET enable_common_expr_pushdown = true"
+
+    // Test 1: The core bug scenario - cross-field OR with one field NULL
+    // title:ALL("Philosophy") OR content:ALL("Disney+ Hotstar")
+    // Should match rows 1-16 (15 with title match despite NULL content, 1 
with content match)
+
+    def test1_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney+ 
Hotstar'
+    """
+
+    def test1_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar")')
+    """
+
+    assertEquals(16, test1_match[0][0], "MATCH should return 16 rows")
+    assertEquals(16, test1_search[0][0], "SEARCH should return 16 rows")
+    assertEquals(test1_match[0][0], test1_search[0][0], "MATCH and SEARCH 
should return same count")
+
+    logger.info("Test 1 PASSED: Cross-field OR with NULL - MATCH and SEARCH 
consistent")
+
+    // Test 2: Complex nested query similar to original bug report
+    def test2_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE content MATCH_ANY 'President of the United States'
+           OR NOT (
+                content MATCH 'Braveheart'
+                AND (
+                    NOT content MATCH_ALL 'List of presidents of India'
+                    AND NOT (
+                        title MATCH_ALL 'Philosophy'
+                        OR content MATCH_ALL 'Disney+ Hotstar'
+                    )
+                )
+            )
+    """
+
+    def test2_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('
+            content:ANY("President of the United States")
+            OR NOT (
+                content:Braveheart
+                AND (
+                    NOT content:ALL("List of presidents of India")
+                    AND NOT (
+                        title:ALL(Philosophy)
+                        OR content:ALL("Disney+ Hotstar")
+                    )
+                )
+            )
+        ')
+    """
+
+    assertEquals(test2_match[0][0], test2_search[0][0],
+        "Complex nested query: MATCH and SEARCH should return same count")
+
+    logger.info("Test 2 PASSED: Complex nested query - MATCH and SEARCH 
consistent (count: ${test2_match[0][0]})")
+
+    // Test 3: Verify the 15 critical rows are included
+    def test3 = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE title MATCH_ALL 'Philosophy'
+          AND content IS NULL
+          AND SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar")')
+    """
+
+    assertEquals(15, test3[0][0], "The 15 rows with NULL content should be 
included")
+
+    logger.info("Test 3 PASSED: 15 NULL content rows correctly included")
+
+    // Test 4: Three-way OR with different NULL patterns
+    def test4_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE title MATCH_ALL 'Philosophy'
+           OR content MATCH_ALL 'Disney+ Hotstar'
+           OR author MATCH_ANY 'Political'
+    """
+
+    def test4_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar") 
OR author:ANY(Political)')
+    """
+
+    assertEquals(test4_match[0][0], test4_search[0][0], "Three-way OR should 
be consistent")
+
+    logger.info("Test 4 PASSED: Three-way OR with different NULL patterns")
+
+    // Test 5: OR within AND with NULL
+    def test5_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE category MATCH 'Education'
+          AND (title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney')
+    """
+
+    def test5_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('category:Education AND (title:ALL(Philosophy) OR 
content:ALL(Disney))')
+    """
+
+    assertEquals(test5_match[0][0], test5_search[0][0], "OR within AND should 
be consistent")
+
+    logger.info("Test 5 PASSED: OR within AND with NULL")
+
+    // Test 6: Deeply nested OR with multiple NULL fields
+    def test6_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE (
+            (title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney')
+            OR (author MATCH_ANY 'Writer' OR category MATCH 'Technology')
+        )
+    """
+
+    def test6_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('
+            (title:ALL(Philosophy) OR content:ALL(Disney))
+            OR (author:ANY(Writer) OR category:Technology)
+        ')
+    """
+
+    assertEquals(test6_match[0][0], test6_search[0][0], "Deeply nested OR 
should be consistent")
+
+    logger.info("Test 6 PASSED: Deeply nested OR with multiple NULL fields 
(count: ${test6_match[0][0]})")
+
+    // Test 7: Verify SQL three-valued logic truth table for OR
+    // TRUE OR NULL = TRUE
+    def test7a = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE (title MATCH_ALL 'Philosophy' AND content IS NULL)
+          AND (title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney+ 
Hotstar')
+    """
+    assertEquals(15, test7a[0][0], "TRUE OR NULL should be TRUE (15 rows)")
+
+    // FALSE OR NULL = NULL (excluded from WHERE clause)
+    def test7b = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE (title NOT MATCH_ALL 'Philosophy' AND content IS NULL)
+          AND (title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney+ 
Hotstar')
+    """
+    assertEquals(0, test7b[0][0], "FALSE OR NULL should be NULL (0 rows)")
+
+    logger.info("Test 7 PASSED: SQL three-valued logic truth table verified")
+
+    // Test 8: Performance - ensure both use inverted index
+    sql "SET enable_profile = true"
+
+    sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar")')
+    """
+
+    // Just ensure query completes without error (actual profile checking 
would be more complex)
+    logger.info("Test 8 PASSED: Query execution successful with profiling 
enabled")
+
+    sql "SET enable_profile = false"
+    logger.info("All tests PASSED: Complex OR NULL semantics work correctly")
+}
diff --git 
a/regression-test/suites/inverted_index_p0/test_cross_field_or_with_null.groovy 
b/regression-test/suites/inverted_index_p0/test_cross_field_or_with_null.groovy
new file mode 100644
index 00000000000..55b317e4329
--- /dev/null
+++ 
b/regression-test/suites/inverted_index_p0/test_cross_field_or_with_null.groovy
@@ -0,0 +1,192 @@
+// 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_cross_field_or_with_null") {
+    // This test verifies the fix for the bug where OR queries with NULL values
+    // incorrectly filtered out rows where "TRUE OR NULL" should evaluate to 
TRUE
+
+    def tableName = "test_cross_field_or_null_table"
+
+    sql "DROP TABLE IF EXISTS ${tableName}"
+
+    // Create table with inverted indexes on multiple fields
+    sql """
+        CREATE TABLE ${tableName} (
+            id INT,
+            title VARCHAR(200),
+            content TEXT,
+            category VARCHAR(100),
+            INDEX idx_title (title) USING INVERTED PROPERTIES("parser" = 
"english"),
+            INDEX idx_content (content) USING INVERTED PROPERTIES("parser" = 
"english"),
+            INDEX idx_category (category) USING INVERTED
+        ) ENGINE=OLAP
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 3
+        PROPERTIES (
+            "replication_allocation" = "tag.location.default: 1"
+        )
+    """
+
+    // Insert test data with specific NULL patterns
+    // Rows 1-15: title matches "Philosophy", content is NULL
+    // Row 16: both title and content have values
+    // Rows 17-20: content is NULL, title does not match
+    sql """ INSERT INTO ${tableName} VALUES
+        (1, 'Philosophy 101', NULL, 'Education'),
+        (2, 'Ancient Philosophy', NULL, 'History'),
+        (3, 'Modern Philosophy', NULL, 'Education'),
+        (4, 'Eastern Philosophy', NULL, 'Culture'),
+        (5, 'Western Philosophy', NULL, 'Culture'),
+        (6, 'Philosophy of Mind', NULL, 'Science'),
+        (7, 'Philosophy of Science', NULL, 'Science'),
+        (8, 'Philosophy Basics', NULL, 'Education'),
+        (9, 'Greek Philosophy', NULL, 'History'),
+        (10, 'Medieval Philosophy', NULL, 'History'),
+        (11, 'Renaissance Philosophy', NULL, 'History'),
+        (12, 'Contemporary Philosophy', NULL, 'Education'),
+        (13, 'Philosophy and Logic', NULL, 'Science'),
+        (14, 'Philosophy Fundamentals', NULL, 'Education'),
+        (15, 'Introduction to Philosophy', NULL, 'Education'),
+        (16, 'Science Today', 'Disney+ Hotstar streaming service', 
'Technology'),
+        (17, 'Random Article', NULL, 'News'),
+        (18, 'Another Topic', NULL, 'General'),
+        (19, 'Sample Entry', NULL, 'Misc'),
+        (20, 'Test Data', NULL, 'Test')
+    """
+
+    // Test 1: Simple cross-field OR with NULL
+    // title MATCH "Philosophy" OR content MATCH "Disney+ Hotstar"
+    // Expected: Rows 1-16 (rows 1-15 have title match, row 16 has content 
match)
+    sql "SET enable_common_expr_pushdown = true"
+
+    def result1_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney+ 
Hotstar'
+    """
+
+    def result1_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar")')
+    """
+
+    // Both should return 16 rows
+    assertEquals(16, result1_match[0][0])
+    assertEquals(16, result1_search[0][0])
+
+    logger.info("Test 1 passed: Simple cross-field OR with NULL")
+
+    // Test 2: Verify that the 15 NULL content rows with matching title are 
included
+    def result2 = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE title MATCH_ALL 'Philosophy'
+          AND content IS NULL
+          AND (title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney+ 
Hotstar')
+    """
+
+    assertEquals(15, result2[0][0])
+    logger.info("Test 2 passed: Verified 15 rows with NULL content are 
included")
+
+    // Test 3: Complex nested OR query
+    // (title MATCH "Philosophy" OR (content MATCH "Disney" OR content MATCH 
"streaming"))
+    def result3_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE title MATCH_ALL 'Philosophy'
+           OR (content MATCH_ALL 'Disney' OR content MATCH_ALL 'streaming')
+    """
+
+    def result3_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('title:ALL(Philosophy) OR (content:ALL(Disney) OR 
content:ALL(streaming))')
+    """
+
+    assertEquals(result3_match[0][0], result3_search[0][0])
+    logger.info("Test 3 passed: Complex nested OR query")
+
+    // Test 4: NOT with cross-field OR
+    // NOT (title MATCH "Philosophy" OR content MATCH "Disney+ Hotstar")
+    // Rows 1-16: title matches or content matches -> OR = TRUE -> NOT TRUE = 
FALSE (excluded)
+    // Rows 17-20: title doesn't match (FALSE) and content is NULL -> FALSE OR 
NULL = NULL -> NOT NULL = NULL (excluded)
+    // Expected: 0 rows (all rows are either TRUE or NULL in the OR, none are 
FALSE)
+    def result4_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE NOT (title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney+ 
Hotstar')
+    """
+
+    def result4_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('NOT (title:ALL(Philosophy) OR content:ALL("Disney+ 
Hotstar"))')
+    """
+
+    assertEquals(0, result4_match[0][0])  // All rows excluded due to NULL 
semantics
+    assertEquals(result4_match[0][0], result4_search[0][0])
+    logger.info("Test 4 passed: NOT with cross-field OR (NULL semantics 
correctly exclude rows)")
+
+    // Test 5: AND with cross-field OR containing NULL
+    // category = "Education" AND (title MATCH "Philosophy" OR content MATCH 
"Disney")
+    def result5_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE category MATCH 'Education'
+          AND (title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney')
+    """
+
+    def result5_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('category:Education AND (title:ALL(Philosophy) OR 
content:ALL(Disney))')
+    """
+
+    assertEquals(result5_match[0][0], result5_search[0][0])
+    logger.info("Test 5 passed: AND with cross-field OR containing NULL")
+
+    // Test 6: Test with pushdown disabled (should also work correctly)
+    sql "SET enable_common_expr_pushdown = false"
+
+    def result6_nopush = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE title MATCH_ALL 'Philosophy' OR content MATCH_ALL 'Disney+ 
Hotstar'
+    """
+
+    assertEquals(16, result6_nopush[0][0])
+    logger.info("Test 6 passed: Query works correctly without pushdown")
+
+    // Test 7: Multiple OR conditions with different NULL patterns
+    sql "SET enable_common_expr_pushdown = true"
+
+    def result7_match = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE title MATCH_ALL 'Philosophy'
+           OR content MATCH_ALL 'Disney+ Hotstar'
+           OR category MATCH 'Technology'
+    """
+
+    def result7_search = sql """
+        SELECT COUNT(*) FROM ${tableName}
+        WHERE SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar") 
OR category:Technology')
+    """
+
+    assertEquals(result7_match[0][0], result7_search[0][0])
+    logger.info("Test 7 passed: Multiple OR conditions with different NULL 
patterns")
+
+    // Test 8: Verify individual field queries work correctly
+    def result8_title = sql "SELECT COUNT(*) FROM ${tableName} WHERE title 
MATCH_ALL 'Philosophy'"
+    def result8_content_null = sql "SELECT COUNT(*) FROM ${tableName} WHERE 
content IS NULL"
+
+    assertEquals(15, result8_title[0][0])
+    assertEquals(19, result8_content_null[0][0])  // Rows 1-15, 17-20
+    logger.info("Test 8 passed: Individual field queries return expected 
counts")
+
+    sql "DROP TABLE IF EXISTS ${tableName}"
+}
diff --git 
a/regression-test/suites/inverted_index_p0/test_search_or_null_semantics.groovy 
b/regression-test/suites/inverted_index_p0/test_search_or_null_semantics.groovy
new file mode 100644
index 00000000000..c9852bccfb5
--- /dev/null
+++ 
b/regression-test/suites/inverted_index_p0/test_search_or_null_semantics.groovy
@@ -0,0 +1,195 @@
+// 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_or_null_semantics") {
+    // This test verifies the fix for the bug in 
UnionScorer::_collect_child_nulls()
+    // in buffered_union_scorer.cpp where NULL bitmaps were incorrectly 
combined using OR
+    // instead of AND for SEARCH function queries
+    //
+    // Bug location: 
be/src/olap/rowset/segment_v2/inverted_index/query_v2/buffered_union_scorer.cpp:151-164
+    // The bug caused rows with (TRUE OR NULL) to be incorrectly filtered out
+
+    def tableName = "test_search_or_null_table"
+
+    sql "DROP TABLE IF EXISTS ${tableName}"
+
+    sql """
+        CREATE TABLE ${tableName} (
+            id INT,
+            title TEXT,
+            content TEXT,
+            author TEXT,
+            category TEXT,
+            INDEX idx_title (title) USING INVERTED PROPERTIES("parser" = 
"english"),
+            INDEX idx_content (content) USING INVERTED PROPERTIES("parser" = 
"english"),
+            INDEX idx_author (author) USING INVERTED PROPERTIES("parser" = 
"english"),
+            INDEX idx_category (category) USING INVERTED
+        ) ENGINE=OLAP
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 3
+        PROPERTIES (
+            "replication_allocation" = "tag.location.default: 1"
+        )
+    """
+
+    // Insert test data simulating Wikipedia-like articles
+    sql """ INSERT INTO ${tableName} VALUES
+        -- Rows 1-15: title = "Philosophy", content = NULL (TRUE OR NULL = 
TRUE)
+        (1, 'Philosophy', NULL, 'Author A', 'Education'),
+        (2, 'Philosophy', NULL, 'Author B', 'Science'),
+        (3, 'Philosophy', NULL, 'Author C', 'History'),
+        (4, 'Philosophy', NULL, 'Author D', 'Culture'),
+        (5, 'Philosophy', NULL, 'Author E', 'Education'),
+        (6, 'Philosophy', NULL, 'Author F', 'Science'),
+        (7, 'Philosophy', NULL, 'Author G', 'History'),
+        (8, 'Philosophy', NULL, 'Author H', 'Education'),
+        (9, 'Philosophy', NULL, 'Author I', 'Culture'),
+        (10, 'Philosophy', NULL, 'Author J', 'Education'),
+        (11, 'Philosophy', NULL, 'Author K', 'Science'),
+        (12, 'Philosophy', NULL, 'Author L', 'History'),
+        (13, 'Philosophy', NULL, 'Author M', 'Education'),
+        (14, 'Philosophy', NULL, 'Author N', 'Culture'),
+        (15, 'Philosophy', NULL, 'Author O', 'Education'),
+        -- Row 16: Different article (FALSE OR TRUE = TRUE)
+        (16, 'Technology News', 'Disney+ Hotstar streaming platform', 'Tech 
Writer', 'Technology'),
+        -- Rows 17-20: Various other articles (FALSE OR NULL = NULL, excluded)
+        (17, 'Science Article', NULL, 'Political Writer', 'Politics'),
+        (18, 'History Piece', NULL, 'Historian', 'History'),
+        (19, 'Movie Review', NULL, 'Critic', 'Entertainment'),
+        (20, 'Tech Article', NULL, 'Academic', 'Education')
+    """
+
+    sql "SET enable_common_expr_pushdown = true"
+
+    // Test 1: The core bug scenario - cross-field OR with one field NULL
+    // Before fix: SEARCH returned 1 row (only row 16, lost 15 rows with NULL 
content)
+    // After fix: SEARCH returns 16 rows (rows 1-16)
+    def test1 = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar")')
+    """
+
+    assertEquals(16, test1[0][0], "SEARCH should return 16 rows (15 with title 
match + 1 with content match)")
+    logger.info("Test 1 PASSED: Cross-field OR with NULL - SEARCH returns 16 
rows")
+
+    // Test 2: Verify the 15 critical rows are included
+    def test2 = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE title MATCH_ALL 'Philosophy'
+          AND content IS NULL
+          AND SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar")')
+    """
+
+    assertEquals(15, test2[0][0], "The 15 rows with NULL content should be 
included (TRUE OR NULL = TRUE)")
+    logger.info("Test 2 PASSED: 15 NULL content rows correctly included")
+
+    // Test 3: Three-way OR with different NULL patterns
+    def test3 = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar") 
OR author:ANY(Political)')
+    """
+
+    assertEquals(17, test3[0][0], "Three-way OR should return 17 rows (15 
title + 1 content + 1 author)")
+    logger.info("Test 3 PASSED: Three-way OR with different NULL patterns")
+
+    // Test 4: OR within AND with NULL
+    def test4 = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE SEARCH('category:Education AND (title:ALL(Philosophy) OR 
content:ALL(Disney))')
+    """
+
+    def test4_count = test4[0][0]
+    assertTrue(test4_count >= 6, "Should return at least 6 rows (rows with 
Education category and title match)")
+    logger.info("Test 4 PASSED: OR within AND with NULL (returned 
${test4_count} rows)")
+
+    // Test 5: Deeply nested OR with multiple NULL fields
+    def test5 = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE SEARCH('
+            (title:ALL(Philosophy) OR content:ALL(Disney))
+            OR (author:ANY(Writer) OR category:Technology)
+        ')
+    """
+
+    def test5_count = test5[0][0]
+    assertTrue(test5_count >= 16, "Deeply nested OR should return at least 16 
rows")
+    logger.info("Test 5 PASSED: Deeply nested OR with multiple NULL fields 
(returned ${test5_count} rows)")
+
+    // Test 6: Verify SQL three-valued logic truth table for OR
+    // TRUE OR NULL = TRUE
+    def test6a = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE (title MATCH_ALL 'Philosophy' AND content IS NULL)
+    """
+    assertEquals(15, test6a[0][0], "Should have 15 rows with TRUE OR NULL 
pattern")
+
+    def test6b = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE (title MATCH_ALL 'Philosophy' AND content IS NULL)
+          AND SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar")')
+    """
+    assertEquals(15, test6b[0][0], "TRUE OR NULL should be TRUE (15 rows 
included)")
+    logger.info("Test 6 PASSED: SQL three-valued logic - TRUE OR NULL = TRUE")
+
+    // Test 7: Complex nested query similar to original bug report
+    def test7 = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE SEARCH('
+            title:ALL(Philosophy)
+            OR NOT (
+                content:ALL(History)
+                AND (
+                    NOT category:Education
+                    AND NOT (
+                        title:ALL(Science)
+                        OR author:ANY(Writer)
+                    )
+                )
+            )
+        ')
+    """
+
+    def test7_count = test7[0][0]
+    assertTrue(test7_count >= 15, "Complex nested query should include the 15 
Philosophy rows")
+    logger.info("Test 7 PASSED: Complex nested query (returned ${test7_count} 
rows)")
+
+    // Test 8: NOT with OR and NULL (SQL three-valued logic)
+    // Rows 1-16: OR = TRUE -> NOT TRUE = FALSE (excluded)
+    // Rows 17-20: OR = NULL -> NOT NULL = NULL (excluded)
+    def test8 = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE SEARCH('NOT (title:ALL(Philosophy) OR content:ALL("Disney+ 
Hotstar"))')
+    """
+
+    assertEquals(0, test8[0][0], "NOT OR should exclude all rows due to NULL 
semantics")
+    logger.info("Test 8 PASSED: NOT OR with NULL correctly excludes all rows")
+
+    // Test 9: Verify SEARCH works with and without pushdown
+    sql "SET enable_common_expr_pushdown = false"
+
+    def test9 = sql """
+        SELECT /*+SET_VAR(enable_common_expr_pushdown=true) */ COUNT(*) FROM 
${tableName}
+        WHERE SEARCH('title:ALL(Philosophy) OR content:ALL("Disney+ Hotstar")')
+    """
+
+    assertEquals(16, test9[0][0], "SEARCH should return 16 rows even without 
pushdown")
+    logger.info("Test 9 PASSED: SEARCH works correctly without pushdown")
+
+    sql "DROP TABLE IF EXISTS ${tableName}"
+
+    logger.info("All tests PASSED: SEARCH OR NULL semantics work correctly")
+}


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

Reply via email to