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