use core::ffi::c_char;
use core::ptr::NonNull;
use core::slice;
use crate::alloc::Allocator;
use crate::error::{Result, check};
use crate::sys;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum Kind {
Void = sys::CHC_VOID,
Int8 = sys::CHC_INT8,
Int16 = sys::CHC_INT16,
Int32 = sys::CHC_INT32,
Int64 = sys::CHC_INT64,
Int128 = sys::CHC_INT128,
Int256 = sys::CHC_INT256,
UInt8 = sys::CHC_UINT8,
UInt16 = sys::CHC_UINT16,
UInt32 = sys::CHC_UINT32,
UInt64 = sys::CHC_UINT64,
UInt128 = sys::CHC_UINT128,
UInt256 = sys::CHC_UINT256,
Float32 = sys::CHC_FLOAT32,
Float64 = sys::CHC_FLOAT64,
BFloat16 = sys::CHC_BFLOAT16,
Bool = sys::CHC_BOOL,
Date = sys::CHC_DATE,
Date32 = sys::CHC_DATE32,
DateTime = sys::CHC_DATETIME,
DateTime64 = sys::CHC_DATETIME64,
Time = sys::CHC_TIME,
Time64 = sys::CHC_TIME64,
String = sys::CHC_STRING,
FixedString = sys::CHC_FIXED_STRING,
Decimal32 = sys::CHC_DECIMAL32,
Decimal64 = sys::CHC_DECIMAL64,
Decimal128 = sys::CHC_DECIMAL128,
Decimal256 = sys::CHC_DECIMAL256,
Uuid = sys::CHC_UUID,
Ipv4 = sys::CHC_IPV4,
Ipv6 = sys::CHC_IPV6,
Enum8 = sys::CHC_ENUM8,
Enum16 = sys::CHC_ENUM16,
Nullable = sys::CHC_NULLABLE,
Array = sys::CHC_ARRAY,
Tuple = sys::CHC_TUPLE,
Map = sys::CHC_MAP,
Nested = sys::CHC_NESTED,
LowCardinality = sys::CHC_LOW_CARDINALITY,
Interval = sys::CHC_INTERVAL,
Point = sys::CHC_POINT,
Ring = sys::CHC_RING,
LineString = sys::CHC_LINE_STRING,
Polygon = sys::CHC_POLYGON,
MultiPolygon = sys::CHC_MULTI_POLYGON,
MultiLineString = sys::CHC_MULTI_LINE_STRING,
Variant = sys::CHC_VARIANT,
Dynamic = sys::CHC_DYNAMIC,
Json = sys::CHC_JSON,
Object = sys::CHC_OBJECT,
AggregateFunction = sys::CHC_AGGREGATE_FUNCTION,
SimpleAggregateFunction = sys::CHC_SIMPLE_AGGREGATE_FUNCTION,
Nothing = sys::CHC_NOTHING,
QBit = sys::CHC_QBIT,
}
impl Kind {
pub(crate) fn from_raw(k: sys::chc_kind) -> Option<Self> {
Some(match k {
sys::CHC_VOID => Self::Void,
sys::CHC_INT8 => Self::Int8,
sys::CHC_INT16 => Self::Int16,
sys::CHC_INT32 => Self::Int32,
sys::CHC_INT64 => Self::Int64,
sys::CHC_INT128 => Self::Int128,
sys::CHC_INT256 => Self::Int256,
sys::CHC_UINT8 => Self::UInt8,
sys::CHC_UINT16 => Self::UInt16,
sys::CHC_UINT32 => Self::UInt32,
sys::CHC_UINT64 => Self::UInt64,
sys::CHC_UINT128 => Self::UInt128,
sys::CHC_UINT256 => Self::UInt256,
sys::CHC_FLOAT32 => Self::Float32,
sys::CHC_FLOAT64 => Self::Float64,
sys::CHC_BFLOAT16 => Self::BFloat16,
sys::CHC_BOOL => Self::Bool,
sys::CHC_DATE => Self::Date,
sys::CHC_DATE32 => Self::Date32,
sys::CHC_DATETIME => Self::DateTime,
sys::CHC_DATETIME64 => Self::DateTime64,
sys::CHC_TIME => Self::Time,
sys::CHC_TIME64 => Self::Time64,
sys::CHC_STRING => Self::String,
sys::CHC_FIXED_STRING => Self::FixedString,
sys::CHC_DECIMAL32 => Self::Decimal32,
sys::CHC_DECIMAL64 => Self::Decimal64,
sys::CHC_DECIMAL128 => Self::Decimal128,
sys::CHC_DECIMAL256 => Self::Decimal256,
sys::CHC_UUID => Self::Uuid,
sys::CHC_IPV4 => Self::Ipv4,
sys::CHC_IPV6 => Self::Ipv6,
sys::CHC_ENUM8 => Self::Enum8,
sys::CHC_ENUM16 => Self::Enum16,
sys::CHC_NULLABLE => Self::Nullable,
sys::CHC_ARRAY => Self::Array,
sys::CHC_TUPLE => Self::Tuple,
sys::CHC_MAP => Self::Map,
sys::CHC_NESTED => Self::Nested,
sys::CHC_LOW_CARDINALITY => Self::LowCardinality,
sys::CHC_INTERVAL => Self::Interval,
sys::CHC_POINT => Self::Point,
sys::CHC_RING => Self::Ring,
sys::CHC_LINE_STRING => Self::LineString,
sys::CHC_POLYGON => Self::Polygon,
sys::CHC_MULTI_POLYGON => Self::MultiPolygon,
sys::CHC_MULTI_LINE_STRING => Self::MultiLineString,
sys::CHC_VARIANT => Self::Variant,
sys::CHC_DYNAMIC => Self::Dynamic,
sys::CHC_JSON => Self::Json,
sys::CHC_OBJECT => Self::Object,
sys::CHC_AGGREGATE_FUNCTION => Self::AggregateFunction,
sys::CHC_SIMPLE_AGGREGATE_FUNCTION => Self::SimpleAggregateFunction,
sys::CHC_NOTHING => Self::Nothing,
sys::CHC_QBIT => Self::QBit,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum IntervalUnit {
Nanosecond = sys::CHC_INTERVAL_NANOSECOND,
Microsecond = sys::CHC_INTERVAL_MICROSECOND,
Millisecond = sys::CHC_INTERVAL_MILLISECOND,
Second = sys::CHC_INTERVAL_SECOND,
Minute = sys::CHC_INTERVAL_MINUTE,
Hour = sys::CHC_INTERVAL_HOUR,
Day = sys::CHC_INTERVAL_DAY,
Week = sys::CHC_INTERVAL_WEEK,
Month = sys::CHC_INTERVAL_MONTH,
Quarter = sys::CHC_INTERVAL_QUARTER,
Year = sys::CHC_INTERVAL_YEAR,
}
impl IntervalUnit {
pub(crate) fn from_raw(u: sys::chc_interval_unit) -> Option<Self> {
Some(match u {
sys::CHC_INTERVAL_NANOSECOND => Self::Nanosecond,
sys::CHC_INTERVAL_MICROSECOND => Self::Microsecond,
sys::CHC_INTERVAL_MILLISECOND => Self::Millisecond,
sys::CHC_INTERVAL_SECOND => Self::Second,
sys::CHC_INTERVAL_MINUTE => Self::Minute,
sys::CHC_INTERVAL_HOUR => Self::Hour,
sys::CHC_INTERVAL_DAY => Self::Day,
sys::CHC_INTERVAL_WEEK => Self::Week,
sys::CHC_INTERVAL_MONTH => Self::Month,
sys::CHC_INTERVAL_QUARTER => Self::Quarter,
sys::CHC_INTERVAL_YEAR => Self::Year,
_ => return None,
})
}
}
pub struct TypeAst {
raw: NonNull<sys::chc_type>,
alloc: Allocator,
}
impl TypeAst {
pub fn parse(name: &str, alloc: Allocator) -> Result<Self> {
let mut out: *mut sys::chc_type = core::ptr::null_mut();
let mut err = sys::chc_err::zeroed();
let rc = unsafe {
sys::chc_type_parse(
name.as_ptr().cast::<c_char>(),
name.len(),
alloc.as_ptr(),
&mut out,
&mut err,
)
};
check(rc, &err)?;
Ok(Self {
raw: NonNull::new(out).expect("chc_type_parse returned OK with NULL"),
alloc,
})
}
pub fn view(&self) -> TypeRef<'_> {
TypeRef {
raw: self.raw.as_ptr().cast_const(),
_marker: core::marker::PhantomData,
}
}
}
impl Drop for TypeAst {
fn drop(&mut self) {
unsafe { sys::chc_type_destroy(self.raw.as_ptr(), self.alloc.as_ptr()) };
}
}
unsafe impl Send for TypeAst {}
#[derive(Clone, Copy)]
pub struct TypeRef<'a> {
pub(crate) raw: *const sys::chc_type,
pub(crate) _marker: core::marker::PhantomData<&'a sys::chc_type>,
}
impl<'a> TypeRef<'a> {
pub fn kind(&self) -> Option<Kind> {
Kind::from_raw(unsafe { sys::chc_type_kind(self.raw) })
}
pub fn n_children(&self) -> usize {
unsafe { sys::chc_type_n_children(self.raw) }
}
pub fn child(&self, i: usize) -> Option<TypeRef<'a>> {
let c = unsafe { sys::chc_type_child(self.raw, i) };
if c.is_null() {
None
} else {
Some(TypeRef {
raw: c,
_marker: core::marker::PhantomData,
})
}
}
pub fn fixed_size(&self) -> i32 {
unsafe { sys::chc_type_fixed_size(self.raw) }
}
pub fn elem_size(&self) -> usize {
unsafe { sys::chc_type_elem_size(self.raw) }
}
pub fn decimal_precision(&self) -> i32 {
unsafe { sys::chc_type_decimal_precision(self.raw) }
}
pub fn decimal_scale(&self) -> i32 {
unsafe { sys::chc_type_decimal_scale(self.raw) }
}
pub fn datetime64_scale(&self) -> i32 {
unsafe { sys::chc_type_datetime64_scale(self.raw) }
}
pub fn interval_unit(&self) -> Option<IntervalUnit> {
IntervalUnit::from_raw(unsafe { sys::chc_type_interval_unit(self.raw) })
}
pub fn qbit_dimension(&self) -> usize {
unsafe { sys::chc_type_qbit_dimension(self.raw) }
}
pub fn qbit_element_size(&self) -> usize {
unsafe { sys::chc_type_qbit_element_size(self.raw) }
}
pub fn timezone(&self) -> Option<&'a [u8]> {
let mut len = 0;
let p = unsafe { sys::chc_type_timezone(self.raw, &mut len) };
if p.is_null() {
None
} else {
Some(unsafe { slice::from_raw_parts(p.cast::<u8>(), len) })
}
}
pub fn name(&self) -> Option<&'a [u8]> {
let mut len = 0;
let p = unsafe { sys::chc_type_name(self.raw, &mut len) };
(!p.is_null()).then(|| unsafe { slice::from_raw_parts(p.cast::<u8>(), len) })
}
pub fn enum_count(&self) -> usize {
unsafe { sys::chc_type_enum_count(self.raw) }
}
pub fn enum_at(&self, i: usize) -> Option<(&'a [u8], i64)> {
if i >= self.enum_count() {
return None;
}
let mut name_ptr: *const c_char = core::ptr::null();
let mut name_len: usize = 0;
let mut value: i64 = 0;
unsafe {
sys::chc_type_enum_at(self.raw, i, &mut name_ptr, &mut name_len, &mut value);
}
(!name_ptr.is_null()).then(|| {
(
unsafe { slice::from_raw_parts(name_ptr.cast::<u8>(), name_len) },
value,
)
})
}
pub fn tuple_field_name(&self, i: usize) -> Option<&'a [u8]> {
let mut len = 0;
let p = unsafe { sys::chc_type_tuple_field_name(self.raw, i, &mut len) };
if p.is_null() {
None
} else {
Some(unsafe { slice::from_raw_parts(p.cast::<u8>(), len) })
}
}
pub fn agg_function(&self) -> Option<&'a [u8]> {
let mut len = 0;
let p = unsafe { sys::chc_type_agg_function(self.raw, &mut len) };
if p.is_null() {
None
} else {
Some(unsafe { slice::from_raw_parts(p.cast::<u8>(), len) })
}
}
pub fn format(&self) -> String {
let needed = unsafe { sys::chc_type_format(self.raw, core::ptr::null_mut(), 0) };
let mut buf = vec![0u8; needed.saturating_add(1)];
let _ =
unsafe { sys::chc_type_format(self.raw, buf.as_mut_ptr().cast::<c_char>(), buf.len()) };
buf.truncate(needed);
String::from_utf8_lossy(&buf).into_owned()
}
}
#[cfg(test)]
mod tests {
use super::{IntervalUnit, Kind, TypeAst};
use crate::Allocator;
use crate::sys;
fn parse(name: &str) -> TypeAst {
TypeAst::parse(name, Allocator::stdlib()).expect(name)
}
#[test]
fn qbit_metadata() {
let ty = parse("QBit(Float32, 16)");
let view = ty.view();
assert_eq!(view.kind(), Some(Kind::QBit));
assert_eq!(view.qbit_dimension(), 16);
assert_eq!(view.qbit_element_size(), 32);
assert_eq!(view.format(), "QBit(Float32, 16)");
assert_eq!(view.child(0).and_then(|c| c.kind()), Some(Kind::Float32));
}
#[test]
fn qbit_accessors_are_zero_off_qbit() {
let ty = parse("Array(UInt32)");
assert_eq!(ty.view().qbit_dimension(), 0);
assert_eq!(ty.view().qbit_element_size(), 0);
}
#[test]
fn interval_names_carry_their_unit() {
let ty = parse("IntervalQuarter");
assert_eq!(ty.view().kind(), Some(Kind::Interval));
assert_eq!(ty.view().interval_unit(), Some(IntervalUnit::Quarter));
let nested = parse("Nullable(IntervalDay)");
let inner = nested.view().child(0).expect("Nullable child");
assert_eq!(inner.interval_unit(), Some(IntervalUnit::Day));
assert_eq!(nested.view().interval_unit(), None);
}
#[test]
fn line_string_kinds_parse() {
assert_eq!(parse("LineString").view().kind(), Some(Kind::LineString));
assert_eq!(
parse("MultiLineString").view().kind(),
Some(Kind::MultiLineString)
);
}
#[test]
fn nested_fields_carry_names() {
let ty = parse("Nested(a UInt32, b String)");
let view = ty.view();
assert_eq!(view.kind(), Some(Kind::Nested));
assert_eq!(view.n_children(), 2);
assert_eq!(view.tuple_field_name(0), Some(&b"a"[..]));
assert_eq!(view.tuple_field_name(1), Some(&b"b"[..]));
}
#[test]
fn aggregate_children_drop_function_parameters() {
let ty = parse("AggregateFunction(quantiles(0.5, 0.9), UInt64)");
let view = ty.view();
assert_eq!(view.kind(), Some(Kind::AggregateFunction));
assert_eq!(view.agg_function(), Some(&b"quantiles"[..]));
assert_eq!(view.n_children(), 1);
assert_eq!(view.child(0).and_then(|c| c.kind()), Some(Kind::UInt64));
}
#[test]
fn aggregate_version_prefix_parses() {
let ty = parse("AggregateFunction(1, sumMap, Array(UInt32), Array(UInt64))");
let view = ty.view();
assert_eq!(view.agg_function(), Some(&b"sumMap"[..]));
assert_eq!(view.n_children(), 2);
}
#[test]
fn simple_aggregate_stores_its_first_argument() {
let ty = parse("SimpleAggregateFunction(anyLast, Nullable(String))");
let view = ty.view();
assert_eq!(view.kind(), Some(Kind::SimpleAggregateFunction));
assert_eq!(view.agg_function(), Some(&b"anyLast"[..]));
assert_eq!(view.child(0).and_then(|c| c.kind()), Some(Kind::Nullable));
}
#[test]
fn agg_function_is_none_off_aggregates() {
assert_eq!(parse("Tuple(UInt32)").view().agg_function(), None);
}
#[test]
fn json_parameters_leave_no_children() {
let ty = parse("JSON(max_dynamic_paths=16, `a.b` UInt32, SKIP `a.e`)");
assert_eq!(ty.view().kind(), Some(Kind::Json));
assert_eq!(ty.view().n_children(), 0);
}
#[test]
fn unknown_discriminant_is_none() {
assert_eq!(Kind::from_raw(i32::MAX), None);
assert_eq!(Kind::from_raw(-1), None);
}
#[test]
fn unknown_interval_unit_is_none() {
assert_eq!(IntervalUnit::from_raw(sys::CHC_INTERVAL_NONE), None);
assert_eq!(IntervalUnit::from_raw(i32::MAX), None);
}
#[test]
fn every_c_interval_unit_maps_to_its_variant() {
for (raw, unit) in [
(sys::CHC_INTERVAL_NANOSECOND, IntervalUnit::Nanosecond),
(sys::CHC_INTERVAL_MICROSECOND, IntervalUnit::Microsecond),
(sys::CHC_INTERVAL_MILLISECOND, IntervalUnit::Millisecond),
(sys::CHC_INTERVAL_SECOND, IntervalUnit::Second),
(sys::CHC_INTERVAL_MINUTE, IntervalUnit::Minute),
(sys::CHC_INTERVAL_HOUR, IntervalUnit::Hour),
(sys::CHC_INTERVAL_DAY, IntervalUnit::Day),
(sys::CHC_INTERVAL_WEEK, IntervalUnit::Week),
(sys::CHC_INTERVAL_MONTH, IntervalUnit::Month),
(sys::CHC_INTERVAL_QUARTER, IntervalUnit::Quarter),
(sys::CHC_INTERVAL_YEAR, IntervalUnit::Year),
] {
assert_eq!(IntervalUnit::from_raw(raw), Some(unit));
}
}
#[test]
fn fixed_string_reports_its_width() {
let ty = parse("FixedString(12)");
assert_eq!(ty.view().kind(), Some(Kind::FixedString));
assert_eq!(ty.view().fixed_size(), 12);
assert_eq!(ty.view().elem_size(), 12);
assert_eq!(parse("UInt32").view().fixed_size(), 0);
}
#[test]
fn decimal_carries_precision_and_scale() {
let ty = parse("Decimal(18, 4)");
assert_eq!(ty.view().kind(), Some(Kind::Decimal64));
assert_eq!(ty.view().decimal_precision(), 18);
assert_eq!(ty.view().decimal_scale(), 4);
let plain = parse("String");
assert_eq!(plain.view().decimal_precision(), 0);
assert_eq!(plain.view().decimal_scale(), 0);
}
#[test]
fn subsecond_scale_comes_from_the_type() {
assert_eq!(parse("DateTime64(9)").view().datetime64_scale(), 9);
assert_eq!(parse("Time64(3)").view().datetime64_scale(), 3);
assert_eq!(parse("DateTime").view().datetime64_scale(), 0);
}
#[test]
fn timezone_is_present_only_on_temporal_types() {
let ty = parse("DateTime64(3, 'Europe/Paris')");
assert_eq!(ty.view().timezone(), Some(&b"Europe/Paris"[..]));
assert_eq!(parse("UInt32").view().timezone(), None);
}
#[test]
fn every_parsed_type_reports_its_source_name() {
assert_eq!(
parse("Array(UInt32)").view().name(),
Some(&b"Array(UInt32)"[..])
);
let child = parse("Array(UInt32)");
let child = child.view().child(0).expect("element type");
assert_eq!(child.name(), Some(&b"UInt32"[..]));
assert_eq!(child.format(), "UInt32");
}
#[test]
fn enum_entries_are_addressable_by_index() {
let ty = parse("Enum8('red' = -1, 'green' = 2)");
let view = ty.view();
assert_eq!(view.kind(), Some(Kind::Enum8));
assert_eq!(view.enum_count(), 2);
assert_eq!(view.enum_at(0), Some((&b"red"[..], -1)));
assert_eq!(view.enum_at(1), Some((&b"green"[..], 2)));
assert_eq!(view.enum_at(2), None);
}
#[test]
fn enum_accessors_are_empty_off_enums() {
let ty = parse("UInt32");
assert_eq!(ty.view().enum_count(), 0);
assert_eq!(ty.view().enum_at(0), None);
}
#[test]
fn unnamed_and_out_of_range_fields_have_no_name() {
let ty = parse("Tuple(UInt8, String)");
assert_eq!(ty.view().tuple_field_name(0), None);
assert_eq!(ty.view().tuple_field_name(9), None);
assert_eq!(parse("UInt32").view().tuple_field_name(0), None);
}
#[test]
fn child_index_past_the_end_is_none() {
let ty = parse("Map(String, UInt64)");
assert_eq!(ty.view().n_children(), 2);
assert!(ty.view().child(2).is_none());
assert!(parse("UInt32").view().child(0).is_none());
}
#[test]
fn a_bad_type_name_is_an_error() {
let err = TypeAst::parse("NotAType(", Allocator::stdlib())
.err()
.expect("unterminated type name accepted");
assert_eq!(err.kind, crate::ErrorKind::Type);
}
}