Skip to content

Commit 09a8387

Browse files
authored
Fix reusing NpgsqlBatch with autoprepare (#4821)
Fixes #4264
1 parent 46adc62 commit 09a8387

File tree

4 files changed

+66
-10
lines changed

4 files changed

+66
-10
lines changed

src/Npgsql/NpgsqlBatchCommand.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ public sealed class NpgsqlBatchCommand : DbBatchCommand
2020
public override string CommandText
2121
{
2222
get => _commandText;
23-
set => _commandText = value ?? string.Empty;
23+
set
24+
{
25+
_commandText = value ?? string.Empty;
26+
27+
ResetPreparation();
28+
// TODO: Technically should do this also if the parameter list (or type) changes
29+
}
2430
}
2531

2632
/// <inheritdoc/>
@@ -153,6 +159,8 @@ internal PreparedStatement? PreparedStatement
153159

154160
PreparedStatement? _preparedStatement;
155161

162+
internal NpgsqlConnector? ConnectorPreparedOn { get; set; }
163+
156164
internal bool IsPreparing;
157165

158166
/// <summary>
@@ -248,6 +256,12 @@ internal void ApplyCommandComplete(CommandCompleteMessage msg)
248256
OID = msg.OID;
249257
}
250258

259+
internal void ResetPreparation()
260+
{
261+
PreparedStatement = null;
262+
ConnectorPreparedOn = null;
263+
}
264+
251265
/// <summary>
252266
/// Returns the <see cref="CommandText"/>.
253267
/// </summary>

src/Npgsql/NpgsqlCommand.cs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,20 +1346,33 @@ internal virtual async ValueTask<NpgsqlDataReader> ExecuteReader(CommandBehavior
13461346
{
13471347
case true:
13481348
Debug.Assert(_connectorPreparedOn != null);
1349-
if (_connectorPreparedOn != connector)
1350-
{
1351-
// The command was prepared, but since then the connector has changed. Detach all prepared statements.
1352-
foreach (var s in InternalBatchCommands)
1353-
s.PreparedStatement = null;
1354-
ResetPreparation();
1355-
goto case false;
1356-
}
1357-
13581349
if (IsWrappedByBatch)
1350+
{
13591351
foreach (var batchCommand in InternalBatchCommands)
1352+
{
1353+
if (batchCommand.ConnectorPreparedOn != connector)
1354+
{
1355+
foreach (var s in InternalBatchCommands)
1356+
s.ResetPreparation();
1357+
ResetPreparation();
1358+
goto case false;
1359+
}
1360+
13601361
batchCommand.Parameters.ProcessParameters(dataSource.TypeMapper, validateParameterValues, CommandType);
1362+
}
1363+
}
13611364
else
1365+
{
1366+
if (_connectorPreparedOn != connector)
1367+
{
1368+
// The command was prepared, but since then the connector has changed. Detach all prepared statements.
1369+
foreach (var s in InternalBatchCommands)
1370+
s.PreparedStatement = null;
1371+
ResetPreparation();
1372+
goto case false;
1373+
}
13621374
Parameters.ProcessParameters(dataSource.TypeMapper, validateParameterValues, CommandType);
1375+
}
13631376

13641377
NpgsqlEventSource.Log.CommandStartPrepared();
13651378
break;
@@ -1377,7 +1390,10 @@ internal virtual async ValueTask<NpgsqlDataReader> ExecuteReader(CommandBehavior
13771390
ProcessRawQuery(connector.SqlQueryParser, connector.UseConformingStrings, batchCommand);
13781391

13791392
if (connector.Settings.MaxAutoPrepare > 0 && batchCommand.TryAutoPrepare(connector))
1393+
{
1394+
batchCommand.ConnectorPreparedOn = connector;
13801395
numPrepared++;
1396+
}
13811397
}
13821398
}
13831399
else

src/Npgsql/NpgsqlDataReader.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,8 @@ async Task<bool> NextResult(bool async, bool isConsuming = false, CancellationTo
563563
{
564564
preparedStatement.State = PreparedState.Invalidated;
565565
Command.ResetPreparation();
566+
foreach (var s in Command.InternalBatchCommands)
567+
s.ResetPreparation();
566568
}
567569
}
568570

test/Npgsql.Tests/BatchTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,30 @@ public async Task ExecuteScalar_without_parameters()
731731
Assert.That(await batch.ExecuteScalarAsync(), Is.EqualTo(1));
732732
}
733733

734+
[Test, IssueLink("https://github.com/npgsql/npgsql/issues/4264")]
735+
public async Task Batch_with_auto_prepare_reuse()
736+
{
737+
var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
738+
{
739+
MaxAutoPrepare = 20
740+
};
741+
742+
await using var conn = await OpenConnectionAsync(csb);
743+
744+
var tempTableName = await CreateTempTable(conn, "id int");
745+
746+
await using var batch = new NpgsqlBatch(conn);
747+
for (var i = 0; i < 2; ++i)
748+
{
749+
for (var j = 0; j < 10; ++j)
750+
{
751+
batch.BatchCommands.Add(new NpgsqlBatchCommand($"DELETE FROM {tempTableName} WHERE 1=0"));
752+
}
753+
await batch.ExecuteNonQueryAsync();
754+
batch.BatchCommands.Clear();
755+
}
756+
}
757+
734758
#endregion Miscellaneous
735759

736760
#region Logging

0 commit comments

Comments
 (0)