Skip to main content

uqa_sql/ast/types/
names.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Type names stream into the caller's ordinary or admitted destination without intermediate owned names.
8
9use super::ColumnType;
10use std::fmt;
11use uqa_core::{
12    memory::{Produced, ProductionControl},
13    ValueRetentionError,
14};
15
16impl ColumnType {
17    #[must_use]
18    pub fn sql_name(&self) -> String {
19        TypeName {
20            ty: self,
21            regtype: false,
22        }
23        .to_string()
24    }
25
26    /// A name that resolves back to this exact type independently of the search path and of later renames: the OID identity of a user-defined type, and the SQL name of a built-in type.
27    #[must_use]
28    pub fn catalog_name(&self) -> String {
29        self.user_type_identity().unwrap_or_else(|| self.sql_name())
30    }
31
32    /// Name emitted by `PostgreSQL`'s regtype output, including `pg_typeof`.
33    #[must_use]
34    pub fn regtype_name(&self) -> String {
35        TypeName {
36            ty: self,
37            regtype: true,
38        }
39        .to_string()
40    }
41
42    pub fn sql_name_with_control(
43        &self,
44        control: &ProductionControl<'_>,
45    ) -> Result<Produced<String>, ValueRetentionError> {
46        control.format(format_args!(
47            "{}",
48            TypeName {
49                ty: self,
50                regtype: false
51            }
52        ))
53    }
54
55    pub fn regtype_name_with_control(
56        &self,
57        control: &ProductionControl<'_>,
58    ) -> Result<Produced<String>, ValueRetentionError> {
59        control.format(format_args!(
60            "{}",
61            TypeName {
62                ty: self,
63                regtype: true
64            }
65        ))
66    }
67}
68
69struct TypeName<'a> {
70    ty: &'a ColumnType,
71    regtype: bool,
72}
73
74impl fmt::Display for TypeName<'_> {
75    #[expect(
76        clippy::too_many_lines,
77        reason = "one formatter preserves exhaustive SQL and regtype spellings"
78    )]
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        if self.regtype {
81            if matches!(self.ty, ColumnType::IntervalWithFields { .. }) {
82                return f.write_str("interval");
83            }
84            if self.ty.temporal_precision().is_some() {
85                return TypeName {
86                    ty: self.ty.without_temporal_modifiers(),
87                    regtype: true,
88                }
89                .fmt(f);
90            }
91            match self.ty {
92                ColumnType::Varchar(_) => return f.write_str("character varying"),
93                ColumnType::Bpchar | ColumnType::Character(_) => return f.write_str("character"),
94                ColumnType::Numeric { .. } => return f.write_str("numeric"),
95                ColumnType::Vector(_) => return f.write_str("vector"),
96                ColumnType::Tensor(_) => return f.write_str("tensor"),
97                ColumnType::Array(element) => {
98                    return write!(
99                        f,
100                        "{}[]",
101                        TypeName {
102                            ty: element,
103                            regtype: true
104                        }
105                    )
106                }
107                _ => {}
108            }
109        }
110        match self.ty {
111            ColumnType::Named(name) => f.write_str(name),
112            // Regtype output qualifies a user-defined type whose schema is not visible, as `format_type_be` does; other spellings name an enum without its schema.
113            ColumnType::Enum(reference) if self.regtype => f.write_str(
114                &super::display::visible_type_name(&reference.schema, &reference.name),
115            ),
116            ColumnType::Enum(reference) => f.write_str(&crate::expr::quote_ident(&reference.name)),
117            ColumnType::Composite(reference) if self.regtype => f.write_str(
118                &super::display::visible_type_name(&reference.schema, &reference.name),
119            ),
120            ColumnType::Composite(reference) => {
121                f.write_str(&crate::expr::quote_ident(&reference.name))
122            }
123            ColumnType::SmallInteger => f.write_str("smallint"),
124            ColumnType::Integer => f.write_str("integer"),
125            ColumnType::BigInteger => f.write_str("bigint"),
126            ColumnType::Oid => f.write_str("oid"),
127            ColumnType::Xid => f.write_str("xid"),
128            ColumnType::Boolean => f.write_str("boolean"),
129            ColumnType::Void => f.write_str("void"),
130            ColumnType::Text => f.write_str("text"),
131            ColumnType::RefCursor => f.write_str("refcursor"),
132            ColumnType::Name => f.write_str("name"),
133            ColumnType::Uuid => f.write_str("uuid"),
134            ColumnType::Varchar(Some(length)) => write!(f, "character varying({length})"),
135            ColumnType::Varchar(None) => f.write_str("character varying"),
136            ColumnType::Bpchar => f.write_str("bpchar"),
137            ColumnType::Character(length) => write!(f, "character({length})"),
138            ColumnType::Real => f.write_str("real"),
139            ColumnType::DoublePrecision => f.write_str("double precision"),
140            ColumnType::Numeric {
141                precision: Some(precision),
142                scale: Some(scale),
143            } => write!(f, "numeric({precision},{scale})"),
144            ColumnType::Numeric { .. } => f.write_str("numeric"),
145            ColumnType::Json => f.write_str("json"),
146            ColumnType::JsonB => f.write_str("jsonb"),
147            ColumnType::Bytea => f.write_str("bytea"),
148            ColumnType::InternalChar => f.write_str("\"char\""),
149            ColumnType::Regproc => f.write_str("regproc"),
150            ColumnType::Regprocedure => f.write_str("regprocedure"),
151            ColumnType::Regclass => f.write_str("regclass"),
152            ColumnType::Regnamespace => f.write_str("regnamespace"),
153            ColumnType::Regrole => f.write_str("regrole"),
154            ColumnType::Regtype => f.write_str("regtype"),
155            ColumnType::PgNodeTree => f.write_str("pg_node_tree"),
156            ColumnType::AclItem => f.write_str("aclitem"),
157            ColumnType::Int2Vector => f.write_str("int2vector"),
158            ColumnType::OidVector => f.write_str("oidvector"),
159            ColumnType::AnyArray => f.write_str("anyarray"),
160            ColumnType::Record => f.write_str("record"),
161            ColumnType::Array(element) => write!(
162                f,
163                "{}[]",
164                TypeName {
165                    ty: element,
166                    regtype: false
167                }
168            ),
169            ColumnType::Date => f.write_str("date"),
170            ColumnType::Time => f.write_str("time without time zone"),
171            ColumnType::TimePrecision(p) => write!(f, "time({p}) without time zone"),
172            ColumnType::TimeTz => f.write_str("time with time zone"),
173            ColumnType::TimeTzPrecision(p) => write!(f, "time({p}) with time zone"),
174            ColumnType::Timestamp => f.write_str("timestamp without time zone"),
175            ColumnType::TimestampPrecision(p) => write!(f, "timestamp({p}) without time zone"),
176            ColumnType::TimestampTz => f.write_str("timestamp with time zone"),
177            ColumnType::TimestampTzPrecision(p) => write!(f, "timestamp({p}) with time zone"),
178            ColumnType::Interval => f.write_str("interval"),
179            ColumnType::IntervalWithFields { fields, precision } => {
180                write!(f, "interval{}", fields.sql_suffix())?;
181                if let Some(precision) = precision {
182                    write!(f, "({precision})")?;
183                }
184                Ok(())
185            }
186            ColumnType::Range(subtype) => f.write_str(subtype.range_name()),
187            ColumnType::Multirange(subtype) => f.write_str(subtype.multirange_name()),
188            ColumnType::Vector(dimension) => write!(f, "vector({dimension})"),
189            ColumnType::Tensor(dimension) => write!(f, "tensor({dimension})"),
190            ColumnType::Domain { schema, name, .. } if self.regtype => {
191                f.write_str(&super::display::visible_type_name(schema, name))
192            }
193            ColumnType::Domain { schema, name, .. } => {
194                crate::compiler::write_relation_component(schema, f)?;
195                f.write_str(".")?;
196                crate::compiler::write_relation_component(name, f)
197            }
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests;