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> = args
453                        .into_iter()
454                        // Handle named tuple fields: "name Type" -> extract just "Type"
455                        .map(strip_tuple_field_name)
456                        .map(Type::from_str)
457                        .collect::<Result<_, _>>()?;
458                    Type::Tuple(inner)
459                }
460                "Nullable" => {
461                    let (args, count) = parse_fixed_args::<1>(following)?;
462                    if count != 1 {
463                        return Err(Error::TypeParseError(format!(
464                            "Nullable expects 1 arg: {args:?}"
465                        )));
466                    }
467                    Type::Nullable(Box::new(Type::from_str(args[0])?))
468                }
469                "Map" => {
470                    let (args, count) = parse_fixed_args::<2>(following)?;
471                    if count != 2 {
472                        return Err(Error::TypeParseError(format!(
473                            "Map expects 2 args, got {count}: {args:?}"
474                        )));
475                    }
476                    Type::Map(
477                        Box::new(Type::from_str(args[0])?),
478                        Box::new(Type::from_str(args[1])?),
479                    )
480                }
481                // Unsupported
482                "Nested" => {
483                    return Err(Error::TypeParseError("unsupported Nested type".to_string()));
484                }
485                id => {
486                    return Err(Error::TypeParseError(format!(
487                        "invalid type with arguments: '{ident}' (ident = {id})"
488                    )));
489                }
490            });
491        }
492        Ok(match ident {
493            "Int8" => Type::Int8,
494            "Int16" => Type::Int16,
495            "Int32" => Type::Int32,
496            "Int64" => Type::Int64,
497            "Int128" => Type::Int128,
498            "Int256" => Type::Int256,
499            "Bool" | "UInt8" => Type::UInt8,
500            "UInt16" => Type::UInt16,
501            "UInt32" => Type::UInt32,
502            "UInt64" => Type::UInt64,
503            "UInt128" => Type::UInt128,
504            "UInt256" => Type::UInt256,
505            "Float32" => Type::Float32,
506            "Float64" => Type::Float64,
507            "String" => Type::String,
508            "UUID" | "Uuid" | "uuid" => Type::Uuid,
509            "Date" => Type::Date,
510            "Date32" => Type::Date32,
511            // TODO: This is duplicated above. Verify if this is needed, for example if ClickHouse
512            // ever sends `DateTime` without tz.
513            "DateTime" => Type::DateTime(chrono_tz::UTC),
514            "IPv4" => Type::Ipv4,
515            "IPv6" => Type::Ipv6,
516            "Point" => Type::Point,
517            "Ring" => Type::Ring,
518            "Polygon" => Type::Polygon,
519            "MultiPolygon" => Type::MultiPolygon,
520            "Object" | "Json" | "OBJECT" | "JSON" => Type::Object,
521            _ => {
522                return Err(Error::TypeParseError(format!("invalid type name: '{ident}'")));
523            }
524        })
525    }
526}
527
528// Assumed complete identifier normalization and type resolution from clickhouse
529fn eat_identifier(input: &str) -> (&str, &str) {
530    for (i, c) in input.char_indices() {
531        if c.is_alphabetic() || c == '_' || c == '$' || (i > 0 && c.is_numeric()) {
532            continue;
533        }
534        return (&input[..i], &input[i..]);
535    }
536    (input, "")
537}
538
539/// Strips the field name from a named tuple argument.
540///
541/// `ClickHouse` tuples can be named (`Tuple(name Type, ...)`) or anonymous (`Tuple(Type, ...)`).
542/// This extracts just the type portion from named fields like `"s String"` → `"String"`.
543///
544/// Important: We must not strip parts of types that contain internal spaces, like
545/// `Map(String, Int32)` where the space after the comma is part of the type itself.
546/// A field name is always a simple identifier (no parentheses) followed by a space.
547fn strip_tuple_field_name(arg: &str) -> &str {
548    let arg = arg.trim();
549    if let Some(space_idx) = arg.find(' ') {
550        let before_space = &arg[..space_idx];
551        // If the part before the space contains '(', it's a type with arguments,
552        // not a field name. E.g., "Map(String, Int32)" - the space is inside the type.
553        if before_space.contains('(') {
554            return arg;
555        }
556        let rest = arg[space_idx..].trim_start();
557        if rest.chars().next().is_some_and(char::is_alphabetic) {
558            return rest;
559        }
560    }
561    arg
562}
563
564/// Parse arguments into a fixed-size array for types with a known number of args
565fn parse_fixed_args<const N: usize>(input: &str) -> Result<([&str; N], usize)> {
566    let mut iter = parse_args_iter(input)?;
567    let mut out = [""; N];
568    let mut count = 0;
569
570    // Take up to N items
571    for (i, arg_result) in iter.by_ref().take(N).enumerate() {
572        out[i] = arg_result?;
573        count += 1;
574    }
575
576    // Check for excess arguments
577    if iter.next().is_some() {
578        return Err(Error::TypeParseError("too many arguments".to_string()));
579    }
580    Ok((out, count))
581}
582
583/// Parse arguments into a Vec for types with variable numbers of args
584fn parse_variable_args(input: &str) -> Result<Vec<&str>> { parse_args_iter(input)?.collect() }
585
586fn parse_scale(from: &str) -> Result<usize> {
587    from.parse().map_err(|_| Error::TypeParseError("couldn't parse scale".to_string()))
588}
589
590fn parse_precision(from: &str) -> Result<usize> {
591    from.parse().map_err(|_| Error::TypeParseError("could not parse precision".to_string()))
592}
593
594/// Core iterator for parsing comma-separated arguments within parentheses
595fn parse_args_iter(input: &str) -> Result<impl Iterator<Item = Result<&str, Error>>> {
596    if !input.starts_with('(') || !input.ends_with(')') {
597        return Err(Error::TypeParseError("Malformed arguments to type".to_string()));
598    }
599    let input = input[1..input.len() - 1].trim();
600    if input.ends_with(',') {
601        return Err(Error::TypeParseError("Trailing comma in argument list".to_string()));
602    }
603
604    Ok(ArgsIterator { input, last_start: 0, in_parens: 0, in_quotes: false, done: false })
605}
606
607struct ArgsIterator<'a> {
608    input:      &'a str,
609    last_start: usize,
610    in_parens:  usize,
611    in_quotes:  bool,
612    done:       bool,
613}
614
615impl<'a> Iterator for ArgsIterator<'a> {
616    type Item = Result<&'a str, Error>;
617
618    #[allow(unused_assignments)]
619    fn next(&mut self) -> Option<Self::Item> {
620        if self.done {
621            return None;
622        }
623
624        let start = self.last_start;
625        let mut i = start;
626        let chars = self.input[start..].char_indices();
627        let mut escaped = false;
628
629        for (offset, c) in chars {
630            i = start + offset;
631            if self.in_quotes {
632                if c == '\\' {
633                    escaped = true;
634                    continue;
635                }
636                if c == '\'' && !escaped {
637                    self.in_quotes = false;
638                }
639                escaped = false;
640                continue;
641            }
642            match c {
643                '\'' if !escaped => {
644                    self.in_quotes = true;
645                }
646                '(' => self.in_parens += 1,
647                ')' => self.in_parens -= 1,
648                ',' if self.in_parens == 0 => {
649                    let slice = self.input[self.last_start..i].trim();
650                    if slice.is_empty() {
651                        return Some(Err(Error::TypeParseError(
652                            "Empty argument in list".to_string(),
653                        )));
654                    }
655                    self.last_start = i + 1;
656                    return Some(Ok(slice));
657                }
658                _ => {}
659            }
660            escaped = false;
661        }
662
663        if self.in_parens != 0 {
664            self.done = true;
665            return Some(Err(Error::TypeParseError("Mismatched parentheses".to_string())));
666        }
667        if self.last_start <= self.input.len() {
668            let slice = self.input[self.last_start..].trim();
669            if slice.is_empty() {
670                self.done = true;
671                return None; // Allow empty input after last comma
672            }
673            if slice == "," {
674                self.done = true;
675                return Some(Err(Error::TypeParseError(
676                    "Trailing comma in argument list".to_string(),
677                )));
678            }
679            self.done = true;
680            return Some(Ok(slice));
681        }
682
683        self.done = true;
684        None
685    }
686}
687
688#[cfg(test)]
689mod tests {
690    use std::str::FromStr;
691
692    use super::*;
693    /// Tests `eat_identifier` for splitting type names and arguments.
694    #[test]
695    fn test_eat_identifier() {
696        assert_eq!(eat_identifier("Int8"), ("Int8", ""));
697        assert_eq!(eat_identifier("Enum8('a'=1)"), ("Enum8", "('a'=1)"));
698        assert_eq!(eat_identifier("DateTime('UTC')"), ("DateTime", "('UTC')"));
699        assert_eq!(eat_identifier("Map(String,Int32)"), ("Map", "(String,Int32)"));
700        assert_eq!(eat_identifier(""), ("", ""));
701        assert_eq!(eat_identifier("Invalid Type"), ("Invalid", " Type"));
702    }
703
704    /// Tests `parse_fixed_args` for fixed-size argument lists.
705    #[test]
706    fn test_parse_fixed_args() {
707        let (args, count) = parse_fixed_args::<2>("(UInt32, String)").unwrap();
708        assert_eq!(count, 2);
709        assert_eq!(args[..count], ["UInt32", "String"]);
710
711        let (args, count) = parse_fixed_args::<1>("(String)").unwrap();
712        assert_eq!(count, 1);
713        assert_eq!(args[..count], ["String"]);
714
715        let (args, count) = parse_fixed_args::<2>("(3, 'UTC')").unwrap();
716        assert_eq!(count, 2);
717        assert_eq!(args[..count], ["3", "'UTC'"]);
718
719        assert!(parse_fixed_args::<1>("(UInt32, String)").is_err()); // Too many args
720        assert!(parse_fixed_args::<1>("(()").is_err()); // Mismatched parens
721        assert!(parse_fixed_args::<1>("(String,)").is_err()); // Trailing comma
722    }
723
724    /// Tests `parse_variable_args` for variable-size argument lists.
725    #[test]
726    fn test_parse_variable_args() {
727        let args = parse_variable_args("(Int8, String, Float64)").unwrap();
728        assert_eq!(args, vec!["Int8", "String", "Float64"]);
729
730        let args = parse_variable_args("(3, 'UTC', 'extra')").unwrap();
731        assert_eq!(args, vec!["3", "'UTC'", "'extra'"]);
732
733        let args = parse_variable_args("(())").unwrap();
734        assert_eq!(args, vec!["()"]);
735
736        let args = parse_variable_args("()").unwrap();
737        assert_eq!(args, Vec::<&str>::new());
738
739        assert!(parse_variable_args("(()").is_err()); // Mismatched parens
740        assert!(parse_variable_args("(String,)").is_err()); // Trailing comma
741    }
742
743    /// Tests `Type::from_str` for primitive types.
744    #[test]
745    fn test_from_str_primitives() {
746        assert_eq!(Type::from_str("Int8").unwrap(), Type::Int8);
747        assert_eq!(Type::from_str("UInt8").unwrap(), Type::UInt8);
748        assert_eq!(Type::from_str("Bool").unwrap(), Type::UInt8); // Bool alias
749        assert_eq!(Type::from_str("Float64").unwrap(), Type::Float64);
750        assert_eq!(Type::from_str("String").unwrap(), Type::String);
751        assert_eq!(Type::from_str("UUID").unwrap(), Type::Uuid);
752        assert_eq!(Type::from_str("Date").unwrap(), Type::Date);
753        assert_eq!(Type::from_str("IPv4").unwrap(), Type::Ipv4);
754        assert_eq!(Type::from_str("IPv6").unwrap(), Type::Ipv6);
755    }
756
757    /// Tests `Type::from_str` for decimal types.
758    #[test]
759    fn test_from_str_decimals() {
760        assert_eq!(Type::from_str("Decimal32(2)").unwrap(), Type::Decimal32(2));
761        assert_eq!(Type::from_str("Decimal64(4)").unwrap(), Type::Decimal64(4));
762        assert_eq!(Type::from_str("Decimal128(6)").unwrap(), Type::Decimal128(6));
763        assert_eq!(Type::from_str("Decimal256(8)").unwrap(), Type::Decimal256(8));
764        assert_eq!(Type::from_str("Decimal(9, 2)").unwrap(), Type::Decimal32(2));
765        assert_eq!(Type::from_str("Decimal(18, 4)").unwrap(), Type::Decimal64(4));
766        assert_eq!(Type::from_str("Decimal(38, 6)").unwrap(), Type::Decimal128(6));
767        assert_eq!(Type::from_str("Decimal(76, 8)").unwrap(), Type::Decimal256(8));
768
769        assert!(Type::from_str("Decimal32(0)").is_err()); // Invalid scale
770        assert!(Type::from_str("Decimal(77, 8)").is_err()); // Precision too large
771        assert!(Type::from_str("Decimal(9)").is_err()); // Missing scale
772    }
773
774    /// Tests `Type::from_str` for string and binary types.
775    #[test]
776    fn test_from_str_strings() {
777        assert_eq!(Type::from_str("String").unwrap(), Type::String);
778        assert_eq!(Type::from_str("FixedString(4)").unwrap(), Type::FixedSizedString(4));
779        assert!(Type::from_str("FixedString(0)").is_err()); // Invalid size
780        assert!(Type::from_str("FixedString(a)").is_err()); // Invalid size
781    }
782
783    /// Tests `Type::from_str` for date and time types.
784    #[test]
785    fn test_from_str_datetime() {
786        assert_eq!(Type::from_str("DateTime").unwrap(), Type::DateTime(chrono_tz::UTC));
787        assert_eq!(Type::from_str("DateTime('UTC')").unwrap(), Type::DateTime(chrono_tz::UTC));
788        assert_eq!(
789            Type::from_str("DateTime('America/New_York')").unwrap(),
790            Type::DateTime(chrono_tz::America::New_York)
791        );
792        assert!(Type::from_str("DateTime('UTC', 'extra')").is_err()); // Too many args
793        assert!(Type::from_str("DateTime(UTC)").is_err()); // Unquoted timezone
794
795        assert_eq!(Type::from_str("DateTime64(3)").unwrap(), Type::DateTime64(3, chrono_tz::UTC));
796        assert_eq!(
797            Type::from_str("DateTime64(6, 'UTC')").unwrap(),
798            Type::DateTime64(6, chrono_tz::UTC)
799        );
800        assert_eq!(
801            Type::from_str("DateTime64(3, 'America/New_York')").unwrap(),
802            Type::DateTime64(3, chrono_tz::America::New_York)
803        );
804        assert!(Type::from_str("DateTime64()").is_err()); // Too few args
805        assert!(Type::from_str("DateTime64(3, 'UTC', 'extra')").is_err()); // Too many args
806        assert!(Type::from_str("DateTime64(3, UTC)").is_err()); // Unquoted timezone
807    }
808
809    /// Tests `Type::from_str` for Enum8 with explicit indices.
810    #[test]
811    fn test_from_str_enum8_explicit() {
812        let enum8 = Type::from_str("Enum8('active' = 1, 'inactive' = 2)").unwrap();
813        assert_eq!(enum8, Type::Enum8(vec![("active".into(), 1), ("inactive".into(), 2)]));
814
815        let single = Type::from_str("Enum8('test' = -1)").unwrap();
816        assert_eq!(single, Type::Enum8(vec![("test".into(), -1)]));
817
818        let negative = Type::from_str("Enum8('neg' = -128, 'zero' = 0)").unwrap();
819        assert_eq!(negative, Type::Enum8(vec![("neg".into(), -128), ("zero".into(), 0)]));
820    }
821
822    /// Tests `Type::from_str` for Enum8 with empty variants.
823    #[test]
824    fn test_from_str_enum8_empty() {
825        let empty = Type::from_str("Enum8()").unwrap();
826        assert_eq!(empty, Type::Enum8(vec![]));
827    }
828
829    /// Tests `Type::from_str` for Enum16 with explicit indices.
830    #[test]
831    fn test_from_str_enum16_explicit() {
832        let enum16 = Type::from_str("Enum16('high' = 1000, 'low' = -1000)").unwrap();
833        assert_eq!(enum16, Type::Enum16(vec![("high".into(), 1000), ("low".into(), -1000)]));
834
835        let single = Type::from_str("Enum16('test' = 0)").unwrap();
836        assert_eq!(single, Type::Enum16(vec![("test".into(), 0)]));
837    }
838
839    /// Tests `Type::from_str` error cases for Enum8.
840    #[test]
841    fn test_from_str_enum8_errors() {
842        assert!(Type::from_str("Enum8('a' = 1, 2)").is_err()); // Lone value
843        assert!(Type::from_str("Enum8('a' = x)").is_err()); // Invalid value
844        assert!(Type::from_str("Enum8(a = 1)").is_err()); // Unquoted name
845        assert!(Type::from_str("Enum8('a' = 1, )").is_err()); // Trailing comma
846        assert!(Type::from_str("Enum8('a' = 1").is_err()); // Unclosed paren
847    }
848
849    /// Tests `Type::from_str` error cases for Enum16.
850    #[test]
851    fn test_from_str_enum16_errors() {
852        assert!(Type::from_str("Enum16('a' = 1, 2)").is_err()); // Lone value
853        assert!(Type::from_str("Enum16('a' = x)").is_err()); // Invalid value
854        assert!(Type::from_str("Enum16(a = 1)").is_err()); // Unquoted name
855        assert!(Type::from_str("Enum16('a' = 1, )").is_err()); // Trailing comma
856        assert!(Type::from_str("Enum16('a' = 1").is_err()); // Unclosed paren
857    }
858
859    /// Tests `Type::from_str` for complex types.
860    #[test]
861    fn test_from_str_complex_types() {
862        assert_eq!(
863            Type::from_str("LowCardinality(String)").unwrap(),
864            Type::LowCardinality(Box::new(Type::String))
865        );
866        assert_eq!(Type::from_str("Array(Int32)").unwrap(), Type::Array(Box::new(Type::Int32)));
867        assert_eq!(
868            Type::from_str("Tuple(Int32, String)").unwrap(),
869            Type::Tuple(vec![Type::Int32, Type::String])
870        );
871        assert_eq!(
872            Type::from_str("Nullable(Int32)").unwrap(),
873            Type::Nullable(Box::new(Type::Int32))
874        );
875        assert_eq!(
876            Type::from_str("Map(String, Int32)").unwrap(),
877            Type::Map(Box::new(Type::String), Box::new(Type::Int32))
878        );
879        assert_eq!(Type::from_str("JSON").unwrap(), Type::Object);
880        assert_eq!(Type::from_str("Object").unwrap(), Type::Object);
881
882        assert!(Type::from_str("LowCardinality()").is_err()); // Missing arg
883        assert!(Type::from_str("Array(Int32, String)").is_err()); // Too many args
884        assert!(Type::from_str("Map(String)").is_err()); // Missing value type
885    }
886
887    /// Tests round-trip `to_string` and `from_str` for all types.
888    #[test]
889    fn test_round_trip_type_strings() {
890        let special_types = vec![
891            (Type::Binary, Type::String),
892            (Type::FixedSizedBinary(8), Type::FixedSizedString(8)),
893        ];
894
895        let types = vec![
896            Type::Int8,
897            Type::UInt8,
898            Type::Float64,
899            Type::String,
900            Type::FixedSizedString(4),
901            Type::Uuid,
902            Type::Date,
903            Type::Date32,
904            Type::DateTime(Tz::UTC),
905            Type::DateTime64(3, Tz::America__New_York),
906            Type::Ipv4,
907            Type::Ipv6,
908            Type::Decimal32(2),
909            Type::Enum8(vec![("active".into(), 1), ("inactive".into(), 2)]),
910            Type::Enum16(vec![("high".into(), 1000)]),
911            Type::LowCardinality(Box::new(Type::String)),
912            Type::Array(Box::new(Type::Int32)),
913            Type::Tuple(vec![Type::Int32, Type::String]),
914            Type::Nullable(Box::new(Type::Int32)),
915            Type::Map(Box::new(Type::String), Box::new(Type::Int32)),
916            Type::Object,
917        ];
918
919        for ty in types {
920            let type_str = ty.to_string();
921            let parsed = Type::from_str(&type_str)
922                .unwrap_or_else(|e| panic!("Failed to parse '{type_str}' for type {ty:?}: {e}"));
923            assert_eq!(
924                parsed, ty,
925                "Round-trip failed for type {ty:?}: expected {ty}, got {parsed}"
926            );
927        }
928
929        for (ty, mapped_ty) in special_types {
930            let type_str = ty.to_string();
931            let parsed = Type::from_str(&type_str)
932                .unwrap_or_else(|e| panic!("Failed to parse '{type_str}' for type {ty:?}: {e}"));
933            assert_eq!(
934                parsed, mapped_ty,
935                "Round-trip failed for type {ty:?}: expected {mapped_ty}, got {parsed}"
936            );
937        }
938    }
939
940    /// Tests error cases for general type parsing.
941    #[test]
942    fn test_from_str_general_errors() {
943        assert!(Type::from_str("").is_err()); // Empty input
944        assert!(Type::from_str("InvalidType").is_err()); // Unknown type
945        assert!(Type::from_str("Nested(String)").is_err()); // Unsupported Nested
946        assert!(Type::from_str("Int8(").is_err()); // Unclosed paren
947        assert!(Type::from_str("Tuple(String,)").is_err()); // Trailing comma
948    }
949
950    /// Tests `strip_tuple_field_name` helper function.
951    #[test]
952    fn test_strip_tuple_field_name() {
953        // Anonymous tuple fields (no name) - should return as-is
954        assert_eq!(strip_tuple_field_name("String"), "String");
955        assert_eq!(strip_tuple_field_name("Int64"), "Int64");
956        assert_eq!(strip_tuple_field_name("Nullable(Int32)"), "Nullable(Int32)");
957
958        // Named tuple fields - should strip the name
959        assert_eq!(strip_tuple_field_name("s String"), "String");
960        assert_eq!(strip_tuple_field_name("i Int64"), "Int64");
961        assert_eq!(strip_tuple_field_name("my_field Nullable(Int32)"), "Nullable(Int32)");
962        assert_eq!(
963            strip_tuple_field_name("status Enum8('active' = 1, 'inactive' = 2)"),
964            "Enum8('active' = 1, 'inactive' = 2)"
965        );
966
967        // Edge cases
968        assert_eq!(strip_tuple_field_name("  s String  "), "String"); // Extra whitespace
969        assert_eq!(strip_tuple_field_name("field123 UInt32"), "UInt32"); // Name with numbers
970
971        // Types with internal spaces (must NOT be stripped) - regression test for Codex review
972        assert_eq!(strip_tuple_field_name("Map(String, Int32)"), "Map(String, Int32)");
973        assert_eq!(strip_tuple_field_name("Array(Nullable(String))"), "Array(Nullable(String))");
974        assert_eq!(strip_tuple_field_name("Tuple(String, Int32)"), "Tuple(String, Int32)");
975
976        // Named field with complex type containing spaces
977        assert_eq!(strip_tuple_field_name("my_map Map(String, Int32)"), "Map(String, Int32)");
978    }
979
980    /// Tests `Type::from_str` for named tuple fields (issue #85).
981    #[test]
982    fn test_from_str_named_tuple() {
983        // Simple named tuple
984        assert_eq!(
985            Type::from_str("Tuple(s String, i Int64)").unwrap(),
986            Type::Tuple(vec![Type::String, Type::Int64])
987        );
988
989        // Named tuple with complex types
990        assert_eq!(
991            Type::from_str("Tuple(name String, value Nullable(Int32))").unwrap(),
992            Type::Tuple(vec![Type::String, Type::Nullable(Box::new(Type::Int32))])
993        );
994
995        // Named tuple with nested types
996        assert_eq!(
997            Type::from_str("Tuple(arr Array(String), map Map(String, Int32))").unwrap(),
998            Type::Tuple(vec![
999                Type::Array(Box::new(Type::String)),
1000                Type::Map(Box::new(Type::String), Box::new(Type::Int32))
1001            ])
1002        );
1003
1004        // Named tuple with Enum
1005        assert_eq!(
1006            Type::from_str("Tuple(status Enum8('active' = 1, 'inactive' = 2), count Int64)")
1007                .unwrap(),
1008            Type::Tuple(vec![
1009                Type::Enum8(vec![("active".into(), 1), ("inactive".into(), 2)]),
1010                Type::Int64
1011            ])
1012        );
1013
1014        // Mixed: some fields named, some not (ClickHouse allows this)
1015        // Actually, ClickHouse requires all or none to be named, but we handle it gracefully
1016        assert_eq!(
1017            Type::from_str("Tuple(String, i Int64)").unwrap(),
1018            Type::Tuple(vec![Type::String, Type::Int64])
1019        );
1020
1021        // Anonymous tuples with types containing internal spaces - regression test for Codex review
1022        // These must continue to parse correctly (they worked before the named tuple fix)
1023        assert_eq!(
1024            Type::from_str("Tuple(Map(String, Int32), Int32)").unwrap(),
1025            Type::Tuple(vec![
1026                Type::Map(Box::new(Type::String), Box::new(Type::Int32)),
1027                Type::Int32
1028            ])
1029        );
1030        assert_eq!(
1031            Type::from_str("Tuple(Array(Nullable(String)), Map(String, Int64))").unwrap(),
1032            Type::Tuple(vec![
1033                Type::Array(Box::new(Type::Nullable(Box::new(Type::String)))),
1034                Type::Map(Box::new(Type::String), Box::new(Type::Int64))
1035            ])
1036        );
1037    }
1038}