use crate::config::types::ColumnTypeConfig;
#[derive(Clone, Debug, PartialEq)]
pub enum CanonicalType {
Text,
Varchar(Option<u32>),
Char(Option<u32>),
SmallInt,
Int,
BigInt,
Real,
Double,
Decimal(Option<(u8, u8)>),
Boolean,
Uuid,
Json,
Jsonb,
Timestamp,
TimestampNtz,
Date,
Time,
Timetz,
Bytes,
Serial,
BigSerial,
Asset,
AssetArray,
Array(Box<CanonicalType>),
Custom(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TypeCategory {
Text,
Int,
Float,
Bool,
Uuid,
Date,
Timestamp,
Time,
Json,
Bytes,
Other,
}
#[derive(Clone, Debug)]
pub enum TypeSupport {
Native(&'static str),
Emulated(&'static str),
Degraded(&'static str, &'static str),
Unsupported,
}
pub fn parse_canonical(ty: &ColumnTypeConfig) -> CanonicalType {
match ty {
ColumnTypeConfig::Simple(s) => parse_canonical_str(s, None),
ColumnTypeConfig::Parameterized { name, params } => {
parse_canonical_str(name, params.as_deref())
}
}
}
fn parse_canonical_str(s: &str, params: Option<&[u32]>) -> CanonicalType {
let lower = s.trim().to_lowercase();
if lower == "asset[]" {
return CanonicalType::AssetArray;
}
if lower == "asset" {
return CanonicalType::Asset;
}
if lower.ends_with("[]") {
let inner_str = &s[..s.len() - 2];
let inner = parse_canonical_str(inner_str, None);
return CanonicalType::Array(Box::new(inner));
}
if lower.contains('.') {
return CanonicalType::Custom(s.to_string());
}
let (base, inline_params) = split_inline_params(&lower);
match base {
"text" => CanonicalType::Text,
"varchar" | "character varying" => {
let n = first_param(params, &inline_params);
CanonicalType::Varchar(n)
}
"char" | "character" | "bpchar" => {
let n = first_param(params, &inline_params);
CanonicalType::Char(n)
}
"citext" | "name" => CanonicalType::Text,
"smallint" | "int2" => CanonicalType::SmallInt,
"smallserial" | "serial2" => CanonicalType::SmallInt, "int" | "integer" | "int4" => CanonicalType::Int,
"serial" | "serial4" => CanonicalType::Serial,
"bigint" | "int8" => CanonicalType::BigInt,
"bigserial" | "serial8" => CanonicalType::BigSerial,
"real" | "float4" => CanonicalType::Real,
"double" | "double precision" | "float8" => CanonicalType::Double,
"float" => CanonicalType::Double,
"money" => CanonicalType::Decimal(None),
"numeric" | "decimal" => {
let params_parsed = two_params(params, &inline_params);
CanonicalType::Decimal(params_parsed)
}
"boolean" | "bool" => CanonicalType::Boolean,
"uuid" => CanonicalType::Uuid,
"json" => CanonicalType::Json,
"jsonb" => CanonicalType::Jsonb,
"timestamptz" | "timestamp with time zone" => CanonicalType::Timestamp,
"timestamp" | "timestamp without time zone" => CanonicalType::Timestamp,
"timestamp_ntz" => CanonicalType::TimestampNtz,
"date" => CanonicalType::Date,
"time" | "time without time zone" => CanonicalType::Time,
"timetz" | "time with time zone" => CanonicalType::Timetz,
"bytea" | "bytes" => CanonicalType::Bytes,
_ => CanonicalType::Custom(s.to_string()),
}
}
fn split_inline_params(s: &str) -> (&str, Option<Vec<u32>>) {
if let Some(paren) = s.find('(') {
let base = s[..paren].trim();
let inner = s[paren + 1..].trim_end_matches(')').trim();
let nums: Vec<u32> = inner
.split(',')
.filter_map(|p| p.trim().parse::<u32>().ok())
.collect();
let params = if nums.is_empty() { None } else { Some(nums) };
(base, params)
} else {
(s, None)
}
}
fn first_param(explicit: Option<&[u32]>, inline: &Option<Vec<u32>>) -> Option<u32> {
explicit
.and_then(|p| p.first().copied())
.or_else(|| inline.as_ref().and_then(|p| p.first().copied()))
}
fn two_params(explicit: Option<&[u32]>, inline: &Option<Vec<u32>>) -> Option<(u8, u8)> {
let src = explicit
.filter(|p| p.len() >= 2)
.or_else(|| inline.as_deref().filter(|p| p.len() >= 2))?;
Some((src[0] as u8, src[1] as u8))
}
pub fn type_category(t: &CanonicalType) -> TypeCategory {
match t {
CanonicalType::Text
| CanonicalType::Varchar(_)
| CanonicalType::Char(_)
| CanonicalType::Asset => TypeCategory::Text,
CanonicalType::SmallInt
| CanonicalType::Int
| CanonicalType::BigInt
| CanonicalType::Serial
| CanonicalType::BigSerial => TypeCategory::Int,
CanonicalType::Real | CanonicalType::Double | CanonicalType::Decimal(_) => {
TypeCategory::Float
}
CanonicalType::Boolean => TypeCategory::Bool,
CanonicalType::Uuid => TypeCategory::Uuid,
CanonicalType::Date => TypeCategory::Date,
CanonicalType::Timestamp | CanonicalType::TimestampNtz => TypeCategory::Timestamp,
CanonicalType::Time | CanonicalType::Timetz => TypeCategory::Time,
CanonicalType::Json | CanonicalType::Jsonb | CanonicalType::AssetArray => {
TypeCategory::Json
}
CanonicalType::Bytes => TypeCategory::Bytes,
CanonicalType::Array(_) | CanonicalType::Custom(_) => TypeCategory::Other,
}
}
pub fn type_category_from_cast(cast: &str) -> TypeCategory {
let base = cast
.trim_end_matches("[]")
.split('(')
.next()
.unwrap_or(cast)
.trim()
.to_lowercase();
match base.as_str() {
"text" | "varchar" | "char" | "bpchar" | "citext" | "name" | "character varying"
| "character" => TypeCategory::Text,
"int2" | "int4" | "int8" | "integer" | "bigint" | "smallint" | "serial" | "bigserial"
| "smallserial" => TypeCategory::Int,
"float4" | "float8" | "numeric" | "decimal" | "real" | "money" | "double precision" => {
TypeCategory::Float
}
"bool" | "boolean" => TypeCategory::Bool,
"uuid" => TypeCategory::Uuid,
"date" => TypeCategory::Date,
"timestamp"
| "timestamptz"
| "timestamp with time zone"
| "timestamp without time zone" => TypeCategory::Timestamp,
"time" | "timetz" | "time with time zone" | "time without time zone" => TypeCategory::Time,
"json" | "jsonb" => TypeCategory::Json,
"bytea" => TypeCategory::Bytes,
_ => TypeCategory::Other,
}
}
pub fn active_cast_name(t: &CanonicalType) -> Option<String> {
#[cfg(feature = "postgres")]
return crate::db::postgres::cast_name(t);
#[cfg(not(feature = "postgres"))]
{
let _ = t;
None
}
}