From 3836c13d7a2f52dc703ab2cd06b8c867f071979f Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Thu, 29 Jan 2026 18:20:38 +0300 Subject: [PATCH] Fix reusing NpgsqlBatch with auto prepare --- src/Npgsql/NpgsqlBatchCommand.cs | 8 ++++++-- src/Npgsql/NpgsqlCommand.cs | 13 +++++++------ src/Npgsql/NpgsqlDataReader.cs | 2 -- test/Npgsql.Tests/AutoPrepareTests.cs | 27 +++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/Npgsql/NpgsqlBatchCommand.cs b/src/Npgsql/NpgsqlBatchCommand.cs index 17cec381b2..2812fdbead 100644 --- a/src/Npgsql/NpgsqlBatchCommand.cs +++ b/src/Npgsql/NpgsqlBatchCommand.cs @@ -172,7 +172,7 @@ internal PreparedStatement? PreparedStatement get => _preparedStatement is { State: PreparedState.Unprepared } ? _preparedStatement = null : _preparedStatement; - set => _preparedStatement = value; + private set => _preparedStatement = value; } PreparedStatement? _preparedStatement; @@ -274,7 +274,11 @@ internal void ApplyCommandComplete(CommandCompleteMessage msg) OID = msg.OID; } - internal void ResetPreparation() => ConnectorPreparedOn = null; + internal void ResetPreparation() + { + ConnectorPreparedOn = null; + PreparedStatement = null; + } internal void PopulateOutputParameters(NpgsqlDataReader reader, ILogger logger) { diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index ffbf86029f..8ddbb2e5fb 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -406,7 +406,12 @@ internal CommandState State } } - internal void ResetPreparation() => _connectorPreparedOn = null; + internal void ResetPreparation() + { + _connectorPreparedOn = null; + foreach (var s in InternalBatchCommands) + s.ResetPreparation(); + } #endregion State management @@ -873,7 +878,7 @@ async Task Unprepare(bool async, CancellationToken cancellationToken = default) if (!pStatement.IsExplicit) connector.PreparedStatementManager.AutoPrepared[pStatement.AutoPreparedSlotIndex] = null; - batchCommand.PreparedStatement = null; + batchCommand.ResetPreparation(); } } @@ -1441,8 +1446,6 @@ internal virtual async ValueTask ExecuteReader(bool async, Com { if (batchCommand.ConnectorPreparedOn != connector) { - foreach (var s in InternalBatchCommands) - s.ResetPreparation(); ResetPreparation(); goto case false; } @@ -1455,8 +1458,6 @@ internal virtual async ValueTask ExecuteReader(bool async, Com if (_connectorPreparedOn != connector) { // The command was prepared, but since then the connector has changed. Detach all prepared statements. - foreach (var s in InternalBatchCommands) - s.PreparedStatement = null; ResetPreparation(); goto case false; } diff --git a/src/Npgsql/NpgsqlDataReader.cs b/src/Npgsql/NpgsqlDataReader.cs index 94499c14ae..55753dc7f6 100644 --- a/src/Npgsql/NpgsqlDataReader.cs +++ b/src/Npgsql/NpgsqlDataReader.cs @@ -559,8 +559,6 @@ async Task NextResult(bool async, bool isConsuming = false, CancellationTo { preparedStatement.State = PreparedState.Invalidated; Command.ResetPreparation(); - foreach (var s in Command.InternalBatchCommands) - s.ResetPreparation(); } } diff --git a/test/Npgsql.Tests/AutoPrepareTests.cs b/test/Npgsql.Tests/AutoPrepareTests.cs index b35fe7c5d3..97a46ad277 100644 --- a/test/Npgsql.Tests/AutoPrepareTests.cs +++ b/test/Npgsql.Tests/AutoPrepareTests.cs @@ -620,6 +620,33 @@ public async Task Auto_prepared_statement_invalidation() Assert.DoesNotThrowAsync(() => command.ExecuteNonQueryAsync()); } + [Test, IssueLink("https://github.com/npgsql/npgsql/issues/6432")] + public async Task Reuse_batch_with_different_connectors() + { + await using var dataSource = CreateDataSource(csb => + { + csb.MaxAutoPrepare = 10; + csb.AutoPrepareMinUsages = 2; + }); + await using var batch = new NpgsqlBatch(); + batch.BatchCommands.Add(new NpgsqlBatchCommand("SELECT 1")); + await using (var connection = await dataSource.OpenConnectionAsync()) + { + batch.Connection = connection; + + for (var i = 0; i < 2; i++) + await batch.ExecuteNonQueryAsync(); + } + + dataSource.Clear(); + + await using (var connection = await dataSource.OpenConnectionAsync()) + { + batch.Connection = connection; + await batch.ExecuteNonQueryAsync(); + } + } + void DumpPreparedStatements(NpgsqlConnection conn) { using var cmd = new NpgsqlCommand("SELECT name,statement FROM pg_prepared_statements", conn);