1use crate::config::types::ColumnTypeConfig;
8
9#[derive(Clone, Debug, PartialEq)]
15pub enum CanonicalType {
16 Text,
18 Varchar(Option<u32>),
20 Char(Option<u32>),
22 SmallInt,
24 Int,
26 BigInt,
28 Real,
30 Double,
32 Decimal(Option<(u8, u8)>),
34 Boolean,
36 Uuid,
38 Json,
40 Jsonb,
42 Timestamp,
44 TimestampNtz,
46 Date,
48 Time,
50 Timetz,
52 Bytes,
54 Serial,
56 BigSerial,
58 Asset,
60 AssetArray,
62 Array(Box<CanonicalType>),
64 Custom(String),
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub enum TypeCategory {
72 Text,
73 Int,
74 Float,
75 Bool,
76 Uuid,
77 Date,
78 Timestamp,
79 Time,
80 Json,
81 Bytes,
82 Other,
84}
85
86#[derive(Clone, Debug)]
88pub enum TypeSupport {
89 Native(&'static str),
91 Emulated(&'static str),
93 Degraded(&'static str, &'static str),
95 Unsupported,
97}
98
99pub fn parse_canonical(ty: &ColumnTypeConfig) -> CanonicalType {
105 match ty {
106 ColumnTypeConfig::Simple(s) => parse_canonical_str(s, None),
107 ColumnTypeConfig::Parameterized { name, params } => {
108 parse_canonical_str(name, params.as_deref())
109 }
110 }
111}
112
113fn parse_canonical_str(s: &str, params: Option<&[u32]>) -> CanonicalType {
114 let lower = s.trim().to_lowercase();
115
116 if lower == "asset[]" {
118 return CanonicalType::AssetArray;
119 }
120 if lower == "asset" {
121 return CanonicalType::Asset;
122 }
123
124 if lower.ends_with("[]") {
126 let inner_str = &s[..s.len() - 2];
127 let inner = parse_canonical_str(inner_str, None);
128 return CanonicalType::Array(Box::new(inner));
129 }
130
131 if lower.contains('.') {
133 return CanonicalType::Custom(s.to_string());
134 }
135
136 let (base, inline_params) = split_inline_params(&lower);
138
139 match base {
140 "text" => CanonicalType::Text,
142 "varchar" | "character varying" => {
143 let n = first_param(params, &inline_params);
144 CanonicalType::Varchar(n)
145 }
146 "char" | "character" | "bpchar" => {
147 let n = first_param(params, &inline_params);
148 CanonicalType::Char(n)
149 }
150 "citext" | "name" => CanonicalType::Text,
151
152 "smallint" | "int2" => CanonicalType::SmallInt,
154 "smallserial" | "serial2" => CanonicalType::SmallInt, "int" | "integer" | "int4" => CanonicalType::Int,
156 "serial" | "serial4" => CanonicalType::Serial,
157 "bigint" | "int8" => CanonicalType::BigInt,
158 "bigserial" | "serial8" => CanonicalType::BigSerial,
159
160 "real" | "float4" => CanonicalType::Real,
162 "double" | "double precision" | "float8" => CanonicalType::Double,
163 "float" => CanonicalType::Double,
164 "money" => CanonicalType::Decimal(None),
165
166 "numeric" | "decimal" => {
168 let params_parsed = two_params(params, &inline_params);
169 CanonicalType::Decimal(params_parsed)
170 }
171
172 "boolean" | "bool" => CanonicalType::Boolean,
174
175 "uuid" => CanonicalType::Uuid,
177
178 "json" => CanonicalType::Json,
180 "jsonb" => CanonicalType::Jsonb,
181
182 "timestamptz" | "timestamp with time zone" => CanonicalType::Timestamp,
184 "timestamp" | "timestamp without time zone" => CanonicalType::Timestamp,
185 "timestamp_ntz" => CanonicalType::TimestampNtz,
186 "date" => CanonicalType::Date,
187 "time" | "time without time zone" => CanonicalType::Time,
188 "timetz" | "time with time zone" => CanonicalType::Timetz,
189
190 "bytea" | "bytes" => CanonicalType::Bytes,
192
193 _ => CanonicalType::Custom(s.to_string()),
195 }
196}
197
198fn split_inline_params(s: &str) -> (&str, Option<Vec<u32>>) {
200 if let Some(paren) = s.find('(') {
201 let base = s[..paren].trim();
202 let inner = s[paren + 1..].trim_end_matches(')').trim();
203 let nums: Vec<u32> = inner
204 .split(',')
205 .filter_map(|p| p.trim().parse::<u32>().ok())
206 .collect();
207 let params = if nums.is_empty() { None } else { Some(nums) };
208 (base, params)
209 } else {
210 (s, None)
211 }
212}
213
214fn first_param(explicit: Option<&[u32]>, inline: &Option<Vec<u32>>) -> Option<u32> {
215 explicit
216 .and_then(|p| p.first().copied())
217 .or_else(|| inline.as_ref().and_then(|p| p.first().copied()))
218}
219
220fn two_params(explicit: Option<&[u32]>, inline: &Option<Vec<u32>>) -> Option<(u8, u8)> {
221 let src = explicit
222 .filter(|p| p.len() >= 2)
223 .or_else(|| inline.as_deref().filter(|p| p.len() >= 2))?;
224 Some((src[0] as u8, src[1] as u8))
225}
226
227pub fn type_category(t: &CanonicalType) -> TypeCategory {
232 match t {
233 CanonicalType::Text
234 | CanonicalType::Varchar(_)
235 | CanonicalType::Char(_)
236 | CanonicalType::Asset => TypeCategory::Text,
237 CanonicalType::SmallInt
238 | CanonicalType::Int
239 | CanonicalType::BigInt
240 | CanonicalType::Serial
241 | CanonicalType::BigSerial => TypeCategory::Int,
242 CanonicalType::Real | CanonicalType::Double | CanonicalType::Decimal(_) => {
243 TypeCategory::Float
244 }
245 CanonicalType::Boolean => TypeCategory::Bool,
246 CanonicalType::Uuid => TypeCategory::Uuid,
247 CanonicalType::Date => TypeCategory::Date,
248 CanonicalType::Timestamp | CanonicalType::TimestampNtz => TypeCategory::Timestamp,
249 CanonicalType::Time | CanonicalType::Timetz => TypeCategory::Time,
250 CanonicalType::Json | CanonicalType::Jsonb | CanonicalType::AssetArray => {
251 TypeCategory::Json
252 }
253 CanonicalType::Bytes => TypeCategory::Bytes,
254 CanonicalType::Array(_) | CanonicalType::Custom(_) => TypeCategory::Other,
255 }
256}
257
258pub fn type_category_from_cast(cast: &str) -> TypeCategory {
262 let base = cast
263 .trim_end_matches("[]")
264 .split('(')
265 .next()
266 .unwrap_or(cast)
267 .trim()
268 .to_lowercase();
269 match base.as_str() {
270 "text" | "varchar" | "char" | "bpchar" | "citext" | "name" | "character varying"
271 | "character" => TypeCategory::Text,
272 "int2" | "int4" | "int8" | "integer" | "bigint" | "smallint" | "serial" | "bigserial"
273 | "smallserial" => TypeCategory::Int,
274 "float4" | "float8" | "numeric" | "decimal" | "real" | "money" | "double precision" => {
275 TypeCategory::Float
276 }
277 "bool" | "boolean" => TypeCategory::Bool,
278 "uuid" => TypeCategory::Uuid,
279 "date" => TypeCategory::Date,
280 "timestamp"
281 | "timestamptz"
282 | "timestamp with time zone"
283 | "timestamp without time zone" => TypeCategory::Timestamp,
284 "time" | "timetz" | "time with time zone" | "time without time zone" => TypeCategory::Time,
285 "json" | "jsonb" => TypeCategory::Json,
286 "bytea" => TypeCategory::Bytes,
287 _ => TypeCategory::Other,
288 }
289}
290
291pub fn active_cast_name(t: &CanonicalType) -> Option<String> {
296 #[cfg(feature = "postgres")]
297 return crate::db::postgres::cast_name(t);
298
299 #[cfg(not(feature = "postgres"))]
300 {
301 let _ = t;
302 None
303 }
304}