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::ScalarType;
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 | postgres_types::Type::CHAR => {
564                Type::BpChar { length: None }
565            }
566            postgres_types::Type::VARCHAR => Type::VarChar { max_length: None },
567            postgres_types::Type::TIME => Type::Time { precision: None },
568            postgres_types::Type::TIMETZ => Type::TimeTz { precision: None },
569            postgres_types::Type::TIMESTAMP => Type::Timestamp { precision: None },
570            postgres_types::Type::TIMESTAMPTZ => Type::TimestampTz { precision: None },
571            postgres_types::Type::UUID => Type::Uuid,
572            postgres_types::Type::REGCLASS => Type::RegClass,
573            postgres_types::Type::REGPROC => Type::RegProc,
574            postgres_types::Type::REGTYPE => Type::RegType,
575            postgres_types::Type::BOOL_ARRAY => Type::Array(Box::new(Type::Bool)),
576            postgres_types::Type::BYTEA_ARRAY => Type::Array(Box::new(Type::Bytea)),
577            postgres_types::Type::BPCHAR_ARRAY => {
578                Type::Array(Box::new(Type::BpChar { length: None }))
579            }
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::FLOAT4 => "real",
832            &postgres_types::Type::FLOAT8 => "double precision",
833            &postgres_types::Type::INT2 => "smallint",
834            &postgres_types::Type::INT4 => "integer",
835            &postgres_types::Type::INT8 => "bigint",
836            &postgres_types::Type::TIMESTAMP => "timestamp without time zone",
837            &postgres_types::Type::TIMESTAMPTZ => "timestamp with time zone",
838            &postgres_types::Type::VARCHAR => "character varying",
839            &postgres_types::Type::REGCLASS_ARRAY => "regclass[]",
840            &postgres_types::Type::REGPROC_ARRAY => "regproc[]",
841            &postgres_types::Type::REGTYPE_ARRAY => "regtype[]",
842            &postgres_types::Type::INT2_VECTOR => "int2vector",
843            other => match other.oid() {
844                oid::TYPE_UINT2_ARRAY_OID => "uint2[]",
845                oid::TYPE_UINT4_ARRAY_OID => "uint4[]",
846                oid::TYPE_UINT8_ARRAY_OID => "uint8[]",
847                oid::TYPE_MZ_TIMESTAMP_ARRAY_OID => "mz_timestamp[]",
848                oid::TYPE_MZ_ACL_ITEM_ARRAY_OID => "mz_aclitem[]",
849                _ => other.name(),
850            },
851        }
852    }
853
854    /// Returns the [OID] of this type.
855    ///
856    /// [OID]: https://www.postgresql.org/docs/current/datatype-oid.html
857    pub fn oid(&self) -> u32 {
858        self.inner().oid()
859    }
860
861    /// Returns the constraint on the type, if any.
862    pub fn constraint(&self) -> Option<&dyn TypeConstraint> {
863        match self {
864            Type::BpChar {
865                length: Some(length),
866            } => Some(length),
867            Type::VarChar {
868                max_length: Some(max_length),
869            } => Some(max_length),
870            Type::Numeric {
871                constraints: Some(constraints),
872            } => Some(constraints),
873            Type::Interval {
874                constraints: Some(constraints),
875            } => Some(constraints),
876            Type::Time {
877                precision: Some(precision),
878            } => Some(precision),
879            Type::TimeTz {
880                precision: Some(precision),
881            } => Some(precision),
882            Type::Timestamp {
883                precision: Some(precision),
884            } => Some(precision),
885            Type::TimestampTz {
886                precision: Some(precision),
887            } => Some(precision),
888            Type::AclItem
889            | Type::Array(_)
890            | Type::Bool
891            | Type::Bytea
892            | Type::BpChar { length: None }
893            | Type::Char
894            | Type::Date
895            | Type::Float4
896            | Type::Float8
897            | Type::Int2
898            | Type::Int4
899            | Type::Int8
900            | Type::UInt2
901            | Type::UInt4
902            | Type::UInt8
903            | Type::Interval { constraints: None }
904            | Type::Json
905            | Type::Jsonb
906            | Type::List(_)
907            | Type::Map { .. }
908            | Type::Name
909            | Type::Numeric { constraints: None }
910            | Type::Int2Vector
911            | Type::Oid
912            | Type::Record(_)
913            | Type::RegClass
914            | Type::RegProc
915            | Type::RegType
916            | Type::Text
917            | Type::Time { precision: None }
918            | Type::TimeTz { precision: None }
919            | Type::Timestamp { precision: None }
920            | Type::TimestampTz { precision: None }
921            | Type::Uuid
922            | Type::MzTimestamp
923            | Type::VarChar { max_length: None }
924            | Type::Range { .. }
925            | Type::MzAclItem => None,
926        }
927    }
928
929    /// Returns the number of bytes in the binary representation of this
930    /// type, or -1 if the type has a variable-length representation.
931    pub fn typlen(&self) -> i16 {
932        match self {
933            Type::Array(_) => -1,
934            Type::Bool => 1,
935            Type::Bytea => -1,
936            Type::Char => 1,
937            Type::Date => 4,
938            Type::Float4 => 4,
939            Type::Float8 => 8,
940            Type::Int2 => 2,
941            Type::Int4 => 4,
942            Type::Int8 => 8,
943            Type::UInt2 => 2,
944            Type::UInt4 => 4,
945            Type::UInt8 => 8,
946            Type::Interval { .. } => 16,
947            Type::Json => -1,
948            Type::Jsonb => -1,
949            Type::List(_) => -1,
950            Type::Map { .. } => -1,
951            Type::Name { .. } => 64,
952            Type::Numeric { .. } => -1,
953            Type::Oid => 4,
954            Type::Record(_) => -1,
955            Type::Text => -1,
956            Type::BpChar { .. } => -1,
957            Type::VarChar { .. } => -1,
958            Type::Time { .. } => 4,
959            Type::TimeTz { .. } => 4,
960            Type::Timestamp { .. } => 8,
961            Type::TimestampTz { .. } => 8,
962            Type::Uuid => 16,
963            Type::RegClass => 4,
964            Type::RegProc => 4,
965            Type::RegType => 4,
966            Type::Int2Vector => -1,
967            // Don't hard code this because should it change in the future it
968            // would be very difficult to remember to change this function.
969            Type::MzTimestamp => size_of::<mz_repr::Timestamp>()
970                .try_into()
971                .expect("must fit"),
972            Type::Range { .. } => -1,
973            Type::MzAclItem => MzAclItem::binary_size().try_into().expect("must fit"),
974            Type::AclItem => AclItem::binary_size().try_into().expect("must fit"),
975        }
976    }
977
978    /// Returns the packed type modifier ("typmod") for the type.
979    ///
980    /// The typmod is a 32-bit integer associated with the type that encodes
981    /// optional constraints on the type. For example, the typmod on
982    /// `Type::VarChar` encodes an optional constraint on the value's length.
983    /// Most types are never associated with a typmod.
984    ///
985    /// Negative typmods indicate no constraint.
986    pub fn typmod(&self) -> i32 {
987        match self.constraint() {
988            Some(constraint) => constraint.into_typmod(),
989            None => -1,
990        }
991    }
992}
993
994impl fmt::Display for Type {
995    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
996        f.write_str(self.inner().name())?;
997        if let Some(constraint) = self.constraint() {
998            constraint.fmt(f)?;
999        }
1000        Ok(())
1001    }
1002}
1003
1004impl TryFrom<&Type> for ScalarType {
1005    type Error = TypeConversionError;
1006
1007    fn try_from(typ: &Type) -> Result<ScalarType, TypeConversionError> {
1008        match typ {
1009            Type::AclItem => Ok(ScalarType::AclItem),
1010            Type::Array(t) => Ok(ScalarType::Array(Box::new(TryFrom::try_from(&**t)?))),
1011            Type::Bool => Ok(ScalarType::Bool),
1012            Type::Bytea => Ok(ScalarType::Bytes),
1013            Type::Char => Ok(ScalarType::PgLegacyChar),
1014            Type::Date => Ok(ScalarType::Date),
1015            Type::Float4 => Ok(ScalarType::Float32),
1016            Type::Float8 => Ok(ScalarType::Float64),
1017            Type::Int2 => Ok(ScalarType::Int16),
1018            Type::Int4 => Ok(ScalarType::Int32),
1019            Type::Int8 => Ok(ScalarType::Int64),
1020            Type::UInt2 => Ok(ScalarType::UInt16),
1021            Type::UInt4 => Ok(ScalarType::UInt32),
1022            Type::UInt8 => Ok(ScalarType::UInt64),
1023            Type::Interval { .. } => Ok(ScalarType::Interval),
1024            Type::Json => Err(TypeConversionError::UnsupportedType(Type::Json)),
1025            Type::Jsonb => Ok(ScalarType::Jsonb),
1026            Type::List(t) => Ok(ScalarType::List {
1027                element_type: Box::new(TryFrom::try_from(&**t)?),
1028                custom_id: None,
1029            }),
1030            Type::Map { value_type } => Ok(ScalarType::Map {
1031                value_type: Box::new(TryFrom::try_from(&**value_type)?),
1032                custom_id: None,
1033            }),
1034            Type::Name => Ok(ScalarType::PgLegacyName),
1035            Type::Numeric { constraints } => {
1036                let max_scale = match constraints {
1037                    Some(constraints) => {
1038                        if constraints.max_precision > i32::from(NUMERIC_DATUM_MAX_PRECISION) {
1039                            return Err(TypeConversionError::InvalidNumericConstraint(format!(
1040                                "precision for type numeric must be between 1 and {}",
1041                                NUMERIC_DATUM_MAX_PRECISION,
1042                            )));
1043                        }
1044                        if constraints.max_scale > constraints.max_precision {
1045                            return Err(TypeConversionError::InvalidNumericConstraint(format!(
1046                                "scale for type numeric must be between 0 and precision {}",
1047                                constraints.max_precision,
1048                            )));
1049                        }
1050                        Some(NumericMaxScale::try_from(i64::from(constraints.max_scale))?)
1051                    }
1052                    None => None,
1053                };
1054                Ok(ScalarType::Numeric { max_scale })
1055            }
1056            Type::Oid => Ok(ScalarType::Oid),
1057            Type::Record(_) => Ok(ScalarType::Record {
1058                fields: [].into(),
1059                custom_id: None,
1060            }),
1061            Type::Text => Ok(ScalarType::String),
1062            Type::Time { precision: None } => Ok(ScalarType::Time),
1063            Type::Time { precision: Some(_) } => {
1064                Err(TypeConversionError::UnsupportedType(typ.clone()))
1065            }
1066            Type::TimeTz { .. } => Err(TypeConversionError::UnsupportedType(typ.clone())),
1067            Type::BpChar { length } => Ok(ScalarType::Char {
1068                length: match length {
1069                    Some(length) => Some(AdtCharLength::try_from(i64::from(length.into_i32()))?),
1070                    None => None,
1071                },
1072            }),
1073            Type::VarChar { max_length } => Ok(ScalarType::VarChar {
1074                max_length: match max_length {
1075                    Some(max_length) => Some(VarCharMaxLength::try_from(i64::from(
1076                        max_length.into_i32(),
1077                    ))?),
1078                    None => None,
1079                },
1080            }),
1081            Type::Timestamp { precision } => Ok(ScalarType::Timestamp {
1082                precision: match precision {
1083                    Some(precision) => Some(AdtTimestampPrecision::try_from(i64::from(
1084                        precision.into_i32(),
1085                    ))?),
1086                    None => None,
1087                },
1088            }),
1089            Type::TimestampTz { precision } => Ok(ScalarType::TimestampTz {
1090                precision: match precision {
1091                    Some(precision) => Some(AdtTimestampPrecision::try_from(i64::from(
1092                        precision.into_i32(),
1093                    ))?),
1094                    None => None,
1095                },
1096            }),
1097            Type::Uuid => Ok(ScalarType::Uuid),
1098            Type::RegClass => Ok(ScalarType::RegClass),
1099            Type::RegProc => Ok(ScalarType::RegProc),
1100            Type::RegType => Ok(ScalarType::RegType),
1101            Type::Int2Vector => Ok(ScalarType::Int2Vector),
1102            Type::MzTimestamp => Ok(ScalarType::MzTimestamp),
1103            Type::Range { element_type } => Ok(ScalarType::Range {
1104                element_type: Box::new(TryFrom::try_from(&**element_type)?),
1105            }),
1106            Type::MzAclItem => Ok(ScalarType::MzAclItem),
1107        }
1108    }
1109}
1110
1111/// An error that can occur when constructing a [`Type`] from an OID.
1112#[derive(Debug, Clone)]
1113pub enum TypeFromOidError {
1114    /// The OID does not specify a known type.
1115    UnknownOid(u32),
1116    /// The specified typmod is invalid for the type.
1117    InvalidTypmod {
1118        /// The type.
1119        typ: Type,
1120        /// The type modifier.
1121        typmod: i32,
1122        /// Details about the nature of the invalidity.
1123        detail: String,
1124    },
1125}
1126
1127impl fmt::Display for TypeFromOidError {
1128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1129        match self {
1130            TypeFromOidError::UnknownOid(oid) => write!(f, "type with OID {oid} is unknown"),
1131            TypeFromOidError::InvalidTypmod {
1132                typ,
1133                typmod,
1134                detail,
1135            } => {
1136                write!(
1137                    f,
1138                    "typmod {typmod} is invalid for type {}: {detail}",
1139                    typ.name()
1140                )
1141            }
1142        }
1143    }
1144}
1145
1146impl Error for TypeFromOidError {}
1147
1148/// An error that can occur when converting a [`Type`] to a [`ScalarType`].
1149#[derive(Debug, Clone)]
1150pub enum TypeConversionError {
1151    /// The source type is unsupported as a `ScalarType`.
1152    UnsupportedType(Type),
1153    /// The source type contained an invalid max scale for a
1154    /// [`ScalarType::Numeric`].
1155    InvalidNumericMaxScale(InvalidNumericMaxScaleError),
1156    /// The source type contained an invalid constraint for a
1157    /// [`ScalarType::Numeric`].
1158    InvalidNumericConstraint(String),
1159    /// The source type contained an invalid length for a
1160    /// [`ScalarType::Char`].
1161    InvalidCharLength(InvalidCharLengthError),
1162    /// The source type contained an invalid max length for a
1163    /// [`ScalarType::VarChar`].
1164    InvalidVarCharMaxLength(InvalidVarCharMaxLengthError),
1165    /// The source type contained an invalid precision for a
1166    /// [`ScalarType::Timestamp`] or [`ScalarType::TimestampTz`].
1167    InvalidTimestampPrecision(InvalidTimestampPrecisionError),
1168}
1169
1170impl fmt::Display for TypeConversionError {
1171    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1172        match self {
1173            TypeConversionError::UnsupportedType(ty) => write!(f, "type {ty} not supported"),
1174            TypeConversionError::InvalidNumericMaxScale(e) => e.fmt(f),
1175            TypeConversionError::InvalidNumericConstraint(msg) => f.write_str(msg),
1176            TypeConversionError::InvalidCharLength(e) => e.fmt(f),
1177            TypeConversionError::InvalidVarCharMaxLength(e) => e.fmt(f),
1178            TypeConversionError::InvalidTimestampPrecision(e) => e.fmt(f),
1179        }
1180    }
1181}
1182
1183impl Error for TypeConversionError {}
1184
1185impl From<InvalidNumericMaxScaleError> for TypeConversionError {
1186    fn from(e: InvalidNumericMaxScaleError) -> TypeConversionError {
1187        TypeConversionError::InvalidNumericMaxScale(e)
1188    }
1189}
1190
1191impl From<InvalidCharLengthError> for TypeConversionError {
1192    fn from(e: InvalidCharLengthError) -> TypeConversionError {
1193        TypeConversionError::InvalidCharLength(e)
1194    }
1195}
1196
1197impl From<InvalidVarCharMaxLengthError> for TypeConversionError {
1198    fn from(e: InvalidVarCharMaxLengthError) -> TypeConversionError {
1199        TypeConversionError::InvalidVarCharMaxLength(e)
1200    }
1201}
1202
1203impl From<InvalidTimestampPrecisionError> for TypeConversionError {
1204    fn from(e: InvalidTimestampPrecisionError) -> TypeConversionError {
1205        TypeConversionError::InvalidTimestampPrecision(e)
1206    }
1207}
1208
1209impl From<&ScalarType> for Type {
1210    fn from(typ: &ScalarType) -> Type {
1211        match typ {
1212            ScalarType::AclItem => Type::AclItem,
1213            ScalarType::Array(t) => Type::Array(Box::new(From::from(&**t))),
1214            ScalarType::Bool => Type::Bool,
1215            ScalarType::Bytes => Type::Bytea,
1216            ScalarType::PgLegacyChar => Type::Char,
1217            ScalarType::Date => Type::Date,
1218            ScalarType::Float64 => Type::Float8,
1219            ScalarType::Float32 => Type::Float4,
1220            ScalarType::Int16 => Type::Int2,
1221            ScalarType::Int32 => Type::Int4,
1222            ScalarType::Int64 => Type::Int8,
1223            ScalarType::UInt16 => Type::UInt2,
1224            ScalarType::UInt32 => Type::UInt4,
1225            ScalarType::UInt64 => Type::UInt8,
1226            ScalarType::Interval => Type::Interval { constraints: None },
1227            ScalarType::Jsonb => Type::Jsonb,
1228            ScalarType::List { element_type, .. } => {
1229                Type::List(Box::new(From::from(&**element_type)))
1230            }
1231            ScalarType::Map { value_type, .. } => Type::Map {
1232                value_type: Box::new(From::from(&**value_type)),
1233            },
1234            ScalarType::PgLegacyName => Type::Name,
1235            ScalarType::Oid => Type::Oid,
1236            ScalarType::Record { fields, .. } => Type::Record(
1237                fields
1238                    .iter()
1239                    .map(|(_name, ty)| Type::from(&ty.scalar_type))
1240                    .collect(),
1241            ),
1242            ScalarType::String => Type::Text,
1243            ScalarType::Char { length } => Type::BpChar {
1244                length: (*length).map(CharLength::from),
1245            },
1246            ScalarType::VarChar { max_length } => Type::VarChar {
1247                max_length: (*max_length).map(CharLength::from),
1248            },
1249            ScalarType::Time => Type::Time { precision: None },
1250            ScalarType::Timestamp { precision } => Type::Timestamp {
1251                precision: (*precision).map(TimestampPrecision::from),
1252            },
1253            ScalarType::TimestampTz { precision } => Type::TimestampTz {
1254                precision: (*precision).map(TimestampPrecision::from),
1255            },
1256            ScalarType::Uuid => Type::Uuid,
1257            ScalarType::Numeric { max_scale } => Type::Numeric {
1258                constraints: Some(NumericConstraints {
1259                    max_precision: i32::from(NUMERIC_DATUM_MAX_PRECISION),
1260                    max_scale: match max_scale {
1261                        Some(max_scale) => i32::from(max_scale.into_u8()),
1262                        None => i32::from(NUMERIC_DATUM_MAX_PRECISION),
1263                    },
1264                }),
1265            },
1266            ScalarType::RegClass => Type::RegClass,
1267            ScalarType::RegProc => Type::RegProc,
1268            ScalarType::RegType => Type::RegType,
1269            ScalarType::Int2Vector => Type::Int2Vector,
1270            ScalarType::MzTimestamp => Type::MzTimestamp,
1271            ScalarType::Range { element_type } => Type::Range {
1272                element_type: Box::new(From::from(&**element_type)),
1273            },
1274            ScalarType::MzAclItem => Type::MzAclItem,
1275        }
1276    }
1277}