Restructure sea-orm-cli & sea-orm-migration

This commit is contained in:
Chris Tsang 2022-05-09 12:14:48 +08:00
parent 498c0154ca
commit 75d5c0f5ea
22 changed files with 444 additions and 260 deletions

View File

@ -14,7 +14,6 @@ categories = [ "database" ]
keywords = ["async", "orm", "mysql", "postgres", "sqlite"] keywords = ["async", "orm", "mysql", "postgres", "sqlite"]
default-run = "sea-orm-cli" default-run = "sea-orm-cli"
[lib] [lib]
name = "sea_orm_cli" name = "sea_orm_cli"
path = "src/lib.rs" path = "src/lib.rs"
@ -22,26 +21,27 @@ path = "src/lib.rs"
[[bin]] [[bin]]
name = "sea-orm-cli" name = "sea-orm-cli"
path = "src/bin/main.rs" path = "src/bin/main.rs"
required-features = ["codegen"]
[[bin]] [[bin]]
name = "sea" name = "sea"
path = "src/bin/sea.rs" path = "src/bin/sea.rs"
required-features = ["codegen"]
[dependencies] [dependencies]
clap = { version = "^2.33.3" } clap = { version = "^2.33.3" }
dotenv = { version = "^0.15" } dotenv = { version = "^0.15" }
async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] } async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] }
sea-orm-codegen = { version = "^0.7.0", path = "../sea-orm-codegen" } sea-orm-codegen = { version = "^0.7.0", path = "../sea-orm-codegen", optional = true }
sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "dump-sea-orm-dep", default-features = false, features = [ sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "remove-migration", default-features = false, features = [
"debug-print", "debug-print",
"sqlx-mysql", "sqlx-mysql",
"sqlx-sqlite", "sqlx-sqlite",
"sqlx-postgres", "sqlx-postgres",
"discovery", "discovery",
"writer", "writer",
"migration", ], optional = true }
] } sqlx = { version = "^0.5", default-features = false, features = [ "mysql", "postgres" ], optional = true }
sqlx = { version = "^0.5", default-features = false, features = [ "mysql", "postgres" ] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = { version = "0.1" } tracing = { version = "0.1" }
url = "^2.2" url = "^2.2"
@ -50,7 +50,8 @@ url = "^2.2"
smol = "1.2.5" smol = "1.2.5"
[features] [features]
default = [ "runtime-async-std-native-tls" ] default = [ "codegen", "runtime-async-std-native-tls" ]
codegen = [ "sea-schema", "sea-orm-codegen" ]
runtime-actix-native-tls = [ runtime-actix-native-tls = [
"sqlx/runtime-actix-native-tls", "sqlx/runtime-actix-native-tls",
"sea-schema/runtime-actix-native-tls", "sea-schema/runtime-actix-native-tls",

View File

@ -1,5 +1,5 @@
use crate::migration::get_subcommands;
use clap::{App, AppSettings, Arg, SubCommand}; use clap::{App, AppSettings, Arg, SubCommand};
use sea_schema::get_cli_subcommands;
pub fn build_cli() -> App<'static, 'static> { pub fn build_cli() -> App<'static, 'static> {
let entity_subcommand = SubCommand::with_name("generate") let entity_subcommand = SubCommand::with_name("generate")
@ -96,7 +96,7 @@ pub fn build_cli() -> App<'static, 'static> {
.arg(arg_migration_dir.clone()), .arg(arg_migration_dir.clone()),
) )
.arg(arg_migration_dir.clone()); .arg(arg_migration_dir.clone());
for subcommand in get_cli_subcommands!() { for subcommand in get_subcommands() {
migrate_subcommands = migrate_subcommands =
migrate_subcommands.subcommand(subcommand.arg(arg_migration_dir.clone())); migrate_subcommands.subcommand(subcommand.arg(arg_migration_dir.clone()));
} }

View File

@ -1,5 +1,8 @@
pub mod cli; pub mod cli;
#[cfg(feature = "codegen")]
pub mod commands; pub mod commands;
pub mod migration;
pub use cli::*; pub use cli::*;
#[cfg(feature = "codegen")]
pub use commands::*; pub use commands::*;

View File

@ -0,0 +1,49 @@
use clap::{App, AppSettings, Arg, SubCommand};
pub fn build_cli() -> App<'static, 'static> {
let mut app = App::new("sea-schema-migration")
.version(env!("CARGO_PKG_VERSION"))
.setting(AppSettings::VersionlessSubcommands)
.arg(
Arg::with_name("VERBOSE")
.long("verbose")
.short("v")
.help("Show debug messages")
.takes_value(false)
.global(true),
);
for subcommand in get_subcommands() {
app = app.subcommand(subcommand);
}
app
}
pub fn get_subcommands() -> Vec<App<'static, 'static>> {
vec![
SubCommand::with_name("fresh")
.about("Drop all tables from the database, then reapply all migrations"),
SubCommand::with_name("refresh")
.about("Rollback all applied migrations, then reapply all migrations"),
SubCommand::with_name("reset").about("Rollback all applied migrations"),
SubCommand::with_name("status").about("Check the status of all migrations"),
SubCommand::with_name("up")
.about("Apply pending migrations")
.arg(
Arg::with_name("NUM_MIGRATION")
.long("num")
.short("n")
.help("Number of pending migrations to be applied")
.takes_value(true),
),
SubCommand::with_name("down")
.about("Rollback applied migrations")
.arg(
Arg::with_name("NUM_MIGRATION")
.long("num")
.short("n")
.help("Number of pending migrations to be rolled back")
.takes_value(true)
.default_value("1"),
),
]
}

View File

@ -15,7 +15,7 @@ name = "sea_orm_codegen"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
sea-query = { version = "0.24.0" } sea-query = { version = "^0.24.0" }
syn = { version = "^1", default-features = false, features = [ syn = { version = "^1", default-features = false, features = [
"derive", "derive",
"parsing", "parsing",

View File

@ -18,11 +18,16 @@ name = "sea_orm_migration"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
sea-orm = { path = "../", default-features = false, features = ["macros"] }
sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "dump-sea-orm-dep", features = ["migration"], default-features = false }
async-std = { version = "^1", features = ["attributes", "tokio1"] } async-std = { version = "^1", features = ["attributes", "tokio1"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = { version = "0.1", features = ["log"] }
async-trait = { version = "^0.1" } async-trait = { version = "^0.1" }
dotenv = { version = "^0.15" }
clap = { version = "^2.33" } clap = { version = "^2.33" }
dotenv = { version = "^0.15" }
sea-orm = { path = "../", default-features = false, features = ["macros"] }
sea-orm-cli = { path = "../sea-orm-cli", default-features = false }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[features]
sqlx-mysql = ["sea-orm/sqlx-mysql", "sea-orm/runtime-async-std-native-tls"]
sqlx-postgres = ["sea-orm/sqlx-postgres", "sea-orm/runtime-async-std-native-tls"]
sqlx-sqlite = ["sea-orm/sqlx-sqlite", "sea-orm/runtime-async-std-native-tls"]

View File

@ -1,15 +1,13 @@
//! Run migrator CLI use clap::App;
use crate::MigratorTrait;
use clap::{App, AppSettings, Arg};
use dotenv::dotenv; use dotenv::dotenv;
use sea_orm::{Database, DbConn};
use sea_schema::get_cli_subcommands;
use std::{fmt::Display, process::exit}; use std::{fmt::Display, process::exit};
use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_subscriber::{prelude::*, EnvFilter};
#[allow(dead_code)] use sea_orm::{Database, DbConn};
/// Migrator CLI application use sea_orm_cli::build_cli;
use super::MigratorTrait;
pub async fn run_cli<M>(migrator: M) pub async fn run_cli<M>(migrator: M)
where where
M: MigratorTrait, M: MigratorTrait,
@ -21,20 +19,20 @@ where
get_matches(migrator, db, app).await; get_matches(migrator, db, app).await;
} }
async fn get_matches<M>(_: M, db: &DbConn, app: App<'static, 'static>) pub async fn get_matches<M>(_: M, db: &DbConn, app: App<'static, 'static>)
where where
M: MigratorTrait, M: MigratorTrait,
{ {
let matches = app.get_matches(); let matches = app.get_matches();
let mut verbose = false; let mut verbose = false;
let filter = match matches.subcommand() { let filter = match matches.subcommand() {
(_, None) => "sea_orm::migration=info", (_, None) => "sea_schema::migration=info",
(_, Some(args)) => match args.is_present("VERBOSE") { (_, Some(args)) => match args.is_present("VERBOSE") {
true => { true => {
verbose = true; verbose = true;
"debug" "debug"
} }
false => "sea_orm::migration=info", false => "sea_schema::migration=info",
}, },
}; };
let filter_layer = EnvFilter::try_new(filter).unwrap(); let filter_layer = EnvFilter::try_new(filter).unwrap();
@ -76,24 +74,6 @@ where
.unwrap_or_else(handle_error); .unwrap_or_else(handle_error);
} }
fn build_cli() -> App<'static, 'static> {
let mut app = App::new("sea-schema-migration")
.version(env!("CARGO_PKG_VERSION"))
.setting(AppSettings::VersionlessSubcommands)
.arg(
Arg::with_name("VERBOSE")
.long("verbose")
.short("v")
.help("Show debug messages")
.takes_value(false)
.global(true),
);
for subcommand in get_cli_subcommands!() {
app = app.subcommand(subcommand);
}
app
}
fn handle_error<E>(error: E) fn handle_error<E>(error: E)
where where
E: Display, E: Display,

View File

@ -1,48 +0,0 @@
//! Manage migration connection
use crate::{into_migration_db_backend, into_migration_err, into_orm_stmt, QueryResult};
use sea_orm::ConnectionTrait;
use sea_schema::migration::{self, MigrationErr, QueryResultTrait};
#[derive(Debug)]
pub(crate) struct DatabaseConnection<'c> {
pub(crate) conn: &'c sea_orm::DatabaseConnection,
}
#[async_trait::async_trait]
impl migration::ConnectionTrait for DatabaseConnection<'_> {
fn get_database_backend(&self) -> migration::DbBackend {
into_migration_db_backend(ConnectionTrait::get_database_backend(self.conn))
}
async fn execute(&self, stmt: migration::Statement) -> Result<(), MigrationErr> {
ConnectionTrait::execute(self.conn, into_orm_stmt(stmt))
.await
.map(|_| ())
.map_err(|e| MigrationErr(e.to_string()))
}
async fn query_one(
&self,
stmt: migration::Statement,
) -> Result<Option<Box<dyn QueryResultTrait>>, MigrationErr> {
ConnectionTrait::query_one(self.conn, into_orm_stmt(stmt))
.await
.map(|res| res.map(|res| Box::new(QueryResult { res }) as Box<dyn QueryResultTrait>))
.map_err(into_migration_err)
}
async fn query_all(
&self,
stmt: migration::Statement,
) -> Result<Vec<Box<dyn QueryResultTrait>>, MigrationErr> {
ConnectionTrait::query_all(self.conn, into_orm_stmt(stmt))
.await
.map(|rows| {
rows.into_iter()
.map(|res| Box::new(QueryResult { res }) as Box<dyn QueryResultTrait>)
.collect()
})
.map_err(into_migration_err)
}
}

View File

@ -1,19 +0,0 @@
//! Convert database backend
use sea_schema::migration;
pub(crate) fn into_orm_db_backend(db_backend: migration::DbBackend) -> sea_orm::DbBackend {
match db_backend {
migration::DbBackend::MySql => sea_orm::DbBackend::MySql,
migration::DbBackend::Postgres => sea_orm::DbBackend::Postgres,
migration::DbBackend::Sqlite => sea_orm::DbBackend::Sqlite,
}
}
pub(crate) fn into_migration_db_backend(db_backend: sea_orm::DbBackend) -> migration::DbBackend {
match db_backend {
sea_orm::DbBackend::MySql => migration::DbBackend::MySql,
sea_orm::DbBackend::Postgres => migration::DbBackend::Postgres,
sea_orm::DbBackend::Sqlite => migration::DbBackend::Sqlite,
}
}

View File

@ -1,11 +0,0 @@
//! Convert migration error
use sea_schema::migration;
pub(crate) fn into_orm_db_err(err: migration::MigrationErr) -> sea_orm::DbErr {
sea_orm::DbErr::Migration(err.to_string())
}
pub(crate) fn into_migration_err(err: sea_orm::DbErr) -> migration::MigrationErr {
migration::MigrationErr(err.to_string())
}

View File

@ -1,31 +1,23 @@
pub mod cli; pub mod cli;
pub mod connection;
pub mod database;
pub mod error;
pub mod manager; pub mod manager;
pub mod migrator; pub mod migrator;
pub mod prelude; pub mod prelude;
pub mod query;
pub mod seaql_migrations; pub mod seaql_migrations;
pub mod statement;
pub use manager::*; pub use manager::*;
pub use migrator::*; pub use migrator::*;
use connection::*; pub use async_std;
use database::*; pub use async_trait;
use error::*; pub use sea_orm;
use query::*; pub use sea_orm::sea_query;
use statement::*; pub use sea_orm::DbErr;
use sea_orm::DbErr;
/// Define the name of a migration
pub trait MigrationName { pub trait MigrationName {
/// Get migration name
fn name(&self) -> &str; fn name(&self) -> &str;
} }
/// Define the actions of a migration /// The migration definition
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait MigrationTrait: MigrationName + Send + Sync { pub trait MigrationTrait: MigrationName + Send + Sync {
/// Define actions to perform when applying the migration /// Define actions to perform when applying the migration

View File

@ -1,163 +1,167 @@
//! Schema manager
use crate::{into_orm_db_err, DatabaseConnection};
use sea_orm::sea_query::{ use sea_orm::sea_query::{
extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement}, extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement},
ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement, Alias, Expr, ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement,
TableAlterStatement, TableCreateStatement, TableDropStatement, TableRenameStatement, IndexDropStatement, Query, TableAlterStatement, TableCreateStatement, TableDropStatement,
TableTruncateStatement, TableRenameStatement, TableTruncateStatement,
}; };
use sea_orm::{ConnectionTrait, DbBackend, DbConn, DbErr, StatementBuilder}; use sea_orm::{Condition, ConnectionTrait, DbBackend, DbConn, DbErr, Statement, StatementBuilder};
use sea_schema::migration;
use super::query_tables;
/// Helper struct for writing migration scripts in migration file /// Helper struct for writing migration scripts in migration file
#[derive(Debug)]
pub struct SchemaManager<'c> { pub struct SchemaManager<'c> {
conn: DatabaseConnection<'c>, conn: &'c DbConn,
} }
impl<'c> SchemaManager<'c> { impl<'c> SchemaManager<'c> {
/// Initialize [`SchemaManager`]
pub fn new(conn: &'c DbConn) -> Self { pub fn new(conn: &'c DbConn) -> Self {
Self { Self { conn }
conn: DatabaseConnection { conn },
}
} }
/// Execute any statement that implemented [`StatementBuilder`]
pub async fn exec_stmt<S>(&self, stmt: S) -> Result<(), DbErr> pub async fn exec_stmt<S>(&self, stmt: S) -> Result<(), DbErr>
where where
S: StatementBuilder, S: StatementBuilder,
{ {
let builder = self.conn.conn.get_database_backend(); let builder = self.conn.get_database_backend();
self.conn self.conn.execute(builder.build(&stmt)).await.map(|_| ())
.conn
.execute(builder.build(&stmt))
.await
.map(|_| ())
} }
/// Get database backend
pub fn get_database_backend(&self) -> DbBackend { pub fn get_database_backend(&self) -> DbBackend {
self.conn.conn.get_database_backend() self.conn.get_database_backend()
} }
/// Borrow database connection
pub fn get_connection(&self) -> &'c DbConn { pub fn get_connection(&self) -> &'c DbConn {
self.conn.conn self.conn
} }
} }
/// Schema Creation /// Schema Creation
impl<'c> SchemaManager<'c> { impl<'c> SchemaManager<'c> {
/// Create table
pub async fn create_table(&self, stmt: TableCreateStatement) -> Result<(), DbErr> { pub async fn create_table(&self, stmt: TableCreateStatement) -> Result<(), DbErr> {
migration::SchemaManager::create_table(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Create index
pub async fn create_index(&self, stmt: IndexCreateStatement) -> Result<(), DbErr> { pub async fn create_index(&self, stmt: IndexCreateStatement) -> Result<(), DbErr> {
migration::SchemaManager::create_index(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Create foreign key
pub async fn create_foreign_key(&self, stmt: ForeignKeyCreateStatement) -> Result<(), DbErr> { pub async fn create_foreign_key(&self, stmt: ForeignKeyCreateStatement) -> Result<(), DbErr> {
migration::SchemaManager::create_foreign_key(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Create type
pub async fn create_type(&self, stmt: TypeCreateStatement) -> Result<(), DbErr> { pub async fn create_type(&self, stmt: TypeCreateStatement) -> Result<(), DbErr> {
migration::SchemaManager::create_type(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
} }
/// Schema Mutation /// Schema Mutation
impl<'c> SchemaManager<'c> { impl<'c> SchemaManager<'c> {
/// Alter table
pub async fn alter_table(&self, stmt: TableAlterStatement) -> Result<(), DbErr> { pub async fn alter_table(&self, stmt: TableAlterStatement) -> Result<(), DbErr> {
migration::SchemaManager::alter_table(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Drop table
pub async fn drop_table(&self, stmt: TableDropStatement) -> Result<(), DbErr> { pub async fn drop_table(&self, stmt: TableDropStatement) -> Result<(), DbErr> {
migration::SchemaManager::drop_table(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Rename table
pub async fn rename_table(&self, stmt: TableRenameStatement) -> Result<(), DbErr> { pub async fn rename_table(&self, stmt: TableRenameStatement) -> Result<(), DbErr> {
migration::SchemaManager::rename_table(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Truncate table
pub async fn truncate_table(&self, stmt: TableTruncateStatement) -> Result<(), DbErr> { pub async fn truncate_table(&self, stmt: TableTruncateStatement) -> Result<(), DbErr> {
migration::SchemaManager::truncate_table(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Drop index
pub async fn drop_index(&self, stmt: IndexDropStatement) -> Result<(), DbErr> { pub async fn drop_index(&self, stmt: IndexDropStatement) -> Result<(), DbErr> {
migration::SchemaManager::drop_index(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Drop foreign key
pub async fn drop_foreign_key(&self, stmt: ForeignKeyDropStatement) -> Result<(), DbErr> { pub async fn drop_foreign_key(&self, stmt: ForeignKeyDropStatement) -> Result<(), DbErr> {
migration::SchemaManager::drop_foreign_key(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Alter type
pub async fn alter_type(&self, stmt: TypeAlterStatement) -> Result<(), DbErr> { pub async fn alter_type(&self, stmt: TypeAlterStatement) -> Result<(), DbErr> {
migration::SchemaManager::alter_type(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
/// Drop type
pub async fn drop_type(&self, stmt: TypeDropStatement) -> Result<(), DbErr> { pub async fn drop_type(&self, stmt: TypeDropStatement) -> Result<(), DbErr> {
migration::SchemaManager::drop_type(stmt, &self.conn) self.exec_stmt(stmt).await
.await
.map_err(into_orm_db_err)
} }
} }
/// Schema Inspection /// Schema Inspection
impl<'c> SchemaManager<'c> { impl<'c> SchemaManager<'c> {
/// Check if a table exists in the database
pub async fn has_table<T>(&self, table: T) -> Result<bool, DbErr> pub async fn has_table<T>(&self, table: T) -> Result<bool, DbErr>
where where
T: AsRef<str>, T: AsRef<str>,
{ {
migration::SchemaManager::has_table(table, &self.conn) let mut stmt = Query::select();
.await let mut subquery = query_tables(self.conn);
.map_err(into_orm_db_err) subquery.cond_where(Expr::col(Alias::new("table_name")).eq(table.as_ref()));
stmt.expr_as(Expr::cust("COUNT(*)"), Alias::new("rows"))
.from_subquery(subquery, Alias::new("subquery"));
let builder = self.conn.get_database_backend();
let res = self
.conn
.query_one(builder.build(&stmt))
.await?
.ok_or_else(|| DbErr::Custom("Fail to check table exists".to_owned()))?;
let rows: i64 = res.try_get("", "rows")?;
Ok(rows > 0)
} }
/// Check if a column exists in a specific database table
pub async fn has_column<T, C>(&self, table: T, column: C) -> Result<bool, DbErr> pub async fn has_column<T, C>(&self, table: T, column: C) -> Result<bool, DbErr>
where where
T: AsRef<str>, T: AsRef<str>,
C: AsRef<str>, C: AsRef<str>,
{ {
migration::SchemaManager::has_column(table, column, &self.conn) let db_backend = self.conn.get_database_backend();
.await let found = match db_backend {
.map_err(into_orm_db_err) DbBackend::MySql | DbBackend::Postgres => {
let schema_name = match db_backend {
DbBackend::MySql => "DATABASE()",
DbBackend::Postgres => "CURRENT_SCHEMA()",
DbBackend::Sqlite => unreachable!(),
};
let mut stmt = Query::select();
stmt.expr_as(Expr::cust("COUNT(*)"), Alias::new("rows"))
.from((Alias::new("information_schema"), Alias::new("columns")))
.cond_where(
Condition::all()
.add(
Expr::expr(Expr::cust(schema_name))
.equals(Alias::new("columns"), Alias::new("table_schema")),
)
.add(Expr::col(Alias::new("table_name")).eq(table.as_ref()))
.add(Expr::col(Alias::new("column_name")).eq(column.as_ref())),
);
let res = self
.conn
.query_one(db_backend.build(&stmt))
.await?
.ok_or_else(|| DbErr::Custom("Fail to check column exists".to_owned()))?;
let rows: i64 = res.try_get("", "rows")?;
rows > 0
}
DbBackend::Sqlite => {
let stmt = Statement::from_string(
db_backend,
format!("PRAGMA table_info({})", table.as_ref()),
);
let results = self.conn.query_all(stmt).await?;
let mut found = false;
for res in results {
let name: String = res.try_get("", "name")?;
if name.as_str() == column.as_ref() {
found = true;
}
}
found
}
};
Ok(found)
} }
} }

View File

@ -1,6 +1,4 @@
//! Migration executor use super::{seaql_migrations, MigrationTrait, SchemaManager};
use crate::{seaql_migrations, MigrationTrait, SchemaManager};
use sea_orm::sea_query::{ use sea_orm::sea_query::{
Alias, Expr, ForeignKey, IntoTableRef, Query, SelectStatement, SimpleExpr, Table, Alias, Expr, ForeignKey, IntoTableRef, Query, SelectStatement, SimpleExpr, Table,
}; };
@ -21,8 +19,6 @@ pub enum MigrationStatus {
Applied, Applied,
} }
/// Wrapper of [`MigrationTrait`] with migration status
#[allow(missing_debug_implementations)]
pub struct Migration { pub struct Migration {
migration: Box<dyn MigrationTrait>, migration: Box<dyn MigrationTrait>,
status: MigrationStatus, status: MigrationStatus,

View File

@ -1,8 +1,11 @@
//! Import migration utility pub use sea_orm_cli::migration as cli;
pub use crate::*; pub use super::manager::SchemaManager;
pub use super::migrator::MigratorTrait;
pub use super::{MigrationName, MigrationTrait};
pub use async_std; pub use async_std;
pub use async_trait; pub use async_trait;
pub use sea_orm;
pub use sea_orm::sea_query; pub use sea_orm::sea_query;
pub use sea_orm::sea_query::*; pub use sea_orm::sea_query::*;
pub use sea_orm::DbErr; pub use sea_orm::DbErr;

View File

@ -1,20 +0,0 @@
//! Get query result from db
use crate::into_migration_err;
use sea_schema::migration::{self, MigrationErr};
pub(crate) struct QueryResult {
pub(crate) res: sea_orm::QueryResult,
}
impl migration::QueryResultTrait for QueryResult {
fn try_get_string(&self, col: &str) -> Result<String, MigrationErr> {
self.res
.try_get::<String>("", col)
.map_err(into_migration_err)
}
fn try_get_i64(&self, col: &str) -> Result<i64, MigrationErr> {
self.res.try_get::<i64>("", col).map_err(into_migration_err)
}
}

View File

@ -1,7 +1,3 @@
#![allow(missing_docs)]
//! Migration entity
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]

View File

@ -1,12 +0,0 @@
//! Convert SQL statement
use crate::into_orm_db_backend;
use sea_schema::migration;
pub(crate) fn into_orm_stmt(stmt: migration::Statement) -> sea_orm::Statement {
sea_orm::Statement {
sql: stmt.sql,
values: stmt.values,
db_backend: into_orm_db_backend(stmt.db_backend),
}
}

View File

@ -0,0 +1,89 @@
mod migrator;
use migrator::Migrator;
use sea_orm::{Database, DbErr};
use sea_orm_migration::prelude::*;
#[async_std::test]
async fn main() -> Result<(), DbErr> {
let url = std::env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set");
let db = &Database::connect(&url).await?;
let manager = SchemaManager::new(db);
println!("\nMigrator::status");
Migrator::status(db).await?;
println!("\nMigrator::install");
Migrator::install(db).await?;
assert!(manager.has_table("seaql_migrations").await?);
println!("\nMigrator::reset");
Migrator::reset(db).await?;
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::up");
Migrator::up(db, Some(0)).await?;
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::up");
Migrator::up(db, Some(1)).await?;
assert!(manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::down");
Migrator::down(db, Some(0)).await?;
assert!(manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::down");
Migrator::down(db, Some(1)).await?;
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::up");
Migrator::up(db, None).await?;
println!("\nMigrator::status");
Migrator::status(db).await?;
assert!(manager.has_table("cake").await?);
assert!(manager.has_table("fruit").await?);
println!("\nMigrator::down");
Migrator::down(db, None).await?;
assert!(manager.has_table("seaql_migrations").await?);
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::fresh");
Migrator::fresh(db).await?;
assert!(manager.has_table("cake").await?);
assert!(manager.has_table("fruit").await?);
println!("\nMigrator::refresh");
Migrator::refresh(db).await?;
assert!(manager.has_table("cake").await?);
assert!(manager.has_table("fruit").await?);
println!("\nMigrator::reset");
Migrator::reset(db).await?;
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::status");
Migrator::status(db).await?;
Ok(())
}

View File

@ -0,0 +1,43 @@
use sea_orm_migration::prelude::*;
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220118_000001_create_cake_table"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Cake::Table)
.col(
ColumnDef::new(Cake::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Cake::Name).string().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Cake::Table).to_owned())
.await
}
}
#[derive(Iden)]
pub enum Cake {
Table,
Id,
Name,
}

View File

@ -0,0 +1,63 @@
use super::m20220118_000001_create_cake_table::Cake;
use sea_orm::DbBackend;
use sea_orm_migration::prelude::*;
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220118_000002_create_fruit_table"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Fruit::Table)
.col(
ColumnDef::new(Fruit::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Fruit::Name).string().not_null())
.col(ColumnDef::new(Fruit::CakeId).integer().not_null())
.foreign_key(
ForeignKey::create()
.name("fk-fruit-cake_id")
.from(Fruit::Table, Fruit::CakeId)
.to(Cake::Table, Cake::Id),
)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
if manager.get_database_backend() != DbBackend::Sqlite {
manager
.drop_foreign_key(
ForeignKey::drop()
.table(Fruit::Table)
.name("fk-fruit-cake_id")
.to_owned(),
)
.await?;
}
manager
.drop_table(Table::drop().table(Fruit::Table).to_owned())
.await
}
}
#[derive(Iden)]
pub enum Fruit {
Table,
Id,
Name,
CakeId,
}

View File

@ -0,0 +1,52 @@
use sea_orm::{entity::prelude::*, Set};
use sea_orm_migration::prelude::*;
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220118_000003_seed_cake_table"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
cake::ActiveModel {
name: Set("Cheesecake".to_owned()),
..Default::default()
}
.insert(db)
.await
.map(|_| ())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
cake::Entity::delete_many()
.filter(cake::Column::Name.eq("Cheesecake"))
.exec(db)
.await
.map(|_| ())
}
}
mod cake {
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "cake")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
}

View File

@ -0,0 +1,18 @@
use sea_orm_migration::prelude::*;
mod m20220118_000001_create_cake_table;
mod m20220118_000002_create_fruit_table;
mod m20220118_000003_seed_cake_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20220118_000001_create_cake_table::Migration),
Box::new(m20220118_000002_create_fruit_table::Migration),
Box::new(m20220118_000003_seed_cake_table::Migration),
]
}
}