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