Skip to content

Commit a9787cb

Browse files
committed
Merge pull request #448 from sodabrew/check_streaming_errors
Check for closed connection while streaming results
2 parents 5fac050 + 6e56949 commit a9787cb

File tree

2 files changed

+61
-36
lines changed

2 files changed

+61
-36
lines changed

ext/mysql2/result.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
430430
ID db_timezone, app_timezone, dbTz, appTz;
431431
mysql2_result_wrapper * wrapper;
432432
unsigned long i;
433+
const char * errstr;
433434
int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1, streaming = 0;
434435
MYSQL_FIELD * fields = NULL;
435436

@@ -493,7 +494,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
493494
}
494495

495496
if (wrapper->lastRowProcessed == 0) {
496-
if(streaming) {
497+
if (streaming) {
497498
/* We can't get number of rows if we're streaming, */
498499
/* until we've finished fetching all rows */
499500
wrapper->numberOfRows = 0;
@@ -509,7 +510,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
509510
}
510511

511512
if (streaming) {
512-
if(!wrapper->streamingComplete) {
513+
if (!wrapper->streamingComplete) {
513514
VALUE row;
514515

515516
fields = mysql_fetch_fields(wrapper->result);
@@ -527,6 +528,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
527528

528529
wrapper->numberOfRows = wrapper->lastRowProcessed;
529530
wrapper->streamingComplete = 1;
531+
532+
// Check for errors, the connection might have gone out from under us
533+
// mysql_error returns an empty string if there is no error
534+
errstr = mysql_error(wrapper->client_wrapper->client);
535+
if (errstr[0]) {
536+
rb_raise(cMysql2Error, "%s", errstr);
537+
}
530538
} else {
531539
rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
532540
}

spec/mysql2/result_spec.rb

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,6 @@
66
@result = @client.query "SELECT 1"
77
end
88

9-
it "should maintain a count while streaming" do
10-
result = @client.query('SELECT 1')
11-
12-
result.count.should eql(1)
13-
result.each.to_a
14-
result.count.should eql(1)
15-
end
16-
17-
it "should set the actual count of rows after streaming" do
18-
@client.query "USE test"
19-
result = @client.query("SELECT * FROM mysql2_test", :stream => true, :cache_rows => false)
20-
result.count.should eql(0)
21-
result.each {|r| }
22-
result.count.should eql(1)
23-
end
24-
25-
it "should not yield nil at the end of streaming" do
26-
result = @client.query('SELECT * FROM mysql2_test', :stream => true)
27-
result.each { |r| r.should_not be_nil}
28-
end
29-
30-
it "#count should be zero for rows after streaming when there were no results " do
31-
@client.query "USE test"
32-
result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false)
33-
result.count.should eql(0)
34-
result.each.to_a
35-
result.count.should eql(0)
36-
end
37-
389
it "should have included Enumerable" do
3910
Mysql2::Result.ancestors.include?(Enumerable).should be_true
4011
end
@@ -121,7 +92,6 @@
12192

12293
context "#fields" do
12394
before(:each) do
124-
@client.query "USE test"
12595
@test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1")
12696
end
12797

@@ -135,9 +105,59 @@
135105
end
136106
end
137107

108+
context "streaming" do
109+
it "should maintain a count while streaming" do
110+
result = @client.query('SELECT 1')
111+
112+
result.count.should eql(1)
113+
result.each.to_a
114+
result.count.should eql(1)
115+
end
116+
117+
it "should set the actual count of rows after streaming" do
118+
result = @client.query("SELECT * FROM mysql2_test", :stream => true, :cache_rows => false)
119+
result.count.should eql(0)
120+
result.each {|r| }
121+
result.count.should eql(1)
122+
end
123+
124+
it "should not yield nil at the end of streaming" do
125+
result = @client.query('SELECT * FROM mysql2_test', :stream => true, :cache_rows => false)
126+
result.each { |r| r.should_not be_nil}
127+
end
128+
129+
it "#count should be zero for rows after streaming when there were no results" do
130+
result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false)
131+
result.count.should eql(0)
132+
result.each.to_a
133+
result.count.should eql(0)
134+
end
135+
136+
it "should raise an exception if streaming ended due to a timeout" do
137+
# Create an extra client instance, since we're going to time it out
138+
client = Mysql2::Client.new DatabaseCredentials['root']
139+
client.query "CREATE TEMPORARY TABLE streamingTest (val BINARY(255))"
140+
141+
# Insert enough records to force the result set into multiple reads
142+
# (the BINARY type is used simply because it forces full width results)
143+
10000.times do |i|
144+
client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')"
145+
end
146+
147+
client.query "SET net_write_timeout = 1"
148+
res = client.query "SELECT * FROM streamingTest", :stream => true, :cache_rows => false
149+
150+
lambda {
151+
res.each_with_index do |row, i|
152+
# Exhaust the first result packet then trigger a timeout
153+
sleep 2 if i > 0 && i % 1000 == 0
154+
end
155+
}.should raise_error(Mysql2::Error, /Lost connection/)
156+
end
157+
end
158+
138159
context "row data type mapping" do
139160
before(:each) do
140-
@client.query "USE test"
141161
@test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
142162
end
143163

@@ -323,7 +343,6 @@
323343
result['enum_test'].encoding.should eql(Encoding.find('utf-8'))
324344

325345
client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
326-
client2.query "USE test"
327346
result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
328347
result['enum_test'].encoding.should eql(Encoding.find('us-ascii'))
329348
client2.close
@@ -353,7 +372,6 @@
353372
result['set_test'].encoding.should eql(Encoding.find('utf-8'))
354373

355374
client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
356-
client2.query "USE test"
357375
result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
358376
result['set_test'].encoding.should eql(Encoding.find('us-ascii'))
359377
client2.close
@@ -436,7 +454,6 @@
436454
result[field].encoding.should eql(Encoding.find('utf-8'))
437455

438456
client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
439-
client2.query "USE test"
440457
result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
441458
result[field].encoding.should eql(Encoding.find('us-ascii'))
442459
client2.close

0 commit comments

Comments
 (0)