mod codegen;
mod diff;
mod entity;
pub(crate) mod runner;
mod sql;
pub(crate) mod builtin;
use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;
use futures_util::future::BoxFuture;
use sea_orm::{DatabaseTransaction, DbErr};
pub(crate) use codegen::{render_list, render_migration};
pub(crate) use diff::{Answers, Detect, Questions, describe, diff};
pub(crate) use entity::{EntityTable, table_of};
pub use runner::MigrationEvent;
#[derive(Clone, Debug)]
pub struct NewMigration {
pub name: String,
pub path: std::path::PathBuf,
pub changes: Vec<String>,
}
pub type RunFn =
Arc<dyn for<'a> Fn(&'a DatabaseTransaction) -> BoxFuture<'a, Result<(), DbErr>> + Send + Sync>;
#[derive(Clone)]
pub struct Migration {
name: String,
operations: Vec<Operation>,
}
impl Migration {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
operations: Vec::new(),
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
fn push(mut self, operation: Operation) -> Self {
self.operations.push(operation);
self
}
#[must_use]
pub fn create_table(self, table: Table) -> Self {
self.push(Operation::CreateTable(table))
}
#[must_use]
pub fn drop_table(self, table: Table) -> Self {
self.push(Operation::DropTable(table))
}
#[must_use]
pub fn rename_table(self, from: impl Into<String>, to: impl Into<String>) -> Self {
self.push(Operation::RenameTable {
from: from.into(),
to: to.into(),
})
}
#[must_use]
pub fn add_column(self, table: impl Into<String>, column: Column) -> Self {
self.push(Operation::AddColumn {
table: table.into(),
column,
fill: None,
})
}
#[must_use]
pub fn add_column_filled(
self,
table: impl Into<String>,
column: Column,
fill: impl Into<Value>,
) -> Self {
self.push(Operation::AddColumn {
table: table.into(),
column,
fill: Some(fill.into()),
})
}
#[must_use]
pub fn drop_column(self, table: impl Into<String>, column: Column) -> Self {
self.push(Operation::DropColumn {
table: table.into(),
column,
})
}
#[must_use]
pub fn rename_column(
self,
table: impl Into<String>,
from: impl Into<String>,
to: impl Into<String>,
) -> Self {
self.push(Operation::RenameColumn {
table: table.into(),
from: from.into(),
to: to.into(),
})
}
#[must_use]
pub fn alter_column(self, table: impl Into<String>, from: Column, to: Column) -> Self {
self.push(Operation::AlterColumn {
table: table.into(),
from,
to,
})
}
#[must_use]
pub fn add_index(self, table: impl Into<String>, index: Index) -> Self {
self.push(Operation::AddIndex {
table: table.into(),
index,
})
}
#[must_use]
pub fn drop_index(self, table: impl Into<String>, index: Index) -> Self {
self.push(Operation::DropIndex {
table: table.into(),
index,
})
}
#[must_use]
pub fn add_foreign_key(self, table: impl Into<String>, key: ForeignKey) -> Self {
self.push(Operation::AddForeignKey {
table: table.into(),
key,
})
}
#[must_use]
pub fn drop_foreign_key(self, table: impl Into<String>, key: ForeignKey) -> Self {
self.push(Operation::DropForeignKey {
table: table.into(),
key,
})
}
#[must_use]
pub fn sql(self, forward: impl Into<String>) -> Self {
self.push(Operation::Sql {
forward: forward.into(),
backward: None,
})
}
#[must_use]
pub fn sql_reversible(self, forward: impl Into<String>, backward: impl Into<String>) -> Self {
self.push(Operation::Sql {
forward: forward.into(),
backward: Some(backward.into()),
})
}
#[must_use]
pub fn run<F>(self, forward: F) -> Self
where
F: for<'a> Fn(&'a DatabaseTransaction) -> BoxFuture<'a, Result<(), DbErr>>
+ Send
+ Sync
+ 'static,
{
self.push(Operation::Run {
forward: Arc::new(forward),
backward: None,
})
}
#[must_use]
pub fn run_reversible<F, B>(self, forward: F, backward: B) -> Self
where
F: for<'a> Fn(&'a DatabaseTransaction) -> BoxFuture<'a, Result<(), DbErr>>
+ Send
+ Sync
+ 'static,
B: for<'a> Fn(&'a DatabaseTransaction) -> BoxFuture<'a, Result<(), DbErr>>
+ Send
+ Sync
+ 'static,
{
self.push(Operation::Run {
forward: Arc::new(forward),
backward: Some(Arc::new(backward)),
})
}
pub(crate) fn operations(&self) -> &[Operation] {
&self.operations
}
pub(crate) fn number(&self) -> Option<u32> {
let digits: String = self.name.chars().take_while(char::is_ascii_digit).collect();
digits.parse().ok()
}
}
impl fmt::Debug for Migration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Migration")
.field("name", &self.name)
.field("operations", &self.operations)
.finish()
}
}
impl PartialEq for Migration {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.operations == other.operations
}
}
#[derive(Clone)]
pub(crate) enum Operation {
CreateTable(Table),
DropTable(Table),
RenameTable {
from: String,
to: String,
},
AddColumn {
table: String,
column: Column,
fill: Option<Value>,
},
DropColumn {
table: String,
column: Column,
},
RenameColumn {
table: String,
from: String,
to: String,
},
AlterColumn {
table: String,
from: Column,
to: Column,
},
AddIndex {
table: String,
index: Index,
},
DropIndex {
table: String,
index: Index,
},
AddForeignKey {
table: String,
key: ForeignKey,
},
DropForeignKey {
table: String,
key: ForeignKey,
},
Sql {
forward: String,
backward: Option<String>,
},
Run {
forward: RunFn,
backward: Option<RunFn>,
},
}
impl fmt::Debug for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CreateTable(t) => f.debug_tuple("CreateTable").field(t).finish(),
Self::DropTable(t) => f.debug_tuple("DropTable").field(t).finish(),
Self::RenameTable { from, to } => write!(f, "RenameTable({from} -> {to})"),
Self::AddColumn {
table,
column,
fill,
} => write!(f, "AddColumn({table}, {column:?}, fill {fill:?})"),
Self::DropColumn { table, column } => write!(f, "DropColumn({table}, {column:?})"),
Self::RenameColumn { table, from, to } => {
write!(f, "RenameColumn({table}: {from} -> {to})")
}
Self::AlterColumn { table, from, to } => {
write!(f, "AlterColumn({table}: {from:?} -> {to:?})")
}
Self::AddIndex { table, index } => write!(f, "AddIndex({table}, {index:?})"),
Self::DropIndex { table, index } => write!(f, "DropIndex({table}, {index:?})"),
Self::AddForeignKey { table, key } => write!(f, "AddForeignKey({table}, {key:?})"),
Self::DropForeignKey { table, key } => write!(f, "DropForeignKey({table}, {key:?})"),
Self::Sql { forward, backward } => write!(f, "Sql({forward:?}, {backward:?})"),
Self::Run { backward, .. } => write!(f, "Run(reversible: {})", backward.is_some()),
}
}
}
impl PartialEq for Operation {
fn eq(&self, other: &Self) -> bool {
use Operation as O;
let same_fn = |a: &RunFn, b: &RunFn| Arc::ptr_eq(a, b);
match (self, other) {
(O::CreateTable(a), O::CreateTable(b)) | (O::DropTable(a), O::DropTable(b)) => a == b,
(O::RenameTable { from: a, to: b }, O::RenameTable { from: c, to: d }) => {
a == c && b == d
}
(
O::AddColumn {
table: a,
column: b,
fill: c,
},
O::AddColumn {
table: d,
column: e,
fill: g,
},
) => a == d && b == e && c == g,
(
O::DropColumn {
table: a,
column: b,
},
O::DropColumn {
table: c,
column: d,
},
) => a == c && b == d,
(
O::RenameColumn {
table: a,
from: b,
to: c,
},
O::RenameColumn {
table: d,
from: e,
to: g,
},
) => a == d && b == e && c == g,
(
O::AlterColumn {
table: a,
from: b,
to: c,
},
O::AlterColumn {
table: d,
from: e,
to: g,
},
) => a == d && b == e && c == g,
(O::AddIndex { table: a, index: b }, O::AddIndex { table: c, index: d })
| (O::DropIndex { table: a, index: b }, O::DropIndex { table: c, index: d }) => {
a == c && b == d
}
(O::AddForeignKey { table: a, key: b }, O::AddForeignKey { table: c, key: d })
| (O::DropForeignKey { table: a, key: b }, O::DropForeignKey { table: c, key: d }) => {
a == c && b == d
}
(
O::Sql {
forward: a,
backward: b,
},
O::Sql {
forward: c,
backward: d,
},
) => a == c && b == d,
(
O::Run {
forward: a,
backward: b,
},
O::Run {
forward: c,
backward: d,
},
) => {
same_fn(a, c)
&& match (b, d) {
(Some(b), Some(d)) => same_fn(b, d),
(None, None) => true,
_ => false,
}
}
_ => false,
}
}
}
impl Operation {
pub(crate) fn reverse(&self) -> Option<Self> {
Some(match self.clone() {
Self::CreateTable(table) => Self::DropTable(table),
Self::DropTable(table) => Self::CreateTable(table),
Self::RenameTable { from, to } => Self::RenameTable { from: to, to: from },
Self::AddColumn { table, column, .. } => Self::DropColumn { table, column },
Self::DropColumn { table, column } => Self::AddColumn {
fill: if column.needs_fill() {
column.kind.empty_value()
} else {
None
},
table,
column,
},
Self::RenameColumn { table, from, to } => Self::RenameColumn {
table,
from: to,
to: from,
},
Self::AlterColumn { table, from, to } => Self::AlterColumn {
table,
from: to,
to: from,
},
Self::AddIndex { table, index } => Self::DropIndex { table, index },
Self::DropIndex { table, index } => Self::AddIndex { table, index },
Self::AddForeignKey { table, key } => Self::DropForeignKey { table, key },
Self::DropForeignKey { table, key } => Self::AddForeignKey { table, key },
Self::Sql { forward, backward } => Self::Sql {
forward: backward?,
backward: Some(forward),
},
Self::Run { forward, backward } => Self::Run {
forward: backward?,
backward: Some(forward),
},
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Table {
pub(crate) name: String,
pub(crate) columns: Vec<Column>,
pub(crate) primary_key: Vec<String>,
pub(crate) indexes: Vec<Index>,
pub(crate) foreign_keys: Vec<ForeignKey>,
}
impl Table {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
columns: Vec::new(),
primary_key: Vec::new(),
indexes: Vec::new(),
foreign_keys: Vec::new(),
}
}
#[must_use]
pub fn column(mut self, column: Column) -> Self {
self.columns.push(column);
self
}
#[must_use]
pub fn primary_key<I, S>(mut self, columns: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.primary_key = columns.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn index(mut self, index: Index) -> Self {
self.indexes.push(index);
self
}
#[must_use]
pub fn foreign_key(mut self, key: ForeignKey) -> Self {
self.foreign_keys.push(key);
self
}
pub(crate) fn find(&self, column: &str) -> Option<&Column> {
self.columns.iter().find(|c| c.name == column)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Column {
pub(crate) name: String,
pub(crate) kind: Type,
pub(crate) null: bool,
pub(crate) unique: bool,
pub(crate) primary_key: bool,
pub(crate) auto_increment: bool,
pub(crate) default: Option<Value>,
}
impl Column {
#[must_use]
pub fn new(name: impl Into<String>, kind: Type) -> Self {
Self {
name: name.into(),
kind,
null: false,
unique: false,
primary_key: false,
auto_increment: false,
default: None,
}
}
#[must_use]
pub fn null(mut self) -> Self {
self.null = true;
self
}
#[must_use]
pub fn unique(mut self) -> Self {
self.unique = true;
self
}
#[must_use]
pub fn primary_key(mut self) -> Self {
self.primary_key = true;
self
}
#[must_use]
pub fn auto_increment(mut self) -> Self {
self.auto_increment = true;
self
}
#[must_use]
pub fn default(mut self, value: impl Into<Value>) -> Self {
self.default = Some(value.into());
self
}
pub(crate) fn same_definition(&self, other: &Self) -> bool {
Self {
name: String::new(),
..self.clone()
} == Self {
name: String::new(),
..other.clone()
}
}
pub(crate) fn needs_fill(&self) -> bool {
!self.null && self.default.is_none() && !self.auto_increment
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Type {
TinyInteger,
SmallInteger,
Integer,
BigInteger,
TinyUnsigned,
SmallUnsigned,
Unsigned,
BigUnsigned,
Float,
Double,
Decimal(Option<(u32, u32)>),
Boolean,
Char(Option<u32>),
String(Option<u32>),
Text,
Date,
Time,
DateTime,
Timestamp,
TimestampTz,
Year,
Json,
JsonBinary,
Uuid,
Binary(u32),
VarBinary(Option<u32>),
Blob,
Custom(String),
}
impl Type {
#[must_use]
pub fn custom(name: impl Into<String>) -> Self {
Self::Custom(name.into())
}
fn empty_value(&self) -> Option<Value> {
Some(match self {
Self::TinyInteger
| Self::SmallInteger
| Self::Integer
| Self::BigInteger
| Self::TinyUnsigned
| Self::SmallUnsigned
| Self::Unsigned
| Self::BigUnsigned
| Self::Year => Value::Int(0),
Self::Float | Self::Double | Self::Decimal(_) => Value::Float(0.0),
Self::Boolean => Value::Bool(false),
Self::Char(_) | Self::String(_) | Self::Text => Value::Text(String::new()),
Self::Date | Self::Time | Self::DateTime | Self::Timestamp | Self::TimestampTz => {
Value::Now
}
Self::Json | Self::JsonBinary => Value::Text("{}".into()),
Self::Uuid => Value::Text("00000000-0000-0000-0000-000000000000".into()),
Self::Binary(_) | Self::VarBinary(_) | Self::Blob | Self::Custom(_) => return None,
})
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Value {
Bool(bool),
Int(i64),
Float(f64),
Text(String),
Now,
Sql(String),
}
impl Value {
#[must_use]
pub fn sql(expression: impl Into<String>) -> Self {
Self::Sql(expression.into())
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<i32> for Value {
fn from(value: i32) -> Self {
Self::Int(value.into())
}
}
impl From<i64> for Value {
fn from(value: i64) -> Self {
Self::Int(value)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Self::Float(value)
}
}
impl From<&str> for Value {
fn from(value: &str) -> Self {
Self::Text(value.to_owned())
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Self::Text(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Index {
pub(crate) name: String,
pub(crate) columns: Vec<String>,
pub(crate) unique: bool,
}
impl Index {
#[must_use]
pub fn new<I, S>(name: impl Into<String>, columns: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
name: name.into(),
columns: columns.into_iter().map(Into::into).collect(),
unique: false,
}
}
#[must_use]
pub fn unique(mut self) -> Self {
self.unique = true;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ForeignKey {
pub(crate) name: String,
pub(crate) columns: Vec<String>,
pub(crate) to_table: String,
pub(crate) to_columns: Vec<String>,
pub(crate) on_delete: Option<Action>,
pub(crate) on_update: Option<Action>,
}
impl ForeignKey {
#[must_use]
pub fn new<C, S, R, T>(name: impl Into<String>, columns: C, to_table: R, to_columns: T) -> Self
where
C: IntoIterator<Item = S>,
T: IntoIterator<Item = S>,
S: Into<String>,
R: Into<String>,
{
Self {
name: name.into(),
columns: columns.into_iter().map(Into::into).collect(),
to_table: to_table.into(),
to_columns: to_columns.into_iter().map(Into::into).collect(),
on_delete: None,
on_update: None,
}
}
#[must_use]
pub fn on_delete(mut self, action: Action) -> Self {
self.on_delete = Some(action);
self
}
#[must_use]
pub fn on_update(mut self, action: Action) -> Self {
self.on_update = Some(action);
self
}
pub(crate) fn same_definition(&self, other: &Self) -> bool {
self.columns == other.columns
&& self.to_table == other.to_table
&& self.to_columns == other.to_columns
&& self.on_delete == other.on_delete
&& self.on_update == other.on_update
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Action {
Restrict,
Cascade,
SetNull,
NoAction,
SetDefault,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct State {
pub(crate) tables: BTreeMap<String, Table>,
}
impl State {
pub(crate) fn after(migrations: &[Migration]) -> Result<Self, String> {
let mut state = Self::default();
for migration in migrations {
for operation in migration.operations() {
state
.apply(operation)
.map_err(|e| format!("migration {}: {e}", migration.name()))?;
}
}
Ok(state)
}
fn table_mut(&mut self, name: &str) -> Result<&mut Table, String> {
self.tables
.get_mut(name)
.ok_or_else(|| format!("there is no table `{name}` at this point"))
}
pub(crate) fn apply(&mut self, operation: &Operation) -> Result<(), String> {
match operation {
Operation::CreateTable(table) => {
if self.tables.contains_key(&table.name) {
return Err(format!("table `{}` already exists", table.name));
}
self.tables.insert(table.name.clone(), table.clone());
}
Operation::DropTable(table) => {
if self.tables.remove(&table.name).is_none() {
return Err(format!("there is no table `{}` to drop", table.name));
}
}
Operation::RenameTable { from, to } => {
if self.tables.contains_key(to) {
return Err(format!("table `{to}` already exists"));
}
let mut table = self
.tables
.remove(from)
.ok_or_else(|| format!("there is no table `{from}` to rename"))?;
table.name.clone_from(to);
self.tables.insert(to.clone(), table);
for other in self.tables.values_mut() {
for key in &mut other.foreign_keys {
if &key.to_table == from {
key.to_table.clone_from(to);
}
}
}
}
Operation::AddColumn { table, column, .. } => {
let t = self.table_mut(table)?;
if t.find(&column.name).is_some() {
return Err(format!("column `{table}.{}` already exists", column.name));
}
t.columns.push(column.clone());
}
Operation::DropColumn { table, column } => {
let t = self.table_mut(table)?;
let before = t.columns.len();
t.columns.retain(|c| c.name != column.name);
if t.columns.len() == before {
return Err(format!(
"there is no column `{table}.{}` to drop",
column.name
));
}
t.indexes.retain(|i| !i.columns.contains(&column.name));
t.foreign_keys.retain(|k| !k.columns.contains(&column.name));
}
Operation::RenameColumn { table, from, to } => {
let t = self.table_mut(table)?;
if t.find(to).is_some() {
return Err(format!("column `{table}.{to}` already exists"));
}
let column = t
.columns
.iter_mut()
.find(|c| &c.name == from)
.ok_or_else(|| format!("there is no column `{table}.{from}` to rename"))?;
column.name.clone_from(to);
let rename = |names: &mut Vec<String>| {
for name in names {
if name == from {
name.clone_from(to);
}
}
};
rename(&mut t.primary_key);
for index in &mut t.indexes {
rename(&mut index.columns);
}
for key in &mut t.foreign_keys {
rename(&mut key.columns);
}
for other in self.tables.values_mut() {
for key in &mut other.foreign_keys {
if &key.to_table == table {
rename(&mut key.to_columns);
}
}
}
}
Operation::AlterColumn { table, from, to } => {
if from.name != to.name {
return Err(format!(
"alter_column on `{table}` changes the name from `{}` to `{}`; use \
rename_column for that",
from.name, to.name
));
}
let t = self.table_mut(table)?;
let column = t
.columns
.iter_mut()
.find(|c| c.name == to.name)
.ok_or_else(|| format!("there is no column `{table}.{}` to alter", to.name))?;
*column = to.clone();
}
Operation::AddIndex { table, index } => {
let t = self.table_mut(table)?;
if t.indexes.iter().any(|i| i.name == index.name) {
return Err(format!("index `{}` already exists", index.name));
}
t.indexes.push(index.clone());
}
Operation::DropIndex { table, index } => {
let t = self.table_mut(table)?;
let before = t.indexes.len();
t.indexes.retain(|i| i.name != index.name);
if t.indexes.len() == before {
return Err(format!("there is no index `{}` to drop", index.name));
}
}
Operation::AddForeignKey { table, key } => {
let t = self.table_mut(table)?;
if t.foreign_keys.iter().any(|k| k.name == key.name) {
return Err(format!("foreign key `{}` already exists", key.name));
}
t.foreign_keys.push(key.clone());
}
Operation::DropForeignKey { table, key } => {
let t = self.table_mut(table)?;
let before = t.foreign_keys.len();
t.foreign_keys.retain(|k| k.name != key.name);
if t.foreign_keys.len() == before {
return Err(format!("there is no foreign key `{}` to drop", key.name));
}
}
Operation::Sql { .. } | Operation::Run { .. } => {}
}
Ok(())
}
}