LCOV - code coverage report
Current view: top level - dashql-core/src/analyzer - sql_types.rs Coverage Total Hit
Test: DashQL 915828b Lines: 93.0 % 186 173
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : //! Structured SQL types, read from the type nodes of the parser (`OBJECT_SQL_TYPENAME`).
       2              : //!
       3              : //! The parser gives the types with standard syntax their own nodes: the numeric types are enums (`int`, `bigint`,
       4              : //! `boolean`, ...) or `OBJECT_SQL_NUMERIC_TYPE` with modifiers (`numeric(10,2)`), the character types
       5              : //! `OBJECT_SQL_CHARACTER_TYPE`, the date and time types `OBJECT_SQL_TIMESTAMP_TYPE` and `OBJECT_SQL_TIME_TYPE`, and the
       6              : //! intervals `OBJECT_SQL_INTERVAL_TYPE`. Every other type is a generic type with a name and modifiers. The generic names
       7              : //! of well-known types (`text`, `date`, `int8`, `timestamptz`, ...) map to their kinds, so `::int8` and `::bigint`
       8              : //! are the same type.
       9              : 
      10              : use dashql_buffers::parser::{AttributeKey as K, CharacterType, IntervalType as IntervalTypeEnum, Node, NodeType, NumericType};
      11              : use dashql_parser::attrs;
      12              : use dashql_parser::parser::{parse, ParsedScript};
      13              : use dashql_parser::scanner::scan;
      14              : use std::fmt;
      15              : use std::sync::Arc;
      16              : 
      17              : /// The lowercased name of a NAME node or of a name path
      18           38 : fn type_name(parsed: &ParsedScript, node: &Node) -> Option<String> {
      19           38 :     let text = |id: u32| parsed.scanned.names.get(id).text.to_lowercase();
      20           38 :     match node.node_type() {
      21           38 :         NodeType::NAME => Some(text(node.children_begin_or_value())),
      22              :         NodeType::ARRAY => {
      23            0 :             let mut parts = Vec::new();
      24            0 :             for child in parsed.children(node) {
      25            0 :                 match child.node_type() {
      26            0 :                     NodeType::OBJECT_EXT_TRAILING_DOT => continue,
      27            0 :                     NodeType::NAME => parts.push(text(child.children_begin_or_value())),
      28            0 :                     _ => return None,
      29              :                 }
      30              :             }
      31            0 :             (!parts.is_empty()).then(|| parts.join("."))
      32              :         }
      33            0 :         _ => None,
      34              :     }
      35           38 : }
      36              : 
      37              : /// The kind of a SQL type
      38              : #[derive(Clone, Debug, PartialEq, Eq, Hash)]
      39              : pub enum SqlTypeKind {
      40              :     Bool,
      41              :     Int1,
      42              :     Int2,
      43              :     Int4,
      44              :     Int8,
      45              :     Float4,
      46              :     Float8,
      47              :     Numeric { precision: Option<u32>, scale: Option<u32> },
      48              :     Varchar { length: Option<u32> },
      49              :     Char { length: Option<u32> },
      50              :     Text,
      51              :     Bit { length: Option<u32>, varying: bool },
      52              :     Date,
      53              :     Time { precision: Option<u32>, with_time_zone: bool },
      54              :     Timestamp { precision: Option<u32>, with_time_zone: bool },
      55              :     Interval { fields: Option<IntervalTypeEnum>, precision: Option<u32> },
      56              :     /// Any other type, by its lowercased name and the text of its modifiers
      57              :     Generic { name: Arc<str>, modifiers: Vec<Arc<str>> },
      58              : }
      59              : 
      60              : /// A SQL type
      61              : #[derive(Clone, Debug, PartialEq, Eq, Hash)]
      62              : pub struct SqlType {
      63              :     /// The kind of the element type
      64              :     pub kind: SqlTypeKind,
      65              :     /// The number of array dimensions, 0 for a scalar type
      66              :     pub array_dimensions: u8,
      67              : }
      68              : 
      69              : impl SqlType {
      70              :     /// A scalar type of a kind
      71          251 :     pub fn scalar(kind: SqlTypeKind) -> Self {
      72          251 :         SqlType { kind, array_dimensions: 0 }
      73          251 :     }
      74              : 
      75              :     /// Is the type a number?
      76           31 :     pub fn is_numeric(&self) -> bool {
      77           31 :         self.array_dimensions == 0
      78           22 :             && matches!(
      79           30 :                 self.kind,
      80              :                 SqlTypeKind::Int1
      81              :                     | SqlTypeKind::Int2
      82              :                     | SqlTypeKind::Int4
      83              :                     | SqlTypeKind::Int8
      84              :                     | SqlTypeKind::Float4
      85              :                     | SqlTypeKind::Float8
      86              :                     | SqlTypeKind::Numeric { .. }
      87              :             )
      88           31 :     }
      89              : 
      90              :     /// Is the type an integer?
      91            7 :     pub fn is_integer(&self) -> bool {
      92            7 :         self.array_dimensions == 0 && matches!(self.kind, SqlTypeKind::Int1 | SqlTypeKind::Int2 | SqlTypeKind::Int4 | SqlTypeKind::Int8)
      93            7 :     }
      94              : 
      95              :     /// Is the type a boolean?
      96            5 :     pub fn is_bool(&self) -> bool {
      97            5 :         self.array_dimensions == 0 && self.kind == SqlTypeKind::Bool
      98            5 :     }
      99              : }
     100              : 
     101              : /// The kind of a well-known generic type name
     102           65 : pub fn generic_kind(name: &str) -> Option<SqlTypeKind> {
     103           65 :     Some(match name {
     104           65 :         "bool" => SqlTypeKind::Bool,
     105           63 :         "tinyint" | "int1" => SqlTypeKind::Int1,
     106           59 :         "int2" => SqlTypeKind::Int2,
     107           58 :         "int4" => SqlTypeKind::Int4,
     108           57 :         "int8" => SqlTypeKind::Int8,
     109           55 :         "float4" => SqlTypeKind::Float4,
     110           54 :         "float8" | "double" => SqlTypeKind::Float8,
     111           51 :         "text" | "string" => SqlTypeKind::Text,
     112           45 :         "date" => SqlTypeKind::Date,
     113           14 :         "timestamptz" => SqlTypeKind::Timestamp { precision: None, with_time_zone: true },
     114            9 :         "timetz" => SqlTypeKind::Time { precision: None, with_time_zone: true },
     115            7 :         _ => return None,
     116              :     })
     117           65 : }
     118              : 
     119              : /// The unsigned integer of a literal node
     120           62 : fn read_u32(parsed: &ParsedScript, node: Option<&Node>) -> Option<u32> {
     121           62 :     let node = node?;
     122           34 :     if node.node_type() != NodeType::LITERAL_INTEGER {
     123            0 :         return None;
     124           34 :     }
     125           34 :     parsed.node_text(node).trim().parse().ok()
     126           62 : }
     127              : 
     128              : /// The value of a bool node, false if missing
     129           20 : fn read_bool(node: Option<&Node>) -> bool {
     130           20 :     node.is_some_and(|n| n.node_type() == NodeType::BOOL && n.children_begin_or_value() != 0)
     131           20 : }
     132              : 
     133              : /// The modifiers of a type, the nodes of its modifier array
     134           50 : fn modifiers<'a>(parsed: &'a ParsedScript, node: Option<&Node>) -> &'a [Node] {
     135           13 :     match node {
     136           13 :         Some(n) if n.node_type() == NodeType::ARRAY => parsed.children(n),
     137           37 :         _ => &[],
     138              :     }
     139           50 : }
     140              : 
     141              : /// The kind of a numeric type enum
     142           61 : fn numeric_kind(value: u32, precision: Option<u32>, scale: Option<u32>) -> Option<SqlTypeKind> {
     143           61 :     Some(match NumericType(value as _) {
     144            0 :         NumericType::INT1 => SqlTypeKind::Int1,
     145            2 :         NumericType::INT2 => SqlTypeKind::Int2,
     146           30 :         NumericType::INT4 => SqlTypeKind::Int4,
     147            7 :         NumericType::INT8 => SqlTypeKind::Int8,
     148            3 :         NumericType::FLOAT4 => SqlTypeKind::Float4,
     149            2 :         NumericType::FLOAT8 => SqlTypeKind::Float8,
     150           12 :         NumericType::NUMERIC => SqlTypeKind::Numeric { precision, scale },
     151            5 :         NumericType::BOOL => SqlTypeKind::Bool,
     152            0 :         _ => return None,
     153              :     })
     154           61 : }
     155              : 
     156              : /// Read the kind of the node of a simple type name, None if the node is no type
     157          137 : fn read_kind(parsed: &ParsedScript, node: &Node) -> Option<SqlTypeKind> {
     158          137 :     match node.node_type() {
     159           49 :         NodeType::ENUM_SQL_NUMERIC_TYPE => numeric_kind(node.children_begin_or_value(), None, None),
     160              :         NodeType::OBJECT_SQL_NUMERIC_TYPE => {
     161           12 :             let [base, mods] = attrs!(parsed.children(node), [K::SQL_NUMERIC_TYPE_BASE, K::SQL_NUMERIC_TYPE_MODIFIERS]);
     162           12 :             let mods = modifiers(parsed, mods);
     163           12 :             let precision = read_u32(parsed, mods.first());
     164           12 :             let scale = read_u32(parsed, mods.get(1));
     165           12 :             numeric_kind(base?.children_begin_or_value(), precision, scale)
     166              :         }
     167              :         NodeType::OBJECT_SQL_CHARACTER_TYPE => {
     168            9 :             let [char_type, length] = attrs!(parsed.children(node), [K::SQL_CHARACTER_TYPE, K::SQL_CHARACTER_TYPE_LENGTH]);
     169            9 :             let length = read_u32(parsed, length);
     170            9 :             match CharacterType(char_type?.children_begin_or_value() as _) {
     171            6 :                 CharacterType::VARCHAR => Some(SqlTypeKind::Varchar { length }),
     172            3 :                 _ => Some(SqlTypeKind::Char { length }),
     173              :             }
     174              :         }
     175              :         NodeType::OBJECT_SQL_BIT_TYPE => {
     176            5 :             let [varying, length] = attrs!(parsed.children(node), [K::SQL_BIT_TYPE_VARYING, K::SQL_BIT_TYPE_LENGTH]);
     177            5 :             Some(SqlTypeKind::Bit { length: read_u32(parsed, length), varying: read_bool(varying) })
     178              :         }
     179              :         NodeType::OBJECT_SQL_TIMESTAMP_TYPE | NodeType::OBJECT_SQL_TIME_TYPE => {
     180           15 :             let [precision, tz] = attrs!(parsed.children(node), [K::SQL_TIME_TYPE_PRECISION, K::SQL_TIME_TYPE_WITH_TIMEZONE]);
     181           15 :             let precision = read_u32(parsed, precision);
     182           15 :             let with_time_zone = read_bool(tz);
     183           15 :             if node.node_type() == NodeType::OBJECT_SQL_TIMESTAMP_TYPE {
     184           10 :                 Some(SqlTypeKind::Timestamp { precision, with_time_zone })
     185              :             } else {
     186            5 :                 Some(SqlTypeKind::Time { precision, with_time_zone })
     187              :             }
     188              :         }
     189              :         NodeType::OBJECT_SQL_INTERVAL_TYPE => {
     190            9 :             let [fields, precision] = attrs!(parsed.children(node), [K::SQL_INTERVAL_TYPE, K::SQL_INTERVAL_PRECISION]);
     191              :             Some(SqlTypeKind::Interval {
     192            9 :                 fields: fields.map(|f| IntervalTypeEnum(f.children_begin_or_value() as _)),
     193            9 :                 precision: read_u32(parsed, precision),
     194              :             })
     195              :         }
     196              :         NodeType::OBJECT_SQL_GENERIC_TYPE => {
     197           38 :             let [name, mods] = attrs!(parsed.children(node), [K::SQL_GENERIC_TYPE_NAME, K::SQL_GENERIC_TYPE_MODIFIERS]);
     198           38 :             let name = type_name(parsed, name?)?;
     199           38 :             let mods = modifiers(parsed, mods);
     200           38 :             if mods.is_empty() {
     201           35 :                 if let Some(kind) = generic_kind(&name) {
     202           31 :                     return Some(kind);
     203            4 :                 }
     204            3 :             }
     205              :             Some(SqlTypeKind::Generic {
     206            7 :                 name: name.into(),
     207            7 :                 modifiers: mods.iter().map(|m| Arc::from(parsed.node_text(m).trim())).collect(),
     208              :             })
     209              :         }
     210            0 :         _ => None,
     211              :     }
     212          137 : }
     213              : 
     214              : /// Read a type name (`OBJECT_SQL_TYPENAME`) of a parsed script, None if the node is no type name
     215          137 : pub fn read_sql_type(parsed: &ParsedScript, node: &Node) -> Option<SqlType> {
     216          137 :     if node.node_type() != NodeType::OBJECT_SQL_TYPENAME {
     217            0 :         return None;
     218          137 :     }
     219          137 :     let [element, array] = attrs!(parsed.children(node), [K::SQL_TYPENAME_TYPE, K::SQL_TYPENAME_ARRAY]);
     220          137 :     let kind = read_kind(parsed, element?)?;
     221              :     // `int[]`, `int[][]`, `int[3]` and `int array` have one dimension per bound, `array` alone has one
     222          137 :     let array_dimensions = match array {
     223            9 :         Some(a) if a.node_type() == NodeType::ARRAY => (a.children_count() as u8).max(1),
     224          128 :         _ => 0,
     225              :     };
     226          137 :     Some(SqlType { kind, array_dimensions })
     227          137 : }
     228              : 
     229              : /// Read a type from its text, `numeric(10, 2)`, None if the text is no single type
     230           94 : pub fn parse_sql_type(text: &str) -> Option<SqlType> {
     231           94 :     let parsed = parse(Arc::new(scan(&format!("select null::{text}"), 0, 0)));
     232          763 :     let mut types = parsed.nodes.iter().filter(|n| n.node_type() == NodeType::OBJECT_SQL_TYPENAME);
     233           94 :     match (parsed.errors.is_empty(), types.next(), types.next()) {
     234           89 :         (true, Some(node), None) => read_sql_type(&parsed, node),
     235            5 :         _ => None,
     236              :     }
     237           94 : }
     238              : 
     239              : /// The keywords of interval fields, `day` or `day to second`
     240          285 : pub fn interval_keyword(fields: IntervalTypeEnum) -> Option<&'static str> {
     241          285 :     Some(match fields {
     242           17 :         IntervalTypeEnum::YEAR => "year",
     243           18 :         IntervalTypeEnum::MONTH => "month",
     244          123 :         IntervalTypeEnum::DAY => "day",
     245           39 :         IntervalTypeEnum::HOUR => "hour",
     246            8 :         IntervalTypeEnum::MINUTE => "minute",
     247           22 :         IntervalTypeEnum::SECOND | IntervalTypeEnum::INTERVAL_SECOND => "second",
     248           14 :         IntervalTypeEnum::YEAR_TO_MONTH => "year to month",
     249            3 :         IntervalTypeEnum::DAY_TO_HOUR => "day to hour",
     250            3 :         IntervalTypeEnum::DAY_TO_MINUTE => "day to minute",
     251           16 :         IntervalTypeEnum::DAY_TO_SECOND => "day to second",
     252            7 :         IntervalTypeEnum::HOUR_TO_MINUTE => "hour to minute",
     253            3 :         IntervalTypeEnum::HOUR_TO_SECOND => "hour to second",
     254           10 :         IntervalTypeEnum::MINUTE_TO_SECOND => "minute to second",
     255            2 :         _ => return None,
     256              :     })
     257          285 : }
     258              : 
     259           24 : fn with_length(f: &mut fmt::Formatter<'_>, name: &str, length: Option<u32>) -> fmt::Result {
     260           24 :     match length {
     261           11 :         Some(length) => write!(f, "{name}({length})"),
     262           13 :         None => f.write_str(name),
     263              :     }
     264           24 : }
     265              : 
     266              : /// The SQL text of the type, e.g. `numeric(10,2)` or `timestamp with time zone`
     267              : impl fmt::Display for SqlType {
     268          106 :     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     269          106 :         match &self.kind {
     270            6 :             SqlTypeKind::Bool => f.write_str("boolean")?,
     271            3 :             SqlTypeKind::Int1 => f.write_str("tinyint")?,
     272            3 :             SqlTypeKind::Int2 => f.write_str("smallint")?,
     273           14 :             SqlTypeKind::Int4 => f.write_str("integer")?,
     274            8 :             SqlTypeKind::Int8 => f.write_str("bigint")?,
     275            3 :             SqlTypeKind::Float4 => f.write_str("real")?,
     276            4 :             SqlTypeKind::Float8 => f.write_str("double precision")?,
     277            8 :             SqlTypeKind::Numeric { precision, scale } => match (precision, scale) {
     278            5 :                 (Some(p), Some(s)) => write!(f, "numeric({p},{s})")?,
     279            1 :                 (Some(p), None) => write!(f, "numeric({p})")?,
     280            2 :                 _ => f.write_str("numeric")?,
     281              :             },
     282            5 :             SqlTypeKind::Varchar { length } => with_length(f, "varchar", *length)?,
     283            3 :             SqlTypeKind::Char { length } => with_length(f, "char", *length)?,
     284            7 :             SqlTypeKind::Text => f.write_str("text")?,
     285            4 :             SqlTypeKind::Bit { length, varying } => with_length(f, if *varying { "bit varying" } else { "bit" }, *length)?,
     286            4 :             SqlTypeKind::Date => f.write_str("date")?,
     287            7 :             SqlTypeKind::Time { precision, with_time_zone } | SqlTypeKind::Timestamp { precision, with_time_zone } => {
     288           12 :                 let name = if matches!(self.kind, SqlTypeKind::Time { .. }) { "time" } else { "timestamp" };
     289           12 :                 with_length(f, name, *precision)?;
     290           12 :                 if *with_time_zone {
     291            7 :                     f.write_str(" with time zone")?;
     292            5 :                 }
     293              :             }
     294           16 :             SqlTypeKind::Interval { fields, precision } => {
     295           16 :                 f.write_str("interval")?;
     296           16 :                 if let Some(fields) = fields {
     297            7 :                     write!(f, " {}", interval_keyword(*fields).unwrap_or(""))?;
     298            9 :                 }
     299           16 :                 if let Some(precision) = precision {
     300            1 :                     write!(f, "({precision})")?;
     301           15 :                 }
     302              :             }
     303            6 :             SqlTypeKind::Generic { name, modifiers } => {
     304            6 :                 f.write_str(name)?;
     305            6 :                 if !modifiers.is_empty() {
     306            2 :                     write!(f, "({})", modifiers.join(","))?;
     307            4 :                 }
     308              :             }
     309              :         }
     310          106 :         for _ in 0..self.array_dimensions {
     311            7 :             f.write_str("[]")?;
     312              :         }
     313          106 :         Ok(())
     314          106 :     }
     315              : }
        

Generated by: LCOV version 2.0-1