Skip to content

Commit 7f74a40

Browse files
committed
Fix affected rows calculation. Fixes #1096
Signed-off-by: Bradley Grainger <[email protected]>
1 parent 832d7b4 commit 7f74a40

File tree

5 files changed

+87
-27
lines changed

5 files changed

+87
-27
lines changed

src/MySqlConnector/Core/ResultSet.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ public void Reset()
2222
BufferState = ResultSetState.None;
2323
ColumnDefinitions = null;
2424
ColumnTypes = null;
25-
RecordsAffected = null;
2625
WarningCount = 0;
2726
State = ResultSetState.None;
2827
ContainsCommandParameters = false;
@@ -46,7 +45,12 @@ public async Task ReadResultSetHeaderAsync(IOBehavior ioBehavior)
4645
if (firstByte == OkPayload.Signature)
4746
{
4847
var ok = OkPayload.Create(payload.Span, Session.SupportsDeprecateEof, Session.SupportsSessionTrack);
49-
RecordsAffected = (RecordsAffected ?? 0) + ok.AffectedRowCount;
48+
49+
// if we've read a result set header then this is a SELECT statement, so we shouldn't overwrite RecordsAffected
50+
// (which should be -1 for SELECT) unless the server reports a non-zero count
51+
if (State != ResultSetState.ReadResultSetHeader || ok.AffectedRowCount != 0)
52+
RecordsAffected = (RecordsAffected ?? 0) + ok.AffectedRowCount;
53+
5054
if (ok.LastInsertId != 0)
5155
Command?.SetLastInsertedId((long) ok.LastInsertId);
5256
WarningCount = ok.WarningCount;

src/MySqlConnector/MySqlDataReader.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ private void ActivateResultSet(CancellationToken cancellationToken)
130130
throw new MySqlException("Failed to read the result set.", m_resultSet.ReadResultSetHeaderException.SourceException);
131131
}
132132

133-
m_recordsAffected = m_recordsAffected is null ? m_resultSet.RecordsAffected : m_recordsAffected.Value + (m_resultSet.RecordsAffected ?? 0);
134133
m_hasWarnings = m_resultSet.WarningCount != 0;
135134
}
136135

@@ -203,7 +202,7 @@ public override bool HasRows
203202
}
204203

205204
public override bool IsClosed => Command is null;
206-
public override int RecordsAffected => m_recordsAffected.HasValue ? checked((int) m_recordsAffected) : -1;
205+
public override int RecordsAffected => GetResultSet().RecordsAffected is ulong recordsAffected ? checked((int) recordsAffected) : -1;
207206

208207
public override int GetOrdinal(string name) => GetResultSet().GetOrdinal(name);
209208

@@ -667,7 +666,6 @@ private ResultSet GetResultSet()
667666
readonly IDictionary<string, CachedProcedure?>? m_cachedProcedures;
668667
CommandListPosition m_commandListPosition;
669668
bool m_closed;
670-
ulong? m_recordsAffected;
671669
bool m_hasWarnings;
672670
ResultSet? m_resultSet;
673671
bool m_hasMoreResults;

tests/SideBySide/CommandTests.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,26 @@ public void ExecuteNonQueryForSelectReturnsNegativeOne()
118118
Assert.Equal(-1, command.ExecuteNonQuery());
119119
}
120120

121-
[Fact]
121+
[Fact]
122122
public async Task ExecuteNonQueryReturnValue()
123123
{
124124
using var connection = new MySqlConnection(m_database.Connection.ConnectionString);
125125
await connection.OpenAsync();
126-
await connection.ExecuteAsync(@"drop table if exists execute_non_query;
127-
create table execute_non_query(id integer not null primary key auto_increment, value text null);");
128-
Assert.Equal(4, await connection.ExecuteAsync("insert into execute_non_query(value) values(null), (null), ('one'), ('two');"));
129-
Assert.Equal(-1, await connection.ExecuteAsync("select value from execute_non_query;"));
130-
Assert.Equal(2, await connection.ExecuteAsync("delete from execute_non_query where value is null;"));
131-
Assert.Equal(1, await connection.ExecuteAsync("update execute_non_query set value = 'three' where value = 'one';"));
126+
const string setUp = @"drop table if exists execute_non_query;
127+
create table execute_non_query(id integer not null primary key auto_increment, value text null);";
128+
await connection.ExecuteAsync(setUp);
129+
130+
const string insert = "insert into execute_non_query(value) values(null), (null), ('one'), ('two');";
131+
Assert.Equal(4, await connection.ExecuteAsync(insert));
132+
const string select = "select value from execute_non_query;";
133+
Assert.Equal(-1, await connection.ExecuteAsync(select));
134+
const string delete = "delete from execute_non_query where value is null;";
135+
Assert.Equal(2, await connection.ExecuteAsync(delete));
136+
const string update = "update execute_non_query set value = 'three' where value = 'one';";
137+
Assert.Equal(1, await connection.ExecuteAsync(update));
138+
139+
await connection.ExecuteAsync(setUp);
140+
Assert.Equal(7, await connection.ExecuteAsync(insert + select + delete + update));
132141
}
133142

134143
[SkippableFact(Baseline = "https://bugs.mysql.com/bug.php?id=88611")]

tests/SideBySide/InsertTests.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public async Task LastInsertedId()
2222
await m_database.Connection.OpenAsync();
2323
using var command = new MySqlCommand("INSERT INTO insert_ai (text) VALUES (@text);", m_database.Connection);
2424
command.Parameters.Add(new() { ParameterName = "@text", Value = "test" });
25-
await command.ExecuteNonQueryAsync();
25+
Assert.Equal(1, await command.ExecuteNonQueryAsync());
2626
Assert.Equal(1L, command.LastInsertedId);
2727
}
2828
finally
@@ -41,7 +41,7 @@ public async Task LastInsertedIdNegative()
4141
await m_database.Connection.OpenAsync();
4242
using var command = new MySqlCommand("INSERT INTO insert_ai(rowid) VALUES (@rowid);", m_database.Connection);
4343
command.Parameters.AddWithValue("@rowid", -1);
44-
await command.ExecuteNonQueryAsync();
44+
Assert.Equal(1, await command.ExecuteNonQueryAsync());
4545
Assert.Equal(-1L, command.LastInsertedId);
4646
}
4747
finally
@@ -60,7 +60,7 @@ public async Task LastInsertedIdUlong()
6060
await m_database.Connection.OpenAsync();
6161
using var command = new MySqlCommand("INSERT INTO insert_ai(rowid) VALUES (@rowid);", m_database.Connection);
6262
command.Parameters.AddWithValue("@rowid", ((ulong) long.MaxValue) + 1);
63-
await command.ExecuteNonQueryAsync();
63+
Assert.Equal(1, await command.ExecuteNonQueryAsync());
6464
Assert.Equal(long.MinValue, command.LastInsertedId);
6565
}
6666
finally
@@ -82,7 +82,7 @@ public async Task LastInsertedIdAfterSelect()
8282
using var command = new MySqlCommand(@"SELECT * FROM insert_ai;
8383
INSERT INTO insert_ai (text) VALUES (@text);", m_database.Connection);
8484
command.Parameters.Add(new() { ParameterName = "@text", Value = "test" });
85-
await command.ExecuteNonQueryAsync();
85+
Assert.Equal(1, await command.ExecuteNonQueryAsync());
8686
Assert.Equal(2L, command.LastInsertedId);
8787
}
8888
finally
@@ -104,7 +104,7 @@ public async Task LastInsertedIdBeforeSelect()
104104
using var command = new MySqlCommand(@"INSERT INTO insert_ai (text) VALUES (@text);
105105
SELECT * FROM insert_ai;", m_database.Connection);
106106
command.Parameters.Add(new() { ParameterName = "@text", Value = "test" });
107-
await command.ExecuteNonQueryAsync();
107+
Assert.Equal(1, await command.ExecuteNonQueryAsync());
108108
Assert.Equal(2L, command.LastInsertedId);
109109
}
110110
finally
@@ -124,7 +124,7 @@ public async Task LastInsertedIdTwoInserts()
124124
await m_database.Connection.OpenAsync();
125125
using var command = new MySqlCommand(@"INSERT INTO insert_ai (text) VALUES ('test1');
126126
INSERT INTO insert_ai (text) VALUES ('test2');", m_database.Connection);
127-
await command.ExecuteNonQueryAsync();
127+
Assert.Equal(2, await command.ExecuteNonQueryAsync());
128128
Assert.Equal(2L, command.LastInsertedId);
129129
}
130130
finally
@@ -145,7 +145,7 @@ public async Task LastInsertedIdLockTables()
145145
using var command = new MySqlCommand(@"LOCK TABLES insert_ai WRITE;
146146
INSERT INTO insert_ai (text) VALUES ('test');
147147
UNLOCK TABLES;", m_database.Connection);
148-
await command.ExecuteNonQueryAsync();
148+
Assert.Equal(1, await command.ExecuteNonQueryAsync());
149149
Assert.Equal(1L, command.LastInsertedId);
150150
}
151151
finally
@@ -176,7 +176,7 @@ Foreign Key(foreign_id) REFERENCES TestTable(id)
176176
await m_database.Connection.OpenAsync();
177177
using var command = new MySqlCommand(@"INSERT INTO TestTable(column1) VALUES('hello');
178178
INSERT INTO TestTableWithForeignKey(foreign_id, column2) VALUES(LAST_INSERT_ID(), 'test');", m_database.Connection);
179-
await command.ExecuteNonQueryAsync();
179+
Assert.Equal(2, await command.ExecuteNonQueryAsync());
180180
Assert.Equal(1L, command.LastInsertedId);
181181
}
182182
finally
@@ -286,7 +286,7 @@ public void InsertDateTimeOffset()
286286
DbType = DbType.DateTimeOffset,
287287
Value = value.datetimeoffset1
288288
});
289-
cmd.ExecuteNonQuery();
289+
Assert.Equal(1, cmd.ExecuteNonQuery());
290290
}
291291
finally
292292
{
@@ -313,7 +313,7 @@ public void InsertMySqlDateTime()
313313
using var cmd = m_database.Connection.CreateCommand();
314314
cmd.CommandText = @"insert into insert_mysqldatetime(ts) values(@ts);";
315315
cmd.Parameters.AddWithValue("@ts", new MySqlDateTime(2018, 6, 9, 12, 34, 56, 123456));
316-
cmd.ExecuteNonQuery();
316+
Assert.Equal(1, cmd.ExecuteNonQuery());
317317
}
318318
finally
319319
{
@@ -341,7 +341,7 @@ public void InsertMemoryStream(bool prepare)
341341
cmd.Parameters.AddWithValue("@blb", new MemoryStream(new byte[] { 97, 98, 99, 100 }, 0, 4, false, true));
342342
if (prepare)
343343
cmd.Prepare();
344-
cmd.ExecuteNonQuery();
344+
Assert.Equal(1, cmd.ExecuteNonQuery());
345345
}
346346
finally
347347
{
@@ -378,7 +378,7 @@ public void InsertStringBuilder(bool prepare)
378378
cmd.Parameters.AddWithValue("@str", value);
379379
if (prepare)
380380
cmd.Prepare();
381-
cmd.ExecuteNonQuery();
381+
Assert.Equal(1, cmd.ExecuteNonQuery());
382382

383383
using var reader = connection.ExecuteReader(@"select str from insert_string_builder order by rowid;");
384384
Assert.True(reader.Read());
@@ -404,7 +404,7 @@ public void InsertBigInteger(bool prepare)
404404
cmd.Parameters.AddWithValue("@value", new BigInteger(value));
405405
if (prepare)
406406
cmd.Prepare();
407-
cmd.ExecuteNonQuery();
407+
Assert.Equal(1, cmd.ExecuteNonQuery());
408408

409409
using var reader = connection.ExecuteReader(@"select value from insert_big_integer order by rowid;");
410410
Assert.True(reader.Read());
@@ -451,7 +451,7 @@ public void InsertMySqlDecimalAsDecimal(bool prepare)
451451
cmd.Parameters.AddWithValue("@value", new MySqlDecimal(value));
452452
if (prepare)
453453
cmd.Prepare();
454-
cmd.ExecuteNonQuery();
454+
Assert.Equal(1, cmd.ExecuteNonQuery());
455455

456456
using var reader = connection.ExecuteReader(@"select value from insert_mysql_decimal order by rowid;");
457457
Assert.True(reader.Read());
@@ -474,7 +474,7 @@ public void ReadMySqlDecimalUsingReader(bool prepare)
474474
using var cmd = connection.CreateCommand();
475475
cmd.CommandText = @"insert into insert_mysql_decimal(value) values(@value);";
476476
cmd.Parameters.AddWithValue("@value", value);
477-
cmd.ExecuteNonQuery();
477+
Assert.Equal(1, cmd.ExecuteNonQuery());
478478

479479
cmd.CommandText = @"select value from insert_mysql_decimal order by rowid;";
480480
if (prepare)

tests/SideBySide/QueryTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,55 @@ public void Dispose()
1313
m_database.Connection.Close();
1414
}
1515

16+
[Theory]
17+
[InlineData(true, false, false)]
18+
[InlineData(false, true, false)]
19+
[InlineData(false, false, true)]
20+
public void Bug1096(bool sprocBeforeInsert, bool sprocAfterInsert, bool sprocTwiceBeforeInsert)
21+
{
22+
var csb = new MySqlConnectionStringBuilder(AppConfig.ConnectionString);
23+
csb.UseAffectedRows = true;
24+
using var connection = new MySqlConnection(csb.ConnectionString);
25+
connection.Open();
26+
27+
connection.Execute(@"
28+
DROP TABLE IF EXISTS bug_1096;
29+
CREATE TABLE bug_1096 (
30+
`Id` INT NOT NULL AUTO_INCREMENT,
31+
`Name` VARCHAR (50) NOT NULL,
32+
PRIMARY KEY (`Id`)
33+
);
34+
35+
DROP PROCEDURE IF EXISTS sp_bug_1096;
36+
CREATE PROCEDURE sp_bug_1096 (in pId INT, IN pName VARCHAR(50))
37+
BEGIN
38+
UPDATE bug_1096 SET `Name` = pName WHERE (`Id` = pId);
39+
SELECT 0 AS ResultCode;
40+
END;
41+
42+
INSERT INTO bug_1096 (`Name`) VALUES ('Demo-Name');");
43+
44+
var sproc = "CALL sp_bug_1096 (1, 'Demo-Name-Updated');";
45+
var insert = "INSERT INTO bug_1096 (`Name`) VALUES ('Demo-Name-Updated-Batch');";
46+
var expectedRowsAffected = connection.ServerVersion.IndexOf("MariaDB") == -1 ? 1 : 2;
47+
48+
if (sprocBeforeInsert)
49+
{
50+
var rowsAffected = connection.Execute(sproc + insert);
51+
Assert.Equal(expectedRowsAffected, rowsAffected);
52+
}
53+
else if (sprocAfterInsert)
54+
{
55+
var rowsAffected = connection.Execute(insert + sproc);
56+
Assert.Equal(expectedRowsAffected, rowsAffected);
57+
}
58+
else if (sprocTwiceBeforeInsert)
59+
{
60+
var rowsAffected = connection.Execute(sproc + sproc + insert);
61+
Assert.Equal(expectedRowsAffected, rowsAffected);
62+
}
63+
}
64+
1665
[Fact]
1766
public void GetOrdinal()
1867
{

0 commit comments

Comments
 (0)