Skip to content

Commit ee6b4cd

Browse files
committed
Rusqlite: support SQLite transaction mode
1 parent 78dd012 commit ee6b4cd

11 files changed

Lines changed: 111 additions & 45 deletions

File tree

sea-orm-sync/src/database/db_connection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ impl TransactionTrait for DatabaseConnection {
329329
#[cfg(feature = "sqlx-postgres")]
330330
DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => conn.begin(None, None),
331331
#[cfg(feature = "sqlx-sqlite")]
332-
DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => conn.begin(None, None),
332+
DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => conn.begin(None, None, None),
333333
#[cfg(feature = "rusqlite")]
334334
DatabaseConnectionType::RusqliteSharedConnection(conn) => conn.begin(None, None, None),
335335
#[cfg(feature = "mock")]
@@ -361,7 +361,7 @@ impl TransactionTrait for DatabaseConnection {
361361
}
362362
#[cfg(feature = "sqlx-sqlite")]
363363
DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => {
364-
conn.begin(_isolation_level, _access_mode)
364+
conn.begin(_isolation_level, _access_mode, None)
365365
}
366366
#[cfg(feature = "rusqlite")]
367367
DatabaseConnectionType::RusqliteSharedConnection(conn) => {

sea-orm-sync/src/database/restricted_connection.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
use crate::rbac::{
2-
PermissionRequest, RbacEngine, RbacError, RbacPermissionsByResources,
3-
RbacResourcesAndPermissions, RbacRoleHierarchyList, RbacRolesAndRanks, RbacUserRolePermissions,
4-
ResourceRequest,
5-
entity::{role::RoleId, user::UserId},
6-
};
71
use crate::{
82
AccessMode, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbBackend, DbErr,
93
ExecResult, IsolationLevel, QueryResult, Statement, StatementBuilder, TransactionError,
10-
TransactionOptions, TransactionSession, TransactionTrait,
4+
TransactionSession, TransactionTrait,
5+
};
6+
use crate::{
7+
TransactionOptions,
8+
rbac::{
9+
PermissionRequest, RbacEngine, RbacError, RbacPermissionsByResources,
10+
RbacResourcesAndPermissions, RbacRoleHierarchyList, RbacRolesAndRanks,
11+
RbacUserRolePermissions, ResourceRequest,
12+
entity::{role::RoleId, user::UserId},
13+
},
1114
};
1215
use std::{
1316
pin::Pin,

sea-orm-sync/src/database/transaction.rs

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
#![allow(unused_assignments)]
2-
use crate::SqliteTransactionMode;
2+
use std::sync::Arc;
3+
4+
#[cfg(feature = "sqlx-dep")]
5+
use sqlx::TransactionManager;
6+
use std::sync::Mutex;
7+
use tracing::instrument;
8+
39
use crate::{
410
AccessMode, ConnectionTrait, DbBackend, DbErr, ExecResult, InnerConnection, IsolationLevel,
5-
QueryResult, Statement, StreamTrait, TransactionOptions, TransactionSession, TransactionStream,
6-
TransactionTrait, debug_print, error::*,
11+
QueryResult, SqliteTransactionMode, Statement, StreamTrait, TransactionOptions,
12+
TransactionSession, TransactionStream, TransactionTrait, debug_print, error::*,
713
};
814
#[cfg(feature = "sqlx-dep")]
915
use crate::{sqlx_error_to_exec_err, sqlx_error_to_query_err};
10-
#[cfg(feature = "sqlx-dep")]
11-
use sqlx::TransactionManager;
12-
use std::sync::Arc;
13-
use std::sync::Mutex;
14-
use tracing::instrument;
1516

1617
/// Defines a database transaction, whether it is an open transaction and the type of
1718
/// backend to use.
@@ -83,22 +84,25 @@ impl DatabaseTransaction {
8384
}
8485
#[cfg(feature = "sqlx-sqlite")]
8586
InnerConnection::Sqlite(c) => {
86-
// in SQLite isolation level and access mode are global settings
8787
crate::driver::sqlx_sqlite::set_transaction_config(
8888
c,
8989
isolation_level,
9090
access_mode,
9191
)?;
92-
// TODO using this for beginning a nested transaction currently causes an error. Should we make it a warning instead?
93-
let statement = sqlite_transaction_mode.map(|mode| {
94-
std::borrow::Cow::from(format!("BEGIN {}", mode.sqlite_keyword()))
95-
});
92+
let depth = <sqlx::Sqlite as sqlx::Database>::TransactionManager::get_transaction_depth(c);
93+
let statement = if depth == 0 {
94+
sqlite_transaction_mode.map(|mode| {
95+
std::borrow::Cow::from(format!("BEGIN {}", mode.sqlite_keyword()))
96+
})
97+
} else {
98+
// Nested transaction uses SAVEPOINT; the mode only applies to the top-level BEGIN
99+
None
100+
};
96101
<sqlx::Sqlite as sqlx::Database>::TransactionManager::begin(c, statement)
97-
.await
98102
.map_err(sqlx_error_to_query_err)
99103
}
100104
#[cfg(feature = "rusqlite")]
101-
InnerConnection::Rusqlite(c) => c.begin(),
105+
InnerConnection::Rusqlite(c) => c.begin(sqlite_transaction_mode),
102106
#[cfg(feature = "mock")]
103107
InnerConnection::Mock(c) => {
104108
c.begin();
@@ -605,19 +609,15 @@ impl TransactionTrait for DatabaseTransaction {
605609
#[instrument(level = "trace")]
606610
fn begin_with_options(
607611
&self,
608-
TransactionOptions {
609-
isolation_level,
610-
access_mode,
611-
sqlite_transaction_mode,
612-
}: TransactionOptions,
612+
options: TransactionOptions,
613613
) -> Result<DatabaseTransaction, DbErr> {
614614
DatabaseTransaction::begin(
615615
Arc::clone(&self.conn),
616616
self.backend,
617617
self.metric_callback.clone(),
618-
isolation_level,
619-
access_mode,
620-
sqlite_transaction_mode,
618+
options.isolation_level,
619+
options.access_mode,
620+
options.sqlite_transaction_mode,
621621
)
622622
}
623623

sea-orm-sync/src/driver/proxy.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ impl crate::DatabaseTransaction {
151151
metric_callback,
152152
None,
153153
None,
154+
None,
154155
)
155156
}
156157
}

sea-orm-sync/src/driver/rusqlite.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,17 @@ impl RusqliteInnerConnection {
557557
}
558558

559559
#[instrument(level = "trace")]
560-
pub(crate) fn begin(&mut self) -> Result<(), DbErr> {
560+
pub(crate) fn begin(
561+
&mut self,
562+
sqlite_transaction_mode: Option<SqliteTransactionMode>,
563+
) -> Result<(), DbErr> {
561564
if self.transaction_depth == 0 {
562-
self.execute_unprepared("BEGIN")?;
565+
match sqlite_transaction_mode {
566+
Some(mode) => {
567+
self.execute_unprepared(&format!("BEGIN {}", mode.sqlite_keyword()))?
568+
}
569+
None => self.execute_unprepared("BEGIN")?,
570+
};
563571
} else {
564572
self.execute_unprepared(&format!("SAVEPOINT sp{}", self.transaction_depth))?;
565573
}

sea-orm-sync/src/driver/sqlx_mysql.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ impl crate::DatabaseTransaction {
343343
metric_callback,
344344
isolation_level,
345345
access_mode,
346+
None,
346347
)
347348
}
348349
}

sea-orm-sync/src/driver/sqlx_postgres.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ impl crate::DatabaseTransaction {
374374
metric_callback,
375375
isolation_level,
376376
access_mode,
377+
None,
377378
)
378379
}
379380
}

sea-orm-sync/src/driver/sqlx_sqlite.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use tracing::{instrument, warn};
1414

1515
use crate::{
1616
AccessMode, ConnectOptions, DatabaseConnection, DatabaseConnectionType, DatabaseTransaction,
17-
IsolationLevel, QueryStream, Statement, TransactionError, debug_print, error::*, executor::*,
18-
sqlx_error_to_exec_err,
17+
IsolationLevel, QueryStream, SqliteTransactionMode, Statement, TransactionError, debug_print,
18+
error::*, executor::*, sqlx_error_to_exec_err,
1919
};
2020

2121
use super::sqlx_common::*;
@@ -214,13 +214,15 @@ impl SqlxSqlitePoolConnection {
214214
&self,
215215
isolation_level: Option<IsolationLevel>,
216216
access_mode: Option<AccessMode>,
217+
sqlite_transaction_mode: Option<SqliteTransactionMode>,
217218
) -> Result<DatabaseTransaction, DbErr> {
218219
let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?;
219220
DatabaseTransaction::new_sqlite(
220221
conn,
221222
self.metric_callback.clone(),
222223
isolation_level,
223224
access_mode,
225+
sqlite_transaction_mode,
224226
)
225227
}
226228

@@ -242,6 +244,7 @@ impl SqlxSqlitePoolConnection {
242244
self.metric_callback.clone(),
243245
isolation_level,
244246
access_mode,
247+
None,
245248
)
246249
.map_err(|e| TransactionError::Connection(e))?;
247250
transaction.run(callback)
@@ -354,13 +357,15 @@ impl crate::DatabaseTransaction {
354357
metric_callback: Option<crate::metric::Callback>,
355358
isolation_level: Option<IsolationLevel>,
356359
access_mode: Option<AccessMode>,
360+
sqlite_transaction_mode: Option<SqliteTransactionMode>,
357361
) -> Result<crate::DatabaseTransaction, DbErr> {
358362
Self::begin(
359363
Arc::new(Mutex::new(crate::InnerConnection::Sqlite(inner))),
360364
crate::DbBackend::Sqlite,
361365
metric_callback,
362366
isolation_level,
363367
access_mode,
368+
sqlite_transaction_mode,
364369
)
365370
}
366371
}

sea-orm-sync/tests/transaction_tests.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ pub mod common;
44

55
pub use common::{TestContext, bakery_chain::*, setup::*};
66
use pretty_assertions::assert_eq;
7-
use sea_orm::{AccessMode, DatabaseTransaction, IsolationLevel, Set, TransactionTrait, prelude::*};
7+
use sea_orm::{
8+
AccessMode, DatabaseTransaction, IsolationLevel, Set, SqliteTransactionMode,
9+
TransactionOptions, TransactionTrait, prelude::*,
10+
};
811

912
#[cfg(not(feature = "sync"))]
1013
type FutureResult<'a> =
@@ -944,3 +947,35 @@ fn _transaction_with_config<'a>(
944947
Ok(())
945948
})
946949
}
950+
951+
#[sea_orm_macros::test]
952+
pub fn transaction_begin_with_options() -> Result<(), DbErr> {
953+
let ctx = TestContext::new("transaction_begin_with_options_test");
954+
create_tables(&ctx.db)?;
955+
956+
assert_eq!(bakery::Entity::find().all(&ctx.db)?.len(), 0);
957+
958+
{
959+
// Transaction begin in this scope
960+
let txn = ctx.db.begin_with_options(TransactionOptions {
961+
sqlite_transaction_mode: Some(SqliteTransactionMode::Immediate),
962+
..Default::default()
963+
})?;
964+
965+
seaside_bakery().save(&txn)?;
966+
967+
assert_eq!(bakery::Entity::find().all(&txn)?.len(), 1);
968+
969+
top_bakery().save(&txn)?;
970+
971+
assert_eq!(bakery::Entity::find().all(&txn)?.len(), 2);
972+
973+
// Commit changes before the end of scope
974+
txn.commit()?;
975+
}
976+
977+
assert_eq!(bakery::Entity::find().all(&ctx.db)?.len(), 2);
978+
979+
ctx.delete();
980+
Ok(())
981+
}

src/database/transaction.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,27 @@ impl DatabaseTransaction {
8888
}
8989
#[cfg(feature = "sqlx-sqlite")]
9090
InnerConnection::Sqlite(c) => {
91-
// in SQLite isolation level and access mode are global settings
9291
crate::driver::sqlx_sqlite::set_transaction_config(
9392
c,
9493
isolation_level,
9594
access_mode,
9695
)
9796
.await?;
98-
// TODO using this for beginning a nested transaction currently causes an error. Should we make it a warning instead?
99-
let statement = sqlite_transaction_mode.map(|mode| {
100-
std::borrow::Cow::from(format!("BEGIN {}", mode.sqlite_keyword()))
101-
});
97+
let depth = <sqlx::Sqlite as sqlx::Database>::TransactionManager::get_transaction_depth(c);
98+
let statement = if depth == 0 {
99+
sqlite_transaction_mode.map(|mode| {
100+
std::borrow::Cow::from(format!("BEGIN {}", mode.sqlite_keyword()))
101+
})
102+
} else {
103+
// Nested transaction uses SAVEPOINT; the mode only applies to the top-level BEGIN
104+
None
105+
};
102106
<sqlx::Sqlite as sqlx::Database>::TransactionManager::begin(c, statement)
103107
.await
104108
.map_err(sqlx_error_to_query_err)
105109
}
106110
#[cfg(feature = "rusqlite")]
107-
InnerConnection::Rusqlite(c) => c.begin(),
111+
InnerConnection::Rusqlite(c) => c.begin(sqlite_transaction_mode),
108112
#[cfg(feature = "mock")]
109113
InnerConnection::Mock(c) => {
110114
c.begin();

0 commit comments

Comments
 (0)