Skip to content

Commit 2583661

Browse files
authored
Save affected_rows on the wrapper when reading results (#1383)
Between fetching the result and accessing the affected_rows property, GC might have been triggered and might have freed some Mysql2::Statement objects. This calls mysql_stmt_close which resets the connection affected_rows to -1, which in turn is treated as an error when calling mysql_affected_rows. ``` client.query("SELECT 1") client.affected_rows # raises Mysql2::Error ``` Note that the data type of mysql_affected_rows changed from my_ulonglong to uint64_t starting with MySQL 8.0. Older versions should still work, though.
1 parent f6a9b68 commit 2583661

File tree

3 files changed

+36
-11
lines changed

3 files changed

+36
-11
lines changed

ext/mysql2/client.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ static VALUE allocate(VALUE klass) {
411411
wrapper->initialized = 0; /* will be set true after calling mysql_init */
412412
wrapper->closed = 1; /* will be set false after calling mysql_real_connect */
413413
wrapper->refcount = 1;
414+
wrapper->affected_rows = -1;
414415
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
415416

416417
return obj;
@@ -669,6 +670,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
669670
wrapper->active_fiber = Qnil;
670671
rb_raise_mysql2_error(wrapper);
671672
}
673+
wrapper->affected_rows = mysql_affected_rows(wrapper->client);
672674

673675
is_streaming = rb_hash_aref(rb_ivar_get(self, intern_current_query_options), sym_stream);
674676
if (is_streaming == Qtrue) {
@@ -1155,12 +1157,12 @@ static VALUE rb_mysql_client_session_track(VALUE self, VALUE type) {
11551157
* if it was an UPDATE, DELETE, or INSERT.
11561158
*/
11571159
static VALUE rb_mysql_client_affected_rows(VALUE self) {
1158-
my_ulonglong retVal;
1160+
uint64_t retVal;
11591161
GET_CLIENT(self);
11601162

11611163
REQUIRE_CONNECTED(wrapper);
1162-
retVal = mysql_affected_rows(wrapper->client);
1163-
if (retVal == (my_ulonglong)-1) {
1164+
retVal = wrapper->affected_rows;
1165+
if (retVal == (uint64_t)-1) {
11641166
rb_raise_mysql2_error(wrapper);
11651167
}
11661168
return ULL2NUM(retVal);

ext/mysql2/client.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ typedef struct {
1212
int initialized;
1313
int refcount;
1414
int closed;
15+
uint64_t affected_rows;
1516
MYSQL *client;
1617
} mysql_client_wrapper;
1718

spec/mysql2/client_spec.rb

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,7 @@ def run_gc
10331033
expect(@client).to respond_to(:last_id)
10341034
end
10351035

1036-
it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
1036+
it "#last_id should return a Fixnum, from the last INSERT/UPDATE" do
10371037
expect(@client.last_id).to eql(0)
10381038
@client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
10391039
expect(@client.last_id).to eql(1)
@@ -1043,13 +1043,6 @@ def run_gc
10431043
expect(@client).to respond_to(:last_id)
10441044
end
10451045

1046-
it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
1047-
@client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
1048-
expect(@client.affected_rows).to eql(1)
1049-
@client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
1050-
expect(@client.affected_rows).to eql(1)
1051-
end
1052-
10531046
it "#last_id should handle BIGINT auto-increment ids above 32 bits" do
10541047
# The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x.
10551048
# Insert a row with a given ID, this should raise the auto-increment state
@@ -1058,6 +1051,35 @@ def run_gc
10581051
@client.query "INSERT INTO lastIdTest (blah) VALUES (5001)"
10591052
expect(@client.last_id).to eql(5000000001)
10601053
end
1054+
1055+
it "#last_id isn't cleared by Statement#close" do
1056+
stmt = @client.prepare("INSERT INTO lastIdTest (blah) VALUES (1234)")
1057+
1058+
@client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
1059+
expect(@client.last_id).to eql(1)
1060+
1061+
stmt.close
1062+
1063+
expect(@client.last_id).to eql(1)
1064+
end
1065+
1066+
it "#affected_rows should return a Fixnum, from the last INSERT/UPDATE" do
1067+
@client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
1068+
expect(@client.affected_rows).to eql(1)
1069+
@client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
1070+
expect(@client.affected_rows).to eql(1)
1071+
end
1072+
1073+
it "#affected_rows isn't cleared by Statement#close" do
1074+
stmt = @client.prepare("INSERT INTO lastIdTest (blah) VALUES (1234)")
1075+
1076+
@client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
1077+
expect(@client.affected_rows).to eql(1)
1078+
1079+
stmt.close
1080+
1081+
expect(@client.affected_rows).to eql(1)
1082+
end
10611083
end
10621084

10631085
it "should respond to #thread_id" do

0 commit comments

Comments
 (0)