Skip to main content

clickhouse_c/
types.rs

1//! ClickHouse type parsing and inspection.
2
3use core::ffi::c_char;
4use core::ptr::NonNull;
5use core::slice;
6
7use crate::alloc::Allocator;
8use crate::error::{Result, check};
9use crate::sys;
10
11/// ClickHouse type kind.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[repr(i32)]
14pub enum Kind {
15    Void = sys::CHC_VOID,
16    Int8 = sys::CHC_INT8,
17    Int16 = sys::CHC_INT16,
18    Int32 = sys::CHC_INT32,
19    Int64 = sys::CHC_INT64,
20    Int128 = sys::CHC_INT128,
21    Int256 = sys::CHC_INT256,
22    UInt8 = sys::CHC_UINT8,
23    UInt16 = sys::CHC_UINT16,
24    UInt32 = sys::CHC_UINT32,
25    UInt64 = sys::CHC_UINT64,
26    UInt128 = sys::CHC_UINT128,
27    UInt256 = sys::CHC_UINT256,
28    Float32 = sys::CHC_FLOAT32,
29    Float64 = sys::CHC_FLOAT64,
30    BFloat16 = sys::CHC_BFLOAT16,
31    Bool = sys::CHC_BOOL,
32    Date = sys::CHC_DATE,
33    Date32 = sys::CHC_DATE32,
34    DateTime = sys::CHC_DATETIME,
35    DateTime64 = sys::CHC_DATETIME64,
36    Time = sys::CHC_TIME,
37    Time64 = sys::CHC_TIME64,
38    String = sys::CHC_STRING,
39    FixedString = sys::CHC_FIXED_STRING,
40    Decimal32 = sys::CHC_DECIMAL32,
41    Decimal64 = sys::CHC_DECIMAL64,
42    Decimal128 = sys::CHC_DECIMAL128,
43    Decimal256 = sys::CHC_DECIMAL256,
44    Uuid = sys::CHC_UUID,
45    Ipv4 = sys::CHC_IPV4,
46    Ipv6 = sys::CHC_IPV6,
47    Enum8 = sys::CHC_ENUM8,
48    Enum16 = sys::CHC_ENUM16,
49    Nullable = sys::CHC_NULLABLE,
50    Array = sys::CHC_ARRAY,
51    Tuple = sys::CHC_TUPLE,
52    Map = sys::CHC_MAP,
53    Nested = sys::CHC_NESTED,
54    LowCardinality = sys::CHC_LOW_CARDINALITY,
55    Interval = sys::CHC_INTERVAL,
56    Point = sys::CHC_POINT,
57    Ring = sys::CHC_RING,
58    LineString = sys::CHC_LINE_STRING,
59    Polygon = sys::CHC_POLYGON,
60    MultiPolygon = sys::CHC_MULTI_POLYGON,
61    MultiLineString = sys::CHC_MULTI_LINE_STRING,
62    Variant = sys::CHC_VARIANT,
63    Dynamic = sys::CHC_DYNAMIC,
64    Json = sys::CHC_JSON,
65    Object = sys::CHC_OBJECT,
66    AggregateFunction = sys::CHC_AGGREGATE_FUNCTION,
67    SimpleAggregateFunction = sys::CHC_SIMPLE_AGGREGATE_FUNCTION,
68    Nothing = sys::CHC_NOTHING,
69    QBit = sys::CHC_QBIT,
70}
71
72impl Kind {
73    pub(crate) fn from_raw(k: sys::chc_kind) -> Option<Self> {
74        // Return None for values added to C API but not represented here
75        Some(match k {
76            sys::CHC_VOID => Self::Void,
77            sys::CHC_INT8 => Self::Int8,
78            sys::CHC_INT16 => Self::Int16,
79            sys::CHC_INT32 => Self::Int32,
80            sys::CHC_INT64 => Self::Int64,
81            sys::CHC_INT128 => Self::Int128,
82            sys::CHC_INT256 => Self::Int256,
83            sys::CHC_UINT8 => Self::UInt8,
84            sys::CHC_UINT16 => Self::UInt16,
85            sys::CHC_UINT32 => Self::UInt32,
86            sys::CHC_UINT64 => Self::UInt64,
87            sys::CHC_UINT128 => Self::UInt128,
88            sys::CHC_UINT256 => Self::UInt256,
89            sys::CHC_FLOAT32 => Self::Float32,
90            sys::CHC_FLOAT64 => Self::Float64,
91            sys::CHC_BFLOAT16 => Self::BFloat16,
92            sys::CHC_BOOL => Self::Bool,
93            sys::CHC_DATE => Self::Date,
94            sys::CHC_DATE32 => Self::Date32,
95            sys::CHC_DATETIME => Self::DateTime,
96            sys::CHC_DATETIME64 => Self::DateTime64,
97            sys::CHC_TIME => Self::Time,
98            sys::CHC_TIME64 => Self::Time64,
99            sys::CHC_STRING => Self::String,
100            sys::CHC_FIXED_STRING => Self::FixedString,
101            sys::CHC_DECIMAL32 => Self::Decimal32,
102            sys::CHC_DECIMAL64 => Self::Decimal64,
103            sys::CHC_DECIMAL128 => Self::Decimal128,
104            sys::CHC_DECIMAL256 => Self::Decimal256,
105            sys::CHC_UUID => Self::Uuid,
106            sys::CHC_IPV4 => Self::Ipv4,
107            sys::CHC_IPV6 => Self::Ipv6,
108            sys::CHC_ENUM8 => Self::Enum8,
109            sys::CHC_ENUM16 => Self::Enum16,
110            sys::CHC_NULLABLE => Self::Nullable,
111            sys::CHC_ARRAY => Self::Array,
112            sys::CHC_TUPLE => Self::Tuple,
113            sys::CHC_MAP => Self::Map,
114            sys::CHC_NESTED => Self::Nested,
115            sys::CHC_LOW_CARDINALITY => Self::LowCardinality,
116            sys::CHC_INTERVAL => Self::Interval,
117            sys::CHC_POINT => Self::Point,
118            sys::CHC_RING => Self::Ring,
119            sys::CHC_LINE_STRING => Self::LineString,
120            sys::CHC_POLYGON => Self::Polygon,
121            sys::CHC_MULTI_POLYGON => Self::MultiPolygon,
122            sys::CHC_MULTI_LINE_STRING => Self::MultiLineString,
123            sys::CHC_VARIANT => Self::Variant,
124            sys::CHC_DYNAMIC => Self::Dynamic,
125            sys::CHC_JSON => Self::Json,
126            sys::CHC_OBJECT => Self::Object,
127            sys::CHC_AGGREGATE_FUNCTION => Self::AggregateFunction,
128            sys::CHC_SIMPLE_AGGREGATE_FUNCTION => Self::SimpleAggregateFunction,
129            sys::CHC_NOTHING => Self::Nothing,
130            sys::CHC_QBIT => Self::QBit,
131            _ => return None,
132        })
133    }
134}
135
136/// Unit of an `Interval` type.
137///
138/// Every `Interval*` type parses to [`Kind::Interval`]; unit is metadata on
139/// type, reported by [`TypeRef::interval_unit`].
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141#[repr(i32)]
142pub enum IntervalUnit {
143    Nanosecond = sys::CHC_INTERVAL_NANOSECOND,
144    Microsecond = sys::CHC_INTERVAL_MICROSECOND,
145    Millisecond = sys::CHC_INTERVAL_MILLISECOND,
146    Second = sys::CHC_INTERVAL_SECOND,
147    Minute = sys::CHC_INTERVAL_MINUTE,
148    Hour = sys::CHC_INTERVAL_HOUR,
149    Day = sys::CHC_INTERVAL_DAY,
150    Week = sys::CHC_INTERVAL_WEEK,
151    Month = sys::CHC_INTERVAL_MONTH,
152    Quarter = sys::CHC_INTERVAL_QUARTER,
153    Year = sys::CHC_INTERVAL_YEAR,
154}
155
156impl IntervalUnit {
157    /// Returns None for `CHC_INTERVAL_NONE` and for units added to C API but
158    /// not represented here.
159    pub(crate) fn from_raw(u: sys::chc_interval_unit) -> Option<Self> {
160        Some(match u {
161            sys::CHC_INTERVAL_NANOSECOND => Self::Nanosecond,
162            sys::CHC_INTERVAL_MICROSECOND => Self::Microsecond,
163            sys::CHC_INTERVAL_MILLISECOND => Self::Millisecond,
164            sys::CHC_INTERVAL_SECOND => Self::Second,
165            sys::CHC_INTERVAL_MINUTE => Self::Minute,
166            sys::CHC_INTERVAL_HOUR => Self::Hour,
167            sys::CHC_INTERVAL_DAY => Self::Day,
168            sys::CHC_INTERVAL_WEEK => Self::Week,
169            sys::CHC_INTERVAL_MONTH => Self::Month,
170            sys::CHC_INTERVAL_QUARTER => Self::Quarter,
171            sys::CHC_INTERVAL_YEAR => Self::Year,
172            _ => return None,
173        })
174    }
175}
176
177/// Parsed ClickHouse type.
178///
179/// Value releases its memory with allocator passed to [`parse`](Self::parse).
180pub struct TypeAst {
181    raw: NonNull<sys::chc_type>,
182    alloc: Allocator,
183}
184
185impl TypeAst {
186    /// Parses a ClickHouse type name such as `"Array(Nullable(UInt32))"`.
187    pub fn parse(name: &str, alloc: Allocator) -> Result<Self> {
188        let mut out: *mut sys::chc_type = core::ptr::null_mut();
189        let mut err = sys::chc_err::zeroed();
190        let rc = unsafe {
191            sys::chc_type_parse(
192                name.as_ptr().cast::<c_char>(),
193                name.len(),
194                alloc.as_ptr(),
195                &mut out,
196                &mut err,
197            )
198        };
199        check(rc, &err)?;
200        Ok(Self {
201            raw: NonNull::new(out).expect("chc_type_parse returned OK with NULL"),
202            alloc,
203        })
204    }
205
206    /// Returns a borrowed view of parsed type.
207    pub fn view(&self) -> TypeRef<'_> {
208        TypeRef {
209            raw: self.raw.as_ptr().cast_const(),
210            _marker: core::marker::PhantomData,
211        }
212    }
213}
214
215impl Drop for TypeAst {
216    fn drop(&mut self) {
217        unsafe { sys::chc_type_destroy(self.raw.as_ptr(), self.alloc.as_ptr()) };
218    }
219}
220
221unsafe impl Send for TypeAst {}
222
223/// Borrowed view of a ClickHouse type.
224#[derive(Clone, Copy)]
225pub struct TypeRef<'a> {
226    pub(crate) raw: *const sys::chc_type,
227    pub(crate) _marker: core::marker::PhantomData<&'a sys::chc_type>,
228}
229
230impl<'a> TypeRef<'a> {
231    pub fn kind(&self) -> Option<Kind> {
232        Kind::from_raw(unsafe { sys::chc_type_kind(self.raw) })
233    }
234
235    /// Returns number of child types.
236    ///
237    /// Child types include array elements, tuple and nested fields, map keys
238    /// and values, aggregate function argument types, and inner types of
239    /// `Nullable` and `LowCardinality`.
240    pub fn n_children(&self) -> usize {
241        unsafe { sys::chc_type_n_children(self.raw) }
242    }
243
244    pub fn child(&self, i: usize) -> Option<TypeRef<'a>> {
245        let c = unsafe { sys::chc_type_child(self.raw, i) };
246        if c.is_null() {
247            None
248        } else {
249            Some(TypeRef {
250                raw: c,
251                _marker: core::marker::PhantomData,
252            })
253        }
254    }
255
256    /// Returns `N` for `FixedString(N)`, or zero for other types.
257    pub fn fixed_size(&self) -> i32 {
258        unsafe { sys::chc_type_fixed_size(self.raw) }
259    }
260
261    /// Returns byte width in a [`Fixed`](crate::ColumnLayout::Fixed) column.
262    /// Returns zero for types without fixed-width storage.
263    pub fn elem_size(&self) -> usize {
264        unsafe { sys::chc_type_elem_size(self.raw) }
265    }
266
267    /// Returns `P` for `Decimal(P, S)`, or zero for other types.
268    pub fn decimal_precision(&self) -> i32 {
269        unsafe { sys::chc_type_decimal_precision(self.raw) }
270    }
271
272    /// Returns `S` for `Decimal(P, S)`, or zero for other types.
273    pub fn decimal_scale(&self) -> i32 {
274        unsafe { sys::chc_type_decimal_scale(self.raw) }
275    }
276
277    /// Returns subsecond precision for `DateTime64(S)` or `Time64(S)`.
278    /// Returns zero for other types.
279    pub fn datetime64_scale(&self) -> i32 {
280        unsafe { sys::chc_type_datetime64_scale(self.raw) }
281    }
282
283    /// Returns unit for an `Interval*` type, or None for other types.
284    pub fn interval_unit(&self) -> Option<IntervalUnit> {
285        IntervalUnit::from_raw(unsafe { sys::chc_type_interval_unit(self.raw) })
286    }
287
288    /// Returns `N` for `QBit(T, N)`, or zero for other types.
289    ///
290    /// A QBit column uses [`ColumnLayout::Tuple`] with one fixed column per
291    /// bit in `T`. Columns use most significant bit first, and each row holds
292    /// `ceil(N / 8)` bytes.
293    ///
294    /// [`ColumnLayout::Tuple`]: crate::ColumnLayout::Tuple
295    /// [`qbit_element_size`]: TypeRef::qbit_element_size
296    pub fn qbit_dimension(&self) -> usize {
297        unsafe { sys::chc_type_qbit_dimension(self.raw) }
298    }
299
300    /// Returns bit width of `T` in `QBit(T, N)`, or zero for other types.
301    /// Supported widths are 16, 32, and 64.
302    pub fn qbit_element_size(&self) -> usize {
303        unsafe { sys::chc_type_qbit_element_size(self.raw) }
304    }
305
306    /// Returns timezone bytes without UTF-8 validation.
307    pub fn timezone(&self) -> Option<&'a [u8]> {
308        let mut len = 0;
309        let p = unsafe { sys::chc_type_timezone(self.raw, &mut len) };
310        if p.is_null() {
311            None
312        } else {
313            // SAFETY: C allocation remains valid for lifetime of borrowed type
314            Some(unsafe { slice::from_raw_parts(p.cast::<u8>(), len) })
315        }
316    }
317
318    /// Returns type name bytes without UTF-8 validation.
319    pub fn name(&self) -> Option<&'a [u8]> {
320        let mut len = 0;
321        let p = unsafe { sys::chc_type_name(self.raw, &mut len) };
322        (!p.is_null()).then(|| unsafe { slice::from_raw_parts(p.cast::<u8>(), len) })
323    }
324
325    /// Returns number of entries for `Enum8` or `Enum16`.
326    /// Returns zero for other types.
327    pub fn enum_count(&self) -> usize {
328        unsafe { sys::chc_type_enum_count(self.raw) }
329    }
330
331    /// Returns name and value for enum entry at `i`.
332    /// Name bytes are not validated as UTF-8.
333    pub fn enum_at(&self, i: usize) -> Option<(&'a [u8], i64)> {
334        if i >= self.enum_count() {
335            return None;
336        }
337        let mut name_ptr: *const c_char = core::ptr::null();
338        let mut name_len: usize = 0;
339        let mut value: i64 = 0;
340        unsafe {
341            sys::chc_type_enum_at(self.raw, i, &mut name_ptr, &mut name_len, &mut value);
342        }
343        (!name_ptr.is_null()).then(|| {
344            (
345                unsafe { slice::from_raw_parts(name_ptr.cast::<u8>(), name_len) },
346                value,
347            )
348        })
349    }
350
351    /// Returns `Tuple` or `Nested` field name bytes without UTF-8 validation.
352    pub fn tuple_field_name(&self, i: usize) -> Option<&'a [u8]> {
353        let mut len = 0;
354        let p = unsafe { sys::chc_type_tuple_field_name(self.raw, i, &mut len) };
355        if p.is_null() {
356            None
357        } else {
358            Some(unsafe { slice::from_raw_parts(p.cast::<u8>(), len) })
359        }
360    }
361
362    /// Returns function name of `AggregateFunction` or
363    /// `SimpleAggregateFunction` without UTF-8 validation.
364    ///
365    /// Function parameters are not part of name. Children hold argument
366    /// types, first of which carries stored values.
367    pub fn agg_function(&self) -> Option<&'a [u8]> {
368        let mut len = 0;
369        let p = unsafe { sys::chc_type_agg_function(self.raw, &mut len) };
370        if p.is_null() {
371            None
372        } else {
373            Some(unsafe { slice::from_raw_parts(p.cast::<u8>(), len) })
374        }
375    }
376
377    /// Formats type as a ClickHouse type name.
378    pub fn format(&self) -> String {
379        let needed = unsafe { sys::chc_type_format(self.raw, core::ptr::null_mut(), 0) };
380        // C writes a trailing NUL past `needed` bytes
381        let mut buf = vec![0u8; needed.saturating_add(1)];
382        let _ =
383            unsafe { sys::chc_type_format(self.raw, buf.as_mut_ptr().cast::<c_char>(), buf.len()) };
384        buf.truncate(needed);
385        String::from_utf8_lossy(&buf).into_owned()
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::{IntervalUnit, Kind, TypeAst};
392    use crate::Allocator;
393    use crate::sys;
394
395    fn parse(name: &str) -> TypeAst {
396        TypeAst::parse(name, Allocator::stdlib()).expect(name)
397    }
398
399    #[test]
400    fn qbit_metadata() {
401        let ty = parse("QBit(Float32, 16)");
402        let view = ty.view();
403        assert_eq!(view.kind(), Some(Kind::QBit));
404        assert_eq!(view.qbit_dimension(), 16);
405        assert_eq!(view.qbit_element_size(), 32);
406        assert_eq!(view.format(), "QBit(Float32, 16)");
407        assert_eq!(view.child(0).and_then(|c| c.kind()), Some(Kind::Float32));
408    }
409
410    #[test]
411    fn qbit_accessors_are_zero_off_qbit() {
412        let ty = parse("Array(UInt32)");
413        assert_eq!(ty.view().qbit_dimension(), 0);
414        assert_eq!(ty.view().qbit_element_size(), 0);
415    }
416
417    #[test]
418    fn interval_names_carry_their_unit() {
419        let ty = parse("IntervalQuarter");
420        assert_eq!(ty.view().kind(), Some(Kind::Interval));
421        assert_eq!(ty.view().interval_unit(), Some(IntervalUnit::Quarter));
422
423        let nested = parse("Nullable(IntervalDay)");
424        let inner = nested.view().child(0).expect("Nullable child");
425        assert_eq!(inner.interval_unit(), Some(IntervalUnit::Day));
426        assert_eq!(nested.view().interval_unit(), None);
427    }
428
429    #[test]
430    fn line_string_kinds_parse() {
431        assert_eq!(parse("LineString").view().kind(), Some(Kind::LineString));
432        assert_eq!(
433            parse("MultiLineString").view().kind(),
434            Some(Kind::MultiLineString)
435        );
436    }
437
438    #[test]
439    fn nested_fields_carry_names() {
440        let ty = parse("Nested(a UInt32, b String)");
441        let view = ty.view();
442        assert_eq!(view.kind(), Some(Kind::Nested));
443        assert_eq!(view.n_children(), 2);
444        assert_eq!(view.tuple_field_name(0), Some(&b"a"[..]));
445        assert_eq!(view.tuple_field_name(1), Some(&b"b"[..]));
446    }
447
448    #[test]
449    fn aggregate_children_drop_function_parameters() {
450        let ty = parse("AggregateFunction(quantiles(0.5, 0.9), UInt64)");
451        let view = ty.view();
452        assert_eq!(view.kind(), Some(Kind::AggregateFunction));
453        assert_eq!(view.agg_function(), Some(&b"quantiles"[..]));
454        assert_eq!(view.n_children(), 1);
455        assert_eq!(view.child(0).and_then(|c| c.kind()), Some(Kind::UInt64));
456    }
457
458    // Server may prefix a serialization version before function name
459    #[test]
460    fn aggregate_version_prefix_parses() {
461        let ty = parse("AggregateFunction(1, sumMap, Array(UInt32), Array(UInt64))");
462        let view = ty.view();
463        assert_eq!(view.agg_function(), Some(&b"sumMap"[..]));
464        assert_eq!(view.n_children(), 2);
465    }
466
467    #[test]
468    fn simple_aggregate_stores_its_first_argument() {
469        let ty = parse("SimpleAggregateFunction(anyLast, Nullable(String))");
470        let view = ty.view();
471        assert_eq!(view.kind(), Some(Kind::SimpleAggregateFunction));
472        assert_eq!(view.agg_function(), Some(&b"anyLast"[..]));
473        assert_eq!(view.child(0).and_then(|c| c.kind()), Some(Kind::Nullable));
474    }
475
476    #[test]
477    fn agg_function_is_none_off_aggregates() {
478        assert_eq!(parse("Tuple(UInt32)").view().agg_function(), None);
479    }
480
481    // JSON hints steer server storage, not string serialization
482    #[test]
483    fn json_parameters_leave_no_children() {
484        let ty = parse("JSON(max_dynamic_paths=16, `a.b` UInt32, SKIP `a.e`)");
485        assert_eq!(ty.view().kind(), Some(Kind::Json));
486        assert_eq!(ty.view().n_children(), 0);
487    }
488
489    // Unknown C discriminants must not convert to adjacent Rust variants
490    #[test]
491    fn unknown_discriminant_is_none() {
492        assert_eq!(Kind::from_raw(i32::MAX), None);
493        assert_eq!(Kind::from_raw(-1), None);
494    }
495
496    #[test]
497    fn unknown_interval_unit_is_none() {
498        assert_eq!(IntervalUnit::from_raw(sys::CHC_INTERVAL_NONE), None);
499        assert_eq!(IntervalUnit::from_raw(i32::MAX), None);
500    }
501
502    #[test]
503    fn every_c_interval_unit_maps_to_its_variant() {
504        for (raw, unit) in [
505            (sys::CHC_INTERVAL_NANOSECOND, IntervalUnit::Nanosecond),
506            (sys::CHC_INTERVAL_MICROSECOND, IntervalUnit::Microsecond),
507            (sys::CHC_INTERVAL_MILLISECOND, IntervalUnit::Millisecond),
508            (sys::CHC_INTERVAL_SECOND, IntervalUnit::Second),
509            (sys::CHC_INTERVAL_MINUTE, IntervalUnit::Minute),
510            (sys::CHC_INTERVAL_HOUR, IntervalUnit::Hour),
511            (sys::CHC_INTERVAL_DAY, IntervalUnit::Day),
512            (sys::CHC_INTERVAL_WEEK, IntervalUnit::Week),
513            (sys::CHC_INTERVAL_MONTH, IntervalUnit::Month),
514            (sys::CHC_INTERVAL_QUARTER, IntervalUnit::Quarter),
515            (sys::CHC_INTERVAL_YEAR, IntervalUnit::Year),
516        ] {
517            assert_eq!(IntervalUnit::from_raw(raw), Some(unit));
518        }
519    }
520
521    #[test]
522    fn fixed_string_reports_its_width() {
523        let ty = parse("FixedString(12)");
524        assert_eq!(ty.view().kind(), Some(Kind::FixedString));
525        assert_eq!(ty.view().fixed_size(), 12);
526        assert_eq!(ty.view().elem_size(), 12);
527        assert_eq!(parse("UInt32").view().fixed_size(), 0);
528    }
529
530    #[test]
531    fn decimal_carries_precision_and_scale() {
532        let ty = parse("Decimal(18, 4)");
533        assert_eq!(ty.view().kind(), Some(Kind::Decimal64));
534        assert_eq!(ty.view().decimal_precision(), 18);
535        assert_eq!(ty.view().decimal_scale(), 4);
536
537        let plain = parse("String");
538        assert_eq!(plain.view().decimal_precision(), 0);
539        assert_eq!(plain.view().decimal_scale(), 0);
540    }
541
542    #[test]
543    fn subsecond_scale_comes_from_the_type() {
544        assert_eq!(parse("DateTime64(9)").view().datetime64_scale(), 9);
545        assert_eq!(parse("Time64(3)").view().datetime64_scale(), 3);
546        assert_eq!(parse("DateTime").view().datetime64_scale(), 0);
547    }
548
549    #[test]
550    fn timezone_is_present_only_on_temporal_types() {
551        let ty = parse("DateTime64(3, 'Europe/Paris')");
552        assert_eq!(ty.view().timezone(), Some(&b"Europe/Paris"[..]));
553        assert_eq!(parse("UInt32").view().timezone(), None);
554    }
555
556    #[test]
557    fn every_parsed_type_reports_its_source_name() {
558        assert_eq!(
559            parse("Array(UInt32)").view().name(),
560            Some(&b"Array(UInt32)"[..])
561        );
562        let child = parse("Array(UInt32)");
563        let child = child.view().child(0).expect("element type");
564        assert_eq!(child.name(), Some(&b"UInt32"[..]));
565        assert_eq!(child.format(), "UInt32");
566    }
567
568    #[test]
569    fn enum_entries_are_addressable_by_index() {
570        let ty = parse("Enum8('red' = -1, 'green' = 2)");
571        let view = ty.view();
572        assert_eq!(view.kind(), Some(Kind::Enum8));
573        assert_eq!(view.enum_count(), 2);
574        assert_eq!(view.enum_at(0), Some((&b"red"[..], -1)));
575        assert_eq!(view.enum_at(1), Some((&b"green"[..], 2)));
576        assert_eq!(view.enum_at(2), None);
577    }
578
579    #[test]
580    fn enum_accessors_are_empty_off_enums() {
581        let ty = parse("UInt32");
582        assert_eq!(ty.view().enum_count(), 0);
583        assert_eq!(ty.view().enum_at(0), None);
584    }
585
586    #[test]
587    fn unnamed_and_out_of_range_fields_have_no_name() {
588        let ty = parse("Tuple(UInt8, String)");
589        assert_eq!(ty.view().tuple_field_name(0), None);
590        assert_eq!(ty.view().tuple_field_name(9), None);
591        assert_eq!(parse("UInt32").view().tuple_field_name(0), None);
592    }
593
594    #[test]
595    fn child_index_past_the_end_is_none() {
596        let ty = parse("Map(String, UInt64)");
597        assert_eq!(ty.view().n_children(), 2);
598        assert!(ty.view().child(2).is_none());
599        assert!(parse("UInt32").view().child(0).is_none());
600    }
601
602    #[test]
603    fn a_bad_type_name_is_an_error() {
604        let err = TypeAst::parse("NotAType(", Allocator::stdlib())
605            .err()
606            .expect("unterminated type name accepted");
607        assert_eq!(err.kind, crate::ErrorKind::Type);
608    }
609}