Skip to content
Draft
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
55 changes: 48 additions & 7 deletions DuckDB.NET.Data/DuckDBCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ public override int ExecuteNonQuery()
{
EnsureConnectionOpen();

if (preparedStatement is { } reusableStatement)
{
return ExecutePreparedNonQuery(reusableStatement, connection!.NativeConnection);
}

var results = ExecuteStatements();

var count = 0;
Expand Down Expand Up @@ -249,26 +254,62 @@ private IEnumerable<DuckDBResult> ExecutePreparedStatement(
ReusablePreparedStatement reusableStatement,
DuckDBNativeConnection nativeConnection)
{
activeExecutions++;
BeginPreparedExecution();

try
{
yield return reusableStatement.Execute(parameters, UseStreamingMode, nativeConnection);
}
finally
{
activeExecutions--;
CompletePreparedExecution();
}
}

if (activeExecutions == 0)
private int ExecutePreparedNonQuery(
ReusablePreparedStatement reusableStatement,
DuckDBNativeConnection nativeConnection)
{
BeginPreparedExecution();

try
{
var result = reusableStatement.Execute(parameters, UseStreamingMode, nativeConnection);

try
{
DisposeDeferredPreparedStatements();
var current = result;
return (int)NativeMethods.Query.DuckDBRowsChanged(ref current);
}

if (disposed && activeExecutions == 0)
finally
{
UnregisterFromConnections();
result.Close();
}
}
finally
{
CompletePreparedExecution();
}
}

private void BeginPreparedExecution()
{
activeExecutions++;
}

private void CompletePreparedExecution()
{
activeExecutions--;

if (activeExecutions == 0)
{
DisposeDeferredPreparedStatements();
}

if (disposed && activeExecutions == 0)
{
UnregisterFromConnections();
}
}

private void InvalidatePreparedStatements()
Expand Down
48 changes: 48 additions & 0 deletions DuckDB.NET.Test/DuckDBCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,54 @@ public void PreparedCommandClearsBindingsBeforeReuse()
command.ExecuteScalar().Should().Be(30);
}

[Fact]
public void PreparedExecuteNonQueryReturnsAffectedRowsAndCanBeReused()
{
using var connection = new DuckDBConnection("DataSource=:memory:");
connection.Open();

ExecuteNonQuery(connection, "CREATE TABLE prepared_values(value INTEGER)");

using var command = connection.CreateCommand();
command.CommandText = "INSERT INTO prepared_values VALUES ($value)";
command.Parameters.Add(new DuckDBParameter("value", 10));
command.Prepare();

command.ExecuteNonQuery().Should().Be(1);

command.Parameters["value"].Value = 20;
command.ExecuteNonQuery().Should().Be(1);

using var query = connection.CreateCommand();
query.CommandText = "SELECT value FROM prepared_values ORDER BY value";
using var reader = query.ExecuteReader();
reader.Read().Should().BeTrue();
reader.GetInt32(0).Should().Be(10);
reader.Read().Should().BeTrue();
reader.GetInt32(0).Should().Be(20);
reader.Read().Should().BeFalse();
}

[Fact]
public void PreparedExecuteNonQueryCanBeReusedAfterExecutionFailure()
{
using var connection = new DuckDBConnection("DataSource=:memory:");
connection.Open();

ExecuteNonQuery(connection, "CREATE TABLE unique_prepared_values(value INTEGER PRIMARY KEY)");

using var command = connection.CreateCommand();
command.CommandText = "INSERT INTO unique_prepared_values VALUES ($value)";
command.Parameters.Add(new DuckDBParameter("value", 10));
command.Prepare();

command.ExecuteNonQuery().Should().Be(1);
command.Invoking(value => value.ExecuteNonQuery()).Should().Throw<DuckDBException>();

command.Parameters["value"].Value = 20;
command.ExecuteNonQuery().Should().Be(1);
}

[Fact]
public void PreparePreservesMultipleResultSetsAcrossExecutions()
{
Expand Down