use serde::{Deserialize, Serialize};
use crate::schema::{
foreign_key::ForeignKeySyntax,
names::ColumnName,
primary_key::PrimaryKeySyntax,
str_or_bool::{StrOrBoolOrArray, StringOrBool},
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub struct ColumnDef {
pub name: ColumnName,
pub r#type: ColumnType,
pub nullable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<StringOrBool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary_key: Option<PrimaryKeySyntax>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unique: Option<StrOrBoolOrArray>,
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<StrOrBoolOrArray>,
#[serde(skip_serializing_if = "Option::is_none")]
pub foreign_key: Option<ForeignKeySyntax>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", untagged)]
pub enum ColumnType {
Simple(SimpleColumnType),
Complex(ComplexColumnType),
}
impl ColumnType {
pub fn supports_auto_increment(&self) -> bool {
match self {
ColumnType::Simple(ty) => ty.supports_auto_increment(),
ColumnType::Complex(_) => false,
}
}
pub fn requires_migration(&self, other: &ColumnType) -> bool {
match (self, other) {
(
ColumnType::Complex(ComplexColumnType::Enum {
values: values1, ..
}),
ColumnType::Complex(ComplexColumnType::Enum {
values: values2, ..
}),
) => {
if values1.is_integer() && values2.is_integer() {
false
} else {
values1 != values2
}
}
_ => self != other,
}
}
pub fn to_rust_type(&self, nullable: bool) -> String {
let base = match self {
ColumnType::Simple(ty) => match ty {
SimpleColumnType::SmallInt => "i16".to_string(),
SimpleColumnType::Integer => "i32".to_string(),
SimpleColumnType::BigInt => "i64".to_string(),
SimpleColumnType::Real => "f32".to_string(),
SimpleColumnType::DoublePrecision => "f64".to_string(),
SimpleColumnType::Text
| SimpleColumnType::Interval
| SimpleColumnType::Inet
| SimpleColumnType::Cidr
| SimpleColumnType::Macaddr
| SimpleColumnType::Xml => "String".to_string(),
SimpleColumnType::Boolean => "bool".to_string(),
SimpleColumnType::Date => "Date".to_string(),
SimpleColumnType::Time => "Time".to_string(),
SimpleColumnType::Timestamp => "DateTime".to_string(),
SimpleColumnType::Timestamptz => "DateTimeWithTimeZone".to_string(),
SimpleColumnType::Bytea => "Vec<u8>".to_string(),
SimpleColumnType::Uuid => "Uuid".to_string(),
SimpleColumnType::Json => "Json".to_string(),
},
ColumnType::Complex(ty) => match ty {
ComplexColumnType::Numeric { .. } => "Decimal".to_string(),
ComplexColumnType::Varchar { .. }
| ComplexColumnType::Char { .. }
| ComplexColumnType::Custom { .. }
| ComplexColumnType::Enum { .. } => "String".to_string(),
},
};
if nullable {
format!("Option<{base}>")
} else {
base
}
}
pub fn to_display_string(&self) -> String {
match self {
ColumnType::Simple(ty) => ty.to_display_string(),
ColumnType::Complex(ty) => ty.to_display_string(),
}
}
pub fn default_fill_value(&self) -> &'static str {
match self {
ColumnType::Simple(ty) => ty.default_fill_value(),
ColumnType::Complex(ty) => ty.default_fill_value(),
}
}
pub fn enum_variant_names(&self) -> Option<Vec<String>> {
match self {
ColumnType::Complex(ComplexColumnType::Enum { values, .. }) => Some(
values
.variant_names()
.into_iter()
.map(String::from)
.collect(),
),
_ => None,
}
}
}
impl ColumnDef {
#[must_use]
pub fn new(name: impl Into<ColumnName>, r#type: ColumnType, nullable: bool) -> Self {
Self {
name: name.into(),
r#type,
nullable,
default: None,
comment: None,
primary_key: None,
unique: None,
index: None,
foreign_key: None,
}
}
#[must_use]
pub fn primary_key(mut self, pk: PrimaryKeySyntax) -> Self {
self.primary_key = Some(pk);
self
}
#[must_use]
pub fn unique(mut self, unique: StrOrBoolOrArray) -> Self {
self.unique = Some(unique);
self
}
#[must_use]
pub fn index(mut self, index: StrOrBoolOrArray) -> Self {
self.index = Some(index);
self
}
#[must_use]
pub fn foreign_key(mut self, fk: ForeignKeySyntax) -> Self {
self.foreign_key = Some(fk);
self
}
#[must_use]
pub fn default(mut self, default: StringOrBool) -> Self {
self.default = Some(default);
self
}
#[must_use]
pub fn comment(mut self, comment: impl Into<String>) -> Self {
self.comment = Some(comment.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum SimpleColumnType {
SmallInt,
Integer,
BigInt,
Real,
DoublePrecision,
Text,
Boolean,
Date,
Time,
Timestamp,
Timestamptz,
Interval,
Bytea,
Uuid,
Json,
Inet,
Cidr,
Macaddr,
Xml,
}
impl SimpleColumnType {
#[must_use]
pub fn sql_type(&self) -> &'static str {
match self {
SimpleColumnType::SmallInt => "SMALLINT",
SimpleColumnType::Integer => "INTEGER",
SimpleColumnType::BigInt => "BIGINT",
SimpleColumnType::Real => "REAL",
SimpleColumnType::DoublePrecision => "DOUBLE PRECISION",
SimpleColumnType::Text => "TEXT",
SimpleColumnType::Boolean => "BOOLEAN",
SimpleColumnType::Date => "DATE",
SimpleColumnType::Time => "TIME",
SimpleColumnType::Timestamp => "TIMESTAMP",
SimpleColumnType::Timestamptz => "TIMESTAMPTZ",
SimpleColumnType::Interval => "INTERVAL",
SimpleColumnType::Bytea => "BYTEA",
SimpleColumnType::Uuid => "UUID",
SimpleColumnType::Json => "JSON",
SimpleColumnType::Inet => "INET",
SimpleColumnType::Cidr => "CIDR",
SimpleColumnType::Macaddr => "MACADDR",
SimpleColumnType::Xml => "XML",
}
}
pub fn supports_auto_increment(&self) -> bool {
matches!(
self,
SimpleColumnType::SmallInt | SimpleColumnType::Integer | SimpleColumnType::BigInt
)
}
pub fn to_display_string(&self) -> String {
match self {
SimpleColumnType::SmallInt => "smallint".to_string(),
SimpleColumnType::Integer => "integer".to_string(),
SimpleColumnType::BigInt => "bigint".to_string(),
SimpleColumnType::Real => "real".to_string(),
SimpleColumnType::DoublePrecision => "double precision".to_string(),
SimpleColumnType::Text => "text".to_string(),
SimpleColumnType::Boolean => "boolean".to_string(),
SimpleColumnType::Date => "date".to_string(),
SimpleColumnType::Time => "time".to_string(),
SimpleColumnType::Timestamp => "timestamp".to_string(),
SimpleColumnType::Timestamptz => "timestamptz".to_string(),
SimpleColumnType::Interval => "interval".to_string(),
SimpleColumnType::Bytea => "bytea".to_string(),
SimpleColumnType::Uuid => "uuid".to_string(),
SimpleColumnType::Json => "json".to_string(),
SimpleColumnType::Inet => "inet".to_string(),
SimpleColumnType::Cidr => "cidr".to_string(),
SimpleColumnType::Macaddr => "macaddr".to_string(),
SimpleColumnType::Xml => "xml".to_string(),
}
}
pub fn default_fill_value(&self) -> &'static str {
match self {
SimpleColumnType::SmallInt | SimpleColumnType::Integer | SimpleColumnType::BigInt => {
"0"
}
SimpleColumnType::Real | SimpleColumnType::DoublePrecision => "0.0",
SimpleColumnType::Boolean => "false",
SimpleColumnType::Text | SimpleColumnType::Bytea => "''",
SimpleColumnType::Date => "'1970-01-01'",
SimpleColumnType::Time => "'00:00:00'",
SimpleColumnType::Timestamp | SimpleColumnType::Timestamptz => "CURRENT_TIMESTAMP",
SimpleColumnType::Interval => "'0'",
SimpleColumnType::Uuid => "'00000000-0000-0000-0000-000000000000'",
SimpleColumnType::Json => "'{}'",
SimpleColumnType::Inet | SimpleColumnType::Cidr => "'0.0.0.0'",
SimpleColumnType::Macaddr => "'00:00:00:00:00:00'",
SimpleColumnType::Xml => "'<xml/>'",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct NumValue {
pub name: String,
pub value: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum EnumValues {
String(Vec<String>),
Integer(Vec<NumValue>),
}
impl EnumValues {
pub fn is_string(&self) -> bool {
matches!(self, EnumValues::String(_))
}
pub fn is_integer(&self) -> bool {
matches!(self, EnumValues::Integer(_))
}
pub fn variant_names(&self) -> Vec<&str> {
match self {
EnumValues::String(values) => values.iter().map(std::string::String::as_str).collect(),
EnumValues::Integer(values) => values.iter().map(|v| v.name.as_str()).collect(),
}
}
pub fn len(&self) -> usize {
match self {
EnumValues::String(values) => values.len(),
EnumValues::Integer(values) => values.len(),
}
}
pub fn is_empty(&self) -> bool {
match self {
EnumValues::String(values) => values.is_empty(),
EnumValues::Integer(values) => values.is_empty(),
}
}
pub fn to_sql_values(&self) -> Vec<String> {
match self {
EnumValues::String(values) => values
.iter()
.map(|s| format!("'{}'", s.replace('\'', "''")))
.collect(),
EnumValues::Integer(values) => values.iter().map(|v| v.value.to_string()).collect(),
}
}
}
impl From<Vec<String>> for EnumValues {
fn from(values: Vec<String>) -> Self {
EnumValues::String(values)
}
}
impl From<Vec<&str>> for EnumValues {
fn from(values: Vec<&str>) -> Self {
EnumValues::String(
values
.into_iter()
.map(std::string::ToString::to_string)
.collect(),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", tag = "kind")]
#[non_exhaustive]
pub enum ComplexColumnType {
Varchar { length: u32 },
Numeric { precision: u32, scale: u32 },
Char { length: u32 },
Custom { custom_type: String },
Enum { name: String, values: EnumValues },
}
impl ComplexColumnType {
#[must_use]
pub fn sql_type(&self) -> &'static str {
match self {
ComplexColumnType::Varchar { .. } => "VARCHAR",
ComplexColumnType::Numeric { .. } => "NUMERIC",
ComplexColumnType::Char { .. } => "CHAR",
ComplexColumnType::Custom { .. } => "CUSTOM",
ComplexColumnType::Enum { .. } => "ENUM",
}
}
pub fn to_display_string(&self) -> String {
match self {
ComplexColumnType::Varchar { length } => format!("varchar({length})"),
ComplexColumnType::Numeric { precision, scale } => {
format!("numeric({precision},{scale})")
}
ComplexColumnType::Char { length } => format!("char({length})"),
ComplexColumnType::Custom { custom_type } => custom_type.to_lowercase(),
ComplexColumnType::Enum { name, values } => {
if values.is_integer() {
format!("enum<{name}> (integer)")
} else {
format!("enum<{name}>")
}
}
}
}
pub fn default_fill_value(&self) -> &'static str {
match self {
ComplexColumnType::Numeric { .. } => "0",
ComplexColumnType::Varchar { .. }
| ComplexColumnType::Char { .. }
| ComplexColumnType::Custom { .. }
| ComplexColumnType::Enum { .. } => "''",
}
}
}