Skip to main content

uqa_sql/ast/
types.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use serde::{Deserialize, Serialize};
8
9use super::{IntervalFields, RangeSubtype};
10
11mod modifiers;
12mod names;
13mod parsing;
14mod production;
15
16pub(crate) use modifiers::split_type_modifier_with_control;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub enum ColumnType {
20    /// A declaration awaiting catalog type resolution. This variant is never a stored column type.
21    Named(String),
22    SmallInteger,
23    Integer,
24    BigInteger,
25    /// `PostgreSQL` object identifier (`pg_catalog.oid`).
26    Oid,
27    /// `PostgreSQL` transaction identifier (`pg_catalog.xid`).
28    Xid,
29    Boolean,
30    /// `PostgreSQL`'s non-null zero-width `void` pseudo-type.
31    Void,
32    Text,
33    /// `PostgreSQL` cursor portal name (`pg_catalog.refcursor`).
34    RefCursor,
35    Name,
36    Uuid,
37    Varchar(Option<u32>),
38    /// Internal unconstrained `bpchar` type used after common-type selection.
39    Bpchar,
40    /// `PostgreSQL` blank-padded `CHARACTER(n)` / `CHAR(n)` (`bpchar`).
41    /// The length counts Unicode scalar values and defaults to one when the
42    /// declaration omits an explicit modifier.
43    Character(u32),
44    Real,
45    DoublePrecision,
46    /// `NUMERIC(precision, scale)` -- exact decimal storage. When
47    /// `scale` is `Some(s)` the engine rounds `INSERT` values to `s`
48    /// fractional digits. `precision` is captured for round-tripping
49    /// the catalog text but is not currently enforced.
50    Numeric {
51        precision: Option<u32>,
52        scale: Option<i32>,
53    },
54    /// `JSON` / `JSONB` columns store typed JSON values.
55    Json,
56    /// `JSONB` columns store typed JSON values with `PostgreSQL` JSONB operators.
57    JsonB,
58    /// `BYTEA` columns store opaque bytes.
59    Bytea,
60    /// `PostgreSQL`'s internal single-byte `"char"` catalog type.
61    InternalChar,
62    Regproc,
63    /// `PostgreSQL` routine-signature object identifier (`pg_catalog.regprocedure`).
64    Regprocedure,
65    /// `PostgreSQL` relation object identifier (`pg_catalog.regclass`).
66    Regclass,
67    /// `PostgreSQL` namespace object identifier (`pg_catalog.regnamespace`).
68    Regnamespace,
69    /// `PostgreSQL` role object identifier (`pg_catalog.regrole`).
70    Regrole,
71    Regtype,
72    PgNodeTree,
73    AclItem,
74    Int2Vector,
75    OidVector,
76    AnyArray,
77    /// `PostgreSQL`'s anonymous composite pseudo-type (OID 2249).
78    Record,
79    /// A `PostgreSQL` array whose elements retain their declared SQL type.
80    /// Nested array bounds are represented recursively.
81    Array(Box<ColumnType>),
82    /// `DATE` columns store days since 1970-01-01.
83    Date,
84    /// `TIME` columns store microseconds since midnight.
85    Time,
86    /// `TIME(p)` with an explicit fractional-second precision.
87    TimePrecision(u32),
88    /// `TIME WITH TIME ZONE` columns store local time plus offset.
89    TimeTz,
90    /// `TIME(p) WITH TIME ZONE` with an explicit fractional-second precision.
91    TimeTzPrecision(u32),
92    /// `TIMESTAMP WITHOUT TIME ZONE` columns store naive microseconds
93    /// since 1970-01-01 00:00:00.
94    Timestamp,
95    /// `TIMESTAMP(p)` with an explicit fractional-second precision.
96    TimestampPrecision(u32),
97    /// `TIMESTAMP WITH TIME ZONE` columns store UTC microseconds since
98    /// 1970-01-01 00:00:00Z.
99    TimestampTz,
100    /// `TIMESTAMP(p) WITH TIME ZONE` with an explicit fractional-second precision.
101    TimestampTzPrecision(u32),
102    Interval,
103    /// An interval retaining its stored-field restriction and fractional-second precision.
104    IntervalWithFields {
105        fields: IntervalFields,
106        precision: Option<u32>,
107    },
108    /// One of `PostgreSQL`'s six built-in range identities. Values use a
109    /// canonical textual carrier so bounds remain durable across every
110    /// storage backend while the declared subtype stays in row metadata.
111    Range(RangeSubtype),
112    /// The `PostgreSQL` multirange paired with one built-in range subtype.
113    Multirange(RangeSubtype),
114    /// `VECTOR(N)` columns store an `N`-dimensional `f32` embedding.
115    Vector(u32),
116    /// `TENSOR(N)` columns store an array of `N`-dimensional `f32`
117    /// embeddings. The row remains the retrieval identity; vector
118    /// indexes score against the best element in the tensor.
119    Tensor(u32),
120    /// A named `PostgreSQL` domain retaining both its own type identity and the
121    /// base type used for value conversion and operator selection.
122    Domain {
123        schema: String,
124        name: String,
125        oid: u32,
126        base: Box<ColumnType>,
127    },
128}
129
130pub(crate) fn builtin_array_element_name(type_name: &str) -> Option<&'static str> {
131    Some(match type_name {
132        "_bool" => "bool",
133        "_bytea" => "bytea",
134        "_char" => "\"char\"",
135        "_name" => "name",
136        "_int8" => "int8",
137        "_int2" => "int2",
138        "_int2vector" => "int2vector",
139        "_int4" => "int4",
140        "_regproc" => "regproc",
141        "_regprocedure" => "regprocedure",
142        "_regclass" => "regclass",
143        "_regrole" => "regrole",
144        "_text" => "text",
145        "_refcursor" => "refcursor",
146        "_oid" => "oid",
147        "_oidvector" => "oidvector",
148        "_bpchar" => "bpchar",
149        "_varchar" => "varchar",
150        "_float4" => "float4",
151        "_float8" => "float8",
152        "_aclitem" => "aclitem",
153        "_date" => "date",
154        "_time" => "time",
155        "_timestamp" => "timestamp",
156        "_timestamptz" => "timestamptz",
157        "_interval" => "interval",
158        "_numeric" => "numeric",
159        "_timetz" => "timetz",
160        "_record" => "record",
161        "_uuid" => "uuid",
162        "_json" => "json",
163        "_jsonb" => "jsonb",
164        "_regtype" => "regtype",
165        "_xid" => "xid",
166        "_pg_node_tree" => "pg_node_tree",
167        "_int4range" => "int4range",
168        "_int8range" => "int8range",
169        "_numrange" => "numrange",
170        "_daterange" => "daterange",
171        "_tsrange" => "tsrange",
172        "_tstzrange" => "tstzrange",
173        "_int4multirange" => "int4multirange",
174        "_int8multirange" => "int8multirange",
175        "_nummultirange" => "nummultirange",
176        "_datemultirange" => "datemultirange",
177        "_tsmultirange" => "tsmultirange",
178        "_tstzmultirange" => "tstzmultirange",
179        _ => return None,
180    })
181}
182
183impl ColumnType {
184    /// Retain the SQL type identity without a declaration's length, scale, or temporal precision.
185    #[must_use]
186    pub fn without_type_modifiers(&self) -> Self {
187        self.without_type_modifiers_with_control(
188            &uqa_core::memory::ProductionControl::uncontrolled(),
189        )
190        .expect("ordinary type modifier removal cannot be limited or cancelled")
191        .into_uncontrolled()
192        .expect("ordinary type modifier removal has no reservation")
193    }
194
195    #[must_use]
196    pub const fn temporal_precision(&self) -> Option<u32> {
197        match self {
198            Self::IntervalWithFields { precision, .. } => *precision,
199            Self::TimePrecision(p)
200            | Self::TimeTzPrecision(p)
201            | Self::TimestampPrecision(p)
202            | Self::TimestampTzPrecision(p) => Some(*p),
203            _ => None,
204        }
205    }
206
207    #[must_use]
208    pub const fn without_temporal_modifiers(&self) -> &Self {
209        match self {
210            Self::IntervalWithFields { .. } => &Self::Interval,
211            Self::TimePrecision(_) => &Self::Time,
212            Self::TimeTzPrecision(_) => &Self::TimeTz,
213            Self::TimestampPrecision(_) => &Self::Timestamp,
214            Self::TimestampTzPrecision(_) => &Self::TimestampTz,
215            other => other,
216        }
217    }
218
219    pub(crate) fn with_temporal_precision(
220        self,
221        precision: Option<i64>,
222    ) -> Result<Self, crate::SQLError> {
223        let Some(precision) = precision else {
224            return Ok(self);
225        };
226        if precision < 0 {
227            return Err(crate::SQLError::Routine {
228                sqlstate: "22023".into(),
229                message: format!(
230                    "{} precision must not be negative",
231                    self.regtype_name().to_uppercase()
232                ),
233            });
234        }
235        let precision = u32::try_from(precision.min(6)).expect("bounded temporal precision");
236        Ok(match self {
237            Self::Time => Self::TimePrecision(precision),
238            Self::TimeTz => Self::TimeTzPrecision(precision),
239            Self::Timestamp => Self::TimestampPrecision(precision),
240            Self::TimestampTz => Self::TimestampTzPrecision(precision),
241            other => other,
242        })
243    }
244
245    pub(crate) fn with_interval_modifiers(
246        fields: IntervalFields,
247        precision: Option<i64>,
248    ) -> Result<Self, crate::SQLError> {
249        let precision = precision
250            .map(|precision| {
251                if precision < 0 {
252                    return Err(crate::SQLError::Routine {
253                        sqlstate: "22023".into(),
254                        message: "INTERVAL precision must not be negative".into(),
255                    });
256                }
257                Ok(u32::try_from(precision.min(6)).expect("bounded interval precision"))
258            })
259            .transpose()?;
260        if fields == IntervalFields::All && precision.is_none() {
261            return Ok(Self::Interval);
262        }
263        Ok(Self::IntervalWithFields { fields, precision })
264    }
265
266    #[must_use]
267    pub fn is_integer(&self) -> bool {
268        match self {
269            Self::SmallInteger | Self::Integer | Self::BigInteger | Self::Oid | Self::Xid => true,
270            Self::Domain { base, .. } => base.is_integer(),
271            _ => false,
272        }
273    }
274
275    #[must_use]
276    pub fn is_character_string(&self) -> bool {
277        match self {
278            Self::Text
279            | Self::Name
280            | Self::Varchar(_)
281            | Self::Bpchar
282            | Self::Character(_)
283            | Self::InternalChar
284            | Self::PgNodeTree
285            | Self::AclItem => true,
286            Self::Domain { base, .. } => base.is_character_string(),
287            _ => false,
288        }
289    }
290}