Skip to main content

rudb_common/
types.rs

1//! The type system, per `spec/10-sql-and-types.md` section 10.1.
2//!
3//! A logical type is what SQL talks about. A physical type is how it is laid out. Keeping the two
4//! apart is what lets `DECIMAL(9, 2)` be stored in an `i32` without the planner having to know,
5//! and it is the same separation that later lets a `VARCHAR` column be handed to an operator as
6//! dictionary codes.
7//!
8//! Type names are spelled the way DuckDB spells them, including the aliases, because
9//! `spec/12-duckdb-compat.md` makes the dialect a compatibility surface and `CREATE TABLE t (a
10//! INT4)` is a thing people write.
11
12use std::fmt;
13
14use crate::error::{Error, Result};
15
16/// A named field of a `STRUCT` or a `UNION`, and a named column of a table.
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub struct Field {
19    /// The field name, unquoted and case sensitive as stored.
20    pub name: String,
21    /// The field type.
22    pub ty: LogicalType,
23    /// Whether the column refuses nulls, which is the `NOT NULL` the DDL declared it with.
24    ///
25    /// Always false inside a `STRUCT` or a `UNION`, because a null is a property of a value at
26    /// every nesting level and no type in SQL says a value cannot be one. This is here rather than
27    /// on a separate column type because a table column is already spelled with this struct, and a
28    /// second one that was this one plus a flag would have to be threaded through the binder, the
29    /// scope and every operator schema to carry a bit that only the insert path reads.
30    pub not_null: bool,
31}
32
33impl Field {
34    /// A field with a name and a type, which accepts nulls.
35    pub fn new(name: impl Into<String>, ty: LogicalType) -> Self {
36        Self { name: name.into(), ty, not_null: false }
37    }
38
39    /// A column with a name and a type, which refuses nulls.
40    pub fn required(name: impl Into<String>, ty: LogicalType) -> Self {
41        Self { name: name.into(), ty, not_null: true }
42    }
43}
44
45/// What SQL thinks a value is.
46///
47/// Nulls are not in here. `spec/10-sql-and-types.md` says null is a per-value property at every
48/// nesting level, which makes it a property of a vector's validity mask rather than of a type.
49/// The one exception is [`LogicalType::Null`], which is the type of a literal `NULL` before
50/// anything has told it what it is, and which every other type absorbs during resolution.
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52#[non_exhaustive]
53pub enum LogicalType {
54    /// The type of an untyped `NULL` literal.
55    Null,
56    /// `BOOLEAN`.
57    Boolean,
58    /// `TINYINT`, 8 bits signed.
59    TinyInt,
60    /// `SMALLINT`, 16 bits signed.
61    SmallInt,
62    /// `INTEGER`, 32 bits signed.
63    Integer,
64    /// `BIGINT`, 64 bits signed.
65    BigInt,
66    /// `HUGEINT`, 128 bits signed.
67    HugeInt,
68    /// `UTINYINT`, 8 bits unsigned.
69    UTinyInt,
70    /// `USMALLINT`, 16 bits unsigned.
71    USmallInt,
72    /// `UINTEGER`, 32 bits unsigned.
73    UInteger,
74    /// `UBIGINT`, 64 bits unsigned.
75    UBigInt,
76    /// `UHUGEINT`, 128 bits unsigned.
77    UHugeInt,
78    /// `FLOAT`, IEEE 754 binary32.
79    Float,
80    /// `DOUBLE`, IEEE 754 binary64.
81    Double,
82    /// `DECIMAL(width, scale)`, stored in the narrowest integer that holds `width` digits.
83    Decimal {
84        /// Total number of decimal digits, 1 through 38.
85        width: u8,
86        /// Digits to the right of the point, no greater than `width`.
87        scale: u8,
88    },
89    /// `VARCHAR`. Length modifiers parse and are then ignored, as they are in DuckDB.
90    Varchar,
91    /// `BLOB`.
92    Blob,
93    /// `BIT`, a bit string.
94    Bit,
95    /// `UUID`.
96    Uuid,
97    /// `DATE`, days since 1970-01-01.
98    Date,
99    /// `TIME`, microseconds since midnight.
100    Time,
101    /// `TIME WITH TIME ZONE`.
102    TimeTz,
103    /// `TIMESTAMP`, microseconds since the epoch.
104    Timestamp,
105    /// `TIMESTAMP_S`, seconds since the epoch.
106    TimestampS,
107    /// `TIMESTAMP_MS`, milliseconds since the epoch.
108    TimestampMs,
109    /// `TIMESTAMP_NS`, nanoseconds since the epoch.
110    TimestampNs,
111    /// `TIMESTAMP WITH TIME ZONE`.
112    TimestampTz,
113    /// `INTERVAL`, the months, days and microseconds triple.
114    Interval,
115    /// `T[]`, a variable length list.
116    List(Box<LogicalType>),
117    /// `T[n]`, a fixed length array.
118    Array(Box<LogicalType>, u32),
119    /// `STRUCT(name type, ...)`.
120    Struct(Vec<Field>),
121    /// `MAP(key, value)`.
122    Map(Box<LogicalType>, Box<LogicalType>),
123    /// `UNION(tag type, ...)`.
124    Union(Vec<Field>),
125}
126
127/// How a value is actually laid out in a vector.
128///
129/// The planner picks operators off this rather than off the logical type, which is why `DATE` and
130/// `INTEGER` share an implementation of everything that does not care what the number means.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132#[non_exhaustive]
133pub enum PhysicalType {
134    /// One byte per value, zero or one.
135    Bool,
136    /// 8 bit signed.
137    Int8,
138    /// 16 bit signed.
139    Int16,
140    /// 32 bit signed.
141    Int32,
142    /// 64 bit signed.
143    Int64,
144    /// 128 bit signed.
145    Int128,
146    /// 8 bit unsigned.
147    UInt8,
148    /// 16 bit unsigned.
149    UInt16,
150    /// 32 bit unsigned.
151    UInt32,
152    /// 64 bit unsigned.
153    UInt64,
154    /// 128 bit unsigned.
155    UInt128,
156    /// IEEE 754 binary32.
157    Float32,
158    /// IEEE 754 binary64.
159    Float64,
160    /// The months, days and microseconds triple.
161    Interval,
162    /// The 16 byte string representation from `spec/07-execution.md` section 7.1.
163    Varlen,
164    /// Offsets plus one child column.
165    List,
166    /// A fixed stride into one child column.
167    Array,
168    /// Named child columns, one per field.
169    Struct,
170    /// No storage. Only the validity mask says anything.
171    Empty,
172}
173
174impl LogicalType {
175    /// A `DECIMAL(width, scale)`, checked.
176    ///
177    /// # Errors
178    ///
179    /// If the width is zero or above 38, or the scale is greater than the width. Those are the
180    /// same bounds DuckDB enforces and the message is the same message.
181    pub fn decimal(width: u8, scale: u8) -> Result<Self> {
182        if width == 0 || width > MAX_DECIMAL_WIDTH {
183            return Err(Error::binder(format!("Width must be between 1 and {MAX_DECIMAL_WIDTH}!")));
184        }
185        if scale > width {
186            return Err(Error::binder(format!(
187                "Scale cannot be bigger than width, {scale} is bigger than {width}"
188            )));
189        }
190        Ok(Self::Decimal { width, scale })
191    }
192
193    /// A list of `element`.
194    #[must_use]
195    pub fn list(element: Self) -> Self {
196        Self::List(Box::new(element))
197    }
198
199    /// A fixed length array of `element`.
200    #[must_use]
201    pub fn array(element: Self, length: u32) -> Self {
202        Self::Array(Box::new(element), length)
203    }
204
205    /// A map from `key` to `value`.
206    #[must_use]
207    pub fn map(key: Self, value: Self) -> Self {
208        Self::Map(Box::new(key), Box::new(value))
209    }
210
211    /// How this type is laid out.
212    #[must_use]
213    pub fn physical(&self) -> PhysicalType {
214        match self {
215            Self::Null => PhysicalType::Empty,
216            Self::Boolean => PhysicalType::Bool,
217            Self::TinyInt => PhysicalType::Int8,
218            Self::SmallInt => PhysicalType::Int16,
219            Self::Integer | Self::Date => PhysicalType::Int32,
220            Self::BigInt
221            | Self::Time
222            | Self::TimeTz
223            | Self::Timestamp
224            | Self::TimestampS
225            | Self::TimestampMs
226            | Self::TimestampNs
227            | Self::TimestampTz => PhysicalType::Int64,
228            Self::HugeInt | Self::Uuid => PhysicalType::Int128,
229            Self::UTinyInt => PhysicalType::UInt8,
230            Self::USmallInt => PhysicalType::UInt16,
231            Self::UInteger => PhysicalType::UInt32,
232            Self::UBigInt => PhysicalType::UInt64,
233            Self::UHugeInt => PhysicalType::UInt128,
234            Self::Float => PhysicalType::Float32,
235            Self::Double => PhysicalType::Float64,
236            // The narrowest integer that holds the requested number of digits, which is the
237            // standard representation and the one DuckDB uses. A DECIMAL(9, 2) column costs four
238            // bytes a value and not sixteen.
239            Self::Decimal { width, .. } => match width {
240                0..=4 => PhysicalType::Int16,
241                5..=9 => PhysicalType::Int32,
242                10..=18 => PhysicalType::Int64,
243                _ => PhysicalType::Int128,
244            },
245            Self::Varchar | Self::Blob | Self::Bit => PhysicalType::Varlen,
246            Self::Interval => PhysicalType::Interval,
247            // A map is a list of two-field structs, which is how Arrow does it and how every
248            // engine that has to interoperate with Arrow ends up doing it.
249            Self::List(_) | Self::Map(_, _) => PhysicalType::List,
250            Self::Array(_, _) => PhysicalType::Array,
251            Self::Struct(_) | Self::Union(_) => PhysicalType::Struct,
252        }
253    }
254
255    /// Whether arithmetic applies.
256    #[must_use]
257    pub fn is_numeric(&self) -> bool {
258        self.is_integer() || matches!(self, Self::Float | Self::Double | Self::Decimal { .. })
259    }
260
261    /// Whether this is one of the integer types, signed or unsigned.
262    #[must_use]
263    pub fn is_integer(&self) -> bool {
264        matches!(
265            self,
266            Self::TinyInt
267                | Self::SmallInt
268                | Self::Integer
269                | Self::BigInt
270                | Self::HugeInt
271                | Self::UTinyInt
272                | Self::USmallInt
273                | Self::UInteger
274                | Self::UBigInt
275                | Self::UHugeInt
276        )
277    }
278
279    /// The width and scale of the decimal that holds every value of this type exactly.
280    ///
281    /// A decimal is its own, an integer is one with no fraction and room for its digits, and
282    /// nothing else has one. This is what the rule for the type of a product is written in terms
283    /// of, since `DECIMAL(4,2) * INTEGER` is as wide as `DECIMAL(4,2) * DECIMAL(10,0)` upstream and
284    /// the two ought to be the same line of code here.
285    #[must_use]
286    pub fn decimal_shape(&self) -> Option<(u8, u8)> {
287        match self {
288            Self::Decimal { width, scale } => Some((*width, *scale)),
289            other if other.is_integer() => Some((decimal_digits(other), 0)),
290            _ => None,
291        }
292    }
293
294    /// Whether this is a date, a time, a timestamp or an interval.
295    #[must_use]
296    pub fn is_temporal(&self) -> bool {
297        matches!(
298            self,
299            Self::Date
300                | Self::Time
301                | Self::TimeTz
302                | Self::Timestamp
303                | Self::TimestampS
304                | Self::TimestampMs
305                | Self::TimestampNs
306                | Self::TimestampTz
307                | Self::Interval
308        )
309    }
310
311    /// Whether this type contains other types.
312    ///
313    /// Nested types are stored columnar all the way down, so this is the question of whether a
314    /// column of this type is one column chunk or several.
315    #[must_use]
316    pub fn is_nested(&self) -> bool {
317        matches!(
318            self,
319            Self::List(_) | Self::Array(_, _) | Self::Struct(_) | Self::Map(_, _) | Self::Union(_)
320        )
321    }
322
323    /// The type both of these can be cast to without losing a value, if there is one.
324    ///
325    /// This is DuckDB's `MaxLogicalType` and it is where the type of `a + b` starts, and it is the
326    /// whole answer for the arms of a `CASE` and the columns of a `UNION`. An addition takes one
327    /// more digit than this when the answer is a decimal, because two eighteen digit numbers add to
328    /// nineteen, and that part is the signature table's rather than this function's: a `UNION` of
329    /// two `DECIMAL(18,0)` columns is a `DECIMAL(18,0)` and their sum is not. The rule is a total
330    /// order over the numeric types
331    /// with everything else absorbing into `VARCHAR` only when it is asked to, and `NULL` absorbing
332    /// into anything, which is what makes `CASE WHEN c THEN NULL ELSE 1 END` an integer.
333    ///
334    /// Mixing signed and unsigned widens rather than reinterprets, so `INTEGER` and `UINTEGER`
335    /// promote to `BIGINT` and not to either of themselves. That costs a byte per value on a case
336    /// that is rare and it is the only version that never silently changes a number, which matters
337    /// more here than the byte does: a wrong answer that is off by 4,294,967,296 is the worst kind
338    /// of bug this engine can have.
339    ///
340    /// Returns `None` when there is no such type, which is the binder's cue to raise rather than to
341    /// guess. Two different structs are `None` and not a struct of promoted fields, because field
342    /// order and field names would have to match and a rule that sometimes works is worse here than
343    /// one that never does.
344    #[must_use]
345    pub fn promote(&self, other: &Self) -> Option<Self> {
346        if self == other {
347            return Some(self.clone());
348        }
349        match (self, other) {
350            (Self::Null, ty) | (ty, Self::Null) => Some(ty.clone()),
351            (Self::List(left), Self::List(right)) => Some(Self::list(left.promote(right)?)),
352            _ if self.is_numeric() && other.is_numeric() => {
353                Some(promote_numeric(self.clone(), other.clone()))
354            }
355            // A date and a timestamp meet at the wider one, which is the timestamp, and the same
356            // holds for the timestamp units. `rank_temporal` is what says which is wider.
357            _ if self.is_temporal() && other.is_temporal() => {
358                match (rank_temporal(self), rank_temporal(other)) {
359                    (Some(left), Some(right)) => {
360                        Some(if left >= right { self.clone() } else { other.clone() })
361                    }
362                    _ => None,
363                }
364            }
365            _ => None,
366        }
367    }
368
369    /// The types this one contains, in child column order, or empty for a scalar type.
370    #[must_use]
371    pub fn children(&self) -> Vec<Self> {
372        match self {
373            Self::List(inner) | Self::Array(inner, _) => vec![inner.as_ref().clone()],
374            Self::Map(key, value) => vec![key.as_ref().clone(), value.as_ref().clone()],
375            Self::Struct(fields) | Self::Union(fields) => {
376                fields.iter().map(|f| f.ty.clone()).collect()
377            }
378            _ => Vec::new(),
379        }
380    }
381
382    /// Parses a SQL type name, aliases included.
383    ///
384    /// # Errors
385    ///
386    /// If the text is not a type name this understands. The message names the offending word,
387    /// because a type parse failure two levels inside a `STRUCT` is otherwise unreadable.
388    pub fn parse(text: &str) -> Result<Self> {
389        let tokens = lex(text)?;
390        let mut parser = TypeParser { tokens: &tokens, position: 0 };
391        let ty = parser.parse_type()?;
392        if parser.position != parser.tokens.len() {
393            return Err(Error::parser(format!("Type \"{text}\" has trailing text")));
394        }
395        Ok(ty)
396    }
397}
398
399/// The largest number of decimal digits a `DECIMAL` can carry, because 38 digits is what fits in
400/// 128 bits and there is no wider physical type.
401pub const MAX_DECIMAL_WIDTH: u8 = 38;
402
403impl fmt::Display for LogicalType {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        match self {
406            Self::Null => f.write_str("\"NULL\""),
407            Self::Boolean => f.write_str("BOOLEAN"),
408            Self::TinyInt => f.write_str("TINYINT"),
409            Self::SmallInt => f.write_str("SMALLINT"),
410            Self::Integer => f.write_str("INTEGER"),
411            Self::BigInt => f.write_str("BIGINT"),
412            Self::HugeInt => f.write_str("HUGEINT"),
413            Self::UTinyInt => f.write_str("UTINYINT"),
414            Self::USmallInt => f.write_str("USMALLINT"),
415            Self::UInteger => f.write_str("UINTEGER"),
416            Self::UBigInt => f.write_str("UBIGINT"),
417            Self::UHugeInt => f.write_str("UHUGEINT"),
418            Self::Float => f.write_str("FLOAT"),
419            Self::Double => f.write_str("DOUBLE"),
420            Self::Decimal { width, scale } => write!(f, "DECIMAL({width},{scale})"),
421            Self::Varchar => f.write_str("VARCHAR"),
422            Self::Blob => f.write_str("BLOB"),
423            Self::Bit => f.write_str("BIT"),
424            Self::Uuid => f.write_str("UUID"),
425            Self::Date => f.write_str("DATE"),
426            Self::Time => f.write_str("TIME"),
427            Self::TimeTz => f.write_str("TIME WITH TIME ZONE"),
428            Self::Timestamp => f.write_str("TIMESTAMP"),
429            Self::TimestampS => f.write_str("TIMESTAMP_S"),
430            Self::TimestampMs => f.write_str("TIMESTAMP_MS"),
431            Self::TimestampNs => f.write_str("TIMESTAMP_NS"),
432            Self::TimestampTz => f.write_str("TIMESTAMP WITH TIME ZONE"),
433            Self::Interval => f.write_str("INTERVAL"),
434            Self::List(inner) => write!(f, "{inner}[]"),
435            Self::Array(inner, length) => write!(f, "{inner}[{length}]"),
436            Self::Map(key, value) => write!(f, "MAP({key}, {value})"),
437            Self::Struct(fields) => write_fields(f, "STRUCT", fields),
438            Self::Union(fields) => write_fields(f, "UNION", fields),
439        }
440    }
441}
442
443/// Where a numeric type sits in the widening order.
444///
445/// The integers are ordered by how many values they hold, which is why an unsigned type ranks
446/// above the signed type of the same width. That order alone is not enough to promote a signed
447/// type with an unsigned one, since neither holds the other, and [`promote_integers`] is what
448/// handles that case before this function is reached.
449fn rank_numeric(ty: &LogicalType) -> u8 {
450    match ty {
451        LogicalType::TinyInt => 1,
452        LogicalType::UTinyInt => 2,
453        LogicalType::SmallInt => 3,
454        LogicalType::USmallInt => 4,
455        LogicalType::Integer => 5,
456        LogicalType::UInteger => 6,
457        LogicalType::BigInt => 7,
458        LogicalType::UBigInt => 8,
459        LogicalType::HugeInt => 9,
460        LogicalType::UHugeInt => 10,
461        LogicalType::Decimal { .. } => 11,
462        LogicalType::Float => 12,
463        LogicalType::Double => 13,
464        _ => 0,
465    }
466}
467
468/// Whether an integer type is signed, and how many bits it is.
469fn integer_shape(ty: &LogicalType) -> (bool, u8) {
470    match ty {
471        LogicalType::TinyInt => (true, 8),
472        LogicalType::SmallInt => (true, 16),
473        LogicalType::Integer => (true, 32),
474        LogicalType::BigInt => (true, 64),
475        LogicalType::HugeInt => (true, 128),
476        LogicalType::UTinyInt => (false, 8),
477        LogicalType::USmallInt => (false, 16),
478        LogicalType::UInteger => (false, 32),
479        LogicalType::UBigInt => (false, 64),
480        _ => (false, 128),
481    }
482}
483
484/// The integer type of that many bits and that signedness.
485fn integer_of(signed: bool, bits: u8) -> Option<LogicalType> {
486    Some(match (signed, bits) {
487        (true, 8) => LogicalType::TinyInt,
488        (true, 16) => LogicalType::SmallInt,
489        (true, 32) => LogicalType::Integer,
490        (true, 64) => LogicalType::BigInt,
491        (true, 128) => LogicalType::HugeInt,
492        (false, 8) => LogicalType::UTinyInt,
493        (false, 16) => LogicalType::USmallInt,
494        (false, 32) => LogicalType::UInteger,
495        (false, 64) => LogicalType::UBigInt,
496        (false, 128) => LogicalType::UHugeInt,
497        _ => return None,
498    })
499}
500
501/// The narrowest integer type that holds every value of both.
502///
503/// Two of the same signedness are just the wider one. A signed and an unsigned need a signed type
504/// strictly wider than the unsigned one, because a `UBIGINT` of 2^63 does not fit in a `BIGINT`
505/// and a `BIGINT` of -1 does not fit in a `UBIGINT`. When that runs off the end of the integer
506/// types, which is only `UHUGEINT` against a signed type, the answer is `DOUBLE`: it loses
507/// precision past 2^53 and it is the only thing left, and DuckDB does the same.
508fn promote_integers(left: &LogicalType, right: &LogicalType) -> LogicalType {
509    let (left_signed, left_bits) = integer_shape(left);
510    let (right_signed, right_bits) = integer_shape(right);
511    if left_signed == right_signed {
512        return if left_bits >= right_bits { left.clone() } else { right.clone() };
513    }
514    let (signed_bits, unsigned_bits) =
515        if left_signed { (left_bits, right_bits) } else { (right_bits, left_bits) };
516    let wanted = signed_bits.max(unsigned_bits.saturating_mul(2));
517    integer_of(true, wanted).unwrap_or(LogicalType::Double)
518}
519
520/// The narrowest numeric type that holds every value of both.
521fn promote_numeric(left: LogicalType, right: LogicalType) -> LogicalType {
522    // A decimal and a float meet at the float, since the ranks already say so, but two decimals
523    // meet at one wide enough for both the integer part and the fraction of each, which the ranks
524    // cannot express.
525    if let (
526        LogicalType::Decimal { width: left_width, scale: left_scale },
527        LogicalType::Decimal { width: right_width, scale: right_scale },
528    ) = (&left, &right)
529    {
530        let scale = (*left_scale).max(*right_scale);
531        let integral =
532            left_width.saturating_sub(*left_scale).max(right_width.saturating_sub(*right_scale));
533        let width = integral.saturating_add(scale).min(MAX_DECIMAL_WIDTH);
534        return LogicalType::Decimal { width, scale: scale.min(width) };
535    }
536    // An integer and a decimal have to leave room for the integer's digits to the left of the
537    // point, so the decimal widens rather than the integer simply casting into it.
538    let widened = match (&left, &right) {
539        (LogicalType::Decimal { width, scale }, other)
540        | (other, LogicalType::Decimal { width, scale })
541            if other.is_integer() =>
542        {
543            let needed = decimal_digits(other).saturating_add(*scale).min(MAX_DECIMAL_WIDTH);
544            Some(LogicalType::Decimal { width: (*width).max(needed), scale: *scale })
545        }
546        _ => None,
547    };
548    if let Some(ty) = widened {
549        return ty;
550    }
551    if left.is_integer() && right.is_integer() {
552        return promote_integers(&left, &right);
553    }
554    if rank_numeric(&left) >= rank_numeric(&right) { left } else { right }
555}
556
557/// How many decimal digits an integer type needs, which is what a decimal has to leave room for.
558///
559/// It is the digits of the largest value the type holds, which is why `BIGINT` is nineteen and
560/// `UBIGINT` is twenty: 9,223,372,036,854,775,807 against 18,446,744,073,709,551,615. The pair
561/// below it is not like that, because a signed type and the unsigned type of the same width have
562/// the same digit count once the sign is off the front. Measured on `v2.0.0-dev84237` through the
563/// type of a sum: `DECIMAL(4,2)` with a `BIGINT` is `DECIMAL(22,2)` and with a `UBIGINT` is
564/// `DECIMAL(23,2)`.
565fn decimal_digits(ty: &LogicalType) -> u8 {
566    match ty {
567        LogicalType::TinyInt | LogicalType::UTinyInt => 3,
568        LogicalType::SmallInt | LogicalType::USmallInt => 5,
569        LogicalType::Integer | LogicalType::UInteger => 10,
570        LogicalType::BigInt => 19,
571        LogicalType::UBigInt => 20,
572        _ => MAX_DECIMAL_WIDTH,
573    }
574}
575
576/// Where a temporal type sits in the widening order, or `None` if it does not widen into another.
577///
578/// An interval is a duration and not a point in time, so it has no rank and never promotes with a
579/// timestamp. That is the difference between `t + INTERVAL 1 DAY`, which is a function call the
580/// binder resolves, and `CASE WHEN c THEN t ELSE INTERVAL 1 DAY END`, which has no type.
581fn rank_temporal(ty: &LogicalType) -> Option<u8> {
582    match ty {
583        LogicalType::Date => Some(1),
584        LogicalType::TimestampS => Some(2),
585        LogicalType::TimestampMs => Some(3),
586        LogicalType::Timestamp => Some(4),
587        LogicalType::TimestampNs => Some(5),
588        LogicalType::TimestampTz => Some(6),
589        _ => None,
590    }
591}
592
593fn write_fields(f: &mut fmt::Formatter<'_>, keyword: &str, fields: &[Field]) -> fmt::Result {
594    f.write_str(keyword)?;
595    f.write_str("(")?;
596    for (index, field) in fields.iter().enumerate() {
597        if index > 0 {
598            f.write_str(", ")?;
599        }
600        write_identifier(f, &field.name)?;
601        write!(f, " {}", field.ty)?;
602    }
603    f.write_str(")")
604}
605
606/// Writes a field name, quoting it if it would not survive being read back unquoted.
607fn write_identifier(f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
608    let plain = !name.is_empty()
609        && name.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
610        && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
611    if plain {
612        f.write_str(name)
613    } else {
614        f.write_str("\"")?;
615        for c in name.chars() {
616            if c == '"' {
617                f.write_str("\"\"")?;
618            } else {
619                write!(f, "{c}")?;
620            }
621        }
622        f.write_str("\"")
623    }
624}
625
626#[derive(Debug, Clone, PartialEq, Eq)]
627enum Token {
628    Word(String),
629    Quoted(String),
630    Number(u32),
631    LeftParen,
632    RightParen,
633    LeftBracket,
634    RightBracket,
635    Comma,
636}
637
638fn lex(text: &str) -> Result<Vec<Token>> {
639    let mut tokens = Vec::new();
640    let chars: Vec<char> = text.chars().collect();
641    let mut i = 0;
642    while i < chars.len() {
643        let c = chars[i];
644        match c {
645            c if c.is_whitespace() => i += 1,
646            '(' => {
647                tokens.push(Token::LeftParen);
648                i += 1;
649            }
650            ')' => {
651                tokens.push(Token::RightParen);
652                i += 1;
653            }
654            '[' => {
655                tokens.push(Token::LeftBracket);
656                i += 1;
657            }
658            ']' => {
659                tokens.push(Token::RightBracket);
660                i += 1;
661            }
662            ',' => {
663                tokens.push(Token::Comma);
664                i += 1;
665            }
666            '"' => {
667                let mut name = String::new();
668                i += 1;
669                loop {
670                    let Some(&c) = chars.get(i) else {
671                        return Err(Error::parser(format!(
672                            "Type \"{text}\" has an unterminated quoted name"
673                        )));
674                    };
675                    i += 1;
676                    if c == '"' {
677                        if chars.get(i) == Some(&'"') {
678                            name.push('"');
679                            i += 1;
680                            continue;
681                        }
682                        break;
683                    }
684                    name.push(c);
685                }
686                tokens.push(Token::Quoted(name));
687            }
688            c if c.is_ascii_digit() => {
689                let start = i;
690                while chars.get(i).is_some_and(char::is_ascii_digit) {
691                    i += 1;
692                }
693                let digits: String = chars[start..i].iter().collect();
694                let number = digits.parse::<u32>().map_err(|_| {
695                    Error::parser(format!("Type \"{text}\" has a number that is too large"))
696                })?;
697                tokens.push(Token::Number(number));
698            }
699            c if c.is_alphabetic() || c == '_' => {
700                let start = i;
701                while chars.get(i).is_some_and(|&c| c.is_alphanumeric() || c == '_') {
702                    i += 1;
703                }
704                tokens.push(Token::Word(chars[start..i].iter().collect()));
705            }
706            other => {
707                return Err(Error::parser(format!(
708                    "Type \"{text}\" has an unexpected character {other:?}"
709                )));
710            }
711        }
712    }
713    Ok(tokens)
714}
715
716struct TypeParser<'a> {
717    tokens: &'a [Token],
718    position: usize,
719}
720
721impl TypeParser<'_> {
722    fn peek(&self) -> Option<&Token> {
723        self.tokens.get(self.position)
724    }
725
726    fn eat(&mut self, token: &Token) -> bool {
727        if self.peek() == Some(token) {
728            self.position += 1;
729            true
730        } else {
731            false
732        }
733    }
734
735    /// Consumes `word` if it is next, case insensitively.
736    fn eat_word(&mut self, word: &str) -> bool {
737        match self.peek() {
738            Some(Token::Word(found)) if found.eq_ignore_ascii_case(word) => {
739                self.position += 1;
740                true
741            }
742            _ => false,
743        }
744    }
745
746    fn parse_type(&mut self) -> Result<LogicalType> {
747        let mut ty = self.parse_base()?;
748        // Suffixes bind left to right, so INTEGER[][3] is an array of three lists.
749        loop {
750            if !self.eat(&Token::LeftBracket) {
751                break;
752            }
753            if let Some(&Token::Number(length)) = self.peek() {
754                self.position += 1;
755                expect(self.eat(&Token::RightBracket), "]")?;
756                ty = LogicalType::array(ty, length);
757            } else {
758                expect(self.eat(&Token::RightBracket), "]")?;
759                ty = LogicalType::list(ty);
760            }
761        }
762        Ok(ty)
763    }
764
765    fn parse_base(&mut self) -> Result<LogicalType> {
766        // A quoted name is accepted here because the null type prints as "NULL" with the quotes,
767        // which is DuckDB's spelling and which has to read back.
768        let word = match self.peek().cloned() {
769            Some(Token::Word(word) | Token::Quoted(word)) => {
770                self.position += 1;
771                word
772            }
773            _ => return Err(Error::parser("Expected a type name".to_string())),
774        };
775        let upper = word.to_ascii_uppercase();
776
777        match upper.as_str() {
778            "STRUCT" | "ROW" => return self.parse_fields().map(LogicalType::Struct),
779            "UNION" => return self.parse_fields().map(LogicalType::Union),
780            "MAP" => {
781                expect(self.eat(&Token::LeftParen), "(")?;
782                let key = self.parse_type()?;
783                expect(self.eat(&Token::Comma), ",")?;
784                let value = self.parse_type()?;
785                expect(self.eat(&Token::RightParen), ")")?;
786                return Ok(LogicalType::map(key, value));
787            }
788            "DECIMAL" | "NUMERIC" | "DEC" => {
789                if !self.eat(&Token::LeftParen) {
790                    // Bare DECIMAL is DECIMAL(18, 3) in DuckDB, which is a surprising default and
791                    // is nonetheless the one people's queries depend on.
792                    return LogicalType::decimal(18, 3);
793                }
794                let width = self.parse_number()?;
795                let scale = if self.eat(&Token::Comma) { self.parse_number()? } else { 0 };
796                expect(self.eat(&Token::RightParen), ")")?;
797                let narrow = |n: u32| u8::try_from(n).unwrap_or(u8::MAX);
798                return LogicalType::decimal(narrow(width), narrow(scale));
799            }
800            // Multiword names. Each of these is a word that only means something with the words
801            // after it, so the lookahead is checked before the alias table is consulted.
802            "DOUBLE" => {
803                self.eat_word("PRECISION");
804                return Ok(LogicalType::Double);
805            }
806            "CHARACTER" => {
807                self.eat_word("VARYING");
808                self.eat_length_modifier()?;
809                return Ok(LogicalType::Varchar);
810            }
811            "TIME" | "TIMESTAMP" => {
812                let with_zone = self.eat_time_zone_suffix();
813                return Ok(match (upper.as_str(), with_zone) {
814                    ("TIME", false) => LogicalType::Time,
815                    ("TIME", true) => LogicalType::TimeTz,
816                    (_, false) => LogicalType::Timestamp,
817                    (_, true) => LogicalType::TimestampTz,
818                });
819            }
820            _ => {}
821        }
822
823        // A length modifier on a string type parses and is discarded, which is what DuckDB does:
824        // VARCHAR(10) does not truncate and does not reject, it is VARCHAR.
825        self.eat_length_modifier()?;
826        alias(&upper).ok_or_else(|| Error::parser(format!("Unrecognized type name \"{word}\"")))
827    }
828
829    /// `WITH TIME ZONE` or `WITHOUT TIME ZONE`, returning whether the zone is carried.
830    fn eat_time_zone_suffix(&mut self) -> bool {
831        let start = self.position;
832        let with = if self.eat_word("WITH") {
833            true
834        } else if self.eat_word("WITHOUT") {
835            false
836        } else {
837            return false;
838        };
839        if self.eat_word("TIME") && self.eat_word("ZONE") {
840            with
841        } else {
842            self.position = start;
843            false
844        }
845    }
846
847    fn eat_length_modifier(&mut self) -> Result<()> {
848        if self.eat(&Token::LeftParen) {
849            self.parse_number()?;
850            expect(self.eat(&Token::RightParen), ")")?;
851        }
852        Ok(())
853    }
854
855    fn parse_fields(&mut self) -> Result<Vec<Field>> {
856        expect(self.eat(&Token::LeftParen), "(")?;
857        let mut fields = Vec::new();
858        if self.eat(&Token::RightParen) {
859            return Ok(fields);
860        }
861        loop {
862            let name = match self.peek().cloned() {
863                Some(Token::Word(name) | Token::Quoted(name)) => {
864                    self.position += 1;
865                    name
866                }
867                _ => return Err(Error::parser("Expected a field name".to_string())),
868            };
869            let ty = self.parse_type()?;
870            fields.push(Field::new(name, ty));
871            if self.eat(&Token::Comma) {
872                continue;
873            }
874            expect(self.eat(&Token::RightParen), ")")?;
875            return Ok(fields);
876        }
877    }
878
879    fn parse_number(&mut self) -> Result<u32> {
880        match self.peek() {
881            Some(&Token::Number(n)) => {
882                self.position += 1;
883                Ok(n)
884            }
885            _ => Err(Error::parser("Expected a number".to_string())),
886        }
887    }
888}
889
890fn expect(matched: bool, what: &str) -> Result<()> {
891    if matched { Ok(()) } else { Err(Error::parser(format!("Expected \"{what}\""))) }
892}
893
894/// The single word type names, aliases included.
895///
896/// The aliases are DuckDB's, and they are here rather than in the parser because `CREATE TABLE t
897/// (a INT4)` and `CAST(x AS INT4)` have to agree and there is only one table.
898fn alias(upper: &str) -> Option<LogicalType> {
899    Some(match upper {
900        "NULL" => LogicalType::Null,
901        "BOOLEAN" | "BOOL" | "LOGICAL" => LogicalType::Boolean,
902        "TINYINT" | "INT1" => LogicalType::TinyInt,
903        "SMALLINT" | "INT2" | "SHORT" => LogicalType::SmallInt,
904        "INTEGER" | "INT" | "INT4" | "SIGNED" => LogicalType::Integer,
905        "BIGINT" | "INT8" | "LONG" => LogicalType::BigInt,
906        "HUGEINT" | "INT128" => LogicalType::HugeInt,
907        "UTINYINT" | "UINT1" => LogicalType::UTinyInt,
908        "USMALLINT" | "UINT2" => LogicalType::USmallInt,
909        "UINTEGER" | "UINT4" => LogicalType::UInteger,
910        "UBIGINT" | "UINT8" => LogicalType::UBigInt,
911        "UHUGEINT" | "UINT128" => LogicalType::UHugeInt,
912        "FLOAT" | "FLOAT4" | "REAL" => LogicalType::Float,
913        "FLOAT8" => LogicalType::Double,
914        "VARCHAR" | "CHAR" | "BPCHAR" | "TEXT" | "STRING" => LogicalType::Varchar,
915        "BLOB" | "BYTEA" | "BINARY" | "VARBINARY" => LogicalType::Blob,
916        "BIT" | "BITSTRING" => LogicalType::Bit,
917        "UUID" | "GUID" => LogicalType::Uuid,
918        "DATE" => LogicalType::Date,
919        "TIMETZ" => LogicalType::TimeTz,
920        "DATETIME" => LogicalType::Timestamp,
921        "TIMESTAMP_S" | "TIMESTAMP_SEC" | "TIMESTAMP_SECONDS" => LogicalType::TimestampS,
922        "TIMESTAMP_MS" | "TIMESTAMP_MILLISECONDS" => LogicalType::TimestampMs,
923        "TIMESTAMP_NS" | "TIMESTAMP_NANOSECONDS" => LogicalType::TimestampNs,
924        "TIMESTAMPTZ" => LogicalType::TimestampTz,
925        "INTERVAL" => LogicalType::Interval,
926        _ => return None,
927    })
928}
929
930#[cfg(test)]
931mod promotion_tests {
932    use super::LogicalType;
933
934    #[test]
935    fn a_type_promotes_with_itself_to_itself() {
936        for ty in [
937            LogicalType::Integer,
938            LogicalType::Varchar,
939            LogicalType::Boolean,
940            LogicalType::Struct(vec![]),
941        ] {
942            assert_eq!(ty.promote(&ty), Some(ty.clone()), "{ty} does not promote with itself");
943        }
944    }
945
946    #[test]
947    fn null_takes_the_other_type() {
948        assert_eq!(LogicalType::Null.promote(&LogicalType::Varchar), Some(LogicalType::Varchar));
949        assert_eq!(LogicalType::Date.promote(&LogicalType::Null), Some(LogicalType::Date));
950        assert_eq!(LogicalType::Null.promote(&LogicalType::Null), Some(LogicalType::Null));
951    }
952
953    #[test]
954    fn the_wider_number_wins() {
955        assert_eq!(
956            LogicalType::Integer.promote(&LogicalType::SmallInt),
957            Some(LogicalType::Integer)
958        );
959        assert_eq!(LogicalType::Integer.promote(&LogicalType::Double), Some(LogicalType::Double));
960        assert_eq!(LogicalType::Float.promote(&LogicalType::Double), Some(LogicalType::Double));
961    }
962
963    /// The one that is worth a test of its own, because reinterpreting instead of widening here is
964    /// a wrong answer off by four billion rather than a crash.
965    #[test]
966    fn signed_and_unsigned_widen_rather_than_reinterpret() {
967        assert_eq!(LogicalType::Integer.promote(&LogicalType::UInteger), Some(LogicalType::BigInt));
968        assert_eq!(
969            LogicalType::TinyInt.promote(&LogicalType::UTinyInt),
970            Some(LogicalType::SmallInt)
971        );
972        assert_eq!(LogicalType::BigInt.promote(&LogicalType::UBigInt), Some(LogicalType::HugeInt));
973    }
974
975    #[test]
976    fn promotion_does_not_care_which_side_a_type_is_on() {
977        let types = [
978            LogicalType::TinyInt,
979            LogicalType::UInteger,
980            LogicalType::BigInt,
981            LogicalType::Double,
982            LogicalType::Decimal { width: 10, scale: 2 },
983            LogicalType::Null,
984            LogicalType::Varchar,
985            LogicalType::Date,
986            LogicalType::Timestamp,
987        ];
988        for left in &types {
989            for right in &types {
990                assert_eq!(
991                    left.promote(right),
992                    right.promote(left),
993                    "{left} and {right} promote differently depending on the order"
994                );
995            }
996        }
997    }
998
999    #[test]
1000    fn a_decimal_keeps_room_for_both_halves() {
1001        let left = LogicalType::Decimal { width: 5, scale: 4 };
1002        let right = LogicalType::Decimal { width: 5, scale: 1 };
1003        assert_eq!(left.promote(&right), Some(LogicalType::Decimal { width: 8, scale: 4 }));
1004    }
1005
1006    #[test]
1007    fn an_integer_next_to_a_decimal_widens_the_decimal() {
1008        let decimal = LogicalType::Decimal { width: 5, scale: 2 };
1009        assert_eq!(
1010            decimal.promote(&LogicalType::Integer),
1011            Some(LogicalType::Decimal { width: 12, scale: 2 })
1012        );
1013    }
1014
1015    /// A signed and an unsigned integer of the same width leave different room, at the top pair.
1016    ///
1017    /// 9,223,372,036,854,775,807 is nineteen digits and 18,446,744,073,709,551,615 is twenty, so
1018    /// the two do not promote with a decimal to the same type, and every narrower pair does.
1019    #[test]
1020    fn a_bigint_leaves_room_for_one_digit_fewer_than_a_ubigint() {
1021        let decimal = LogicalType::Decimal { width: 4, scale: 2 };
1022        assert_eq!(
1023            decimal.promote(&LogicalType::BigInt),
1024            Some(LogicalType::Decimal { width: 21, scale: 2 })
1025        );
1026        assert_eq!(
1027            decimal.promote(&LogicalType::UBigInt),
1028            Some(LogicalType::Decimal { width: 22, scale: 2 })
1029        );
1030        assert_eq!(decimal.promote(&LogicalType::Integer), decimal.promote(&LogicalType::UInteger));
1031    }
1032
1033    #[test]
1034    fn a_date_and_a_timestamp_meet_at_the_timestamp() {
1035        assert_eq!(
1036            LogicalType::Date.promote(&LogicalType::Timestamp),
1037            Some(LogicalType::Timestamp)
1038        );
1039        assert_eq!(
1040            LogicalType::TimestampS.promote(&LogicalType::TimestampNs),
1041            Some(LogicalType::TimestampNs)
1042        );
1043    }
1044
1045    /// An interval is a duration and a timestamp is a point, so there is no type that holds both
1046    /// and saying so is the binder's cue to raise instead of guessing.
1047    #[test]
1048    fn types_that_do_not_meet_say_so() {
1049        assert_eq!(LogicalType::Timestamp.promote(&LogicalType::Interval), None);
1050        assert_eq!(LogicalType::Integer.promote(&LogicalType::Varchar), None);
1051        assert_eq!(LogicalType::Boolean.promote(&LogicalType::Integer), None);
1052    }
1053
1054    #[test]
1055    fn a_list_promotes_by_its_element() {
1056        let left = LogicalType::list(LogicalType::Integer);
1057        let right = LogicalType::list(LogicalType::BigInt);
1058        assert_eq!(left.promote(&right), Some(LogicalType::list(LogicalType::BigInt)));
1059        assert_eq!(left.promote(&LogicalType::list(LogicalType::Varchar)), None);
1060    }
1061}
1062
1063#[cfg(test)]
1064mod tests {
1065    use super::{Field, LogicalType, PhysicalType};
1066
1067    /// Every type this crate knows about, used by the round trip test and by anything else that
1068    /// wants to be exhaustive without listing them again.
1069    fn every_type() -> Vec<LogicalType> {
1070        vec![
1071            LogicalType::Null,
1072            LogicalType::Boolean,
1073            LogicalType::TinyInt,
1074            LogicalType::SmallInt,
1075            LogicalType::Integer,
1076            LogicalType::BigInt,
1077            LogicalType::HugeInt,
1078            LogicalType::UTinyInt,
1079            LogicalType::USmallInt,
1080            LogicalType::UInteger,
1081            LogicalType::UBigInt,
1082            LogicalType::UHugeInt,
1083            LogicalType::Float,
1084            LogicalType::Double,
1085            LogicalType::Decimal { width: 18, scale: 3 },
1086            LogicalType::Decimal { width: 38, scale: 0 },
1087            LogicalType::Varchar,
1088            LogicalType::Blob,
1089            LogicalType::Bit,
1090            LogicalType::Uuid,
1091            LogicalType::Date,
1092            LogicalType::Time,
1093            LogicalType::TimeTz,
1094            LogicalType::Timestamp,
1095            LogicalType::TimestampS,
1096            LogicalType::TimestampMs,
1097            LogicalType::TimestampNs,
1098            LogicalType::TimestampTz,
1099            LogicalType::Interval,
1100            LogicalType::list(LogicalType::Integer),
1101            LogicalType::list(LogicalType::list(LogicalType::Varchar)),
1102            LogicalType::array(LogicalType::Double, 3),
1103            LogicalType::map(LogicalType::Varchar, LogicalType::Integer),
1104            LogicalType::Struct(vec![
1105                Field::new("a", LogicalType::Integer),
1106                Field::new("b", LogicalType::list(LogicalType::Varchar)),
1107            ]),
1108            LogicalType::Union(vec![
1109                Field::new("num", LogicalType::Integer),
1110                Field::new("str", LogicalType::Varchar),
1111            ]),
1112        ]
1113    }
1114
1115    #[test]
1116    fn every_type_survives_being_printed_and_read_back() {
1117        // The textual plan format in spec/04-architecture.md round trips, and a plan carries
1118        // types, so this is the bottom of that guarantee. Failing it means a plan that cannot be
1119        // reparsed, which is the whole reason the format exists.
1120        for ty in every_type() {
1121            let printed = ty.to_string();
1122            let parsed = LogicalType::parse(&printed)
1123                .unwrap_or_else(|e| panic!("{printed} did not parse back: {e}"));
1124            assert_eq!(parsed, ty, "{printed} parsed to something else");
1125        }
1126    }
1127
1128    #[test]
1129    fn a_field_name_that_needs_quoting_gets_quoted() {
1130        let ty = LogicalType::Struct(vec![
1131            Field::new("plain", LogicalType::Integer),
1132            Field::new("has space", LogicalType::Integer),
1133            Field::new("has\"quote", LogicalType::Integer),
1134            Field::new("2leading", LogicalType::Integer),
1135        ]);
1136        assert_eq!(
1137            ty.to_string(),
1138            "STRUCT(plain INTEGER, \"has space\" INTEGER, \"has\"\"quote\" INTEGER, \
1139             \"2leading\" INTEGER)"
1140        );
1141        assert_eq!(LogicalType::parse(&ty.to_string()).unwrap(), ty);
1142    }
1143
1144    #[test]
1145    fn the_duckdb_aliases_resolve() {
1146        let cases = [
1147            ("int4", LogicalType::Integer),
1148            ("INT", LogicalType::Integer),
1149            ("signed", LogicalType::Integer),
1150            ("int8", LogicalType::BigInt),
1151            ("float4", LogicalType::Float),
1152            ("float8", LogicalType::Double),
1153            ("double precision", LogicalType::Double),
1154            ("text", LogicalType::Varchar),
1155            ("varchar(10)", LogicalType::Varchar),
1156            ("character varying(255)", LogicalType::Varchar),
1157            ("bool", LogicalType::Boolean),
1158            ("datetime", LogicalType::Timestamp),
1159            ("numeric(9, 2)", LogicalType::Decimal { width: 9, scale: 2 }),
1160            ("decimal", LogicalType::Decimal { width: 18, scale: 3 }),
1161            ("timestamp without time zone", LogicalType::Timestamp),
1162            ("timestamp with time zone", LogicalType::TimestampTz),
1163            ("time with time zone", LogicalType::TimeTz),
1164        ];
1165        for (text, expected) in cases {
1166            assert_eq!(LogicalType::parse(text).unwrap(), expected, "{text}");
1167        }
1168    }
1169
1170    #[test]
1171    fn list_and_array_suffixes_bind_left_to_right() {
1172        assert_eq!(
1173            LogicalType::parse("INTEGER[][3]").unwrap(),
1174            LogicalType::array(LogicalType::list(LogicalType::Integer), 3)
1175        );
1176        assert_eq!(
1177            LogicalType::parse("STRUCT(a INT)[]").unwrap(),
1178            LogicalType::list(LogicalType::Struct(vec![Field::new("a", LogicalType::Integer)]))
1179        );
1180    }
1181
1182    #[test]
1183    fn a_decimal_is_stored_in_the_narrowest_integer_that_holds_it() {
1184        assert_eq!(LogicalType::decimal(4, 2).unwrap().physical(), PhysicalType::Int16);
1185        assert_eq!(LogicalType::decimal(9, 2).unwrap().physical(), PhysicalType::Int32);
1186        assert_eq!(LogicalType::decimal(18, 2).unwrap().physical(), PhysicalType::Int64);
1187        assert_eq!(LogicalType::decimal(38, 2).unwrap().physical(), PhysicalType::Int128);
1188    }
1189
1190    #[test]
1191    fn a_decimal_outside_the_bounds_is_rejected_rather_than_clamped() {
1192        assert!(LogicalType::decimal(0, 0).is_err());
1193        assert!(LogicalType::decimal(39, 0).is_err());
1194        assert!(LogicalType::decimal(4, 5).is_err());
1195        assert!(LogicalType::parse("DECIMAL(39,0)").is_err());
1196    }
1197
1198    #[test]
1199    fn a_date_and_an_integer_share_a_layout_and_not_a_meaning() {
1200        assert_eq!(LogicalType::Date.physical(), LogicalType::Integer.physical());
1201        assert_ne!(LogicalType::Date, LogicalType::Integer);
1202        assert!(LogicalType::Date.is_temporal());
1203        assert!(!LogicalType::Date.is_numeric());
1204    }
1205
1206    #[test]
1207    fn nesting_reports_its_children_in_child_column_order() {
1208        let ty = LogicalType::map(LogicalType::Varchar, LogicalType::Integer);
1209        assert!(ty.is_nested());
1210        assert_eq!(ty.children(), vec![LogicalType::Varchar, LogicalType::Integer]);
1211        assert_eq!(LogicalType::Integer.children(), Vec::new());
1212    }
1213
1214    #[test]
1215    fn text_that_is_not_a_type_is_rejected_with_the_word_that_broke_it() {
1216        let error = LogicalType::parse("INTEGRE").unwrap_err();
1217        assert!(error.message().contains("INTEGRE"), "{error}");
1218        assert!(LogicalType::parse("INTEGER JUNK").is_err());
1219        assert!(LogicalType::parse("STRUCT(a)").is_err());
1220        assert!(LogicalType::parse("").is_err());
1221    }
1222}