blob: 7888d3b05ced98b65e2ad1304979b29136642a97 [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 <array>
#include <cstdint>
#include <cstring>
#include <limits>
#include "common/exception.h"
#include "core/types.h"
#include "core/value/variant/variant_parquet_encoding.h"
#include "util/jsonb_document.h"
#include "util/jsonb_writer.h"
namespace doris {
namespace {
static_assert(MaxNestingLevel <= VARIANT_MAX_NESTING_DEPTH);
class BoundedJsonbCursor {
public:
BoundedJsonbCursor(const char* data, size_t size) : _current(data), _remaining(size) {}
template <typename T>
T read(const char* description) {
require(sizeof(T), description);
T value;
std::memcpy(&value, _current, sizeof(T));
_current += sizeof(T);
_remaining -= sizeof(T);
return value;
}
StringRef read_bytes(size_t size, const char* description) {
require(size, description);
const StringRef result {_current, size};
_current += size;
_remaining -= size;
return result;
}
BoundedJsonbCursor read_region(size_t size, const char* description) {
const StringRef region = read_bytes(size, description);
return {region.data, region.size};
}
bool empty() const noexcept { return _remaining == 0; }
size_t remaining() const noexcept { return _remaining; }
private:
void require(size_t size, const char* description) const {
if (_remaining < size) {
throw Exception(ErrorCode::CORRUPTION,
"Truncated JSONB while reading {}: need {} bytes, have {}", description,
size, _remaining);
}
}
const char* _current;
size_t _remaining;
};
template <typename Integer>
uint32_t decimal_digits(Integer value) {
uint32_t digits = 0;
do {
value /= 10;
++digits;
} while (value != 0);
return digits;
}
template <typename Integer>
void require_decimal(Integer value, uint32_t precision, uint32_t scale, uint32_t maximum_precision,
uint32_t maximum_scale, const char* description) {
if (precision == 0 || precision > maximum_precision) {
throw Exception(ErrorCode::CORRUPTION, "JSONB {} precision {} is outside [1, {}]",
description, precision, maximum_precision);
}
if (scale > maximum_scale) {
throw Exception(ErrorCode::CORRUPTION, "JSONB {} scale {} exceeds {}", description, scale,
maximum_scale);
}
const uint32_t digits = decimal_digits(value);
if (digits > precision) {
throw Exception(ErrorCode::CORRUPTION,
"JSONB {} value has {} digits but declared precision is {}", description,
digits, precision);
}
}
void require_jsonb_depth(uint32_t depth, bool is_container) {
if (depth > MaxNestingLevel || (is_container && depth == MaxNestingLevel)) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"JSONB traversal exceeds maximum container nesting {}", MaxNestingLevel);
}
}
void require_variant_depth(uint32_t base_depth, uint32_t relative_depth, bool is_container) {
if (base_depth > VARIANT_MAX_NESTING_DEPTH ||
relative_depth > VARIANT_MAX_NESTING_DEPTH - base_depth ||
(is_container && base_depth + relative_depth == VARIANT_MAX_NESTING_DEPTH)) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"JSONB traversal exceeds maximum Variant container nesting {}",
VARIANT_MAX_NESTING_DEPTH);
}
}
template <typename Builder>
void collect_jsonb_value(BoundedJsonbCursor& cursor, Builder& builder, uint32_t relative_depth,
uint32_t base_depth);
template <typename Builder>
void collect_jsonb_object(BoundedJsonbCursor& cursor, Builder& builder, uint32_t relative_depth,
uint32_t base_depth) {
require_jsonb_depth(relative_depth, true);
require_variant_depth(base_depth, relative_depth, true);
const auto payload_size = cursor.read<uint32_t>("object payload size");
BoundedJsonbCursor payload = cursor.read_region(payload_size, "object payload");
auto scope = builder.start_object();
while (!payload.empty()) {
const auto key_size = payload.read<uint8_t>("object key size");
StringRef key;
if (key_size == 0) {
const auto key_id = payload.read<JsonbKeyValue::keyid_type>("object key id");
if (key_id != JsonbKeyValue::sMaxKeyId) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Cannot resolve external JSONB object key id {} without a "
"dictionary",
key_id);
}
} else {
key = payload.read_bytes(key_size, "object key");
}
scope.add_key(key);
collect_jsonb_value(payload, builder, relative_depth + 1, base_depth);
}
scope.finish();
}
template <typename Builder>
void collect_jsonb_array(BoundedJsonbCursor& cursor, Builder& builder, uint32_t relative_depth,
uint32_t base_depth) {
require_jsonb_depth(relative_depth, true);
require_variant_depth(base_depth, relative_depth, true);
const auto payload_size = cursor.read<uint32_t>("array payload size");
BoundedJsonbCursor payload = cursor.read_region(payload_size, "array payload");
auto scope = builder.start_array();
while (!payload.empty()) {
collect_jsonb_value(payload, builder, relative_depth + 1, base_depth);
}
scope.finish();
}
template <typename Native, typename Builder>
void collect_jsonb_decimal(BoundedJsonbCursor& cursor, Builder& builder, uint8_t width,
uint32_t maximum_precision, const char* description) {
const auto precision = cursor.read<uint32_t>("decimal precision");
const auto scale = cursor.read<uint32_t>("decimal scale");
const auto value = cursor.read<Native>("decimal value");
require_decimal(value, precision, scale, maximum_precision, 38, description);
builder.add_decimal(static_cast<__int128>(value), static_cast<uint8_t>(scale), width);
}
template <typename Builder>
void collect_jsonb_value(BoundedJsonbCursor& cursor, Builder& builder, uint32_t relative_depth,
uint32_t base_depth) {
require_jsonb_depth(relative_depth, false);
require_variant_depth(base_depth, relative_depth, false);
const auto type = static_cast<JsonbType>(cursor.read<uint8_t>("value type"));
switch (type) {
case JsonbType::T_Null:
builder.add_null();
return;
case JsonbType::T_True:
builder.add_bool(true);
return;
case JsonbType::T_False:
builder.add_bool(false);
return;
case JsonbType::T_Int8:
builder.add_int(cursor.read<int8_t>("int8 value"));
return;
case JsonbType::T_Int16:
builder.add_int(cursor.read<int16_t>("int16 value"));
return;
case JsonbType::T_Int32:
builder.add_int(cursor.read<int32_t>("int32 value"));
return;
case JsonbType::T_Int64:
builder.add_int(cursor.read<int64_t>("int64 value"));
return;
case JsonbType::T_Double:
builder.add_double(cursor.read<double>("double value"));
return;
case JsonbType::T_String: {
const auto size = cursor.read<uint32_t>("string size");
builder.add_string(cursor.read_bytes(size, "string payload"));
return;
}
case JsonbType::T_Binary: {
const auto size = cursor.read<uint32_t>("binary size");
builder.add_binary(cursor.read_bytes(size, "binary payload"));
return;
}
case JsonbType::T_Object:
collect_jsonb_object(cursor, builder, relative_depth, base_depth);
return;
case JsonbType::T_Array:
collect_jsonb_array(cursor, builder, relative_depth, base_depth);
return;
case JsonbType::T_Int128:
builder.add_largeint(cursor.read<__int128>("int128 value"));
return;
case JsonbType::T_Float:
builder.add_float(cursor.read<float>("float value"));
return;
case JsonbType::T_Decimal32:
collect_jsonb_decimal<int32_t>(cursor, builder, 4, 9, "Decimal32");
return;
case JsonbType::T_Decimal64:
collect_jsonb_decimal<int64_t>(cursor, builder, 8, 18, "Decimal64");
return;
case JsonbType::T_Decimal128:
collect_jsonb_decimal<__int128>(cursor, builder, 16, 38, "Decimal128");
return;
case JsonbType::T_Decimal256:
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Conversion from JSONB DECIMAL256 to Variant V2 is not supported");
case JsonbType::NUM_TYPES:
break;
}
throw Exception(ErrorCode::CORRUPTION, "Unknown JSONB value type {}",
static_cast<uint8_t>(type));
}
template <typename Builder>
void collect_jsonb_document(StringRef document, Builder& builder, uint32_t initial_depth = 0,
bool* root_is_null = nullptr) {
if (root_is_null != nullptr) {
*root_is_null = false;
}
if (document.data == nullptr && document.size != 0) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"JSONB input has a null data pointer for {} bytes", document.size);
}
if (document.size == 0) {
if (root_is_null != nullptr) {
*root_is_null = true;
}
builder.add_null();
return;
}
BoundedJsonbCursor cursor(document.data, document.size);
const auto version = cursor.read<uint8_t>("document version");
if (version != JSONB_VER) {
throw Exception(ErrorCode::CORRUPTION, "Unsupported JSONB version {}, expected {}", version,
JSONB_VER);
}
if (root_is_null != nullptr && document.size >= sizeof(uint8_t) * 2) {
*root_is_null =
static_cast<JsonbType>(static_cast<uint8_t>(document.data[1])) == JsonbType::T_Null;
}
collect_jsonb_value(cursor, builder, 0, initial_depth);
if (!cursor.empty()) {
throw Exception(ErrorCode::CORRUPTION,
"JSONB document has {} trailing bytes after its root value",
cursor.remaining());
}
}
void require_jsonb_write(bool written, const char* operation) {
if (!written) {
throw Exception(ErrorCode::INVALID_ARGUMENT, "JSONB writer rejected {}", operation);
}
}
void write_jsonb_string(JsonbWriter& writer, StringRef value, const char* description) {
variant_json::require_valid_json_utf8(value, description);
require_jsonb_write(writer.writeStartString(), "string start");
require_jsonb_write(writer.writeString(value.data, value.size), "string payload");
require_jsonb_write(writer.writeEndString(), "string end");
}
void write_jsonb_string(JsonbWriter& writer, const variant_json::FormattedScalar& value) {
write_jsonb_string(writer, {value.bytes.data(), value.size}, "formatted string");
}
void write_jsonb_binary(JsonbWriter& writer, StringRef value) {
require_jsonb_write(writer.writeStartBinary(), "binary start");
require_jsonb_write(writer.writeBinary(value.data, value.size), "binary payload");
require_jsonb_write(writer.writeEndBinary(), "binary end");
}
class VariantToJsonbConverter {
public:
VariantToJsonbConverter(JsonbWriter& writer, const VariantJsonFormatOptions& options)
: _writer(writer), _options(options) {}
void write(VariantRef value, uint32_t depth) {
const VariantBasicType type = value.basic_type();
const bool is_container =
type == VariantBasicType::OBJECT || type == VariantBasicType::ARRAY;
require_jsonb_depth(depth, is_container);
switch (type) {
case VariantBasicType::SHORT_STRING:
write_jsonb_string(_writer, value.get_string(), "Variant string");
return;
case VariantBasicType::PRIMITIVE:
write_primitive(value);
return;
case VariantBasicType::OBJECT:
write_object(value, depth);
return;
case VariantBasicType::ARRAY:
write_array(value, depth);
return;
}
}
private:
void write_decimal(VariantDecimal decimal) {
uint32_t precision = 38;
if (decimal.width == 4) {
precision = 9;
} else if (decimal.width == 8) {
precision = 18;
}
require_decimal(decimal.unscaled, precision, decimal.scale, precision, 38,
"Variant decimal");
if (decimal.width == 4) {
require_jsonb_write(
_writer.writeDecimal(Decimal32 {static_cast<int32_t>(decimal.unscaled)},
precision, decimal.scale),
"Decimal32 value");
} else if (decimal.width == 8) {
require_jsonb_write(
_writer.writeDecimal(Decimal64 {static_cast<int64_t>(decimal.unscaled)},
precision, decimal.scale),
"Decimal64 value");
} else {
require_jsonb_write(
_writer.writeDecimal(Decimal128V3 {decimal.unscaled}, precision, decimal.scale),
"Decimal128 value");
}
}
void write_primitive(VariantRef value) {
switch (value.primitive_id()) {
case VariantPrimitiveId::NULL_VALUE:
require_jsonb_write(_writer.writeNull(), "null value");
return;
case VariantPrimitiveId::TRUE_VALUE:
require_jsonb_write(_writer.writeBool(true), "true value");
return;
case VariantPrimitiveId::FALSE_VALUE:
require_jsonb_write(_writer.writeBool(false), "false value");
return;
case VariantPrimitiveId::INT8:
require_jsonb_write(_writer.writeInt8(static_cast<int8_t>(value.get_int())),
"int8 value");
return;
case VariantPrimitiveId::INT16:
require_jsonb_write(_writer.writeInt16(static_cast<int16_t>(value.get_int())),
"int16 value");
return;
case VariantPrimitiveId::INT32:
require_jsonb_write(_writer.writeInt32(static_cast<int32_t>(value.get_int())),
"int32 value");
return;
case VariantPrimitiveId::INT64:
require_jsonb_write(_writer.writeInt64(value.get_int()), "int64 value");
return;
case VariantPrimitiveId::DOUBLE:
require_jsonb_write(_writer.writeDouble(value.get_double()), "double value");
return;
case VariantPrimitiveId::DECIMAL4:
case VariantPrimitiveId::DECIMAL8:
case VariantPrimitiveId::DECIMAL16:
write_decimal(value.get_decimal());
return;
case VariantPrimitiveId::DATE:
write_jsonb_string(_writer, variant_json::format_json_date(value.get_date()));
return;
case VariantPrimitiveId::TIMESTAMP_MICROS:
write_jsonb_string(_writer,
variant_json::format_json_timestamp(value.get_timestamp_micros(), 6,
true, _options.timezone));
return;
case VariantPrimitiveId::TIMESTAMP_NTZ_MICROS:
write_jsonb_string(_writer,
variant_json::format_json_timestamp(value.get_timestamp_ntz_micros(),
6, false, nullptr));
return;
case VariantPrimitiveId::FLOAT:
require_jsonb_write(_writer.writeFloat(value.get_float()), "float value");
return;
case VariantPrimitiveId::BINARY:
write_jsonb_binary(_writer, value.get_binary());
return;
case VariantPrimitiveId::STRING:
write_jsonb_string(_writer, value.get_string(), "Variant string");
return;
case VariantPrimitiveId::TIME_NTZ_MICROS:
write_jsonb_string(_writer,
variant_json::format_json_time_micros(value.get_time_ntz_micros()));
return;
case VariantPrimitiveId::TIMESTAMP_NANOS:
write_jsonb_string(_writer,
variant_json::format_json_timestamp(value.get_timestamp_nanos(), 9,
true, _options.timezone));
return;
case VariantPrimitiveId::TIMESTAMP_NTZ_NANOS:
write_jsonb_string(
_writer, variant_json::format_json_timestamp(value.get_timestamp_ntz_nanos(), 9,
false, nullptr));
return;
case VariantPrimitiveId::UUID:
write_jsonb_string(_writer, variant_json::format_json_uuid(value.get_uuid()));
return;
}
throw Exception(ErrorCode::CORRUPTION, "Unknown Variant primitive id");
}
void write_object(VariantRef value, uint32_t depth) {
require_jsonb_write(_writer.writeStartObject(), "object start");
const uint32_t count = value.num_elements();
StringRef previous_key;
for (uint32_t index = 0; index < count; ++index) {
uint32_t field_id = 0;
const VariantRef child = value.object_value_at(index, &field_id);
const StringRef key = value.metadata.key_at(field_id);
variant_json::require_json_object_key(key, previous_key, index);
if (key.size > std::numeric_limits<uint8_t>::max()) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant object key length {} exceeds JSONB maximum {}", key.size,
std::numeric_limits<uint8_t>::max());
}
require_jsonb_write(_writer.writeKey(key.data, static_cast<uint8_t>(key.size)),
"object key");
write(child, depth + 1);
previous_key = key;
}
require_jsonb_write(_writer.writeEndObject(), "object end");
}
void write_array(VariantRef value, uint32_t depth) {
require_jsonb_write(_writer.writeStartArray(), "array start");
const uint32_t count = value.num_elements();
for (uint32_t index = 0; index < count; ++index) {
write(value.array_at(index), depth + 1);
}
require_jsonb_write(_writer.writeEndArray(), "array end");
}
JsonbWriter& _writer;
const VariantJsonFormatOptions& _options;
};
} // namespace
struct JsonbToVariantEncoder::Impl {
enum class State : uint8_t { COLLECTING, FINISHED, FAILED };
explicit Impl(VariantBatchBuilder::ReserveHint hint) : builder(hint) {}
void require_collecting() const {
if (state == State::FINISHED) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant JSONB encoder is already finished");
}
if (state == State::FAILED) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant JSONB encoder is in a terminal failed state");
}
}
VariantBatchBuilder builder;
State state = State::COLLECTING;
};
JsonbToVariantEncoder::JsonbToVariantEncoder()
: JsonbToVariantEncoder(VariantBatchBuilder::ReserveHint {}) {}
JsonbToVariantEncoder::JsonbToVariantEncoder(VariantBatchBuilder::ReserveHint hint)
: _impl(std::make_unique<Impl>(hint)) {}
JsonbToVariantEncoder::~JsonbToVariantEncoder() = default;
JsonbToVariantEncoder::JsonbToVariantEncoder(JsonbToVariantEncoder&&) noexcept = default;
JsonbToVariantEncoder& JsonbToVariantEncoder::operator=(JsonbToVariantEncoder&&) noexcept = default;
void JsonbToVariantEncoder::add_null() {
_impl->require_collecting();
try {
auto row = _impl->builder.begin_row();
row.add_null();
row.finish();
} catch (...) {
_impl->state = Impl::State::FAILED;
throw;
}
}
void JsonbToVariantEncoder::add_jsonb(StringRef document) {
_impl->require_collecting();
try {
auto row = _impl->builder.begin_row();
collect_jsonb_document(document, row);
row.finish();
} catch (...) {
_impl->state = Impl::State::FAILED;
throw;
}
}
VariantBatchBuilder JsonbToVariantEncoder::finish_batch() {
_impl->require_collecting();
try {
VariantBatchBuilder block = _impl->builder.finish_batch();
_impl->state = Impl::State::FINISHED;
return block;
} catch (...) {
_impl->state = Impl::State::FAILED;
throw;
}
}
void jsonb_to_variant(StringRef document, VariantBatchBuilder::Row& row, uint32_t initial_depth,
bool* root_is_null) {
try {
collect_jsonb_document(document, row, initial_depth, root_is_null);
} catch (...) {
row.abort();
throw;
}
}
void variant_to_jsonb(VariantRef value, JsonbWriter& writer,
const VariantJsonFormatOptions& options) {
writer.reset();
try {
if (value.metadata.version() != VARIANT_ENCODING_VERSION) {
throw Exception(ErrorCode::CORRUPTION, "Unsupported Variant metadata version {}",
value.metadata.version());
}
static_cast<void>(value.metadata.dict_size());
variant_json::require_exact_json_value(value);
VariantToJsonbConverter(writer, options).write(value, 0);
} catch (...) {
writer.reset();
throw;
}
}
} // namespace doris