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