clickhouse_arrow/native/types/
deserialize.rs

1pub(crate) mod array;
2pub(crate) mod geo;
3pub(crate) mod low_cardinality;
4pub(crate) mod map;
5pub(crate) mod nullable;
6pub(crate) mod object;
7pub(crate) mod sized;
8pub(crate) mod string;
9pub(crate) mod tuple;
10
11use super::low_cardinality::LOW_CARDINALITY_VERSION;
12use super::*;
13use crate::io::ClickHouseBytesRead;
14
15// Core protocol parsing
16pub(crate) trait ClickHouseNativeDeserializer {
17    fn deserialize_prefix_async<'a, R: ClickHouseRead>(
18        &'a self,
19        reader: &'a mut R,
20        state: &'a mut DeserializerState,
21    ) -> impl Future<Output = Result<()>> + Send + 'a;
22
23    #[allow(dead_code)] // TODO: remove once synchronous native path is fully retired
24    fn deserialize_prefix<R: ClickHouseBytesRead>(&self, reader: &mut R) -> Result<()>;
25}
26
27impl ClickHouseNativeDeserializer for Type {
28    fn deserialize_prefix_async<'a, R: ClickHouseRead>(
29        &'a self,
30        reader: &'a mut R,
31        state: &'a mut DeserializerState,
32    ) -> impl Future<Output = Result<()>> + Send + 'a {
33        use deserialize::*;
34        async move {
35            match self {
36                Type::Int8
37                | Type::Int16
38                | Type::Int32
39                | Type::Int64
40                | Type::Int128
41                | Type::Int256
42                | Type::UInt8
43                | Type::UInt16
44                | Type::UInt32
45                | Type::UInt64
46                | Type::UInt128
47                | Type::UInt256
48                | Type::Float32
49                | Type::Float64
50                | Type::Decimal32(_)
51                | Type::Decimal64(_)
52                | Type::Decimal128(_)
53                | Type::Decimal256(_)
54                | Type::Uuid
55                | Type::Date
56                | Type::Date32
57                | Type::DateTime(_)
58                | Type::DateTime64(_, _)
59                | Type::Ipv4
60                | Type::Ipv6
61                | Type::Enum8(_)
62                | Type::Enum16(_) => {
63                    sized::SizedDeserializer::read_prefix(self, reader, state).await?;
64                }
65
66                Type::String
67                | Type::FixedSizedString(_)
68                | Type::Binary
69                | Type::FixedSizedBinary(_) => {
70                    string::StringDeserializer::read_prefix(self, reader, state).await?;
71                }
72
73                Type::Array(_) => {
74                    array::ArrayDeserializer::read_prefix(self, reader, state).await?;
75                }
76                Type::Tuple(_) => {
77                    tuple::TupleDeserializer::read_prefix(self, reader, state).await?;
78                }
79                Type::Point => geo::PointDeserializer::read_prefix(self, reader, state).await?,
80                Type::Ring => geo::RingDeserializer::read_prefix(self, reader, state).await?,
81                Type::Polygon => geo::PolygonDeserializer::read_prefix(self, reader, state).await?,
82                Type::MultiPolygon => {
83                    geo::MultiPolygonDeserializer::read_prefix(self, reader, state).await?;
84                }
85                Type::Nullable(_) => {
86                    nullable::NullableDeserializer::read_prefix(self, reader, state).await?;
87                }
88                Type::Map(_, _) => map::MapDeserializer::read_prefix(self, reader, state).await?,
89                Type::LowCardinality(_) => {
90                    low_cardinality::LowCardinalityDeserializer::read_prefix(self, reader, state)
91                        .await?;
92                }
93                Type::Object => {
94                    object::ObjectDeserializer::read_prefix(self, reader, state).await?;
95                }
96            }
97            Ok(())
98        }
99        .boxed()
100    }
101
102    #[allow(dead_code)] // TODO: remove once synchronous native path is fully retired
103    fn deserialize_prefix<R: ClickHouseBytesRead>(&self, reader: &mut R) -> Result<()> {
104        match self {
105            Type::Array(inner) | Type::Nullable(inner) => inner.deserialize_prefix(reader)?,
106            Type::Point => {
107                for _ in 0..2 {
108                    Type::Float64.deserialize_prefix(reader)?;
109                }
110            }
111            Type::LowCardinality(_) => {
112                let version = reader.try_get_u64_le()?;
113                if version != LOW_CARDINALITY_VERSION {
114                    return Err(Error::DeserializeError(format!(
115                        "LowCardinality: invalid low cardinality version: {version}"
116                    )));
117                }
118            }
119            Type::Map(key, value) => {
120                let nested = super::map::normalize_map_type(key, value);
121                nested.deserialize_prefix(reader)?;
122            }
123            Type::Tuple(inner) => {
124                for inner_type in inner {
125                    inner_type.deserialize_prefix(reader)?;
126                }
127            }
128            Type::Object => {
129                let _ = reader.try_get_i8()?;
130            }
131            _ => {}
132        }
133        Ok(())
134    }
135}
136
137// ---
138// String => Type Deserialization
139// ---
140
141// For Date32: Days from 1900-01-01 to 1970-01-01
142pub(crate) const DAYS_1900_TO_1970: i32 = 25_567;
143
144trait EnumValueType: FromStr + std::fmt::Debug {}
145impl EnumValueType for i8 {}
146impl EnumValueType for i16 {}
147
148macro_rules! parse_enum_options {
149    ($opt_str:expr, $num_type:ty) => {{
150        fn inner_parse(input: &str) -> Result<Vec<(String, $num_type)>> {
151            if !input.starts_with('(') || !input.ends_with(')') {
152                return Err(Error::TypeParseError(
153                    "Enum arguments must be enclosed in parentheses".to_string(),
154                ));
155            }
156
157            let input = input[1..input.len() - 1].trim();
158            if input.is_empty() {
159                return Ok(Vec::new());
160            }
161
162            let mut options = Vec::new();
163            let mut name = String::new();
164            let mut value = String::new();
165            let mut state = EnumParseState::ExpectQuote;
166            let mut escaped = false;
167
168            for ch in input.chars() {
169                match state {
170                    EnumParseState::ExpectQuote => {
171                        if ch == '\'' {
172                            state = EnumParseState::InName;
173                        } else if !ch.is_whitespace() {
174                            return Err(Error::TypeParseError(format!(
175                                "Expected single quote at start of variant name, found '{}'",
176                                ch
177                            )));
178                        }
179                    }
180                    EnumParseState::InName => {
181                        if escaped {
182                            name.push(ch);
183                            escaped = false;
184                        } else if ch == '\\' {
185                            escaped = true;
186                        } else if ch == '\'' {
187                            state = EnumParseState::ExpectEqual;
188                        } else {
189                            name.push(ch);
190                        }
191                    }
192                    EnumParseState::ExpectEqual => {
193                        if ch == '=' {
194                            state = EnumParseState::InValue;
195                        } else if !ch.is_whitespace() {
196                            return Err(Error::TypeParseError(format!(
197                                "Expected '=' after variant name, found '{}'",
198                                ch
199                            )));
200                        }
201                    }
202                    EnumParseState::InValue => {
203                        if ch == ',' {
204                            let parsed_value = value.parse::<$num_type>().map_err(|e| {
205                                Error::TypeParseError(format!("Invalid enum value '{value}': {e}"))
206                            })?;
207                            options.push((name, parsed_value));
208                            name = String::new();
209                            value = String::new();
210                            state = EnumParseState::ExpectQuote;
211                        } else if !ch.is_whitespace() {
212                            value.push(ch);
213                        }
214                    }
215                }
216            }
217
218            match state {
219                EnumParseState::InValue if !value.is_empty() => {
220                    let parsed_value = value.parse::<$num_type>().map_err(|e| {
221                        Error::TypeParseError(format!("Invalid enum value '{value}': {e}"))
222                    })?;
223                    options.push((name, parsed_value));
224                }
225                EnumParseState::ExpectQuote if !input.is_empty() => {
226                    return Err(Error::TypeParseError(
227                        "Expected enum variant, found end of input".to_string(),
228                    ));
229                }
230                EnumParseState::InName | EnumParseState::ExpectEqual => {
231                    return Err(Error::TypeParseError(
232                        "Incomplete enum variant at end of input".to_string(),
233                    ));
234                }
235                _ => {}
236            }
237
238            if input.ends_with(',') {
239                return Err(Error::TypeParseError("Trailing comma in enum variants".to_string()));
240            }
241
242            Ok(options)
243        }
244
245        fn assert_numeric_type<T: EnumValueType>() {}
246        assert_numeric_type::<$num_type>();
247        inner_parse($opt_str)
248    }};
249}
250
251#[derive(PartialEq)]
252enum EnumParseState {
253    ExpectQuote,
254    InName,
255    ExpectEqual,
256    InValue,
257}
258
259impl FromStr for Type {
260    type Err = Error;
261
262    #[expect(clippy::too_many_lines)]
263    fn from_str(s: &str) -> Result<Self> {
264        let (ident, following) = eat_identifier(s);
265
266        if ident.is_empty() {
267            return Err(Error::TypeParseError(format!("invalid empty identifier for type: '{s}'")));
268        }
269
270        let following = following.trim();
271        if !following.is_empty() {
272            return Ok(match ident {
273                "Decimal" => {
274                    let (args, count) = parse_fixed_args::<2>(following)?;
275                    if count != 2 {
276                        return Err(Error::TypeParseError(format!(
277                            "Decimal expects 2 args, got {count}: {args:?}"
278                        )));
279                    }
280                    let p: usize = parse_precision(args[0])?;
281                    let s: usize = parse_scale(args[1])?;
282                    if s == 0
283                        || (p <= 9 && s > 9)
284                        || (p <= 18 && s > 18)
285                        || (p <= 38 && s > 38)
286                        || (p <= 76 && s > 76)
287                    {
288                        return Err(Error::TypeParseError(format!(
289                            "Invalid scale {s} for precision {p}"
290                        )));
291                    }
292                    if p <= 9 {
293                        Type::Decimal32(s)
294                    } else if p <= 18 {
295                        Type::Decimal64(s)
296                    } else if p <= 38 {
297                        Type::Decimal128(s)
298                    } else if p <= 76 {
299                        Type::Decimal256(s)
300                    } else {
301                        return Err(Error::TypeParseError(
302                            "bad decimal spec, cannot exceed 76 precision".to_string(),
303                        ));
304                    }
305                }
306                "Decimal32" => {
307                    let (args, count) = parse_fixed_args::<1>(following)?;
308                    if count != 1 {
309                        return Err(Error::TypeParseError(format!(
310                            "bad arg count for Decimal32, expected 1 and got {count}: {args:?}"
311                        )));
312                    }
313                    let s: usize = parse_scale(args[0])?;
314                    if s == 0 || s > 9 {
315                        return Err(Error::TypeParseError(format!(
316                            "Invalid scale {s} for Decimal32, must be 1..=9"
317                        )));
318                    }
319                    Type::Decimal32(s)
320                }
321                "Decimal64" => {
322                    let (args, count) = parse_fixed_args::<1>(following)?;
323                    if count != 1 {
324                        return Err(Error::TypeParseError(format!(
325                            "bad arg count for Decimal64, expected 1 and got {count}: {args:?}"
326                        )));
327                    }
328                    let s: usize = parse_scale(args[0])?;
329                    if s == 0 || s > 18 {
330                        return Err(Error::TypeParseError(format!(
331                            "Invalid scale {s} for Decimal64, must be 1..=18"
332                        )));
333                    }
334                    Type::Decimal64(s)
335                }
336                "Decimal128" => {
337                    let (args, count) = parse_fixed_args::<1>(following)?;
338                    if count != 1 {
339                        return Err(Error::TypeParseError(format!(
340                            "bad arg count for Decimal128, expected 1 and got {count}: {args:?}"
341                        )));
342                    }
343                    let s: usize = parse_scale(args[0])?;
344                    if s == 0 || s > 38 {
345                        return Err(Error::TypeParseError(format!(
346                            "Invalid scale {s} for Decimal128, must be 1..=38"
347                        )));
348                    }
349                    Type::Decimal128(s)
350                }
351                "Decimal256" => {
352                    let (args, count) = parse_fixed_args::<1>(following)?;
353                    if count != 1 {
354                        return Err(Error::TypeParseError(format!(
355                            "bad arg count for Decimal256, expected 1 and got {count}: {args:?}"
356                        )));
357                    }
358                    let s: usize = parse_scale(args[0])?;
359                    if s == 0 || s > 76 {
360                        return Err(Error::TypeParseError(format!(
361                            "Invalid scale {s} for Decimal256, must be 1..=76"
362                        )));
363                    }
364                    Type::Decimal256(s)
365                }
366                "FixedString" => {
367                    let (args, count) = parse_fixed_args::<1>(following)?;
368                    if count != 1 {
369                        return Err(Error::TypeParseError(format!(
370                            "bad arg count for FixedString, expected 1 and got {count}: {args:?}"
371                        )));
372                    }
373                    let s: usize = parse_scale(args[0])?;
374                    if s == 0 {
375                        return Err(Error::TypeParseError(
376                            "FixedString size must be greater than 0".to_string(),
377                        ));
378                    }
379                    Type::FixedSizedString(s)
380                }
381                "DateTime" => {
382                    let (args, count) = parse_fixed_args::<1>(following)?;
383                    if count > 1 {
384                        return Err(Error::TypeParseError(format!(
385                            "DateTime expects 0 or 1 arg: {args:?}"
386                        )));
387                    }
388                    if count == 0 {
389                        Type::DateTime(chrono_tz::UTC)
390                    } else {
391                        let tz_str = args[0];
392                        if !tz_str.starts_with('\'') || !tz_str.ends_with('\'') {
393                            return Err(Error::TypeParseError(format!(
394                                "DateTime timezone must be quoted: '{tz_str}'"
395                            )));
396                        }
397                        let tz = tz_str[1..tz_str.len() - 1].parse().map_err(|e| {
398                            Error::TypeParseError(format!(
399                                "failed to parse timezone '{tz_str}': {e}"
400                            ))
401                        })?;
402                        Type::DateTime(tz)
403                    }
404                }
405                "DateTime64" => {
406                    let (args, count) = parse_fixed_args::<2>(following)?;
407                    if !(1..=2).contains(&count) {
408                        return Err(Error::TypeParseError(format!(
409                            "DateTime64 expects 1 or 2 args, got {count}: {args:?}"
410                        )));
411                    }
412                    let precision = parse_precision(args[0])?;
413                    let tz = if count == 2 {
414                        let tz_str = args[1];
415                        if !tz_str.starts_with('\'') || !tz_str.ends_with('\'') {
416                            return Err(Error::TypeParseError(format!(
417                                "DateTime64 timezone must be quoted: '{tz_str}'"
418                            )));
419                        }
420                        tz_str[1..tz_str.len() - 1].parse().map_err(|e| {
421                            Error::TypeParseError(format!(
422                                "failed to parse timezone '{tz_str}': {e}"
423                            ))
424                        })?
425                    } else {
426                        chrono_tz::UTC
427                    };
428                    Type::DateTime64(precision, tz)
429                }
430                "Enum8" => Type::Enum8(parse_enum_options!(following, i8)?),
431                "Enum16" => Type::Enum16(parse_enum_options!(following, i16)?),
432                "LowCardinality" => {
433                    let (args, count) = parse_fixed_args::<1>(following)?;
434                    if count != 1 {
435                        return Err(Error::TypeParseError(format!(
436                            "LowCardinality expected 1 arg and got {count}: {args:?}"
437                        )));
438                    }
439                    Type::LowCardinality(Box::new(Type::from_str(args[0])?))
440                }
441                "Array" => {
442                    let (args, count) = parse_fixed_args::<1>(following)?;
443                    if count != 1 {
444                        return Err(Error::TypeParseError(format!(
445                            "Array expected 1 arg and got {count}: {args:?}"
446                        )));
447                    }
448                    Type::Array(Box::new(Type::from_str(args[0])?))
449                }
450                "Tuple" => {
451                    let args = parse_variable_args(following)?;
452                    let inner: Vec<Type> =
453                        args.into_iter().map(Type::from_str).collect::<Result<_, _>>()?;
454                    Type::Tuple(inner)
455                }
456                "Nullable" => {
457                    let (args, count) = parse_fixed_args::<1>(following)?;
458                    if count != 1 {
459                        return Err(Error::TypeParseError(format!(
460                            "Nullable expects 1 arg: {args:?}"
461                        )));
462                    }
463                    Type::Nullable(Box::new(Type::from_str(args[0])?))
464                }
465                "Map" => {
466                    let (args, count) = parse_fixed_args::<2>(following)?;
467                    if count != 2 {
468                        return Err(Error::TypeParseError(format!(
469                            "Map expects 2 args, got {count}: {args:?}"
470                        )));
471                    }
472                    Type::Map(
473                        Box::new(Type::from_str(args[0])?),
474                        Box::new(Type::from_str(args[1])?),
475                    )
476                }
477                // Unsupported
478                "Nested" => {
479                    return Err(Error::TypeParseError("unsupported Nested type".to_string()));
480                }
481                id => {
482                    return Err(Error::TypeParseError(format!(
483                        "invalid type with arguments: '{ident}' (ident = {id})"
484                    )));
485                }
486            });
487        }
488        Ok(match ident {
489            "Int8" => Type::Int8,
490            "Int16" => Type::Int16,
491            "Int32" => Type::Int32,
492            "Int64" => Type::Int64,
493            "Int128" => Type::Int128,
494            "Int256" => Type::Int256,
495            "Bool" | "UInt8" => Type::UInt8,
496            "UInt16" => Type::UInt16,
497            "UInt32" => Type::UInt32,
498            "UInt64" => Type::UInt64,
499            "UInt128" => Type::UInt128,
500            "UInt256" => Type::UInt256,
501            "Float32" => Type::Float32,
502            "Float64" => Type::Float64,
503            "String" => Type::String,
504            "UUID" | "Uuid" | "uuid" => Type::Uuid,
505            "Date" => Type::Date,
506            "Date32" => Type::Date32,
507            // TODO: This is duplicated above. Verify if this is needed, for example if ClickHouse
508            // ever sends `DateTime` without tz.
509            "DateTime" => Type::DateTime(chrono_tz::UTC),
510            "IPv4" => Type::Ipv4,
511            "IPv6" => Type::Ipv6,
512            "Point" => Type::Point,
513            "Ring" => Type::Ring,
514            "Polygon" => Type::Polygon,
515            "MultiPolygon" => Type::MultiPolygon,
516            "Object" | "Json" | "OBJECT" | "JSON" => Type::Object,
517            _ => {
518                return Err(Error::TypeParseError(format!("invalid type name: '{ident}'")));
519            }
520        })
521    }
522}
523
524// Assumed complete identifier normalization and type resolution from clickhouse
525fn eat_identifier(input: &str) -> (&str, &str) {
526    for (i, c) in input.char_indices() {
527        if c.is_alphabetic() || c == '_' || c == '$' || (i > 0 && c.is_numeric()) {
528            continue;
529        }
530        return (&input[..i], &input[i..]);
531    }
532    (input, "")
533}
534
535/// Parse arguments into a fixed-size array for types with a known number of args
536fn parse_fixed_args<const N: usize>(input: &str) -> Result<([&str; N], usize)> {
537    let mut iter = parse_args_iter(input)?;
538    let mut out = [""; N];
539    let mut count = 0;
540
541    // Take up to N items
542    for (i, arg_result) in iter.by_ref().take(N).enumerate() {
543        out[i] = arg_result?;
544        count += 1;
545    }
546
547    // Check for excess arguments
548    if iter.next().is_some() {
549        return Err(Error::TypeParseError("too many arguments".to_string()));
550    }
551    Ok((out, count))
552}
553
554/// Parse arguments into a Vec for types with variable numbers of args
555fn parse_variable_args(input: &str) -> Result<Vec<&str>> { parse_args_iter(input)?.collect() }
556
557fn parse_scale(from: &str) -> Result<usize> {
558    from.parse().map_err(|_| Error::TypeParseError("couldn't parse scale".to_string()))
559}
560
561fn parse_precision(from: &str) -> Result<usize> {
562    from.parse().map_err(|_| Error::TypeParseError("could not parse precision".to_string()))
563}
564
565/// Core iterator for parsing comma-separated arguments within parentheses
566fn parse_args_iter(input: &str) -> Result<impl Iterator<Item = Result<&str, Error>>> {
567    if !input.starts_with('(') || !input.ends_with(')') {
568        return Err(Error::TypeParseError("Malformed arguments to type".to_string()));
569    }
570    let input = input[1..input.len() - 1].trim();
571    if input.ends_with(',') {
572        return Err(Error::TypeParseError("Trailing comma in argument list".to_string()));
573    }
574
575    Ok(ArgsIterator { input, last_start: 0, in_parens: 0, in_quotes: false, done: false })
576}
577
578struct ArgsIterator<'a> {
579    input:      &'a str,
580    last_start: usize,
581    in_parens:  usize,
582    in_quotes:  bool,
583    done:       bool,
584}
585
586impl<'a> Iterator for ArgsIterator<'a> {
587    type Item = Result<&'a str, Error>;
588
589    #[allow(unused_assignments)]
590    fn next(&mut self) -> Option<Self::Item> {
591        if self.done {
592            return None;
593        }
594
595        let start = self.last_start;
596        let mut i = start;
597        let chars = self.input[start..].char_indices();
598        let mut escaped = false;
599
600        for (offset, c) in chars {
601            i = start + offset;
602            if self.in_quotes {
603                if c == '\\' {
604                    escaped = true;
605                    continue;
606                }
607                if c == '\'' && !escaped {
608                    self.in_quotes = false;
609                }
610                escaped = false;
611                continue;
612            }
613            match c {
614                '\'' if !escaped => {
615                    self.in_quotes = true;
616                }
617                '(' => self.in_parens += 1,
618                ')' => self.in_parens -= 1,
619                ',' if self.in_parens == 0 => {
620                    let slice = self.input[self.last_start..i].trim();
621                    if slice.is_empty() {
622                        return Some(Err(Error::TypeParseError(
623                            "Empty argument in list".to_string(),
624                        )));
625                    }
626                    self.last_start = i + 1;
627                    return Some(Ok(slice));
628                }
629                _ => {}
630            }
631            escaped = false;
632        }
633
634        if self.in_parens != 0 {
635            self.done = true;
636            return Some(Err(Error::TypeParseError("Mismatched parentheses".to_string())));
637        }
638        if self.last_start <= self.input.len() {
639            let slice = self.input[self.last_start..].trim();
640            if slice.is_empty() {
641                self.done = true;
642                return None; // Allow empty input after last comma
643            }
644            if slice == "," {
645                self.done = true;
646                return Some(Err(Error::TypeParseError(
647                    "Trailing comma in argument list".to_string(),
648                )));
649            }
650            self.done = true;
651            return Some(Ok(slice));
652        }
653
654        self.done = true;
655        None
656    }
657}
658
659#[cfg(test)]
660mod tests {
661    use std::str::FromStr;
662
663    use super::*;
664    /// Tests `eat_identifier` for splitting type names and arguments.
665    #[test]
666    fn test_eat_identifier() {
667        assert_eq!(eat_identifier("Int8"), ("Int8", ""));
668        assert_eq!(eat_identifier("Enum8('a'=1)"), ("Enum8", "('a'=1)"));
669        assert_eq!(eat_identifier("DateTime('UTC')"), ("DateTime", "('UTC')"));
670        assert_eq!(eat_identifier("Map(String,Int32)"), ("Map", "(String,Int32)"));
671        assert_eq!(eat_identifier(""), ("", ""));
672        assert_eq!(eat_identifier("Invalid Type"), ("Invalid", " Type"));
673    }
674
675    /// Tests `parse_fixed_args` for fixed-size argument lists.
676    #[test]
677    fn test_parse_fixed_args() {
678        let (args, count) = parse_fixed_args::<2>("(UInt32, String)").unwrap();
679        assert_eq!(count, 2);
680        assert_eq!(args[..count], ["UInt32", "String"]);
681
682        let (args, count) = parse_fixed_args::<1>("(String)").unwrap();
683        assert_eq!(count, 1);
684        assert_eq!(args[..count], ["String"]);
685
686        let (args, count) = parse_fixed_args::<2>("(3, 'UTC')").unwrap();
687        assert_eq!(count, 2);
688        assert_eq!(args[..count], ["3", "'UTC'"]);
689
690        assert!(parse_fixed_args::<1>("(UInt32, String)").is_err()); // Too many args
691        assert!(parse_fixed_args::<1>("(()").is_err()); // Mismatched parens
692        assert!(parse_fixed_args::<1>("(String,)").is_err()); // Trailing comma
693    }
694
695    /// Tests `parse_variable_args` for variable-size argument lists.
696    #[test]
697    fn test_parse_variable_args() {
698        let args = parse_variable_args("(Int8, String, Float64)").unwrap();
699        assert_eq!(args, vec!["Int8", "String", "Float64"]);
700
701        let args = parse_variable_args("(3, 'UTC', 'extra')").unwrap();
702        assert_eq!(args, vec!["3", "'UTC'", "'extra'"]);
703
704        let args = parse_variable_args("(())").unwrap();
705        assert_eq!(args, vec!["()"]);
706
707        let args = parse_variable_args("()").unwrap();
708        assert_eq!(args, Vec::<&str>::new());
709
710        assert!(parse_variable_args("(()").is_err()); // Mismatched parens
711        assert!(parse_variable_args("(String,)").is_err()); // Trailing comma
712    }
713
714    /// Tests `Type::from_str` for primitive types.
715    #[test]
716    fn test_from_str_primitives() {
717        assert_eq!(Type::from_str("Int8").unwrap(), Type::Int8);
718        assert_eq!(Type::from_str("UInt8").unwrap(), Type::UInt8);
719        assert_eq!(Type::from_str("Bool").unwrap(), Type::UInt8); // Bool alias
720        assert_eq!(Type::from_str("Float64").unwrap(), Type::Float64);
721        assert_eq!(Type::from_str("String").unwrap(), Type::String);
722        assert_eq!(Type::from_str("UUID").unwrap(), Type::Uuid);
723        assert_eq!(Type::from_str("Date").unwrap(), Type::Date);
724        assert_eq!(Type::from_str("IPv4").unwrap(), Type::Ipv4);
725        assert_eq!(Type::from_str("IPv6").unwrap(), Type::Ipv6);
726    }
727
728    /// Tests `Type::from_str` for decimal types.
729    #[test]
730    fn test_from_str_decimals() {
731        assert_eq!(Type::from_str("Decimal32(2)").unwrap(), Type::Decimal32(2));
732        assert_eq!(Type::from_str("Decimal64(4)").unwrap(), Type::Decimal64(4));
733        assert_eq!(Type::from_str("Decimal128(6)").unwrap(), Type::Decimal128(6));
734        assert_eq!(Type::from_str("Decimal256(8)").unwrap(), Type::Decimal256(8));
735        assert_eq!(Type::from_str("Decimal(9, 2)").unwrap(), Type::Decimal32(2));
736        assert_eq!(Type::from_str("Decimal(18, 4)").unwrap(), Type::Decimal64(4));
737        assert_eq!(Type::from_str("Decimal(38, 6)").unwrap(), Type::Decimal128(6));
738        assert_eq!(Type::from_str("Decimal(76, 8)").unwrap(), Type::Decimal256(8));
739
740        assert!(Type::from_str("Decimal32(0)").is_err()); // Invalid scale
741        assert!(Type::from_str("Decimal(77, 8)").is_err()); // Precision too large
742        assert!(Type::from_str("Decimal(9)").is_err()); // Missing scale
743    }
744
745    /// Tests `Type::from_str` for string and binary types.
746    #[test]
747    fn test_from_str_strings() {
748        assert_eq!(Type::from_str("String").unwrap(), Type::String);
749        assert_eq!(Type::from_str("FixedString(4)").unwrap(), Type::FixedSizedString(4));
750        assert!(Type::from_str("FixedString(0)").is_err()); // Invalid size
751        assert!(Type::from_str("FixedString(a)").is_err()); // Invalid size
752    }
753
754    /// Tests `Type::from_str` for date and time types.
755    #[test]
756    fn test_from_str_datetime() {
757        assert_eq!(Type::from_str("DateTime").unwrap(), Type::DateTime(chrono_tz::UTC));
758        assert_eq!(Type::from_str("DateTime('UTC')").unwrap(), Type::DateTime(chrono_tz::UTC));
759        assert_eq!(
760            Type::from_str("DateTime('America/New_York')").unwrap(),
761            Type::DateTime(chrono_tz::America::New_York)
762        );
763        assert!(Type::from_str("DateTime('UTC', 'extra')").is_err()); // Too many args
764        assert!(Type::from_str("DateTime(UTC)").is_err()); // Unquoted timezone
765
766        assert_eq!(Type::from_str("DateTime64(3)").unwrap(), Type::DateTime64(3, chrono_tz::UTC));
767        assert_eq!(
768            Type::from_str("DateTime64(6, 'UTC')").unwrap(),
769            Type::DateTime64(6, chrono_tz::UTC)
770        );
771        assert_eq!(
772            Type::from_str("DateTime64(3, 'America/New_York')").unwrap(),
773            Type::DateTime64(3, chrono_tz::America::New_York)
774        );
775        assert!(Type::from_str("DateTime64()").is_err()); // Too few args
776        assert!(Type::from_str("DateTime64(3, 'UTC', 'extra')").is_err()); // Too many args
777        assert!(Type::from_str("DateTime64(3, UTC)").is_err()); // Unquoted timezone
778    }
779
780    /// Tests `Type::from_str` for Enum8 with explicit indices.
781    #[test]
782    fn test_from_str_enum8_explicit() {
783        let enum8 = Type::from_str("Enum8('active' = 1, 'inactive' = 2)").unwrap();
784        assert_eq!(enum8, Type::Enum8(vec![("active".into(), 1), ("inactive".into(), 2)]));
785
786        let single = Type::from_str("Enum8('test' = -1)").unwrap();
787        assert_eq!(single, Type::Enum8(vec![("test".into(), -1)]));
788
789        let negative = Type::from_str("Enum8('neg' = -128, 'zero' = 0)").unwrap();
790        assert_eq!(negative, Type::Enum8(vec![("neg".into(), -128), ("zero".into(), 0)]));
791    }
792
793    /// Tests `Type::from_str` for Enum8 with empty variants.
794    #[test]
795    fn test_from_str_enum8_empty() {
796        let empty = Type::from_str("Enum8()").unwrap();
797        assert_eq!(empty, Type::Enum8(vec![]));
798    }
799
800    /// Tests `Type::from_str` for Enum16 with explicit indices.
801    #[test]
802    fn test_from_str_enum16_explicit() {
803        let enum16 = Type::from_str("Enum16('high' = 1000, 'low' = -1000)").unwrap();
804        assert_eq!(enum16, Type::Enum16(vec![("high".into(), 1000), ("low".into(), -1000)]));
805
806        let single = Type::from_str("Enum16('test' = 0)").unwrap();
807        assert_eq!(single, Type::Enum16(vec![("test".into(), 0)]));
808    }
809
810    /// Tests `Type::from_str` error cases for Enum8.
811    #[test]
812    fn test_from_str_enum8_errors() {
813        assert!(Type::from_str("Enum8('a' = 1, 2)").is_err()); // Lone value
814        assert!(Type::from_str("Enum8('a' = x)").is_err()); // Invalid value
815        assert!(Type::from_str("Enum8(a = 1)").is_err()); // Unquoted name
816        assert!(Type::from_str("Enum8('a' = 1, )").is_err()); // Trailing comma
817        assert!(Type::from_str("Enum8('a' = 1").is_err()); // Unclosed paren
818    }
819
820    /// Tests `Type::from_str` error cases for Enum16.
821    #[test]
822    fn test_from_str_enum16_errors() {
823        assert!(Type::from_str("Enum16('a' = 1, 2)").is_err()); // Lone value
824        assert!(Type::from_str("Enum16('a' = x)").is_err()); // Invalid value
825        assert!(Type::from_str("Enum16(a = 1)").is_err()); // Unquoted name
826        assert!(Type::from_str("Enum16('a' = 1, )").is_err()); // Trailing comma
827        assert!(Type::from_str("Enum16('a' = 1").is_err()); // Unclosed paren
828    }
829
830    /// Tests `Type::from_str` for complex types.
831    #[test]
832    fn test_from_str_complex_types() {
833        assert_eq!(
834            Type::from_str("LowCardinality(String)").unwrap(),
835            Type::LowCardinality(Box::new(Type::String))
836        );
837        assert_eq!(Type::from_str("Array(Int32)").unwrap(), Type::Array(Box::new(Type::Int32)));
838        assert_eq!(
839            Type::from_str("Tuple(Int32, String)").unwrap(),
840            Type::Tuple(vec![Type::Int32, Type::String])
841        );
842        assert_eq!(
843            Type::from_str("Nullable(Int32)").unwrap(),
844            Type::Nullable(Box::new(Type::Int32))
845        );
846        assert_eq!(
847            Type::from_str("Map(String, Int32)").unwrap(),
848            Type::Map(Box::new(Type::String), Box::new(Type::Int32))
849        );
850        assert_eq!(Type::from_str("JSON").unwrap(), Type::Object);
851        assert_eq!(Type::from_str("Object").unwrap(), Type::Object);
852
853        assert!(Type::from_str("LowCardinality()").is_err()); // Missing arg
854        assert!(Type::from_str("Array(Int32, String)").is_err()); // Too many args
855        assert!(Type::from_str("Map(String)").is_err()); // Missing value type
856    }
857
858    /// Tests round-trip `to_string` and `from_str` for all types.
859    #[test]
860    fn test_round_trip_type_strings() {
861        let special_types = vec![
862            (Type::Binary, Type::String),
863            (Type::FixedSizedBinary(8), Type::FixedSizedString(8)),
864        ];
865
866        let types = vec![
867            Type::Int8,
868            Type::UInt8,
869            Type::Float64,
870            Type::String,
871            Type::FixedSizedString(4),
872            Type::Uuid,
873            Type::Date,
874            Type::Date32,
875            Type::DateTime(Tz::UTC),
876            Type::DateTime64(3, Tz::America__New_York),
877            Type::Ipv4,
878            Type::Ipv6,
879            Type::Decimal32(2),
880            Type::Enum8(vec![("active".into(), 1), ("inactive".into(), 2)]),
881            Type::Enum16(vec![("high".into(), 1000)]),
882            Type::LowCardinality(Box::new(Type::String)),
883            Type::Array(Box::new(Type::Int32)),
884            Type::Tuple(vec![Type::Int32, Type::String]),
885            Type::Nullable(Box::new(Type::Int32)),
886            Type::Map(Box::new(Type::String), Box::new(Type::Int32)),
887            Type::Object,
888        ];
889
890        for ty in types {
891            let type_str = ty.to_string();
892            let parsed = Type::from_str(&type_str)
893                .unwrap_or_else(|e| panic!("Failed to parse '{type_str}' for type {ty:?}: {e}"));
894            assert_eq!(
895                parsed, ty,
896                "Round-trip failed for type {ty:?}: expected {ty}, got {parsed}"
897            );
898        }
899
900        for (ty, mapped_ty) in special_types {
901            let type_str = ty.to_string();
902            let parsed = Type::from_str(&type_str)
903                .unwrap_or_else(|e| panic!("Failed to parse '{type_str}' for type {ty:?}: {e}"));
904            assert_eq!(
905                parsed, mapped_ty,
906                "Round-trip failed for type {ty:?}: expected {mapped_ty}, got {parsed}"
907            );
908        }
909    }
910
911    /// Tests error cases for general type parsing.
912    #[test]
913    fn test_from_str_general_errors() {
914        assert!(Type::from_str("").is_err()); // Empty input
915        assert!(Type::from_str("InvalidType").is_err()); // Unknown type
916        assert!(Type::from_str("Nested(String)").is_err()); // Unsupported Nested
917        assert!(Type::from_str("Int8(").is_err()); // Unclosed paren
918        assert!(Type::from_str("Tuple(String,)").is_err()); // Trailing comma
919    }
920}