This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.0 by this push:
new a88e687d483 branch-4.0: [fix](inverted index) handle NULL literal to
prevent BE crash #59916 (#60016)
a88e687d483 is described below
commit a88e687d483c1e082174090ee7b33168f31b0188
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Jan 19 19:36:39 2026 +0800
branch-4.0: [fix](inverted index) handle NULL literal to prevent BE crash
#59916 (#60016)
Cherry-picked from #59916
Co-authored-by: zzzxl <[email protected]>
---
be/src/vec/functions/functions_comparison.h | 3 +
...ion_comparison_evaluate_inverted_index_test.cpp | 125 +++++++++++++++++++++
.../test_inverted_index_null_literal.out | 19 ++++
.../test_inverted_index_null_literal.groovy | 62 ++++++++++
4 files changed, 209 insertions(+)
diff --git a/be/src/vec/functions/functions_comparison.h
b/be/src/vec/functions/functions_comparison.h
index f45a8d99b96..d785ea6515b 100644
--- a/be/src/vec/functions/functions_comparison.h
+++ b/be/src/vec/functions/functions_comparison.h
@@ -487,6 +487,9 @@ public:
}
Field param_value;
arguments[0].column->get(0, param_value);
+ if (param_value.is_null()) {
+ return Status::OK();
+ }
auto param_type = arguments[0].type->get_primitive_type();
std::unique_ptr<segment_v2::InvertedIndexQueryParamFactory>
query_param = nullptr;
RETURN_IF_ERROR(segment_v2::InvertedIndexQueryParamFactory::create_query_value(
diff --git
a/be/test/vec/function/function_comparison_evaluate_inverted_index_test.cpp
b/be/test/vec/function/function_comparison_evaluate_inverted_index_test.cpp
new file mode 100644
index 00000000000..f414412be06
--- /dev/null
+++ b/be/test/vec/function/function_comparison_evaluate_inverted_index_test.cpp
@@ -0,0 +1,125 @@
+// 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.
+
+#include "gtest/gtest.h"
+#include "olap/rowset/segment_v2/index_iterator.h"
+#include "olap/rowset/segment_v2/inverted_index_reader.h"
+#include "vec/columns/column_const.h"
+#include "vec/columns/column_nullable.h"
+#include "vec/data_types/data_type_nullable.h"
+#include "vec/data_types/data_type_number.h"
+#include "vec/data_types/data_type_string.h"
+#include "vec/functions/functions_comparison.h"
+
+namespace doris::vectorized {
+
+class MockInvertedIndexReader : public segment_v2::InvertedIndexReader {
+public:
+ MockInvertedIndexReader(const TabletIndex& index_meta)
+ : segment_v2::InvertedIndexReader(&index_meta, nullptr) {}
+ ~MockInvertedIndexReader() override = default;
+
+ segment_v2::InvertedIndexReaderType type() override {
+ return segment_v2::InvertedIndexReaderType::BKD;
+ }
+
+ Status query(const segment_v2::IndexQueryContextPtr& context, const
std::string& column_name,
+ const void* query_value, segment_v2::InvertedIndexQueryType
query_type,
+ std::shared_ptr<roaring::Roaring>& bit_map,
+ const InvertedIndexAnalyzerCtx* analyzer_ctx = nullptr)
override {
+ return Status::OK();
+ }
+
+ Status try_query(const segment_v2::IndexQueryContextPtr& context,
+ const std::string& column_name, const void* query_value,
+ segment_v2::InvertedIndexQueryType query_type, size_t*
count) override {
+ return Status::OK();
+ }
+
+ Status new_iterator(std::unique_ptr<segment_v2::IndexIterator>* iterator)
override {
+ return Status::OK();
+ }
+};
+
+class MockComparisonIndexIterator : public segment_v2::IndexIterator {
+public:
+ MockComparisonIndexIterator(std::shared_ptr<MockInvertedIndexReader>
reader)
+ : _reader(reader) {}
+ ~MockComparisonIndexIterator() override = default;
+
+ segment_v2::IndexReaderPtr get_reader(segment_v2::IndexReaderType
reader_type) const override {
+ if
(std::holds_alternative<segment_v2::InvertedIndexReaderType>(reader_type)) {
+ if (std::get<segment_v2::InvertedIndexReaderType>(reader_type) ==
+ segment_v2::InvertedIndexReaderType::BKD) {
+ return _reader;
+ }
+ }
+ return nullptr;
+ }
+
+ Status read_from_index(const segment_v2::IndexParam& param) override {
+ auto* p = std::get<segment_v2::InvertedIndexParam*>(param);
+ p->roaring->addRange(0, 10);
+ return Status::OK();
+ }
+
+ Status read_null_bitmap(segment_v2::InvertedIndexQueryCacheHandle*
cache_handle) override {
+ return Status::OK();
+ }
+
+ Result<bool> has_null() override { return false; }
+
+private:
+ std::shared_ptr<MockInvertedIndexReader> _reader;
+};
+
+TEST(FunctionComparisonTest, evaluate_inverted_index_with_null_param) {
+ FunctionComparison<EqualsOp, NameEquals> func;
+
+ auto nested_col = ColumnInt32::create();
+ nested_col->insert_default();
+
+ auto null_map = ColumnUInt8::create();
+ null_map->insert_value(1);
+
+ auto nullable_col = ColumnNullable::create(std::move(nested_col),
std::move(null_map));
+
+ auto const_nullable_col = ColumnConst::create(std::move(nullable_col), 1);
+
+ auto nullable_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>());
+ ColumnsWithTypeAndName arguments = {
+ {std::move(const_nullable_col), nullable_type, "null_param"}};
+
+ std::vector<IndexFieldNameAndTypePair> data_type_with_names = {
+ {"test_col", std::make_shared<DataTypeInt32>()}};
+
+ TabletIndex index_meta;
+ auto reader = std::make_shared<MockInvertedIndexReader>(index_meta);
+ auto iter = std::make_unique<MockComparisonIndexIterator>(reader);
+ std::vector<segment_v2::IndexIterator*> iterators = {iter.get()};
+
+ segment_v2::InvertedIndexResultBitmap bitmap_result;
+ auto status = func.evaluate_inverted_index(arguments,
data_type_with_names, iterators, 100,
+ nullptr, bitmap_result);
+
+ ASSERT_TRUE(status.ok()) << "Status should be OK when param is NULL";
+
+ ASSERT_EQ(bitmap_result.get_data_bitmap(), nullptr)
+ << "bitmap_result should not be set when param is NULL";
+}
+
+} // namespace doris::vectorized
diff --git
a/regression-test/data/inverted_index_p0/test_inverted_index_null_literal.out
b/regression-test/data/inverted_index_p0/test_inverted_index_null_literal.out
new file mode 100644
index 00000000000..55bc0b2e54e
--- /dev/null
+++
b/regression-test/data/inverted_index_p0/test_inverted_index_null_literal.out
@@ -0,0 +1,19 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !select_null_literal --
+1
+2
+3
+
+-- !select_null_eq --
+
+-- !select_null_ne --
+
+-- !select_null_between --
+
+-- !select_is_null --
+3
+
+-- !select_is_not_null --
+1
+2
+
diff --git
a/regression-test/suites/inverted_index_p0/test_inverted_index_null_literal.groovy
b/regression-test/suites/inverted_index_p0/test_inverted_index_null_literal.groovy
new file mode 100644
index 00000000000..1170a4a28ff
--- /dev/null
+++
b/regression-test/suites/inverted_index_p0/test_inverted_index_null_literal.groovy
@@ -0,0 +1,62 @@
+// 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_inverted_index_null_literal") {
+
+ def tableName = "test_inverted_index_null_literal"
+
+ sql "DROP TABLE IF EXISTS ${tableName}"
+
+ sql """
+ CREATE TABLE ${tableName} (
+ pk INT,
+ col_varchar VARCHAR(20),
+ INDEX idx_col_varchar (`col_varchar`) USING INVERTED
+ )
+ DUPLICATE KEY(pk)
+ DISTRIBUTED BY HASH(pk) BUCKETS 1
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ )
+ """
+
+ sql """
+ INSERT INTO ${tableName} VALUES
+ (1, 'hello'),
+ (2, 'world'),
+ (3, NULL)
+ """
+
+ sql """ SET disable_nereids_expression_rules = "FOLD_CONSTANT_ON_FE" """
+ sql """ SET enable_fold_constant_by_be = "true" """
+ sql """ SET enable_sql_cache = 0 """
+
+ qt_select_null_literal """
+ SELECT pk FROM ${tableName}
+ WHERE col_varchar = NULL OR pk >= 0
+ ORDER BY pk
+ """
+
+ qt_select_null_eq """SELECT pk FROM ${tableName} WHERE col_varchar = NULL
ORDER BY pk"""
+ qt_select_null_ne """SELECT pk FROM ${tableName} WHERE col_varchar != NULL
ORDER BY pk"""
+ qt_select_null_between """SELECT pk FROM ${tableName} WHERE col_varchar
BETWEEN NULL AND NULL ORDER BY pk"""
+
+ qt_select_is_null """SELECT pk FROM ${tableName} WHERE col_varchar IS NULL
ORDER BY pk"""
+ qt_select_is_not_null """SELECT pk FROM ${tableName} WHERE col_varchar IS
NOT NULL ORDER BY pk"""
+
+ sql "DROP TABLE IF EXISTS ${tableName}"
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]