From f4937b031aba582e8ba76445603e1ebe4c81cd76 Mon Sep 17 00:00:00 2001 From: "Dailey, Alaric" Date: Tue, 20 Oct 2026 08:40:01 -0500 Subject: [PATCH 1/2] Report records affected according to ADO.NET --- .../FbDataReaderTests.cs | 14 ++++++++ .../FirebirdClient/FbCommand.cs | 34 +++++++++++-------- .../FirebirdClient/FbDataReader.cs | 7 ++-- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs b/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs index c87c804bc..c593d6b01 100644 --- a/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs +++ b/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs @@ -535,4 +535,18 @@ public async Task GetFieldValueTest() await transaction.RollbackAsync(); } } + + [Test] + public async Task RecordsAffectedIsMinusOneForSelectTest() + { + // DbDataReader.RecordsAffected: "-1 for SELECT statements; 0 if no rows were affected or the statement failed." + await using (var command = new FbCommand("select INT_FIELD from TEST where 1 = 0", Connection)) + { + var reader = await command.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { } + await reader.CloseAsync(); + Assert.AreEqual(-1, reader.RecordsAffected); + } + } } diff --git a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs index 551c6ea64..f70580289 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs @@ -263,6 +263,24 @@ internal int RecordsAffected } } + /// + /// The rows an INSERT, UPDATE or DELETE affected, or -1 for any other statement (DbCommand.ExecuteNonQuery: "For all + /// other types of statements, the return value is -1"; DbDataReader.RecordsAffected: "-1 for SELECT statements"). + /// + internal int StatementRecordsAffected + { + get + { + return _statement?.StatementType switch + { + DbStatementType.Insert => RecordsAffected, + DbStatementType.Update => RecordsAffected, + DbStatementType.Delete => RecordsAffected, + _ => -1, + }; + } + } + internal bool IsDisposed { get { return _disposed; } @@ -529,13 +547,7 @@ public override int ExecuteNonQuery() } } - return _statement.StatementType switch - { - DbStatementType.Insert => RecordsAffected, - DbStatementType.Update => RecordsAffected, - DbStatementType.Delete => RecordsAffected, - _ => -1, - }; + return StatementRecordsAffected; } public override async Task ExecuteNonQueryAsync(CancellationToken cancellationToken) { @@ -566,13 +578,7 @@ public override async Task ExecuteNonQueryAsync(CancellationToken cancellat } } - return _statement.StatementType switch - { - DbStatementType.Insert => RecordsAffected, - DbStatementType.Update => RecordsAffected, - DbStatementType.Delete => RecordsAffected, - _ => -1, - }; + return StatementRecordsAffected; } public new FbDataReader ExecuteReader() => ExecuteReader(CommandBehavior.Default); diff --git a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs index 0cf1d40c4..1c9984af0 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs @@ -51,7 +51,7 @@ public sealed class FbDataReader : DbDataReader private bool _eof; private bool _isClosed; private int _position; - private int _recordsAffected; + private int _recordsAffected = -1; private Dictionary _columnsIndexesOrdinal; private Dictionary _columnsIndexesOrdinalCI; @@ -1016,10 +1016,11 @@ private void UpdateRecordsAffected() { if (_command != null && !_command.IsDisposed) { - if (_command.RecordsAffected != -1) + var recordsAffected = _command.StatementRecordsAffected; + if (recordsAffected != -1) { _recordsAffected = _recordsAffected == -1 ? 0 : _recordsAffected; - _recordsAffected += _command.RecordsAffected; + _recordsAffected += recordsAffected; } } } From e82f3ea9cbc07244bd4d610e61566414d0d555ce Mon Sep 17 00:00:00 2001 From: "Dailey, Alaric" Date: Wed, 4 Nov 2026 06:46:45 -0600 Subject: [PATCH 2/2] Keep the server count for INSERT ... RETURNING Firebird classifies INSERT ... RETURNING and DML inside an execute block as stored procedure execution, so the statement-type switch reported -1 for them and the reader stopped reporting the insert count it used to report. Count stored procedure execution only when the server reports a positive count, so procedures that change no rows keep -1. Also set RecordsAffected to -1 in the FbDataReader constructor instead of the field initializer, as requested in review. Co-Authored-By: Claude Haiku 5.5 --- .../FbDataReaderTests.cs | 42 +++++++++++++++++++ .../FirebirdClient/FbCommand.cs | 3 ++ .../FirebirdClient/FbDataReader.cs | 3 +- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs b/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs index c593d6b01..224e24a6c 100644 --- a/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs +++ b/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs @@ -549,4 +549,46 @@ public async Task RecordsAffectedIsMinusOneForSelectTest() Assert.AreEqual(-1, reader.RecordsAffected); } } + + [Test] + public async Task RecordsAffectedForInsertReturningTest() + { + // INSERT ... RETURNING is classified by Firebird as a stored procedure execution, not as an INSERT. + await using (var transaction = await Connection.BeginTransactionAsync()) + { + await using (var command = new FbCommand("insert into test (int_field) values (100001) returning int_field", Connection, transaction)) + { + await using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { } + Assert.AreEqual(1, reader.RecordsAffected); + } + } + await transaction.RollbackAsync(); + } + } + + [Test] + public async Task ExecuteNonQueryForStatementWithoutRowChangesIsMinusOneTest() + { + // Stored procedure execution that changes no rows keeps the -1 of "all other types of statements". + await using (var command = new FbCommand("execute block as begin end", Connection)) + { + Assert.AreEqual(-1, await command.ExecuteNonQueryAsync()); + } + } + + [Test] + public async Task ExecuteNonQueryForInsertReturningTest() + { + await using (var transaction = await Connection.BeginTransactionAsync()) + { + await using (var command = new FbCommand("insert into test (int_field) values (100002) returning int_field", Connection, transaction)) + { + Assert.AreEqual(1, await command.ExecuteNonQueryAsync()); + } + await transaction.RollbackAsync(); + } + } } diff --git a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs index f70580289..7bb0c0b16 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs @@ -266,6 +266,8 @@ internal int RecordsAffected /// /// The rows an INSERT, UPDATE or DELETE affected, or -1 for any other statement (DbCommand.ExecuteNonQuery: "For all /// other types of statements, the return value is -1"; DbDataReader.RecordsAffected: "-1 for SELECT statements"). + /// Firebird classifies INSERT ... RETURNING and DML inside an execute block as stored procedure execution; the server's + /// count is reported for those only when it is positive, so procedures that change no rows keep -1. /// internal int StatementRecordsAffected { @@ -276,6 +278,7 @@ internal int StatementRecordsAffected DbStatementType.Insert => RecordsAffected, DbStatementType.Update => RecordsAffected, DbStatementType.Delete => RecordsAffected, + DbStatementType.StoredProcedure when RecordsAffected > 0 => RecordsAffected, _ => -1, }; } diff --git a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs index 1c9984af0..aa8008700 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs @@ -51,7 +51,7 @@ public sealed class FbDataReader : DbDataReader private bool _eof; private bool _isClosed; private int _position; - private int _recordsAffected = -1; + private int _recordsAffected; private Dictionary _columnsIndexesOrdinal; private Dictionary _columnsIndexesOrdinalCI; @@ -84,6 +84,7 @@ internal FbDataReader(FbCommand command, FbConnection connection, CommandBehavio _connection = connection; _commandBehavior = commandBehavior; _fields = _command.GetFieldsDescriptor(); + _recordsAffected = -1; UpdateRecordsAffected(); }