| // 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 "runtime/variant-value.h" |
| |
| #include <cstdio> |
| #include <cstring> |
| |
| #include <rapidjson/stringbuffer.h> |
| #include <rapidjson/writer.h> |
| |
| #include "common/names.h" |
| #include "gutil/strings/substitute.h" |
| #include "runtime/date-parse-util.h" |
| #include "runtime/date-value.h" |
| #include "runtime/datetime-simple-date-format-parser.h" |
| #include "runtime/decimal-value.inline.h" |
| #include "runtime/timestamp-parse-util.h" |
| #include "runtime/timestamp-value.h" |
| #include "runtime/timestamp-value.inline.h" |
| #include "udf/udf.h" |
| #include "util/coding-util.h" |
| |
| using impala::datetime_parse_util::SimpleDateFormatTokenizer; |
| using std::string_view; |
| |
| namespace impala { |
| |
| // --- VariantMetadata --- |
| |
| Status VariantMetadata::Init(const uint8_t* data, uint32_t len) { |
| if (len < 1) { |
| return Status("Variant metadata blob is empty"); |
| } |
| |
| uint8_t header = data[0]; |
| version_ = header & 0x0F; |
| if (version_ != 1) { |
| return Status(Substitute( |
| "Unsupported variant metadata version: $0", version_)); |
| } |
| is_sorted_ = (header >> 4) & 0x01; |
| offset_size_ = ((header >> 6) & 0x03) + 1; |
| |
| // All offset arithmetic below is done in 64-bit so that a corrupt (large) dict_size_ |
| // cannot overflow the bounds checks and let an under-sized blob pass validation. |
| uint64_t pos = 1; |
| if (pos + offset_size_ > len) { |
| return Status("Variant metadata too short for dictionary size"); |
| } |
| dict_size_ = 0; |
| for (int i = 0; i < offset_size_; ++i) { |
| dict_size_ |= static_cast<uint32_t>(data[pos + i]) << (8 * i); |
| } |
| pos += offset_size_; |
| |
| // Offsets array: (dict_size_ + 1) entries of offset_size_ bytes each. |
| offsets_ = data + pos; |
| uint64_t offsets_len = (static_cast<uint64_t>(dict_size_) + 1) * offset_size_; |
| if (pos + offsets_len > len) { |
| return Status("Variant metadata too short for offset array"); |
| } |
| pos += offsets_len; |
| |
| string_data_ = data + pos; |
| // pos <= len is guaranteed by the check above, so this fits in uint32_t. |
| string_data_len_ = static_cast<uint32_t>(len - pos); |
| |
| // Validate the dictionary offsets once here (rather than on every field-name access): |
| // they must be non-decreasing and stay within the string-data region, so GetFieldName() |
| // can never construct a string_view that points out of bounds. |
| uint32_t prev = ReadOffset(0); |
| for (uint32_t i = 1; i <= dict_size_; ++i) { |
| uint32_t cur = ReadOffset(i); |
| // Offsets must be non-decreasing. Equal consecutive offsets denote a zero-length |
| // (empty-string) field name, e.g. the key in {"": 1}: that is valid JSON and is not |
| // forbidden by the Variant spec, so only a strict decrease is rejected. |
| if (cur < prev) { |
| return Status("Variant metadata has non-monotonic dictionary offsets"); |
| } |
| prev = cur; |
| } |
| if (prev > string_data_len_) { |
| return Status("Variant metadata dictionary offset exceeds string data"); |
| } |
| return Status::OK(); |
| } |
| |
| uint32_t VariantMetadata::ReadOffset(uint32_t index) const { |
| static_assert(__BYTE_ORDER == __LITTLE_ENDIAN, "This code assumes little-endianness"); |
| const uint8_t* p = offsets_ + index * offset_size_; |
| uint32_t val = 0; |
| |
| switch (offset_size_) { |
| case 4: |
| std::memcpy(&val, p, 4); |
| return val; |
| case 2: |
| std::memcpy(&val, p, 2); |
| return val; |
| case 1: |
| return *p; |
| case 3: |
| std::memcpy(&val, p, 3); |
| return val; |
| default: |
| return 0; |
| } |
| } |
| |
| string_view VariantMetadata::GetFieldName(uint32_t index) const { |
| // Callers (FindFieldId and FieldNameFromLayout) must bound-check the index. |
| DCHECK_LT(index, dict_size_); |
| uint32_t start = ReadOffset(index); |
| uint32_t end = ReadOffset(index + 1); |
| // Guaranteed by the offset validation in Init(). |
| DCHECK_LE(start, end); |
| DCHECK_LE(end, string_data_len_); |
| return string_view(reinterpret_cast<const char*>(string_data_ + start), |
| end - start); |
| } |
| |
| int VariantMetadata::FindFieldId(string_view name) const { |
| if (is_sorted_) { |
| int lo = 0, hi = dict_size_ - 1; |
| while (lo <= hi) { |
| int mid = (lo + hi) / 2; |
| int cmp = name.compare(GetFieldName(mid)); |
| if (cmp == 0) return mid; |
| if (cmp < 0) hi = mid - 1; |
| else lo = mid + 1; |
| } |
| } else { |
| for (uint32_t i = 0; i < dict_size_; ++i) { |
| if (name == GetFieldName(i)) return i; |
| } |
| } |
| return -1; |
| } |
| |
| // --- VariantValue --- |
| |
| uint32_t VariantValue::ReadUint(const uint8_t* data, uint32_t size) { |
| static_assert(__BYTE_ORDER == __LITTLE_ENDIAN, "This code assumes little-endianness"); |
| uint32_t val = 0; |
| switch (size) { |
| case 4: |
| std::memcpy(&val, data, 4); |
| return val; |
| case 2: |
| std::memcpy(&val, data, 2); |
| return val; |
| case 1: |
| return *data; |
| case 3: |
| std::memcpy(&val, data, 3); |
| return val; |
| default: |
| return 0; |
| } |
| } |
| |
| VariantBasicType VariantValue::GetBasicType() const { |
| DCHECK(data_ != nullptr); |
| return static_cast<VariantBasicType>(data_[0] & 0x03); |
| } |
| |
| VariantPhysicalType VariantValue::GetPhysicalType() const { |
| DCHECK_EQ(GetBasicType(), VariantBasicType::PRIMITIVE); |
| return static_cast<VariantPhysicalType>((data_[0] >> 2) & 0x3F); |
| } |
| |
| bool VariantValue::IsNull() const { |
| return GetBasicType() == VariantBasicType::PRIMITIVE |
| && GetPhysicalType() == VariantPhysicalType::VNULL; |
| } |
| |
| bool VariantValue::GetBoolean(bool* out) const { |
| VariantPhysicalType pt; |
| if (!AsPrimitive(&pt)) return false; |
| if (pt == VariantPhysicalType::BOOLEAN_TRUE) { *out = true; return true; } |
| if (pt == VariantPhysicalType::BOOLEAN_FALSE) { *out = false; return true; } |
| return false; |
| } |
| |
| bool VariantValue::GetInt8(int8_t* out) const { |
| return ReadValueOfType(VariantPhysicalType::INT8, out); |
| } |
| |
| bool VariantValue::GetInt16(int16_t* out) const { |
| return ReadValueOfType(VariantPhysicalType::INT16, out); |
| } |
| |
| bool VariantValue::GetInt32(int32_t* out) const { |
| return ReadValueOfType(VariantPhysicalType::INT32, out); |
| } |
| |
| bool VariantValue::GetInt64(int64_t* out) const { |
| return ReadValueOfType(VariantPhysicalType::INT64, out); |
| } |
| |
| bool VariantValue::GetFloat(float* out) const { |
| return ReadValueOfType(VariantPhysicalType::FLOAT, out); |
| } |
| |
| bool VariantValue::GetDouble(double* out) const { |
| return ReadValueOfType(VariantPhysicalType::DOUBLE, out); |
| } |
| |
| bool VariantValue::GetString(StringValue* out) const { |
| if (data_ == nullptr || len_ < 1) return false; |
| VariantBasicType bt = GetBasicType(); |
| if (bt == VariantBasicType::SHORT_STRING) { |
| uint32_t str_len = (data_[0] >> 2) & 0x3F; |
| if (str_len > len_ - 1) return false; |
| out->Assign(reinterpret_cast<char*>(const_cast<uint8_t*>(data_ + 1)), str_len); |
| return true; |
| } |
| if (bt != VariantBasicType::PRIMITIVE |
| || GetPhysicalType() != VariantPhysicalType::STRING || len_ < 5) { |
| return false; |
| } |
| uint32_t str_len = ReadUint(data_ + 1, 4); |
| if (str_len > len_ - 5) return false; |
| out->Assign(reinterpret_cast<char*>(const_cast<uint8_t*>(data_ + 5)), str_len); |
| return true; |
| } |
| |
| bool VariantValue::GetBinary(StringValue* out) const { |
| VariantPhysicalType pt; |
| if (!AsPrimitive(&pt) || pt != VariantPhysicalType::BINARY || len_ < 5) return false; |
| uint32_t bin_len = ReadUint(data_ + 1, 4); |
| if (bin_len > len_ - 5) return false; |
| out->Assign(reinterpret_cast<char*>(const_cast<uint8_t*>(data_ + 5)), bin_len); |
| return true; |
| } |
| |
| // --- Object accessors --- |
| |
| uint32_t VariantValue::ObjectFieldIdSize() const { |
| DCHECK_EQ(GetBasicType(), VariantBasicType::OBJECT); |
| return ((data_[0] >> 4) & 0x03) + 1; |
| } |
| |
| uint32_t VariantValue::ObjectOffsetSize() const { |
| DCHECK_EQ(GetBasicType(), VariantBasicType::OBJECT); |
| return ((data_[0] >> 2) & 0x03) + 1; |
| } |
| |
| bool VariantValue::ParseObjectLayout(ObjectLayout* out) const { |
| if (data_ == nullptr || len_ < 1) return false; |
| if (GetBasicType() != VariantBasicType::OBJECT) return false; |
| bool is_large = (data_[0] >> 6) & 0x01; |
| uint32_t num_size = is_large ? 4 : 1; |
| if (len_ < 1 + num_size) return false; |
| uint32_t num_fields = ReadUint(data_ + 1, num_size); |
| uint32_t field_id_size = ObjectFieldIdSize(); |
| uint32_t offset_size = ObjectOffsetSize(); |
| // Layout: header | field_ids[num_fields] | offsets[num_fields + 1] | data. |
| // 64-bit math so a corrupt (large) num_fields cannot overflow the bound check. |
| uint64_t data_start_off = static_cast<uint64_t>(1) + num_size |
| + static_cast<uint64_t>(num_fields) * field_id_size |
| + (static_cast<uint64_t>(num_fields) + 1) * offset_size; |
| if (data_start_off > len_) return false; |
| out->num_fields = num_fields; |
| out->field_ids = data_ + 1 + num_size; |
| out->offsets = out->field_ids + static_cast<uint64_t>(num_fields) * field_id_size; |
| out->data = data_ + data_start_off; |
| out->data_len = static_cast<uint32_t>(len_ - data_start_off); |
| return true; |
| } |
| |
| bool VariantValue::GetObjectSize(uint32_t* out) const { |
| ObjectLayout layout; |
| if (!ParseObjectLayout(&layout)) return false; |
| *out = layout.num_fields; |
| return true; |
| } |
| |
| bool VariantValue::GetFieldByName(string_view name, VariantValue* result) const { |
| if (metadata_ == nullptr) return false; |
| ObjectLayout layout; |
| if (!ParseObjectLayout(&layout)) return false; |
| |
| int field_id = metadata_->FindFieldId(name); |
| if (field_id < 0) return false; |
| |
| // Search for this field_id in the object's field_id array. |
| uint32_t field_id_size = ObjectFieldIdSize(); |
| for (uint32_t i = 0; i < layout.num_fields; ++i) { |
| uint32_t fid = ReadUint(layout.field_ids + i * field_id_size, field_id_size); |
| if (fid == static_cast<uint32_t>(field_id)) return FieldFromLayout(layout, i, result); |
| } |
| return false; |
| } |
| |
| bool VariantValue::FieldFromLayout(const ObjectLayout& layout, uint32_t index, |
| VariantValue* result) const { |
| if (index >= layout.num_fields) return false; |
| uint32_t offset_size = ObjectOffsetSize(); |
| uint32_t start = ReadUint(layout.offsets + index * offset_size, offset_size); |
| uint32_t end = ReadUint(layout.offsets + (index + 1) * offset_size, offset_size); |
| if (end < start || end > layout.data_len) return false; |
| *result = VariantValue(layout.data + start, end - start, metadata_); |
| return true; |
| } |
| |
| bool VariantValue::GetFieldByIndex(uint32_t index, VariantValue* result) const { |
| ObjectLayout layout; |
| if (!ParseObjectLayout(&layout)) return false; |
| return FieldFromLayout(layout, index, result); |
| } |
| |
| bool VariantValue::FieldNameFromLayout(const ObjectLayout& layout, uint32_t index, |
| string_view* out) const { |
| if (index >= layout.num_fields) return false; |
| uint32_t field_id_size = ObjectFieldIdSize(); |
| uint32_t fid = ReadUint(layout.field_ids + index * field_id_size, field_id_size); |
| uint32_t dict_size = (metadata_ != nullptr) ? metadata_->DictionarySize() : 0; |
| if (fid >= dict_size) return false; |
| *out = metadata_->GetFieldName(fid); |
| return true; |
| } |
| |
| bool VariantValue::GetFieldNameByIndex(uint32_t index, string_view* out) const { |
| ObjectLayout layout; |
| if (!ParseObjectLayout(&layout)) return false; |
| return FieldNameFromLayout(layout, index, out); |
| } |
| |
| // --- Array accessors --- |
| |
| uint32_t VariantValue::ArrayOffsetSize() const { |
| DCHECK_EQ(GetBasicType(), VariantBasicType::ARRAY); |
| return ((data_[0] >> 2) & 0x03) + 1; |
| } |
| |
| bool VariantValue::ParseArrayLayout(ArrayLayout* out) const { |
| if (data_ == nullptr || len_ < 1) return false; |
| if (GetBasicType() != VariantBasicType::ARRAY) return false; |
| bool is_large = (data_[0] >> 4) & 0x01; |
| uint32_t num_size = is_large ? 4 : 1; |
| if (len_ < 1 + num_size) return false; |
| uint32_t num_elems = ReadUint(data_ + 1, num_size); |
| uint32_t offset_size = ArrayOffsetSize(); |
| // Layout: header | offsets[num_elems + 1] | data. |
| uint64_t data_start_off = static_cast<uint64_t>(1) + num_size |
| + (static_cast<uint64_t>(num_elems) + 1) * offset_size; |
| if (data_start_off > len_) return false; |
| out->num_elems = num_elems; |
| out->offsets = data_ + 1 + num_size; |
| out->data = data_ + data_start_off; |
| out->data_len = static_cast<uint32_t>(len_ - data_start_off); |
| return true; |
| } |
| |
| bool VariantValue::GetArraySize(uint32_t* out) const { |
| ArrayLayout layout; |
| if (!ParseArrayLayout(&layout)) return false; |
| *out = layout.num_elems; |
| return true; |
| } |
| |
| bool VariantValue::ElementFromLayout(const ArrayLayout& layout, uint32_t index, |
| VariantValue* result) const { |
| if (index >= layout.num_elems) return false; |
| uint32_t offset_size = ArrayOffsetSize(); |
| uint32_t start = ReadUint(layout.offsets + index * offset_size, offset_size); |
| uint32_t end = ReadUint(layout.offsets + (index + 1) * offset_size, offset_size); |
| if (end < start || end > layout.data_len) return false; |
| *result = VariantValue(layout.data + start, end - start, metadata_); |
| return true; |
| } |
| |
| bool VariantValue::GetArrayElement(uint32_t index, VariantValue* result) const { |
| ArrayLayout layout; |
| if (!ParseArrayLayout(&layout)) return false; |
| return ElementFromLayout(layout, index, result); |
| } |
| |
| // --- Path navigation --- |
| |
| bool VariantValue::NavigatePath(const string& path, |
| VariantValue* result) const { |
| *result = *this; |
| if (path == "$") return true; |
| |
| const char* p = path.data(); |
| const char* end = p + path.size(); |
| |
| // Path must start with '$'. |
| if (p >= end || *p != '$') return false; |
| ++p; |
| |
| // '$' alone is handled above; after '$' we need '.' or '['. |
| if (p >= end) return false; |
| if (*p != '.' && *p != '[') return false; |
| if (*p == '.') ++p; |
| |
| // Must have at least one segment after the prefix. |
| if (p >= end) return false; |
| |
| while (p < end) { |
| if (*p == '[') { |
| ++p; |
| // Require at least one digit. |
| const char* digits_start = p; |
| int index = 0; |
| while (p < end && *p >= '0' && *p <= '9') { |
| index = index * 10 + (*p - '0'); |
| ++p; |
| } |
| if (p == digits_start) return false; |
| if (p >= end || *p != ']') return false; |
| ++p; // skip ']' |
| if (!result->GetArrayElement(index, result)) return false; |
| if (p < end && *p == '.') ++p; |
| } else { |
| const char* seg_start = p; |
| while (p < end && *p != '.' && *p != '[') ++p; |
| int seg_len = p - seg_start; |
| if (seg_len == 0) return false; |
| if (!result->GetFieldByName(string_view(seg_start, seg_len), result)) { |
| return false; |
| } |
| if (p < end && *p == '.') ++p; |
| } |
| } |
| return true; |
| } |
| |
| // --- JSON serialization --- |
| |
| using JsonWriter = rapidjson::Writer<rapidjson::StringBuffer>; |
| |
| // Builds the error status returned when a variant value cannot be decoded because it is |
| // truncated or its payload would read out of bounds. 'what' names the offending part, |
| // e.g. "INT32" or "object field name". |
| static Status MalformedVariant(const char* what) { |
| return Status(Substitute("Malformed variant $0", what)); |
| } |
| |
| // Serializes a VariantValue to JSON. A friend of VariantValue (declared in the header) so |
| // it can parse each object/array layout once via the private layout helpers and reuse it |
| // across the field/element loop, while keeping rapidjson out of variant-value.h. |
| struct VariantJsonSerializer { |
| static Status Write(const VariantValue& val, const VariantMetadata& metadata, |
| JsonWriter* writer, int depth = ColumnType::MAX_NESTING_DEPTH); |
| }; |
| |
| Status VariantJsonSerializer::Write(const VariantValue& val, |
| const VariantMetadata& metadata, JsonWriter* writer, int depth) { |
| if (UNLIKELY(depth <= 0)) { |
| return Status("Variant value nesting exceeds the maximum allowed depth"); |
| } |
| if (UNLIKELY(!val.IsValid() || val.Len() < 1)) { |
| return MalformedVariant("value: empty or truncated value buffer"); |
| } |
| switch (val.GetBasicType()) { |
| case VariantBasicType::SHORT_STRING: { |
| StringValue sv; |
| if (UNLIKELY(!val.GetString(&sv))) { |
| return MalformedVariant("short string"); |
| } |
| writer->String(sv.Ptr(), sv.Len()); |
| return Status::OK(); |
| } |
| case VariantBasicType::PRIMITIVE: { |
| switch (val.GetPhysicalType()) { |
| case VariantPhysicalType::VNULL: |
| writer->Null(); |
| break; |
| case VariantPhysicalType::BOOLEAN_TRUE: |
| writer->Bool(true); |
| break; |
| case VariantPhysicalType::BOOLEAN_FALSE: |
| writer->Bool(false); |
| break; |
| case VariantPhysicalType::INT8: { |
| int8_t v; |
| if (UNLIKELY(!val.GetInt8(&v))) return MalformedVariant("INT8"); |
| writer->Int(v); |
| break; |
| } |
| case VariantPhysicalType::INT16: { |
| int16_t v; |
| if (UNLIKELY(!val.GetInt16(&v))) return MalformedVariant("INT16"); |
| writer->Int(v); |
| break; |
| } |
| case VariantPhysicalType::INT32: { |
| int32_t v; |
| if (UNLIKELY(!val.GetInt32(&v))) return MalformedVariant("INT32"); |
| writer->Int(v); |
| break; |
| } |
| case VariantPhysicalType::INT64: { |
| int64_t v; |
| if (UNLIKELY(!val.GetInt64(&v))) return MalformedVariant("INT64"); |
| writer->Int64(v); |
| break; |
| } |
| case VariantPhysicalType::FLOAT: { |
| float v; |
| if (UNLIKELY(!val.GetFloat(&v))) return MalformedVariant("FLOAT"); |
| char buf[24]; |
| int n = snprintf(buf, sizeof(buf), "%g", v); |
| writer->RawValue(buf, n, rapidjson::kNumberType); |
| break; |
| } |
| case VariantPhysicalType::DOUBLE: { |
| double v; |
| if (UNLIKELY(!val.GetDouble(&v))) return MalformedVariant("DOUBLE"); |
| writer->Double(v); |
| break; |
| } |
| case VariantPhysicalType::STRING: { |
| StringValue sv; |
| if (UNLIKELY(!val.GetString(&sv))) return MalformedVariant("STRING"); |
| writer->String(sv.Ptr(), sv.Len()); |
| break; |
| } |
| case VariantPhysicalType::DATE: { |
| int32_t days; |
| if (UNLIKELY(!val.ReadValue(&days))) return MalformedVariant("DATE"); |
| DateValue dv(static_cast<int64_t>(days)); |
| char buf[SimpleDateFormatTokenizer::DEFAULT_DATE_FMT_LEN]; |
| int n = DateParser::FormatDefault(dv, buf); |
| if (UNLIKELY(n <= 0)) return Status("Variant DATE value out of range"); |
| DCHECK_LE(n, sizeof(buf)); |
| writer->String(buf, n); |
| break; |
| } |
| case VariantPhysicalType::DECIMAL4: { |
| int32_t unscaled; |
| if (UNLIKELY(!val.ReadValue(&unscaled, 2))) { |
| return MalformedVariant("DECIMAL4"); |
| } |
| int scale = val.Data()[1]; |
| if (UNLIKELY(scale > ColumnType::MAX_DECIMAL4_PRECISION)) { |
| return MalformedVariant("DECIMAL4: scale out of range"); |
| } |
| string s = Decimal4Value(unscaled).ToString( |
| ColumnType::MAX_DECIMAL4_PRECISION, scale); |
| writer->RawValue(s.data(), s.size(), rapidjson::kNumberType); |
| break; |
| } |
| case VariantPhysicalType::DECIMAL8: { |
| int64_t unscaled; |
| if (UNLIKELY(!val.ReadValue(&unscaled, 2))) { |
| return MalformedVariant("DECIMAL8"); |
| } |
| int scale = val.Data()[1]; |
| if (UNLIKELY(scale > ColumnType::MAX_DECIMAL8_PRECISION)) { |
| return MalformedVariant("DECIMAL8: scale out of range"); |
| } |
| string s = Decimal8Value(unscaled).ToString( |
| ColumnType::MAX_DECIMAL8_PRECISION, scale); |
| writer->RawValue(s.data(), s.size(), rapidjson::kNumberType); |
| break; |
| } |
| case VariantPhysicalType::DECIMAL16: { |
| __int128_t unscaled; |
| if (UNLIKELY(!val.ReadValue(&unscaled, 2))) { |
| return MalformedVariant("DECIMAL16"); |
| } |
| int scale = val.Data()[1]; |
| if (UNLIKELY(scale > ColumnType::MAX_PRECISION)) { |
| return MalformedVariant("DECIMAL16: scale out of range"); |
| } |
| string s = Decimal16Value(unscaled).ToString( |
| ColumnType::MAX_PRECISION, scale); |
| writer->RawValue(s.data(), s.size(), rapidjson::kNumberType); |
| break; |
| } |
| case VariantPhysicalType::TIMESTAMPNTZ: { |
| int64_t micros; |
| if (UNLIKELY(!val.ReadValue(µs))) { |
| return MalformedVariant("TIMESTAMP"); |
| } |
| TimestampValue ts = TimestampValue::UtcFromUnixTimeMicros(micros); |
| char buf[SimpleDateFormatTokenizer::DEFAULT_DATE_TIME_FMT_LEN]; |
| int n = TimestampParser::FormatDefault(ts.date(), ts.time(), buf); |
| if (UNLIKELY(n <= 0)) return Status("Variant TIMESTAMP value out of range"); |
| DCHECK_LE(n, sizeof(buf)); |
| writer->String(buf, n); |
| break; |
| } |
| case VariantPhysicalType::TIMESTAMPNTZ_NANOS: { |
| int64_t nanos; |
| if (UNLIKELY(!val.ReadValue(&nanos))) { |
| return MalformedVariant("TIMESTAMP"); |
| } |
| TimestampValue ts = |
| TimestampValue::UtcFromUnixTimeLimitedRangeNanos(nanos); |
| char buf[SimpleDateFormatTokenizer::DEFAULT_DATE_TIME_FMT_LEN]; |
| int n = TimestampParser::FormatDefault(ts.date(), ts.time(), buf); |
| if (UNLIKELY(n <= 0)) return Status("Variant TIMESTAMP value out of range"); |
| DCHECK_LE(n, sizeof(buf)); |
| writer->String(buf, n); |
| break; |
| } |
| case VariantPhysicalType::BINARY: { |
| StringValue sv; |
| if (UNLIKELY(!val.GetBinary(&sv))) return MalformedVariant("BINARY"); |
| int64_t out_max; |
| if (UNLIKELY(!Base64EncodeBufLen(sv.Len(), &out_max))) { |
| return Status("Variant BINARY value too large to encode"); |
| } |
| string encoded(out_max, '\0'); |
| unsigned out_len; |
| Base64Encode(sv.Ptr(), sv.Len(), out_max, encoded.data(), &out_len); |
| writer->String(encoded.data(), out_len); |
| break; |
| } |
| case VariantPhysicalType::TIMESTAMPTZ: |
| case VariantPhysicalType::TIME: |
| case VariantPhysicalType::TIMESTAMPTZ_NANOS: |
| case VariantPhysicalType::UUID: |
| // TODO: implement proper formatting for these types. |
| writer->String("<unsupported-type>"); |
| break; |
| } |
| return Status::OK(); |
| } |
| case VariantBasicType::OBJECT: { |
| // Parse and bounds-check the header + offset table once, then reuse the layout for |
| // every field rather than re-parsing on each accessor call. |
| VariantValue::ObjectLayout layout; |
| if (UNLIKELY(!val.ParseObjectLayout(&layout))) { |
| return MalformedVariant("object"); |
| } |
| writer->StartObject(); |
| for (uint32_t i = 0; i < layout.num_fields; ++i) { |
| string_view field_name; |
| if (UNLIKELY(!val.FieldNameFromLayout(layout, i, &field_name))) { |
| return MalformedVariant("object field name"); |
| } |
| writer->Key(field_name.data(), field_name.size()); |
| VariantValue child; |
| if (UNLIKELY(!val.FieldFromLayout(layout, i, &child))) { |
| return Status("Failed to read object field"); |
| } |
| RETURN_IF_ERROR(Write(child, metadata, writer, depth - 1)); |
| } |
| writer->EndObject(); |
| return Status::OK(); |
| } |
| case VariantBasicType::ARRAY: { |
| VariantValue::ArrayLayout layout; |
| if (UNLIKELY(!val.ParseArrayLayout(&layout))) { |
| return MalformedVariant("array"); |
| } |
| writer->StartArray(); |
| for (uint32_t i = 0; i < layout.num_elems; ++i) { |
| VariantValue elem; |
| if (UNLIKELY(!val.ElementFromLayout(layout, i, &elem))) { |
| return Status("Failed to read array element"); |
| } |
| RETURN_IF_ERROR(Write(elem, metadata, writer, depth - 1)); |
| } |
| writer->EndArray(); |
| return Status::OK(); |
| } |
| } |
| return Status("Unknown variant basic type"); |
| } |
| |
| inline rapidjson::StringBuffer CreateStringBuffer(size_t expected_size) { |
| constexpr auto default_capacity = rapidjson::StringBuffer::kDefaultCapacity; |
| size_t capacity = max(expected_size, default_capacity); |
| return {/*allocator=*/nullptr, capacity}; |
| } |
| |
| Status VariantValue::ToJson(std::string* json_out) const { |
| DCHECK(metadata_ != nullptr); |
| auto buffer = CreateStringBuffer(Len() * 2); |
| JsonWriter writer(buffer); |
| RETURN_IF_ERROR(VariantJsonSerializer::Write( |
| *this, *metadata_, &writer)); |
| json_out->assign(buffer.GetString(), buffer.GetSize()); |
| return Status::OK(); |
| } |
| |
| Status VariantValue::ToJson(impala_udf::FunctionContext* ctx, |
| impala_udf::StringVal* result) const { |
| DCHECK(metadata_ != nullptr); |
| auto buffer = CreateStringBuffer(Len() * 2); |
| JsonWriter writer(buffer); |
| RETURN_IF_ERROR(VariantJsonSerializer::Write( |
| *this, *metadata_, &writer)); |
| *result = impala_udf::StringVal::CopyFrom(ctx, |
| reinterpret_cast<const uint8_t*>(buffer.GetString()), buffer.GetSize()); |
| return Status::OK(); |
| } |
| |
| Status VariantValue::ToJson(std::ostream* out) const { |
| DCHECK(metadata_ != nullptr); |
| // Buffer the whole document first so that a decode failure (which can surface after |
| // some bytes have already been written to 'writer') does not leak partial JSON. |
| auto buffer = CreateStringBuffer(Len() * 2); |
| JsonWriter writer(buffer); |
| RETURN_IF_ERROR(VariantJsonSerializer::Write( |
| *this, *metadata_, &writer)); |
| out->write(buffer.GetString(), buffer.GetSize()); |
| return Status::OK(); |
| } |
| |
| } // namespace impala |