blob: 34b74c0b775cd1e87b6d08f98c55990904cd1bbc [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.
*/
/* 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