risingwave_common/types/postgres_type.rs
1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use postgres_types::Type as PgType;
16
17use super::DataType;
18
19/// `DataType` information extracted from PostgreSQL `pg_type`
20///
21/// ```sql
22/// select oid, typarray, typname, typinput, typlen from pg_type
23/// where oid in (16, 21, 23, 20, 1700, 700, 701, 1043, 17, 1082, 1114, 1184, 1083, 1186, 3802);
24/// ```
25///
26/// See also:
27/// * <https://www.postgresql.org/docs/15/catalog-pg-type.html>
28/// * <https://github.com/postgres/postgres/blob/REL_15_4/src/include/catalog/pg_type.dat>
29#[macro_export]
30macro_rules! for_all_base_types {
31 ($macro:ident $(, $x:tt)*) => {
32 $macro! {
33 $($x, )*
34 { Boolean | 16 | 1000 | bool | boolin | 1 }
35 { Bytea | 17 | 1001 | bytea | byteain | -1 }
36 { Int64 | 20 | 1016 | int8 | int8in | 8 }
37 { Int16 | 21 | 1005 | int2 | int2in | 2 }
38 { Int32 | 23 | 1007 | int4 | int4in | 4 }
39 { Float32 | 700 | 1021 | float4 | float4in | 4 }
40 { Float64 | 701 | 1022 | float8 | float8in | 8 }
41 { Varchar | 1043 | 1015 | varchar | varcharin | -1 }
42 { Date | 1082 | 1182 | date | date_in | 4 }
43 { Time | 1083 | 1183 | time | time_in | 8 }
44 { Timestamp | 1114 | 1115 | timestamp | timestamp_in | 8 }
45 { Timestamptz | 1184 | 1185 | timestamptz | timestamptz_in | 8 }
46 { Interval | 1186 | 1187 | interval | interval_in | 16 }
47 { Decimal | 1700 | 1231 | numeric | numeric_in | -1 }
48 { Jsonb | 3802 | 3807 | jsonb | jsonb_in | -1 }
49 }
50 };
51}
52
53#[derive(Debug, thiserror::Error)]
54#[error("Unsupported oid {0}")]
55pub struct UnsupportedOid(i32);
56
57/// Get type information compatible with Postgres type, such as oid, type length.
58impl DataType {
59 /// For a fixed-size type, typlen is the number of bytes in the internal representation of the type.
60 /// But for a variable-length type, typlen is negative.
61 /// -1 indicates a “varlena” type (one that has a length word),
62 /// -2 indicates a null-terminated C string.
63 ///
64 /// <https://www.postgresql.org/docs/15/catalog-pg-type.html#:~:text=of%20the%20type-,typlen,-int2>
65 pub fn type_len(&self) -> i16 {
66 macro_rules! impl_type_len {
67 ($( { $enum:ident | $oid:literal | $oid_array:literal | $name:ident | $input:ident | $len:literal } )*) => {
68 match self {
69 $(
70 DataType::$enum => $len,
71 )*
72 DataType::Serial => 8,
73 DataType::Int256 => -1,
74 DataType::Vector(_) => -1,
75 DataType::List(_) | DataType::Struct(_) | DataType::Map(_) => -1,
76 }
77 }
78 }
79 for_all_base_types! { impl_type_len }
80 }
81
82 // NOTE:
83 // Refer https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat when add new TypeOid.
84 // Be careful to distinguish oid from array_type_oid.
85 // Such as:
86 // https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat#L347
87 // For Numeric(aka Decimal): oid = 1700, array_type_oid = 1231
88 pub fn from_oid(oid: i32) -> Result<Self, UnsupportedOid> {
89 macro_rules! impl_from_oid {
90 ($( { $enum:ident | $oid:literal | $oid_array:literal | $name:ident | $input:ident | $len:literal } )*) => {
91 match oid {
92 $(
93 $oid => Ok(DataType::$enum),
94 )*
95 $(
96 $oid_array => Ok(DataType::List(Box::new(DataType::$enum))),
97 )*
98 // workaround to support text in extended mode.
99 25 => Ok(DataType::Varchar),
100 1009 => Ok(DataType::List(Box::new(DataType::Varchar))),
101 _ => Err(UnsupportedOid(oid)),
102 }
103 }
104 }
105 for_all_base_types! { impl_from_oid }
106 }
107
108 /// Refer to [`Self::from_oid`]
109 pub fn to_oid(&self) -> i32 {
110 macro_rules! impl_to_oid {
111 ($( { $enum:ident | $oid:literal | $oid_array:literal | $name:ident | $input:ident | $len:literal } )*) => {
112 match self {
113 $(
114 DataType::$enum => $oid,
115 )*
116 DataType::List(inner) => match inner.unnest_list() {
117 $(
118 DataType::$enum => $oid_array,
119 )*
120 DataType::Int256 => 1302,
121 DataType::Serial => 1016,
122 DataType::Struct(_) => 2287, // pseudo-type of array[struct] (see `pg_type.dat`)
123 DataType::List { .. } => unreachable!("Never reach here!"),
124 DataType::Map(_) => 1304,
125 DataType::Vector(_) => todo!("VECTOR_PLACEHOLDER"),
126 }
127 DataType::Serial => 20,
128 // XXX: what does the oid mean here? Why we don't have from_oid for them?
129 DataType::Int256 => 1301,
130 DataType::Map(_) => 1303,
131 // TODO: Support to give a new oid for custom struct type. #9434
132 DataType::Struct(_) => 2249, // pseudo-type of struct (see `pg_type.dat`)
133 DataType::Vector(_) => todo!("VECTOR_PLACEHOLDER"),
134 }
135 }
136 }
137 for_all_base_types! { impl_to_oid }
138 }
139
140 pub fn pg_name(&self) -> &'static str {
141 macro_rules! impl_pg_name {
142 ($( { $enum:ident | $oid:literal | $oid_array:literal | $name:ident | $input:ident | $len:literal } )*) => {
143 match self {
144 $(
145 DataType::$enum => stringify!($name),
146 )*
147 DataType::Struct(_) => "struct",
148 DataType::List(_) => "list",
149 DataType::Serial => "serial",
150 DataType::Int256 => "rw_int256",
151 DataType::Map(_) => "map",
152 DataType::Vector(_) => "vector",
153 }
154 }
155 }
156 for_all_base_types! { impl_pg_name }
157 }
158
159 pub fn to_pg_type(&self) -> PgType {
160 let oid = self.to_oid();
161 PgType::from_oid(oid as u32).unwrap()
162 }
163}