Migration (#335)

* Refactor `ConnectionTrait`

* Refactoring

* Build index & foreign key statements

* Fix imports

* Fixup

* Rocket example with migration

* async-std compatible with the tokio 1.0 runtime

* Use reexported dependency

* Compile without selecting any db backend

* Updating sea-orm-cli dep

* sea-orm-cli migrate commands

* cargo fmt

* Test [cli]

* Refactoring

* Clap app name should be "sea-orm-cli"

* Correctly capture MIGRATION_DIR

* Rename README

* Add `sea-orm-cli migrate init` command

* Update README

* Try restructured sea-query dependency (SeaQL/sea-schema#41)

* Set `DATABASE_URL` environment variable
This commit is contained in:
Billy Chan 2022-02-05 20:32:30 +08:00 committed by Chris Tsang
parent e63d463155
commit 8eb095385d
44 changed files with 548 additions and 144 deletions

View File

@ -43,7 +43,7 @@ once_cell = "1.8"
[dev-dependencies]
smol = { version = "^1.2" }
smol-potat = { version = "^1.1" }
async-std = { version = "^1.9", features = ["attributes"] }
async-std = { version = "^1.9", features = ["attributes", "tokio1"] }
tokio = { version = "^1.6", features = ["full"] }
actix-rt = { version = "2.2.0" }
maplit = { version = "^1" }

View File

@ -8,7 +8,7 @@ edition = "2021"
publish = false
[dependencies]
async-std = { version = "^1.9", features = [ "attributes" ] }
async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] }
sea-orm = { path = "../../", features = [ "sqlx-all", "runtime-async-std-native-tls" ] }
serde_json = { version = "^1" }
futures = { version = "^0.3" }

View File

@ -6,6 +6,7 @@ edition = "2021"
publish = false
[workspace]
members = [".", "entity", "migration"]
[dependencies]
async-stream = { version = "^0.3" }
@ -19,18 +20,9 @@ rocket_dyn_templates = { version = "0.1.0-rc.1", features = [
"tera",
] }
serde_json = { version = "^1" }
[dependencies.sea-orm]
path = "../../" # remove this line in your own project
version = "^0.5.0"
features = ["macros", "runtime-tokio-native-tls"]
default-features = false
entity = { path = "entity" }
migration = { path = "migration" }
[dependencies.sea-orm-rocket]
path = "../../sea-orm-rocket/lib" # remove this line in your own project and use the git line
# git = "https://github.com/SeaQL/sea-orm"
[features]
default = ["sqlx-postgres"]
sqlx-mysql = ["sea-orm/sqlx-mysql"]
sqlx-postgres = ["sea-orm/sqlx-postgres"]

View File

@ -4,7 +4,7 @@
1. Modify the `url` var in `Rocket.toml` to point to your chosen database
1. Turn on the appropriate database feature for your chosen db in `Cargo.toml` (the `default = ["sqlx-postgres"]` line)
1. Turn on the appropriate database feature for your chosen db in `entity/Cargo.toml` (the `"sqlx-postgres",` line)
1. `cargo run` to start the server

View File

@ -0,0 +1,27 @@
[package]
name = "entity"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "entity"
path = "src/lib.rs"
[dependencies]
rocket = { version = "0.5.0-rc.1", features = [
"json",
] }
[dependencies.sea-orm]
# path = "../../../" # remove this line in your own project
git = "https://github.com/SeaQL/sea-orm"
branch = "migration"
version = "^0.5.0"
features = [
"macros",
"runtime-tokio-native-tls",
"sqlx-postgres",
# "sqlx-mysql",
]
default-features = false

View File

@ -0,0 +1,6 @@
#[macro_use]
extern crate rocket;
pub mod post;
pub use sea_orm;

View File

@ -0,0 +1,14 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
sea-schema = { git = "https://github.com/SeaQL/sea-schema.git", branch = "restructure-sea-query-dep", default-features = false, features = [ "migration", "debug-print" ] }
entity = { path = "../entity" }
rocket = { version = "0.5.0-rc.1" }

View File

@ -0,0 +1,37 @@
# Running Migrator CLI
- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```

View File

@ -0,0 +1,12 @@
pub use sea_schema::migration::*;
mod m20220120_000001_create_post_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220120_000001_create_post_table::Migration)]
}
}

View File

@ -0,0 +1,42 @@
use entity::post::*;
use sea_schema::migration::{
sea_query::{self, *},
*,
};
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220120_000001_create_post_table"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Entity)
.if_not_exists()
.col(
ColumnDef::new(Column::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Column::Title).string().not_null())
.col(ColumnDef::new(Column::Text).string().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Entity).to_owned())
.await
}
}

View File

@ -0,0 +1,18 @@
use migration::Migrator;
use sea_schema::migration::*;
#[async_std::main]
async fn main() {
// Setting `DATABASE_URL` environment variable
let key = "DATABASE_URL";
if std::env::var(key).is_err() {
// Getting the database URL from Rocket.toml if it's not set
let figment = rocket::Config::figment();
let database_url: String = figment
.extract_inner("databases.sea_orm.url")
.expect("Cannot find Database URL in Rocket.toml");
std::env::set_var(key, database_url);
}
cli::run_cli(Migrator).await;
}

View File

@ -10,16 +10,16 @@ use rocket::{Build, Request, Rocket};
use rocket_dyn_templates::Template;
use serde_json::json;
use entity::sea_orm;
use migration::MigratorTrait;
use sea_orm::{entity::*, query::*};
use sea_orm_rocket::{Connection, Database};
mod pool;
use pool::Db;
mod setup;
mod post;
pub use post::Entity as Post;
pub use entity::post;
pub use entity::post::Entity as Post;
const DEFAULT_POSTS_PER_PAGE: usize = 5;
@ -114,7 +114,7 @@ async fn list(
"num_pages": num_pages,
"posts": posts,
"flash": flash.map(FlashMessage::into_inner),
})
}),
)
}
@ -131,7 +131,7 @@ async fn edit(conn: Connection<'_, Db>, id: i32) -> Template {
"edit",
json! ({
"post": post,
})
}),
)
}
@ -160,13 +160,13 @@ pub fn not_found(req: &Request<'_>) -> Template {
"error/404",
json! ({
"uri": req.uri()
})
}),
)
}
async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
let conn = &Db::fetch(&rocket).unwrap().conn;
let _ = setup::create_post_table(conn).await;
let _ = migration::Migrator::up(conn, None).await;
Ok(rocket)
}

View File

@ -1,4 +1,5 @@
use async_trait::async_trait;
use entity::sea_orm;
use sea_orm::ConnectOptions;
use sea_orm_rocket::{rocket::figment::Figment, Config, Database};
use std::time::Duration;

View File

@ -1,33 +0,0 @@
use sea_orm::sea_query::{ColumnDef, TableCreateStatement};
use sea_orm::{error::*, query::*, sea_query, DbConn, ExecResult};
async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result<ExecResult, DbErr> {
let builder = db.get_database_backend();
db.execute(builder.build(stmt)).await
}
pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
.table(super::post::Entity)
.if_not_exists()
.col(
ColumnDef::new(super::post::Column::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(super::post::Column::Title)
.string()
.not_null(),
)
.col(
ColumnDef::new(super::post::Column::Text)
.string()
.not_null(),
)
.to_owned();
create_table(db, &stmt).await
}

View File

@ -9,4 +9,4 @@ publish = false
[dependencies]
sea-orm = { path = "../../", features = [ "sqlx-all", "runtime-async-std-native-tls", "debug-print" ] }
async-std = { version = "^1", features = ["attributes"] }
async-std = { version = "^1", features = ["attributes", "tokio1"] }

View File

@ -8,7 +8,7 @@ edition = "2021"
publish = false
[dependencies]
async-std = { version = "^1", features = ["attributes"] }
async-std = { version = "^1", features = ["attributes", "tokio1"] }
serde = { version = "^1", features = ["derive"] }
sea-orm = { path = "../../", features = [
"sqlx-mysql",

View File

@ -20,15 +20,16 @@ path = "src/main.rs"
[dependencies]
clap = { version = "^2.33.3" }
dotenv = { version = "^0.15" }
async-std = { version = "^1.9", features = [ "attributes" ] }
async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] }
sea-orm-codegen = { version = "^0.5.0", path = "../sea-orm-codegen" }
sea-schema = { version = "0.4.0", default-features = false, features = [
sea-schema = { git = "https://github.com/SeaQL/sea-schema.git", branch = "restructure-sea-query-dep", 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" ] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

View File

@ -6,7 +6,7 @@ Getting Help:
cargo run -- -h
```
Running Entity Generator:
## Running Entity Generator:
```sh
# MySQL (`--database-schema` option is ignored)
@ -18,3 +18,46 @@ 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
```

View File

@ -74,10 +74,30 @@ pub fn build_cli() -> App<'static, 'static> {
)
.setting(AppSettings::SubcommandRequiredElseHelp);
App::new("sea-orm")
let arg_migration_dir = Arg::with_name("MIGRATION_DIR")
.long("migration-dir")
.short("d")
.help("Migration script directory")
.takes_value(true)
.default_value("./migration");
let mut migrate_subcommands = SubCommand::with_name("migrate")
.about("Migration related commands")
.subcommand(
SubCommand::with_name("init")
.about("Initialize migration directory")
.arg(arg_migration_dir.clone()),
)
.arg(arg_migration_dir.clone());
for subcommand in sea_schema::migration::get_subcommands() {
migrate_subcommands =
migrate_subcommands.subcommand(subcommand.arg(arg_migration_dir.clone()));
}
App::new("sea-orm-cli")
.version(env!("CARGO_PKG_VERSION"))
.setting(AppSettings::VersionlessSubcommands)
.subcommand(entity_subcommand)
.subcommand(migrate_subcommands)
.arg(
Arg::with_name("VERBOSE")
.long("verbose")

View File

@ -16,6 +16,7 @@ async fn main() {
("generate", Some(matches)) => run_generate_command(matches)
.await
.unwrap_or_else(handle_error),
("migrate", Some(matches)) => run_migrate_command(matches).unwrap_or_else(handle_error),
_ => unreachable!("You should never see this message"),
}
}
@ -187,6 +188,78 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Er
Ok(())
}
fn run_migrate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
let migrate_subcommand = matches.subcommand();
// If it's `migrate init`
if let ("init", Some(args)) = migrate_subcommand {
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
let migration_dir = match migration_dir.ends_with('/') {
true => migration_dir.to_string(),
false => format!("{}/", migration_dir),
};
println!("Initializing migration directory...");
macro_rules! write_file {
($filename: literal) => {
let filepath = [&migration_dir, $filename].join("");
println!("Creating file `{}`", filepath);
let path = Path::new(&filepath);
let prefix = path.parent().unwrap();
fs::create_dir_all(prefix).unwrap();
let mut file = fs::File::create(path)?;
let content = include_str!(concat!("../template/migration/", $filename));
file.write_all(content.as_bytes())?;
};
}
write_file!("src/lib.rs");
write_file!("src/m20220101_000001_create_table.rs");
write_file!("src/main.rs");
write_file!("Cargo.toml");
write_file!("README.md");
println!("Done!");
// Early exit!
return Ok(());
}
let (subcommand, migration_dir, steps, verbose) = match migrate_subcommand {
// Catch all command with pattern `migrate xxx`
(subcommand, Some(args)) => {
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
let steps = args.value_of("NUM_MIGRATION");
let verbose = args.is_present("VERBOSE");
(subcommand, migration_dir, steps, verbose)
}
// Catch command `migrate`, this will be treated as `migrate up`
_ => {
let migration_dir = matches.value_of("MIGRATION_DIR").unwrap();
let verbose = matches.is_present("VERBOSE");
("up", migration_dir, None, verbose)
}
};
// Construct the `--manifest-path`
let manifest_path = if migration_dir.ends_with('/') {
format!("{}Cargo.toml", migration_dir)
} else {
format!("{}/Cargo.toml", migration_dir)
};
// Construct the arguments that will be supplied to `cargo` command
let mut args = vec![
"run",
"--manifest-path",
manifest_path.as_str(),
"--",
subcommand,
];
if let Some(steps) = steps {
args.extend(["-n", steps]);
}
if verbose {
args.push("-v");
}
// Run migrator CLI on user's behalf
println!("Running `cargo {}`", args.join(" "));
Command::new("cargo").args(args).spawn()?.wait()?;
Ok(())
}
fn handle_error<E>(error: E)
where
E: Display,
@ -197,11 +270,13 @@ where
#[cfg(test)]
mod tests {
use clap::AppSettings;
use super::*;
use clap::AppSettings;
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: RelativeUrlWithoutBase")]
#[should_panic(
expected = "called `Result::unwrap()` on an `Err` value: RelativeUrlWithoutBase"
)]
fn test_generate_entity_no_protocol() {
let matches = cli::build_cli()
.setting(AppSettings::NoBinaryName)
@ -216,7 +291,9 @@ mod tests {
}
#[test]
#[should_panic(expected = "There is no database name as part of the url path: postgresql://root:root@localhost:3306")]
#[should_panic(
expected = "There is no database name as part of the url path: postgresql://root:root@localhost:3306"
)]
fn test_generate_entity_no_database_section() {
let matches = cli::build_cli()
.setting(AppSettings::NoBinaryName)
@ -231,7 +308,9 @@ mod tests {
}
#[test]
#[should_panic(expected = "There is no database name as part of the url path: mysql://root:root@localhost:3306/")]
#[should_panic(
expected = "There is no database name as part of the url path: mysql://root:root@localhost:3306/"
)]
fn test_generate_entity_no_database_path() {
let matches = cli::build_cli()
.setting(AppSettings::NoBinaryName)

View File

@ -0,0 +1,12 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
sea-schema = { git = "https://github.com/SeaQL/sea-schema.git", branch = "restructure-sea-query-dep", default-features = false, features = [ "migration", "debug-print" ] }

View File

@ -0,0 +1,37 @@
# Running Migrator CLI
- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```

View File

@ -0,0 +1,12 @@
pub use sea_schema::migration::*;
mod m20220101_000001_create_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220101_000001_create_table::Migration)]
}
}

View File

@ -0,0 +1,23 @@
use sea_schema::migration::{
sea_query::{self, *},
*,
};
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220101_000001_create_table"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
todo!()
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
todo!()
}
}

View File

@ -0,0 +1,7 @@
use migration::Migrator;
use sea_schema::migration::*;
#[async_std::main]
async fn main() {
cli::run_cli(Migrator).await;
}

View File

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

View File

@ -7,10 +7,7 @@ use std::{future::Future, pin::Pin};
/// Creates constraints for any structure that can create a database connection
/// and execute SQL statements
#[async_trait::async_trait]
pub trait ConnectionTrait<'a>: Sync {
/// Create a stream for the [QueryResult]
type Stream: Stream<Item = Result<QueryResult, DbErr>>;
pub trait ConnectionTrait: Sync {
/// Fetch the database backend as specified in [DbBackend].
/// This depends on feature flags enabled.
fn get_database_backend(&self) -> DbBackend;
@ -24,12 +21,34 @@ pub trait ConnectionTrait<'a>: Sync {
/// Execute a [Statement] and return a collection Vec<[QueryResult]> on success
async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr>;
/// Check if the connection supports `RETURNING` syntax on insert and update
fn support_returning(&self) -> bool {
let db_backend = self.get_database_backend();
db_backend.support_returning()
}
/// Check if the connection is a test connection for the Mock database
fn is_mock_connection(&self) -> bool {
false
}
}
/// Stream query results
#[async_trait::async_trait]
pub trait StreamTrait<'a>: Sync {
/// Create a stream for the [QueryResult]
type Stream: Stream<Item = Result<QueryResult, DbErr>>;
/// Execute a [Statement] and return a stream of results
fn stream(
&'a self,
stmt: Statement,
) -> Pin<Box<dyn Future<Output = Result<Self::Stream, DbErr>> + 'a>>;
}
/// Spawn database transaction
#[async_trait::async_trait]
pub trait TransactionTrait {
/// Execute SQL `BEGIN` transaction.
/// Returns a Transaction that can be committed or rolled back
async fn begin(&self) -> Result<DatabaseTransaction, DbErr>;
@ -44,15 +63,4 @@ pub trait ConnectionTrait<'a>: Sync {
+ Send,
T: Send,
E: std::error::Error + Send;
/// Check if the connection supports `RETURNING` syntax on insert and update
fn support_returning(&self) -> bool {
let db_backend = self.get_database_backend();
db_backend.support_returning()
}
/// Check if the connection is a test connection for the Mock database
fn is_mock_connection(&self) -> bool {
false
}
}

View File

@ -1,6 +1,6 @@
use crate::{
error::*, ConnectionTrait, DatabaseTransaction, ExecResult, QueryResult, Statement,
StatementBuilder, TransactionError,
StatementBuilder, StreamTrait, TransactionError, TransactionTrait,
};
use sea_query::{MysqlQueryBuilder, PostgresQueryBuilder, QueryBuilder, SqliteQueryBuilder};
use std::{future::Future, pin::Pin};
@ -89,9 +89,7 @@ impl std::fmt::Debug for DatabaseConnection {
}
#[async_trait::async_trait]
impl<'a> ConnectionTrait<'a> for DatabaseConnection {
type Stream = crate::QueryStream;
impl ConnectionTrait for DatabaseConnection {
fn get_database_backend(&self) -> DbBackend {
match self {
#[cfg(feature = "sqlx-mysql")]
@ -151,6 +149,16 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection {
}
}
#[cfg(feature = "mock")]
fn is_mock_connection(&self) -> bool {
matches!(self, DatabaseConnection::MockDatabaseConnection(_))
}
}
#[async_trait::async_trait]
impl<'a> StreamTrait<'a> for DatabaseConnection {
type Stream = crate::QueryStream;
#[instrument(level = "trace")]
fn stream(
&'a self,
@ -172,7 +180,10 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection {
})
})
}
}
#[async_trait::async_trait]
impl TransactionTrait for DatabaseConnection {
#[instrument(level = "trace")]
async fn begin(&self) -> Result<DatabaseTransaction, DbErr> {
match self {
@ -221,11 +232,6 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection {
DatabaseConnection::Disconnected => panic!("Disconnected"),
}
}
#[cfg(feature = "mock")]
fn is_mock_connection(&self) -> bool {
matches!(self, DatabaseConnection::MockDatabaseConnection(_))
}
}
#[cfg(feature = "mock")]

View File

@ -311,8 +311,8 @@ impl OpenTransaction {
#[cfg(feature = "mock")]
mod tests {
use crate::{
entity::*, tests_cfg::*, ConnectionTrait, DbBackend, DbErr, MockDatabase, Statement,
Transaction, TransactionError,
entity::*, tests_cfg::*, DbBackend, DbErr, MockDatabase, Statement, Transaction,
TransactionError, TransactionTrait,
};
use pretty_assertions::assert_eq;

View File

@ -124,6 +124,10 @@ build_schema_stmt!(sea_query::TableDropStatement);
build_schema_stmt!(sea_query::TableAlterStatement);
build_schema_stmt!(sea_query::TableRenameStatement);
build_schema_stmt!(sea_query::TableTruncateStatement);
build_schema_stmt!(sea_query::IndexCreateStatement);
build_schema_stmt!(sea_query::IndexDropStatement);
build_schema_stmt!(sea_query::ForeignKeyCreateStatement);
build_schema_stmt!(sea_query::ForeignKeyDropStatement);
macro_rules! build_type_stmt {
($stmt: ty) => {

View File

@ -156,6 +156,8 @@ impl QueryStream {
}
#[cfg(feature = "mock")]
InnerConnection::Mock(c) => c.fetch(stmt),
#[allow(unreachable_patterns)]
_ => unreachable!(),
},
}
.build()

View File

@ -85,6 +85,8 @@ impl<'a> TransactionStream<'a> {
}
#[cfg(feature = "mock")]
InnerConnection::Mock(c) => c.fetch(stmt),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
})
},

View File

@ -1,6 +1,6 @@
use crate::{
debug_print, ConnectionTrait, DbBackend, DbErr, ExecResult, InnerConnection, QueryResult,
Statement, TransactionStream,
Statement, StreamTrait, TransactionStream, TransactionTrait,
};
#[cfg(feature = "sqlx-dep")]
use crate::{sqlx_error_to_exec_err, sqlx_error_to_query_err};
@ -226,6 +226,8 @@ impl DatabaseTransaction {
InnerConnection::Mock(c) => {
c.rollback();
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
} else {
//this should never happen
@ -242,9 +244,7 @@ impl Drop for DatabaseTransaction {
}
#[async_trait::async_trait]
impl<'a> ConnectionTrait<'a> for DatabaseTransaction {
type Stream = TransactionStream<'a>;
impl ConnectionTrait for DatabaseTransaction {
fn get_database_backend(&self) -> DbBackend {
// this way we don't need to lock
self.backend
@ -278,6 +278,8 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction {
}
#[cfg(feature = "mock")]
InnerConnection::Mock(conn) => return conn.execute(stmt),
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
#[cfg(feature = "sqlx-dep")]
_res.map_err(sqlx_error_to_exec_err)
@ -305,6 +307,8 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction {
}
#[cfg(feature = "mock")]
InnerConnection::Mock(conn) => return conn.query_one(stmt),
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
#[cfg(feature = "sqlx-dep")]
if let Err(sqlx::Error::RowNotFound) = _res {
@ -345,10 +349,17 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction {
}
#[cfg(feature = "mock")]
InnerConnection::Mock(conn) => return conn.query_all(stmt),
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
#[cfg(feature = "sqlx-dep")]
_res.map_err(sqlx_error_to_query_err)
}
}
#[async_trait::async_trait]
impl<'a> StreamTrait<'a> for DatabaseTransaction {
type Stream = TransactionStream<'a>;
#[instrument(level = "trace")]
fn stream(
@ -364,7 +375,10 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction {
.await)
})
}
}
#[async_trait::async_trait]
impl TransactionTrait for DatabaseTransaction {
#[instrument(level = "trace")]
async fn begin(&self) -> Result<DatabaseTransaction, DbErr> {
DatabaseTransaction::begin(

View File

@ -276,7 +276,7 @@ pub trait ActiveModelTrait: Clone + Debug {
where
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
Self: ActiveModelBehavior + 'a,
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let am = ActiveModelBehavior::before_save(self, true)?;
let model = <Self::Entity as EntityTrait>::insert(am)
@ -398,7 +398,7 @@ pub trait ActiveModelTrait: Clone + Debug {
where
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
Self: ActiveModelBehavior + 'a,
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let am = ActiveModelBehavior::before_save(self, false)?;
let model: <Self::Entity as EntityTrait>::Model = Self::Entity::update(am).exec(db).await?;
@ -411,7 +411,7 @@ pub trait ActiveModelTrait: Clone + Debug {
where
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
Self: ActiveModelBehavior + 'a,
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let mut is_update = true;
for key in <Self::Entity as EntityTrait>::PrimaryKey::iter() {
@ -475,7 +475,7 @@ pub trait ActiveModelTrait: Clone + Debug {
async fn delete<'a, C>(self, db: &'a C) -> Result<DeleteResult, DbErr>
where
Self: ActiveModelBehavior + 'a,
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let am = ActiveModelBehavior::before_delete(self)?;
let am_clone = am.clone();

View File

@ -41,7 +41,7 @@ pub trait ModelTrait: Clone + Send + Debug {
async fn delete<'a, A, C>(self, db: &'a C) -> Result<DeleteResult, DbErr>
where
Self: IntoActiveModel<A>,
C: ConnectionTrait<'a>,
C: ConnectionTrait,
A: ActiveModelTrait<Entity = Self::Entity> + ActiveModelBehavior + Send + 'a,
{
self.into_active_model().delete(db).await

View File

@ -24,7 +24,7 @@ where
/// Execute a DELETE operation on one ActiveModel
pub fn exec<C>(self, db: &'a C) -> impl Future<Output = Result<DeleteResult, DbErr>> + '_
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
// so that self is dropped before entering await
exec_delete_only(self.query, db)
@ -38,7 +38,7 @@ where
/// Execute a DELETE operation on many ActiveModels
pub fn exec<C>(self, db: &'a C) -> impl Future<Output = Result<DeleteResult, DbErr>> + '_
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
// so that self is dropped before entering await
exec_delete_only(self.query, db)
@ -54,7 +54,7 @@ impl Deleter {
/// Execute a DELETE operation
pub fn exec<'a, C>(self, db: &'a C) -> impl Future<Output = Result<DeleteResult, DbErr>> + '_
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let builder = db.get_database_backend();
exec_delete(builder.build(&self.query), db)
@ -63,14 +63,14 @@ impl Deleter {
async fn exec_delete_only<'a, C>(query: DeleteStatement, db: &'a C) -> Result<DeleteResult, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
Deleter::new(query).exec(db).await
}
async fn exec_delete<'a, C>(statement: Statement, db: &'a C) -> Result<DeleteResult, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let result = db.execute(statement).await?;
Ok(DeleteResult {

View File

@ -46,6 +46,8 @@ impl ExecResult {
}
#[cfg(feature = "mock")]
ExecResultHolder::Mock(result) => result.last_insert_id,
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
@ -60,6 +62,8 @@ impl ExecResult {
ExecResultHolder::SqlxSqlite(result) => result.rows_affected(),
#[cfg(feature = "mock")]
ExecResultHolder::Mock(result) => result.rows_affected,
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}

View File

@ -36,7 +36,7 @@ where
#[allow(unused_mut)]
pub fn exec<'a, C>(self, db: &'a C) -> impl Future<Output = Result<InsertResult<A>, DbErr>> + '_
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
A: 'a,
{
// so that self is dropped before entering await
@ -58,7 +58,7 @@ where
) -> impl Future<Output = Result<<A::Entity as EntityTrait>::Model, DbErr>> + '_
where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait<'a>,
C: ConnectionTrait,
A: 'a,
{
Inserter::<A>::new(self.primary_key, self.query).exec_with_returning(db)
@ -81,7 +81,7 @@ where
/// Execute an insert operation
pub fn exec<'a, C>(self, db: &'a C) -> impl Future<Output = Result<InsertResult<A>, DbErr>> + '_
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
A: 'a,
{
let builder = db.get_database_backend();
@ -95,7 +95,7 @@ where
) -> impl Future<Output = Result<<A::Entity as EntityTrait>::Model, DbErr>> + '_
where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait<'a>,
C: ConnectionTrait,
A: 'a,
{
exec_insert_with_returning::<A, _>(self.primary_key, self.query, db)
@ -108,7 +108,7 @@ async fn exec_insert<'a, A, C>(
db: &'a C,
) -> Result<InsertResult<A>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
A: ActiveModelTrait,
{
type PrimaryKey<A> = <<A as ActiveModelTrait>::Entity as EntityTrait>::PrimaryKey;
@ -143,7 +143,7 @@ async fn exec_insert_with_returning<'a, A, C>(
) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait<'a>,
C: ConnectionTrait,
A: ActiveModelTrait,
{
let db_backend = db.get_database_backend();

View File

@ -14,7 +14,7 @@ pub type PinBoxStream<'db, Item> = Pin<Box<dyn Stream<Item = Item> + 'db>>;
#[derive(Clone, Debug)]
pub struct Paginator<'db, C, S>
where
C: ConnectionTrait<'db>,
C: ConnectionTrait,
S: SelectorTrait + 'db,
{
pub(crate) query: SelectStatement,
@ -28,7 +28,7 @@ where
impl<'db, C, S> Paginator<'db, C, S>
where
C: ConnectionTrait<'db>,
C: ConnectionTrait,
S: SelectorTrait + 'db,
{
/// Fetch a specific page; page index starts from zero
@ -184,7 +184,7 @@ where
/// A Trait for any type that can paginate results
pub trait PaginatorTrait<'db, C>
where
C: ConnectionTrait<'db>,
C: ConnectionTrait,
{
/// Select operation
type Selector: SelectorTrait + Send + Sync + 'db;
@ -203,7 +203,7 @@ where
impl<'db, C, S> PaginatorTrait<'db, C> for Selector<S>
where
C: ConnectionTrait<'db>,
C: ConnectionTrait,
S: SelectorTrait + Send + Sync + 'db,
{
type Selector = S;
@ -221,7 +221,7 @@ where
impl<'db, C, M, E> PaginatorTrait<'db, C> for Select<E>
where
C: ConnectionTrait<'db>,
C: ConnectionTrait,
E: EntityTrait<Model = M>,
M: FromQueryResult + Sized + Send + Sync + 'db,
{
@ -234,7 +234,7 @@ where
impl<'db, C, M, N, E, F> PaginatorTrait<'db, C> for SelectTwo<E, F>
where
C: ConnectionTrait<'db>,
C: ConnectionTrait,
E: EntityTrait<Model = M>,
F: EntityTrait<Model = N>,
M: FromQueryResult + Sized + Send + Sync + 'db,

View File

@ -75,6 +75,8 @@ impl fmt::Debug for QueryResultRow {
Self::SqlxSqlite(_) => write!(f, "QueryResultRow::SqlxSqlite cannot be inspected"),
#[cfg(feature = "mock")]
Self::Mock(row) => write!(f, "{:?}", row),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
@ -124,6 +126,8 @@ macro_rules! try_getable_all {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
@ -160,6 +164,8 @@ macro_rules! try_getable_unsigned {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
@ -193,6 +199,8 @@ macro_rules! try_getable_mysql {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
@ -236,6 +244,8 @@ macro_rules! try_getable_date_time {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
@ -321,6 +331,8 @@ impl TryGetable for Decimal {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}

View File

@ -1,7 +1,7 @@
use crate::{
error::*, ConnectionTrait, EntityTrait, FromQueryResult, IdenStatic, Iterable, ModelTrait,
PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo, SelectTwoMany, Statement,
TryGetableMany,
StreamTrait, TryGetableMany,
};
use futures::{Stream, TryStreamExt};
use sea_query::SelectStatement;
@ -256,7 +256,7 @@ where
/// Get one Model from the SELECT query
pub async fn one<'a, C>(self, db: &C) -> Result<Option<E::Model>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
self.into_model().one(db).await
}
@ -264,7 +264,7 @@ where
/// Get all Models from the SELECT query
pub async fn all<'a, C>(self, db: &C) -> Result<Vec<E::Model>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
self.into_model().all(db).await
}
@ -275,7 +275,7 @@ where
db: &'a C,
) -> Result<impl Stream<Item = Result<E::Model, DbErr>> + 'b, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait + StreamTrait<'a>,
{
self.into_model().stream(db).await
}
@ -310,7 +310,7 @@ where
/// Get one Model from the Select query
pub async fn one<'a, C>(self, db: &C) -> Result<Option<(E::Model, Option<F::Model>)>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
self.into_model().one(db).await
}
@ -318,7 +318,7 @@ where
/// Get all Models from the Select query
pub async fn all<'a, C>(self, db: &C) -> Result<Vec<(E::Model, Option<F::Model>)>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
self.into_model().all(db).await
}
@ -329,7 +329,7 @@ where
db: &'a C,
) -> Result<impl Stream<Item = Result<(E::Model, Option<F::Model>), DbErr>> + 'b, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait + StreamTrait<'a>,
{
self.into_model().stream(db).await
}
@ -364,7 +364,7 @@ where
/// Select one Model
pub async fn one<'a, C>(self, db: &C) -> Result<Option<(E::Model, Option<F::Model>)>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
self.into_model().one(db).await
}
@ -375,7 +375,7 @@ where
db: &'a C,
) -> Result<impl Stream<Item = Result<(E::Model, Option<F::Model>), DbErr>> + 'b, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait + StreamTrait<'a>,
{
self.into_model().stream(db).await
}
@ -383,7 +383,7 @@ where
/// Get all Models from the select operation
pub async fn all<'a, C>(self, db: &C) -> Result<Vec<(E::Model, Vec<F::Model>)>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let rows = self.into_model().all(db).await?;
Ok(consolidate_query_result::<E, F>(rows))
@ -421,7 +421,7 @@ where
fn into_selector_raw<'a, C>(self, db: &C) -> SelectorRaw<S>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let builder = db.get_database_backend();
let stmt = builder.build(&self.query);
@ -434,7 +434,7 @@ where
/// Get an item from the Select query
pub async fn one<'a, C>(mut self, db: &C) -> Result<Option<S::Item>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
self.query.limit(1);
self.into_selector_raw(db).one(db).await
@ -443,7 +443,7 @@ where
/// Get all items from the Select query
pub async fn all<'a, C>(self, db: &C) -> Result<Vec<S::Item>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
self.into_selector_raw(db).all(db).await
}
@ -454,7 +454,7 @@ where
db: &'a C,
) -> Result<Pin<Box<dyn Stream<Item = Result<S::Item, DbErr>> + 'b>>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait + StreamTrait<'a>,
S: 'b,
{
self.into_selector_raw(db).stream(db).await
@ -672,7 +672,7 @@ where
/// ```
pub async fn one<'a, C>(self, db: &C) -> Result<Option<S::Item>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let row = db.query_one(self.stmt).await?;
match row {
@ -723,7 +723,7 @@ where
/// ```
pub async fn all<'a, C>(self, db: &C) -> Result<Vec<S::Item>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let rows = db.query_all(self.stmt).await?;
let mut models = Vec::new();
@ -739,7 +739,7 @@ where
db: &'a C,
) -> Result<Pin<Box<dyn Stream<Item = Result<S::Item, DbErr>> + 'b>>, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait + StreamTrait<'a>,
S: 'b,
{
let stream = db.stream(self.stmt).await?;

View File

@ -1,6 +1,6 @@
use crate::{
error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Iterable, SelectModel,
SelectorRaw, Statement, UpdateMany, UpdateOne,
error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, IntoActiveModel,
Iterable, SelectModel, SelectorRaw, Statement, UpdateMany, UpdateOne,
};
use sea_query::{Alias, Expr, FromValueTuple, Query, UpdateStatement};
use std::future::Future;
@ -26,7 +26,8 @@ where
/// Execute an update operation on an ActiveModel
pub async fn exec<'b, C>(self, db: &'b C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
where
C: ConnectionTrait<'b>,
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait,
{
// so that self is dropped before entering await
exec_update_and_return_updated(self.query, self.model, db).await
@ -40,7 +41,7 @@ where
/// Execute an update operation on multiple ActiveModels
pub fn exec<C>(self, db: &'a C) -> impl Future<Output = Result<UpdateResult, DbErr>> + '_
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
// so that self is dropped before entering await
exec_update_only(self.query, db)
@ -65,7 +66,7 @@ impl Updater {
/// Execute an update operation
pub fn exec<'a, C>(self, db: &'a C) -> impl Future<Output = Result<UpdateResult, DbErr>> + '_
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let builder = db.get_database_backend();
exec_update(builder.build(&self.query), db, self.check_record_exists)
@ -74,7 +75,7 @@ impl Updater {
async fn exec_update_only<'a, C>(query: UpdateStatement, db: &'a C) -> Result<UpdateResult, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
Updater::new(query).exec(db).await
}
@ -86,7 +87,7 @@ async fn exec_update_and_return_updated<'a, A, C>(
) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
where
A: ActiveModelTrait,
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
match db.support_returning() {
true => {
@ -141,7 +142,7 @@ async fn exec_update<'a, C>(
check_record_exists: bool,
) -> Result<UpdateResult, DbErr>
where
C: ConnectionTrait<'a>,
C: ConnectionTrait,
{
let result = db.execute(statement).await?;
if check_record_exists && result.rows_affected() == 0 {

View File

@ -23,5 +23,6 @@ pub use update::*;
pub use util::*;
pub use crate::{
ConnectionTrait, InsertResult, PaginatorTrait, Statement, UpdateResult, Value, Values,
ConnectionTrait, InsertResult, PaginatorTrait, Statement, StreamTrait, TransactionTrait,
UpdateResult, Value, Values,
};