blob: dc225c266bf9d424ab00b2e7038163ec48c36603 [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
/// \file iceberg/type.h
/// Data types for Iceberg. This header defines the data types, but see
/// iceberg/type_fwd.h for the enum defining the list of types.
#include <array>
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <unordered_map>
#include <vector>
#include "iceberg/iceberg_export.h"
#include "iceberg/result.h"
#include "iceberg/schema_field.h"
#include "iceberg/util/formattable.h"
#include "iceberg/util/lazy.h"
namespace iceberg {
/// \brief Interface for a data type for a field.
class ICEBERG_EXPORT Type : public iceberg::util::Formattable {
public:
~Type() override = default;
/// \brief Get the type ID.
virtual TypeId type_id() const = 0;
/// \brief Is this a primitive type (may not have child fields)?
virtual bool is_primitive() const = 0;
/// \brief Is this a nested type (may have child fields)?
virtual bool is_nested() const = 0;
/// \brief Is this a struct type?
bool is_struct() const { return type_id() == TypeId::kStruct; }
/// \brief Is this a list type?
bool is_list() const { return type_id() == TypeId::kList; }
/// \brief Is this a map type?
bool is_map() const { return type_id() == TypeId::kMap; }
/// \brief Is this a variant type?
bool is_variant() const { return type_id() == TypeId::kVariant; }
/// \brief Compare two types for equality.
friend bool operator==(const Type& lhs, const Type& rhs) { return lhs.Equals(rhs); }
protected:
/// \brief Compare two types for equality.
virtual bool Equals(const Type& other) const = 0;
};
/// \brief A data type that does not have child fields.
class ICEBERG_EXPORT PrimitiveType : public Type {
public:
bool is_primitive() const override { return true; }
bool is_nested() const override { return false; }
};
/// \brief A data type that has child fields.
class ICEBERG_EXPORT NestedType : public Type {
public:
bool is_primitive() const override { return false; }
bool is_nested() const override { return true; }
/// \brief Get a view of the child fields.
virtual std::span<const SchemaField> fields() const = 0;
using SchemaFieldConstRef = std::reference_wrapper<const SchemaField>;
/// \brief Get a field by field ID.
///
/// \note This is O(1) complexity.
virtual Result<std::optional<SchemaFieldConstRef>> GetFieldById(
int32_t field_id) const = 0;
/// \brief Get a field by index.
///
/// \note This is O(1) complexity.
virtual Result<std::optional<SchemaFieldConstRef>> GetFieldByIndex(
int32_t index) const = 0;
/// \brief Get a field by name. Return an error Status if
/// the field name is not unique; prefer GetFieldById or GetFieldByIndex
/// when possible.
///
/// \note This is O(1) complexity.
virtual Result<std::optional<SchemaFieldConstRef>> GetFieldByName(
std::string_view name, bool case_sensitive) const = 0;
/// \brief Get a field by name (case-sensitive).
Result<std::optional<SchemaFieldConstRef>> GetFieldByName(std::string_view name) const;
};
/// \defgroup type-nested Nested Types
/// Nested types have child fields.
/// @{
/// \brief A data type representing a struct with nested fields.
class ICEBERG_EXPORT StructType : public NestedType {
public:
constexpr static TypeId kTypeId = TypeId::kStruct;
explicit StructType(std::vector<SchemaField> fields);
~StructType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
std::span<const SchemaField> fields() const override;
Result<std::optional<SchemaFieldConstRef>> GetFieldById(
int32_t field_id) const override;
Result<std::optional<SchemaFieldConstRef>> GetFieldByIndex(
int32_t index) const override;
Result<std::optional<SchemaFieldConstRef>> GetFieldByName(
std::string_view name, bool case_sensitive) const override;
using NestedType::GetFieldByName;
std::unique_ptr<Schema> ToSchema() const;
protected:
bool Equals(const Type& other) const override;
static Result<std::unordered_map<int32_t, SchemaFieldConstRef>> InitFieldById(
const StructType&);
static Result<std::unordered_map<std::string_view, SchemaFieldConstRef>>
InitFieldByName(const StructType&);
static Result<std::unordered_map<std::string, SchemaFieldConstRef>>
InitFieldByLowerCaseName(const StructType&);
std::vector<SchemaField> fields_;
Lazy<InitFieldById> field_by_id_;
Lazy<InitFieldByName> field_by_name_;
Lazy<InitFieldByLowerCaseName> field_by_lowercase_name_;
};
/// \brief A data type representing a list of values.
class ICEBERG_EXPORT ListType : public NestedType {
public:
constexpr static const TypeId kTypeId = TypeId::kList;
constexpr static const std::string_view kElementName = "element";
static Result<std::unique_ptr<ListType>> Make(SchemaField element);
/// \brief Construct a list of the given element.
///
/// Use Make or list to validate that the element field name is "element".
explicit ListType(SchemaField element);
/// \brief Construct a list of the given element type.
ListType(int32_t field_id, std::shared_ptr<Type> type, bool optional);
~ListType() override = default;
TypeId type_id() const override;
const SchemaField& element() const;
std::string ToString() const override;
std::span<const SchemaField> fields() const override;
Result<std::optional<SchemaFieldConstRef>> GetFieldById(
int32_t field_id) const override;
Result<std::optional<SchemaFieldConstRef>> GetFieldByIndex(
int32_t index) const override;
Result<std::optional<SchemaFieldConstRef>> GetFieldByName(
std::string_view name, bool case_sensitive) const override;
using NestedType::GetFieldByName;
protected:
bool Equals(const Type& other) const override;
SchemaField element_;
};
/// \brief A data type representing a dictionary of values.
class ICEBERG_EXPORT MapType : public NestedType {
public:
constexpr static const TypeId kTypeId = TypeId::kMap;
constexpr static const std::string_view kKeyName = "key";
constexpr static const std::string_view kValueName = "value";
static Result<std::unique_ptr<MapType>> Make(SchemaField key, SchemaField value);
/// \brief Construct a map of the given key/value fields.
///
/// Use Make or map to validate that the field names are "key" and "value".
explicit MapType(SchemaField key, SchemaField value);
~MapType() override = default;
const SchemaField& key() const;
const SchemaField& value() const;
TypeId type_id() const override;
std::string ToString() const override;
std::span<const SchemaField> fields() const override;
Result<std::optional<SchemaFieldConstRef>> GetFieldById(
int32_t field_id) const override;
Result<std::optional<SchemaFieldConstRef>> GetFieldByIndex(
int32_t index) const override;
Result<std::optional<SchemaFieldConstRef>> GetFieldByName(
std::string_view name, bool case_sensitive) const override;
using NestedType::GetFieldByName;
protected:
bool Equals(const Type& other) const override;
std::array<SchemaField, 2> fields_;
};
/// @}
/// \defgroup type-semi-structured Semi-structured Types
/// Semi-structured types may contain values whose structure varies across rows.
/// @{
/// \brief A semi-structured type whose structure may vary across rows.
class ICEBERG_EXPORT VariantType : public Type {
public:
constexpr static const TypeId kTypeId = TypeId::kVariant;
VariantType() = default;
~VariantType() override = default;
bool is_primitive() const override { return false; }
bool is_nested() const override { return false; }
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// @}
/// \defgroup type-primitive Primitive Types
/// Primitive types do not have nested fields.
/// @{
/// \brief A data type representing a boolean (true or false).
class ICEBERG_EXPORT BooleanType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kBoolean;
BooleanType() = default;
~BooleanType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a 32-bit signed integer.
class ICEBERG_EXPORT IntType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kInt;
IntType() = default;
~IntType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a 64-bit signed integer.
class ICEBERG_EXPORT LongType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kLong;
LongType() = default;
~LongType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a 32-bit (single precision) IEEE-754
/// float.
class ICEBERG_EXPORT FloatType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kFloat;
FloatType() = default;
~FloatType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a 64-bit (double precision) IEEE-754
/// float.
class ICEBERG_EXPORT DoubleType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kDouble;
DoubleType() = default;
~DoubleType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a fixed-precision decimal.
class ICEBERG_EXPORT DecimalType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kDecimal;
constexpr static const int32_t kMaxPrecision = 38;
/// \brief Construct a decimal type with the given precision and scale.
/// \throws IcebergError if precision is outside the supported range.
DecimalType(int32_t precision, int32_t scale);
~DecimalType() override = default;
/// \brief Get the precision (the number of decimal digits).
int32_t precision() const;
/// \brief Get the scale (essentially, the number of decimal digits after
/// the decimal point; precisely, the value is scaled by $$10^{-s}$$.).
int32_t scale() const;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
private:
int32_t precision_;
int32_t scale_;
};
/// \brief A data type representing a calendar date without reference to a
/// timezone or time.
class ICEBERG_EXPORT DateType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kDate;
DateType() = default;
~DateType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a wall clock time in microseconds without
/// reference to a timezone or date.
class ICEBERG_EXPORT TimeType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kTime;
TimeType() = default;
~TimeType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A base class for any timestamp time (irrespective of unit or
/// timezone).
class ICEBERG_EXPORT TimestampBase : public PrimitiveType {
public:
/// \brief Is this type zoned or naive?
virtual bool is_zoned() const = 0;
/// \brief The time resolution.
virtual TimeUnit time_unit() const = 0;
};
/// \brief A data type representing a timestamp in microseconds without
/// reference to a timezone.
class ICEBERG_EXPORT TimestampType : public TimestampBase {
public:
constexpr static const TypeId kTypeId = TypeId::kTimestamp;
TimestampType() = default;
~TimestampType() override = default;
bool is_zoned() const override;
TimeUnit time_unit() const override;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a timestamp as microseconds since the
/// epoch in UTC. A time zone or offset is not stored.
class ICEBERG_EXPORT TimestampTzType : public TimestampBase {
public:
constexpr static const TypeId kTypeId = TypeId::kTimestampTz;
TimestampTzType() = default;
~TimestampTzType() override = default;
bool is_zoned() const override;
TimeUnit time_unit() const override;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a timestamp in nanoseconds without
/// reference to a timezone.
class ICEBERG_EXPORT TimestampNsType : public TimestampBase {
public:
constexpr static const TypeId kTypeId = TypeId::kTimestampNs;
TimestampNsType() = default;
~TimestampNsType() override = default;
bool is_zoned() const override;
TimeUnit time_unit() const override;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a timestamp as nanoseconds since the
/// epoch in UTC. A time zone or offset is not stored.
class ICEBERG_EXPORT TimestampTzNsType : public TimestampBase {
public:
constexpr static const TypeId kTypeId = TypeId::kTimestampTzNs;
TimestampTzNsType() = default;
~TimestampTzNsType() override = default;
bool is_zoned() const override;
TimeUnit time_unit() const override;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing an arbitrary-length byte sequence.
class ICEBERG_EXPORT BinaryType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kBinary;
BinaryType() = default;
~BinaryType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing an arbitrary-length character sequence
/// (encoded in UTF-8).
class ICEBERG_EXPORT StringType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kString;
StringType() = default;
~StringType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing a fixed-length bytestring.
class ICEBERG_EXPORT FixedType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kFixed;
/// \brief Construct a fixed type with the given length.
/// \throws IcebergError if length is negative.
explicit FixedType(int32_t length);
~FixedType() override = default;
/// \brief The length (the number of bytes to store).
int32_t length() const;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
private:
int32_t length_;
};
/// \brief A data type representing a UUID. While defined as a distinct type,
/// it is effectively a fixed(16).
class ICEBERG_EXPORT UuidType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kUuid;
UuidType() = default;
~UuidType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A null-only placeholder type used when a more specific type is not known.
class ICEBERG_EXPORT UnknownType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kUnknown;
UnknownType() = default;
~UnknownType() override = default;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
};
/// \brief A data type representing OGC geometry in WKB format.
class ICEBERG_EXPORT GeometryType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kGeometry;
constexpr static std::string_view kDefaultCrs = "OGC:CRS84";
static Result<std::unique_ptr<GeometryType>> Make();
static Result<std::unique_ptr<GeometryType>> Make(std::string crs);
~GeometryType() override = default;
std::string_view crs() const;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
private:
GeometryType() = default;
explicit GeometryType(std::string crs);
std::string crs_{kDefaultCrs};
};
/// \brief A data type representing OGC geography in WKB format.
class ICEBERG_EXPORT GeographyType : public PrimitiveType {
public:
constexpr static const TypeId kTypeId = TypeId::kGeography;
constexpr static std::string_view kDefaultCrs = "OGC:CRS84";
constexpr static EdgeAlgorithm kDefaultAlgorithm = EdgeAlgorithm::kSpherical;
static Result<std::unique_ptr<GeographyType>> Make();
static Result<std::unique_ptr<GeographyType>> Make(std::string crs);
static Result<std::unique_ptr<GeographyType>> Make(std::string crs,
EdgeAlgorithm algorithm);
~GeographyType() override = default;
std::string_view crs() const;
EdgeAlgorithm algorithm() const;
TypeId type_id() const override;
std::string ToString() const override;
protected:
bool Equals(const Type& other) const override;
private:
GeographyType() = default;
explicit GeographyType(std::string crs);
GeographyType(std::string crs, EdgeAlgorithm algorithm);
std::string crs_{kDefaultCrs};
EdgeAlgorithm algorithm_{kDefaultAlgorithm};
};
/// @}
/// \defgroup type-factories Factory functions for creating data types
///
/// Factory functions for creating data types
/// @{
/// \brief Return a BooleanType instance.
ICEBERG_EXPORT const std::shared_ptr<BooleanType>& boolean();
/// \brief Return an IntType instance.
ICEBERG_EXPORT const std::shared_ptr<IntType>& int32();
/// \brief Return a LongType instance.
ICEBERG_EXPORT const std::shared_ptr<LongType>& int64();
/// \brief Return a FloatType instance.
ICEBERG_EXPORT const std::shared_ptr<FloatType>& float32();
/// \brief Return a DoubleType instance.
ICEBERG_EXPORT const std::shared_ptr<DoubleType>& float64();
/// \brief Return a DateType instance.
ICEBERG_EXPORT const std::shared_ptr<DateType>& date();
/// \brief Return a TimeType instance.
ICEBERG_EXPORT const std::shared_ptr<TimeType>& time();
/// \brief Return a TimestampType instance.
ICEBERG_EXPORT const std::shared_ptr<TimestampType>& timestamp();
/// \brief Return a TimestampTzType instance.
ICEBERG_EXPORT const std::shared_ptr<TimestampTzType>& timestamp_tz();
/// \brief Return a TimestampNsType instance.
ICEBERG_EXPORT const std::shared_ptr<TimestampNsType>& timestamp_ns();
/// \brief Return a TimestampTzNsType instance.
ICEBERG_EXPORT const std::shared_ptr<TimestampTzNsType>& timestamptz_ns();
/// \brief Return a BinaryType instance.
ICEBERG_EXPORT const std::shared_ptr<BinaryType>& binary();
/// \brief Return a StringType instance.
ICEBERG_EXPORT const std::shared_ptr<StringType>& string();
/// \brief Return a UuidType instance.
ICEBERG_EXPORT const std::shared_ptr<UuidType>& uuid();
/// \brief Return an UnknownType instance.
ICEBERG_EXPORT const std::shared_ptr<UnknownType>& unknown();
/// \brief Return a VariantType instance.
ICEBERG_EXPORT const std::shared_ptr<VariantType>& variant();
/// \brief Return the default GeometryType instance.
ICEBERG_EXPORT const std::shared_ptr<GeometryType>& geometry();
/// \brief Return the default GeographyType instance.
ICEBERG_EXPORT const std::shared_ptr<GeographyType>& geography();
/// \brief Create a DecimalType with the given precision and scale.
/// \param precision The number of decimal digits (max 38).
/// \param scale The number of decimal digits after the decimal point.
/// \return A shared pointer to the DecimalType instance.
/// \throws IcebergError if precision is outside the supported range.
ICEBERG_EXPORT std::shared_ptr<DecimalType> decimal(int32_t precision, int32_t scale);
/// \brief Create a FixedType with the given length.
/// \param length The number of bytes to store (must be >= 0).
/// \return A shared pointer to the FixedType instance.
/// \throws IcebergError if length is negative.
ICEBERG_EXPORT std::shared_ptr<FixedType> fixed(int32_t length);
/// \brief Create a GeometryType with the given CRS.
/// \throws IcebergError if crs is empty.
ICEBERG_EXPORT std::shared_ptr<GeometryType> geometry(std::string crs);
/// \brief Create a GeographyType with the given CRS.
/// \throws IcebergError if crs is empty.
ICEBERG_EXPORT std::shared_ptr<GeographyType> geography(std::string crs);
/// \brief Create a GeographyType with the given CRS and edge algorithm.
/// \throws IcebergError if crs is empty.
ICEBERG_EXPORT std::shared_ptr<GeographyType> geography(std::string crs,
EdgeAlgorithm algorithm);
/// \brief Create a StructType with the given fields.
/// \param fields The fields of the struct.
/// \return A shared pointer to the StructType instance.
ICEBERG_EXPORT std::shared_ptr<StructType> struct_(std::vector<SchemaField> fields);
/// \brief Create a ListType with the given element field.
/// \param element The element field of the list.
/// \return A shared pointer to the ListType instance.
/// \throws IcebergError if element's name is not "element".
ICEBERG_EXPORT std::shared_ptr<ListType> list(SchemaField element);
/// \brief Create a MapType with the given key and value fields.
/// \param key The key field of the map.
/// \param value The value field of the map.
/// \return A shared pointer to the MapType instance.
/// \throws IcebergError if the key or value field has an invalid name.
ICEBERG_EXPORT std::shared_ptr<MapType> map(SchemaField key, SchemaField value);
/// @}
/// \brief Get the lowercase string representation of a TypeId.
///
/// This returns the same lowercase string as used by Type::ToString() methods.
/// For example: TypeId::kBoolean -> "boolean", TypeId::kInt -> "int", etc.
///
/// \param id The TypeId to convert to string
/// \return A string_view containing the lowercase type name
ICEBERG_EXPORT std::string_view ToString(TypeId id);
/// \brief Get the lowercase string representation of an EdgeAlgorithm.
ICEBERG_EXPORT std::string_view ToString(EdgeAlgorithm algorithm);
/// \brief Parse a lowercase edge algorithm name.
ICEBERG_EXPORT Result<EdgeAlgorithm> EdgeAlgorithmFromString(std::string_view name);
} // namespace iceberg