use crate::{ColumnType, Dialect, Entity, RefAction};
use sea_query::{
Alias, ColumnDef, ForeignKey, ForeignKeyAction, ForeignKeyCreateStatement, Index, Table,
TableCreateStatement,
};
fn effective_type(col: &crate::column::ColumnSpec) -> ColumnType {
if !col.auto_increment {
return col.ty;
}
match col.ty {
ColumnType::BigUint => ColumnType::BigInt,
ColumnType::Uint => ColumnType::Int,
other => other,
}
}
pub fn create_table_stmt<E: Entity>() -> TableCreateStatement {
let mut stmt = Table::create();
stmt.table(Alias::new(E::table())).if_not_exists();
for col in E::columns() {
let keyed =
col.pk || col.unique || E::indexes().iter().any(|ix| ix.columns.contains(&col.name));
let mut def = ColumnDef::new(Alias::new(col.name));
apply_type(&mut def, effective_type(col), keyed);
if col.pk {
def.primary_key();
if col.auto_increment {
def.auto_increment();
}
}
if col.nullable {
def.null();
} else {
def.not_null();
}
if col.unique {
def.unique_key();
}
stmt.col(&mut def);
}
for fk in E::foreign_keys() {
stmt.foreign_key(&mut foreign_key_stmt::<E>(fk));
}
stmt
}
pub fn create_table_ddl<E: Entity>(dialect: Dialect) -> String {
dialect.build_schema(&create_table_stmt::<E>())
}
pub fn create_index_ddls<E: Entity>(dialect: Dialect) -> Vec<String> {
E::indexes()
.iter()
.map(|ix| {
let mut stmt = Index::create();
stmt.name(ix.name).table(Alias::new(E::table())).if_not_exists();
if ix.unique {
stmt.unique();
}
for c in ix.columns {
stmt.col(Alias::new(*c));
}
dialect.build_schema(&stmt)
})
.collect()
}
pub fn schema_ddl<E: Entity>(dialect: Dialect) -> Vec<String> {
let mut out = Vec::with_capacity(1 + E::indexes().len());
out.push(create_table_ddl::<E>(dialect));
out.extend(create_index_ddls::<E>(dialect));
out
}
fn foreign_key_stmt<E: Entity>(fk: &crate::ForeignKeySpec) -> ForeignKeyCreateStatement {
let mut stmt = ForeignKey::create();
stmt.name(fk.name).from_tbl(Alias::new(E::table()));
for c in fk.columns {
stmt.from_col(Alias::new(*c));
}
stmt.to_tbl(Alias::new(fk.foreign_table));
for c in fk.foreign_columns {
stmt.to_col(Alias::new(*c));
}
if let Some(a) = fk.on_delete {
stmt.on_delete(map_action(a));
}
if let Some(a) = fk.on_update {
stmt.on_update(map_action(a));
}
stmt
}
fn map_action(a: RefAction) -> ForeignKeyAction {
match a {
RefAction::Cascade => ForeignKeyAction::Cascade,
RefAction::Restrict => ForeignKeyAction::Restrict,
RefAction::SetNull => ForeignKeyAction::SetNull,
RefAction::SetDefault => ForeignKeyAction::SetDefault,
RefAction::NoAction => ForeignKeyAction::NoAction,
}
}
pub fn apply_type(def: &mut ColumnDef, ty: ColumnType, keyed: bool) {
const KEYED_TEXT_LEN: u32 = 255;
match ty {
ColumnType::Bool => def.boolean(),
ColumnType::Int => def.integer(),
ColumnType::BigInt => def.big_integer(),
ColumnType::Uint => def.unsigned(),
ColumnType::BigUint => def.big_unsigned(),
ColumnType::Double => def.double(),
ColumnType::Text if keyed => def.string_len(KEYED_TEXT_LEN),
ColumnType::Text => def.text(),
ColumnType::Binary => def.blob(),
ColumnType::Timestamp => def.timestamp(),
ColumnType::Date => def.date(),
};
}
#[cfg(test)]
mod auto_increment_tests {
use super::*;
#[derive(crate::Entity, Clone)]
#[orm(table = "widgets")]
struct Widget {
#[orm(pk, auto_increment)]
id: u64,
name: String,
}
#[derive(crate::Entity, Clone)]
#[orm(table = "counters")]
struct Counter {
#[orm(pk)]
id: u64,
hits: u64,
}
#[test]
fn an_auto_increment_u64_key_renders_on_every_dialect() {
for dialect in [Dialect::Sqlite, Dialect::MySql, Dialect::Postgres] {
let ddl = create_table_ddl::<Widget>(dialect);
assert!(ddl.contains("widgets"), "{dialect:?}: {ddl}");
}
}
#[test]
fn postgres_gets_a_sequence_backed_key() {
let ddl = create_table_ddl::<Widget>(Dialect::Postgres);
assert!(ddl.contains("serial"), "{ddl}");
}
#[test]
fn a_non_generated_unsigned_column_keeps_its_type() {
let ddl = create_table_ddl::<Counter>(Dialect::MySql);
assert!(ddl.to_lowercase().contains("unsigned"), "{ddl}");
}
}