Skip to main content

rupg_types/
types.rs

1//! The type of a PostgreSQL value as a client sees it, and the facts of each built-in type.
2//!
3//! Lifted from `crates/rudb-pgtypes/src/types.rs` of tamnd/rudb at f5f7065a (spec/04 section 4.9).
4
5use std::borrow::Cow;
6
7use crate::generated::oids::TYPES;
8use crate::oid;
9
10/// The object ID of a type, as in `pg_type.oid`. It is the plain `u32` of [`rupg_common::Oid`], as the tables and the protocol use it.
11pub type Oid = u32;
12
13/// The PostgreSQL type of a value: the OID that `RowDescription` and `ParameterDescription` send, and the typmod. A typmod of -1 means that the type has no modifier. rupg carries it beside its own [`crate::TypeId`] (spec/07 section 7.9).
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct PgType {
16    pub oid: Oid,
17    pub typmod: i32,
18}
19
20impl PgType {
21    /// The type with no typmod.
22    pub const fn new(oid: Oid) -> PgType {
23        PgType { oid, typmod: -1 }
24    }
25
26    pub const fn with_typmod(oid: Oid, typmod: i32) -> PgType {
27        PgType { oid, typmod }
28    }
29
30    /// The row of the type in `pg_type`, if it is a built-in type.
31    pub fn info(self) -> Option<&'static TypeInfo> {
32        TypeInfo::get(self.oid)
33    }
34}
35
36/// The columns of `pg_type` that the wire format and the type rules read.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct TypeInfo {
39    pub oid: Oid,
40    pub name: &'static str,
41    /// `typtype`: `b` for a base type, `p` for a pseudo-type, `r` for a range, `m` for a multirange.
42    pub kind: u8,
43    /// `typcategory`, the letter that the rules for implicit casts use.
44    pub category: u8,
45    /// `typlen`: the size in bytes, -1 for a value of variable length and -2 for a C string.
46    pub len: i16,
47    /// `typelem`: the element type of an array, or 0.
48    pub elem: Oid,
49    /// `typarray`: the array type of this type, or 0.
50    pub array: Oid,
51    /// `typdelim`: the character between the elements of an array in the text format.
52    pub delim: u8,
53}
54
55/// The row constructor of the generated table, short so that each row fits on one line.
56#[allow(clippy::too_many_arguments)]
57pub(crate) const fn t(
58    oid: Oid,
59    name: &'static str,
60    kind: u8,
61    category: u8,
62    len: i16,
63    elem: Oid,
64    array: Oid,
65    delim: u8,
66) -> TypeInfo {
67    TypeInfo { oid, name, kind, category, len, elem, array, delim }
68}
69
70impl TypeInfo {
71    /// The built-in type with this OID.
72    pub fn get(oid: Oid) -> Option<&'static TypeInfo> {
73        TYPES.binary_search_by_key(&oid, |info| info.oid).ok().map(|i| &TYPES[i])
74    }
75
76    /// The built-in type with this `typname`, such as `int4` or `_int4`.
77    pub fn by_name(name: &str) -> Option<&'static TypeInfo> {
78        TYPES.iter().find(|info| info.name == name)
79    }
80
81    /// Every built-in type, in the order of the OIDs.
82    pub fn all() -> &'static [TypeInfo] {
83        &TYPES
84    }
85
86    /// Whether the type is an array in the sense of SQL, which `format_type` writes as the element type and `[]`. This is `IsTrueArrayType` with a storage that is not plain. So `int2vector` and `oidvector` are not arrays, but `_record`, which is a pseudo-type, is.
87    pub fn is_array(&self) -> bool {
88        match self.oid {
89            oid::INT2VECTOR | oid::OIDVECTOR => false,
90            oid::RECORD_ARRAY => true,
91            _ => self.category == b'A' && self.elem != 0,
92        }
93    }
94}
95
96/// The name of a type as `format_type(oid, NULL)` gives it: the SQL name of a type that has one, such as `integer` for `int4`, the element type and `[]` for an array, `-` for OID 0 and `???` for an OID that is not a type. The row types of the catalogs that have a fixed OID are not in [`TypeInfo::all`], but they have their name here.
97pub fn format_type(oid: Oid) -> Cow<'static, str> {
98    let name = match oid {
99        0 => "-",
100        oid::BOOL => "boolean",
101        oid::CHAR => "\"char\"",
102        oid::INT8 => "bigint",
103        oid::INT2 => "smallint",
104        oid::INT4 => "integer",
105        oid::FLOAT4 => "real",
106        oid::FLOAT8 => "double precision",
107        oid::BPCHAR => "character",
108        oid::VARCHAR => "character varying",
109        oid::TIME => "time without time zone",
110        oid::TIMESTAMP => "timestamp without time zone",
111        oid::TIMESTAMPTZ => "timestamp with time zone",
112        oid::TIMETZ => "time with time zone",
113        oid::VARBIT => "bit varying",
114        oid::ANY => "\"any\"",
115        71 => "pg_type",
116        75 => "pg_attribute",
117        81 => "pg_proc",
118        83 => "pg_class",
119        1248 => "pg_database",
120        2173 => "pg_parameter_acl",
121        2842 => "pg_authid",
122        2843 => "pg_auth_members",
123        4066 => "pg_shseclabel",
124        6101 => "pg_subscription",
125        _ => match TypeInfo::get(oid) {
126            Some(info) if info.is_array() => {
127                return Cow::Owned(format!("{}[]", format_type(info.elem)));
128            }
129            Some(info) => info.name,
130            None => "???",
131        },
132    };
133    Cow::Borrowed(name)
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::oid;
140
141    #[test]
142    fn the_table_has_the_types_that_clients_ask_for() {
143        // The OIDs that every driver has in a table of its own.
144        for (oid, name) in [
145            (oid::BOOL, "bool"),
146            (oid::BYTEA, "bytea"),
147            (oid::CHAR, "char"),
148            (oid::NAME, "name"),
149            (oid::INT8, "int8"),
150            (oid::INT2, "int2"),
151            (oid::INT4, "int4"),
152            (oid::TEXT, "text"),
153            (oid::OID, "oid"),
154            (oid::FLOAT4, "float4"),
155            (oid::FLOAT8, "float8"),
156            (oid::UNKNOWN, "unknown"),
157            (oid::VARCHAR, "varchar"),
158            (oid::BPCHAR, "bpchar"),
159            (oid::DATE, "date"),
160            (oid::TIMESTAMPTZ, "timestamptz"),
161            (oid::NUMERIC, "numeric"),
162            (oid::UUID, "uuid"),
163            (oid::JSONB, "jsonb"),
164            (oid::INT4_ARRAY, "_int4"),
165            (oid::TEXT_ARRAY, "_text"),
166            (oid::RECORD, "record"),
167        ] {
168            assert_eq!(TypeInfo::get(oid).map(|info| info.name), Some(name));
169            assert_eq!(TypeInfo::by_name(name).map(|info| info.oid), Some(oid));
170        }
171        assert_eq!(
172            [oid::BOOL, oid::INT4, oid::TEXT, oid::INT4_ARRAY, oid::RECORD],
173            [16, 23, 25, 1007, 2249]
174        );
175    }
176
177    #[test]
178    fn an_array_points_at_its_element_and_back() {
179        let int4 = PgType::new(oid::INT4).info().unwrap();
180        let array = TypeInfo::get(int4.array).unwrap();
181        assert!(array.is_array() && !int4.is_array());
182        assert_eq!((array.elem, array.len, array.delim), (oid::INT4, -1, b','));
183        assert_eq!(TypeInfo::get(oid::BOX_ARRAY).unwrap().delim, b';');
184        // `name` is an array of `char` for subscripts, but its category is not `A`.
185        assert!(!TypeInfo::get(oid::NAME).unwrap().is_array());
186        assert!(TypeInfo::all().windows(2).all(|w| w[0].oid < w[1].oid));
187        assert_eq!(TypeInfo::get(0), None);
188        let int2vector = TypeInfo::get(oid::INT2VECTOR).unwrap();
189        assert!(int2vector.category == b'A' && !int2vector.is_array());
190        assert!(TypeInfo::get(oid::RECORD_ARRAY).unwrap().is_array());
191    }
192
193    #[test]
194    fn format_type_gives_the_sql_name() {
195        assert_eq!(format_type(oid::INT4), "integer");
196        assert_eq!(format_type(oid::INT4_ARRAY), "integer[]");
197        assert_eq!(format_type(oid::INT2VECTOR), "int2vector");
198        assert_eq!(format_type(oid::RECORD_ARRAY), "record[]");
199        assert_eq!(format_type(oid::TEXT), "text");
200        assert_eq!((format_type(0), format_type(9999)), ("-".into(), "???".into()));
201    }
202}