From 498c0154cad2b11c5df52eccb1a39d624acb1267 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Apr 2022 14:06:34 +0800 Subject: [PATCH 1/8] Restructure SeaORM migration Bump codegen's sea-query version [cli] Update CLI subcommand method Move migration utility into sea-orm-migration --- Cargo.toml | 2 +- examples/actix3_example/entity/Cargo.toml | 2 +- examples/actix3_example/migration/Cargo.toml | 5 +- examples/actix3_example/migration/src/lib.rs | 2 +- .../src/m20220120_000001_create_post_table.rs | 2 +- examples/actix3_example/migration/src/main.rs | 5 +- examples/actix_example/entity/Cargo.toml | 2 +- examples/actix_example/migration/Cargo.toml | 5 +- examples/actix_example/migration/src/lib.rs | 2 +- .../src/m20220120_000001_create_post_table.rs | 2 +- examples/actix_example/migration/src/main.rs | 5 +- examples/actix_example/src/main.rs | 2 +- examples/axum_example/entity/Cargo.toml | 2 +- examples/axum_example/migration/Cargo.toml | 5 +- examples/axum_example/migration/src/lib.rs | 2 +- .../src/m20220120_000001_create_post_table.rs | 2 +- examples/axum_example/migration/src/main.rs | 5 +- examples/graphql_example/entity/Cargo.toml | 1 + examples/graphql_example/migration/Cargo.toml | 7 +- examples/graphql_example/migration/src/lib.rs | 2 +- .../src/m20220101_000001_create_table.rs | 5 +- .../graphql_example/migration/src/main.rs | 5 +- examples/jsonrpsee_example/entity/Cargo.toml | 2 +- .../jsonrpsee_example/migration/Cargo.toml | 5 +- .../jsonrpsee_example/migration/src/lib.rs | 2 +- .../src/m20220120_000001_create_post_table.rs | 2 +- .../jsonrpsee_example/migration/src/main.rs | 5 +- examples/poem_example/entity/Cargo.toml | 2 +- examples/poem_example/migration/Cargo.toml | 5 +- examples/poem_example/migration/src/lib.rs | 2 +- .../src/m20220120_000001_create_post_table.rs | 2 +- examples/poem_example/migration/src/main.rs | 5 +- examples/rocket_example/entity/Cargo.toml | 2 +- examples/rocket_example/migration/Cargo.toml | 5 +- examples/rocket_example/migration/src/lib.rs | 2 +- .../src/m20220120_000001_create_post_table.rs | 2 +- examples/rocket_example/migration/src/main.rs | 5 +- examples/tonic_example/Cargo.toml | 1 - examples/tonic_example/entity/Cargo.toml | 2 +- examples/tonic_example/migration/Cargo.toml | 5 +- examples/tonic_example/migration/src/lib.rs | 2 +- .../src/m20220120_000001_create_post_table.rs | 2 +- examples/tonic_example/migration/src/main.rs | 5 +- examples/tonic_example/src/server.rs | 2 +- sea-orm-cli/Cargo.toml | 2 +- sea-orm-cli/src/cli.rs | 3 +- sea-orm-cli/template/migration/_Cargo.toml | 6 +- sea-orm-cli/template/migration/src/lib.rs | 2 +- .../src/m20220101_000001_create_table.rs | 2 +- sea-orm-cli/template/migration/src/main.rs | 5 +- sea-orm-codegen/Cargo.toml | 2 +- sea-orm-migration/Cargo.toml | 28 ++ sea-orm-migration/README.md | 77 ++++ sea-orm-migration/src/cli.rs | 103 ++++++ sea-orm-migration/src/connection.rs | 48 +++ sea-orm-migration/src/database.rs | 19 + sea-orm-migration/src/error.rs | 11 + sea-orm-migration/src/lib.rs | 36 ++ sea-orm-migration/src/manager.rs | 163 +++++++++ sea-orm-migration/src/migrator.rs | 338 ++++++++++++++++++ sea-orm-migration/src/prelude.rs | 8 + sea-orm-migration/src/query.rs | 20 ++ sea-orm-migration/src/seaql_migrations.rs | 18 + sea-orm-migration/src/statement.rs | 12 + src/error.rs | 3 + 65 files changed, 973 insertions(+), 72 deletions(-) create mode 100644 sea-orm-migration/Cargo.toml create mode 100644 sea-orm-migration/README.md create mode 100644 sea-orm-migration/src/cli.rs create mode 100644 sea-orm-migration/src/connection.rs create mode 100644 sea-orm-migration/src/database.rs create mode 100644 sea-orm-migration/src/error.rs create mode 100644 sea-orm-migration/src/lib.rs create mode 100644 sea-orm-migration/src/manager.rs create mode 100644 sea-orm-migration/src/migrator.rs create mode 100644 sea-orm-migration/src/prelude.rs create mode 100644 sea-orm-migration/src/query.rs create mode 100644 sea-orm-migration/src/seaql_migrations.rs create mode 100644 sea-orm-migration/src/statement.rs diff --git a/Cargo.toml b/Cargo.toml index c3ea1ce8..6dc978d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ once_cell = "1.8" [dev-dependencies] smol = { version = "^1.2" } smol-potat = { version = "^1.1" } -async-std = { version = "^1.9", features = ["attributes", "tokio1"] } +async-std = { version = "^1", features = ["attributes", "tokio1"] } tokio = { version = "^1.6", features = ["full"] } actix-rt = { version = "2.2.0" } maplit = { version = "^1" } diff --git a/examples/actix3_example/entity/Cargo.toml b/examples/actix3_example/entity/Cargo.toml index b0daaeda..67879a0a 100644 --- a/examples/actix3_example/entity/Cargo.toml +++ b/examples/actix3_example/entity/Cargo.toml @@ -12,7 +12,7 @@ path = "src/lib.rs" serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] -# path = "../../../" # remove this line in your own project +path = "../../../" # remove this line in your own project version = "^0.7.0" features = [ "macros", diff --git a/examples/actix3_example/migration/Cargo.toml b/examples/actix3_example/migration/Cargo.toml index 89174a73..4ffcdd4e 100644 --- a/examples/actix3_example/migration/Cargo.toml +++ b/examples/actix3_example/migration/Cargo.toml @@ -9,5 +9,8 @@ name = "migration" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] } entity = { path = "../entity" } + +[dependencies.sea-orm-migration] +path = "../../../sea-orm-migration" # remove this line in your own project +version = "^0.7.0" diff --git a/examples/actix3_example/migration/src/lib.rs b/examples/actix3_example/migration/src/lib.rs index 3679d81f..af8d9b2a 100644 --- a/examples/actix3_example/migration/src/lib.rs +++ b/examples/actix3_example/migration/src/lib.rs @@ -1,4 +1,4 @@ -pub use sea_schema::migration::prelude::*; +pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; diff --git a/examples/actix3_example/migration/src/m20220120_000001_create_post_table.rs b/examples/actix3_example/migration/src/m20220120_000001_create_post_table.rs index 0fe872c4..097c2267 100644 --- a/examples/actix3_example/migration/src/m20220120_000001_create_post_table.rs +++ b/examples/actix3_example/migration/src/m20220120_000001_create_post_table.rs @@ -1,5 +1,5 @@ use entity::post::*; -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; pub struct Migration; diff --git a/examples/actix3_example/migration/src/main.rs b/examples/actix3_example/migration/src/main.rs index 7e5e996d..c6b6e48d 100644 --- a/examples/actix3_example/migration/src/main.rs +++ b/examples/actix3_example/migration/src/main.rs @@ -1,7 +1,6 @@ -use migration::Migrator; -use sea_schema::migration::*; +use sea_orm_migration::prelude::*; #[async_std::main] async fn main() { - cli::run_cli(Migrator).await; + cli::run_cli(migration::Migrator).await; } diff --git a/examples/actix_example/entity/Cargo.toml b/examples/actix_example/entity/Cargo.toml index 9db2a583..c0d21f5c 100644 --- a/examples/actix_example/entity/Cargo.toml +++ b/examples/actix_example/entity/Cargo.toml @@ -12,7 +12,7 @@ path = "src/lib.rs" serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] -# path = "../../../" # remove this line in your own project +path = "../../../" # remove this line in your own project version = "^0.7.0" features = [ "macros", diff --git a/examples/actix_example/migration/Cargo.toml b/examples/actix_example/migration/Cargo.toml index 89174a73..4ffcdd4e 100644 --- a/examples/actix_example/migration/Cargo.toml +++ b/examples/actix_example/migration/Cargo.toml @@ -9,5 +9,8 @@ name = "migration" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] } entity = { path = "../entity" } + +[dependencies.sea-orm-migration] +path = "../../../sea-orm-migration" # remove this line in your own project +version = "^0.7.0" diff --git a/examples/actix_example/migration/src/lib.rs b/examples/actix_example/migration/src/lib.rs index 3679d81f..af8d9b2a 100644 --- a/examples/actix_example/migration/src/lib.rs +++ b/examples/actix_example/migration/src/lib.rs @@ -1,4 +1,4 @@ -pub use sea_schema::migration::prelude::*; +pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; diff --git a/examples/actix_example/migration/src/m20220120_000001_create_post_table.rs b/examples/actix_example/migration/src/m20220120_000001_create_post_table.rs index 0fe872c4..097c2267 100644 --- a/examples/actix_example/migration/src/m20220120_000001_create_post_table.rs +++ b/examples/actix_example/migration/src/m20220120_000001_create_post_table.rs @@ -1,5 +1,5 @@ use entity::post::*; -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; pub struct Migration; diff --git a/examples/actix_example/migration/src/main.rs b/examples/actix_example/migration/src/main.rs index 7e5e996d..c6b6e48d 100644 --- a/examples/actix_example/migration/src/main.rs +++ b/examples/actix_example/migration/src/main.rs @@ -1,7 +1,6 @@ -use migration::Migrator; -use sea_schema::migration::*; +use sea_orm_migration::prelude::*; #[async_std::main] async fn main() { - cli::run_cli(Migrator).await; + cli::run_cli(migration::Migrator).await; } diff --git a/examples/actix_example/src/main.rs b/examples/actix_example/src/main.rs index 5855bbed..dd100fef 100644 --- a/examples/actix_example/src/main.rs +++ b/examples/actix_example/src/main.rs @@ -1,6 +1,6 @@ use actix_files::Files as Fs; use actix_web::{ - error, get, middleware, post, web, App, Error, HttpRequest, HttpResponse, HttpServer, Result + error, get, middleware, post, web, App, Error, HttpRequest, HttpResponse, HttpServer, Result, }; use entity::post; diff --git a/examples/axum_example/entity/Cargo.toml b/examples/axum_example/entity/Cargo.toml index 407a6942..961eb33c 100644 --- a/examples/axum_example/entity/Cargo.toml +++ b/examples/axum_example/entity/Cargo.toml @@ -12,7 +12,7 @@ path = "src/lib.rs" serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] -# path = "../../../" # remove this line in your own project +path = "../../../" # remove this line in your own project version = "^0.7.0" features = [ "macros", diff --git a/examples/axum_example/migration/Cargo.toml b/examples/axum_example/migration/Cargo.toml index 89174a73..4ffcdd4e 100644 --- a/examples/axum_example/migration/Cargo.toml +++ b/examples/axum_example/migration/Cargo.toml @@ -9,5 +9,8 @@ name = "migration" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] } entity = { path = "../entity" } + +[dependencies.sea-orm-migration] +path = "../../../sea-orm-migration" # remove this line in your own project +version = "^0.7.0" diff --git a/examples/axum_example/migration/src/lib.rs b/examples/axum_example/migration/src/lib.rs index 3679d81f..af8d9b2a 100644 --- a/examples/axum_example/migration/src/lib.rs +++ b/examples/axum_example/migration/src/lib.rs @@ -1,4 +1,4 @@ -pub use sea_schema::migration::prelude::*; +pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; diff --git a/examples/axum_example/migration/src/m20220120_000001_create_post_table.rs b/examples/axum_example/migration/src/m20220120_000001_create_post_table.rs index 0fe872c4..097c2267 100644 --- a/examples/axum_example/migration/src/m20220120_000001_create_post_table.rs +++ b/examples/axum_example/migration/src/m20220120_000001_create_post_table.rs @@ -1,5 +1,5 @@ use entity::post::*; -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; pub struct Migration; diff --git a/examples/axum_example/migration/src/main.rs b/examples/axum_example/migration/src/main.rs index 7e5e996d..c6b6e48d 100644 --- a/examples/axum_example/migration/src/main.rs +++ b/examples/axum_example/migration/src/main.rs @@ -1,7 +1,6 @@ -use migration::Migrator; -use sea_schema::migration::*; +use sea_orm_migration::prelude::*; #[async_std::main] async fn main() { - cli::run_cli(Migrator).await; + cli::run_cli(migration::Migrator).await; } diff --git a/examples/graphql_example/entity/Cargo.toml b/examples/graphql_example/entity/Cargo.toml index c7e1da0a..5623ad04 100644 --- a/examples/graphql_example/entity/Cargo.toml +++ b/examples/graphql_example/entity/Cargo.toml @@ -15,6 +15,7 @@ serde = { version = "1", features = ["derive"] } version = "^3.0.38" [dependencies.sea-orm] +path = "../../../" # remove this line in your own project version = "^0.7.0" features = [ "macros", diff --git a/examples/graphql_example/migration/Cargo.toml b/examples/graphql_example/migration/Cargo.toml index af696384..9c5242f8 100644 --- a/examples/graphql_example/migration/Cargo.toml +++ b/examples/graphql_example/migration/Cargo.toml @@ -9,6 +9,9 @@ name = "migration" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] } dotenv = "0.15.0" -entity = { path = "../entity" } \ No newline at end of file +entity = { path = "../entity" } + +[dependencies.sea-orm-migration] +path = "../../../sea-orm-migration" # remove this line in your own project +version = "^0.7.0" diff --git a/examples/graphql_example/migration/src/lib.rs b/examples/graphql_example/migration/src/lib.rs index 339d693b..2c605afb 100644 --- a/examples/graphql_example/migration/src/lib.rs +++ b/examples/graphql_example/migration/src/lib.rs @@ -1,4 +1,4 @@ -pub use sea_schema::migration::*; +pub use sea_orm_migration::prelude::*; mod m20220101_000001_create_table; diff --git a/examples/graphql_example/migration/src/m20220101_000001_create_table.rs b/examples/graphql_example/migration/src/m20220101_000001_create_table.rs index 0c754f6c..42278d2c 100644 --- a/examples/graphql_example/migration/src/m20220101_000001_create_table.rs +++ b/examples/graphql_example/migration/src/m20220101_000001_create_table.rs @@ -1,11 +1,8 @@ +use sea_orm_migration::prelude::*; use entity::{ note, sea_orm::{DbBackend, EntityTrait, Schema}, }; -use sea_schema::migration::{ - sea_query::*, - *, -}; pub struct Migration; diff --git a/examples/graphql_example/migration/src/main.rs b/examples/graphql_example/migration/src/main.rs index 5a5548f8..3e249b20 100644 --- a/examples/graphql_example/migration/src/main.rs +++ b/examples/graphql_example/migration/src/main.rs @@ -1,5 +1,4 @@ -use migration::Migrator; -use sea_schema::migration::*; +use sea_orm_migration::prelude::*; use std::path::PathBuf; #[cfg(debug_assertions)] @@ -22,5 +21,5 @@ async fn main() { } }; - cli::run_cli(Migrator).await; + cli::run_cli(migration::Migrator).await; } diff --git a/examples/jsonrpsee_example/entity/Cargo.toml b/examples/jsonrpsee_example/entity/Cargo.toml index e0ff8062..d24020b8 100644 --- a/examples/jsonrpsee_example/entity/Cargo.toml +++ b/examples/jsonrpsee_example/entity/Cargo.toml @@ -12,7 +12,7 @@ path = "src/lib.rs" serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] -# path = "../../../" # remove this line in your own project +path = "../../../" # remove this line in your own project version = "^0.7.0" features = [ "macros", diff --git a/examples/jsonrpsee_example/migration/Cargo.toml b/examples/jsonrpsee_example/migration/Cargo.toml index 89174a73..4ffcdd4e 100644 --- a/examples/jsonrpsee_example/migration/Cargo.toml +++ b/examples/jsonrpsee_example/migration/Cargo.toml @@ -9,5 +9,8 @@ name = "migration" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] } entity = { path = "../entity" } + +[dependencies.sea-orm-migration] +path = "../../../sea-orm-migration" # remove this line in your own project +version = "^0.7.0" diff --git a/examples/jsonrpsee_example/migration/src/lib.rs b/examples/jsonrpsee_example/migration/src/lib.rs index 3679d81f..af8d9b2a 100644 --- a/examples/jsonrpsee_example/migration/src/lib.rs +++ b/examples/jsonrpsee_example/migration/src/lib.rs @@ -1,4 +1,4 @@ -pub use sea_schema::migration::prelude::*; +pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; diff --git a/examples/jsonrpsee_example/migration/src/m20220120_000001_create_post_table.rs b/examples/jsonrpsee_example/migration/src/m20220120_000001_create_post_table.rs index 0fe872c4..097c2267 100644 --- a/examples/jsonrpsee_example/migration/src/m20220120_000001_create_post_table.rs +++ b/examples/jsonrpsee_example/migration/src/m20220120_000001_create_post_table.rs @@ -1,5 +1,5 @@ use entity::post::*; -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; pub struct Migration; diff --git a/examples/jsonrpsee_example/migration/src/main.rs b/examples/jsonrpsee_example/migration/src/main.rs index 7e5e996d..c6b6e48d 100644 --- a/examples/jsonrpsee_example/migration/src/main.rs +++ b/examples/jsonrpsee_example/migration/src/main.rs @@ -1,7 +1,6 @@ -use migration::Migrator; -use sea_schema::migration::*; +use sea_orm_migration::prelude::*; #[async_std::main] async fn main() { - cli::run_cli(Migrator).await; + cli::run_cli(migration::Migrator).await; } diff --git a/examples/poem_example/entity/Cargo.toml b/examples/poem_example/entity/Cargo.toml index e0ff8062..d24020b8 100644 --- a/examples/poem_example/entity/Cargo.toml +++ b/examples/poem_example/entity/Cargo.toml @@ -12,7 +12,7 @@ path = "src/lib.rs" serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] -# path = "../../../" # remove this line in your own project +path = "../../../" # remove this line in your own project version = "^0.7.0" features = [ "macros", diff --git a/examples/poem_example/migration/Cargo.toml b/examples/poem_example/migration/Cargo.toml index 89174a73..4ffcdd4e 100644 --- a/examples/poem_example/migration/Cargo.toml +++ b/examples/poem_example/migration/Cargo.toml @@ -9,5 +9,8 @@ name = "migration" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] } entity = { path = "../entity" } + +[dependencies.sea-orm-migration] +path = "../../../sea-orm-migration" # remove this line in your own project +version = "^0.7.0" diff --git a/examples/poem_example/migration/src/lib.rs b/examples/poem_example/migration/src/lib.rs index 3679d81f..af8d9b2a 100644 --- a/examples/poem_example/migration/src/lib.rs +++ b/examples/poem_example/migration/src/lib.rs @@ -1,4 +1,4 @@ -pub use sea_schema::migration::prelude::*; +pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; diff --git a/examples/poem_example/migration/src/m20220120_000001_create_post_table.rs b/examples/poem_example/migration/src/m20220120_000001_create_post_table.rs index 0fe872c4..097c2267 100644 --- a/examples/poem_example/migration/src/m20220120_000001_create_post_table.rs +++ b/examples/poem_example/migration/src/m20220120_000001_create_post_table.rs @@ -1,5 +1,5 @@ use entity::post::*; -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; pub struct Migration; diff --git a/examples/poem_example/migration/src/main.rs b/examples/poem_example/migration/src/main.rs index 7e5e996d..c6b6e48d 100644 --- a/examples/poem_example/migration/src/main.rs +++ b/examples/poem_example/migration/src/main.rs @@ -1,7 +1,6 @@ -use migration::Migrator; -use sea_schema::migration::*; +use sea_orm_migration::prelude::*; #[async_std::main] async fn main() { - cli::run_cli(Migrator).await; + cli::run_cli(migration::Migrator).await; } diff --git a/examples/rocket_example/entity/Cargo.toml b/examples/rocket_example/entity/Cargo.toml index 645dbf3f..b29afd4e 100644 --- a/examples/rocket_example/entity/Cargo.toml +++ b/examples/rocket_example/entity/Cargo.toml @@ -14,7 +14,7 @@ rocket = { version = "0.5.0-rc.1", features = [ ] } [dependencies.sea-orm] -# path = "../../../" # remove this line in your own project +path = "../../../" # remove this line in your own project version = "^0.7.0" features = [ "macros", diff --git a/examples/rocket_example/migration/Cargo.toml b/examples/rocket_example/migration/Cargo.toml index a5e565b7..01f5b9b7 100644 --- a/examples/rocket_example/migration/Cargo.toml +++ b/examples/rocket_example/migration/Cargo.toml @@ -9,6 +9,9 @@ name = "migration" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] } entity = { path = "../entity" } + +[dependencies.sea-orm-migration] +path = "../../../sea-orm-migration" # remove this line in your own project +version = "^0.7.0" rocket = { version = "0.5.0-rc.1" } diff --git a/examples/rocket_example/migration/src/lib.rs b/examples/rocket_example/migration/src/lib.rs index 3679d81f..af8d9b2a 100644 --- a/examples/rocket_example/migration/src/lib.rs +++ b/examples/rocket_example/migration/src/lib.rs @@ -1,4 +1,4 @@ -pub use sea_schema::migration::prelude::*; +pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; diff --git a/examples/rocket_example/migration/src/m20220120_000001_create_post_table.rs b/examples/rocket_example/migration/src/m20220120_000001_create_post_table.rs index 0fe872c4..097c2267 100644 --- a/examples/rocket_example/migration/src/m20220120_000001_create_post_table.rs +++ b/examples/rocket_example/migration/src/m20220120_000001_create_post_table.rs @@ -1,5 +1,5 @@ use entity::post::*; -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; pub struct Migration; diff --git a/examples/rocket_example/migration/src/main.rs b/examples/rocket_example/migration/src/main.rs index e47018eb..4626e82f 100644 --- a/examples/rocket_example/migration/src/main.rs +++ b/examples/rocket_example/migration/src/main.rs @@ -1,5 +1,4 @@ -use migration::Migrator; -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; #[async_std::main] async fn main() { @@ -14,5 +13,5 @@ async fn main() { std::env::set_var(key, database_url); } - cli::run_cli(Migrator).await; + cli::run_cli(migration::Migrator).await; } diff --git a/examples/tonic_example/Cargo.toml b/examples/tonic_example/Cargo.toml index cded186c..64620a1c 100644 --- a/examples/tonic_example/Cargo.toml +++ b/examples/tonic_example/Cargo.toml @@ -14,7 +14,6 @@ tonic = "0.7" tokio = { version = "1.17", features = ["macros", "rt-multi-thread", "full"] } entity = { path = "entity" } migration = { path = "migration" } -sea-orm = { version = "0.7.1", features = [ "sqlx-postgres", "runtime-tokio-rustls", "macros" ] } prost = "0.10.0" serde = "1.0" diff --git a/examples/tonic_example/entity/Cargo.toml b/examples/tonic_example/entity/Cargo.toml index 178dcbec..7b430b07 100644 --- a/examples/tonic_example/entity/Cargo.toml +++ b/examples/tonic_example/entity/Cargo.toml @@ -12,7 +12,7 @@ path = "src/lib.rs" serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] -# path = "../../../" # remove this line in your own project +path = "../../../" # remove this line in your own project version = "^0.7.0" features = [ "macros", diff --git a/examples/tonic_example/migration/Cargo.toml b/examples/tonic_example/migration/Cargo.toml index 89174a73..4ffcdd4e 100644 --- a/examples/tonic_example/migration/Cargo.toml +++ b/examples/tonic_example/migration/Cargo.toml @@ -9,5 +9,8 @@ name = "migration" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] } entity = { path = "../entity" } + +[dependencies.sea-orm-migration] +path = "../../../sea-orm-migration" # remove this line in your own project +version = "^0.7.0" diff --git a/examples/tonic_example/migration/src/lib.rs b/examples/tonic_example/migration/src/lib.rs index 3679d81f..af8d9b2a 100644 --- a/examples/tonic_example/migration/src/lib.rs +++ b/examples/tonic_example/migration/src/lib.rs @@ -1,4 +1,4 @@ -pub use sea_schema::migration::prelude::*; +pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; diff --git a/examples/tonic_example/migration/src/m20220120_000001_create_post_table.rs b/examples/tonic_example/migration/src/m20220120_000001_create_post_table.rs index 0fe872c4..097c2267 100644 --- a/examples/tonic_example/migration/src/m20220120_000001_create_post_table.rs +++ b/examples/tonic_example/migration/src/m20220120_000001_create_post_table.rs @@ -1,5 +1,5 @@ use entity::post::*; -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; pub struct Migration; diff --git a/examples/tonic_example/migration/src/main.rs b/examples/tonic_example/migration/src/main.rs index 7e5e996d..c6b6e48d 100644 --- a/examples/tonic_example/migration/src/main.rs +++ b/examples/tonic_example/migration/src/main.rs @@ -1,7 +1,6 @@ -use migration::Migrator; -use sea_schema::migration::*; +use sea_orm_migration::prelude::*; #[async_std::main] async fn main() { - cli::run_cli(Migrator).await; + cli::run_cli(migration::Migrator).await; } diff --git a/examples/tonic_example/src/server.rs b/examples/tonic_example/src/server.rs index 6e3d2632..c51ebb10 100644 --- a/examples/tonic_example/src/server.rs +++ b/examples/tonic_example/src/server.rs @@ -8,7 +8,7 @@ use sea_orm_tonic_example::post::{ use entity::{ post::{self, Entity as PostEntity}, - sea_orm::{entity::*, query::*, DatabaseConnection}, + sea_orm::{self, entity::*, query::*, DatabaseConnection}, }; use migration::{Migrator, MigratorTrait}; diff --git a/sea-orm-cli/Cargo.toml b/sea-orm-cli/Cargo.toml index 0efea548..33cc67bf 100644 --- a/sea-orm-cli/Cargo.toml +++ b/sea-orm-cli/Cargo.toml @@ -32,7 +32,7 @@ clap = { version = "^2.33.3" } dotenv = { version = "^0.15" } async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] } sea-orm-codegen = { version = "^0.7.0", path = "../sea-orm-codegen" } -sea-schema = { version = "^0.7.0", default-features = false, features = [ +sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "dump-sea-orm-dep", default-features = false, features = [ "debug-print", "sqlx-mysql", "sqlx-sqlite", diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index e8e22a1f..3b1efab7 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -1,4 +1,5 @@ use clap::{App, AppSettings, Arg, SubCommand}; +use sea_schema::get_cli_subcommands; pub fn build_cli() -> App<'static, 'static> { let entity_subcommand = SubCommand::with_name("generate") @@ -95,7 +96,7 @@ pub fn build_cli() -> App<'static, 'static> { .arg(arg_migration_dir.clone()), ) .arg(arg_migration_dir.clone()); - for subcommand in sea_schema::migration::get_subcommands() { + for subcommand in get_cli_subcommands!() { migrate_subcommands = migrate_subcommands.subcommand(subcommand.arg(arg_migration_dir.clone())); } diff --git a/sea-orm-cli/template/migration/_Cargo.toml b/sea-orm-cli/template/migration/_Cargo.toml index 73d34bfa..4ffcdd4e 100644 --- a/sea-orm-cli/template/migration/_Cargo.toml +++ b/sea-orm-cli/template/migration/_Cargo.toml @@ -9,4 +9,8 @@ name = "migration" path = "src/lib.rs" [dependencies] -sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] } +entity = { path = "../entity" } + +[dependencies.sea-orm-migration] +path = "../../../sea-orm-migration" # remove this line in your own project +version = "^0.7.0" diff --git a/sea-orm-cli/template/migration/src/lib.rs b/sea-orm-cli/template/migration/src/lib.rs index 3cabe22c..2c605afb 100644 --- a/sea-orm-cli/template/migration/src/lib.rs +++ b/sea-orm-cli/template/migration/src/lib.rs @@ -1,4 +1,4 @@ -pub use sea_schema::migration::prelude::*; +pub use sea_orm_migration::prelude::*; mod m20220101_000001_create_table; diff --git a/sea-orm-cli/template/migration/src/m20220101_000001_create_table.rs b/sea-orm-cli/template/migration/src/m20220101_000001_create_table.rs index a5907a28..b4dc891e 100644 --- a/sea-orm-cli/template/migration/src/m20220101_000001_create_table.rs +++ b/sea-orm-cli/template/migration/src/m20220101_000001_create_table.rs @@ -1,4 +1,4 @@ -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; pub struct Migration; diff --git a/sea-orm-cli/template/migration/src/main.rs b/sea-orm-cli/template/migration/src/main.rs index 89b349c9..c6b6e48d 100644 --- a/sea-orm-cli/template/migration/src/main.rs +++ b/sea-orm-cli/template/migration/src/main.rs @@ -1,7 +1,6 @@ -use migration::Migrator; -use sea_schema::migration::prelude::*; +use sea_orm_migration::prelude::*; #[async_std::main] async fn main() { - cli::run_cli(Migrator).await; + cli::run_cli(migration::Migrator).await; } diff --git a/sea-orm-codegen/Cargo.toml b/sea-orm-codegen/Cargo.toml index e1d408db..6af01e87 100644 --- a/sea-orm-codegen/Cargo.toml +++ b/sea-orm-codegen/Cargo.toml @@ -15,7 +15,7 @@ name = "sea_orm_codegen" path = "src/lib.rs" [dependencies] -sea-query = { version = "0.22.0" } +sea-query = { version = "0.24.0" } syn = { version = "^1", default-features = false, features = [ "derive", "parsing", diff --git a/sea-orm-migration/Cargo.toml b/sea-orm-migration/Cargo.toml new file mode 100644 index 00000000..c5e2e1d2 --- /dev/null +++ b/sea-orm-migration/Cargo.toml @@ -0,0 +1,28 @@ +[workspace] +# A separate workspace + +[package] +name = "sea-orm-migration" +version = "0.7.2" +authors = [ "Billy Chan " ] +edition = "2021" +description = "Migration utility for SeaORM" +license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/sea-orm" +repository = "https://github.com/SeaQL/sea-orm" +categories = [ "database" ] +keywords = ["async", "orm", "mysql", "postgres", "sqlite"] + +[lib] +name = "sea_orm_migration" +path = "src/lib.rs" + +[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"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing = { version = "0.1", features = ["log"] } +async-trait = { version = "^0.1" } +dotenv = { version = "^0.15" } +clap = { version = "^2.33" } diff --git a/sea-orm-migration/README.md b/sea-orm-migration/README.md new file mode 100644 index 00000000..87a41232 --- /dev/null +++ b/sea-orm-migration/README.md @@ -0,0 +1,77 @@ +# SeaORM CLI + +Install and Usage: + +```sh +> cargo install sea-orm-cli +> sea-orm-cli help +``` + +Or: + +```sh +> cargo install --bin sea +> sea help +``` + +Getting Help: + +```sh +cargo run -- -h +``` + +## Running Entity Generator: + +```sh +# MySQL (`--database-schema` option is ignored) +cargo run -- generate entity -u mysql://sea:sea@localhost/bakery -o out + +# SQLite (`--database-schema` option is ignored) +cargo run -- generate entity -u sqlite://bakery.db -o out + +# PostgreSQL +cargo run -- generate entity -u postgres://sea:sea@localhost/bakery -s public -o out +``` + +## Running Migration: + +- Initialize migration directory + ```sh + cargo run -- migrate init + ``` +- Apply all pending migrations + ```sh + cargo run -- migrate + ``` + ```sh + cargo run -- migrate up + ``` +- Apply first 10 pending migrations + ```sh + cargo run -- migrate up -n 10 + ``` +- Rollback last applied migrations + ```sh + cargo run -- migrate down + ``` +- Rollback last 10 applied migrations + ```sh + cargo run -- migrate down -n 10 + ``` +- Drop all tables from the database, then reapply all migrations + ```sh + cargo run -- migrate fresh + ``` +- Rollback all applied migrations, then reapply all migrations + ```sh + cargo run -- migrate refresh + ``` +- Rollback all applied migrations + ```sh + cargo run -- migrate reset + ``` +- Check the status of all migrations + ```sh + cargo run -- migrate status + ``` + diff --git a/sea-orm-migration/src/cli.rs b/sea-orm-migration/src/cli.rs new file mode 100644 index 00000000..bc211f29 --- /dev/null +++ b/sea-orm-migration/src/cli.rs @@ -0,0 +1,103 @@ +//! Run migrator CLI + +use crate::MigratorTrait; +use clap::{App, AppSettings, Arg}; +use dotenv::dotenv; +use sea_orm::{Database, DbConn}; +use sea_schema::get_cli_subcommands; +use std::{fmt::Display, process::exit}; +use tracing_subscriber::{prelude::*, EnvFilter}; + +#[allow(dead_code)] +/// Migrator CLI application +pub async fn run_cli(migrator: M) +where + M: MigratorTrait, +{ + dotenv().ok(); + let url = std::env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set"); + let db = &Database::connect(&url).await.unwrap(); + let app = build_cli(); + get_matches(migrator, db, app).await; +} + +async fn get_matches(_: M, db: &DbConn, app: App<'static, 'static>) +where + M: MigratorTrait, +{ + let matches = app.get_matches(); + let mut verbose = false; + let filter = match matches.subcommand() { + (_, None) => "sea_orm::migration=info", + (_, Some(args)) => match args.is_present("VERBOSE") { + true => { + verbose = true; + "debug" + } + false => "sea_orm::migration=info", + }, + }; + let filter_layer = EnvFilter::try_new(filter).unwrap(); + if verbose { + let fmt_layer = tracing_subscriber::fmt::layer(); + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt_layer) + .init() + } else { + let fmt_layer = tracing_subscriber::fmt::layer() + .with_target(false) + .with_level(false) + .without_time(); + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt_layer) + .init() + }; + match matches.subcommand() { + ("fresh", _) => M::fresh(db).await, + ("refresh", _) => M::refresh(db).await, + ("reset", _) => M::reset(db).await, + ("status", _) => M::status(db).await, + ("up", None) => M::up(db, None).await, + ("down", None) => M::down(db, Some(1)).await, + ("up", Some(args)) => { + let str = args.value_of("NUM_MIGRATION").unwrap_or_default(); + let steps = str.parse().ok(); + M::up(db, steps).await + } + ("down", Some(args)) => { + let str = args.value_of("NUM_MIGRATION").unwrap(); + let steps = str.parse().ok().unwrap_or(1); + M::down(db, Some(steps)).await + } + _ => M::up(db, None).await, + } + .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(error: E) +where + E: Display, +{ + eprintln!("{}", error); + exit(1); +} diff --git a/sea-orm-migration/src/connection.rs b/sea-orm-migration/src/connection.rs new file mode 100644 index 00000000..9d49b97a --- /dev/null +++ b/sea-orm-migration/src/connection.rs @@ -0,0 +1,48 @@ +//! 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>, MigrationErr> { + ConnectionTrait::query_one(self.conn, into_orm_stmt(stmt)) + .await + .map(|res| res.map(|res| Box::new(QueryResult { res }) as Box)) + .map_err(into_migration_err) + } + + async fn query_all( + &self, + stmt: migration::Statement, + ) -> Result>, MigrationErr> { + ConnectionTrait::query_all(self.conn, into_orm_stmt(stmt)) + .await + .map(|rows| { + rows.into_iter() + .map(|res| Box::new(QueryResult { res }) as Box) + .collect() + }) + .map_err(into_migration_err) + } +} diff --git a/sea-orm-migration/src/database.rs b/sea-orm-migration/src/database.rs new file mode 100644 index 00000000..82142840 --- /dev/null +++ b/sea-orm-migration/src/database.rs @@ -0,0 +1,19 @@ +//! 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, + } +} diff --git a/sea-orm-migration/src/error.rs b/sea-orm-migration/src/error.rs new file mode 100644 index 00000000..43bd4dd4 --- /dev/null +++ b/sea-orm-migration/src/error.rs @@ -0,0 +1,11 @@ +//! 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()) +} diff --git a/sea-orm-migration/src/lib.rs b/sea-orm-migration/src/lib.rs new file mode 100644 index 00000000..cb2712f3 --- /dev/null +++ b/sea-orm-migration/src/lib.rs @@ -0,0 +1,36 @@ +pub mod cli; +pub mod connection; +pub mod database; +pub mod error; +pub mod manager; +pub mod migrator; +pub mod prelude; +pub mod query; +pub mod seaql_migrations; +pub mod statement; + +pub use manager::*; +pub use migrator::*; + +use connection::*; +use database::*; +use error::*; +use query::*; +use statement::*; +use sea_orm::DbErr; + +/// Define the name of a migration +pub trait MigrationName { + /// Get migration name + fn name(&self) -> &str; +} + +/// Define the actions of a migration +#[async_trait::async_trait] +pub trait MigrationTrait: MigrationName + Send + Sync { + /// Define actions to perform when applying the migration + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>; + + /// Define actions to perform when rolling back the migration + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr>; +} diff --git a/sea-orm-migration/src/manager.rs b/sea-orm-migration/src/manager.rs new file mode 100644 index 00000000..4bd0c16a --- /dev/null +++ b/sea-orm-migration/src/manager.rs @@ -0,0 +1,163 @@ +//! Schema manager + +use crate::{into_orm_db_err, DatabaseConnection}; +use sea_orm::sea_query::{ + extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement}, + ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement, + TableAlterStatement, TableCreateStatement, TableDropStatement, TableRenameStatement, + TableTruncateStatement, +}; +use sea_orm::{ConnectionTrait, DbBackend, DbConn, DbErr, StatementBuilder}; +use sea_schema::migration; + +/// Helper struct for writing migration scripts in migration file +#[derive(Debug)] +pub struct SchemaManager<'c> { + conn: DatabaseConnection<'c>, +} + +impl<'c> SchemaManager<'c> { + /// Initialize [`SchemaManager`] + pub fn new(conn: &'c DbConn) -> Self { + Self { + conn: DatabaseConnection { conn }, + } + } + + /// Execute any statement that implemented [`StatementBuilder`] + pub async fn exec_stmt(&self, stmt: S) -> Result<(), DbErr> + where + S: StatementBuilder, + { + let builder = self.conn.conn.get_database_backend(); + self.conn + .conn + .execute(builder.build(&stmt)) + .await + .map(|_| ()) + } + + /// Get database backend + pub fn get_database_backend(&self) -> DbBackend { + self.conn.conn.get_database_backend() + } + + /// Borrow database connection + pub fn get_connection(&self) -> &'c DbConn { + self.conn.conn + } +} + +/// Schema Creation +impl<'c> SchemaManager<'c> { + /// Create table + pub async fn create_table(&self, stmt: TableCreateStatement) -> Result<(), DbErr> { + migration::SchemaManager::create_table(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Create index + pub async fn create_index(&self, stmt: IndexCreateStatement) -> Result<(), DbErr> { + migration::SchemaManager::create_index(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Create foreign key + pub async fn create_foreign_key(&self, stmt: ForeignKeyCreateStatement) -> Result<(), DbErr> { + migration::SchemaManager::create_foreign_key(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Create type + pub async fn create_type(&self, stmt: TypeCreateStatement) -> Result<(), DbErr> { + migration::SchemaManager::create_type(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } +} + +/// Schema Mutation +impl<'c> SchemaManager<'c> { + /// Alter table + pub async fn alter_table(&self, stmt: TableAlterStatement) -> Result<(), DbErr> { + migration::SchemaManager::alter_table(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Drop table + pub async fn drop_table(&self, stmt: TableDropStatement) -> Result<(), DbErr> { + migration::SchemaManager::drop_table(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Rename table + pub async fn rename_table(&self, stmt: TableRenameStatement) -> Result<(), DbErr> { + migration::SchemaManager::rename_table(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Truncate table + pub async fn truncate_table(&self, stmt: TableTruncateStatement) -> Result<(), DbErr> { + migration::SchemaManager::truncate_table(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Drop index + pub async fn drop_index(&self, stmt: IndexDropStatement) -> Result<(), DbErr> { + migration::SchemaManager::drop_index(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Drop foreign key + pub async fn drop_foreign_key(&self, stmt: ForeignKeyDropStatement) -> Result<(), DbErr> { + migration::SchemaManager::drop_foreign_key(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Alter type + pub async fn alter_type(&self, stmt: TypeAlterStatement) -> Result<(), DbErr> { + migration::SchemaManager::alter_type(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Drop type + pub async fn drop_type(&self, stmt: TypeDropStatement) -> Result<(), DbErr> { + migration::SchemaManager::drop_type(stmt, &self.conn) + .await + .map_err(into_orm_db_err) + } +} + +/// Schema Inspection +impl<'c> SchemaManager<'c> { + /// Check if a table exists in the database + pub async fn has_table(&self, table: T) -> Result + where + T: AsRef, + { + migration::SchemaManager::has_table(table, &self.conn) + .await + .map_err(into_orm_db_err) + } + + /// Check if a column exists in a specific database table + pub async fn has_column(&self, table: T, column: C) -> Result + where + T: AsRef, + C: AsRef, + { + migration::SchemaManager::has_column(table, column, &self.conn) + .await + .map_err(into_orm_db_err) + } +} diff --git a/sea-orm-migration/src/migrator.rs b/sea-orm-migration/src/migrator.rs new file mode 100644 index 00000000..a846f123 --- /dev/null +++ b/sea-orm-migration/src/migrator.rs @@ -0,0 +1,338 @@ +//! Migration executor + +use crate::{seaql_migrations, MigrationTrait, SchemaManager}; +use sea_orm::sea_query::{ + Alias, Expr, ForeignKey, IntoTableRef, Query, SelectStatement, SimpleExpr, Table, +}; +use sea_orm::{ + ActiveModelTrait, ActiveValue, ColumnTrait, Condition, ConnectionTrait, DbBackend, DbConn, + DbErr, EntityTrait, QueryFilter, QueryOrder, Schema, Statement, +}; +use std::fmt::Display; +use std::time::SystemTime; +use tracing::info; + +#[derive(Debug, PartialEq)] +/// Status of migration +pub enum MigrationStatus { + /// Not yet applied + Pending, + /// Applied + Applied, +} + +/// Wrapper of [`MigrationTrait`] with migration status +#[allow(missing_debug_implementations)] +pub struct Migration { + migration: Box, + status: MigrationStatus, +} + +impl Display for MigrationStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let status = match self { + MigrationStatus::Pending => "Pending", + MigrationStatus::Applied => "Applied", + }; + write!(f, "{}", status) + } +} + +/// Performing migrations on a database +#[async_trait::async_trait] +pub trait MigratorTrait: Send { + /// Vector of migrations in time sequence + fn migrations() -> Vec>; + + /// Get list of migrations wrapped in `Migration` struct + fn get_migration_files() -> Vec { + Self::migrations() + .into_iter() + .map(|migration| Migration { + migration, + status: MigrationStatus::Pending, + }) + .collect() + } + + /// Get list of applied migrations from database + async fn get_migration_models(db: &DbConn) -> Result, DbErr> { + Self::install(db).await?; + seaql_migrations::Entity::find() + .order_by_asc(seaql_migrations::Column::Version) + .all(db) + .await + } + + /// Get list of migrations with status + async fn get_migration_with_status(db: &DbConn) -> Result, DbErr> { + Self::install(db).await?; + let mut migration_files = Self::get_migration_files(); + let migration_models = Self::get_migration_models(db).await?; + for (i, migration_model) in migration_models.into_iter().enumerate() { + if let Some(migration_file) = migration_files.get_mut(i) { + if migration_file.migration.name() == migration_model.version.as_str() { + migration_file.status = MigrationStatus::Applied; + } else { + return Err(DbErr::Custom(format!("Migration mismatch: applied migration != migration file, '{0}' != '{1}'\nMigration '{0}' has been applied but its corresponding migration file is missing.", migration_file.migration.name(), migration_model.version))); + } + } else { + return Err(DbErr::Custom(format!("Migration file of version '{}' is missing, this migration has been applied but its file is missing", migration_model.version))); + } + } + Ok(migration_files) + } + + /// Get list of pending migrations + async fn get_pending_migrations(db: &DbConn) -> Result, DbErr> { + Self::install(db).await?; + Ok(Self::get_migration_with_status(db) + .await? + .into_iter() + .filter(|file| file.status == MigrationStatus::Pending) + .collect()) + } + + /// Get list of applied migrations + async fn get_applied_migrations(db: &DbConn) -> Result, DbErr> { + Self::install(db).await?; + Ok(Self::get_migration_with_status(db) + .await? + .into_iter() + .filter(|file| file.status == MigrationStatus::Applied) + .collect()) + } + + /// Create migration table `seaql_migrations` in the database + async fn install(db: &DbConn) -> Result<(), DbErr> { + let builder = db.get_database_backend(); + let schema = Schema::new(builder); + let mut stmt = schema.create_table_from_entity(seaql_migrations::Entity); + stmt.if_not_exists(); + db.execute(builder.build(&stmt)).await.map(|_| ()) + } + + /// Drop all tables from the database, then reapply all migrations + async fn fresh(db: &DbConn) -> Result<(), DbErr> { + Self::install(db).await?; + let db_backend = db.get_database_backend(); + + // Temporarily disable the foreign key check + if db_backend == DbBackend::Sqlite { + info!("Disabling foreign key check"); + db.execute(Statement::from_string( + db_backend, + "PRAGMA foreign_keys = OFF".to_owned(), + )) + .await?; + info!("Foreign key check disabled"); + } + + // Drop all foreign keys + if db_backend == DbBackend::MySql { + info!("Dropping all foreign keys"); + let mut stmt = Query::select(); + stmt.columns([Alias::new("TABLE_NAME"), Alias::new("CONSTRAINT_NAME")]) + .from(( + Alias::new("information_schema"), + Alias::new("table_constraints"), + )) + .cond_where( + Condition::all() + .add( + Expr::expr(get_current_schema(db)).equals( + Alias::new("table_constraints"), + Alias::new("table_schema"), + ), + ) + .add(Expr::expr(Expr::value("FOREIGN KEY")).equals( + Alias::new("table_constraints"), + Alias::new("constraint_type"), + )), + ); + let rows = db.query_all(db_backend.build(&stmt)).await?; + for row in rows.into_iter() { + let constraint_name: String = row.try_get("", "CONSTRAINT_NAME")?; + let table_name: String = row.try_get("", "TABLE_NAME")?; + info!( + "Dropping foreign key '{}' from table '{}'", + constraint_name, table_name + ); + let mut stmt = ForeignKey::drop(); + stmt.table(Alias::new(table_name.as_str())) + .name(constraint_name.as_str()); + db.execute(db_backend.build(&stmt)).await?; + info!("Foreign key '{}' has been dropped", constraint_name); + } + info!("All foreign keys dropped"); + } + + // Drop all tables + let stmt = query_tables(db); + let rows = db.query_all(db_backend.build(&stmt)).await?; + for row in rows.into_iter() { + let table_name: String = row.try_get("", "table_name")?; + info!("Dropping table '{}'", table_name); + let mut stmt = Table::drop(); + stmt.table(Alias::new(table_name.as_str())) + .if_exists() + .cascade(); + db.execute(db_backend.build(&stmt)).await?; + info!("Table '{}' has been dropped", table_name); + } + + // Restore the foreign key check + if db_backend == DbBackend::Sqlite { + info!("Restoring foreign key check"); + db.execute(Statement::from_string( + db_backend, + "PRAGMA foreign_keys = ON".to_owned(), + )) + .await?; + info!("Foreign key check restored"); + } + + // Reapply all migrations + Self::up(db, None).await + } + + /// Rollback all applied migrations, then reapply all migrations + async fn refresh(db: &DbConn) -> Result<(), DbErr> { + Self::down(db, None).await?; + Self::up(db, None).await + } + + /// Rollback all applied migrations + async fn reset(db: &DbConn) -> Result<(), DbErr> { + Self::down(db, None).await + } + + /// Check the status of all migrations + async fn status(db: &DbConn) -> Result<(), DbErr> { + Self::install(db).await?; + + info!("Checking migration status"); + + for Migration { migration, status } in Self::get_migration_with_status(db).await? { + info!("Migration '{}'... {}", migration.name(), status); + } + + Ok(()) + } + + /// Apply pending migrations + async fn up(db: &DbConn, mut steps: Option) -> Result<(), DbErr> { + Self::install(db).await?; + let manager = SchemaManager::new(db); + + if let Some(steps) = steps { + info!("Applying {} pending migrations", steps); + } else { + info!("Applying all pending migrations"); + } + + let migrations = Self::get_pending_migrations(db).await?.into_iter(); + if migrations.len() == 0 { + info!("No pending migrations"); + } + for Migration { migration, .. } in migrations { + if let Some(steps) = steps.as_mut() { + if steps == &0 { + break; + } + *steps -= 1; + } + info!("Applying migration '{}'", migration.name()); + migration.up(&manager).await?; + info!("Migration '{}' has been applied", migration.name()); + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("SystemTime before UNIX EPOCH!"); + seaql_migrations::ActiveModel { + version: ActiveValue::Set(migration.name().to_owned()), + applied_at: ActiveValue::Set(now.as_secs() as i64), + } + .insert(db) + .await?; + } + + Ok(()) + } + + /// Rollback applied migrations + async fn down(db: &DbConn, mut steps: Option) -> Result<(), DbErr> { + Self::install(db).await?; + let manager = SchemaManager::new(db); + + if let Some(steps) = steps { + info!("Rolling back {} applied migrations", steps); + } else { + info!("Rolling back all applied migrations"); + } + + let migrations = Self::get_applied_migrations(db).await?.into_iter().rev(); + if migrations.len() == 0 { + info!("No applied migrations"); + } + for Migration { migration, .. } in migrations { + if let Some(steps) = steps.as_mut() { + if steps == &0 { + break; + } + *steps -= 1; + } + info!("Rolling back migration '{}'", migration.name()); + migration.down(&manager).await?; + info!("Migration '{}' has been rollbacked", migration.name()); + seaql_migrations::Entity::delete_many() + .filter(seaql_migrations::Column::Version.eq(migration.name())) + .exec(db) + .await?; + } + + Ok(()) + } +} + +pub(crate) fn query_tables(db: &DbConn) -> SelectStatement { + let mut stmt = Query::select(); + let (expr, tbl_ref, condition) = match db.get_database_backend() { + DbBackend::MySql => ( + Expr::col(Alias::new("table_name")), + (Alias::new("information_schema"), Alias::new("tables")).into_table_ref(), + Condition::all().add( + Expr::expr(get_current_schema(db)) + .equals(Alias::new("tables"), Alias::new("table_schema")), + ), + ), + DbBackend::Postgres => ( + Expr::col(Alias::new("table_name")), + (Alias::new("information_schema"), Alias::new("tables")).into_table_ref(), + Condition::all() + .add( + Expr::expr(get_current_schema(db)) + .equals(Alias::new("tables"), Alias::new("table_schema")), + ) + .add(Expr::col(Alias::new("table_type")).eq("BASE TABLE")), + ), + DbBackend::Sqlite => ( + Expr::col(Alias::new("name")), + Alias::new("sqlite_master").into_table_ref(), + Condition::all() + .add(Expr::col(Alias::new("type")).eq("table")) + .add(Expr::col(Alias::new("name")).ne("sqlite_sequence")), + ), + }; + stmt.expr_as(expr, Alias::new("table_name")) + .from(tbl_ref) + .cond_where(condition); + stmt +} + +pub(crate) fn get_current_schema(db: &DbConn) -> SimpleExpr { + match db.get_database_backend() { + DbBackend::MySql => Expr::cust("DATABASE()"), + DbBackend::Postgres => Expr::cust("CURRENT_SCHEMA()"), + DbBackend::Sqlite => unimplemented!(), + } +} diff --git a/sea-orm-migration/src/prelude.rs b/sea-orm-migration/src/prelude.rs new file mode 100644 index 00000000..c116a49c --- /dev/null +++ b/sea-orm-migration/src/prelude.rs @@ -0,0 +1,8 @@ +//! Import migration utility + +pub use crate::*; +pub use async_std; +pub use async_trait; +pub use sea_orm::sea_query; +pub use sea_orm::sea_query::*; +pub use sea_orm::DbErr; diff --git a/sea-orm-migration/src/query.rs b/sea-orm-migration/src/query.rs new file mode 100644 index 00000000..0b23d0d5 --- /dev/null +++ b/sea-orm-migration/src/query.rs @@ -0,0 +1,20 @@ +//! 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 { + self.res + .try_get::("", col) + .map_err(into_migration_err) + } + + fn try_get_i64(&self, col: &str) -> Result { + self.res.try_get::("", col).map_err(into_migration_err) + } +} diff --git a/sea-orm-migration/src/seaql_migrations.rs b/sea-orm-migration/src/seaql_migrations.rs new file mode 100644 index 00000000..b387b57c --- /dev/null +++ b/sea-orm-migration/src/seaql_migrations.rs @@ -0,0 +1,18 @@ +#![allow(missing_docs)] + +//! Migration entity + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "seaql_migrations")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub version: String, + pub applied_at: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-migration/src/statement.rs b/sea-orm-migration/src/statement.rs new file mode 100644 index 00000000..c897aee8 --- /dev/null +++ b/sea-orm-migration/src/statement.rs @@ -0,0 +1,12 @@ +//! 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), + } +} diff --git a/src/error.rs b/src/error.rs index 273b9c9d..3e6753c6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,6 +15,8 @@ pub enum DbErr { Type(String), /// Error occurred while parsing json value as target type Json(String), + /// A migration error + Migration(String), } impl std::error::Error for DbErr {} @@ -29,6 +31,7 @@ impl std::fmt::Display for DbErr { Self::Custom(s) => write!(f, "Custom Error: {}", s), Self::Type(s) => write!(f, "Type Error: {}", s), Self::Json(s) => write!(f, "Json Error: {}", s), + Self::Migration(s) => write!(f, "Migration Error: {}", s), } } } From 75d5c0f5ea16ab8d346a2ec6d41aea4a7050c100 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 9 May 2022 12:14:48 +0800 Subject: [PATCH 2/8] Restructure sea-orm-cli & sea-orm-migration --- sea-orm-cli/Cargo.toml | 15 +- sea-orm-cli/src/cli.rs | 4 +- sea-orm-cli/src/lib.rs | 3 + sea-orm-cli/src/migration.rs | 49 ++++++ sea-orm-codegen/Cargo.toml | 2 +- sea-orm-migration/Cargo.toml | 15 +- sea-orm-migration/src/cli.rs | 38 +--- sea-orm-migration/src/connection.rs | 48 ----- sea-orm-migration/src/database.rs | 19 -- sea-orm-migration/src/error.rs | 11 -- sea-orm-migration/src/lib.rs | 20 +-- sea-orm-migration/src/manager.rs | 166 +++++++++--------- sea-orm-migration/src/migrator.rs | 6 +- sea-orm-migration/src/prelude.rs | 7 +- sea-orm-migration/src/query.rs | 20 --- sea-orm-migration/src/seaql_migrations.rs | 4 - sea-orm-migration/src/statement.rs | 12 -- sea-orm-migration/tests/main.rs | 89 ++++++++++ .../m20220118_000001_create_cake_table.rs | 43 +++++ .../m20220118_000002_create_fruit_table.rs | 63 +++++++ .../m20220118_000003_seed_cake_table.rs | 52 ++++++ sea-orm-migration/tests/migrator/mod.rs | 18 ++ 22 files changed, 444 insertions(+), 260 deletions(-) create mode 100644 sea-orm-cli/src/migration.rs delete mode 100644 sea-orm-migration/src/connection.rs delete mode 100644 sea-orm-migration/src/database.rs delete mode 100644 sea-orm-migration/src/error.rs delete mode 100644 sea-orm-migration/src/query.rs delete mode 100644 sea-orm-migration/src/statement.rs create mode 100644 sea-orm-migration/tests/main.rs create mode 100644 sea-orm-migration/tests/migrator/m20220118_000001_create_cake_table.rs create mode 100644 sea-orm-migration/tests/migrator/m20220118_000002_create_fruit_table.rs create mode 100644 sea-orm-migration/tests/migrator/m20220118_000003_seed_cake_table.rs create mode 100644 sea-orm-migration/tests/migrator/mod.rs diff --git a/sea-orm-cli/Cargo.toml b/sea-orm-cli/Cargo.toml index 33cc67bf..1b5290bb 100644 --- a/sea-orm-cli/Cargo.toml +++ b/sea-orm-cli/Cargo.toml @@ -14,7 +14,6 @@ categories = [ "database" ] keywords = ["async", "orm", "mysql", "postgres", "sqlite"] default-run = "sea-orm-cli" - [lib] name = "sea_orm_cli" path = "src/lib.rs" @@ -22,26 +21,27 @@ path = "src/lib.rs" [[bin]] name = "sea-orm-cli" path = "src/bin/main.rs" +required-features = ["codegen"] [[bin]] name = "sea" path = "src/bin/sea.rs" +required-features = ["codegen"] [dependencies] clap = { version = "^2.33.3" } dotenv = { version = "^0.15" } async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] } -sea-orm-codegen = { version = "^0.7.0", path = "../sea-orm-codegen" } -sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "dump-sea-orm-dep", default-features = false, features = [ +sea-orm-codegen = { version = "^0.7.0", path = "../sea-orm-codegen", optional = true } +sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "remove-migration", default-features = false, features = [ "debug-print", "sqlx-mysql", "sqlx-sqlite", "sqlx-postgres", "discovery", "writer", - "migration", -] } -sqlx = { version = "^0.5", default-features = false, features = [ "mysql", "postgres" ] } +], optional = true } +sqlx = { version = "^0.5", default-features = false, features = [ "mysql", "postgres" ], optional = true } tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing = { version = "0.1" } url = "^2.2" @@ -50,7 +50,8 @@ url = "^2.2" smol = "1.2.5" [features] -default = [ "runtime-async-std-native-tls" ] +default = [ "codegen", "runtime-async-std-native-tls" ] +codegen = [ "sea-schema", "sea-orm-codegen" ] runtime-actix-native-tls = [ "sqlx/runtime-actix-native-tls", "sea-schema/runtime-actix-native-tls", diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index 3b1efab7..362e4914 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -1,5 +1,5 @@ +use crate::migration::get_subcommands; use clap::{App, AppSettings, Arg, SubCommand}; -use sea_schema::get_cli_subcommands; pub fn build_cli() -> App<'static, 'static> { 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()); - for subcommand in get_cli_subcommands!() { + for subcommand in get_subcommands() { migrate_subcommands = migrate_subcommands.subcommand(subcommand.arg(arg_migration_dir.clone())); } diff --git a/sea-orm-cli/src/lib.rs b/sea-orm-cli/src/lib.rs index 366b8fa6..d334c9d6 100644 --- a/sea-orm-cli/src/lib.rs +++ b/sea-orm-cli/src/lib.rs @@ -1,5 +1,8 @@ pub mod cli; +#[cfg(feature = "codegen")] pub mod commands; +pub mod migration; pub use cli::*; +#[cfg(feature = "codegen")] pub use commands::*; diff --git a/sea-orm-cli/src/migration.rs b/sea-orm-cli/src/migration.rs new file mode 100644 index 00000000..41cd507b --- /dev/null +++ b/sea-orm-cli/src/migration.rs @@ -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> { + 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"), + ), + ] +} diff --git a/sea-orm-codegen/Cargo.toml b/sea-orm-codegen/Cargo.toml index 6af01e87..88b2ed86 100644 --- a/sea-orm-codegen/Cargo.toml +++ b/sea-orm-codegen/Cargo.toml @@ -15,7 +15,7 @@ name = "sea_orm_codegen" path = "src/lib.rs" [dependencies] -sea-query = { version = "0.24.0" } +sea-query = { version = "^0.24.0" } syn = { version = "^1", default-features = false, features = [ "derive", "parsing", diff --git a/sea-orm-migration/Cargo.toml b/sea-orm-migration/Cargo.toml index c5e2e1d2..91495e9b 100644 --- a/sea-orm-migration/Cargo.toml +++ b/sea-orm-migration/Cargo.toml @@ -18,11 +18,16 @@ name = "sea_orm_migration" path = "src/lib.rs" [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"] } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tracing = { version = "0.1", features = ["log"] } async-trait = { version = "^0.1" } -dotenv = { version = "^0.15" } 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"] diff --git a/sea-orm-migration/src/cli.rs b/sea-orm-migration/src/cli.rs index bc211f29..c00e4824 100644 --- a/sea-orm-migration/src/cli.rs +++ b/sea-orm-migration/src/cli.rs @@ -1,15 +1,13 @@ -//! Run migrator CLI - -use crate::MigratorTrait; -use clap::{App, AppSettings, Arg}; +use clap::App; use dotenv::dotenv; -use sea_orm::{Database, DbConn}; -use sea_schema::get_cli_subcommands; use std::{fmt::Display, process::exit}; use tracing_subscriber::{prelude::*, EnvFilter}; -#[allow(dead_code)] -/// Migrator CLI application +use sea_orm::{Database, DbConn}; +use sea_orm_cli::build_cli; + +use super::MigratorTrait; + pub async fn run_cli(migrator: M) where M: MigratorTrait, @@ -21,20 +19,20 @@ where get_matches(migrator, db, app).await; } -async fn get_matches(_: M, db: &DbConn, app: App<'static, 'static>) +pub async fn get_matches(_: M, db: &DbConn, app: App<'static, 'static>) where M: MigratorTrait, { let matches = app.get_matches(); let mut verbose = false; let filter = match matches.subcommand() { - (_, None) => "sea_orm::migration=info", + (_, None) => "sea_schema::migration=info", (_, Some(args)) => match args.is_present("VERBOSE") { true => { verbose = true; "debug" } - false => "sea_orm::migration=info", + false => "sea_schema::migration=info", }, }; let filter_layer = EnvFilter::try_new(filter).unwrap(); @@ -76,24 +74,6 @@ where .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(error: E) where E: Display, diff --git a/sea-orm-migration/src/connection.rs b/sea-orm-migration/src/connection.rs deleted file mode 100644 index 9d49b97a..00000000 --- a/sea-orm-migration/src/connection.rs +++ /dev/null @@ -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>, MigrationErr> { - ConnectionTrait::query_one(self.conn, into_orm_stmt(stmt)) - .await - .map(|res| res.map(|res| Box::new(QueryResult { res }) as Box)) - .map_err(into_migration_err) - } - - async fn query_all( - &self, - stmt: migration::Statement, - ) -> Result>, MigrationErr> { - ConnectionTrait::query_all(self.conn, into_orm_stmt(stmt)) - .await - .map(|rows| { - rows.into_iter() - .map(|res| Box::new(QueryResult { res }) as Box) - .collect() - }) - .map_err(into_migration_err) - } -} diff --git a/sea-orm-migration/src/database.rs b/sea-orm-migration/src/database.rs deleted file mode 100644 index 82142840..00000000 --- a/sea-orm-migration/src/database.rs +++ /dev/null @@ -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, - } -} diff --git a/sea-orm-migration/src/error.rs b/sea-orm-migration/src/error.rs deleted file mode 100644 index 43bd4dd4..00000000 --- a/sea-orm-migration/src/error.rs +++ /dev/null @@ -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()) -} diff --git a/sea-orm-migration/src/lib.rs b/sea-orm-migration/src/lib.rs index cb2712f3..a77f6d2f 100644 --- a/sea-orm-migration/src/lib.rs +++ b/sea-orm-migration/src/lib.rs @@ -1,31 +1,23 @@ pub mod cli; -pub mod connection; -pub mod database; -pub mod error; pub mod manager; pub mod migrator; pub mod prelude; -pub mod query; pub mod seaql_migrations; -pub mod statement; pub use manager::*; pub use migrator::*; -use connection::*; -use database::*; -use error::*; -use query::*; -use statement::*; -use sea_orm::DbErr; +pub use async_std; +pub use async_trait; +pub use sea_orm; +pub use sea_orm::sea_query; +pub use sea_orm::DbErr; -/// Define the name of a migration pub trait MigrationName { - /// Get migration name fn name(&self) -> &str; } -/// Define the actions of a migration +/// The migration definition #[async_trait::async_trait] pub trait MigrationTrait: MigrationName + Send + Sync { /// Define actions to perform when applying the migration diff --git a/sea-orm-migration/src/manager.rs b/sea-orm-migration/src/manager.rs index 4bd0c16a..fec6a22d 100644 --- a/sea-orm-migration/src/manager.rs +++ b/sea-orm-migration/src/manager.rs @@ -1,163 +1,167 @@ -//! Schema manager - -use crate::{into_orm_db_err, DatabaseConnection}; use sea_orm::sea_query::{ extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement}, - ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement, - TableAlterStatement, TableCreateStatement, TableDropStatement, TableRenameStatement, - TableTruncateStatement, + Alias, Expr, ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, + IndexDropStatement, Query, TableAlterStatement, TableCreateStatement, TableDropStatement, + TableRenameStatement, TableTruncateStatement, }; -use sea_orm::{ConnectionTrait, DbBackend, DbConn, DbErr, StatementBuilder}; -use sea_schema::migration; +use sea_orm::{Condition, ConnectionTrait, DbBackend, DbConn, DbErr, Statement, StatementBuilder}; + +use super::query_tables; /// Helper struct for writing migration scripts in migration file -#[derive(Debug)] pub struct SchemaManager<'c> { - conn: DatabaseConnection<'c>, + conn: &'c DbConn, } impl<'c> SchemaManager<'c> { - /// Initialize [`SchemaManager`] pub fn new(conn: &'c DbConn) -> Self { - Self { - conn: DatabaseConnection { conn }, - } + Self { conn } } - /// Execute any statement that implemented [`StatementBuilder`] pub async fn exec_stmt(&self, stmt: S) -> Result<(), DbErr> where S: StatementBuilder, { - let builder = self.conn.conn.get_database_backend(); - self.conn - .conn - .execute(builder.build(&stmt)) - .await - .map(|_| ()) + let builder = self.conn.get_database_backend(); + self.conn.execute(builder.build(&stmt)).await.map(|_| ()) } - /// Get database backend 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 { - self.conn.conn + self.conn } } /// Schema Creation impl<'c> SchemaManager<'c> { - /// Create table pub async fn create_table(&self, stmt: TableCreateStatement) -> Result<(), DbErr> { - migration::SchemaManager::create_table(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Create index pub async fn create_index(&self, stmt: IndexCreateStatement) -> Result<(), DbErr> { - migration::SchemaManager::create_index(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Create foreign key pub async fn create_foreign_key(&self, stmt: ForeignKeyCreateStatement) -> Result<(), DbErr> { - migration::SchemaManager::create_foreign_key(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Create type pub async fn create_type(&self, stmt: TypeCreateStatement) -> Result<(), DbErr> { - migration::SchemaManager::create_type(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } } /// Schema Mutation impl<'c> SchemaManager<'c> { - /// Alter table pub async fn alter_table(&self, stmt: TableAlterStatement) -> Result<(), DbErr> { - migration::SchemaManager::alter_table(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Drop table pub async fn drop_table(&self, stmt: TableDropStatement) -> Result<(), DbErr> { - migration::SchemaManager::drop_table(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Rename table pub async fn rename_table(&self, stmt: TableRenameStatement) -> Result<(), DbErr> { - migration::SchemaManager::rename_table(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Truncate table pub async fn truncate_table(&self, stmt: TableTruncateStatement) -> Result<(), DbErr> { - migration::SchemaManager::truncate_table(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Drop index pub async fn drop_index(&self, stmt: IndexDropStatement) -> Result<(), DbErr> { - migration::SchemaManager::drop_index(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Drop foreign key pub async fn drop_foreign_key(&self, stmt: ForeignKeyDropStatement) -> Result<(), DbErr> { - migration::SchemaManager::drop_foreign_key(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Alter type pub async fn alter_type(&self, stmt: TypeAlterStatement) -> Result<(), DbErr> { - migration::SchemaManager::alter_type(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } - /// Drop type pub async fn drop_type(&self, stmt: TypeDropStatement) -> Result<(), DbErr> { - migration::SchemaManager::drop_type(stmt, &self.conn) - .await - .map_err(into_orm_db_err) + self.exec_stmt(stmt).await } } /// Schema Inspection impl<'c> SchemaManager<'c> { - /// Check if a table exists in the database pub async fn has_table(&self, table: T) -> Result where T: AsRef, { - migration::SchemaManager::has_table(table, &self.conn) - .await - .map_err(into_orm_db_err) + let mut stmt = Query::select(); + let mut subquery = query_tables(self.conn); + 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(&self, table: T, column: C) -> Result where T: AsRef, C: AsRef, { - migration::SchemaManager::has_column(table, column, &self.conn) - .await - .map_err(into_orm_db_err) + let db_backend = self.conn.get_database_backend(); + let found = match db_backend { + 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) } } diff --git a/sea-orm-migration/src/migrator.rs b/sea-orm-migration/src/migrator.rs index a846f123..6c3d5386 100644 --- a/sea-orm-migration/src/migrator.rs +++ b/sea-orm-migration/src/migrator.rs @@ -1,6 +1,4 @@ -//! Migration executor - -use crate::{seaql_migrations, MigrationTrait, SchemaManager}; +use super::{seaql_migrations, MigrationTrait, SchemaManager}; use sea_orm::sea_query::{ Alias, Expr, ForeignKey, IntoTableRef, Query, SelectStatement, SimpleExpr, Table, }; @@ -21,8 +19,6 @@ pub enum MigrationStatus { Applied, } -/// Wrapper of [`MigrationTrait`] with migration status -#[allow(missing_debug_implementations)] pub struct Migration { migration: Box, status: MigrationStatus, diff --git a/sea-orm-migration/src/prelude.rs b/sea-orm-migration/src/prelude.rs index c116a49c..21122408 100644 --- a/sea-orm-migration/src/prelude.rs +++ b/sea-orm-migration/src/prelude.rs @@ -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_trait; +pub use sea_orm; pub use sea_orm::sea_query; pub use sea_orm::sea_query::*; pub use sea_orm::DbErr; diff --git a/sea-orm-migration/src/query.rs b/sea-orm-migration/src/query.rs deleted file mode 100644 index 0b23d0d5..00000000 --- a/sea-orm-migration/src/query.rs +++ /dev/null @@ -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 { - self.res - .try_get::("", col) - .map_err(into_migration_err) - } - - fn try_get_i64(&self, col: &str) -> Result { - self.res.try_get::("", col).map_err(into_migration_err) - } -} diff --git a/sea-orm-migration/src/seaql_migrations.rs b/sea-orm-migration/src/seaql_migrations.rs index b387b57c..43f044ec 100644 --- a/sea-orm-migration/src/seaql_migrations.rs +++ b/sea-orm-migration/src/seaql_migrations.rs @@ -1,7 +1,3 @@ -#![allow(missing_docs)] - -//! Migration entity - use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] diff --git a/sea-orm-migration/src/statement.rs b/sea-orm-migration/src/statement.rs deleted file mode 100644 index c897aee8..00000000 --- a/sea-orm-migration/src/statement.rs +++ /dev/null @@ -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), - } -} diff --git a/sea-orm-migration/tests/main.rs b/sea-orm-migration/tests/main.rs new file mode 100644 index 00000000..b7e7a97b --- /dev/null +++ b/sea-orm-migration/tests/main.rs @@ -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(()) +} diff --git a/sea-orm-migration/tests/migrator/m20220118_000001_create_cake_table.rs b/sea-orm-migration/tests/migrator/m20220118_000001_create_cake_table.rs new file mode 100644 index 00000000..bc70ba3b --- /dev/null +++ b/sea-orm-migration/tests/migrator/m20220118_000001_create_cake_table.rs @@ -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, +} diff --git a/sea-orm-migration/tests/migrator/m20220118_000002_create_fruit_table.rs b/sea-orm-migration/tests/migrator/m20220118_000002_create_fruit_table.rs new file mode 100644 index 00000000..238e4af3 --- /dev/null +++ b/sea-orm-migration/tests/migrator/m20220118_000002_create_fruit_table.rs @@ -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, +} diff --git a/sea-orm-migration/tests/migrator/m20220118_000003_seed_cake_table.rs b/sea-orm-migration/tests/migrator/m20220118_000003_seed_cake_table.rs new file mode 100644 index 00000000..03ba594c --- /dev/null +++ b/sea-orm-migration/tests/migrator/m20220118_000003_seed_cake_table.rs @@ -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 {} +} diff --git a/sea-orm-migration/tests/migrator/mod.rs b/sea-orm-migration/tests/migrator/mod.rs new file mode 100644 index 00000000..fdb92d9e --- /dev/null +++ b/sea-orm-migration/tests/migrator/mod.rs @@ -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> { + 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), + ] + } +} From 730b52088f68bf85350ed021b4c55cc8d6e47923 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 9 May 2022 12:28:22 +0800 Subject: [PATCH 3/8] Add tests --- .github/workflows/rust.yml | 24 ++++++++++++++++++++++++ sea-orm-cli/Cargo.toml | 30 ++++++------------------------ sea-orm-migration/Cargo.toml | 12 +++++++++--- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 21e2bd3e..4ab12836 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -383,6 +383,14 @@ jobs: --test '*' --features default,sqlx-sqlite,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --manifest-path sea-orm-migration/Cargo.toml + --test '*' + --features sqlx-sqlite,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + mysql: name: MySQL needs: @@ -444,6 +452,14 @@ jobs: --test '*' --features default,sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --manifest-path sea-orm-migration/Cargo.toml + --test '*' + --features sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + mariadb: name: MariaDB needs: @@ -562,3 +578,11 @@ jobs: args: > --test '*' --features default,sqlx-postgres,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --manifest-path sea-orm-migration/Cargo.toml + --test '*' + --features sqlx-postgres,runtime-${{ matrix.runtime }}-${{ matrix.tls }} diff --git a/sea-orm-cli/Cargo.toml b/sea-orm-cli/Cargo.toml index 1b5290bb..27f5e093 100644 --- a/sea-orm-cli/Cargo.toml +++ b/sea-orm-cli/Cargo.toml @@ -52,27 +52,9 @@ smol = "1.2.5" [features] default = [ "codegen", "runtime-async-std-native-tls" ] codegen = [ "sea-schema", "sea-orm-codegen" ] -runtime-actix-native-tls = [ - "sqlx/runtime-actix-native-tls", - "sea-schema/runtime-actix-native-tls", -] -runtime-async-std-native-tls = [ - "sqlx/runtime-async-std-native-tls", - "sea-schema/runtime-async-std-native-tls", -] -runtime-tokio-native-tls = [ - "sqlx/runtime-tokio-native-tls", - "sea-schema/runtime-tokio-native-tls", -] -runtime-actix-rustls = [ - "sqlx/runtime-actix-rustls", - "sea-schema/runtime-actix-rustls", -] -runtime-async-std-rustls = [ - "sqlx/runtime-async-std-rustls", - "sea-schema/runtime-async-std-rustls", -] -runtime-tokio-rustls = [ - "sqlx/runtime-tokio-rustls", - "sea-schema/runtime-tokio-rustls", -] +runtime-actix-native-tls = [ "sqlx/runtime-actix-native-tls", "sea-schema/runtime-actix-native-tls" ] +runtime-async-std-native-tls = [ "sqlx/runtime-async-std-native-tls", "sea-schema/runtime-async-std-native-tls" ] +runtime-tokio-native-tls = [ "sqlx/runtime-tokio-native-tls", "sea-schema/runtime-tokio-native-tls" ] +runtime-actix-rustls = [ "sqlx/runtime-actix-rustls", "sea-schema/runtime-actix-rustls" ] +runtime-async-std-rustls = [ "sqlx/runtime-async-std-rustls", "sea-schema/runtime-async-std-rustls" ] +runtime-tokio-rustls = [ "sqlx/runtime-tokio-rustls", "sea-schema/runtime-tokio-rustls" ] diff --git a/sea-orm-migration/Cargo.toml b/sea-orm-migration/Cargo.toml index 91495e9b..672871b7 100644 --- a/sea-orm-migration/Cargo.toml +++ b/sea-orm-migration/Cargo.toml @@ -28,6 +28,12 @@ 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"] +sqlx-mysql = ["sea-orm/sqlx-mysql"] +sqlx-postgres = ["sea-orm/sqlx-postgres"] +sqlx-sqlite = ["sea-orm/sqlx-sqlite"] +runtime-actix-native-tls = [ "sea-orm/runtime-actix-native-tls" ] +runtime-async-std-native-tls = [ "sea-orm/runtime-async-std-native-tls" ] +runtime-tokio-native-tls = [ "sea-orm/runtime-tokio-native-tls" ] +runtime-actix-rustls = [ "sea-orm/runtime-actix-rustls" ] +runtime-async-std-rustls = [ "sea-orm/runtime-async-std-rustls" ] +runtime-tokio-rustls = [ "sea-orm/runtime-tokio-rustls" ] \ No newline at end of file From e0d7d2bc788c4de3f861588f1d66f17789841b34 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 9 May 2022 13:51:21 +0800 Subject: [PATCH 4/8] Fix tests --- sea-orm-migration/tests/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sea-orm-migration/tests/main.rs b/sea-orm-migration/tests/main.rs index b7e7a97b..9b7c27c7 100644 --- a/sea-orm-migration/tests/main.rs +++ b/sea-orm-migration/tests/main.rs @@ -7,6 +7,8 @@ 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_name = "sea_orm_migration"; + let url = format!("{}/{}", url, db_name); let db = &Database::connect(&url).await?; let manager = SchemaManager::new(db); From c466e46ae070b3e93b61bef35fb24eac867b9bbf Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 9 May 2022 14:44:12 +0800 Subject: [PATCH 5/8] Fix tests --- sea-orm-cli/Cargo.toml | 2 +- sea-orm-cli/src/commands.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sea-orm-cli/Cargo.toml b/sea-orm-cli/Cargo.toml index 27f5e093..f0643856 100644 --- a/sea-orm-cli/Cargo.toml +++ b/sea-orm-cli/Cargo.toml @@ -33,7 +33,7 @@ clap = { version = "^2.33.3" } dotenv = { version = "^0.15" } async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] } sea-orm-codegen = { version = "^0.7.0", path = "../sea-orm-codegen", optional = true } -sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "remove-migration", default-features = false, features = [ +sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "master", default-features = false, features = [ "debug-print", "sqlx-mysql", "sqlx-sqlite", diff --git a/sea-orm-cli/src/commands.rs b/sea-orm-cli/src/commands.rs index 6a071fc2..c9a1ef00 100644 --- a/sea-orm-cli/src/commands.rs +++ b/sea-orm-cli/src/commands.rs @@ -117,7 +117,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box { - use sea_schema::sqlite::SchemaDiscovery; + use sea_schema::sqlite::discovery::SchemaDiscovery; use sqlx::Sqlite; let connection = connect::(max_connections, url.as_str()).await?; From 07c8af2b3a8a62f7d730ddb4c36e21bf64f5bdff Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 9 May 2022 17:04:12 +0800 Subject: [PATCH 6/8] Use sea-schema's SchemaProbe --- sea-orm-cli/Cargo.toml | 11 ++---- sea-orm-migration/Cargo.toml | 1 + sea-orm-migration/src/migrator.rs | 56 +++++++++---------------------- 3 files changed, 18 insertions(+), 50 deletions(-) diff --git a/sea-orm-cli/Cargo.toml b/sea-orm-cli/Cargo.toml index f0643856..6bfb65a9 100644 --- a/sea-orm-cli/Cargo.toml +++ b/sea-orm-cli/Cargo.toml @@ -33,14 +33,7 @@ clap = { version = "^2.33.3" } dotenv = { version = "^0.15" } async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] } sea-orm-codegen = { version = "^0.7.0", path = "../sea-orm-codegen", optional = true } -sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "master", default-features = false, features = [ - "debug-print", - "sqlx-mysql", - "sqlx-sqlite", - "sqlx-postgres", - "discovery", - "writer", -], optional = true } +sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "master" } sqlx = { version = "^0.5", default-features = false, features = [ "mysql", "postgres" ], optional = true } tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing = { version = "0.1" } @@ -51,7 +44,7 @@ smol = "1.2.5" [features] default = [ "codegen", "runtime-async-std-native-tls" ] -codegen = [ "sea-schema", "sea-orm-codegen" ] +codegen = [ "sea-schema/sqlx-all", "sea-orm-codegen" ] runtime-actix-native-tls = [ "sqlx/runtime-actix-native-tls", "sea-schema/runtime-actix-native-tls" ] runtime-async-std-native-tls = [ "sqlx/runtime-async-std-native-tls", "sea-schema/runtime-async-std-native-tls" ] runtime-tokio-native-tls = [ "sqlx/runtime-tokio-native-tls", "sea-schema/runtime-tokio-native-tls" ] diff --git a/sea-orm-migration/Cargo.toml b/sea-orm-migration/Cargo.toml index 672871b7..5b4ed006 100644 --- a/sea-orm-migration/Cargo.toml +++ b/sea-orm-migration/Cargo.toml @@ -24,6 +24,7 @@ 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 } +sea-schema = { git = "https://github.com/SeaQL/sea-schema", branch = "master" } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/sea-orm-migration/src/migrator.rs b/sea-orm-migration/src/migrator.rs index 6c3d5386..f5e12b05 100644 --- a/sea-orm-migration/src/migrator.rs +++ b/sea-orm-migration/src/migrator.rs @@ -1,14 +1,15 @@ -use super::{seaql_migrations, MigrationTrait, SchemaManager}; -use sea_orm::sea_query::{ - Alias, Expr, ForeignKey, IntoTableRef, Query, SelectStatement, SimpleExpr, Table, -}; +use std::fmt::Display; +use std::time::SystemTime; +use tracing::info; + +use sea_orm::sea_query::{Alias, Expr, ForeignKey, Query, SelectStatement, SimpleExpr, Table}; use sea_orm::{ ActiveModelTrait, ActiveValue, ColumnTrait, Condition, ConnectionTrait, DbBackend, DbConn, DbErr, EntityTrait, QueryFilter, QueryOrder, Schema, Statement, }; -use std::fmt::Display; -use std::time::SystemTime; -use tracing::info; +use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite}; + +use super::{seaql_migrations, MigrationTrait, SchemaManager}; #[derive(Debug, PartialEq)] /// Status of migration @@ -291,44 +292,17 @@ pub trait MigratorTrait: Send { } pub(crate) fn query_tables(db: &DbConn) -> SelectStatement { - let mut stmt = Query::select(); - let (expr, tbl_ref, condition) = match db.get_database_backend() { - DbBackend::MySql => ( - Expr::col(Alias::new("table_name")), - (Alias::new("information_schema"), Alias::new("tables")).into_table_ref(), - Condition::all().add( - Expr::expr(get_current_schema(db)) - .equals(Alias::new("tables"), Alias::new("table_schema")), - ), - ), - DbBackend::Postgres => ( - Expr::col(Alias::new("table_name")), - (Alias::new("information_schema"), Alias::new("tables")).into_table_ref(), - Condition::all() - .add( - Expr::expr(get_current_schema(db)) - .equals(Alias::new("tables"), Alias::new("table_schema")), - ) - .add(Expr::col(Alias::new("table_type")).eq("BASE TABLE")), - ), - DbBackend::Sqlite => ( - Expr::col(Alias::new("name")), - Alias::new("sqlite_master").into_table_ref(), - Condition::all() - .add(Expr::col(Alias::new("type")).eq("table")) - .add(Expr::col(Alias::new("name")).ne("sqlite_sequence")), - ), - }; - stmt.expr_as(expr, Alias::new("table_name")) - .from(tbl_ref) - .cond_where(condition); - stmt + match db.get_database_backend() { + DbBackend::MySql => MySql::query_tables(), + DbBackend::Postgres => Postgres::query_tables(), + DbBackend::Sqlite => Sqlite::query_tables(), + } } pub(crate) fn get_current_schema(db: &DbConn) -> SimpleExpr { match db.get_database_backend() { - DbBackend::MySql => Expr::cust("DATABASE()"), - DbBackend::Postgres => Expr::cust("CURRENT_SCHEMA()"), + DbBackend::MySql => MySql::get_current_schema(), + DbBackend::Postgres => Postgres::get_current_schema(), DbBackend::Sqlite => unimplemented!(), } } From 31cdecf54919515570df3db309eca8efd3414d88 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 9 May 2022 18:17:44 +0800 Subject: [PATCH 7/8] Use SchemaProbe fully --- sea-orm-migration/src/manager.rs | 74 +++++++++----------------------- sea-orm-migration/tests/main.rs | 21 +++++++-- 2 files changed, 38 insertions(+), 57 deletions(-) diff --git a/sea-orm-migration/src/manager.rs b/sea-orm-migration/src/manager.rs index fec6a22d..727e8340 100644 --- a/sea-orm-migration/src/manager.rs +++ b/sea-orm-migration/src/manager.rs @@ -5,8 +5,7 @@ use sea_orm::sea_query::{ TableRenameStatement, TableTruncateStatement, }; use sea_orm::{Condition, ConnectionTrait, DbBackend, DbConn, DbErr, Statement, StatementBuilder}; - -use super::query_tables; +use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite}; /// Helper struct for writing migration scripts in migration file pub struct SchemaManager<'c> { @@ -95,11 +94,11 @@ impl<'c> SchemaManager<'c> { where T: AsRef, { - let mut stmt = Query::select(); - let mut subquery = query_tables(self.conn); - 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 stmt = match self.conn.get_database_backend() { + DbBackend::MySql => MySql::has_table(table), + DbBackend::Postgres => Postgres::has_table(table), + DbBackend::Sqlite => Sqlite::has_table(table), + }; let builder = self.conn.get_database_backend(); let res = self @@ -107,9 +106,8 @@ impl<'c> SchemaManager<'c> { .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) + Ok(res.try_get("", "has_table")?) } pub async fn has_column(&self, table: T, column: C) -> Result @@ -117,51 +115,19 @@ impl<'c> SchemaManager<'c> { T: AsRef, C: AsRef, { - let db_backend = self.conn.get_database_backend(); - let found = match db_backend { - 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 - } + let stmt = match self.conn.get_database_backend() { + DbBackend::MySql => MySql::has_column(table, column), + DbBackend::Postgres => Postgres::has_column(table, column), + DbBackend::Sqlite => Sqlite::has_column(table, column), }; - Ok(found) + + 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()))?; + + Ok(res.try_get("", "has_column")?) } } diff --git a/sea-orm-migration/tests/main.rs b/sea-orm-migration/tests/main.rs index 9b7c27c7..4131bae4 100644 --- a/sea-orm-migration/tests/main.rs +++ b/sea-orm-migration/tests/main.rs @@ -1,15 +1,27 @@ mod migrator; use migrator::Migrator; -use sea_orm::{Database, DbErr}; +use sea_orm::{ConnectionTrait, Database, DbBackend, DbErr, Statement}; 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_name = "sea_orm_migration"; - let url = format!("{}/{}", url, db_name); - let db = &Database::connect(&url).await?; + let db = Database::connect(&url).await?; + let db = &match db.get_database_backend() { + DbBackend::MySql | DbBackend::Postgres => { + db.execute(Statement::from_string( + db.get_database_backend(), + format!("CREATE DATABASE IF NOT EXISTS `{}`;", db_name), + )) + .await?; + + let url = format!("{}/{}", url, db_name); + Database::connect(&url).await? + } + DbBackend::Sqlite => db, + }; let manager = SchemaManager::new(db); println!("\nMigrator::status"); @@ -59,6 +71,9 @@ async fn main() -> Result<(), DbErr> { assert!(manager.has_table("cake").await?); assert!(manager.has_table("fruit").await?); + assert!(manager.has_column("cake", "name").await?); + assert!(manager.has_column("fruit", "cake_id").await?); + println!("\nMigrator::down"); Migrator::down(db, None).await?; From b622a6e1aa06c018042cea27dca1761f5b961f9f Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 9 May 2022 18:24:46 +0800 Subject: [PATCH 8/8] Tweaks --- sea-orm-migration/src/manager.rs | 4 ++-- sea-orm-migration/tests/main.rs | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/sea-orm-migration/src/manager.rs b/sea-orm-migration/src/manager.rs index 727e8340..ef0f5f38 100644 --- a/sea-orm-migration/src/manager.rs +++ b/sea-orm-migration/src/manager.rs @@ -105,7 +105,7 @@ impl<'c> SchemaManager<'c> { .conn .query_one(builder.build(&stmt)) .await? - .ok_or_else(|| DbErr::Custom("Fail to check table exists".to_owned()))?; + .ok_or_else(|| DbErr::Custom("Failed to check table exists".to_owned()))?; Ok(res.try_get("", "has_table")?) } @@ -126,7 +126,7 @@ impl<'c> SchemaManager<'c> { .conn .query_one(builder.build(&stmt)) .await? - .ok_or_else(|| DbErr::Custom("Fail to check table exists".to_owned()))?; + .ok_or_else(|| DbErr::Custom("Failed to check column exists".to_owned()))?; Ok(res.try_get("", "has_column")?) } diff --git a/sea-orm-migration/tests/main.rs b/sea-orm-migration/tests/main.rs index 4131bae4..13f0b404 100644 --- a/sea-orm-migration/tests/main.rs +++ b/sea-orm-migration/tests/main.rs @@ -10,7 +10,7 @@ async fn main() -> Result<(), DbErr> { let db_name = "sea_orm_migration"; let db = Database::connect(&url).await?; let db = &match db.get_database_backend() { - DbBackend::MySql | DbBackend::Postgres => { + DbBackend::MySql => { db.execute(Statement::from_string( db.get_database_backend(), format!("CREATE DATABASE IF NOT EXISTS `{}`;", db_name), @@ -20,6 +20,21 @@ async fn main() -> Result<(), DbErr> { let url = format!("{}/{}", url, db_name); Database::connect(&url).await? } + DbBackend::Postgres => { + db.execute(Statement::from_string( + db.get_database_backend(), + format!("DROP DATABASE IF EXISTS \"{}\";", db_name), + )) + .await?; + db.execute(Statement::from_string( + db.get_database_backend(), + format!("CREATE DATABASE \"{}\";", db_name), + )) + .await?; + + let url = format!("{}/{}", url, db_name); + Database::connect(&url).await? + } DbBackend::Sqlite => db, }; let manager = SchemaManager::new(db);