blob: 7e90225c6dd25637e8e7e78a61b759bfe82a9f31 [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 "exprs/function/parse/variant_jsonb_parse.h"
#include <cctz/time_zone.h>
#include <gtest/gtest.h>
#include <array>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <limits>
#include <ranges>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "common/exception.h"
#include "core/value/variant/variant_batch_builder.h"
#include "core/value/variant/variant_canonical.h"
#include "util/jsonb_writer.h"
#include "variant_test_utils.h"
namespace doris {
namespace {
struct OwnedVariant {
std::string metadata;
std::string value;
VariantRef ref() const {
return {.metadata = {.data = metadata.data(), .size = metadata.size()},
.value = {value.data(), value.size()}};
}
};
StringRef string_ref(std::string_view value) {
return {value.data(), value.size()};
}
template <typename Function>
void expect_jsonb_exception_code(int code, Function&& function) {
try {
function();
FAIL() << "Expected doris::Exception";
} catch (const Exception& exception) {
EXPECT_EQ(exception.code(), code) << exception.what();
}
}
std::string jsonb_bytes(JsonbWriter& writer) {
return {writer.getOutput()->getBuffer(), static_cast<size_t>(writer.getOutput()->getSize())};
}
OwnedVariant encode_jsonb(std::string_view document) {
VariantBatchBuilder builder;
auto row = builder.begin_row();
jsonb_to_variant(string_ref(document), row);
row.finish();
VariantBatchBuilder block = builder.finish_batch();
validate_canonical(block.value_at(0));
const VariantMetadataRef metadata = block.metadata_ref();
const VariantRef value = block.value_at(0);
return {.metadata = std::string(metadata.data, metadata.size),
.value = std::string(value.value.data, value.value.size)};
}
std::string block_value_bytes(const VariantBatchBuilder& block) {
const StringRef bytes = block.value_bytes();
return {bytes.data, bytes.size};
}
std::vector<uint32_t> block_value_offsets(const VariantBatchBuilder& block) {
const std::span<const uint32_t> offsets = block.value_offsets();
return {offsets.begin(), offsets.end()};
}
void expect_jsonb_failure_and_aborted( // NOLINT(readability-function-cognitive-complexity) --
std::string_view document) { // GoogleTest exception macros inflate the metric.
VariantBatchBuilder builder;
auto row = builder.begin_row();
EXPECT_THROW(jsonb_to_variant(string_ref(document), row), Exception);
EXPECT_EQ(builder.finish_batch().num_rows(), 0);
}
template <typename Decimal>
void expect_invalid_jsonb_decimal(Decimal value, uint32_t precision, uint32_t scale) {
JsonbWriter writer;
EXPECT_TRUE(writer.writeDecimal(value, precision, scale));
expect_jsonb_failure_and_aborted(jsonb_bytes(writer));
}
template <typename T>
void append_pod(std::string& bytes, const T& value) {
bytes.append(reinterpret_cast<const char*>(&value), sizeof(T));
}
std::string nested_jsonb_arrays(uint32_t count) {
std::string value(1, static_cast<char>(JsonbType::T_Null));
for (uint32_t index = 0; index < count; ++index) {
std::string parent;
parent.push_back(static_cast<char>(JsonbType::T_Array));
const auto size = static_cast<uint32_t>(value.size());
append_pod(parent, size);
parent.append(value);
value = std::move(parent);
}
value.insert(value.begin(), static_cast<char>(JSONB_VER));
return value;
}
__int128 power_of_ten_128(uint8_t exponent) {
__int128 value = 1;
for (uint8_t digit = 0; digit < exponent; ++digit) {
value *= 10;
}
return value;
}
template <typename Fill>
OwnedVariant build_variant(Fill&& fill) {
VariantBatchBuilder builder;
auto row = builder.begin_row();
fill(row);
row.finish();
VariantBatchBuilder block = builder.finish_batch();
validate_canonical(block.value_at(0));
const VariantMetadataRef metadata = block.metadata_ref();
const VariantRef value = block.value_at(0);
return {.metadata = std::string(metadata.data, metadata.size),
.value = std::string(value.value.data, value.value.size)};
}
std::string encode_variant_jsonb(
VariantRef value, const VariantJsonFormatOptions& options = VariantJsonFormatOptions {}) {
JsonbWriter writer;
variant_to_jsonb(value, writer, options);
return jsonb_bytes(writer);
}
const JsonbValue* jsonb_root(const std::string& document) {
const JsonbDocument* parsed = nullptr;
const Status status =
JsonbDocument::checkAndCreateDocument(document.data(), document.size(), &parsed);
if (!status.ok()) {
throw Exception(ErrorCode::INTERNAL_ERROR, "Failed to inspect generated JSONB: {}",
status.to_string());
}
return parsed->getValue();
}
std::string_view jsonb_blob(const JsonbValue* value) {
const auto* blob = value->unpack<JsonbBinaryVal>();
return {blob->getBlob(), blob->getBlobLen()};
}
OwnedVariant nested_variant_arrays(uint32_t count) {
return build_variant([&](VariantBatchBuilder::Row& builder) {
std::vector<VariantBatchBuilder::Row::ArrayScope> scopes;
scopes.reserve(count);
for (uint32_t index = 0; index < count; ++index) {
scopes.push_back(builder.start_array());
}
builder.add_null();
for (auto& scope : std::ranges::reverse_view(scopes)) {
scope.finish();
}
});
}
TEST(VariantJsonbTest, JsonbNullToVariant) {
JsonbWriter writer;
ASSERT_TRUE(writer.writeNull());
EXPECT_TRUE(encode_jsonb(jsonb_bytes(writer)).ref().is_null());
EXPECT_TRUE(encode_jsonb({}).ref().is_null());
}
TEST(VariantJsonbTest, AllJsonbTypesMapToCanonicalVariant) {
JsonbWriter writer;
ASSERT_TRUE(writer.writeStartArray());
ASSERT_TRUE(writer.writeNull());
ASSERT_TRUE(writer.writeBool(true));
ASSERT_TRUE(writer.writeBool(false));
ASSERT_TRUE(writer.writeInt8(7));
ASSERT_TRUE(writer.writeInt16(300));
ASSERT_TRUE(writer.writeInt32(70'000));
ASSERT_TRUE(writer.writeInt64(5'000'000'000));
ASSERT_TRUE(writer.writeDouble(-1.25));
ASSERT_TRUE(writer.writeStartString());
const std::string string_with_nul("a\0", 2);
ASSERT_TRUE(writer.writeString(string_with_nul.data(), string_with_nul.size()));
ASSERT_TRUE(writer.writeEndString());
ASSERT_TRUE(writer.writeStartBinary());
const std::string binary("\0\xFF", 2);
ASSERT_TRUE(writer.writeBinary(binary.data(), binary.size()));
ASSERT_TRUE(writer.writeEndBinary());
ASSERT_TRUE(writer.writeStartObject());
ASSERT_TRUE(writer.writeKey("k", 1));
ASSERT_TRUE(writer.writeNull());
ASSERT_TRUE(writer.writeEndObject());
ASSERT_TRUE(writer.writeStartArray());
ASSERT_TRUE(writer.writeEndArray());
ASSERT_TRUE(writer.writeInt128(power_of_ten_128(20)));
ASSERT_TRUE(writer.writeFloat(1.5F));
ASSERT_TRUE(writer.writeDecimal(Decimal32 {123}, 3, 38));
ASSERT_TRUE(writer.writeDecimal(Decimal64 {int64_t(12'345'678'901)}, 11, 38));
ASSERT_TRUE(writer.writeDecimal(Decimal128V3 {power_of_ten_128(20)}, 21, 6));
ASSERT_TRUE(writer.writeEndArray());
const OwnedVariant encoded = encode_jsonb(jsonb_bytes(writer));
const VariantRef root = encoded.ref();
ASSERT_EQ(root.num_elements(), 17);
EXPECT_TRUE(root.array_at(0).is_null());
EXPECT_TRUE(root.array_at(1).get_bool());
EXPECT_FALSE(root.array_at(2).get_bool());
EXPECT_EQ(root.array_at(3).primitive_id(), VariantPrimitiveId::INT8);
EXPECT_EQ(root.array_at(4).primitive_id(), VariantPrimitiveId::INT16);
EXPECT_EQ(root.array_at(5).primitive_id(), VariantPrimitiveId::INT32);
EXPECT_EQ(root.array_at(6).primitive_id(), VariantPrimitiveId::INT64);
EXPECT_DOUBLE_EQ(root.array_at(7).get_double(), -1.25);
EXPECT_EQ(root.array_at(8).get_string(), StringRef(string_with_nul));
EXPECT_EQ(root.array_at(9).get_binary(), StringRef(binary));
VariantRef object_child;
ASSERT_TRUE(root.array_at(10).object_find(string_ref("k"), &object_child));
EXPECT_TRUE(object_child.is_null());
EXPECT_EQ(root.array_at(11).num_elements(), 0);
EXPECT_EQ(root.array_at(12).get_decimal(), (VariantDecimal {power_of_ten_128(20), 0, 16}));
EXPECT_FLOAT_EQ(root.array_at(13).get_float(), 1.5F);
EXPECT_EQ(root.array_at(14).get_decimal(), (VariantDecimal {123, 38, 4}));
EXPECT_EQ(root.array_at(15).get_decimal(), (VariantDecimal {12'345'678'901, 38, 8}));
EXPECT_EQ(root.array_at(16).get_decimal(), (VariantDecimal {power_of_ten_128(20), 6, 16}));
const OwnedVariant semantic_roundtrip = encode_jsonb(encode_variant_jsonb(root));
EXPECT_TRUE(canonical_equals(root, semantic_roundtrip.ref()));
}
TEST(VariantJsonbTest, Int128BoundariesUseDecimal16OrStringFallback) {
const __int128 maximum_decimal = power_of_ten_128(38) - 1;
const __int128 first_fallback = power_of_ten_128(38);
JsonbWriter writer;
ASSERT_TRUE(writer.writeStartArray());
for (__int128 value :
{maximum_decimal, -maximum_decimal, first_fallback, -first_fallback,
std::numeric_limits<__int128>::max(), std::numeric_limits<__int128>::min()}) {
ASSERT_TRUE(writer.writeInt128(value));
}
ASSERT_TRUE(writer.writeEndArray());
const OwnedVariant encoded = encode_jsonb(jsonb_bytes(writer));
const VariantRef root = encoded.ref();
EXPECT_EQ(root.array_at(0).get_decimal(), (VariantDecimal {maximum_decimal, 0, 16}));
EXPECT_EQ(root.array_at(1).get_decimal(), (VariantDecimal {-maximum_decimal, 0, 16}));
EXPECT_EQ(root.array_at(2).get_string(), string_ref("100000000000000000000000000000000000000"));
EXPECT_EQ(root.array_at(3).get_string(),
string_ref("-100000000000000000000000000000000000000"));
EXPECT_EQ(root.array_at(4).get_string(), string_ref("170141183460469231731687303715884105727"));
EXPECT_EQ(root.array_at(5).get_string(),
string_ref("-170141183460469231731687303715884105728"));
}
TEST(VariantJsonbTest, DecimalScale38IsEncodedCanonically) {
JsonbWriter valid;
ASSERT_TRUE(valid.writeStartArray());
ASSERT_TRUE(valid.writeDecimal(Decimal32 {1}, 1, 38));
ASSERT_TRUE(valid.writeDecimal(Decimal64 {1}, 1, 38));
ASSERT_TRUE(valid.writeDecimal(Decimal128V3 {-1}, 1, 38));
ASSERT_TRUE(valid.writeEndArray());
const OwnedVariant encoded = encode_jsonb(jsonb_bytes(valid));
EXPECT_EQ(encoded.ref().array_at(0).get_decimal(), (VariantDecimal {1, 38, 4}));
EXPECT_EQ(encoded.ref().array_at(1).get_decimal(), (VariantDecimal {1, 38, 8}));
EXPECT_EQ(encoded.ref().array_at(2).get_decimal(), (VariantDecimal {-1, 38, 16}));
}
TEST(VariantJsonbTest, InvalidDecimalMetadataAndPrecisionAbortRow) {
expect_invalid_jsonb_decimal(Decimal32 {1}, 0, 0);
expect_invalid_jsonb_decimal(Decimal32 {1}, 10, 0);
expect_invalid_jsonb_decimal(Decimal32 {100}, 2, 0);
expect_invalid_jsonb_decimal(Decimal32 {1}, 1, 39);
expect_invalid_jsonb_decimal(Decimal64 {1}, 0, 0);
expect_invalid_jsonb_decimal(Decimal64 {1}, 19, 0);
expect_invalid_jsonb_decimal(Decimal64 {100}, 2, 0);
expect_invalid_jsonb_decimal(Decimal64 {1}, 1, 39);
expect_invalid_jsonb_decimal(Decimal128V3 {1}, 0, 0);
expect_invalid_jsonb_decimal(Decimal128V3 {1}, 39, 0);
expect_invalid_jsonb_decimal(Decimal128V3 {100}, 2, 0);
expect_invalid_jsonb_decimal(Decimal128V3 {1}, 1, 39);
}
TEST(VariantJsonbTest, Decimal256IsRejectedAndAbortsRow) {
JsonbWriter decimal256;
ASSERT_TRUE(decimal256.writeDecimal(Decimal256 {wide::Int256(-12'345)}, 5, 3));
const std::string decimal256_document = jsonb_bytes(decimal256);
VariantBatchBuilder builder;
auto row = builder.begin_row();
try {
jsonb_to_variant(StringRef(decimal256_document), row);
FAIL() << "Expected DECIMAL256 rejection";
} catch (const Exception& exception) {
EXPECT_EQ(exception.code(), ErrorCode::INVALID_ARGUMENT);
EXPECT_NE(std::string_view(exception.what()).find("DECIMAL256"), std::string_view::npos);
}
EXPECT_EQ(builder.finish_batch().num_rows(), 0);
}
TEST(VariantJsonbTest, // NOLINT(readability-function-cognitive-complexity) --
FixedWidthRootTruncationAndNullPointerFail) { // GoogleTest assertions inflate the metric.
std::vector<std::string> documents;
const auto add_document = [&](auto&& write) {
JsonbWriter writer;
ASSERT_TRUE(write(writer));
documents.push_back(jsonb_bytes(writer));
};
add_document([](JsonbWriter& writer) { return writer.writeInt8(1); });
add_document([](JsonbWriter& writer) { return writer.writeInt16(1); });
add_document([](JsonbWriter& writer) { return writer.writeInt32(1); });
add_document([](JsonbWriter& writer) { return writer.writeInt64(1); });
add_document([](JsonbWriter& writer) { return writer.writeInt128(1); });
add_document([](JsonbWriter& writer) { return writer.writeFloat(1.0F); });
add_document([](JsonbWriter& writer) { return writer.writeDouble(1.0); });
add_document([](JsonbWriter& writer) { return writer.writeDecimal(Decimal32 {1}, 1, 0); });
add_document([](JsonbWriter& writer) { return writer.writeDecimal(Decimal64 {1}, 1, 0); });
add_document([](JsonbWriter& writer) { return writer.writeDecimal(Decimal128V3 {1}, 1, 0); });
add_document([](JsonbWriter& writer) {
return writer.writeDecimal(Decimal256 {wide::Int256(1)}, 1, 0);
});
for (const std::string& document : documents) {
for (size_t size = 1; size < document.size(); ++size) {
expect_jsonb_failure_and_aborted(std::string_view(document.data(), size));
}
}
VariantBatchBuilder builder;
auto row = builder.begin_row();
EXPECT_THROW(jsonb_to_variant({static_cast<const char*>(nullptr), 1}, row), Exception);
EXPECT_EQ(builder.finish_batch().num_rows(), 0);
}
TEST(VariantJsonbTest, ContainersEmptyKeyAndMaximumKeyLength) {
const std::string maximum_key(255, 'k');
JsonbWriter writer;
ASSERT_TRUE(writer.writeStartObject());
ASSERT_TRUE(writer.writeKey("", 0));
ASSERT_TRUE(writer.writeStartObject());
ASSERT_TRUE(writer.writeEndObject());
ASSERT_TRUE(writer.writeKey(maximum_key.data(), static_cast<uint8_t>(maximum_key.size())));
ASSERT_TRUE(writer.writeStartArray());
ASSERT_TRUE(writer.writeNull());
ASSERT_TRUE(writer.writeEndArray());
ASSERT_TRUE(writer.writeEndObject());
const OwnedVariant encoded = encode_jsonb(jsonb_bytes(writer));
VariantRef child;
ASSERT_TRUE(encoded.ref().object_find(string_ref(""), &child));
EXPECT_EQ(child.num_elements(), 0);
ASSERT_TRUE(encoded.ref().object_find(StringRef(maximum_key), &child));
ASSERT_EQ(child.num_elements(), 1);
EXPECT_TRUE(child.array_at(0).is_null());
}
TEST(VariantJsonbTest, ExternalDictionaryIdsAndDuplicateKeysFail) {
JsonbWriter dictionary_key;
ASSERT_TRUE(dictionary_key.writeStartObject());
ASSERT_TRUE(dictionary_key.writeKey(JsonbKeyValue::keyid_type {42}));
ASSERT_TRUE(dictionary_key.writeNull());
ASSERT_TRUE(dictionary_key.writeEndObject());
expect_jsonb_failure_and_aborted(jsonb_bytes(dictionary_key));
JsonbWriter duplicate;
ASSERT_TRUE(duplicate.writeStartObject());
ASSERT_TRUE(duplicate.writeKey("a", 1));
ASSERT_TRUE(duplicate.writeNull());
ASSERT_TRUE(duplicate.writeKey("a", 1));
ASSERT_TRUE(duplicate.writeBool(true));
ASSERT_TRUE(duplicate.writeEndObject());
expect_jsonb_failure_and_aborted(jsonb_bytes(duplicate));
}
TEST(VariantJsonbTest, JsonbContainerDepthUsesWriterLimit) {
EXPECT_NO_THROW(static_cast<void>(encode_jsonb(nested_jsonb_arrays(MaxNestingLevel))));
expect_jsonb_failure_and_aborted(nested_jsonb_arrays(MaxNestingLevel + 1));
}
TEST(VariantJsonbTest, // NOLINT(readability-function-cognitive-complexity) --
MalformedAndTruncatedDocumentsFailDeepValidation) { // GoogleTest assertions inflate metric.
JsonbWriter string_writer;
ASSERT_TRUE(string_writer.writeStartString());
ASSERT_TRUE(string_writer.writeString("value", 5));
ASSERT_TRUE(string_writer.writeEndString());
JsonbWriter binary_writer;
ASSERT_TRUE(binary_writer.writeStartBinary());
ASSERT_TRUE(binary_writer.writeBinary("value", 5));
ASSERT_TRUE(binary_writer.writeEndBinary());
JsonbWriter object_writer;
ASSERT_TRUE(object_writer.writeStartObject());
ASSERT_TRUE(object_writer.writeKey("key", 3));
ASSERT_TRUE(object_writer.writeInt64(1));
ASSERT_TRUE(object_writer.writeEndObject());
JsonbWriter decimal_writer;
ASSERT_TRUE(decimal_writer.writeDecimal(Decimal128V3 {123}, 3, 2));
for (const std::string& valid : {jsonb_bytes(string_writer), jsonb_bytes(binary_writer),
jsonb_bytes(object_writer), jsonb_bytes(decimal_writer)}) {
for (size_t size = 1; size < valid.size(); ++size) {
expect_jsonb_failure_and_aborted(std::string_view(valid.data(), size));
}
}
std::string bad_version = jsonb_bytes(string_writer);
bad_version[0] = 2;
expect_jsonb_failure_and_aborted(bad_version);
std::string unknown_type {static_cast<char>(JSONB_VER), static_cast<char>(0x7F)};
expect_jsonb_failure_and_aborted(unknown_type);
std::string trailing = jsonb_bytes(string_writer);
trailing.push_back('\0');
expect_jsonb_failure_and_aborted(trailing);
JsonbWriter invalid_utf8_string;
ASSERT_TRUE(invalid_utf8_string.writeStartString());
const char invalid_byte = static_cast<char>(0xFF);
ASSERT_TRUE(invalid_utf8_string.writeString(&invalid_byte, 1));
ASSERT_TRUE(invalid_utf8_string.writeEndString());
expect_jsonb_failure_and_aborted(jsonb_bytes(invalid_utf8_string));
JsonbWriter invalid_utf8_key;
ASSERT_TRUE(invalid_utf8_key.writeStartObject());
ASSERT_TRUE(invalid_utf8_key.writeKey(&invalid_byte, 1));
ASSERT_TRUE(invalid_utf8_key.writeNull());
ASSERT_TRUE(invalid_utf8_key.writeEndObject());
expect_jsonb_failure_and_aborted(jsonb_bytes(invalid_utf8_key));
}
TEST(VariantJsonbTest, // NOLINT(readability-function-cognitive-complexity) --
AllVariantPrimitiveTypesMapDirectlyToJsonb) { // GoogleTest assertions inflate the metric.
std::array<uint8_t, 16> uuid {};
for (uint8_t index = 0; index < uuid.size(); ++index) {
uuid[index] = index;
}
const std::string binary("\0\x01\x02\xFF", 4);
const std::string long_string(64, 's');
const OwnedVariant encoded = build_variant([&](VariantBatchBuilder::Row& builder) {
auto array = builder.start_array();
builder.add_null();
builder.add_bool(true);
builder.add_bool(false);
builder.add_int(-128);
builder.add_int(-129);
builder.add_int(32'768);
builder.add_int(2'147'483'648LL);
builder.add_double(1.25);
builder.add_decimal(-12'345, 2, 4);
builder.add_decimal(1'000'000'000, 3, 8);
builder.add_decimal(static_cast<__int128>(1'000'000'000'000'000'000LL), 0, 16);
builder.add_date(0);
builder.add_timestamp_micros(0, true);
builder.add_timestamp_micros(-1, false);
builder.add_float(-0.0F);
builder.add_binary(StringRef(binary));
builder.add_string(StringRef(long_string));
builder.add_time_ntz_micros(1);
builder.add_timestamp_nanos(-1, true);
builder.add_timestamp_nanos(-1, false);
builder.add_uuid(uuid);
builder.add_string(string_ref("short"));
array.finish();
});
const std::string document = encode_variant_jsonb(encoded.ref());
const JsonbValue* root = jsonb_root(document);
ASSERT_EQ(root->type, JsonbType::T_Array);
const auto* array = root->unpack<ArrayVal>();
ASSERT_EQ(array->numElem(), 22);
const std::array<JsonbType, 22> expected_types {
JsonbType::T_Null, JsonbType::T_True, JsonbType::T_False,
JsonbType::T_Int8, JsonbType::T_Int16, JsonbType::T_Int32,
JsonbType::T_Int64, JsonbType::T_Double, JsonbType::T_Decimal32,
JsonbType::T_Decimal64, JsonbType::T_Decimal128, JsonbType::T_String,
JsonbType::T_String, JsonbType::T_String, JsonbType::T_Float,
JsonbType::T_Binary, JsonbType::T_String, JsonbType::T_String,
JsonbType::T_String, JsonbType::T_String, JsonbType::T_String,
JsonbType::T_String,
};
for (uint32_t index = 0; index < expected_types.size(); ++index) {
EXPECT_EQ(array->get(index)->type, expected_types[index]) << index;
}
const auto* decimal4 = array->get(8)->unpack<JsonbDecimal32>();
EXPECT_EQ(decimal4->precision, 9);
EXPECT_EQ(decimal4->scale, 2);
EXPECT_EQ(decimal4->val(), -12'345);
const auto* decimal8 = array->get(9)->unpack<JsonbDecimal64>();
EXPECT_EQ(decimal8->precision, 18);
EXPECT_EQ(decimal8->scale, 3);
EXPECT_EQ(decimal8->val(), 1'000'000'000);
const auto* decimal16 = array->get(10)->unpack<JsonbDecimal128>();
EXPECT_EQ(decimal16->precision, 38);
EXPECT_EQ(decimal16->scale, 0);
EXPECT_EQ(decimal16->val(), static_cast<__int128>(1'000'000'000'000'000'000LL));
EXPECT_EQ(jsonb_blob(array->get(11)), "1970-01-01");
EXPECT_EQ(jsonb_blob(array->get(12)), "1970-01-01 00:00:00.000000+00:00");
EXPECT_EQ(jsonb_blob(array->get(13)), "1969-12-31 23:59:59.999999");
EXPECT_EQ(jsonb_blob(array->get(15)), std::string_view(binary));
EXPECT_EQ(jsonb_blob(array->get(16)), std::string_view(long_string));
EXPECT_EQ(jsonb_blob(array->get(17)), "00:00:00.000001");
EXPECT_EQ(jsonb_blob(array->get(18)), "1969-12-31 23:59:59.999999999+00:00");
EXPECT_EQ(jsonb_blob(array->get(19)), "1969-12-31 23:59:59.999999999");
EXPECT_EQ(jsonb_blob(array->get(20)), "00010203-0405-0607-0809-0a0b0c0d0e0f");
EXPECT_EQ(jsonb_blob(array->get(21)), "short");
}
TEST(VariantJsonbTest, TimestampFallbackUsesSharedTimezoneContract) {
const OwnedVariant encoded = build_variant([](VariantBatchBuilder::Row& builder) {
auto array = builder.start_array();
builder.add_timestamp_micros(-1, true);
builder.add_timestamp_micros(-1, false);
builder.add_timestamp_nanos(-1, true);
builder.add_timestamp_nanos(-1, false);
array.finish();
});
const cctz::time_zone timezone = cctz::fixed_time_zone(std::chrono::seconds(19'800));
const VariantJsonFormatOptions options {.timezone = &timezone};
const std::string document = encode_variant_jsonb(encoded.ref(), options);
const auto* array = jsonb_root(document)->unpack<ArrayVal>();
EXPECT_EQ(jsonb_blob(array->get(0)), "1970-01-01 05:29:59.999999+05:30");
EXPECT_EQ(jsonb_blob(array->get(1)), "1969-12-31 23:59:59.999999");
EXPECT_EQ(jsonb_blob(array->get(2)), "1970-01-01 05:29:59.999999999+05:30");
EXPECT_EQ(jsonb_blob(array->get(3)), "1969-12-31 23:59:59.999999999");
}
TEST(VariantJsonbTest, DecimalScale38OutputIsAcceptedByJsonbReader) {
const OwnedVariant source = build_variant([](VariantBatchBuilder::Row& builder) {
auto array = builder.start_array();
builder.add_decimal(1, 38, 4);
builder.add_decimal(-1, 38, 8);
builder.add_decimal(42, 0, 16);
array.finish();
});
const OwnedVariant roundtrip = encode_jsonb(encode_variant_jsonb(source.ref()));
EXPECT_EQ(roundtrip.ref().array_at(0).get_decimal(), (VariantDecimal {1, 38, 4}));
EXPECT_EQ(roundtrip.ref().array_at(1).get_decimal(), (VariantDecimal {-1, 38, 8}));
EXPECT_EQ(roundtrip.ref().array_at(2).get_decimal(), (VariantDecimal {42, 0, 16}));
}
TEST(VariantJsonbTest, Int128FallbackRoundTrips) {
JsonbWriter small_int128;
ASSERT_TRUE(small_int128.writeInt128(42));
const OwnedVariant small_variant = encode_jsonb(jsonb_bytes(small_int128));
const std::string small_document = encode_variant_jsonb(small_variant.ref());
const JsonbValue* small_root = jsonb_root(small_document);
ASSERT_EQ(small_root->type, JsonbType::T_Decimal128);
const auto* small_decimal = small_root->unpack<JsonbDecimal128>();
EXPECT_EQ(small_decimal->precision, 38);
EXPECT_EQ(small_decimal->scale, 0);
EXPECT_EQ(small_decimal->val(), 42);
JsonbWriter large_int128;
ASSERT_TRUE(large_int128.writeInt128(std::numeric_limits<__int128>::max()));
const OwnedVariant large_variant = encode_jsonb(jsonb_bytes(large_int128));
const std::string large_document = encode_variant_jsonb(large_variant.ref());
const JsonbValue* large_root = jsonb_root(large_document);
ASSERT_EQ(large_root->type, JsonbType::T_String);
EXPECT_EQ(jsonb_blob(large_root), "170141183460469231731687303715884105727");
}
TEST(VariantJsonbTest, VariantEmptyContainersAndJsonbKeyBoundaries) {
const std::string maximum_key(255, 'm');
const OwnedVariant source = build_variant([&](VariantBatchBuilder::Row& builder) {
auto root = builder.start_array();
auto empty_object = builder.start_object();
empty_object.finish();
auto empty_array = builder.start_array();
empty_array.finish();
auto object = builder.start_object();
object.add_key(string_ref(""));
builder.add_null();
object.add_key(StringRef(maximum_key));
builder.add_bool(true);
object.finish();
root.finish();
});
const std::string document = encode_variant_jsonb(source.ref());
const auto* root = jsonb_root(document)->unpack<ArrayVal>();
ASSERT_EQ(root->numElem(), 3);
EXPECT_EQ(root->get(0)->unpack<ObjectVal>()->numElem(), 0);
EXPECT_EQ(root->get(1)->unpack<ArrayVal>()->numElem(), 0);
const auto* object = root->get(2)->unpack<ObjectVal>();
auto field = object->begin();
ASSERT_NE(field, object->end());
EXPECT_EQ(field->klen(), 0);
EXPECT_EQ(field->getKeyId(), std::numeric_limits<JsonbKeyValue::keyid_type>::max());
EXPECT_TRUE(field->value()->isNull());
++field;
ASSERT_NE(field, object->end());
EXPECT_EQ(field->klen(), 255);
EXPECT_EQ(std::string_view(field->getKeyStr(), field->klen()), maximum_key);
EXPECT_TRUE(field->value()->isTrue());
}
TEST(VariantJsonbTest, LegalNonCanonicalVariantObjectConvertsWithoutCanonicalAssumptions) {
const std::string metadata {char {0x01}, char {0x02}, char {0x00}, char {0x01},
char {0x02}, 'b', 'a'};
const std::string value {
char {0x02},
char {0x02},
char {0x01},
char {0x00},
char {0x01},
char {0x00},
char {0x02},
char {static_cast<uint8_t>(VariantPrimitiveId::FALSE_VALUE) << 2},
char {static_cast<uint8_t>(VariantPrimitiveId::TRUE_VALUE) << 2},
};
const VariantRef ref {.metadata = {.data = metadata.data(), .size = metadata.size()},
.value = {value.data(), value.size()}};
EXPECT_FALSE(ref.metadata.sorted_strings());
const std::string document = encode_variant_jsonb(ref);
const auto* object = jsonb_root(document)->unpack<ObjectVal>();
auto field = object->begin();
ASSERT_NE(field, object->end());
EXPECT_EQ(std::string_view(field->getKeyStr(), field->klen()), "a");
EXPECT_TRUE(field->value()->isTrue());
++field;
ASSERT_NE(field, object->end());
EXPECT_EQ(std::string_view(field->getKeyStr(), field->klen()), "b");
EXPECT_TRUE(field->value()->isFalse());
++field;
EXPECT_EQ(field, object->end());
}
TEST(VariantJsonbTest, VariantContainerDepthUsesWriterLimit) {
const OwnedVariant maximum = nested_variant_arrays(MaxNestingLevel);
EXPECT_NO_THROW(static_cast<void>(encode_variant_jsonb(maximum.ref())));
const OwnedVariant too_deep = nested_variant_arrays(MaxNestingLevel + 1);
JsonbWriter writer;
EXPECT_THROW(variant_to_jsonb(too_deep.ref(), writer), Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
}
TEST(VariantJsonbTest, // NOLINT(readability-function-cognitive-complexity) --
VariantFailuresResetWriterAndRejectUnrepresentableKeys) { // GoogleTest macros inflate metric.
const OwnedVariant valid =
build_variant([](VariantBatchBuilder::Row& builder) { builder.add_null(); });
OwnedVariant trailing = valid;
trailing.value.push_back('\0');
JsonbWriter writer;
ASSERT_TRUE(writer.writeBool(true));
EXPECT_THROW(variant_to_jsonb(trailing.ref(), writer), Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
EXPECT_NO_THROW(variant_to_jsonb(valid.ref(), writer));
EXPECT_TRUE(jsonb_root(jsonb_bytes(writer))->isNull());
const std::string oversized_key(256, 'k');
const OwnedVariant oversized = build_variant([&](VariantBatchBuilder::Row& builder) {
auto object = builder.start_object();
object.add_key(StringRef(oversized_key));
builder.add_null();
object.finish();
});
EXPECT_THROW(variant_to_jsonb(oversized.ref(), writer), Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
const OwnedVariant invalid_time = build_variant(
[](VariantBatchBuilder::Row& builder) { builder.add_time_ntz_micros(-1); });
EXPECT_THROW(variant_to_jsonb(invalid_time.ref(), writer), Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
}
TEST(VariantJsonbTest, MalformedVariantStringsAndDecimalsFailExplicitly) {
const std::string empty_metadata_bytes("\x11\0\0", 3);
const VariantMetadataRef empty_metadata {empty_metadata_bytes.data(),
empty_metadata_bytes.size()};
std::string invalid_string;
invalid_string.push_back(
static_cast<char>((1U << VARIANT_VALUE_HEADER_SHIFT) |
static_cast<uint8_t>(VariantBasicType::SHORT_STRING)));
invalid_string.push_back(static_cast<char>(0xFF));
JsonbWriter writer;
EXPECT_THROW(variant_to_jsonb({empty_metadata, {invalid_string.data(), invalid_string.size()}},
writer),
Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
std::string invalid_decimal;
invalid_decimal.push_back(static_cast<char>(static_cast<uint8_t>(VariantPrimitiveId::DECIMAL4)
<< VARIANT_VALUE_HEADER_SHIFT));
invalid_decimal.push_back('\0');
const int32_t ten_digit_value = 1'000'000'000;
append_pod(invalid_decimal, ten_digit_value);
EXPECT_THROW(
variant_to_jsonb({empty_metadata, {invalid_decimal.data(), invalid_decimal.size()}},
writer),
Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
const std::string object_metadata {char {0x11}, char {0x02}, char {0x00}, char {0x01},
char {0x02}, 'a', 'b'};
const std::string duplicate_key_object {
char {0x02}, char {0x02}, char {0x00}, char {0x00}, char {0x00},
char {0x01}, char {0x02}, char {0x00}, char {0x00},
};
EXPECT_THROW(variant_to_jsonb({{object_metadata.data(), object_metadata.size()},
{duplicate_key_object.data(), duplicate_key_object.size()}},
writer),
Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
const std::string null_value(1, '\0');
const std::string truncated_metadata(1, '\x01');
ASSERT_TRUE(writer.writeBool(true));
EXPECT_THROW(variant_to_jsonb({{truncated_metadata.data(), truncated_metadata.size()},
{null_value.data(), null_value.size()}},
writer),
Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
const OwnedVariant valid_array = build_variant([](VariantBatchBuilder::Row& builder) {
auto array = builder.start_array();
builder.add_null();
array.finish();
});
OwnedVariant truncated_array = valid_array;
truncated_array.value.pop_back();
ASSERT_TRUE(writer.writeBool(true));
EXPECT_THROW(variant_to_jsonb(truncated_array.ref(), writer), Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
const std::string reverse_key_object {
char {0x02}, char {0x02}, char {0x01}, char {0x00}, char {0x00},
char {0x01}, char {0x02}, char {0x00}, char {0x04},
};
ASSERT_TRUE(writer.writeBool(true));
EXPECT_THROW(variant_to_jsonb({{object_metadata.data(), object_metadata.size()},
{reverse_key_object.data(), reverse_key_object.size()}},
writer),
Exception);
EXPECT_EQ(writer.getOutput()->getSize(), 0);
}
TEST(VariantJsonbBlockTest, ExplicitNullPlaceholderProducesOneNullRow) {
JsonbToVariantEncoder encoder({.rows = 1, .scalar_bytes = 1, .nodes = 1});
encoder.add_null();
VariantBatchBuilder block = encoder.finish_batch();
ASSERT_EQ(block.num_rows(), 1);
EXPECT_TRUE(block.value_at(0).is_null());
EXPECT_EQ(block.metadata_ref().dict_size(), 0);
}
TEST(VariantJsonbBlockTest, RealJsonbAndEmptyDocumentsProduceNullRows) {
JsonbWriter writer;
ASSERT_TRUE(writer.writeNull());
const std::string document = jsonb_bytes(writer);
JsonbToVariantEncoder encoder;
encoder.add_jsonb(StringRef(document));
encoder.add_jsonb(StringRef {});
VariantBatchBuilder block = encoder.finish_batch();
ASSERT_EQ(block.num_rows(), 2);
EXPECT_TRUE(block.value_at(0).is_null());
EXPECT_TRUE(block.value_at(1).is_null());
EXPECT_EQ(block.value_at(0).metadata.data, block.value_at(1).metadata.data);
EXPECT_EQ(block.metadata_ref().dict_size(), 0);
}
TEST(VariantJsonbBlockTest, EmptyEncoderProducesMinimalEmptyBlock) {
JsonbToVariantEncoder encoder(VariantBatchBuilder::ReserveHint {});
VariantBatchBuilder block = encoder.finish_batch();
const std::string expected_metadata("\x11\0\0", 3);
EXPECT_EQ(std::string(block.metadata_ref().data, block.metadata_ref().size), expected_metadata);
EXPECT_EQ(block_value_bytes(block), "");
EXPECT_EQ(block_value_offsets(block), (std::vector<uint32_t> {0}));
EXPECT_EQ(block.num_rows(), 0);
}
TEST(VariantJsonbBlockTest, AddCopiesBorrowedDocumentBeforeCallerMutatesIt) {
JsonbWriter writer;
ASSERT_TRUE(writer.writeStartObject());
ASSERT_TRUE(writer.writeKey("key", 3));
ASSERT_TRUE(writer.writeStartString());
ASSERT_TRUE(writer.writeString("value", 5));
ASSERT_TRUE(writer.writeEndString());
ASSERT_TRUE(writer.writeEndObject());
std::string document = jsonb_bytes(writer);
const OwnedVariant expected = encode_jsonb(document);
JsonbToVariantEncoder encoder({.rows = 1,
.metadata_keys = 1,
.scalar_bytes = document.size(),
.nodes = 2,
.containers = 1,
.children = 1});
encoder.add_jsonb(StringRef(document));
document.assign(document.size(), '\0');
VariantBatchBuilder block = encoder.finish_batch();
ASSERT_EQ(block.num_rows(), 1);
EXPECT_EQ(std::string(block.metadata_ref().data, block.metadata_ref().size), expected.metadata);
EXPECT_EQ(block_value_bytes(block), expected.value);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- GTest macros inflate this matrix.
TEST(VariantJsonbBlockTest, SharesDictionaryAcrossObjectsArraysAndNullRows) {
JsonbWriter first_writer;
ASSERT_TRUE(first_writer.writeStartObject());
ASSERT_TRUE(first_writer.writeKey("z", 1));
ASSERT_TRUE(first_writer.writeInt8(1));
ASSERT_TRUE(first_writer.writeEndObject());
const std::string first = jsonb_bytes(first_writer);
JsonbWriter second_writer;
ASSERT_TRUE(second_writer.writeStartArray());
ASSERT_TRUE(second_writer.writeNull());
ASSERT_TRUE(second_writer.writeStartObject());
ASSERT_TRUE(second_writer.writeKey("a", 1));
ASSERT_TRUE(second_writer.writeBool(true));
ASSERT_TRUE(second_writer.writeKey("z", 1));
ASSERT_TRUE(second_writer.writeInt16(2));
ASSERT_TRUE(second_writer.writeEndObject());
ASSERT_TRUE(second_writer.writeEndArray());
const std::string second = jsonb_bytes(second_writer);
JsonbWriter null_writer;
ASSERT_TRUE(null_writer.writeNull());
const std::string null_document = jsonb_bytes(null_writer);
JsonbToVariantEncoder encoder;
encoder.add_jsonb(StringRef(first));
encoder.add_jsonb(StringRef(second));
encoder.add_jsonb(StringRef(null_document));
VariantBatchBuilder block = encoder.finish_batch();
ASSERT_EQ(block.num_rows(), 3);
ASSERT_EQ(block.metadata_ref().dict_size(), 2);
EXPECT_EQ(block.metadata_ref().key_at(0), string_ref("a"));
EXPECT_EQ(block.metadata_ref().key_at(1), string_ref("z"));
EXPECT_EQ(block.value_at(0).metadata.data, block.value_at(1).metadata.data);
EXPECT_EQ(block.value_at(1).metadata.data, block.value_at(2).metadata.data);
std::vector<VariantRef> rows;
rows.reserve(block.num_rows());
for (size_t row = 0; row < block.num_rows(); ++row) {
rows.push_back(block.value_at(row));
}
validate_canonical(block.metadata_ref(), rows);
VariantRef child;
ASSERT_TRUE(block.value_at(0).object_find(string_ref("z"), &child));
EXPECT_EQ(child.get_int(), 1);
ASSERT_EQ(block.value_at(1).num_elements(), 2);
EXPECT_TRUE(block.value_at(1).array_at(0).is_null());
ASSERT_TRUE(block.value_at(1).array_at(1).object_find(string_ref("a"), &child));
EXPECT_TRUE(child.get_bool());
EXPECT_TRUE(block.value_at(2).is_null());
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- GTest macros inflate this matrix.
TEST(VariantJsonbBlockTest, SingleCellOutputMatchesRowAdapter) {
JsonbWriter writer;
ASSERT_TRUE(writer.writeStartArray());
ASSERT_TRUE(writer.writeInt64(5'000'000'000));
ASSERT_TRUE(writer.writeStartString());
ASSERT_TRUE(writer.writeString("value", 5));
ASSERT_TRUE(writer.writeEndString());
ASSERT_TRUE(writer.writeStartObject());
ASSERT_TRUE(writer.writeKey("key", 3));
ASSERT_TRUE(writer.writeNull());
ASSERT_TRUE(writer.writeEndObject());
ASSERT_TRUE(writer.writeEndArray());
const std::string document = jsonb_bytes(writer);
const OwnedVariant expected = encode_jsonb(document);
JsonbToVariantEncoder encoder;
encoder.add_jsonb(StringRef(document));
VariantBatchBuilder block = encoder.finish_batch();
ASSERT_EQ(block.num_rows(), 1);
EXPECT_EQ(std::string(block.metadata_ref().data, block.metadata_ref().size), expected.metadata);
EXPECT_EQ(block_value_bytes(block), expected.value);
EXPECT_EQ(block_value_offsets(block),
(std::vector<uint32_t> {0, static_cast<uint32_t>(expected.value.size())}));
expect_jsonb_exception_code(ErrorCode::INVALID_ARGUMENT,
[&] { encoder.add_jsonb(StringRef(document)); });
expect_jsonb_exception_code(ErrorCode::INVALID_ARGUMENT,
[&] { static_cast<void>(encoder.finish_batch()); });
}
TEST(VariantJsonbBlockTest, InvalidDuplicateAndDeepInputsAreTerminal) {
const std::string bad_version(1, static_cast<char>(JSONB_VER + 1));
JsonbToVariantEncoder bad_version_encoder;
expect_jsonb_exception_code(ErrorCode::CORRUPTION,
[&] { bad_version_encoder.add_jsonb(StringRef(bad_version)); });
expect_jsonb_exception_code(ErrorCode::INVALID_ARGUMENT,
[&] { static_cast<void>(bad_version_encoder.finish_batch()); });
JsonbWriter duplicate_writer;
ASSERT_TRUE(duplicate_writer.writeStartObject());
ASSERT_TRUE(duplicate_writer.writeKey("a", 1));
ASSERT_TRUE(duplicate_writer.writeNull());
ASSERT_TRUE(duplicate_writer.writeKey("a", 1));
ASSERT_TRUE(duplicate_writer.writeBool(true));
ASSERT_TRUE(duplicate_writer.writeEndObject());
const std::string duplicate = jsonb_bytes(duplicate_writer);
JsonbToVariantEncoder duplicate_encoder;
expect_jsonb_exception_code(ErrorCode::INVALID_ARGUMENT,
[&] { duplicate_encoder.add_jsonb(StringRef(duplicate)); });
expect_jsonb_exception_code(ErrorCode::INVALID_ARGUMENT,
[&] { duplicate_encoder.add_jsonb(StringRef {}); });
const std::string too_deep = nested_jsonb_arrays(MaxNestingLevel + 1);
JsonbToVariantEncoder deep_encoder;
expect_jsonb_exception_code(ErrorCode::INVALID_ARGUMENT,
[&] { deep_encoder.add_jsonb(StringRef(too_deep)); });
expect_jsonb_exception_code(ErrorCode::INVALID_ARGUMENT,
[&] { static_cast<void>(deep_encoder.finish_batch()); });
}
struct JsonbBlockBenchmarkResult {
double seconds = 0;
uint64_t checksum = 0;
};
uint64_t run_jsonb_block_benchmark(StringRef document, uint32_t rows) {
const size_t row_count = rows;
JsonbToVariantEncoder encoder({.rows = rows,
.metadata_keys = 9,
.scalar_bytes = document.size * row_count,
.nodes = 112 * row_count,
.containers = 2 * row_count,
.children = 109 * row_count});
for (uint32_t row = 0; row < rows; ++row) {
encoder.add_jsonb(document);
}
VariantBatchBuilder block = encoder.finish_batch();
return block.value_bytes().size + block.metadata_ref().size;
}
JsonbBlockBenchmarkResult measure_jsonb_block_benchmark(StringRef document, uint32_t rows) {
static_cast<void>(run_jsonb_block_benchmark(document, 128));
const auto start = std::chrono::steady_clock::now();
JsonbBlockBenchmarkResult result;
result.checksum = run_jsonb_block_benchmark(document, rows);
result.seconds =
std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
return result;
}
std::string make_jsonb_block_benchmark_document() {
JsonbWriter writer;
EXPECT_TRUE(writer.writeStartObject());
for (uint8_t index = 0; index < 8; ++index) {
const std::string key = "key_" + std::to_string(index);
EXPECT_TRUE(writer.writeKey(key.data(), static_cast<uint8_t>(key.size())));
EXPECT_TRUE(writer.writeInt32(index));
}
EXPECT_TRUE(writer.writeKey("array", 5));
EXPECT_TRUE(writer.writeStartArray());
for (int32_t value = 0; value < 100; ++value) {
EXPECT_TRUE(writer.writeInt32(value));
}
EXPECT_TRUE(writer.writeEndArray());
EXPECT_TRUE(writer.writeEndObject());
return jsonb_bytes(writer);
}
// Run each disabled test as an exact filter in a fresh run-be-ut.sh process wrapped by
// /usr/bin/time -v: one unmeasured process warmup, then five measured processes. Compare median
// seconds and peak RSS, and require the printed checksums to match before applying the thresholds.
TEST(VariantJsonbBlockTest, DISABLED_SharedBlockJsonbBenchmark) {
const std::string document = make_jsonb_block_benchmark_document();
constexpr uint32_t ROWS = 65'536;
const JsonbBlockBenchmarkResult result =
measure_jsonb_block_benchmark(StringRef(document), ROWS);
EXPECT_GT(result.checksum, 0);
std::cout << "JSONB block RELEASE rows=" << ROWS << " seconds=" << result.seconds
<< " checksum=" << result.checksum << std::endl;
}
} // namespace
} // namespace doris