Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions DuckDB.NET.Data/DuckDBTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,23 @@ private void FinishTransaction(string finalizer)
throw new InvalidOperationException("Transaction has already been finished.");
}

connection.ExecuteNonQuery(finalizer);
connection.Transaction = null;
finished = true;
try
{
connection.ExecuteNonQuery(finalizer);
connection.Transaction = null;
finished = true;
}
// If something goes wrong with the transaction, to match the
// transaction's internal duckdb state it should still be considered
// finished and should no longer be used
catch (DuckDBException ex) when (ex.ErrorType == Native.DuckDBErrorType.Transaction)
{
connection.Transaction = null;
finished = true;
throw;
}
}



protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Expand All @@ -53,4 +64,4 @@ protected override void Dispose(bool disposing)
Rollback();
}
}
}
}
2 changes: 1 addition & 1 deletion DuckDB.NET.Data/PreparedStatement/ClrToDuckDBConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private static DuckDBValue DecimalToDuckDBValue(decimal value)

result += new BigInteger(decimal.Multiply(fractionalPart, (decimal)power));

int width = result.IsZero ? 1 : (int)Math.Floor(BigInteger.Log10(BigInteger.Abs(result))) + 1;
int width = Math.Max(scale, result.IsZero ? 1 : (int)Math.Floor(BigInteger.Log10(BigInteger.Abs(result))) + 1);

return NativeMethods.Value.DuckDBCreateDecimal(new DuckDBDecimal((byte)width, scale, new DuckDBHugeInt(result)));
}
Expand Down
32 changes: 31 additions & 1 deletion DuckDB.NET.Test/Parameters/DecimalParameterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,34 @@ public void BindParameterWithoutTable()
result.Should().BeOfType<decimal>().Subject.Should().Be(value);
}
}
}

[Fact]
public void BindParameterInComparison()
{
var testCases = new (decimal value, bool expectedResult)[]
{
(decimal.Zero, true),
(0.00m, true),
(123456789.987654321m, false),
(-123456789.987654321m, true),
(1.230m, false),
(-1.23m, true),
(0.000000001m, true),
(-0.000000001m, true),
(1000000.000000001m, false),
(-1000000.000000001m, true),
(1.123456789012345678901m, false)
};

foreach (var (value, expectedResult) in testCases)
{
Command.CommandText = "SELECT 0.1 > ?;";
Command.Parameters.Clear();
Command.Parameters.Add(new DuckDBParameter(value));

var result = Command.ExecuteScalar();

result.Should().BeOfType<bool>().Subject.Should().Be(expectedResult);
}
}
}
40 changes: 39 additions & 1 deletion DuckDB.NET.Test/TransactionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,42 @@ public void TransactionInvalidStateTest()
Connection.Invoking(connection => connection.BeginTransaction(IsolationLevel.Serializable)).Should()
.Throw<ArgumentException>();
}
}

[Fact]
public void AbortedTransactionTest()
{
// This block of code is to make the transaction commit fail using an index limitation in duckdb
// (https://github.com/duckdb/duckdb/issues/17802)
Command.CommandText = "CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY);";
Command.ExecuteNonQuery();
Command.CommandText = "INSERT OR IGNORE INTO test_table VALUES (1);";
Command.ExecuteNonQuery();
// Keep a reference to the row in an open transaction to trigger the concurrent access limitation
using var tx1 = Connection.BeginTransaction();
Command.Transaction = tx1;
Command.CommandText = "SELECT id FROM test_table LIMIT 1;";
using var reader1 = Command.ExecuteReader();
using var conn2 = Connection.Duplicate();
conn2.Open();
using var cmd2 = conn2.CreateCommand();
cmd2.CommandText = "UPDATE test_table SET id = 1 WHERE id = 1;";
cmd2.ExecuteNonQuery();
var tx2 = conn2.BeginTransaction();
cmd2.Transaction = tx2;
cmd2.CommandText = "UPDATE test_table SET id = 1 WHERE id = 1;";
cmd2.ExecuteNonQuery();

using (new FluentAssertions.Execution.AssertionScope())
{
// Check that when the transaction commit fails and the transaction
// enters an aborted state, the transaction and connection objects
// remain in the expected state.
tx2.Invoking(tx2 => tx2.Commit()).Should().Throw<DuckDBException>().Where(ex => ex.ErrorType == Native.DuckDBErrorType.Transaction);
tx2.Invoking(tx2 => tx2.Commit()).Should().Throw<InvalidOperationException>();
tx2.Invoking(tx2 => tx2.Rollback()).Should().Throw<InvalidOperationException>();
tx2.Invoking(tx2 => tx2.Dispose()).Should().NotThrow();

conn2.Invoking(conn2 => conn2.BeginTransaction()).Should().NotThrow();
}
}
}
Loading