blob: 9dff7d24189ad112d612ad9bee900671df54a1ed [file]
// 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 <array>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "core/assert_cast.h"
#include "core/binary_cast.hpp"
#include "core/column/column_array.h"
#include "core/column/column_decimal.h"
#include "core/column/column_map.h"
#include "core/column/column_nullable.h"
#include "core/column/column_string.h"
#include "core/column/column_vector.h"
#include "core/data_type/data_type_array.h"
#include "core/data_type/data_type_date.h"
#include "core/data_type/data_type_date_time.h"
#include "core/data_type/data_type_decimal.h"
#include "core/data_type/data_type_jsonb.h"
#include "core/data_type/data_type_nullable.h"
#include "core/value/decimalv2_value.h"
#include "core/value/jsonb_value.h"
#include "core/value/timestamptz_value.h"
#include "exprs/function/parse/variant_string_parse.h"
#include "storage/segment/variant/v2/variant_assembler.h"
#include "storage/segment/variant/v2/variant_column_reader.h"
#include "storage/segment/variant/v2/variant_storage_cell.h"
#include "util/variant/variant_test_utils.h"
namespace doris::segment_v2::variant_v2 {
namespace {
struct JsonWriter {
void write(const char* data, size_t size) { value.append(data, size); }
std::string value;
};
std::string json_at(ColumnVariantV2& column, size_t row) {
if (column.is_typed()) {
column.ensure_encoded();
}
JsonWriter writer;
to_json(column.get_value_ref(row), writer);
return writer.value;
}
ColumnVariantV2& assembled_values(const ColumnNullable::MutablePtr& output) {
return assert_cast<ColumnVariantV2&>(output->get_nested_column());
}
std::string serialize_storage_cell(const DataTypePtr& type, const IColumn& column, size_t row) {
ColumnString::Chars chars;
type->get_serde()->write_one_cell_to_binary(column, chars, row);
return {reinterpret_cast<const char*>(chars.data()), chars.size()};
}
template <typename T>
std::string fixed_storage_cell(FieldType type, const T& value) {
std::string cell(1, static_cast<char>(type));
cell.append(reinterpret_cast<const char*>(&value), sizeof(value));
return cell;
}
template <typename T>
std::string scaled_storage_cell(FieldType type, uint8_t scale, const T& value) {
std::string cell(1, static_cast<char>(type));
cell.push_back(static_cast<char>(scale));
cell.append(reinterpret_cast<const char*>(&value), sizeof(value));
return cell;
}
template <typename T>
std::string decimal_storage_cell(FieldType type, uint8_t precision, uint8_t scale, const T& value) {
std::string cell(1, static_cast<char>(type));
cell.push_back(static_cast<char>(precision));
cell.push_back(static_cast<char>(scale));
cell.append(reinterpret_cast<const char*>(&value), sizeof(value));
return cell;
}
std::string string_storage_cell(std::string_view value) {
std::string cell(1, static_cast<char>(FieldType::OLAP_FIELD_TYPE_STRING));
const size_t size = value.size();
cell.append(reinterpret_cast<const char*>(&size), sizeof(size));
cell.append(value);
return cell;
}
std::string jsonb_storage_cell(std::string_view value) {
JsonBinaryValue jsonb;
const Status status = jsonb.from_json_string(value.data(), value.size());
EXPECT_TRUE(status.ok()) << status;
std::string cell(1, static_cast<char>(FieldType::OLAP_FIELD_TYPE_JSONB));
const size_t size = jsonb.size();
cell.append(reinterpret_cast<const char*>(&size), sizeof(size));
cell.append(jsonb.value(), jsonb.size());
return cell;
}
ColumnString::MutablePtr jsonb_column(std::string_view value) {
JsonBinaryValue jsonb;
const Status status = jsonb.from_json_string(value.data(), value.size());
EXPECT_TRUE(status.ok()) << status;
auto column = ColumnString::create();
column->insert_data(jsonb.value(), jsonb.size());
return column;
}
std::string nested_array_storage_cell(size_t array_depth) {
std::string cell(1, static_cast<char>(FieldType::OLAP_FIELD_TYPE_NONE));
constexpr size_t ONE_ELEMENT = 1;
for (size_t depth = 0; depth < array_depth; ++depth) {
std::string parent(1, static_cast<char>(FieldType::OLAP_FIELD_TYPE_ARRAY));
parent.append(reinterpret_cast<const char*>(&ONE_ELEMENT), sizeof(ONE_ELEMENT));
parent.append(cell);
cell = std::move(parent);
}
return cell;
}
std::string dotted_path(size_t parts) {
std::string path;
for (size_t part = 0; part < parts; ++part) {
if (!path.empty()) {
path.push_back('.');
}
path.append("p");
path.append(std::to_string(part));
}
return path;
}
std::unique_ptr<VariantAssembler> create_assembler(VariantAssemblerOptions options) {
auto result = VariantAssembler::create(std::move(options));
DORIS_CHECK(result.has_value()) << result.error();
return std::move(result).value();
}
Status assemble_binary_cells(std::span<const StringRef> cells, ColumnNullable::MutablePtr* output,
std::span<const uint8_t> outer_nulls = {},
std::span<const uint8_t> missing = {}) {
return decode_v1_storage_cells(cells, outer_nulls, missing, output);
}
void append_binary_batch(MutableColumnPtr& destination, std::span<const StringRef> cells,
bool expected_typed, std::span<const uint8_t> outer_nulls = {}) {
ColumnNullable::MutablePtr assembled;
ASSERT_TRUE(assemble_binary_cells(cells, &assembled, outer_nulls).ok());
ASSERT_EQ(assembled_values(assembled).is_typed(), expected_typed);
ASSERT_TRUE(append_assembled_variant(destination, std::move(assembled)).ok());
}
using MapEntries = std::vector<std::pair<std::string, std::string>>;
ColumnMap::MutablePtr map_column_rows(const std::vector<MapEntries>& rows) {
auto paths = ColumnString::create();
auto values = ColumnString::create();
auto offsets = ColumnArray::ColumnOffsets::create();
for (const auto& entries : rows) {
for (const auto& [path, value] : entries) {
paths->insert_data(path.data(), path.size());
values->insert_data(value.data(), value.size());
}
offsets->insert_value(paths->size());
}
return ColumnMap::create(std::move(paths), std::move(values), std::move(offsets));
}
ColumnMap::MutablePtr map_column(const MapEntries& entries) {
return map_column_rows({entries});
}
struct LegacyCells {
DataTypePtr date_type = std::make_shared<DataTypeDate>();
DataTypePtr datetime_type = std::make_shared<DataTypeDateTime>();
DataTypePtr decimal_type = std::make_shared<DataTypeDecimalV2>(27, 9, 10, 2);
ColumnDate::MutablePtr dates = ColumnDate::create();
ColumnDateTime::MutablePtr datetimes = ColumnDateTime::create();
ColumnDecimal128V2::MutablePtr decimals = ColumnDecimal128V2::create(0, 9);
std::array<std::string, 2> date_cells;
std::array<std::string, 2> datetime_cells;
std::array<std::string, 2> decimal_cells;
std::string date_array_cell;
LegacyCells() {
dates->insert_value(
VecDateTimeValue::create_from_olap_date((1970U << 9U) | (1U << 5U) | 2U));
dates->insert_value(
VecDateTimeValue::create_from_olap_date((1970U << 9U) | (1U << 5U) | 3U));
datetimes->insert_value(
VecDateTimeValue::create_from_olap_datetime(uint64_t {19700101000001ULL}));
datetimes->insert_value(
VecDateTimeValue::create_from_olap_datetime(uint64_t {19700101000002ULL}));
decimals->insert_value(DecimalV2Value(std::string_view {"12.34"}));
decimals->insert_value(DecimalV2Value(std::string_view {"-56.78"}));
for (size_t row = 0; row < 2; ++row) {
date_cells[row] = serialize_storage_cell(date_type, *dates, row);
datetime_cells[row] = serialize_storage_cell(datetime_type, *datetimes, row);
decimal_cells[row] = serialize_storage_cell(decimal_type, *decimals, row);
EXPECT_EQ(static_cast<uint8_t>(date_cells[row][0]),
static_cast<uint8_t>(FieldType::OLAP_FIELD_TYPE_DATE));
EXPECT_EQ(static_cast<uint8_t>(datetime_cells[row][0]),
static_cast<uint8_t>(FieldType::OLAP_FIELD_TYPE_DATETIME));
EXPECT_EQ(static_cast<uint8_t>(decimal_cells[row][0]),
static_cast<uint8_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL));
}
auto array_dates = ColumnDate::create();
array_dates->insert_value(dates->get_data()[0]);
array_dates->insert_value(dates->get_data()[1]);
auto offsets = ColumnArray::ColumnOffsets::create();
offsets->insert_value(2);
auto array = ColumnArray::create(
ColumnNullable::create(std::move(array_dates), ColumnUInt8::create(2, 0)),
std::move(offsets));
date_array_cell =
serialize_storage_cell(std::make_shared<DataTypeArray>(date_type), *array, 0);
}
};
void expect_typed_cells(const std::array<std::string, 2>& owned_cells, PrimitiveType expected_type,
const std::array<std::string_view, 2>& expected_json) {
const std::array<StringRef, 3> cells {StringRef(owned_cells[0]), StringRef(owned_cells[1]),
StringRef()};
constexpr std::array<uint8_t, 3> MISSING {0, 0, 1};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assemble_binary_cells(cells, &output, {}, MISSING).ok());
auto& values = assembled_values(output);
ASSERT_TRUE(values.is_typed());
EXPECT_EQ(values.typed_type()->get_primitive_type(), expected_type);
if (expected_type == TYPE_DECIMALV2) {
const auto& decimal_type = assert_cast<const DataTypeDecimalV2&>(*values.typed_type());
EXPECT_EQ(decimal_type.get_original_precision(), 27);
EXPECT_EQ(decimal_type.get_original_scale(), 2);
}
const auto& nullable = assert_cast<const ColumnNullable&>(values.typed_column());
EXPECT_EQ(nullable.get_null_map_data(), (NullMap {0, 0, 1}));
EXPECT_EQ(output->get_null_map_data(), (PaddedPODArray<uint8_t> {0, 0, 1}));
EXPECT_EQ(json_at(values, 0), expected_json[0]);
EXPECT_EQ(json_at(values, 1), expected_json[1]);
EXPECT_EQ(json_at(values, 2), "null");
}
TEST(VariantAssemblerLegacyTest, BinaryExtractPublishesHomogeneousLegacyCellsAsTyped) {
LegacyCells source;
expect_typed_cells(source.date_cells, TYPE_DATE, {R"("1970-01-02")", R"("1970-01-03")"});
expect_typed_cells(source.datetime_cells, TYPE_DATETIME,
{R"("1970-01-01 00:00:01.000000")", R"("1970-01-01 00:00:02.000000")"});
expect_typed_cells(source.decimal_cells, TYPE_DECIMALV2, {"12.340000000", "-56.780000000"});
}
TEST(VariantAssemblerLegacyTest, BinaryExtractScalarTypeMatrixPreservesTypedState) {
expect_typed_cells({fixed_storage_cell<uint8_t>(FieldType::OLAP_FIELD_TYPE_BOOL, 0),
fixed_storage_cell<uint8_t>(FieldType::OLAP_FIELD_TYPE_BOOL, 1)},
TYPE_BOOLEAN, {"false", "true"});
expect_typed_cells({fixed_storage_cell<int8_t>(FieldType::OLAP_FIELD_TYPE_TINYINT, -7),
fixed_storage_cell<int8_t>(FieldType::OLAP_FIELD_TYPE_TINYINT, 8)},
TYPE_TINYINT, {"-7", "8"});
expect_typed_cells({fixed_storage_cell<int16_t>(FieldType::OLAP_FIELD_TYPE_SMALLINT, -123),
fixed_storage_cell<int16_t>(FieldType::OLAP_FIELD_TYPE_SMALLINT, 456)},
TYPE_SMALLINT, {"-123", "456"});
expect_typed_cells({fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, -123456),
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 654321)},
TYPE_INT, {"-123456", "654321"});
expect_typed_cells(
{fixed_storage_cell<int64_t>(FieldType::OLAP_FIELD_TYPE_BIGINT, -1234567890123LL),
fixed_storage_cell<int64_t>(FieldType::OLAP_FIELD_TYPE_BIGINT, 9876543210123LL)},
TYPE_BIGINT, {"-1234567890123", "9876543210123"});
expect_typed_cells({fixed_storage_cell<__int128>(FieldType::OLAP_FIELD_TYPE_LARGEINT,
static_cast<__int128>(-1234567890123LL)),
fixed_storage_cell<__int128>(FieldType::OLAP_FIELD_TYPE_LARGEINT,
static_cast<__int128>(9876543210123LL))},
TYPE_LARGEINT, {"-1234567890123", "9876543210123"});
expect_typed_cells({fixed_storage_cell<float>(FieldType::OLAP_FIELD_TYPE_FLOAT, 1.25F),
fixed_storage_cell<float>(FieldType::OLAP_FIELD_TYPE_FLOAT, -2.5F)},
TYPE_FLOAT, {"1.25", "-2.5"});
expect_typed_cells({fixed_storage_cell<double>(FieldType::OLAP_FIELD_TYPE_DOUBLE, 3.5),
fixed_storage_cell<double>(FieldType::OLAP_FIELD_TYPE_DOUBLE, -4.25)},
TYPE_DOUBLE, {"3.5", "-4.25"});
expect_typed_cells({string_storage_cell("alpha"), string_storage_cell("beta")}, TYPE_STRING,
{R"("alpha")", R"("beta")"});
expect_typed_cells({fixed_storage_cell<IPv4>(FieldType::OLAP_FIELD_TYPE_IPV4, IPv4 {}),
fixed_storage_cell<IPv4>(FieldType::OLAP_FIELD_TYPE_IPV4, IPv4 {})},
TYPE_IPV4, {R"("0.0.0.0")", R"("0.0.0.0")"});
expect_typed_cells({fixed_storage_cell<IPv6>(FieldType::OLAP_FIELD_TYPE_IPV6, IPv6 {}),
fixed_storage_cell<IPv6>(FieldType::OLAP_FIELD_TYPE_IPV6, IPv6 {})},
TYPE_IPV6, {R"("::")", R"("::")"});
DateV2Value<DateV2ValueType> date_one;
date_one.unchecked_set_time(1970, 1, 2, 0, 0, 0, 0);
DateV2Value<DateV2ValueType> date_two;
date_two.unchecked_set_time(1970, 1, 3, 0, 0, 0, 0);
expect_typed_cells({fixed_storage_cell<UInt32>(
FieldType::OLAP_FIELD_TYPE_DATEV2,
binary_cast<DateV2Value<DateV2ValueType>, UInt32>(date_one)),
fixed_storage_cell<UInt32>(
FieldType::OLAP_FIELD_TYPE_DATEV2,
binary_cast<DateV2Value<DateV2ValueType>, UInt32>(date_two))},
TYPE_DATEV2, {R"("1970-01-02")", R"("1970-01-03")"});
DateV2Value<DateTimeV2ValueType> datetime_one;
datetime_one.unchecked_set_time(1970, 1, 1, 0, 0, 1, 0);
DateV2Value<DateTimeV2ValueType> datetime_two;
datetime_two.unchecked_set_time(1970, 1, 1, 0, 0, 2, 0);
expect_typed_cells(
{scaled_storage_cell<UInt64>(
FieldType::OLAP_FIELD_TYPE_DATETIMEV2, 6,
binary_cast<DateV2Value<DateTimeV2ValueType>, UInt64>(datetime_one)),
scaled_storage_cell<UInt64>(
FieldType::OLAP_FIELD_TYPE_DATETIMEV2, 6,
binary_cast<DateV2Value<DateTimeV2ValueType>, UInt64>(datetime_two))},
TYPE_DATETIMEV2,
{R"("1970-01-01 00:00:01.000000")", R"("1970-01-01 00:00:02.000000")"});
TimestampTzValue timestamp_one;
timestamp_one.unchecked_set_time(1970, 1, 1, 0, 0, 1, 0);
TimestampTzValue timestamp_two;
timestamp_two.unchecked_set_time(1970, 1, 1, 0, 0, 2, 0);
expect_typed_cells(
{scaled_storage_cell<UInt64>(FieldType::OLAP_FIELD_TYPE_TIMESTAMPTZ, 6,
binary_cast<TimestampTzValue, UInt64>(timestamp_one)),
scaled_storage_cell<UInt64>(FieldType::OLAP_FIELD_TYPE_TIMESTAMPTZ, 6,
binary_cast<TimestampTzValue, UInt64>(timestamp_two))},
TYPE_TIMESTAMPTZ,
{R"("1970-01-01 00:00:01.000000+00:00")", R"("1970-01-01 00:00:02.000000+00:00")"});
expect_typed_cells(
{decimal_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL32, 9, 2, 1234),
decimal_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL32, 9, 2, -5678)},
TYPE_DECIMAL32, {"12.34", "-56.78"});
expect_typed_cells(
{decimal_storage_cell<int64_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL64, 18, 3, 12345),
decimal_storage_cell<int64_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL64, 18, 3, -67890)},
TYPE_DECIMAL64, {"12.345", "-67.890"});
expect_typed_cells({decimal_storage_cell<__int128>(FieldType::OLAP_FIELD_TYPE_DECIMAL128I, 38,
4, static_cast<__int128>(123456)),
decimal_storage_cell<__int128>(FieldType::OLAP_FIELD_TYPE_DECIMAL128I, 38,
4, static_cast<__int128>(-654321))},
TYPE_DECIMAL128I, {"12.3456", "-65.4321"});
}
TEST(VariantAssemblerLegacyTest, BinaryExtractHeterogeneousComplexAndUnsupportedCells) {
const std::array<std::string, 2> heterogeneous {
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 7),
fixed_storage_cell<float>(FieldType::OLAP_FIELD_TYPE_FLOAT, 1.5F)};
const std::array<StringRef, 2> heterogeneous_refs {StringRef(heterogeneous[0]),
StringRef(heterogeneous[1])};
ColumnNullable::MutablePtr heterogeneous_output;
ASSERT_TRUE(assemble_binary_cells(heterogeneous_refs, &heterogeneous_output).ok());
EXPECT_FALSE(assembled_values(heterogeneous_output).is_typed());
EXPECT_EQ(json_at(assembled_values(heterogeneous_output), 0), "7");
EXPECT_EQ(json_at(assembled_values(heterogeneous_output), 1), "1.5");
LegacyCells source;
const std::array<StringRef, 2> arrays {StringRef(source.date_array_cell),
StringRef(source.date_array_cell)};
ColumnNullable::MutablePtr array_output;
ASSERT_TRUE(assemble_binary_cells(arrays, &array_output).ok());
EXPECT_FALSE(assembled_values(array_output).is_typed());
EXPECT_EQ(json_at(assembled_values(array_output), 0), R"(["1970-01-02","1970-01-03"])");
EXPECT_EQ(json_at(assembled_values(array_output), 1), R"(["1970-01-02","1970-01-03"])");
const std::array<std::string, 2> decimal256_cells {
decimal_storage_cell<wide::Int256>(FieldType::OLAP_FIELD_TYPE_DECIMAL256, 76, 4,
wide::Int256 {123456}),
decimal_storage_cell<wide::Int256>(FieldType::OLAP_FIELD_TYPE_DECIMAL256, 76, 4,
wide::Int256 {-654321})};
const std::array<StringRef, 2> decimal256_refs {StringRef(decimal256_cells[0]),
StringRef(decimal256_cells[1])};
ColumnNullable::MutablePtr decimal256_output;
const Status status = assemble_binary_cells(decimal256_refs, &decimal256_output);
EXPECT_TRUE(status.is<ErrorCode::NOT_IMPLEMENTED_ERROR>()) << status;
EXPECT_NE(status.to_string().find("Decimal256 storage cell"), std::string::npos);
}
TEST(VariantAssemblerLegacyTest,
BinaryExtractSameTagDifferentDecimalMetadataFallsBackToCanonicalEncoded) {
const std::array<std::array<std::string, 2>, 2> cases {{
{decimal_storage_cell<int64_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL64, 17, 2, 1234),
decimal_storage_cell<int64_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL64, 18, 2, 5678)},
{decimal_storage_cell<int64_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL64, 18, 2, 1234),
decimal_storage_cell<int64_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL64, 18, 3, 5678)},
}};
const std::array<std::array<std::string_view, 2>, 2> expected {{
{"12.34", "56.78"},
{"12.34", "5.678"},
}};
for (size_t case_index = 0; case_index < cases.size(); ++case_index) {
SCOPED_TRACE(case_index);
const auto& owned_cells = cases[case_index];
const std::array<StringRef, 2> cells {StringRef(owned_cells[0]), StringRef(owned_cells[1])};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assemble_binary_cells(cells, &output).ok());
auto& values = assembled_values(output);
ASSERT_FALSE(values.is_typed());
for (size_t row = 0; row < cells.size(); ++row) {
EXPECT_EQ(json_at(values, row), expected[case_index][row]);
ASSERT_NO_THROW(validate_canonical(values.get_value_ref(row)));
}
}
}
TEST(VariantAssemblerLegacyTest,
BinaryExtractSameTimestampTagDifferentScaleFallsBackToCanonicalEncoded) {
DateV2Value<DateTimeV2ValueType> datetime_one;
datetime_one.unchecked_set_time(1970, 1, 1, 0, 0, 1, 0);
DateV2Value<DateTimeV2ValueType> datetime_two;
datetime_two.unchecked_set_time(1970, 1, 1, 0, 0, 2, 0);
const std::array<std::string, 2> datetime_cells {
scaled_storage_cell<UInt64>(
FieldType::OLAP_FIELD_TYPE_DATETIMEV2, 6,
binary_cast<DateV2Value<DateTimeV2ValueType>, UInt64>(datetime_one)),
scaled_storage_cell<UInt64>(
FieldType::OLAP_FIELD_TYPE_DATETIMEV2, 3,
binary_cast<DateV2Value<DateTimeV2ValueType>, UInt64>(datetime_two)),
};
TimestampTzValue timestamp_one;
timestamp_one.unchecked_set_time(1970, 1, 1, 0, 0, 1, 0);
TimestampTzValue timestamp_two;
timestamp_two.unchecked_set_time(1970, 1, 1, 0, 0, 2, 0);
const std::array<std::string, 2> timestamp_cells {
scaled_storage_cell<UInt64>(FieldType::OLAP_FIELD_TYPE_TIMESTAMPTZ, 6,
binary_cast<TimestampTzValue, UInt64>(timestamp_one)),
scaled_storage_cell<UInt64>(FieldType::OLAP_FIELD_TYPE_TIMESTAMPTZ, 3,
binary_cast<TimestampTzValue, UInt64>(timestamp_two)),
};
const auto expect_fallback = [](const std::array<std::string, 2>& owned_cells) {
const std::array<StringRef, 2> cells {StringRef(owned_cells[0]), StringRef(owned_cells[1])};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assemble_binary_cells(cells, &output).ok());
auto& values = assembled_values(output);
ASSERT_FALSE(values.is_typed());
for (size_t row = 0; row < cells.size(); ++row) {
ASSERT_NO_THROW(validate_canonical(values.get_value_ref(row)));
}
};
expect_fallback(datetime_cells);
expect_fallback(timestamp_cells);
}
TEST(VariantAssemblerLegacyTest, BinaryExtractLateSignatureMismatchReplaysWholeBatch) {
const std::array<std::string, 6> owned_cells {
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 1),
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 2),
std::string(1, static_cast<char>(0xFF)),
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 3),
string_storage_cell("late"),
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 4),
};
std::array<StringRef, owned_cells.size()> cells;
for (size_t row = 0; row < cells.size(); ++row) {
cells[row] = StringRef(owned_cells[row]);
}
constexpr std::array<uint8_t, cells.size()> OUTER_NULLS {0, 0, 1, 0, 0, 0};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assemble_binary_cells(cells, &output, OUTER_NULLS).ok());
auto& values = assembled_values(output);
ASSERT_FALSE(values.is_typed());
EXPECT_EQ(output->get_null_map_data(), (PaddedPODArray<uint8_t> {0, 0, 1, 0, 0, 0}));
const std::array<std::string_view, cells.size()> expected {"1", "2", "null",
"3", R"("late")", "4"};
for (size_t row = 0; row < expected.size(); ++row) {
EXPECT_EQ(json_at(values, row), expected[row]) << "row=" << row;
}
}
TEST(VariantAssemblerLegacyTest, BinaryExtractKeepsPresentVariantNullsInsidePayload) {
const std::array<std::string, 3> owned_cells {
std::string(1, static_cast<char>(FieldType::OLAP_FIELD_TYPE_NONE)),
jsonb_storage_cell("null"),
jsonb_storage_cell(R"({"a":7,"b":[true,null]})"),
};
const std::array<StringRef, 3> cells {StringRef(owned_cells[0]), StringRef(owned_cells[1]),
StringRef(owned_cells[2])};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assemble_binary_cells(cells, &output).ok());
auto& values = assembled_values(output);
EXPECT_FALSE(values.is_typed());
EXPECT_EQ(json_at(values, 0), "null");
EXPECT_EQ(json_at(values, 1), "null");
EXPECT_EQ(json_at(values, 2), R"({"a":7,"b":[true,null]})");
EXPECT_EQ(output->get_null_map_data(), (PaddedPODArray<uint8_t> {0, 0, 0}));
MutableColumnPtr non_nullable_destination = ColumnVariantV2::create();
const Status status = append_assembled_variant(non_nullable_destination, std::move(output));
EXPECT_TRUE(status.ok()) << status;
EXPECT_EQ(non_nullable_destination->size(), 3);
}
TEST(VariantAssemblerLegacyTest, BinaryExtractDoesNotDecodeMaskedMalformedCells) {
const std::array<std::string, 3> owned_cells {
std::string {},
std::string(1, static_cast<char>(0xFF)),
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 42),
};
const std::array<StringRef, 3> cells {StringRef(owned_cells[0]), StringRef(owned_cells[1]),
StringRef(owned_cells[2])};
constexpr std::array<uint8_t, 3> OUTER_NULLS {0, 1, 0};
constexpr std::array<uint8_t, 3> MISSING {1, 0, 0};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assemble_binary_cells(cells, &output, OUTER_NULLS, MISSING).ok());
auto& values = assembled_values(output);
ASSERT_TRUE(values.is_typed());
EXPECT_EQ(values.typed_type()->get_primitive_type(), TYPE_INT);
const auto& nullable = assert_cast<const ColumnNullable&>(values.typed_column());
EXPECT_EQ(nullable.get_null_map_data(), (NullMap {1, 1, 0}));
EXPECT_EQ(output->get_null_map_data(), (PaddedPODArray<uint8_t> {1, 1, 0}));
EXPECT_EQ(json_at(values, 0), "null");
EXPECT_EQ(json_at(values, 1), "null");
EXPECT_EQ(json_at(values, 2), "42");
}
TEST(VariantAssemblerLegacyTest, BinaryExtractAllMissingCellsUseGenericFallback) {
const std::array<std::string, 3> owned_cells {
std::string {},
std::string(1, static_cast<char>(0xFF)),
std::string {},
};
const std::array<StringRef, 3> cells {StringRef(owned_cells[0]), StringRef(owned_cells[1]),
StringRef(owned_cells[2])};
constexpr std::array<uint8_t, 3> OUTER_NULLS {1, 0, 1};
constexpr std::array<uint8_t, 3> MISSING {0, 1, 0};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assemble_binary_cells(cells, &output, OUTER_NULLS, MISSING).ok());
auto& values = assembled_values(output);
EXPECT_FALSE(values.is_typed());
EXPECT_EQ(output->get_null_map_data(), (PaddedPODArray<uint8_t> {1, 1, 1}));
for (size_t row = 0; row < cells.size(); ++row) {
EXPECT_EQ(json_at(values, row), "null");
}
}
TEST(VariantAssemblerLegacyTest, BinaryExtractRejectsMalformedLaterTypedCellAtomically) {
auto trailing_int = fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 2);
trailing_int.push_back('\0');
auto truncated_string = string_storage_cell("truncated");
truncated_string.pop_back();
const std::array<std::array<std::string, 2>, 2> cases {{
{fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 1),
std::move(trailing_int)},
{string_storage_cell("valid"), std::move(truncated_string)},
}};
for (const auto& owned_cells : cases) {
const std::array<StringRef, 2> cells {StringRef(owned_cells[0]), StringRef(owned_cells[1])};
ColumnNullable::MutablePtr output;
const Status status = assemble_binary_cells(cells, &output);
EXPECT_TRUE(status.is<ErrorCode::CORRUPTION>()) << status;
EXPECT_EQ(output.get(), nullptr);
}
}
TEST(VariantAssemblerLegacyTest, BinaryExtractLateFallbackFailureIsAtomicAndReusable) {
const std::string initial = fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 42);
const std::array<StringRef, 1> initial_cells {StringRef(initial)};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assemble_binary_cells(initial_cells, &output).ok());
const auto* output_before = output.get();
EXPECT_EQ(json_at(assembled_values(output), 0), "42");
auto truncated = string_storage_cell("truncated");
truncated.pop_back();
const std::array<std::string, 4> malformed {
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 1),
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 2),
string_storage_cell("fallback"),
std::move(truncated),
};
const std::array<StringRef, malformed.size()> malformed_cells {
StringRef(malformed[0]), StringRef(malformed[1]), StringRef(malformed[2]),
StringRef(malformed[3])};
const Status failure = assemble_binary_cells(malformed_cells, &output);
EXPECT_TRUE(failure.is<ErrorCode::CORRUPTION>()) << failure;
EXPECT_EQ(output.get(), output_before);
EXPECT_EQ(json_at(assembled_values(output), 0), "42");
const std::string recovery = string_storage_cell("recovered");
const std::array<StringRef, 1> recovery_cells {StringRef(recovery)};
ASSERT_TRUE(assemble_binary_cells(recovery_cells, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"("recovered")");
}
TEST(VariantAssemblerLegacyTest, BinaryExtractBatchesAppendAcrossTypedAndEncodedStates) {
MutableColumnPtr destination =
ColumnNullable::create(ColumnVariantV2::create(), ColumnUInt8::create());
const std::array<std::string, 2> int_cells {
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 42), std::string {}};
const std::array<StringRef, 2> int_refs {StringRef(int_cells[0]), StringRef(int_cells[1])};
constexpr std::array<uint8_t, 2> INT_OUTER_NULLS {0, 1};
append_binary_batch(destination, int_refs, true, INT_OUTER_NULLS);
const std::array<std::string, 2> heterogeneous_cells {
fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 7),
string_storage_cell("seven")};
const std::array<StringRef, 2> heterogeneous_refs {StringRef(heterogeneous_cells[0]),
StringRef(heterogeneous_cells[1])};
append_binary_batch(destination, heterogeneous_refs, false);
const std::array<std::string, 2> string_cells {string_storage_cell("alpha"),
string_storage_cell("beta")};
const std::array<StringRef, 2> string_refs {StringRef(string_cells[0]),
StringRef(string_cells[1])};
append_binary_batch(destination, string_refs, true);
auto& nullable = assert_cast<ColumnNullable&>(*destination);
auto& values = assert_cast<ColumnVariantV2&>(nullable.get_nested_column());
ASSERT_FALSE(values.is_typed());
EXPECT_EQ(nullable.get_null_map_data(), (NullMap {0, 1, 0, 0, 0, 0}));
const std::array<std::string_view, 6> expected {"42", "null", "7",
R"("seven")", R"("alpha")", R"("beta")"};
for (size_t row = 0; row < expected.size(); ++row) {
EXPECT_EQ(json_at(values, row), expected[row]) << "row=" << row;
}
}
TEST(VariantAssemblerLegacyTest, HierarchicalMaterializedDecimalAndRecursiveArray) {
auto decimal_type = std::make_shared<DataTypeDecimal64>(18, 2);
auto decimal = ColumnDecimal64::create(0, 2);
decimal->insert_value(Decimal64 {7890});
auto nested_decimals = ColumnDecimal64::create(0, 2);
nested_decimals->insert_value(Decimal64 {123});
nested_decimals->insert_value(Decimal64 {});
nested_decimals->insert_value(Decimal64 {-456});
auto nested_decimal_nulls = ColumnUInt8::create();
nested_decimal_nulls->insert_value(0);
nested_decimal_nulls->insert_value(1);
nested_decimal_nulls->insert_value(0);
auto nullable_decimals =
ColumnNullable::create(std::move(nested_decimals), std::move(nested_decimal_nulls));
auto inner_offsets = ColumnArray::ColumnOffsets::create();
inner_offsets->insert_value(2);
inner_offsets->insert_value(3);
inner_offsets->insert_value(3);
auto inner_arrays = ColumnArray::create(std::move(nullable_decimals), std::move(inner_offsets));
auto inner_array_nulls = ColumnUInt8::create();
inner_array_nulls->insert_value(0);
inner_array_nulls->insert_value(0);
inner_array_nulls->insert_value(1);
auto nullable_inner_arrays =
ColumnNullable::create(std::move(inner_arrays), std::move(inner_array_nulls));
auto outer_offsets = ColumnArray::ColumnOffsets::create();
outer_offsets->insert_value(3);
auto outer_array =
ColumnArray::create(std::move(nullable_inner_arrays), std::move(outer_offsets));
auto nullable_decimal_type = std::make_shared<DataTypeNullable>(decimal_type);
auto inner_array_type = std::make_shared<DataTypeArray>(nullable_decimal_type);
auto nullable_inner_array_type = std::make_shared<DataTypeNullable>(inner_array_type);
auto outer_array_type = std::make_shared<DataTypeArray>(nullable_inner_array_type);
VariantAssemblerOptions options;
options.materialized_paths = {
{.path = PathInData("a"), .type = outer_array_type},
{.path = PathInData("d"), .type = decimal_type},
};
auto assembler = create_assembler(std::move(options));
ASSERT_NE(assembler, nullptr);
const std::array<const IColumn*, 2> materialized {outer_array.get(), decimal.get()};
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.materialized_columns = materialized;
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_FALSE(assembled_values(output).is_typed());
EXPECT_EQ(json_at(assembled_values(output), 0),
R"({"a":[[1.23,null],[-4.56],null],"d":78.90})");
}
TEST(VariantAssemblerLegacyTest, HierarchicalMaterializedDecimal256ReturnsNotSupported) {
auto decimal_type = std::make_shared<DataTypeDecimal256>(76, 2);
auto decimal = ColumnDecimal256::create(0, 2);
decimal->insert_value(Decimal256 {wide::Int256 {123}});
VariantAssemblerOptions options;
options.materialized_paths = {{.path = PathInData("d"), .type = decimal_type}};
auto assembler = create_assembler(std::move(options));
ASSERT_NE(assembler, nullptr);
const IColumn* materialized = decimal.get();
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.materialized_columns = {&materialized, 1};
ColumnNullable::MutablePtr output;
const Status status = assembler->assemble(batch, &output);
EXPECT_TRUE(status.is<ErrorCode::NOT_IMPLEMENTED_ERROR>()) << status;
EXPECT_NE(status.to_string().find("Decimal256 materialized storage column"), std::string::npos);
EXPECT_EQ(output.get(), nullptr);
}
TEST(VariantAssemblerLegacyTest, MaterializedArraysPreserveExplicitValues) {
auto values = ColumnString::create();
auto nulls = ColumnUInt8::create();
auto insert_jsonb = [&](std::string_view json) {
JsonBinaryValue jsonb;
ASSERT_TRUE(jsonb.from_json_string(json.data(), json.size()).ok());
values->insert_data(jsonb.value(), jsonb.size());
nulls->insert_value(0);
};
auto insert_null = [&]() {
values->insert_default();
nulls->insert_value(1);
};
auto offsets = ColumnArray::ColumnOffsets::create();
offsets->insert_value(0); // []
insert_null();
offsets->insert_value(1); // [null]
insert_jsonb("{}");
offsets->insert_value(2); // [{}]
insert_jsonb(R"({"L2":[]})");
offsets->insert_value(3); // [{"L2":[]}]
insert_jsonb("1");
offsets->insert_value(4); // [1]
insert_null();
insert_jsonb(R"({"x":1})");
offsets->insert_value(6); // [null, {"x":1}]
offsets->insert_value(6); // SQL NULL row, carried by the nullable materialized/root columns
auto nested = ColumnNullable::create(std::move(values), std::move(nulls));
auto arrays = ColumnArray::create(std::move(nested), std::move(offsets));
auto array_nulls = ColumnUInt8::create();
for (uint8_t is_null : {0, 0, 0, 0, 0, 0, 1}) {
array_nulls->insert_value(is_null);
}
auto nullable_arrays = ColumnNullable::create(std::move(arrays), std::move(array_nulls));
auto array_type =
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeJsonb>()));
auto nullable_array_type = make_nullable(array_type);
constexpr std::array<uint8_t, 7> OUTER_NULLS {0, 0, 0, 0, 0, 0, 1};
auto root_values = ColumnString::create();
auto root_nulls = ColumnUInt8::create();
for (uint8_t is_null : OUTER_NULLS) {
root_values->insert_default();
root_nulls->insert_value(is_null);
}
auto nullable_root = ColumnNullable::create(std::move(root_values), std::move(root_nulls));
const std::array<const IColumn*, 1> materialized {nullable_arrays.get()};
VariantAssemblerOptions whole_options;
whole_options.materialized_paths = {{.path = PathInData("a"), .type = nullable_array_type}};
whole_options.has_root = true;
auto whole_assembler = create_assembler(std::move(whole_options));
ASSERT_NE(whole_assembler, nullptr);
VariantAssemblerBatchView whole_batch;
whole_batch.num_rows = OUTER_NULLS.size();
whole_batch.root_jsonb = nullable_root.get();
whole_batch.materialized_columns = materialized;
ColumnNullable::MutablePtr whole_output;
ASSERT_TRUE(whole_assembler->assemble(whole_batch, &whole_output).ok());
const std::array<std::string_view, 7> expected_whole {
"{}", R"({"a":[null]})", R"({"a":[{}]})", R"({"a":[{"L2":[]}]})",
R"({"a":[1]})", R"({"a":[null,{"x":1}]})", "null"};
for (size_t row = 0; row < expected_whole.size(); ++row) {
EXPECT_EQ(json_at(assembled_values(whole_output), row), expected_whole[row])
<< "row=" << row;
}
EXPECT_EQ(whole_output->get_null_map_data(), (PaddedPODArray<uint8_t> {0, 0, 0, 0, 0, 0, 1}));
VariantAssemblerOptions subtree_options;
subtree_options.requested_path = PathInData("a");
subtree_options.materialized_paths = {{.path = PathInData("a"), .type = nullable_array_type}};
auto subtree_assembler = create_assembler(std::move(subtree_options));
ASSERT_NE(subtree_assembler, nullptr);
VariantAssemblerBatchView subtree_batch;
subtree_batch.num_rows = OUTER_NULLS.size();
subtree_batch.materialized_columns = materialized;
ColumnNullable::MutablePtr subtree_output;
ASSERT_TRUE(subtree_assembler->assemble(subtree_batch, &subtree_output).ok());
const std::array<std::string_view, 7> expected_subtree {
"null", "[null]", "[{}]", R"([{"L2":[]}])", "[1]", R"([null,{"x":1}])", "null"};
for (size_t row = 0; row < expected_subtree.size(); ++row) {
EXPECT_EQ(json_at(assembled_values(subtree_output), row), expected_subtree[row])
<< "row=" << row;
}
EXPECT_EQ(subtree_output->get_null_map_data(), (PaddedPODArray<uint8_t> {1, 0, 0, 0, 0, 0, 1}));
}
TEST(VariantAssemblerLegacyTest, HierarchicalMaterializedLegacyDateTimeAndDecimalV2) {
LegacyCells source;
VariantAssemblerOptions options;
options.materialized_paths = {
{.path = PathInData("dt"), .type = source.datetime_type},
{.path = PathInData("m"), .type = source.decimal_type},
};
auto assembler = create_assembler(std::move(options));
ASSERT_NE(assembler, nullptr);
const std::array<const IColumn*, 2> materialized {source.datetimes.get(),
source.decimals.get()};
VariantAssemblerBatchView batch;
batch.num_rows = 2;
batch.materialized_columns = materialized;
ColumnNullable::MutablePtr output;
const Status status = assembler->assemble(batch, &output);
ASSERT_TRUE(status.ok()) << status;
ASSERT_FALSE(assembled_values(output).is_typed());
EXPECT_EQ(json_at(assembled_values(output), 0),
R"({"dt":"1970-01-01 00:00:01.000000","m":12.340000000})");
EXPECT_EQ(json_at(assembled_values(output), 1),
R"({"dt":"1970-01-01 00:00:02.000000","m":-56.780000000})");
}
TEST(VariantAssemblerLegacyTest, UnsortedMaterializedPathsKeepSourceColumns) {
auto z = ColumnInt32::create();
z->insert_value(10);
auto a = ColumnInt32::create();
a->insert_value(20);
auto child = ColumnInt32::create();
child->insert_value(30);
const auto int_type = std::make_shared<DataTypeInt32>();
VariantAssemblerOptions options;
options.materialized_paths = {
{.path = PathInData("z"), .type = int_type},
{.path = PathInData("a"), .type = int_type},
{.path = PathInData("m.child"), .type = int_type},
};
auto assembler = create_assembler(std::move(options));
const std::array<const IColumn*, 3> materialized {z.get(), a.get(), child.get()};
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.materialized_columns = materialized;
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"a":20,"m":{"child":30},"z":10})");
}
TEST(VariantAssemblerLegacyTest, RootSidecarYieldsToVisibleHierarchicalStreams) {
const auto int_type = std::make_shared<DataTypeInt32>();
const std::string sparse_cell = fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 20);
{
VariantAssemblerOptions options;
options.has_root = true;
auto assembler = create_assembler(std::move(options));
auto root = jsonb_column(R"({"source":"root"})");
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.root_jsonb = root.get();
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"source":"root"})");
}
{
VariantAssemblerOptions options;
options.has_root = true;
options.storage_map_kind = StorageMapKind::SPARSE;
auto assembler = create_assembler(std::move(options));
auto root = jsonb_column(R"({"source":"root"})");
auto sparse = map_column({});
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.root_jsonb = root.get();
batch.storage_map = sparse.get();
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"source":"root"})");
}
{
VariantAssemblerOptions options;
options.has_root = true;
options.storage_map_kind = StorageMapKind::SPARSE;
auto assembler = create_assembler(std::move(options));
auto root = jsonb_column(R"({"source":"root"})");
auto sparse = map_column({{"sparse", sparse_cell}});
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.root_jsonb = root.get();
batch.storage_map = sparse.get();
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"sparse":20})");
}
{
VariantAssemblerOptions options;
options.has_root = true;
options.materialized_paths = {{.path = PathInData("materialized"), .type = int_type}};
auto assembler = create_assembler(std::move(options));
auto root = jsonb_column(R"({"source":"root"})");
auto materialized = ColumnInt32::create();
materialized->insert_value(30);
const IColumn* materialized_ptr = materialized.get();
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.root_jsonb = root.get();
batch.materialized_columns = {&materialized_ptr, 1};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"materialized":30})");
}
{
VariantAssemblerOptions options;
options.has_root = true;
options.storage_map_kind = StorageMapKind::DOC;
auto assembler = create_assembler(std::move(options));
auto root = jsonb_column(R"({"source":"root"})");
auto doc = map_column({{"doc", sparse_cell}});
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.root_jsonb = root.get();
batch.storage_map = doc.get();
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"doc":20})");
}
}
TEST(VariantAssemblerLegacyTest, HierarchicalFailuresAreAtomicAndAssemblerCanBeReused) {
VariantAssemblerOptions options;
options.storage_map_kind = StorageMapKind::SPARSE;
auto assembler = create_assembler(std::move(options));
const std::string valid_42 = fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 42);
auto valid = map_column({{"a", valid_42}});
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.storage_map = valid.get();
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"a":42})");
const auto* wrapper_before = output.get();
const auto* values_before = output->get_nested_column_ptr().get();
const auto* null_map_before = output->get_null_map_column_ptr().get();
const std::string json_before = json_at(assembled_values(output), 0);
auto truncated = valid_42;
truncated.pop_back();
auto malformed = map_column({{"a", truncated}});
batch.storage_map = malformed.get();
const Status status = assembler->assemble(batch, &output);
ASSERT_TRUE(status.is<ErrorCode::CORRUPTION>()) << status;
EXPECT_EQ(output.get(), wrapper_before);
EXPECT_EQ(output->get_nested_column_ptr().get(), values_before);
EXPECT_EQ(output->get_null_map_column_ptr().get(), null_map_before);
EXPECT_EQ(json_at(assembled_values(output), 0), json_before);
const std::string valid_43 = fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 43);
auto next = map_column({{"a", valid_43}});
batch.storage_map = next.get();
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"a":43})");
}
TEST(VariantAssemblerLegacyTest, DepthBoundaries) {
{
VariantAssemblerOptions options;
options.materialized_paths.push_back(
{.path = PathInData(dotted_path(VARIANT_MAX_NESTING_DEPTH)),
.type = std::make_shared<DataTypeInt32>()});
auto assembler = create_assembler(std::move(options));
EXPECT_NE(assembler, nullptr);
}
const std::array<std::string, 2> owned_cells {
nested_array_storage_cell(VARIANT_MAX_NESTING_DEPTH),
nested_array_storage_cell(VARIANT_MAX_NESTING_DEPTH + 1),
};
for (size_t row = 0; row < owned_cells.size(); ++row) {
const std::array<StringRef, 1> cells {StringRef(owned_cells[row])};
ColumnNullable::MutablePtr output;
const Status status = assemble_binary_cells(cells, &output);
if (row == 0) {
EXPECT_TRUE(status.ok()) << status;
} else {
EXPECT_TRUE(status.is<ErrorCode::CORRUPTION>()) << status;
}
}
const std::string value = fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 1);
VariantAssemblerOptions sparse_options;
sparse_options.storage_map_kind = StorageMapKind::SPARSE;
auto sparse_assembler = create_assembler(std::move(sparse_options));
for (size_t depth : {VARIANT_MAX_NESTING_DEPTH, VARIANT_MAX_NESTING_DEPTH + 1}) {
auto sparse = map_column({{dotted_path(depth), value}});
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.storage_map = sparse.get();
ColumnNullable::MutablePtr output;
const Status status = sparse_assembler->assemble(batch, &output);
if (depth == VARIANT_MAX_NESTING_DEPTH) {
EXPECT_TRUE(status.ok()) << status;
} else {
EXPECT_TRUE(status.is<ErrorCode::CORRUPTION>()) << status;
EXPECT_EQ(output.get(), nullptr);
}
}
}
TEST(VariantAssemblerLegacyTest, MalformedStorageCellsFailAtomicallyAndAllowLaterBatches) {
LegacyCells source;
auto truncated_date = source.date_cells[0];
truncated_date.pop_back();
auto truncated_datetime = source.datetime_cells[0];
truncated_datetime.pop_back();
auto truncated_decimal = source.decimal_cells[0];
truncated_decimal.pop_back();
auto truncated_string = string_storage_cell("truncated");
truncated_string.pop_back();
std::vector<std::string> malformed {
{},
fixed_storage_cell<size_t>(FieldType::OLAP_FIELD_TYPE_ARRAY, 2),
std::move(truncated_string),
std::move(truncated_date),
std::move(truncated_datetime),
std::move(truncated_decimal),
// The tag fixes the payload width. A precision selecting another physical Decimal
// column would make the typed SerDe cast its destination to the wrong column type.
decimal_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL32, 18, 2, 1234),
decimal_storage_cell<int64_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL64, 9, 2, 1234),
std::string(1, static_cast<char>(0xFF)),
};
auto trailing = fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 1);
trailing.push_back('\0');
malformed.push_back(std::move(trailing));
for (const std::string& cell : malformed) {
const std::array<StringRef, 1> cells {StringRef(cell)};
ColumnNullable::MutablePtr output;
const Status status = assemble_binary_cells(cells, &output);
EXPECT_TRUE(status.is<ErrorCode::CORRUPTION>()) << status;
EXPECT_EQ(output.get(), nullptr);
}
const std::string valid = fixed_storage_cell<int32_t>(FieldType::OLAP_FIELD_TYPE_INT, 42);
const std::array<StringRef, 1> valid_cells {StringRef(valid)};
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assemble_binary_cells(valid_cells, &output).ok());
ASSERT_NE(output.get(), nullptr);
const auto* wrapper_before = output.get();
const auto& output_before = static_cast<const ColumnNullable&>(*output);
const auto* values_before = output_before.get_nested_column_ptr().get();
const auto* null_map_before = output_before.get_null_map_column_ptr().get();
const std::string json_before = json_at(assembled_values(output), 0);
const std::array<StringRef, 1> malformed_cells {StringRef(malformed.back())};
const Status first = assemble_binary_cells(malformed_cells, &output);
ASSERT_TRUE(first.is<ErrorCode::CORRUPTION>()) << first;
EXPECT_EQ(output.get(), wrapper_before);
const auto& output_after = static_cast<const ColumnNullable&>(*output);
EXPECT_EQ(output_after.get_nested_column_ptr().get(), values_before);
EXPECT_EQ(output_after.get_null_map_column_ptr().get(), null_map_before);
EXPECT_EQ(json_at(assembled_values(output), 0), json_before);
ASSERT_TRUE(assemble_binary_cells(valid_cells, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), "42");
}
TEST(VariantAssemblerLegacyTest, SparseAndDocDecodeLegacyCellsToCanonicalEncoding) {
LegacyCells source;
const std::vector<std::pair<std::string, std::string>> entries {
{"a", source.date_array_cell},
{"d", source.date_cells[0]},
{"dt", source.datetime_cells[0]},
{"m", source.decimal_cells[0]},
};
auto values = map_column(entries);
constexpr std::string_view EXPECTED = R"({"a":["1970-01-02","1970-01-03"],"d":"1970-01-02",)"
R"("dt":"1970-01-01 00:00:01.000000",)"
R"("m":12.340000000})";
VariantAssemblerOptions sparse_options;
sparse_options.storage_map_kind = StorageMapKind::SPARSE;
auto sparse_assembler = create_assembler(std::move(sparse_options));
ASSERT_NE(sparse_assembler, nullptr);
const ColumnMap* sparse = values.get();
VariantAssemblerBatchView sparse_batch;
sparse_batch.num_rows = 1;
sparse_batch.storage_map = sparse;
ColumnNullable::MutablePtr sparse_output;
ASSERT_TRUE(sparse_assembler->assemble(sparse_batch, &sparse_output).ok());
EXPECT_FALSE(assembled_values(sparse_output).is_typed());
EXPECT_EQ(json_at(assembled_values(sparse_output), 0), EXPECTED);
VariantAssemblerOptions doc_options;
doc_options.storage_map_kind = StorageMapKind::DOC;
auto doc_assembler = create_assembler(std::move(doc_options));
ASSERT_NE(doc_assembler, nullptr);
VariantAssemblerBatchView doc_batch;
doc_batch.num_rows = 1;
doc_batch.storage_map = values.get();
ColumnNullable::MutablePtr doc_output;
ASSERT_TRUE(doc_assembler->assemble(doc_batch, &doc_output).ok());
EXPECT_FALSE(assembled_values(doc_output).is_typed());
EXPECT_EQ(json_at(assembled_values(doc_output), 0), EXPECTED);
}
TEST(VariantAssemblerLegacyTest, LegacyAncestorDescendantConflictsPreferDescendants) {
LegacyCells source;
auto same_stream = map_column_rows({
{{"0", source.date_cells[0]},
{"a", source.date_cells[0]},
{"a-", source.date_cells[0]},
{"a.b", source.date_cells[1]},
{"b", source.date_cells[0]}},
{{"0", source.date_cells[0]},
{"a", source.date_cells[1]},
{"a-", source.date_cells[0]},
{"b", source.date_cells[0]}},
});
VariantAssemblerOptions sparse_options;
sparse_options.requested_path = PathInData("a");
sparse_options.storage_map_kind = StorageMapKind::SPARSE;
auto sparse_assembler = create_assembler(std::move(sparse_options));
ASSERT_NE(sparse_assembler, nullptr);
const ColumnMap* sparse = same_stream.get();
VariantAssemblerBatchView sparse_batch;
sparse_batch.num_rows = 2;
sparse_batch.storage_map = sparse;
ColumnNullable::MutablePtr sparse_output;
ASSERT_TRUE(sparse_assembler->assemble(sparse_batch, &sparse_output).ok());
EXPECT_EQ(json_at(assembled_values(sparse_output), 0), R"({"b":"1970-01-03"})");
EXPECT_EQ(json_at(assembled_values(sparse_output), 1), R"("1970-01-03")");
VariantAssemblerOptions doc_options;
doc_options.requested_path = PathInData("a");
doc_options.storage_map_kind = StorageMapKind::DOC;
auto doc_assembler = create_assembler(std::move(doc_options));
ASSERT_NE(doc_assembler, nullptr);
VariantAssemblerBatchView doc_batch;
doc_batch.num_rows = 2;
doc_batch.storage_map = same_stream.get();
ColumnNullable::MutablePtr doc_output;
ASSERT_TRUE(doc_assembler->assemble(doc_batch, &doc_output).ok());
EXPECT_EQ(json_at(assembled_values(doc_output), 0), R"({"b":"1970-01-03"})");
EXPECT_EQ(json_at(assembled_values(doc_output), 1), R"("1970-01-03")");
auto sparse_child = map_column_rows({
{{"a.b", source.date_cells[1]}},
{},
});
VariantAssemblerOptions materialized_ancestor_options;
materialized_ancestor_options.requested_path = PathInData("a");
materialized_ancestor_options.materialized_paths.push_back(
{.path = PathInData("a"), .type = source.date_type});
materialized_ancestor_options.storage_map_kind = StorageMapKind::SPARSE;
auto materialized_ancestor_assembler =
create_assembler(std::move(materialized_ancestor_options));
ASSERT_NE(materialized_ancestor_assembler, nullptr);
const IColumn* materialized_ancestor = source.dates.get();
const ColumnMap* sparse_child_ptr = sparse_child.get();
VariantAssemblerBatchView materialized_ancestor_batch;
materialized_ancestor_batch.num_rows = 2;
materialized_ancestor_batch.materialized_columns = {&materialized_ancestor, 1};
materialized_ancestor_batch.storage_map = sparse_child_ptr;
ColumnNullable::MutablePtr materialized_ancestor_output;
ASSERT_TRUE(materialized_ancestor_assembler
->assemble(materialized_ancestor_batch, &materialized_ancestor_output)
.ok());
EXPECT_EQ(json_at(assembled_values(materialized_ancestor_output), 0), R"({"b":"1970-01-03"})");
EXPECT_EQ(json_at(assembled_values(materialized_ancestor_output), 1), R"("1970-01-03")");
auto sparse_ancestor = map_column_rows({
{{"a", source.date_cells[0]}},
{{"a", source.date_cells[1]}},
});
VariantAssemblerOptions materialized_descendant_options;
materialized_descendant_options.requested_path = PathInData("a");
materialized_descendant_options.materialized_paths.push_back(
{.path = PathInData("a.b"), .type = source.date_type});
materialized_descendant_options.storage_map_kind = StorageMapKind::SPARSE;
auto materialized_descendant_assembler =
create_assembler(std::move(materialized_descendant_options));
ASSERT_NE(materialized_descendant_assembler, nullptr);
const IColumn* materialized_descendant = source.dates.get();
const ColumnMap* sparse_ancestor_ptr = sparse_ancestor.get();
VariantAssemblerBatchView materialized_descendant_batch;
materialized_descendant_batch.num_rows = 2;
materialized_descendant_batch.materialized_columns = {&materialized_descendant, 1};
materialized_descendant_batch.storage_map = sparse_ancestor_ptr;
ColumnNullable::MutablePtr materialized_descendant_output;
ASSERT_TRUE(materialized_descendant_assembler
->assemble(materialized_descendant_batch, &materialized_descendant_output)
.ok());
EXPECT_EQ(json_at(assembled_values(materialized_descendant_output), 0),
R"({"b":"1970-01-02"})");
EXPECT_EQ(json_at(assembled_values(materialized_descendant_output), 1),
R"({"b":"1970-01-03"})");
}
TEST(VariantAssemblerLegacyTest, RawOrderedConflictsKeepDescendantsWithoutSorting) {
LegacyCells source;
auto values = map_column_rows({
{{"a-", source.date_cells[0]}, {"a.b", source.date_cells[1]}},
{{"a", source.date_cells[0]},
{"a-", source.date_cells[0]},
{"a-.b", source.date_cells[1]},
{"a.b", source.date_cells[1]}},
});
constexpr std::array<std::string_view, 2> EXPECTED {
R"({"a":{"b":"1970-01-03"},"a-":"1970-01-02"})",
R"({"a":{"b":"1970-01-03"},"a-":{"b":"1970-01-03"}})",
};
VariantAssemblerOptions sparse_options;
sparse_options.storage_map_kind = StorageMapKind::SPARSE;
auto sparse_assembler = create_assembler(std::move(sparse_options));
ASSERT_NE(sparse_assembler, nullptr);
const ColumnMap* sparse = values.get();
VariantAssemblerBatchView sparse_batch;
sparse_batch.num_rows = 2;
sparse_batch.storage_map = sparse;
ColumnNullable::MutablePtr sparse_output;
ASSERT_TRUE(sparse_assembler->assemble(sparse_batch, &sparse_output).ok());
EXPECT_EQ(json_at(assembled_values(sparse_output), 0), EXPECTED[0]);
EXPECT_EQ(json_at(assembled_values(sparse_output), 1), EXPECTED[1]);
VariantAssemblerOptions doc_options;
doc_options.storage_map_kind = StorageMapKind::DOC;
auto doc_assembler = create_assembler(std::move(doc_options));
ASSERT_NE(doc_assembler, nullptr);
VariantAssemblerBatchView doc_batch;
doc_batch.num_rows = 2;
doc_batch.storage_map = values.get();
ColumnNullable::MutablePtr doc_output;
ASSERT_TRUE(doc_assembler->assemble(doc_batch, &doc_output).ok());
EXPECT_EQ(json_at(assembled_values(doc_output), 0), EXPECTED[0]);
EXPECT_EQ(json_at(assembled_values(doc_output), 1), EXPECTED[1]);
auto materialized_a = ColumnDate::create();
materialized_a->insert_value(source.dates->get_data()[0]);
auto sparse_interposed = map_column_rows({
{{"a-", source.date_cells[0]}, {"a.b", source.date_cells[1]}},
});
VariantAssemblerOptions cross_source_options;
cross_source_options.materialized_paths.push_back(
{.path = PathInData("a"), .type = source.date_type});
cross_source_options.storage_map_kind = StorageMapKind::SPARSE;
auto cross_source_assembler = create_assembler(std::move(cross_source_options));
ASSERT_NE(cross_source_assembler, nullptr);
const IColumn* materialized_a_ptr = materialized_a.get();
const ColumnMap* sparse_interposed_ptr = sparse_interposed.get();
VariantAssemblerBatchView cross_source_batch;
cross_source_batch.num_rows = 1;
cross_source_batch.materialized_columns = {&materialized_a_ptr, 1};
cross_source_batch.storage_map = sparse_interposed_ptr;
ColumnNullable::MutablePtr cross_source_output;
ASSERT_TRUE(cross_source_assembler->assemble(cross_source_batch, &cross_source_output).ok());
EXPECT_EQ(json_at(assembled_values(cross_source_output), 0), EXPECTED[0]);
auto materialized_root = ColumnDate::create();
materialized_root->insert_value(source.dates->get_data()[0]);
auto materialized_dash = ColumnDate::create();
materialized_dash->insert_value(source.dates->get_data()[0]);
auto materialized_child = ColumnDate::create();
materialized_child->insert_value(source.dates->get_data()[1]);
VariantAssemblerOptions materialized_options;
materialized_options.materialized_paths = {
{.path = PathInData("a"), .type = source.date_type},
{.path = PathInData("a-"), .type = source.date_type},
{.path = PathInData("a.b"), .type = source.date_type},
};
auto materialized_assembler = create_assembler(std::move(materialized_options));
const std::array<const IColumn*, 3> materialized_columns {
materialized_root.get(), materialized_dash.get(), materialized_child.get()};
VariantAssemblerBatchView materialized_batch;
materialized_batch.num_rows = 1;
materialized_batch.materialized_columns = materialized_columns;
ColumnNullable::MutablePtr materialized_output;
ASSERT_TRUE(materialized_assembler->assemble(materialized_batch, &materialized_output).ok());
EXPECT_EQ(json_at(assembled_values(materialized_output), 0), EXPECTED[0]);
}
TEST(VariantAssemblerLegacyTest, EmptyObjectKeyAncestorPrefersDescendantWithoutNullMemcmp) {
LegacyCells source;
auto values = map_column({{"", source.date_cells[0]}, {".child", source.date_cells[1]}});
VariantAssemblerOptions options;
options.storage_map_kind = StorageMapKind::DOC;
auto assembler = create_assembler(std::move(options));
ASSERT_NE(assembler, nullptr);
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.storage_map = values.get();
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"":{"child":"1970-01-03"}})");
VariantAssemblerOptions subtree_options;
subtree_options.requested_path = PathInData("");
subtree_options.storage_map_kind = StorageMapKind::DOC;
auto subtree_assembler = create_assembler(std::move(subtree_options));
ColumnNullable::MutablePtr subtree_output;
ASSERT_TRUE(subtree_assembler->assemble(batch, &subtree_output).ok());
EXPECT_EQ(json_at(assembled_values(subtree_output), 0), R"({"child":"1970-01-03"})");
}
TEST(VariantAssemblerLegacyTest, RequestedPathUsesExactDotBoundary) {
LegacyCells source;
auto values = map_column_rows({
{{"a.b", source.date_cells[0]},
{"a.b-", source.date_cells[0]},
{"a.b.c", source.date_cells[1]}},
{{"a.b-", source.date_cells[0]}},
});
VariantAssemblerOptions options;
options.requested_path = PathInData("a.b");
options.storage_map_kind = StorageMapKind::SPARSE;
auto assembler = create_assembler(std::move(options));
VariantAssemblerBatchView batch;
batch.num_rows = 2;
batch.storage_map = values.get();
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"c":"1970-01-03"})");
EXPECT_EQ(json_at(assembled_values(output), 1), "null");
EXPECT_EQ(output->get_null_map_data(), (PaddedPODArray<uint8_t> {0, 1}));
}
TEST(VariantAssemblerLegacyTest, LogicalRootAndEmptyKeyShareRawBytesButNotSemantics) {
LegacyCells source;
auto root = ColumnDate::create();
root->insert_value(source.dates->get_data()[0]);
auto empty_key = ColumnDate::create();
empty_key->insert_value(source.dates->get_data()[1]);
VariantAssemblerOptions options;
options.materialized_paths = {
{.path = PathInData(), .type = source.date_type},
{.path = PathInData(""), .type = source.date_type},
};
auto assembler = create_assembler(std::move(options));
const std::array<const IColumn*, 2> materialized {root.get(), empty_key.get()};
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.materialized_columns = materialized;
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), R"({"":"1970-01-03"})");
}
TEST(VariantAssemblerLegacyTest, EmptyDocRowKeepsRawOrderedMaterializedPaths) {
LegacyCells source;
auto dash = ColumnDate::create();
dash->insert_value(source.dates->get_data()[0]);
auto child = ColumnDate::create();
child->insert_value(source.dates->get_data()[1]);
auto empty_doc = map_column_rows({{}});
VariantAssemblerOptions options;
options.storage_map_kind = StorageMapKind::DOC;
options.materialized_paths = {
{.path = PathInData("a-"), .type = source.date_type},
{.path = PathInData("a.b"), .type = source.date_type},
};
auto assembler = create_assembler(std::move(options));
ASSERT_NE(assembler, nullptr);
const std::array<const IColumn*, 2> materialized {dash.get(), child.get()};
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.materialized_columns = materialized;
batch.storage_map = empty_doc.get();
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0),
R"({"a":{"b":"1970-01-03"},"a-":"1970-01-02"})");
}
TEST(VariantAssemblerLegacyTest, EmptyPhysicalRowsPublishAsNull) {
VariantAssemblerOptions root_options;
root_options.has_root = true;
auto root_assembler = create_assembler(std::move(root_options));
ASSERT_NE(root_assembler, nullptr);
auto empty_root = ColumnString::create();
empty_root->insert_default();
empty_root->insert_default();
auto root_nulls = ColumnUInt8::create();
root_nulls->insert_value(0);
root_nulls->insert_value(1);
auto nullable_root = ColumnNullable::create(std::move(empty_root), std::move(root_nulls));
VariantAssemblerBatchView root_batch;
root_batch.num_rows = 2;
root_batch.root_jsonb = nullable_root.get();
ColumnNullable::MutablePtr root_output;
ASSERT_TRUE(root_assembler->assemble(root_batch, &root_output).ok());
EXPECT_EQ(json_at(assembled_values(root_output), 0), "null");
EXPECT_EQ(json_at(assembled_values(root_output), 1), "null");
EXPECT_EQ(root_output->get_null_map_data(), (PaddedPODArray<uint8_t> {1, 1}));
VariantAssemblerOptions subtree_options;
subtree_options.requested_path = PathInData("a");
subtree_options.storage_map_kind = StorageMapKind::SPARSE;
auto subtree_assembler = create_assembler(std::move(subtree_options));
ASSERT_NE(subtree_assembler, nullptr);
auto empty_sparse = map_column_rows({{}});
VariantAssemblerBatchView subtree_batch;
subtree_batch.num_rows = 1;
subtree_batch.storage_map = empty_sparse.get();
ColumnNullable::MutablePtr subtree_output;
ASSERT_TRUE(subtree_assembler->assemble(subtree_batch, &subtree_output).ok());
EXPECT_EQ(json_at(assembled_values(subtree_output), 0), "null");
EXPECT_EQ(subtree_output->get_null_map_data(), (PaddedPODArray<uint8_t> {1}));
}
TEST(VariantAssemblerLegacyTest, HierarchicalStorageKeepsPresentVariantNullsInsidePayload) {
VariantAssemblerOptions root_options;
root_options.has_root = true;
auto root_assembler = create_assembler(std::move(root_options));
ASSERT_NE(root_assembler, nullptr);
auto root = jsonb_column("null");
VariantAssemblerBatchView root_batch;
root_batch.num_rows = 1;
root_batch.root_jsonb = root.get();
ColumnNullable::MutablePtr root_output;
ASSERT_TRUE(root_assembler->assemble(root_batch, &root_output).ok());
EXPECT_EQ(json_at(assembled_values(root_output), 0), "null");
EXPECT_EQ(root_output->get_null_map_data(), (PaddedPODArray<uint8_t> {0}));
VariantAssemblerOptions doc_options;
doc_options.storage_map_kind = StorageMapKind::DOC;
doc_options.requested_path = PathInData("a");
auto doc_assembler = create_assembler(std::move(doc_options));
ASSERT_NE(doc_assembler, nullptr);
auto doc = map_column({{"a", jsonb_storage_cell("null")}});
VariantAssemblerBatchView doc_batch;
doc_batch.num_rows = 1;
doc_batch.storage_map = doc.get();
ColumnNullable::MutablePtr doc_output;
ASSERT_TRUE(doc_assembler->assemble(doc_batch, &doc_output).ok());
EXPECT_EQ(json_at(assembled_values(doc_output), 0), "null");
EXPECT_EQ(doc_output->get_null_map_data(), (PaddedPODArray<uint8_t> {0}));
}
TEST(VariantAssemblerLegacyTest, EmptyHierarchicalRootRowRemainsAnObject) {
VariantAssemblerOptions options;
options.storage_map_kind = StorageMapKind::DOC;
auto assembler = create_assembler(std::move(options));
ASSERT_NE(assembler, nullptr);
auto doc = map_column({});
VariantAssemblerBatchView batch;
batch.num_rows = 1;
batch.storage_map = doc.get();
ColumnNullable::MutablePtr output;
ASSERT_TRUE(assembler->assemble(batch, &output).ok());
EXPECT_EQ(json_at(assembled_values(output), 0), "{}");
EXPECT_EQ(output->get_null_map_data(), (PaddedPODArray<uint8_t> {0}));
}
TEST(VariantAssemblerLegacyTest, NestedArrayPathReturnsNotSupported) {
PathInDataBuilder requested_builder;
requested_builder.append("items", false);
requested_builder.append("id", true);
VariantAssemblerOptions requested_options;
requested_options.requested_path = requested_builder.build();
auto requested = VariantAssembler::create(std::move(requested_options));
ASSERT_FALSE(requested.has_value());
EXPECT_TRUE(requested.error().is<ErrorCode::NOT_IMPLEMENTED_ERROR>()) << requested.error();
EXPECT_NE(requested.error().to_string().find("nested array path 'items.id'"),
std::string::npos);
PathInDataBuilder materialized_builder;
materialized_builder.append("items", false);
materialized_builder.append("id", true);
VariantAssemblerOptions materialized_options;
materialized_options.materialized_paths.push_back(
{.path = materialized_builder.build(), .type = std::make_shared<DataTypeInt32>()});
auto materialized = VariantAssembler::create(std::move(materialized_options));
ASSERT_FALSE(materialized.has_value());
EXPECT_TRUE(materialized.error().is<ErrorCode::NOT_IMPLEMENTED_ERROR>())
<< materialized.error();
EXPECT_NE(materialized.error().to_string().find("nested array path 'items.id'"),
std::string::npos);
}
} // namespace
} // namespace doris::segment_v2::variant_v2