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