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 : }
|