diff --git a/src/SingleStoreConnector/SingleStoreCommand.cs b/src/SingleStoreConnector/SingleStoreCommand.cs index 5901ba438..00a71c695 100644 --- a/src/SingleStoreConnector/SingleStoreCommand.cs +++ b/src/SingleStoreConnector/SingleStoreCommand.cs @@ -104,7 +104,9 @@ private SingleStoreCommand(SingleStoreCommand other) /// The number of rows affected. /// For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. /// For stored procedures, the return value is the number of rows affected by the last statement in the stored procedure, - /// or zero if the last statement is a SELECT. For all other types of statements, the return value is -1. + /// or zero if the last statement is a SELECT. For all other types of statements, the return value is -1. + /// If the number of affected rows exceeds , the return value is ; + /// use to get the exact count. public override int ExecuteNonQuery() => ExecuteNonQueryAsync(IOBehavior.Synchronous, CancellationToken.None).Result; public override object? ExecuteScalar() => ExecuteScalarAsync(IOBehavior.Synchronous, CancellationToken.None).Result; @@ -292,7 +294,9 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => /// A task representing the asynchronous operation. /// For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. /// For stored procedures, the return value is the number of rows affected by the last statement in the stored procedure, - /// or zero if the last statement is a SELECT. For all other types of statements, the return value is -1. + /// or zero if the last statement is a SELECT. For all other types of statements, the return value is -1. + /// If the number of affected rows exceeds , the return value is ; + /// use to get the exact count. public override Task ExecuteNonQueryAsync(CancellationToken cancellationToken) => ExecuteNonQueryAsync(AsyncIOBehavior, cancellationToken).AsTask(); diff --git a/src/SingleStoreConnector/SingleStoreDataReader.cs b/src/SingleStoreConnector/SingleStoreDataReader.cs index 92ed68a5a..acadde98e 100644 --- a/src/SingleStoreConnector/SingleStoreDataReader.cs +++ b/src/SingleStoreConnector/SingleStoreDataReader.cs @@ -208,8 +208,10 @@ public override bool HasRows /// /// For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. /// For stored procedures, the return value is the number of rows affected by the last statement in the stored procedure, - /// or zero if the last statement is a SELECT. For all other types of statements, the return value is -1. - public override int RecordsAffected => RealRecordsAffected is ulong recordsAffected ? checked((int) recordsAffected) : -1; + /// or zero if the last statement is a SELECT. For all other types of statements, the return value is -1. + /// If the number of affected rows exceeds , the return value is ; + /// use to get the exact count. + public override int RecordsAffected => RealRecordsAffected is ulong recordsAffected ? (int) Math.Min(recordsAffected, int.MaxValue) : -1; public override int GetOrdinal(string name) => GetResultSet().GetOrdinal(name); public override bool GetBoolean(int ordinal) => GetResultSet().GetCurrentRow().GetBoolean(ordinal); @@ -463,7 +465,11 @@ protected override void Dispose(bool disposing) internal Activity? Activity { get; private set; } internal ISingleStoreCommand? Command { get; private set; } internal SingleStoreConnection? Connection => Command?.Connection; - internal ulong? RealRecordsAffected { get; set; } + /// + /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement, without truncation to . + /// + /// Returns null when would return -1. + public ulong? RealRecordsAffected { get; internal set; } internal ServerSession? Session => Command?.Connection!.Session; internal async Task InitAsync(CommandListPosition commandListPosition, ICommandPayloadCreator payloadCreator, IDictionary? cachedProcedures, ISingleStoreCommand command, CommandBehavior behavior, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken) diff --git a/tests/SingleStoreConnector.Tests/ConnectionTests.cs b/tests/SingleStoreConnector.Tests/ConnectionTests.cs index 6f453f1a8..1889422a3 100644 --- a/tests/SingleStoreConnector.Tests/ConnectionTests.cs +++ b/tests/SingleStoreConnector.Tests/ConnectionTests.cs @@ -19,6 +19,18 @@ public void Dispose() m_server.Stop(); } + [Fact] + public void RecordsAffectedAboveInt32MaxValueIsClamped() + { + using var connection = new SingleStoreConnection(m_csb.ConnectionString); + connection.Open(); + using var command = new SingleStoreCommand("ALTER TABLE big_table ADD COLUMN c INT", connection); + Assert.Equal(int.MaxValue, command.ExecuteNonQuery()); + using var reader = command.ExecuteReader(); + Assert.Equal(int.MaxValue, reader.RecordsAffected); + Assert.Equal(4_000_000_000UL, reader.RealRecordsAffected); + } + [Fact] public void PooledConnectionIsReturnedToPool() { diff --git a/tests/SingleStoreConnector.Tests/FakeSingleStoreServerConnection.cs b/tests/SingleStoreConnector.Tests/FakeSingleStoreServerConnection.cs index 3b3eabedd..9733b67a1 100644 --- a/tests/SingleStoreConnector.Tests/FakeSingleStoreServerConnection.cs +++ b/tests/SingleStoreConnector.Tests/FakeSingleStoreServerConnection.cs @@ -167,6 +167,10 @@ public async Task RunAsync(TcpClient client, CancellationToken token) var wasSet = CancelQueryEvent.Wait(0, token); await SendAsync(stream, 1, WriteOk); } + else if (query == "ALTER TABLE big_table ADD COLUMN c INT") + { + await SendAsync(stream, 1, x => WriteOk(x, 4_000_000_000UL)); + } else if (query == "select infinity") { var packets = new[] @@ -295,10 +299,20 @@ private void WriteInitialHandshake(BinaryWriter writer) writer.Write((byte) 0); } - private static void WriteOk(BinaryWriter writer) + private static void WriteOk(BinaryWriter writer) => WriteOk(writer, 0); + + private static void WriteOk(BinaryWriter writer, ulong affectedRows) { writer.Write((byte) 0); // signature - writer.Write((byte) 0); // 0 rows affected + if (affectedRows < 251) + { + writer.Write((byte) affectedRows); // rows affected + } + else + { + writer.Write((byte) 0xFE); // 8-byte length-encoded integer + writer.Write(affectedRows); // rows affected + } writer.Write((byte) 0); // last insert ID writer.Write((ushort) 0); // server status writer.Write((ushort) 0); // warning count