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 => "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::Interval => "String".to_string(),
SimpleColumnType::Bytea => "Vec<u8>".to_string(),
SimpleColumnType::Uuid => "Uuid".to_string(),
SimpleColumnType::Json => "Json".to_string(),
SimpleColumnType::Inet | SimpleColumnType::Cidr => "String".to_string(),
SimpleColumnType::Macaddr => "String".to_string(),
SimpleColumnType::Xml => "String".to_string(),
},
ColumnType::Complex(ty) => match ty {
ComplexColumnType::Varchar { .. } => "String".to_string(),
ComplexColumnType::Numeric { .. } => "Decimal".to_string(),
ComplexColumnType::Char { .. } => "String".to_string(),
ComplexColumnType::Custom { .. } => "String".to_string(), 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,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SimpleColumnType {
SmallInt,
Integer,
BigInt,
Real,
DoublePrecision,
Text,
Boolean,
Date,
Time,
Timestamp,
Timestamptz,
Interval,
Bytea,
Uuid,
Json,
Inet,
Cidr,
Macaddr,
Xml,
}
impl SimpleColumnType {
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::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::Bytea => "''",
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: i32,
}
#[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(|s| s.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 {
self.len() == 0
}
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(|s| s.to_string()).collect())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", tag = "kind")]
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 {
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<{}> (integer)", name)
} else {
format!("enum<{}>", name)
}
}
}
}
pub fn default_fill_value(&self) -> &'static str {
match self {
ComplexColumnType::Varchar { .. } | ComplexColumnType::Char { .. } => "''",
ComplexColumnType::Numeric { .. } => "0",
ComplexColumnType::Custom { .. } => "''",
ComplexColumnType::Enum { .. } => "''",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case(SimpleColumnType::SmallInt, "i16")]
#[case(SimpleColumnType::Integer, "i32")]
#[case(SimpleColumnType::BigInt, "i64")]
#[case(SimpleColumnType::Real, "f32")]
#[case(SimpleColumnType::DoublePrecision, "f64")]
#[case(SimpleColumnType::Text, "String")]
#[case(SimpleColumnType::Boolean, "bool")]
#[case(SimpleColumnType::Date, "Date")]
#[case(SimpleColumnType::Time, "Time")]
#[case(SimpleColumnType::Timestamp, "DateTime")]
#[case(SimpleColumnType::Timestamptz, "DateTimeWithTimeZone")]
#[case(SimpleColumnType::Interval, "String")]
#[case(SimpleColumnType::Bytea, "Vec<u8>")]
#[case(SimpleColumnType::Uuid, "Uuid")]
#[case(SimpleColumnType::Json, "Json")]
#[case(SimpleColumnType::Inet, "String")]
#[case(SimpleColumnType::Cidr, "String")]
#[case(SimpleColumnType::Macaddr, "String")]
#[case(SimpleColumnType::Xml, "String")]
fn test_simple_column_type_to_rust_type_not_nullable(
#[case] column_type: SimpleColumnType,
#[case] expected: &str,
) {
assert_eq!(
ColumnType::Simple(column_type).to_rust_type(false),
expected
);
}
#[rstest]
#[case(SimpleColumnType::SmallInt, "Option<i16>")]
#[case(SimpleColumnType::Integer, "Option<i32>")]
#[case(SimpleColumnType::BigInt, "Option<i64>")]
#[case(SimpleColumnType::Real, "Option<f32>")]
#[case(SimpleColumnType::DoublePrecision, "Option<f64>")]
#[case(SimpleColumnType::Text, "Option<String>")]
#[case(SimpleColumnType::Boolean, "Option<bool>")]
#[case(SimpleColumnType::Date, "Option<Date>")]
#[case(SimpleColumnType::Time, "Option<Time>")]
#[case(SimpleColumnType::Timestamp, "Option<DateTime>")]
#[case(SimpleColumnType::Timestamptz, "Option<DateTimeWithTimeZone>")]
#[case(SimpleColumnType::Interval, "Option<String>")]
#[case(SimpleColumnType::Bytea, "Option<Vec<u8>>")]
#[case(SimpleColumnType::Uuid, "Option<Uuid>")]
#[case(SimpleColumnType::Json, "Option<Json>")]
#[case(SimpleColumnType::Inet, "Option<String>")]
#[case(SimpleColumnType::Cidr, "Option<String>")]
#[case(SimpleColumnType::Macaddr, "Option<String>")]
#[case(SimpleColumnType::Xml, "Option<String>")]
fn test_simple_column_type_to_rust_type_nullable(
#[case] column_type: SimpleColumnType,
#[case] expected: &str,
) {
assert_eq!(ColumnType::Simple(column_type).to_rust_type(true), expected);
}
#[rstest]
#[case(ComplexColumnType::Varchar { length: 255 }, false, "String")]
#[case(ComplexColumnType::Varchar { length: 50 }, false, "String")]
#[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, false, "Decimal")]
#[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, false, "Decimal")]
#[case(ComplexColumnType::Char { length: 10 }, false, "String")]
#[case(ComplexColumnType::Char { length: 1 }, false, "String")]
#[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, false, "String")]
#[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, false, "String")]
#[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec!["active".into(), "inactive".into()]) }, false, "String")]
fn test_complex_column_type_to_rust_type_not_nullable(
#[case] column_type: ComplexColumnType,
#[case] nullable: bool,
#[case] expected: &str,
) {
assert_eq!(
ColumnType::Complex(column_type).to_rust_type(nullable),
expected
);
}
#[rstest]
#[case(ComplexColumnType::Varchar { length: 255 }, "Option<String>")]
#[case(ComplexColumnType::Varchar { length: 50 }, "Option<String>")]
#[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, "Option<Decimal>")]
#[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, "Option<Decimal>")]
#[case(ComplexColumnType::Char { length: 10 }, "Option<String>")]
#[case(ComplexColumnType::Char { length: 1 }, "Option<String>")]
#[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, "Option<String>")]
#[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, "Option<String>")]
#[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec!["active".into(), "inactive".into()]) }, "Option<String>")]
fn test_complex_column_type_to_rust_type_nullable(
#[case] column_type: ComplexColumnType,
#[case] expected: &str,
) {
assert_eq!(
ColumnType::Complex(column_type).to_rust_type(true),
expected
);
}
#[rstest]
#[case(ComplexColumnType::Varchar { length: 255 })]
#[case(ComplexColumnType::Numeric { precision: 10, scale: 2 })]
#[case(ComplexColumnType::Char { length: 1 })]
#[case(ComplexColumnType::Custom { custom_type: "SERIAL".into() })]
#[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec![]) })]
fn test_complex_column_type_does_not_support_auto_increment(
#[case] column_type: ComplexColumnType,
) {
assert!(!ColumnType::Complex(column_type).supports_auto_increment());
}
#[test]
fn test_enum_values_is_string() {
let string_vals = EnumValues::String(vec!["active".into()]);
let int_vals = EnumValues::Integer(vec![NumValue {
name: "Active".into(),
value: 1,
}]);
assert!(string_vals.is_string());
assert!(!int_vals.is_string());
}
#[test]
fn test_enum_values_is_integer() {
let string_vals = EnumValues::String(vec!["active".into()]);
let int_vals = EnumValues::Integer(vec![NumValue {
name: "Active".into(),
value: 1,
}]);
assert!(!string_vals.is_integer());
assert!(int_vals.is_integer());
}
#[test]
fn test_enum_values_variant_names_string() {
let vals = EnumValues::String(vec!["pending".into(), "active".into()]);
assert_eq!(vals.variant_names(), vec!["pending", "active"]);
}
#[test]
fn test_enum_values_variant_names_integer() {
let vals = EnumValues::Integer(vec![
NumValue {
name: "Low".into(),
value: 0,
},
NumValue {
name: "High".into(),
value: 10,
},
]);
assert_eq!(vals.variant_names(), vec!["Low", "High"]);
}
#[test]
fn test_enum_values_len_and_is_empty() {
let empty = EnumValues::String(vec![]);
let non_empty = EnumValues::String(vec!["a".into()]);
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
assert!(!non_empty.is_empty());
assert_eq!(non_empty.len(), 1);
let empty_int = EnumValues::Integer(vec![]);
let non_empty_int = EnumValues::Integer(vec![
NumValue {
name: "A".into(),
value: 0,
},
NumValue {
name: "B".into(),
value: 1,
},
]);
assert!(empty_int.is_empty());
assert_eq!(empty_int.len(), 0);
assert!(!non_empty_int.is_empty());
assert_eq!(non_empty_int.len(), 2);
}
#[test]
fn test_enum_values_to_sql_values_string() {
let vals = EnumValues::String(vec!["active".into(), "pending".into()]);
assert_eq!(vals.to_sql_values(), vec!["'active'", "'pending'"]);
}
#[test]
fn test_enum_values_to_sql_values_integer() {
let vals = EnumValues::Integer(vec![
NumValue {
name: "Low".into(),
value: 0,
},
NumValue {
name: "High".into(),
value: 10,
},
]);
assert_eq!(vals.to_sql_values(), vec!["0", "10"]);
}
#[test]
fn test_enum_values_from_vec_string() {
let vals: EnumValues = vec!["a".to_string(), "b".to_string()].into();
assert!(matches!(vals, EnumValues::String(_)));
}
#[test]
fn test_enum_values_from_vec_str() {
let vals: EnumValues = vec!["a", "b"].into();
assert!(matches!(vals, EnumValues::String(_)));
}
#[rstest]
#[case(SimpleColumnType::SmallInt, true)]
#[case(SimpleColumnType::Integer, true)]
#[case(SimpleColumnType::BigInt, true)]
#[case(SimpleColumnType::Text, false)]
#[case(SimpleColumnType::Boolean, false)]
fn test_simple_column_type_supports_auto_increment(
#[case] ty: SimpleColumnType,
#[case] expected: bool,
) {
assert_eq!(ty.supports_auto_increment(), expected);
}
#[rstest]
#[case(SimpleColumnType::Integer, true)]
#[case(SimpleColumnType::Text, false)]
fn test_column_type_simple_supports_auto_increment(
#[case] ty: SimpleColumnType,
#[case] expected: bool,
) {
assert_eq!(ColumnType::Simple(ty).supports_auto_increment(), expected);
}
#[test]
fn test_requires_migration_integer_enum_values_changed() {
let from = ColumnType::Complex(ComplexColumnType::Enum {
name: "status".into(),
values: EnumValues::Integer(vec![
NumValue {
name: "Pending".into(),
value: 0,
},
NumValue {
name: "Active".into(),
value: 1,
},
]),
});
let to = ColumnType::Complex(ComplexColumnType::Enum {
name: "status".into(),
values: EnumValues::Integer(vec![
NumValue {
name: "Pending".into(),
value: 0,
},
NumValue {
name: "Active".into(),
value: 1,
},
NumValue {
name: "Completed".into(),
value: 100,
},
]),
});
assert!(!from.requires_migration(&to));
}
#[test]
fn test_requires_migration_integer_enum_name_changed() {
let from = ColumnType::Complex(ComplexColumnType::Enum {
name: "old_status".into(),
values: EnumValues::Integer(vec![NumValue {
name: "Pending".into(),
value: 0,
}]),
});
let to = ColumnType::Complex(ComplexColumnType::Enum {
name: "new_status".into(),
values: EnumValues::Integer(vec![NumValue {
name: "Pending".into(),
value: 0,
}]),
});
assert!(!from.requires_migration(&to));
}
#[test]
fn test_requires_migration_string_enum_values_changed() {
let from = ColumnType::Complex(ComplexColumnType::Enum {
name: "status".into(),
values: EnumValues::String(vec!["pending".into(), "active".into()]),
});
let to = ColumnType::Complex(ComplexColumnType::Enum {
name: "status".into(),
values: EnumValues::String(vec!["pending".into(), "active".into(), "completed".into()]),
});
assert!(from.requires_migration(&to));
}
#[test]
fn test_requires_migration_simple_types() {
let int = ColumnType::Simple(SimpleColumnType::Integer);
let text = ColumnType::Simple(SimpleColumnType::Text);
assert!(int.requires_migration(&text));
assert!(!int.requires_migration(&int));
}
#[test]
fn test_requires_migration_mixed_enum_types() {
let string_enum = ColumnType::Complex(ComplexColumnType::Enum {
name: "status".into(),
values: EnumValues::String(vec!["pending".into()]),
});
let int_enum = ColumnType::Complex(ComplexColumnType::Enum {
name: "status".into(),
values: EnumValues::Integer(vec![NumValue {
name: "Pending".into(),
value: 0,
}]),
});
assert!(string_enum.requires_migration(&int_enum));
}
#[rstest]
#[case(SimpleColumnType::SmallInt, "smallint")]
#[case(SimpleColumnType::Integer, "integer")]
#[case(SimpleColumnType::BigInt, "bigint")]
#[case(SimpleColumnType::Real, "real")]
#[case(SimpleColumnType::DoublePrecision, "double precision")]
#[case(SimpleColumnType::Text, "text")]
#[case(SimpleColumnType::Boolean, "boolean")]
#[case(SimpleColumnType::Date, "date")]
#[case(SimpleColumnType::Time, "time")]
#[case(SimpleColumnType::Timestamp, "timestamp")]
#[case(SimpleColumnType::Timestamptz, "timestamptz")]
#[case(SimpleColumnType::Interval, "interval")]
#[case(SimpleColumnType::Bytea, "bytea")]
#[case(SimpleColumnType::Uuid, "uuid")]
#[case(SimpleColumnType::Json, "json")]
#[case(SimpleColumnType::Inet, "inet")]
#[case(SimpleColumnType::Cidr, "cidr")]
#[case(SimpleColumnType::Macaddr, "macaddr")]
#[case(SimpleColumnType::Xml, "xml")]
fn test_simple_column_type_to_display_string(
#[case] column_type: SimpleColumnType,
#[case] expected: &str,
) {
assert_eq!(column_type.to_display_string(), expected);
}
#[test]
fn test_complex_column_type_to_display_string_varchar() {
let ty = ComplexColumnType::Varchar { length: 255 };
assert_eq!(ty.to_display_string(), "varchar(255)");
}
#[test]
fn test_complex_column_type_to_display_string_numeric() {
let ty = ComplexColumnType::Numeric {
precision: 10,
scale: 2,
};
assert_eq!(ty.to_display_string(), "numeric(10,2)");
}
#[test]
fn test_complex_column_type_to_display_string_char() {
let ty = ComplexColumnType::Char { length: 5 };
assert_eq!(ty.to_display_string(), "char(5)");
}
#[test]
fn test_complex_column_type_to_display_string_custom() {
let ty = ComplexColumnType::Custom {
custom_type: "TSVECTOR".into(),
};
assert_eq!(ty.to_display_string(), "tsvector");
}
#[test]
fn test_complex_column_type_to_display_string_string_enum() {
let ty = ComplexColumnType::Enum {
name: "user_status".into(),
values: EnumValues::String(vec!["active".into(), "inactive".into()]),
};
assert_eq!(ty.to_display_string(), "enum<user_status>");
}
#[test]
fn test_complex_column_type_to_display_string_integer_enum() {
let ty = ComplexColumnType::Enum {
name: "priority".into(),
values: EnumValues::Integer(vec![
NumValue {
name: "Low".into(),
value: 0,
},
NumValue {
name: "High".into(),
value: 10,
},
]),
};
assert_eq!(ty.to_display_string(), "enum<priority> (integer)");
}
#[test]
fn test_column_type_to_display_string_simple() {
let ty = ColumnType::Simple(SimpleColumnType::Integer);
assert_eq!(ty.to_display_string(), "integer");
}
#[test]
fn test_column_type_to_display_string_complex() {
let ty = ColumnType::Complex(ComplexColumnType::Varchar { length: 100 });
assert_eq!(ty.to_display_string(), "varchar(100)");
}
#[rstest]
#[case(SimpleColumnType::SmallInt, "0")]
#[case(SimpleColumnType::Integer, "0")]
#[case(SimpleColumnType::BigInt, "0")]
#[case(SimpleColumnType::Real, "0.0")]
#[case(SimpleColumnType::DoublePrecision, "0.0")]
#[case(SimpleColumnType::Boolean, "false")]
#[case(SimpleColumnType::Text, "''")]
#[case(SimpleColumnType::Date, "'1970-01-01'")]
#[case(SimpleColumnType::Time, "'00:00:00'")]
#[case(SimpleColumnType::Timestamp, "CURRENT_TIMESTAMP")]
#[case(SimpleColumnType::Timestamptz, "CURRENT_TIMESTAMP")]
#[case(SimpleColumnType::Interval, "'0'")]
#[case(SimpleColumnType::Bytea, "''")]
#[case(SimpleColumnType::Uuid, "'00000000-0000-0000-0000-000000000000'")]
#[case(SimpleColumnType::Json, "'{}'")]
#[case(SimpleColumnType::Inet, "'0.0.0.0'")]
#[case(SimpleColumnType::Cidr, "'0.0.0.0'")]
#[case(SimpleColumnType::Macaddr, "'00:00:00:00:00:00'")]
#[case(SimpleColumnType::Xml, "'<xml/>'")]
fn test_simple_column_type_default_fill_value(
#[case] column_type: SimpleColumnType,
#[case] expected: &str,
) {
assert_eq!(column_type.default_fill_value(), expected);
}
#[test]
fn test_complex_column_type_default_fill_value_varchar() {
let ty = ComplexColumnType::Varchar { length: 255 };
assert_eq!(ty.default_fill_value(), "''");
}
#[test]
fn test_complex_column_type_default_fill_value_char() {
let ty = ComplexColumnType::Char { length: 1 };
assert_eq!(ty.default_fill_value(), "''");
}
#[test]
fn test_complex_column_type_default_fill_value_numeric() {
let ty = ComplexColumnType::Numeric {
precision: 10,
scale: 2,
};
assert_eq!(ty.default_fill_value(), "0");
}
#[test]
fn test_complex_column_type_default_fill_value_custom() {
let ty = ComplexColumnType::Custom {
custom_type: "MONEY".into(),
};
assert_eq!(ty.default_fill_value(), "''");
}
#[test]
fn test_complex_column_type_default_fill_value_enum() {
let ty = ComplexColumnType::Enum {
name: "status".into(),
values: EnumValues::String(vec!["active".into()]),
};
assert_eq!(ty.default_fill_value(), "''");
}
#[test]
fn test_column_type_default_fill_value_simple() {
let ty = ColumnType::Simple(SimpleColumnType::Integer);
assert_eq!(ty.default_fill_value(), "0");
}
#[test]
fn test_column_type_default_fill_value_complex() {
let ty = ColumnType::Complex(ComplexColumnType::Varchar { length: 100 });
assert_eq!(ty.default_fill_value(), "''");
}
#[test]
fn test_enum_variant_names_simple_type_returns_none() {
let ty = ColumnType::Simple(SimpleColumnType::Integer);
assert_eq!(ty.enum_variant_names(), None);
}
#[test]
fn test_enum_variant_names_complex_non_enum_returns_none() {
let ty = ColumnType::Complex(ComplexColumnType::Varchar { length: 255 });
assert_eq!(ty.enum_variant_names(), None);
}
#[test]
fn test_enum_variant_names_complex_numeric_returns_none() {
let ty = ColumnType::Complex(ComplexColumnType::Numeric {
precision: 10,
scale: 2,
});
assert_eq!(ty.enum_variant_names(), None);
}
#[test]
fn test_enum_variant_names_complex_char_returns_none() {
let ty = ColumnType::Complex(ComplexColumnType::Char { length: 1 });
assert_eq!(ty.enum_variant_names(), None);
}
#[test]
fn test_enum_variant_names_complex_custom_returns_none() {
let ty = ColumnType::Complex(ComplexColumnType::Custom {
custom_type: "TSVECTOR".into(),
});
assert_eq!(ty.enum_variant_names(), None);
}
#[test]
fn test_enum_variant_names_string_enum() {
let ty = ColumnType::Complex(ComplexColumnType::Enum {
name: "status".into(),
values: EnumValues::String(vec!["active".into(), "inactive".into(), "pending".into()]),
});
assert_eq!(
ty.enum_variant_names(),
Some(vec![
"active".to_string(),
"inactive".to_string(),
"pending".to_string()
])
);
}
#[test]
fn test_enum_variant_names_integer_enum() {
let ty = ColumnType::Complex(ComplexColumnType::Enum {
name: "priority".into(),
values: EnumValues::Integer(vec![
NumValue {
name: "Low".into(),
value: 0,
},
NumValue {
name: "Medium".into(),
value: 5,
},
NumValue {
name: "High".into(),
value: 10,
},
]),
});
assert_eq!(
ty.enum_variant_names(),
Some(vec![
"Low".to_string(),
"Medium".to_string(),
"High".to_string()
])
);
}
#[test]
fn test_enum_variant_names_empty_string_enum() {
let ty = ColumnType::Complex(ComplexColumnType::Enum {
name: "empty".into(),
values: EnumValues::String(vec![]),
});
assert_eq!(ty.enum_variant_names(), Some(vec![]));
}
#[test]
fn test_enum_variant_names_empty_integer_enum() {
let ty = ColumnType::Complex(ComplexColumnType::Enum {
name: "empty".into(),
values: EnumValues::Integer(vec![]),
});
assert_eq!(ty.enum_variant_names(), Some(vec![]));
}
}