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            "regnamespace" => Ok(Self::Regnamespace),
138            "regrole" => Ok(Self::Regrole),
139            "regtype" => Ok(Self::Regtype),
140            "pg_node_tree" => Ok(Self::PgNodeTree),
141            "aclitem" => Ok(Self::AclItem),
142            "int2vector" => Ok(Self::Int2Vector),
143            "oidvector" => Ok(Self::OidVector),
144            "anyarray" => Ok(Self::AnyArray),
145            "record" => Ok(Self::Record),
146            "date" => Ok(Self::Date),
147            "time" | "time without time zone" => {
148                Self::Time.with_temporal_precision(temporal_precision()?)
149            }
150            "timetz" | "time with time zone" => {
151                Self::TimeTz.with_temporal_precision(temporal_precision()?)
152            }
153            "timestamp" | "datetime" | "timestamp without time zone" => {
154                Self::Timestamp.with_temporal_precision(temporal_precision()?)
155            }
156            "timestamptz" | "timestamp with time zone" => {
157                Self::TimestampTz.with_temporal_precision(temporal_precision()?)
158            }
159            "interval" => Self::with_interval_modifiers(IntervalFields::All, temporal_precision()?),
160            other if other.starts_with("interval ") => {
161                let fields = IntervalFields::from_sql_suffix(&other[9..]).ok_or_else(|| {
162                    crate::SQLError::TypeMismatch(format!("invalid interval fields: {other}"))
163                })?;
164                Self::with_interval_modifiers(fields, temporal_precision()?)
165            }
166            "int4range" => Ok(Self::Range(RangeSubtype::Integer)),
167            "int8range" => Ok(Self::Range(RangeSubtype::BigInteger)),
168            "numrange" => Ok(Self::Range(RangeSubtype::Numeric)),
169            "daterange" => Ok(Self::Range(RangeSubtype::Date)),
170            "tsrange" => Ok(Self::Range(RangeSubtype::Timestamp)),
171            "tstzrange" => Ok(Self::Range(RangeSubtype::TimestampTz)),
172            "int4multirange" => Ok(Self::Multirange(RangeSubtype::Integer)),
173            "int8multirange" => Ok(Self::Multirange(RangeSubtype::BigInteger)),
174            "nummultirange" => Ok(Self::Multirange(RangeSubtype::Numeric)),
175            "datemultirange" => Ok(Self::Multirange(RangeSubtype::Date)),
176            "tsmultirange" => Ok(Self::Multirange(RangeSubtype::Timestamp)),
177            "tstzmultirange" => Ok(Self::Multirange(RangeSubtype::TimestampTz)),
178            "vector" => modifier
179                .and_then(|value| value.parse::<u32>().ok())
180                .filter(|dimension| *dimension > 0)
181                .map(Self::Vector)
182                .ok_or_else(|| crate::SQLError::TypeMismatch("VECTOR requires a dimension".into())),
183            "tensor" => modifier
184                .and_then(|value| value.parse::<u32>().ok())
185                .filter(|dimension| *dimension > 0)
186                .map(Self::Tensor)
187                .ok_or_else(|| crate::SQLError::TypeMismatch("TENSOR requires a dimension".into())),
188            other => Err(crate::SQLError::Unsupported(format!(
189                "SQL type `{other}` is not supported"
190            ))),
191        }?;
192        control
193            .finish(parsed, control.empty_reservation())
194            .map_err(Into::into)
195    }
196}
197
198fn normalized_type_name<'a>(
199    name: &'a str,
200    control: &ProductionControl<'_>,
201) -> Result<Produced<Cow<'a, str>>, ValueRetentionError> {
202    control.check()?;
203    let name = name.trim();
204    let mut uppercase = false;
205    for chunk in name.as_bytes().chunks(4096) {
206        control.check()?;
207        if chunk.iter().any(u8::is_ascii_uppercase) {
208            uppercase = true;
209            break;
210        }
211    }
212    if !uppercase {
213        return control.finish(Cow::Borrowed(name), control.empty_reservation());
214    }
215    let mut normalized = ProductionString::new(*control);
216    normalized.reserve(name.len())?;
217    for character in name.chars() {
218        normalized.push(character.to_ascii_lowercase())?;
219    }
220    let (normalized, memory) = normalized.finish()?.into_parts();
221    control.finish(Cow::Owned(normalized), memory)
222}