@@ -113,4 +113,42 @@ public void TransactionInvalidStateTest()
113113 Connection . Invoking ( connection => connection . BeginTransaction ( IsolationLevel . Serializable ) ) . Should ( )
114114 . Throw < ArgumentException > ( ) ;
115115 }
116- }
116+
117+ [ Fact ]
118+ public void AbortedTransactionTest ( )
119+ {
120+ // This block of code is to make the transaction commit fail using an index limitation in duckdb
121+ // (https://github.com/duckdb/duckdb/issues/17802)
122+ Command . CommandText = "CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY);" ;
123+ Command . ExecuteNonQuery ( ) ;
124+ Command . CommandText = "INSERT OR IGNORE INTO test_table VALUES (1);" ;
125+ Command . ExecuteNonQuery ( ) ;
126+ // Keep a reference to the row in an open transaction to trigger the concurrent access limitation
127+ using var tx1 = Connection . BeginTransaction ( ) ;
128+ Command . Transaction = tx1 ;
129+ Command . CommandText = "SELECT id FROM test_table LIMIT 1;" ;
130+ using var reader1 = Command . ExecuteReader ( ) ;
131+ using var conn2 = Connection . Duplicate ( ) ;
132+ conn2 . Open ( ) ;
133+ using var cmd2 = conn2 . CreateCommand ( ) ;
134+ cmd2 . CommandText = "UPDATE test_table SET id = 1 WHERE id = 1;" ;
135+ cmd2 . ExecuteNonQuery ( ) ;
136+ var tx2 = conn2 . BeginTransaction ( ) ;
137+ cmd2 . Transaction = tx2 ;
138+ cmd2 . CommandText = "UPDATE test_table SET id = 1 WHERE id = 1;" ;
139+ cmd2 . ExecuteNonQuery ( ) ;
140+
141+ using ( new FluentAssertions . Execution . AssertionScope ( ) )
142+ {
143+ // Check that when the transaction commit fails and the transaction
144+ // enters an aborted state, the transaction and connection objects
145+ // remain in the expected state.
146+ tx2 . Invoking ( tx2 => tx2 . Commit ( ) ) . Should ( ) . Throw < DuckDBException > ( ) . Where ( ex => ex . ErrorType == Native . DuckDBErrorType . Transaction ) ;
147+ tx2 . Invoking ( tx2 => tx2 . Commit ( ) ) . Should ( ) . Throw < InvalidOperationException > ( ) ;
148+ tx2 . Invoking ( tx2 => tx2 . Rollback ( ) ) . Should ( ) . Throw < InvalidOperationException > ( ) ;
149+ tx2 . Invoking ( tx2 => tx2 . Dispose ( ) ) . Should ( ) . NotThrow ( ) ;
150+
151+ conn2 . Invoking ( conn2 => conn2 . BeginTransaction ( ) ) . Should ( ) . NotThrow ( ) ;
152+ }
153+ }
154+ }
0 commit comments