//! SeaORM migration Rust code generation — `up`/`down` files, CREATE TABLE, FK, indexes, triggers.
use crate::migration::utils::{
helpers::col_type_to_method,
types::{Changes, DbKind, ParsedColumn, ParsedSchema},
};
/// Generates the migration file for a CREATE TABLE — no FK constraints (they go in the relations file).
/// Also used for snapshots (via `DbKind::Other`) to enable FK diffing on subsequent runs.
pub fn generate_create_file(schema: &ParsedSchema, db_kind: &DbKind) -> String {
let cols = build_create_table_cols(schema, db_kind);
let idx_stmts = build_index_create_stmts(schema);
let trigger_stmts = build_updated_at_trigger_stmts(schema, db_kind);
let enum_stmts = build_enum_type_stmts(schema, db_kind);
let enum_drops = build_enum_type_drops(schema, db_kind);
let idx_drops = build_index_drop_stmts(schema);
let trigger_drops = build_updated_at_trigger_drops(schema, db_kind);
let mut up = String::new();
up.push_str(&enum_stmts);
up.push_str(" manager\n");
up.push_str(" .create_table(\n");
up.push_str(" Table::create()\n");
up.push_str(&format!(
" .table(Alias::new(\"{}\"))\n",
schema.table_name
));
up.push_str(" .if_not_exists()\n");
up.push_str(&cols);
up.push_str(" .to_owned()\n");
up.push_str(" )\n");
up.push_str(" .await?;\n\n");
up.push_str(&idx_stmts);
up.push_str(&trigger_stmts);
up.push_str(" Ok(())\n");
let mut down = String::new();
down.push_str(&trigger_drops);
down.push_str(&idx_drops);
down.push_str(" manager\n");
down.push_str(" .drop_table(Table::drop()\n");
down.push_str(&format!(
" .table(Alias::new(\"{}\"))\n",
schema.table_name
));
down.push_str(" .to_owned())\n");
down.push_str(" .await?;\n");
down.push_str(&enum_drops);
down.push_str(" Ok(())\n");
format!(
"use sea_orm_migration::prelude::*;\n\n\
#[derive(DeriveMigrationName)]\n\
pub struct Migration;\n\n\
#[async_trait::async_trait]\n\
impl MigrationTrait for Migration {{\n\
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {{\n\
{up}\
}}\n\n\
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {{\n\
{down}\
}}\n\
}}\n",
up = up,
down = down,
)
}
/// Generates the snapshot file (includes FK stmts so diffs detect FK additions/removals).
pub fn generate_snapshot_file(schema: &ParsedSchema) -> String {
let cols = build_create_table_cols(schema, &DbKind::Other);
let fk_stmts = build_fk_create_stmts(schema);
let idx_stmts = build_index_create_stmts(schema);
let fk_drops = build_fk_drop_stmts(schema);
let idx_drops = build_index_drop_stmts(schema);
let mut up = String::new();
up.push_str(" manager\n");
up.push_str(" .create_table(\n");
up.push_str(" Table::create()\n");
up.push_str(&format!(
" .table(Alias::new(\"{}\"))\n",
schema.table_name
));
up.push_str(" .if_not_exists()\n");
up.push_str(&cols);
up.push_str(" .to_owned()\n");
up.push_str(" )\n");
up.push_str(" .await?;\n\n");
up.push_str(&fk_stmts);
up.push_str(&idx_stmts);
up.push_str(" Ok(())\n");
let mut down = String::new();
down.push_str(&fk_drops);
down.push_str(&idx_drops);
down.push_str(" manager\n");
down.push_str(" .drop_table(Table::drop()\n");
down.push_str(&format!(
" .table(Alias::new(\"{}\"))\n",
schema.table_name
));
down.push_str(" .to_owned())\n");
down.push_str(" .await?;\n");
down.push_str(" Ok(())\n");
format!(
"use sea_orm_migration::prelude::*;\n\n\
#[derive(DeriveMigrationName)]\n\
pub struct Migration;\n\n\
#[async_trait::async_trait]\n\
impl MigrationTrait for Migration {{\n\
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {{\n\
{up}\
}}\n\n\
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {{\n\
{down}\
}}\n\
}}\n",
up = up,
down = down,
)
}
/// Generates a single migration that adds all FK constraints for a batch of new tables.
/// Placed last in lib.rs so all tables exist before FK constraints are applied.
pub fn generate_relations_file(schemas: &[&ParsedSchema]) -> String {
let mut up = String::new();
let mut down = String::new();
for schema in schemas {
up.push_str(&build_fk_create_stmts(schema));
}
for schema in schemas.iter().rev() {
down.push_str(&build_fk_drop_stmts(schema));
}
let up_param = if !up.trim().is_empty() {
"manager"
} else {
"_manager"
};
let down_param = if !down.trim().is_empty() {
"manager"
} else {
"_manager"
};
format!(
"use sea_orm_migration::prelude::*;\n\n\
#[derive(DeriveMigrationName)]\n\
pub struct Migration;\n\n\
#[async_trait::async_trait]\n\
impl MigrationTrait for Migration {{\n\
async fn up(&self, {up_param}: &SchemaManager) -> Result<(), DbErr> {{\n\
{up} Ok(())\n\
}}\n\n\
async fn down(&self, {down_param}: &SchemaManager) -> Result<(), DbErr> {{\n\
{down} Ok(())\n\
}}\n\
}}\n",
up = up,
down = down,
)
}
fn build_enum_type_stmts(schema: &ParsedSchema, db_kind: &DbKind) -> String {
if *db_kind != DbKind::Postgres {
return String::new();
}
let mut out = String::new();
for col in &schema.columns {
if col.enum_string_values.is_empty() {
continue;
}
let name = col.enum_name.as_deref().unwrap_or(&col.name);
let variants: Vec<String> = col
.enum_string_values
.iter()
.map(|v| format!("'{}'", v)) // ← single quotes
.collect();
out.push_str(&format!(
" manager.get_connection().execute_unprepared(\n \"DO $$ BEGIN CREATE TYPE {name} AS ENUM ({variants}); EXCEPTION WHEN duplicate_object THEN NULL; END $$\"\n ).await?;\n\n",
name = name,
variants = variants.join(", "),
));
}
out
}
fn build_enum_type_drops(schema: &ParsedSchema, db_kind: &DbKind) -> String {
if *db_kind != DbKind::Postgres {
return String::new();
}
let mut out = String::new();
for col in &schema.columns {
if col.enum_string_values.is_empty() {
continue;
}
let name = col.enum_name.as_deref().unwrap_or(&col.name);
out.push_str(&format!(
" manager.get_connection().execute_unprepared(\n \"DROP TYPE IF EXISTS {name}\"\n ).await?;\n\n",
name = name,
));
}
out
}
fn build_enum_create_stmts_for_cols(cols: &[ParsedColumn], db_kind: &DbKind) -> String {
if *db_kind != DbKind::Postgres {
return String::new();
}
let mut out = String::new();
for col in cols {
if col.enum_string_values.is_empty() {
continue;
}
let name = col.enum_name.as_deref().unwrap_or(&col.name);
let variants: Vec<String> = col
.enum_string_values
.iter()
.map(|v| format!("'{}'", v))
.collect();
out.push_str(&format!(
" manager.get_connection().execute_unprepared(\n \"DO $$ BEGIN CREATE TYPE {name} AS ENUM ({variants}); EXCEPTION WHEN duplicate_object THEN NULL; END $$\"\n ).await?;\n\n",
name = name,
variants = variants.join(", "),
));
}
out
}
fn build_enum_drop_stmts_for_cols(cols: &[ParsedColumn], db_kind: &DbKind) -> String {
if *db_kind != DbKind::Postgres {
return String::new();
}
let mut out = String::new();
for col in cols {
if col.enum_string_values.is_empty() {
continue;
}
let name = col.enum_name.as_deref().unwrap_or(&col.name);
out.push_str(&format!(
" manager.get_connection().execute_unprepared(\n \"DROP TYPE IF EXISTS {name}\"\n ).await?;\n\n",
name = name,
));
}
out
}
pub fn generate_alter_file(change: &Changes, db_kind: &DbKind) -> String {
let (up_body, down_body) = build_alter_bodies(change);
let enum_creates = build_enum_create_stmts_for_cols(&change.added_columns, db_kind);
let enum_drops = build_enum_drop_stmts_for_cols(&change.added_columns, db_kind);
let full_up = format!("{}{}", enum_creates, up_body);
let full_down = format!("{}{}", down_body, enum_drops);
let up_param = if !full_up.trim().is_empty() {
"manager"
} else {
"_manager"
};
let down_param = if !full_down.trim().is_empty() {
"manager"
} else {
"_manager"
};
format!(
"use sea_orm_migration::prelude::*;\n\n#[derive(DeriveMigrationName)]\npub struct Migration;\n\n#[async_trait::async_trait]\nimpl MigrationTrait for Migration {{\n async fn up(&self, {up_param}: &SchemaManager) -> Result<(), DbErr> {{\n{up}\n Ok(())\n }}\n\n async fn down(&self, {down_param}: &SchemaManager) -> Result<(), DbErr> {{\n{down}\n Ok(())\n }}\n}}\n",
up_param = up_param,
down_param = down_param,
up = full_up.trim_end(),
down = full_down.trim_end()
)
}
pub fn generate_batch_up_file(changes: &[&Changes], timestamp: &str) -> String {
let mut body = String::new();
for change in changes {
append_up_ops(change, &mut body);
}
format!(
"// Batch up - auto-generated by runique\n// Timestamp: {0}\n// Tables: {1}\nuse sea_orm_migration::prelude::*;\n\n#[derive(DeriveMigrationName)]\npub struct Migration;\n\n#[async_trait::async_trait]\nimpl MigrationTrait for Migration {{\n async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {{\n{2}\n Ok(())\n }}\n\n async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {{\n Ok(())\n }}\n}}\n",
timestamp,
changes
.iter()
.map(|c| c.table_name.as_str())
.collect::<Vec<_>>()
.join(", "),
body.trim_end()
)
}
pub fn generate_batch_down_file(changes: &[&Changes], timestamp: &str) -> String {
let mut body = String::new();
for change in changes {
append_down_ops(change, &mut body);
}
format!(
"// Batch down - auto-generated by runique\n// Timestamp: {0}\n// Tables: {1}\nuse sea_orm_migration::prelude::*;\n\n#[derive(DeriveMigrationName)]\npub struct Migration;\n\n#[async_trait::async_trait]\nimpl MigrationTrait for Migration {{\n async fn up(&self, _manager: &SchemaManager) -> Result<(), DbErr> {{\n Ok(())\n }}\n\n async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {{\n{2}\n Ok(())\n }}\n}}\n",
timestamp,
changes
.iter()
.map(|c| c.table_name.as_str())
.collect::<Vec<_>>()
.join(", "),
body.trim_end()
)
}
fn build_create_table_cols(schema: &ParsedSchema, db_kind: &DbKind) -> String {
let mut cols = String::new();
if let Some(ref pk) = schema.primary_key {
cols.push_str(&format!("{}\n", render_pk_col(pk)));
}
for col in schema.columns.iter().filter(|c| !c.ignored) {
cols.push_str(&format!(
" .col({})\n",
render_column_def(col, db_kind)
));
}
cols
}
fn build_fk_create_stmts(schema: &ParsedSchema) -> String {
let mut out = String::new();
for fk in &schema.foreign_keys {
out.push_str(&format!(
" manager\n .create_foreign_key(\n ForeignKey::create()\n .name(\"{table}_{from}_{to_table}_fkey\")\n .from(Alias::new(\"{table}\"), Alias::new(\"{from}\"))\n .to(Alias::new(\"{to_table}\"), Alias::new(\"{to_col}\"))\n .on_delete(ForeignKeyAction::{on_delete})\n .on_update(ForeignKeyAction::{on_update})\n .to_owned(),\n )\n .await?;\n\n",
table = schema.table_name,
from = fk.from_column,
to_table = fk.to_table,
to_col = fk.to_column,
on_delete = fk.on_delete,
on_update = fk.on_update
));
}
out
}
fn build_index_create_stmts(schema: &ParsedSchema) -> String {
let mut out = String::new();
for idx in &schema.indexes {
out.push_str(&render_create_index_stmt(
&schema.table_name,
&idx.name,
&idx.columns,
idx.unique,
));
out.push('\n');
}
out
}
fn build_fk_drop_stmts(schema: &ParsedSchema) -> String {
let mut out = String::new();
for fk in &schema.foreign_keys {
out.push_str(&format!(
" manager\n .drop_foreign_key(\n ForeignKey::drop()\n .table(Alias::new(\"{table}\"))\n .name(\"{table}_{from}_{to}_fkey\")\n .to_owned(),\n )\n .await?;\n\n",
table = schema.table_name,
from = fk.from_column,
to = fk.to_table
));
}
out
}
fn build_index_drop_stmts(schema: &ParsedSchema) -> String {
let mut out = String::new();
for idx in &schema.indexes {
out.push_str(&format!(
" manager\n .drop_index(Index::drop().name(\"{idx}\").table(Alias::new(\"{table}\")).to_owned())\n .await?;\n\n",
idx = idx.name,
table = schema.table_name
));
}
out
}
fn build_alter_bodies(change: &Changes) -> (String, String) {
let mut up = String::new();
let mut down = String::new();
// 1) DROP indexes (up) / DROP added indexes (down)
for idx in &change.dropped_indexes {
push_drop_index(&mut up, &change.table_name, &idx.name);
}
for idx in &change.added_indexes {
push_drop_index(&mut down, &change.table_name, &idx.name);
}
// 2) DROP FK
for fk in &change.dropped_fks {
push_drop_fk(&mut up, &change.table_name, &fk.from_column, &fk.to_table);
}
for fk in &change.added_fks {
push_drop_fk(&mut down, &change.table_name, &fk.from_column, &fk.to_table);
}
// 3) DROP columns
for col in &change.dropped_columns {
push_drop_column(&mut up, &change.table_name, &col.name);
}
for col in &change.added_columns {
push_drop_column(&mut down, &change.table_name, &col.name);
}
// 4) MODIFY columns
for (old, new) in &change.modified_columns {
// type change => manual
if old.col_type != new.col_type {
up.push_str(&format!(
" // WARNING: type change on column '{col}': {old} -> {new}\n // Manual migration required.\n\n",
col = new.name,
old = old.col_type,
new = new.col_type
));
continue;
}
// nullable -> not_null => destructive unless you backfill
if old.nullable && !new.nullable {
// Generates modify_column anyway (risky if NULLs exist)
push_modify_column(
&mut up,
&change.table_name,
&new.name,
&new.col_type,
new.nullable,
new.unique,
);
push_modify_column(
&mut down,
&change.table_name,
&old.name,
&old.col_type,
old.nullable,
old.unique,
);
continue;
}
// safe modify
push_modify_column(
&mut up,
&change.table_name,
&new.name,
&new.col_type,
new.nullable,
new.unique,
);
push_modify_column(
&mut down,
&change.table_name,
&old.name,
&old.col_type,
old.nullable,
old.unique,
);
}
// 5) ADD columns
for col in &change.added_columns {
push_add_column(&mut up, &change.table_name, col);
}
// 6) Recreate dropped columns in DOWN (now correct because we store ParsedColumn)
for col in &change.dropped_columns {
push_add_column(&mut down, &change.table_name, col);
}
// 7) ADD FK
for fk in &change.added_fks {
push_create_fk(
&mut up,
&change.table_name,
&fk.from_column,
&fk.to_table,
&fk.to_column,
&fk.on_delete,
&fk.on_update,
);
}
for fk in &change.dropped_fks {
push_create_fk(
&mut down,
&change.table_name,
&fk.from_column,
&fk.to_table,
&fk.to_column,
&fk.on_delete,
&fk.on_update,
);
}
// 8) ADD indexes
for idx in &change.added_indexes {
push_create_index(
&mut up,
&change.table_name,
&idx.name,
&idx.columns,
idx.unique,
);
}
for idx in &change.dropped_indexes {
push_create_index(
&mut down,
&change.table_name,
&idx.name,
&idx.columns,
idx.unique,
);
}
// 9) Enum renames (data migration)
for (col, old_val, new_val) in &change.enum_renames {
up.push_str(&format!(
" manager.get_connection().execute_unprepared(\n \"UPDATE {table} SET {col} = '{new}' WHERE {col} = '{old}'\"\n ).await?;\n\n",
table = change.table_name, col = col, old = old_val, new = new_val,
));
// down: inverse
down.push_str(&format!(
" manager.get_connection().execute_unprepared(\n \"UPDATE {table} SET {col} = '{old}' WHERE {col} = '{new}'\"\n ).await?;\n\n",
table = change.table_name, col = col, old = old_val, new = new_val,
));
}
// 10) Enum value additions/removals — manual migration required
for (_col, enum_name, val) in &change.enum_value_adds {
up.push_str(&format!(
" manager.get_connection().execute_unprepared(\n \"ALTER TYPE {enum_name} ADD VALUE IF NOT EXISTS '{val}'\"\n ).await?;\n\n",
enum_name = enum_name, val = val,
));
}
for (_col, enum_name, val) in &change.enum_value_drops {
up.push_str(&format!(
" // WARNING: value '{val}' removed from {enum_name} — manual migration required.\n\n",
val = val, enum_name = enum_name,
));
down.push_str(&format!(
" manager.get_connection().execute_unprepared(\n \"ALTER TYPE {enum_name} ADD VALUE IF NOT EXISTS '{val}'\"\n ).await?;\n\n",
enum_name = enum_name, val = val,
));
}
(up, down)
}
fn append_up_ops(change: &Changes, buf: &mut String) {
for idx in &change.dropped_indexes {
push_drop_index(buf, &change.table_name, &idx.name);
}
for fk in &change.dropped_fks {
push_drop_fk(buf, &change.table_name, &fk.from_column, &fk.to_table);
}
for col in &change.dropped_columns {
push_drop_column(buf, &change.table_name, &col.name);
}
for col in &change.added_columns {
push_add_column(buf, &change.table_name, col);
}
for fk in &change.added_fks {
push_create_fk(
buf,
&change.table_name,
&fk.from_column,
&fk.to_table,
&fk.to_column,
&fk.on_delete,
&fk.on_update,
);
}
for idx in &change.added_indexes {
push_create_index(buf, &change.table_name, &idx.name, &idx.columns, idx.unique);
}
}
fn append_down_ops(change: &Changes, buf: &mut String) {
for idx in &change.added_indexes {
push_drop_index(buf, &change.table_name, &idx.name);
}
for fk in &change.added_fks {
push_drop_fk(buf, &change.table_name, &fk.from_column, &fk.to_table);
}
for col in &change.added_columns {
push_drop_column(buf, &change.table_name, &col.name);
}
// recreate dropped columns
for col in &change.dropped_columns {
push_add_column(buf, &change.table_name, col);
}
for fk in &change.dropped_fks {
push_create_fk(
buf,
&change.table_name,
&fk.from_column,
&fk.to_table,
&fk.to_column,
&fk.on_delete,
&fk.on_update,
);
}
for idx in &change.dropped_indexes {
push_create_index(buf, &change.table_name, &idx.name, &idx.columns, idx.unique);
}
}
fn render_pk_col(pk: &ParsedColumn) -> String {
let ty = col_type_to_method(&pk.col_type);
// auto_increment only makes sense for integer-ish PK.
let autoinc_ok = matches!(
pk.col_type.as_str(),
"Integer" | "BigInteger" | "SmallInteger" | "TinyInteger" | "Unsigned" | "BigUnsigned"
);
let mut s = format!(
" .col(ColumnDef::new(Alias::new(\"{name}\")).{ty}.not_null()",
name = pk.name,
ty = ty
);
if autoinc_ok {
s.push_str(".auto_increment()");
}
s.push_str(".primary_key())");
s
}
fn render_column_def(col: &ParsedColumn, db_kind: &DbKind) -> String {
let null = if col.nullable {
".null()"
} else {
".not_null()"
};
let uniq = if col.unique { ".unique_key()" } else { "" };
let default = if col.has_default_now {
".default(Expr::current_timestamp())"
} else {
""
};
let on_update = if col.updated_at && *db_kind == DbKind::Mysql {
".extra(\"ON UPDATE CURRENT_TIMESTAMP\")"
} else {
""
};
if !col.enum_string_values.is_empty() {
let name = col.enum_name.as_deref().unwrap_or(&col.name);
let variants: Vec<String> = col
.enum_string_values
.iter()
.map(|v| format!("Alias::new(\"{}\").into_iden()", v))
.collect();
format!(
"ColumnDef::new_with_type(Alias::new(\"{name}\"), ColumnType::Enum {{ name: Alias::new(\"{enum_name}\").into_iden(), variants: vec![{variants}] }}){null}{uniq}",
name = col.name,
enum_name = name,
variants = variants.join(", "),
null = null,
uniq = uniq,
)
} else {
let ty = col_type_to_method(&col.col_type);
format!(
"ColumnDef::new(Alias::new(\"{name}\")).{ty}{null}{uniq}{default}{on_update}",
name = col.name,
ty = ty,
null = null,
uniq = uniq,
default = default,
on_update = on_update,
)
}
}
/// Generates PostgreSQL triggers for `updated_at` columns.
/// For MySQL, handling is inline via `.extra("ON UPDATE CURRENT_TIMESTAMP")`.
fn build_updated_at_trigger_stmts(schema: &ParsedSchema, db_kind: &DbKind) -> String {
if *db_kind != DbKind::Postgres {
return String::new();
}
let updated_at_cols: Vec<_> = schema.columns.iter().filter(|c| c.updated_at).collect();
if updated_at_cols.is_empty() {
return String::new();
}
let table = &schema.table_name;
let fn_name = format!("set_updated_at_{}", table);
let trigger_name = format!("trg_{}_updated_at", table);
format!(
" manager.get_connection().execute_unprepared(\n \"CREATE OR REPLACE FUNCTION {fn_name}() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql;\"\n ).await?;\n manager.get_connection().execute_unprepared(\n \"CREATE TRIGGER {trigger_name} BEFORE UPDATE ON {table} FOR EACH ROW EXECUTE PROCEDURE {fn_name}();\"\n ).await?;\n\n",
fn_name = fn_name,
trigger_name = trigger_name,
table = table,
)
}
/// Drops PostgreSQL `updated_at` triggers in the `down` block.
fn build_updated_at_trigger_drops(schema: &ParsedSchema, db_kind: &DbKind) -> String {
if *db_kind != DbKind::Postgres {
return String::new();
}
let has_updated_at = schema.columns.iter().any(|c| c.updated_at);
if !has_updated_at {
return String::new();
}
let table = &schema.table_name;
let fn_name = format!("set_updated_at_{}", table);
let trigger_name = format!("trg_{}_updated_at", table);
format!(
" manager.get_connection().execute_unprepared(\n \"DROP TRIGGER IF EXISTS {trigger_name} ON {table};\"\n ).await?;\n manager.get_connection().execute_unprepared(\n \"DROP FUNCTION IF EXISTS {fn_name}();\"\n ).await?;\n\n",
trigger_name = trigger_name,
table = table,
fn_name = fn_name,
)
}
fn push_drop_index(buf: &mut String, table: &str, idx_name: &str) {
buf.push_str(&format!(
" manager\n .drop_index(Index::drop().name(\"{idx}\").table(Alias::new(\"{table}\")).to_owned())\n .await?;\n\n",
idx = idx_name,
table = table
));
}
fn push_drop_fk(buf: &mut String, table: &str, from_col: &str, to_table: &str) {
buf.push_str(&format!(
" manager\n .drop_foreign_key(\n ForeignKey::drop()\n .table(Alias::new(\"{table}\"))\n .name(\"{table}_{from}_{to}_fkey\")\n .to_owned(),\n )\n .await?;\n\n",
table = table,
from = from_col,
to = to_table
));
}
fn push_drop_column(buf: &mut String, table: &str, col: &str) {
buf.push_str(&format!(
" manager\n .alter_table(\n Table::alter()\n .table(Alias::new(\"{table}\"))\n .drop_column(Alias::new(\"{col}\"))\n .to_owned(),\n )\n .await?;\n\n",
table = table,
col = col
));
}
fn push_modify_column(
buf: &mut String,
table: &str,
col: &str,
col_type: &str,
nullable: bool,
unique: bool,
) {
let null = if nullable { ".null()" } else { ".not_null()" };
let uniq = if unique { ".unique_key()" } else { "" };
buf.push_str(&format!(
" manager\n .alter_table(\n Table::alter()\n .table(Alias::new(\"{table}\"))\n .modify_column(ColumnDef::new(Alias::new(\"{col}\")).{ty}{null}{uniq})\n .to_owned(),\n )\n .await?;\n\n",
table = table,
col = col,
ty = col_type_to_method(col_type),
null = null,
uniq = uniq
));
}
fn push_add_column(buf: &mut String, table: &str, col: &ParsedColumn) {
buf.push_str(&format!(
" manager\n .alter_table(\n Table::alter()\n .table(Alias::new(\"{table}\"))\n .add_column({coldef})\n .to_owned(),\n )\n .await?;\n\n",
table = table,
coldef = render_column_def(col, &DbKind::Other),
));
}
fn push_create_fk(
buf: &mut String,
table: &str,
from_col: &str,
to_table: &str,
to_col: &str,
on_delete: &str,
on_update: &str,
) {
buf.push_str(&format!(
" manager\n .create_foreign_key(\n ForeignKey::create()\n .name(\"{table}_{from}_{to_table}_fkey\")\n .from(Alias::new(\"{table}\"), Alias::new(\"{from}\"))\n .to(Alias::new(\"{to_table}\"), Alias::new(\"{to_col}\"))\n .on_delete(ForeignKeyAction::{on_delete})\n .on_update(ForeignKeyAction::{on_update})\n .to_owned(),\n )\n .await?;\n\n",
table = table,
from = from_col,
to_table = to_table,
to_col = to_col,
on_delete = on_delete,
on_update = on_update
));
}
fn push_create_index(
buf: &mut String,
table: &str,
idx_name: &str,
columns: &[String],
unique: bool,
) {
buf.push_str(&render_create_index_stmt(table, idx_name, columns, unique));
buf.push('\n');
}
fn render_create_index_stmt(
table: &str,
idx_name: &str,
columns: &[String],
unique: bool,
) -> String {
let mut cols_chain = String::new();
for c in columns {
cols_chain.push_str(&format!(
" .col(Alias::new(\"{c}\"))\n",
c = c
));
}
let uniq_line = if unique {
" .unique()\n"
} else {
""
};
format!(
" manager\n .create_index(\n Index::create()\n .name(\"{idx}\")\n .table(Alias::new(\"{table}\"))\n{cols}{uniq} .to_owned(),\n )\n .await?;\n",
idx = idx_name,
table = table,
cols = cols_chain,
uniq = uniq_line
)
}