refactoring Schema to expose functions for database updates (#1256)

* extracting get_column_def from create_table_from_entity to make it available for database upgrade processes.

* Align code example formatting

* Converting the foreign key related code from create_table_from_entity into From<RelationDef> implementations to make its usage easier in different context, like updating a database.

* Refactor

* Fixup

Co-authored-by: Billy Chan <ccw.billy.123@gmail.com>
This commit is contained in:
mohs8421 2022-12-19 10:24:11 +01:00 committed by GitHub
parent 17ed7156c4
commit 1f27837f49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 187 additions and 84 deletions

View File

@ -1,6 +1,9 @@
use crate::{EntityTrait, Identity, IdentityOf, Iterable, QuerySelect, Select};
use crate::{unpack_table_ref, EntityTrait, Identity, IdentityOf, Iterable, QuerySelect, Select};
use core::marker::PhantomData;
use sea_query::{Alias, Condition, DynIden, JoinType, SeaRc, TableRef};
use sea_query::{
Alias, Condition, DynIden, ForeignKeyCreateStatement, JoinType, SeaRc, TableForeignKey,
TableRef,
};
use std::fmt::Debug;
/// Defines the type of relationship
@ -309,6 +312,98 @@ where
}
}
macro_rules! set_foreign_key_stmt {
( $relation: ident, $foreign_key: ident ) => {
let from_cols: Vec<String> = match $relation.from_col {
Identity::Unary(o1) => vec![o1],
Identity::Binary(o1, o2) => vec![o1, o2],
Identity::Ternary(o1, o2, o3) => vec![o1, o2, o3],
}
.into_iter()
.map(|col| {
let col_name = col.to_string();
$foreign_key.from_col(col);
col_name
})
.collect();
match $relation.to_col {
Identity::Unary(o1) => {
$foreign_key.to_col(o1);
}
Identity::Binary(o1, o2) => {
$foreign_key.to_col(o1);
$foreign_key.to_col(o2);
}
Identity::Ternary(o1, o2, o3) => {
$foreign_key.to_col(o1);
$foreign_key.to_col(o2);
$foreign_key.to_col(o3);
}
}
if let Some(action) = $relation.on_delete {
$foreign_key.on_delete(action);
}
if let Some(action) = $relation.on_update {
$foreign_key.on_update(action);
}
let name = if let Some(name) = $relation.fk_name {
name
} else {
let from_tbl = unpack_table_ref(&$relation.from_tbl);
format!("fk-{}-{}", from_tbl.to_string(), from_cols.join("-"))
};
$foreign_key.name(&name);
};
}
impl From<RelationDef> for ForeignKeyCreateStatement {
fn from(relation: RelationDef) -> Self {
let mut foreign_key_stmt = Self::new();
set_foreign_key_stmt!(relation, foreign_key_stmt);
foreign_key_stmt
.from_tbl(unpack_table_ref(&relation.from_tbl))
.to_tbl(unpack_table_ref(&relation.to_tbl))
.take()
}
}
/// Creates a column definition for example to update a table.
/// ```
/// use sea_query::{Alias, IntoIden, MysqlQueryBuilder, TableAlterStatement, TableRef};
/// use sea_orm::{EnumIter, Iden, Identity, PrimaryKeyTrait, RelationDef, RelationTrait, RelationType};
///
/// let relation = RelationDef {
/// rel_type: RelationType::HasOne,
/// from_tbl: TableRef::Table(Alias::new("foo").into_iden()),
/// to_tbl: TableRef::Table(Alias::new("bar").into_iden()),
/// from_col: Identity::Unary(Alias::new("bar_id").into_iden()),
/// to_col: Identity::Unary(Alias::new("bar_id").into_iden()),
/// is_owner: false,
/// on_delete: None,
/// on_update: None,
/// on_condition: None,
/// fk_name: Some("foo-bar".to_string()),
/// };
///
/// let mut alter_table = TableAlterStatement::new()
/// .table(TableRef::Table(Alias::new("foo").into_iden()))
/// .add_foreign_key(&mut relation.into()).take();
/// assert_eq!(
/// alter_table.to_string(MysqlQueryBuilder::default()),
/// "ALTER TABLE `foo` ADD CONSTRAINT `foo-bar` FOREIGN KEY (`bar_id`) REFERENCES `bar` (`bar_id`)"
/// );
/// ```
impl From<RelationDef> for TableForeignKey {
fn from(relation: RelationDef) -> Self {
let mut foreign_key = Self::new();
set_foreign_key_stmt!(relation, foreign_key);
foreign_key
.from_tbl(unpack_table_ref(&relation.from_tbl))
.to_tbl(unpack_table_ref(&relation.to_tbl))
.take()
}
}
#[cfg(test)]
mod tests {
use crate::{

View File

@ -1,10 +1,10 @@
use crate::{
unpack_table_ref, ActiveEnum, ColumnTrait, ColumnType, DbBackend, EntityTrait, Identity,
Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, RelationTrait, Schema,
ActiveEnum, ColumnTrait, ColumnType, DbBackend, EntityTrait, Iterable, PrimaryKeyToColumn,
PrimaryKeyTrait, RelationTrait, Schema,
};
use sea_query::{
extension::postgres::{Type, TypeCreateStatement},
ColumnDef, ForeignKeyCreateStatement, Iden, Index, IndexCreateStatement, TableCreateStatement,
ColumnDef, Iden, Index, IndexCreateStatement, TableCreateStatement,
};
impl Schema {
@ -40,6 +40,51 @@ impl Schema {
{
create_index_from_entity(entity, self.backend)
}
/// Creates a column definition for example to update a table.
/// ```
/// use crate::sea_orm::IdenStatic;
/// use sea_orm::{
/// ActiveModelBehavior, ColumnDef, ColumnTrait, ColumnType, DbBackend, EntityName,
/// EntityTrait, EnumIter, PrimaryKeyTrait, RelationDef, RelationTrait, Schema,
/// };
/// use sea_orm_macros::{DeriveEntityModel, DerivePrimaryKey};
/// use sea_query::{MysqlQueryBuilder, TableAlterStatement};
///
/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
/// #[sea_orm(table_name = "posts")]
/// pub struct Model {
/// #[sea_orm(primary_key)]
/// pub id: u32,
/// pub title: String,
/// }
///
/// #[derive(Copy, Clone, Debug, EnumIter)]
/// pub enum Relation {}
/// impl RelationTrait for Relation {
/// fn def(&self) -> RelationDef {
/// panic!("No RelationDef")
/// }
/// }
/// impl ActiveModelBehavior for ActiveModel {}
///
/// let schema = Schema::new(DbBackend::MySql);
///
/// let mut alter_table = TableAlterStatement::new()
/// .table(Entity)
/// .add_column(&mut schema.get_column_def::<Entity>(Column::Title))
/// .take();
/// assert_eq!(
/// alter_table.to_string(MysqlQueryBuilder::default()),
/// "ALTER TABLE `posts` ADD COLUMN `title` varchar(255) NOT NULL"
/// );
/// ```
pub fn get_column_def<E>(&self, column: E::Column) -> ColumnDef
where
E: EntityTrait,
{
column_def_from_entity_column::<E>(column, self.backend)
}
}
pub(crate) fn create_enum_from_active_enum<A>(backend: DbBackend) -> TypeCreateStatement
@ -117,6 +162,33 @@ where
let mut stmt = TableCreateStatement::new();
for column in E::Column::iter() {
let mut column_def = column_def_from_entity_column::<E>(column, backend);
stmt.col(&mut column_def);
}
if E::PrimaryKey::iter().count() > 1 {
let mut idx_pk = Index::create();
for primary_key in E::PrimaryKey::iter() {
idx_pk.col(primary_key);
}
stmt.primary_key(idx_pk.name(&format!("pk-{}", entity.to_string())).primary());
}
for relation in E::Relation::iter() {
let relation = relation.def();
if relation.is_owner {
continue;
}
stmt.foreign_key(&mut relation.into());
}
stmt.table(entity.table_ref()).take()
}
fn column_def_from_entity_column<E>(column: E::Column, backend: DbBackend) -> ColumnDef
where
E: EntityTrait,
{
let orm_column_def = column.def();
let types = match orm_column_def.col_type {
ColumnType::Enum { name, variants } => match backend {
@ -150,71 +222,7 @@ where
}
}
}
stmt.col(&mut column_def);
}
if E::PrimaryKey::iter().count() > 1 {
let mut idx_pk = Index::create();
for primary_key in E::PrimaryKey::iter() {
idx_pk.col(primary_key);
}
stmt.primary_key(idx_pk.name(&format!("pk-{}", entity.to_string())).primary());
}
for relation in E::Relation::iter() {
let relation = relation.def();
if relation.is_owner {
continue;
}
let mut foreign_key_stmt = ForeignKeyCreateStatement::new();
let from_tbl = unpack_table_ref(&relation.from_tbl);
let to_tbl = unpack_table_ref(&relation.to_tbl);
let from_cols: Vec<String> = match relation.from_col {
Identity::Unary(o1) => vec![o1],
Identity::Binary(o1, o2) => vec![o1, o2],
Identity::Ternary(o1, o2, o3) => vec![o1, o2, o3],
}
.into_iter()
.map(|col| {
let col_name = col.to_string();
foreign_key_stmt.from_col(col);
col_name
})
.collect();
match relation.to_col {
Identity::Unary(o1) => {
foreign_key_stmt.to_col(o1);
}
Identity::Binary(o1, o2) => {
foreign_key_stmt.to_col(o1);
foreign_key_stmt.to_col(o2);
}
Identity::Ternary(o1, o2, o3) => {
foreign_key_stmt.to_col(o1);
foreign_key_stmt.to_col(o2);
foreign_key_stmt.to_col(o3);
}
}
if let Some(action) = relation.on_delete {
foreign_key_stmt.on_delete(action);
}
if let Some(action) = relation.on_update {
foreign_key_stmt.on_update(action);
}
let name = if let Some(name) = relation.fk_name {
name
} else {
format!("fk-{}-{}", from_tbl.to_string(), from_cols.join("-"))
};
stmt.foreign_key(
foreign_key_stmt
.name(&name)
.from_tbl(from_tbl)
.to_tbl(to_tbl),
);
}
stmt.table(entity.table_ref()).take()
column_def
}
#[cfg(test)]