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]
313 pub fn promote(&self, other: &Self) -> Option<Self> {
314 if self == other {
315 return Some(self.clone());
316 }
317 match (self, other) {
318 (Self::Null, ty) | (ty, Self::Null) => Some(ty.clone()),
319 (Self::List(left), Self::List(right)) => Some(Self::list(left.promote(right)?)),
320 _ if self.is_numeric() && other.is_numeric() => {
321 Some(promote_numeric(self.clone(), other.clone()))
322 }
323 _ if self.is_temporal() && other.is_temporal() => {
326 match (rank_temporal(self), rank_temporal(other)) {
327 (Some(left), Some(right)) => {
328 Some(if left >= right { self.clone() } else { other.clone() })
329 }
330 _ => None,
331 }
332 }
333 _ => None,
334 }
335 }
336
337 #[must_use]
339 pub fn children(&self) -> Vec<Self> {
340 match self {
341 Self::List(inner) | Self::Array(inner, _) => vec![inner.as_ref().clone()],
342 Self::Map(key, value) => vec![key.as_ref().clone(), value.as_ref().clone()],
343 Self::Struct(fields) | Self::Union(fields) => {
344 fields.iter().map(|f| f.ty.clone()).collect()
345 }
346 _ => Vec::new(),
347 }
348 }
349
350 pub fn parse(text: &str) -> Result<Self> {
357 let tokens = lex(text)?;
358 let mut parser = TypeParser { tokens: &tokens, position: 0 };
359 let ty = parser.parse_type()?;
360 if parser.position != parser.tokens.len() {
361 return Err(Error::parser(format!("Type \"{text}\" has trailing text")));
362 }
363 Ok(ty)
364 }
365}
366
367pub const MAX_DECIMAL_WIDTH: u8 = 38;
370
371impl fmt::Display for LogicalType {
372 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373 match self {
374 Self::Null => f.write_str("\"NULL\""),
375 Self::Boolean => f.write_str("BOOLEAN"),
376 Self::TinyInt => f.write_str("TINYINT"),
377 Self::SmallInt => f.write_str("SMALLINT"),
378 Self::Integer => f.write_str("INTEGER"),
379 Self::BigInt => f.write_str("BIGINT"),
380 Self::HugeInt => f.write_str("HUGEINT"),
381 Self::UTinyInt => f.write_str("UTINYINT"),
382 Self::USmallInt => f.write_str("USMALLINT"),
383 Self::UInteger => f.write_str("UINTEGER"),
384 Self::UBigInt => f.write_str("UBIGINT"),
385 Self::UHugeInt => f.write_str("UHUGEINT"),
386 Self::Float => f.write_str("FLOAT"),
387 Self::Double => f.write_str("DOUBLE"),
388 Self::Decimal { width, scale } => write!(f, "DECIMAL({width},{scale})"),
389 Self::Varchar => f.write_str("VARCHAR"),
390 Self::Blob => f.write_str("BLOB"),
391 Self::Bit => f.write_str("BIT"),
392 Self::Uuid => f.write_str("UUID"),
393 Self::Date => f.write_str("DATE"),
394 Self::Time => f.write_str("TIME"),
395 Self::TimeTz => f.write_str("TIME WITH TIME ZONE"),
396 Self::Timestamp => f.write_str("TIMESTAMP"),
397 Self::TimestampS => f.write_str("TIMESTAMP_S"),
398 Self::TimestampMs => f.write_str("TIMESTAMP_MS"),
399 Self::TimestampNs => f.write_str("TIMESTAMP_NS"),
400 Self::TimestampTz => f.write_str("TIMESTAMP WITH TIME ZONE"),
401 Self::Interval => f.write_str("INTERVAL"),
402 Self::List(inner) => write!(f, "{inner}[]"),
403 Self::Array(inner, length) => write!(f, "{inner}[{length}]"),
404 Self::Map(key, value) => write!(f, "MAP({key}, {value})"),
405 Self::Struct(fields) => write_fields(f, "STRUCT", fields),
406 Self::Union(fields) => write_fields(f, "UNION", fields),
407 }
408 }
409}
410
411fn rank_numeric(ty: &LogicalType) -> u8 {
418 match ty {
419 LogicalType::TinyInt => 1,
420 LogicalType::UTinyInt => 2,
421 LogicalType::SmallInt => 3,
422 LogicalType::USmallInt => 4,
423 LogicalType::Integer => 5,
424 LogicalType::UInteger => 6,
425 LogicalType::BigInt => 7,
426 LogicalType::UBigInt => 8,
427 LogicalType::HugeInt => 9,
428 LogicalType::UHugeInt => 10,
429 LogicalType::Decimal { .. } => 11,
430 LogicalType::Float => 12,
431 LogicalType::Double => 13,
432 _ => 0,
433 }
434}
435
436fn integer_shape(ty: &LogicalType) -> (bool, u8) {
438 match ty {
439 LogicalType::TinyInt => (true, 8),
440 LogicalType::SmallInt => (true, 16),
441 LogicalType::Integer => (true, 32),
442 LogicalType::BigInt => (true, 64),
443 LogicalType::HugeInt => (true, 128),
444 LogicalType::UTinyInt => (false, 8),
445 LogicalType::USmallInt => (false, 16),
446 LogicalType::UInteger => (false, 32),
447 LogicalType::UBigInt => (false, 64),
448 _ => (false, 128),
449 }
450}
451
452fn integer_of(signed: bool, bits: u8) -> Option<LogicalType> {
454 Some(match (signed, bits) {
455 (true, 8) => LogicalType::TinyInt,
456 (true, 16) => LogicalType::SmallInt,
457 (true, 32) => LogicalType::Integer,
458 (true, 64) => LogicalType::BigInt,
459 (true, 128) => LogicalType::HugeInt,
460 (false, 8) => LogicalType::UTinyInt,
461 (false, 16) => LogicalType::USmallInt,
462 (false, 32) => LogicalType::UInteger,
463 (false, 64) => LogicalType::UBigInt,
464 (false, 128) => LogicalType::UHugeInt,
465 _ => return None,
466 })
467}
468
469fn promote_integers(left: &LogicalType, right: &LogicalType) -> LogicalType {
477 let (left_signed, left_bits) = integer_shape(left);
478 let (right_signed, right_bits) = integer_shape(right);
479 if left_signed == right_signed {
480 return if left_bits >= right_bits { left.clone() } else { right.clone() };
481 }
482 let (signed_bits, unsigned_bits) =
483 if left_signed { (left_bits, right_bits) } else { (right_bits, left_bits) };
484 let wanted = signed_bits.max(unsigned_bits.saturating_mul(2));
485 integer_of(true, wanted).unwrap_or(LogicalType::Double)
486}
487
488fn promote_numeric(left: LogicalType, right: LogicalType) -> LogicalType {
490 if let (
494 LogicalType::Decimal { width: left_width, scale: left_scale },
495 LogicalType::Decimal { width: right_width, scale: right_scale },
496 ) = (&left, &right)
497 {
498 let scale = (*left_scale).max(*right_scale);
499 let integral =
500 left_width.saturating_sub(*left_scale).max(right_width.saturating_sub(*right_scale));
501 let width = integral.saturating_add(scale).min(MAX_DECIMAL_WIDTH);
502 return LogicalType::Decimal { width, scale: scale.min(width) };
503 }
504 let widened = match (&left, &right) {
507 (LogicalType::Decimal { width, scale }, other)
508 | (other, LogicalType::Decimal { width, scale })
509 if other.is_integer() =>
510 {
511 let needed = decimal_digits(other).saturating_add(*scale).min(MAX_DECIMAL_WIDTH);
512 Some(LogicalType::Decimal { width: (*width).max(needed), scale: *scale })
513 }
514 _ => None,
515 };
516 if let Some(ty) = widened {
517 return ty;
518 }
519 if left.is_integer() && right.is_integer() {
520 return promote_integers(&left, &right);
521 }
522 if rank_numeric(&left) >= rank_numeric(&right) { left } else { right }
523}
524
525fn decimal_digits(ty: &LogicalType) -> u8 {
527 match ty {
528 LogicalType::TinyInt | LogicalType::UTinyInt => 3,
529 LogicalType::SmallInt | LogicalType::USmallInt => 5,
530 LogicalType::Integer | LogicalType::UInteger => 10,
531 LogicalType::BigInt | LogicalType::UBigInt => 20,
532 _ => MAX_DECIMAL_WIDTH,
533 }
534}
535
536fn rank_temporal(ty: &LogicalType) -> Option<u8> {
542 match ty {
543 LogicalType::Date => Some(1),
544 LogicalType::TimestampS => Some(2),
545 LogicalType::TimestampMs => Some(3),
546 LogicalType::Timestamp => Some(4),
547 LogicalType::TimestampNs => Some(5),
548 LogicalType::TimestampTz => Some(6),
549 _ => None,
550 }
551}
552
553fn write_fields(f: &mut fmt::Formatter<'_>, keyword: &str, fields: &[Field]) -> fmt::Result {
554 f.write_str(keyword)?;
555 f.write_str("(")?;
556 for (index, field) in fields.iter().enumerate() {
557 if index > 0 {
558 f.write_str(", ")?;
559 }
560 write_identifier(f, &field.name)?;
561 write!(f, " {}", field.ty)?;
562 }
563 f.write_str(")")
564}
565
566fn write_identifier(f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
568 let plain = !name.is_empty()
569 && name.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
570 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
571 if plain {
572 f.write_str(name)
573 } else {
574 f.write_str("\"")?;
575 for c in name.chars() {
576 if c == '"' {
577 f.write_str("\"\"")?;
578 } else {
579 write!(f, "{c}")?;
580 }
581 }
582 f.write_str("\"")
583 }
584}
585
586#[derive(Debug, Clone, PartialEq, Eq)]
587enum Token {
588 Word(String),
589 Quoted(String),
590 Number(u32),
591 LeftParen,
592 RightParen,
593 LeftBracket,
594 RightBracket,
595 Comma,
596}
597
598fn lex(text: &str) -> Result<Vec<Token>> {
599 let mut tokens = Vec::new();
600 let chars: Vec<char> = text.chars().collect();
601 let mut i = 0;
602 while i < chars.len() {
603 let c = chars[i];
604 match c {
605 c if c.is_whitespace() => i += 1,
606 '(' => {
607 tokens.push(Token::LeftParen);
608 i += 1;
609 }
610 ')' => {
611 tokens.push(Token::RightParen);
612 i += 1;
613 }
614 '[' => {
615 tokens.push(Token::LeftBracket);
616 i += 1;
617 }
618 ']' => {
619 tokens.push(Token::RightBracket);
620 i += 1;
621 }
622 ',' => {
623 tokens.push(Token::Comma);
624 i += 1;
625 }
626 '"' => {
627 let mut name = String::new();
628 i += 1;
629 loop {
630 let Some(&c) = chars.get(i) else {
631 return Err(Error::parser(format!(
632 "Type \"{text}\" has an unterminated quoted name"
633 )));
634 };
635 i += 1;
636 if c == '"' {
637 if chars.get(i) == Some(&'"') {
638 name.push('"');
639 i += 1;
640 continue;
641 }
642 break;
643 }
644 name.push(c);
645 }
646 tokens.push(Token::Quoted(name));
647 }
648 c if c.is_ascii_digit() => {
649 let start = i;
650 while chars.get(i).is_some_and(char::is_ascii_digit) {
651 i += 1;
652 }
653 let digits: String = chars[start..i].iter().collect();
654 let number = digits.parse::<u32>().map_err(|_| {
655 Error::parser(format!("Type \"{text}\" has a number that is too large"))
656 })?;
657 tokens.push(Token::Number(number));
658 }
659 c if c.is_alphabetic() || c == '_' => {
660 let start = i;
661 while chars.get(i).is_some_and(|&c| c.is_alphanumeric() || c == '_') {
662 i += 1;
663 }
664 tokens.push(Token::Word(chars[start..i].iter().collect()));
665 }
666 other => {
667 return Err(Error::parser(format!(
668 "Type \"{text}\" has an unexpected character {other:?}"
669 )));
670 }
671 }
672 }
673 Ok(tokens)
674}
675
676struct TypeParser<'a> {
677 tokens: &'a [Token],
678 position: usize,
679}
680
681impl TypeParser<'_> {
682 fn peek(&self) -> Option<&Token> {
683 self.tokens.get(self.position)
684 }
685
686 fn eat(&mut self, token: &Token) -> bool {
687 if self.peek() == Some(token) {
688 self.position += 1;
689 true
690 } else {
691 false
692 }
693 }
694
695 fn eat_word(&mut self, word: &str) -> bool {
697 match self.peek() {
698 Some(Token::Word(found)) if found.eq_ignore_ascii_case(word) => {
699 self.position += 1;
700 true
701 }
702 _ => false,
703 }
704 }
705
706 fn parse_type(&mut self) -> Result<LogicalType> {
707 let mut ty = self.parse_base()?;
708 loop {
710 if !self.eat(&Token::LeftBracket) {
711 break;
712 }
713 if let Some(&Token::Number(length)) = self.peek() {
714 self.position += 1;
715 expect(self.eat(&Token::RightBracket), "]")?;
716 ty = LogicalType::array(ty, length);
717 } else {
718 expect(self.eat(&Token::RightBracket), "]")?;
719 ty = LogicalType::list(ty);
720 }
721 }
722 Ok(ty)
723 }
724
725 fn parse_base(&mut self) -> Result<LogicalType> {
726 let word = match self.peek().cloned() {
729 Some(Token::Word(word) | Token::Quoted(word)) => {
730 self.position += 1;
731 word
732 }
733 _ => return Err(Error::parser("Expected a type name".to_string())),
734 };
735 let upper = word.to_ascii_uppercase();
736
737 match upper.as_str() {
738 "STRUCT" | "ROW" => return self.parse_fields().map(LogicalType::Struct),
739 "UNION" => return self.parse_fields().map(LogicalType::Union),
740 "MAP" => {
741 expect(self.eat(&Token::LeftParen), "(")?;
742 let key = self.parse_type()?;
743 expect(self.eat(&Token::Comma), ",")?;
744 let value = self.parse_type()?;
745 expect(self.eat(&Token::RightParen), ")")?;
746 return Ok(LogicalType::map(key, value));
747 }
748 "DECIMAL" | "NUMERIC" | "DEC" => {
749 if !self.eat(&Token::LeftParen) {
750 return LogicalType::decimal(18, 3);
753 }
754 let width = self.parse_number()?;
755 let scale = if self.eat(&Token::Comma) { self.parse_number()? } else { 0 };
756 expect(self.eat(&Token::RightParen), ")")?;
757 let narrow = |n: u32| u8::try_from(n).unwrap_or(u8::MAX);
758 return LogicalType::decimal(narrow(width), narrow(scale));
759 }
760 "DOUBLE" => {
763 self.eat_word("PRECISION");
764 return Ok(LogicalType::Double);
765 }
766 "CHARACTER" => {
767 self.eat_word("VARYING");
768 self.eat_length_modifier()?;
769 return Ok(LogicalType::Varchar);
770 }
771 "TIME" | "TIMESTAMP" => {
772 let with_zone = self.eat_time_zone_suffix();
773 return Ok(match (upper.as_str(), with_zone) {
774 ("TIME", false) => LogicalType::Time,
775 ("TIME", true) => LogicalType::TimeTz,
776 (_, false) => LogicalType::Timestamp,
777 (_, true) => LogicalType::TimestampTz,
778 });
779 }
780 _ => {}
781 }
782
783 self.eat_length_modifier()?;
786 alias(&upper).ok_or_else(|| Error::parser(format!("Unrecognized type name \"{word}\"")))
787 }
788
789 fn eat_time_zone_suffix(&mut self) -> bool {
791 let start = self.position;
792 let with = if self.eat_word("WITH") {
793 true
794 } else if self.eat_word("WITHOUT") {
795 false
796 } else {
797 return false;
798 };
799 if self.eat_word("TIME") && self.eat_word("ZONE") {
800 with
801 } else {
802 self.position = start;
803 false
804 }
805 }
806
807 fn eat_length_modifier(&mut self) -> Result<()> {
808 if self.eat(&Token::LeftParen) {
809 self.parse_number()?;
810 expect(self.eat(&Token::RightParen), ")")?;
811 }
812 Ok(())
813 }
814
815 fn parse_fields(&mut self) -> Result<Vec<Field>> {
816 expect(self.eat(&Token::LeftParen), "(")?;
817 let mut fields = Vec::new();
818 if self.eat(&Token::RightParen) {
819 return Ok(fields);
820 }
821 loop {
822 let name = match self.peek().cloned() {
823 Some(Token::Word(name) | Token::Quoted(name)) => {
824 self.position += 1;
825 name
826 }
827 _ => return Err(Error::parser("Expected a field name".to_string())),
828 };
829 let ty = self.parse_type()?;
830 fields.push(Field::new(name, ty));
831 if self.eat(&Token::Comma) {
832 continue;
833 }
834 expect(self.eat(&Token::RightParen), ")")?;
835 return Ok(fields);
836 }
837 }
838
839 fn parse_number(&mut self) -> Result<u32> {
840 match self.peek() {
841 Some(&Token::Number(n)) => {
842 self.position += 1;
843 Ok(n)
844 }
845 _ => Err(Error::parser("Expected a number".to_string())),
846 }
847 }
848}
849
850fn expect(matched: bool, what: &str) -> Result<()> {
851 if matched { Ok(()) } else { Err(Error::parser(format!("Expected \"{what}\""))) }
852}
853
854fn alias(upper: &str) -> Option<LogicalType> {
859 Some(match upper {
860 "NULL" => LogicalType::Null,
861 "BOOLEAN" | "BOOL" | "LOGICAL" => LogicalType::Boolean,
862 "TINYINT" | "INT1" => LogicalType::TinyInt,
863 "SMALLINT" | "INT2" | "SHORT" => LogicalType::SmallInt,
864 "INTEGER" | "INT" | "INT4" | "SIGNED" => LogicalType::Integer,
865 "BIGINT" | "INT8" | "LONG" => LogicalType::BigInt,
866 "HUGEINT" | "INT128" => LogicalType::HugeInt,
867 "UTINYINT" | "UINT1" => LogicalType::UTinyInt,
868 "USMALLINT" | "UINT2" => LogicalType::USmallInt,
869 "UINTEGER" | "UINT4" => LogicalType::UInteger,
870 "UBIGINT" | "UINT8" => LogicalType::UBigInt,
871 "UHUGEINT" | "UINT128" => LogicalType::UHugeInt,
872 "FLOAT" | "FLOAT4" | "REAL" => LogicalType::Float,
873 "FLOAT8" => LogicalType::Double,
874 "VARCHAR" | "CHAR" | "BPCHAR" | "TEXT" | "STRING" => LogicalType::Varchar,
875 "BLOB" | "BYTEA" | "BINARY" | "VARBINARY" => LogicalType::Blob,
876 "BIT" | "BITSTRING" => LogicalType::Bit,
877 "UUID" | "GUID" => LogicalType::Uuid,
878 "DATE" => LogicalType::Date,
879 "TIMETZ" => LogicalType::TimeTz,
880 "DATETIME" => LogicalType::Timestamp,
881 "TIMESTAMP_S" | "TIMESTAMP_SEC" | "TIMESTAMP_SECONDS" => LogicalType::TimestampS,
882 "TIMESTAMP_MS" | "TIMESTAMP_MILLISECONDS" => LogicalType::TimestampMs,
883 "TIMESTAMP_NS" | "TIMESTAMP_NANOSECONDS" => LogicalType::TimestampNs,
884 "TIMESTAMPTZ" => LogicalType::TimestampTz,
885 "INTERVAL" => LogicalType::Interval,
886 _ => return None,
887 })
888}
889
890#[cfg(test)]
891mod promotion_tests {
892 use super::LogicalType;
893
894 #[test]
895 fn a_type_promotes_with_itself_to_itself() {
896 for ty in [
897 LogicalType::Integer,
898 LogicalType::Varchar,
899 LogicalType::Boolean,
900 LogicalType::Struct(vec![]),
901 ] {
902 assert_eq!(ty.promote(&ty), Some(ty.clone()), "{ty} does not promote with itself");
903 }
904 }
905
906 #[test]
907 fn null_takes_the_other_type() {
908 assert_eq!(LogicalType::Null.promote(&LogicalType::Varchar), Some(LogicalType::Varchar));
909 assert_eq!(LogicalType::Date.promote(&LogicalType::Null), Some(LogicalType::Date));
910 assert_eq!(LogicalType::Null.promote(&LogicalType::Null), Some(LogicalType::Null));
911 }
912
913 #[test]
914 fn the_wider_number_wins() {
915 assert_eq!(
916 LogicalType::Integer.promote(&LogicalType::SmallInt),
917 Some(LogicalType::Integer)
918 );
919 assert_eq!(LogicalType::Integer.promote(&LogicalType::Double), Some(LogicalType::Double));
920 assert_eq!(LogicalType::Float.promote(&LogicalType::Double), Some(LogicalType::Double));
921 }
922
923 #[test]
926 fn signed_and_unsigned_widen_rather_than_reinterpret() {
927 assert_eq!(LogicalType::Integer.promote(&LogicalType::UInteger), Some(LogicalType::BigInt));
928 assert_eq!(
929 LogicalType::TinyInt.promote(&LogicalType::UTinyInt),
930 Some(LogicalType::SmallInt)
931 );
932 assert_eq!(LogicalType::BigInt.promote(&LogicalType::UBigInt), Some(LogicalType::HugeInt));
933 }
934
935 #[test]
936 fn promotion_does_not_care_which_side_a_type_is_on() {
937 let types = [
938 LogicalType::TinyInt,
939 LogicalType::UInteger,
940 LogicalType::BigInt,
941 LogicalType::Double,
942 LogicalType::Decimal { width: 10, scale: 2 },
943 LogicalType::Null,
944 LogicalType::Varchar,
945 LogicalType::Date,
946 LogicalType::Timestamp,
947 ];
948 for left in &types {
949 for right in &types {
950 assert_eq!(
951 left.promote(right),
952 right.promote(left),
953 "{left} and {right} promote differently depending on the order"
954 );
955 }
956 }
957 }
958
959 #[test]
960 fn a_decimal_keeps_room_for_both_halves() {
961 let left = LogicalType::Decimal { width: 5, scale: 4 };
962 let right = LogicalType::Decimal { width: 5, scale: 1 };
963 assert_eq!(left.promote(&right), Some(LogicalType::Decimal { width: 8, scale: 4 }));
964 }
965
966 #[test]
967 fn an_integer_next_to_a_decimal_widens_the_decimal() {
968 let decimal = LogicalType::Decimal { width: 5, scale: 2 };
969 assert_eq!(
970 decimal.promote(&LogicalType::Integer),
971 Some(LogicalType::Decimal { width: 12, scale: 2 })
972 );
973 }
974
975 #[test]
976 fn a_date_and_a_timestamp_meet_at_the_timestamp() {
977 assert_eq!(
978 LogicalType::Date.promote(&LogicalType::Timestamp),
979 Some(LogicalType::Timestamp)
980 );
981 assert_eq!(
982 LogicalType::TimestampS.promote(&LogicalType::TimestampNs),
983 Some(LogicalType::TimestampNs)
984 );
985 }
986
987 #[test]
990 fn types_that_do_not_meet_say_so() {
991 assert_eq!(LogicalType::Timestamp.promote(&LogicalType::Interval), None);
992 assert_eq!(LogicalType::Integer.promote(&LogicalType::Varchar), None);
993 assert_eq!(LogicalType::Boolean.promote(&LogicalType::Integer), None);
994 }
995
996 #[test]
997 fn a_list_promotes_by_its_element() {
998 let left = LogicalType::list(LogicalType::Integer);
999 let right = LogicalType::list(LogicalType::BigInt);
1000 assert_eq!(left.promote(&right), Some(LogicalType::list(LogicalType::BigInt)));
1001 assert_eq!(left.promote(&LogicalType::list(LogicalType::Varchar)), None);
1002 }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007 use super::{Field, LogicalType, PhysicalType};
1008
1009 fn every_type() -> Vec<LogicalType> {
1012 vec![
1013 LogicalType::Null,
1014 LogicalType::Boolean,
1015 LogicalType::TinyInt,
1016 LogicalType::SmallInt,
1017 LogicalType::Integer,
1018 LogicalType::BigInt,
1019 LogicalType::HugeInt,
1020 LogicalType::UTinyInt,
1021 LogicalType::USmallInt,
1022 LogicalType::UInteger,
1023 LogicalType::UBigInt,
1024 LogicalType::UHugeInt,
1025 LogicalType::Float,
1026 LogicalType::Double,
1027 LogicalType::Decimal { width: 18, scale: 3 },
1028 LogicalType::Decimal { width: 38, scale: 0 },
1029 LogicalType::Varchar,
1030 LogicalType::Blob,
1031 LogicalType::Bit,
1032 LogicalType::Uuid,
1033 LogicalType::Date,
1034 LogicalType::Time,
1035 LogicalType::TimeTz,
1036 LogicalType::Timestamp,
1037 LogicalType::TimestampS,
1038 LogicalType::TimestampMs,
1039 LogicalType::TimestampNs,
1040 LogicalType::TimestampTz,
1041 LogicalType::Interval,
1042 LogicalType::list(LogicalType::Integer),
1043 LogicalType::list(LogicalType::list(LogicalType::Varchar)),
1044 LogicalType::array(LogicalType::Double, 3),
1045 LogicalType::map(LogicalType::Varchar, LogicalType::Integer),
1046 LogicalType::Struct(vec![
1047 Field::new("a", LogicalType::Integer),
1048 Field::new("b", LogicalType::list(LogicalType::Varchar)),
1049 ]),
1050 LogicalType::Union(vec![
1051 Field::new("num", LogicalType::Integer),
1052 Field::new("str", LogicalType::Varchar),
1053 ]),
1054 ]
1055 }
1056
1057 #[test]
1058 fn every_type_survives_being_printed_and_read_back() {
1059 for ty in every_type() {
1063 let printed = ty.to_string();
1064 let parsed = LogicalType::parse(&printed)
1065 .unwrap_or_else(|e| panic!("{printed} did not parse back: {e}"));
1066 assert_eq!(parsed, ty, "{printed} parsed to something else");
1067 }
1068 }
1069
1070 #[test]
1071 fn a_field_name_that_needs_quoting_gets_quoted() {
1072 let ty = LogicalType::Struct(vec![
1073 Field::new("plain", LogicalType::Integer),
1074 Field::new("has space", LogicalType::Integer),
1075 Field::new("has\"quote", LogicalType::Integer),
1076 Field::new("2leading", LogicalType::Integer),
1077 ]);
1078 assert_eq!(
1079 ty.to_string(),
1080 "STRUCT(plain INTEGER, \"has space\" INTEGER, \"has\"\"quote\" INTEGER, \
1081 \"2leading\" INTEGER)"
1082 );
1083 assert_eq!(LogicalType::parse(&ty.to_string()).unwrap(), ty);
1084 }
1085
1086 #[test]
1087 fn the_duckdb_aliases_resolve() {
1088 let cases = [
1089 ("int4", LogicalType::Integer),
1090 ("INT", LogicalType::Integer),
1091 ("signed", LogicalType::Integer),
1092 ("int8", LogicalType::BigInt),
1093 ("float4", LogicalType::Float),
1094 ("float8", LogicalType::Double),
1095 ("double precision", LogicalType::Double),
1096 ("text", LogicalType::Varchar),
1097 ("varchar(10)", LogicalType::Varchar),
1098 ("character varying(255)", LogicalType::Varchar),
1099 ("bool", LogicalType::Boolean),
1100 ("datetime", LogicalType::Timestamp),
1101 ("numeric(9, 2)", LogicalType::Decimal { width: 9, scale: 2 }),
1102 ("decimal", LogicalType::Decimal { width: 18, scale: 3 }),
1103 ("timestamp without time zone", LogicalType::Timestamp),
1104 ("timestamp with time zone", LogicalType::TimestampTz),
1105 ("time with time zone", LogicalType::TimeTz),
1106 ];
1107 for (text, expected) in cases {
1108 assert_eq!(LogicalType::parse(text).unwrap(), expected, "{text}");
1109 }
1110 }
1111
1112 #[test]
1113 fn list_and_array_suffixes_bind_left_to_right() {
1114 assert_eq!(
1115 LogicalType::parse("INTEGER[][3]").unwrap(),
1116 LogicalType::array(LogicalType::list(LogicalType::Integer), 3)
1117 );
1118 assert_eq!(
1119 LogicalType::parse("STRUCT(a INT)[]").unwrap(),
1120 LogicalType::list(LogicalType::Struct(vec![Field::new("a", LogicalType::Integer)]))
1121 );
1122 }
1123
1124 #[test]
1125 fn a_decimal_is_stored_in_the_narrowest_integer_that_holds_it() {
1126 assert_eq!(LogicalType::decimal(4, 2).unwrap().physical(), PhysicalType::Int16);
1127 assert_eq!(LogicalType::decimal(9, 2).unwrap().physical(), PhysicalType::Int32);
1128 assert_eq!(LogicalType::decimal(18, 2).unwrap().physical(), PhysicalType::Int64);
1129 assert_eq!(LogicalType::decimal(38, 2).unwrap().physical(), PhysicalType::Int128);
1130 }
1131
1132 #[test]
1133 fn a_decimal_outside_the_bounds_is_rejected_rather_than_clamped() {
1134 assert!(LogicalType::decimal(0, 0).is_err());
1135 assert!(LogicalType::decimal(39, 0).is_err());
1136 assert!(LogicalType::decimal(4, 5).is_err());
1137 assert!(LogicalType::parse("DECIMAL(39,0)").is_err());
1138 }
1139
1140 #[test]
1141 fn a_date_and_an_integer_share_a_layout_and_not_a_meaning() {
1142 assert_eq!(LogicalType::Date.physical(), LogicalType::Integer.physical());
1143 assert_ne!(LogicalType::Date, LogicalType::Integer);
1144 assert!(LogicalType::Date.is_temporal());
1145 assert!(!LogicalType::Date.is_numeric());
1146 }
1147
1148 #[test]
1149 fn nesting_reports_its_children_in_child_column_order() {
1150 let ty = LogicalType::map(LogicalType::Varchar, LogicalType::Integer);
1151 assert!(ty.is_nested());
1152 assert_eq!(ty.children(), vec![LogicalType::Varchar, LogicalType::Integer]);
1153 assert_eq!(LogicalType::Integer.children(), Vec::new());
1154 }
1155
1156 #[test]
1157 fn text_that_is_not_a_type_is_rejected_with_the_word_that_broke_it() {
1158 let error = LogicalType::parse("INTEGRE").unwrap_err();
1159 assert!(error.message().contains("INTEGRE"), "{error}");
1160 assert!(LogicalType::parse("INTEGER JUNK").is_err());
1161 assert!(LogicalType::parse("STRUCT(a)").is_err());
1162 assert!(LogicalType::parse("").is_err());
1163 }
1164}