blob: 4363b66f9928cf8a210a5c796ac63a00f323d783 [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.
#pragma once
#include <ostream>
#include <string>
#include <string_view>
#include "common/status.h"
#include "runtime/string-value.h"
namespace impala_udf {
class FunctionContext;
struct StringVal;
}
namespace impala {
// Physical type tags for the variant binary encoding.
// Corresponds to basic_type=0 (primitive) with type_info encoding the physical type.
enum class VariantPhysicalType : uint8_t {
VNULL = 0,
BOOLEAN_TRUE = 1,
BOOLEAN_FALSE = 2,
INT8 = 3,
INT16 = 4,
INT32 = 5,
INT64 = 6,
DOUBLE = 7,
DECIMAL4 = 8,
DECIMAL8 = 9,
DECIMAL16 = 10,
DATE = 11,
TIMESTAMPTZ = 12,
TIMESTAMPNTZ = 13,
FLOAT = 14,
BINARY = 15,
STRING = 16,
TIME = 17,
TIMESTAMPTZ_NANOS = 18,
TIMESTAMPNTZ_NANOS = 19,
UUID = 20
};
inline std::ostream& operator<<(std::ostream& os, VariantPhysicalType pt) {
switch (pt) {
case VariantPhysicalType::VNULL: return os << "VNULL";
case VariantPhysicalType::BOOLEAN_TRUE: return os << "BOOLEAN_TRUE";
case VariantPhysicalType::BOOLEAN_FALSE: return os << "BOOLEAN_FALSE";
case VariantPhysicalType::INT8: return os << "INT8";
case VariantPhysicalType::INT16: return os << "INT16";
case VariantPhysicalType::INT32: return os << "INT32";
case VariantPhysicalType::INT64: return os << "INT64";
case VariantPhysicalType::DOUBLE: return os << "DOUBLE";
case VariantPhysicalType::DECIMAL4: return os << "DECIMAL4";
case VariantPhysicalType::DECIMAL8: return os << "DECIMAL8";
case VariantPhysicalType::DECIMAL16: return os << "DECIMAL16";
case VariantPhysicalType::DATE: return os << "DATE";
case VariantPhysicalType::TIMESTAMPTZ: return os << "TIMESTAMPTZ";
case VariantPhysicalType::TIMESTAMPNTZ: return os << "TIMESTAMPNTZ";
case VariantPhysicalType::FLOAT: return os << "FLOAT";
case VariantPhysicalType::BINARY: return os << "BINARY";
case VariantPhysicalType::STRING: return os << "STRING";
case VariantPhysicalType::TIME: return os << "TIME";
case VariantPhysicalType::TIMESTAMPTZ_NANOS: return os << "TIMESTAMPTZ_NANOS";
case VariantPhysicalType::TIMESTAMPNTZ_NANOS: return os << "TIMESTAMPNTZ_NANOS";
case VariantPhysicalType::UUID: return os << "UUID";
}
return os << "UNKNOWN(" << static_cast<int>(pt) << ")";
}
// Basic type tags encoded in bits 0-1 of the value header byte.
enum class VariantBasicType : uint8_t {
PRIMITIVE = 0,
SHORT_STRING = 1,
OBJECT = 2,
ARRAY = 3
};
inline std::ostream& operator<<(std::ostream& os, VariantBasicType bt) {
switch (bt) {
case VariantBasicType::PRIMITIVE: return os << "PRIMITIVE";
case VariantBasicType::SHORT_STRING: return os << "SHORT_STRING";
case VariantBasicType::OBJECT: return os << "OBJECT";
case VariantBasicType::ARRAY: return os << "ARRAY";
}
return os << "UNKNOWN(" << static_cast<int>(bt) << ")";
}
// Result of looking up an object field, an array element or a path in a variant value.
enum class VariantLookupResult : uint8_t {
// '*result' holds the value.
FOUND,
// No such field, the array index is out of range, a path step does not match the type
// of the value (e.g. a field name on an array or on a scalar), or the path is
// malformed. Callers map this to SQL NULL.
NOT_FOUND,
// The value, or the entry that was looked up, is malformed (e.g. a truncated header, a
// field id outside the dictionary, or a field whose encoding does not fit).
CORRUPT
};
inline std::ostream& operator<<(std::ostream& os, VariantLookupResult r) {
switch (r) {
case VariantLookupResult::FOUND: return os << "FOUND";
case VariantLookupResult::NOT_FOUND: return os << "NOT_FOUND";
case VariantLookupResult::CORRUPT: return os << "CORRUPT";
}
return os << "UNKNOWN(" << static_cast<int>(r) << ")";
}
// Parses and provides access to the variant metadata blob (field name dictionary).
// The metadata format:
// - Header byte: version (bits 0-3), sorted flag (bit 4),
// offset_size_minus_one (bits 6-7)
// - Dictionary size: variable-width integer (1-4 bytes based on offset size)
// - Offsets: (dict_size + 1) entries of offset_size bytes each
// - String data: concatenated field name strings
class VariantMetadata {
public:
VariantMetadata() = default;
// Initialize from a metadata blob. Returns error if the blob is malformed.
Status Init(const uint8_t* data, uint32_t len);
// Returns the number of field names in the dictionary.
uint32_t DictionarySize() const { return dict_size_; }
// Returns the field name at the given dictionary index.
// index must be in [0, DictionarySize()).
std::string_view GetFieldName(uint32_t index) const;
// True if the dictionary claims to be sorted and unique (the sorted_strings bit).
// Lookups do not rely on it.
bool IsSorted() const { return is_sorted_; }
bool IsValid() const { return offsets_ != nullptr; }
private:
uint32_t ReadOffset(uint32_t index) const;
const uint8_t* offsets_ = nullptr;
const uint8_t* string_data_ = nullptr;
uint32_t string_data_len_ = 0; // number of bytes in the string-data region
uint32_t dict_size_ = 0;
uint8_t version_ = 0;
uint8_t offset_size_ = 0; // 1, 2, 3, or 4 bytes per offset
bool is_sorted_ = false;
};
// Provides access to a variant value blob. Requires a VariantMetadata for field name
// lookups when navigating objects.
//
// Value format header byte:
// bits 0-1: basic_type (PRIMITIVE=0, SHORT_STRING=1, OBJECT=2, ARRAY=3)
// bits 2-7: type_info
// - For PRIMITIVE: physical type id
// - For SHORT_STRING: string length (0-63 bytes)
// - For OBJECT: field_offset_size_minus_one (bits 2-3),
// field_id_size_minus_one (bits 4-5), is_large (bit 6)
// - For ARRAY: offset_size_minus_one (bits 2-3), is_large (bit 4)
class VariantValue {
public:
VariantValue() = default;
VariantValue(const uint8_t* data, uint32_t len, const VariantMetadata* metadata)
: data_(data), len_(len), metadata_(metadata) {}
// Returns the basic type of this value.
VariantBasicType GetBasicType() const;
// Returns the physical type (only valid for PRIMITIVE basic type).
VariantPhysicalType GetPhysicalType() const;
// Returns true if this value is null.
bool IsNull() const;
// Scalar accessors. Each returns false and leaves '*out' unchanged if the value is not
// of the expected physical type or its payload would read out of bounds.
[[nodiscard]] bool GetBoolean(bool* out) const;
[[nodiscard]] bool GetInt8(int8_t* out) const;
[[nodiscard]] bool GetInt16(int16_t* out) const;
[[nodiscard]] bool GetInt32(int32_t* out) const;
[[nodiscard]] bool GetInt64(int64_t* out) const;
[[nodiscard]] bool GetFloat(float* out) const;
[[nodiscard]] bool GetDouble(double* out) const;
[[nodiscard]] bool GetString(StringValue* out) const;
[[nodiscard]] bool GetBinary(StringValue* out) const;
// Object access by position. Each returns false if this value is not a well-formed
// object (a corrupt or truncated header/offset table), if 'index' is out of range, or
// if the field's encoding is out of bounds.
[[nodiscard]] bool GetObjectSize(uint32_t* out) const;
// Gets the field value by position index.
[[nodiscard]] bool GetFieldByIndex(uint32_t index, VariantValue* result) const;
// Gets the field name at position index in this object.
[[nodiscard]] bool GetFieldNameByIndex(uint32_t index, std::string_view* out) const;
// Gets the value of the field named 'name'. Searches this object's field ids by their
// names, so it works with any dictionary (sorted or not, with or without duplicate
// strings) and with field ids in UTF-16 order. NOT_FOUND if this is not an object or
// has no such field; CORRUPT if this is an empty value, there is no metadata, the
// object is malformed, or the field (or a field id compared before it) cannot be
// read.
[[nodiscard]] VariantLookupResult GetFieldByName(
std::string_view name, VariantValue* result) const;
// Array access. GetArraySize() returns false on a corrupt/truncated array.
[[nodiscard]] bool GetArraySize(uint32_t* out) const;
// NOT_FOUND if this is not an array or 'index' is out of range; CORRUPT if this is an
// empty value, a malformed array, or the element cannot be read.
[[nodiscard]] VariantLookupResult GetArrayElement(
uint32_t index, VariantValue* result) const;
// Navigates a path like "$.field.nested[0].value". '*result' is unspecified unless
// FOUND. CORRUPT if this value (sized by ValueSize()) or a value looked up on the path
// cannot be read. A malformed path gives NOT_FOUND (the FE rejects malformed constant
// paths).
[[nodiscard]] VariantLookupResult NavigatePath(
std::string_view path, VariantValue* result) const;
// Computes the encoded size of the value starting at 'data' from its header, like
// parquet-java's VariantUtil.valueSize(): a fixed size for most primitives,
// 1 + length for a short string, 5 + length for a long string or binary, and
// header + tables + last offset for an object or array (nested values are not
// validated). Returns false if 'data' is null or 'avail' is 0, the primitive type id
// is unknown (21-63), an object or array header or offset table is malformed, or the
// value would extend past 'avail' bytes.
[[nodiscard]] static bool ValueSize(const uint8_t* data, uint32_t avail,
uint32_t* size);
// Serialize this variant value to JSON string.
Status ToJson(std::string* json_out) const;
Status ToJson(impala_udf::FunctionContext* ctx, impala_udf::StringVal* result) const;
// Serialize this variant value as JSON directly into 'out'. The JSON is fully
// buffered internally and only flushed to 'out' on success.
Status ToJson(std::ostream* out) const;
bool IsValid() const { return data_ != nullptr; }
const uint8_t* Data() const { return data_; }
uint32_t Len() const { return len_; }
// Reads a primitive of type T from the payload at data_ + offset (offset defaults to 1,
// just past the header byte). Returns false without modifying '*out' if the read would
// extend past the end of the value buffer.
template <typename T>
[[nodiscard]] bool ReadValue(T* out, uint32_t offset = 1) const {
if (offset > len_ || sizeof(T) > len_ - offset) return false;
memcpy(out, data_ + offset, sizeof(T));
return true;
}
private:
// Helper to read a variable-width unsigned integer.
static uint32_t ReadUint(const uint8_t* data, uint32_t size);
// If this value is a PRIMITIVE with a readable header byte, sets '*pt' to its physical
// type and returns true; otherwise returns false.
[[nodiscard]] bool AsPrimitive(VariantPhysicalType* pt) const {
if (data_ == nullptr || len_ < 1) return false;
if (GetBasicType() != VariantBasicType::PRIMITIVE) return false;
*pt = GetPhysicalType();
return true;
}
// Checks this value is a PRIMITIVE of 'expected' physical type, then reads a T-sized
// payload just past the header byte (see ReadValue()). Returns false without modifying
// '*out' on a type mismatch or a truncated payload. Shared by the typed scalar getters.
template <typename T>
[[nodiscard]] bool ReadValueOfType(VariantPhysicalType expected, T* out) const {
VariantPhysicalType pt;
if (!AsPrimitive(&pt) || pt != expected) return false;
return ReadValue(out);
}
// Validated internal layout of an object/array value. The pointers and lengths are
// guaranteed to lie within the value buffer once the matching Parse*() returns true.
struct ObjectLayout {
uint32_t num_fields;
const uint8_t* field_ids;
const uint8_t* offsets;
const uint8_t* data;
// Size of the value data, i.e. the last offset, which is validated to fit in the
// buffer. Bytes after it (e.g. padding) are not part of any value.
uint32_t data_len;
};
struct ArrayLayout {
uint32_t num_elems;
const uint8_t* offsets;
const uint8_t* data;
// Size of the value data, i.e. the last offset, which is validated to fit in the
// buffer. Bytes after it (e.g. padding) are not part of any value.
uint32_t data_len;
};
// Parse and bounds-check the object/array header + offset table. Returns false (without
// reading out of bounds) if the value is not that type or the header/tables do not fit.
[[nodiscard]] bool ParseObjectLayout(ObjectLayout* out) const;
[[nodiscard]] bool ParseArrayLayout(ArrayLayout* out) const;
// Extract a single field/element from an already-parsed, validated layout. A value is
// located by its entry's start offset, which is bounds-checked against data_len, and
// sized from its own header; the next offset is not used, because values may be stored
// in any order. For a field name, checks that its dictionary id is in range. Shared by
// the public single-access accessors and by JSON serialization, which parses the
// layout once and reuses it across the whole loop.
[[nodiscard]] bool FieldFromLayout(const ObjectLayout& layout, uint32_t index,
VariantValue* result) const;
[[nodiscard]] bool FieldNameFromLayout(const ObjectLayout& layout, uint32_t index,
std::string_view* out) const;
// Sets '*index' to the position of the field named 'name' in 'layout'. NOT_FOUND if
// there is no such field; CORRUPT if a field id it compares is outside the dictionary.
[[nodiscard]] VariantLookupResult FindField(const ObjectLayout& layout,
std::string_view name, uint32_t* index) const;
[[nodiscard]] bool ElementFromLayout(const ArrayLayout& layout, uint32_t index,
VariantValue* result) const;
// Sets '*result' to the value that starts at offset 'start' of the value data
// ['data', 'data' + 'data_len'), sized by ValueSize(). Returns false if it does not
// fit.
[[nodiscard]] bool SliceValue(const uint8_t* data, uint32_t data_len, uint32_t start,
VariantValue* result) const;
// JSON serialization lives in the .cc (so rapidjson stays out of this header) but needs
// the private layout helpers above to parse each object/array once and reuse it.
friend struct VariantJsonSerializer;
// Bit-width helpers derived from the header byte (require a readable header).
uint32_t ObjectFieldIdSize() const;
uint32_t ObjectOffsetSize() const;
uint32_t ArrayOffsetSize() const;
const uint8_t* data_ = nullptr;
uint32_t len_ = 0;
const VariantMetadata* metadata_ = nullptr;
};
} // namespace impala