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