blob: 1c24037747beea71a81c3368a9b7ba48d8988849 [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_string_parse.h"
#include <cctz/time_zone.h>
#include <fmt/compile.h>
#include <fmt/format.h>
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <limits>
#include <string_view>
#include <unordered_set>
#include <utility>
#include "common/config.h"
#include "common/exception.h"
#include "core/value/variant/variant_batch_builder.h"
#include "core/value/variant/variant_parquet_encoding.h"
#include "util/json/simd_json_parser.h"
#include "util/utf8_check.h"
namespace doris {
namespace {
using variant_json::FormattedScalar;
void append_char(FormattedScalar* result, char value) {
result->bytes[result->size++] = value;
}
void append_unsigned(FormattedScalar* result, uint64_t value, size_t minimum_digits = 1) {
std::array<char, 32> reversed {};
size_t digits = 0;
do {
reversed[digits++] = static_cast<char>('0' + value % 10);
value /= 10;
} while (value != 0);
while (digits < minimum_digits) {
reversed[digits++] = '0';
}
while (digits != 0) {
append_char(result, reversed[--digits]);
}
}
void append_year(FormattedScalar* result, int64_t year) {
if (year >= 0 && year <= 9999) {
append_unsigned(result, static_cast<uint64_t>(year), 4);
return;
}
if (year >= 0) {
append_char(result, '+');
append_unsigned(result, static_cast<uint64_t>(year), 4);
return;
}
append_char(result, '-');
append_unsigned(result, static_cast<uint64_t>(-(year + 1)) + 1, 4);
}
void append_date_time(FormattedScalar* result, const cctz::civil_second& civil, bool include_time) {
append_year(result, civil.year());
append_char(result, '-');
append_unsigned(result, civil.month(), 2);
append_char(result, '-');
append_unsigned(result, civil.day(), 2);
if (!include_time) {
return;
}
append_char(result, ' ');
append_unsigned(result, civil.hour(), 2);
append_char(result, ':');
append_unsigned(result, civil.minute(), 2);
append_char(result, ':');
append_unsigned(result, civil.second(), 2);
}
void append_fraction(FormattedScalar* result, uint64_t fraction, uint8_t digits) {
append_char(result, '.');
append_unsigned(result, fraction, digits);
}
void append_offset(FormattedScalar* result, int offset_seconds) {
const bool negative = offset_seconds < 0;
const uint64_t magnitude_seconds =
negative ? static_cast<uint64_t>(-(static_cast<int64_t>(offset_seconds)))
: static_cast<uint64_t>(offset_seconds);
append_char(result, negative ? '-' : '+');
append_unsigned(result, magnitude_seconds / 3600, 2);
append_char(result, ':');
append_unsigned(result, (magnitude_seconds % 3600) / 60, 2);
if (magnitude_seconds % 60 != 0) {
append_char(result, ':');
append_unsigned(result, magnitude_seconds % 60, 2);
}
}
std::pair<int64_t, uint64_t> split_epoch(int64_t value, int64_t units_per_second) {
int64_t seconds = value / units_per_second;
int64_t fraction = value % units_per_second;
if (fraction < 0) {
--seconds;
fraction += units_per_second;
}
return {seconds, static_cast<uint64_t>(fraction)};
}
StringRef to_string_ref(std::string_view value) {
return {value.data(), value.size()};
}
void require_json_key_length(std::string_view key, uint32_t maximum) {
if (key.size() > maximum) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant JSON object key length {} exceeds maximum {} bytes", key.size(),
maximum);
}
}
class JsonTreeCollector {
public:
JsonTreeCollector(VariantBatchBuilder::Row& builder, const JsonToVariantOptions& options)
: _builder(builder), _options(options) {}
void collect(SimdJSONParser::Element element, uint32_t depth) {
variant_json::require_json_depth(depth);
if (element.isNull()) {
_builder.add_null();
} else if (element.isBool()) {
_builder.add_bool(element.getBool());
} else if (element.isInt64()) {
_builder.add_int(element.getInt64());
} else if (element.isUInt64()) {
_builder.add_largeint(static_cast<__int128>(element.getUInt64()));
} else if (element.isDouble()) {
_builder.add_double(element.getDouble());
} else if (element.isString()) {
_builder.add_string(to_string_ref(element.getString()));
} else if (element.isArray()) {
collect_array(element.getArray(), depth);
} else if (element.isObject()) {
collect_object(element.getObject(), depth);
}
}
private:
void collect_array(const SimdJSONParser::Array& array, uint32_t depth) {
auto scope = _builder.start_array();
for (SimdJSONParser::Element child : array) {
collect(child, depth + 1);
}
scope.finish();
}
void collect_object(const SimdJSONParser::Object& object, uint32_t depth) {
auto scope = _builder.start_object();
if (!_options.check_duplicate_json_path) {
for (const auto& [key, child] : object) {
require_json_key_length(key, _options.max_json_key_length);
scope.add_key(to_string_ref(key));
collect(child, depth + 1);
}
scope.finish();
return;
}
std::unordered_set<std::string_view> seen_keys;
seen_keys.reserve(object.size());
for (const auto& [key, child] : object) {
require_json_key_length(key, _options.max_json_key_length);
if (seen_keys.emplace(key).second) {
scope.add_key(to_string_ref(key));
collect(child, depth + 1);
} else {
validate_ignored(child, depth + 1);
}
}
scope.finish();
}
void validate_ignored(SimdJSONParser::Element element, uint32_t depth) const {
variant_json::require_json_depth(depth);
if (element.isArray()) {
for (SimdJSONParser::Element child : element.getArray()) {
validate_ignored(child, depth + 1);
}
} else if (element.isObject()) {
for (const auto& [key, child] : element.getObject()) {
require_json_key_length(key, _options.max_json_key_length);
validate_ignored(child, depth + 1);
}
}
}
VariantBatchBuilder::Row& _builder;
const JsonToVariantOptions& _options;
};
} // namespace
namespace variant_json {
void require_json_depth(uint32_t depth) {
if (depth > VARIANT_MAX_NESTING_DEPTH) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant JSON traversal exceeds maximum depth {}",
VARIANT_MAX_NESTING_DEPTH);
}
}
void require_exact_json_value(VariantRef value) {
const size_t encoded_size = value.value_size();
if (encoded_size != value.value.size) {
throw Exception(ErrorCode::CORRUPTION,
"Variant value has {} trailing bytes after its {} byte root",
value.value.size - encoded_size, encoded_size);
}
}
void require_valid_json_utf8(StringRef value, const char* description) {
if (value.size != 0 && !validate_utf8(value.data, value.size)) {
throw Exception(ErrorCode::CORRUPTION, "Variant {} is not valid UTF-8", description);
}
}
void require_json_object_key(StringRef key, StringRef previous_key, uint32_t field_index) {
require_valid_json_utf8(key, "object key");
if (field_index != 0 && previous_key.compare(key) >= 0) {
throw Exception(ErrorCode::CORRUPTION,
"Variant object keys are not strictly byte-sorted at field {}",
field_index);
}
}
[[noreturn]] void throw_unsupported_json_primitive(VariantPrimitiveId id) {
throw Exception(ErrorCode::INVALID_ARGUMENT, "Unsupported Variant JSON primitive id {}",
static_cast<uint8_t>(id));
}
FormattedScalar format_json_int(int64_t value) {
FormattedScalar result;
char* end = fmt::format_to(result.bytes.data(), FMT_COMPILE("{}"), value);
result.size = end - result.bytes.data();
return result;
}
FormattedScalar format_json_float(float value) {
FormattedScalar result;
char* end = fmt::format_to(result.bytes.data(), FMT_COMPILE("{:.{}g}"), value,
std::numeric_limits<float>::digits10 + 1);
result.size = end - result.bytes.data();
return result;
}
FormattedScalar format_json_double(double value) {
FormattedScalar result;
char* end = fmt::format_to(result.bytes.data(), FMT_COMPILE("{:.{}g}"), value,
std::numeric_limits<double>::digits10 + 1);
result.size = end - result.bytes.data();
return result;
}
FormattedScalar format_json_decimal(VariantDecimal value) {
FormattedScalar result;
const bool negative = value.unscaled < 0;
std::array<char, 39> reversed {};
size_t digits = 0;
unsigned __int128 remaining = variant_unsigned_magnitude(value.unscaled);
do {
reversed[digits++] = static_cast<char>('0' + remaining % 10);
remaining /= 10;
} while (remaining != 0);
if (negative) {
append_char(&result, '-');
}
if (value.scale == 0) {
while (digits != 0) {
append_char(&result, reversed[--digits]);
}
return result;
}
if (digits <= value.scale) {
append_char(&result, '0');
append_char(&result, '.');
for (size_t zero = digits; zero < value.scale; ++zero) {
append_char(&result, '0');
}
while (digits != 0) {
append_char(&result, reversed[--digits]);
}
return result;
}
while (digits > value.scale) {
append_char(&result, reversed[--digits]);
}
append_char(&result, '.');
while (digits != 0) {
append_char(&result, reversed[--digits]);
}
return result;
}
FormattedScalar format_json_date(int32_t days_since_epoch) {
constexpr int64_t SECONDS_PER_DAY = 86'400;
const int64_t seconds = static_cast<int64_t>(days_since_epoch) * SECONDS_PER_DAY;
const auto lookup =
cctz::utc_time_zone().lookup(cctz::time_point<cctz::seconds>(cctz::seconds(seconds)));
FormattedScalar result;
append_date_time(&result, lookup.cs, false);
return result;
}
FormattedScalar format_json_timestamp(int64_t value, uint8_t fractional_digits, bool utc_adjusted,
const cctz::time_zone* timezone) {
if (fractional_digits != 6 && fractional_digits != 9) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant timestamp JSON precision must be 6 or 9, got {}",
fractional_digits);
}
const int64_t units_per_second = fractional_digits == 6 ? 1'000'000 : 1'000'000'000;
const auto [seconds, fraction] = split_epoch(value, units_per_second);
const cctz::time_zone& zone =
utc_adjusted && timezone != nullptr ? *timezone : cctz::utc_time_zone();
const auto lookup = zone.lookup(cctz::time_point<cctz::seconds>(cctz::seconds(seconds)));
FormattedScalar result;
append_date_time(&result, lookup.cs, true);
append_fraction(&result, fraction, fractional_digits);
if (utc_adjusted) {
append_offset(&result, lookup.offset);
}
return result;
}
FormattedScalar format_json_time_micros(int64_t value) {
constexpr int64_t MICROS_PER_SECOND = 1'000'000;
constexpr int64_t MICROS_PER_DAY = 86'400 * MICROS_PER_SECOND;
if (value < 0 || value >= MICROS_PER_DAY) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant time value {} is outside [0, {}) microseconds", value,
MICROS_PER_DAY);
}
const auto [seconds, micros] = split_epoch(value, MICROS_PER_SECOND);
FormattedScalar result;
append_unsigned(&result, static_cast<uint64_t>(seconds / 3600), 2);
append_char(&result, ':');
append_unsigned(&result, static_cast<uint64_t>((seconds % 3600) / 60), 2);
append_char(&result, ':');
append_unsigned(&result, static_cast<uint64_t>(seconds % 60), 2);
append_fraction(&result, micros, 6);
return result;
}
FormattedScalar format_json_uuid(const std::array<uint8_t, 16>& value) {
static constexpr char HEX[] = "0123456789abcdef";
FormattedScalar result;
for (size_t index = 0; index < value.size(); ++index) {
if (index == 4 || index == 6 || index == 8 || index == 10) {
append_char(&result, '-');
}
append_char(&result, HEX[value[index] >> 4]);
append_char(&result, HEX[value[index] & 0x0F]);
}
return result;
}
} // namespace variant_json
JsonToVariantOptions JsonToVariantOptions::current_config() {
return {.max_json_key_length = static_cast<uint32_t>(config::variant_max_json_key_length),
.throw_on_invalid_json = config::variant_throw_exeception_on_invalid_json,
.check_duplicate_json_path = config::variant_enable_duplicate_json_path_check};
}
struct JsonStringToVariantEncoder::Impl {
enum class State : uint8_t { COLLECTING, FINISHED, FAILED };
explicit Impl(JsonToVariantOptions options_) : options(options_) {
if (options.max_json_key_length == 0) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant maximum JSON key length must be positive");
}
}
void require_collecting() const {
if (state == State::FINISHED) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant JSON encoder is already finished");
}
if (state == State::FAILED) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant JSON encoder is in a terminal failed state");
}
}
void add_json_row(StringRef json) {
auto row = builder.begin_row();
if (json.size != 0 && json.data == nullptr) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"Variant JSON input has a null data pointer");
}
if (json.size == 0) {
auto object = row.start_object();
object.finish();
row.finish();
return;
}
SimdJSONParser::Element root;
if (!parser.parse(json.data, json.size, root)) {
if (options.throw_on_invalid_json) {
throw Exception(ErrorCode::INVALID_ARGUMENT, "Failed to parse JSON as Variant");
}
row.add_string(json);
row.finish();
return;
}
JsonTreeCollector(row, options).collect(root, 0);
row.finish();
}
JsonToVariantOptions options;
VariantBatchBuilder builder;
SimdJSONParser parser;
State state = State::COLLECTING;
};
JsonStringToVariantEncoder::JsonStringToVariantEncoder()
: JsonStringToVariantEncoder(JsonToVariantOptions::current_config()) {}
JsonStringToVariantEncoder::JsonStringToVariantEncoder(JsonToVariantOptions options)
: _impl(std::make_unique<Impl>(options)) {}
JsonStringToVariantEncoder::~JsonStringToVariantEncoder() = default;
JsonStringToVariantEncoder::JsonStringToVariantEncoder(JsonStringToVariantEncoder&&) noexcept =
default;
JsonStringToVariantEncoder& JsonStringToVariantEncoder::operator=(
JsonStringToVariantEncoder&&) noexcept = default;
void JsonStringToVariantEncoder::add_json(StringRef json) {
_impl->require_collecting();
try {
_impl->add_json_row(json);
} catch (...) {
_impl->state = Impl::State::FAILED;
throw;
}
}
Status JsonStringToVariantEncoder::try_add_json(StringRef json) {
_impl->require_collecting();
try {
_impl->add_json_row(json);
return Status::OK();
} catch (const Exception& exception) {
if (exception.code() == ErrorCode::INVALID_ARGUMENT) {
return exception.to_status();
}
_impl->state = Impl::State::FAILED;
throw;
} catch (...) {
_impl->state = Impl::State::FAILED;
throw;
}
}
VariantBatchBuilder JsonStringToVariantEncoder::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;
}
}
} // namespace doris