Repository navigation
Clamp RecordsAffected to int.MaxValue instead of throwing - #28
francoisjehl wants to merge 2 commits into
Conversation
PR SummaryLow Risk Overview A regression test runs Reviewed by Cursor Bugbot for commit 4576eb6. Configure here. |
| 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 <see cref="int.MaxValue"/>, the return value is <see cref="int.MaxValue"/>.</remarks> | ||
| public override int RecordsAffected => RealRecordsAffected is ulong recordsAffected ? (int) Math.Min(recordsAffected, int.MaxValue) : -1; |
There was a problem hiding this comment.
thanks for the fix!
one thing to keep in mind is that previously the OverflowException made the caller aware that the count didn't fit into int, now there's no way a caller who gets int.MaxValue can't tell whether they actually did get int.MaxValue or more.
that's why I think it makes sense to make RealRecordsAffected public with internal set and add a mention of it in the remarks, so the users know where to look for it.
got this idea from Npsql connector -- https://github.com/npgsql/npgsql/blob/af6676e70ea51699df13b9f97db8f02bb9b6f629/src/Npgsql/NpgsqlDataReader.cs#L885-L905
There was a problem hiding this comment.
You're welcome and good call!
| var wasSet = CancelQueryEvent.Wait(0, token); | ||
| await SendAsync(stream, 1, WriteOk); | ||
| } | ||
| else if (query == "ALTER TABLE big_table ADD COLUMN c INT") |
There was a problem hiding this comment.
nit: this duplicates WriteOk except for the value of affected-rows field. an optional ulong affectedRows = 0 parameter in WriteOk might be a better choice to avoid duplications
There was a problem hiding this comment.
WriteOk is passed as a method group in several places, so an optional parameter didn't want to compile.
Thus I thought an overload would do the job? You tell me.
There was a problem hiding this comment.
good point! optional parameters aren't considered in method group conversion, so an overload
is the right call. lets have WriteOk(BinaryWriter) delgating to new WriteOk(BinaryWriter, ulong affectedRows)
| /// 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.</remarks> | ||
| /// 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 <see cref="int.MaxValue"/>, the return value is <see cref="int.MaxValue"/>; |
There was a problem hiding this comment.
thanks for updating these!
one detail though: ExecuteNonQuery() creates and disposes the reader internally, so users can't access RealRecordsAffected. could we add this info here and in ExecuteNonQueryAsync()? smth like:
If the number of affected rows exceeds int.MaxValue, the return value is int.MaxValue. To obtain the exact count, use ExecuteReader(), call SingleStoreDataReader.NextResult until it returns false, then read SingleStoreDataReader.RealRecordsAffected
for the async version we should use ExecuteReaderAsync() and NextResultAsync()
Context
On DDL like
ALTER TABLE ADD COLUMN, SingleStore reports the table's row count as affected rows.Issue is, this is 32 bits in ADO.NET and thus once a table passes 2^31 rows, it throws
OverflowException.We faced that at Pigment on a 4B row table.
Implementation
(int) Math.Min(recordsAffected, int.MaxValue). This keeps theintcontract ofDbDataReader.RecordsAffected, saturates instead of throwing, and documents the cap in the XML remarks.