1use serde::{Deserialize, Serialize};
8
9use super::RangeSubtype;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub enum ColumnType {
13 SmallInteger,
14 Integer,
15 BigInteger,
16 Oid,
18 Xid,
20 Boolean,
21 Void,
23 Text,
24 RefCursor,
26 Name,
27 Uuid,
28 Varchar(Option<u32>),
29 Bpchar,
31 Character(u32),
35 Real,
36 DoublePrecision,
37 Numeric {
42 precision: Option<u32>,
43 scale: Option<i32>,
44 },
45 Json,
47 JsonB,
49 Bytea,
51 InternalChar,
53 Regproc,
54 Regprocedure,
56 Regclass,
58 Regnamespace,
60 Regrole,
62 Regtype,
63 PgNodeTree,
64 AclItem,
65 Int2Vector,
66 OidVector,
67 AnyArray,
68 Record,
70 Array(Box<ColumnType>),
73 Date,
75 Time,
77 TimeTz,
79 Timestamp,
82 TimestampTz,
85 Interval,
86 Range(RangeSubtype),
90 Multirange(RangeSubtype),
92 Vector(u32),
94 Tensor(u32),
98 Domain {
101 schema: String,
102 name: String,
103 oid: u32,
104 base: Box<ColumnType>,
105 },
106}
107
108pub(crate) fn builtin_array_element_name(type_name: &str) -> Option<&'static str> {
109 Some(match type_name {
110 "_bool" => "bool",
111 "_bytea" => "bytea",
112 "_char" => "\"char\"",
113 "_name" => "name",
114 "_int8" => "int8",
115 "_int2" => "int2",
116 "_int2vector" => "int2vector",
117 "_int4" => "int4",
118 "_regproc" => "regproc",
119 "_regprocedure" => "regprocedure",
120 "_regclass" => "regclass",
121 "_regrole" => "regrole",
122 "_text" => "text",
123 "_refcursor" => "refcursor",
124 "_oid" => "oid",
125 "_oidvector" => "oidvector",
126 "_bpchar" => "bpchar",
127 "_varchar" => "varchar",
128 "_float4" => "float4",
129 "_float8" => "float8",
130 "_aclitem" => "aclitem",
131 "_date" => "date",
132 "_time" => "time",
133 "_timestamp" => "timestamp",
134 "_timestamptz" => "timestamptz",
135 "_interval" => "interval",
136 "_numeric" => "numeric",
137 "_timetz" => "timetz",
138 "_record" => "record",
139 "_uuid" => "uuid",
140 "_json" => "json",
141 "_jsonb" => "jsonb",
142 "_regtype" => "regtype",
143 "_xid" => "xid",
144 "_pg_node_tree" => "pg_node_tree",
145 "_int4range" => "int4range",
146 "_int8range" => "int8range",
147 "_numrange" => "numrange",
148 "_daterange" => "daterange",
149 "_tsrange" => "tsrange",
150 "_tstzrange" => "tstzrange",
151 "_int4multirange" => "int4multirange",
152 "_int8multirange" => "int8multirange",
153 "_nummultirange" => "nummultirange",
154 "_datemultirange" => "datemultirange",
155 "_tsmultirange" => "tsmultirange",
156 "_tstzmultirange" => "tstzmultirange",
157 _ => return None,
158 })
159}
160
161impl ColumnType {
162 #[must_use]
163 pub fn is_integer(&self) -> bool {
164 match self {
165 Self::SmallInteger | Self::Integer | Self::BigInteger | Self::Oid | Self::Xid => true,
166 Self::Domain { base, .. } => base.is_integer(),
167 _ => false,
168 }
169 }
170
171 #[must_use]
172 pub fn is_character_string(&self) -> bool {
173 match self {
174 Self::Text
175 | Self::Name
176 | Self::Varchar(_)
177 | Self::Bpchar
178 | Self::Character(_)
179 | Self::InternalChar
180 | Self::PgNodeTree
181 | Self::AclItem => true,
182 Self::Domain { base, .. } => base.is_character_string(),
183 _ => false,
184 }
185 }
186
187 #[expect(
191 clippy::too_many_lines,
192 reason = "exhaustive AST migration preserves every serialized variant"
193 )]
194 pub fn from_sql_name(name: &str) -> Result<Self, crate::SQLError> {
195 let normalized = name.trim().to_ascii_lowercase();
196 if let Some(element) = builtin_array_element_name(&normalized) {
197 return Self::from_sql_name(element).map(|ty| Self::Array(Box::new(ty)));
198 }
199 if let Some(element) = normalized.strip_suffix("[]") {
200 let element_type = Self::from_sql_name(element)?;
201 if matches!(element_type, Self::Void) {
202 return Err(crate::SQLError::Routine {
203 sqlstate: "42704".into(),
204 message: format!("type \"{normalized}\" does not exist"),
205 });
206 }
207 return Ok(Self::Array(Box::new(element_type)));
208 }
209 let (base, modifier) = normalized
210 .strip_suffix(')')
211 .and_then(|prefix| prefix.rsplit_once('('))
212 .map_or((normalized.as_str(), None), |(base, modifier)| {
213 (base.trim(), Some(modifier.trim()))
214 });
215 let base = base.strip_prefix("pg_catalog.").unwrap_or(base);
216 let character_length = || -> Result<Option<u32>, crate::SQLError> {
217 modifier
218 .map(|value| {
219 value
220 .parse::<u32>()
221 .ok()
222 .filter(|length| *length > 0)
223 .ok_or_else(|| {
224 crate::SQLError::TypeMismatch(format!(
225 "character length must be greater than zero, got {value}"
226 ))
227 })
228 })
229 .transpose()
230 };
231 match base {
232 "smallint" | "int2" | "smallserial" | "serial2" => Ok(Self::SmallInteger),
233 "integer" | "int" | "int4" | "serial" | "serial4" => Ok(Self::Integer),
234 "bigint" | "int8" | "bigserial" | "serial8" => Ok(Self::BigInteger),
235 "oid" => Ok(Self::Oid),
236 "xid" => Ok(Self::Xid),
237 "boolean" | "bool" => Ok(Self::Boolean),
238 "void" => Ok(Self::Void),
239 "text" => Ok(Self::Text),
240 "refcursor" => Ok(Self::RefCursor),
241 "name" => Ok(Self::Name),
242 "uuid" => Ok(Self::Uuid),
243 "varchar" | "character varying" => Ok(Self::Varchar(character_length()?)),
244 "character" | "char" => Ok(Self::Character(character_length()?.unwrap_or(1))),
245 "bpchar" => Ok(character_length()?.map_or(Self::Bpchar, Self::Character)),
246 "real" | "float4" => Ok(Self::Real),
247 "double" | "double precision" | "float8" => Ok(Self::DoublePrecision),
248 "numeric" | "decimal" => {
249 let (precision, scale) = match modifier {
250 None => (None, None),
251 Some(modifier) => {
252 let mut parts = modifier.split(',').map(str::trim);
253 let precision = parts
254 .next()
255 .and_then(|value| value.parse::<u32>().ok())
256 .ok_or_else(|| {
257 crate::SQLError::TypeMismatch(format!(
258 "invalid numeric modifier `{modifier}`"
259 ))
260 })?;
261 let scale = parts
262 .next()
263 .map(|value| value.parse::<i32>())
264 .transpose()
265 .map_err(|_| {
266 crate::SQLError::TypeMismatch(format!(
267 "invalid numeric modifier `{modifier}`"
268 ))
269 })?
270 .unwrap_or(0);
271 if parts.next().is_some() {
272 return Err(crate::SQLError::TypeMismatch(format!(
273 "invalid numeric modifier `{modifier}`"
274 )));
275 }
276 (Some(precision), Some(scale))
277 }
278 };
279 Ok(Self::Numeric { precision, scale })
280 }
281 "json" => Ok(Self::Json),
282 "jsonb" => Ok(Self::JsonB),
283 "bytea" => Ok(Self::Bytea),
284 "\"char\"" => Ok(Self::InternalChar),
285 "regproc" => Ok(Self::Regproc),
286 "regprocedure" => Ok(Self::Regprocedure),
287 "regclass" => Ok(Self::Regclass),
288 "regnamespace" => Ok(Self::Regnamespace),
289 "regrole" => Ok(Self::Regrole),
290 "regtype" => Ok(Self::Regtype),
291 "pg_node_tree" => Ok(Self::PgNodeTree),
292 "aclitem" => Ok(Self::AclItem),
293 "int2vector" => Ok(Self::Int2Vector),
294 "oidvector" => Ok(Self::OidVector),
295 "anyarray" => Ok(Self::AnyArray),
296 "record" => Ok(Self::Record),
297 "date" => Ok(Self::Date),
298 "time" | "time without time zone" => Ok(Self::Time),
299 "timetz" | "time with time zone" => Ok(Self::TimeTz),
300 "timestamp" | "datetime" | "timestamp without time zone" => Ok(Self::Timestamp),
301 "timestamptz" | "timestamp with time zone" => Ok(Self::TimestampTz),
302 "interval" => Ok(Self::Interval),
303 "int4range" => Ok(Self::Range(RangeSubtype::Integer)),
304 "int8range" => Ok(Self::Range(RangeSubtype::BigInteger)),
305 "numrange" => Ok(Self::Range(RangeSubtype::Numeric)),
306 "daterange" => Ok(Self::Range(RangeSubtype::Date)),
307 "tsrange" => Ok(Self::Range(RangeSubtype::Timestamp)),
308 "tstzrange" => Ok(Self::Range(RangeSubtype::TimestampTz)),
309 "int4multirange" => Ok(Self::Multirange(RangeSubtype::Integer)),
310 "int8multirange" => Ok(Self::Multirange(RangeSubtype::BigInteger)),
311 "nummultirange" => Ok(Self::Multirange(RangeSubtype::Numeric)),
312 "datemultirange" => Ok(Self::Multirange(RangeSubtype::Date)),
313 "tsmultirange" => Ok(Self::Multirange(RangeSubtype::Timestamp)),
314 "tstzmultirange" => Ok(Self::Multirange(RangeSubtype::TimestampTz)),
315 "vector" => modifier
316 .and_then(|value| value.parse::<u32>().ok())
317 .filter(|dimension| *dimension > 0)
318 .map(Self::Vector)
319 .ok_or_else(|| crate::SQLError::TypeMismatch("VECTOR requires a dimension".into())),
320 "tensor" => modifier
321 .and_then(|value| value.parse::<u32>().ok())
322 .filter(|dimension| *dimension > 0)
323 .map(Self::Tensor)
324 .ok_or_else(|| crate::SQLError::TypeMismatch("TENSOR requires a dimension".into())),
325 other => Err(crate::SQLError::Unsupported(format!(
326 "SQL type `{other}` is not supported"
327 ))),
328 }
329 }
330
331 #[must_use]
332 pub fn sql_name(&self) -> String {
333 match self {
334 Self::SmallInteger => "smallint".into(),
335 Self::Integer => "integer".into(),
336 Self::BigInteger => "bigint".into(),
337 Self::Oid => "oid".into(),
338 Self::Xid => "xid".into(),
339 Self::Boolean => "boolean".into(),
340 Self::Void => "void".into(),
341 Self::Text => "text".into(),
342 Self::RefCursor => "refcursor".into(),
343 Self::Name => "name".into(),
344 Self::Uuid => "uuid".into(),
345 Self::Varchar(Some(length)) => format!("character varying({length})"),
346 Self::Varchar(None) => "character varying".into(),
347 Self::Bpchar => "bpchar".into(),
348 Self::Character(length) => format!("character({length})"),
349 Self::Real => "real".into(),
350 Self::DoublePrecision => "double precision".into(),
351 Self::Numeric {
352 precision: Some(precision),
353 scale: Some(scale),
354 } => format!("numeric({precision},{scale})"),
355 Self::Numeric { .. } => "numeric".into(),
356 Self::Json => "json".into(),
357 Self::JsonB => "jsonb".into(),
358 Self::Bytea => "bytea".into(),
359 Self::InternalChar => "\"char\"".into(),
360 Self::Regproc => "regproc".into(),
361 Self::Regprocedure => "regprocedure".into(),
362 Self::Regclass => "regclass".into(),
363 Self::Regnamespace => "regnamespace".into(),
364 Self::Regrole => "regrole".into(),
365 Self::Regtype => "regtype".into(),
366 Self::PgNodeTree => "pg_node_tree".into(),
367 Self::AclItem => "aclitem".into(),
368 Self::Int2Vector => "int2vector".into(),
369 Self::OidVector => "oidvector".into(),
370 Self::AnyArray => "anyarray".into(),
371 Self::Record => "record".into(),
372 Self::Array(element) => format!("{}[]", element.sql_name()),
373 Self::Date => "date".into(),
374 Self::Time => "time without time zone".into(),
375 Self::TimeTz => "time with time zone".into(),
376 Self::Timestamp => "timestamp without time zone".into(),
377 Self::TimestampTz => "timestamp with time zone".into(),
378 Self::Interval => "interval".into(),
379 Self::Range(subtype) => subtype.range_name().into(),
380 Self::Multirange(subtype) => subtype.multirange_name().into(),
381 Self::Vector(dimension) => format!("vector({dimension})"),
382 Self::Tensor(dimension) => format!("tensor({dimension})"),
383 Self::Domain { schema, name, .. } => format!("{schema}.{name}"),
384 }
385 }
386
387 #[must_use]
390 pub fn regtype_name(&self) -> String {
391 match self {
392 Self::Varchar(_) => "character varying".into(),
393 Self::Bpchar | Self::Character(_) => "character".into(),
394 Self::Numeric { .. } => "numeric".into(),
395 Self::Vector(_) => "vector".into(),
396 Self::Tensor(_) => "tensor".into(),
397 Self::Domain { schema, name, .. } => format!("{schema}.{name}"),
398 Self::Array(element) => format!("{}[]", element.regtype_name()),
399 other => other.sql_name(),
400 }
401 }
402}