Skip to main content

mz_pgrepr/
types.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::error::Error;
11use std::fmt;
12use std::mem::size_of;
13use std::sync::LazyLock;
14
15use mz_repr::SqlScalarType;
16use mz_repr::adt::char::{CharLength as AdtCharLength, InvalidCharLengthError};
17use mz_repr::adt::mz_acl_item::{AclItem, MzAclItem};
18use mz_repr::adt::numeric::{
19    InvalidNumericMaxScaleError, NUMERIC_DATUM_MAX_PRECISION, NumericMaxScale,
20};
21use mz_repr::adt::timestamp::{
22    InvalidTimestampPrecisionError, TimestampPrecision as AdtTimestampPrecision,
23};
24use mz_repr::adt::varchar::{InvalidVarCharMaxLengthError, VarCharMaxLength};
25use mz_repr::namespaces::MZ_CATALOG_SCHEMA;
26
27use crate::oid;
28
29/// Mirror of PostgreSQL's [`VARHDRSZ`] constant.
30///
31/// [`VARHDRSZ`]: https://github.com/postgres/postgres/blob/REL_14_0/src/include/c.h#L627
32const VARHDRSZ: i32 = 4;
33
34/// Mirror of PostgreSQL's [`MAX_INTERVAL_PRECISION`] constant.
35///
36/// See: <https://github.com/postgres/postgres/blob/27b77ecf9/src/include/datatype/timestamp.h#L54>
37const MAX_INTERVAL_PRECISION: i32 = 6;
38
39/// Mirror of PostgreSQL's [`MAX_TIMESTAMP_PRECISION`] constant.
40///
41/// See: <https://github.com/postgres/postgres/blob/27b77ecf9/src/include/datatype/timestamp.h#L53>
42const MAX_TIMESTAMP_PRECISION: i32 = 6;
43
44/// Mirror of PostgreSQL's [`MAX_TIME_PRECISION`] constant.
45///
46/// See: <https://github.com/postgres/postgres/blob/27b77ecf9/src/include/utils/date.h#L51>
47const MAX_TIME_PRECISION: i32 = 6;
48
49/// The type of a [`Value`](crate::Value).
50///
51/// The [`Display`](fmt::Display) representation of a type is guaranteed to be
52/// valid PostgreSQL syntax that names the type and any modifiers.
53#[derive(Debug, Clone, Eq, PartialEq)]
54pub enum Type {
55    /// A variable-length multidimensional array of values.
56    Array(Box<Type>),
57    /// A boolean value.
58    Bool,
59    /// A byte array, i.e., a variable-length binary string.
60    Bytea,
61    /// A single-byte character.
62    Char,
63    /// A date.
64    Date,
65    /// A 4-byte floating point number.
66    Float4,
67    /// An 8-byte floating point number.
68    Float8,
69    /// A 2-byte signed integer.
70    Int2,
71    /// A 4-byte signed integer.
72    Int4,
73    /// An 8-byte signed integer.
74    Int8,
75    /// A 2-byte unsigned integer. This does not exist in PostgreSQL.
76    UInt2,
77    /// A 4-byte unsigned integer. This does not exist in PostgreSQL.
78    UInt4,
79    /// An 8-byte unsigned integer. This does not exist in PostgreSQL.
80    UInt8,
81    /// A time interval.
82    Interval {
83        /// Optional constraints on the type.
84        constraints: Option<IntervalConstraints>,
85    },
86    /// A textual JSON blob.
87    Json,
88    /// A binary JSON blob.
89    Jsonb,
90    /// A sequence of homogeneous values.
91    List(Box<Type>),
92    /// A map with text keys and homogeneous values.
93    Map {
94        /// The type of the values in the map.
95        value_type: Box<Type>,
96    },
97    /// A character type for storing identifiers of no more than 64 bytes
98    /// in length.
99    Name,
100    /// An arbitrary precision number.
101    Numeric {
102        /// Optional constraints on the type.
103        constraints: Option<NumericConstraints>,
104    },
105    /// An object identifier.
106    Oid,
107    /// A sequence of heterogeneous values.
108    Record(Vec<Type>),
109    /// A variable-length string.
110    Text,
111    /// A (usually) fixed-length string.
112    BpChar {
113        /// The length of the string.
114        ///
115        /// If unspecified, the type represents a variable-length string.
116        length: Option<CharLength>,
117    },
118    /// A variable-length string with an optional limit.
119    VarChar {
120        /// An optional maximum length to enforce, in characters.
121        max_length: Option<CharLength>,
122    },
123    /// A time of day without a day.
124    Time {
125        /// An optional precision for the fractional digits in the second field.
126        precision: Option<TimePrecision>,
127    },
128    /// A time with a time zone.
129    TimeTz {
130        /// An optional precision for the fractional digits in the second field.
131        precision: Option<TimePrecision>,
132    },
133    /// A date and time, without a timezone.
134    Timestamp {
135        /// An optional precision for the fractional digits in the second field.
136        precision: Option<TimestampPrecision>,
137    },
138    /// A date and time, with a timezone.
139    TimestampTz {
140        /// An optional precision for the fractional digits in the second field.
141        precision: Option<TimestampPrecision>,
142    },
143    /// A universally unique identifier.
144    Uuid,
145    /// A function name.
146    RegProc,
147    /// A type name.
148    RegType,
149    /// A class name.
150    RegClass,
151    /// A small int vector.
152    Int2Vector,
153    /// A Materialize timestamp.
154    MzTimestamp,
155    /// A range of values of the inner type.
156    Range {
157        /// The domain type.
158        element_type: Box<Type>,
159    },
160    /// A list of privileges granted to a user, that uses [`mz_repr::role_id::RoleId`]s for role
161    /// references.
162    MzAclItem,
163    /// A list of privileges granted to a user that uses [`mz_repr::adt::system::Oid`]s for role
164    /// references. This type is used primarily for compatibility with PostgreSQL.
165    AclItem,
166}
167
168/// An unpacked [`typmod`](Type::typmod) for a [`Type`].
169pub trait TypeConstraint: fmt::Display {
170    /// Unpacks the type constraint from a typmod value.
171    fn from_typmod(typmod: i32) -> Result<Option<Self>, String>
172    where
173        Self: Sized;
174
175    /// Packs the type constraint into a typmod value.
176    fn into_typmod(&self) -> i32;
177}
178
179/// A length associated with [`Type::Char`] and [`Type::VarChar`].
180#[derive(Debug, Clone, Copy, Eq, PartialEq)]
181pub struct CharLength(i32);
182
183impl TypeConstraint for CharLength {
184    fn from_typmod(typmod: i32) -> Result<Option<CharLength>, String> {
185        // https://github.com/postgres/postgres/blob/52377bb81/src/backend/utils/adt/varchar.c#L139
186        if typmod >= VARHDRSZ {
187            Ok(Some(CharLength(typmod - VARHDRSZ)))
188        } else {
189            Ok(None)
190        }
191    }
192
193    fn into_typmod(&self) -> i32 {
194        // https://github.com/postgres/postgres/blob/52377bb81/src/backend/utils/adt/varchar.c#L60-L65
195        self.0 + VARHDRSZ
196    }
197}
198
199impl CharLength {
200    /// Consumes the newtype wrapper, returning the contents as an `i32`.
201    pub fn into_i32(self) -> i32 {
202        self.0
203    }
204}
205
206impl From<AdtCharLength> for CharLength {
207    fn from(length: AdtCharLength) -> CharLength {
208        // The `AdtCharLength` newtype wrapper ensures that the inner `u32` is
209        // small enough to fit into an `i32` with room for `VARHDRSZ`.
210        CharLength(i32::try_from(length.into_u32()).unwrap())
211    }
212}
213
214impl From<VarCharMaxLength> for CharLength {
215    fn from(length: VarCharMaxLength) -> CharLength {
216        // The `VarCharMaxLength` newtype wrapper ensures that the inner `u32`
217        // is small enough to fit into an `i32` with room for `VARHDRSZ`.
218        CharLength(i32::try_from(length.into_u32()).unwrap())
219    }
220}
221
222impl fmt::Display for CharLength {
223    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224        // https://github.com/postgres/postgres/blob/52377bb81/src/backend/utils/adt/varchar.c#L77
225        write!(f, "({})", self.0)
226    }
227}
228
229/// Constraints associated with [`Type::Interval`]
230#[derive(Debug, Clone, Copy, Eq, PartialEq)]
231pub struct IntervalConstraints {
232    /// The range of the interval.
233    range: i32,
234    /// The precision of the interval.
235    precision: i32,
236}
237
238impl TypeConstraint for IntervalConstraints {
239    fn from_typmod(typmod: i32) -> Result<Option<IntervalConstraints>, String> {
240        if typmod < 0 {
241            Ok(None)
242        } else {
243            // https://github.com/postgres/postgres/blob/27b77ecf9/src/include/utils/timestamp.h#L53-L54
244            let range = (typmod >> 16) & 0x7fff;
245            let precision = typmod & 0xffff;
246            if precision > MAX_INTERVAL_PRECISION {
247                return Err(format!(
248                    "exceeds maximum interval precision {MAX_INTERVAL_PRECISION}"
249                ));
250            }
251            Ok(Some(IntervalConstraints { range, precision }))
252        }
253    }
254
255    fn into_typmod(&self) -> i32 {
256        (self.range << 16) | self.precision
257    }
258}
259
260impl fmt::Display for IntervalConstraints {
261    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262        // https://github.com/postgres/postgres/blob/27b77ecf9/src/include/utils/timestamp.h#L52
263        // TODO: handle output of range.
264        write!(f, "({})", self.precision)
265    }
266}
267
268/// A precision associated with [`Type::Time`] and [`Type::TimeTz`].
269#[derive(Debug, Clone, Copy, Eq, PartialEq)]
270pub struct TimePrecision(i32);
271
272impl TypeConstraint for TimePrecision {
273    fn from_typmod(typmod: i32) -> Result<Option<TimePrecision>, String> {
274        if typmod > MAX_TIME_PRECISION {
275            Err(format!(
276                "exceeds maximum time precision {MAX_TIME_PRECISION}"
277            ))
278        } else if typmod >= 0 {
279            Ok(Some(TimePrecision(typmod)))
280        } else {
281            Ok(None)
282        }
283    }
284
285    fn into_typmod(&self) -> i32 {
286        self.0
287    }
288}
289
290impl fmt::Display for TimePrecision {
291    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
292        // https://github.com/postgres/postgres/blob/27b77ecf9/src/backend/utils/adt/date.c#L97
293        write!(f, "({})", self.0)
294    }
295}
296
297/// A precision associated with [`Type::Timestamp`] and [`Type::TimestampTz`].
298#[derive(Debug, Clone, Copy, Eq, PartialEq)]
299pub struct TimestampPrecision(i32);
300
301impl TimestampPrecision {
302    /// Consumes the newtype wrapper, returning the contents as an `i32`.
303    pub fn into_i32(self) -> i32 {
304        self.0
305    }
306}
307
308impl TypeConstraint for TimestampPrecision {
309    fn from_typmod(typmod: i32) -> Result<Option<TimestampPrecision>, String> {
310        if typmod > MAX_TIMESTAMP_PRECISION {
311            Err(format!(
312                "exceeds maximum timestamp precision {MAX_TIMESTAMP_PRECISION}"
313            ))
314        } else if typmod >= 0 {
315            Ok(Some(TimestampPrecision(typmod)))
316        } else {
317            Ok(None)
318        }
319    }
320
321    fn into_typmod(&self) -> i32 {
322        self.0
323    }
324}
325
326impl From<AdtTimestampPrecision> for TimestampPrecision {
327    fn from(precision: AdtTimestampPrecision) -> TimestampPrecision {
328        TimestampPrecision(i32::from(precision.into_u8()))
329    }
330}
331
332impl fmt::Display for TimestampPrecision {
333    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
334        // https://github.com/postgres/postgres/blob/54bd1e43c/src/backend/utils/adt/timestamp.c#L131
335        write!(f, "({})", self.0)
336    }
337}
338
339/// Constraints on [`Type::Numeric`].
340#[derive(Debug, Clone, Copy, Eq, PartialEq)]
341pub struct NumericConstraints {
342    /// The maximum precision.
343    max_precision: i32,
344    /// The maximum scale.
345    max_scale: i32,
346}
347
348impl NumericConstraints {
349    /// Returns the maximum precision constraint.
350    pub fn max_precision(&self) -> i32 {
351        self.max_precision
352    }
353
354    /// Returns the maximum scale constraint.
355    pub fn max_scale(&self) -> i32 {
356        self.max_scale
357    }
358}
359
360impl TypeConstraint for NumericConstraints {
361    fn from_typmod(typmod: i32) -> Result<Option<NumericConstraints>, String> {
362        // https://github.com/postgres/postgres/blob/52377bb81/src/backend/utils/adt/numeric.c#L829-L862
363        if typmod >= VARHDRSZ {
364            Ok(Some(NumericConstraints {
365                max_precision: ((typmod - VARHDRSZ) >> 16) & 0xffff,
366                max_scale: (((typmod - VARHDRSZ) & 0x7ff) ^ 1024) - 1024,
367            }))
368        } else {
369            Ok(None)
370        }
371    }
372
373    fn into_typmod(&self) -> i32 {
374        // https://github.com/postgres/postgres/blob/52377bb81/src/backend/utils/adt/numeric.c#L826
375        ((self.max_precision << 16) | (self.max_scale & 0x7ff)) + VARHDRSZ
376    }
377}
378
379impl fmt::Display for NumericConstraints {
380    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381        // https://github.com/postgres/postgres/blob/52377bb81/src/backend/utils/adt/numeric.c#L1292-L1294
382        write!(f, "({},{})", self.max_precision, self.max_scale)
383    }
384}
385
386/// An anonymous [`Type::List`], akin to [`postgres_types::Type::ANYARRAY`].
387pub static LIST: LazyLock<postgres_types::Type> = LazyLock::new(|| {
388    postgres_types::Type::new(
389        "list".to_owned(),
390        // OID chosen to be the first OID not considered stable by
391        // PostgreSQL. See the "OID Assignment" section of the PostgreSQL
392        // documentation for details:
393        // https://www.postgresql.org/docs/current/system-catalog-initial-data.html#SYSTEM-CATALOG-OID-ASSIGNMENT
394        oid::TYPE_LIST_OID,
395        postgres_types::Kind::Pseudo,
396        MZ_CATALOG_SCHEMA.to_owned(),
397    )
398});
399
400/// An anonymous [`Type::Map`], akin to [`postgres_types::Type::ANYARRAY`].
401pub static MAP: LazyLock<postgres_types::Type> = LazyLock::new(|| {
402    postgres_types::Type::new(
403        "map".to_owned(),
404        // OID chosen to follow our "LIST" type.
405        oid::TYPE_MAP_OID,
406        postgres_types::Kind::Pseudo,
407        MZ_CATALOG_SCHEMA.to_owned(),
408    )
409});
410
411/// An anonymous [`Type::List`], akin to [`postgres_types::Type::ANYCOMPATIBLEARRAY`].
412pub static ANYCOMPATIBLELIST: LazyLock<postgres_types::Type> = LazyLock::new(|| {
413    postgres_types::Type::new(
414        "anycompatiblelist".to_owned(),
415        oid::TYPE_ANYCOMPATIBLELIST_OID,
416        postgres_types::Kind::Pseudo,
417        MZ_CATALOG_SCHEMA.to_owned(),
418    )
419});
420
421/// An anonymous [`Type::Map`], akin to [`postgres_types::Type::ANYCOMPATIBLEARRAY`].
422pub static ANYCOMPATIBLEMAP: LazyLock<postgres_types::Type> = LazyLock::new(|| {
423    postgres_types::Type::new(
424        "anycompatiblemap".to_owned(),
425        oid::TYPE_ANYCOMPATIBLEMAP_OID,
426        postgres_types::Kind::Pseudo,
427        MZ_CATALOG_SCHEMA.to_owned(),
428    )
429});
430
431/// An anonymous [`Type::UInt2`], akin to [`postgres_types::Type::INT2`].
432pub static UINT2: LazyLock<postgres_types::Type> = LazyLock::new(|| {
433    postgres_types::Type::new(
434        "uint2".to_owned(),
435        oid::TYPE_UINT2_OID,
436        postgres_types::Kind::Pseudo,
437        MZ_CATALOG_SCHEMA.to_owned(),
438    )
439});
440
441/// An anonymous [`Type::UInt4`], akin to [`postgres_types::Type::INT4`].
442pub static UINT4: LazyLock<postgres_types::Type> = LazyLock::new(|| {
443    postgres_types::Type::new(
444        "uint4".to_owned(),
445        oid::TYPE_UINT4_OID,
446        postgres_types::Kind::Pseudo,
447        MZ_CATALOG_SCHEMA.to_owned(),
448    )
449});
450
451/// An anonymous [`Type::UInt8`], akin to [`postgres_types::Type::INT8`].
452pub static UINT8: LazyLock<postgres_types::Type> = LazyLock::new(|| {
453    postgres_types::Type::new(
454        "uint8".to_owned(),
455        oid::TYPE_UINT8_OID,
456        postgres_types::Kind::Pseudo,
457        MZ_CATALOG_SCHEMA.to_owned(),
458    )
459});
460
461/// An anonymous [`Type::Array`], akin to [`postgres_types::Type::INT2_ARRAY`].
462pub static UINT2_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
463    postgres_types::Type::new(
464        "_uint2".to_owned(),
465        oid::TYPE_UINT2_ARRAY_OID,
466        postgres_types::Kind::Pseudo,
467        MZ_CATALOG_SCHEMA.to_owned(),
468    )
469});
470
471/// An anonymous [`Type::Array`], akin to [`postgres_types::Type::INT4_ARRAY`].
472pub static UINT4_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
473    postgres_types::Type::new(
474        "_uint4".to_owned(),
475        oid::TYPE_UINT4_ARRAY_OID,
476        postgres_types::Kind::Pseudo,
477        MZ_CATALOG_SCHEMA.to_owned(),
478    )
479});
480
481/// An anonymous [`Type::Array`], akin to [`postgres_types::Type::INT8_ARRAY`].
482pub static UINT8_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
483    postgres_types::Type::new(
484        "_uint8".to_owned(),
485        oid::TYPE_UINT8_ARRAY_OID,
486        postgres_types::Kind::Pseudo,
487        MZ_CATALOG_SCHEMA.to_owned(),
488    )
489});
490
491/// An anonymous [`Type::MzTimestamp`], akin to [`postgres_types::Type::TEXT`].
492pub static MZ_TIMESTAMP: LazyLock<postgres_types::Type> = LazyLock::new(|| {
493    postgres_types::Type::new(
494        "mz_timestamp".to_owned(),
495        oid::TYPE_MZ_TIMESTAMP_OID,
496        postgres_types::Kind::Pseudo,
497        MZ_CATALOG_SCHEMA.to_owned(),
498    )
499});
500
501/// An anonymous [`Type::Array`], akin to [`postgres_types::Type::TEXT_ARRAY`].
502pub static MZ_TIMESTAMP_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
503    postgres_types::Type::new(
504        "_mz_timestamp".to_owned(),
505        oid::TYPE_MZ_TIMESTAMP_ARRAY_OID,
506        postgres_types::Kind::Pseudo,
507        MZ_CATALOG_SCHEMA.to_owned(),
508    )
509});
510
511/// An anonymous [`Type::MzAclItem`], akin to [`postgres_types::Type::TEXT`].
512pub static MZ_ACL_ITEM: LazyLock<postgres_types::Type> = LazyLock::new(|| {
513    postgres_types::Type::new(
514        "mz_aclitem".to_owned(),
515        oid::TYPE_MZ_ACL_ITEM_OID,
516        postgres_types::Kind::Pseudo,
517        MZ_CATALOG_SCHEMA.to_owned(),
518    )
519});
520
521/// An anonymous [`Type::Array`], akin to [`postgres_types::Type::TEXT_ARRAY`].
522pub static MZ_ACL_ITEM_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
523    postgres_types::Type::new(
524        "_mz_aclitem".to_owned(),
525        oid::TYPE_MZ_ACL_ITEM_ARRAY_OID,
526        postgres_types::Kind::Pseudo,
527        MZ_CATALOG_SCHEMA.to_owned(),
528    )
529});
530
531impl Type {
532    /// Returns the type corresponding to the provided OID, if the OID is known.
533    pub fn from_oid(oid: u32) -> Result<Type, TypeFromOidError> {
534        Type::from_oid_and_typmod(oid, -1)
535    }
536
537    /// The name of the type with OID `oid`.
538    ///
539    /// Our existing [`Type::from_oid`] only accepts `oid`s that `rust-postgres`
540    /// knows about, thus we need a match statement to handle our custom `oid`s.
541    ///
542    /// We use this function such that our builtin relation `mz_catalog.mz_columns`
543    /// can get a type's name from its durably stored `oid` using a SQL scalar function.
544    /// However, we can replace the scalar function with a join on `mz_builtin_types` once it's been
545    /// implemented.
546    ///
547    /// TODO(SQL-495): Remove this function once `mz_builtin_types` is merged.
548    pub fn name_from_oid(oid: u32) -> Option<&'static str> {
549        let typ = match oid {
550            oid::TYPE_NAME_OID => Type::Name,
551            oid::TYPE_NAME_ARRAY_OID => Type::Array(Box::new(Type::Name)),
552            o if o == postgres_types::Type::ACLITEM.oid() => Type::AclItem,
553            o if o == postgres_types::Type::ACLITEM_ARRAY.oid() => {
554                Type::Array(Box::new(Type::AclItem))
555            }
556            oid::TYPE_UINT2_OID => Type::UInt2,
557            oid::TYPE_UINT2_ARRAY_OID => Type::Array(Box::new(Type::UInt2)),
558            oid::TYPE_UINT4_OID => Type::UInt4,
559            oid::TYPE_UINT4_ARRAY_OID => Type::Array(Box::new(Type::UInt4)),
560            oid::TYPE_UINT8_OID => Type::UInt8,
561            oid::TYPE_UINT8_ARRAY_OID => Type::Array(Box::new(Type::UInt8)),
562            oid::TYPE_MZ_TIMESTAMP_OID => Type::MzTimestamp,
563            oid::TYPE_MZ_TIMESTAMP_ARRAY_OID => Type::Array(Box::new(Type::MzTimestamp)),
564            oid::TYPE_MZ_ACL_ITEM_OID => Type::MzAclItem,
565            oid::TYPE_MZ_ACL_ITEM_ARRAY_OID => Type::Array(Box::new(Type::MzAclItem)),
566            oid::TYPE_RECORD_OID => Type::Record(vec![]),
567            oid::TYPE_RECORD_ARRAY_OID => Type::Array(Box::new(Type::Record(vec![]))),
568            oid::TYPE_LIST_OID => return Some(LIST.name()),
569            oid::TYPE_MAP_OID => return Some(MAP.name()),
570            _ => Type::from_oid(oid).ok()?,
571        };
572        Some(typ.name())
573    }
574
575    /// Returns the `Type` corresponding to the provided OID and packed type
576    /// modifier ("typmod").
577    ///
578    /// For details about typmods, see the [`typmod`](Type::typmod) method.
579    ///
580    /// # Errors
581    ///
582    /// Returns an error if the OID is unknown or if the typmod is invalid for
583    /// the type.
584    pub fn from_oid_and_typmod(oid: u32, typmod: i32) -> Result<Type, TypeFromOidError> {
585        let typ = postgres_types::Type::from_oid(oid).ok_or(TypeFromOidError::UnknownOid(oid))?;
586        let mut typ = match typ {
587            postgres_types::Type::BOOL => Type::Bool,
588            postgres_types::Type::BYTEA => Type::Bytea,
589            postgres_types::Type::DATE => Type::Date,
590            postgres_types::Type::FLOAT4 => Type::Float4,
591            postgres_types::Type::FLOAT8 => Type::Float8,
592            postgres_types::Type::INT2 => Type::Int2,
593            postgres_types::Type::INT4 => Type::Int4,
594            postgres_types::Type::INT8 => Type::Int8,
595            postgres_types::Type::INTERVAL => Type::Interval { constraints: None },
596            postgres_types::Type::JSON => Type::Json,
597            postgres_types::Type::JSONB => Type::Jsonb,
598            postgres_types::Type::NUMERIC => Type::Numeric { constraints: None },
599            postgres_types::Type::OID => Type::Oid,
600            postgres_types::Type::TEXT => Type::Text,
601            postgres_types::Type::BPCHAR => Type::BpChar { length: None },
602            postgres_types::Type::CHAR => Type::Char,
603            postgres_types::Type::VARCHAR => Type::VarChar { max_length: None },
604            postgres_types::Type::TIME => Type::Time { precision: None },
605            postgres_types::Type::TIMETZ => Type::TimeTz { precision: None },
606            postgres_types::Type::TIMESTAMP => Type::Timestamp { precision: None },
607            postgres_types::Type::TIMESTAMPTZ => Type::TimestampTz { precision: None },
608            postgres_types::Type::UUID => Type::Uuid,
609            postgres_types::Type::REGCLASS => Type::RegClass,
610            postgres_types::Type::REGPROC => Type::RegProc,
611            postgres_types::Type::REGTYPE => Type::RegType,
612            postgres_types::Type::BOOL_ARRAY => Type::Array(Box::new(Type::Bool)),
613            postgres_types::Type::BYTEA_ARRAY => Type::Array(Box::new(Type::Bytea)),
614            postgres_types::Type::BPCHAR_ARRAY => {
615                Type::Array(Box::new(Type::BpChar { length: None }))
616            }
617            postgres_types::Type::CHAR_ARRAY => Type::Array(Box::new(Type::Char)),
618            postgres_types::Type::DATE_ARRAY => Type::Array(Box::new(Type::Date)),
619            postgres_types::Type::FLOAT4_ARRAY => Type::Array(Box::new(Type::Float4)),
620            postgres_types::Type::FLOAT8_ARRAY => Type::Array(Box::new(Type::Float8)),
621            postgres_types::Type::INT2_ARRAY => Type::Array(Box::new(Type::Int2)),
622            postgres_types::Type::INT4_ARRAY => Type::Array(Box::new(Type::Int4)),
623            postgres_types::Type::INT8_ARRAY => Type::Array(Box::new(Type::Int8)),
624            postgres_types::Type::INTERVAL_ARRAY => {
625                Type::Array(Box::new(Type::Interval { constraints: None }))
626            }
627            postgres_types::Type::JSON_ARRAY => Type::Array(Box::new(Type::Json)),
628            postgres_types::Type::JSONB_ARRAY => Type::Array(Box::new(Type::Jsonb)),
629            postgres_types::Type::NUMERIC_ARRAY => {
630                Type::Array(Box::new(Type::Numeric { constraints: None }))
631            }
632            postgres_types::Type::OID_ARRAY => Type::Array(Box::new(Type::Oid)),
633            postgres_types::Type::TEXT_ARRAY => Type::Array(Box::new(Type::Text)),
634            postgres_types::Type::TIME_ARRAY => {
635                Type::Array(Box::new(Type::Time { precision: None }))
636            }
637            postgres_types::Type::TIMETZ_ARRAY => {
638                Type::Array(Box::new(Type::TimeTz { precision: None }))
639            }
640            postgres_types::Type::TIMESTAMP_ARRAY => {
641                Type::Array(Box::new(Type::Timestamp { precision: None }))
642            }
643            postgres_types::Type::TIMESTAMPTZ_ARRAY => {
644                Type::Array(Box::new(Type::TimestampTz { precision: None }))
645            }
646            postgres_types::Type::UUID_ARRAY => Type::Array(Box::new(Type::Uuid)),
647            postgres_types::Type::VARCHAR_ARRAY => {
648                Type::Array(Box::new(Type::VarChar { max_length: None }))
649            }
650            postgres_types::Type::REGCLASS_ARRAY => Type::Array(Box::new(Type::RegClass)),
651            postgres_types::Type::REGPROC_ARRAY => Type::Array(Box::new(Type::RegProc)),
652            postgres_types::Type::REGTYPE_ARRAY => Type::Array(Box::new(Type::RegType)),
653            postgres_types::Type::INT2_VECTOR => Type::Int2Vector,
654            postgres_types::Type::INT2_VECTOR_ARRAY => Type::Array(Box::new(Type::Int2Vector)),
655            postgres_types::Type::INT4_RANGE => Type::Range {
656                element_type: Box::new(Type::Int4),
657            },
658            postgres_types::Type::INT4_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
659                element_type: Box::new(Type::Int4),
660            })),
661            postgres_types::Type::INT8_RANGE => Type::Range {
662                element_type: Box::new(Type::Int8),
663            },
664            postgres_types::Type::INT8_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
665                element_type: Box::new(Type::Int8),
666            })),
667            postgres_types::Type::NUM_RANGE => Type::Range {
668                element_type: Box::new(Type::Numeric { constraints: None }),
669            },
670            postgres_types::Type::NUM_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
671                element_type: Box::new(Type::Numeric { constraints: None }),
672            })),
673            postgres_types::Type::TS_RANGE => Type::Range {
674                element_type: Box::new(Type::Timestamp { precision: None }),
675            },
676            postgres_types::Type::TS_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
677                element_type: Box::new(Type::Timestamp { precision: None }),
678            })),
679            postgres_types::Type::TSTZ_RANGE => Type::Range {
680                element_type: Box::new(Type::TimestampTz { precision: None }),
681            },
682            postgres_types::Type::TSTZ_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
683                element_type: Box::new(Type::TimestampTz { precision: None }),
684            })),
685            postgres_types::Type::DATE_RANGE => Type::Range {
686                element_type: Box::new(Type::Date),
687            },
688            postgres_types::Type::DATE_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
689                element_type: Box::new(Type::Date),
690            })),
691            _ => return Err(TypeFromOidError::UnknownOid(oid)),
692        };
693
694        // Apply the typmod. For arrays, the typmod applies to the element type.
695        // We use a funny-looking immediately-invoked closure to share the
696        // construction of the invalid typmod error across all error paths.
697        let res = ({
698            let typ = &mut typ;
699            || {
700                let elem_typ = match typ {
701                    Type::Array(typ) => &mut **typ,
702                    typ => typ,
703                };
704                match elem_typ {
705                    Type::BpChar { length } => *length = CharLength::from_typmod(typmod)?,
706                    Type::Numeric { constraints } => {
707                        *constraints = NumericConstraints::from_typmod(typmod)?
708                    }
709                    Type::Interval { constraints } => {
710                        *constraints = IntervalConstraints::from_typmod(typmod)?
711                    }
712                    Type::Time { precision } => *precision = TimePrecision::from_typmod(typmod)?,
713                    Type::TimeTz { precision } => *precision = TimePrecision::from_typmod(typmod)?,
714                    Type::Timestamp { precision } => {
715                        *precision = TimestampPrecision::from_typmod(typmod)?
716                    }
717                    Type::TimestampTz { precision } => {
718                        *precision = TimestampPrecision::from_typmod(typmod)?
719                    }
720                    Type::VarChar { max_length } => *max_length = CharLength::from_typmod(typmod)?,
721                    _ if typmod != -1 => return Err("type does not support type modifiers".into()),
722                    _ => (),
723                }
724                Ok(())
725            }
726        })();
727        match res {
728            Ok(()) => Ok(typ),
729            Err(detail) => Err(TypeFromOidError::InvalidTypmod {
730                typ,
731                typmod,
732                detail,
733            }),
734        }
735    }
736
737    pub(crate) fn inner(&self) -> &'static postgres_types::Type {
738        match self {
739            Type::AclItem => &postgres_types::Type::ACLITEM,
740            Type::Array(t) => match &**t {
741                Type::AclItem => &postgres_types::Type::ACLITEM_ARRAY,
742                Type::Array(_) => unreachable!(),
743                Type::Bool => &postgres_types::Type::BOOL_ARRAY,
744                Type::Bytea => &postgres_types::Type::BYTEA_ARRAY,
745                Type::Char => &postgres_types::Type::CHAR_ARRAY,
746                Type::Date => &postgres_types::Type::DATE_ARRAY,
747                Type::Float4 => &postgres_types::Type::FLOAT4_ARRAY,
748                Type::Float8 => &postgres_types::Type::FLOAT8_ARRAY,
749                Type::Int2 => &postgres_types::Type::INT2_ARRAY,
750                Type::Int4 => &postgres_types::Type::INT4_ARRAY,
751                Type::Int8 => &postgres_types::Type::INT8_ARRAY,
752                Type::UInt2 => &UINT2_ARRAY,
753                Type::UInt4 => &UINT4_ARRAY,
754                Type::UInt8 => &UINT8_ARRAY,
755                Type::Interval { .. } => &postgres_types::Type::INTERVAL_ARRAY,
756                Type::Json => &postgres_types::Type::JSON_ARRAY,
757                Type::Jsonb => &postgres_types::Type::JSONB_ARRAY,
758                Type::List(_) => unreachable!(),
759                Type::Map { .. } => unreachable!(),
760                Type::Name { .. } => &postgres_types::Type::NAME_ARRAY,
761                Type::Numeric { .. } => &postgres_types::Type::NUMERIC_ARRAY,
762                Type::Oid => &postgres_types::Type::OID_ARRAY,
763                Type::Record(_) => &postgres_types::Type::RECORD_ARRAY,
764                Type::Text => &postgres_types::Type::TEXT_ARRAY,
765                Type::BpChar { .. } => &postgres_types::Type::BPCHAR_ARRAY,
766                Type::VarChar { .. } => &postgres_types::Type::VARCHAR_ARRAY,
767                Type::Time { .. } => &postgres_types::Type::TIME_ARRAY,
768                Type::TimeTz { .. } => &postgres_types::Type::TIMETZ_ARRAY,
769                Type::Timestamp { .. } => &postgres_types::Type::TIMESTAMP_ARRAY,
770                Type::TimestampTz { .. } => &postgres_types::Type::TIMESTAMPTZ_ARRAY,
771                Type::Uuid => &postgres_types::Type::UUID_ARRAY,
772                Type::RegClass => &postgres_types::Type::REGCLASS_ARRAY,
773                Type::RegProc => &postgres_types::Type::REGPROC_ARRAY,
774                Type::RegType => &postgres_types::Type::REGTYPE_ARRAY,
775                Type::Int2Vector => &postgres_types::Type::INT2_VECTOR_ARRAY,
776                Type::MzTimestamp => &MZ_TIMESTAMP_ARRAY,
777                Type::Range { element_type } => match **element_type {
778                    Type::Int4 => &postgres_types::Type::INT4_RANGE_ARRAY,
779                    Type::Int8 => &postgres_types::Type::INT8_RANGE_ARRAY,
780                    Type::Numeric { .. } => &postgres_types::Type::NUM_RANGE_ARRAY,
781                    Type::Timestamp { .. } => &postgres_types::Type::TS_RANGE_ARRAY,
782                    Type::TimestampTz { .. } => &postgres_types::Type::TSTZ_RANGE_ARRAY,
783                    Type::Date => &postgres_types::Type::DATE_RANGE_ARRAY,
784                    _ => unreachable!(),
785                },
786                Type::MzAclItem => &MZ_ACL_ITEM_ARRAY,
787            },
788            Type::Bool => &postgres_types::Type::BOOL,
789            Type::Bytea => &postgres_types::Type::BYTEA,
790            Type::Char => &postgres_types::Type::CHAR,
791            Type::Date => &postgres_types::Type::DATE,
792            Type::Float4 => &postgres_types::Type::FLOAT4,
793            Type::Float8 => &postgres_types::Type::FLOAT8,
794            Type::Int2 => &postgres_types::Type::INT2,
795            Type::Int4 => &postgres_types::Type::INT4,
796            Type::Int8 => &postgres_types::Type::INT8,
797            Type::UInt2 => &UINT2,
798            Type::UInt4 => &UINT4,
799            Type::UInt8 => &UINT8,
800            Type::Interval { .. } => &postgres_types::Type::INTERVAL,
801            Type::Json => &postgres_types::Type::JSON,
802            Type::Jsonb => &postgres_types::Type::JSONB,
803            Type::List(_) => &LIST,
804            Type::Map { .. } => &MAP,
805            Type::Name => &postgres_types::Type::NAME,
806            Type::Numeric { .. } => &postgres_types::Type::NUMERIC,
807            Type::Oid => &postgres_types::Type::OID,
808            Type::Record(_) => &postgres_types::Type::RECORD,
809            Type::Text => &postgres_types::Type::TEXT,
810            Type::BpChar { .. } => &postgres_types::Type::BPCHAR,
811            Type::VarChar { .. } => &postgres_types::Type::VARCHAR,
812            Type::Time { .. } => &postgres_types::Type::TIME,
813            Type::TimeTz { .. } => &postgres_types::Type::TIMETZ,
814            Type::Timestamp { .. } => &postgres_types::Type::TIMESTAMP,
815            Type::TimestampTz { .. } => &postgres_types::Type::TIMESTAMPTZ,
816            Type::Uuid => &postgres_types::Type::UUID,
817            Type::RegClass => &postgres_types::Type::REGCLASS,
818            Type::RegProc => &postgres_types::Type::REGPROC,
819            Type::RegType => &postgres_types::Type::REGTYPE,
820            Type::Int2Vector => &postgres_types::Type::INT2_VECTOR,
821            Type::MzTimestamp => &MZ_TIMESTAMP,
822            Type::Range { element_type } => match &**element_type {
823                Type::Int4 => &postgres_types::Type::INT4_RANGE,
824                Type::Int8 => &postgres_types::Type::INT8_RANGE,
825                Type::Numeric { .. } => &postgres_types::Type::NUM_RANGE,
826                Type::Timestamp { .. } => &postgres_types::Type::TS_RANGE,
827                Type::TimestampTz { .. } => &postgres_types::Type::TSTZ_RANGE,
828                Type::Date => &postgres_types::Type::DATE_RANGE,
829                t => unreachable!("{t:?} is not a range element type"),
830            },
831            Type::MzAclItem => &MZ_ACL_ITEM,
832        }
833    }
834
835    /// Returns the item's name in a way that guarantees it's resolvable in the
836    /// catalog.
837    pub fn catalog_name(&self) -> &'static str {
838        self.inner().name()
839    }
840
841    /// Returns the user-friendly name that PostgreSQL uses for this type.
842    pub fn name(&self) -> &'static str {
843        // postgres_types' `name()` uses the pg_catalog name, and not the pretty
844        // SQL standard name.
845        match self.inner() {
846            &postgres_types::Type::ACLITEM_ARRAY => "aclitem[]",
847            &postgres_types::Type::BOOL_ARRAY => "boolean[]",
848            &postgres_types::Type::BYTEA_ARRAY => "bytea[]",
849            &postgres_types::Type::BPCHAR_ARRAY => "character[]",
850            &postgres_types::Type::DATE_ARRAY => "date[]",
851            &postgres_types::Type::FLOAT4_ARRAY => "real[]",
852            &postgres_types::Type::FLOAT8_ARRAY => "double precision[]",
853            &postgres_types::Type::INT2_ARRAY => "smallint[]",
854            &postgres_types::Type::INT4_ARRAY => "integer[]",
855            &postgres_types::Type::INT8_ARRAY => "bigint[]",
856            &postgres_types::Type::INTERVAL_ARRAY => "interval[]",
857            &postgres_types::Type::JSONB_ARRAY => "jsonb[]",
858            &postgres_types::Type::NUMERIC_ARRAY => "numeric[]",
859            &postgres_types::Type::OID_ARRAY => "oid[]",
860            &postgres_types::Type::RECORD_ARRAY => "record[]",
861            &postgres_types::Type::TEXT_ARRAY => "text[]",
862            &postgres_types::Type::TIME_ARRAY => "time[]",
863            &postgres_types::Type::TIMESTAMP_ARRAY => "timestamp without time zone[]",
864            &postgres_types::Type::TIMESTAMPTZ_ARRAY => "timestamp with time zone[]",
865            &postgres_types::Type::UUID_ARRAY => "uuid[]",
866            &postgres_types::Type::VARCHAR_ARRAY => "character varying[]",
867            &postgres_types::Type::BOOL => "boolean",
868            &postgres_types::Type::BPCHAR => "character",
869            &postgres_types::Type::CHAR => "\"char\"",
870            &postgres_types::Type::CHAR_ARRAY => "\"char\"[]",
871            &postgres_types::Type::FLOAT4 => "real",
872            &postgres_types::Type::FLOAT8 => "double precision",
873            &postgres_types::Type::INT2 => "smallint",
874            &postgres_types::Type::INT4 => "integer",
875            &postgres_types::Type::INT8 => "bigint",
876            &postgres_types::Type::TIMESTAMP => "timestamp without time zone",
877            &postgres_types::Type::TIMESTAMPTZ => "timestamp with time zone",
878            &postgres_types::Type::VARCHAR => "character varying",
879            &postgres_types::Type::REGCLASS_ARRAY => "regclass[]",
880            &postgres_types::Type::REGPROC_ARRAY => "regproc[]",
881            &postgres_types::Type::REGTYPE_ARRAY => "regtype[]",
882            &postgres_types::Type::INT2_VECTOR => "int2vector",
883            other => match other.oid() {
884                oid::TYPE_UINT2_ARRAY_OID => "uint2[]",
885                oid::TYPE_UINT4_ARRAY_OID => "uint4[]",
886                oid::TYPE_UINT8_ARRAY_OID => "uint8[]",
887                oid::TYPE_MZ_TIMESTAMP_ARRAY_OID => "mz_timestamp[]",
888                oid::TYPE_MZ_ACL_ITEM_ARRAY_OID => "mz_aclitem[]",
889                _ => other.name(),
890            },
891        }
892    }
893
894    /// Returns the [OID] of this type.
895    ///
896    /// [OID]: https://www.postgresql.org/docs/current/datatype-oid.html
897    pub fn oid(&self) -> u32 {
898        self.inner().oid()
899    }
900
901    /// Returns the constraint on the type, if any.
902    pub fn constraint(&self) -> Option<&dyn TypeConstraint> {
903        match self {
904            Type::BpChar {
905                length: Some(length),
906            } => Some(length),
907            Type::VarChar {
908                max_length: Some(max_length),
909            } => Some(max_length),
910            Type::Numeric {
911                constraints: Some(constraints),
912            } => Some(constraints),
913            Type::Interval {
914                constraints: Some(constraints),
915            } => Some(constraints),
916            Type::Time {
917                precision: Some(precision),
918            } => Some(precision),
919            Type::TimeTz {
920                precision: Some(precision),
921            } => Some(precision),
922            Type::Timestamp {
923                precision: Some(precision),
924            } => Some(precision),
925            Type::TimestampTz {
926                precision: Some(precision),
927            } => Some(precision),
928            Type::AclItem
929            | Type::Array(_)
930            | Type::Bool
931            | Type::Bytea
932            | Type::BpChar { length: None }
933            | Type::Char
934            | Type::Date
935            | Type::Float4
936            | Type::Float8
937            | Type::Int2
938            | Type::Int4
939            | Type::Int8
940            | Type::UInt2
941            | Type::UInt4
942            | Type::UInt8
943            | Type::Interval { constraints: None }
944            | Type::Json
945            | Type::Jsonb
946            | Type::List(_)
947            | Type::Map { .. }
948            | Type::Name
949            | Type::Numeric { constraints: None }
950            | Type::Int2Vector
951            | Type::Oid
952            | Type::Record(_)
953            | Type::RegClass
954            | Type::RegProc
955            | Type::RegType
956            | Type::Text
957            | Type::Time { precision: None }
958            | Type::TimeTz { precision: None }
959            | Type::Timestamp { precision: None }
960            | Type::TimestampTz { precision: None }
961            | Type::Uuid
962            | Type::MzTimestamp
963            | Type::VarChar { max_length: None }
964            | Type::Range { .. }
965            | Type::MzAclItem => None,
966        }
967    }
968
969    /// Returns the number of bytes in the binary representation of this
970    /// type, or -1 if the type has a variable-length representation.
971    pub fn typlen(&self) -> i16 {
972        match self {
973            Type::Array(_) => -1,
974            Type::Bool => 1,
975            Type::Bytea => -1,
976            Type::Char => 1,
977            Type::Date => 4,
978            Type::Float4 => 4,
979            Type::Float8 => 8,
980            Type::Int2 => 2,
981            Type::Int4 => 4,
982            Type::Int8 => 8,
983            Type::UInt2 => 2,
984            Type::UInt4 => 4,
985            Type::UInt8 => 8,
986            Type::Interval { .. } => 16,
987            Type::Json => -1,
988            Type::Jsonb => -1,
989            Type::List(_) => -1,
990            Type::Map { .. } => -1,
991            Type::Name { .. } => 64,
992            Type::Numeric { .. } => -1,
993            Type::Oid => 4,
994            Type::Record(_) => -1,
995            Type::Text => -1,
996            Type::BpChar { .. } => -1,
997            Type::VarChar { .. } => -1,
998            Type::Time { .. } => 8,
999            Type::TimeTz { .. } => 12,
1000            Type::Timestamp { .. } => 8,
1001            Type::TimestampTz { .. } => 8,
1002            Type::Uuid => 16,
1003            Type::RegClass => 4,
1004            Type::RegProc => 4,
1005            Type::RegType => 4,
1006            Type::Int2Vector => -1,
1007            // Don't hard code this because should it change in the future it
1008            // would be very difficult to remember to change this function.
1009            Type::MzTimestamp => size_of::<mz_repr::Timestamp>()
1010                .try_into()
1011                .expect("must fit"),
1012            Type::Range { .. } => -1,
1013            Type::MzAclItem => MzAclItem::binary_size().try_into().expect("must fit"),
1014            Type::AclItem => AclItem::binary_size().try_into().expect("must fit"),
1015        }
1016    }
1017
1018    /// Returns the packed type modifier ("typmod") for the type.
1019    ///
1020    /// The typmod is a 32-bit integer associated with the type that encodes
1021    /// optional constraints on the type. For example, the typmod on
1022    /// `Type::VarChar` encodes an optional constraint on the value's length.
1023    /// Most types are never associated with a typmod.
1024    ///
1025    /// Negative typmods indicate no constraint.
1026    pub fn typmod(&self) -> i32 {
1027        match self.constraint() {
1028            Some(constraint) => constraint.into_typmod(),
1029            None => -1,
1030        }
1031    }
1032}
1033
1034impl fmt::Display for Type {
1035    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1036        f.write_str(self.inner().name())?;
1037        if let Some(constraint) = self.constraint() {
1038            constraint.fmt(f)?;
1039        }
1040        Ok(())
1041    }
1042}
1043
1044impl TryFrom<&Type> for SqlScalarType {
1045    type Error = TypeConversionError;
1046
1047    fn try_from(typ: &Type) -> Result<SqlScalarType, TypeConversionError> {
1048        match typ {
1049            Type::AclItem => Ok(SqlScalarType::AclItem),
1050            Type::Array(t) => Ok(SqlScalarType::Array(Box::new(TryFrom::try_from(&**t)?))),
1051            Type::Bool => Ok(SqlScalarType::Bool),
1052            Type::Bytea => Ok(SqlScalarType::Bytes),
1053            Type::Char => Ok(SqlScalarType::PgLegacyChar),
1054            Type::Date => Ok(SqlScalarType::Date),
1055            Type::Float4 => Ok(SqlScalarType::Float32),
1056            Type::Float8 => Ok(SqlScalarType::Float64),
1057            Type::Int2 => Ok(SqlScalarType::Int16),
1058            Type::Int4 => Ok(SqlScalarType::Int32),
1059            Type::Int8 => Ok(SqlScalarType::Int64),
1060            Type::UInt2 => Ok(SqlScalarType::UInt16),
1061            Type::UInt4 => Ok(SqlScalarType::UInt32),
1062            Type::UInt8 => Ok(SqlScalarType::UInt64),
1063            Type::Interval { .. } => Ok(SqlScalarType::Interval),
1064            Type::Json => Err(TypeConversionError::UnsupportedType(Type::Json)),
1065            Type::Jsonb => Ok(SqlScalarType::Jsonb),
1066            Type::List(t) => Ok(SqlScalarType::List {
1067                element_type: Box::new(TryFrom::try_from(&**t)?),
1068                custom_id: None,
1069            }),
1070            Type::Map { value_type } => Ok(SqlScalarType::Map {
1071                value_type: Box::new(TryFrom::try_from(&**value_type)?),
1072                custom_id: None,
1073            }),
1074            Type::Name => Ok(SqlScalarType::PgLegacyName),
1075            Type::Numeric { constraints } => {
1076                let max_scale = match constraints {
1077                    Some(constraints) => {
1078                        if constraints.max_precision > i32::from(NUMERIC_DATUM_MAX_PRECISION) {
1079                            return Err(TypeConversionError::InvalidNumericConstraint(format!(
1080                                "precision for type numeric must be between 1 and {}",
1081                                NUMERIC_DATUM_MAX_PRECISION,
1082                            )));
1083                        }
1084                        if constraints.max_scale > constraints.max_precision {
1085                            return Err(TypeConversionError::InvalidNumericConstraint(format!(
1086                                "scale for type numeric must be between 0 and precision {}",
1087                                constraints.max_precision,
1088                            )));
1089                        }
1090                        Some(NumericMaxScale::try_from(i64::from(constraints.max_scale))?)
1091                    }
1092                    None => None,
1093                };
1094                Ok(SqlScalarType::Numeric { max_scale })
1095            }
1096            Type::Oid => Ok(SqlScalarType::Oid),
1097            Type::Record(_) => Ok(SqlScalarType::Record {
1098                fields: [].into(),
1099                custom_id: None,
1100            }),
1101            Type::Text => Ok(SqlScalarType::String),
1102            Type::Time { precision: None } => Ok(SqlScalarType::Time),
1103            Type::Time { precision: Some(_) } => {
1104                Err(TypeConversionError::UnsupportedType(typ.clone()))
1105            }
1106            Type::TimeTz { .. } => Err(TypeConversionError::UnsupportedType(typ.clone())),
1107            Type::BpChar { length } => Ok(SqlScalarType::Char {
1108                length: match length {
1109                    Some(length) => Some(AdtCharLength::try_from(i64::from(length.into_i32()))?),
1110                    None => None,
1111                },
1112            }),
1113            Type::VarChar { max_length } => Ok(SqlScalarType::VarChar {
1114                max_length: match max_length {
1115                    Some(max_length) => Some(VarCharMaxLength::try_from(i64::from(
1116                        max_length.into_i32(),
1117                    ))?),
1118                    None => None,
1119                },
1120            }),
1121            Type::Timestamp { precision } => Ok(SqlScalarType::Timestamp {
1122                precision: match precision {
1123                    Some(precision) => Some(AdtTimestampPrecision::try_from(i64::from(
1124                        precision.into_i32(),
1125                    ))?),
1126                    None => None,
1127                },
1128            }),
1129            Type::TimestampTz { precision } => Ok(SqlScalarType::TimestampTz {
1130                precision: match precision {
1131                    Some(precision) => Some(AdtTimestampPrecision::try_from(i64::from(
1132                        precision.into_i32(),
1133                    ))?),
1134                    None => None,
1135                },
1136            }),
1137            Type::Uuid => Ok(SqlScalarType::Uuid),
1138            Type::RegClass => Ok(SqlScalarType::RegClass),
1139            Type::RegProc => Ok(SqlScalarType::RegProc),
1140            Type::RegType => Ok(SqlScalarType::RegType),
1141            Type::Int2Vector => Ok(SqlScalarType::Int2Vector),
1142            Type::MzTimestamp => Ok(SqlScalarType::MzTimestamp),
1143            Type::Range { element_type } => Ok(SqlScalarType::Range {
1144                element_type: Box::new(TryFrom::try_from(&**element_type)?),
1145            }),
1146            Type::MzAclItem => Ok(SqlScalarType::MzAclItem),
1147        }
1148    }
1149}
1150
1151/// An error that can occur when constructing a [`Type`] from an OID.
1152#[derive(Debug, Clone)]
1153pub enum TypeFromOidError {
1154    /// The OID does not specify a known type.
1155    UnknownOid(u32),
1156    /// The specified typmod is invalid for the type.
1157    InvalidTypmod {
1158        /// The type.
1159        typ: Type,
1160        /// The type modifier.
1161        typmod: i32,
1162        /// Details about the nature of the invalidity.
1163        detail: String,
1164    },
1165}
1166
1167impl fmt::Display for TypeFromOidError {
1168    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1169        match self {
1170            TypeFromOidError::UnknownOid(oid) => write!(f, "type with OID {oid} is unknown"),
1171            TypeFromOidError::InvalidTypmod {
1172                typ,
1173                typmod,
1174                detail,
1175            } => {
1176                write!(
1177                    f,
1178                    "typmod {typmod} is invalid for type {}: {detail}",
1179                    typ.name()
1180                )
1181            }
1182        }
1183    }
1184}
1185
1186impl Error for TypeFromOidError {}
1187
1188/// An error that can occur when converting a [`Type`] to a [`SqlScalarType`].
1189#[derive(Debug, Clone)]
1190pub enum TypeConversionError {
1191    /// The source type is unsupported as a `SqlScalarType`.
1192    UnsupportedType(Type),
1193    /// The source type contained an invalid max scale for a
1194    /// [`SqlScalarType::Numeric`].
1195    InvalidNumericMaxScale(InvalidNumericMaxScaleError),
1196    /// The source type contained an invalid constraint for a
1197    /// [`SqlScalarType::Numeric`].
1198    InvalidNumericConstraint(String),
1199    /// The source type contained an invalid length for a
1200    /// [`SqlScalarType::Char`].
1201    InvalidCharLength(InvalidCharLengthError),
1202    /// The source type contained an invalid max length for a
1203    /// [`SqlScalarType::VarChar`].
1204    InvalidVarCharMaxLength(InvalidVarCharMaxLengthError),
1205    /// The source type contained an invalid precision for a
1206    /// [`SqlScalarType::Timestamp`] or [`SqlScalarType::TimestampTz`].
1207    InvalidTimestampPrecision(InvalidTimestampPrecisionError),
1208}
1209
1210impl fmt::Display for TypeConversionError {
1211    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1212        match self {
1213            TypeConversionError::UnsupportedType(ty) => write!(f, "type {ty} not supported"),
1214            TypeConversionError::InvalidNumericMaxScale(e) => e.fmt(f),
1215            TypeConversionError::InvalidNumericConstraint(msg) => f.write_str(msg),
1216            TypeConversionError::InvalidCharLength(e) => e.fmt(f),
1217            TypeConversionError::InvalidVarCharMaxLength(e) => e.fmt(f),
1218            TypeConversionError::InvalidTimestampPrecision(e) => e.fmt(f),
1219        }
1220    }
1221}
1222
1223impl Error for TypeConversionError {}
1224
1225impl From<InvalidNumericMaxScaleError> for TypeConversionError {
1226    fn from(e: InvalidNumericMaxScaleError) -> TypeConversionError {
1227        TypeConversionError::InvalidNumericMaxScale(e)
1228    }
1229}
1230
1231impl From<InvalidCharLengthError> for TypeConversionError {
1232    fn from(e: InvalidCharLengthError) -> TypeConversionError {
1233        TypeConversionError::InvalidCharLength(e)
1234    }
1235}
1236
1237impl From<InvalidVarCharMaxLengthError> for TypeConversionError {
1238    fn from(e: InvalidVarCharMaxLengthError) -> TypeConversionError {
1239        TypeConversionError::InvalidVarCharMaxLength(e)
1240    }
1241}
1242
1243impl From<InvalidTimestampPrecisionError> for TypeConversionError {
1244    fn from(e: InvalidTimestampPrecisionError) -> TypeConversionError {
1245        TypeConversionError::InvalidTimestampPrecision(e)
1246    }
1247}
1248
1249impl From<&SqlScalarType> for Type {
1250    fn from(typ: &SqlScalarType) -> Type {
1251        match typ {
1252            SqlScalarType::AclItem => Type::AclItem,
1253            SqlScalarType::Array(t) => Type::Array(Box::new(From::from(&**t))),
1254            SqlScalarType::Bool => Type::Bool,
1255            SqlScalarType::Bytes => Type::Bytea,
1256            SqlScalarType::PgLegacyChar => Type::Char,
1257            SqlScalarType::Date => Type::Date,
1258            SqlScalarType::Float64 => Type::Float8,
1259            SqlScalarType::Float32 => Type::Float4,
1260            SqlScalarType::Int16 => Type::Int2,
1261            SqlScalarType::Int32 => Type::Int4,
1262            SqlScalarType::Int64 => Type::Int8,
1263            SqlScalarType::UInt16 => Type::UInt2,
1264            SqlScalarType::UInt32 => Type::UInt4,
1265            SqlScalarType::UInt64 => Type::UInt8,
1266            SqlScalarType::Interval => Type::Interval { constraints: None },
1267            SqlScalarType::Jsonb => Type::Jsonb,
1268            SqlScalarType::List { element_type, .. } => {
1269                Type::List(Box::new(From::from(&**element_type)))
1270            }
1271            SqlScalarType::Map { value_type, .. } => Type::Map {
1272                value_type: Box::new(From::from(&**value_type)),
1273            },
1274            SqlScalarType::PgLegacyName => Type::Name,
1275            SqlScalarType::Oid => Type::Oid,
1276            SqlScalarType::Record { fields, .. } => Type::Record(
1277                fields
1278                    .iter()
1279                    .map(|(_name, ty)| Type::from(&ty.scalar_type))
1280                    .collect(),
1281            ),
1282            SqlScalarType::String => Type::Text,
1283            SqlScalarType::Char { length } => Type::BpChar {
1284                length: (*length).map(CharLength::from),
1285            },
1286            SqlScalarType::VarChar { max_length } => Type::VarChar {
1287                max_length: (*max_length).map(CharLength::from),
1288            },
1289            SqlScalarType::Time => Type::Time { precision: None },
1290            SqlScalarType::Timestamp { precision } => Type::Timestamp {
1291                precision: (*precision).map(TimestampPrecision::from),
1292            },
1293            SqlScalarType::TimestampTz { precision } => Type::TimestampTz {
1294                precision: (*precision).map(TimestampPrecision::from),
1295            },
1296            SqlScalarType::Uuid => Type::Uuid,
1297            SqlScalarType::Numeric { max_scale } => Type::Numeric {
1298                constraints: max_scale.map(|max_scale| NumericConstraints {
1299                    max_precision: i32::from(NUMERIC_DATUM_MAX_PRECISION),
1300                    max_scale: i32::from(max_scale.into_u8()),
1301                }),
1302            },
1303            SqlScalarType::RegClass => Type::RegClass,
1304            SqlScalarType::RegProc => Type::RegProc,
1305            SqlScalarType::RegType => Type::RegType,
1306            SqlScalarType::Int2Vector => Type::Int2Vector,
1307            SqlScalarType::MzTimestamp => Type::MzTimestamp,
1308            SqlScalarType::Range { element_type } => Type::Range {
1309                element_type: Box::new(From::from(&**element_type)),
1310            },
1311            SqlScalarType::MzAclItem => Type::MzAclItem,
1312        }
1313    }
1314}