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) {
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 base = base.strip_prefix("pg_catalog.").unwrap_or(&base);
54        let temporal_precision = || {
55            modifier
56                .map(|value| {
57                    value.trim().parse::<i64>().map_err(|_| {
58                        crate::SQLError::TypeMismatch(format!(
59                            "invalid temporal precision: {value}"
60                        ))
61                    })
62                })
63                .transpose()
64        };
65        let character_length = || -> Result<Option<u32>, crate::SQLError> {
66            modifier
67                .map(|value| {
68                    value
69                        .parse::<u32>()
70                        .ok()
71                        .filter(|length| *length > 0)
72                        .ok_or_else(|| {
73                            crate::SQLError::TypeMismatch(format!(
74                                "character length must be greater than zero, got {value}"
75                            ))
76                        })
77                })
78                .transpose()
79        };
80        let parsed = match base {
81            "smallint" | "int2" => Ok(Self::SmallInteger),
82            "integer" | "int" | "int4" => Ok(Self::Integer),
83            "bigint" | "int8" => Ok(Self::BigInteger),
84            "oid" => Ok(Self::Oid),
85            "xid" => Ok(Self::Xid),
86            "boolean" | "bool" => Ok(Self::Boolean),
87            "void" => Ok(Self::Void),
88            "text" => Ok(Self::Text),
89            "refcursor" => Ok(Self::RefCursor),
90            "name" => Ok(Self::Name),
91            "uuid" => Ok(Self::Uuid),
92            "varchar" | "character varying" => Ok(Self::Varchar(character_length()?)),
93            "character" | "char" => Ok(Self::Character(character_length()?.unwrap_or(1))),
94            "bpchar" => Ok(character_length()?.map_or(Self::Bpchar, Self::Character)),
95            "real" | "float4" => Ok(Self::Real),
96            "double" | "double precision" | "float8" => Ok(Self::DoublePrecision),
97            "numeric" | "decimal" => {
98                let (precision, scale) = match modifier {
99                    None => (None, None),
100                    Some(modifier) => {
101                        let mut parts = modifier.split(',').map(str::trim);
102                        let precision = parts
103                            .next()
104                            .and_then(|value| value.parse::<u32>().ok())
105                            .ok_or_else(|| {
106                                crate::SQLError::TypeMismatch(format!(
107                                    "invalid numeric modifier `{modifier}`"
108                                ))
109                            })?;
110                        let scale = parts
111                            .next()
112                            .map(|value| value.parse::<i32>())
113                            .transpose()
114                            .map_err(|_| {
115                                crate::SQLError::TypeMismatch(format!(
116                                    "invalid numeric modifier `{modifier}`"
117                                ))
118                            })?
119                            .unwrap_or(0);
120                        if parts.next().is_some() {
121                            return Err(crate::SQLError::TypeMismatch(format!(
122                                "invalid numeric modifier `{modifier}`"
123                            )));
124                        }
125                        (Some(precision), Some(scale))
126                    }
127                };
128                Ok(Self::Numeric { precision, scale })
129            }
130            "json" => Ok(Self::Json),
131            "jsonb" => Ok(Self::JsonB),
132            "bytea" => Ok(Self::Bytea),
133            "\"char\"" => Ok(Self::InternalChar),
134            "regproc" => Ok(Self::Regproc),
135            "regprocedure" => Ok(Self::Regprocedure),
136            "regclass" => Ok(Self::Regclass),
137            "regcollation" => Ok(Self::Regcollation),
138            "regnamespace" => Ok(Self::Regnamespace),
139            "regrole" => Ok(Self::Regrole),
140            "regtype" => Ok(Self::Regtype),
141            "pg_node_tree" => Ok(Self::PgNodeTree),
142            "aclitem" => Ok(Self::AclItem),
143            "int2vector" => Ok(Self::Int2Vector),
144            "oidvector" => Ok(Self::OidVector),
145            "anyarray" => Ok(Self::AnyArray),
146            "record" => Ok(Self::Record),
147            "date" => Ok(Self::Date),
148            "time" | "time without time zone" => {
149                Self::Time.with_temporal_precision(temporal_precision()?)
150            }
151            "timetz" | "time with time zone" => {
152                Self::TimeTz.with_temporal_precision(temporal_precision()?)
153            }
154            "timestamp" | "datetime" | "timestamp without time zone" => {
155                Self::Timestamp.with_temporal_precision(temporal_precision()?)
156            }
157            "timestamptz" | "timestamp with time zone" => {
158                Self::TimestampTz.with_temporal_precision(temporal_precision()?)
159            }
160            "interval" => Self::with_interval_modifiers(IntervalFields::All, temporal_precision()?),
161            other if other.starts_with("interval ") => {
162                let fields = IntervalFields::from_sql_suffix(&other[9..]).ok_or_else(|| {
163                    crate::SQLError::TypeMismatch(format!("invalid interval fields: {other}"))
164                })?;
165                Self::with_interval_modifiers(fields, temporal_precision()?)
166            }
167            "int4range" => Ok(Self::Range(RangeSubtype::Integer)),
168            "int8range" => Ok(Self::Range(RangeSubtype::BigInteger)),
169            "numrange" => Ok(Self::Range(RangeSubtype::Numeric)),
170            "daterange" => Ok(Self::Range(RangeSubtype::Date)),
171            "tsrange" => Ok(Self::Range(RangeSubtype::Timestamp)),
172            "tstzrange" => Ok(Self::Range(RangeSubtype::TimestampTz)),
173            "int4multirange" => Ok(Self::Multirange(RangeSubtype::Integer)),
174            "int8multirange" => Ok(Self::Multirange(RangeSubtype::BigInteger)),
175            "nummultirange" => Ok(Self::Multirange(RangeSubtype::Numeric)),
176            "datemultirange" => Ok(Self::Multirange(RangeSubtype::Date)),
177            "tsmultirange" => Ok(Self::Multirange(RangeSubtype::Timestamp)),
178            "tstzmultirange" => Ok(Self::Multirange(RangeSubtype::TimestampTz)),
179            "vector" => modifier
180                .and_then(|value| value.parse::<u32>().ok())
181                .filter(|dimension| *dimension > 0)
182                .map(Self::Vector)
183                .ok_or_else(|| crate::SQLError::TypeMismatch("VECTOR requires a dimension".into())),
184            "tensor" => modifier
185                .and_then(|value| value.parse::<u32>().ok())
186                .filter(|dimension| *dimension > 0)
187                .map(Self::Tensor)
188                .ok_or_else(|| crate::SQLError::TypeMismatch("TENSOR requires a dimension".into())),
189            other => Err(crate::SQLError::Unsupported(format!(
190                "SQL type `{other}` is not supported"
191            ))),
192        }?;
193        control
194            .finish(parsed, control.empty_reservation())
195            .map_err(Into::into)
196    }
197}
198
199fn normalized_type_name<'a>(
200    name: &'a str,
201    control: &ProductionControl<'_>,
202) -> Result<Produced<Cow<'a, str>>, ValueRetentionError> {
203    control.check()?;
204    let name = name.trim();
205    let mut uppercase = false;
206    for chunk in name.as_bytes().chunks(4096) {
207        control.check()?;
208        if chunk.iter().any(u8::is_ascii_uppercase) {
209            uppercase = true;
210            break;
211        }
212    }
213    if !uppercase {
214        return control.finish(Cow::Borrowed(name), control.empty_reservation());
215    }
216    let mut normalized = ProductionString::new(*control);
217    normalized.reserve(name.len())?;
218    for character in name.chars() {
219        normalized.push(character.to_ascii_lowercase())?;
220    }
221    let (normalized, memory) = normalized.finish()?.into_parts();
222    control.finish(Cow::Owned(normalized), memory)
223}