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 e4131f9aee6 branch-3.1: [fix](variant)fix variant type conflicts in
nested types #52696 (#53119)
e4131f9aee6 is described below
commit e4131f9aee61e5cd8e96abf05a6b95cb51cab1f2
Author: amory <[email protected]>
AuthorDate: Fri Jul 25 17:36:39 2025 +0800
branch-3.1: [fix](variant)fix variant type conflicts in nested types #52696
(#53119)
picked from #52696
---
be/src/vec/columns/column_object.cpp | 6 -
be/src/vec/columns/column_object.h | 1 +
be/src/vec/data_types/convert_field_to_type.cpp | 2 +-
be/src/vec/json/json_parser.cpp | 12 +-
be/src/vec/json/json_parser.h | 2 +
.../vec/columns/column_variant_allocated_bytes.out | Bin 53 -> 49 bytes
.../vec/columns/column_variant_byte_size.out | Bin 49 -> 41 bytes
.../column_variant_update_crc_with_value.out | Bin 53 -> 52 bytes
..._variant_update_crc_with_value_with_nullmap.out | Bin 53 -> 52 bytes
.../column_variant_update_crcs_with_value.out | Bin 21667 -> 195 bytes
...variant_update_crcs_with_value_with_nullmap.out | Bin 21667 -> 195 bytes
.../column_variant_update_hashes_with_value.out | Bin 41224 -> 330 bytes
...riant_update_hashes_with_value_with_nullmap.out | Bin 41224 -> 330 bytes
.../column_variant_update_xxHash_with_value.out | Bin 63 -> 62 bytes
...riant_update_xxHash_with_value_with_nullmap.out | Bin 63 -> 62 bytes
be/test/vec/columns/column_object_test.cpp | 15 +-
be/test/vec/columns/column_variant_test.cpp | 60 +--
.../function/cast/function_variant_cast_test.cpp | 466 ++++++++++++++++++
be/test/vec/jsonb/convert_field_to_type_test.cpp | 521 +++++++++++++++++++++
be/test/vec/jsonb/json_parser_test.cpp | 169 +++++++
regression-test/data/variant_p0/desc.out | Bin 5887 -> 5908 bytes
regression-test/data/variant_p0/load.out | Bin 16310 -> 16319 bytes
regression-test/data/variant_p0/nested2.out | Bin 0 -> 3467 bytes
regression-test/suites/variant_p0/nested2.groovy | 151 ++++++
24 files changed, 1344 insertions(+), 61 deletions(-)
diff --git a/be/src/vec/columns/column_object.cpp
b/be/src/vec/columns/column_object.cpp
index 01bddae0016..f825be48f80 100644
--- a/be/src/vec/columns/column_object.cpp
+++ b/be/src/vec/columns/column_object.cpp
@@ -87,12 +87,6 @@ DataTypePtr create_array_of_type(TypeIndex type, size_t
num_dimensions, bool is_
if (type == TypeIndex::Nothing) {
return std::make_shared<DataTypeNothing>();
}
- if (type == ColumnObject::MOST_COMMON_TYPE_ID) {
- // JSONB type MUST NOT wrapped in ARRAY column, it should be top level.
- // So we ignored num_dimensions.
- return is_nullable ?
make_nullable(std::make_shared<ColumnObject::MostCommonType>())
- : std::make_shared<ColumnObject::MostCommonType>();
- }
DataTypePtr result =
DataTypeFactory::instance().create_data_type(type, is_nullable,
precision, scale);
for (size_t i = 0; i < num_dimensions; ++i) {
diff --git a/be/src/vec/columns/column_object.h
b/be/src/vec/columns/column_object.h
index 971537c6155..319e5569e95 100644
--- a/be/src/vec/columns/column_object.h
+++ b/be/src/vec/columns/column_object.h
@@ -114,6 +114,7 @@ public:
constexpr static TypeIndex MOST_COMMON_TYPE_ID = TypeIndex::JSONB;
// Nullable(Array(Nullable(Object)))
const static DataTypePtr NESTED_TYPE;
+
// Finlize mode for subcolumns, write mode will estimate which subcolumns
are sparse columns(too many null values inside column),
// merge and encode them into a shared column in root column. Only affects
in flush block to segments.
// Otherwise read mode should be as default mode.
diff --git a/be/src/vec/data_types/convert_field_to_type.cpp
b/be/src/vec/data_types/convert_field_to_type.cpp
index ca8984b313d..5a50b446b9b 100644
--- a/be/src/vec/data_types/convert_field_to_type.cpp
+++ b/be/src/vec/data_types/convert_field_to_type.cpp
@@ -335,4 +335,4 @@ void convert_field_to_type(const Field& from_value, const
IDataType& to_type, Fi
return convert_field_to_typeImpl(from_value, to_type, from_type_hint,
to);
}
}
-} // namespace doris::vectorized
\ No newline at end of file
+} // namespace doris::vectorized
diff --git a/be/src/vec/json/json_parser.cpp b/be/src/vec/json/json_parser.cpp
index f6e8a65cc08..e031f168820 100644
--- a/be/src/vec/json/json_parser.cpp
+++ b/be/src/vec/json/json_parser.cpp
@@ -59,8 +59,14 @@ void JSONDataParser<ParserImpl>::traverse(const Element&
element, ParseContext&
if (element.isObject()) {
traverseObject(element.getObject(), ctx);
} else if (element.isArray()) {
+ if (ctx.has_nested_in_flatten) {
+ throw doris::Exception(doris::ErrorCode::INVALID_ARGUMENT,
+ "Nesting of array in Nested array within
variant subcolumns is "
+ "currently not supported.");
+ }
has_nested = false;
check_has_nested_object(element);
+ ctx.has_nested_in_flatten = has_nested && ctx.enable_flatten_nested;
if (has_nested && !ctx.enable_flatten_nested) {
// Parse nested arrays to JsonbField
JsonbWriter writer;
@@ -71,6 +77,8 @@ void JSONDataParser<ParserImpl>::traverse(const Element&
element, ParseContext&
} else {
traverseArray(element.getArray(), ctx);
}
+ // we should set has_nested_in_flatten to false when traverse array
finished for next array otherwise it will be true for next array
+ ctx.has_nested_in_flatten = false;
} else {
ctx.paths.push_back(ctx.builder.get_parts());
ctx.values.push_back(getValueAsField(element));
@@ -137,6 +145,7 @@ template <typename ParserImpl>
void JSONDataParser<ParserImpl>::traverseArray(const JSONArray& array,
ParseContext& ctx) {
/// Traverse elements of array and collect an array of fields by each path.
ParseArrayContext array_ctx;
+ array_ctx.has_nested_in_flatten = ctx.has_nested_in_flatten;
array_ctx.total_size = array.size();
for (auto it = array.begin(); it != array.end(); ++it) {
traverseArrayElement(*it, array_ctx);
@@ -162,8 +171,9 @@ template <typename ParserImpl>
void JSONDataParser<ParserImpl>::traverseArrayElement(const Element& element,
ParseArrayContext& ctx) {
ParseContext element_ctx;
+ element_ctx.has_nested_in_flatten = ctx.has_nested_in_flatten;
traverse(element, element_ctx);
- auto& [_, paths, values, flatten_nested] = element_ctx;
+ auto& [_, paths, values, flatten_nested, has_nested] = element_ctx;
size_t size = paths.size();
size_t keys_to_update = ctx.arrays_by_path.size();
for (size_t i = 0; i < size; ++i) {
diff --git a/be/src/vec/json/json_parser.h b/be/src/vec/json/json_parser.h
index 52336d455c0..8d47e1936fd 100644
--- a/be/src/vec/json/json_parser.h
+++ b/be/src/vec/json/json_parser.h
@@ -145,6 +145,7 @@ private:
std::vector<PathInData::Parts> paths;
std::vector<Field> values;
bool enable_flatten_nested = false;
+ bool has_nested_in_flatten = false;
};
using PathPartsWithArray = std::pair<PathInData::Parts, Array>;
using PathToArray = phmap::flat_hash_map<UInt128, PathPartsWithArray,
UInt128TrivialHash>;
@@ -154,6 +155,7 @@ private:
size_t total_size = 0;
PathToArray arrays_by_path;
KeyToSizes nested_sizes_by_key;
+ bool has_nested_in_flatten = false;
};
void traverse(const Element& element, ParseContext& ctx);
void traverseObject(const JSONObject& object, ParseContext& ctx);
diff --git
a/be/test/expected_result/vec/columns/column_variant_allocated_bytes.out
b/be/test/expected_result/vec/columns/column_variant_allocated_bytes.out
index 346c07705ac..3aec885b6b8 100644
Binary files
a/be/test/expected_result/vec/columns/column_variant_allocated_bytes.out and
b/be/test/expected_result/vec/columns/column_variant_allocated_bytes.out differ
diff --git a/be/test/expected_result/vec/columns/column_variant_byte_size.out
b/be/test/expected_result/vec/columns/column_variant_byte_size.out
index 54e7a490f26..85e0a0b6fb5 100644
Binary files a/be/test/expected_result/vec/columns/column_variant_byte_size.out
and b/be/test/expected_result/vec/columns/column_variant_byte_size.out differ
diff --git
a/be/test/expected_result/vec/columns/column_variant_update_crc_with_value.out
b/be/test/expected_result/vec/columns/column_variant_update_crc_with_value.out
index b2e93e82ac0..26218ea29a0 100644
Binary files
a/be/test/expected_result/vec/columns/column_variant_update_crc_with_value.out
and
b/be/test/expected_result/vec/columns/column_variant_update_crc_with_value.out
differ
diff --git
a/be/test/expected_result/vec/columns/column_variant_update_crc_with_value_with_nullmap.out
b/be/test/expected_result/vec/columns/column_variant_update_crc_with_value_with_nullmap.out
index b2e93e82ac0..26218ea29a0 100644
Binary files
a/be/test/expected_result/vec/columns/column_variant_update_crc_with_value_with_nullmap.out
and
b/be/test/expected_result/vec/columns/column_variant_update_crc_with_value_with_nullmap.out
differ
diff --git
a/be/test/expected_result/vec/columns/column_variant_update_crcs_with_value.out
b/be/test/expected_result/vec/columns/column_variant_update_crcs_with_value.out
index 6a347c4861a..18beb2922b8 100644
Binary files
a/be/test/expected_result/vec/columns/column_variant_update_crcs_with_value.out
and
b/be/test/expected_result/vec/columns/column_variant_update_crcs_with_value.out
differ
diff --git
a/be/test/expected_result/vec/columns/column_variant_update_crcs_with_value_with_nullmap.out
b/be/test/expected_result/vec/columns/column_variant_update_crcs_with_value_with_nullmap.out
index 6a347c4861a..18beb2922b8 100644
Binary files
a/be/test/expected_result/vec/columns/column_variant_update_crcs_with_value_with_nullmap.out
and
b/be/test/expected_result/vec/columns/column_variant_update_crcs_with_value_with_nullmap.out
differ
diff --git
a/be/test/expected_result/vec/columns/column_variant_update_hashes_with_value.out
b/be/test/expected_result/vec/columns/column_variant_update_hashes_with_value.out
index 94ed6fb2fd6..1675df9272e 100644
Binary files
a/be/test/expected_result/vec/columns/column_variant_update_hashes_with_value.out
and
b/be/test/expected_result/vec/columns/column_variant_update_hashes_with_value.out
differ
diff --git
a/be/test/expected_result/vec/columns/column_variant_update_hashes_with_value_with_nullmap.out
b/be/test/expected_result/vec/columns/column_variant_update_hashes_with_value_with_nullmap.out
index 94ed6fb2fd6..1675df9272e 100644
Binary files
a/be/test/expected_result/vec/columns/column_variant_update_hashes_with_value_with_nullmap.out
and
b/be/test/expected_result/vec/columns/column_variant_update_hashes_with_value_with_nullmap.out
differ
diff --git
a/be/test/expected_result/vec/columns/column_variant_update_xxHash_with_value.out
b/be/test/expected_result/vec/columns/column_variant_update_xxHash_with_value.out
index 80c79e48777..a47b1bf2981 100644
Binary files
a/be/test/expected_result/vec/columns/column_variant_update_xxHash_with_value.out
and
b/be/test/expected_result/vec/columns/column_variant_update_xxHash_with_value.out
differ
diff --git
a/be/test/expected_result/vec/columns/column_variant_update_xxHash_with_value_with_nullmap.out
b/be/test/expected_result/vec/columns/column_variant_update_xxHash_with_value_with_nullmap.out
index 80c79e48777..a47b1bf2981 100644
Binary files
a/be/test/expected_result/vec/columns/column_variant_update_xxHash_with_value_with_nullmap.out
and
b/be/test/expected_result/vec/columns/column_variant_update_xxHash_with_value_with_nullmap.out
differ
diff --git a/be/test/vec/columns/column_object_test.cpp
b/be/test/vec/columns/column_object_test.cpp
index 7d10b5e77ef..25a41722f95 100644
--- a/be/test/vec/columns/column_object_test.cpp
+++ b/be/test/vec/columns/column_object_test.cpp
@@ -634,10 +634,17 @@ TEST(ColumnVariantTest, advanced_insert_range_from) {
}
} else if (column->path.get_path().size() == 5) {
EXPECT_EQ(column->data.get_non_null_value_size(), 10);
- EXPECT_EQ(assert_cast<const
DataTypeNullable*>(column->data.data_types[0].get())
- ->get_nested_type()
- ->get_type_id(),
- TypeIndex::JSONB);
+ if (column->path.get_path() == "v.d.d") {
+ EXPECT_EQ(assert_cast<const
DataTypeNullable*>(column->data.data_types[0].get())
+ ->get_nested_type()
+ ->get_type_id(),
+ TypeIndex::Array);
+ } else if (column->path.get_path() == "v.c.d") {
+ EXPECT_EQ(assert_cast<const
DataTypeNullable*>(column->data.data_types[0].get())
+ ->get_nested_type()
+ ->get_type_id(),
+ TypeIndex::JSONB);
+ }
for (size_t row = 0; row < 5; ++row) {
EXPECT_TRUE(column->data.data[0]->is_null_at(row));
}
diff --git a/be/test/vec/columns/column_variant_test.cpp
b/be/test/vec/columns/column_variant_test.cpp
index a7fba428b37..d6f35a50bb1 100644
--- a/be/test/vec/columns/column_variant_test.cpp
+++ b/be/test/vec/columns/column_variant_test.cpp
@@ -49,15 +49,14 @@ static ColumnObject::MutablePtr column_variant;
class ColumnObjectTest : public CommonColumnTest {
protected:
static void SetUpTestSuite() {
- root_dir = std::string(getenv("ROOT"));
+ column_variant = VariantUtil::construct_advanced_varint_column();
+ std::cout << column_variant->get_name() << std::endl;
+ root_dir = std::string(getenv("DORIS_HOME"));
+ // which is /root/doris/be/ut_build_ASAN/test//
std::cout << "root_dir: " << root_dir << std::endl;
- test_data_dir = root_dir + "/be/test/data/vec/columns";
- test_result_dir = root_dir + "/be/test/expected_result/vec/columns";
-
- column_variant = ColumnObject::create(true);
- std::cout << dt_variant->get_name() << std::endl;
-
- load_json_columns_data();
+ test_data_dir = root_dir + "../../../be/test/data/vec/columns";
+ test_result_dir = root_dir +
"../../../be/test/expected_result/vec/columns";
+ //load_json_columns_data();
}
static void load_json_columns_data() {
@@ -220,11 +219,7 @@ TEST_F(ColumnObjectTest, field_test) {
}
};
ColumnObject::MutablePtr obj;
- obj = ColumnObject::create(1);
- MutableColumns cols;
- cols.push_back(obj->get_ptr());
- const auto& json_file_obj = test_data_dir_json +
"json_variant/object_boundary.jsonl";
- load_columns_data_from_file(cols, serde, '\n', {0}, json_file_obj);
+ obj = VariantUtil::construct_advanced_varint_column();
EXPECT_TRUE(!obj->empty());
test_func(obj);
}
@@ -750,11 +745,7 @@ TEST_F(ColumnObjectTest, get_subcolumn) {
TEST_F(ColumnObjectTest, ensure_root_node_type) {
ColumnObject::MutablePtr obj;
- obj = ColumnObject::create(1);
- MutableColumns cols;
- cols.push_back(obj->get_ptr());
- const auto& json_file_obj = test_data_dir_json +
"json_variant/object_boundary.jsonl";
- load_columns_data_from_file(cols, serde, '\n', {0}, json_file_obj);
+ obj = VariantUtil::construct_advanced_varint_column();
EXPECT_TRUE(!obj->empty());
// Store original root type
auto root = obj->get_subcolumns().get_root();
@@ -1188,11 +1179,7 @@ TEST_F(ColumnObjectTest,
find_path_lower_bound_in_sparse_data) {
}
};
ColumnObject::MutablePtr obj;
- obj = ColumnObject::create(1);
- MutableColumns cols;
- cols.push_back(obj->get_ptr());
- const auto& json_file_obj = test_data_dir_json +
"json_variant/object_boundary.jsonl";
- load_columns_data_from_file(cols, serde, '\n', {0}, json_file_obj);
+ obj = VariantUtil::construct_advanced_varint_column();
EXPECT_TRUE(!obj->empty());
std::cout << "column variant size: " << obj->size() << std::endl;
test_func(obj);
@@ -1201,11 +1188,7 @@ TEST_F(ColumnObjectTest,
find_path_lower_bound_in_sparse_data) {
// used in SparseColumnExtractIterator::_fill_path_column
TEST_F(ColumnObjectTest, fill_path_column_from_sparse_data) {
ColumnObject::MutablePtr obj;
- obj = ColumnObject::create(1);
- MutableColumns cols;
- cols.push_back(obj->get_ptr());
- const auto& json_file_obj = test_data_dir_json +
"json_variant/object_boundary.jsonl";
- load_columns_data_from_file(cols, serde, '\n', {0}, json_file_obj);
+ obj = VariantUtil::construct_advanced_varint_column();
EXPECT_TRUE(!obj->empty());
auto sparse_col = obj->get_sparse_column();
auto cloned_sparse = sparse_col->clone_empty();
@@ -1226,27 +1209,6 @@ TEST_F(ColumnObjectTest,
fill_path_column_from_sparse_data) {
EXPECT_ANY_THROW(obj->check_consistency());
}
-TEST_F(ColumnObjectTest, not_finalized) {
- ColumnObject::MutablePtr obj;
- obj = ColumnObject::create(1);
- MutableColumns cols;
- cols.push_back(obj->get_ptr());
- const auto& json_file_obj = test_data_dir_json +
"json_variant/object_boundary.jsonl";
- load_columns_data_from_file(cols, serde, '\n', {0}, json_file_obj);
- const auto& json_file_arr = test_data_dir_json +
"json_variant/array_object_boundary.jsonl";
- load_columns_data_from_file(cols, serde, '\n', {0}, json_file_arr);
- EXPECT_TRUE(obj->size() == 200);
- EXPECT_FALSE(obj->is_finalized());
- // test get_finalized_column_ptr/ get_finalized_column for subColumn
- auto subcolumns = obj->get_subcolumns();
- for (const auto& subcolumn : subcolumns) {
- EXPECT_TRUE(subcolumn != nullptr);
- EXPECT_FALSE(subcolumn->data.is_finalized());
- EXPECT_ANY_THROW(subcolumn->data.get_finalized_column_ptr());
- EXPECT_ANY_THROW(subcolumn->data.get_finalized_column());
- }
-}
-
doris::vectorized::Field get_field_v2(std::string_view type, size_t
array_element_cnt = 0) {
static std::unordered_map<std::string_view, doris::vectorized::Field>
field_map;
if (field_map.empty()) {
diff --git a/be/test/vec/function/cast/function_variant_cast_test.cpp
b/be/test/vec/function/cast/function_variant_cast_test.cpp
new file mode 100644
index 00000000000..aa026d4a155
--- /dev/null
+++ b/be/test/vec/function/cast/function_variant_cast_test.cpp
@@ -0,0 +1,466 @@
+// 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 <vector>
+
+#include "common/status.h"
+#include "gtest/gtest_pred_impl.h"
+#include "olap/field.h"
+#include "runtime/define_primitive_type.h"
+#include "runtime/primitive_type.h"
+#include "runtime/runtime_state.h"
+#include "vec/columns/column_array.h"
+#include "vec/columns/column_object.h"
+#include "vec/core/field.h"
+#include "vec/data_types/data_type_array.h"
+#include "vec/data_types/data_type_nullable.h"
+#include "vec/data_types/data_type_number.h"
+#include "vec/data_types/data_type_object.h"
+#include "vec/data_types/data_type_string.h"
+#include "vec/functions/simple_function_factory.h"
+
+namespace doris::vectorized {
+static doris::vectorized::Field construct_variant_map(
+ const std::vector<std::pair<std::string, doris::vectorized::Field>>&
key_and_values) {
+ doris::vectorized::Field res = VariantMap();
+ auto& object = res.get<VariantMap&>();
+ for (const auto& [k, v] : key_and_values) {
+ PathInData path(k);
+ object.try_emplace(path, v);
+ }
+ return res;
+}
+
+static auto construct_basic_varint_column() {
+ // 1. create an empty variant column
+ auto variant = ColumnObject::create(5);
+
+ std::vector<std::pair<std::string, doris::vectorized::Field>> data;
+
+ // 2. subcolumn path
+ data.emplace_back("v.a", 20);
+ data.emplace_back("v.b", "20");
+ data.emplace_back("v.c", 20);
+ data.emplace_back("v.f", 20);
+ data.emplace_back("v.e", "50");
+ for (int i = 0; i < 5; ++i) {
+ auto field = construct_variant_map(data);
+ variant->try_insert(field);
+ }
+
+ return variant;
+}
+
+TEST(FunctionVariantCast, CastToVariant) {
+ // Test casting from basic types to variant
+ {
+ // Test Int32 to variant
+ auto int32_type = std::make_shared<DataTypeInt32>();
+ auto variant_type = std::make_shared<DataTypeObject>();
+ auto int32_col = ColumnInt32::create();
+ int32_col->insert(42);
+ int32_col->insert(100);
+ int32_col->insert(-1);
+
+ ColumnsWithTypeAndName arguments {{int32_col->get_ptr(), int32_type,
"int32_col"},
+ {nullptr, variant_type,
"variant_type"}};
+
+ auto function =
+ SimpleFunctionFactory::instance().get_function("CAST",
arguments, variant_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, variant_type, "result"});
+
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
3).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ const auto* variant_col = assert_cast<const
ColumnObject*>(result_col.get());
+ ASSERT_EQ(variant_col->size(), 3);
+ }
+
+ // Test casting from string to variant
+ {
+ auto string_type = std::make_shared<DataTypeString>();
+ auto variant_type = std::make_shared<DataTypeObject>();
+ auto string_col = ColumnString::create();
+ string_col->insert_data("hello", 5);
+ string_col->insert_data("world", 5);
+
+ ColumnsWithTypeAndName arguments {{string_col->get_ptr(), string_type,
"string_col"},
+ {nullptr, variant_type,
"variant_type"}};
+
+ auto function = SimpleFunctionFactory::instance().get_function("CAST",
arguments,
+
make_nullable(variant_type));
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, variant_type, "result"});
+
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
2).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ const auto* variant_col =
+ assert_cast<const
ColumnObject*>(remove_nullable(result_col).get());
+ ASSERT_EQ(variant_col->size(), 2);
+ }
+
+ // Test casting from array to variant
+ {
+ auto array_type =
std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>());
+ auto variant_type = std::make_shared<DataTypeObject>();
+ auto array_col =
+ ColumnArray::create(ColumnInt32::create(),
ColumnArray::ColumnOffsets::create());
+ auto& data = assert_cast<ColumnInt32&>(array_col->get_data());
+ auto& offsets = array_col->get_offsets();
+
+ data.insert(1);
+ data.insert(2);
+ data.insert(3);
+ offsets.push_back(3);
+
+ ColumnsWithTypeAndName arguments {{array_col->get_ptr(), array_type,
"array_col"},
+ {nullptr, variant_type,
"variant_type"}};
+
+ auto function =
+ SimpleFunctionFactory::instance().get_function("CAST",
arguments, variant_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, variant_type, "result"});
+
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
1).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ const auto* variant_col =
+ assert_cast<const
ColumnObject*>(remove_nullable(result_col).get());
+ ASSERT_EQ(variant_col->size(), 1);
+ }
+}
+
+TEST(FunctionVariantCast, CastFromVariant) {
+ // Test casting from variant to basic types
+ {
+ auto variant_type = std::make_shared<DataTypeObject>();
+ auto int32_type = std::make_shared<DataTypeInt32>();
+ auto variant_col = ColumnObject::create(true);
+
+ // Create a variant column with integer values
+ variant_col->create_root(int32_type, ColumnInt32::create());
+ MutableColumnPtr data = variant_col->get_root();
+ data->insert(42);
+ data->insert(100);
+ data->insert(-1);
+
+ ColumnsWithTypeAndName arguments {{variant_col->get_ptr(),
variant_type, "variant_col"},
+ {nullptr, int32_type, "int32_type"}};
+
+ auto function =
+ SimpleFunctionFactory::instance().get_function("CAST",
arguments, int32_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, int32_type, "result"});
+
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
3).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ // always nullable
+ const auto* int32_result =
+ assert_cast<const
ColumnInt32*>(remove_nullable(result_col).get());
+ ASSERT_EQ(int32_result->size(), 3);
+ ASSERT_EQ(int32_result->get_element(0), 42);
+ ASSERT_EQ(int32_result->get_element(1), 100);
+ ASSERT_EQ(int32_result->get_element(2), -1);
+ }
+
+ // Test casting from variant to string
+ {
+ auto variant_type = std::make_shared<DataTypeObject>();
+ auto string_type = std::make_shared<DataTypeString>();
+ auto variant_col = ColumnObject::create(true);
+
+ // Create a variant column with string values
+ variant_col->create_root(string_type, ColumnString::create());
+ MutableColumnPtr data = variant_col->get_root();
+ data->insert_data("hello", 5);
+ data->insert_data("world", 5);
+
+ ColumnsWithTypeAndName arguments {{variant_col->get_ptr(),
variant_type, "variant_col"},
+ {nullptr, string_type,
"string_type"}};
+
+ auto function =
+ SimpleFunctionFactory::instance().get_function("CAST",
arguments, string_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, string_type, "result"});
+
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
2).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ const auto* string_result =
+ assert_cast<const
ColumnString*>(remove_nullable(result_col).get());
+ ASSERT_EQ(string_result->size(), 2);
+ ASSERT_EQ(string_result->get_data_at(0).to_string(), "hello");
+ ASSERT_EQ(string_result->get_data_at(1).to_string(), "world");
+ }
+
+ // Test casting from variant to array
+ {
+ auto variant_type = std::make_shared<DataTypeObject>();
+ auto array_type =
std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>());
+ auto variant_col = ColumnObject::create(true);
+
+ // Create a variant column with array values
+ variant_col->create_root(
+ array_type,
+ ColumnArray::create(ColumnInt32::create(),
ColumnArray::ColumnOffsets::create()));
+ MutableColumnPtr data = variant_col->get_root();
+
+ Field a = Array {1, 2, 3};
+
+ data->insert(a);
+
+ ColumnsWithTypeAndName arguments {{variant_col->get_ptr(),
variant_type, "variant_col"},
+ {nullptr, array_type, "array_type"}};
+
+ auto function =
+ SimpleFunctionFactory::instance().get_function("CAST",
arguments, array_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, array_type, "result"});
+
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
1).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ const auto* array_result =
+ assert_cast<const
ColumnArray*>(remove_nullable(result_col).get());
+ ASSERT_EQ(array_result->size(), 1);
+ const auto& result_data = assert_cast<const
ColumnInt32&>(array_result->get_data());
+ ASSERT_EQ(result_data.size(), 3);
+ ASSERT_EQ(result_data.get_element(0), 1);
+ ASSERT_EQ(result_data.get_element(1), 2);
+ ASSERT_EQ(result_data.get_element(2), 3);
+ }
+}
+
+TEST(FunctionVariantCast, CastVariantWithNull) {
+ auto variant_type = std::make_shared<DataTypeObject>();
+ auto int32_type = std::make_shared<DataTypeInt32>();
+ auto nullable_int32_type = std::make_shared<DataTypeNullable>(int32_type);
+
+ // Create a variant column with nullable integer values
+ auto variant_col = ColumnObject::create(true);
+ variant_col->create_root(nullable_int32_type,
+ ColumnNullable::create(ColumnInt32::create(),
ColumnUInt8::create()));
+ MutableColumnPtr data = variant_col->get_root();
+
+ data->insert(42);
+ data->insert(Null());
+ data->insert(100);
+
+ ColumnsWithTypeAndName arguments {{variant_col->get_ptr(), variant_type,
"variant_col"},
+ {nullptr, nullable_int32_type,
"nullable_int32_type"}};
+
+ variant_col->finalize();
+ auto function =
+ SimpleFunctionFactory::instance().get_function("CAST", arguments,
nullable_int32_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, nullable_int32_type, "result"});
+
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
3).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ const auto* nullable_result = assert_cast<const
ColumnNullable*>(result_col.get());
+ ASSERT_EQ(nullable_result->size(), 3);
+
+ const auto& result_data = assert_cast<const
ColumnInt32&>(nullable_result->get_nested_column());
+ const auto& result_null_map = nullable_result->get_null_map_data();
+
+ ASSERT_EQ(result_data.get_element(0), 42);
+ ASSERT_EQ(result_null_map[0], 0);
+ ASSERT_EQ(result_null_map[1], 1);
+ ASSERT_EQ(result_data.get_element(2), 100);
+}
+
+TEST(FunctionVariantCast, CastFromVariantWithEmptyRoot) {
+ // Test case 1: variant.empty() branch
+ {
+ auto variant_type = std::make_shared<DataTypeObject>();
+ auto int32_type = std::make_shared<DataTypeInt32>();
+ MutableColumnPtr root = ColumnInt32::create();
+ root->insert(42);
+ vectorized::ColumnObject::Subcolumns dynamic_subcolumns;
+ dynamic_subcolumns.add(
+ vectorized::PathInData(ColumnObject::COLUMN_NAME_DUMMY),
+ vectorized::ColumnObject::Subcolumn {root->get_ptr(),
int32_type, true, true});
+ auto variant_col = ColumnObject::create(0,
std::move(dynamic_subcolumns));
+
+ variant_col->finalize();
+ ColumnsWithTypeAndName arguments {{variant_col->get_ptr(),
variant_type, "variant_col"},
+ {nullptr, int32_type, "int32_type"}};
+
+ auto function =
+ SimpleFunctionFactory::instance().get_function("CAST",
arguments, int32_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, int32_type, "result"});
+
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
1).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ // always nullable
+ const auto* int32_result =
+ assert_cast<const
ColumnInt32*>(remove_nullable(result_col).get());
+ ASSERT_EQ(int32_result->size(), 1);
+ // because of variant.empty() we insert_default with data_type_to
+ ASSERT_EQ(int32_result->get_element(0), 0);
+ }
+
+ // Test case 2: !data_type_to->is_nullable() &&
!WhichDataType(data_type_to).is_string() branch
+ {
+ // object has sparse column
+ auto int32_type = std::make_shared<DataTypeInt32>();
+ auto variant_col = construct_basic_varint_column();
+ auto variant_type = std::make_shared<DataTypeObject>();
+
+ ColumnsWithTypeAndName arguments {{variant_col->get_ptr(),
variant_type, "variant_col"},
+ {nullptr, int32_type, "int32_type"}};
+
+ variant_col->finalize();
+ auto function =
+ SimpleFunctionFactory::instance().get_function("CAST",
arguments, int32_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, int32_type, "result"});
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
1).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ const auto* nullable_result = assert_cast<const
ColumnNullable*>(result_col.get());
+ ASSERT_EQ(nullable_result->size(), 1);
+ ASSERT_TRUE(nullable_result->is_null_at(0));
+ }
+
+ // Test case 3: WhichDataType(data_type_to).is_string() branch
+ {
+ // variant has sparse column
+ auto int32_type = std::make_shared<DataTypeInt32>();
+ auto variant_col = construct_basic_varint_column();
+
+ auto string_type = std::make_shared<DataTypeString>();
+ auto variant_type = std::make_shared<DataTypeObject>();
+
+ ColumnsWithTypeAndName arguments {{variant_col->get_ptr(),
variant_type, "variant_col"},
+ {nullptr, string_type,
"string_type"}};
+
+ variant_col->finalize();
+ auto function =
+ SimpleFunctionFactory::instance().get_function("CAST",
arguments, string_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, string_type, "result"});
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
1).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ const auto* string_result = assert_cast<const
ColumnString*>(result_col.get());
+ // just call ConvertImplGenericToString which will insert all source
column data to ColumnString
+ ASSERT_EQ(string_result->size(), variant_col->size());
+ ASSERT_EQ(string_result->get_data_at(0).to_string(),
+
"{\"v\":{\"a\":20,\"b\":\"20\",\"c\":20,\"e\":\"50\",\"f\":20}}");
+ }
+
+ // Test case 4: else branch (nullable type)
+ {
+ auto variant_col = construct_basic_varint_column();
+ variant_col->finalize();
+ auto nullable_variant_col = make_nullable(variant_col->get_ptr());
+
+ auto nullable_string_type =
make_nullable(std::make_shared<DataTypeString>());
+ auto variant_type = std::make_shared<DataTypeObject>();
+ auto nullable_variant_type = make_nullable(variant_type);
+
+ ColumnsWithTypeAndName arguments {
+ {nullable_variant_col->get_ptr(), nullable_variant_type,
"variant_col"},
+ {nullptr, nullable_string_type, "nullable_string_type"}};
+
+ auto function = SimpleFunctionFactory::instance().get_function("CAST",
arguments,
+
nullable_string_type);
+ ASSERT_NE(function, nullptr);
+
+ Block block {arguments};
+ size_t result_column = block.columns();
+ block.insert({nullptr, nullable_string_type, "result"});
+ RuntimeState state;
+ auto ctx = FunctionContext::create_context(&state, {}, {});
+ ASSERT_TRUE(function->execute(ctx.get(), block, {0}, result_column,
1).ok());
+
+ auto result_col = block.get_by_position(result_column).column;
+ ASSERT_NE(result_col.get(), nullptr);
+ const auto* nullable_result = assert_cast<const
ColumnNullable*>(result_col.get());
+ ASSERT_EQ(nullable_result->size(), 1);
+ ASSERT_TRUE(nullable_result->is_null_at(1));
+ }
+}
+
+} // namespace doris::vectorized
diff --git a/be/test/vec/jsonb/convert_field_to_type_test.cpp
b/be/test/vec/jsonb/convert_field_to_type_test.cpp
new file mode 100644
index 00000000000..065d86c039c
--- /dev/null
+++ b/be/test/vec/jsonb/convert_field_to_type_test.cpp
@@ -0,0 +1,521 @@
+// 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 "vec/data_types/convert_field_to_type.cpp"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string>
+
+#include "runtime/jsonb_value.h"
+#include "util/jsonb_document.h"
+#include "util/jsonb_writer.h"
+#include "vec/core/field.h"
+#include "vec/core/types.h"
+#include "vec/data_types/data_type.h"
+#include "vec/data_types/data_type_array.h"
+#include "vec/data_types/data_type_jsonb.h"
+#include "vec/data_types/data_type_nullable.h"
+
+namespace doris::vectorized {
+
+class ConvertFieldToTypeTest : public ::testing::Test {
+protected:
+ void SetUp() override {}
+};
+
+// Test FieldVisitorToJsonb with different field types using the same pattern
as convert_field_to_typeImpl
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_Null) {
+ JsonbWriter writer;
+
+ // Test null field using Field::dispatch pattern
+ Field null_field;
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ null_field);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's a null value
+ ASSERT_TRUE(doc->getValue()->isNull());
+}
+
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_Int64) {
+ JsonbWriter writer;
+
+ // Test Int64 field using Field::dispatch pattern
+ Int64 test_value = 12345;
+ Field int_field = test_value;
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ int_field);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's an integer value
+ ASSERT_TRUE(doc->getValue()->isInt64());
+ ASSERT_EQ(((const JsonbIntVal*)doc->getValue())->val(), test_value);
+}
+
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_UInt64) {
+ JsonbWriter writer;
+
+ // Test UInt64 field using Field::dispatch pattern
+ UInt64 test_value = 12345;
+ Field uint_field = test_value;
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ uint_field);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's an integer value
+ ASSERT_TRUE(doc->getValue()->isInt64());
+ ASSERT_EQ(((const JsonbIntVal*)doc->getValue())->val(),
static_cast<Int64>(test_value));
+}
+
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_Float64) {
+ JsonbWriter writer;
+
+ // Test Float64 field using Field::dispatch pattern
+ Float64 test_value = 123.456;
+ Field double_field = test_value;
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ double_field);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's a double value
+ ASSERT_TRUE(doc->getValue()->isDouble());
+ ASSERT_DOUBLE_EQ(((const JsonbDoubleVal*)doc->getValue())->val(),
test_value);
+}
+
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_String) {
+ JsonbWriter writer;
+
+ // Test String field using Field::dispatch pattern
+ Field string_field = "hello world";
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ string_field);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's a string value
+ ASSERT_TRUE(doc->getValue()->isString());
+ const auto* string_val = static_cast<const JsonbBlobVal*>(doc->getValue());
+ std::string real_string(string_val->getBlob(), string_val->getBlobLen());
+ ASSERT_EQ(real_string, string_field.get<String>());
+}
+
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_JsonbField) {
+ JsonbWriter writer;
+ JsonBinaryValue jsonb_value;
+ std::string test_data = R"({"a": ["1", "2"]})";
+ THROW_IF_ERROR(jsonb_value.from_json_string(test_data.data(),
test_data.size()));
+ Field jsonb_field_obj = JsonbField(jsonb_value.value(),
jsonb_value.size());
+
+ // Test JsonbField using Field::dispatch pattern
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ jsonb_field_obj);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's an object value
+ ASSERT_TRUE(doc->getValue()->isObject());
+}
+
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_Array) {
+ JsonbWriter writer;
+
+ // Create an array with mixed types
+ Array array_field;
+ array_field.push_back(123);
+ array_field.push_back("hello");
+ array_field.push_back(456.789);
+
+ Field array_obj = array_field;
+
+ // Test Array using Field::dispatch pattern
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ array_obj);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's an array value
+ ASSERT_TRUE(doc->getValue()->isArray());
+ const ArrayVal& array = static_cast<const ArrayVal&>(*doc->getValue());
+ ASSERT_EQ(array.numElem(), 3);
+}
+
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_NestedArray) {
+ JsonbWriter writer;
+
+ // Create a nested array
+ Array inner_array;
+ inner_array.push_back(1);
+ inner_array.push_back(2);
+
+ Array outer_array;
+ outer_array.push_back(inner_array);
+ outer_array.push_back("nested");
+
+ Field nested_array_obj = outer_array;
+
+ // Test nested Array using Field::dispatch pattern
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ nested_array_obj);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's an array value
+ ASSERT_TRUE(doc->getValue()->isArray());
+ const ArrayVal& array = static_cast<const ArrayVal&>(*doc->getValue());
+ ASSERT_EQ(array.numElem(), 2);
+}
+
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_LargeInt) {
+ JsonbWriter writer;
+
+ // Test Int128 field using Field::dispatch pattern
+ Int128 test_value = 1234567890123456789;
+ Field largeint_field = test_value;
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ largeint_field);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's an int128 value
+ ASSERT_TRUE(doc->getValue()->isInt128());
+ ASSERT_EQ(((const JsonbIntVal*)doc->getValue())->val(), test_value);
+}
+
+TEST_F(ConvertFieldToTypeTest, FieldVisitorToJsonb_UInt128) {
+ JsonbWriter writer;
+
+ // Test UInt128 field using Field::dispatch pattern
+ UInt128 test_value = 1234567890123456789;
+ Field uint128_field = test_value;
+ Field::dispatch([&writer](const auto& value) {
FieldVisitorToJsonb()(value, &writer); },
+ uint128_field);
+
+ auto* output = writer.getOutput();
+ ASSERT_NE(output, nullptr);
+ ASSERT_GT(output->getSize(), 0);
+
+ // Verify the output is valid JSONB
+ JsonbDocument* doc = nullptr;
+ auto status =
+ JsonbDocument::checkAndCreateDocument(output->getBuffer(),
output->getSize(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+
+ // Verify it's an int128 value
+ ASSERT_TRUE(doc->getValue()->isInt128());
+ ASSERT_EQ(((const JsonbIntVal*)doc->getValue())->val(),
static_cast<Int128>(test_value));
+}
+
+// Test convert_field_to_type function with JSONB type (similar to
convert_field_to_typeImpl)
+TEST_F(ConvertFieldToTypeTest, ConvertFieldToType_ToJsonb) {
+ DataTypeJsonb jsonb_type;
+
+ // Test converting Int64 to JSONB
+ {
+ Int64 test_value = 12345;
+ Field int_field = test_value;
+ Field result;
+
+ convert_field_to_type(int_field, jsonb_type, &result);
+
+ ASSERT_EQ(result.get_type(), Field::Types::JSONB);
+ ASSERT_FALSE(result.is_null());
+
+ const JsonbField& jsonb_result = result.get<JsonbField>();
+ ASSERT_NE(jsonb_result.get_value(), nullptr);
+ ASSERT_GT(jsonb_result.get_size(), 0);
+
+ // Verify the JSONB content
+ JsonbDocument* doc = nullptr;
+ auto status =
JsonbDocument::checkAndCreateDocument(jsonb_result.get_value(),
+
jsonb_result.get_size(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+ ASSERT_TRUE(doc->getValue()->isInt64());
+ ASSERT_EQ(((const JsonbIntVal*)doc->getValue())->val(), test_value);
+ }
+
+ // Test converting String to JSONB
+ {
+ Field string_field = "hello world";
+ Field result;
+
+ convert_field_to_type(string_field, jsonb_type, &result);
+
+ ASSERT_EQ(result.get_type(), Field::Types::JSONB);
+ ASSERT_FALSE(result.is_null());
+
+ const JsonbField& jsonb_result = result.get<JsonbField>();
+ ASSERT_NE(jsonb_result.get_value(), nullptr);
+ ASSERT_GT(jsonb_result.get_size(), 0);
+
+ // Verify the JSONB content
+ JsonbDocument* doc = nullptr;
+ auto status =
JsonbDocument::checkAndCreateDocument(jsonb_result.get_value(),
+
jsonb_result.get_size(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+ ASSERT_TRUE(doc->getValue()->isString());
+ const auto* string_val = static_cast<const
JsonbBlobVal*>(doc->getValue());
+ std::string real_string(string_val->getBlob(),
string_val->getBlobLen());
+ ASSERT_EQ(real_string, string_field.get<String>());
+ }
+
+ // Test converting Array to JSONB
+ {
+ Array array_field;
+ array_field.push_back(1);
+ array_field.push_back("test");
+ array_field.push_back(3.14);
+
+ Field array_obj = array_field;
+ Field result;
+
+ convert_field_to_type(array_obj, jsonb_type, &result);
+
+ ASSERT_EQ(result.get_type(), Field::Types::JSONB);
+ ASSERT_FALSE(result.is_null());
+
+ const JsonbField& jsonb_result = result.get<JsonbField>();
+ ASSERT_NE(jsonb_result.get_value(), nullptr);
+ ASSERT_GT(jsonb_result.get_size(), 0);
+
+ // Verify the JSONB content
+ JsonbDocument* doc = nullptr;
+ auto status =
JsonbDocument::checkAndCreateDocument(jsonb_result.get_value(),
+
jsonb_result.get_size(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+ ASSERT_TRUE(doc->getValue()->isArray());
+ const ArrayVal& array = static_cast<const ArrayVal&>(*doc->getValue());
+ ASSERT_EQ(array.numElem(), 3);
+ }
+
+ // Test converting JSONB to JSONB (should be no-op)
+ {
+ JsonbWriter test_writer;
+ test_writer.writeStartObject();
+ test_writer.writeKey("key");
+ test_writer.writeString("value");
+ test_writer.writeEndObject();
+
+ auto* test_output = test_writer.getOutput();
+ JsonbField original_jsonb(test_output->getBuffer(),
test_output->getSize());
+ Field jsonb_field = original_jsonb;
+ Field result;
+
+ convert_field_to_type(jsonb_field, jsonb_type, &result);
+
+ ASSERT_EQ(result.get_type(), Field::Types::JSONB);
+ ASSERT_FALSE(result.is_null());
+
+ const JsonbField& jsonb_result = result.get<JsonbField>();
+ ASSERT_NE(jsonb_result.get_value(), nullptr);
+ ASSERT_EQ(jsonb_result.get_size(), original_jsonb.get_size());
+ ASSERT_EQ(memcmp(jsonb_result.get_value(), original_jsonb.get_value(),
+ original_jsonb.get_size()),
+ 0);
+ }
+}
+
+// Test convert_field_to_type with nullable JSONB type
+TEST_F(ConvertFieldToTypeTest, ConvertFieldToType_ToNullableJsonb) {
+ auto nullable_jsonb_type =
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeJsonb>());
+
+ // Test converting null field
+ {
+ Field null_field;
+ Field result;
+
+ convert_field_to_type(null_field, *nullable_jsonb_type, &result);
+
+ ASSERT_TRUE(result.is_null());
+ }
+
+ // Test converting non-null field
+ {
+ Field string_field = "test string";
+ Field result;
+
+ convert_field_to_type(string_field, *nullable_jsonb_type, &result);
+
+ ASSERT_EQ(result.get_type(), Field::Types::JSONB);
+ ASSERT_FALSE(result.is_null());
+
+ const JsonbField& jsonb_result = result.get<JsonbField>();
+ ASSERT_NE(jsonb_result.get_value(), nullptr);
+ ASSERT_GT(jsonb_result.get_size(), 0);
+
+ // Verify the JSONB content
+ JsonbDocument* doc = nullptr;
+ auto status =
JsonbDocument::checkAndCreateDocument(jsonb_result.get_value(),
+
jsonb_result.get_size(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument: " <<
status.to_string();
+ ASSERT_NE(doc, nullptr);
+ ASSERT_TRUE(doc->getValue()->isString());
+ const auto* string_val = static_cast<const
JsonbBlobVal*>(doc->getValue());
+ std::string real_string(string_val->getBlob(),
string_val->getBlobLen());
+ ASSERT_EQ(real_string, string_field.get<String>());
+ }
+}
+
+// Test convert_field_to_type with array of JSONB
+TEST_F(ConvertFieldToTypeTest, ConvertFieldToType_ArrayToJsonb) {
+ auto array_jsonb_type =
std::make_shared<DataTypeArray>(std::make_shared<DataTypeJsonb>());
+
+ // Create an array with mixed types that will be converted to JSONB
+ Array array_field;
+ array_field.push_back(123);
+ array_field.push_back("hello");
+ array_field.push_back(456.789);
+
+ Field array_obj = array_field;
+ Field result;
+
+ convert_field_to_type(array_obj, *array_jsonb_type, &result);
+
+ ASSERT_EQ(result.get_type(), Field::Types::Array);
+ ASSERT_FALSE(result.is_null());
+
+ const Array& result_array = result.get<Array>();
+ ASSERT_EQ(result_array.size(), 3);
+
+ // Verify each element is converted to JSONB
+ for (size_t i = 0; i < result_array.size(); ++i) {
+ ASSERT_EQ(result_array[i].get_type(), Field::Types::JSONB);
+ ASSERT_FALSE(result_array[i].is_null());
+
+ const auto& jsonb_element = result_array[i].get<JsonbField>();
+ ASSERT_NE(jsonb_element.get_value(), nullptr);
+ ASSERT_GT(jsonb_element.get_size(), 0);
+
+ // Verify the JSONB content
+ JsonbDocument* doc = nullptr;
+ auto status =
JsonbDocument::checkAndCreateDocument(jsonb_element.get_value(),
+
jsonb_element.get_size(), &doc);
+ ASSERT_TRUE(status.ok()) << "Failed to create JsonbDocument for
element " << i << ": "
+ << status.to_string();
+ ASSERT_NE(doc, nullptr);
+ }
+}
+
+// Test error cases
+TEST_F(ConvertFieldToTypeTest, ConvertFieldToType_ErrorCases) {
+ DataTypeJsonb jsonb_type;
+
+ // Test with unsupported types (should throw exception)
+ {
+ Field tuple_field = Tuple();
+
+ EXPECT_THROW(
+ {
+ Field result;
+ convert_field_to_type(tuple_field, jsonb_type, &result);
+ },
+ doris::Exception);
+ }
+}
+
+} // namespace doris::vectorized
\ No newline at end of file
diff --git a/be/test/vec/jsonb/json_parser_test.cpp
b/be/test/vec/jsonb/json_parser_test.cpp
new file mode 100644
index 00000000000..257a7c370c7
--- /dev/null
+++ b/be/test/vec/jsonb/json_parser_test.cpp
@@ -0,0 +1,169 @@
+// 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 "vec/json/json_parser.h"
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+using doris::vectorized::JSONDataParser;
+using doris::vectorized::SimdJSONParser;
+using doris::vectorized::ParseConfig;
+
+TEST(JsonParserTest, ParseSimpleTypes) {
+ JSONDataParser<SimdJSONParser> parser;
+ ParseConfig config;
+
+ // int
+ auto result = parser.parse("123", 3, config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 1);
+
+ // double
+ result = parser.parse("1.23", 4, config);
+ ASSERT_TRUE(result.has_value());
+
+ // bool
+ result = parser.parse("true", 4, config);
+ ASSERT_TRUE(result.has_value());
+
+ // null
+ result = parser.parse("null", 4, config);
+ ASSERT_TRUE(result.has_value());
+
+ // string
+ result = parser.parse("\"abc\"", 5, config);
+ ASSERT_TRUE(result.has_value());
+}
+
+TEST(JsonParserTest, ParseObjectAndArray) {
+ JSONDataParser<SimdJSONParser> parser;
+ ParseConfig config;
+
+ // Object
+ auto result = parser.parse(R"({"a":1,"b":2})", 13, config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 2);
+
+ // Array
+ result = parser.parse("[1,2,3]", 7, config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 1);
+}
+
+TEST(JsonParserTest, ParseMultiLevelNestedArray) {
+ JSONDataParser<SimdJSONParser> parser;
+ ParseConfig config;
+
+ auto result = parser.parse("[[1,2],[3,4]]", 13, config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 1);
+ EXPECT_EQ(result->paths.size(), 1);
+ EXPECT_EQ(result->values[0].get_type(),
doris::vectorized::Field::Types::Array);
+
+ result = parser.parse("[[[1],[2]],[[3],[4]]]", 21, config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 1);
+ EXPECT_EQ(result->paths.size(), 1);
+ EXPECT_EQ(result->values[0].get_type(),
doris::vectorized::Field::Types::Array);
+
+ result = parser.parse("[[1,2],[3],[4,5,6]]", 19, config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 1);
+ EXPECT_EQ(result->paths.size(), 1);
+
+ // Test complex nested structure
+ config.enable_flatten_nested = false;
+ std::string json1 = R"({"a":[[1,2],[3],[4,5,6]]})";
+ // multi level nested array in object
+ result = parser.parse(json1.c_str(), json1.size(), config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 1);
+ EXPECT_EQ(result->paths.size(), 1);
+ EXPECT_EQ(result->values[0].get_type(),
doris::vectorized::Field::Types::Array);
+
+ std::string json = R"({"nested": [{"a": [1,2,3]}]})";
+ // result should be jsonbField
+ result = parser.parse(json.c_str(), json.size(), config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 1);
+ EXPECT_EQ(result->paths.size(), 1);
+ EXPECT_EQ(result->values[0].get_type(),
doris::vectorized::Field::Types::JSONB);
+
+ // multi level nested array in nested array object
+ std::string json2 = R"({"a":[{"b":[[1,2,3]]}]})";
+ result = parser.parse(json2.c_str(), json2.size(), config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 1);
+ EXPECT_EQ(result->paths.size(), 1);
+ EXPECT_EQ(result->values[0].get_type(),
doris::vectorized::Field::Types::JSONB);
+
+ // test flatten nested
+ config.enable_flatten_nested = true;
+ EXPECT_ANY_THROW(parser.parse(json.c_str(), json.size(), config));
+ // test flatten nested with multi level nested array
+ // no throw because it is not nested object array
+ result = parser.parse(json1.c_str(), json1.size(), config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(result->values.size(), 1);
+ EXPECT_EQ(result->paths.size(), 1);
+ EXPECT_EQ(result->values[0].get_type(),
doris::vectorized::Field::Types::Array);
+
+ EXPECT_ANY_THROW(parser.parse(json2.c_str(), json2.size(), config));
+}
+
+TEST(JsonParserTest, ParseNestedAndFlatten) {
+ JSONDataParser<SimdJSONParser> parser;
+ ParseConfig config;
+ config.enable_flatten_nested = true;
+
+ std::string json = R"({"a":[{"b":1},{"b":2}]})";
+ auto result = parser.parse(json.c_str(), json.size(), config);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_GT(result->values.size(), 0);
+
+ config.enable_flatten_nested = false;
+ std::string json2 = R"({"a":[{"b":1},{"b":2}]})";
+ result = parser.parse(json2.c_str(), json2.size(), config);
+ ASSERT_TRUE(result.has_value());
+}
+
+TEST(JsonParserTest, ParseInvalidJson) {
+ JSONDataParser<SimdJSONParser> parser;
+ ParseConfig config;
+
+ auto result = parser.parse("{a:1}", 5, config);
+ ASSERT_FALSE(result.has_value());
+
+ result = parser.parse("", 0, config);
+ ASSERT_FALSE(result.has_value());
+}
+
+TEST(JsonParserTest, ParseCornerCases) {
+ JSONDataParser<SimdJSONParser> parser;
+ ParseConfig config;
+
+ auto result = parser.parse("{}", 2, config);
+ ASSERT_TRUE(result.has_value());
+
+ result = parser.parse("[]", 2, config);
+ ASSERT_TRUE(result.has_value());
+
+ result = parser.parse(R"({"a":"\n\t"})", 12, config);
+ ASSERT_TRUE(result.has_value());
+}
diff --git a/regression-test/data/variant_p0/desc.out
b/regression-test/data/variant_p0/desc.out
index 1eff52e4484..71f804cc25c 100644
Binary files a/regression-test/data/variant_p0/desc.out and
b/regression-test/data/variant_p0/desc.out differ
diff --git a/regression-test/data/variant_p0/load.out
b/regression-test/data/variant_p0/load.out
index 300c97a8dfb..fefa6a3dc7a 100644
Binary files a/regression-test/data/variant_p0/load.out and
b/regression-test/data/variant_p0/load.out differ
diff --git a/regression-test/data/variant_p0/nested2.out
b/regression-test/data/variant_p0/nested2.out
new file mode 100644
index 00000000000..3703c7e5aa8
Binary files /dev/null and b/regression-test/data/variant_p0/nested2.out differ
diff --git a/regression-test/suites/variant_p0/nested2.groovy
b/regression-test/suites/variant_p0/nested2.groovy
new file mode 100644
index 00000000000..73180128bfb
--- /dev/null
+++ b/regression-test/suites/variant_p0/nested2.groovy
@@ -0,0 +1,151 @@
+// 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.
+
+// this test is used to test the type conflict of nested array
+suite("variant_nested_type_conflict", "p0"){
+
+ try {
+
+ def table_name = "var_nested_type_conflict"
+ sql "DROP TABLE IF EXISTS ${table_name}"
+ sql """set describe_extend_variant_column = true"""
+
+ sql """
+ CREATE TABLE IF NOT EXISTS ${table_name} (
+ k bigint,
+ v variant
+ )
+ DUPLICATE KEY(`k`)
+ DISTRIBUTED BY HASH(k) BUCKETS 1 -- 1 bucket make really
compaction in conflict case
+ properties("replication_num" = "1", "disable_auto_compaction"
= "false", "variant_enable_flatten_nested" = "true");
+ """
+ def sql_select_batch = {
+ qt_sql_0 """select * from ${table_name} order by k"""
+
+ qt_sql_1 """select v['nested']['a'] from ${table_name} order by
k"""
+ qt_sql_2 """select v['nested']['b'] from ${table_name} order by
k"""
+ qt_sql_3 """select v['nested']['c'] from ${table_name} order by
k"""
+
+ qt_sql_4 """select v['nested'] from ${table_name} order by k"""
+ }
+
+ def sql_test_cast_to_array = {
+ // test cast to array<int>
+ qt_sql_8 """select cast(v['nested']['a'] as array<int>),
size(cast(v['nested']['a'] as array<int>)) from ${table_name} order by k"""
+ qt_sql_9 """select cast(v['nested']['b'] as array<int>),
size(cast(v['nested']['b'] as array<int>)) from ${table_name} order by k"""
+ qt_sql_10 """select cast(v['nested']['c'] as array<int>),
size(cast(v['nested']['c'] as array<int>)) from ${table_name} order by k"""
+
+ // test cast to array<string>
+ qt_sql_11 """select cast(v['nested']['a'] as array<string>),
size(cast(v['nested']['a'] as array<string>)) from ${table_name} order by k"""
+ qt_sql_12 """select cast(v['nested']['b'] as array<string>),
size(cast(v['nested']['b'] as array<string>)) from ${table_name} order by k"""
+ qt_sql_13 """select cast(v['nested']['c'] as array<string>),
size(cast(v['nested']['c'] as array<string>)) from ${table_name} order by k"""
+
+ // test cast to array<double>
+ qt_sql_14 """select cast(v['nested']['a'] as array<double>),
size(cast(v['nested']['a'] as array<double>)) from ${table_name} order by k"""
+ qt_sql_15 """select cast(v['nested']['b'] as array<double>),
size(cast(v['nested']['b'] as array<double>)) from ${table_name} order by k"""
+ qt_sql_16 """select cast(v['nested']['c'] as array<double>),
size(cast(v['nested']['c'] as array<double>)) from ${table_name} order by k"""
+
+ }
+ // insert Nested array in Nested array which is not supported
+ test {
+ sql """
+ insert into ${table_name} values (1, '{"nested": [{"a":
[1,2,3]}]}');
+ """
+ exception "Nesting of array in Nested array within variant
subcolumns is currently not supported."
+ }
+ /// insert a array of object for a, b, c
+ // insert type conflict in multiple rows
+ sql """
+ insert into ${table_name} values (1, '{"nested": [{"a": 1, "c":
1.1}, {"b": "1"}]}');
+ """
+
+ // for cloud we should select first and then desc for syncing rowset
to get latest schema
+ sql """
+ select * from ${table_name} order by k limit 1;
+ """
+ qt_sql_desc_1 """
+ select variant_type(v) from ${table_name} order by k
+ """
+ // now select for a, b, c
+ sql_select_batch()
+ sql_test_cast_to_array()
+ /// insert a, b type changed to double
+ sql """
+ insert into ${table_name} values (2, '{"nested": [{"a": 2.5, "b":
123.1}]}');
+ """
+ // for cloud we should select first and then desc for syncing rowset
to get latest schema
+ sql """
+ select * from ${table_name} order by k limit 1;
+ """
+ qt_sql_desc_2 """
+ select variant_type(v) from ${table_name} order by k
+ """
+ // now select for a, b, c
+ sql_select_batch()
+ sql_test_cast_to_array()
+
+ // trigger and wait compaction
+ trigger_and_wait_compaction("${table_name}", "full")
+
+ // now select for a, b, c
+ sql_select_batch()
+ sql_test_cast_to_array()
+
+ sql """ truncate table ${table_name} """
+
+
+ // insert type conflict in one row
+ sql """
+ insert into ${table_name} values (1, '{"nested": [{"a": 1, "b":
1.1}, {"a": "1", "b": "1", "c": "1"}]}');
+ """
+ // for cloud we should select first and then desc for syncing rowset
to get latest schema
+ sql """
+ select * from ${table_name} order by k limit 1;
+ """
+ qt_sql_desc_4 """
+ select variant_type(v) from ${table_name} order by k
+ """
+ // now select for a, b, c
+ sql_select_batch()
+ sql_test_cast_to_array()
+
+ // insert c type changed to double
+ sql """
+ insert into ${table_name} values (2, '{"nested": [{"a": 1, "c":
1.1}]}');
+ """
+ // for cloud we should select first and then desc for syncing rowset
to get latest schema
+ sql """
+ select * from ${table_name} order by k limit 1;
+ """
+ qt_sql_desc_5 """
+ select variant_type(v) from ${table_name} order by k
+ """
+ // now select for a, b, c
+ sql_select_batch()
+ sql_test_cast_to_array()
+
+ // trigger and wait compaction
+ trigger_and_wait_compaction("${table_name}", "full")
+
+ // now select for a, b, c
+ sql_select_batch()
+ sql_test_cast_to_array()
+
+ } finally {
+ }
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]