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    /// Returns the `Type` corresponding to the provided OID and packed type
538    /// modifier ("typmod").
539    ///
540    /// For details about typmods, see the [`typmod`](Type::typmod) method.
541    ///
542    /// # Errors
543    ///
544    /// Returns an error if the OID is unknown or if the typmod is invalid for
545    /// the type.
546    pub fn from_oid_and_typmod(oid: u32, typmod: i32) -> Result<Type, TypeFromOidError> {
547        let typ = postgres_types::Type::from_oid(oid).ok_or(TypeFromOidError::UnknownOid(oid))?;
548        let mut typ = match typ {
549            postgres_types::Type::BOOL => Type::Bool,
550            postgres_types::Type::BYTEA => Type::Bytea,
551            postgres_types::Type::DATE => Type::Date,
552            postgres_types::Type::FLOAT4 => Type::Float4,
553            postgres_types::Type::FLOAT8 => Type::Float8,
554            postgres_types::Type::INT2 => Type::Int2,
555            postgres_types::Type::INT4 => Type::Int4,
556            postgres_types::Type::INT8 => Type::Int8,
557            postgres_types::Type::INTERVAL => Type::Interval { constraints: None },
558            postgres_types::Type::JSON => Type::Json,
559            postgres_types::Type::JSONB => Type::Jsonb,
560            postgres_types::Type::NUMERIC => Type::Numeric { constraints: None },
561            postgres_types::Type::OID => Type::Oid,
562            postgres_types::Type::TEXT => Type::Text,
563            postgres_types::Type::BPCHAR => Type::BpChar { length: None },
564            postgres_types::Type::CHAR => Type::Char,
565            postgres_types::Type::VARCHAR => Type::VarChar { max_length: None },
566            postgres_types::Type::TIME => Type::Time { precision: None },
567            postgres_types::Type::TIMETZ => Type::TimeTz { precision: None },
568            postgres_types::Type::TIMESTAMP => Type::Timestamp { precision: None },
569            postgres_types::Type::TIMESTAMPTZ => Type::TimestampTz { precision: None },
570            postgres_types::Type::UUID => Type::Uuid,
571            postgres_types::Type::REGCLASS => Type::RegClass,
572            postgres_types::Type::REGPROC => Type::RegProc,
573            postgres_types::Type::REGTYPE => Type::RegType,
574            postgres_types::Type::BOOL_ARRAY => Type::Array(Box::new(Type::Bool)),
575            postgres_types::Type::BYTEA_ARRAY => Type::Array(Box::new(Type::Bytea)),
576            postgres_types::Type::BPCHAR_ARRAY => {
577                Type::Array(Box::new(Type::BpChar { length: None }))
578            }
579            postgres_types::Type::CHAR_ARRAY => Type::Array(Box::new(Type::Char)),
580            postgres_types::Type::DATE_ARRAY => Type::Array(Box::new(Type::Date)),
581            postgres_types::Type::FLOAT4_ARRAY => Type::Array(Box::new(Type::Float4)),
582            postgres_types::Type::FLOAT8_ARRAY => Type::Array(Box::new(Type::Float8)),
583            postgres_types::Type::INT2_ARRAY => Type::Array(Box::new(Type::Int2)),
584            postgres_types::Type::INT4_ARRAY => Type::Array(Box::new(Type::Int4)),
585            postgres_types::Type::INT8_ARRAY => Type::Array(Box::new(Type::Int8)),
586            postgres_types::Type::INTERVAL_ARRAY => {
587                Type::Array(Box::new(Type::Interval { constraints: None }))
588            }
589            postgres_types::Type::JSON_ARRAY => Type::Array(Box::new(Type::Json)),
590            postgres_types::Type::JSONB_ARRAY => Type::Array(Box::new(Type::Jsonb)),
591            postgres_types::Type::NUMERIC_ARRAY => {
592                Type::Array(Box::new(Type::Numeric { constraints: None }))
593            }
594            postgres_types::Type::OID_ARRAY => Type::Array(Box::new(Type::Oid)),
595            postgres_types::Type::TEXT_ARRAY => Type::Array(Box::new(Type::Text)),
596            postgres_types::Type::TIME_ARRAY => {
597                Type::Array(Box::new(Type::Time { precision: None }))
598            }
599            postgres_types::Type::TIMETZ_ARRAY => {
600                Type::Array(Box::new(Type::TimeTz { precision: None }))
601            }
602            postgres_types::Type::TIMESTAMP_ARRAY => {
603                Type::Array(Box::new(Type::Timestamp { precision: None }))
604            }
605            postgres_types::Type::TIMESTAMPTZ_ARRAY => {
606                Type::Array(Box::new(Type::TimestampTz { precision: None }))
607            }
608            postgres_types::Type::UUID_ARRAY => Type::Array(Box::new(Type::Uuid)),
609            postgres_types::Type::VARCHAR_ARRAY => {
610                Type::Array(Box::new(Type::VarChar { max_length: None }))
611            }
612            postgres_types::Type::REGCLASS_ARRAY => Type::Array(Box::new(Type::RegClass)),
613            postgres_types::Type::REGPROC_ARRAY => Type::Array(Box::new(Type::RegProc)),
614            postgres_types::Type::REGTYPE_ARRAY => Type::Array(Box::new(Type::RegType)),
615            postgres_types::Type::INT2_VECTOR => Type::Int2Vector,
616            postgres_types::Type::INT2_VECTOR_ARRAY => Type::Array(Box::new(Type::Int2Vector)),
617            postgres_types::Type::INT4_RANGE => Type::Range {
618                element_type: Box::new(Type::Int4),
619            },
620            postgres_types::Type::INT4_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
621                element_type: Box::new(Type::Int4),
622            })),
623            postgres_types::Type::INT8_RANGE => Type::Range {
624                element_type: Box::new(Type::Int8),
625            },
626            postgres_types::Type::INT8_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
627                element_type: Box::new(Type::Int8),
628            })),
629            postgres_types::Type::NUM_RANGE => Type::Range {
630                element_type: Box::new(Type::Numeric { constraints: None }),
631            },
632            postgres_types::Type::NUM_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
633                element_type: Box::new(Type::Numeric { constraints: None }),
634            })),
635            postgres_types::Type::TS_RANGE => Type::Range {
636                element_type: Box::new(Type::Timestamp { precision: None }),
637            },
638            postgres_types::Type::TS_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
639                element_type: Box::new(Type::Timestamp { precision: None }),
640            })),
641            postgres_types::Type::TSTZ_RANGE => Type::Range {
642                element_type: Box::new(Type::TimestampTz { precision: None }),
643            },
644            postgres_types::Type::TSTZ_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
645                element_type: Box::new(Type::TimestampTz { precision: None }),
646            })),
647            postgres_types::Type::DATE_RANGE => Type::Range {
648                element_type: Box::new(Type::Date),
649            },
650            postgres_types::Type::DATE_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
651                element_type: Box::new(Type::Date),
652            })),
653            _ => return Err(TypeFromOidError::UnknownOid(oid)),
654        };
655
656        // Apply the typmod. For arrays, the typmod applies to the element type.
657        // We use a funny-looking immediately-invoked closure to share the
658        // construction of the invalid typmod error across all error paths.
659        let res = ({
660            let typ = &mut typ;
661            || {
662                let elem_typ = match typ {
663                    Type::Array(typ) => &mut **typ,
664                    typ => typ,
665                };
666                match elem_typ {
667                    Type::BpChar { length } => *length = CharLength::from_typmod(typmod)?,
668                    Type::Numeric { constraints } => {
669                        *constraints = NumericConstraints::from_typmod(typmod)?
670                    }
671                    Type::Interval { constraints } => {
672                        *constraints = IntervalConstraints::from_typmod(typmod)?
673                    }
674                    Type::Time { precision } => *precision = TimePrecision::from_typmod(typmod)?,
675                    Type::TimeTz { precision } => *precision = TimePrecision::from_typmod(typmod)?,
676                    Type::Timestamp { precision } => {
677                        *precision = TimestampPrecision::from_typmod(typmod)?
678                    }
679                    Type::TimestampTz { precision } => {
680                        *precision = TimestampPrecision::from_typmod(typmod)?
681                    }
682                    Type::VarChar { max_length } => *max_length = CharLength::from_typmod(typmod)?,
683                    _ if typmod != -1 => return Err("type does not support type modifiers".into()),
684                    _ => (),
685                }
686                Ok(())
687            }
688        })();
689        match res {
690            Ok(()) => Ok(typ),
691            Err(detail) => Err(TypeFromOidError::InvalidTypmod {
692                typ,
693                typmod,
694                detail,
695            }),
696        }
697    }
698
699    pub(crate) fn inner(&self) -> &'static postgres_types::Type {
700        match self {
701            Type::AclItem => &postgres_types::Type::ACLITEM,
702            Type::Array(t) => match &**t {
703                Type::AclItem => &postgres_types::Type::ACLITEM_ARRAY,
704                Type::Array(_) => unreachable!(),
705                Type::Bool => &postgres_types::Type::BOOL_ARRAY,
706                Type::Bytea => &postgres_types::Type::BYTEA_ARRAY,
707                Type::Char => &postgres_types::Type::CHAR_ARRAY,
708                Type::Date => &postgres_types::Type::DATE_ARRAY,
709                Type::Float4 => &postgres_types::Type::FLOAT4_ARRAY,
710                Type::Float8 => &postgres_types::Type::FLOAT8_ARRAY,
711                Type::Int2 => &postgres_types::Type::INT2_ARRAY,
712                Type::Int4 => &postgres_types::Type::INT4_ARRAY,
713                Type::Int8 => &postgres_types::Type::INT8_ARRAY,
714                Type::UInt2 => &UINT2_ARRAY,
715                Type::UInt4 => &UINT4_ARRAY,
716                Type::UInt8 => &UINT8_ARRAY,
717                Type::Interval { .. } => &postgres_types::Type::INTERVAL_ARRAY,
718                Type::Json => &postgres_types::Type::JSON_ARRAY,
719                Type::Jsonb => &postgres_types::Type::JSONB_ARRAY,
720                Type::List(_) => unreachable!(),
721                Type::Map { .. } => unreachable!(),
722                Type::Name { .. } => &postgres_types::Type::NAME_ARRAY,
723                Type::Numeric { .. } => &postgres_types::Type::NUMERIC_ARRAY,
724                Type::Oid => &postgres_types::Type::OID_ARRAY,
725                Type::Record(_) => &postgres_types::Type::RECORD_ARRAY,
726                Type::Text => &postgres_types::Type::TEXT_ARRAY,
727                Type::BpChar { .. } => &postgres_types::Type::BPCHAR_ARRAY,
728                Type::VarChar { .. } => &postgres_types::Type::VARCHAR_ARRAY,
729                Type::Time { .. } => &postgres_types::Type::TIME_ARRAY,
730                Type::TimeTz { .. } => &postgres_types::Type::TIMETZ_ARRAY,
731                Type::Timestamp { .. } => &postgres_types::Type::TIMESTAMP_ARRAY,
732                Type::TimestampTz { .. } => &postgres_types::Type::TIMESTAMPTZ_ARRAY,
733                Type::Uuid => &postgres_types::Type::UUID_ARRAY,
734                Type::RegClass => &postgres_types::Type::REGCLASS_ARRAY,
735                Type::RegProc => &postgres_types::Type::REGPROC_ARRAY,
736                Type::RegType => &postgres_types::Type::REGTYPE_ARRAY,
737                Type::Int2Vector => &postgres_types::Type::INT2_VECTOR_ARRAY,
738                Type::MzTimestamp => &MZ_TIMESTAMP_ARRAY,
739                Type::Range { element_type } => match **element_type {
740                    Type::Int4 => &postgres_types::Type::INT4_RANGE_ARRAY,
741                    Type::Int8 => &postgres_types::Type::INT8_RANGE_ARRAY,
742                    Type::Numeric { .. } => &postgres_types::Type::NUM_RANGE_ARRAY,
743                    Type::Timestamp { .. } => &postgres_types::Type::TS_RANGE_ARRAY,
744                    Type::TimestampTz { .. } => &postgres_types::Type::TSTZ_RANGE_ARRAY,
745                    Type::Date => &postgres_types::Type::DATE_RANGE_ARRAY,
746                    _ => unreachable!(),
747                },
748                Type::MzAclItem => &MZ_ACL_ITEM_ARRAY,
749            },
750            Type::Bool => &postgres_types::Type::BOOL,
751            Type::Bytea => &postgres_types::Type::BYTEA,
752            Type::Char => &postgres_types::Type::CHAR,
753            Type::Date => &postgres_types::Type::DATE,
754            Type::Float4 => &postgres_types::Type::FLOAT4,
755            Type::Float8 => &postgres_types::Type::FLOAT8,
756            Type::Int2 => &postgres_types::Type::INT2,
757            Type::Int4 => &postgres_types::Type::INT4,
758            Type::Int8 => &postgres_types::Type::INT8,
759            Type::UInt2 => &UINT2,
760            Type::UInt4 => &UINT4,
761            Type::UInt8 => &UINT8,
762            Type::Interval { .. } => &postgres_types::Type::INTERVAL,
763            Type::Json => &postgres_types::Type::JSON,
764            Type::Jsonb => &postgres_types::Type::JSONB,
765            Type::List(_) => &LIST,
766            Type::Map { .. } => &MAP,
767            Type::Name => &postgres_types::Type::NAME,
768            Type::Numeric { .. } => &postgres_types::Type::NUMERIC,
769            Type::Oid => &postgres_types::Type::OID,
770            Type::Record(_) => &postgres_types::Type::RECORD,
771            Type::Text => &postgres_types::Type::TEXT,
772            Type::BpChar { .. } => &postgres_types::Type::BPCHAR,
773            Type::VarChar { .. } => &postgres_types::Type::VARCHAR,
774            Type::Time { .. } => &postgres_types::Type::TIME,
775            Type::TimeTz { .. } => &postgres_types::Type::TIMETZ,
776            Type::Timestamp { .. } => &postgres_types::Type::TIMESTAMP,
777            Type::TimestampTz { .. } => &postgres_types::Type::TIMESTAMPTZ,
778            Type::Uuid => &postgres_types::Type::UUID,
779            Type::RegClass => &postgres_types::Type::REGCLASS,
780            Type::RegProc => &postgres_types::Type::REGPROC,
781            Type::RegType => &postgres_types::Type::REGTYPE,
782            Type::Int2Vector => &postgres_types::Type::INT2_VECTOR,
783            Type::MzTimestamp => &MZ_TIMESTAMP,
784            Type::Range { element_type } => match &**element_type {
785                Type::Int4 => &postgres_types::Type::INT4_RANGE,
786                Type::Int8 => &postgres_types::Type::INT8_RANGE,
787                Type::Numeric { .. } => &postgres_types::Type::NUM_RANGE,
788                Type::Timestamp { .. } => &postgres_types::Type::TS_RANGE,
789                Type::TimestampTz { .. } => &postgres_types::Type::TSTZ_RANGE,
790                Type::Date => &postgres_types::Type::DATE_RANGE,
791                t => unreachable!("{t:?} is not a range element type"),
792            },
793            Type::MzAclItem => &MZ_ACL_ITEM,
794        }
795    }
796
797    /// Returns the item's name in a way that guarantees it's resolvable in the
798    /// catalog.
799    pub fn catalog_name(&self) -> &'static str {
800        self.inner().name()
801    }
802
803    /// Returns the user-friendly name that PostgreSQL uses for this type.
804    pub fn name(&self) -> &'static str {
805        // postgres_types' `name()` uses the pg_catalog name, and not the pretty
806        // SQL standard name.
807        match self.inner() {
808            &postgres_types::Type::ACLITEM_ARRAY => "aclitem[]",
809            &postgres_types::Type::BOOL_ARRAY => "boolean[]",
810            &postgres_types::Type::BYTEA_ARRAY => "bytea[]",
811            &postgres_types::Type::BPCHAR_ARRAY => "character[]",
812            &postgres_types::Type::DATE_ARRAY => "date[]",
813            &postgres_types::Type::FLOAT4_ARRAY => "real[]",
814            &postgres_types::Type::FLOAT8_ARRAY => "double precision[]",
815            &postgres_types::Type::INT2_ARRAY => "smallint[]",
816            &postgres_types::Type::INT4_ARRAY => "integer[]",
817            &postgres_types::Type::INT8_ARRAY => "bigint[]",
818            &postgres_types::Type::INTERVAL_ARRAY => "interval[]",
819            &postgres_types::Type::JSONB_ARRAY => "jsonb[]",
820            &postgres_types::Type::NUMERIC_ARRAY => "numeric[]",
821            &postgres_types::Type::OID_ARRAY => "oid[]",
822            &postgres_types::Type::RECORD_ARRAY => "record[]",
823            &postgres_types::Type::TEXT_ARRAY => "text[]",
824            &postgres_types::Type::TIME_ARRAY => "time[]",
825            &postgres_types::Type::TIMESTAMP_ARRAY => "timestamp without time zone[]",
826            &postgres_types::Type::TIMESTAMPTZ_ARRAY => "timestamp with time zone[]",
827            &postgres_types::Type::UUID_ARRAY => "uuid[]",
828            &postgres_types::Type::VARCHAR_ARRAY => "character varying[]",
829            &postgres_types::Type::BOOL => "boolean",
830            &postgres_types::Type::BPCHAR => "character",
831            &postgres_types::Type::CHAR => "\"char\"",
832            &postgres_types::Type::CHAR_ARRAY => "\"char\"[]",
833            &postgres_types::Type::FLOAT4 => "real",
834            &postgres_types::Type::FLOAT8 => "double precision",
835            &postgres_types::Type::INT2 => "smallint",
836            &postgres_types::Type::INT4 => "integer",
837            &postgres_types::Type::INT8 => "bigint",
838            &postgres_types::Type::TIMESTAMP => "timestamp without time zone",
839            &postgres_types::Type::TIMESTAMPTZ => "timestamp with time zone",
840            &postgres_types::Type::VARCHAR => "character varying",
841            &postgres_types::Type::REGCLASS_ARRAY => "regclass[]",
842            &postgres_types::Type::REGPROC_ARRAY => "regproc[]",
843            &postgres_types::Type::REGTYPE_ARRAY => "regtype[]",
844            &postgres_types::Type::INT2_VECTOR => "int2vector",
845            other => match other.oid() {
846                oid::TYPE_UINT2_ARRAY_OID => "uint2[]",
847                oid::TYPE_UINT4_ARRAY_OID => "uint4[]",
848                oid::TYPE_UINT8_ARRAY_OID => "uint8[]",
849                oid::TYPE_MZ_TIMESTAMP_ARRAY_OID => "mz_timestamp[]",
850                oid::TYPE_MZ_ACL_ITEM_ARRAY_OID => "mz_aclitem[]",
851                _ => other.name(),
852            },
853        }
854    }
855
856    /// Returns the [OID] of this type.
857    ///
858    /// [OID]: https://www.postgresql.org/docs/current/datatype-oid.html
859    pub fn oid(&self) -> u32 {
860        self.inner().oid()
861    }
862
863    /// Returns the constraint on the type, if any.
864    pub fn constraint(&self) -> Option<&dyn TypeConstraint> {
865        match self {
866            Type::BpChar {
867                length: Some(length),
868            } => Some(length),
869            Type::VarChar {
870                max_length: Some(max_length),
871            } => Some(max_length),
872            Type::Numeric {
873                constraints: Some(constraints),
874            } => Some(constraints),
875            Type::Interval {
876                constraints: Some(constraints),
877            } => Some(constraints),
878            Type::Time {
879                precision: Some(precision),
880            } => Some(precision),
881            Type::TimeTz {
882                precision: Some(precision),
883            } => Some(precision),
884            Type::Timestamp {
885                precision: Some(precision),
886            } => Some(precision),
887            Type::TimestampTz {
888                precision: Some(precision),
889            } => Some(precision),
890            Type::AclItem
891            | Type::Array(_)
892            | Type::Bool
893            | Type::Bytea
894            | Type::BpChar { length: None }
895            | Type::Char
896            | Type::Date
897            | Type::Float4
898            | Type::Float8
899            | Type::Int2
900            | Type::Int4
901            | Type::Int8
902            | Type::UInt2
903            | Type::UInt4
904            | Type::UInt8
905            | Type::Interval { constraints: None }
906            | Type::Json
907            | Type::Jsonb
908            | Type::List(_)
909            | Type::Map { .. }
910            | Type::Name
911            | Type::Numeric { constraints: None }
912            | Type::Int2Vector
913            | Type::Oid
914            | Type::Record(_)
915            | Type::RegClass
916            | Type::RegProc
917            | Type::RegType
918            | Type::Text
919            | Type::Time { precision: None }
920            | Type::TimeTz { precision: None }
921            | Type::Timestamp { precision: None }
922            | Type::TimestampTz { precision: None }
923            | Type::Uuid
924            | Type::MzTimestamp
925            | Type::VarChar { max_length: None }
926            | Type::Range { .. }
927            | Type::MzAclItem => None,
928        }
929    }
930
931    /// Returns the number of bytes in the binary representation of this
932    /// type, or -1 if the type has a variable-length representation.
933    pub fn typlen(&self) -> i16 {
934        match self {
935            Type::Array(_) => -1,
936            Type::Bool => 1,
937            Type::Bytea => -1,
938            Type::Char => 1,
939            Type::Date => 4,
940            Type::Float4 => 4,
941            Type::Float8 => 8,
942            Type::Int2 => 2,
943            Type::Int4 => 4,
944            Type::Int8 => 8,
945            Type::UInt2 => 2,
946            Type::UInt4 => 4,
947            Type::UInt8 => 8,
948            Type::Interval { .. } => 16,
949            Type::Json => -1,
950            Type::Jsonb => -1,
951            Type::List(_) => -1,
952            Type::Map { .. } => -1,
953            Type::Name { .. } => 64,
954            Type::Numeric { .. } => -1,
955            Type::Oid => 4,
956            Type::Record(_) => -1,
957            Type::Text => -1,
958            Type::BpChar { .. } => -1,
959            Type::VarChar { .. } => -1,
960            Type::Time { .. } => 8,
961            Type::TimeTz { .. } => 12,
962            Type::Timestamp { .. } => 8,
963            Type::TimestampTz { .. } => 8,
964            Type::Uuid => 16,
965            Type::RegClass => 4,
966            Type::RegProc => 4,
967            Type::RegType => 4,
968            Type::Int2Vector => -1,
969            // Don't hard code this because should it change in the future it
970            // would be very difficult to remember to change this function.
971            Type::MzTimestamp => size_of::<mz_repr::Timestamp>()
972                .try_into()
973                .expect("must fit"),
974            Type::Range { .. } => -1,
975            Type::MzAclItem => MzAclItem::binary_size().try_into().expect("must fit"),
976            Type::AclItem => AclItem::binary_size().try_into().expect("must fit"),
977        }
978    }
979
980    /// Returns the packed type modifier ("typmod") for the type.
981    ///
982    /// The typmod is a 32-bit integer associated with the type that encodes
983    /// optional constraints on the type. For example, the typmod on
984    /// `Type::VarChar` encodes an optional constraint on the value's length.
985    /// Most types are never associated with a typmod.
986    ///
987    /// Negative typmods indicate no constraint.
988    pub fn typmod(&self) -> i32 {
989        match self.constraint() {
990            Some(constraint) => constraint.into_typmod(),
991            None => -1,
992        }
993    }
994}
995
996impl fmt::Display for Type {
997    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
998        f.write_str(self.inner().name())?;
999        if let Some(constraint) = self.constraint() {
1000            constraint.fmt(f)?;
1001        }
1002        Ok(())
1003    }
1004}
1005
1006impl TryFrom<&Type> for SqlScalarType {
1007    type Error = TypeConversionError;
1008
1009    fn try_from(typ: &Type) -> Result<SqlScalarType, TypeConversionError> {
1010        match typ {
1011            Type::AclItem => Ok(SqlScalarType::AclItem),
1012            Type::Array(t) => Ok(SqlScalarType::Array(Box::new(TryFrom::try_from(&**t)?))),
1013            Type::Bool => Ok(SqlScalarType::Bool),
1014            Type::Bytea => Ok(SqlScalarType::Bytes),
1015            Type::Char => Ok(SqlScalarType::PgLegacyChar),
1016            Type::Date => Ok(SqlScalarType::Date),
1017            Type::Float4 => Ok(SqlScalarType::Float32),
1018            Type::Float8 => Ok(SqlScalarType::Float64),
1019            Type::Int2 => Ok(SqlScalarType::Int16),
1020            Type::Int4 => Ok(SqlScalarType::Int32),
1021            Type::Int8 => Ok(SqlScalarType::Int64),
1022            Type::UInt2 => Ok(SqlScalarType::UInt16),
1023            Type::UInt4 => Ok(SqlScalarType::UInt32),
1024            Type::UInt8 => Ok(SqlScalarType::UInt64),
1025            Type::Interval { .. } => Ok(SqlScalarType::Interval),
1026            Type::Json => Err(TypeConversionError::UnsupportedType(Type::Json)),
1027            Type::Jsonb => Ok(SqlScalarType::Jsonb),
1028            Type::List(t) => Ok(SqlScalarType::List {
1029                element_type: Box::new(TryFrom::try_from(&**t)?),
1030                custom_id: None,
1031            }),
1032            Type::Map { value_type } => Ok(SqlScalarType::Map {
1033                value_type: Box::new(TryFrom::try_from(&**value_type)?),
1034                custom_id: None,
1035            }),
1036            Type::Name => Ok(SqlScalarType::PgLegacyName),
1037            Type::Numeric { constraints } => {
1038                let max_scale = match constraints {
1039                    Some(constraints) => {
1040                        if constraints.max_precision > i32::from(NUMERIC_DATUM_MAX_PRECISION) {
1041                            return Err(TypeConversionError::InvalidNumericConstraint(format!(
1042                                "precision for type numeric must be between 1 and {}",
1043                                NUMERIC_DATUM_MAX_PRECISION,
1044                            )));
1045                        }
1046                        if constraints.max_scale > constraints.max_precision {
1047                            return Err(TypeConversionError::InvalidNumericConstraint(format!(
1048                                "scale for type numeric must be between 0 and precision {}",
1049                                constraints.max_precision,
1050                            )));
1051                        }
1052                        Some(NumericMaxScale::try_from(i64::from(constraints.max_scale))?)
1053                    }
1054                    None => None,
1055                };
1056                Ok(SqlScalarType::Numeric { max_scale })
1057            }
1058            Type::Oid => Ok(SqlScalarType::Oid),
1059            Type::Record(_) => Ok(SqlScalarType::Record {
1060                fields: [].into(),
1061                custom_id: None,
1062            }),
1063            Type::Text => Ok(SqlScalarType::String),
1064            Type::Time { precision: None } => Ok(SqlScalarType::Time),
1065            Type::Time { precision: Some(_) } => {
1066                Err(TypeConversionError::UnsupportedType(typ.clone()))
1067            }
1068            Type::TimeTz { .. } => Err(TypeConversionError::UnsupportedType(typ.clone())),
1069            Type::BpChar { length } => Ok(SqlScalarType::Char {
1070                length: match length {
1071                    Some(length) => Some(AdtCharLength::try_from(i64::from(length.into_i32()))?),
1072                    None => None,
1073                },
1074            }),
1075            Type::VarChar { max_length } => Ok(SqlScalarType::VarChar {
1076                max_length: match max_length {
1077                    Some(max_length) => Some(VarCharMaxLength::try_from(i64::from(
1078                        max_length.into_i32(),
1079                    ))?),
1080                    None => None,
1081                },
1082            }),
1083            Type::Timestamp { precision } => Ok(SqlScalarType::Timestamp {
1084                precision: match precision {
1085                    Some(precision) => Some(AdtTimestampPrecision::try_from(i64::from(
1086                        precision.into_i32(),
1087                    ))?),
1088                    None => None,
1089                },
1090            }),
1091            Type::TimestampTz { precision } => Ok(SqlScalarType::TimestampTz {
1092                precision: match precision {
1093                    Some(precision) => Some(AdtTimestampPrecision::try_from(i64::from(
1094                        precision.into_i32(),
1095                    ))?),
1096                    None => None,
1097                },
1098            }),
1099            Type::Uuid => Ok(SqlScalarType::Uuid),
1100            Type::RegClass => Ok(SqlScalarType::RegClass),
1101            Type::RegProc => Ok(SqlScalarType::RegProc),
1102            Type::RegType => Ok(SqlScalarType::RegType),
1103            Type::Int2Vector => Ok(SqlScalarType::Int2Vector),
1104            Type::MzTimestamp => Ok(SqlScalarType::MzTimestamp),
1105            Type::Range { element_type } => Ok(SqlScalarType::Range {
1106                element_type: Box::new(TryFrom::try_from(&**element_type)?),
1107            }),
1108            Type::MzAclItem => Ok(SqlScalarType::MzAclItem),
1109        }
1110    }
1111}
1112
1113/// An error that can occur when constructing a [`Type`] from an OID.
1114#[derive(Debug, Clone)]
1115pub enum TypeFromOidError {
1116    /// The OID does not specify a known type.
1117    UnknownOid(u32),
1118    /// The specified typmod is invalid for the type.
1119    InvalidTypmod {
1120        /// The type.
1121        typ: Type,
1122        /// The type modifier.
1123        typmod: i32,
1124        /// Details about the nature of the invalidity.
1125        detail: String,
1126    },
1127}
1128
1129impl fmt::Display for TypeFromOidError {
1130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1131        match self {
1132            TypeFromOidError::UnknownOid(oid) => write!(f, "type with OID {oid} is unknown"),
1133            TypeFromOidError::InvalidTypmod {
1134                typ,
1135                typmod,
1136                detail,
1137            } => {
1138                write!(
1139                    f,
1140                    "typmod {typmod} is invalid for type {}: {detail}",
1141                    typ.name()
1142                )
1143            }
1144        }
1145    }
1146}
1147
1148impl Error for TypeFromOidError {}
1149
1150/// An error that can occur when converting a [`Type`] to a [`SqlScalarType`].
1151#[derive(Debug, Clone)]
1152pub enum TypeConversionError {
1153    /// The source type is unsupported as a `SqlScalarType`.
1154    UnsupportedType(Type),
1155    /// The source type contained an invalid max scale for a
1156    /// [`SqlScalarType::Numeric`].
1157    InvalidNumericMaxScale(InvalidNumericMaxScaleError),
1158    /// The source type contained an invalid constraint for a
1159    /// [`SqlScalarType::Numeric`].
1160    InvalidNumericConstraint(String),
1161    /// The source type contained an invalid length for a
1162    /// [`SqlScalarType::Char`].
1163    InvalidCharLength(InvalidCharLengthError),
1164    /// The source type contained an invalid max length for a
1165    /// [`SqlScalarType::VarChar`].
1166    InvalidVarCharMaxLength(InvalidVarCharMaxLengthError),
1167    /// The source type contained an invalid precision for a
1168    /// [`SqlScalarType::Timestamp`] or [`SqlScalarType::TimestampTz`].
1169    InvalidTimestampPrecision(InvalidTimestampPrecisionError),
1170}
1171
1172impl fmt::Display for TypeConversionError {
1173    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1174        match self {
1175            TypeConversionError::UnsupportedType(ty) => write!(f, "type {ty} not supported"),
1176            TypeConversionError::InvalidNumericMaxScale(e) => e.fmt(f),
1177            TypeConversionError::InvalidNumericConstraint(msg) => f.write_str(msg),
1178            TypeConversionError::InvalidCharLength(e) => e.fmt(f),
1179            TypeConversionError::InvalidVarCharMaxLength(e) => e.fmt(f),
1180            TypeConversionError::InvalidTimestampPrecision(e) => e.fmt(f),
1181        }
1182    }
1183}
1184
1185impl Error for TypeConversionError {}
1186
1187impl From<InvalidNumericMaxScaleError> for TypeConversionError {
1188    fn from(e: InvalidNumericMaxScaleError) -> TypeConversionError {
1189        TypeConversionError::InvalidNumericMaxScale(e)
1190    }
1191}
1192
1193impl From<InvalidCharLengthError> for TypeConversionError {
1194    fn from(e: InvalidCharLengthError) -> TypeConversionError {
1195        TypeConversionError::InvalidCharLength(e)
1196    }
1197}
1198
1199impl From<InvalidVarCharMaxLengthError> for TypeConversionError {
1200    fn from(e: InvalidVarCharMaxLengthError) -> TypeConversionError {
1201        TypeConversionError::InvalidVarCharMaxLength(e)
1202    }
1203}
1204
1205impl From<InvalidTimestampPrecisionError> for TypeConversionError {
1206    fn from(e: InvalidTimestampPrecisionError) -> TypeConversionError {
1207        TypeConversionError::InvalidTimestampPrecision(e)
1208    }
1209}
1210
1211impl From<&SqlScalarType> for Type {
1212    fn from(typ: &SqlScalarType) -> Type {
1213        match typ {
1214            SqlScalarType::AclItem => Type::AclItem,
1215            SqlScalarType::Array(t) => Type::Array(Box::new(From::from(&**t))),
1216            SqlScalarType::Bool => Type::Bool,
1217            SqlScalarType::Bytes => Type::Bytea,
1218            SqlScalarType::PgLegacyChar => Type::Char,
1219            SqlScalarType::Date => Type::Date,
1220            SqlScalarType::Float64 => Type::Float8,
1221            SqlScalarType::Float32 => Type::Float4,
1222            SqlScalarType::Int16 => Type::Int2,
1223            SqlScalarType::Int32 => Type::Int4,
1224            SqlScalarType::Int64 => Type::Int8,
1225            SqlScalarType::UInt16 => Type::UInt2,
1226            SqlScalarType::UInt32 => Type::UInt4,
1227            SqlScalarType::UInt64 => Type::UInt8,
1228            SqlScalarType::Interval => Type::Interval { constraints: None },
1229            SqlScalarType::Jsonb => Type::Jsonb,
1230            SqlScalarType::List { element_type, .. } => {
1231                Type::List(Box::new(From::from(&**element_type)))
1232            }
1233            SqlScalarType::Map { value_type, .. } => Type::Map {
1234                value_type: Box::new(From::from(&**value_type)),
1235            },
1236            SqlScalarType::PgLegacyName => Type::Name,
1237            SqlScalarType::Oid => Type::Oid,
1238            SqlScalarType::Record { fields, .. } => Type::Record(
1239                fields
1240                    .iter()
1241                    .map(|(_name, ty)| Type::from(&ty.scalar_type))
1242                    .collect(),
1243            ),
1244            SqlScalarType::String => Type::Text,
1245            SqlScalarType::Char { length } => Type::BpChar {
1246                length: (*length).map(CharLength::from),
1247            },
1248            SqlScalarType::VarChar { max_length } => Type::VarChar {
1249                max_length: (*max_length).map(CharLength::from),
1250            },
1251            SqlScalarType::Time => Type::Time { precision: None },
1252            SqlScalarType::Timestamp { precision } => Type::Timestamp {
1253                precision: (*precision).map(TimestampPrecision::from),
1254            },
1255            SqlScalarType::TimestampTz { precision } => Type::TimestampTz {
1256                precision: (*precision).map(TimestampPrecision::from),
1257            },
1258            SqlScalarType::Uuid => Type::Uuid,
1259            SqlScalarType::Numeric { max_scale } => Type::Numeric {
1260                constraints: Some(NumericConstraints {
1261                    max_precision: i32::from(NUMERIC_DATUM_MAX_PRECISION),
1262                    max_scale: match max_scale {
1263                        Some(max_scale) => i32::from(max_scale.into_u8()),
1264                        None => i32::from(NUMERIC_DATUM_MAX_PRECISION),
1265                    },
1266                }),
1267            },
1268            SqlScalarType::RegClass => Type::RegClass,
1269            SqlScalarType::RegProc => Type::RegProc,
1270            SqlScalarType::RegType => Type::RegType,
1271            SqlScalarType::Int2Vector => Type::Int2Vector,
1272            SqlScalarType::MzTimestamp => Type::MzTimestamp,
1273            SqlScalarType::Range { element_type } => Type::Range {
1274                element_type: Box::new(From::from(&**element_type)),
1275            },
1276            SqlScalarType::MzAclItem => Type::MzAclItem,
1277        }
1278    }
1279}