This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
commit 1b3e2a585dbdf6a14756bd612c08e3bdf7424ed4
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Sep 28 09:29:49 2026 +0800
branch-4.1: [fix](be) Fix constant nullable JSON path evaluation #68427
(#68525)
Cherry-picked from #68427
Co-authored-by: Jerry Hu <[email protected]>
---
be/src/exprs/function/function_jsonb.cpp | 10 +-
be/test/exprs/function/function_jsonb_test.cpp | 111 ++++++++++++++++++++-
.../test_json_const_nullable_path.out | 6 ++
.../test_json_const_nullable_path.groovy | 43 ++++++++
4 files changed, 164 insertions(+), 6 deletions(-)
diff --git a/be/src/exprs/function/function_jsonb.cpp
b/be/src/exprs/function/function_jsonb.cpp
index e572573a3c8..bfee90dc1a9 100644
--- a/be/src/exprs/function/function_jsonb.cpp
+++ b/be/src/exprs/function/function_jsonb.cpp
@@ -1405,8 +1405,10 @@ struct JsonbLengthUtil {
}
for (size_t i = 0; i < input_rows_count; ++i) {
- if (jsonb_data_column->is_null_at(i) || path_column->is_null_at(i)
||
- (jsonb_data_column->get_data_at(i).size == 0)) {
+ const auto jsonb_index = index_check_const(i, jsonb_data_const);
+ if (jsonb_data_column->is_null_at(jsonb_index) ||
+ path_column->is_null_at(index_check_const(i, is_const)) ||
+ (jsonb_data_column->get_data_at(jsonb_index).size == 0)) {
null_map->get_data()[i] = 1;
res->insert_data(nullptr, 0);
continue;
@@ -1421,7 +1423,7 @@ struct JsonbLengthUtil {
path_value.size));
}
}
- auto jsonb_value = jsonb_data_column->get_data_at(i);
+ auto jsonb_value = jsonb_data_column->get_data_at(jsonb_index);
// doc is NOT necessary to be deleted since JsonbDocument will not
allocate memory
const JsonbDocument* doc = nullptr;
RETURN_IF_ERROR(JsonbDocument::checkAndCreateDocument(jsonb_value.data,
@@ -1520,7 +1522,7 @@ struct JsonbContainsUtil {
for (size_t i = 0; i < input_rows_count; ++i) {
if (jsonb_data1_column->is_null_at(i) ||
jsonb_data2_column->is_null_at(i) ||
- path_column->is_null_at(i)) {
+ path_column->is_null_at(index_check_const(i, is_const))) {
null_map->get_data()[i] = 1;
res->insert_data(nullptr, 0);
continue;
diff --git a/be/test/exprs/function/function_jsonb_test.cpp
b/be/test/exprs/function/function_jsonb_test.cpp
index 8d9df92d402..7736362271c 100644
--- a/be/test/exprs/function/function_jsonb_test.cpp
+++ b/be/test/exprs/function/function_jsonb_test.cpp
@@ -67,6 +67,12 @@ ColumnPtr create_const_path_column(const std::string& path,
size_t rows) {
return ColumnConst::create(create_path_column({path}), rows);
}
+ColumnPtr create_const_nullable_path_column(const std::string& path, size_t
rows) {
+ auto nullable_path = ColumnNullable::create(ColumnString::create(),
ColumnUInt8::create());
+ nullable_path->insert_data(path.data(), path.size());
+ return ColumnConst::create(std::move(nullable_path), rows);
+}
+
Status execute_json_keys_with_path(ColumnPtr path_column, size_t rows) {
auto jsonb_type = std::make_shared<DataTypeJsonb>();
auto path_type = std::make_shared<DataTypeString>();
@@ -902,7 +908,7 @@ TEST(FunctionJsonbTEST, JsonLength) {
arg_types.emplace_back(json_data_type);
arg_types.emplace_back(std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>()));
- FunctionUtils fn_utils(return_type, arg_types, 0);
+ FunctionUtils fn_utils(return_type, arg_types, false);
auto* fn_ctx = fn_utils.get_fn_ctx();
// fn_ctx->set_constant_cols(constant_cols);
auto st = func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL);
@@ -917,6 +923,70 @@ TEST(FunctionJsonbTEST, JsonLength) {
ASSERT_TRUE(st.ok()) << "execute failed: " << st.to_string();
}
+TEST(FunctionJsonbTEST, JsonLengthConstNullablePathMultiRows) {
+ constexpr size_t rows = 3;
+ auto jsonb_type = std::make_shared<DataTypeJsonb>();
+ auto path_type = make_nullable(std::make_shared<DataTypeString>());
+ auto return_type = make_nullable(std::make_shared<DataTypeInt32>());
+
+ auto jsonb_column = jsonb_type->create_column();
+ ASSERT_TRUE(insert_cell(jsonb_column, jsonb_type,
STRING(R"({"a":[1,2]})")));
+ ASSERT_TRUE(insert_cell(jsonb_column, jsonb_type,
STRING(R"({"a":[3,4,5]})")));
+ ASSERT_TRUE(insert_cell(jsonb_column, jsonb_type, STRING(R"({"a":[]})")));
+
+ Block block;
+ block.insert({std::move(jsonb_column), jsonb_type, "jsonb"});
+ block.insert({create_const_nullable_path_column("$.a", rows), path_type,
"path"});
+
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ "json_length", block.get_columns_with_type_and_name(),
return_type);
+ ASSERT_NE(func, nullptr);
+
+ FunctionUtils fn_utils(return_type, {jsonb_type, path_type}, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok());
+
+ block.insert({nullptr, return_type, "result"});
+ ASSERT_TRUE(func->execute(fn_ctx, block, {0, 1}, 2, rows).ok());
+
+ const auto& result = assert_cast<const
ColumnNullable&>(*block.get_by_position(2).column);
+ EXPECT_EQ(result.get_null_map_data(), NullMap({0, 0, 0}));
+ EXPECT_EQ(assert_cast<const
ColumnInt32&>(result.get_nested_column()).get_data(),
+ PaddedPODArray<Int32>({2, 3, 0}));
+}
+
+TEST(FunctionJsonbTEST, JsonLengthConstJsonVaryingPathMultiRows) {
+ constexpr size_t rows = 3;
+ auto jsonb_type = std::make_shared<DataTypeJsonb>();
+ auto path_type = std::make_shared<DataTypeString>();
+ auto return_type = make_nullable(std::make_shared<DataTypeInt32>());
+
+ auto jsonb_column = jsonb_type->create_column();
+ ASSERT_TRUE(insert_cell(jsonb_column, jsonb_type,
STRING(R"({"a":[1,2]})")));
+
+ Block block;
+ block.insert({ColumnConst::create(std::move(jsonb_column), rows),
jsonb_type, "jsonb"});
+ block.insert({create_path_column({"$.a", "$.a[0]", "$.missing"}),
path_type, "path"});
+
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ "json_length", block.get_columns_with_type_and_name(),
return_type);
+ ASSERT_NE(func, nullptr);
+
+ FunctionUtils fn_utils(return_type, {jsonb_type, path_type}, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok());
+
+ block.insert({nullptr, return_type, "result"});
+ ASSERT_TRUE(func->execute(fn_ctx, block, {0, 1}, 2, rows).ok());
+
+ const auto& result = assert_cast<const
ColumnNullable&>(*block.get_by_position(2).column);
+ EXPECT_EQ(result.get_null_map_data(), NullMap({0, 0, 1}));
+ EXPECT_EQ(assert_cast<const
ColumnInt32&>(result.get_nested_column()).get_data(),
+ PaddedPODArray<Int32>({2, 1, 0}));
+}
+
TEST(FunctionJsonbTEST, JsonContains) {
std::string func_name = "json_contains";
@@ -971,7 +1041,7 @@ TEST(FunctionJsonbTEST, JsonContains) {
arg_types.emplace_back(json_data_type);
arg_types.emplace_back(std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>()));
- FunctionUtils fn_utils(return_type, arg_types, 0);
+ FunctionUtils fn_utils(return_type, arg_types, false);
auto* fn_ctx = fn_utils.get_fn_ctx();
// fn_ctx->set_constant_cols(constant_cols);
auto st = func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL);
@@ -986,6 +1056,43 @@ TEST(FunctionJsonbTEST, JsonContains) {
ASSERT_TRUE(st.ok()) << "execute failed: " << st.to_string();
}
+TEST(FunctionJsonbTEST, JsonContainsConstNullablePathMultiRows) {
+ constexpr size_t rows = 3;
+ auto jsonb_type = std::make_shared<DataTypeJsonb>();
+ auto path_type = make_nullable(std::make_shared<DataTypeString>());
+ auto return_type = make_nullable(std::make_shared<DataTypeBool>());
+
+ auto jsonb_column = jsonb_type->create_column();
+ ASSERT_TRUE(insert_cell(jsonb_column, jsonb_type,
STRING(R"({"a":[1,2]})")));
+ ASSERT_TRUE(insert_cell(jsonb_column, jsonb_type,
STRING(R"({"a":[3,4,5]})")));
+ ASSERT_TRUE(insert_cell(jsonb_column, jsonb_type, STRING(R"({"a":[]})")));
+
+ auto candidate_column = jsonb_type->create_column();
+ ASSERT_TRUE(insert_cell(candidate_column, jsonb_type, STRING("[3]")));
+
+ Block block;
+ block.insert({std::move(jsonb_column), jsonb_type, "jsonb"});
+ block.insert({ColumnConst::create(std::move(candidate_column), rows),
jsonb_type, "candidate"});
+ block.insert({create_const_nullable_path_column("$.a", rows), path_type,
"path"});
+
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ "json_contains", block.get_columns_with_type_and_name(),
return_type);
+ ASSERT_NE(func, nullptr);
+
+ FunctionUtils fn_utils(return_type, {jsonb_type, jsonb_type, path_type},
false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok());
+
+ block.insert({nullptr, return_type, "result"});
+ ASSERT_TRUE(func->execute(fn_ctx, block, {0, 1, 2}, 3, rows).ok());
+
+ const auto& result = assert_cast<const
ColumnNullable&>(*block.get_by_position(3).column);
+ EXPECT_EQ(result.get_null_map_data(), NullMap({0, 0, 0}));
+ EXPECT_EQ(assert_cast<const
ColumnUInt8&>(result.get_nested_column()).get_data(),
+ PaddedPODArray<UInt8>({0, 1, 0}));
+}
+
TEST(FunctionJsonbTEST, JsonbModifyMissingPathParent) {
InputTypeSet input_types = {PrimitiveType::TYPE_JSONB,
PrimitiveType::TYPE_VARCHAR,
PrimitiveType::TYPE_JSONB};
diff --git
a/regression-test/data/query_p0/sql_functions/json_functions/test_json_const_nullable_path.out
b/regression-test/data/query_p0/sql_functions/json_functions/test_json_const_nullable_path.out
new file mode 100644
index 00000000000..1b6a587b084
--- /dev/null
+++
b/regression-test/data/query_p0/sql_functions/json_functions/test_json_const_nullable_path.out
@@ -0,0 +1,6 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !const_nullable_path --
+1 2 false
+2 3 true
+3 0 false
+
diff --git
a/regression-test/suites/query_p0/sql_functions/json_functions/test_json_const_nullable_path.groovy
b/regression-test/suites/query_p0/sql_functions/json_functions/test_json_const_nullable_path.groovy
new file mode 100644
index 00000000000..dc55a99d7a2
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/json_functions/test_json_const_nullable_path.groovy
@@ -0,0 +1,43 @@
+// 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_json_const_nullable_path", "p0") {
+ sql "DROP TABLE IF EXISTS test_json_const_nullable_path"
+ sql """
+ CREATE TABLE test_json_const_nullable_path (
+ id INT,
+ js JSON
+ )
+ DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES ("replication_num" = "1")
+ """
+ sql """
+ INSERT INTO test_json_const_nullable_path VALUES
+ (1, '{"a":[1,2]}'),
+ (2, '{"a":[3,4,5]}'),
+ (3, '{"a":[]}')
+ """
+
+ order_qt_const_nullable_path """
+ SELECT id,
+ json_length(js, nullable('\$.a')),
+ json_contains(js, CAST('[3]' AS JSON), nullable('\$.a'))
+ FROM test_json_const_nullable_path
+ ORDER BY id
+ """
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]