1use serde::{Deserialize, Serialize};
8
9use super::{IntervalFields, RangeSubtype};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub enum ColumnType {
13 Named(String),
15 SmallInteger,
16 Integer,
17 BigInteger,
18 Oid,
20 Xid,
22 Boolean,
23 Void,
25 Text,
26 RefCursor,
28 Name,
29 Uuid,
30 Varchar(Option<u32>),
31 Bpchar,
33 Character(u32),
37 Real,
38 DoublePrecision,
39 Numeric {
44 precision: Option<u32>,
45 scale: Option<i32>,
46 },
47 Json,
49 JsonB,
51 Bytea,
53 InternalChar,
55 Regproc,
56 Regprocedure,
58 Regclass,
60 Regnamespace,
62 Regrole,
64 Regtype,
65 PgNodeTree,
66 AclItem,
67 Int2Vector,
68 OidVector,
69 AnyArray,
70 Record,
72 Array(Box<ColumnType>),
75 Date,
77 Time,
79 TimePrecision(u32),
81 TimeTz,
83 TimeTzPrecision(u32),
85 Timestamp,
88 TimestampPrecision(u32),
90 TimestampTz,
93 TimestampTzPrecision(u32),
95 Interval,
96 IntervalWithFields {
98 fields: IntervalFields,
99 precision: Option<u32>,
100 },
101 Range(RangeSubtype),
105 Multirange(RangeSubtype),
107 Vector(u32),
109 Tensor(u32),
113 Domain {
116 schema: String,
117 name: String,
118 oid: u32,
119 base: Box<ColumnType>,
120 },
121}
122
123pub(crate) fn builtin_array_element_name(type_name: &str) -> Option<&'static str> {
124 Some(match type_name {
125 "_bool" => "bool",
126 "_bytea" => "bytea",
127 "_char" => "\"char\"",
128 "_name" => "name",
129 "_int8" => "int8",
130 "_int2" => "int2",
131 "_int2vector" => "int2vector",
132 "_int4" => "int4",
133 "_regproc" => "regproc",
134 "_regprocedure" => "regprocedure",
135 "_regclass" => "regclass",
136 "_regrole" => "regrole",
137 "_text" => "text",
138 "_refcursor" => "refcursor",
139 "_oid" => "oid",
140 "_oidvector" => "oidvector",
141 "_bpchar" => "bpchar",
142 "_varchar" => "varchar",
143 "_float4" => "float4",
144 "_float8" => "float8",
145 "_aclitem" => "aclitem",
146 "_date" => "date",
147 "_time" => "time",
148 "_timestamp" => "timestamp",
149 "_timestamptz" => "timestamptz",
150 "_interval" => "interval",
151 "_numeric" => "numeric",
152 "_timetz" => "timetz",
153 "_record" => "record",
154 "_uuid" => "uuid",
155 "_json" => "json",
156 "_jsonb" => "jsonb",
157 "_regtype" => "regtype",
158 "_xid" => "xid",
159 "_pg_node_tree" => "pg_node_tree",
160 "_int4range" => "int4range",
161 "_int8range" => "int8range",
162 "_numrange" => "numrange",
163 "_daterange" => "daterange",
164 "_tsrange" => "tsrange",
165 "_tstzrange" => "tstzrange",
166 "_int4multirange" => "int4multirange",
167 "_int8multirange" => "int8multirange",
168 "_nummultirange" => "nummultirange",
169 "_datemultirange" => "datemultirange",
170 "_tsmultirange" => "tsmultirange",
171 "_tstzmultirange" => "tstzmultirange",
172 _ => return None,
173 })
174}
175
176impl ColumnType {
177 #[must_use]
179 pub fn without_type_modifiers(&self) -> Self {
180 match self {
181 Self::Varchar(_) => Self::Varchar(None),
182 Self::Character(_) => Self::Bpchar,
183 Self::Numeric { .. } => Self::Numeric {
184 precision: None,
185 scale: None,
186 },
187 Self::Array(element) => Self::Array(Box::new(element.without_type_modifiers())),
188 other => other.without_temporal_modifiers().clone(),
189 }
190 }
191
192 #[must_use]
193 pub const fn temporal_precision(&self) -> Option<u32> {
194 match self {
195 Self::IntervalWithFields { precision, .. } => *precision,
196 Self::TimePrecision(p)
197 | Self::TimeTzPrecision(p)
198 | Self::TimestampPrecision(p)
199 | Self::TimestampTzPrecision(p) => Some(*p),
200 _ => None,
201 }
202 }
203
204 #[must_use]
205 pub const fn without_temporal_modifiers(&self) -> &Self {
206 match self {
207 Self::IntervalWithFields { .. } => &Self::Interval,
208 Self::TimePrecision(_) => &Self::Time,
209 Self::TimeTzPrecision(_) => &Self::TimeTz,
210 Self::TimestampPrecision(_) => &Self::Timestamp,
211 Self::TimestampTzPrecision(_) => &Self::TimestampTz,
212 other => other,
213 }
214 }
215
216 pub(crate) fn with_temporal_precision(
217 self,
218 precision: Option<i64>,
219 ) -> Result<Self, crate::SQLError> {
220 let Some(precision) = precision else {
221 return Ok(self);
222 };
223 if precision < 0 {
224 return Err(crate::SQLError::Routine {
225 sqlstate: "22023".into(),
226 message: format!(
227 "{} precision must not be negative",
228 self.regtype_name().to_uppercase()
229 ),
230 });
231 }
232 let precision = u32::try_from(precision.min(6)).expect("bounded temporal precision");
233 Ok(match self {
234 Self::Time => Self::TimePrecision(precision),
235 Self::TimeTz => Self::TimeTzPrecision(precision),
236 Self::Timestamp => Self::TimestampPrecision(precision),
237 Self::TimestampTz => Self::TimestampTzPrecision(precision),
238 other => other,
239 })
240 }
241
242 pub(crate) fn with_interval_modifiers(
243 fields: IntervalFields,
244 precision: Option<i64>,
245 ) -> Result<Self, crate::SQLError> {
246 let precision = precision
247 .map(|precision| {
248 if precision < 0 {
249 return Err(crate::SQLError::Routine {
250 sqlstate: "22023".into(),
251 message: "INTERVAL precision must not be negative".into(),
252 });
253 }
254 Ok(u32::try_from(precision.min(6)).expect("bounded interval precision"))
255 })
256 .transpose()?;
257 if fields == IntervalFields::All && precision.is_none() {
258 return Ok(Self::Interval);
259 }
260 Ok(Self::IntervalWithFields { fields, precision })
261 }
262
263 #[must_use]
264 pub fn is_integer(&self) -> bool {
265 match self {
266 Self::SmallInteger | Self::Integer | Self::BigInteger | Self::Oid | Self::Xid => true,
267 Self::Domain { base, .. } => base.is_integer(),
268 _ => false,
269 }
270 }
271
272 #[must_use]
273 pub fn is_character_string(&self) -> bool {
274 match self {
275 Self::Text
276 | Self::Name
277 | Self::Varchar(_)
278 | Self::Bpchar
279 | Self::Character(_)
280 | Self::InternalChar
281 | Self::PgNodeTree
282 | Self::AclItem => true,
283 Self::Domain { base, .. } => base.is_character_string(),
284 _ => false,
285 }
286 }
287
288 #[expect(
292 clippy::too_many_lines,
293 reason = "exhaustive AST migration preserves every serialized variant"
294 )]
295 pub fn from_sql_name(name: &str) -> Result<Self, crate::SQLError> {
296 let normalized = name.trim().to_ascii_lowercase();
297 if let Some(element) = builtin_array_element_name(&normalized) {
298 return Self::from_sql_name(element).map(|ty| Self::Array(Box::new(ty)));
299 }
300 if let Some(element) = normalized.strip_suffix("[]") {
301 let element_type = Self::from_sql_name(element)?;
302 if matches!(element_type, Self::Void) {
303 return Err(crate::SQLError::Routine {
304 sqlstate: "42704".into(),
305 message: format!("type \"{normalized}\" does not exist"),
306 });
307 }
308 return Ok(Self::Array(Box::new(element_type)));
309 }
310 let (base, modifier) = split_type_modifier(&normalized);
311 let base = base.strip_prefix("pg_catalog.").unwrap_or(&base);
312 let temporal_precision = || {
313 modifier
314 .map(|value| {
315 value.trim().parse::<i64>().map_err(|_| {
316 crate::SQLError::TypeMismatch(format!(
317 "invalid temporal precision: {value}"
318 ))
319 })
320 })
321 .transpose()
322 };
323 let character_length = || -> Result<Option<u32>, crate::SQLError> {
324 modifier
325 .map(|value| {
326 value
327 .parse::<u32>()
328 .ok()
329 .filter(|length| *length > 0)
330 .ok_or_else(|| {
331 crate::SQLError::TypeMismatch(format!(
332 "character length must be greater than zero, got {value}"
333 ))
334 })
335 })
336 .transpose()
337 };
338 match base {
339 "smallint" | "int2" => Ok(Self::SmallInteger),
340 "integer" | "int" | "int4" => Ok(Self::Integer),
341 "bigint" | "int8" => Ok(Self::BigInteger),
342 "oid" => Ok(Self::Oid),
343 "xid" => Ok(Self::Xid),
344 "boolean" | "bool" => Ok(Self::Boolean),
345 "void" => Ok(Self::Void),
346 "text" => Ok(Self::Text),
347 "refcursor" => Ok(Self::RefCursor),
348 "name" => Ok(Self::Name),
349 "uuid" => Ok(Self::Uuid),
350 "varchar" | "character varying" => Ok(Self::Varchar(character_length()?)),
351 "character" | "char" => Ok(Self::Character(character_length()?.unwrap_or(1))),
352 "bpchar" => Ok(character_length()?.map_or(Self::Bpchar, Self::Character)),
353 "real" | "float4" => Ok(Self::Real),
354 "double" | "double precision" | "float8" => Ok(Self::DoublePrecision),
355 "numeric" | "decimal" => {
356 let (precision, scale) = match modifier {
357 None => (None, None),
358 Some(modifier) => {
359 let mut parts = modifier.split(',').map(str::trim);
360 let precision = parts
361 .next()
362 .and_then(|value| value.parse::<u32>().ok())
363 .ok_or_else(|| {
364 crate::SQLError::TypeMismatch(format!(
365 "invalid numeric modifier `{modifier}`"
366 ))
367 })?;
368 let scale = parts
369 .next()
370 .map(|value| value.parse::<i32>())
371 .transpose()
372 .map_err(|_| {
373 crate::SQLError::TypeMismatch(format!(
374 "invalid numeric modifier `{modifier}`"
375 ))
376 })?
377 .unwrap_or(0);
378 if parts.next().is_some() {
379 return Err(crate::SQLError::TypeMismatch(format!(
380 "invalid numeric modifier `{modifier}`"
381 )));
382 }
383 (Some(precision), Some(scale))
384 }
385 };
386 Ok(Self::Numeric { precision, scale })
387 }
388 "json" => Ok(Self::Json),
389 "jsonb" => Ok(Self::JsonB),
390 "bytea" => Ok(Self::Bytea),
391 "\"char\"" => Ok(Self::InternalChar),
392 "regproc" => Ok(Self::Regproc),
393 "regprocedure" => Ok(Self::Regprocedure),
394 "regclass" => Ok(Self::Regclass),
395 "regnamespace" => Ok(Self::Regnamespace),
396 "regrole" => Ok(Self::Regrole),
397 "regtype" => Ok(Self::Regtype),
398 "pg_node_tree" => Ok(Self::PgNodeTree),
399 "aclitem" => Ok(Self::AclItem),
400 "int2vector" => Ok(Self::Int2Vector),
401 "oidvector" => Ok(Self::OidVector),
402 "anyarray" => Ok(Self::AnyArray),
403 "record" => Ok(Self::Record),
404 "date" => Ok(Self::Date),
405 "time" | "time without time zone" => {
406 Self::Time.with_temporal_precision(temporal_precision()?)
407 }
408 "timetz" | "time with time zone" => {
409 Self::TimeTz.with_temporal_precision(temporal_precision()?)
410 }
411 "timestamp" | "datetime" | "timestamp without time zone" => {
412 Self::Timestamp.with_temporal_precision(temporal_precision()?)
413 }
414 "timestamptz" | "timestamp with time zone" => {
415 Self::TimestampTz.with_temporal_precision(temporal_precision()?)
416 }
417 "interval" => Self::with_interval_modifiers(IntervalFields::All, temporal_precision()?),
418 other if other.starts_with("interval ") => {
419 let fields = IntervalFields::from_sql_suffix(&other[9..]).ok_or_else(|| {
420 crate::SQLError::TypeMismatch(format!("invalid interval fields: {other}"))
421 })?;
422 Self::with_interval_modifiers(fields, temporal_precision()?)
423 }
424 "int4range" => Ok(Self::Range(RangeSubtype::Integer)),
425 "int8range" => Ok(Self::Range(RangeSubtype::BigInteger)),
426 "numrange" => Ok(Self::Range(RangeSubtype::Numeric)),
427 "daterange" => Ok(Self::Range(RangeSubtype::Date)),
428 "tsrange" => Ok(Self::Range(RangeSubtype::Timestamp)),
429 "tstzrange" => Ok(Self::Range(RangeSubtype::TimestampTz)),
430 "int4multirange" => Ok(Self::Multirange(RangeSubtype::Integer)),
431 "int8multirange" => Ok(Self::Multirange(RangeSubtype::BigInteger)),
432 "nummultirange" => Ok(Self::Multirange(RangeSubtype::Numeric)),
433 "datemultirange" => Ok(Self::Multirange(RangeSubtype::Date)),
434 "tsmultirange" => Ok(Self::Multirange(RangeSubtype::Timestamp)),
435 "tstzmultirange" => Ok(Self::Multirange(RangeSubtype::TimestampTz)),
436 "vector" => modifier
437 .and_then(|value| value.parse::<u32>().ok())
438 .filter(|dimension| *dimension > 0)
439 .map(Self::Vector)
440 .ok_or_else(|| crate::SQLError::TypeMismatch("VECTOR requires a dimension".into())),
441 "tensor" => modifier
442 .and_then(|value| value.parse::<u32>().ok())
443 .filter(|dimension| *dimension > 0)
444 .map(Self::Tensor)
445 .ok_or_else(|| crate::SQLError::TypeMismatch("TENSOR requires a dimension".into())),
446 other => Err(crate::SQLError::Unsupported(format!(
447 "SQL type `{other}` is not supported"
448 ))),
449 }
450 }
451
452 #[must_use]
453 pub fn sql_name(&self) -> String {
454 match self {
455 Self::Named(name) => name.clone(),
456 Self::SmallInteger => "smallint".into(),
457 Self::Integer => "integer".into(),
458 Self::BigInteger => "bigint".into(),
459 Self::Oid => "oid".into(),
460 Self::Xid => "xid".into(),
461 Self::Boolean => "boolean".into(),
462 Self::Void => "void".into(),
463 Self::Text => "text".into(),
464 Self::RefCursor => "refcursor".into(),
465 Self::Name => "name".into(),
466 Self::Uuid => "uuid".into(),
467 Self::Varchar(Some(length)) => format!("character varying({length})"),
468 Self::Varchar(None) => "character varying".into(),
469 Self::Bpchar => "bpchar".into(),
470 Self::Character(length) => format!("character({length})"),
471 Self::Real => "real".into(),
472 Self::DoublePrecision => "double precision".into(),
473 Self::Numeric {
474 precision: Some(precision),
475 scale: Some(scale),
476 } => format!("numeric({precision},{scale})"),
477 Self::Numeric { .. } => "numeric".into(),
478 Self::Json => "json".into(),
479 Self::JsonB => "jsonb".into(),
480 Self::Bytea => "bytea".into(),
481 Self::InternalChar => "\"char\"".into(),
482 Self::Regproc => "regproc".into(),
483 Self::Regprocedure => "regprocedure".into(),
484 Self::Regclass => "regclass".into(),
485 Self::Regnamespace => "regnamespace".into(),
486 Self::Regrole => "regrole".into(),
487 Self::Regtype => "regtype".into(),
488 Self::PgNodeTree => "pg_node_tree".into(),
489 Self::AclItem => "aclitem".into(),
490 Self::Int2Vector => "int2vector".into(),
491 Self::OidVector => "oidvector".into(),
492 Self::AnyArray => "anyarray".into(),
493 Self::Record => "record".into(),
494 Self::Array(element) => format!("{}[]", element.sql_name()),
495 Self::Date => "date".into(),
496 Self::Time => "time without time zone".into(),
497 Self::TimePrecision(p) => format!("time({p}) without time zone"),
498 Self::TimeTz => "time with time zone".into(),
499 Self::TimeTzPrecision(p) => format!("time({p}) with time zone"),
500 Self::Timestamp => "timestamp without time zone".into(),
501 Self::TimestampPrecision(p) => format!("timestamp({p}) without time zone"),
502 Self::TimestampTz => "timestamp with time zone".into(),
503 Self::TimestampTzPrecision(p) => format!("timestamp({p}) with time zone"),
504 Self::Interval => "interval".into(),
505 Self::IntervalWithFields { fields, precision } => {
506 let precision =
507 precision.map_or_else(String::new, |precision| format!("({precision})"));
508 format!("interval{}{precision}", fields.sql_suffix())
509 }
510 Self::Range(subtype) => subtype.range_name().into(),
511 Self::Multirange(subtype) => subtype.multirange_name().into(),
512 Self::Vector(dimension) => format!("vector({dimension})"),
513 Self::Tensor(dimension) => format!("tensor({dimension})"),
514 Self::Domain { schema, name, .. } => format!(
515 "{}.{}",
516 crate::compiler::render_relation_component(schema),
517 crate::compiler::render_relation_component(name)
518 ),
519 }
520 }
521
522 #[must_use]
525 pub fn regtype_name(&self) -> String {
526 if matches!(self, Self::IntervalWithFields { .. }) {
527 return "interval".into();
528 }
529 if self.temporal_precision().is_some() {
530 return self.without_temporal_modifiers().regtype_name();
531 }
532 match self {
533 Self::Varchar(_) => "character varying".into(),
534 Self::Bpchar | Self::Character(_) => "character".into(),
535 Self::Numeric { .. } => "numeric".into(),
536 Self::Vector(_) => "vector".into(),
537 Self::Tensor(_) => "tensor".into(),
538 Self::Domain { .. } => self.sql_name(),
539 Self::Array(element) => format!("{}[]", element.regtype_name()),
540 other => other.sql_name(),
541 }
542 }
543}
544
545pub(crate) fn split_type_modifier(ty: &str) -> (std::borrow::Cow<'_, str>, Option<&str>) {
547 use std::borrow::Cow;
548 match (ty.find('('), ty.rfind(')')) {
549 (Some(open), Some(close)) if close > open => {
550 let prefix = ty[..open].trim_end();
551 let suffix = ty[close + 1..].trim();
552 let base = if suffix.is_empty() {
553 Cow::Borrowed(prefix)
554 } else {
555 Cow::Owned(format!("{prefix} {suffix}"))
556 };
557 (base, Some(ty[open + 1..close].trim()))
558 }
559 _ => (Cow::Borrowed(ty), None),
560 }
561}