Skip to main content

uqa_sql/ast/types/
parsing.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Canonical type parsing keeps normalized spelling and nested result constructors in distinct resource owners.
8
9use super::{builtin_array_element_name, split_type_modifier_with_control, ColumnType};
10use crate::ast::{IntervalFields, RangeSubtype};
11use std::borrow::Cow;
12use uqa_core::memory::{Produced, ProductionControl, ProductionString};
13use uqa_core::ValueRetentionError;
14
15impl ColumnType {
16    /// Parse the canonical or accepted spelling of one implemented SQL type.
17    pub fn from_sql_name(name: &str) -> Result<Self, crate::SQLError> {
18        Self::from_sql_name_with_control(name, &ProductionControl::uncontrolled()).map(|value| {
19            value
20                .into_uncontrolled()
21                .expect("ordinary type parsing has no reservation")
22        })
23    }
24
25    /// Parse through the same grammar while each temporary name and nested result allocation uses the caller's allowance.
26    #[expect(
27        clippy::too_many_lines,
28        reason = "one type grammar preserves every accepted spelling and diagnostic"
29    )]
30    pub fn from_sql_name_with_control(
31        name: &str,
32        control: &ProductionControl<'_>,
33    ) -> Result<Produced<Self>, crate::SQLError> {
34        let normalized = normalized_type_name(name, control)?;
35        if let Some(element) = builtin_array_element_name(&normalized) {
36            return Self::array_with_control(
37                Self::from_sql_name_with_control(element, control)?,
38                control,
39            )
40            .map_err(Into::into);
41        }
42        if let Some(element) = normalized.strip_suffix("[]") {
43            let element_type = Self::from_sql_name_with_control(element, control)?;
44            if matches!(*element_type, Self::Void | Self::PgNodeTree) {
45                return Err(crate::SQLError::Routine {
46                    sqlstate: "42704".into(),
47                    message: format!("type \"{}\" does not exist", normalized.as_ref()),
48                });
49            }
50            return Self::array_with_control(element_type, control).map_err(Into::into);
51        }
52        let (base, modifier) = split_type_modifier_with_control(&normalized, control)?;
53        let catalog_name = base
54            .strip_prefix("pg_catalog.")
55            .or_else(|| base.strip_prefix("\"pg_catalog\"."));
56        let catalog_modifiers = catalog_name.is_some() || base.starts_with('"');
57        let catalog_type;
58        let base = if let Some(name) =
59            catalog_name.or_else(|| base.strip_prefix('"').map(|_| &**base))
60        {
61            let name = name
62                .strip_prefix('"')
63                .and_then(|name| name.strip_suffix('"'))
64                .unwrap_or(name);
65            if let Some(element) = builtin_array_element_name(name) {
66                return Self::array_with_control(
67                    Self::from_sql_name_with_control(element, control)?,
68                    control,
69                )
70                .map_err(Into::into);
71            }
72            let ty = crate::catalog::type_metadata::builtin_scalar_type_by_name(name).ok_or_else(
73                || crate::SQLError::Unsupported(format!("SQL type `{}` is not supported", &**base)),
74            )?;
75            // Catalog identity without a modifier already determines the type. Regtype output is display syntax: bpchar prints as character, whose input spelling implies a length of one.
76            if modifier.is_none() && !matches!(ty, Self::Vector(_) | Self::Tensor(_)) {
77                return ty.clone_with_control(control).map_err(Into::into);
78            }
79            catalog_type = ty.regtype_name_with_control(control)?;
80            catalog_type.as_str()
81        } else {
82            &base
83        };
84        let temporal_precision = || {
85            modifier
86                .map(|value| {
87                    value.trim().parse::<i64>().map_err(|_| {
88                        crate::SQLError::TypeMismatch(format!(
89                            "invalid temporal precision: {value}"
90                        ))
91                    })
92                })
93                .transpose()
94        };
95        let character_length = || -> Result<Option<u32>, crate::SQLError> {
96            modifier
97                .map(|value| {
98                    value
99                        .parse::<u32>()
100                        .ok()
101                        .filter(|length| *length > 0)
102                        .ok_or_else(|| {
103                            crate::SQLError::TypeMismatch(format!(
104                                "character length must be greater than zero, got {value}"
105                            ))
106                        })
107                })
108                .transpose()
109        };
110        let parsed = match base {
111            "smallint" | "int2" => Ok(Self::SmallInteger),
112            "integer" | "int" | "int4" => Ok(Self::Integer),
113            "bigint" | "int8" => Ok(Self::BigInteger),
114            "oid" => Ok(Self::Oid),
115            "xid" => Ok(Self::Xid),
116            "boolean" | "bool" => Ok(Self::Boolean),
117            "void" => Ok(Self::Void),
118            "text" => Ok(Self::Text),
119            "refcursor" => Ok(Self::RefCursor),
120            "name" => Ok(Self::Name),
121            "uuid" => Ok(Self::Uuid),
122            "varchar" | "character varying" => Ok(Self::Varchar(character_length()?)),
123            "character" | "char" => Ok(Self::Character(character_length()?.unwrap_or(1))),
124            "bpchar" => Ok(character_length()?.map_or(Self::Bpchar, Self::Character)),
125            "real" | "float4" => Ok(Self::Real),
126            "double" | "double precision" | "float8" => Ok(Self::DoublePrecision),
127            "numeric" | "decimal" => {
128                let (precision, scale) = match modifier {
129                    None => (None, None),
130                    Some(modifier) => {
131                        let mut parts = modifier.split(',').map(str::trim);
132                        let precision = parts
133                            .next()
134                            .and_then(|value| value.parse::<u32>().ok())
135                            .ok_or_else(|| {
136                                crate::SQLError::TypeMismatch(format!(
137                                    "invalid numeric modifier `{modifier}`"
138                                ))
139                            })?;
140                        let scale = parts
141                            .next()
142                            .map(|value| value.parse::<i32>())
143                            .transpose()
144                            .map_err(|_| {
145                                crate::SQLError::TypeMismatch(format!(
146                                    "invalid numeric modifier `{modifier}`"
147                                ))
148                            })?
149                            .unwrap_or(0);
150                        if parts.next().is_some() {
151                            return Err(crate::SQLError::TypeMismatch(format!(
152                                "invalid numeric modifier `{modifier}`"
153                            )));
154                        }
155                        (Some(precision), Some(scale))
156                    }
157                };
158                Ok(Self::Numeric { precision, scale })
159            }
160            "json" => Ok(Self::Json),
161            "jsonb" => Ok(Self::JsonB),
162            "bytea" => Ok(Self::Bytea),
163            "\"char\"" => Ok(Self::InternalChar),
164            "regproc" => Ok(Self::Regproc),
165            "regprocedure" => Ok(Self::Regprocedure),
166            "regclass" => Ok(Self::Regclass),
167            "regcollation" => Ok(Self::Regcollation),
168            "regnamespace" => Ok(Self::Regnamespace),
169            "regrole" => Ok(Self::Regrole),
170            "regtype" => Ok(Self::Regtype),
171            "pg_node_tree" => Ok(Self::PgNodeTree),
172            "aclitem" => Ok(Self::AclItem),
173            "int2vector" => Ok(Self::Int2Vector),
174            "oidvector" => Ok(Self::OidVector),
175            "anyarray" => Ok(Self::AnyArray),
176            "record" => Ok(Self::Record),
177            "date" => Ok(Self::Date),
178            "time" | "time without time zone" => {
179                Self::Time.with_temporal_precision(temporal_precision()?)
180            }
181            "timetz" | "time with time zone" => {
182                Self::TimeTz.with_temporal_precision(temporal_precision()?)
183            }
184            "timestamp" | "datetime" | "timestamp without time zone" => {
185                Self::Timestamp.with_temporal_precision(temporal_precision()?)
186            }
187            "timestamptz" | "timestamp with time zone" => {
188                Self::TimestampTz.with_temporal_precision(temporal_precision()?)
189            }
190            "interval" if catalog_modifiers => catalog_interval_type(modifier),
191            "interval" => Self::with_interval_modifiers(IntervalFields::All, temporal_precision()?),
192            other if other.starts_with("interval ") => {
193                let fields = IntervalFields::from_sql_suffix(&other[9..]).ok_or_else(|| {
194                    crate::SQLError::TypeMismatch(format!("invalid interval fields: {other}"))
195                })?;
196                Self::with_interval_modifiers(fields, temporal_precision()?)
197            }
198            "int4range" => Ok(Self::Range(RangeSubtype::Integer)),
199            "int8range" => Ok(Self::Range(RangeSubtype::BigInteger)),
200            "numrange" => Ok(Self::Range(RangeSubtype::Numeric)),
201            "daterange" => Ok(Self::Range(RangeSubtype::Date)),
202            "tsrange" => Ok(Self::Range(RangeSubtype::Timestamp)),
203            "tstzrange" => Ok(Self::Range(RangeSubtype::TimestampTz)),
204            "int4multirange" => Ok(Self::Multirange(RangeSubtype::Integer)),
205            "int8multirange" => Ok(Self::Multirange(RangeSubtype::BigInteger)),
206            "nummultirange" => Ok(Self::Multirange(RangeSubtype::Numeric)),
207            "datemultirange" => Ok(Self::Multirange(RangeSubtype::Date)),
208            "tsmultirange" => Ok(Self::Multirange(RangeSubtype::Timestamp)),
209            "tstzmultirange" => Ok(Self::Multirange(RangeSubtype::TimestampTz)),
210            "vector" => modifier
211                .and_then(|value| value.parse::<u32>().ok())
212                .filter(|dimension| *dimension > 0)
213                .map(Self::Vector)
214                .ok_or_else(|| crate::SQLError::TypeMismatch("VECTOR requires a dimension".into())),
215            "tensor" => modifier
216                .and_then(|value| value.parse::<u32>().ok())
217                .filter(|dimension| *dimension > 0)
218                .map(Self::Tensor)
219                .ok_or_else(|| crate::SQLError::TypeMismatch("TENSOR requires a dimension".into())),
220            other => Err(crate::SQLError::Unsupported(format!(
221                "SQL type `{other}` is not supported"
222            ))),
223        }?;
224        control
225            .finish(parsed, control.empty_reservation())
226            .map_err(Into::into)
227    }
228}
229
230fn normalized_type_name<'a>(
231    name: &'a str,
232    control: &ProductionControl<'_>,
233) -> Result<Produced<Cow<'a, str>>, ValueRetentionError> {
234    control.check()?;
235    let name = name.trim();
236    let mut uppercase = false;
237    for chunk in name.as_bytes().chunks(4096) {
238        control.check()?;
239        if chunk.iter().any(u8::is_ascii_uppercase) {
240            uppercase = true;
241            break;
242        }
243    }
244    if !uppercase {
245        return control.finish(Cow::Borrowed(name), control.empty_reservation());
246    }
247    let mut normalized = ProductionString::new(*control);
248    normalized.reserve(name.len())?;
249    let mut quoted = false;
250    for character in name.chars() {
251        if character == '"' {
252            quoted = !quoted;
253        }
254        normalized.push(if quoted {
255            character
256        } else {
257            character.to_ascii_lowercase()
258        })?;
259    }
260    let (normalized, memory) = normalized.finish()?.into_parts();
261    control.finish(Cow::Owned(normalized), memory)
262}
263
264fn catalog_interval_type(modifier: Option<&str>) -> Result<ColumnType, crate::SQLError> {
265    let Some(modifier) = modifier else {
266        return Ok(ColumnType::Interval);
267    };
268    let mut parts = modifier.split(',').map(str::trim);
269    let mask = parts
270        .next()
271        .and_then(|mask| mask.parse::<i64>().ok())
272        .and_then(IntervalFields::from_modifier_mask)
273        .ok_or_else(|| crate::SQLError::TypeMismatch("invalid interval fields".into()))?;
274    let precision = parts
275        .next()
276        .map(|precision| {
277            precision.parse::<i64>().map_err(|_| {
278                crate::SQLError::TypeMismatch(format!("invalid temporal precision: {precision}"))
279            })
280        })
281        .transpose()?;
282    if parts.next().is_some() {
283        return Err(crate::SQLError::TypeMismatch(
284            "invalid interval type modifier".into(),
285        ));
286    }
287    ColumnType::with_interval_modifiers(mask, precision)
288}