use super::types::*;
use crate::config::DatabaseType;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ColumnType {
Integer,
BigInteger,
String(Option<u32>),
Text,
Boolean,
Float,
Double,
Date,
Time,
DateTime,
Timestamp,
Json,
Binary,
Custom(String),
}
impl ColumnType {
pub fn to_sql(&self, db_type: DatabaseType) -> String {
match self {
ColumnType::Integer => "INTEGER".to_string(),
ColumnType::BigInteger => match db_type {
DatabaseType::MySql => "BIGINT".to_string(),
_ => "BIGINT".to_string(),
},
ColumnType::String(None) => match db_type {
DatabaseType::MySql => "VARCHAR(255)".to_string(),
DatabaseType::Postgres => "VARCHAR(255)".to_string(),
DatabaseType::Sqlite => "TEXT".to_string(),
},
ColumnType::String(Some(len)) => match db_type {
DatabaseType::MySql => format!("VARCHAR({})", len),
DatabaseType::Postgres => format!("VARCHAR({})", len),
DatabaseType::Sqlite => "TEXT".to_string(),
},
ColumnType::Text => "TEXT".to_string(),
ColumnType::Boolean => match db_type {
DatabaseType::MySql => "BOOLEAN".to_string(),
DatabaseType::Postgres => "BOOLEAN".to_string(),
DatabaseType::Sqlite => "INTEGER".to_string(),
},
ColumnType::Float => "FLOAT".to_string(),
ColumnType::Double => "DOUBLE PRECISION".to_string(),
ColumnType::Date => "DATE".to_string(),
ColumnType::Time => "TIME".to_string(),
ColumnType::DateTime => match db_type {
DatabaseType::MySql => "DATETIME".to_string(),
DatabaseType::Postgres => "TIMESTAMP".to_string(),
DatabaseType::Sqlite => "TEXT".to_string(),
},
ColumnType::Timestamp => "TIMESTAMP".to_string(),
ColumnType::Json => match db_type {
DatabaseType::MySql => "JSON".to_string(),
DatabaseType::Postgres => "JSONB".to_string(),
DatabaseType::Sqlite => "TEXT".to_string(),
},
ColumnType::Binary => "BLOB".to_string(),
ColumnType::Custom(name) => name.to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Column {
pub name: String,
pub column_type: ColumnType,
pub is_primary_key: bool,
pub is_nullable: bool,
pub has_default: bool,
pub default_value: Option<String>,
pub is_auto_increment: bool,
pub comment: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Table {
pub name: String,
pub columns: Vec<Column>,
pub primary_key_columns: Vec<String>,
pub indexes: Vec<Index>,
pub foreign_keys: Vec<ForeignKey>,
pub comment: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Index {
pub name: String,
pub table_name: String,
pub columns: Vec<String>,
pub is_unique: bool,
pub is_constraint: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ForeignKey {
pub name: String,
pub table_name: String,
pub column_name: String,
pub referenced_table_name: String,
pub referenced_column_name: String,
pub on_delete: Option<ForeignKeyAction>,
pub on_update: Option<ForeignKeyAction>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ForeignKeyAction {
Cascade,
SetNull,
SetDefault,
Restrict,
NoAction,
}
impl fmt::Display for ForeignKeyAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ForeignKeyAction::Cascade => write!(f, "CASCADE"),
ForeignKeyAction::SetNull => write!(f, "SET NULL"),
ForeignKeyAction::SetDefault => write!(f, "SET DEFAULT"),
ForeignKeyAction::Restrict => write!(f, "RESTRICT"),
ForeignKeyAction::NoAction => write!(f, "NO ACTION"),
}
}
}
#[derive(Debug, Clone)]
pub struct Schema {
pub database_type: DatabaseType,
pub tables: Vec<Table>,
table_index: HashMap<String, usize>,
}
impl Default for Schema {
fn default() -> Self {
Self {
database_type: DatabaseType::Sqlite,
tables: Vec::new(),
table_index: HashMap::new(),
}
}
}
impl Schema {
pub fn new(database_type: DatabaseType) -> Self {
Self {
database_type,
tables: Vec::new(),
table_index: HashMap::new(),
}
}
pub fn add_table(&mut self, table: Table) {
let index = self.tables.len();
self.table_index.insert(table.name.clone(), index);
self.tables.push(table);
}
pub fn get_table(&self, name: &str) -> Option<&Table> {
if let Some(&index) = self.table_index.get(name) {
self.tables.get(index)
} else {
None
}
}
pub fn get_table_mut(&mut self, name: &str) -> Option<&mut Table> {
if let Some(&index) = self.table_index.get(name) {
self.tables.get_mut(index)
} else {
None
}
}
pub fn has_table(&self, name: &str) -> bool {
self.table_index.contains_key(name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Migration {
pub version: u32,
pub description: String,
pub table_changes: Vec<TableChange>,
pub sql: Option<String>,
pub timestamp: Option<time::OffsetDateTime>,
}
impl Migration {
pub fn new(version: u32, description: String) -> Self {
Self {
version,
description,
table_changes: Vec::new(),
sql: None,
timestamp: Some(time::OffsetDateTime::now_utc()),
}
}
pub fn add_table_change(&mut self, change: TableChange) {
self.table_changes.push(change);
}
}
#[derive(Debug, Clone)]
pub struct MigrationVersion {
pub version: u32,
pub description: String,
pub applied_at: time::OffsetDateTime,
pub file_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SerializableMigrationVersion {
pub version: u32,
pub description: String,
pub applied_at: String, pub file_path: String,
}
impl From<MigrationVersion> for SerializableMigrationVersion {
fn from(mv: MigrationVersion) -> Self {
Self {
version: mv.version,
description: mv.description,
applied_at: mv.applied_at.to_string(),
file_path: mv.file_path,
}
}
}
impl From<SerializableMigrationVersion> for MigrationVersion {
fn from(sm: SerializableMigrationVersion) -> Self {
let applied_at =
match time::OffsetDateTime::parse(&sm.applied_at, &time::format_description::well_known::Rfc3339) {
Ok(dt) => dt,
Err(e) => {
tracing::warn!(
"Failed to parse applied_at '{}': {}, using current time",
sm.applied_at,
e
);
time::OffsetDateTime::now_utc()
}
};
Self {
version: sm.version,
description: sm.description,
applied_at,
file_path: sm.file_path,
}
}
}
#[derive(Debug, Clone)]
pub struct MigrationHistory {
pub applied_migrations: Vec<MigrationVersion>,
}
impl Default for MigrationHistory {
fn default() -> Self {
Self::new()
}
}
impl MigrationHistory {
pub fn new() -> Self {
Self {
applied_migrations: Vec::new(),
}
}
pub fn add_migration(&mut self, migration: MigrationVersion) {
self.applied_migrations.push(migration);
self.applied_migrations.sort_by_key(|m| m.version);
}
pub fn is_version_applied(&self, version: u32) -> bool {
self.applied_migrations.iter().any(|m| m.version == version)
}
pub fn get_latest_version(&self) -> Option<u32> {
self.applied_migrations.iter().map(|m| m.version).max()
}
pub fn get_pending_migrations<'a>(&self, all_migrations: &'a [Migration]) -> Vec<&'a Migration> {
all_migrations
.iter()
.filter(|m| !self.is_version_applied(m.version))
.collect()
}
}