Skip to main content

clickhouse_native_client/types/
mod.rs

1//! # ClickHouse Type System
2//!
3//! This module implements the ClickHouse type system for the native TCP
4//! protocol.
5//!
6//! ## ClickHouse Documentation References
7//!
8//! ### Numeric Types
9//! - [Integer Types](https://clickhouse.com/docs/en/sql-reference/data-types/int-uint)
10//!   - Int8/16/32/64/128, UInt8/16/32/64/128
11//! - [Floating-Point Types](https://clickhouse.com/docs/en/sql-reference/data-types/float)
12//!   - Float32, Float64
13//! - [Decimal Types](https://clickhouse.com/docs/en/sql-reference/data-types/decimal)
14//!   - Decimal, Decimal32/64/128
15//!
16//! ### String Types
17//! - [String](https://clickhouse.com/docs/en/sql-reference/data-types/string)
18//!   - Variable-length strings
19//! - [FixedString](https://clickhouse.com/docs/en/sql-reference/data-types/fixedstring)
20//!   - Fixed-length binary strings
21//!
22//! ### Date and Time Types
23//! - [Date](https://clickhouse.com/docs/en/sql-reference/data-types/date) -
24//!   Days since 1970-01-01
25//! - [Date32](https://clickhouse.com/docs/en/sql-reference/data-types/date32)
26//!   - Extended date range
27//! - [DateTime](https://clickhouse.com/docs/en/sql-reference/data-types/datetime)
28//!   - Unix timestamp (UInt32)
29//! - [DateTime64](https://clickhouse.com/docs/en/sql-reference/data-types/datetime64)
30//!   - High precision timestamp (Int64)
31//!
32//! ### Compound Types
33//! - [Array](https://clickhouse.com/docs/en/sql-reference/data-types/array) -
34//!   Arrays of elements
35//! - [Tuple](https://clickhouse.com/docs/en/sql-reference/data-types/tuple) -
36//!   Fixed-size collections
37//! - [Map](https://clickhouse.com/docs/en/sql-reference/data-types/map) -
38//!   Key-value pairs
39//!
40//! ### Special Types
41//! - [Nullable](https://clickhouse.com/docs/en/sql-reference/data-types/nullable)
42//!   - Adds NULL support to any type
43//! - [LowCardinality](https://clickhouse.com/docs/en/sql-reference/data-types/lowcardinality)
44//!   - Dictionary encoding for compression
45//! - [Enum8/Enum16](https://clickhouse.com/docs/en/sql-reference/data-types/enum)
46//!   - Enumerated values
47//! - [UUID](https://clickhouse.com/docs/en/sql-reference/data-types/uuid) -
48//!   Universally unique identifiers
49//! - [IPv4/IPv6](https://clickhouse.com/docs/en/sql-reference/data-types/ipv4)
50//!   - IP addresses
51//!
52//! ### Geo Types
53//! - [Point](https://clickhouse.com/docs/en/sql-reference/data-types/geo) - 2D
54//!   point (Tuple(Float64, Float64))
55//! - [Ring](https://clickhouse.com/docs/en/sql-reference/data-types/geo) -
56//!   Array of Points
57//! - [Polygon](https://clickhouse.com/docs/en/sql-reference/data-types/geo) -
58//!   Array of Rings
59//! - [MultiPolygon](https://clickhouse.com/docs/en/sql-reference/data-types/geo)
60//!   - Array of Polygons
61//!
62//! ## Type Nesting Rules
63//!
64//! ClickHouse enforces strict type nesting rules (Error code 43:
65//! `ILLEGAL_TYPE_OF_ARGUMENT`):
66//!
67//! **✅ Allowed:**
68//! - `Array(Nullable(T))` - Array where each element can be NULL
69//! - `LowCardinality(Nullable(T))` - Dictionary-encoded nullable values
70//! - `Array(LowCardinality(T))` - Array of dictionary-encoded values
71//! - `Array(LowCardinality(Nullable(T)))` - Combination of all three
72//!
73//! **❌ NOT Allowed:**
74//! - `Nullable(Array(T))` - Arrays themselves cannot be NULL (use empty array
75//!   instead)
76//! - `Nullable(LowCardinality(T))` - Wrong nesting order
77//! - `Nullable(Nullable(T))` - Double-nullable is invalid
78//!
79//! For more details, see the [column module documentation](crate::column).
80
81mod parser;
82
83pub use parser::{
84    parse_type_name,
85    TypeAst,
86    TypeMeta,
87};
88
89use std::sync::Arc;
90
91/// Trait for mapping Rust primitive types to ClickHouse types
92/// Equivalent to C++ `Type::CreateSimple<T>()` template specializations
93///
94/// This trait allows type inference in column constructors, eliminating the
95/// need to pass Type explicitly when creating typed columns.
96///
97/// # Examples
98///
99/// ```
100/// use clickhouse_native_client::types::{Type, ToType};
101///
102/// assert_eq!(i32::to_type(), Type::int32());
103/// assert_eq!(u64::to_type(), Type::uint64());
104/// assert_eq!(f64::to_type(), Type::float64());
105/// ```
106pub trait ToType {
107    /// Returns the corresponding ClickHouse [`Type`] for this Rust type.
108    fn to_type() -> Type;
109}
110
111// Implement ToType for all primitive numeric types
112impl ToType for i8 {
113    fn to_type() -> Type {
114        Type::int8()
115    }
116}
117
118impl ToType for i16 {
119    fn to_type() -> Type {
120        Type::int16()
121    }
122}
123
124impl ToType for i32 {
125    fn to_type() -> Type {
126        Type::int32()
127    }
128}
129
130impl ToType for i64 {
131    fn to_type() -> Type {
132        Type::int64()
133    }
134}
135
136impl ToType for i128 {
137    fn to_type() -> Type {
138        Type::int128()
139    }
140}
141
142impl ToType for u8 {
143    fn to_type() -> Type {
144        Type::uint8()
145    }
146}
147
148impl ToType for u16 {
149    fn to_type() -> Type {
150        Type::uint16()
151    }
152}
153
154impl ToType for u32 {
155    fn to_type() -> Type {
156        Type::uint32()
157    }
158}
159
160impl ToType for u64 {
161    fn to_type() -> Type {
162        Type::uint64()
163    }
164}
165
166impl ToType for u128 {
167    fn to_type() -> Type {
168        Type::uint128()
169    }
170}
171
172impl ToType for f32 {
173    fn to_type() -> Type {
174        Type::float32()
175    }
176}
177
178impl ToType for f64 {
179    fn to_type() -> Type {
180        Type::float64()
181    }
182}
183
184/// Type code enumeration matching ClickHouse types
185///
186/// Each variant represents a base type in ClickHouse. For parametric types
187/// (like Array, Nullable, etc.), see the [`Type`] enum which includes
188/// parameters.
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
190pub enum TypeCode {
191    /// Nothing/Void type, used for NULL-only columns.
192    Void = 0,
193    /// Signed 8-bit integer (-128 to 127).
194    Int8,
195    /// Signed 16-bit integer (-32768 to 32767).
196    Int16,
197    /// Signed 32-bit integer.
198    Int32,
199    /// Signed 64-bit integer.
200    Int64,
201    /// Unsigned 8-bit integer (0 to 255), also used as Bool.
202    UInt8,
203    /// Unsigned 16-bit integer (0 to 65535).
204    UInt16,
205    /// Unsigned 32-bit integer.
206    UInt32,
207    /// Unsigned 64-bit integer.
208    UInt64,
209    /// 32-bit IEEE 754 floating-point number.
210    Float32,
211    /// 64-bit IEEE 754 floating-point number.
212    Float64,
213    /// Variable-length byte string.
214    String,
215    /// Fixed-length byte string, padded with null bytes.
216    FixedString,
217    /// Date and time as a Unix timestamp (UInt32), with optional timezone.
218    DateTime,
219    /// Date stored as days since 1970-01-01 (UInt16).
220    Date,
221    /// Variable-length array of elements of a single type.
222    Array,
223    /// Wrapper type that adds NULL support to the nested type.
224    Nullable,
225    /// Fixed-size ordered collection of heterogeneous types.
226    Tuple,
227    /// Enumeration with Int8 storage (up to 128 values).
228    Enum8,
229    /// Enumeration with Int16 storage (up to 32768 values).
230    Enum16,
231    /// Universally unique identifier (128-bit).
232    UUID,
233    /// IPv4 address stored as UInt32.
234    IPv4,
235    /// IPv6 address stored as 16 bytes in network byte order.
236    IPv6,
237    /// Signed 128-bit integer.
238    Int128,
239    /// Unsigned 128-bit integer.
240    UInt128,
241    /// Arbitrary-precision decimal with configurable precision and scale.
242    Decimal,
243    /// Decimal with up to 9 digits of precision (stored as Int32).
244    Decimal32,
245    /// Decimal with up to 18 digits of precision (stored as Int64).
246    Decimal64,
247    /// Decimal with up to 38 digits of precision (stored as Int128).
248    Decimal128,
249    /// Dictionary-encoded column for low-cardinality data.
250    LowCardinality,
251    /// High-precision date and time stored as Int64, with sub-second
252    /// precision.
253    DateTime64,
254    /// Extended date range stored as Int32 (days since 1970-01-01).
255    Date32,
256    /// Key-value pairs with typed keys and values.
257    Map,
258    /// 2D geographic point as Tuple(Float64, Float64).
259    Point,
260    /// Geographic ring as Array(Point).
261    Ring,
262    /// Geographic polygon as Array(Ring).
263    Polygon,
264    /// Collection of polygons as Array(Polygon).
265    MultiPolygon,
266}
267
268impl TypeCode {
269    /// Returns the ClickHouse type name string for this type code.
270    pub fn name(&self) -> &'static str {
271        match self {
272            TypeCode::Void => "Void",
273            TypeCode::Int8 => "Int8",
274            TypeCode::Int16 => "Int16",
275            TypeCode::Int32 => "Int32",
276            TypeCode::Int64 => "Int64",
277            TypeCode::UInt8 => "UInt8",
278            TypeCode::UInt16 => "UInt16",
279            TypeCode::UInt32 => "UInt32",
280            TypeCode::UInt64 => "UInt64",
281            TypeCode::Float32 => "Float32",
282            TypeCode::Float64 => "Float64",
283            TypeCode::String => "String",
284            TypeCode::FixedString => "FixedString",
285            TypeCode::DateTime => "DateTime",
286            TypeCode::Date => "Date",
287            TypeCode::Array => "Array",
288            TypeCode::Nullable => "Nullable",
289            TypeCode::Tuple => "Tuple",
290            TypeCode::Enum8 => "Enum8",
291            TypeCode::Enum16 => "Enum16",
292            TypeCode::UUID => "UUID",
293            TypeCode::IPv4 => "IPv4",
294            TypeCode::IPv6 => "IPv6",
295            TypeCode::Int128 => "Int128",
296            TypeCode::UInt128 => "UInt128",
297            TypeCode::Decimal => "Decimal",
298            TypeCode::Decimal32 => "Decimal32",
299            TypeCode::Decimal64 => "Decimal64",
300            TypeCode::Decimal128 => "Decimal128",
301            TypeCode::LowCardinality => "LowCardinality",
302            TypeCode::DateTime64 => "DateTime64",
303            TypeCode::Date32 => "Date32",
304            TypeCode::Map => "Map",
305            TypeCode::Point => "Point",
306            TypeCode::Ring => "Ring",
307            TypeCode::Polygon => "Polygon",
308            TypeCode::MultiPolygon => "MultiPolygon",
309        }
310    }
311}
312
313/// Enum item for Enum8/Enum16 types, mapping a name to its integer value.
314#[derive(Debug, Clone, PartialEq, Eq)]
315pub struct EnumItem {
316    /// The string name of this enum variant.
317    pub name: String,
318    /// The integer value associated with this enum variant.
319    pub value: i16,
320}
321
322/// ClickHouse type definition, representing both simple and parametric types.
323#[derive(Debug, Clone)]
324pub enum Type {
325    /// A non-parametric type identified by its [`TypeCode`].
326    Simple(TypeCode),
327    /// Fixed-length byte string with the given size in bytes.
328    FixedString {
329        /// Length of the fixed string in bytes.
330        size: usize,
331    },
332    /// Date and time with optional timezone.
333    DateTime {
334        /// Optional IANA timezone name (e.g. "UTC", "Europe/Moscow").
335        timezone: Option<String>,
336    },
337    /// High-precision date and time with sub-second precision and optional
338    /// timezone.
339    DateTime64 {
340        /// Number of sub-second decimal digits (0 to 18).
341        precision: usize,
342        /// Optional IANA timezone name.
343        timezone: Option<String>,
344    },
345    /// Arbitrary-precision decimal with given precision and scale.
346    Decimal {
347        /// Total number of significant digits.
348        precision: usize,
349        /// Number of digits after the decimal point.
350        scale: usize,
351    },
352    /// Enum with Int8 storage, containing named integer variants.
353    Enum8 {
354        /// The named variants with their integer values.
355        items: Vec<EnumItem>,
356    },
357    /// Enum with Int16 storage, containing named integer variants.
358    Enum16 {
359        /// The named variants with their integer values.
360        items: Vec<EnumItem>,
361    },
362    /// Variable-length array of the given element type.
363    Array {
364        /// The type of each element in the array.
365        item_type: Box<Type>,
366    },
367    /// Nullable wrapper around the given nested type.
368    Nullable {
369        /// The type that is made nullable.
370        nested_type: Box<Type>,
371    },
372    /// Fixed-size tuple of heterogeneous element types.
373    Tuple {
374        /// The ordered list of element types in the tuple.
375        item_types: Vec<Type>,
376    },
377    /// Dictionary-encoded wrapper around the given nested type.
378    LowCardinality {
379        /// The type that is dictionary-encoded.
380        nested_type: Box<Type>,
381    },
382    /// Key-value map with typed keys and values.
383    Map {
384        /// The type of map keys.
385        key_type: Box<Type>,
386        /// The type of map values.
387        value_type: Box<Type>,
388    },
389}
390
391impl Type {
392    /// Returns the [`TypeCode`] for this type.
393    pub fn code(&self) -> TypeCode {
394        match self {
395            Type::Simple(code) => *code,
396            Type::FixedString { .. } => TypeCode::FixedString,
397            Type::DateTime { .. } => TypeCode::DateTime,
398            Type::DateTime64 { .. } => TypeCode::DateTime64,
399            Type::Decimal { .. } => TypeCode::Decimal,
400            Type::Enum8 { .. } => TypeCode::Enum8,
401            Type::Enum16 { .. } => TypeCode::Enum16,
402            Type::Array { .. } => TypeCode::Array,
403            Type::Nullable { .. } => TypeCode::Nullable,
404            Type::Tuple { .. } => TypeCode::Tuple,
405            Type::LowCardinality { .. } => TypeCode::LowCardinality,
406            Type::Map { .. } => TypeCode::Map,
407        }
408    }
409
410    /// Returns the full ClickHouse type name string, including parameters.
411    pub fn name(&self) -> String {
412        match self {
413            Type::Simple(code) => code.name().to_string(),
414            Type::FixedString { size } => format!("FixedString({})", size),
415            Type::DateTime { timezone: None } => "DateTime".to_string(),
416            Type::DateTime { timezone: Some(tz) } => {
417                format!("DateTime('{}')", tz)
418            }
419            Type::DateTime64 { precision, timezone: None } => {
420                format!("DateTime64({})", precision)
421            }
422            Type::DateTime64 { precision, timezone: Some(tz) } => {
423                format!("DateTime64({}, '{}')", precision, tz)
424            }
425            Type::Decimal { precision, scale } => {
426                format!("Decimal({}, {})", precision, scale)
427            }
428            Type::Enum8 { items } => {
429                format!("Enum8({})", format_enum_items(items))
430            }
431            Type::Enum16 { items } => {
432                format!("Enum16({})", format_enum_items(items))
433            }
434            Type::Array { item_type } => {
435                format!("Array({})", item_type.name())
436            }
437            Type::Nullable { nested_type } => {
438                format!("Nullable({})", nested_type.name())
439            }
440            Type::Tuple { item_types } => {
441                let types: Vec<String> =
442                    item_types.iter().map(|t| t.name()).collect();
443                format!("Tuple({})", types.join(", "))
444            }
445            Type::LowCardinality { nested_type } => {
446                format!("LowCardinality({})", nested_type.name())
447            }
448            Type::Map { key_type, value_type } => {
449                format!("Map({}, {})", key_type.name(), value_type.name())
450            }
451        }
452    }
453
454    /// Returns the storage size in bytes for fixed-size types
455    ///
456    /// This is used for calculating buffer sizes when reading/writing
457    /// uncompressed column data. Returns `None` for variable-length types.
458    ///
459    /// # Examples
460    ///
461    /// ```
462    /// use clickhouse_native_client::types::Type;
463    ///
464    /// assert_eq!(Type::uint32().storage_size_bytes(), Some(4));
465    /// assert_eq!(Type::uint64().storage_size_bytes(), Some(8));
466    /// assert_eq!(Type::fixed_string(10).storage_size_bytes(), Some(10));
467    /// assert_eq!(Type::string().storage_size_bytes(), None); // Variable length
468    /// ```
469    pub fn storage_size_bytes(&self) -> Option<usize> {
470        match self {
471            Type::Simple(code) => match code {
472                TypeCode::Int8 | TypeCode::UInt8 => Some(1),
473                TypeCode::Int16 | TypeCode::UInt16 => Some(2),
474                TypeCode::Int32 | TypeCode::UInt32 | TypeCode::Float32 => {
475                    Some(4)
476                }
477                TypeCode::Int64 | TypeCode::UInt64 | TypeCode::Float64 => {
478                    Some(8)
479                }
480                TypeCode::Int128 | TypeCode::UInt128 | TypeCode::UUID => {
481                    Some(16)
482                }
483                TypeCode::Date => Some(2),   // UInt16
484                TypeCode::Date32 => Some(4), // Int32
485                TypeCode::IPv4 => Some(4),
486                TypeCode::IPv6 => Some(16),
487                TypeCode::Point => Some(16), // 2x Float64
488                TypeCode::String => None,    // Variable length
489                _ => None,
490            },
491            Type::FixedString { size } => Some(*size),
492            Type::DateTime { .. } => Some(4), // UInt32 timestamp
493            Type::DateTime64 { .. } => Some(8), // Int64 timestamp
494            Type::Enum8 { .. } => Some(1),    // Stored as Int8
495            Type::Enum16 { .. } => Some(2),   // Stored as Int16
496            Type::Decimal { precision, .. } => {
497                // Decimal storage depends on precision
498                if *precision <= 9 {
499                    Some(4) // Decimal32
500                } else if *precision <= 18 {
501                    Some(8) // Decimal64
502                } else {
503                    Some(16) // Decimal128
504                }
505            }
506            // Complex types don't have fixed storage size
507            Type::Array { .. }
508            | Type::Nullable { .. }
509            | Type::Tuple { .. }
510            | Type::LowCardinality { .. }
511            | Type::Map { .. } => None,
512        }
513    }
514
515    /// Creates an Int8 type.
516    pub fn int8() -> Self {
517        Type::Simple(TypeCode::Int8)
518    }
519
520    /// Creates an Int16 type.
521    pub fn int16() -> Self {
522        Type::Simple(TypeCode::Int16)
523    }
524
525    /// Creates an Int32 type.
526    pub fn int32() -> Self {
527        Type::Simple(TypeCode::Int32)
528    }
529
530    /// Creates an Int64 type.
531    pub fn int64() -> Self {
532        Type::Simple(TypeCode::Int64)
533    }
534
535    /// Creates an Int128 type.
536    pub fn int128() -> Self {
537        Type::Simple(TypeCode::Int128)
538    }
539
540    /// Creates a UInt8 type.
541    pub fn uint8() -> Self {
542        Type::Simple(TypeCode::UInt8)
543    }
544
545    /// Creates a UInt16 type.
546    pub fn uint16() -> Self {
547        Type::Simple(TypeCode::UInt16)
548    }
549
550    /// Creates a UInt32 type.
551    pub fn uint32() -> Self {
552        Type::Simple(TypeCode::UInt32)
553    }
554
555    /// Creates a UInt64 type.
556    pub fn uint64() -> Self {
557        Type::Simple(TypeCode::UInt64)
558    }
559
560    /// Creates a UInt128 type.
561    pub fn uint128() -> Self {
562        Type::Simple(TypeCode::UInt128)
563    }
564
565    /// Creates a Float32 type.
566    pub fn float32() -> Self {
567        Type::Simple(TypeCode::Float32)
568    }
569
570    /// Creates a Float64 type.
571    pub fn float64() -> Self {
572        Type::Simple(TypeCode::Float64)
573    }
574
575    /// Creates a variable-length String type.
576    pub fn string() -> Self {
577        Type::Simple(TypeCode::String)
578    }
579
580    /// Creates a FixedString type with the given size in bytes.
581    pub fn fixed_string(size: usize) -> Self {
582        Type::FixedString { size }
583    }
584
585    /// Creates a Date type (days since 1970-01-01, stored as UInt16).
586    pub fn date() -> Self {
587        Type::Simple(TypeCode::Date)
588    }
589
590    /// Creates a Date32 type (days since 1970-01-01, stored as Int32).
591    pub fn date32() -> Self {
592        Type::Simple(TypeCode::Date32)
593    }
594
595    /// Creates a DateTime type with an optional timezone.
596    pub fn datetime(timezone: Option<String>) -> Self {
597        Type::DateTime { timezone }
598    }
599
600    /// Creates a DateTime64 type with the given sub-second precision and
601    /// optional timezone.
602    pub fn datetime64(precision: usize, timezone: Option<String>) -> Self {
603        Type::DateTime64 { precision, timezone }
604    }
605
606    /// Creates a Decimal type with the given precision and scale.
607    pub fn decimal(precision: usize, scale: usize) -> Self {
608        Type::Decimal { precision, scale }
609    }
610
611    /// Creates an IPv4 address type.
612    pub fn ipv4() -> Self {
613        Type::Simple(TypeCode::IPv4)
614    }
615
616    /// Creates an IPv6 address type.
617    pub fn ipv6() -> Self {
618        Type::Simple(TypeCode::IPv6)
619    }
620
621    /// Creates a UUID type.
622    pub fn uuid() -> Self {
623        Type::Simple(TypeCode::UUID)
624    }
625
626    /// Creates an Array type with the given element type.
627    pub fn array(item_type: Type) -> Self {
628        Type::Array { item_type: Box::new(item_type) }
629    }
630
631    /// Creates a Nullable wrapper around the given type.
632    pub fn nullable(nested_type: Type) -> Self {
633        Type::Nullable { nested_type: Box::new(nested_type) }
634    }
635
636    /// Creates a Tuple type with the given element types.
637    pub fn tuple(item_types: Vec<Type>) -> Self {
638        Type::Tuple { item_types }
639    }
640
641    /// Creates an Enum8 type with the given name-value items.
642    pub fn enum8(items: Vec<EnumItem>) -> Self {
643        Type::Enum8 { items }
644    }
645
646    /// Creates an Enum16 type with the given name-value items.
647    pub fn enum16(items: Vec<EnumItem>) -> Self {
648        Type::Enum16 { items }
649    }
650
651    /// Creates a LowCardinality wrapper around the given type.
652    pub fn low_cardinality(nested_type: Type) -> Self {
653        Type::LowCardinality { nested_type: Box::new(nested_type) }
654    }
655
656    /// Creates a Map type with the given key and value types.
657    pub fn map(key_type: Type, value_type: Type) -> Self {
658        Type::Map {
659            key_type: Box::new(key_type),
660            value_type: Box::new(value_type),
661        }
662    }
663
664    /// Returns true if this enum type contains a variant with the given
665    /// integer value.
666    pub fn has_enum_value(&self, value: i16) -> bool {
667        match self {
668            Type::Enum8 { items } => {
669                items.iter().any(|item| item.value == value)
670            }
671            Type::Enum16 { items } => {
672                items.iter().any(|item| item.value == value)
673            }
674            _ => false,
675        }
676    }
677
678    /// Returns true if this enum type contains a variant with the given name.
679    pub fn has_enum_name(&self, name: &str) -> bool {
680        match self {
681            Type::Enum8 { items } => {
682                items.iter().any(|item| item.name == name)
683            }
684            Type::Enum16 { items } => {
685                items.iter().any(|item| item.name == name)
686            }
687            _ => false,
688        }
689    }
690
691    /// Returns the enum variant name for the given integer value, if it
692    /// exists.
693    pub fn get_enum_name(&self, value: i16) -> Option<&str> {
694        match self {
695            Type::Enum8 { items } => items
696                .iter()
697                .find(|item| item.value == value)
698                .map(|item| item.name.as_str()),
699            Type::Enum16 { items } => items
700                .iter()
701                .find(|item| item.value == value)
702                .map(|item| item.name.as_str()),
703            _ => None,
704        }
705    }
706
707    /// Returns the integer value for the given enum variant name, if it
708    /// exists.
709    pub fn get_enum_value(&self, name: &str) -> Option<i16> {
710        match self {
711            Type::Enum8 { items } => items
712                .iter()
713                .find(|item| item.name == name)
714                .map(|item| item.value),
715            Type::Enum16 { items } => items
716                .iter()
717                .find(|item| item.name == name)
718                .map(|item| item.value),
719            _ => None,
720        }
721    }
722
723    /// Returns the enum items slice if this is an Enum8 or Enum16 type, or
724    /// None otherwise.
725    pub fn enum_items(&self) -> Option<&[EnumItem]> {
726        match self {
727            Type::Enum8 { items } => Some(items),
728            Type::Enum16 { items } => Some(items),
729            _ => None,
730        }
731    }
732
733    /// Creates a Point geo type (Tuple(Float64, Float64)).
734    pub fn point() -> Self {
735        Type::Simple(TypeCode::Point)
736    }
737
738    /// Creates a Ring geo type (Array(Point)).
739    pub fn ring() -> Self {
740        Type::Simple(TypeCode::Ring)
741    }
742
743    /// Creates a Polygon geo type (Array(Ring)).
744    pub fn polygon() -> Self {
745        Type::Simple(TypeCode::Polygon)
746    }
747
748    /// Creates a MultiPolygon geo type (Array(Polygon)).
749    pub fn multi_polygon() -> Self {
750        Type::Simple(TypeCode::MultiPolygon)
751    }
752
753    /// Creates a Nothing/Void type, used for NULL-only columns.
754    pub fn nothing() -> Self {
755        Type::Simple(TypeCode::Void)
756    }
757
758    /// Create a Type from a Rust primitive type
759    /// Equivalent to C++ `Type::CreateSimple<T>()`
760    ///
761    /// # Examples
762    ///
763    /// ```
764    /// use clickhouse_native_client::types::Type;
765    ///
766    /// assert_eq!(Type::for_rust_type::<i32>(), Type::int32());
767    /// assert_eq!(Type::for_rust_type::<u64>(), Type::uint64());
768    /// assert_eq!(Type::for_rust_type::<f32>(), Type::float32());
769    /// ```
770    pub fn for_rust_type<T: ToType>() -> Self {
771        T::to_type()
772    }
773
774    /// Convert TypeAst to Type
775    /// Mirrors C++ CreateColumnFromAst logic
776    pub fn from_ast(ast: &TypeAst) -> crate::Result<Self> {
777        match ast.meta {
778            TypeMeta::Terminal => {
779                // Simple terminal types
780                match ast.code {
781                    TypeCode::Void
782                    | TypeCode::Int8
783                    | TypeCode::Int16
784                    | TypeCode::Int32
785                    | TypeCode::Int64
786                    | TypeCode::Int128
787                    | TypeCode::UInt8
788                    | TypeCode::UInt16
789                    | TypeCode::UInt32
790                    | TypeCode::UInt64
791                    | TypeCode::UInt128
792                    | TypeCode::Float32
793                    | TypeCode::Float64
794                    | TypeCode::String
795                    | TypeCode::Date
796                    | TypeCode::Date32
797                    | TypeCode::UUID
798                    | TypeCode::IPv4
799                    | TypeCode::IPv6
800                    | TypeCode::Point
801                    | TypeCode::Ring
802                    | TypeCode::Polygon
803                    | TypeCode::MultiPolygon => Ok(Type::Simple(ast.code)),
804
805                    TypeCode::FixedString => {
806                        // First element should be the size (Number)
807                        if ast.elements.is_empty() {
808                            return Err(crate::Error::Protocol(
809                                "FixedString requires size parameter"
810                                    .to_string(),
811                            ));
812                        }
813                        let size = ast.elements[0].value as usize;
814                        Ok(Type::FixedString { size })
815                    }
816
817                    TypeCode::DateTime => {
818                        // Optional timezone parameter
819                        if ast.elements.is_empty() {
820                            Ok(Type::DateTime { timezone: None })
821                        } else {
822                            let timezone =
823                                Some(ast.elements[0].value_string.clone());
824                            Ok(Type::DateTime { timezone })
825                        }
826                    }
827
828                    TypeCode::DateTime64 => {
829                        // Precision + optional timezone
830                        if ast.elements.is_empty() {
831                            return Err(crate::Error::Protocol(
832                                "DateTime64 requires precision parameter"
833                                    .to_string(),
834                            ));
835                        }
836                        let precision = ast.elements[0].value as usize;
837                        let timezone = if ast.elements.len() > 1 {
838                            Some(ast.elements[1].value_string.clone())
839                        } else {
840                            None
841                        };
842                        Ok(Type::DateTime64 { precision, timezone })
843                    }
844
845                    TypeCode::Decimal
846                    | TypeCode::Decimal32
847                    | TypeCode::Decimal64
848                    | TypeCode::Decimal128 => {
849                        if ast.elements.len() >= 2 {
850                            let precision = ast.elements[0].value as usize;
851                            let scale = ast.elements[1].value as usize;
852                            Ok(Type::Decimal { precision, scale })
853                        } else if ast.elements.len() == 1 {
854                            // For Decimal32/64/128, scale may default to the
855                            // last element
856                            let scale = ast.elements[0].value as usize;
857                            let precision = match ast.code {
858                                TypeCode::Decimal32 => 9,
859                                TypeCode::Decimal64 => 18,
860                                TypeCode::Decimal128 => 38,
861                                _ => scale,
862                            };
863                            Ok(Type::Decimal { precision, scale })
864                        } else {
865                            Err(crate::Error::Protocol(
866                                "Decimal requires precision and scale parameters".to_string(),
867                            ))
868                        }
869                    }
870
871                    _ => Err(crate::Error::Protocol(format!(
872                        "Unsupported terminal type: {:?}",
873                        ast.code
874                    ))),
875                }
876            }
877
878            TypeMeta::Array => {
879                if ast.elements.is_empty() {
880                    return Err(crate::Error::Protocol(
881                        "Array requires element type".to_string(),
882                    ));
883                }
884                let item_type = Type::from_ast(&ast.elements[0])?;
885                Ok(Type::Array { item_type: Box::new(item_type) })
886            }
887
888            TypeMeta::Nullable => {
889                if ast.elements.is_empty() {
890                    return Err(crate::Error::Protocol(
891                        "Nullable requires nested type".to_string(),
892                    ));
893                }
894                let nested_type = Type::from_ast(&ast.elements[0])?;
895                Ok(Type::Nullable { nested_type: Box::new(nested_type) })
896            }
897
898            TypeMeta::Tuple => {
899                let mut item_types = Vec::new();
900                for elem in &ast.elements {
901                    item_types.push(Type::from_ast(elem)?);
902                }
903                Ok(Type::Tuple { item_types })
904            }
905
906            TypeMeta::Enum => {
907                // Enum elements are stored as: name1, value1, name2, value2,
908                // ...
909                let mut items = Vec::new();
910                for i in (0..ast.elements.len()).step_by(2) {
911                    if i + 1 >= ast.elements.len() {
912                        break;
913                    }
914                    let name = ast.elements[i].value_string.clone();
915                    let value = ast.elements[i + 1].value as i16;
916                    items.push(EnumItem { name, value });
917                }
918
919                match ast.code {
920                    TypeCode::Enum8 => Ok(Type::Enum8 { items }),
921                    TypeCode::Enum16 => Ok(Type::Enum16 { items }),
922                    _ => Err(crate::Error::Protocol(format!(
923                        "Invalid enum type code: {:?}",
924                        ast.code
925                    ))),
926                }
927            }
928
929            TypeMeta::LowCardinality => {
930                if ast.elements.is_empty() {
931                    return Err(crate::Error::Protocol(
932                        "LowCardinality requires nested type".to_string(),
933                    ));
934                }
935                let nested_type = Type::from_ast(&ast.elements[0])?;
936                Ok(Type::LowCardinality { nested_type: Box::new(nested_type) })
937            }
938
939            TypeMeta::Map => {
940                if ast.elements.len() != 2 {
941                    return Err(crate::Error::Protocol(
942                        "Map requires exactly 2 type parameters".to_string(),
943                    ));
944                }
945                let key_type = Type::from_ast(&ast.elements[0])?;
946                let value_type = Type::from_ast(&ast.elements[1])?;
947                Ok(Type::Map {
948                    key_type: Box::new(key_type),
949                    value_type: Box::new(value_type),
950                })
951            }
952
953            TypeMeta::SimpleAggregateFunction => {
954                // SimpleAggregateFunction(func, Type) -> unwrap to Type
955                // Last element is the actual type
956                if ast.elements.is_empty() {
957                    return Err(crate::Error::Protocol(
958                        "SimpleAggregateFunction requires type parameter"
959                            .to_string(),
960                    ));
961                }
962                let type_elem = ast.elements.last().unwrap();
963                Type::from_ast(type_elem)
964            }
965
966            TypeMeta::Number
967            | TypeMeta::String
968            | TypeMeta::Assign
969            | TypeMeta::Null => {
970                // These are intermediate AST nodes, not actual types
971                Err(crate::Error::Protocol(format!(
972                    "Cannot convert AST meta {:?} to Type",
973                    ast.meta
974                )))
975            }
976        }
977    }
978
979    /// Parse a type from its string representation
980    ///
981    /// Uses token-based parser with AST caching for performance
982    pub fn parse(type_str: &str) -> crate::Result<Self> {
983        let ast = parse_type_name(type_str)?;
984        Type::from_ast(&ast)
985    }
986
987    /// Parse a type from its string representation (old implementation for
988    /// fallback)
989    #[allow(dead_code)]
990    fn parse_old(type_str: &str) -> crate::Result<Self> {
991        let type_str = type_str.trim();
992
993        // Handle empty/whitespace-only strings
994        if type_str.is_empty() {
995            return Err(crate::Error::Protocol(
996                "Empty type string".to_string(),
997            ));
998        }
999
1000        // Find the first '(' to split type name from parameters
1001        if let Some(paren_pos) = type_str.find('(') {
1002            if !type_str.ends_with(')') {
1003                return Err(crate::Error::Protocol(format!(
1004                    "Mismatched parentheses in type: {}",
1005                    type_str
1006                )));
1007            }
1008
1009            let type_name = &type_str[..paren_pos];
1010            let params_str = &type_str[paren_pos + 1..type_str.len() - 1];
1011
1012            return match type_name {
1013                "Nullable" => Ok(Type::nullable(Type::parse(params_str)?)),
1014                "Array" => Ok(Type::array(Type::parse(params_str)?)),
1015                "FixedString" => {
1016                    let size = params_str.parse::<usize>().map_err(|_| {
1017                        crate::Error::Protocol(format!(
1018                            "Invalid FixedString size: {}",
1019                            params_str
1020                        ))
1021                    })?;
1022                    Ok(Type::fixed_string(size))
1023                }
1024                "DateTime" => {
1025                    // DateTime('UTC') or DateTime('Europe/Minsk')
1026                    let tz = parse_string_literal(params_str)?;
1027                    Ok(Type::datetime(Some(tz)))
1028                }
1029                "DateTime64" => {
1030                    // DateTime64(3, 'UTC') or DateTime64(3)
1031                    let params = parse_comma_separated(params_str)?;
1032                    if params.is_empty() {
1033                        return Err(crate::Error::Protocol(
1034                            "DateTime64 requires precision parameter"
1035                                .to_string(),
1036                        ));
1037                    }
1038                    let precision =
1039                        params[0].parse::<usize>().map_err(|_| {
1040                            crate::Error::Protocol(format!(
1041                                "Invalid DateTime64 precision: {}",
1042                                params[0]
1043                            ))
1044                        })?;
1045                    let timezone = if params.len() > 1 {
1046                        Some(parse_string_literal(&params[1])?)
1047                    } else {
1048                        None
1049                    };
1050                    Ok(Type::datetime64(precision, timezone))
1051                }
1052                "Decimal" => {
1053                    // Decimal(12, 5)
1054                    let params = parse_comma_separated(params_str)?;
1055                    if params.len() != 2 {
1056                        return Err(crate::Error::Protocol(format!(
1057                            "Decimal requires 2 parameters, got {}",
1058                            params.len()
1059                        )));
1060                    }
1061                    let precision =
1062                        params[0].parse::<usize>().map_err(|_| {
1063                            crate::Error::Protocol(format!(
1064                                "Invalid Decimal precision: {}",
1065                                params[0]
1066                            ))
1067                        })?;
1068                    let scale = params[1].parse::<usize>().map_err(|_| {
1069                        crate::Error::Protocol(format!(
1070                            "Invalid Decimal scale: {}",
1071                            params[1]
1072                        ))
1073                    })?;
1074                    Ok(Type::decimal(precision, scale))
1075                }
1076                "Decimal32" | "Decimal64" | "Decimal128" => {
1077                    // Decimal32(7) - single precision parameter, scale
1078                    // defaults to 0
1079                    let precision =
1080                        params_str.parse::<usize>().map_err(|_| {
1081                            crate::Error::Protocol(format!(
1082                                "Invalid {} precision: {}",
1083                                type_name, params_str
1084                            ))
1085                        })?;
1086                    Ok(Type::decimal(precision, 0))
1087                }
1088                "Enum8" => {
1089                    // Enum8('red' = 1, 'green' = 2)
1090                    let items = parse_enum_items(params_str)?;
1091                    Ok(Type::enum8(items))
1092                }
1093                "Enum16" => {
1094                    // Enum16('red' = 1, 'green' = 2)
1095                    let items = parse_enum_items(params_str)?;
1096                    Ok(Type::enum16(items))
1097                }
1098                "LowCardinality" => {
1099                    Ok(Type::low_cardinality(Type::parse(params_str)?))
1100                }
1101                "Map" => {
1102                    // Map(Int32, String)
1103                    let params = parse_comma_separated(params_str)?;
1104                    if params.len() != 2 {
1105                        return Err(crate::Error::Protocol(format!(
1106                            "Map requires 2 type parameters, got {}",
1107                            params.len()
1108                        )));
1109                    }
1110                    let key_type = Type::parse(&params[0])?;
1111                    let value_type = Type::parse(&params[1])?;
1112                    Ok(Type::map(key_type, value_type))
1113                }
1114                "Tuple" => {
1115                    // Tuple(UInt8, String, Date)
1116                    let params = parse_comma_separated(params_str)?;
1117                    if params.is_empty() {
1118                        return Err(crate::Error::Protocol(
1119                            "Tuple requires at least one type parameter"
1120                                .to_string(),
1121                        ));
1122                    }
1123                    let mut item_types = Vec::new();
1124                    for param in params {
1125                        item_types.push(Type::parse(&param)?);
1126                    }
1127                    Ok(Type::tuple(item_types))
1128                }
1129                "SimpleAggregateFunction" => {
1130                    // SimpleAggregateFunction(func, Type) -> unwrap to Type
1131                    // Example: SimpleAggregateFunction(func, Int32) -> Int32
1132                    let params = parse_comma_separated(params_str)?;
1133                    if params.len() < 2 {
1134                        return Err(crate::Error::Protocol("SimpleAggregateFunction requires at least 2 parameters".to_string()));
1135                    }
1136                    // First param is function name, second is type - we just
1137                    // care about the type
1138                    Type::parse(&params[1])
1139                }
1140                "AggregateFunction" => {
1141                    // AggregateFunction is not supported for reading
1142                    // Matches C++ client behavior which throws
1143                    // UnimplementedError These columns
1144                    // contain internal aggregation state which requires
1145                    // specialized deserialization logic for each aggregate
1146                    // function
1147                    Err(crate::Error::Protocol(
1148                        "AggregateFunction columns are not supported. Use SimpleAggregateFunction or finalize the aggregation with -State combinators.".to_string()
1149                    ))
1150                }
1151                _ => Err(crate::Error::Protocol(format!(
1152                    "Unknown parametric type: {}",
1153                    type_name
1154                ))),
1155            };
1156        }
1157
1158        // Simple types without parameters
1159        match type_str {
1160            "UInt8" => Ok(Type::uint8()),
1161            "UInt16" => Ok(Type::uint16()),
1162            "UInt32" => Ok(Type::uint32()),
1163            "UInt64" => Ok(Type::uint64()),
1164            "UInt128" => Ok(Type::Simple(TypeCode::UInt128)),
1165            "Int8" => Ok(Type::int8()),
1166            "Int16" => Ok(Type::int16()),
1167            "Int32" => Ok(Type::int32()),
1168            "Int64" => Ok(Type::int64()),
1169            "Int128" => Ok(Type::Simple(TypeCode::Int128)),
1170            "Float32" => Ok(Type::float32()),
1171            "Float64" => Ok(Type::float64()),
1172            "String" => Ok(Type::string()),
1173            "Date" => Ok(Type::date()),
1174            "Date32" => Ok(Type::date32()),
1175            "DateTime" => Ok(Type::datetime(None)),
1176            "UUID" => Ok(Type::uuid()),
1177            "IPv4" => Ok(Type::ipv4()),
1178            "IPv6" => Ok(Type::ipv6()),
1179            "Bool" => Ok(Type::uint8()), // Bool is an alias for UInt8
1180            "Nothing" => Ok(Type::Simple(TypeCode::Void)), /* Nothing type for NULL columns */
1181            "Point" => Ok(Type::point()), // Point is Tuple(Float64, Float64)
1182            "Ring" => Ok(Type::ring()),   // Ring is Array(Point)
1183            "Polygon" => Ok(Type::polygon()), // Polygon is Array(Ring)
1184            "MultiPolygon" => Ok(Type::multi_polygon()), /* MultiPolygon is Array(Polygon) */
1185            _ => Err(crate::Error::Protocol(format!(
1186                "Unknown type: {}",
1187                type_str
1188            ))),
1189        }
1190    }
1191}
1192
1193// Helper functions for type parsing
1194
1195/// Parse a string literal from 'quoted' or "quoted" format
1196fn parse_string_literal(s: &str) -> crate::Result<String> {
1197    let s = s.trim();
1198    if (s.starts_with('\'') && s.ends_with('\''))
1199        || (s.starts_with('"') && s.ends_with('"'))
1200    {
1201        Ok(s[1..s.len() - 1].to_string())
1202    } else {
1203        Err(crate::Error::Protocol(format!(
1204            "Expected quoted string, got: {}",
1205            s
1206        )))
1207    }
1208}
1209
1210/// Split comma-separated parameters, respecting nested parentheses
1211/// Example: "Int32, String" -> ["Int32", "String"]
1212/// Example: "Map(Int32, String), UInt64" -> ["Map(Int32, String)", "UInt64"]
1213fn parse_comma_separated(s: &str) -> crate::Result<Vec<String>> {
1214    let mut params = Vec::new();
1215    let mut current = String::new();
1216    let mut paren_depth = 0;
1217    let mut in_quotes = false;
1218    let mut quote_char = '\0';
1219
1220    for ch in s.chars() {
1221        match ch {
1222            '\'' | '"' if !in_quotes => {
1223                in_quotes = true;
1224                quote_char = ch;
1225                current.push(ch);
1226            }
1227            ch if in_quotes && ch == quote_char => {
1228                in_quotes = false;
1229                current.push(ch);
1230            }
1231            '(' if !in_quotes => {
1232                paren_depth += 1;
1233                current.push(ch);
1234            }
1235            ')' if !in_quotes => {
1236                paren_depth -= 1;
1237                current.push(ch);
1238            }
1239            ',' if !in_quotes && paren_depth == 0 => {
1240                params.push(current.trim().to_string());
1241                current.clear();
1242            }
1243            _ => {
1244                current.push(ch);
1245            }
1246        }
1247    }
1248
1249    if !current.trim().is_empty() {
1250        params.push(current.trim().to_string());
1251    }
1252
1253    Ok(params)
1254}
1255
1256/// Parse enum items from string like "'red' = 1, 'green' = 2, 'blue' = 3"
1257fn parse_enum_items(s: &str) -> crate::Result<Vec<EnumItem>> {
1258    let mut items = Vec::new();
1259    let parts = parse_comma_separated(s)?;
1260
1261    for part in parts {
1262        // Each part should be 'name' = value
1263        let eq_parts: Vec<&str> = part.split('=').collect();
1264        if eq_parts.len() != 2 {
1265            return Err(crate::Error::Protocol(format!(
1266                "Invalid enum item format (expected 'name' = value): {}",
1267                part
1268            )));
1269        }
1270
1271        let name = parse_string_literal(eq_parts[0].trim())?;
1272        let value = eq_parts[1].trim().parse::<i16>().map_err(|_| {
1273            crate::Error::Protocol(format!(
1274                "Invalid enum value: {}",
1275                eq_parts[1]
1276            ))
1277        })?;
1278
1279        items.push(EnumItem { name, value });
1280    }
1281
1282    Ok(items)
1283}
1284
1285impl PartialEq for Type {
1286    fn eq(&self, other: &Self) -> bool {
1287        match (self, other) {
1288            (Type::Simple(a), Type::Simple(b)) => a == b,
1289            (Type::FixedString { size: a }, Type::FixedString { size: b }) => {
1290                a == b
1291            }
1292            (
1293                Type::DateTime { timezone: tz_a },
1294                Type::DateTime { timezone: tz_b },
1295            ) => tz_a == tz_b,
1296            (
1297                Type::DateTime64 { precision: p_a, timezone: tz_a },
1298                Type::DateTime64 { precision: p_b, timezone: tz_b },
1299            ) => p_a == p_b && tz_a == tz_b,
1300            (
1301                Type::Decimal { precision: p_a, scale: s_a },
1302                Type::Decimal { precision: p_b, scale: s_b },
1303            ) => p_a == p_b && s_a == s_b,
1304            (Type::Enum8 { items: a }, Type::Enum8 { items: b }) => a == b,
1305            (Type::Enum16 { items: a }, Type::Enum16 { items: b }) => a == b,
1306            (Type::Array { item_type: a }, Type::Array { item_type: b }) => {
1307                a == b
1308            }
1309            (
1310                Type::Nullable { nested_type: a },
1311                Type::Nullable { nested_type: b },
1312            ) => a == b,
1313            (Type::Tuple { item_types: a }, Type::Tuple { item_types: b }) => {
1314                a == b
1315            }
1316            (
1317                Type::LowCardinality { nested_type: a },
1318                Type::LowCardinality { nested_type: b },
1319            ) => a == b,
1320            (
1321                Type::Map { key_type: k_a, value_type: v_a },
1322                Type::Map { key_type: k_b, value_type: v_b },
1323            ) => k_a == k_b && v_a == v_b,
1324            _ => false,
1325        }
1326    }
1327}
1328
1329impl Eq for Type {}
1330
1331/// Reference-counted shared pointer to a [`Type`].
1332pub type TypeRef = Arc<Type>;
1333
1334fn format_enum_items(items: &[EnumItem]) -> String {
1335    let formatted: Vec<String> = items
1336        .iter()
1337        .map(|item| format!("'{}' = {}", item.name, item.value))
1338        .collect();
1339    formatted.join(", ")
1340}
1341
1342#[cfg(test)]
1343#[cfg_attr(coverage_nightly, coverage(off))]
1344mod tests {
1345    use super::*;
1346
1347    #[test]
1348    fn test_type_code_name() {
1349        assert_eq!(TypeCode::Int32.name(), "Int32");
1350        assert_eq!(TypeCode::String.name(), "String");
1351        assert_eq!(TypeCode::DateTime.name(), "DateTime");
1352    }
1353
1354    #[test]
1355    fn test_simple_type_name() {
1356        assert_eq!(Type::int32().name(), "Int32");
1357        assert_eq!(Type::uint64().name(), "UInt64");
1358        assert_eq!(Type::string().name(), "String");
1359    }
1360
1361    #[test]
1362    fn test_fixed_string_type() {
1363        let t = Type::fixed_string(10);
1364        assert_eq!(t.code(), TypeCode::FixedString);
1365        assert_eq!(t.name(), "FixedString(10)");
1366    }
1367
1368    #[test]
1369    fn test_array_type() {
1370        let t = Type::array(Type::int32());
1371        assert_eq!(t.code(), TypeCode::Array);
1372        assert_eq!(t.name(), "Array(Int32)");
1373    }
1374
1375    #[test]
1376    fn test_nullable_type() {
1377        let t = Type::nullable(Type::string());
1378        assert_eq!(t.code(), TypeCode::Nullable);
1379        assert_eq!(t.name(), "Nullable(String)");
1380    }
1381
1382    #[test]
1383    fn test_tuple_type() {
1384        let t = Type::tuple(vec![Type::int32(), Type::string()]);
1385        assert_eq!(t.code(), TypeCode::Tuple);
1386        assert_eq!(t.name(), "Tuple(Int32, String)");
1387    }
1388
1389    #[test]
1390    fn test_map_type() {
1391        let t = Type::map(Type::string(), Type::int32());
1392        assert_eq!(t.code(), TypeCode::Map);
1393        assert_eq!(t.name(), "Map(String, Int32)");
1394    }
1395
1396    #[test]
1397    fn test_datetime_with_timezone() {
1398        let t = Type::datetime(Some("UTC".to_string()));
1399        assert_eq!(t.name(), "DateTime('UTC')");
1400    }
1401
1402    #[test]
1403    fn test_decimal_type() {
1404        let t = Type::decimal(10, 2);
1405        assert_eq!(t.name(), "Decimal(10, 2)");
1406    }
1407
1408    #[test]
1409    fn test_type_equality() {
1410        assert_eq!(Type::int32(), Type::int32());
1411        assert_eq!(Type::array(Type::string()), Type::array(Type::string()));
1412        assert_ne!(Type::int32(), Type::int64());
1413        assert_ne!(Type::fixed_string(10), Type::fixed_string(20));
1414    }
1415}