| /* |
| * 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. |
| */ |
| |
| /* This file is based on source code from the Spark Project (http://spark.apache.org/), licensed |
| * by the Apache Software Foundation (ASF) under the Apache License, Version 2.0. See the NOTICE |
| * file distributed with this work for additional information regarding copyright ownership. */ |
| |
| #include "paimon/common/data/variant/generic_variant.h" |
| |
| #include <cmath> |
| #include <cstring> |
| #include <utility> |
| |
| #include "arrow/util/base64.h" |
| #include "fmt/format.h" |
| #include "paimon/common/data/variant/variant_builder.h" |
| #include "paimon/common/data/variant/variant_defs.h" |
| #include "paimon/common/data/variant/variant_json_utils.h" |
| |
| namespace paimon { |
| |
| namespace { |
| |
| Status ToJsonImpl(std::string_view value, std::string_view metadata, int32_t pos, |
| const std::string& zone_id, std::string* out) { |
| PAIMON_ASSIGN_OR_RAISE(VariantValueType type, VariantBinaryUtil::GetType(value, pos)); |
| switch (type) { |
| case VariantValueType::kObject: { |
| PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info, |
| VariantBinaryUtil::GetObjectInfo(value, pos)); |
| out->push_back('{'); |
| for (int32_t i = 0; i < info.num_elements; ++i) { |
| PAIMON_ASSIGN_OR_RAISE(int32_t id, |
| VariantBinaryUtil::ReadUnsigned( |
| value, info.id_start + info.id_size * i, info.id_size)); |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t offset, |
| VariantBinaryUtil::ReadUnsigned(value, info.offset_start + info.offset_size * i, |
| info.offset_size)); |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t element_pos, |
| VariantBinaryUtil::CheckedElementPos(info.data_start, offset, value.size())); |
| if (i != 0) { |
| out->push_back(','); |
| } |
| PAIMON_ASSIGN_OR_RAISE(std::string_view key, |
| VariantBinaryUtil::GetMetadataKey(metadata, id)); |
| VariantJsonUtils::AppendEscapedJson(key, out); |
| out->push_back(':'); |
| PAIMON_RETURN_NOT_OK(ToJsonImpl(value, metadata, element_pos, zone_id, out)); |
| } |
| out->push_back('}'); |
| return Status::OK(); |
| } |
| case VariantValueType::kArray: { |
| PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ArrayInfo info, |
| VariantBinaryUtil::GetArrayInfo(value, pos)); |
| out->push_back('['); |
| for (int32_t i = 0; i < info.num_elements; ++i) { |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t offset, |
| VariantBinaryUtil::ReadUnsigned(value, info.offset_start + info.offset_size * i, |
| info.offset_size)); |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t element_pos, |
| VariantBinaryUtil::CheckedElementPos(info.data_start, offset, value.size())); |
| if (i != 0) { |
| out->push_back(','); |
| } |
| PAIMON_RETURN_NOT_OK(ToJsonImpl(value, metadata, element_pos, zone_id, out)); |
| } |
| out->push_back(']'); |
| return Status::OK(); |
| } |
| case VariantValueType::kNull: |
| out->append("null"); |
| return Status::OK(); |
| case VariantValueType::kBoolean: { |
| PAIMON_ASSIGN_OR_RAISE(bool b, VariantBinaryUtil::GetBoolean(value, pos)); |
| out->append(b ? "true" : "false"); |
| return Status::OK(); |
| } |
| case VariantValueType::kLong: { |
| PAIMON_ASSIGN_OR_RAISE(int64_t l, VariantBinaryUtil::GetLong(value, pos)); |
| out->append(std::to_string(l)); |
| return Status::OK(); |
| } |
| case VariantValueType::kString: { |
| PAIMON_ASSIGN_OR_RAISE(std::string_view s, VariantBinaryUtil::GetString(value, pos)); |
| VariantJsonUtils::AppendEscapedJson(s, out); |
| return Status::OK(); |
| } |
| case VariantValueType::kDouble: { |
| PAIMON_ASSIGN_OR_RAISE(double d, VariantBinaryUtil::GetDouble(value, pos)); |
| std::string repr = VariantJsonUtils::JavaDoubleToString(d); |
| if (std::isfinite(d)) { |
| out->append(repr); |
| } else { |
| out->push_back('"'); |
| out->append(repr); |
| out->push_back('"'); |
| } |
| return Status::OK(); |
| } |
| case VariantValueType::kDecimal: { |
| PAIMON_ASSIGN_OR_RAISE(VariantDecimal d, VariantBinaryUtil::GetDecimal(value, pos)); |
| out->append(d.ToPlainString()); |
| return Status::OK(); |
| } |
| case VariantValueType::kDate: { |
| PAIMON_ASSIGN_OR_RAISE(int64_t days, VariantBinaryUtil::GetLong(value, pos)); |
| out->push_back('"'); |
| out->append(VariantJsonUtils::DateToString(static_cast<int32_t>(days))); |
| out->push_back('"'); |
| return Status::OK(); |
| } |
| case VariantValueType::kTimestamp: { |
| PAIMON_ASSIGN_OR_RAISE(int64_t micros, VariantBinaryUtil::GetLong(value, pos)); |
| PAIMON_ASSIGN_OR_RAISE(int32_t offset_seconds, |
| VariantJsonUtils::GetZoneOffsetSeconds(zone_id, micros)); |
| out->push_back('"'); |
| out->append(VariantJsonUtils::TimestampToString(micros, offset_seconds, true)); |
| out->push_back('"'); |
| return Status::OK(); |
| } |
| case VariantValueType::kTimestampNtz: { |
| PAIMON_ASSIGN_OR_RAISE(int64_t micros, VariantBinaryUtil::GetLong(value, pos)); |
| out->push_back('"'); |
| out->append(VariantJsonUtils::TimestampToString(micros, 0, false)); |
| out->push_back('"'); |
| return Status::OK(); |
| } |
| case VariantValueType::kFloat: { |
| PAIMON_ASSIGN_OR_RAISE(float f, VariantBinaryUtil::GetFloat(value, pos)); |
| std::string repr = VariantJsonUtils::JavaFloatToString(f); |
| if (std::isfinite(f)) { |
| out->append(repr); |
| } else { |
| out->push_back('"'); |
| out->append(repr); |
| out->push_back('"'); |
| } |
| return Status::OK(); |
| } |
| case VariantValueType::kBinary: { |
| PAIMON_ASSIGN_OR_RAISE(std::string_view binary, |
| VariantBinaryUtil::GetBinary(value, pos)); |
| out->push_back('"'); |
| out->append(arrow::util::base64_encode(binary)); |
| out->push_back('"'); |
| return Status::OK(); |
| } |
| case VariantValueType::kUuid: { |
| PAIMON_ASSIGN_OR_RAISE(std::string_view uuid, VariantBinaryUtil::GetUuid(value, pos)); |
| out->push_back('"'); |
| out->append(VariantBinaryUtil::UuidToString(uuid)); |
| out->push_back('"'); |
| return Status::OK(); |
| } |
| } |
| return VariantBinaryUtil::MalformedVariant("unknown variant value type in JSON rendering"); |
| } |
| |
| } // namespace |
| |
| GenericVariant::GenericVariant(std::shared_ptr<Bytes> value, std::shared_ptr<Bytes> metadata, |
| int32_t pos) |
| : value_(std::move(value)), metadata_(std::move(metadata)), pos_(pos) {} |
| |
| Result<std::shared_ptr<GenericVariant>> GenericVariant::Create(std::shared_ptr<Bytes> value, |
| std::shared_ptr<Bytes> metadata) { |
| if (!value || !metadata) { |
| return Status::Invalid("variant value and metadata must not be null"); |
| } |
| // There is currently only one allowed version. |
| if (metadata->size() < 1 || (static_cast<uint8_t>((*metadata)[0]) & |
| VariantDefs::kVersionMask) != VariantDefs::kVersion) { |
| return VariantBinaryUtil::MalformedVariant("unsupported variant metadata version"); |
| } |
| // Don't attempt to use a Variant larger than 128 MiB. We'll never produce one, and it risks |
| // memory instability. |
| if (metadata->size() > static_cast<size_t>(VariantDefs::kSizeLimit) || |
| value->size() > static_cast<size_t>(VariantDefs::kSizeLimit)) { |
| return VariantBinaryUtil::VariantConstructorSizeLimit(); |
| } |
| return std::shared_ptr<GenericVariant>( |
| new GenericVariant(std::move(value), std::move(metadata), 0)); |
| } |
| |
| Result<std::shared_ptr<GenericVariant>> GenericVariant::Create( |
| std::string_view value, std::string_view metadata, const std::shared_ptr<MemoryPool>& pool) { |
| // Reject over-limit inputs before allocating and copying them. |
| if (value.size() > static_cast<size_t>(VariantDefs::kSizeLimit) || |
| metadata.size() > static_cast<size_t>(VariantDefs::kSizeLimit)) { |
| return VariantBinaryUtil::VariantConstructorSizeLimit(); |
| } |
| std::shared_ptr<Bytes> value_bytes = Bytes::AllocateBytes(value.size(), pool.get()); |
| if (!value.empty()) { |
| std::memcpy(value_bytes->data(), value.data(), value.size()); |
| } |
| std::shared_ptr<Bytes> metadata_bytes = Bytes::AllocateBytes(metadata.size(), pool.get()); |
| if (!metadata.empty()) { |
| // An empty (malformed) metadata view may carry a null data pointer; the size check |
| // keeps memcpy away from it. |
| std::memcpy(metadata_bytes->data(), metadata.data(), metadata.size()); |
| } |
| return Create(std::move(value_bytes), std::move(metadata_bytes)); |
| } |
| |
| Result<std::shared_ptr<GenericVariant>> GenericVariant::FromJson( |
| std::string_view json, const std::shared_ptr<MemoryPool>& pool) { |
| return VariantBuilder::ParseJson(json, /*allow_duplicate_keys=*/false, pool); |
| } |
| |
| Result<std::string_view> GenericVariant::Value() const { |
| std::string_view raw = RawValue(); |
| if (pos_ == 0) { |
| return raw; |
| } |
| PAIMON_ASSIGN_OR_RAISE(int32_t size, VariantBinaryUtil::ValueSize(raw, pos_)); |
| PAIMON_RETURN_NOT_OK( |
| VariantBinaryUtil::CheckIndex(pos_ + size - 1, static_cast<int32_t>(raw.size()))); |
| return raw.substr(pos_, size); |
| } |
| |
| std::string_view GenericVariant::RawValue() const { |
| return {value_->data(), value_->size()}; |
| } |
| |
| std::string_view GenericVariant::Metadata() const { |
| return {metadata_->data(), metadata_->size()}; |
| } |
| |
| int64_t GenericVariant::SizeInBytes() const { |
| return static_cast<int64_t>(value_->size()) + static_cast<int64_t>(metadata_->size()); |
| } |
| |
| Result<std::string> GenericVariant::ToJson(const std::string& zone_id) const { |
| std::string result; |
| PAIMON_RETURN_NOT_OK(ToJsonImpl(RawValue(), Metadata(), pos_, zone_id, &result)); |
| return result; |
| } |
| |
| Result<VariantValueType> GenericVariant::GetType() const { |
| return VariantBinaryUtil::GetType(RawValue(), pos_); |
| } |
| |
| Result<int32_t> GenericVariant::GetTypeInfo() const { |
| return VariantBinaryUtil::GetTypeInfo(RawValue(), pos_); |
| } |
| |
| Result<bool> GenericVariant::GetBoolean() const { |
| return VariantBinaryUtil::GetBoolean(RawValue(), pos_); |
| } |
| |
| Result<int64_t> GenericVariant::GetLong() const { |
| return VariantBinaryUtil::GetLong(RawValue(), pos_); |
| } |
| |
| Result<double> GenericVariant::GetDouble() const { |
| return VariantBinaryUtil::GetDouble(RawValue(), pos_); |
| } |
| |
| Result<VariantDecimal> GenericVariant::GetDecimal() const { |
| return VariantBinaryUtil::GetDecimal(RawValue(), pos_); |
| } |
| |
| Result<float> GenericVariant::GetFloat() const { |
| return VariantBinaryUtil::GetFloat(RawValue(), pos_); |
| } |
| |
| Result<std::string_view> GenericVariant::GetBinary() const { |
| return VariantBinaryUtil::GetBinary(RawValue(), pos_); |
| } |
| |
| Result<std::string_view> GenericVariant::GetString() const { |
| return VariantBinaryUtil::GetString(RawValue(), pos_); |
| } |
| |
| Result<std::string_view> GenericVariant::GetUuid() const { |
| return VariantBinaryUtil::GetUuid(RawValue(), pos_); |
| } |
| |
| Result<int32_t> GenericVariant::ObjectSize() const { |
| PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info, |
| VariantBinaryUtil::GetObjectInfo(RawValue(), pos_)); |
| return info.num_elements; |
| } |
| |
| std::shared_ptr<GenericVariant> GenericVariant::SubVariant(int32_t pos) const { |
| return std::shared_ptr<GenericVariant>(new GenericVariant(value_, metadata_, pos)); |
| } |
| |
| Result<std::shared_ptr<GenericVariant>> GenericVariant::GetFieldByKey(std::string_view key) const { |
| std::string_view raw = RawValue(); |
| std::string_view metadata = Metadata(); |
| PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info, |
| VariantBinaryUtil::GetObjectInfo(raw, pos_)); |
| // Use linear search for a short list. Switch to binary search when the length reaches |
| // `kBinarySearchThreshold`. |
| if (info.num_elements < VariantDefs::kBinarySearchThreshold) { |
| for (int32_t i = 0; i < info.num_elements; ++i) { |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t id, VariantBinaryUtil::ReadUnsigned(raw, info.id_start + info.id_size * i, |
| info.id_size)); |
| PAIMON_ASSIGN_OR_RAISE(std::string_view field_key, |
| VariantBinaryUtil::GetMetadataKey(metadata, id)); |
| if (field_key == key) { |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t offset, |
| VariantBinaryUtil::ReadUnsigned(raw, info.offset_start + info.offset_size * i, |
| info.offset_size)); |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t element_pos, |
| VariantBinaryUtil::CheckedElementPos(info.data_start, offset, raw.size())); |
| return SubVariant(element_pos); |
| } |
| } |
| } else { |
| int32_t low = 0; |
| int32_t high = info.num_elements - 1; |
| while (low <= high) { |
| // Use an unsigned shift to compute the middle of `low` and `high`, which properly |
| // handles the case where `low + high` overflows int32. |
| auto mid = static_cast<int32_t>( |
| (static_cast<uint32_t>(low) + static_cast<uint32_t>(high)) >> 1); |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t id, VariantBinaryUtil::ReadUnsigned(raw, info.id_start + info.id_size * mid, |
| info.id_size)); |
| PAIMON_ASSIGN_OR_RAISE(std::string_view field_key, |
| VariantBinaryUtil::GetMetadataKey(metadata, id)); |
| int32_t cmp = field_key.compare(key); |
| if (cmp < 0) { |
| low = mid + 1; |
| } else if (cmp > 0) { |
| high = mid - 1; |
| } else { |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t offset, |
| VariantBinaryUtil::ReadUnsigned(raw, info.offset_start + info.offset_size * mid, |
| info.offset_size)); |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t element_pos, |
| VariantBinaryUtil::CheckedElementPos(info.data_start, offset, raw.size())); |
| return SubVariant(element_pos); |
| } |
| } |
| } |
| return std::shared_ptr<GenericVariant>(nullptr); |
| } |
| |
| Result<std::optional<GenericVariant::ObjectField>> GenericVariant::GetFieldAtIndex( |
| int32_t index) const { |
| std::string_view raw = RawValue(); |
| PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info, |
| VariantBinaryUtil::GetObjectInfo(raw, pos_)); |
| if (index < 0 || index >= info.num_elements) { |
| return std::optional<ObjectField>(std::nullopt); |
| } |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t id, |
| VariantBinaryUtil::ReadUnsigned(raw, info.id_start + info.id_size * index, info.id_size)); |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t offset, VariantBinaryUtil::ReadUnsigned( |
| raw, info.offset_start + info.offset_size * index, info.offset_size)); |
| PAIMON_ASSIGN_OR_RAISE(std::string_view key, VariantBinaryUtil::GetMetadataKey(Metadata(), id)); |
| ObjectField field; |
| field.key = std::string(key); |
| PAIMON_ASSIGN_OR_RAISE(int32_t field_pos, VariantBinaryUtil::CheckedElementPos( |
| info.data_start, offset, raw.size())); |
| field.value = SubVariant(field_pos); |
| return std::optional<ObjectField>(std::move(field)); |
| } |
| |
| Result<int32_t> GenericVariant::GetDictionaryIdAtIndex(int32_t index) const { |
| std::string_view raw = RawValue(); |
| PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info, |
| VariantBinaryUtil::GetObjectInfo(raw, pos_)); |
| if (index < 0 || index >= info.num_elements) { |
| return VariantBinaryUtil::MalformedVariant(fmt::format( |
| "object field index {} is out of bounds for {} fields", index, info.num_elements)); |
| } |
| return VariantBinaryUtil::ReadUnsigned(raw, info.id_start + info.id_size * index, info.id_size); |
| } |
| |
| Result<int32_t> GenericVariant::ArraySize() const { |
| PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ArrayInfo info, |
| VariantBinaryUtil::GetArrayInfo(RawValue(), pos_)); |
| return info.num_elements; |
| } |
| |
| Result<std::shared_ptr<GenericVariant>> GenericVariant::GetElementAtIndex(int32_t index) const { |
| std::string_view raw = RawValue(); |
| PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ArrayInfo info, |
| VariantBinaryUtil::GetArrayInfo(raw, pos_)); |
| if (index < 0 || index >= info.num_elements) { |
| return std::shared_ptr<GenericVariant>(nullptr); |
| } |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t offset, VariantBinaryUtil::ReadUnsigned( |
| raw, info.offset_start + info.offset_size * index, info.offset_size)); |
| PAIMON_ASSIGN_OR_RAISE(int32_t element_pos, VariantBinaryUtil::CheckedElementPos( |
| info.data_start, offset, raw.size())); |
| return SubVariant(element_pos); |
| } |
| |
| bool GenericVariant::operator==(const GenericVariant& other) const { |
| return pos_ == other.pos_ && RawValue() == other.RawValue() && Metadata() == other.Metadata(); |
| } |
| |
| } // namespace paimon |