Merge pull request #292 from SeaQL/returning

Returning
This commit is contained in:
Chris Tsang 2021-11-17 17:41:28 +08:00 committed by GitHub
commit 0deedddf28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 888 additions and 199 deletions

View File

@ -288,6 +288,7 @@ jobs:
name: Examples
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
path: [basic, actix_example, actix4_example, axum_example, rocket_example]
@ -312,6 +313,7 @@ jobs:
if: ${{ (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-issues == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
path: [86, 249, 262, 319, 324]
@ -350,6 +352,7 @@ jobs:
env:
DATABASE_URL: "sqlite::memory:"
strategy:
fail-fast: false
matrix:
runtime: [async-std, actix, tokio]
tls: [native-tls, rustls]
@ -392,6 +395,7 @@ jobs:
env:
DATABASE_URL: "mysql://root:@localhost"
strategy:
fail-fast: false
matrix:
version: [8.0, 5.7]
runtime: [async-std, actix, tokio]
@ -452,8 +456,9 @@ jobs:
env:
DATABASE_URL: "mysql://root:@localhost"
strategy:
fail-fast: false
matrix:
version: [10.6]
version: [10.6, 10.5, 10.4]
runtime: [async-std, actix, tokio]
tls: [native-tls]
services:
@ -512,8 +517,9 @@ jobs:
env:
DATABASE_URL: "postgres://root:root@localhost"
strategy:
fail-fast: false
matrix:
version: [13.3, 12.7, 11.12, 10.17, 9.6.22]
version: [13, 12, 11, 10, 9]
runtime: [tokio]
tls: [native-tls]
services:

View File

@ -45,6 +45,12 @@ pub trait ConnectionTrait<'a>: Sync {
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

@ -267,6 +267,11 @@ impl DbBackend {
Self::Sqlite => Box::new(SqliteQueryBuilder),
}
}
/// Check if the database supports `RETURNING` syntax on insert and update
pub fn support_returning(&self) -> bool {
matches!(self, Self::Postgres)
}
}
#[cfg(test)]

View File

@ -3,7 +3,12 @@
//! Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1.
//!
//! ```
//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*, DatabaseConnection, DbBackend, MockDatabase, Transaction, IntoMockRow};
//! # use sea_orm::{error::*, tests_cfg::*, *};
//! #
//! # #[smol_potat::main]
//! # #[cfg(feature = "mock")]
//! # pub async fn main() -> Result<(), DbErr> {
//! #
//! # let db = MockDatabase::new(DbBackend::Postgres)
//! # .append_query_results(vec![
//! # vec![cake::Model {
@ -19,7 +24,7 @@
//! # .into_mock_row()],
//! # ])
//! # .into_connection();
//! # let _: Result<(), DbErr> = smol::block_on(async {
//! #
//! // execute multiple queries in parallel
//! let cakes_and_fruits: (Vec<cake::Model>, Vec<fruit::Model>) =
//! futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?;
@ -53,7 +58,7 @@
//! # ]
//! # );
//! # Ok(())
//! # });
//! # }
//! ```
//!
//! 2. Dynamic

View File

@ -19,7 +19,8 @@ pub struct MockDatabaseConnector;
/// Defines a connection for the [MockDatabase]
#[derive(Debug)]
pub struct MockDatabaseConnection {
counter: AtomicUsize,
execute_counter: AtomicUsize,
query_counter: AtomicUsize,
mocker: Mutex<Box<dyn MockDatabaseTrait>>,
}
@ -100,7 +101,8 @@ impl MockDatabaseConnection {
M: MockDatabaseTrait,
{
Self {
counter: AtomicUsize::new(0),
execute_counter: AtomicUsize::new(0),
query_counter: AtomicUsize::new(0),
mocker: Mutex::new(Box::new(m)),
}
}
@ -117,14 +119,14 @@ impl MockDatabaseConnection {
/// Execute the SQL statement in the [MockDatabase]
pub fn execute(&self, statement: Statement) -> Result<ExecResult, DbErr> {
debug_print!("{}", statement);
let counter = self.counter.fetch_add(1, Ordering::SeqCst);
let counter = self.execute_counter.fetch_add(1, Ordering::SeqCst);
self.mocker.lock().unwrap().execute(counter, statement)
}
/// Return one [QueryResult] if the query was successful
pub fn query_one(&self, statement: Statement) -> Result<Option<QueryResult>, DbErr> {
debug_print!("{}", statement);
let counter = self.counter.fetch_add(1, Ordering::SeqCst);
let counter = self.query_counter.fetch_add(1, Ordering::SeqCst);
let result = self.mocker.lock().unwrap().query(counter, statement)?;
Ok(result.into_iter().next())
}
@ -132,7 +134,7 @@ impl MockDatabaseConnection {
/// Return all [QueryResult]s if the query was successful
pub fn query_all(&self, statement: Statement) -> Result<Vec<QueryResult>, DbErr> {
debug_print!("{}", statement);
let counter = self.counter.fetch_add(1, Ordering::SeqCst);
let counter = self.query_counter.fetch_add(1, Ordering::SeqCst);
self.mocker.lock().unwrap().query(counter, statement)
}

View File

@ -140,6 +140,113 @@ pub trait ActiveModelTrait: Clone + Debug {
}
/// Perform an `INSERT` operation on the ActiveModel
///
/// # Example (Postgres)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![
/// # vec![cake::Model {
/// # id: 15,
/// # name: "Apple Pie".to_owned(),
/// # }],
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// let apple = cake::ActiveModel {
/// name: Set("Apple Pie".to_owned()),
/// ..Default::default()
/// };
///
/// assert_eq!(
/// apple.insert(&db).await?,
/// cake::Model {
/// id: 15,
/// name: "Apple Pie".to_owned(),
/// }
/// .into_active_model()
/// );
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id", "name""#,
/// vec!["Apple Pie".into()]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
///
/// # Example (MySQL)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::MySql)
/// # .append_query_results(vec![
/// # vec![cake::Model {
/// # id: 15,
/// # name: "Apple Pie".to_owned(),
/// # }],
/// # ])
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 15,
/// # rows_affected: 1,
/// # },
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// let apple = cake::ActiveModel {
/// name: Set("Apple Pie".to_owned()),
/// ..Default::default()
/// };
///
/// assert_eq!(
/// apple.insert(&db).await?,
/// cake::Model {
/// id: 15,
/// name: "Apple Pie".to_owned(),
/// }
/// .into_active_model()
/// );
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![
/// Transaction::from_sql_and_values(
/// DbBackend::MySql,
/// r#"INSERT INTO `cake` (`name`) VALUES (?)"#,
/// vec!["Apple Pie".into()]
/// ),
/// Transaction::from_sql_and_values(
/// DbBackend::MySql,
/// r#"SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = ? LIMIT ?"#,
/// vec![15.into(), 1u64.into()]
/// )
/// ]
/// );
/// #
/// # Ok(())
/// # }
/// ```
async fn insert<'a, C>(self, db: &'a C) -> Result<Self, DbErr>
where
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
@ -147,20 +254,126 @@ pub trait ActiveModelTrait: Clone + Debug {
C: ConnectionTrait<'a>,
{
let am = ActiveModelBehavior::before_save(self, true)?;
let res = <Self::Entity as EntityTrait>::insert(am).exec(db).await?;
let found = <Self::Entity as EntityTrait>::find_by_id(res.last_insert_id)
.one(db)
let am = <Self::Entity as EntityTrait>::insert(am)
.exec_with_returning(db)
.await?;
let am = match found {
Some(model) => model.into_active_model(),
None => return Err(DbErr::Exec("Failed to find inserted item".to_owned())),
};
ActiveModelBehavior::after_save(am, true)
}
/// Perform the `UPDATE` operation on an ActiveModel
///
/// # Example (Postgres)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![
/// # vec![fruit::Model {
/// # id: 1,
/// # name: "Orange".to_owned(),
/// # cake_id: None,
/// # }],
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit};
///
/// let orange = fruit::ActiveModel {
/// id: Set(1),
/// name: Set("Orange".to_owned()),
/// ..Default::default()
/// };
///
/// assert_eq!(
/// orange.update(&db).await?,
/// fruit::Model {
/// id: 1,
/// name: "Orange".to_owned(),
/// cake_id: None,
/// }
/// .into_active_model()
/// );
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2 RETURNING "id", "name", "cake_id""#,
/// vec!["Orange".into(), 1i32.into()]
/// )]);
/// #
/// # Ok(())
/// # }
/// ```
///
/// # Example (MySQL)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::MySql)
/// # .append_query_results(vec![
/// # vec![fruit::Model {
/// # id: 1,
/// # name: "Orange".to_owned(),
/// # cake_id: None,
/// # }],
/// # ])
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 0,
/// # rows_affected: 1,
/// # },
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit};
///
/// let orange = fruit::ActiveModel {
/// id: Set(1),
/// name: Set("Orange".to_owned()),
/// ..Default::default()
/// };
///
/// assert_eq!(
/// orange.update(&db).await?,
/// fruit::Model {
/// id: 1,
/// name: "Orange".to_owned(),
/// cake_id: None,
/// }
/// .into_active_model()
/// );
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![
/// Transaction::from_sql_and_values(
/// DbBackend::MySql,
/// r#"UPDATE `fruit` SET `name` = ? WHERE `fruit`.`id` = ?"#,
/// vec!["Orange".into(), 1i32.into()]
/// ),
/// Transaction::from_sql_and_values(
/// DbBackend::MySql,
/// r#"SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = ? LIMIT ?"#,
/// vec![1i32.into(), 1u64.into()]
/// )]);
/// #
/// # Ok(())
/// # }
/// ```
async fn update<'a, C>(self, db: &'a C) -> Result<Self, DbErr>
where
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
Self: ActiveModelBehavior + 'a,
C: ConnectionTrait<'a>,
{
@ -195,6 +408,48 @@ pub trait ActiveModelTrait: Clone + Debug {
}
/// Delete an active model by its primary key
///
/// # Example
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 0,
/// # rows_affected: 1,
/// # },
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit};
///
/// let orange = fruit::ActiveModel {
/// id: Set(3),
/// ..Default::default()
/// };
///
/// let delete_result = orange.delete(&db).await?;
///
/// assert_eq!(delete_result.rows_affected, 1);
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#,
/// vec![3i32.into()]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
async fn delete<'a, C>(self, db: &'a C) -> Result<DeleteResult, DbErr>
where
Self: ActiveModelBehavior + 'a,

View File

@ -95,8 +95,11 @@ pub trait EntityTrait: EntityName {
/// # Example
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![
@ -121,8 +124,6 @@ pub trait EntityTrait: EntityName {
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// assert_eq!(
/// cake::Entity::find().one(&db).await?,
/// Some(cake::Model {
@ -144,9 +145,6 @@ pub trait EntityTrait: EntityName {
/// },
/// ]
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -163,6 +161,9 @@ pub trait EntityTrait: EntityName {
/// ),
/// ]
/// );
/// #
/// # Ok(())
/// # }
/// ```
fn find() -> Select<Self> {
Select::new()
@ -173,8 +174,11 @@ pub trait EntityTrait: EntityName {
/// # Example
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![
@ -189,8 +193,6 @@ pub trait EntityTrait: EntityName {
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// assert_eq!(
/// cake::Entity::find_by_id(11).all(&db).await?,
/// vec![cake::Model {
@ -198,9 +200,6 @@ pub trait EntityTrait: EntityName {
/// name: "Sponge Cake".to_owned(),
/// }]
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -210,11 +209,17 @@ pub trait EntityTrait: EntityName {
/// vec![11i32.into()]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
/// Find by composite key
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![
@ -229,8 +234,6 @@ pub trait EntityTrait: EntityName {
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake_filling};
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// assert_eq!(
/// cake_filling::Entity::find_by_id((2, 3)).all(&db).await?,
/// vec![cake_filling::Model {
@ -238,9 +241,6 @@ pub trait EntityTrait: EntityName {
/// filling_id: 3,
/// }]
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -252,6 +252,9 @@ pub trait EntityTrait: EntityName {
/// ].join(" ").as_str(),
/// vec![2i32.into(), 3i32.into()]
/// )]);
/// #
/// # Ok(())
/// # }
/// ```
fn find_by_id(values: <Self::PrimaryKey as PrimaryKeyTrait>::ValueType) -> Select<Self> {
let mut select = Self::find();
@ -272,13 +275,55 @@ pub trait EntityTrait: EntityName {
/// Insert an model into database
///
/// # Example
/// # Example (Postgres)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![maplit::btreemap! {
/// # "id" => Into::<Value>::into(15),
/// # }]])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// let apple = cake::ActiveModel {
/// name: Set("Apple Pie".to_owned()),
/// ..Default::default()
/// };
///
/// let insert_result = cake::Entity::insert(apple).exec(&db).await?;
///
/// assert_eq!(dbg!(insert_result.last_insert_id), 15);
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id""#,
/// vec!["Apple Pie".into()]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
///
/// # Example (MySQL)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::MySql)
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 15,
@ -294,21 +339,21 @@ pub trait EntityTrait: EntityName {
/// ..Default::default()
/// };
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let insert_result = cake::Entity::insert(apple).exec(&db).await?;
///
/// assert_eq!(insert_result.last_insert_id, 15);
/// // assert_eq!(insert_result.rows_affected, 1);
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id""#, vec!["Apple Pie".into()]
/// )]);
/// DbBackend::MySql,
/// r#"INSERT INTO `cake` (`name`) VALUES (?)"#,
/// vec!["Apple Pie".into()]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
fn insert<A>(model: A) -> Insert<A>
where
@ -319,13 +364,61 @@ pub trait EntityTrait: EntityName {
/// Insert many models into database
///
/// # Example
/// # Example (Postgres)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![maplit::btreemap! {
/// # "id" => Into::<Value>::into(28),
/// # }]])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// let apple = cake::ActiveModel {
/// name: Set("Apple Pie".to_owned()),
/// ..Default::default()
/// };
/// let orange = cake::ActiveModel {
/// name: Set("Orange Scone".to_owned()),
/// ..Default::default()
/// };
///
/// let insert_result = cake::Entity::insert_many(vec![apple, orange])
/// .exec(&db)
/// .await?;
///
/// assert_eq!(insert_result.last_insert_id, 28);
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"INSERT INTO "cake" ("name") VALUES ($1), ($2) RETURNING "id""#,
/// vec!["Apple Pie".into(), "Orange Scone".into()]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
///
/// # Example (MySQL)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::MySql)
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 28,
@ -345,22 +438,23 @@ pub trait EntityTrait: EntityName {
/// ..Default::default()
/// };
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let insert_result = cake::Entity::insert_many(vec![apple, orange]).exec(&db).await?;
/// let insert_result = cake::Entity::insert_many(vec![apple, orange])
/// .exec(&db)
/// .await?;
///
/// assert_eq!(insert_result.last_insert_id, 28);
/// // assert_eq!(insert_result.rows_affected, 2);
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1), ($2) RETURNING "id""#,
/// DbBackend::MySql,
/// r#"INSERT INTO `cake` (`name`) VALUES (?), (?)"#,
/// vec!["Apple Pie".into(), "Orange Scone".into()]
/// )]);
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
fn insert_many<A, I>(models: I) -> Insert<A>
where
@ -374,18 +468,22 @@ pub trait EntityTrait: EntityName {
///
/// - To apply where conditions / filters, see [`QueryFilter`](crate::query::QueryFilter)
///
/// # Example
/// # Example (Postgres)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 0,
/// # rows_affected: 1,
/// # },
/// # .append_query_results(vec![
/// # vec![fruit::Model {
/// # id: 1,
/// # name: "Orange".to_owned(),
/// # cake_id: None,
/// # }],
/// # ])
/// # .into_connection();
/// #
@ -397,25 +495,93 @@ pub trait EntityTrait: EntityName {
/// ..Default::default()
/// };
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// assert_eq!(
/// fruit::Entity::update(orange.clone())
/// .filter(fruit::Column::Name.contains("orange"))
/// .exec(&db)
/// .await?,
/// orange
/// fruit::Model {
/// id: 1,
/// name: "Orange".to_owned(),
/// cake_id: None,
/// }
/// .into_active_model(),
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres, r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2 AND "fruit"."name" LIKE $3"#,
/// DbBackend::Postgres,
/// r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2 AND "fruit"."name" LIKE $3 RETURNING "id", "name", "cake_id""#,
/// vec!["Orange".into(), 1i32.into(), "%orange%".into()]
/// )]);
/// #
/// # Ok(())
/// # }
/// ```
///
/// # Example (MySQL)
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::MySql)
/// # .append_exec_results(vec![
/// # MockExecResult {
/// # last_insert_id: 0,
/// # rows_affected: 1,
/// # },
/// # ])
/// # .append_query_results(vec![
/// # vec![fruit::Model {
/// # id: 1,
/// # name: "Orange".to_owned(),
/// # cake_id: None,
/// # }],
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit};
///
/// let orange = fruit::ActiveModel {
/// id: Set(1),
/// name: Set("Orange".to_owned()),
/// ..Default::default()
/// };
///
/// assert_eq!(
/// fruit::Entity::update(orange.clone())
/// .filter(fruit::Column::Name.contains("orange"))
/// .exec(&db)
/// .await?,
/// fruit::Model {
/// id: 1,
/// name: "Orange".to_owned(),
/// cake_id: None,
/// }
/// .into_active_model(),
/// );
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![
/// Transaction::from_sql_and_values(
/// DbBackend::MySql,
/// r#"UPDATE `fruit` SET `name` = ? WHERE `fruit`.`id` = ? AND `fruit`.`name` LIKE ?"#,
/// vec!["Orange".into(), 1i32.into(), "%orange%".into()]
/// ),
/// Transaction::from_sql_and_values(
/// DbBackend::MySql,
/// r#"SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = ? LIMIT ?"#,
/// vec![1i32.into(), 1u64.into()]
/// )]);
/// #
/// # Ok(())
/// # }
/// ```
fn update<A>(model: A) -> UpdateOne<A>
where
@ -431,8 +597,11 @@ pub trait EntityTrait: EntityName {
/// # Example
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_exec_results(vec![
@ -443,10 +612,13 @@ pub trait EntityTrait: EntityName {
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit, sea_query::{Expr, Value}};
/// use sea_orm::{
/// entity::*,
/// query::*,
/// sea_query::{Expr, Value},
/// tests_cfg::fruit,
/// };
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let update_result = fruit::Entity::update_many()
/// .col_expr(fruit::Column::CakeId, Expr::value(Value::Int(None)))
/// .filter(fruit::Column::Name.contains("Apple"))
@ -454,15 +626,18 @@ pub trait EntityTrait: EntityName {
/// .await?;
///
/// assert_eq!(update_result.rows_affected, 5);
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres, r#"UPDATE "fruit" SET "cake_id" = $1 WHERE "fruit"."name" LIKE $2"#, vec![Value::Int(None), "%Apple%".into()]
/// )]);
/// DbBackend::Postgres,
/// r#"UPDATE "fruit" SET "cake_id" = $1 WHERE "fruit"."name" LIKE $2"#,
/// vec![Value::Int(None), "%Apple%".into()]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
fn update_many() -> UpdateMany<Self> {
Update::many(Self::default())
@ -475,8 +650,11 @@ pub trait EntityTrait: EntityName {
/// # Example
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_exec_results(vec![
@ -494,20 +672,21 @@ pub trait EntityTrait: EntityName {
/// ..Default::default()
/// };
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let delete_result = fruit::Entity::delete(orange).exec(&db).await?;
///
/// assert_eq!(delete_result.rows_affected, 1);
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres, r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, vec![3i32.into()]
/// )]);
/// DbBackend::Postgres,
/// r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#,
/// vec![3i32.into()]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
fn delete<A>(model: A) -> DeleteOne<A>
where
@ -523,8 +702,11 @@ pub trait EntityTrait: EntityName {
/// # Example
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{entity::*, error::*, query::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_exec_results(vec![
@ -533,27 +715,34 @@ pub trait EntityTrait: EntityName {
/// # rows_affected: 5,
/// # },
/// # ])
/// # .append_query_results(vec![
/// # vec![cake::Model {
/// # id: 15,
/// # name: "Apple Pie".to_owned(),
/// # }],
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::fruit};
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let delete_result = fruit::Entity::delete_many()
/// .filter(fruit::Column::Name.contains("Apple"))
/// .exec(&db)
/// .await?;
///
/// assert_eq!(delete_result.rows_affected, 5);
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres, r#"DELETE FROM "fruit" WHERE "fruit"."name" LIKE $1"#, vec!["%Apple%".into()]
/// )]);
/// DbBackend::Postgres,
/// r#"DELETE FROM "fruit" WHERE "fruit"."name" LIKE $1"#,
/// vec!["%Apple%".into()]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
fn delete_many() -> DeleteMany<Self> {
Delete::many(Self::default())

View File

@ -47,8 +47,11 @@ pub trait FromQueryResult: Sized {
}
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
@ -67,8 +70,6 @@ pub trait FromQueryResult: Sized {
/// num_of_cakes: i32,
/// }
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let res: Vec<SelectResult> = SelectResult::find_by_statement(Statement::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"SELECT "name", COUNT(*) AS "num_of_cakes" FROM "cake" GROUP BY("name")"#,
@ -85,8 +86,6 @@ pub trait FromQueryResult: Sized {
/// },]
/// );
/// #
/// # Ok(())
/// # });
/// # assert_eq!(
/// # db.into_transaction_log(),
/// # vec![Transaction::from_sql_and_values(
@ -95,6 +94,9 @@ pub trait FromQueryResult: Sized {
/// # vec![]
/// # ),]
/// # );
/// #
/// # Ok(())
/// # }
/// ```
fn find_by_statement(stmt: Statement) -> SelectorRaw<SelectModel<Self>> {
SelectorRaw::<SelectModel<Self>>::from_statement(stmt)

View File

@ -1,8 +1,10 @@
use crate::{
error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, Insert, PrimaryKeyTrait,
Statement, TryFromU64,
error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel,
Iterable, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64,
};
use sea_query::{
Alias, Expr, FromValueTuple, Iden, InsertStatement, IntoColumnRef, Query, ValueTuple,
};
use sea_query::{FromValueTuple, InsertStatement, ValueTuple};
use std::{future::Future, marker::PhantomData};
/// Defines a structure to perform INSERT operations in an ActiveModel
@ -39,18 +41,28 @@ where
{
// so that self is dropped before entering await
let mut query = self.query;
if db.get_database_backend() == DbBackend::Postgres {
use crate::{sea_query::Query, Iterable};
if <A::Entity as EntityTrait>::PrimaryKey::iter().count() > 0 {
query.returning(
Query::select()
.columns(<A::Entity as EntityTrait>::PrimaryKey::iter())
.take(),
);
}
if db.support_returning() && <A::Entity as EntityTrait>::PrimaryKey::iter().count() > 0 {
let mut returning = Query::select();
returning.columns(
<A::Entity as EntityTrait>::PrimaryKey::iter().map(|c| c.into_column_ref()),
);
query.returning(returning);
}
Inserter::<A>::new(self.primary_key, query).exec(db)
}
/// Execute an insert operation and return the inserted model (use `RETURNING` syntax if database supported)
pub fn exec_with_returning<'a, C>(
self,
db: &'a C,
) -> impl Future<Output = Result<A, DbErr>> + '_
where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait<'a>,
A: 'a,
{
Inserter::<A>::new(self.primary_key, self.query).exec_with_returning(db)
}
}
impl<A> Inserter<A>
@ -75,6 +87,19 @@ where
let builder = db.get_database_backend();
exec_insert(self.primary_key, builder.build(&self.query), db)
}
/// Execute an insert operation and return the inserted model (use `RETURNING` syntax if database supported)
pub fn exec_with_returning<'a, C>(
self,
db: &'a C,
) -> impl Future<Output = Result<A, DbErr>> + '_
where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait<'a>,
A: 'a,
{
exec_insert_with_returning(self.primary_key, self.query, db)
}
}
async fn exec_insert<'a, A, C>(
@ -88,16 +113,15 @@ where
{
type PrimaryKey<A> = <<A as ActiveModelTrait>::Entity as EntityTrait>::PrimaryKey;
type ValueTypeOf<A> = <PrimaryKey<A> as PrimaryKeyTrait>::ValueType;
let last_insert_id_opt = match db.get_database_backend() {
DbBackend::Postgres => {
use crate::{sea_query::Iden, Iterable};
let last_insert_id_opt = match db.support_returning() {
true => {
let cols = PrimaryKey::<A>::iter()
.map(|col| col.to_string())
.collect::<Vec<_>>();
let res = db.query_one(statement).await?.unwrap();
res.try_get_many("", cols.as_ref()).ok()
}
_ => {
false => {
let last_insert_id = db.execute(statement).await?.last_insert_id();
ValueTypeOf::<A>::try_from_u64(last_insert_id).ok()
}
@ -111,3 +135,47 @@ where
};
Ok(InsertResult { last_insert_id })
}
async fn exec_insert_with_returning<'a, A, C>(
primary_key: Option<ValueTuple>,
mut insert_statement: InsertStatement,
db: &'a C,
) -> Result<A, DbErr>
where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait<'a>,
A: ActiveModelTrait,
{
let db_backend = db.get_database_backend();
let found = match db.support_returning() {
true => {
let mut returning = Query::select();
returning.exprs(<A::Entity as EntityTrait>::Column::iter().map(|c| {
let col = Expr::col(c);
let col_def = ColumnTrait::def(&c);
let col_type = col_def.get_column_type();
match col_type.get_enum_name() {
Some(_) => col.as_enum(Alias::new("text")),
None => col.into(),
}
}));
insert_statement.returning(returning);
SelectorRaw::<SelectModel<<A::Entity as EntityTrait>::Model>>::from_statement(
db_backend.build(&insert_statement),
)
.one(db)
.await?
}
false => {
let insert_res =
exec_insert::<A, _>(primary_key, db_backend.build(&insert_statement), db).await?;
<A::Entity as EntityTrait>::find_by_id(insert_res.last_insert_id)
.one(db)
.await?
}
};
match found {
Some(model) => Ok(model.into_active_model()),
None => Err(DbErr::Exec("Failed to find inserted item".to_owned())),
}
}

View File

@ -96,12 +96,23 @@ where
/// Fetch one page and increment the page counter
///
/// ```rust
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, MockDatabase, DbBackend};
/// # let owned_db = MockDatabase::new(DbBackend::Postgres).into_connection();
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let owned_db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![
/// # vec![cake::Model {
/// # id: 1,
/// # name: "Cake".to_owned(),
/// # }],
/// # vec![],
/// # ])
/// # .into_connection();
/// # let db = &owned_db;
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
/// let mut cake_pages = cake::Entity::find()
@ -113,7 +124,7 @@ where
/// }
/// #
/// # Ok(())
/// # });
/// # }
/// ```
pub async fn fetch_and_next(&mut self) -> Result<Option<Vec<S::Item>>, DbErr> {
let vec = self.fetch().await?;
@ -124,12 +135,23 @@ where
/// Convert self into an async stream
///
/// ```rust
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, MockDatabase, DbBackend};
/// # let owned_db = MockDatabase::new(DbBackend::Postgres).into_connection();
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let owned_db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![
/// # vec![cake::Model {
/// # id: 1,
/// # name: "Cake".to_owned(),
/// # }],
/// # vec![],
/// # ])
/// # .into_connection();
/// # let db = &owned_db;
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// use futures::TryStreamExt;
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
@ -143,7 +165,7 @@ where
/// }
/// #
/// # Ok(())
/// # });
/// # }
/// ```
pub fn into_stream(mut self) -> PinBoxStream<'db, Result<Vec<S::Item>, DbErr>> {
Box::pin(stream! {

View File

@ -320,8 +320,11 @@ pub trait TryGetableMany: Sized {
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError>;
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(all(feature = "mock", feature = "macros"))]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
@ -344,8 +347,6 @@ pub trait TryGetableMany: Sized {
/// NumOfCakes,
/// }
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let res: Vec<(String, i32)> =
/// <(String, i32)>::find_by_statement::<ResultCol>(Statement::from_sql_and_values(
/// DbBackend::Postgres,
@ -362,9 +363,6 @@ pub trait TryGetableMany: Sized {
/// ("New York Cheese".to_owned(), 1),
/// ]
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -374,6 +372,9 @@ pub trait TryGetableMany: Sized {
/// vec![]
/// ),]
/// );
/// #
/// # Ok(())
/// # }
/// ```
fn find_by_statement<C>(stmt: Statement) -> SelectorRaw<SelectGetableValue<Self, C>>
where

View File

@ -143,8 +143,11 @@ where
}
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(all(feature = "mock", feature = "macros"))]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
@ -164,8 +167,6 @@ where
/// CakeName,
/// }
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let res: Vec<String> = cake::Entity::find()
/// .select_only()
/// .column_as(cake::Column::Name, QueryAs::CakeName)
@ -177,9 +178,6 @@ where
/// res,
/// vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned()]
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -189,11 +187,17 @@ where
/// vec![]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(all(feature = "mock", feature = "macros"))]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
@ -212,8 +216,6 @@ where
/// NumOfCakes,
/// }
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let res: Vec<(String, i64)> = cake::Entity::find()
/// .select_only()
/// .column_as(cake::Column::Name, QueryAs::CakeName)
@ -224,9 +226,6 @@ where
/// .await?;
///
/// assert_eq!(res, vec![("Chocolate Forest".to_owned(), 2i64)]);
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -241,6 +240,9 @@ where
/// vec![]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
pub fn into_values<T, C>(self) -> Selector<SelectGetableValue<T, C>>
where
@ -490,8 +492,11 @@ where
}
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
@ -514,8 +519,6 @@ where
/// num_of_cakes: i32,
/// }
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let res: Vec<SelectResult> = cake::Entity::find()
/// .from_raw_sql(Statement::from_sql_and_values(
/// DbBackend::Postgres,
@ -539,9 +542,6 @@ where
/// },
/// ]
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -551,6 +551,9 @@ where
/// vec![]
/// ),]
/// );
/// #
/// # Ok(())
/// # }
/// ```
pub fn into_model<M>(self) -> SelectorRaw<SelectModel<M>>
where
@ -563,8 +566,11 @@ where
}
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
@ -581,8 +587,6 @@ where
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let res: Vec<serde_json::Value> = cake::Entity::find().from_raw_sql(
/// Statement::from_sql_and_values(
/// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![]
@ -605,9 +609,6 @@ where
/// }),
/// ]
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -616,6 +617,9 @@ where
/// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![]
/// ),
/// ]);
/// #
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "with-json")]
pub fn into_json(self) -> SelectorRaw<SelectModel<JsonValue>> {
@ -627,15 +631,23 @@ where
/// Get an item from the Select query
/// ```
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres).into_connection();
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![
/// # vec![cake::Model {
/// # id: 1,
/// # name: "Cake".to_owned(),
/// # }],
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let _: Option<cake::Model> = cake::Entity::find()
/// .from_raw_sql(Statement::from_sql_and_values(
/// DbBackend::Postgres,
@ -644,9 +656,6 @@ where
/// ))
/// .one(&db)
/// .await?;
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -656,6 +665,9 @@ where
/// vec![1.into()]
/// ),]
/// );
/// #
/// # Ok(())
/// # }
/// ```
pub async fn one<'a, C>(self, db: &C) -> Result<Option<S::Item>, DbErr>
where
@ -670,15 +682,23 @@ where
/// Get all items from the Select query
/// ```
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres).into_connection();
/// # #[smol_potat::main]
/// # #[cfg(feature = "mock")]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![
/// # vec![cake::Model {
/// # id: 1,
/// # name: "Cake".to_owned(),
/// # }],
/// # ])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let _: Vec<cake::Model> = cake::Entity::find()
/// .from_raw_sql(Statement::from_sql_and_values(
/// DbBackend::Postgres,
@ -687,9 +707,6 @@ where
/// ))
/// .all(&db)
/// .await?;
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
@ -699,6 +716,9 @@ where
/// vec![]
/// ),]
/// );
/// #
/// # Ok(())
/// # }
/// ```
pub async fn all<'a, C>(self, db: &C) -> Result<Vec<S::Item>, DbErr>
where

View File

@ -1,7 +1,8 @@
use crate::{
error::*, ActiveModelTrait, ConnectionTrait, EntityTrait, Statement, UpdateMany, UpdateOne,
error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, IntoActiveModel,
Iterable, SelectModel, SelectorRaw, Statement, UpdateMany, UpdateOne,
};
use sea_query::UpdateStatement;
use sea_query::{Alias, Expr, FromValueTuple, Query, UpdateStatement};
use std::future::Future;
/// Defines an update operation
@ -25,10 +26,11 @@ where
/// Execute an update operation on an ActiveModel
pub async fn exec<'b, C>(self, db: &'b C) -> Result<A, DbErr>
where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait<'b>,
{
// so that self is dropped before entering await
exec_update_and_return_original(self.query, self.model, db).await
exec_update_and_return_updated(self.query, self.model, db).await
}
}
@ -78,17 +80,61 @@ where
Updater::new(query).exec(db).await
}
async fn exec_update_and_return_original<'a, A, C>(
query: UpdateStatement,
async fn exec_update_and_return_updated<'a, A, C>(
mut query: UpdateStatement,
model: A,
db: &'a C,
) -> Result<A, DbErr>
where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
A: ActiveModelTrait,
C: ConnectionTrait<'a>,
{
Updater::new(query).check_record_exists().exec(db).await?;
Ok(model)
match db.support_returning() {
true => {
let mut returning = Query::select();
returning.exprs(<A::Entity as EntityTrait>::Column::iter().map(|c| {
let col = Expr::col(c);
let col_def = c.def();
let col_type = col_def.get_column_type();
match col_type.get_enum_name() {
Some(_) => col.as_enum(Alias::new("text")),
None => col.into(),
}
}));
query.returning(returning);
let db_backend = db.get_database_backend();
let found: Option<<A::Entity as EntityTrait>::Model> =
SelectorRaw::<SelectModel<<A::Entity as EntityTrait>::Model>>::from_statement(
db_backend.build(&query),
)
.one(db)
.await?;
// If we got `None` then we are updating a row that does not exist.
match found {
Some(model) => Ok(model.into_active_model()),
None => Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned(),
)),
}
}
false => {
// If we updating a row that does not exist then an error will be thrown here.
Updater::new(query).check_record_exists().exec(db).await?;
let primary_key_value = match model.get_primary_key_value() {
Some(val) => FromValueTuple::from_value_tuple(val),
None => return Err(DbErr::Exec("Fail to get primary key from model".to_owned())),
};
let found = <A::Entity as EntityTrait>::find_by_id(primary_key_value)
.one(db)
.await?;
// If we cannot select the updated row from db by the cached primary key
match found {
Some(model) => Ok(model.into_active_model()),
None => Err(DbErr::Exec("Failed to find inserted item".to_owned())),
}
}
}
}
async fn exec_update<'a, C>(
@ -119,23 +165,16 @@ mod tests {
#[smol_potat::test]
async fn update_record_not_found_1() -> Result<(), DbErr> {
let db = MockDatabase::new(DbBackend::Postgres)
.append_query_results(vec![
vec![cake::Model {
id: 1,
name: "Cheese Cake".to_owned(),
}],
vec![],
vec![],
vec![],
])
.append_exec_results(vec![
MockExecResult {
last_insert_id: 0,
rows_affected: 1,
},
MockExecResult {
last_insert_id: 0,
rows_affected: 0,
},
MockExecResult {
last_insert_id: 0,
rows_affected: 0,
},
MockExecResult {
last_insert_id: 0,
rows_affected: 0,
},
MockExecResult {
last_insert_id: 0,
rows_affected: 0,
@ -217,22 +256,22 @@ mod tests {
vec![
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#,
vec!["Cheese Cake".into(), 1i32.into()]
),
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#,
vec!["Cheese Cake".into(), 2i32.into()]
),
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#,
vec!["Cheese Cake".into(), 2i32.into()]
),
Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#,
vec!["Cheese Cake".into(), 2i32.into()]
),
Transaction::from_sql_and_values(

69
tests/returning_tests.rs Normal file
View File

@ -0,0 +1,69 @@
pub mod common;
pub use common::{bakery_chain::*, setup::*, TestContext};
pub use sea_orm::{entity::prelude::*, *};
pub use sea_query::Query;
#[sea_orm_macros::test]
#[cfg(any(
feature = "sqlx-mysql",
feature = "sqlx-sqlite",
feature = "sqlx-postgres"
))]
async fn main() -> Result<(), DbErr> {
use bakery::*;
let ctx = TestContext::new("returning_tests").await;
let db = &ctx.db;
let builder = db.get_database_backend();
let mut insert = Query::insert();
insert
.into_table(Entity)
.columns(vec![Column::Name, Column::ProfitMargin])
.values_panic(vec!["Bakery Shop".into(), 0.5.into()]);
let mut update = Query::update();
update
.table(Entity)
.values(vec![
(Column::Name, "Bakery Shop".into()),
(Column::ProfitMargin, 0.5.into()),
])
.and_where(Column::Id.eq(1));
let mut returning = Query::select();
returning.columns(vec![Column::Id, Column::Name, Column::ProfitMargin]);
create_tables(db).await?;
if db.support_returning() {
insert.returning(returning.clone());
let insert_res = db
.query_one(builder.build(&insert))
.await?
.expect("Insert failed with query_one");
let _id: i32 = insert_res.try_get("", "id")?;
let _name: String = insert_res.try_get("", "name")?;
let _profit_margin: f64 = insert_res.try_get("", "profit_margin")?;
update.returning(returning.clone());
let update_res = db
.query_one(builder.build(&update))
.await?
.expect("Update filed with query_one");
let _id: i32 = update_res.try_get("", "id")?;
let _name: String = update_res.try_get("", "name")?;
let _profit_margin: f64 = update_res.try_get("", "profit_margin")?;
} else {
let insert_res = db.execute(builder.build(&insert)).await?;
assert!(insert_res.rows_affected() > 0);
let update_res = db.execute(builder.build(&update)).await?;
assert!(update_res.rows_affected() > 0);
}
ctx.delete().await;
Ok(())
}