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` role object identifier (`pg_catalog.regrole`).
76    Regrole,
77    Regtype,
78    PgNodeTree,
79    AclItem,
80    Int2Vector,
81    OidVector,
82    AnyArray,
83    /// `PostgreSQL`'s anonymous composite pseudo-type (OID 2249).
84    Record,
85    /// A `PostgreSQL` array whose elements retain their declared SQL type.
86    /// Nested array bounds are represented recursively.
87    Array(Box<ColumnType>),
88    /// `DATE` columns store days since 1970-01-01.
89    Date,
90    /// `TIME` columns store microseconds since midnight.
91    Time,
92    /// `TIME(p)` with an explicit fractional-second precision.
93    TimePrecision(u32),
94    /// `TIME WITH TIME ZONE` columns store local time plus offset.
95    TimeTz,
96    /// `TIME(p) WITH TIME ZONE` with an explicit fractional-second precision.
97    TimeTzPrecision(u32),
98    /// `TIMESTAMP WITHOUT TIME ZONE` columns store naive microseconds
99    /// since 1970-01-01 00:00:00.
100    Timestamp,
101    /// `TIMESTAMP(p)` with an explicit fractional-second precision.
102    TimestampPrecision(u32),
103    /// `TIMESTAMP WITH TIME ZONE` columns store UTC microseconds since
104    /// 1970-01-01 00:00:00Z.
105    TimestampTz,
106    /// `TIMESTAMP(p) WITH TIME ZONE` with an explicit fractional-second precision.
107    TimestampTzPrecision(u32),
108    Interval,
109    /// An interval retaining its stored-field restriction and fractional-second precision.
110    IntervalWithFields {
111        fields: IntervalFields,
112        precision: Option<u32>,
113    },
114    /// One of `PostgreSQL`'s six built-in range identities. Values use a
115    /// canonical textual carrier so bounds remain durable across every
116    /// storage backend while the declared subtype stays in row metadata.
117    Range(RangeSubtype),
118    /// The `PostgreSQL` multirange paired with one built-in range subtype.
119    Multirange(RangeSubtype),
120    /// `VECTOR(N)` columns store an `N`-dimensional `f32` embedding.
121    Vector(u32),
122    /// `TENSOR(N)` columns store an array of `N`-dimensional `f32`
123    /// embeddings. The row remains the retrieval identity; vector
124    /// indexes score against the best element in the tensor.
125    Tensor(u32),
126    /// A named `PostgreSQL` domain retaining both its own type identity and the
127    /// base type used for value conversion and operator selection.
128    Domain {
129        schema: String,
130        name: String,
131        oid: u32,
132        /// 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.
133        #[serde(default, skip_serializing_if = "Option::is_none")]
134        array_oid: Option<u32>,
135        base: Box<ColumnType>,
136    },
137    /// A user-defined enum type bound by catalog identity.
138    Enum(super::EnumTypeReference),
139    /// A composite type bound by catalog identity: a standalone composite type or a relation's row type.
140    Composite(super::CompositeTypeReference),
141}
142
143pub(crate) fn builtin_array_element_name(type_name: &str) -> Option<&'static str> {
144    Some(match type_name {
145        "_bool" => "bool",
146        "_bytea" => "bytea",
147        "_char" => "\"char\"",
148        "_name" => "name",
149        "_int8" => "int8",
150        "_int2" => "int2",
151        "_int2vector" => "int2vector",
152        "_int4" => "int4",
153        "_regproc" => "regproc",
154        "_regprocedure" => "regprocedure",
155        "_regclass" => "regclass",
156        "_regrole" => "regrole",
157        "_text" => "text",
158        "_refcursor" => "refcursor",
159        "_oid" => "oid",
160        "_oidvector" => "oidvector",
161        "_bpchar" => "bpchar",
162        "_varchar" => "varchar",
163        "_float4" => "float4",
164        "_float8" => "float8",
165        "_aclitem" => "aclitem",
166        "_date" => "date",
167        "_time" => "time",
168        "_timestamp" => "timestamp",
169        "_timestamptz" => "timestamptz",
170        "_interval" => "interval",
171        "_numeric" => "numeric",
172        "_timetz" => "timetz",
173        "_record" => "record",
174        "_uuid" => "uuid",
175        "_json" => "json",
176        "_jsonb" => "jsonb",
177        "_regtype" => "regtype",
178        "_xid" => "xid",
179        "_pg_node_tree" => "pg_node_tree",
180        "_int4range" => "int4range",
181        "_int8range" => "int8range",
182        "_numrange" => "numrange",
183        "_daterange" => "daterange",
184        "_tsrange" => "tsrange",
185        "_tstzrange" => "tstzrange",
186        "_int4multirange" => "int4multirange",
187        "_int8multirange" => "int8multirange",
188        "_nummultirange" => "nummultirange",
189        "_datemultirange" => "datemultirange",
190        "_tsmultirange" => "tsmultirange",
191        "_tstzmultirange" => "tstzmultirange",
192        _ => return None,
193    })
194}
195
196impl ColumnType {
197    /// Retain the SQL type identity without a declaration's length, scale, or temporal precision.
198    #[must_use]
199    pub fn without_type_modifiers(&self) -> Self {
200        self.without_type_modifiers_with_control(
201            &uqa_core::memory::ProductionControl::uncontrolled(),
202        )
203        .expect("ordinary type modifier removal cannot be limited or cancelled")
204        .into_uncontrolled()
205        .expect("ordinary type modifier removal has no reservation")
206    }
207
208    #[must_use]
209    pub const fn temporal_precision(&self) -> Option<u32> {
210        match self {
211            Self::IntervalWithFields { precision, .. } => *precision,
212            Self::TimePrecision(p)
213            | Self::TimeTzPrecision(p)
214            | Self::TimestampPrecision(p)
215            | Self::TimestampTzPrecision(p) => Some(*p),
216            _ => None,
217        }
218    }
219
220    #[must_use]
221    pub const fn without_temporal_modifiers(&self) -> &Self {
222        match self {
223            Self::IntervalWithFields { .. } => &Self::Interval,
224            Self::TimePrecision(_) => &Self::Time,
225            Self::TimeTzPrecision(_) => &Self::TimeTz,
226            Self::TimestampPrecision(_) => &Self::Timestamp,
227            Self::TimestampTzPrecision(_) => &Self::TimestampTz,
228            other => other,
229        }
230    }
231
232    pub(crate) fn with_temporal_precision(
233        self,
234        precision: Option<i64>,
235    ) -> Result<Self, crate::SQLError> {
236        let Some(precision) = precision else {
237            return Ok(self);
238        };
239        if precision < 0 {
240            return Err(crate::SQLError::Routine {
241                sqlstate: "22023".into(),
242                message: format!(
243                    "{} precision must not be negative",
244                    self.regtype_name().to_uppercase()
245                ),
246            });
247        }
248        let precision = u32::try_from(precision.min(6)).expect("bounded temporal precision");
249        Ok(match self {
250            Self::Time => Self::TimePrecision(precision),
251            Self::TimeTz => Self::TimeTzPrecision(precision),
252            Self::Timestamp => Self::TimestampPrecision(precision),
253            Self::TimestampTz => Self::TimestampTzPrecision(precision),
254            other => other,
255        })
256    }
257
258    pub(crate) fn with_interval_modifiers(
259        fields: IntervalFields,
260        precision: Option<i64>,
261    ) -> Result<Self, crate::SQLError> {
262        let precision = precision
263            .map(|precision| {
264                if precision < 0 {
265                    return Err(crate::SQLError::Routine {
266                        sqlstate: "22023".into(),
267                        message: "INTERVAL precision must not be negative".into(),
268                    });
269                }
270                Ok(u32::try_from(precision.min(6)).expect("bounded interval precision"))
271            })
272            .transpose()?;
273        if fields == IntervalFields::All && precision.is_none() {
274            return Ok(Self::Interval);
275        }
276        Ok(Self::IntervalWithFields { fields, precision })
277    }
278
279    #[must_use]
280    pub fn is_integer(&self) -> bool {
281        match self {
282            Self::SmallInteger | Self::Integer | Self::BigInteger | Self::Oid | Self::Xid => true,
283            Self::Domain { base, .. } => base.is_integer(),
284            _ => false,
285        }
286    }
287
288    #[must_use]
289    pub fn is_character_string(&self) -> bool {
290        match self {
291            Self::Text
292            | Self::Name
293            | Self::Varchar(_)
294            | Self::Bpchar
295            | Self::Character(_)
296            | Self::InternalChar
297            | Self::PgNodeTree
298            | Self::AclItem => true,
299            Self::Domain { base, .. } => base.is_character_string(),
300            _ => false,
301        }
302    }
303}