| // 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. |
| |
| #pragma once |
| |
| #include <array> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| |
| #include "common/status.h" |
| #include "core/value/variant/variant_batch_builder.h" |
| #include "core/value/variant/variant_value.h" |
| |
| namespace cctz { |
| class time_zone; |
| } |
| |
| namespace doris { |
| |
| // Formatting state supplied by the column adapter. A null time zone means UTC. |
| struct VariantJsonFormatOptions { |
| const cctz::time_zone* timezone = nullptr; |
| }; |
| |
| struct JsonToVariantOptions { |
| uint32_t max_json_key_length = 255; |
| bool throw_on_invalid_json = false; |
| bool check_duplicate_json_path = false; |
| |
| // Takes a value snapshot. Later changes to mutable config do not affect an encoder. |
| static JsonToVariantOptions current_config(); |
| }; |
| |
| // Reuses one JSON parser and one batch builder. Each add_json() collects into a stack-only row; |
| // finish_batch() performs pass 2. A failed add_json() is terminal, as is a successful |
| // finish_batch(). try_add_json() rolls back invalid input rows and keeps collecting. |
| class JsonStringToVariantEncoder { |
| public: |
| JsonStringToVariantEncoder(); |
| explicit JsonStringToVariantEncoder(JsonToVariantOptions options); |
| ~JsonStringToVariantEncoder(); |
| |
| JsonStringToVariantEncoder(const JsonStringToVariantEncoder&) = delete; |
| JsonStringToVariantEncoder& operator=(const JsonStringToVariantEncoder&) = delete; |
| JsonStringToVariantEncoder(JsonStringToVariantEncoder&&) noexcept; |
| JsonStringToVariantEncoder& operator=(JsonStringToVariantEncoder&&) noexcept; |
| |
| void add_json(StringRef json); |
| Status try_add_json(StringRef json); |
| VariantBatchBuilder finish_batch(); |
| |
| private: |
| struct Impl; |
| std::unique_ptr<Impl> _impl; |
| }; |
| |
| namespace variant_json { |
| |
| struct FormattedScalar { |
| std::array<char, 128> bytes {}; |
| size_t size = 0; |
| }; |
| |
| void require_json_depth(uint32_t depth); |
| void require_exact_json_value(VariantRef value); |
| void require_valid_json_utf8(StringRef value, const char* description); |
| void require_json_object_key(StringRef key, StringRef previous_key, uint32_t field_index); |
| [[noreturn]] void throw_unsupported_json_primitive(VariantPrimitiveId id); |
| |
| FormattedScalar format_json_int(int64_t value); |
| FormattedScalar format_json_float(float value); |
| FormattedScalar format_json_double(double value); |
| FormattedScalar format_json_decimal(VariantDecimal value); |
| FormattedScalar format_json_date(int32_t days_since_epoch); |
| FormattedScalar format_json_timestamp(int64_t value, uint8_t fractional_digits, bool utc_adjusted, |
| const cctz::time_zone* timezone); |
| FormattedScalar format_json_time_micros(int64_t value); |
| FormattedScalar format_json_uuid(const std::array<uint8_t, 16>& value); |
| |
| template <typename Writer, size_t N> |
| void write_literal(Writer& out, const char (&value)[N]) { |
| out.write(value, N - 1); |
| } |
| |
| template <typename Writer> |
| void write_scalar(Writer& out, const FormattedScalar& value) { |
| out.write(value.bytes.data(), value.size); |
| } |
| |
| template <typename Writer> |
| void write_quoted_scalar(Writer& out, const FormattedScalar& value) { |
| write_literal(out, "\""); |
| write_scalar(out, value); |
| write_literal(out, "\""); |
| } |
| |
| template <typename Writer> |
| void write_escaped_json_string(Writer& out, StringRef value) { |
| write_literal(out, "\""); |
| size_t plain_begin = 0; |
| size_t index = 0; |
| while (index < value.size) { |
| const auto byte = static_cast<uint8_t>(value.data[index]); |
| const char* escaped = nullptr; |
| size_t escaped_size = 0; |
| size_t consumed = 1; |
| switch (byte) { |
| case '"': |
| escaped = "\\\""; |
| escaped_size = 2; |
| break; |
| case '\\': |
| escaped = "\\\\"; |
| escaped_size = 2; |
| break; |
| case '\b': |
| escaped = "\\b"; |
| escaped_size = 2; |
| break; |
| case '\f': |
| escaped = "\\f"; |
| escaped_size = 2; |
| break; |
| case '\n': |
| escaped = "\\n"; |
| escaped_size = 2; |
| break; |
| case '\r': |
| escaped = "\\r"; |
| escaped_size = 2; |
| break; |
| case '\t': |
| escaped = "\\t"; |
| escaped_size = 2; |
| break; |
| default: |
| if (byte < 0x20) { |
| static constexpr char HEX[] = "0123456789ABCDEF"; |
| char unicode_escape[] = {'\\', 'u', '0', '0', HEX[byte >> 4], HEX[byte & 0x0F]}; |
| if (index != plain_begin) { |
| out.write(value.data + plain_begin, index - plain_begin); |
| } |
| out.write(unicode_escape, sizeof(unicode_escape)); |
| ++index; |
| plain_begin = index; |
| continue; |
| } |
| if (index + 2 < value.size && byte == 0xE2 && |
| static_cast<uint8_t>(value.data[index + 1]) == 0x80 && |
| (static_cast<uint8_t>(value.data[index + 2]) == 0xA8 || |
| static_cast<uint8_t>(value.data[index + 2]) == 0xA9)) { |
| escaped = |
| static_cast<uint8_t>(value.data[index + 2]) == 0xA8 ? "\\u2028" : "\\u2029"; |
| escaped_size = 6; |
| consumed = 3; |
| } |
| break; |
| } |
| if (escaped == nullptr) { |
| ++index; |
| continue; |
| } |
| if (index != plain_begin) { |
| out.write(value.data + plain_begin, index - plain_begin); |
| } |
| out.write(escaped, escaped_size); |
| index += consumed; |
| plain_begin = index; |
| } |
| if (plain_begin != value.size) { |
| out.write(value.data + plain_begin, value.size - plain_begin); |
| } |
| write_literal(out, "\""); |
| } |
| |
| template <typename Writer> |
| void write_json_string(Writer& out, StringRef value, const char* description) { |
| require_valid_json_utf8(value, description); |
| write_escaped_json_string(out, value); |
| } |
| |
| template <typename Writer> |
| void write_json_binary(Writer& out, StringRef value) { |
| static constexpr char BASE64[] = |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| write_literal(out, "\""); |
| std::array<char, 1024> encoded {}; |
| size_t encoded_size = 0; |
| size_t index = 0; |
| const auto flush = [&]() { |
| if (encoded_size != 0) { |
| out.write(encoded.data(), encoded_size); |
| encoded_size = 0; |
| } |
| }; |
| while (index + 3 <= value.size) { |
| if (encoded.size() - encoded_size < 4) { |
| flush(); |
| } |
| const auto first = static_cast<uint8_t>(value.data[index]); |
| const auto second = static_cast<uint8_t>(value.data[index + 1]); |
| const auto third = static_cast<uint8_t>(value.data[index + 2]); |
| encoded[encoded_size++] = BASE64[first >> 2]; |
| encoded[encoded_size++] = BASE64[((first & 0x03) << 4) | (second >> 4)]; |
| encoded[encoded_size++] = BASE64[((second & 0x0F) << 2) | (third >> 6)]; |
| encoded[encoded_size++] = BASE64[third & 0x3F]; |
| index += 3; |
| } |
| if (index != value.size) { |
| if (encoded.size() - encoded_size < 4) { |
| flush(); |
| } |
| const auto first = static_cast<uint8_t>(value.data[index]); |
| encoded[encoded_size++] = BASE64[first >> 2]; |
| if (index + 1 == value.size) { |
| encoded[encoded_size++] = BASE64[(first & 0x03) << 4]; |
| encoded[encoded_size++] = '='; |
| encoded[encoded_size++] = '='; |
| } else { |
| const auto second = static_cast<uint8_t>(value.data[index + 1]); |
| encoded[encoded_size++] = BASE64[((first & 0x03) << 4) | (second >> 4)]; |
| encoded[encoded_size++] = BASE64[(second & 0x0F) << 2]; |
| encoded[encoded_size++] = '='; |
| } |
| } |
| flush(); |
| write_literal(out, "\""); |
| } |
| |
| template <typename Writer> |
| class Printer { |
| public: |
| Printer(Writer& out, const VariantJsonFormatOptions& options) : _out(out), _options(options) {} |
| |
| void write(VariantRef value, uint32_t depth) { |
| require_json_depth(depth); |
| switch (value.basic_type()) { |
| case VariantBasicType::SHORT_STRING: |
| write_json_string(_out, value.get_string(), "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; |
| } |
| } |
| |
| void write_plain_scalar(VariantRef value) { |
| require_json_depth(0); |
| if (value.basic_type() == VariantBasicType::SHORT_STRING) { |
| const StringRef string = value.get_string(); |
| require_valid_json_utf8(string, "string"); |
| _out.write(string.data, string.size); |
| return; |
| } |
| if (value.basic_type() != VariantBasicType::PRIMITIVE) { |
| write(value, 0); |
| return; |
| } |
| |
| switch (value.primitive_id()) { |
| case VariantPrimitiveId::STRING: { |
| const StringRef string = value.get_string(); |
| require_valid_json_utf8(string, "string"); |
| _out.write(string.data, string.size); |
| return; |
| } |
| case VariantPrimitiveId::DATE: |
| write_scalar(_out, format_json_date(value.get_date())); |
| return; |
| case VariantPrimitiveId::TIMESTAMP_MICROS: |
| write_scalar(_out, format_json_timestamp(value.get_timestamp_micros(), 6, true, |
| _options.timezone)); |
| return; |
| case VariantPrimitiveId::TIMESTAMP_NTZ_MICROS: |
| write_scalar(_out, format_json_timestamp(value.get_timestamp_ntz_micros(), 6, false, |
| nullptr)); |
| return; |
| case VariantPrimitiveId::TIME_NTZ_MICROS: |
| write_scalar(_out, format_json_time_micros(value.get_time_ntz_micros())); |
| return; |
| case VariantPrimitiveId::TIMESTAMP_NANOS: |
| write_scalar(_out, format_json_timestamp(value.get_timestamp_nanos(), 9, true, |
| _options.timezone)); |
| return; |
| case VariantPrimitiveId::TIMESTAMP_NTZ_NANOS: |
| write_scalar(_out, |
| format_json_timestamp(value.get_timestamp_ntz_nanos(), 9, false, nullptr)); |
| return; |
| case VariantPrimitiveId::UUID: |
| write_scalar(_out, format_json_uuid(value.get_uuid())); |
| return; |
| default: |
| write_primitive(value); |
| return; |
| } |
| } |
| |
| private: |
| void write_primitive(VariantRef value) { |
| const VariantPrimitiveId id = value.primitive_id(); |
| switch (id) { |
| case VariantPrimitiveId::NULL_VALUE: |
| write_literal(_out, "null"); |
| return; |
| case VariantPrimitiveId::TRUE_VALUE: |
| write_literal(_out, "true"); |
| return; |
| case VariantPrimitiveId::FALSE_VALUE: |
| write_literal(_out, "false"); |
| return; |
| case VariantPrimitiveId::INT8: |
| case VariantPrimitiveId::INT16: |
| case VariantPrimitiveId::INT32: |
| case VariantPrimitiveId::INT64: |
| write_scalar(_out, format_json_int(value.get_int())); |
| return; |
| case VariantPrimitiveId::DOUBLE: { |
| const double number = value.get_double(); |
| if (!std::isfinite(number)) { |
| write_non_finite(number); |
| return; |
| } |
| write_scalar(_out, format_json_double(number)); |
| return; |
| } |
| case VariantPrimitiveId::DECIMAL4: |
| case VariantPrimitiveId::DECIMAL8: |
| case VariantPrimitiveId::DECIMAL16: |
| write_scalar(_out, format_json_decimal(value.get_decimal())); |
| return; |
| case VariantPrimitiveId::DATE: |
| write_quoted_scalar(_out, format_json_date(value.get_date())); |
| return; |
| case VariantPrimitiveId::TIMESTAMP_MICROS: |
| write_quoted_scalar(_out, format_json_timestamp(value.get_timestamp_micros(), 6, true, |
| _options.timezone)); |
| return; |
| case VariantPrimitiveId::TIMESTAMP_NTZ_MICROS: |
| write_quoted_scalar(_out, format_json_timestamp(value.get_timestamp_ntz_micros(), 6, |
| false, nullptr)); |
| return; |
| case VariantPrimitiveId::FLOAT: { |
| const float number = value.get_float(); |
| if (!std::isfinite(number)) { |
| write_non_finite(number); |
| return; |
| } |
| write_scalar(_out, format_json_float(number)); |
| return; |
| } |
| case VariantPrimitiveId::BINARY: |
| write_json_binary(_out, value.get_binary()); |
| return; |
| case VariantPrimitiveId::STRING: |
| write_json_string(_out, value.get_string(), "string"); |
| return; |
| case VariantPrimitiveId::TIME_NTZ_MICROS: |
| write_quoted_scalar(_out, format_json_time_micros(value.get_time_ntz_micros())); |
| return; |
| case VariantPrimitiveId::TIMESTAMP_NANOS: |
| write_quoted_scalar(_out, format_json_timestamp(value.get_timestamp_nanos(), 9, true, |
| _options.timezone)); |
| return; |
| case VariantPrimitiveId::TIMESTAMP_NTZ_NANOS: |
| write_quoted_scalar(_out, format_json_timestamp(value.get_timestamp_ntz_nanos(), 9, |
| false, nullptr)); |
| return; |
| case VariantPrimitiveId::UUID: |
| write_quoted_scalar(_out, format_json_uuid(value.get_uuid())); |
| return; |
| } |
| throw_unsupported_json_primitive(id); |
| } |
| |
| template <typename Number> |
| void write_non_finite(Number value) { |
| if (std::isnan(value)) { |
| write_literal(_out, "\"NaN\""); |
| } else if (value > 0) { |
| write_literal(_out, "\"Infinity\""); |
| } else { |
| write_literal(_out, "\"-Infinity\""); |
| } |
| } |
| |
| void write_object(VariantRef value, uint32_t depth) { |
| write_literal(_out, "{"); |
| const uint32_t count = value.num_elements(); |
| StringRef previous_key; |
| for (uint32_t index = 0; index < count; ++index) { |
| if (index != 0) { |
| write_literal(_out, ","); |
| } |
| uint32_t field_id = 0; |
| VariantRef child = value.object_value_at(index, &field_id); |
| const StringRef key = value.metadata.key_at(field_id); |
| require_json_object_key(key, previous_key, index); |
| write_escaped_json_string(_out, key); |
| write_literal(_out, ":"); |
| write(child, depth + 1); |
| previous_key = key; |
| } |
| write_literal(_out, "}"); |
| } |
| |
| void write_array(VariantRef value, uint32_t depth) { |
| write_literal(_out, "["); |
| const uint32_t count = value.num_elements(); |
| for (uint32_t index = 0; index < count; ++index) { |
| if (index != 0) { |
| write_literal(_out, ","); |
| } |
| write(value.array_at(index), depth + 1); |
| } |
| write_literal(_out, "]"); |
| } |
| |
| Writer& _out; |
| const VariantJsonFormatOptions& _options; |
| }; |
| |
| } // namespace variant_json |
| |
| // Writer is statically dispatched and only needs write(const char*, size_t). |
| template <typename Writer> |
| void to_json(VariantRef value, Writer& out, |
| const VariantJsonFormatOptions& options = VariantJsonFormatOptions {}) { |
| variant_json::require_exact_json_value(value); |
| variant_json::Printer<Writer>(out, options).write(value, 0); |
| } |
| |
| // Preserve the current Variant V1 SQL result contract for top-level scalar values: JSON strings |
| // such as "value" and "2" are returned as value and 2. Once V1 is removed, this compatibility |
| // output will change to the uniform JSON representation ("value" and "2"). |
| template <typename Writer> |
| void to_sql_string(VariantRef value, Writer& out, |
| const VariantJsonFormatOptions& options = VariantJsonFormatOptions {}) { |
| variant_json::require_exact_json_value(value); |
| variant_json::Printer<Writer>(out, options).write_plain_scalar(value); |
| } |
| |
| } // namespace doris |