From 018eae3d30d365c114415f6ad879a75978eec199 Mon Sep 17 00:00:00 2001 From: Jacek Malec Date: Tue, 26 Nov 2024 17:10:49 +0000 Subject: [PATCH] fix: fix isolation level issue in mssql --- quaint/src/single.rs | 22 +++++++++++++++------- quaint/src/tests/query.rs | 28 +++++++++++++++------------- quaint/src/tests/test_api/mssql.rs | 4 ++++ 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/quaint/src/single.rs b/quaint/src/single.rs index 13be8c4bc857..004b84e0da54 100644 --- a/quaint/src/single.rs +++ b/quaint/src/single.rs @@ -2,7 +2,7 @@ use crate::{ ast, - connector::{self, impl_default_TransactionCapable, ConnectionInfo, IsolationLevel, Queryable, TransactionCapable}, + connector::{self, ConnectionInfo, IsolationLevel, Queryable, TransactionCapable}, }; use async_trait::async_trait; use std::{fmt, sync::Arc}; @@ -16,7 +16,7 @@ use crate::connector::NativeConnectionInfo; /// The main entry point and an abstraction over a database connection. #[derive(Clone)] pub struct Quaint { - inner: Arc, + inner: Arc, connection_info: Arc, } @@ -26,7 +26,15 @@ impl fmt::Debug for Quaint { } } -impl_default_TransactionCapable!(Quaint); +#[async_trait] +impl TransactionCapable for Quaint { + async fn start_transaction<'a>( + &'a self, + isolation: Option, + ) -> crate::Result> { + self.inner.start_transaction(isolation).await + } +} impl Quaint { /// Create a new connection to the database. The connection string @@ -137,28 +145,28 @@ impl Quaint { let params = connector::SqliteParams::try_from(s)?; let sqlite = connector::Sqlite::new(¶ms.file_path)?; - Arc::new(sqlite) as Arc + Arc::new(sqlite) as Arc } #[cfg(feature = "mysql-native")] s if s.starts_with("mysql") => { let url = connector::MysqlUrl::new(url::Url::parse(s)?)?; let mysql = connector::Mysql::new(url).await?; - Arc::new(mysql) as Arc + Arc::new(mysql) as Arc } #[cfg(feature = "postgresql-native")] s if s.starts_with("postgres") || s.starts_with("postgresql") => { let url = connector::PostgresNativeUrl::new(url::Url::parse(s)?)?; let tls_manager = connector::MakeTlsConnectorManager::new(url.clone()); let psql = connector::PostgreSql::new(url, &tls_manager).await?; - Arc::new(psql) as Arc + Arc::new(psql) as Arc } #[cfg(feature = "mssql-native")] s if s.starts_with("jdbc:sqlserver") | s.starts_with("sqlserver") => { let url = connector::MssqlUrl::new(s)?; let psql = connector::Mssql::new(url).await?; - Arc::new(psql) as Arc + Arc::new(psql) as Arc } _ => unimplemented!("Supported url schemes: file or sqlite, mysql, postgresql or jdbc:sqlserver."), }; diff --git a/quaint/src/tests/query.rs b/quaint/src/tests/query.rs index ff3b1e00e7bc..61ed9e0b2b1c 100644 --- a/quaint/src/tests/query.rs +++ b/quaint/src/tests/query.rs @@ -117,27 +117,29 @@ async fn transactions_with_isolation_works(api: &mut dyn TestApi) -> crate::Resu #[test_each_connector(tags("mssql"))] async fn mssql_transaction_isolation_level(api: &mut dyn TestApi) -> crate::Result<()> { - // For Mssql, the isolation level is set on the connection and lives until it's changed. - // We need to test that the isolation level set per transaction is ignored. let table = api.create_temp_table("id int, value int").await?; - // The connection default is "READ COMMITTED" - let conn = api.conn(); - - // Start a transaction with the default isolation level of "READ COMMITTED" - // and insert a row, but do not commit the transaction - let tx_a = conn.start_transaction(None).await?; + let conn_a = api.conn(); + // Start a transaction with the default isolation level, which in tests is + // set to READ UNCOMMITED via the DB url and insert a row, but do not commit the transaction + let tx_a = conn_a.start_transaction(None).await?; let insert = Insert::single_into(&table).value("value", 3).value("id", 4); let rows_affected = tx_a.execute(insert.into()).await?; assert_eq!(1, rows_affected); - // Start a transaction with an explicit isolation level of "READ UNCOMMITTED", which would ordinarily allow you to read - // rows that have been added but not committed. - let tx_b = conn.start_transaction(Some(IsolationLevel::ReadUncommitted)).await?; + let conn_b = api.create_additional_connection().await?; + // Start a transaction that explicitly sets the isolation level to "SNAPSHOT" and query the table + // expecting to see the old state. + let tx_b = conn_b.start_transaction(Some(IsolationLevel::Snapshot)).await?; let res = tx_b.query(Select::from_table(&table).into()).await?; - - // The query should return an empty result set because the isolation level is set on the connection assert_eq!(0, res.len()); + + // Start a transaction without an explicit isolation level, it should be run with the default + // again, which is set to READ UNCOMMITED here. + let tx_c = conn_b.start_transaction(None).await?; + let res = tx_c.query(Select::from_table(&table).into()).await?; + assert_eq!(1, res.len()); + Ok(()) } diff --git a/quaint/src/tests/test_api/mssql.rs b/quaint/src/tests/test_api/mssql.rs index 164b3fb7ddeb..5d31757d756c 100644 --- a/quaint/src/tests/test_api/mssql.rs +++ b/quaint/src/tests/test_api/mssql.rs @@ -21,6 +21,10 @@ impl<'a> MsSql<'a> { let names = Generator::default(); let conn = Quaint::new(&CONN_STR).await?; + // snapshot isolation enables us to test isolation levels easily + conn.raw_cmd(&format!("ALTER DATABASE tempdb SET ALLOW_SNAPSHOT_ISOLATION ON",)) + .await?; + Ok(Self { names, conn }) } }