This is an automated email from the ASF dual-hosted git repository.

morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.1 by this push:
     new 70934d6d1ca branch-3.1 : [Fix](Variant) Fix null value handling when 
serializing to JSON string #55876 (#56138)
70934d6d1ca is described below

commit 70934d6d1caa0704539808a58ff171ce75a34a3e
Author: lihangyu <[email protected]>
AuthorDate: Fri Sep 19 10:52:39 2025 +0800

    branch-3.1 : [Fix](Variant) Fix null value handling when serializing to 
JSON string #55876 (#56138)
    
    cherry-pick #55876
---
 .../variant/hierarchical_data_iterator.cpp         |  25 +++++++++++++-
 be/src/vec/columns/column_object.cpp               |  38 +++++++++------------
 .../data/variant_p0/test_sub_path_pruning.out      | Bin 5972 -> 5966 bytes
 .../data/variant_p0/variant_hirachinal.out         | Bin 697 -> 889 bytes
 .../suites/variant_p0/variant_hirachinal.groovy    |  13 +++++++
 5 files changed, 54 insertions(+), 22 deletions(-)

diff --git 
a/be/src/olap/rowset/segment_v2/variant/hierarchical_data_iterator.cpp 
b/be/src/olap/rowset/segment_v2/variant/hierarchical_data_iterator.cpp
index 77e23d264fb..96e31010de8 100644
--- a/be/src/olap/rowset/segment_v2/variant/hierarchical_data_iterator.cpp
+++ b/be/src/olap/rowset/segment_v2/variant/hierarchical_data_iterator.cpp
@@ -254,8 +254,11 @@ Status 
HierarchicalDataIterator::_init_container(vectorized::MutableColumnPtr& c
         MutableColumnPtr column = _root_reader->column->get_ptr();
         // container_variant.add_sub_column({}, std::move(column), 
_root_reader->type);
         DCHECK(column->size() == nrows);
+        auto nullable_column = make_nullable(column->get_ptr());
+        auto type = make_nullable(_root_reader->type);
+        // make sure the root type is nullable
         container =
-                ColumnObject::create(max_subcolumns_count, _root_reader->type, 
std::move(column));
+                ColumnObject::create(max_subcolumns_count, type, 
nullable_column->assume_mutable());
     } else {
         auto root_type =
                 
vectorized::DataTypeFactory::instance().create_data_type(TypeIndex::Nothing, 
false);
@@ -485,6 +488,26 @@ Status 
HierarchicalDataIterator::_init_null_map_and_clear_columns(
             dst_null_map.insert_range_from(*fake_nullable_column, 0, nrows);
         }
     }
+    // root column nullmap need to be reset, for example, the src_null_map is 
from the whole
+    // variant column, but the root column rows should reset to null when empty
+    ColumnObject* variant = nullptr;
+    if (dst->is_nullable()) {
+        variant =
+                
&assert_cast<ColumnObject&>(assert_cast<ColumnNullable&>(*dst).get_nested_column());
+    } else {
+        variant = &assert_cast<ColumnObject&>(*dst);
+    }
+    if (_path.get_parts().empty()) {
+        // update nullmap for root column, since the original nullmap is from 
the whole variant column
+        auto& dst_map_data =
+                
assert_cast<ColumnNullable&>(*variant->get_root()).get_null_map_column().get_data();
+        for (size_t i = 0; i < variant->get_root()->size(); ++i) {
+            StringRef ref = variant->get_root()->get_data_at(i);
+            if (ref.size == 0) {
+                dst_map_data[i] = 1; // mark null when root jsonb is empty
+            }
+        }
+    }
     return Status::OK();
 }
 
diff --git a/be/src/vec/columns/column_object.cpp 
b/be/src/vec/columns/column_object.cpp
index 361e108029d..b7ff43a68d3 100644
--- a/be/src/vec/columns/column_object.cpp
+++ b/be/src/vec/columns/column_object.cpp
@@ -1415,31 +1415,37 @@ const ColumnObject::Subcolumn* 
ColumnObject::get_subcolumn(const PathInData& key
     return &node->data;
 }
 
+const std::string_view EMPTY_JSON = "{}";
 size_t ColumnObject::Subcolumn::serialize_text_json(size_t n, BufferWritable& 
output,
                                                     
DataTypeSerDe::FormatOptions opt) const {
     if (least_common_type.get_base_type_id() == TypeIndex::Nothing) {
-        output.write(DataTypeSerDe::NULL_IN_COMPLEX_TYPE.data(),
-                     DataTypeSerDe::NULL_IN_COMPLEX_TYPE.size());
-        return DataTypeSerDe::NULL_IN_COMPLEX_TYPE.size();
+        output.write(EMPTY_JSON.data(), EMPTY_JSON.size());
+        return EMPTY_JSON.size();
     }
 
     size_t ind = n;
     if (ind < num_of_defaults_in_prefix) {
-        output.write(DataTypeSerDe::NULL_IN_COMPLEX_TYPE.data(),
-                     DataTypeSerDe::NULL_IN_COMPLEX_TYPE.size());
-        return DataTypeSerDe::NULL_IN_COMPLEX_TYPE.size();
+        output.write(EMPTY_JSON.data(), EMPTY_JSON.size());
+        return EMPTY_JSON.size();
     }
 
     ind -= num_of_defaults_in_prefix;
     for (size_t i = 0; i < data.size(); ++i) {
-        const auto& part = data[i];
+        const auto& part = (*data[i]);
         const auto& part_type_serde = data_serdes[i];
 
-        if (ind < part->size()) {
-            return part_type_serde->serialize_one_cell_to_json(*part, ind, 
output, opt);
+        if (ind < part.size()) {
+            // special case when null flag is true, but the value is empty 
string in JSON type,
+            // other wise will serialize to '\N'
+            const auto* nullable_col = 
check_and_get_column<ColumnNullable>(*data[i]);
+            if (nullable_col && nullable_col->is_null_at(ind)) {
+                output.write(EMPTY_JSON.data(), EMPTY_JSON.size());
+                return EMPTY_JSON.size();
+            }
+            return part_type_serde->serialize_one_cell_to_json(part, ind, 
output, opt);
         }
 
-        ind -= part->size();
+        ind -= part.size();
     }
     throw doris::Exception(ErrorCode::OUT_OF_BOUND,
                            "Index ({}) for serializing JSON is out of range", 
n);
@@ -1725,17 +1731,7 @@ bool ColumnObject::is_visible_root_value(size_t nrow) 
const {
             return false;
         }
     }
-    size_t ind = nrow - root->data.num_of_defaults_in_prefix;
-    // null value as empty json, todo: think a better way to disinguish empty 
json and null json.
-    for (const auto& part : root->data.data) {
-        if (ind < part->size()) {
-            return !part->get_data_at(ind).empty();
-        }
-        ind -= part->size();
-    }
-
-    throw doris::Exception(ErrorCode::OUT_OF_BOUND, "Index ({}) for getting 
field is out of range",
-                           nrow);
+    return !root->data.is_null_at(nrow);
 }
 
 void ColumnObject::serialize_one_row_to_json_format(int64_t row_num, 
BufferWritable& output,
diff --git a/regression-test/data/variant_p0/test_sub_path_pruning.out 
b/regression-test/data/variant_p0/test_sub_path_pruning.out
index 0c78380b131..4cf36fbc57e 100644
Binary files a/regression-test/data/variant_p0/test_sub_path_pruning.out and 
b/regression-test/data/variant_p0/test_sub_path_pruning.out differ
diff --git a/regression-test/data/variant_p0/variant_hirachinal.out 
b/regression-test/data/variant_p0/variant_hirachinal.out
index 48ed2967326..c23fce09221 100644
Binary files a/regression-test/data/variant_p0/variant_hirachinal.out and 
b/regression-test/data/variant_p0/variant_hirachinal.out differ
diff --git a/regression-test/suites/variant_p0/variant_hirachinal.groovy 
b/regression-test/suites/variant_p0/variant_hirachinal.groovy
index 3336ca4d23f..54732efa789 100644
--- a/regression-test/suites/variant_p0/variant_hirachinal.groovy
+++ b/regression-test/suites/variant_p0/variant_hirachinal.groovy
@@ -39,6 +39,7 @@ suite("regression_test_variant_hirachinal", "variant_type"){
     order_qt_sql2 "select cast(v['c'] as string) from var_rs where k = -3 or k 
= -2 or k = 1 order by k, cast(v['c'] as text) limit 3"
 
 
+    table_name = "var_rs2" 
     sql "DROP TABLE IF EXISTS ${table_name}"
 
     sql """
@@ -70,7 +71,19 @@ suite("regression_test_variant_hirachinal", "variant_type"){
 
     sql """insert into ${table_name} values (1, '{"a": 1, "b": 2, "c" : {"d" : 
2}}'), (2, '{"a": 3, "b": 4}');"""
     sql """insert into ${table_name} values (3, '{"c": {"d": 6}}');"""
+    sql """insert into ${table_name} values (4, NULL);"""
+    sql """insert into ${table_name} values (5, '{}');"""
 
     qt_sql """select v['c'] from ${table_name} order by k;"""
+    qt_sql """select v from ${table_name} order by k;"""
+
+    sql "DROP TABLE IF EXISTS t"
+    sql """create table t(a int, v variant, vn variant not null) PROPERTIES 
("replication_allocation" = "tag.location.default: 1");"""
+    sql """insert into t values(1, '{}', '{}');"""
+    sql """insert into t values(2, '{}', '{}');"""
+    sql """insert into t values(3, NULL, '{"a" : 1, "b" : 2, "c" : 3, "d" : 
4}');"""
+    qt_sql """select * from t order by a;"""
+    qt_sql """select * from t where v is null;"""
+
 
 }
\ No newline at end of file


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

Reply via email to