1use std::fmt;
13
14use crate::error::{Error, Result};
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub struct Field {
19 pub name: String,
21 pub ty: LogicalType,
23}
24
25impl Field {
26 pub fn new(name: impl Into<String>, ty: LogicalType) -> Self {
28 Self { name: name.into(), ty }
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39#[non_exhaustive]
40pub enum LogicalType {
41 Null,
43 Boolean,
45 TinyInt,
47 SmallInt,
49 Integer,
51 BigInt,
53 HugeInt,
55 UTinyInt,
57 USmallInt,
59 UInteger,
61 UBigInt,
63 UHugeInt,
65 Float,
67 Double,
69 Decimal {
71 width: u8,
73 scale: u8,
75 },
76 Varchar,
78 Blob,
80 Bit,
82 Uuid,
84 Date,
86 Time,
88 TimeTz,
90 Timestamp,
92 TimestampS,
94 TimestampMs,
96 TimestampNs,
98 TimestampTz,
100 Interval,
102 List(Box<LogicalType>),
104 Array(Box<LogicalType>, u32),
106 Struct(Vec<Field>),
108 Map(Box<LogicalType>, Box<LogicalType>),
110 Union(Vec<Field>),
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
119#[non_exhaustive]
120pub enum PhysicalType {
121 Bool,
123 Int8,
125 Int16,
127 Int32,
129 Int64,
131 Int128,
133 UInt8,
135 UInt16,
137 UInt32,
139 UInt64,
141 UInt128,
143 Float32,
145 Float64,
147 Interval,
149 Varlen,
151 List,
153 Array,
155 Struct,
157 Empty,
159}
160
161impl LogicalType {
162 pub fn decimal(width: u8, scale: u8) -> Result<Self> {
169 if width == 0 || width > MAX_DECIMAL_WIDTH {
170 return Err(Error::binder(format!("Width must be between 1 and {MAX_DECIMAL_WIDTH}!")));
171 }
172 if scale > width {
173 return Err(Error::binder(format!(
174 "Scale cannot be bigger than width, {scale} is bigger than {width}"
175 )));
176 }
177 Ok(Self::Decimal { width, scale })
178 }
179
180 #[must_use]
182 pub fn list(element: Self) -> Self {
183 Self::List(Box::new(element))
184 }
185
186 #[must_use]
188 pub fn array(element: Self, length: u32) -> Self {
189 Self::Array(Box::new(element), length)
190 }
191
192 #[must_use]
194 pub fn map(key: Self, value: Self) -> Self {
195 Self::Map(Box::new(key), Box::new(value))
196 }
197
198 #[must_use]
200 pub fn physical(&self) -> PhysicalType {
201 match self {
202 Self::Null => PhysicalType::Empty,
203 Self::Boolean => PhysicalType::Bool,
204 Self::TinyInt => PhysicalType::Int8,
205 Self::SmallInt => PhysicalType::Int16,
206 Self::Integer | Self::Date => PhysicalType::Int32,
207 Self::BigInt
208 | Self::Time
209 | Self::TimeTz
210 | Self::Timestamp
211 | Self::TimestampS
212 | Self::TimestampMs
213 | Self::TimestampNs
214 | Self::TimestampTz => PhysicalType::Int64,
215 Self::HugeInt | Self::Uuid => PhysicalType::Int128,
216 Self::UTinyInt => PhysicalType::UInt8,
217 Self::USmallInt => PhysicalType::UInt16,
218 Self::UInteger => PhysicalType::UInt32,
219 Self::UBigInt => PhysicalType::UInt64,
220 Self::UHugeInt => PhysicalType::UInt128,
221 Self::Float => PhysicalType::Float32,
222 Self::Double => PhysicalType::Float64,
223 Self::Decimal { width, .. } => match width {
227 0..=4 => PhysicalType::Int16,
228 5..=9 => PhysicalType::Int32,
229 10..=18 => PhysicalType::Int64,
230 _ => PhysicalType::Int128,
231 },
232 Self::Varchar | Self::Blob | Self::Bit => PhysicalType::Varlen,
233 Self::Interval => PhysicalType::Interval,
234 Self::List(_) | Self::Map(_, _) => PhysicalType::List,
237 Self::Array(_, _) => PhysicalType::Array,
238 Self::Struct(_) | Self::Union(_) => PhysicalType::Struct,
239 }
240 }
241
242 #[must_use]
244 pub fn is_numeric(&self) -> bool {
245 self.is_integer() || matches!(self, Self::Float | Self::Double | Self::Decimal { .. })
246 }
247
248 #[must_use]
250 pub fn is_integer(&self) -> bool {
251 matches!(
252 self,
253 Self::TinyInt
254 | Self::SmallInt
255 | Self::Integer
256 | Self::BigInt
257 | Self::HugeInt
258 | Self::UTinyInt
259 | Self::USmallInt
260 | Self::UInteger
261 | Self::UBigInt
262 | Self::UHugeInt
263 )
264 }
265
266 #[must_use]
268 pub fn is_temporal(&self) -> bool {
269 matches!(
270 self,
271 Self::Date
272 | Self::Time
273 | Self::TimeTz
274 | Self::Timestamp
275 | Self::TimestampS
276 | Self::TimestampMs
277 | Self::TimestampNs
278 | Self::TimestampTz
279 | Self::Interval
280 )
281 }
282
283 #[must_use]
288 pub fn is_nested(&self) -> bool {
289 matches!(
290 self,
291 Self::List(_) | Self::Array(_, _) | Self::Struct(_) | Self::Map(_, _) | Self::Union(_)
292 )
293 }
294
295 #[must_use]
297 pub fn children(&self) -> Vec<Self> {
298 match self {
299 Self::List(inner) | Self::Array(inner, _) => vec![inner.as_ref().clone()],
300 Self::Map(key, value) => vec![key.as_ref().clone(), value.as_ref().clone()],
301 Self::Struct(fields) | Self::Union(fields) => {
302 fields.iter().map(|f| f.ty.clone()).collect()
303 }
304 _ => Vec::new(),
305 }
306 }
307
308 pub fn parse(text: &str) -> Result<Self> {
315 let tokens = lex(text)?;
316 let mut parser = TypeParser { tokens: &tokens, position: 0 };
317 let ty = parser.parse_type()?;
318 if parser.position != parser.tokens.len() {
319 return Err(Error::parser(format!("Type \"{text}\" has trailing text")));
320 }
321 Ok(ty)
322 }
323}
324
325pub const MAX_DECIMAL_WIDTH: u8 = 38;
328
329impl fmt::Display for LogicalType {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 match self {
332 Self::Null => f.write_str("\"NULL\""),
333 Self::Boolean => f.write_str("BOOLEAN"),
334 Self::TinyInt => f.write_str("TINYINT"),
335 Self::SmallInt => f.write_str("SMALLINT"),
336 Self::Integer => f.write_str("INTEGER"),
337 Self::BigInt => f.write_str("BIGINT"),
338 Self::HugeInt => f.write_str("HUGEINT"),
339 Self::UTinyInt => f.write_str("UTINYINT"),
340 Self::USmallInt => f.write_str("USMALLINT"),
341 Self::UInteger => f.write_str("UINTEGER"),
342 Self::UBigInt => f.write_str("UBIGINT"),
343 Self::UHugeInt => f.write_str("UHUGEINT"),
344 Self::Float => f.write_str("FLOAT"),
345 Self::Double => f.write_str("DOUBLE"),
346 Self::Decimal { width, scale } => write!(f, "DECIMAL({width},{scale})"),
347 Self::Varchar => f.write_str("VARCHAR"),
348 Self::Blob => f.write_str("BLOB"),
349 Self::Bit => f.write_str("BIT"),
350 Self::Uuid => f.write_str("UUID"),
351 Self::Date => f.write_str("DATE"),
352 Self::Time => f.write_str("TIME"),
353 Self::TimeTz => f.write_str("TIME WITH TIME ZONE"),
354 Self::Timestamp => f.write_str("TIMESTAMP"),
355 Self::TimestampS => f.write_str("TIMESTAMP_S"),
356 Self::TimestampMs => f.write_str("TIMESTAMP_MS"),
357 Self::TimestampNs => f.write_str("TIMESTAMP_NS"),
358 Self::TimestampTz => f.write_str("TIMESTAMP WITH TIME ZONE"),
359 Self::Interval => f.write_str("INTERVAL"),
360 Self::List(inner) => write!(f, "{inner}[]"),
361 Self::Array(inner, length) => write!(f, "{inner}[{length}]"),
362 Self::Map(key, value) => write!(f, "MAP({key}, {value})"),
363 Self::Struct(fields) => write_fields(f, "STRUCT", fields),
364 Self::Union(fields) => write_fields(f, "UNION", fields),
365 }
366 }
367}
368
369fn write_fields(f: &mut fmt::Formatter<'_>, keyword: &str, fields: &[Field]) -> fmt::Result {
370 f.write_str(keyword)?;
371 f.write_str("(")?;
372 for (index, field) in fields.iter().enumerate() {
373 if index > 0 {
374 f.write_str(", ")?;
375 }
376 write_identifier(f, &field.name)?;
377 write!(f, " {}", field.ty)?;
378 }
379 f.write_str(")")
380}
381
382fn write_identifier(f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
384 let plain = !name.is_empty()
385 && name.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
386 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
387 if plain {
388 f.write_str(name)
389 } else {
390 f.write_str("\"")?;
391 for c in name.chars() {
392 if c == '"' {
393 f.write_str("\"\"")?;
394 } else {
395 write!(f, "{c}")?;
396 }
397 }
398 f.write_str("\"")
399 }
400}
401
402#[derive(Debug, Clone, PartialEq, Eq)]
403enum Token {
404 Word(String),
405 Quoted(String),
406 Number(u32),
407 LeftParen,
408 RightParen,
409 LeftBracket,
410 RightBracket,
411 Comma,
412}
413
414fn lex(text: &str) -> Result<Vec<Token>> {
415 let mut tokens = Vec::new();
416 let chars: Vec<char> = text.chars().collect();
417 let mut i = 0;
418 while i < chars.len() {
419 let c = chars[i];
420 match c {
421 c if c.is_whitespace() => i += 1,
422 '(' => {
423 tokens.push(Token::LeftParen);
424 i += 1;
425 }
426 ')' => {
427 tokens.push(Token::RightParen);
428 i += 1;
429 }
430 '[' => {
431 tokens.push(Token::LeftBracket);
432 i += 1;
433 }
434 ']' => {
435 tokens.push(Token::RightBracket);
436 i += 1;
437 }
438 ',' => {
439 tokens.push(Token::Comma);
440 i += 1;
441 }
442 '"' => {
443 let mut name = String::new();
444 i += 1;
445 loop {
446 let Some(&c) = chars.get(i) else {
447 return Err(Error::parser(format!(
448 "Type \"{text}\" has an unterminated quoted name"
449 )));
450 };
451 i += 1;
452 if c == '"' {
453 if chars.get(i) == Some(&'"') {
454 name.push('"');
455 i += 1;
456 continue;
457 }
458 break;
459 }
460 name.push(c);
461 }
462 tokens.push(Token::Quoted(name));
463 }
464 c if c.is_ascii_digit() => {
465 let start = i;
466 while chars.get(i).is_some_and(char::is_ascii_digit) {
467 i += 1;
468 }
469 let digits: String = chars[start..i].iter().collect();
470 let number = digits.parse::<u32>().map_err(|_| {
471 Error::parser(format!("Type \"{text}\" has a number that is too large"))
472 })?;
473 tokens.push(Token::Number(number));
474 }
475 c if c.is_alphabetic() || c == '_' => {
476 let start = i;
477 while chars.get(i).is_some_and(|&c| c.is_alphanumeric() || c == '_') {
478 i += 1;
479 }
480 tokens.push(Token::Word(chars[start..i].iter().collect()));
481 }
482 other => {
483 return Err(Error::parser(format!(
484 "Type \"{text}\" has an unexpected character {other:?}"
485 )));
486 }
487 }
488 }
489 Ok(tokens)
490}
491
492struct TypeParser<'a> {
493 tokens: &'a [Token],
494 position: usize,
495}
496
497impl TypeParser<'_> {
498 fn peek(&self) -> Option<&Token> {
499 self.tokens.get(self.position)
500 }
501
502 fn eat(&mut self, token: &Token) -> bool {
503 if self.peek() == Some(token) {
504 self.position += 1;
505 true
506 } else {
507 false
508 }
509 }
510
511 fn eat_word(&mut self, word: &str) -> bool {
513 match self.peek() {
514 Some(Token::Word(found)) if found.eq_ignore_ascii_case(word) => {
515 self.position += 1;
516 true
517 }
518 _ => false,
519 }
520 }
521
522 fn parse_type(&mut self) -> Result<LogicalType> {
523 let mut ty = self.parse_base()?;
524 loop {
526 if !self.eat(&Token::LeftBracket) {
527 break;
528 }
529 if let Some(&Token::Number(length)) = self.peek() {
530 self.position += 1;
531 expect(self.eat(&Token::RightBracket), "]")?;
532 ty = LogicalType::array(ty, length);
533 } else {
534 expect(self.eat(&Token::RightBracket), "]")?;
535 ty = LogicalType::list(ty);
536 }
537 }
538 Ok(ty)
539 }
540
541 fn parse_base(&mut self) -> Result<LogicalType> {
542 let word = match self.peek().cloned() {
545 Some(Token::Word(word) | Token::Quoted(word)) => {
546 self.position += 1;
547 word
548 }
549 _ => return Err(Error::parser("Expected a type name".to_string())),
550 };
551 let upper = word.to_ascii_uppercase();
552
553 match upper.as_str() {
554 "STRUCT" | "ROW" => return self.parse_fields().map(LogicalType::Struct),
555 "UNION" => return self.parse_fields().map(LogicalType::Union),
556 "MAP" => {
557 expect(self.eat(&Token::LeftParen), "(")?;
558 let key = self.parse_type()?;
559 expect(self.eat(&Token::Comma), ",")?;
560 let value = self.parse_type()?;
561 expect(self.eat(&Token::RightParen), ")")?;
562 return Ok(LogicalType::map(key, value));
563 }
564 "DECIMAL" | "NUMERIC" | "DEC" => {
565 if !self.eat(&Token::LeftParen) {
566 return LogicalType::decimal(18, 3);
569 }
570 let width = self.parse_number()?;
571 let scale = if self.eat(&Token::Comma) { self.parse_number()? } else { 0 };
572 expect(self.eat(&Token::RightParen), ")")?;
573 let narrow = |n: u32| u8::try_from(n).unwrap_or(u8::MAX);
574 return LogicalType::decimal(narrow(width), narrow(scale));
575 }
576 "DOUBLE" => {
579 self.eat_word("PRECISION");
580 return Ok(LogicalType::Double);
581 }
582 "CHARACTER" => {
583 self.eat_word("VARYING");
584 self.eat_length_modifier()?;
585 return Ok(LogicalType::Varchar);
586 }
587 "TIME" | "TIMESTAMP" => {
588 let with_zone = self.eat_time_zone_suffix();
589 return Ok(match (upper.as_str(), with_zone) {
590 ("TIME", false) => LogicalType::Time,
591 ("TIME", true) => LogicalType::TimeTz,
592 (_, false) => LogicalType::Timestamp,
593 (_, true) => LogicalType::TimestampTz,
594 });
595 }
596 _ => {}
597 }
598
599 self.eat_length_modifier()?;
602 alias(&upper).ok_or_else(|| Error::parser(format!("Unrecognized type name \"{word}\"")))
603 }
604
605 fn eat_time_zone_suffix(&mut self) -> bool {
607 let start = self.position;
608 let with = if self.eat_word("WITH") {
609 true
610 } else if self.eat_word("WITHOUT") {
611 false
612 } else {
613 return false;
614 };
615 if self.eat_word("TIME") && self.eat_word("ZONE") {
616 with
617 } else {
618 self.position = start;
619 false
620 }
621 }
622
623 fn eat_length_modifier(&mut self) -> Result<()> {
624 if self.eat(&Token::LeftParen) {
625 self.parse_number()?;
626 expect(self.eat(&Token::RightParen), ")")?;
627 }
628 Ok(())
629 }
630
631 fn parse_fields(&mut self) -> Result<Vec<Field>> {
632 expect(self.eat(&Token::LeftParen), "(")?;
633 let mut fields = Vec::new();
634 if self.eat(&Token::RightParen) {
635 return Ok(fields);
636 }
637 loop {
638 let name = match self.peek().cloned() {
639 Some(Token::Word(name) | Token::Quoted(name)) => {
640 self.position += 1;
641 name
642 }
643 _ => return Err(Error::parser("Expected a field name".to_string())),
644 };
645 let ty = self.parse_type()?;
646 fields.push(Field::new(name, ty));
647 if self.eat(&Token::Comma) {
648 continue;
649 }
650 expect(self.eat(&Token::RightParen), ")")?;
651 return Ok(fields);
652 }
653 }
654
655 fn parse_number(&mut self) -> Result<u32> {
656 match self.peek() {
657 Some(&Token::Number(n)) => {
658 self.position += 1;
659 Ok(n)
660 }
661 _ => Err(Error::parser("Expected a number".to_string())),
662 }
663 }
664}
665
666fn expect(matched: bool, what: &str) -> Result<()> {
667 if matched { Ok(()) } else { Err(Error::parser(format!("Expected \"{what}\""))) }
668}
669
670fn alias(upper: &str) -> Option<LogicalType> {
675 Some(match upper {
676 "NULL" => LogicalType::Null,
677 "BOOLEAN" | "BOOL" | "LOGICAL" => LogicalType::Boolean,
678 "TINYINT" | "INT1" => LogicalType::TinyInt,
679 "SMALLINT" | "INT2" | "SHORT" => LogicalType::SmallInt,
680 "INTEGER" | "INT" | "INT4" | "SIGNED" => LogicalType::Integer,
681 "BIGINT" | "INT8" | "LONG" => LogicalType::BigInt,
682 "HUGEINT" | "INT128" => LogicalType::HugeInt,
683 "UTINYINT" | "UINT1" => LogicalType::UTinyInt,
684 "USMALLINT" | "UINT2" => LogicalType::USmallInt,
685 "UINTEGER" | "UINT4" => LogicalType::UInteger,
686 "UBIGINT" | "UINT8" => LogicalType::UBigInt,
687 "UHUGEINT" | "UINT128" => LogicalType::UHugeInt,
688 "FLOAT" | "FLOAT4" | "REAL" => LogicalType::Float,
689 "FLOAT8" => LogicalType::Double,
690 "VARCHAR" | "CHAR" | "BPCHAR" | "TEXT" | "STRING" => LogicalType::Varchar,
691 "BLOB" | "BYTEA" | "BINARY" | "VARBINARY" => LogicalType::Blob,
692 "BIT" | "BITSTRING" => LogicalType::Bit,
693 "UUID" | "GUID" => LogicalType::Uuid,
694 "DATE" => LogicalType::Date,
695 "TIMETZ" => LogicalType::TimeTz,
696 "DATETIME" => LogicalType::Timestamp,
697 "TIMESTAMP_S" | "TIMESTAMP_SEC" | "TIMESTAMP_SECONDS" => LogicalType::TimestampS,
698 "TIMESTAMP_MS" | "TIMESTAMP_MILLISECONDS" => LogicalType::TimestampMs,
699 "TIMESTAMP_NS" | "TIMESTAMP_NANOSECONDS" => LogicalType::TimestampNs,
700 "TIMESTAMPTZ" => LogicalType::TimestampTz,
701 "INTERVAL" => LogicalType::Interval,
702 _ => return None,
703 })
704}
705
706#[cfg(test)]
707mod tests {
708 use super::{Field, LogicalType, PhysicalType};
709
710 fn every_type() -> Vec<LogicalType> {
713 vec![
714 LogicalType::Null,
715 LogicalType::Boolean,
716 LogicalType::TinyInt,
717 LogicalType::SmallInt,
718 LogicalType::Integer,
719 LogicalType::BigInt,
720 LogicalType::HugeInt,
721 LogicalType::UTinyInt,
722 LogicalType::USmallInt,
723 LogicalType::UInteger,
724 LogicalType::UBigInt,
725 LogicalType::UHugeInt,
726 LogicalType::Float,
727 LogicalType::Double,
728 LogicalType::Decimal { width: 18, scale: 3 },
729 LogicalType::Decimal { width: 38, scale: 0 },
730 LogicalType::Varchar,
731 LogicalType::Blob,
732 LogicalType::Bit,
733 LogicalType::Uuid,
734 LogicalType::Date,
735 LogicalType::Time,
736 LogicalType::TimeTz,
737 LogicalType::Timestamp,
738 LogicalType::TimestampS,
739 LogicalType::TimestampMs,
740 LogicalType::TimestampNs,
741 LogicalType::TimestampTz,
742 LogicalType::Interval,
743 LogicalType::list(LogicalType::Integer),
744 LogicalType::list(LogicalType::list(LogicalType::Varchar)),
745 LogicalType::array(LogicalType::Double, 3),
746 LogicalType::map(LogicalType::Varchar, LogicalType::Integer),
747 LogicalType::Struct(vec![
748 Field::new("a", LogicalType::Integer),
749 Field::new("b", LogicalType::list(LogicalType::Varchar)),
750 ]),
751 LogicalType::Union(vec![
752 Field::new("num", LogicalType::Integer),
753 Field::new("str", LogicalType::Varchar),
754 ]),
755 ]
756 }
757
758 #[test]
759 fn every_type_survives_being_printed_and_read_back() {
760 for ty in every_type() {
764 let printed = ty.to_string();
765 let parsed = LogicalType::parse(&printed)
766 .unwrap_or_else(|e| panic!("{printed} did not parse back: {e}"));
767 assert_eq!(parsed, ty, "{printed} parsed to something else");
768 }
769 }
770
771 #[test]
772 fn a_field_name_that_needs_quoting_gets_quoted() {
773 let ty = LogicalType::Struct(vec![
774 Field::new("plain", LogicalType::Integer),
775 Field::new("has space", LogicalType::Integer),
776 Field::new("has\"quote", LogicalType::Integer),
777 Field::new("2leading", LogicalType::Integer),
778 ]);
779 assert_eq!(
780 ty.to_string(),
781 "STRUCT(plain INTEGER, \"has space\" INTEGER, \"has\"\"quote\" INTEGER, \
782 \"2leading\" INTEGER)"
783 );
784 assert_eq!(LogicalType::parse(&ty.to_string()).unwrap(), ty);
785 }
786
787 #[test]
788 fn the_duckdb_aliases_resolve() {
789 let cases = [
790 ("int4", LogicalType::Integer),
791 ("INT", LogicalType::Integer),
792 ("signed", LogicalType::Integer),
793 ("int8", LogicalType::BigInt),
794 ("float4", LogicalType::Float),
795 ("float8", LogicalType::Double),
796 ("double precision", LogicalType::Double),
797 ("text", LogicalType::Varchar),
798 ("varchar(10)", LogicalType::Varchar),
799 ("character varying(255)", LogicalType::Varchar),
800 ("bool", LogicalType::Boolean),
801 ("datetime", LogicalType::Timestamp),
802 ("numeric(9, 2)", LogicalType::Decimal { width: 9, scale: 2 }),
803 ("decimal", LogicalType::Decimal { width: 18, scale: 3 }),
804 ("timestamp without time zone", LogicalType::Timestamp),
805 ("timestamp with time zone", LogicalType::TimestampTz),
806 ("time with time zone", LogicalType::TimeTz),
807 ];
808 for (text, expected) in cases {
809 assert_eq!(LogicalType::parse(text).unwrap(), expected, "{text}");
810 }
811 }
812
813 #[test]
814 fn list_and_array_suffixes_bind_left_to_right() {
815 assert_eq!(
816 LogicalType::parse("INTEGER[][3]").unwrap(),
817 LogicalType::array(LogicalType::list(LogicalType::Integer), 3)
818 );
819 assert_eq!(
820 LogicalType::parse("STRUCT(a INT)[]").unwrap(),
821 LogicalType::list(LogicalType::Struct(vec![Field::new("a", LogicalType::Integer)]))
822 );
823 }
824
825 #[test]
826 fn a_decimal_is_stored_in_the_narrowest_integer_that_holds_it() {
827 assert_eq!(LogicalType::decimal(4, 2).unwrap().physical(), PhysicalType::Int16);
828 assert_eq!(LogicalType::decimal(9, 2).unwrap().physical(), PhysicalType::Int32);
829 assert_eq!(LogicalType::decimal(18, 2).unwrap().physical(), PhysicalType::Int64);
830 assert_eq!(LogicalType::decimal(38, 2).unwrap().physical(), PhysicalType::Int128);
831 }
832
833 #[test]
834 fn a_decimal_outside_the_bounds_is_rejected_rather_than_clamped() {
835 assert!(LogicalType::decimal(0, 0).is_err());
836 assert!(LogicalType::decimal(39, 0).is_err());
837 assert!(LogicalType::decimal(4, 5).is_err());
838 assert!(LogicalType::parse("DECIMAL(39,0)").is_err());
839 }
840
841 #[test]
842 fn a_date_and_an_integer_share_a_layout_and_not_a_meaning() {
843 assert_eq!(LogicalType::Date.physical(), LogicalType::Integer.physical());
844 assert_ne!(LogicalType::Date, LogicalType::Integer);
845 assert!(LogicalType::Date.is_temporal());
846 assert!(!LogicalType::Date.is_numeric());
847 }
848
849 #[test]
850 fn nesting_reports_its_children_in_child_column_order() {
851 let ty = LogicalType::map(LogicalType::Varchar, LogicalType::Integer);
852 assert!(ty.is_nested());
853 assert_eq!(ty.children(), vec![LogicalType::Varchar, LogicalType::Integer]);
854 assert_eq!(LogicalType::Integer.children(), Vec::new());
855 }
856
857 #[test]
858 fn text_that_is_not_a_type_is_rejected_with_the_word_that_broke_it() {
859 let error = LogicalType::parse("INTEGRE").unwrap_err();
860 assert!(error.message().contains("INTEGRE"), "{error}");
861 assert!(LogicalType::parse("INTEGER JUNK").is_err());
862 assert!(LogicalType::parse("STRUCT(a)").is_err());
863 assert!(LogicalType::parse("").is_err());
864 }
865}