diff --git a/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs b/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs index c87c804b..224e24a6 100644 --- a/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs +++ b/src/FirebirdSql.Data.FirebirdClient.Tests/FbDataReaderTests.cs @@ -535,4 +535,60 @@ 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); + } + } + + [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 551c6ea6..7bb0c0b1 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs @@ -263,6 +263,27 @@ 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 + { + get + { + return _statement?.StatementType switch + { + DbStatementType.Insert => RecordsAffected, + DbStatementType.Update => RecordsAffected, + DbStatementType.Delete => RecordsAffected, + DbStatementType.StoredProcedure when RecordsAffected > 0 => RecordsAffected, + _ => -1, + }; + } + } + internal bool IsDisposed { get { return _disposed; } @@ -529,13 +550,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 +581,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 0cf1d40c..aa800870 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDataReader.cs @@ -84,6 +84,7 @@ internal FbDataReader(FbCommand command, FbConnection connection, CommandBehavio _connection = connection; _commandBehavior = commandBehavior; _fields = _command.GetFieldsDescriptor(); + _recordsAffected = -1; UpdateRecordsAffected(); } @@ -1016,10 +1017,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; } } }