From f7b5af43a8a6a3e16a15e53a75eb4bffe42fc424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ho=CC=88ltje?= Date: Fri, 1 Mar 2013 12:27:58 -0500 Subject: [PATCH 001/783] Mysql SSL test: Needs to verify it is using ssl The MySQL SSL test was not actually testing that SSL was enabled. It was previously only detecting if SSL was enabled on the Server. --- spec/mysql2/client_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index afdec3308..9d83a4581 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -81,11 +81,13 @@ def connect *args results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a results[0]['Variable_name'].should eql('Ssl_cipher') results[0]['Value'].should_not be_nil - results[0]['Value'].class.should eql(String) + results[0]['Value'].should be_kind_of(String) + results[0]['Value'].should_not be_empty results[1]['Variable_name'].should eql('Ssl_version') results[1]['Value'].should_not be_nil - results[1]['Value'].class.should eql(String) + results[1]['Value'].should be_kind_of(String) + results[1]['Value'].should_not be_empty end it "should respond to #close" do From fcaf15e1bcbd0947dfa4856c7f5e7c6ffd657e4a Mon Sep 17 00:00:00 2001 From: Paul Bowsher Date: Tue, 12 Mar 2013 10:15:46 +0000 Subject: [PATCH 002/783] Make `cast_booleans` also cast BIT(1) Using `BIT(1)` to represent booleans is a common pattern within MySQL. This makes the existing `cast_booleans` switch cast `BIT(1)` as TrueClass or FalseClass --- ext/mysql2/result.c | 6 +++++- spec/mysql2/result_spec.rb | 21 ++++++++++++++++++++- spec/spec_helper.rb | 5 +++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index e4f3d3666..dd2cbf329 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -223,7 +223,11 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo val = Qnil; break; case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ - val = rb_str_new(row[i], fieldLengths[i]); + if (castBool && fields[i].length == 1) { + val = *row[i] == 1 ? Qtrue : Qfalse; + }else{ + val = rb_str_new(row[i], fieldLengths[i]); + } break; case MYSQL_TYPE_TINY: /* TINYINT field */ if (castBool && fields[i].length == 1) { diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index c0e50acf5..5b160d373 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -160,11 +160,16 @@ @test_result['null_test'].should eql(nil) end - it "should return Fixnum for a BIT value" do + it "should return String for a BIT(64) value" do @test_result['bit_test'].class.should eql(String) @test_result['bit_test'].should eql("\000\000\000\000\000\000\000\005") end + it "should return String for a BIT(1) value" do + @test_result['single_bit_test'].class.should eql(String) + @test_result['single_bit_test'].should eql("\001") + end + it "should return Fixnum for a TINYINT value" do [Fixnum, Bignum].should include(@test_result['tiny_int_test'].class) @test_result['tiny_int_test'].should eql(1) @@ -188,6 +193,20 @@ @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" end + it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do + @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)' + id1 = @client.last_id + @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)' + id2 = @client.last_id + + result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", :cast_booleans => true + result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", :cast_booleans => true + result1.first['single_bit_test'].should be_true + result2.first['single_bit_test'].should be_false + + @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" + end + it "should return Fixnum for a SMALLINT value" do [Fixnum, Bignum].should include(@test_result['small_int_test'].class) @test_result['small_int_test'].should eql(10) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4e29fe658..093b432fc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,6 +14,7 @@ id MEDIUMINT NOT NULL AUTO_INCREMENT, null_test VARCHAR(10), bit_test BIT(64), + single_bit_test BIT(1), tiny_int_test TINYINT, bool_cast_test TINYINT(1), small_int_test SMALLINT, @@ -50,7 +51,7 @@ client.query "DELETE FROM mysql2_test;" client.query %[ INSERT INTO mysql2_test ( - null_test, bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test, + null_test, bit_test, single_bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test, float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test, year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test, tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test, @@ -58,7 +59,7 @@ ) VALUES ( - NULL, b'101', 1, 1, 10, 10, 10, 10, + NULL, b'101', b'1', 1, 1, 10, 10, 10, 10, 10.3, 0, 10.3, 10.3, 0, '2010-4-4', '2010-4-4 11:44:00', '2010-4-4 11:44:00', '11:44:00', 2009, "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", From 3a6ebac784af04e314a7f60c7b02ed39a0c0047b Mon Sep 17 00:00:00 2001 From: AJ Acevedo Date: Thu, 21 Mar 2013 01:15:19 -0400 Subject: [PATCH 003/783] Add secure source to Gemfile and Gemfile.lock The source :rubygems is deprecated because HTTP requests are insecure. modified: Gemfile modified: Gemfile.lock --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index c0dfbf294..451c67371 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source :rubygems +source 'https://rubygems.org/' gemspec diff --git a/Gemfile.lock b/Gemfile.lock index 1fa6de09f..b76da8744 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,7 +4,7 @@ PATH mysql2 (0.3.12b6) GEM - remote: http://rubygems.org/ + remote: https://rubygems.org/ specs: activemodel (3.2.1) activesupport (= 3.2.1) From e488147773eef41ae96afba5224b7791bdef330f Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Tue, 26 Mar 2013 16:15:06 -0700 Subject: [PATCH 004/783] Add license to gemspec Prior to this commit, the gemspec did not include the license, which means that one can't use the rubygems API to automatically determine licensing of vendored gems. This commit adds the licensing to the gemspec. --- mysql2.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql2.gemspec b/mysql2.gemspec index 148b33ed8..01ad2192d 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -4,6 +4,7 @@ Gem::Specification.new do |s| s.name = %q{mysql2} s.version = Mysql2::VERSION s.authors = ["Brian Lopez"] + s.license = "MIT" s.email = %q{seniorlopez@gmail.com} s.extensions = ["ext/mysql2/extconf.rb"] s.homepage = %q{http://github.com/brianmario/mysql2} From 158954c86b0a95930c9f336a3dc5b82ba7e0657f Mon Sep 17 00:00:00 2001 From: Timur Alperovich Date: Fri, 19 Apr 2013 16:30:21 -0700 Subject: [PATCH 005/783] Remove the check for SSL CA or Key being set. Currently, the C bindings for the mysql2 gem enforce specifying either the ssl-ca or ssl-key option, in order to set any of the SSL flags. This makes the usage of mysql client worse for a few reasons. For one, it's impossible to specify the ssl-capath flag by itself. This is troubling if the server certificate is signed by a trusted authority, whose certificate is present in /etc/ssl/certs, for example. The other issue is that --ssl-cipher may not be specified on itsown. The reason it may be desired is that it forces the client to use SSL to connect to the server, without requiring the client to validate the server certificate. In certain situations that may actually be desirable (e.g. self-signed certificates which do not have a CA certificate generated). This patch also guards against calling mysql_ssl_set() uncondtionally by ensuring that at least one of the SSL options is set. Fixes: #355 --- ext/mysql2/client.c | 14 ++++++-------- lib/mysql2/client.rb | 3 ++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 5edfd244e..d99d27e07 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1051,14 +1051,12 @@ static VALUE set_charset_name(VALUE self, VALUE value) { static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) { GET_CLIENT(self); - if(!NIL_P(ca) || !NIL_P(key)) { - mysql_ssl_set(wrapper->client, - NIL_P(key) ? NULL : StringValuePtr(key), - NIL_P(cert) ? NULL : StringValuePtr(cert), - NIL_P(ca) ? NULL : StringValuePtr(ca), - NIL_P(capath) ? NULL : StringValuePtr(capath), - NIL_P(cipher) ? NULL : StringValuePtr(cipher)); - } + mysql_ssl_set(wrapper->client, + NIL_P(key) ? NULL : StringValuePtr(key), + NIL_P(cert) ? NULL : StringValuePtr(cert), + NIL_P(ca) ? NULL : StringValuePtr(ca), + NIL_P(capath) ? NULL : StringValuePtr(capath), + NIL_P(cipher) ? NULL : StringValuePtr(cipher)); return self; } diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index a24d34db2..95ff9a5ca 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -36,7 +36,8 @@ def initialize(opts = {}) # force the encoding to utf8 self.charset_name = opts[:encoding] || 'utf8' - ssl_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)) + ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher) + ssl_set(*ssl_options) if ssl_options.any? if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) } warn "============= WARNING FROM mysql2 =============" From fcb61fa796c19dd7730f9af6c870b38a6482b75a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 1 May 2013 14:54:11 -0700 Subject: [PATCH 006/783] Add rbx-20mode to Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1fbe6e356..13d3b83ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ rvm: - ree - rbx-18mode - rbx-19mode + - rbx-20mode bundler_args: --without benchmarks script: - bundle exec rake From 79c48a5a021351f9488b3955d842f29742947714 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 3 May 2013 15:47:46 -0700 Subject: [PATCH 007/783] Add task spec:valgrind --- tasks/rspec.rake | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tasks/rspec.rake b/tasks/rspec.rake index c4173f6e8..487091c54 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -2,10 +2,27 @@ begin require 'rspec' require 'rspec/core/rake_task' + desc " Run all examples with Valgrind" + namespace :spec do + task :valgrind do + VALGRIND_OPTS = %w{ + --num-callers=50 + --error-limit=no + --partial-loads-ok=yes + --undef-value-errors=no + --trace-children=yes + } + cmdline = "valgrind #{VALGRIND_OPTS.join(' ')} bundle exec rake spec" + puts cmdline + system cmdline + end + end + desc "Run all examples with RCov" RSpec::Core::RakeTask.new('spec:rcov') do |t| t.rcov = true end + RSpec::Core::RakeTask.new('spec') do |t| t.verbose = true end From b241f547d3404c3fac3515231f743d81e5f37ea9 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Sun, 9 Jun 2013 09:32:45 -0500 Subject: [PATCH 008/783] Track connection refcount to prevent GC races In certain cases a Mysql2::Client and a Mysql2::Result may be freed in the same GC run. If the Client is freed before the Result a segv may occur when the Result tries to use the socket to make sure there isn't any more data to read before freeing itself. Huge thanks to @evanphx for helping me track this one down and @rkh for helping with TravisCI since that's where the issue was finally reproducable. --- ext/mysql2/client.c | 13 ++++++++----- ext/mysql2/client.h | 1 + ext/mysql2/result.c | 5 ++++- ext/mysql2/result.h | 3 ++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index d99d27e07..19a504da5 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -185,10 +185,12 @@ static VALUE nogvl_close(void *ptr) { static void rb_mysql_client_free(void * ptr) { mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; - nogvl_close(wrapper); + if (wrapper->refcount == 0) { + nogvl_close(wrapper); - xfree(wrapper->client); - xfree(ptr); + xfree(wrapper->client); + xfree(ptr); + } } static VALUE allocate(VALUE klass) { @@ -200,6 +202,7 @@ static VALUE allocate(VALUE klass) { wrapper->reconnect_enabled = 0; wrapper->connected = 0; /* means that a database connection is open */ wrapper->initialized = 0; /* means that that the wrapper is initialized */ + wrapper->refcount = 0; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); return obj; } @@ -393,7 +396,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { return Qnil; } - resultObj = rb_mysql_result_to_obj(result); + resultObj = rb_mysql_result_to_obj(wrapper, result); /* pass-through query options for result construction later */ rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options"))); @@ -942,7 +945,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) return Qnil; } - resultObj = rb_mysql_result_to_obj(result); + resultObj = rb_mysql_result_to_obj(wrapper, result); /* pass-through query options for result construction later */ rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options"))); diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 49a1f0e3e..a70aa7bfa 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -38,6 +38,7 @@ typedef struct { int active; int connected; int initialized; + int refcount; MYSQL *client; } mysql_client_wrapper; diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index dd2cbf329..a15cb6d97 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -72,6 +72,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { if (wrapper && wrapper->resultFreed != 1) { mysql_free_result(wrapper->result); wrapper->resultFreed = 1; + wrapper->client_wrapper->refcount--; } } @@ -562,7 +563,7 @@ static VALUE rb_mysql_result_count(VALUE self) { } /* Mysql2::Result */ -VALUE rb_mysql_result_to_obj(MYSQL_RES * r) { +VALUE rb_mysql_result_to_obj(mysql_client_wrapper *client_wrapper, MYSQL_RES *r) { VALUE obj; mysql2_result_wrapper * wrapper; obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper); @@ -575,6 +576,8 @@ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) { wrapper->rows = Qnil; wrapper->encoding = Qnil; wrapper->streamingComplete = 0; + wrapper->client_wrapper = client_wrapper; + wrapper->client_wrapper->refcount++; rb_obj_call_init(obj, 0, NULL); return obj; } diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 396007254..c296ad892 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -2,7 +2,7 @@ #define MYSQL2_RESULT_H void init_mysql2_result(); -VALUE rb_mysql_result_to_obj(MYSQL_RES * r); +VALUE rb_mysql_result_to_obj(mysql_client_wrapper *client_wrapper, MYSQL_RES * r); typedef struct { VALUE fields; @@ -14,6 +14,7 @@ typedef struct { char streamingComplete; char resultFreed; MYSQL_RES *result; + mysql_client_wrapper *client_wrapper; } mysql2_result_wrapper; #define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj)); From faa95409221ae1acf08982740d19ed77947a8015 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Sun, 9 Jun 2013 09:46:35 -0500 Subject: [PATCH 009/783] If a Result is freed after it's Client, allow it to free the Client's underlying MYSQL* structure to prevent leaks. This will be pretty rare but needs to be accounted for. --- ext/mysql2/client.c | 6 ++++-- ext/mysql2/client.h | 1 + ext/mysql2/result.c | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 19a504da5..7f5e794de 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -185,9 +185,10 @@ static VALUE nogvl_close(void *ptr) { static void rb_mysql_client_free(void * ptr) { mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; - if (wrapper->refcount == 0) { - nogvl_close(wrapper); + wrapper->freed = 1; + nogvl_close(wrapper); + if (wrapper->refcount == 0) { xfree(wrapper->client); xfree(ptr); } @@ -203,6 +204,7 @@ static VALUE allocate(VALUE klass) { wrapper->connected = 0; /* means that a database connection is open */ wrapper->initialized = 0; /* means that that the wrapper is initialized */ wrapper->refcount = 0; + wrapper->freed = 0; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); return obj; } diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index a70aa7bfa..1d12365d7 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -39,6 +39,7 @@ typedef struct { int connected; int initialized; int refcount; + int freed; MYSQL *client; } mysql_client_wrapper; diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index a15cb6d97..74d02792c 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -73,6 +73,10 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { mysql_free_result(wrapper->result); wrapper->resultFreed = 1; wrapper->client_wrapper->refcount--; + if (wrapper->client_wrapper->refcount == 0 && wrapper->client_wrapper->freed) { + xfree(wrapper->client_wrapper->client); + xfree(wrapper->client_wrapper); + } } } From ec7be53bb82d611b90e6e7486cd76f71eb8bb3ca Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Sun, 9 Jun 2013 18:58:09 -0500 Subject: [PATCH 010/783] need to re-think this test --- spec/mysql2/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 9d83a4581..f18d76a4a 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -257,7 +257,7 @@ def connect *args mark[:END] = Time.now mark.include?(:USR1).should be_true (mark[:USR1] - mark[:START]).should >= 1 - (mark[:USR1] - mark[:START]).should < 1.1 + (mark[:USR1] - mark[:START]).should < 1.2 (mark[:END] - mark[:USR1]).should > 0.9 (mark[:END] - mark[:START]).should >= 2 (mark[:END] - mark[:START]).should < 2.1 From 02da91ac005dcca9b15ed3e0f33bd5a407c22a40 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Sun, 9 Jun 2013 19:06:34 -0500 Subject: [PATCH 011/783] bump timings in this test again --- spec/mysql2/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index f18d76a4a..ac62e37b0 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -260,7 +260,7 @@ def connect *args (mark[:USR1] - mark[:START]).should < 1.2 (mark[:END] - mark[:USR1]).should > 0.9 (mark[:END] - mark[:START]).should >= 2 - (mark[:END] - mark[:START]).should < 2.1 + (mark[:END] - mark[:START]).should < 2.2 Process.kill(:TERM, pid) Process.waitpid2(pid) ensure From ec9b61cbe32f53f59fe581823b7a34f34020a49a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 17 Jun 2013 22:05:48 -0700 Subject: [PATCH 012/783] Result objects now keep a reference to the Client that they came from. --- ext/mysql2/client.c | 16 +++++++--------- ext/mysql2/result.c | 26 +++++++++++++++++--------- ext/mysql2/result.h | 3 ++- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 7f5e794de..6086b28d8 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -182,15 +182,14 @@ static VALUE nogvl_close(void *ptr) { return Qnil; } -static void rb_mysql_client_free(void * ptr) { +static void rb_mysql_client_free(void *ptr) { mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; - wrapper->freed = 1; - nogvl_close(wrapper); - + wrapper->refcount--; if (wrapper->refcount == 0) { + nogvl_close(wrapper); xfree(wrapper->client); - xfree(ptr); + xfree(wrapper); } } @@ -203,8 +202,7 @@ static VALUE allocate(VALUE klass) { wrapper->reconnect_enabled = 0; wrapper->connected = 0; /* means that a database connection is open */ wrapper->initialized = 0; /* means that that the wrapper is initialized */ - wrapper->refcount = 0; - wrapper->freed = 0; + wrapper->refcount = 1; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); return obj; } @@ -398,7 +396,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { return Qnil; } - resultObj = rb_mysql_result_to_obj(wrapper, result); + resultObj = rb_mysql_result_to_obj(self, result); /* pass-through query options for result construction later */ rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options"))); @@ -947,7 +945,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) return Qnil; } - resultObj = rb_mysql_result_to_obj(wrapper, result); + resultObj = rb_mysql_result_to_obj(self, result); /* pass-through query options for result construction later */ rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options"))); diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 74d02792c..60e92555d 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -64,27 +64,33 @@ static void rb_mysql_result_mark(void * wrapper) { rb_gc_mark(w->fields); rb_gc_mark(w->rows); rb_gc_mark(w->encoding); + rb_gc_mark(w->client); } } /* this may be called manually or during GC */ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { if (wrapper && wrapper->resultFreed != 1) { + /* FIXME: this may call flush_use_result, which can hit the socket */ mysql_free_result(wrapper->result); wrapper->resultFreed = 1; + } +} + +/* this is called during GC */ +static void rb_mysql_result_free(void *ptr) { + mysql2_result_wrapper * wrapper = ptr; + rb_mysql_result_free_result(wrapper); + + // If the GC gets to client first it will be nil + if (wrapper->client != Qnil) { wrapper->client_wrapper->refcount--; - if (wrapper->client_wrapper->refcount == 0 && wrapper->client_wrapper->freed) { + if (wrapper->client_wrapper->refcount == 0) { xfree(wrapper->client_wrapper->client); xfree(wrapper->client_wrapper); } } -} -/* this is called during GC */ -static void rb_mysql_result_free(void * wrapper) { - mysql2_result_wrapper * w = wrapper; - /* FIXME: this may call flush_use_result, which can hit the socket */ - rb_mysql_result_free_result(w); xfree(wrapper); } @@ -567,7 +573,7 @@ static VALUE rb_mysql_result_count(VALUE self) { } /* Mysql2::Result */ -VALUE rb_mysql_result_to_obj(mysql_client_wrapper *client_wrapper, MYSQL_RES *r) { +VALUE rb_mysql_result_to_obj(VALUE client, MYSQL_RES *r) { VALUE obj; mysql2_result_wrapper * wrapper; obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper); @@ -580,8 +586,10 @@ VALUE rb_mysql_result_to_obj(mysql_client_wrapper *client_wrapper, MYSQL_RES *r) wrapper->rows = Qnil; wrapper->encoding = Qnil; wrapper->streamingComplete = 0; - wrapper->client_wrapper = client_wrapper; + wrapper->client = client; + wrapper->client_wrapper = DATA_PTR(client); wrapper->client_wrapper->refcount++; + rb_obj_call_init(obj, 0, NULL); return obj; } diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index c296ad892..814fb742a 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -2,11 +2,12 @@ #define MYSQL2_RESULT_H void init_mysql2_result(); -VALUE rb_mysql_result_to_obj(mysql_client_wrapper *client_wrapper, MYSQL_RES * r); +VALUE rb_mysql_result_to_obj(VALUE client, MYSQL_RES * r); typedef struct { VALUE fields; VALUE rows; + VALUE client; VALUE encoding; unsigned int numberOfFields; unsigned long numberOfRows; From 84d6c0b0484ed10885b8b7d0340748baa2aa5bb9 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 17 Jun 2013 22:35:14 -0700 Subject: [PATCH 013/783] Refactor how options and encoding are passed from Client to Result --- ext/mysql2/client.c | 25 ++----------------------- ext/mysql2/result.c | 7 +++++-- ext/mysql2/result.h | 2 +- 3 files changed, 8 insertions(+), 26 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 6086b28d8..5c031859a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -364,9 +364,6 @@ static VALUE nogvl_use_result(void *ptr) { static VALUE rb_mysql_client_async_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; -#ifdef HAVE_RUBY_ENCODING_H - mysql2_result_wrapper * result_wrapper; -#endif GET_CLIENT(self); /* if we're not waiting on a result, do nothing */ @@ -396,14 +393,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { return Qnil; } - resultObj = rb_mysql_result_to_obj(self, result); - /* pass-through query options for result construction later */ - rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options"))); - -#ifdef HAVE_RUBY_ENCODING_H - GetMysql2Result(resultObj, result_wrapper); - result_wrapper->encoding = wrapper->encoding; -#endif + resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, rb_hash_dup(rb_iv_get(self, "@current_query_options")), result); return resultObj; } @@ -929,10 +919,6 @@ static VALUE rb_mysql_client_store_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; -#ifdef HAVE_RUBY_ENCODING_H - mysql2_result_wrapper * result_wrapper; -#endif - GET_CLIENT(self); result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); @@ -945,14 +931,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) return Qnil; } - resultObj = rb_mysql_result_to_obj(self, result); - /* pass-through query options for result construction later */ - rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options"))); - -#ifdef HAVE_RUBY_ENCODING_H - GetMysql2Result(resultObj, result_wrapper); - result_wrapper->encoding = wrapper->encoding; -#endif + resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, rb_hash_dup(rb_iv_get(self, "@current_query_options")), result); return resultObj; } diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 60e92555d..715c234fb 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -573,7 +573,7 @@ static VALUE rb_mysql_result_count(VALUE self) { } /* Mysql2::Result */ -VALUE rb_mysql_result_to_obj(VALUE client, MYSQL_RES *r) { +VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) { VALUE obj; mysql2_result_wrapper * wrapper; obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper); @@ -584,13 +584,16 @@ VALUE rb_mysql_result_to_obj(VALUE client, MYSQL_RES *r) { wrapper->result = r; wrapper->fields = Qnil; wrapper->rows = Qnil; - wrapper->encoding = Qnil; + wrapper->encoding = encoding; wrapper->streamingComplete = 0; wrapper->client = client; wrapper->client_wrapper = DATA_PTR(client); wrapper->client_wrapper->refcount++; rb_obj_call_init(obj, 0, NULL); + + rb_iv_set(obj, "@query_options", options); + return obj; } diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 814fb742a..2bb62077e 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -2,7 +2,7 @@ #define MYSQL2_RESULT_H void init_mysql2_result(); -VALUE rb_mysql_result_to_obj(VALUE client, MYSQL_RES * r); +VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r); typedef struct { VALUE fields; From e5f468c2c7d7190d7f8fe68fced74f70a8b5455d Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 26 Jun 2013 16:55:58 -0700 Subject: [PATCH 014/783] skip creating intermediate ruby string for symbol keys on 1.9+ --- ext/mysql2/result.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index dd2cbf329..bfd48dce6 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -114,15 +114,18 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int field = mysql_fetch_field_direct(wrapper->result, idx); if (symbolize_keys) { - VALUE colStr; char buf[field->name_length+1]; memcpy(buf, field->name, field->name_length); buf[field->name_length] = 0; - colStr = rb_str_new2(buf); + #ifdef HAVE_RUBY_ENCODING_H - rb_enc_associate(colStr, rb_utf8_encoding()); -#endif + rb_field = rb_intern3(buf, field->name_length, rb_utf8_encoding()); + rb_field = ID2SYM(rb_field); +#else + VALUE colStr; + colStr = rb_str_new2(buf); rb_field = ID2SYM(rb_to_id(colStr)); +#endif } else { rb_field = rb_str_new(field->name, field->name_length); #ifdef HAVE_RUBY_ENCODING_H From 17dd2e11e437a5fd58c5cabe69b9166caceb8c20 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 2 Jul 2013 12:03:53 -0700 Subject: [PATCH 015/783] get rid of changelog in favor of releases --- CHANGELOG.md | 276 --------------------------------------------------- 1 file changed, 276 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index d84fecd29..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,276 +0,0 @@ -# Changelog - -## 0.3.12 (not yet released) -## 0.3.12b5 (December 14, 2012) -* builds on Ruby 2.0-head and Rubinius 2.0-dev -* encoding names now stored in a Gperf lookup rather than an array -* long-standing bug fix: options set on a single query must not be applied to subsequent queries -* add method warning_count -* add method abandon_results! -* add setter for reconnect option -* remove options method (added in 0.3.12b1) -* support microsecond Time resolution -* several INT / UINT fixes - -## 0.3.12b4 (August 22, 2012) -* add write_timeout as well - -## 0.3.12b3 (August 22, 2012) -* several INT / LONG fixes -* fix linking to MySQL 5.5 - -## 0.3.12b2 (August 10, 2012) -* more_results is now more_results? - -## 0.3.12b1 (August 8, 2012) -* several threading and async bug fixes -* better handling of read and write timeouts -* add :local_infile connection option -* add MULTI_STATEMENTS connection flag and methods store_result, next_result, more_results -* add select_db and options methods -* add :stream query option -* add support for utf8mb4 encoding -* deprecation warnings for the :user, :pass, :hostname, :dbname, :db, :sock connection options - -## 0.3.11 (December 6th, 2011) -* change mysql error detection strategy from using mysql_field_count to the more explicit mysql_errno -* bugfix to avoid race condition with active connections that error out -* revert back to using xmalloc/xfree for allocations -* avoid potentially unsafe Ruby C API usage w/o GVL -* reacquire GVL before retrying on EINTR on connect - -## 0.3.10 (November 9th, 2011) - -## 0.3.9 (November 9th, 2011) - -## 0.3.8 (November 9th, 2011) -* remove fiber support from mysql2, the code has moved to the - em-synchrony gem. -* use rb_wait_for_single_fd() if available -* fixed a bug with inheriting query options -* remove ext/ from the default loadpath -* fix build issues on OSX with Xcode 4.2 (gcc-llvm compiler) - -## 0.3.7 (August 16th, 2011) -* ensure symbolized column names support encodings in 1.9 - -## 0.3.6 (June 17th, 2011) -* fix bug in Time/DateTime range detection -* (win32) fix bug where the Mysql2::Client object wasn't cleaned up properly if interrupted during a query -* add Mysql2::Result#count (aliased as size) to get the row count for the dataset - this can be especially helpful if you want to get the number of rows without having to inflate - the entire dataset into ruby (since this happens lazily) - -## 0.3.5 (June 15th, 2011) -* bug fix for Time/DateTime usage depending on 32/64bit Ruby - -## 0.3.4 (June 15th, 2011) -* fix a long standing bug where a signal would interrupt rb_thread_select and put the connection in a permanently broken state -* turn on casting in the ActiveRecord again, users can disable it if they need to for performance reasons - -## 0.3.3 (June 14th, 2011) -* disable async support, and access to the underlying file descriptor under Windows. It's never worked reliably and ruby-core has a lot of work to do in order to make it possible. -* added support for turning eager-casting off. This is especially useful in ORMs that will lazily cast values upon access. -* added a warning if a 0.2.x release is being used with ActiveRecord 3.1 since both the 0.2.x releases and AR 3.1 have mysql2 adapters, we want you to use the one in AR 3.1 -* added Mysql2::Client.escape (class-level method) -* disabled eager-casting in the bundled ActiveRecord adapter (for Rails 3.0 or less) - -## 0.3.2 (April 26th, 2011) -* Fix typo in initialization for older ActiveRecord versions - -## 0.3.1 (April 26th, 2011) -* Fix typo in initialization for older ActiveRecord versions - -## 0.3.0 (April 26th, 2011) -* switch to MySQL Connector/C for win32 builds -* win32 bugfixes -* BREAKING CHANGE: the ActiveRecord adapter has been pulled into Rails 3.1 and is no longer part of the gem -* added Mysql2::Client.escape (class-level) for raw one-off non-encoding-aware escaping - -## 0.2.18 (December 6th, 2011) -* change mysql error detection strategy from using mysql_field_count to the more explicit mysql_errno -* bugfix to avoid race condition with active connections that error out -* revert back to using xmalloc/xfree for allocations -* avoid potentially unsafe Ruby C API usage w/o GVL -* reacquire GVL before retrying on EINTR on connect - -## 0.2.17 (November 9th, 2011) - -## 0.2.16 (November 9th, 2011) - -## 0.2.15 (November 9th, 2011) - -## 0.2.14 (November 9th, 2011) -* use rb_wait_for_single_fd() if available -* fixed a bug with inheriting query options -* remove ext/ from the default loadpath -* fix build issues on OSX with Xcode 4.2 (gcc-llvm compiler) - -## 0.2.13 (August 16th, 2011) -* fix stupid bug around symbol encoding support (thanks coderrr!) - -## 0.2.12 (August 16th, 2011) -* ensure symbolized column names support encodings in 1.9 -* plugging sql vulnerability in mysql2 adapter - -## 0.2.11 (June 17th, 2011) -* fix bug in Time/DateTime range detection -* (win32) fix bug where the Mysql2::Client object wasn't cleaned up properly if interrupted during a query -* add Mysql2::Result#count (aliased as size) to get the row count for the dataset - this can be especially helpful if you want to get the number of rows without having to inflate - the entire dataset into ruby (since this happens lazily) - -## 0.2.10 (June 15th, 2011) -* bug fix for Time/DateTime usage depending on 32/64bit Ruby - -## 0.2.9 (June 15th, 2011) -* fix a long standing bug where a signal would interrupt rb_thread_select and put the connection in a permanently broken state -* turn on casting in the ActiveRecord again, users can disable it if they need to for performance reasons - -## 0.2.8 (June 14th, 2011) -* disable async support, and access to the underlying file descriptor under Windows. It's never worked reliably and ruby-core has a lot of work to do in order to make it possible. -* added support for turning eager-casting off. This is especially useful in ORMs that will lazily cast values upon access. -* added a warning if a 0.2.x release is being used with ActiveRecord 3.1 since both the 0.2.x releases and AR 3.1 have mysql2 adapters, we want you to use the one in AR 3.1 -* added Mysql2::Client.escape (class-level method) -* disabled eager-casting in the bundled ActiveRecord adapter (for Rails 3.0 or less) - -## 0.2.7 (March 28th, 2011) -* various fixes for em_mysql2 and fiber usage -* use our own Mysql2IndexDefinition class for better compatibility across ActiveRecord versions -* ensure the query is a string earlier in the Mysql2::Client#query codepath for 1.9 -* only set binary ruby encoding on fields that have a binary flag *and* encoding set -* a few various optimizations -* add support for :read_timeout to be set on a connection -* Fix to install with MariDB on Windows -* add fibered em connection without activerecord -* fix some 1.9.3 compilation warnings -* add LD_RUN_PATH when using hard coded mysql paths - this should help users with MySQL installed in non-standard locations -* for windows support, duplicate the socket from libmysql and create a temporary CRT fd -* fix for handling years before 1970 on Windows -* fixes to the Fiber adapter -* set wait_timeout maximum on Windows to 2147483 -* update supported range for Time objects -* upon being required, make sure the libmysql we're using is the one we were built against -* add Mysql2::Client#thread_id -* add Mysql2::Client#ping -* switch connection check in AR adapter to use Mysql2::Client#ping for efficiency -* prefer linking against thread-safe version of libmysqlclient -* define RSTRING_NOT_MODIFIED for an awesome rbx speed boost -* expose Mysql2::Client#encoding in 1.9, make sure we set the error message and sqlstate encodings accordingly -* do not segfault when raising for invalid charset (found in 1.9.3dev) - -## 0.2.6 (October 19th, 2010) -* version bump since the 0.2.5 win32 binary gems were broken - -## 0.2.5 (October 19th, 2010) -* fixes for easier Win32 binary gem deployment for targeting 1.8 and 1.9 in the same gem -* refactor of connection checks and management to avoid race conditions with the GC/threading to prevent the unexpected loss of connections -* update the default flags during connection -* add support for setting wait_timeout on AR adapter -* upgrade to rspec2 -* bugfix for an edge case where the GC would clean up a Mysql2::Client object before the underlying MYSQL pointer had been initialized -* fix to CFLAGS to allow compilation on SPARC with sunstudio compiler - Anko painting - -## 0.2.4 (September 17th, 2010) -* a few patches for win32 support from Luis Lavena - thanks man! -* bugfix from Eric Wong to avoid a potential stack overflow during Mysql2::Client#escape -* added the ability to turn internal row caching on/off via the :cache_rows => true/false option -* a couple of small patches for rbx compatibility -* set IndexDefinition#length in AR adapter - Kouhei Yanagita -* fix a long-standing data corruption bug - thank you thank you thank you to @joedamato (http://github.com/ice799) -* bugfix from calling mysql_close on a closed/freed connection surfaced by the above fix - -## 0.2.3 (August 20th, 2010) -* connection flags can now be passed to the constructor via the :flags key -* switch AR adapter connection over to use FOUND_ROWS option -* patch to ensure we use DateTime objects in place of Time for timestamps that are out of the supported range on 32bit platforms < 1.9.2 - -## 0.2.2 (August 19th, 2010) -* Change how AR adapter would send initial commands upon connecting -** we can make multiple session variable assignments in a single query -* fix signal handling when waiting on queries -* retry connect if interrupted by signals - -## 0.2.1 (August 16th, 2010) -* bring mysql2 ActiveRecord adapter back into gem - -## 0.2.0 (August 16th, 2010) -* switch back to letting libmysql manage all allocation/thread-state/freeing for the connection -* cache various numeric type conversions in hot-spots of the code for a little speed boost -* ActiveRecord adapter moved into Rails 3 core -** Don't worry 2.3.x users! We'll either release the adapter as a separate gem, or try to get it into 2.3.9 -* Fix for the "closed MySQL connection" error (GH #31) -* Fix for the "can't modify frozen object" error in 1.9.2 (GH #37) -* Introduce cascading query and result options (more info in README) -* Sequel adapter pulled into core (will be in the next release - 3.15.0 at the time of writing) -* add a safety check when attempting to send a query before a result has been fetched - -## 0.1.9 (July 17th, 2010) -* Support async ActiveRecord access with fibers and EventMachine (mperham) -* string encoding support for 1.9, respecting Encoding.default_internal -* added support for rake-compiler (tenderlove) -* bugfixes for ActiveRecord driver -** one minor bugfix for TimeZone support -** fix the select_rows method to return what it should according to the docs (r-stu31) -* Mysql2::Client#fields method added - returns the array of field names from a resultset, as strings -* Sequel adapter -** bugfix regarding sybolized field names (Eric Wong) -** fix query logging in Sequel adapter -* Lots of nice code cleanup (tenderlove) -** Mysql2::Error definition moved to pure-Ruby -** Mysql2::client#initialize definition moved to pure-Ruby -** Mysql2::Result partially moved to pure-Ruby - -## 0.1.8 (June 2nd, 2010) -* fixes for AR adapter for timezone juggling -* fixes to be able to run benchmarks and specs under 1.9.2 - -## 0.1.7 (May 22nd, 2010) -* fix a bug when using the disconnect! method on a closed connection in the AR driver - -## 0.1.6 (May 14th, 2010) -* more fixes to the AR adapter related to casting -* add missing index creation override method to AR adapter -* added sql_state and error_number methods to the Mysql2::Error exception class - -## 0.1.5 (May 12th, 2010) -* quite a few patches from Eric Wong related to thread-safety, non-blocking I/O and general cleanup -** wrap mysql_real_connect with rb_thread_blocking_region -** release GVL for possibly blocking mysql_* library calls -** [cleanup] quiet down warnings -** [cleanup] make all C symbols static -** add Mysql2::Client#close method -** correctly free the wrapped result in case of EOF -** Fix memory leak from the result wrapper struct itself -** make Mysql2::Client destructor safely non-blocking -* bug fixes for ActiveRecord adapter -** added casting for default values since they all come back from Mysql as strings (!?!) -** missing constant was added -** fixed a typo in the show_variable method -* switched over sscanf for date/time parsing in C -* made some specs a little finer-grained -* initial Sequel adapter added -* updated query benchmarks to reflect the difference between casting in C and in Ruby - -## 0.1.4 (April 23rd, 2010) -* optimization: implemented a local cache for rows that are lazily created in ruby during iteration. The MySQL C result is freed as soon as all the results have been cached -* optimization: implemented a local cache for field names so every row reuses the same objects as field names/keys -* refactor the Mysql2 connection adapter for ActiveRecord to not extend the Mysql adapter - now being a free-standing connection adapter - -## 0.1.3 (April 15th, 2010) -* added an EventMachine Deferrable API -* added an ActiveRecord connection adapter -** should be compatible with 2.3.5 and 3.0 (including Arel) - -## 0.1.2 (April 9th, 2010) -* fix a bug (copy/paste fail) around checking for empty TIME values and returning nil (thanks @marius) - -## 0.1.1 (April 6th, 2010) -* added affected_rows method (mysql_affected_rows) -* added last_id method (last_insert_id) -* enable reconnect option by default -* added initial async query support -* updated extconf (thanks to the mysqlplus project) for easier gem building - -## 0.1.0 (April 6th, 2010) -* initial release From eff3a35c4cf9cf99078dd0cd0106ac2688f7a351 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 2 Jul 2013 20:44:42 -0700 Subject: [PATCH 016/783] Use a test for RbConfig RPATHFLAG --- ext/mysql2/extconf.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 0eabe2836..c2838208c 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -69,8 +69,13 @@ def asplode lib if RbConfig::MAKEFILE_CONFIG['CC'] =~ /gcc/ $CFLAGS << ' -Wall -funroll-loops' - if hard_mysql_path = $libs[%r{-L(/[^ ]+)}, 1] - $LDFLAGS << " -Wl,-rpath,#{hard_mysql_path}" + if libdir = $libs[%r{-L(/[^ ]+)}, 1] + # The following comment and test is borrowed from the Pg gem: + # Try to use runtime path linker option, even if RbConfig doesn't know about it. + # The rpath option is usually set implicit by dir_config(), but so far not on Mac OS X. + if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', " -Wl,-rpath,#{libdir}") + $LDFLAGS << " -Wl,-rpath,#{libdir}" + end end end From add96781ca63ca218fbf526e0d5007db16be9353 Mon Sep 17 00:00:00 2001 From: monban Date: Mon, 8 Jul 2013 16:22:43 -0400 Subject: [PATCH 017/783] support for mysql-connector-c check for LIBMYSQL_VERSION, if it exists we are linking against the connector library, otherwise, use MYSQL_SERVER_VERSION as before added preprocessor variable to track connector library version --- ext/mysql2/client.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 28f9cedf7..905e681ea 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -24,6 +24,17 @@ static ID intern_merge, intern_error_number_eql, intern_sql_state_eql; mysql_client_wrapper *wrapper; \ Data_Get_Struct(self, mysql_client_wrapper, wrapper) +/* + * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct + * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when + * linking against the server itself + */ +#ifdef LIBMYSQL_VERSION + #define MYSQL_LINK_VERSION LIBMYSQL_VERSION +#else + #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION +#endif + /* * used to pass all arguments to mysql_real_connect while inside * rb_thread_blocking_region @@ -740,14 +751,15 @@ void init_mysql2_client() { int i; int dots = 0; const char *lib = mysql_get_client_info(); - for (i = 0; lib[i] != 0 && MYSQL_SERVER_VERSION[i] != 0; i++) { + + for (i = 0; lib[i] != 0 && MYSQL_LINK_VERSION[i] != 0; i++) { if (lib[i] == '.') { dots++; // we only compare MAJOR and MINOR if (dots == 2) break; } - if (lib[i] != MYSQL_SERVER_VERSION[i]) { - rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_SERVER_VERSION, lib); + if (lib[i] != MYSQL_LINK_VERSION[i]) { + rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib); return; } } From e4e43436cd8353d401689d0faed79e24b9423278 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 9 Jul 2013 15:22:56 -0700 Subject: [PATCH 018/783] allow rbx19 and rbx20 to fail until rb_intern3 is implemented --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 13d3b83ab..6e15fe3fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,9 @@ rvm: - rbx-19mode - rbx-20mode bundler_args: --without benchmarks +allow_failures: + - rvm: rbx-19mode + - rvm: rbx-20mode script: - bundle exec rake - bundle exec rspec From 8ec3beff962ccd5c6823e310b3b168069aec2f71 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 9 Jul 2013 15:23:49 -0700 Subject: [PATCH 019/783] new rbenv version force --- .rbenv-version => .ruby-version | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .rbenv-version => .ruby-version (100%) diff --git a/.rbenv-version b/.ruby-version similarity index 100% rename from .rbenv-version rename to .ruby-version From b28f691e621eb77db251fcf6fa364d46dacdb7d3 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 9 Jul 2013 15:31:04 -0700 Subject: [PATCH 020/783] use proper travis syntax --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6e15fe3fc..0e964e895 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,8 @@ rvm: - rbx-19mode - rbx-20mode bundler_args: --without benchmarks -allow_failures: +matrix: + allow_failures: - rvm: rbx-19mode - rvm: rbx-20mode script: From bcdcc86335c4b2d86e0c5e20e51793495faf9f77 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 9 Jul 2013 15:52:42 -0700 Subject: [PATCH 021/783] bump dev deps in Gemfile.lock --- Gemfile.lock | 62 +++++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b76da8744..5b593877a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,33 +6,39 @@ PATH GEM remote: https://rubygems.org/ specs: - activemodel (3.2.1) - activesupport (= 3.2.1) - builder (~> 3.0.0) - activerecord (3.2.1) - activemodel (= 3.2.1) - activesupport (= 3.2.1) - arel (~> 3.0.0) - tzinfo (~> 0.3.29) - activesupport (3.2.1) - i18n (~> 0.6) - multi_json (~> 1.0) - addressable (2.2.6) - arel (3.0.0) - builder (3.0.0) - data_objects (0.10.8) + activemodel (4.0.0) + activesupport (= 4.0.0) + builder (~> 3.1.0) + activerecord (4.0.0) + activemodel (= 4.0.0) + activerecord-deprecated_finders (~> 1.0.2) + activesupport (= 4.0.0) + arel (~> 4.0.0) + activerecord-deprecated_finders (1.0.3) + activesupport (4.0.0) + i18n (~> 0.6, >= 0.6.4) + minitest (~> 4.2) + multi_json (~> 1.3) + thread_safe (~> 0.1) + tzinfo (~> 0.3.37) + addressable (2.3.5) + arel (4.0.0) + atomic (1.1.10) + builder (3.1.4) + data_objects (0.10.13) addressable (~> 2.1) diff-lcs (1.1.3) - do_mysql (0.10.8) - data_objects (= 0.10.8) - eventmachine (0.12.10) - faker (1.0.1) - i18n (~> 0.4) - i18n (0.6.0) - multi_json (1.0.4) - mysql (2.8.1) - rake (0.9.4) - rake-compiler (0.8.1) + do_mysql (0.10.13) + data_objects (= 0.10.13) + eventmachine (1.0.3) + faker (1.1.2) + i18n (~> 0.5) + i18n (0.6.4) + minitest (4.7.5) + multi_json (1.7.7) + mysql (2.9.1) + rake (0.9.6) + rake-compiler (0.8.3) rake rspec (2.8.0) rspec-core (~> 2.8.0) @@ -42,8 +48,10 @@ GEM rspec-expectations (2.8.0) diff-lcs (~> 1.1.2) rspec-mocks (2.8.0) - sequel (3.32.0) - tzinfo (0.3.31) + sequel (4.0.0) + thread_safe (0.1.0) + atomic + tzinfo (0.3.37) PLATFORMS ruby From 5cb8f7dcb3cd248df444e2085c108a259c2caada Mon Sep 17 00:00:00 2001 From: monban Date: Wed, 10 Jul 2013 00:08:37 -0400 Subject: [PATCH 022/783] workaround for bug in mkmf works-around a known bug in mkmf, which was resolved in https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 Since the current Windows Ruby distribution doesn't include this fix yet, we work around it by detecting whether mkmf is functioning properly or not, and fixing it if it's not. This behavior will degrade gracefully if the user is running a version of mkmf with the fix. --- ext/mysql2/extconf.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index c2838208c..fe301bb0d 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -29,6 +29,14 @@ def asplode lib if RUBY_PLATFORM =~ /mswin|mingw/ inc, lib = dir_config('mysql') + + # Ruby versions not incorporating the mkmf fix at + # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 + # do not properly search for lib directories, and must be corrected + unless lib[-3, 3] == 'lib' + @libdir_basename = 'lib' + inc, lib = dir_config('mysql') + end exit 1 unless have_library("libmysql") elsif mc = (with_config('mysql-config') || Dir[GLOB].first) then mc = Dir[GLOB].first if mc == true From cb4d68baab675a2e2c4423d3a05ea239c9be8a10 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 29 Dec 2012 20:45:07 -0600 Subject: [PATCH 023/783] Add type checks and GC guards before accessing the query options hash in the result object. See #321. --- ext/mysql2/client.c | 26 +++++++++++++++++++------- ext/mysql2/result.c | 2 ++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 87a8d736b..9274e94a1 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -375,6 +375,7 @@ static VALUE nogvl_use_result(void *ptr) { static VALUE rb_mysql_client_async_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; + VALUE current, is_streaming; GET_CLIENT(self); /* if we're not waiting on a result, do nothing */ @@ -388,7 +389,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { return rb_raise_mysql2_error(wrapper); } - VALUE is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream); + is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream); if(is_streaming == Qtrue) { result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_use_result, wrapper, RUBY_UBF_IO, 0); } else { @@ -404,7 +405,11 @@ static VALUE rb_mysql_client_async_result(VALUE self) { return Qnil; } - resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, rb_hash_dup(rb_iv_get(self, "@current_query_options")), result); + current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); + RB_GC_GUARD(current); + Check_Type(current, T_HASH); + resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result); + return resultObj; } @@ -549,10 +554,13 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { REQUIRE_CONNECTED(wrapper); args.mysql = wrapper->client; - rb_iv_set(self, "@current_query_options", rb_hash_dup(rb_iv_get(self, "@query_options"))); - current = rb_iv_get(self, "@current_query_options"); + current = rb_hash_dup(rb_iv_get(self, "@query_options")); + RB_GC_GUARD(current); + Check_Type(current, T_HASH); + rb_iv_set(self, "@current_query_options", current); + if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { - opts = rb_funcall(current, intern_merge_bang, 1, opts); + rb_funcall(current, intern_merge_bang, 1, opts); if (rb_hash_aref(current, sym_async) == Qtrue) { async = 1; @@ -930,6 +938,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; + VALUE current; GET_CLIENT(self); result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); @@ -942,9 +951,12 @@ static VALUE rb_mysql_client_store_result(VALUE self) return Qnil; } - resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, rb_hash_dup(rb_iv_get(self, "@current_query_options")), result); - return resultObj; + current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); + RB_GC_GUARD(current); + Check_Type(current, T_HASH); + resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result); + return resultObj; } #ifdef HAVE_RUBY_ENCODING_H diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index c80b5f345..f3d99cdaa 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -394,6 +394,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { GetMysql2Result(self, wrapper); defaults = rb_iv_get(self, "@query_options"); + Check_Type(defaults, T_HASH); if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) { symbolizeKeys = 1; } @@ -423,6 +424,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { GetMysql2Result(self, wrapper); defaults = rb_iv_get(self, "@query_options"); + Check_Type(defaults, T_HASH); if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) { opts = rb_funcall(defaults, intern_merge, 1, opts); } else { From 0d9c806d8f6d8fca2f3f22845d89c2320be83457 Mon Sep 17 00:00:00 2001 From: Eugene Pimenov Date: Tue, 15 Jan 2013 16:46:46 +0100 Subject: [PATCH 024/783] Add Client#query_info that calls mysql_info. The function is really useful if you want to retrieve the result of the LOAD DATA INFILE command. --- ext/mysql2/client.c | 20 ++++++++++++++++++++ spec/mysql2/client_spec.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 9274e94a1..4c02cd126 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -259,6 +259,25 @@ static VALUE rb_mysql_client_warning_count(VALUE self) { return UINT2NUM(warning_count); } +static VALUE rb_mysql_info(VALUE self) { + const char *info; + VALUE rb_str; + GET_CLIENT(self); + + info = mysql_info(wrapper->client); + + if (info == NULL) { + return Qnil; + } + + rb_str = rb_str_new2(info); +#ifdef HAVE_RUBY_ENCODING_H + rb_enc_associate(rb_str, rb_utf8_encoding()); +#endif + + return rb_str; +} + static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) { struct nogvl_connect_args args; VALUE rv; @@ -1124,6 +1143,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0); rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1); rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0); + rb_define_method(cMysql2Client, "query_info", rb_mysql_info, 0); #ifdef HAVE_RUBY_ENCODING_H rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); #endif diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index ac62e37b0..cb2cbbd4a 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -130,6 +130,40 @@ def connect *args end end + it "should respond to #query_info" do + @client.should respond_to(:query_info) + end + + context "#query_info" do + context "when no info present" do + before(:each) do + @client.query('select 1') + end + it "should 0" do + @client.query_info.should be_nil + end + end + context "when has some info" do + before(:each) do + @client.query "USE test" + @client.query "CREATE TABLE IF NOT EXISTS infoTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" + end + + after(:each) do + @client.query "DROP TABLE infoTest" + end + + before(:each) do + # http://dev.mysql.com/doc/refman/5.0/en/mysql-info.html says + # # Note that mysql_info() returns a non-NULL value for INSERT ... VALUES only for the multiple-row form of the statement (that is, only if multiple value lists are specified). + @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)") + end + it "should retrieve it" do + @client.query_info.should eq 'Records: 2 Duplicates: 0 Warnings: 0' + end + end + end + it "should expect connect_timeout to be a positive integer" do lambda { Mysql2::Client.new(:connect_timeout => -1) From 41f8b327a4029c9dce507eda24d5401891ac5cec Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 11 Jul 2013 06:05:49 -0700 Subject: [PATCH 025/783] Parse query_info to a hash --- ext/mysql2/client.c | 2 +- lib/mysql2/client.rb | 8 ++++++++ spec/mysql2/client_spec.rb | 6 ++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 4c02cd126..b3651bfb1 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1143,7 +1143,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0); rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1); rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0); - rb_define_method(cMysql2Client, "query_info", rb_mysql_info, 0); + rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0); #ifdef HAVE_RUBY_ENCODING_H rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); #endif diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 95ff9a5ca..b21f59ba0 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -61,6 +61,14 @@ def self.default_query_options @@default_query_options end + def query_info + info = query_info_string + return {} unless info + info_hash = {} + info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i } + info_hash + end + private def self.local_offset ::Time.local(2010).utc_offset.to_r / 86400 diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index cb2cbbd4a..8e5bd6543 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -140,7 +140,8 @@ def connect *args @client.query('select 1') end it "should 0" do - @client.query_info.should be_nil + @client.query_info.should be_empty + @client.query_info_string.should be_nil end end context "when has some info" do @@ -159,7 +160,8 @@ def connect *args @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)") end it "should retrieve it" do - @client.query_info.should eq 'Records: 2 Duplicates: 0 Warnings: 0' + @client.query_info.should == {:records => 2, :duplicates => 0, :warnings => 0} + @client.query_info_string.should eq 'Records: 2 Duplicates: 0 Warnings: 0' end end end From 2d26e0ae3e88d87d2272bfb3831f8092d2f2fa0d Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 15 Jul 2013 00:48:36 -0700 Subject: [PATCH 026/783] protect around rb_intern3 --- ext/mysql2/extconf.rb | 1 + ext/mysql2/result.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index fe301bb0d..ab327da3b 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -9,6 +9,7 @@ def asplode lib have_func('rb_thread_blocking_region') have_func('rb_wait_for_single_fd') have_func('rb_hash_dup') +have_func('rb_intern3') # borrowed from mysqlplus # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index f3d99cdaa..d20e6eb94 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -129,7 +129,7 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int memcpy(buf, field->name, field->name_length); buf[field->name_length] = 0; -#ifdef HAVE_RUBY_ENCODING_H +#ifdef HAVE_RB_INTERN3 rb_field = rb_intern3(buf, field->name_length, rb_utf8_encoding()); rb_field = ID2SYM(rb_field); #else From 3461f95eabca01324ac8aa2ba4e2fc11c1e2adbb Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 15 Jul 2013 00:49:11 -0700 Subject: [PATCH 027/783] try building on rbx again --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0e964e895..13d3b83ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,6 @@ rvm: - rbx-19mode - rbx-20mode bundler_args: --without benchmarks -matrix: - allow_failures: - - rvm: rbx-19mode - - rvm: rbx-20mode script: - bundle exec rake - bundle exec rspec From 98785bfdc32f18d496ccfc134e3e98d422987711 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 15 Jul 2013 01:03:41 -0700 Subject: [PATCH 028/783] really need to rethink this test --- spec/mysql2/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index ac62e37b0..72f5fc159 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -257,7 +257,7 @@ def connect *args mark[:END] = Time.now mark.include?(:USR1).should be_true (mark[:USR1] - mark[:START]).should >= 1 - (mark[:USR1] - mark[:START]).should < 1.2 + (mark[:USR1] - mark[:START]).should < 1.3 (mark[:END] - mark[:USR1]).should > 0.9 (mark[:END] - mark[:START]).should >= 2 (mark[:END] - mark[:START]).should < 2.2 From 898d8ff596f2ee62533391848333d8bbdcf5fdcb Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 15 Jul 2013 00:29:25 -0700 Subject: [PATCH 029/783] add a test console --- Gemfile | 4 ++++ Gemfile.lock | 8 ++++++++ lib/mysql2/console.rb | 5 +++++ script/bootstrap | 5 +++++ script/console | 7 +++++++ 5 files changed, 29 insertions(+) create mode 100644 lib/mysql2/console.rb create mode 100755 script/bootstrap create mode 100755 script/console diff --git a/Gemfile b/Gemfile index 451c67371..8c3704c07 100644 --- a/Gemfile +++ b/Gemfile @@ -10,3 +10,7 @@ group :benchmarks do gem 'sequel' gem 'faker' end + +group :development do + gem 'pry' +end diff --git a/Gemfile.lock b/Gemfile.lock index 5b593877a..6dc4f5dd9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -25,6 +25,7 @@ GEM arel (4.0.0) atomic (1.1.10) builder (3.1.4) + coderay (1.0.9) data_objects (0.10.13) addressable (~> 2.1) diff-lcs (1.1.3) @@ -34,9 +35,14 @@ GEM faker (1.1.2) i18n (~> 0.5) i18n (0.6.4) + method_source (0.8.1) minitest (4.7.5) multi_json (1.7.7) mysql (2.9.1) + pry (0.9.12.2) + coderay (~> 1.0.5) + method_source (~> 0.8) + slop (~> 3.4) rake (0.9.6) rake-compiler (0.8.3) rake @@ -49,6 +55,7 @@ GEM diff-lcs (~> 1.1.2) rspec-mocks (2.8.0) sequel (4.0.0) + slop (3.4.5) thread_safe (0.1.0) atomic tzinfo (0.3.37) @@ -63,6 +70,7 @@ DEPENDENCIES faker mysql mysql2! + pry rake (~> 0.9.3) rake-compiler (~> 0.8.1) rspec (~> 2.8.0) diff --git a/lib/mysql2/console.rb b/lib/mysql2/console.rb new file mode 100644 index 000000000..cad824375 --- /dev/null +++ b/lib/mysql2/console.rb @@ -0,0 +1,5 @@ +# Loaded by script/console. Land helpers here. + +Pry.config.prompt = lambda do |context, nesting, pry| + "[mysql2] #{context}> " +end diff --git a/script/bootstrap b/script/bootstrap new file mode 100755 index 000000000..500005c6b --- /dev/null +++ b/script/bootstrap @@ -0,0 +1,5 @@ +#!/bin/sh +set -e + +cd "$(dirname "$0")/.." +exec bundle install --binstubs --path vendor/gems "$@" diff --git a/script/console b/script/console new file mode 100755 index 000000000..9145f1691 --- /dev/null +++ b/script/console @@ -0,0 +1,7 @@ +#!/bin/sh +# Run a Ruby REPL. + +set -e + +cd $(dirname "$0")/.. +exec ruby -S bin/pry -Ilib -r mysql2 -r mysql2/console From 455396b0fff2a3a329ca7baa8443d876dcc97741 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 15 Jul 2013 13:30:51 -0700 Subject: [PATCH 030/783] blah --- spec/mysql2/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 13ce52a47..aa12b1e03 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -296,7 +296,7 @@ def connect *args (mark[:USR1] - mark[:START]).should < 1.3 (mark[:END] - mark[:USR1]).should > 0.9 (mark[:END] - mark[:START]).should >= 2 - (mark[:END] - mark[:START]).should < 2.2 + (mark[:END] - mark[:START]).should < 2.3 Process.kill(:TERM, pid) Process.waitpid2(pid) ensure From 4e769bb496e875a2271c04ae378cc557e031c468 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 15 Jul 2013 16:06:00 -0700 Subject: [PATCH 031/783] update version for 0.3.12 release --- Gemfile.lock | 2 +- lib/mysql2/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6dc4f5dd9..7846cd6ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - mysql2 (0.3.12b6) + mysql2 (0.3.12) GEM remote: https://rubygems.org/ diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index bd9267d64..01207e183 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.3.12b6" + VERSION = "0.3.12" end From be16e0c8f7d5d7ffe8dbfbc92b5af3da01fe553e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Tue, 16 Jul 2013 10:48:57 +0200 Subject: [PATCH 032/783] Update benchmarks. - Upgraded to new ActiveRecord API (table_name, limit, to_a). - Locked to ActiveRecord >= 3.0. ^ - Used Mysql2::Error instead of undefined Mysql2::Mysql2Error. - Specify root user to sequel benchmark. Other benchmarks are using password less root account. - Fixed picking random array value in benchmark/setup_db.rb (1.8 and 1.9 compat). [ci skip] --- Gemfile | 2 +- Gemfile.lock | 2 +- benchmark/active_record.rb | 10 +++++----- benchmark/allocations.rb | 10 +++++----- benchmark/sequel.rb | 8 ++++---- benchmark/setup_db.rb | 6 +++--- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Gemfile b/Gemfile index 8c3704c07..39777220f 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ gemspec # benchmarks group :benchmarks do - gem 'activerecord' + gem 'activerecord', '>= 3.0' gem 'mysql' gem 'do_mysql' gem 'sequel' diff --git a/Gemfile.lock b/Gemfile.lock index 7846cd6ce..42b56b673 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,7 +64,7 @@ PLATFORMS ruby DEPENDENCIES - activerecord + activerecord (>= 3.0) do_mysql eventmachine faker diff --git a/benchmark/active_record.rb b/benchmark/active_record.rb index 5120ac7d8..942bc52b1 100644 --- a/benchmark/active_record.rb +++ b/benchmark/active_record.rb @@ -19,18 +19,18 @@ } class Mysql2Model < ActiveRecord::Base - set_table_name :mysql2_test + self.table_name = "mysql2_test" end class MysqlModel < ActiveRecord::Base - set_table_name :mysql2_test + self.table_name = "mysql2_test" end Benchmark.bmbm do |x| x.report "Mysql2" do Mysql2Model.establish_connection(mysql2_opts) number_of.times do - Mysql2Model.all(:limit => 1000).each{ |r| + Mysql2Model.limit(1000).to_a.each{ |r| r.attributes.keys.each{ |k| r.send(k.to_sym) } @@ -41,11 +41,11 @@ class MysqlModel < ActiveRecord::Base x.report "Mysql" do MysqlModel.establish_connection(mysql_opts) number_of.times do - MysqlModel.all(:limit => 1000).each{ |r| + MysqlModel.limit(1000).to_a.each{ |r| r.attributes.keys.each{ |k| r.send(k.to_sym) } } end end -end \ No newline at end of file +end diff --git a/benchmark/allocations.rb b/benchmark/allocations.rb index cf0c9313e..b605fdf4c 100644 --- a/benchmark/allocations.rb +++ b/benchmark/allocations.rb @@ -1,17 +1,17 @@ # encoding: UTF-8 $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') -raise Mysql2::Mysql2Error.new("GC allocation benchmarks only supported on Ruby 1.9!") unless RUBY_VERSION =~ /1\.9/ - require 'rubygems' require 'benchmark' require 'active_record' +raise Mysql2::Error.new("GC allocation benchmarks only supported on Ruby 1.9!") unless RUBY_VERSION > '1.9' + ActiveRecord::Base.default_timezone = :local ActiveRecord::Base.time_zone_aware_attributes = true class Mysql2Model < ActiveRecord::Base - set_table_name :mysql2_test + self.table_name = "mysql2_test" end def bench_allocations(feature, iterations = 10, &blk) @@ -25,9 +25,9 @@ def bench_allocations(feature, iterations = 10, &blk) end bench_allocations('coercion') do - Mysql2Model.all(:limit => 1000).each{ |r| + Mysql2Model.limit(1000).to_a.each{ |r| r.attributes.keys.each{ |k| r.send(k.to_sym) } } -end \ No newline at end of file +end diff --git a/benchmark/sequel.rb b/benchmark/sequel.rb index 0dac47fee..93b2d72d1 100644 --- a/benchmark/sequel.rb +++ b/benchmark/sequel.rb @@ -8,9 +8,9 @@ require 'sequel/adapters/do' number_of = 10 -mysql2_opts = "mysql2://localhost/test" -mysql_opts = "mysql://localhost/test" -do_mysql_opts = "do:mysql://localhost/test" +mysql2_opts = "mysql2://root@localhost/test" +mysql_opts = "mysql://root@localhost/test" +do_mysql_opts = "do:mysql://root@localhost/test" class Mysql2Model < Sequel::Model(Sequel.connect(mysql2_opts)[:mysql2_test]); end class MysqlModel < Sequel::Model(Sequel.connect(mysql_opts)[:mysql2_test]); end @@ -34,4 +34,4 @@ class DOMysqlModel < Sequel::Model(Sequel.connect(do_mysql_opts)[:mysql2_test]); MysqlModel.limit(1000).all end end -end \ No newline at end of file +end diff --git a/benchmark/setup_db.rb b/benchmark/setup_db.rb index a5395b33d..0d1938914 100644 --- a/benchmark/setup_db.rb +++ b/benchmark/setup_db.rb @@ -107,8 +107,8 @@ def insert_record(args) :medium_text_test => twenty5_paragraphs, :long_blob_test => twenty5_paragraphs, :long_text_test => twenty5_paragraphs, - :enum_test => ['val1', 'val2'].rand, - :set_test => ['val1', 'val2', 'val1,val2'].rand + :enum_test => ['val1', 'val2'][rand(2)], + :set_test => ['val1', 'val2', 'val1,val2'][rand(3)] ) if n % 100 == 0 $stdout.putc '.' @@ -116,4 +116,4 @@ def insert_record(args) end end puts -puts "Done" \ No newline at end of file +puts "Done" From b9e30bc0863b3399d63447675010489cf1fe37a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Tue, 16 Jul 2013 12:49:52 +0200 Subject: [PATCH 033/783] Initialize read_timeout to avoid 'warning: instance variable @read_timeout not initialized'. --- lib/mysql2/client.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index b21f59ba0..8b726054f 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -1,6 +1,6 @@ module Mysql2 class Client - attr_reader :query_options + attr_reader :query_options, :read_timeout @@default_query_options = { :as => :hash, # the type of object you want each row back as; also supports :array (an array of values) :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result @@ -15,6 +15,7 @@ class Client def initialize(opts = {}) opts = Mysql2::Util.key_hash_as_symbols( opts ) + @read_timeout = nil @query_options = @@default_query_options.dup @query_options.merge! opts From e7e43411c5fd3191605b0dfb4d0dfb97c1466561 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 16 Jul 2013 10:15:20 -0700 Subject: [PATCH 034/783] make sure write_timeout and local_infile ivars are setup to avoid warnings --- lib/mysql2/client.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 8b726054f..7c0c19bb3 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -1,6 +1,6 @@ module Mysql2 class Client - attr_reader :query_options, :read_timeout + attr_reader :query_options, :read_timeout, :write_timeout, :local_infile @@default_query_options = { :as => :hash, # the type of object you want each row back as; also supports :array (an array of values) :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result @@ -16,6 +16,8 @@ class Client def initialize(opts = {}) opts = Mysql2::Util.key_hash_as_symbols( opts ) @read_timeout = nil + @write_timeout = nil + @local_infile = nil @query_options = @@default_query_options.dup @query_options.merge! opts From 46f4fcd5e9945ef61713aebb13ac7249a3e1ac42 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 16 Jul 2013 10:21:53 -0700 Subject: [PATCH 035/783] get rid of some more warnings --- spec/em/em_spec.rb | 4 ++-- spec/mysql2/client_spec.rb | 26 +++++++++++++------------- spec/mysql2/result_spec.rb | 10 +++++----- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 5a6b42ee2..36dcfb37b 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -34,8 +34,8 @@ defer1.callback do |result| results << result.first defer2 = client.query "SELECT sleep(0.025) as second_query" - defer2.callback do |result| - results << result.first + defer2.callback do |r| + results << r.first EM.stop_event_loop end end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index aa12b1e03..672260fe4 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -17,17 +17,17 @@ if defined? Encoding it "should raise an exception on create for invalid encodings" do lambda { - c = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake")) + Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake")) }.should raise_error(Mysql2::Error) end it "should not raise an exception on create for a valid encoding" do lambda { - c = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) + Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) }.should_not raise_error(Mysql2::Error) lambda { - c = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) + Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) }.should_not raise_error(Mysql2::Error) end end @@ -160,8 +160,8 @@ def connect *args @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)") end it "should retrieve it" do - @client.query_info.should == {:records => 2, :duplicates => 0, :warnings => 0} - @client.query_info_string.should eq 'Records: 2 Duplicates: 0 Warnings: 0' + @client.query_info.should eql({:records => 2, :duplicates => 0, :warnings => 0}) + @client.query_info_string.should eq('Records: 2 Duplicates: 0 Warnings: 0') end end end @@ -419,12 +419,12 @@ def connect *args end it "returns multiple result sets" do - @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'").first.should == { 'set_1' => 1 } + @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'").first.should eql({ 'set_1' => 1 }) - @multi_client.next_result.should == true - @multi_client.store_result.first.should == { 'set_2' => 2 } + @multi_client.next_result.should be_true + @multi_client.store_result.first.should eql({ 'set_2' => 2 }) - @multi_client.next_result.should == false + @multi_client.next_result.should be_false end it "does not interfere with other statements" do @@ -453,12 +453,12 @@ def connect *args it "#more_results? should work" do @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'") - @multi_client.more_results?.should == true + @multi_client.more_results?.should be_true @multi_client.next_result @multi_client.store_result - @multi_client.more_results?.should == false + @multi_client.more_results?.should be_false end end end @@ -621,11 +621,11 @@ def connect *args it "should raise a Mysql2::Error exception upon connection failure" do lambda { - bad_client = Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42' + Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42' }.should raise_error(Mysql2::Error) lambda { - good_client = Mysql2::Client.new DatabaseCredentials['root'] + Mysql2::Client.new DatabaseCredentials['root'] }.should_not raise_error(Mysql2::Error) end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 5b160d373..5ddd2f520 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -148,11 +148,11 @@ it "should return nil values for NULL and strings for everything else when :cast is false" do result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast => false).first result["null_test"].should be_nil - result["tiny_int_test"].should == "1" - result["bool_cast_test"].should == "1" - result["int_test"].should == "10" - result["date_test"].should == "2010-04-04" - result["enum_test"].should == "val1" + result["tiny_int_test"].should eql("1") + result["bool_cast_test"].should eql("1") + result["int_test"].should eql("10") + result["date_test"].should eql("2010-04-04") + result["enum_test"].should eql("val1") end it "should return nil for a NULL value" do From 0fc4e0e146763eefb0ffc266cb8ef4afd33613ce Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 16 Jul 2013 10:47:20 -0700 Subject: [PATCH 036/783] clean up connections in specs where we can --- spec/em/em_spec.rb | 4 ++++ spec/mysql2/client_spec.rb | 6 ++---- spec/mysql2/error_spec.rb | 12 ++++++++---- spec/mysql2/result_spec.rb | 7 +++---- spec/spec_helper.rb | 8 ++++++++ 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 36dcfb37b..7bead7c34 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -12,6 +12,7 @@ defer1 = client1.query "SELECT sleep(0.1) as first_query" defer1.callback do |result| results << result.first + client1.close EM.stop_event_loop end @@ -19,6 +20,7 @@ defer2 = client2.query "SELECT sleep(0.025) second_query" defer2.callback do |result| results << result.first + client2.close end end @@ -36,6 +38,7 @@ defer2 = client.query "SELECT sleep(0.025) as second_query" defer2.callback do |r| results << r.first + client.close EM.stop_event_loop end end @@ -51,6 +54,7 @@ client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query "SELECT sleep(0.1) as first_query" defer.callback do |result| + client.close raise 'some error' end defer.errback do |err| diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 672260fe4..7d4cbc527 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -2,10 +2,6 @@ require 'spec_helper' describe Mysql2::Client do - before(:each) do - @client = Mysql2::Client.new DatabaseCredentials['root'] - end - it "should raise an exception upon connection failure" do lambda { # The odd local host IP address forces the mysql client library to @@ -88,6 +84,8 @@ def connect *args results[1]['Value'].should_not be_nil results[1]['Value'].should be_kind_of(String) results[1]['Value'].should_not be_empty + + ssl_client.close end it "should respond to #close" do diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 3e0fcab2e..f62c74b9b 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -3,18 +3,22 @@ describe Mysql2::Error do before(:each) do - @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) begin - @client.query("HAHAHA") + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) + client.query("HAHAHA") rescue Mysql2::Error => e @error = e + ensure + client.close end - @client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) begin - @client2.query("HAHAHA") + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) + client.query("HAHAHA") rescue Mysql2::Error => e @error2 = e + ensure + client.close end end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 5ddd2f520..ce8331986 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -2,10 +2,6 @@ require 'spec_helper' describe Mysql2::Result do - before(:each) do - @client = Mysql2::Client.new DatabaseCredentials['root'] - end - before(:each) do @result = @client.query "SELECT 1" end @@ -330,6 +326,7 @@ client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['enum_test'].encoding.should eql(Encoding.find('us-ascii')) + client2.close end it "should use Encoding.default_internal" do @@ -359,6 +356,7 @@ client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['set_test'].encoding.should eql(Encoding.find('us-ascii')) + client2.close end it "should use Encoding.default_internal" do @@ -441,6 +439,7 @@ client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result[field].encoding.should eql(Encoding.find('us-ascii')) + client2.close end it "should use Encoding.default_internal" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 093b432fc..eb26edbc9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,14 @@ DatabaseCredentials = YAML.load_file('spec/configuration.yml') RSpec.configure do |config| + config.before :each do + @client = Mysql2::Client.new DatabaseCredentials['root'] + end + + config.after :each do + @client.close + end + config.before(:all) do client = Mysql2::Client.new DatabaseCredentials['root'] client.query %[ From 6087968ce83a7d3fe0cb047abc351fe0398a1cfc Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 16 Jul 2013 10:47:32 -0700 Subject: [PATCH 037/783] some more spec cleanup --- spec/mysql2/client_spec.rb | 27 ++++++++------------------- spec/mysql2/result_spec.rb | 8 ++++---- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 7d4cbc527..2155bc342 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -109,20 +109,16 @@ def connect *args context "#warning_count" do context "when no warnings" do - before(:each) do - @client.query('select 1') - end it "should 0" do + @client.query('select 1') @client.warning_count.should == 0 end end context "when has a warnings" do - before(:each) do + it "should > 0" do # "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS" # http://dev.mysql.com/doc/refman/5.0/en/explain-extended.html @client.query("explain extended select 1") - end - it "should > 0" do @client.warning_count.should > 0 end end @@ -134,32 +130,25 @@ def connect *args context "#query_info" do context "when no info present" do - before(:each) do - @client.query('select 1') - end it "should 0" do + @client.query('select 1') @client.query_info.should be_empty @client.query_info_string.should be_nil end end context "when has some info" do - before(:each) do + it "should retrieve it" do @client.query "USE test" @client.query "CREATE TABLE IF NOT EXISTS infoTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" - end - - after(:each) do - @client.query "DROP TABLE infoTest" - end - before(:each) do # http://dev.mysql.com/doc/refman/5.0/en/mysql-info.html says # # Note that mysql_info() returns a non-NULL value for INSERT ... VALUES only for the multiple-row form of the statement (that is, only if multiple value lists are specified). @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)") - end - it "should retrieve it" do + @client.query_info.should eql({:records => 2, :duplicates => 0, :warnings => 0}) @client.query_info_string.should eq('Records: 2 Duplicates: 0 Warnings: 0') + + @client.query "DROP TABLE infoTest" end end end @@ -184,7 +173,7 @@ def connect *args context "#query" do it "should let you query again if iterating is finished when streaming" do - @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each {} + @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a expect { @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index ce8331986..0d9cca948 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -10,7 +10,7 @@ result = @client.query('SELECT 1') result.count.should eql(1) - result.each { |r| } + result.each.to_a result.count.should eql(1) end @@ -31,7 +31,7 @@ @client.query "USE test" result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false) result.count.should eql(0) - result.each {|r| } + result.each.to_a result.count.should eql(0) end @@ -113,8 +113,8 @@ result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false expect { - result.each {} - result.each {} + result.each.to_a + result.each.to_a }.to raise_exception(Mysql2::Error) end end From 0917ff58531c5d2142faeb4b8b0f6aa3bc6ab1de Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 16 Jul 2013 11:48:06 -0700 Subject: [PATCH 038/783] copy pasta err --- spec/mysql2/error_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index f62c74b9b..8f94932b8 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -4,7 +4,7 @@ describe Mysql2::Error do before(:each) do begin - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) client.query("HAHAHA") rescue Mysql2::Error => e @error = e From 16dcb7100f4f14d674c2fada4606e30aa02ffdc4 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 16 Jul 2013 11:53:27 -0700 Subject: [PATCH 039/783] went a little overzealous in my cleanup heh --- spec/mysql2/error_spec.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 8f94932b8..b308bc8be 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -4,21 +4,21 @@ describe Mysql2::Error do before(:each) do begin - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) - client.query("HAHAHA") + @err_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) + @err_client.query("HAHAHA") rescue Mysql2::Error => e @error = e ensure - client.close + @err_client.close end begin - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) - client.query("HAHAHA") + @err_client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) + @err_client2.query("HAHAHA") rescue Mysql2::Error => e @error2 = e ensure - client.close + @err_client2.close end end @@ -42,8 +42,8 @@ unless RUBY_VERSION =~ /1.8/ it "#message encoding should match the connection's encoding, or Encoding.default_internal if set" do if Encoding.default_internal.nil? - @error.message.encoding.should eql(@client.encoding) - @error2.message.encoding.should eql(@client2.encoding) + @error.message.encoding.should eql(@err_client.encoding) + @error2.message.encoding.should eql(@err_client2.encoding) else @error.message.encoding.should eql(Encoding.default_internal) @error2.message.encoding.should eql(Encoding.default_internal) @@ -52,8 +52,8 @@ it "#error encoding should match the connection's encoding, or Encoding.default_internal if set" do if Encoding.default_internal.nil? - @error.error.encoding.should eql(@client.encoding) - @error2.error.encoding.should eql(@client2.encoding) + @error.error.encoding.should eql(@err_client.encoding) + @error2.error.encoding.should eql(@err_client2.encoding) else @error.error.encoding.should eql(Encoding.default_internal) @error2.error.encoding.should eql(Encoding.default_internal) @@ -62,8 +62,8 @@ it "#sql_state encoding should match the connection's encoding, or Encoding.default_internal if set" do if Encoding.default_internal.nil? - @error.sql_state.encoding.should eql(@client.encoding) - @error2.sql_state.encoding.should eql(@client2.encoding) + @error.sql_state.encoding.should eql(@err_client.encoding) + @error2.sql_state.encoding.should eql(@err_client2.encoding) else @error.sql_state.encoding.should eql(Encoding.default_internal) @error2.sql_state.encoding.should eql(Encoding.default_internal) From 89c3baebeba22dbe9597b344cc98ab65aac99562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Wed, 17 Jul 2013 15:16:22 +0200 Subject: [PATCH 040/783] Revert "make sure write_timeout and local_infile ivars are setup to avoid warnings" This reverts commit e7e43411c5fd3191605b0dfb4d0dfb97c1466561. --- lib/mysql2/client.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 7c0c19bb3..8b726054f 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -1,6 +1,6 @@ module Mysql2 class Client - attr_reader :query_options, :read_timeout, :write_timeout, :local_infile + attr_reader :query_options, :read_timeout @@default_query_options = { :as => :hash, # the type of object you want each row back as; also supports :array (an array of values) :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result @@ -16,8 +16,6 @@ class Client def initialize(opts = {}) opts = Mysql2::Util.key_hash_as_symbols( opts ) @read_timeout = nil - @write_timeout = nil - @local_infile = nil @query_options = @@default_query_options.dup @query_options.merge! opts From b9c7c7ee5bc464448a1b77298618afc4e9bf8310 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 17 Jul 2013 11:51:20 -0700 Subject: [PATCH 041/783] just skip these tests on rbx :\ --- spec/mysql2/client_spec.rb | 48 ++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 2155bc342..6b6336627 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -264,30 +264,32 @@ def connect *args }.should raise_error(Mysql2::Error) end - # XXX this test is not deterministic (because Unix signal handling is not) - # and may fail on a loaded system - it "should run signal handlers while waiting for a response" do - mark = {} - trap(:USR1) { mark[:USR1] = Time.now } - begin - mark[:START] = Time.now - pid = fork do - sleep 1 # wait for client "SELECT sleep(2)" query to start - Process.kill(:USR1, Process.ppid) - sleep # wait for explicit kill to prevent GC disconnect + if !defined? Rubinius + # XXX this test is not deterministic (because Unix signal handling is not) + # and may fail on a loaded system + it "should run signal handlers while waiting for a response" do + mark = {} + trap(:USR1) { mark[:USR1] = Time.now } + begin + mark[:START] = Time.now + pid = fork do + sleep 1 # wait for client "SELECT sleep(2)" query to start + Process.kill(:USR1, Process.ppid) + sleep # wait for explicit kill to prevent GC disconnect + end + @client.query("SELECT sleep(2)") + mark[:END] = Time.now + mark.include?(:USR1).should be_true + (mark[:USR1] - mark[:START]).should >= 1 + (mark[:USR1] - mark[:START]).should < 1.3 + (mark[:END] - mark[:USR1]).should > 0.9 + (mark[:END] - mark[:START]).should >= 2 + (mark[:END] - mark[:START]).should < 2.3 + Process.kill(:TERM, pid) + Process.waitpid2(pid) + ensure + trap(:USR1, 'DEFAULT') end - @client.query("SELECT sleep(2)") - mark[:END] = Time.now - mark.include?(:USR1).should be_true - (mark[:USR1] - mark[:START]).should >= 1 - (mark[:USR1] - mark[:START]).should < 1.3 - (mark[:END] - mark[:USR1]).should > 0.9 - (mark[:END] - mark[:START]).should >= 2 - (mark[:END] - mark[:START]).should < 2.3 - Process.kill(:TERM, pid) - Process.waitpid2(pid) - ensure - trap(:USR1, 'DEFAULT') end end From 773ed65c9c2797480f0c4079a18607668cb317d8 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 17 Jul 2013 13:14:09 -0700 Subject: [PATCH 042/783] If dir_config returns [nil, nil] this will prevent trying to index into nil --- ext/mysql2/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index ab327da3b..243b6838c 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -34,7 +34,7 @@ def asplode lib # Ruby versions not incorporating the mkmf fix at # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 # do not properly search for lib directories, and must be corrected - unless lib[-3, 3] == 'lib' + unless lib && lib[-3, 3] == 'lib' @libdir_basename = 'lib' inc, lib = dir_config('mysql') end From b5ab12c0f43c453ef35ddfcb8799a68af7dd09da Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 17 Jul 2013 15:05:13 -0700 Subject: [PATCH 043/783] bump version for 0.3.13 release --- Gemfile.lock | 2 +- lib/mysql2/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 42b56b673..2bf875612 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - mysql2 (0.3.12) + mysql2 (0.3.13) GEM remote: https://rubygems.org/ diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 01207e183..7777767db 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.3.12" + VERSION = "0.3.13" end From 70882382530a178383fe88d49ab1f6a22db5d340 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 24 Jul 2013 16:15:54 -0700 Subject: [PATCH 044/783] Use mysql_config --include instead of --cflags --- ext/mysql2/extconf.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index ab327da3b..230e6634e 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -42,7 +42,7 @@ def asplode lib elsif mc = (with_config('mysql-config') || Dir[GLOB].first) then mc = Dir[GLOB].first if mc == true ver = `#{mc} --version`.chomp.to_f - cflags = `#{mc} --cflags`.chomp + includes = `#{mc} --include`.chomp exit 1 if $? != 0 libs = `#{mc} --libs_r`.chomp # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r). @@ -50,7 +50,7 @@ def asplode lib libs = `#{mc} --libs`.chomp end exit 1 if $? != 0 - $CPPFLAGS += ' ' + cflags + $INCFLAGS += ' ' + includes $libs = libs + " " + $libs else inc, lib = dir_config('mysql', '/usr/local') From 91f6c1b3a97b4fa6c527c530e848c238921c6fc4 Mon Sep 17 00:00:00 2001 From: Ian Duggan Date: Wed, 28 Aug 2013 13:36:07 -0700 Subject: [PATCH 045/783] Add secure_auth mysql_option() --- README.md | 1 + ext/mysql2/client.c | 10 ++++++++++ lib/mysql2/client.rb | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae93b3008..d5169fb77 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ Mysql2::Client.new( :connect_timeout = seconds, :reconnect = true/false, :local_infile = true/false, + :secure_auth = true/false ) ``` diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index b3651bfb1..e20d2c1f0 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -716,6 +716,11 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = &boolval; break; + case MYSQL_SECURE_AUTH: + boolval = (value == Qfalse ? 0 : 1); + retval = &boolval; + break; + default: return Qfalse; } @@ -1085,6 +1090,10 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE return self; } +static VALUE set_secure_auth(VALUE self, VALUE value) { + return _mysql_client_options(self, MYSQL_SECURE_AUTH, value); +} + static VALUE initialize_ext(VALUE self) { GET_CLIENT(self); @@ -1153,6 +1162,7 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "write_timeout=", set_write_timeout, 1); rb_define_private_method(cMysql2Client, "local_infile=", set_local_infile, 1); rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1); + rb_define_private_method(cMysql2Client, "secure_auth=", set_secure_auth, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); rb_define_private_method(cMysql2Client, "connect", rb_connect, 7); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 8b726054f..7f78a38a8 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -22,10 +22,10 @@ def initialize(opts = {}) initialize_ext # Set MySQL connection options (each one is a call to mysql_options()) - [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout].each do |key| + [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :secure_auth].each do |key| next unless opts.key?(key) case key - when :reconnect, :local_infile + when :reconnect, :local_infile, :secure_auth send(:"#{key}=", !!opts[key]) when :connect_timeout, :read_timeout, :write_timeout send(:"#{key}=", opts[key].to_i) From ecb675b5494f4347817547e7fcb5441f4378cffc Mon Sep 17 00:00:00 2001 From: Ian Duggan Date: Wed, 28 Aug 2013 21:02:50 -0700 Subject: [PATCH 046/783] Add secure_auth option description and ActiveRecord database.yml example to README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index d5169fb77..c99bb5f7a 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ Mysql2::Client.new( :secure_auth = true/false ) ``` +### Multiple result sets You can also retrieve multiple result sets. For this to work you need to connect with flags `Mysql2::Client::MULTI_STATEMENTS`. Using multiple result sets is normally used @@ -128,6 +129,22 @@ end See https://gist.github.com/1367987 for using MULTI_STATEMENTS with Active Record. +### Secure auth + +Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). This option causes the server to block connections by clients that attempt to use accounts that have passwords stored in the old (pre-4.1) format. It also causes clients to refuse to attempt a connection using the older password format. To bypass this restriction in the client, pass the option :secure_auth => false to Mysql2::Client.new(). If you using ActiveRecord, your database.yml might look something like this: + +``` +development: + adapter: mysql2 + encoding: utf8 + database: my_db_name + username: root + password: my_password + host: 127.0.0.1 + port: 3306 + secure_auth: false +```` + ## Cascading config The default config hash is at: From ff722f54564dfa8ef3c4289a47308c200c0df6d3 Mon Sep 17 00:00:00 2001 From: "Urabe, Shyouhei" Date: Thu, 29 Aug 2013 17:11:35 +0900 Subject: [PATCH 047/783] Avoid buffer overrun When you connect to a very old (4.0.x) MySQL a field might have chrsetnr of zero, which results in out-of-bounds access. That case you cannot assume any character sets so just mark them binary and let programmers handle the situation. --- ext/mysql2/result.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index d20e6eb94..09e0bc66c 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -157,6 +157,8 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e /* if binary flag is set, respect it's wishes */ if (field.flags & BINARY_FLAG && field.charsetnr == 63) { rb_enc_associate(val, binaryEncoding); + } else if (!field.charsetnr) { + rb_enc_associate(val, binaryEncoding); } else { /* lookup the encoding configured on this field */ const char *enc_name; From 738031040637fd452a99ab0335a809953ce56ca9 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 29 Aug 2013 10:04:13 -0700 Subject: [PATCH 048/783] Doc updates for secure_auth --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c99bb5f7a..78d522683 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,11 @@ See https://gist.github.com/1367987 for using MULTI_STATEMENTS with Active Recor ### Secure auth -Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). This option causes the server to block connections by clients that attempt to use accounts that have passwords stored in the old (pre-4.1) format. It also causes clients to refuse to attempt a connection using the older password format. To bypass this restriction in the client, pass the option :secure_auth => false to Mysql2::Client.new(). If you using ActiveRecord, your database.yml might look something like this: +Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). +When secure_auth is enabled, the server will refuse a connection if the account password is stored in old pre-MySQL 4.1 format. +The MySQL 5.6.5 client library may also refuse to attempt a connection if provided an older format password. +To bypass this restriction in the client, pass the option :secure_auth => false to Mysql2::Client.new(). +If using ActiveRecord, your database.yml might look something like this: ``` development: From 996438ebc72a34f0cf59fffe7fe0dc3a1c53d940 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 29 Aug 2013 10:02:24 -0700 Subject: [PATCH 049/783] Comment for missing field charset --- ext/mysql2/result.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 09e0bc66c..82f417692 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -158,6 +158,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e if (field.flags & BINARY_FLAG && field.charsetnr == 63) { rb_enc_associate(val, binaryEncoding); } else if (!field.charsetnr) { + /* MySQL 4.x may not provide an encoding, binary will get the bytes through */ rb_enc_associate(val, binaryEncoding); } else { /* lookup the encoding configured on this field */ From 83fbdcfff473bbee013aa1b8d9b774664f810481 Mon Sep 17 00:00:00 2001 From: flynn Date: Sat, 7 Sep 2013 13:52:04 -0400 Subject: [PATCH 050/783] Added clearer installation instructions for Windows users --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 78d522683..9eb1835db 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Mysql2::Client - your connection to the database Mysql2::Result - returned from issuing a #query on the connection. It includes Enumerable. ## Installing - +### OSX / Linux ``` sh gem install mysql2 ``` @@ -24,6 +24,26 @@ This gem links against MySQL's `libmysqlclient` C shared library. You may need t If you have installed MySQL to a non-standard location, add `gem install mysql2 --with-mysql-config=/some/random/path/bin/mysql_config` +### Windows +First, make sure you have the DevKit installed (http://rubyinstaller.org/downloads/) and its variables +are loaded by running devkit\devktvars.bat . + +Next, you need a MySQL library to link against. If you have MySQL loaded on your development machine, +you can use that. If not, you will need to either copy the MySQL directory from your server, or else +obtain a copy of the MySQL C connector: http://dev.mysql.com/downloads/connector/c/ + +If you're using the connector, I recommend just getting the .zip file and unzipping it someplace convenient. + +Now you can install mysql2. You must use the `--with-mysql-dir` option to tell gem where your MySQL library +files are. For example, if you unzipped the connector to c:\mysql-connector-c-6.1.1-win32 you would install +the gem like this: + + gem install mysql2 -- --with-mysql-dir=c:\mysql-connector-c-6.1.1-win32 + +Finally, you must copy libmysql.dll from the lib subdirectory of your MySQL or MySQL connector directory into +your ruby\bin directory. In the above example, libmysql.dll would be located at +c:\mysql-connector-c-6.1.1-win32\lib . + ## Usage Connect to a database: From 2221c6a52f847885b1fdbe5fdd670458e3265806 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sat, 24 Aug 2013 11:55:20 +0900 Subject: [PATCH 051/783] fix warning: ensure to check unused variable --- ext/mysql2/result.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 82f417692..505ccf654 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -283,6 +283,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo int tokens; unsigned int hour=0, min=0, sec=0; tokens = sscanf(row[i], "%2u:%2u:%2u", &hour, &min, &sec); + if (tokens < 3) { + val = Qnil; + break; + } val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec)); if (!NIL_P(app_timezone)) { if (app_timezone == intern_local) { @@ -300,6 +304,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo uint64_t seconds; tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6u", &year, &month, &day, &hour, &min, &sec, &msec); + if (tokens < 6) { /* msec might be empty */ + val = Qnil; + break; + } seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec; if (seconds == 0) { @@ -342,6 +350,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo int tokens; unsigned int year=0, month=0, day=0; tokens = sscanf(row[i], "%4u-%2u-%2u", &year, &month, &day); + if (tokens < 3) { + val = Qnil; + break; + } if (year+month+day == 0) { val = Qnil; } else { From 78b43e4b4d7c21dfc90c5b875592d5c6a6e814f2 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sat, 24 Aug 2013 12:00:01 +0900 Subject: [PATCH 052/783] fix warning: ISO C90 forbids mixed declarations and code --- ext/mysql2/client.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index e20d2c1f0..9b46ea7ff 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -124,6 +124,7 @@ static void rb_mysql_client_mark(void * wrapper) { static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client)); VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client)); + VALUE e; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc = rb_default_internal_encoding(); rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); @@ -136,7 +137,7 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { } #endif - VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg); + e = rb_exc_new3(cMysql2Error, rb_error_msg); rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client))); rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state); rb_exc_raise(e); @@ -530,11 +531,11 @@ static VALUE finish_and_mark_inactive(void *args) { * again. */ static VALUE rb_mysql_client_abandon_results(VALUE self) { - GET_CLIENT(self); - MYSQL_RES *result; int ret; + GET_CLIENT(self); + while (mysql_more_results(wrapper->client) == 1) { ret = mysql_next_result(wrapper->client); if (ret > 0) { @@ -810,9 +811,12 @@ static VALUE rb_mysql_client_server_info(VALUE self) { static VALUE rb_mysql_client_socket(VALUE self) { GET_CLIENT(self); #ifndef _WIN32 - REQUIRE_CONNECTED(wrapper); - int fd_set_fd = wrapper->client->net.fd; - return INT2NUM(fd_set_fd); + { + int fd_set_fd; + REQUIRE_CONNECTED(wrapper); + fd_set_fd = wrapper->client->net.fd; + return INT2NUM(fd_set_fd); + } #else rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows"); #endif @@ -939,8 +943,8 @@ static VALUE rb_mysql_client_more_results(VALUE self) */ static VALUE rb_mysql_client_next_result(VALUE self) { - GET_CLIENT(self); int ret; + GET_CLIENT(self); ret = mysql_next_result(wrapper->client); if (ret > 0) { rb_raise_mysql2_error(wrapper); From 21c30c05899eb2ae838ab9dd3b91329fd2099b17 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sat, 24 Aug 2013 13:07:24 +0900 Subject: [PATCH 053/783] fix deprecation warning: migrate rb_thread_blocking_region to rb_thread_call_without_gvl --- ext/mysql2/client.c | 70 ++++++++++++++++++++--------------------- ext/mysql2/client.h | 21 ++++++++----- ext/mysql2/extconf.rb | 3 ++ ext/mysql2/mysql2_ext.h | 3 ++ ext/mysql2/result.c | 6 ++-- 5 files changed, 58 insertions(+), 45 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 9b46ea7ff..2fa18c1f3 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -57,7 +57,7 @@ static VALUE rb_hash_dup(VALUE other) { /* * used to pass all arguments to mysql_real_connect while inside - * rb_thread_blocking_region + * rb_thread_call_without_gvl */ struct nogvl_connect_args { MYSQL *mysql; @@ -72,7 +72,7 @@ struct nogvl_connect_args { /* * used to pass all arguments to mysql_send_query while inside - * rb_thread_blocking_region + * rb_thread_call_without_gvl */ struct nogvl_send_query_args { MYSQL *mysql; @@ -84,7 +84,7 @@ struct nogvl_send_query_args { /* * used to pass all arguments to mysql_select_db while inside - * rb_thread_blocking_region + * rb_thread_call_without_gvl */ struct nogvl_select_db_args { MYSQL *mysql; @@ -144,15 +144,15 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { return Qnil; } -static VALUE nogvl_init(void *ptr) { +static void *nogvl_init(void *ptr) { MYSQL *client; /* may initialize embedded server and read /etc/services off disk */ client = mysql_init((MYSQL *)ptr); - return client ? Qtrue : Qfalse; + return (void*)(client ? Qtrue : Qfalse); } -static VALUE nogvl_connect(void *ptr) { +static void *nogvl_connect(void *ptr) { struct nogvl_connect_args *args = ptr; MYSQL *client; @@ -161,10 +161,10 @@ static VALUE nogvl_connect(void *ptr) { args->db, args->port, args->unix_socket, args->client_flag); - return client ? Qtrue : Qfalse; + return (void *)(client ? Qtrue : Qfalse); } -static VALUE nogvl_close(void *ptr) { +static void *nogvl_close(void *ptr) { mysql_client_wrapper *wrapper; #ifndef _WIN32 int flags; @@ -191,7 +191,7 @@ static VALUE nogvl_close(void *ptr) { mysql_close(wrapper->client); } - return Qnil; + return NULL; } static void rb_mysql_client_free(void *ptr) { @@ -293,11 +293,11 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); - rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0); + rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); if (rv == Qfalse) { while (rv == Qfalse && errno == EINTR && !mysql_errno(wrapper->client)) { errno = 0; - rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0); + rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); } if (rv == Qfalse) return rb_raise_mysql2_error(wrapper); @@ -317,7 +317,7 @@ static VALUE rb_mysql_client_close(VALUE self) { GET_CLIENT(self); if (wrapper->connected) { - rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0); + rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0); } return Qnil; @@ -328,19 +328,19 @@ static VALUE rb_mysql_client_close(VALUE self) { * enough to fit in a socket buffer, but sometimes large UPDATE and * INSERTs will cause the process to block */ -static VALUE nogvl_send_query(void *ptr) { +static void *nogvl_send_query(void *ptr) { struct nogvl_send_query_args *args = ptr; int rv; rv = mysql_send_query(args->mysql, args->sql_ptr, args->sql_len); - return rv == 0 ? Qtrue : Qfalse; + return (void*)(rv == 0 ? Qtrue : Qfalse); } static VALUE do_send_query(void *args) { struct nogvl_send_query_args *query_args = args; mysql_client_wrapper *wrapper = query_args->wrapper; - if (rb_thread_blocking_region(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { + if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); @@ -353,14 +353,14 @@ static VALUE do_send_query(void *args) { * response can overflow the socket buffers and cause us to eventually * block while calling mysql_read_query_result */ -static VALUE nogvl_read_query_result(void *ptr) { +static void *nogvl_read_query_result(void *ptr) { MYSQL * client = ptr; my_bool res = mysql_read_query_result(client); - return res == 0 ? Qtrue : Qfalse; + return (void *)(res == 0 ? Qtrue : Qfalse); } -static VALUE nogvl_do_result(void *ptr, char use_result) { +static void *nogvl_do_result(void *ptr, char use_result) { mysql_client_wrapper *wrapper; MYSQL_RES *result; @@ -375,15 +375,15 @@ static VALUE nogvl_do_result(void *ptr, char use_result) { ready for another command to be issued */ wrapper->active_thread = Qnil; - return (VALUE)result; + return result; } /* mysql_store_result may (unlikely) read rows off the socket */ -static VALUE nogvl_store_result(void *ptr) { +static void *nogvl_store_result(void *ptr) { return nogvl_do_result(ptr, 0); } -static VALUE nogvl_use_result(void *ptr) { +static void *nogvl_use_result(void *ptr) { return nogvl_do_result(ptr, 1); } @@ -403,7 +403,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { return Qnil; REQUIRE_CONNECTED(wrapper); - if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { + if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, mark this connection inactive */ MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); @@ -411,9 +411,9 @@ static VALUE rb_mysql_client_async_result(VALUE self) { is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream); if(is_streaming == Qtrue) { - result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_use_result, wrapper, RUBY_UBF_IO, 0); + result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0); } else { - result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); + result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); } if (result == NULL) { @@ -512,7 +512,7 @@ static VALUE finish_and_mark_inactive(void *args) { /* if we got here, the result hasn't been read off the wire yet so lets do that and then throw it away because we have no way of getting it back up to the caller from here */ - result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); + result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); mysql_free_result(result); wrapper->active_thread = Qnil; @@ -542,7 +542,7 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) { rb_raise_mysql2_error(wrapper); } - result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); + result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); if (result != NULL) { mysql_free_result(result); @@ -866,13 +866,13 @@ static VALUE rb_mysql_client_thread_id(VALUE self) { return ULL2NUM(retVal); } -static VALUE nogvl_select_db(void *ptr) { +static void *nogvl_select_db(void *ptr) { struct nogvl_select_db_args *args = ptr; if (mysql_select_db(args->mysql, args->db) == 0) - return Qtrue; + return (void *)Qtrue; else - return Qfalse; + return (void *)Qfalse; } /* call-seq: @@ -891,16 +891,16 @@ static VALUE rb_mysql_client_select_db(VALUE self, VALUE db) args.mysql = wrapper->client; args.db = StringValuePtr(db); - if (rb_thread_blocking_region(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse) + if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse) rb_raise_mysql2_error(wrapper); return db; } -static VALUE nogvl_ping(void *ptr) { +static void *nogvl_ping(void *ptr) { MYSQL *client = ptr; - return mysql_ping(client) == 0 ? Qtrue : Qfalse; + return (void *)(mysql_ping(client) == 0 ? Qtrue : Qfalse); } /* call-seq: @@ -917,7 +917,7 @@ static VALUE rb_mysql_client_ping(VALUE self) { if (!wrapper->connected) { return Qfalse; } else { - return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0); + return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0); } } @@ -969,7 +969,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) VALUE current; GET_CLIENT(self); - result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); + result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { @@ -1101,7 +1101,7 @@ static VALUE set_secure_auth(VALUE self, VALUE value) { static VALUE initialize_ext(VALUE self) { GET_CLIENT(self); - if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { + if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* TODO: warning - not enough memory? */ return rb_raise_mysql2_error(wrapper); } diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 1d12365d7..2f933bb49 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -1,24 +1,30 @@ #ifndef MYSQL2_CLIENT_H #define MYSQL2_CLIENT_H +#ifndef HAVE_RB_THREAD_CALL_WITHOUT_GVL +#ifdef HAVE_RB_THREAD_BLOCKING_REGION + +/* emulate rb_thread_call_without_gvl with rb_thread_blocking_region */ +#define rb_thread_call_without_gvl(func, data1, ubf, data2) \ + rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2) + +#else /* ! HAVE_RB_THREAD_BLOCKING_REGION */ /* - * partial emulation of the 1.9 rb_thread_blocking_region under 1.8, + * partial emulation of the 2.0 rb_thread_call_without_gvl under 1.8, * this is enough for dealing with blocking I/O functions in the * presence of threads. */ -#ifndef HAVE_RB_THREAD_BLOCKING_REGION #include #define RUBY_UBF_IO ((rb_unblock_function_t *)-1) typedef void rb_unblock_function_t(void *); -typedef VALUE rb_blocking_function_t(void *); -static VALUE -rb_thread_blocking_region( - rb_blocking_function_t *func, void *data1, +static void * +rb_thread_call_without_gvl( + void *(*func), void *data1, RB_MYSQL_UNUSED rb_unblock_function_t *ubf, RB_MYSQL_UNUSED void *data2) { - VALUE rv; + void *rv; TRAP_BEG; rv = func(data1); @@ -28,6 +34,7 @@ rb_thread_blocking_region( } #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ +#endif /* ! HAVE_RB_THREAD_CALL_WITHOUT_GVL */ void init_mysql2_client(); diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index c7ddd9d89..652c9f10e 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -5,6 +5,9 @@ def asplode lib abort "-----\n#{lib} is missing. please check your installation of mysql and try again.\n-----" end +# 2.0-only +have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h') + # 1.9-only have_func('rb_thread_blocking_region') have_func('rb_wait_for_single_fd') diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index 01bd8840c..36a4cdf53 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -29,6 +29,9 @@ typedef unsigned int uint; #ifdef HAVE_RUBY_ENCODING_H #include #endif +#ifdef HAVE_RUBY_THREAD_H +#include +#endif #if defined(__GNUC__) && (__GNUC__ >= 3) #define RB_MYSQL_UNUSED __attribute__ ((unused)) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 505ccf654..bcdc2c8d7 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -99,10 +99,10 @@ static void rb_mysql_result_free(void *ptr) { * reliable way for us to tell this so we'll always release the GVL * to be safe */ -static VALUE nogvl_fetch_row(void *ptr) { +static void *nogvl_fetch_row(void *ptr) { MYSQL_RES *result = ptr; - return (VALUE)mysql_fetch_row(result); + return mysql_fetch_row(result); } static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) { @@ -203,7 +203,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo #endif ptr = wrapper->result; - row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0); + row = (MYSQL_ROW)rb_thread_call_without_gvl(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0); if (row == NULL) { return Qnil; } From 958e1f83a1b689a3fd44546e7061e3a17b60f5ad Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sat, 24 Aug 2013 13:40:14 +0900 Subject: [PATCH 054/783] fix some warnings with Ruby 1.8 --- ext/mysql2/client.c | 6 +++--- ext/mysql2/client.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 2fa18c1f3..a2e63fd6f 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -750,8 +750,8 @@ static VALUE rb_mysql_client_info(VALUE self) { #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; -#endif GET_CLIENT(self); +#endif version = rb_hash_new(); #ifdef HAVE_RUBY_ENCODING_H @@ -1050,18 +1050,18 @@ static VALUE set_write_timeout(VALUE self, VALUE value) { static VALUE set_charset_name(VALUE self, VALUE value) { char *charset_name; +#ifdef HAVE_RUBY_ENCODING_H size_t charset_name_len; const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb; -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *enc; VALUE rb_enc; #endif GET_CLIENT(self); charset_name = RSTRING_PTR(value); - charset_name_len = RSTRING_LEN(value); #ifdef HAVE_RUBY_ENCODING_H + charset_name_len = RSTRING_LEN(value); mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, charset_name_len); if (mysql2rb == NULL || mysql2rb->rb_name == NULL) { VALUE inspect = rb_inspect(value); diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 2f933bb49..fedf9a368 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -20,7 +20,7 @@ typedef void rb_unblock_function_t(void *); static void * rb_thread_call_without_gvl( - void *(*func), void *data1, + void *(*func)(void *), void *data1, RB_MYSQL_UNUSED rb_unblock_function_t *ubf, RB_MYSQL_UNUSED void *data2) { From c998b1d56a0214c52dd2f1a8314bc7de248bc273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Thu, 25 Jul 2013 03:19:41 +0200 Subject: [PATCH 055/783] Allow read connection defaults from custom default file and group. #262 For example: @client = Mysql2::Client.new(:default_file => "/user/.my.cnf", :default_group => "client") --- .gitignore | 1 + ext/mysql2/client.c | 25 +++++++++++++++++++++++-- lib/mysql2/client.rb | 11 ++++++----- spec/my.cnf.example | 9 +++++++++ spec/mysql2/client_spec.rb | 16 ++++++++++++++++ tasks/rspec.rake | 9 +++++++++ 6 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 spec/my.cnf.example diff --git a/.gitignore b/.gitignore index 4aada83d2..c8d6b1ddc 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ tmp vendor lib/mysql2/mysql2.rb spec/configuration.yml +spec/my.cnf diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index a2e63fd6f..5ba08639c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -284,9 +284,9 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po VALUE rv; GET_CLIENT(self); - args.host = NIL_P(host) ? "localhost" : StringValuePtr(host); + args.host = NIL_P(host) ? NULL : StringValuePtr(host); args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket); - args.port = NIL_P(port) ? 3306 : NUM2INT(port); + args.port = NIL_P(port) ? NULL : NUM2INT(port); args.user = NIL_P(user) ? NULL : StringValuePtr(user); args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass); args.db = NIL_P(database) ? NULL : StringValuePtr(database); @@ -682,6 +682,7 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { int result; void *retval = NULL; unsigned int intval = 0; + const char * charval = NULL; my_bool boolval; GET_CLIENT(self); @@ -722,6 +723,16 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = &boolval; break; + case MYSQL_READ_DEFAULT_FILE: + charval = (const char *)StringValuePtr(value); + retval = charval; + break; + + case MYSQL_READ_DEFAULT_GROUP: + charval = (const char *)StringValuePtr(value); + retval = charval; + break; + default: return Qfalse; } @@ -1098,6 +1109,14 @@ static VALUE set_secure_auth(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_SECURE_AUTH, value); } +static VALUE set_read_default_file(VALUE self, VALUE value) { + return _mysql_client_options(self, MYSQL_READ_DEFAULT_FILE, value); +} + +static VALUE set_read_default_group(VALUE self, VALUE value) { + return _mysql_client_options(self, MYSQL_READ_DEFAULT_GROUP, value); +} + static VALUE initialize_ext(VALUE self) { GET_CLIENT(self); @@ -1167,6 +1186,8 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "local_infile=", set_local_infile, 1); rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1); rb_define_private_method(cMysql2Client, "secure_auth=", set_secure_auth, 1); + rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1); + rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); rb_define_private_method(cMysql2Client, "connect", rb_connect, 7); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 7f78a38a8..551236815 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -10,7 +10,9 @@ class Client :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller :cache_rows => true, # tells Mysql2 to use it's internal row cache for results :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION, - :cast => true + :cast => true, + :default_file => nil, + :default_group => nil } def initialize(opts = {}) @@ -21,8 +23,7 @@ def initialize(opts = {}) initialize_ext - # Set MySQL connection options (each one is a call to mysql_options()) - [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :secure_auth].each do |key| + [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth].each do |key| next unless opts.key?(key) case key when :reconnect, :local_infile, :secure_auth @@ -49,8 +50,8 @@ def initialize(opts = {}) user = opts[:username] || opts[:user] pass = opts[:password] || opts[:pass] - host = opts[:host] || opts[:hostname] || 'localhost' - port = opts[:port] || 3306 + host = opts[:host] || opts[:hostname] + port = opts[:port] database = opts[:database] || opts[:dbname] || opts[:db] socket = opts[:socket] || opts[:sock] flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags] diff --git a/spec/my.cnf.example b/spec/my.cnf.example new file mode 100644 index 000000000..5e7792d35 --- /dev/null +++ b/spec/my.cnf.example @@ -0,0 +1,9 @@ +[root] +host=localhost +user=LOCALUSERNAME +password= + +[client] +host=localhost +user=LOCALUSERNAME +password= diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 6b6336627..a6774a28a 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -2,6 +2,22 @@ require 'spec_helper' describe Mysql2::Client do + context "using defaults file" do + let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) } + + it "should not raise an exception for valid defaults group" do + lambda { + @client = Mysql2::Client.new(:default_file => cnf_file, :default_group => "test") + }.should_not raise_error(Mysql2::Error) + end + + it "should not raise an exception without default group" do + lambda { + @client = Mysql2::Client.new(:default_file => cnf_file) + }.should_not raise_error(Mysql2::Error) + end + end + it "should raise an exception upon connection failure" do lambda { # The odd local host IP address forces the mysql client library to diff --git a/tasks/rspec.rake b/tasks/rspec.rake index 487091c54..96a4caf5f 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -40,4 +40,13 @@ file 'spec/configuration.yml' => 'spec/configuration.yml.example' do |task| sh "sed -i 's/LOCALUSERNAME/#{ENV['USER']}/' #{dst_path}" end +file 'spec/my.cnf' => 'spec/my.cnf.example' do |task| + CLEAN.exclude task.name + src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) + dst_path = File.expand_path("../../#{task.name}", __FILE__) + cp src_path, dst_path + sh "sed -i 's/LOCALUSERNAME/#{ENV['USER']}/' #{dst_path}" +end + Rake::Task[:spec].prerequisites << :'spec/configuration.yml' +Rake::Task[:spec].prerequisites << :'spec/my.cnf' From 14975dc028ac9c8823879c837cfeb698b898611d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 2 Oct 2013 13:26:18 -0700 Subject: [PATCH 056/783] Add docs for default_file and default_group --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78d522683..d062159ec 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,9 @@ Mysql2::Client.new( :connect_timeout = seconds, :reconnect = true/false, :local_infile = true/false, - :secure_auth = true/false + :secure_auth = true/false, + :default_file = '/path/to/my.cfg', + :default_group = 'my.cfg section' ) ``` ### Multiple result sets @@ -147,7 +149,16 @@ development: host: 127.0.0.1 port: 3306 secure_auth: false -```` +``` + +### Reading a MySQL config file +You may read configuration options from a MySQL configuration file by passing +the `:default_file` and `:default_group` paramters. For example: + +``` + client = Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') +``` + ## Cascading config From daf61cd460c7aee1893578ca9eab76cac591285c Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Fri, 4 Oct 2013 17:13:55 +0900 Subject: [PATCH 057/783] fix warning: int is not a pointer. --- ext/mysql2/client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 5ba08639c..5b6c94f75 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -286,7 +286,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po args.host = NIL_P(host) ? NULL : StringValuePtr(host); args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket); - args.port = NIL_P(port) ? NULL : NUM2INT(port); + args.port = NIL_P(port) ? 0 : NUM2INT(port); args.user = NIL_P(user) ? NULL : StringValuePtr(user); args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass); args.db = NIL_P(database) ? NULL : StringValuePtr(database); From 5096776a0a6a30b7fbf77f1239b01a4024c8cf2e Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Fri, 4 Oct 2013 17:14:42 +0900 Subject: [PATCH 058/783] fix warning: object that pointed is const. --- ext/mysql2/client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 5b6c94f75..8db616de3 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -680,7 +680,7 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) { static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { int result; - void *retval = NULL; + const void *retval = NULL; unsigned int intval = 0; const char * charval = NULL; my_bool boolval; From 890c5390fe27605683eac3523fa9db06cc93a8ae Mon Sep 17 00:00:00 2001 From: Hiro Asari Date: Thu, 3 Oct 2013 10:49:00 -0400 Subject: [PATCH 059/783] Test connecting to DB with numeric-only name --- spec/configuration.yml.example | 6 ++++++ spec/mysql2/client_spec.rb | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/spec/configuration.yml.example b/spec/configuration.yml.example index 6024a1ce9..5a4406fc7 100644 --- a/spec/configuration.yml.example +++ b/spec/configuration.yml.example @@ -9,3 +9,9 @@ user: username: LOCALUSERNAME password: database: mysql2_test + +numericuser: + host: localhost + username: LOCALUSERNAME + password: + database: 12345 diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index a6774a28a..350a43f4d 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -104,6 +104,16 @@ def connect *args ssl_client.close end + it "should be able to connect to database with numeric-only name" do + lambda { + creds = DatabaseCredentials['numericuser'] + @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`" + @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`" + client = Mysql2::Client.new creds + @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`" + }.should_not raise_error + end + it "should respond to #close" do @client.should respond_to(:close) end From ddb1c6eafdaea6c8ce8dc02c1193ad145060c6d4 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 4 Oct 2013 13:29:40 -0700 Subject: [PATCH 060/783] Per tests from BanzaiMan, convert non-nil values before passing them to the C connect function. --- lib/mysql2/client.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 551236815..6290abc7d 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -56,6 +56,14 @@ def initialize(opts = {}) socket = opts[:socket] || opts[:sock] flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags] + # Correct the data types before passing these values down to the C level + user = user.to_s unless user.nil? + pass = pass.to_s unless pass.nil? + host = host.to_s unless host.nil? + port = port.to_i unless port.nil? + database = database.to_s unless database.nil? + socket = socket.to_s unless socket.nil? + connect user, pass, host, port, database, socket, flags end From 24391e67f1d4639aacd365a957bd771c2773b5f3 Mon Sep 17 00:00:00 2001 From: Hiro Asari Date: Fri, 4 Oct 2013 22:36:39 -0400 Subject: [PATCH 061/783] Eschew use of `sed` At least on the Mac, the `sed` command silently fails, not replacing `LOCALUSERNAME`. There, a correct invocation is something like: ``` sh "sed -i '' -e 's/LOCALUSERNAME/#{ENV['USER']}/' #{dst_path}" ``` Rather than comprehensively account for differences in `sed` on various platforms, use Ruby's File class to manipulate the files. --- tasks/rspec.rake | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tasks/rspec.rake b/tasks/rspec.rake index 96a4caf5f..3bb5228f9 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -36,16 +36,26 @@ file 'spec/configuration.yml' => 'spec/configuration.yml.example' do |task| CLEAN.exclude task.name src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) dst_path = File.expand_path("../../#{task.name}", __FILE__) - cp src_path, dst_path - sh "sed -i 's/LOCALUSERNAME/#{ENV['USER']}/' #{dst_path}" + + dst_file = File.open(dst_path, 'w') + File.open(src_path) do |f| + f.each_line do |line| + dst_file.write line.gsub(/LOCALUSERNAME/, ENV['USER']) + end + end end file 'spec/my.cnf' => 'spec/my.cnf.example' do |task| CLEAN.exclude task.name src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) dst_path = File.expand_path("../../#{task.name}", __FILE__) - cp src_path, dst_path - sh "sed -i 's/LOCALUSERNAME/#{ENV['USER']}/' #{dst_path}" + + dst_file = File.open(dst_path, 'w') + File.open(src_path) do |f| + f.each_line do |line| + dst_file.write line.gsub(/LOCALUSERNAME/, ENV['USER']) + end + end end Rake::Task[:spec].prerequisites << :'spec/configuration.yml' From cd1d5edb679ac60cd791c2f719bcec51db04f54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Mon, 29 Jul 2013 19:37:34 +0200 Subject: [PATCH 062/783] Added spec case for #306. --- spec/mysql2/client_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 350a43f4d..cc5a65f2d 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -433,6 +433,15 @@ def connect *args @multi_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:flags => Mysql2::Client::MULTI_STATEMENTS)) end + it "should raise an exception when one of multiple statements fails" do + result = @multi_client.query("SELECT 1 as 'set_1'; SELECT * FROM invalid_table_name;SELECT 2 as 'set_2';") + result.first['set_1'].should be(1) + lambda { + @multi_client.next_result + }.should raise_error(Mysql2::Error) + @multi_client.next_result.should be_false + end + it "returns multiple result sets" do @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'").first.should eql({ 'set_1' => 1 }) From 30a6f94f1500465d67801b43d3414a3c35539fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Tue, 5 Nov 2013 17:46:53 +0100 Subject: [PATCH 063/783] Remove .ruby-version, .rvmrc and Gemfile.lock. --- .gitignore | 3 ++ .ruby-version | 1 - .rvmrc | 1 - Gemfile.lock | 77 --------------------------------------------------- 4 files changed, 3 insertions(+), 79 deletions(-) delete mode 100644 .ruby-version delete mode 100644 .rvmrc delete mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index c8d6b1ddc..adecb40ed 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ vendor lib/mysql2/mysql2.rb spec/configuration.yml spec/my.cnf +Gemfile.lock +.ruby-version +.rvmrc diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 77fee73a8..000000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -1.9.3 diff --git a/.rvmrc b/.rvmrc deleted file mode 100644 index ed6705c46..000000000 --- a/.rvmrc +++ /dev/null @@ -1 +0,0 @@ -rvm use 1.9.3@mysql2 --create diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 2bf875612..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,77 +0,0 @@ -PATH - remote: . - specs: - mysql2 (0.3.13) - -GEM - remote: https://rubygems.org/ - specs: - activemodel (4.0.0) - activesupport (= 4.0.0) - builder (~> 3.1.0) - activerecord (4.0.0) - activemodel (= 4.0.0) - activerecord-deprecated_finders (~> 1.0.2) - activesupport (= 4.0.0) - arel (~> 4.0.0) - activerecord-deprecated_finders (1.0.3) - activesupport (4.0.0) - i18n (~> 0.6, >= 0.6.4) - minitest (~> 4.2) - multi_json (~> 1.3) - thread_safe (~> 0.1) - tzinfo (~> 0.3.37) - addressable (2.3.5) - arel (4.0.0) - atomic (1.1.10) - builder (3.1.4) - coderay (1.0.9) - data_objects (0.10.13) - addressable (~> 2.1) - diff-lcs (1.1.3) - do_mysql (0.10.13) - data_objects (= 0.10.13) - eventmachine (1.0.3) - faker (1.1.2) - i18n (~> 0.5) - i18n (0.6.4) - method_source (0.8.1) - minitest (4.7.5) - multi_json (1.7.7) - mysql (2.9.1) - pry (0.9.12.2) - coderay (~> 1.0.5) - method_source (~> 0.8) - slop (~> 3.4) - rake (0.9.6) - rake-compiler (0.8.3) - rake - rspec (2.8.0) - rspec-core (~> 2.8.0) - rspec-expectations (~> 2.8.0) - rspec-mocks (~> 2.8.0) - rspec-core (2.8.0) - rspec-expectations (2.8.0) - diff-lcs (~> 1.1.2) - rspec-mocks (2.8.0) - sequel (4.0.0) - slop (3.4.5) - thread_safe (0.1.0) - atomic - tzinfo (0.3.37) - -PLATFORMS - ruby - -DEPENDENCIES - activerecord (>= 3.0) - do_mysql - eventmachine - faker - mysql - mysql2! - pry - rake (~> 0.9.3) - rake-compiler (~> 0.8.1) - rspec (~> 2.8.0) - sequel From b1e14b3be29eb51ee914be29c758f0f1c5811ee9 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 11 Oct 2013 17:48:39 -0700 Subject: [PATCH 064/783] Handle dir_config and with_config mysql more strictly, add rpath more often, add notices --- ext/mysql2/extconf.rb | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 652c9f10e..062c56806 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -31,9 +31,9 @@ def asplode lib GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}" -if RUBY_PLATFORM =~ /mswin|mingw/ - inc, lib = dir_config('mysql') - +# If the user has provided a --with-mysql-dir argument, we must respect it or fail. +inc, lib = dir_config('mysql') +if inc && lib # Ruby versions not incorporating the mkmf fix at # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 # do not properly search for lib directories, and must be corrected @@ -41,9 +41,17 @@ def asplode lib @libdir_basename = 'lib' inc, lib = dir_config('mysql') end - exit 1 unless have_library("libmysql") -elsif mc = (with_config('mysql-config') || Dir[GLOB].first) then + abort "-----\nCannot find include dir at #{inc}\n-----" unless inc && File.directory?(inc) + abort "-----\nCannot find library dir at #{lib}\n-----" unless lib && File.directory?(lib) + warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" + rpath_dir = lib +elsif mc = (with_config('mysql-config') || Dir[GLOB].first) + # If the user has provided a --with-mysql-config argument, we must respect it or fail. + # If the user gave --with-mysql-config with no argument means we should try to find it. mc = Dir[GLOB].first if mc == true + abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exists?(mc) + abort "-----\nCannot execute mysql_config at #{mc}\n-----" unless File.executable?(mc) + warn "-----\nUsing mysql_config at #{mc}\n-----" ver = `#{mc} --version`.chomp.to_f includes = `#{mc} --include`.chomp exit 1 if $? != 0 @@ -55,6 +63,7 @@ def asplode lib exit 1 if $? != 0 $INCFLAGS += ' ' + includes $libs = libs + " " + $libs + rpath_dir = libs else inc, lib = dir_config('mysql', '/usr/local') libs = ['m', 'z', 'socket', 'nsl', 'mygcc'] @@ -62,11 +71,16 @@ def asplode lib exit 1 if libs.empty? have_library(libs.shift) end + rpath_dir = lib +end + +if RUBY_PLATFORM =~ /mswin|mingw/ + exit 1 unless have_library('libmysql') end -if have_header('mysql.h') then +if have_header('mysql.h') prefix = nil -elsif have_header('mysql/mysql.h') then +elsif have_header('mysql/mysql.h') prefix = 'mysql' else asplode 'mysql.h' @@ -77,16 +91,20 @@ def asplode lib asplode h unless have_header h end -# GCC specific flags -if RbConfig::MAKEFILE_CONFIG['CC'] =~ /gcc/ +# GCC | Clang | XCode specific flags +if RbConfig::MAKEFILE_CONFIG['CC'] =~ /gcc|clang|xcrun/ $CFLAGS << ' -Wall -funroll-loops' - if libdir = $libs[%r{-L(/[^ ]+)}, 1] + if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] # The following comment and test is borrowed from the Pg gem: # Try to use runtime path linker option, even if RbConfig doesn't know about it. # The rpath option is usually set implicit by dir_config(), but so far not on Mac OS X. if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', " -Wl,-rpath,#{libdir}") + warn "-----\nSetting rpath to #{libdir}\n-----" $LDFLAGS << " -Wl,-rpath,#{libdir}" + else + # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. + $LIBPATH << libdir unless $LIBPATH.include?(libdir) end end end From bcb1a0246d66a98bbde25f1926551a5e5a1ed329 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 13 Oct 2013 21:33:04 -0700 Subject: [PATCH 065/783] Documentation for the gem install options --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a07082e8..e488f674a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,27 @@ gem install mysql2 This gem links against MySQL's `libmysqlclient` C shared library. You may need to install a package such as `libmysqlclient-dev`, `mysql-devel`, or other appropriate package for your system. -If you have installed MySQL to a non-standard location, add `gem install mysql2 --with-mysql-config=/some/random/path/bin/mysql_config` +By default, the mysql2 gem will try to find a copy of MySQL in this order: + +* Option `--with-mysql-dir`, if provided (see below). +* Option `--with-mysql-config`, if provided (see below). +* Several typical paths for `msyql_config` (default for the majority of users). +* The directory `/usr/local`. + +### Configuration options + +Use these options by `gem install mysql2 -- [--optionA] [--optionB=argument]`. +The following options are mutually exclusive. + +* `--with-mysql-dir[=/path/to/mysqldir]` - +Specify the directory where MySQL is installed. The mysql2 gem will not use +`mysql_config`, but will instead look at `mysqldir/lib` and `mysqldir/include` +for the library and header files. + +* `--with-mysql-config[=/path/to/mysql_config]` - +Specify a path to the `mysql_config` binary provided by your copy of MySQL. The +mysql2 gem will ask this `mysql_config` binary about the compiler and linker +arguments needed. ### Windows First, make sure you have the DevKit installed (http://rubyinstaller.org/downloads/) and its variables From 0e6c618382feae277e29fc128090c01756cf7ccc Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 16 Oct 2013 12:56:51 -0700 Subject: [PATCH 066/783] Use try_link to test compiler flags instead of checking for gcc specifically. --- ext/mysql2/extconf.rb | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 062c56806..3794971ab 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -91,21 +91,27 @@ def asplode lib asplode h unless have_header h end -# GCC | Clang | XCode specific flags -if RbConfig::MAKEFILE_CONFIG['CC'] =~ /gcc|clang|xcrun/ - $CFLAGS << ' -Wall -funroll-loops' +# These gcc style flags are also supported by clang and xcode compilers, +# so we'll use a does-it-work test instead of an is-it-gcc test. +gcc_flags = ' -Wall -funroll-loops' +if try_link('int main() {return 0;}', gcc_flags) + $CFLAGS << gcc_flags +end - if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] - # The following comment and test is borrowed from the Pg gem: - # Try to use runtime path linker option, even if RbConfig doesn't know about it. - # The rpath option is usually set implicit by dir_config(), but so far not on Mac OS X. - if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', " -Wl,-rpath,#{libdir}") - warn "-----\nSetting rpath to #{libdir}\n-----" - $LDFLAGS << " -Wl,-rpath,#{libdir}" - else - # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. - $LIBPATH << libdir unless $LIBPATH.include?(libdir) +if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] + rpath_flags = " -Wl,-rpath,#{libdir}" + if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) + # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. + warn "-----\nSetting rpath to #{libdir}\n-----" + $LDFLAGS << rpath_flags + else + if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? + # If we got here because try_link failed, warn the user + warn "-----\nDon't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load\n-----" end + # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. + warn "-----\nSetting libpath to #{libdir}\n-----" + $LIBPATH << libdir unless $LIBPATH.include?(libdir) end end From 60f5731b2d8c418fb3ec01e8175205ca3748ce13 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 11 Oct 2013 03:22:39 -0700 Subject: [PATCH 067/783] Provide --with-mysql-rpath to override rpath if needed. --- ext/mysql2/extconf.rb | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 3794971ab..71a7a5799 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -98,20 +98,32 @@ def asplode lib $CFLAGS << gcc_flags end -if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] - rpath_flags = " -Wl,-rpath,#{libdir}" - if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) - # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. - warn "-----\nSetting rpath to #{libdir}\n-----" - $LDFLAGS << rpath_flags - else - if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? - # If we got here because try_link failed, warn the user - warn "-----\nDon't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load\n-----" +case explicit_rpath = with_config('mysql-rpath') +when true + abort "-----\nOption --with-mysql-rpath must have an argument\n-----" +when false + warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" +when String + # The user gave us a value so use it + rpath_flags = " -Wl,-rpath,#{explicit_rpath}" + warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" + $LDFLAGS << rpath_flags +else + if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] + rpath_flags = " -Wl,-rpath,#{libdir}" + if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) + # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. + warn "-----\nSetting rpath to #{libdir}\n-----" + $LDFLAGS << rpath_flags + else + if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? + # If we got here because try_link failed, warn the user + warn "-----\nDon't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load\n-----" + end + # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. + warn "-----\nSetting libpath to #{libdir}\n-----" + $LIBPATH << libdir unless $LIBPATH.include?(libdir) end - # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. - warn "-----\nSetting libpath to #{libdir}\n-----" - $LIBPATH << libdir unless $LIBPATH.include?(libdir) end end From 1b6f2d62e579c71481e33dc019d98334e6fc82fd Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 6 Nov 2013 16:46:49 -0800 Subject: [PATCH 068/783] README updated for --with-mysql-rpath --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e488f674a..484746020 100644 --- a/README.md +++ b/README.md @@ -32,17 +32,24 @@ By default, the mysql2 gem will try to find a copy of MySQL in this order: ### Configuration options Use these options by `gem install mysql2 -- [--optionA] [--optionB=argument]`. -The following options are mutually exclusive. * `--with-mysql-dir[=/path/to/mysqldir]` - Specify the directory where MySQL is installed. The mysql2 gem will not use `mysql_config`, but will instead look at `mysqldir/lib` and `mysqldir/include` for the library and header files. +This option is mutually exclusive with `--with-mysql-config`. * `--with-mysql-config[=/path/to/mysql_config]` - Specify a path to the `mysql_config` binary provided by your copy of MySQL. The mysql2 gem will ask this `mysql_config` binary about the compiler and linker arguments needed. +This option is mutually exclusive with `--with-mysql-dir`. + +* `--with-mysql-rpath=/path/to/mysql/lib` / `--without-mysql-rpath` - +Override the runtime path used to find the MySQL libraries. +This may be needed if you deploy to a system where these libraries +are located somewhere different than on your build system. +This overrides any rpath calculated by default or by the options above. ### Windows First, make sure you have the DevKit installed (http://rubyinstaller.org/downloads/) and its variables From 3f1b39e9d06239d09bdb62281fd33eb81771a166 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 6 Nov 2013 22:19:15 -0800 Subject: [PATCH 069/783] bump for 0.3.14 release --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 7777767db..431885945 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.3.13" + VERSION = "0.3.14" end From 520f4bc61230af945ca064a36e08419b9aa4392e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 7 Nov 2013 05:46:34 -0800 Subject: [PATCH 070/783] Update the Compatibility section --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 484746020..4311483f7 100644 --- a/README.md +++ b/README.md @@ -405,15 +405,15 @@ As for field values themselves, I'm workin on it - but expect that soon. ## Compatibility -The specs pass on my system (SL 10.6.3, x86_64) in these rubies: +This gem is regularly tested against the following Ruby versions on Linux and Mac OS X: -* 1.8.7-p249 -* ree-1.8.7-2010.01 -* 1.9.1-p378 -* ruby-trunk -* rbx-head - broken at the moment, working with the rbx team for a solution + * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0 (ongoing patch releases). + * Ruby Enterprise Edition (based on MRI 1.8.7). + * Rubinius 2.0 in compatibility modes 1.8, 1.9, 2.0. -The Active Record driver should work on 2.3.5 and 3.0 +The mysql2 gem 0.2.x series includes an Active Record driver that works with AR +2.3.x and 3.0.x. Starting in Active Record 3.1, a mysql2 driver is included in +the Active Record codebase and no longer provided in mysql2 gem 0.3 and above. ## Yeah... but why? @@ -422,7 +422,6 @@ Someone: Dude, the Mysql gem works fiiiiiine. Me: It sure does, but it only hands you nil and strings for field values. Leaving you to convert them into proper Ruby types in Ruby-land - which is slow as balls. - Someone: OK fine, but do_mysql can already give me back values with Ruby objects mapped to MySQL types. Me: Yep, but it's API is considerably more complex *and* can be ~2x slower. From dd795523b23128c11d90e570e7e251aeedad0511 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 7 Nov 2013 05:55:09 -0800 Subject: [PATCH 071/783] Add SSL configuration options to README --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 4311483f7..4d7b84079 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,26 @@ Mysql2::Client.new( :default_group = 'my.cfg section' ) ``` + +### SSL options + +Setting any of the following options will enable an SSL connection, but only if +your MySQL client library and server have been compiled with SSL support. +MySQL client library defaults will be used for any parameters that are left out +or set to nil. Relative paths are allowed, and may be required by managed +hosting providers such as Heroku. + +``` +Mysql2::Client.new( + ...options as above..., + :sslkey => '/path/to/client-key.pem', + :sslcert => '/path/to/client-cert.pem', + :sslca => '/path/to/ca-cert.pem', + :sslcapath => '/path/to/cacerts', + :sslcipher => 'DHE-RSA-AES256-SHA' + ) +``` + ### Multiple result sets You can also retrieve multiple result sets. For this to work you need to connect with From 9e4cefc8c17de7fb95d65dce02da211d66732b94 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 7 Nov 2013 06:15:28 -0800 Subject: [PATCH 072/783] Set the code type in several quoted sections --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4d7b84079..7fb7f736b 100644 --- a/README.md +++ b/README.md @@ -169,9 +169,9 @@ MySQL client library defaults will be used for any parameters that are left out or set to nil. Relative paths are allowed, and may be required by managed hosting providers such as Heroku. -``` +``` ruby Mysql2::Client.new( - ...options as above..., + # ...options as above..., :sslkey => '/path/to/client-key.pem', :sslcert => '/path/to/client-cert.pem', :sslca => '/path/to/ca-cert.pem', @@ -206,7 +206,7 @@ The MySQL 5.6.5 client library may also refuse to attempt a connection if provid To bypass this restriction in the client, pass the option :secure_auth => false to Mysql2::Client.new(). If using ActiveRecord, your database.yml might look something like this: -``` +``` yaml development: adapter: mysql2 encoding: utf8 @@ -219,11 +219,12 @@ development: ``` ### Reading a MySQL config file + You may read configuration options from a MySQL configuration file by passing the `:default_file` and `:default_group` paramters. For example: -``` - client = Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') +``` ruby +Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') ``` From aa768da03b9a5df24b0cfa378b3d6936a25161dd Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 9 Nov 2013 03:02:51 -0800 Subject: [PATCH 073/783] Test for insert_id above 32-bit max --- spec/mysql2/client_spec.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 350a43f4d..6c9364f0b 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -647,7 +647,7 @@ def connect *args context 'write operations api' do before(:each) do @client.query "USE test" - @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" + @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" end after(:each) do @@ -674,6 +674,15 @@ def connect *args @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1" @client.affected_rows.should eql(1) end + + it "#last_id should handle BIGINT auto-increment ids above 32 bits" do + # The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x. + # Insert a row with a given ID, this should raise the auto-increment state + @client.query "INSERT INTO lastIdTest (id, blah) VALUES (5000000000, 5000)" + @client.last_id.should eql(5000000000) + @client.query "INSERT INTO lastIdTest (blah) VALUES (5001)" + @client.last_id.should eql(5000000001) + end end it "should respond to #thread_id" do From 997e426f61da562854dceb46a09a08a1614f834a Mon Sep 17 00:00:00 2001 From: rlineweaver Date: Wed, 13 Nov 2013 15:54:47 -0500 Subject: [PATCH 074/783] expose a new function to properly close a MySQL connection for a given client when the garbage collection of a result triggers freeing of the client --- ext/mysql2/client.c | 13 ++++++++++--- ext/mysql2/client.h | 1 + ext/mysql2/result.c | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 8db616de3..f4b04d633 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -199,9 +199,7 @@ static void rb_mysql_client_free(void *ptr) { wrapper->refcount--; if (wrapper->refcount == 0) { - nogvl_close(wrapper); - xfree(wrapper->client); - xfree(wrapper); + close_connection_and_free_mysql2_client(wrapper); } } @@ -1311,3 +1309,12 @@ void init_mysql2_client() { LONG2NUM(CLIENT_BASIC_FLAGS)); #endif } + +void close_connection_and_free_mysql2_client(void *ptr) { + mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; + + nogvl_close(wrapper); + xfree(wrapper->client); + xfree(wrapper); +} + diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index fedf9a368..34906ff8d 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -37,6 +37,7 @@ rb_thread_call_without_gvl( #endif /* ! HAVE_RB_THREAD_CALL_WITHOUT_GVL */ void init_mysql2_client(); +void close_connection_and_free_mysql2_client(void *); typedef struct { VALUE encoding; diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index bcdc2c8d7..8cbe25896 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -1,6 +1,7 @@ #include #include +#include "client.h" #include "mysql_enc_to_ruby.h" #ifdef HAVE_RUBY_ENCODING_H @@ -86,8 +87,7 @@ static void rb_mysql_result_free(void *ptr) { if (wrapper->client != Qnil) { wrapper->client_wrapper->refcount--; if (wrapper->client_wrapper->refcount == 0) { - xfree(wrapper->client_wrapper->client); - xfree(wrapper->client_wrapper); + close_connection_and_free_mysql2_client(wrapper->client_wrapper); } } From 17d5cfc3b53a10ec1a15e525e569e8e1efba5401 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 22 Nov 2013 15:32:56 -0800 Subject: [PATCH 075/783] Spec for should not leaving dangling connections --- spec/mysql2/client_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 6c9364f0b..de65e4162 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -104,6 +104,22 @@ def connect *args ssl_client.close end + it "should not leave dangling connections after garbage collection" do + GC.start + client = Mysql2::Client.new(DatabaseCredentials['root']) + before_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i + + 10.times do + Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1') + end + after_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i + after_count.should == before_count + 10 + + GC.start + final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i + final_count.should == before_count + end + it "should be able to connect to database with numeric-only name" do lambda { creds = DatabaseCredentials['numericuser'] From 4aa9b14f21e7ae99501e148ada0e5e8e27aa1eb0 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 22 Nov 2013 15:32:13 -0800 Subject: [PATCH 076/783] Refactor 997e426 with function decr_mysql2_client --- ext/mysql2/client.c | 17 +++++++---------- ext/mysql2/client.h | 6 +++--- ext/mysql2/result.c | 6 +----- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index f4b04d633..e42f71f08 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -196,10 +196,16 @@ static void *nogvl_close(void *ptr) { static void rb_mysql_client_free(void *ptr) { mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; + decr_mysql2_client(wrapper); +} +void decr_mysql2_client(mysql_client_wrapper *wrapper) +{ wrapper->refcount--; if (wrapper->refcount == 0) { - close_connection_and_free_mysql2_client(wrapper); + nogvl_close(wrapper); + xfree(wrapper->client); + xfree(wrapper); } } @@ -1309,12 +1315,3 @@ void init_mysql2_client() { LONG2NUM(CLIENT_BASIC_FLAGS)); #endif } - -void close_connection_and_free_mysql2_client(void *ptr) { - mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; - - nogvl_close(wrapper); - xfree(wrapper->client); - xfree(wrapper); -} - diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 34906ff8d..16cb28cc3 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -36,9 +36,6 @@ rb_thread_call_without_gvl( #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ #endif /* ! HAVE_RB_THREAD_CALL_WITHOUT_GVL */ -void init_mysql2_client(); -void close_connection_and_free_mysql2_client(void *); - typedef struct { VALUE encoding; VALUE active_thread; /* rb_thread_current() or Qnil */ @@ -51,4 +48,7 @@ typedef struct { MYSQL *client; } mysql_client_wrapper; +void init_mysql2_client(); +void decr_mysql2_client(mysql_client_wrapper *wrapper); + #endif diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 8cbe25896..6c6364b8e 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -1,7 +1,6 @@ #include #include -#include "client.h" #include "mysql_enc_to_ruby.h" #ifdef HAVE_RUBY_ENCODING_H @@ -85,10 +84,7 @@ static void rb_mysql_result_free(void *ptr) { // If the GC gets to client first it will be nil if (wrapper->client != Qnil) { - wrapper->client_wrapper->refcount--; - if (wrapper->client_wrapper->refcount == 0) { - close_connection_and_free_mysql2_client(wrapper->client_wrapper); - } + decr_mysql2_client(wrapper->client_wrapper); } xfree(wrapper); From dda503f8368a5614c53faff1cdc1e3a224c80e40 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 20 Nov 2013 23:53:45 -0800 Subject: [PATCH 077/783] Travis has updated its Rubinius, and Rubinius has a gem for the standard library --- .travis.yml | 4 +--- Gemfile | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 13d3b83ab..b94d3c24a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,7 @@ rvm: - 1.9.3 - 2.0.0 - ree - - rbx-18mode - - rbx-19mode - - rbx-20mode + - rbx bundler_args: --without benchmarks script: - bundle exec rake diff --git a/Gemfile b/Gemfile index 39777220f..76c05e4a7 100644 --- a/Gemfile +++ b/Gemfile @@ -14,3 +14,7 @@ end group :development do gem 'pry' end + +platforms :rbx do + gem 'rubysl' +end From 06e9af194c3c182d7a8f392259b8e1a3982d7152 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 22 Nov 2013 22:24:26 -0800 Subject: [PATCH 078/783] Add gc.honor_start for Rubninius. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index b94d3c24a..efd9dc163 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ rvm: - 2.0.0 - ree - rbx +env: RBXOPT=-Xgc.honor_start=true bundler_args: --without benchmarks script: - bundle exec rake From e657969168513f997ea1615508bbeb2b712695ac Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 25 Nov 2013 15:56:54 -0800 Subject: [PATCH 079/783] Cut the GC some slack in Rubinius. --- spec/mysql2/client_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index de65e4162..a63a80b5c 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -117,7 +117,8 @@ def connect *args GC.start final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i - final_count.should == before_count + final_count.should == before_count if !defined? Rubinius + final_count.should < before_count + 5 if defined? Rubinius # Cut GC some slack end it "should be able to connect to database with numeric-only name" do From 84d78eb513d8334ba7ca3158dad1b788bee9f4f2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 25 Nov 2013 16:04:48 -0800 Subject: [PATCH 080/783] Run Travis tests on Ruby 2.1.0-preview2 --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index efd9dc163..03dff985f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,12 @@ rvm: - 1.9.2 - 1.9.3 - 2.0.0 + - 2.1.0-preview2 - ree - rbx +matrix: + allow_failures: + - rvm: 2.1.0-preview2 env: RBXOPT=-Xgc.honor_start=true bundler_args: --without benchmarks script: From e0be4f7accdd54ae9c02f841e69302b45c3224e3 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 29 Nov 2013 09:53:52 -0600 Subject: [PATCH 081/783] Make sure generated files are closed before we try to use them --- tasks/rspec.rake | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tasks/rspec.rake b/tasks/rspec.rake index 3bb5228f9..27e865a58 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -37,9 +37,8 @@ file 'spec/configuration.yml' => 'spec/configuration.yml.example' do |task| src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) dst_path = File.expand_path("../../#{task.name}", __FILE__) - dst_file = File.open(dst_path, 'w') - File.open(src_path) do |f| - f.each_line do |line| + File.open(dst_path, 'w') do |dst_file| + File.open(src_path).each_line do |line| dst_file.write line.gsub(/LOCALUSERNAME/, ENV['USER']) end end @@ -50,9 +49,8 @@ file 'spec/my.cnf' => 'spec/my.cnf.example' do |task| src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) dst_path = File.expand_path("../../#{task.name}", __FILE__) - dst_file = File.open(dst_path, 'w') - File.open(src_path) do |f| - f.each_line do |line| + File.open(dst_path, 'w') do |dst_file| + File.open(src_path).each_line do |line| dst_file.write line.gsub(/LOCALUSERNAME/, ENV['USER']) end end From 1a068cf0556ebcf67a27cef191e3af647b1aab0c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 29 Nov 2013 22:47:11 -0600 Subject: [PATCH 082/783] Spell out the load order of rake tasks --- Rakefile | 10 ++++++++-- tasks/rspec.rake | 2 -- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 7bb616b34..98f6ece21 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,11 @@ # encoding: UTF-8 require 'rake' -# Load custom tasks -Dir['tasks/*.rake'].sort.each { |f| load f } +# Load custom tasks (careful attention to define tasks before prerequisites) +load 'tasks/rspec.rake' +load 'tasks/compile.rake' +load 'tasks/generate.rake' +load 'tasks/benchmarks.rake' +load 'tasks/vendor_mysql.rake' + +task :default => :spec diff --git a/tasks/rspec.rake b/tasks/rspec.rake index 27e865a58..123343def 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -26,8 +26,6 @@ begin RSpec::Core::RakeTask.new('spec') do |t| t.verbose = true end - - task :default => :spec rescue LoadError puts "rspec, or one of its dependencies, is not available. Install it with: sudo gem install rspec" end From 06bdc79979f35506d8e043562c4e3e7014aaae48 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 29 Nov 2013 23:07:01 -0600 Subject: [PATCH 083/783] Just run tests once now that prereqs are fixed --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03dff985f..b6efffe8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,5 +13,4 @@ matrix: env: RBXOPT=-Xgc.honor_start=true bundler_args: --without benchmarks script: - - bundle exec rake - - bundle exec rspec + - bundle exec rake spec From bffb9a3ced6ad2c78c2d43d935614b6497e14dda Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 30 Nov 2013 16:46:07 -0600 Subject: [PATCH 084/783] Update rubysl-foo gems and allow time for Rubinius GC --- Gemfile | 4 +++- spec/mysql2/client_spec.rb | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 76c05e4a7..a3b95aed7 100644 --- a/Gemfile +++ b/Gemfile @@ -16,5 +16,7 @@ group :development do end platforms :rbx do - gem 'rubysl' + gem 'rubysl-rake' + gem 'rubysl-drb' + gem 'rubysl-bigdecimal' end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index a63a80b5c..5d74c48d4 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -106,6 +106,7 @@ def connect *args it "should not leave dangling connections after garbage collection" do GC.start + sleep 1 if defined? Rubinius # Let the rbx GC thread do its work client = Mysql2::Client.new(DatabaseCredentials['root']) before_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i @@ -116,9 +117,9 @@ def connect *args after_count.should == before_count + 10 GC.start + sleep 1 if defined? Rubinius # Let the rbx GC thread do its work final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i - final_count.should == before_count if !defined? Rubinius - final_count.should < before_count + 5 if defined? Rubinius # Cut GC some slack + final_count.should == before_count end it "should be able to connect to database with numeric-only name" do From e99b11bde37dfe4b6633620add72d8cd64e85a79 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 1 Dec 2013 07:54:33 -0600 Subject: [PATCH 085/783] Update README for multiple result set errors. --- README.md | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7fb7f736b..cb0513302 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ files are. For example, if you unzipped the connector to c:\mysql-connector-c-6. the gem like this: gem install mysql2 -- --with-mysql-dir=c:\mysql-connector-c-6.1.1-win32 - + Finally, you must copy libmysql.dll from the lib subdirectory of your MySQL or MySQL connector directory into your ruby\bin directory. In the above example, libmysql.dll would be located at c:\mysql-connector-c-6.1.1-win32\lib . @@ -182,20 +182,45 @@ Mysql2::Client.new( ### Multiple result sets -You can also retrieve multiple result sets. For this to work you need to connect with -flags `Mysql2::Client::MULTI_STATEMENTS`. Using multiple result sets is normally used -when calling stored procedures that return more than one result set +You can also retrieve multiple result sets. For this to work you need to +connect with flags `Mysql2::Client::MULTI_STATEMENTS`. Multiple result sets can +be used with stored procedures that return more than one result set, and for +bundling several SQL statements into a single call to `client.query`. ``` ruby -client = Mysql2::Client.new(:host => "localhost", :username => "root", :flags => Mysql2::Client::MULTI_STATEMENTS ) -result = client.query( 'CALL sp_customer_list( 25, 10 )') +client = Mysql2::Client.new(:host => "localhost", :username => "root", :flags => Mysql2::Client::MULTI_STATEMENTS) +result = client.query('CALL sp_customer_list( 25, 10 )') # result now contains the first result set -while ( client.next_result) - result = client.store_result - # result now contains the next result set +while client.next_result + result = client.store_result + # result now contains the next result set +end +``` + +Repeated calls to `client.next_result` will return true, false, or raise an +exception if the respective query erred. When `client.next_result` returns true, +call `client.store_result` to retieve a result object. Exceptions are not +raised until `client.next_result` is called to find the status of the respective +query. Subsequent queries are not executed if an earlier query raised an +exception. + +``` ruby +result = client.query('SELECT 1; SELECT 2; SELECT A; SELECT 3') +p result.first + +while client.next_result + result = client.store_result + p result.first end ``` +Yields: +``` +{"1"=>1} +{"2"=>2} +next_result: Unknown column 'A' in 'field list' (Mysql2::Error) +``` + See https://gist.github.com/1367987 for using MULTI_STATEMENTS with Active Record. ### Secure auth From 74f7eeec6e2d588ea1988606416a9eec476a7205 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 1 Dec 2013 06:35:33 -0800 Subject: [PATCH 086/783] One more sentence about next_result. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb0513302..83e25a246 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ exception if the respective query erred. When `client.next_result` returns true, call `client.store_result` to retieve a result object. Exceptions are not raised until `client.next_result` is called to find the status of the respective query. Subsequent queries are not executed if an earlier query raised an -exception. +exception. Subsequent calls to `client.next_result` will return false. ``` ruby result = client.query('SELECT 1; SELECT 2; SELECT A; SELECT 3') From a9571b60d0dc48229cb722ef8a5769b91162f10a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 19 Nov 2013 07:45:16 -0800 Subject: [PATCH 087/783] Local local infile handler --- ext/mysql2/client.c | 12 ++-- ext/mysql2/infile.c | 119 ++++++++++++++++++++++++++++++++++++++++ ext/mysql2/infile.h | 1 + ext/mysql2/mysql2_ext.h | 1 + ext/mysql2/result.c | 1 + 5 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 ext/mysql2/infile.c create mode 100644 ext/mysql2/infile.h diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index e42f71f08..6a55258c6 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1,5 +1,5 @@ #include -#include + #include #ifndef _WIN32 #include @@ -146,9 +146,13 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { static void *nogvl_init(void *ptr) { MYSQL *client; + mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; /* may initialize embedded server and read /etc/services off disk */ - client = mysql_init((MYSQL *)ptr); + client = mysql_init(wrapper->client); + + if (client) mysql2_set_local_infile(client, wrapper); + return (void*)(client ? Qtrue : Qfalse); } @@ -1124,7 +1128,7 @@ static VALUE set_read_default_group(VALUE self, VALUE value) { static VALUE initialize_ext(VALUE self) { GET_CLIENT(self); - if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { + if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) { /* TODO: warning - not enough memory? */ return rb_raise_mysql2_error(wrapper); } @@ -1139,7 +1143,7 @@ void init_mysql2_client() { int i; int dots = 0; const char *lib = mysql_get_client_info(); - + for (i = 0; lib[i] != 0 && MYSQL_LINK_VERSION[i] != 0; i++) { if (lib[i] == '.') { dots++; diff --git a/ext/mysql2/infile.c b/ext/mysql2/infile.c new file mode 100644 index 000000000..c6b6a556d --- /dev/null +++ b/ext/mysql2/infile.c @@ -0,0 +1,119 @@ +#include + +#include +#include + +#define ERROR_LEN 1024 +typedef struct +{ + int fd; + char *filename; + char error[ERROR_LEN]; + mysql_client_wrapper *wrapper; +} mysql2_local_infile_data; + +/* MySQL calls this function when a user begins a LOAD DATA LOCAL INFILE query. + * + * Allocate a data struct and pass it back through the data pointer. + * + * Returns: + * 0 on success + * 1 on error + */ +static int +mysql2_local_infile_init(void **ptr, const char *filename, void *userdata) +{ + mysql2_local_infile_data *data = malloc(sizeof(mysql2_local_infile_data)); + if (!data) return 1; + + *ptr = data; + data->error[0] = 0; + data->wrapper = userdata; + + data->filename = strdup(filename); + if (!data->filename) { + snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename); + return 1; + } + + data->fd = open(filename, O_RDONLY); + if (data->fd < 0) { + snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename); + return 1; + } + + return 0; +} + +/* MySQL calls this function to read data from the local file. + * + * Returns: + * > 0 number of bytes read + * == 0 end of file + * < 0 error + */ +static int +mysql2_local_infile_read(void *ptr, char *buf, uint buf_len) +{ + int count; + mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr; + + count = (int)read(data->fd, buf, buf_len); + if (count < 0) { + snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), data->filename); + } + + return count; +} + +/* MySQL calls this function when we're done with the LOCAL INFILE query. + * + * ptr will be null if the init function failed. + */ +static void +mysql2_local_infile_end(void *ptr) +{ + mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr; + if (data) { + if (data->fd >= 0) + close(data->fd); + if (data->filename) + free(data->filename); + free(data); + } +} + +/* MySQL calls this function if any of the functions above returned an error. + * + * This function is called even if init failed, with whatever ptr value + * init has set, regardless of the return value of the init function. + * + * Returns: + * Error message number (see http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html) + */ +static int +mysql2_local_infile_error(void *ptr, char *error_msg, uint error_msg_len) +{ + mysql2_local_infile_data *data = (mysql2_local_infile_data *) ptr; + + if (data) { + snprintf(error_msg, error_msg_len, "%s", data->error); + return CR_UNKNOWN_ERROR; + } + + snprintf(error_msg, error_msg_len, "Out of memory"); + return CR_OUT_OF_MEMORY; +} + +/* Tell MySQL Client to use our own local_infile functions. + * This is both due to bugginess in the default handlers, + * and to improve the Rubyness of the handlers here. + */ +void mysql2_set_local_infile(MYSQL *mysql, void *userdata) +{ + mysql_set_local_infile_handler(mysql, + mysql2_local_infile_init, + mysql2_local_infile_read, + mysql2_local_infile_end, + mysql2_local_infile_error, userdata); +} diff --git a/ext/mysql2/infile.h b/ext/mysql2/infile.h new file mode 100644 index 000000000..ffc4aa023 --- /dev/null +++ b/ext/mysql2/infile.h @@ -0,0 +1 @@ +void mysql2_set_local_infile(MYSQL *mysql, void *userdata); diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index 36a4cdf53..bb06079b2 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -41,5 +41,6 @@ typedef unsigned int uint; #include #include +#include #endif diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 6c6364b8e..7461b8987 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -1,4 +1,5 @@ #include + #include #include "mysql_enc_to_ruby.h" From 6b9c23df7fd3a65d793de253837386ff4862a0b6 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 20 Nov 2013 23:17:16 -0800 Subject: [PATCH 088/783] Tests for local infile --- spec/mysql2/client_spec.rb | 43 ++++++++++++++++++++++++++++++++++++++ spec/test_data | 1 + 2 files changed, 44 insertions(+) create mode 100644 spec/test_data diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 3d7cab045..26908b739 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -197,6 +197,49 @@ def connect *args end end + context ":local_infile" do + before(:all) do + @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true) + local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'" + local_enabled = local.any? {|x| x['Value'] == 'ON'} + pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled + + @client_i.query %[ + CREATE TABLE IF NOT EXISTS infileTest ( + id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + foo VARCHAR(10), + bar MEDIUMTEXT + ) + ] + end + + after(:all) do + @client_i.query "DROP TABLE infileTest" + end + + it "should raise an error when local_infile is disabled" do + client = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => false) + lambda { + client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" + }.should raise_error(Mysql2::Error, %r{command is not allowed}) + end + + it "should raise an error when a non-existent file is loaded" do + lambda { + @client_i.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest" + }.should_not raise_error(Mysql2::Error, %r{file not found: this/file/is/not/here}) + end + + it "should LOAD DATA LOCAL INFILE" do + @client_i.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" + info = @client_i.query_info + info.should eql({:records => 1, :deleted => 0, :skipped => 0, :warnings => 0}) + + result = @client_i.query "SELECT * FROM infileTest" + result.first.should eql({'id' => 1, 'foo' => 'Hello', 'bar' => 'World'}) + end + end + it "should expect connect_timeout to be a positive integer" do lambda { Mysql2::Client.new(:connect_timeout => -1) diff --git a/spec/test_data b/spec/test_data new file mode 100644 index 000000000..e5488b1b2 --- /dev/null +++ b/spec/test_data @@ -0,0 +1 @@ +\N Hello World From 9e56c0731a0883f36da6f59466470b0a717e4a8d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 20 Nov 2013 23:28:20 -0800 Subject: [PATCH 089/783] Check for SSL state to give a more specific pending state message --- spec/mysql2/client_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 26908b739..733df9e00 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -78,7 +78,10 @@ def connect *args end it "should be able to connect via SSL options" do - pending("DON'T WORRY, THIS TEST PASSES :) - but is machine-specific. You need to have MySQL running with SSL configured and enabled. Then update the paths in this test to your needs and remove the pending state.") + ssl = @client.query "SHOW VARIABLES LIKE 'have_%ssl'" + ssl_enabled = ssl.any? {|x| x['Value'] == 'ENABLED'} + pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") unless ssl_enabled + pending("DON'T WORRY, THIS TEST PASSES - but you must update the SSL cert paths in this test and remove this pending state.") ssl_client = nil lambda { ssl_client = Mysql2::Client.new( From 5f5372c3e38500f214b92ffba55eeda39067f392 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 1 Dec 2013 20:24:33 -0600 Subject: [PATCH 090/783] Spelling fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 83e25a246..736d17a84 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ end Repeated calls to `client.next_result` will return true, false, or raise an exception if the respective query erred. When `client.next_result` returns true, -call `client.store_result` to retieve a result object. Exceptions are not +call `client.store_result` to retrieve a result object. Exceptions are not raised until `client.next_result` is called to find the status of the respective query. Subsequent queries are not executed if an earlier query raised an exception. Subsequent calls to `client.next_result` will return false. From 5677e2e0b9d37cb33c53c91f7dbbbdeef1ab7976 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 22 Nov 2013 16:21:23 -0800 Subject: [PATCH 091/783] Check for errors after streaming results, the connection might have gone away due to read timeout. --- ext/mysql2/result.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 6c6364b8e..15579a679 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -429,6 +429,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { ID db_timezone, app_timezone, dbTz, appTz; mysql2_result_wrapper * wrapper; unsigned long i; + const char * errstr; int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1, streaming = 0; MYSQL_FIELD * fields = NULL; @@ -492,7 +493,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } if (wrapper->lastRowProcessed == 0) { - if(streaming) { + if (streaming) { /* We can't get number of rows if we're streaming, */ /* until we've finished fetching all rows */ wrapper->numberOfRows = 0; @@ -508,7 +509,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } if (streaming) { - if(!wrapper->streamingComplete) { + if (!wrapper->streamingComplete) { VALUE row; fields = mysql_fetch_fields(wrapper->result); @@ -526,6 +527,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { wrapper->numberOfRows = wrapper->lastRowProcessed; wrapper->streamingComplete = 1; + + // Check for errors, the connection might have gone out from under us + // mysql_error returns an empty string if there is no error + errstr = mysql_error(wrapper->client_wrapper->client); + if (errstr[0]) { + rb_raise(cMysql2Error, "%s", errstr); + } } else { rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery)."); } From 2607cda11b1840ce87e216b9c14eaa16cacd6b2b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 22 Nov 2013 20:14:00 -0800 Subject: [PATCH 092/783] Spec for exception if streaming ended due to a timeout --- spec/mysql2/result_spec.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 0d9cca948..418a337e3 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -27,7 +27,7 @@ result.each { |r| r.should_not be_nil} end - it "#count should be zero for rows after streaming when there were no results " do + it "#count should be zero for rows after streaming when there were no results" do @client.query "USE test" result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false) result.count.should eql(0) @@ -35,6 +35,27 @@ result.count.should eql(0) end + it "should raise an exception if streaming ended due to a timeout" do + # Create an extra client instance, since we're going to time it out + client = Mysql2::Client.new DatabaseCredentials['root'] + client.query "CREATE TEMPORARY TABLE streamingTest (val VARCHAR(10))" + + # Insert enough records to force the result set into multiple reads + 10000.times do |i| + client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')" + end + + client.query "SET net_write_timeout = 1" + res = client.query "SELECT * FROM streamingTest", :stream => true + + lambda { + res.each_with_index do |row, i| + # Exhaust the first result packet then trigger a timeout + sleep 2 if i > 0 && i % 1000 == 0 + end + }.should raise_error(Mysql2::Error, /Lost connection/) + end + it "should have included Enumerable" do Mysql2::Result.ancestors.include?(Enumerable).should be_true end From 478bba315d2480757e6cb2174f74d3e43c49683a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 2 Dec 2013 08:11:50 -0600 Subject: [PATCH 093/783] Move the streaming tests into a context together --- spec/mysql2/result_spec.rb | 102 +++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 418a337e3..d49b7db75 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -6,56 +6,6 @@ @result = @client.query "SELECT 1" end - it "should maintain a count while streaming" do - result = @client.query('SELECT 1') - - result.count.should eql(1) - result.each.to_a - result.count.should eql(1) - end - - it "should set the actual count of rows after streaming" do - @client.query "USE test" - result = @client.query("SELECT * FROM mysql2_test", :stream => true, :cache_rows => false) - result.count.should eql(0) - result.each {|r| } - result.count.should eql(1) - end - - it "should not yield nil at the end of streaming" do - result = @client.query('SELECT * FROM mysql2_test', :stream => true) - result.each { |r| r.should_not be_nil} - end - - it "#count should be zero for rows after streaming when there were no results" do - @client.query "USE test" - result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false) - result.count.should eql(0) - result.each.to_a - result.count.should eql(0) - end - - it "should raise an exception if streaming ended due to a timeout" do - # Create an extra client instance, since we're going to time it out - client = Mysql2::Client.new DatabaseCredentials['root'] - client.query "CREATE TEMPORARY TABLE streamingTest (val VARCHAR(10))" - - # Insert enough records to force the result set into multiple reads - 10000.times do |i| - client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')" - end - - client.query "SET net_write_timeout = 1" - res = client.query "SELECT * FROM streamingTest", :stream => true - - lambda { - res.each_with_index do |row, i| - # Exhaust the first result packet then trigger a timeout - sleep 2 if i > 0 && i % 1000 == 0 - end - }.should raise_error(Mysql2::Error, /Lost connection/) - end - it "should have included Enumerable" do Mysql2::Result.ancestors.include?(Enumerable).should be_true end @@ -156,6 +106,58 @@ end end + context "streaming" do + it "should maintain a count while streaming" do + result = @client.query('SELECT 1') + + result.count.should eql(1) + result.each.to_a + result.count.should eql(1) + end + + it "should set the actual count of rows after streaming" do + @client.query "USE test" + result = @client.query("SELECT * FROM mysql2_test", :stream => true, :cache_rows => false) + result.count.should eql(0) + result.each {|r| } + result.count.should eql(1) + end + + it "should not yield nil at the end of streaming" do + result = @client.query('SELECT * FROM mysql2_test', :stream => true) + result.each { |r| r.should_not be_nil} + end + + it "#count should be zero for rows after streaming when there were no results" do + @client.query "USE test" + result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false) + result.count.should eql(0) + result.each.to_a + result.count.should eql(0) + end + + it "should raise an exception if streaming ended due to a timeout" do + # Create an extra client instance, since we're going to time it out + client = Mysql2::Client.new DatabaseCredentials['root'] + client.query "CREATE TEMPORARY TABLE streamingTest (val VARCHAR(10))" + + # Insert enough records to force the result set into multiple reads + 10000.times do |i| + client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')" + end + + client.query "SET net_write_timeout = 1" + res = client.query "SELECT * FROM streamingTest", :stream => true + + lambda { + res.each_with_index do |row, i| + # Exhaust the first result packet then trigger a timeout + sleep 2 if i > 0 && i % 1000 == 0 + end + }.should raise_error(Mysql2::Error, /Lost connection/) + end + end + context "row data type mapping" do before(:each) do @client.query "USE test" From cf751c4b55b8795963f3f2d21771ce3291cc3287 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 2 Dec 2013 09:10:16 -0600 Subject: [PATCH 094/783] Need more rows to max out the packet size --- spec/mysql2/result_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index d49b7db75..964b0bcd9 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -139,9 +139,10 @@ it "should raise an exception if streaming ended due to a timeout" do # Create an extra client instance, since we're going to time it out client = Mysql2::Client.new DatabaseCredentials['root'] - client.query "CREATE TEMPORARY TABLE streamingTest (val VARCHAR(10))" + client.query "CREATE TEMPORARY TABLE streamingTest (val BINARY(255))" # Insert enough records to force the result set into multiple reads + # (the BINARY type is used simply because it forces full width results) 10000.times do |i| client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')" end From c9ef244118154691c31c4304857c4272cd528bcd Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 2 Dec 2013 08:30:02 -0600 Subject: [PATCH 095/783] Silence warnings about cache_rows being ignored --- spec/mysql2/result_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 964b0bcd9..52fa0f103 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -124,7 +124,7 @@ end it "should not yield nil at the end of streaming" do - result = @client.query('SELECT * FROM mysql2_test', :stream => true) + result = @client.query('SELECT * FROM mysql2_test', :stream => true, :cache_rows => false) result.each { |r| r.should_not be_nil} end @@ -148,7 +148,7 @@ end client.query "SET net_write_timeout = 1" - res = client.query "SELECT * FROM streamingTest", :stream => true + res = client.query "SELECT * FROM streamingTest", :stream => true, :cache_rows => false lambda { res.each_with_index do |row, i| From 6e56949be9fea2bed36d0b2593be425c2e4db767 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 2 Dec 2013 08:29:56 -0600 Subject: [PATCH 096/783] test database is already in use --- spec/mysql2/result_spec.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 52fa0f103..485b370a7 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -92,7 +92,6 @@ context "#fields" do before(:each) do - @client.query "USE test" @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") end @@ -116,7 +115,6 @@ end it "should set the actual count of rows after streaming" do - @client.query "USE test" result = @client.query("SELECT * FROM mysql2_test", :stream => true, :cache_rows => false) result.count.should eql(0) result.each {|r| } @@ -129,7 +127,6 @@ end it "#count should be zero for rows after streaming when there were no results" do - @client.query "USE test" result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false) result.count.should eql(0) result.each.to_a @@ -161,7 +158,6 @@ context "row data type mapping" do before(:each) do - @client.query "USE test" @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first end @@ -347,7 +343,6 @@ result['enum_test'].encoding.should eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['enum_test'].encoding.should eql(Encoding.find('us-ascii')) client2.close @@ -377,7 +372,6 @@ result['set_test'].encoding.should eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['set_test'].encoding.should eql(Encoding.find('us-ascii')) client2.close @@ -460,7 +454,6 @@ result[field].encoding.should eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result[field].encoding.should eql(Encoding.find('us-ascii')) client2.close From eb03953f54323321c28d6ddb3e5905e2ad751662 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 4 Dec 2013 00:04:17 -0800 Subject: [PATCH 097/783] Error encoding is mixed/binary if MySQL < 5.5 and connection encoding if MySQL >= 5.5 --- ext/mysql2/client.c | 25 +++++++++++++++++-------- ext/mysql2/client.h | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 6a55258c6..30863818f 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -126,14 +126,21 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client)); VALUE e; #ifdef HAVE_RUBY_ENCODING_H - rb_encoding *default_internal_enc = rb_default_internal_encoding(); - rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); - - rb_enc_associate(rb_error_msg, conn_enc); - rb_enc_associate(rb_sql_state, conn_enc); - if (default_internal_enc) { - rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc); - rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); + if (wrapper->server_version < 50500) { + /* MySQL < 5.5 uses mixed encoding, just call it binary. */ + int err_enc = rb_ascii8bit_encindex(); + rb_enc_associate_index(rb_error_msg, err_enc); + rb_enc_associate_index(rb_sql_state, err_enc); + } else { + /* MySQL >= 5.5 uses UTF-8 errors internally and converts them to the connection encoding. */ + rb_encoding *default_internal_enc = rb_default_internal_encoding(); + rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); + rb_enc_associate(rb_error_msg, conn_enc); + rb_enc_associate(rb_sql_state, conn_enc); + if (default_internal_enc) { + rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc); + rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); + } } #endif @@ -219,6 +226,7 @@ static VALUE allocate(VALUE klass) { obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; wrapper->active_thread = Qnil; + wrapper->server_version = 0; wrapper->reconnect_enabled = 0; wrapper->connected = 0; /* means that a database connection is open */ wrapper->initialized = 0; /* means that that the wrapper is initialized */ @@ -311,6 +319,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po return rb_raise_mysql2_error(wrapper); } + wrapper->server_version = mysql_get_server_version(wrapper->client); wrapper->connected = 1; return self; } diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 16cb28cc3..1b4149d22 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -39,6 +39,7 @@ rb_thread_call_without_gvl( typedef struct { VALUE encoding; VALUE active_thread; /* rb_thread_current() or Qnil */ + long server_version; int reconnect_enabled; int active; int connected; From 0fedea140dd9a993aca1a57faae190eaa6776bdc Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 11 Dec 2013 23:15:01 -0800 Subject: [PATCH 098/783] Rewrite the error encoding specs --- spec/mysql2/error_spec.rb | 105 ++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index b308bc8be..784d38a4c 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -1,73 +1,78 @@ # encoding: UTF-8 require 'spec_helper' +# The matrix of error encoding tests: +# ('Enc = X' means 'Encoding.default_internal = X') +# MySQL < 5.5 MySQL >= 5.5 +# Ruby 1.8 N/A N/A +# Ruby 1.9+ +# Enc = nil +# :enc = nil BINARY UTF-8 +# +# Enc = XYZ +# :enc = XYZ BINARY XYZ +# +# Enc = FOO +# :enc = BAR BINARY FOO +# + + describe Mysql2::Error do - before(:each) do + shared_examples "mysql2 error" do begin - @err_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) - @err_client.query("HAHAHA") + err_client = Mysql2::Client.new(DatabaseCredentials['root']) + err_client.query("HAHAHA") rescue Mysql2::Error => e - @error = e + error = e ensure - @err_client.close + err_client.close end + subject { error } + it { should respond_to(:error_number) } + it { should respond_to(:sql_state) } + + # Mysql gem compatibility + it { should respond_to(:errno) } + it { should respond_to(:error) } + end + + shared_examples "mysql2 error encoding" do |db_enc, def_enc, err_enc| + Encoding.default_internal = def_enc + begin - @err_client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) - @err_client2.query("HAHAHA") + err_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => db_enc)) + err_client.query("造字") rescue Mysql2::Error => e - @error2 = e + error = e ensure - @err_client2.close + err_client.close end - end - it "should respond to #error_number" do - @error.should respond_to(:error_number) - end + subject { error.message.encoding } + it { should eql(err_enc) } - it "should respond to #sql_state" do - @error.should respond_to(:sql_state) - end + subject { error.error.encoding } + it { should eql(err_enc) } - # Mysql gem compatibility - it "should alias #error_number to #errno" do - @error.should respond_to(:errno) + subject { error.sql_state.encoding } + it { should eql(err_enc) } end - it "should alias #message to #error" do - @error.should respond_to(:error) - end + it_behaves_like "mysql2 error" unless RUBY_VERSION =~ /1.8/ - it "#message encoding should match the connection's encoding, or Encoding.default_internal if set" do - if Encoding.default_internal.nil? - @error.message.encoding.should eql(@err_client.encoding) - @error2.message.encoding.should eql(@err_client2.encoding) - else - @error.message.encoding.should eql(Encoding.default_internal) - @error2.message.encoding.should eql(Encoding.default_internal) - end - end - - it "#error encoding should match the connection's encoding, or Encoding.default_internal if set" do - if Encoding.default_internal.nil? - @error.error.encoding.should eql(@err_client.encoding) - @error2.error.encoding.should eql(@err_client2.encoding) - else - @error.error.encoding.should eql(Encoding.default_internal) - @error2.error.encoding.should eql(Encoding.default_internal) - end - end - - it "#sql_state encoding should match the connection's encoding, or Encoding.default_internal if set" do - if Encoding.default_internal.nil? - @error.sql_state.encoding.should eql(@err_client.encoding) - @error2.sql_state.encoding.should eql(@err_client2.encoding) - else - @error.sql_state.encoding.should eql(Encoding.default_internal) - @error2.sql_state.encoding.should eql(Encoding.default_internal) - end + mysql_ver = Mysql2::Client.new(DatabaseCredentials['root']).server_info[:id] + if mysql_ver < 50505 + it_behaves_like "mysql2 error encoding", nil, nil, Encoding::ASCII_8BIT + it_behaves_like "mysql2 error encoding", 'utf8', Encoding::UTF_8, Encoding::ASCII_8BIT + it_behaves_like "mysql2 error encoding", 'big5', Encoding::Big5, Encoding::ASCII_8BIT + it_behaves_like "mysql2 error encoding", 'big5', Encoding::US_ASCII, Encoding::ASCII_8BIT + else + it_behaves_like "mysql2 error encoding", nil, nil, Encoding::UTF_8 + it_behaves_like "mysql2 error encoding", 'utf8', Encoding::UTF_8, Encoding::UTF_8 + it_behaves_like "mysql2 error encoding", 'big5', Encoding::Big5, Encoding::Big5 + it_behaves_like "mysql2 error encoding", 'big5', Encoding::US_ASCII, Encoding::US_ASCII end end end From 3f3c7be9eb102738ed123eb1a7e1f9e2487f49e7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 14 Dec 2013 00:24:35 -0800 Subject: [PATCH 099/783] Spec titling for MySQL version under test --- spec/mysql2/error_spec.rb | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 784d38a4c..c42bb8618 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -50,13 +50,21 @@ end subject { error.message.encoding } - it { should eql(err_enc) } + it "#message should transcode from #{db_enc.inspect} to #{err_enc}" do should eql(err_enc) end subject { error.error.encoding } - it { should eql(err_enc) } + it "#error should transcode from #{db_enc.inspect} to #{err_enc}" do should eql(err_enc) end subject { error.sql_state.encoding } - it { should eql(err_enc) } + it "#sql_state should transcode from #{db_enc.inspect} to #{err_enc}" do should eql(err_enc) end + end + + shared_examples "mysql2 error encoding (MySQL < 5.5)" do |db_enc, def_enc, err_enc| + include_examples "mysql2 error encoding", db_enc, def_enc, err_enc + end + + shared_examples "mysql2 error encoding (MySQL >= 5.5)" do |db_enc, def_enc, err_enc| + include_examples "mysql2 error encoding", db_enc, def_enc, err_enc end it_behaves_like "mysql2 error" @@ -64,15 +72,15 @@ unless RUBY_VERSION =~ /1.8/ mysql_ver = Mysql2::Client.new(DatabaseCredentials['root']).server_info[:id] if mysql_ver < 50505 - it_behaves_like "mysql2 error encoding", nil, nil, Encoding::ASCII_8BIT - it_behaves_like "mysql2 error encoding", 'utf8', Encoding::UTF_8, Encoding::ASCII_8BIT - it_behaves_like "mysql2 error encoding", 'big5', Encoding::Big5, Encoding::ASCII_8BIT - it_behaves_like "mysql2 error encoding", 'big5', Encoding::US_ASCII, Encoding::ASCII_8BIT + it_behaves_like "mysql2 error encoding (MySQL < 5.5)", nil, nil, Encoding::ASCII_8BIT + it_behaves_like "mysql2 error encoding (MySQL < 5.5)", 'utf8', Encoding::UTF_8, Encoding::ASCII_8BIT + it_behaves_like "mysql2 error encoding (MySQL < 5.5)", 'big5', Encoding::Big5, Encoding::ASCII_8BIT + it_behaves_like "mysql2 error encoding (MySQL < 5.5)", 'big5', Encoding::US_ASCII, Encoding::ASCII_8BIT else - it_behaves_like "mysql2 error encoding", nil, nil, Encoding::UTF_8 - it_behaves_like "mysql2 error encoding", 'utf8', Encoding::UTF_8, Encoding::UTF_8 - it_behaves_like "mysql2 error encoding", 'big5', Encoding::Big5, Encoding::Big5 - it_behaves_like "mysql2 error encoding", 'big5', Encoding::US_ASCII, Encoding::US_ASCII + it_behaves_like "mysql2 error encoding (MySQL >= 5.5)", nil, nil, Encoding::UTF_8 + it_behaves_like "mysql2 error encoding (MySQL >= 5.5)", 'utf8', Encoding::UTF_8, Encoding::UTF_8 + it_behaves_like "mysql2 error encoding (MySQL >= 5.5)", 'big5', Encoding::Big5, Encoding::Big5 + it_behaves_like "mysql2 error encoding (MySQL >= 5.5)", 'big5', Encoding::US_ASCII, Encoding::US_ASCII end end end From 0140a681cd05960cd24947284bf0be8ecdc9e942 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 18 Dec 2013 16:19:28 -0800 Subject: [PATCH 100/783] we don't plan on supporting other output formats, for now --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 736d17a84..76874b9f2 100644 --- a/README.md +++ b/README.md @@ -300,11 +300,6 @@ Pass the `:as => :array` option to any of the above methods of configuration The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash -### Others... - -I may add support for `:as => :csv` or even `:as => :json` to allow for *much* more efficient generation of those data types from result sets. -If you'd like to see either of these (or others), open an issue and start bugging me about it ;) - ### Timezones Mysql2 now supports two timezone options: From fe283669f2340d9bba9b3ee60acfae0ad37acad2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 20 Dec 2013 20:33:59 -0800 Subject: [PATCH 101/783] Ruby 2.1.0 on Travis, no longer expected to fail. --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index b6efffe8f..1f9ed1ec7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,12 +4,9 @@ rvm: - 1.9.2 - 1.9.3 - 2.0.0 - - 2.1.0-preview2 + - 2.1.0 - ree - rbx -matrix: - allow_failures: - - rvm: 2.1.0-preview2 env: RBXOPT=-Xgc.honor_start=true bundler_args: --without benchmarks script: From a3d1aade56af16075ef97e6a3a74f7bafb10a36d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 20 Dec 2013 21:15:25 -0800 Subject: [PATCH 102/783] Timeout behavior has changed in Ruby 2.1, set these tests to pending for now. --- spec/mysql2/client_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 733df9e00..9c78e8ea9 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -396,6 +396,7 @@ def connect *args end it "should close the connection when an exception is raised" do + pending "Ruby 2.1 has changed Timeout behavior." if RUBY_VERSION =~ /2.1/ begin Timeout.timeout(1) do @client.query("SELECT sleep(2)") @@ -409,6 +410,7 @@ def connect *args end it "should handle Timeouts without leaving the connection hanging if reconnect is true" do + pending "Ruby 2.1 has changed Timeout behavior." if RUBY_VERSION =~ /2.1/ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) begin Timeout.timeout(1) do @@ -423,6 +425,7 @@ def connect *args end it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction true" do + pending "Ruby 2.1 has changed Timeout behavior." if RUBY_VERSION =~ /2.1/ client = Mysql2::Client.new(DatabaseCredentials['root']) begin Timeout.timeout(1) do From fa077395db73dbc7543d4e1e75578bf18a4fdf75 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 2 Jan 2014 00:10:45 -0800 Subject: [PATCH 103/783] Always give the GC some time, not just on Rubinius --- spec/mysql2/client_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 9c78e8ea9..0e22b1dcd 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -109,7 +109,7 @@ def connect *args it "should not leave dangling connections after garbage collection" do GC.start - sleep 1 if defined? Rubinius # Let the rbx GC thread do its work + sleep 0.300 # Let GC do its work client = Mysql2::Client.new(DatabaseCredentials['root']) before_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i @@ -120,7 +120,7 @@ def connect *args after_count.should == before_count + 10 GC.start - sleep 1 if defined? Rubinius # Let the rbx GC thread do its work + sleep 0.300 # Let GC do its work final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i final_count.should == before_count end From ba0068c37a284eb2700fa3c0b15fd74d0275a976 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 2 Jan 2014 00:16:00 -0800 Subject: [PATCH 104/783] Temporary workaround for broken Rubygems on Travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1f9ed1ec7..0ee13501b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,3 +11,7 @@ env: RBXOPT=-Xgc.honor_start=true bundler_args: --without benchmarks script: - bundle exec rake spec +# Temporary workaround for broken Rubygems on Travis +before_install: + - gem update --system 2.1.11 + - gem --version From e3fdbcb01f623f16673a028c2365de3139b61596 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 16 Jan 2014 01:24:30 +0000 Subject: [PATCH 105/783] result: avoid dynamically-sized stack allocation Otherwise, long field names have the potential to cause stack overflow and perhaps slow down the MRI GC. This reduces one extraneous data copy while we're at it, too. Using the checkstack.pl script in the Linux kernel reveals no other dynamically sized stack allcations after this change. usage: objdump -d mysql2.so | ~/linux/scripts/checkstack.pl x86_64 --- ext/mysql2/result.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index d3ad6f334..553ce2fd9 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -122,16 +122,12 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int field = mysql_fetch_field_direct(wrapper->result, idx); if (symbolize_keys) { - char buf[field->name_length+1]; - memcpy(buf, field->name, field->name_length); - buf[field->name_length] = 0; - #ifdef HAVE_RB_INTERN3 - rb_field = rb_intern3(buf, field->name_length, rb_utf8_encoding()); + rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding()); rb_field = ID2SYM(rb_field); #else VALUE colStr; - colStr = rb_str_new2(buf); + colStr = rb_str_new(field->name, field->name_length); rb_field = ID2SYM(rb_to_id(colStr)); #endif } else { From 271ea5eac738a1fa23b5e9d521962256822496dc Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 22 Jan 2014 16:31:51 -0800 Subject: [PATCH 106/783] Ensure error messages are always valid UTF-8 Given all of the scenarios for a MySQL error message's encoding, the end result is either a) a potentially corrupt string or b) a valid UTF-8 string. In any case, we'll want to end up with a UTF-8 string eventually. --- ext/mysql2/client.c | 24 +++------ lib/mysql2/error.rb | 71 ++++++++++++++++++++++--- spec/mysql2/error_spec.rb | 107 ++++++++++++++++---------------------- 3 files changed, 117 insertions(+), 85 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 30863818f..5879f53b2 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -12,7 +12,7 @@ VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error; static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; -static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql; +static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql, intern_server_version; #ifndef HAVE_RB_HASH_DUP static VALUE rb_hash_dup(VALUE other) { @@ -125,26 +125,13 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client)); VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client)); VALUE e; + #ifdef HAVE_RUBY_ENCODING_H - if (wrapper->server_version < 50500) { - /* MySQL < 5.5 uses mixed encoding, just call it binary. */ - int err_enc = rb_ascii8bit_encindex(); - rb_enc_associate_index(rb_error_msg, err_enc); - rb_enc_associate_index(rb_sql_state, err_enc); - } else { - /* MySQL >= 5.5 uses UTF-8 errors internally and converts them to the connection encoding. */ - rb_encoding *default_internal_enc = rb_default_internal_encoding(); - rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); - rb_enc_associate(rb_error_msg, conn_enc); - rb_enc_associate(rb_sql_state, conn_enc); - if (default_internal_enc) { - rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc); - rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); - } - } + rb_enc_associate(rb_error_msg, rb_utf8_encoding()); + rb_enc_associate(rb_sql_state, rb_usascii_encoding()); #endif - e = rb_exc_new3(cMysql2Error, rb_error_msg); + e = rb_funcall(cMysql2Error, rb_intern("new"), 2, rb_error_msg, LONG2FIX(wrapper->server_version)); rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client))); rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state); rb_exc_raise(e); @@ -1221,6 +1208,7 @@ void init_mysql2_client() { intern_merge_bang = rb_intern("merge!"); intern_error_number_eql = rb_intern("error_number="); intern_sql_state_eql = rb_intern("sql_state="); + intern_server_version = rb_intern("server_version="); #ifdef CLIENT_LONG_PASSWORD rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index e195c2cbb..f8af572f4 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -1,15 +1,74 @@ +# encoding: UTF-8 + module Mysql2 class Error < StandardError - attr_accessor :error_number, :sql_state + REPLACEMENT_CHAR = '?' - def initialize msg - super - @error_number = nil - @sql_state = nil - end + attr_accessor :error_number, :sql_state + attr_writer :server_version # Mysql gem compatibility alias_method :errno, :error_number alias_method :error, :message + + def initialize(msg, server_version=nil) + self.server_version = server_version + + super(clean_message(msg)) + end + + private + + # In MySQL 5.5+ error messages are always constructed server-side as UTF-8 + # then returned in the encoding set by the `character_set_results` system + # variable. + # + # See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for + # more contetx. + # + # Before MySQL 5.5 error message template strings are in whatever encoding + # is associated with the error message language. + # See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html + # for more information. + # + # The issue is that the user-data inserted in the message could potentially + # be in any encoding MySQL supports and is insert into the latin1, euckr or + # koi8r string raw. Meaning there's a high probability the string will be + # corrupt encoding-wise. + # + # See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for + # more information. + # + # So in an attempt to make sure the error message string is always in a valid + # encoding, we'll assume UTF-8 and clean the string of anything that's not a + # valid UTF-8 character. + # + # Except for if we're on 1.8, where we'll do nothing ;) + # + # Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8 + def clean_message(message) + return message if !message.respond_to?(:encoding) + + if @server_version && @server_version > 50500 + message + else + if message.respond_to? :scrub + message.scrub + else + # This is ugly as hell but Ruby 1.9 doesn't provide a way to clean a string + # and retain it's valid UTF-8 characters, that I know of. + + new_message = "".force_encoding(Encoding::UTF_8) + message.chars.each do |char| + if char.valid_encoding? + new_message << char + else + new_message << REPLACEMENT_CHAR + end + end + new_message + end + end + end end end diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index c42bb8618..999061938 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -1,86 +1,71 @@ # encoding: UTF-8 -require 'spec_helper' - -# The matrix of error encoding tests: -# ('Enc = X' means 'Encoding.default_internal = X') -# MySQL < 5.5 MySQL >= 5.5 -# Ruby 1.8 N/A N/A -# Ruby 1.9+ -# Enc = nil -# :enc = nil BINARY UTF-8 -# -# Enc = XYZ -# :enc = XYZ BINARY XYZ -# -# Enc = FOO -# :enc = BAR BINARY FOO -# +require 'spec_helper' describe Mysql2::Error do - shared_examples "mysql2 error" do + let(:client) { Mysql2::Client.new(DatabaseCredentials['root']) } + + let :error do begin - err_client = Mysql2::Client.new(DatabaseCredentials['root']) - err_client.query("HAHAHA") + client.query("HAHAHA") rescue Mysql2::Error => e error = e ensure - err_client.close + client.close end - subject { error } - it { should respond_to(:error_number) } - it { should respond_to(:sql_state) } + error + end + + it "responds to error_number and sql_state, with aliases" do + error.should respond_to(:error_number) + error.should respond_to(:sql_state) # Mysql gem compatibility - it { should respond_to(:errno) } - it { should respond_to(:error) } + error.should respond_to(:errno) + error.should respond_to(:error) end - shared_examples "mysql2 error encoding" do |db_enc, def_enc, err_enc| - Encoding.default_internal = def_enc + if "".respond_to? :encoding + let :error do + client = Mysql2::Client.new(DatabaseCredentials['root']) + begin + client.query("造字") + rescue Mysql2::Error => e + error = e + ensure + client.close + end - begin - err_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => db_enc)) - err_client.query("造字") - rescue Mysql2::Error => e - error = e - ensure - err_client.close + error end - subject { error.message.encoding } - it "#message should transcode from #{db_enc.inspect} to #{err_enc}" do should eql(err_enc) end + let :bad_err do + client = Mysql2::Client.new(DatabaseCredentials['root']) + begin + client.query("\xE5\xC6\x7D\x1F") + rescue Mysql2::Error => e + error = e + ensure + client.close + end - subject { error.error.encoding } - it "#error should transcode from #{db_enc.inspect} to #{err_enc}" do should eql(err_enc) end - - subject { error.sql_state.encoding } - it "#sql_state should transcode from #{db_enc.inspect} to #{err_enc}" do should eql(err_enc) end - end + error + end - shared_examples "mysql2 error encoding (MySQL < 5.5)" do |db_enc, def_enc, err_enc| - include_examples "mysql2 error encoding", db_enc, def_enc, err_enc - end + it "returns error messages as UTF-8" do + error.message.encoding.should eql(Encoding::UTF_8) + error.message.valid_encoding? - shared_examples "mysql2 error encoding (MySQL >= 5.5)" do |db_enc, def_enc, err_enc| - include_examples "mysql2 error encoding", db_enc, def_enc, err_enc - end + bad_err.message.encoding.should eql(Encoding::UTF_8) + bad_err.message.valid_encoding? - it_behaves_like "mysql2 error" + bad_err.message.should include("??}\u001F") + end - unless RUBY_VERSION =~ /1.8/ - mysql_ver = Mysql2::Client.new(DatabaseCredentials['root']).server_info[:id] - if mysql_ver < 50505 - it_behaves_like "mysql2 error encoding (MySQL < 5.5)", nil, nil, Encoding::ASCII_8BIT - it_behaves_like "mysql2 error encoding (MySQL < 5.5)", 'utf8', Encoding::UTF_8, Encoding::ASCII_8BIT - it_behaves_like "mysql2 error encoding (MySQL < 5.5)", 'big5', Encoding::Big5, Encoding::ASCII_8BIT - it_behaves_like "mysql2 error encoding (MySQL < 5.5)", 'big5', Encoding::US_ASCII, Encoding::ASCII_8BIT - else - it_behaves_like "mysql2 error encoding (MySQL >= 5.5)", nil, nil, Encoding::UTF_8 - it_behaves_like "mysql2 error encoding (MySQL >= 5.5)", 'utf8', Encoding::UTF_8, Encoding::UTF_8 - it_behaves_like "mysql2 error encoding (MySQL >= 5.5)", 'big5', Encoding::Big5, Encoding::Big5 - it_behaves_like "mysql2 error encoding (MySQL >= 5.5)", 'big5', Encoding::US_ASCII, Encoding::US_ASCII + it "returns sql state as ASCII" do + error.sql_state.encoding.should eql(Encoding::US_ASCII) + error.sql_state.valid_encoding? end end end From 000963cd18bf87d6b6e50d5417459c38ddfc2b5c Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 22 Jan 2014 17:25:58 -0800 Subject: [PATCH 107/783] make sure we return error message and sql state in Encoding.default_internal if set --- lib/mysql2/error.rb | 12 +++++++++--- spec/mysql2/error_spec.rb | 13 +++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index f8af572f4..a1193a04a 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -17,6 +17,12 @@ def initialize(msg, server_version=nil) super(clean_message(msg)) end + if "".respond_to? :encode + def sql_state=(state) + @sql_state = state.encode + end + end + private # In MySQL 5.5+ error messages are always constructed server-side as UTF-8 @@ -50,10 +56,10 @@ def clean_message(message) return message if !message.respond_to?(:encoding) if @server_version && @server_version > 50500 - message + message.encode else if message.respond_to? :scrub - message.scrub + message.scrub.encode else # This is ugly as hell but Ruby 1.9 doesn't provide a way to clean a string # and retain it's valid UTF-8 characters, that I know of. @@ -66,7 +72,7 @@ def clean_message(message) new_message << REPLACEMENT_CHAR end end - new_message + new_message.encode end end end diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 999061938..14070fd05 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -67,5 +67,18 @@ error.sql_state.encoding.should eql(Encoding::US_ASCII) error.sql_state.valid_encoding? end + + it "returns error messages and sql state in Encoding.default_internal if set" do + interal_before = Encoding.default_internal + Encoding.default_internal = 'UTF-16' + + error.message.encoding.should eql(Encoding.default_internal) + error.message.valid_encoding? + + bad_err.message.encoding.should eql(Encoding.default_internal) + bad_err.message.valid_encoding? + + Encoding.default_internal = interal_before + end end end From e32011589c46cc3a3dfab83b584414e0f844772f Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 22 Jan 2014 17:32:59 -0800 Subject: [PATCH 108/783] try and work around rspec bug maybe? --- spec/mysql2/error_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 14070fd05..277377e4d 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -30,7 +30,7 @@ let :error do client = Mysql2::Client.new(DatabaseCredentials['root']) begin - client.query("造字") + client.query("\xE9\x80\xA0\xE5\xAD\x97") rescue Mysql2::Error => e error = e ensure From 8900258873ca23a8ba0c67502c75d83935d03bab Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 22 Jan 2014 23:20:39 -0800 Subject: [PATCH 109/783] make sure all test clients use DatabaseCredentials --- spec/mysql2/client_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 0e22b1dcd..8969b789c 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -7,13 +7,14 @@ it "should not raise an exception for valid defaults group" do lambda { - @client = Mysql2::Client.new(:default_file => cnf_file, :default_group => "test") + opts = DatabaseCredentials['root'].merge(:default_file => cnf_file, :default_group => "test") + @client = Mysql2::Client.new(opts) }.should_not raise_error(Mysql2::Error) end it "should not raise an exception without default group" do lambda { - @client = Mysql2::Client.new(:default_file => cnf_file) + @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:default_file => cnf_file)) }.should_not raise_error(Mysql2::Error) end end From 235b55d2f6d733124ffa27d418e3be57240ebc75 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 22 Jan 2014 23:20:52 -0800 Subject: [PATCH 110/783] reset Encoding.default_internal before depending on it's behavior --- spec/mysql2/error_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 277377e4d..b443d1b70 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -53,7 +53,9 @@ error end - it "returns error messages as UTF-8" do + it "returns error messages as UTF-8 by default" do + Encoding.default_internal = nil + error.message.encoding.should eql(Encoding::UTF_8) error.message.valid_encoding? From da05ad2eca27735a03f8c46d9450c28d0f463113 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 22 Jan 2014 23:29:19 -0800 Subject: [PATCH 111/783] use a more specific UTF-16 variant --- spec/mysql2/error_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index b443d1b70..3b44b21c8 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -72,7 +72,7 @@ it "returns error messages and sql state in Encoding.default_internal if set" do interal_before = Encoding.default_internal - Encoding.default_internal = 'UTF-16' + Encoding.default_internal = 'UTF-16LE' error.message.encoding.should eql(Encoding.default_internal) error.message.valid_encoding? From d88dea2d80e727a5804782b456155d45e6d73982 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 22 Jan 2014 23:41:50 -0800 Subject: [PATCH 112/783] replace invalid chars on encode --- lib/mysql2/error.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index a1193a04a..21045a444 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -3,6 +3,7 @@ module Mysql2 class Error < StandardError REPLACEMENT_CHAR = '?' + ENCODE_OPTS = {:undef => :replace, :invalid => :replace} attr_accessor :error_number, :sql_state attr_writer :server_version @@ -19,7 +20,7 @@ def initialize(msg, server_version=nil) if "".respond_to? :encode def sql_state=(state) - @sql_state = state.encode + @sql_state = state.encode(ENCODE_OPTS) end end @@ -56,10 +57,10 @@ def clean_message(message) return message if !message.respond_to?(:encoding) if @server_version && @server_version > 50500 - message.encode + message.encode(ENCODE_OPTS) else if message.respond_to? :scrub - message.scrub.encode + message.scrub.encode(ENCODE_OPTS) else # This is ugly as hell but Ruby 1.9 doesn't provide a way to clean a string # and retain it's valid UTF-8 characters, that I know of. @@ -72,7 +73,7 @@ def clean_message(message) new_message << REPLACEMENT_CHAR end end - new_message.encode + new_message.encode(ENCODE_OPTS) end end end From fb0c9735e702fc7d7cf9446aac83b38e68aa9d4d Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 23 Jan 2014 00:32:04 -0800 Subject: [PATCH 113/783] clean up reassignments of Encoding.default_internal to ensure it's set back to it's original value --- spec/mysql2/client_spec.rb | 40 ++++++----- spec/mysql2/error_spec.rb | 29 ++++---- spec/mysql2/result_spec.rb | 140 +++++++++++++++++++++---------------- spec/spec_helper.rb | 9 +++ 4 files changed, 126 insertions(+), 92 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 8969b789c..3296b4358 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -656,18 +656,22 @@ def connect *args if defined? Encoding context "strings returned by #info" do it "should default to the connection's encoding if Encoding.default_internal is nil" do - Encoding.default_internal = nil - @client.info[:version].encoding.should eql(Encoding.find('utf-8')) + with_internal_encoding nil do + @client.info[:version].encoding.should eql(Encoding.find('utf-8')) - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - client2.info[:version].encoding.should eql(Encoding.find('us-ascii')) + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + client2.info[:version].encoding.should eql(Encoding.find('us-ascii')) + end end it "should use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - @client.info[:version].encoding.should eql(Encoding.default_internal) - Encoding.default_internal = Encoding.find('us-ascii') - @client.info[:version].encoding.should eql(Encoding.default_internal) + with_internal_encoding 'utf-8' do + @client.info[:version].encoding.should eql(Encoding.default_internal) + end + + with_internal_encoding 'us-ascii' do + @client.info[:version].encoding.should eql(Encoding.default_internal) + end end end end @@ -695,18 +699,22 @@ def connect *args if defined? Encoding context "strings returned by #server_info" do it "should default to the connection's encoding if Encoding.default_internal is nil" do - Encoding.default_internal = nil - @client.server_info[:version].encoding.should eql(Encoding.find('utf-8')) + with_internal_encoding nil do + @client.server_info[:version].encoding.should eql(Encoding.find('utf-8')) - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii')) + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii')) + end end it "should use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - @client.server_info[:version].encoding.should eql(Encoding.default_internal) - Encoding.default_internal = Encoding.find('us-ascii') - @client.server_info[:version].encoding.should eql(Encoding.default_internal) + with_internal_encoding 'utf-8' do + @client.server_info[:version].encoding.should eql(Encoding.default_internal) + end + + with_internal_encoding 'us-ascii' do + @client.server_info[:version].encoding.should eql(Encoding.default_internal) + end end end end diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 3b44b21c8..8b5cb2bce 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -54,15 +54,15 @@ end it "returns error messages as UTF-8 by default" do - Encoding.default_internal = nil + with_internal_encoding nil do + error.message.encoding.should eql(Encoding::UTF_8) + error.message.valid_encoding? - error.message.encoding.should eql(Encoding::UTF_8) - error.message.valid_encoding? + bad_err.message.encoding.should eql(Encoding::UTF_8) + bad_err.message.valid_encoding? - bad_err.message.encoding.should eql(Encoding::UTF_8) - bad_err.message.valid_encoding? - - bad_err.message.should include("??}\u001F") + bad_err.message.should include("??}\u001F") + end end it "returns sql state as ASCII" do @@ -71,16 +71,13 @@ end it "returns error messages and sql state in Encoding.default_internal if set" do - interal_before = Encoding.default_internal - Encoding.default_internal = 'UTF-16LE' - - error.message.encoding.should eql(Encoding.default_internal) - error.message.valid_encoding? + with_internal_encoding 'UTF-16LE' do + error.message.encoding.should eql(Encoding.default_internal) + error.message.valid_encoding? - bad_err.message.encoding.should eql(Encoding.default_internal) - bad_err.message.valid_encoding? - - Encoding.default_internal = interal_before + bad_err.message.encoding.should eql(Encoding.default_internal) + bad_err.message.valid_encoding? + end end end end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 485b370a7..56c65aedf 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -338,23 +338,27 @@ if defined? Encoding context "string encoding for ENUM values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.find('us-ascii')) - client2.close + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['enum_test'].encoding.should eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['enum_test'].encoding.should eql(Encoding.find('us-ascii')) + client2.close + end end it "should use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.default_internal) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.default_internal) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['enum_test'].encoding.should eql(Encoding.default_internal) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['enum_test'].encoding.should eql(Encoding.default_internal) + end end end end @@ -367,23 +371,27 @@ if defined? Encoding context "string encoding for SET values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.find('us-ascii')) - client2.close + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['set_test'].encoding.should eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['set_test'].encoding.should eql(Encoding.find('us-ascii')) + client2.close + end end it "should use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.default_internal) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.default_internal) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['set_test'].encoding.should eql(Encoding.default_internal) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['set_test'].encoding.should eql(Encoding.default_internal) + end end end end @@ -396,18 +404,22 @@ if defined? Encoding context "string encoding for BINARY values" do it "should default to binary if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end end it "should not use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end end end end @@ -434,38 +446,46 @@ context "string encoding for #{type} values" do if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) it "should default to binary if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end end it "should not use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end end else it "should default to utf-8 if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.find('us-ascii')) - client2.close + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result[field].encoding.should eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result[field].encoding.should eql(Encoding.find('us-ascii')) + client2.close + end end it "should use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.default_internal) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.default_internal) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result[field].encoding.should eql(Encoding.default_internal) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result[field].encoding.should eql(Encoding.default_internal) + end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index eb26edbc9..4bcb702c0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,15 @@ DatabaseCredentials = YAML.load_file('spec/configuration.yml') RSpec.configure do |config| + def with_internal_encoding(encoding) + old_enc = Encoding.default_internal + Encoding.default_internal = encoding + + yield + ensure + Encoding.default_internal = old_enc + end + config.before :each do @client = Mysql2::Client.new DatabaseCredentials['root'] end From 26f66c3c09c1e0255475df3fff49a718d9686418 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 23 Jan 2014 00:54:16 -0800 Subject: [PATCH 114/783] allow rbx failures for now See https://github.com/rubinius/rubinius/pull/2892 for more info --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0ee13501b..f1d783cab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,3 +15,6 @@ script: before_install: - gem update --system 2.1.11 - gem --version +matrix: + allow_failures: + - rvm: rbx From cd089e9c51f312145dde65e2dc85a6419edba5fa Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 23 Jan 2014 14:39:04 -0800 Subject: [PATCH 115/783] use '?' for replacement character even on 2.1 and mysql 5.1 --- lib/mysql2/error.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 21045a444..57131d75c 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -3,7 +3,7 @@ module Mysql2 class Error < StandardError REPLACEMENT_CHAR = '?' - ENCODE_OPTS = {:undef => :replace, :invalid => :replace} + ENCODE_OPTS = {:undef => :replace, :invalid => :replace, :replace => REPLACEMENT_CHAR} attr_accessor :error_number, :sql_state attr_writer :server_version @@ -60,7 +60,7 @@ def clean_message(message) message.encode(ENCODE_OPTS) else if message.respond_to? :scrub - message.scrub.encode(ENCODE_OPTS) + message.scrub(REPLACEMENT_CHAR).encode(ENCODE_OPTS) else # This is ugly as hell but Ruby 1.9 doesn't provide a way to clean a string # and retain it's valid UTF-8 characters, that I know of. From c2a3bf77902042d22cd4485ae0209a8f6cf5c5ac Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 23 Jan 2014 15:50:54 -0800 Subject: [PATCH 116/783] bump version for 0.3.15 release --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 431885945..6ec8bb6ff 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.3.14" + VERSION = "0.3.15" end From f1eb12bed96bb8934c2ea3a93103914fa0d64abf Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Sat, 25 Jan 2014 01:03:14 -0800 Subject: [PATCH 117/783] remove unused intern_server_version --- ext/mysql2/client.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 5879f53b2..23855cb20 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -12,7 +12,7 @@ VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error; static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; -static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql, intern_server_version; +static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql; #ifndef HAVE_RB_HASH_DUP static VALUE rb_hash_dup(VALUE other) { @@ -1208,7 +1208,6 @@ void init_mysql2_client() { intern_merge_bang = rb_intern("merge!"); intern_error_number_eql = rb_intern("error_number="); intern_sql_state_eql = rb_intern("sql_state="); - intern_server_version = rb_intern("server_version="); #ifdef CLIENT_LONG_PASSWORD rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), From f19ff013d9919f77011dc43d163a8d6435fef9a1 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Sat, 25 Jan 2014 01:03:58 -0800 Subject: [PATCH 118/783] small update to readme --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 76874b9f2..fb1d3b1da 100644 --- a/README.md +++ b/README.md @@ -119,8 +119,7 @@ end How about with symbolized keys? ``` ruby -# NOTE: the :symbolize_keys and future options will likely move to the #query method soon -client.query("SELECT * FROM users WHERE group='githubbers'").each(:symbolize_keys => true) do |row| +client.query("SELECT * FROM users WHERE group='githubbers'", :symbolize_keys => true) do |row| # do something with row, it's ready to rock end ``` From 90325109127ed5deae12cf61569a112c8803d733 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 14 Feb 2014 13:50:43 -0800 Subject: [PATCH 119/783] Check that dir_config returns at least one valid directory separated by PATH_SEPARATOR --- ext/mysql2/extconf.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 71a7a5799..9895f5999 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -41,8 +41,8 @@ def asplode lib @libdir_basename = 'lib' inc, lib = dir_config('mysql') end - abort "-----\nCannot find include dir at #{inc}\n-----" unless inc && File.directory?(inc) - abort "-----\nCannot find library dir at #{lib}\n-----" unless lib && File.directory?(lib) + abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any?{|dir| File.directory?(dir)} + abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any?{|dir| File.directory?(dir)} warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" rpath_dir = lib elsif mc = (with_config('mysql-config') || Dir[GLOB].first) From 62c8c68761f08e3032a3564becbe835a64c749f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Sun, 23 Feb 2014 08:30:59 +0100 Subject: [PATCH 120/783] README.md update [ci skip] --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fb1d3b1da..0f58f52af 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ It also forces the use of UTF-8 [or binary] for the connection [and all strings The API consists of two classes: -Mysql2::Client - your connection to the database +`Mysql2::Client` - your connection to the database -Mysql2::Result - returned from issuing a #query on the connection. It includes Enumerable. +`Mysql2::Result` - returned from issuing a #query on the connection. It includes Enumerable. ## Installing ### OSX / Linux @@ -439,7 +439,7 @@ For example, if you were to yield 4 rows from a 100 row dataset, only 4 hashes w Now say you were to iterate over that same collection again, this time yielding 15 rows - the 4 previous rows that had already been turned into ruby hashes would be pulled from an internal cache, then 11 more would be created and stored in that cache. Once the entire dataset has been converted into ruby objects, Mysql2::Result will free the Mysql C result object as it's no longer needed. -This caching behavior can be disabled by setting the :cache_rows option to false. +This caching behavior can be disabled by setting the `:cache_rows` option to false. As for field values themselves, I'm workin on it - but expect that soon. From a2bcfd6af2f30ee21200b2d5dfa4893e401c3df4 Mon Sep 17 00:00:00 2001 From: Evan Miller Date: Mon, 10 Feb 2014 10:41:12 -0800 Subject: [PATCH 121/783] Avoid shutdown() on shared sockets (fixes #213) Close the mysql socket before calling mysql_close(). mysql_close() calls shutdown() on the attached socket. In a situation involving fork() and copies of an object spanning multiple interpreter instances this will result in the first instance to GC breaking all other instances sharing the socket. dylanahsmith notes that we can avoid this by simply closing the socket before entering mysql_close(), preventing the shutdown() from having any impact on the shared socket state. --- ext/mysql2/client.c | 23 ++++++++--------------- spec/mysql2/client_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 23855cb20..c88899872 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -164,26 +164,19 @@ static void *nogvl_connect(void *ptr) { static void *nogvl_close(void *ptr) { mysql_client_wrapper *wrapper; -#ifndef _WIN32 - int flags; -#endif wrapper = ptr; if (wrapper->connected) { wrapper->active_thread = Qnil; wrapper->connected = 0; - /* - * we'll send a QUIT message to the server, but that message is more of a - * formality than a hard requirement since the socket is getting shutdown - * anyways, so ensure the socket write does not block our interpreter - * - * - * if the socket is dead we have no chance of blocking, - * so ignore any potential fcntl errors since they don't matter - */ #ifndef _WIN32 - flags = fcntl(wrapper->client->net.fd, F_GETFL); - if (flags > 0 && !(flags & O_NONBLOCK)) - fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK); + /* Call close() on the socket before calling mysql_close(). This prevents + * mysql_close() from sending a mysql-QUIT or from calling shutdown() on + * the socket. The difference is that close() will drop this process's + * reference to the socket only, while a QUIT or shutdown() would render + * the underlying connection unusable, interrupting other processes which + * share this object across a fork(). + */ + close(wrapper->client->net.fd); #endif mysql_close(wrapper->client); diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 3296b4358..4e9227290 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -126,6 +126,27 @@ def connect *args final_count.should == before_count end + if Process.respond_to?(:fork) + it "should not close connections when running in a child process" do + GC.start + sleep 1 if defined? Rubinius # Let the rbx GC thread do its work + client = Mysql2::Client.new(DatabaseCredentials['root']) + + fork do + client.query('SELECT 1') + client = nil + GC.start + sleep 1 if defined? Rubinius # Let the rbx GC thread do its work + end + + Process.wait + + # this will throw an error if the underlying socket was shutdown by the + # child's GC + expect { client.query('SELECT 1') }.to_not raise_exception + end + end + it "should be able to connect to database with numeric-only name" do lambda { creds = DatabaseCredentials['numericuser'] From 73c355a975b97d4d67935b17577b4515be358618 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 27 Feb 2014 12:42:29 -0800 Subject: [PATCH 122/783] Move fcntl.h from mysql2_ext.h to infile.c --- ext/mysql2/infile.c | 1 + ext/mysql2/mysql2_ext.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/infile.c b/ext/mysql2/infile.c index c6b6a556d..b77400b1d 100644 --- a/ext/mysql2/infile.c +++ b/ext/mysql2/infile.c @@ -2,6 +2,7 @@ #include #include +#include #define ERROR_LEN 1024 typedef struct diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index bb06079b2..d6d5fd626 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -6,7 +6,6 @@ we'll never modify the pointers we get back from RSTRING_PTR */ #define RSTRING_NOT_MODIFIED #include -#include #ifndef HAVE_UINT #define HAVE_UINT From fe628cc50657dec4557f2e338198a60714a9f910 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 1 Mar 2014 08:06:00 -0800 Subject: [PATCH 123/783] Travis tests on Ruby 2.1.1 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f1d783cab..f83840698 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ rvm: - 1.9.2 - 1.9.3 - 2.0.0 - - 2.1.0 + - 2.1.1 - ree - rbx env: RBXOPT=-Xgc.honor_start=true From 4e6769f72d929b6d714fcebe29b9682b8a962b60 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 22 Mar 2014 02:41:51 +0000 Subject: [PATCH 124/783] avoid redundant close from calling mysql_close Calling close redundantly on an FD means a race condition will break multithreaded applications (or worse, lead to inadvertant information disclosure). Consider the following multithreaded timeline with two threads: thread 1 (mysql2 close) | thread 2 (blocking accept loop) ---------------------------------+---------------------------------- close(9) -> fd=9 released | | accept() -> fd=9 acquired mysql_close -> close(9) | # releases fd=9 which thread 2 | # just took using accept() | | read(9) -> EBADF! We must prevent mysql_close from possibly hitting a recycled FD when it calls close again. So we redirect the existing FD to a dummy socket (atomically closing the original FD/socket in the process). This means mysql_close will call close on a valid FD, but that FD now points to a dummy socket and not a valid FD. I considered using socketpair (to ensure write/shutdown inside mysql_close succeeds), but decided against it as there may be a chance the client library will one day wait for a disconnect message from the server. Thanks to Evan Miller for this idea. --- ext/mysql2/client.c | 49 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index c88899872..0df44672d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -2,6 +2,7 @@ #include #ifndef _WIN32 +#include #include #endif #include @@ -162,6 +163,36 @@ static void *nogvl_connect(void *ptr) { return (void *)(client ? Qtrue : Qfalse); } +#ifndef _WIN32 +/* + * Redirect clientfd to a dummy socket for mysql_close to + * write, shutdown, and close on as a no-op. + * We do this hack because we want to call mysql_close to release + * memory, but do not want mysql_close to drop connections in the + * parent if the socket got shared in fork. + * Returns Qtrue or false (success or failure) + */ +static VALUE invalidate_fd(int clientfd) +{ + /* TODO: set cloexec flags, atomically if possible */ + int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (sockfd < 0) { + /* + * Cannot raise here, because one or both of the following may be true: + * a) we have no GVL (in C Ruby) + * b) are running as a GC finalizer + */ + return Qfalse; + } + + dup2(sockfd, clientfd); + close(sockfd); + + return Qtrue; +} +#endif /* _WIN32 */ + static void *nogvl_close(void *ptr) { mysql_client_wrapper *wrapper; wrapper = ptr; @@ -169,17 +200,21 @@ static void *nogvl_close(void *ptr) { wrapper->active_thread = Qnil; wrapper->connected = 0; #ifndef _WIN32 - /* Call close() on the socket before calling mysql_close(). This prevents + /* Invalidate the socket before calling mysql_close(). This prevents * mysql_close() from sending a mysql-QUIT or from calling shutdown() on - * the socket. The difference is that close() will drop this process's - * reference to the socket only, while a QUIT or shutdown() would render - * the underlying connection unusable, interrupting other processes which - * share this object across a fork(). + * the socket. The difference is that invalidate_fd will drop this + * process's reference to the socket only, while a QUIT or shutdown() + * would render the underlying connection unusable, interrupting other + * processes which share this object across a fork(). */ - close(wrapper->client->net.fd); + if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { + fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, leaking some memory\n"); + close(wrapper->client->net.fd); + return NULL; + } #endif - mysql_close(wrapper->client); + mysql_close(wrapper->client); /* only used to free memory at this point */ } return NULL; From 90d66269af1e8f63c677b1303f4c46bd0d3690a3 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 22 Mar 2014 22:54:39 -0700 Subject: [PATCH 125/783] Call invalidate_fd in disconnect_and_raise --- ext/mysql2/client.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 0df44672d..8adac123c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -477,10 +477,13 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) { wrapper->active_thread = Qnil; wrapper->connected = 0; - /* manually close the socket for read/write - this feels dirty, but is there another way? */ - close(wrapper->client->net.fd); - wrapper->client->net.fd = -1; + /* Invalidate the MySQL socket to prevent further communication. + * The GC will come along later and call mysql_close to free it. + */ + if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { + fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n"); + close(wrapper->client->net.fd); + } rb_exc_raise(error); From 6206d2f8d3f525e8f89cbaeb5a6ae02a538b8a3d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 24 Mar 2014 17:49:07 -0700 Subject: [PATCH 126/783] Atomically set CLOEXEC on the dummy socket in invalidate_fd if possible --- ext/mysql2/client.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 8adac123c..c1ed8434c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -6,6 +6,7 @@ #include #endif #include +#include #include "wait_for_single_fd.h" #include "mysql_enc_name_to_ruby.h" @@ -170,12 +171,23 @@ static void *nogvl_connect(void *ptr) { * We do this hack because we want to call mysql_close to release * memory, but do not want mysql_close to drop connections in the * parent if the socket got shared in fork. - * Returns Qtrue or false (success or failure) + * Returns Qtrue or Qfalse (success or failure) */ static VALUE invalidate_fd(int clientfd) { - /* TODO: set cloexec flags, atomically if possible */ +#ifdef SOCK_CLOEXEC + /* Atomically set CLOEXEC on the new FD in case another thread forks */ + int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sockfd < 0) { + /* Maybe SOCK_CLOEXEC is defined but not available on this kernel */ + int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + fcntl(sockfd, F_SETFD, FD_CLOEXEC); + } +#else + /* Well we don't have SOCK_CLOEXEC, so just set FD_CLOEXEC quickly */ int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + fcntl(sockfd, F_SETFD, FD_CLOEXEC); +#endif if (sockfd < 0) { /* From 607f2f10fdcdfbcb37002cb421d8ce10b30f4fa4 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 25 Mar 2014 13:44:34 -0700 Subject: [PATCH 127/783] Update the README Compatibility section for MariaDB etc. --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0f58f52af..6dfbd5620 100644 --- a/README.md +++ b/README.md @@ -445,15 +445,22 @@ As for field values themselves, I'm workin on it - but expect that soon. ## Compatibility -This gem is regularly tested against the following Ruby versions on Linux and Mac OS X: +This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0 (ongoing patch releases). - * Ruby Enterprise Edition (based on MRI 1.8.7). - * Rubinius 2.0 in compatibility modes 1.8, 1.9, 2.0. + * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.x (ongoing patch releases) + * Ruby Enterprise Edition (based on MRI 1.8.7) + * Rubinius 2.x -The mysql2 gem 0.2.x series includes an Active Record driver that works with AR -2.3.x and 3.0.x. Starting in Active Record 3.1, a mysql2 driver is included in -the Active Record codebase and no longer provided in mysql2 gem 0.3 and above. +This gem is tested with the following MySQL and MariaDB versions: + + * MySQL 5.0, 5.1, 5.5, 5.6 + * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) + * MariaDB 5.5, 10.0 + +This gem has two version families: + + * mysql2 0.2.x includes an Active Record driver compatible with AR 2.3 and 3.0 + * mysql2 0.3.x does not include an AR driver because it is included in AR 3.1 and above ## Yeah... but why? From ef4d758c9eabe947e9e9f87edc516c4ae00266fc Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 25 Mar 2014 13:56:42 -0700 Subject: [PATCH 128/783] Add Ruby 2.0 + MariaDB 5.5 / 10.0 to the Travis matrix --- .travis.yml | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index f83840698..2e540b673 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,4 @@ language: ruby -rvm: - - 1.8.7 - - 1.9.2 - - 1.9.3 - - 2.0.0 - - 2.1.1 - - ree - - rbx -env: RBXOPT=-Xgc.honor_start=true bundler_args: --without benchmarks script: - bundle exec rake spec @@ -15,6 +6,37 @@ script: before_install: - gem update --system 2.1.11 - gem --version + - | + bash -c " # Install MariaDB if DB=mariadb + if [[ x$DB =~ xmariadb ]]; then + sudo service mysql stop + sudo apt-get purge '^mysql*' 'libmysql*' + sudo apt-get install python-software-properties + sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db + if [[ x$DB = xmariadb55 ]]; then + sudo add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/5.5/ubuntu precise main' + elif [[ x$DB = xmariadb10 ]]; then + sudo add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/10.0/ubuntu precise main' + fi + sudo apt-get update + sudo apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -y install mariadb-server libmariadbd-dev + fi + " + - mysqld --version +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 + - 2.0.0 + - 2.1.1 + - ree matrix: allow_failures: - rvm: rbx + include: + - rvm: 2.0.0 + env: DB=mariadb55 + - rvm: 2.0.0 + env: DB=mariadb10 + - rvm: rbx + env: RBXOPT=-Xgc.honor_start=true From d759e8bf3a48b3995891c07a5b9a498b7a1bf334 Mon Sep 17 00:00:00 2001 From: Marcin Bunsch Date: Wed, 2 Apr 2014 18:09:10 +0200 Subject: [PATCH 129/783] Allow setting the init command on the connection. When reconnect is enabled, it is handled within libmysqlclient. In such case, if a connection sets any session variables after connecting, they will be lost after the reconnection. --- ext/mysql2/client.c | 10 ++++++-- lib/mysql2/client.rb | 4 +++- spec/mysql2/client_spec.rb | 48 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index c1ed8434c..9606ea541 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -67,6 +67,7 @@ struct nogvl_connect_args { const char *user; const char *passwd; const char *db; + const char *init_command; unsigned int port; const char *unix_socket; unsigned long client_flag; @@ -156,6 +157,10 @@ static void *nogvl_connect(void *ptr) { struct nogvl_connect_args *args = ptr; MYSQL *client; + if (args->init_command != NULL) { + mysql_options(args->mysql, MYSQL_INIT_COMMAND, args->init_command); + } + client = mysql_real_connect(args->mysql, args->host, args->user, args->passwd, args->db, args->port, args->unix_socket, @@ -322,7 +327,7 @@ static VALUE rb_mysql_info(VALUE self) { return rb_str; } -static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) { +static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags, VALUE init_command) { struct nogvl_connect_args args; VALUE rv; GET_CLIENT(self); @@ -335,6 +340,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po args.db = NIL_P(database) ? NULL : StringValuePtr(database); args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); + args.init_command = NIL_P(init_command) ? NULL : StringValuePtr(init_command); rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); if (rv == Qfalse) { @@ -1237,7 +1243,7 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); - rb_define_private_method(cMysql2Client, "connect", rb_connect, 7); + rb_define_private_method(cMysql2Client, "connect", rb_connect, 8); sym_id = ID2SYM(rb_intern("id")); sym_version = ID2SYM(rb_intern("version")); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 6290abc7d..c6be35a03 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -55,6 +55,7 @@ def initialize(opts = {}) database = opts[:database] || opts[:dbname] || opts[:db] socket = opts[:socket] || opts[:sock] flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags] + init_command = opts[:init_command] # Correct the data types before passing these values down to the C level user = user.to_s unless user.nil? @@ -63,8 +64,9 @@ def initialize(opts = {}) port = port.to_i unless port.nil? database = database.to_s unless database.nil? socket = socket.to_s unless socket.nil? + init_command = init_command.to_s unless init_command.nil? - connect user, pass, host, port, database, socket, flags + connect user, pass, host, port, database, socket, flags, init_command end def self.default_query_options diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 4e9227290..54887c2f6 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -54,7 +54,7 @@ def connect *args end end client = klient.new :flags => Mysql2::Client::FOUND_ROWS - (client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true + (client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).should be_true end it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do @@ -66,7 +66,7 @@ def connect *args end end client = klient.new - (client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS | + (client.connect_args.last[6] & (Mysql2::Client::REMEMBER_OPTIONS | Mysql2::Client::LONG_PASSWORD | Mysql2::Client::LONG_FLAG | Mysql2::Client::TRANSACTIONS | @@ -74,6 +74,50 @@ def connect *args Mysql2::Client::SECURE_CONNECTION)).should be_true end + it "should accept init_command" do + klient = Class.new(Mysql2::Client) do + attr_reader :connect_args + def connect *args + @connect_args ||= [] + @connect_args << args + end + end + command = "SET @@session.something = 1" + client = klient.new :init_command => command + client.connect_args.last[7].should eq(command) + end + + it "should execute init command" do + options = DatabaseCredentials['root'].dup + options[:init_command] = "SET @something = 'setting_value';" + client = Mysql2::Client.new(options) + result = client.query("SELECT @something;") + result.first['@something'].should eq('setting_value') + end + + it "should send init_command after reconnect" do + pending "Ruby 2.1 has changed Timeout behavior." if RUBY_VERSION =~ /2.1/ + options = DatabaseCredentials['root'].dup + options[:init_command] = "SET @something = 'setting_value';" + options[:reconnect] = true + client = Mysql2::Client.new(options) + + result = client.query("SELECT @something;") + result.first['@something'].should eq('setting_value') + + # simulate a broken connection + begin + Timeout.timeout(1) do + client.query("SELECT sleep(2)") + end + rescue Timeout::Error + end + client.ping.should be_false + + result = client.query("SELECT @something;") + result.first['@something'].should eq('setting_value') + end + it "should have a global default_query_options hash" do Mysql2::Client.should respond_to(:default_query_options) end From 23718caecd6e0dd99fff648be4733e6d39396b4e Mon Sep 17 00:00:00 2001 From: Marcin Bunsch Date: Fri, 4 Apr 2014 12:00:13 +0200 Subject: [PATCH 130/783] Update README with information about the init_command option --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6dfbd5620..bc2ee1730 100644 --- a/README.md +++ b/README.md @@ -156,10 +156,23 @@ Mysql2::Client.new( :local_infile = true/false, :secure_auth = true/false, :default_file = '/path/to/my.cfg', - :default_group = 'my.cfg section' + :default_group = 'my.cfg section', + :init_command => sql ) ``` +### init_command + +If you specify the init_command option, the SQL string you provide will be executed after the connection is established. + +If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect. + +It is useful if you want to provide session options which survive reconnection. + +``` ruby +Mysql2::Client.new(:init_command => "SET @@SESSION.sql_mode = 'STRICT_ALL_TABLES'") +``` + ### SSL options Setting any of the following options will enable an SSL connection, but only if From 95bf8a2b4fe865cc74ae64d9dbb72ff5e42ce1b8 Mon Sep 17 00:00:00 2001 From: Marcin Bunsch Date: Fri, 4 Apr 2014 13:19:58 +0200 Subject: [PATCH 131/783] Moving the init_command section in README --- README.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bc2ee1730..9b125f58a 100644 --- a/README.md +++ b/README.md @@ -161,17 +161,6 @@ Mysql2::Client.new( ) ``` -### init_command - -If you specify the init_command option, the SQL string you provide will be executed after the connection is established. - -If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect. - -It is useful if you want to provide session options which survive reconnection. - -``` ruby -Mysql2::Client.new(:init_command => "SET @@SESSION.sql_mode = 'STRICT_ALL_TABLES'") -``` ### SSL options @@ -264,6 +253,15 @@ the `:default_file` and `:default_group` paramters. For example: Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') ``` +### init_command + +If you specify the init_command option, the SQL string you provide will be executed after the connection is established. +If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect. +It is useful if you want to provide session options which survive reconnection. + +``` ruby +Mysql2::Client.new(:init_command => "SET @@SESSION.sql_mode = 'STRICT_ALL_TABLES'") +``` ## Cascading config From 3c2af97aad72cc54bc0e06ff95a3c428428e111b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 7 Apr 2014 19:13:35 -0700 Subject: [PATCH 132/783] Add a setter method for init_command rather than a connect arg --- ext/mysql2/client.c | 20 ++++++++++++-------- lib/mysql2/client.rb | 6 ++---- spec/mysql2/client_spec.rb | 13 ------------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 9606ea541..4dc5145b8 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -67,7 +67,6 @@ struct nogvl_connect_args { const char *user; const char *passwd; const char *db; - const char *init_command; unsigned int port; const char *unix_socket; unsigned long client_flag; @@ -157,10 +156,6 @@ static void *nogvl_connect(void *ptr) { struct nogvl_connect_args *args = ptr; MYSQL *client; - if (args->init_command != NULL) { - mysql_options(args->mysql, MYSQL_INIT_COMMAND, args->init_command); - } - client = mysql_real_connect(args->mysql, args->host, args->user, args->passwd, args->db, args->port, args->unix_socket, @@ -327,7 +322,7 @@ static VALUE rb_mysql_info(VALUE self) { return rb_str; } -static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags, VALUE init_command) { +static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) { struct nogvl_connect_args args; VALUE rv; GET_CLIENT(self); @@ -340,7 +335,6 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po args.db = NIL_P(database) ? NULL : StringValuePtr(database); args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); - args.init_command = NIL_P(init_command) ? NULL : StringValuePtr(init_command); rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); if (rv == Qfalse) { @@ -786,6 +780,11 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = charval; break; + case MYSQL_INIT_COMMAND: + charval = (const char *)StringValuePtr(value); + retval = charval; + break; + default: return Qfalse; } @@ -1170,6 +1169,10 @@ static VALUE set_read_default_group(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_READ_DEFAULT_GROUP, value); } +static VALUE set_init_command(VALUE self, VALUE value) { + return _mysql_client_options(self, MYSQL_INIT_COMMAND, value); +} + static VALUE initialize_ext(VALUE self) { GET_CLIENT(self); @@ -1241,9 +1244,10 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "secure_auth=", set_secure_auth, 1); rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1); rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1); + rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); - rb_define_private_method(cMysql2Client, "connect", rb_connect, 8); + rb_define_private_method(cMysql2Client, "connect", rb_connect, 7); sym_id = ID2SYM(rb_intern("id")); sym_version = ID2SYM(rb_intern("version")); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index c6be35a03..f894c0df6 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -23,7 +23,7 @@ def initialize(opts = {}) initialize_ext - [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth].each do |key| + [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].each do |key| next unless opts.key?(key) case key when :reconnect, :local_infile, :secure_auth @@ -55,7 +55,6 @@ def initialize(opts = {}) database = opts[:database] || opts[:dbname] || opts[:db] socket = opts[:socket] || opts[:sock] flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags] - init_command = opts[:init_command] # Correct the data types before passing these values down to the C level user = user.to_s unless user.nil? @@ -64,9 +63,8 @@ def initialize(opts = {}) port = port.to_i unless port.nil? database = database.to_s unless database.nil? socket = socket.to_s unless socket.nil? - init_command = init_command.to_s unless init_command.nil? - connect user, pass, host, port, database, socket, flags, init_command + connect user, pass, host, port, database, socket, flags end def self.default_query_options diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 54887c2f6..75857db2c 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -74,19 +74,6 @@ def connect *args Mysql2::Client::SECURE_CONNECTION)).should be_true end - it "should accept init_command" do - klient = Class.new(Mysql2::Client) do - attr_reader :connect_args - def connect *args - @connect_args ||= [] - @connect_args << args - end - end - command = "SET @@session.something = 1" - client = klient.new :init_command => command - client.connect_args.last[7].should eq(command) - end - it "should execute init command" do options = DatabaseCredentials['root'].dup options[:init_command] = "SET @something = 'setting_value';" From 0935d140ee83524f1a0e41c8ef82cb64d6c28c96 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Mon, 7 Apr 2014 17:35:07 -0400 Subject: [PATCH 133/783] Revert "Merge pull request #254 from abrody/master" This reverts commit 3ac7d82951a955b310c87db673ae0992432581b5, reversing changes made to 5098c81d66ee15d4be60ee9acc81297a80f400ac. --- ext/mysql2/client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 4dc5145b8..951434a83 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -338,7 +338,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); if (rv == Qfalse) { - while (rv == Qfalse && errno == EINTR && !mysql_errno(wrapper->client)) { + while (rv == Qfalse && errno == EINTR) { errno = 0; rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); } From 58f3f3ac5a6ca8ef09b3092a996f4d1e2c257cb8 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Tue, 8 Apr 2014 02:16:17 -0400 Subject: [PATCH 134/783] Reduce connect_timeout by elapsed time when retrying. If a signal interrupts mysql_real_connect, then using the original connect_timeout would allow the connect method to take longer than connect_timeout, so reduce by the elapsed time. --- ext/mysql2/client.c | 35 ++++++++++++++++++++++++++++++++--- ext/mysql2/client.h | 1 + 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 951434a83..866f1a25c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1,5 +1,6 @@ #include +#include #include #ifndef _WIN32 #include @@ -255,6 +256,7 @@ static VALUE allocate(VALUE klass) { wrapper->active_thread = Qnil; wrapper->server_version = 0; wrapper->reconnect_enabled = 0; + wrapper->connect_timeout = 0; wrapper->connected = 0; /* means that a database connection is open */ wrapper->initialized = 0; /* means that that the wrapper is initialized */ wrapper->refcount = 1; @@ -326,6 +328,8 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po struct nogvl_connect_args args; VALUE rv; GET_CLIENT(self); + time_t start_time, end_time; + unsigned int elapsed_time, connect_timeout; args.host = NIL_P(host) ? NULL : StringValuePtr(host); args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket); @@ -336,12 +340,31 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); + if (wrapper->connect_timeout) + time(&start_time); rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); if (rv == Qfalse) { while (rv == Qfalse && errno == EINTR) { + if (wrapper->connect_timeout) { + time(&end_time); + /* avoid long connect timeout from system time changes */ + if (end_time < start_time) + start_time = end_time; + elapsed_time = end_time - start_time; + /* avoid an early timeout due to time truncating milliseconds off the start time */ + if (elapsed_time > 0) + elapsed_time--; + if (elapsed_time >= wrapper->connect_timeout) + break; + connect_timeout = wrapper->connect_timeout - elapsed_time; + mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout); + } errno = 0; rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); } + /* restore the connect timeout for reconnecting */ + if (wrapper->connect_timeout) + mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout); if (rv == Qfalse) return rb_raise_mysql2_error(wrapper); } @@ -795,9 +818,15 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { if (result != 0) { rb_warn("%s\n", mysql_error(wrapper->client)); } else { - /* Special case for reconnect, this option is also stored in the wrapper struct */ - if (opt == MYSQL_OPT_RECONNECT) - wrapper->reconnect_enabled = boolval; + /* Special case for options that are stored in the wrapper struct */ + switch (opt) { + case MYSQL_OPT_RECONNECT: + wrapper->reconnect_enabled = boolval; + break; + case MYSQL_OPT_CONNECT_TIMEOUT: + wrapper->connect_timeout = intval; + break; + } } return (result == 0) ? Qtrue : Qfalse; diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 1b4149d22..211e509e0 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -41,6 +41,7 @@ typedef struct { VALUE active_thread; /* rb_thread_current() or Qnil */ long server_version; int reconnect_enabled; + int connect_timeout; int active; int connected; int initialized; From 53591ca9ccaf1ad344016862f0d88d4753f6dc32 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Wed, 9 Apr 2014 14:52:00 -0400 Subject: [PATCH 135/783] Add a default connect_timeout option of 2 minutes. --- lib/mysql2/client.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index f894c0df6..99a043f9d 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -23,6 +23,9 @@ def initialize(opts = {}) initialize_ext + # Set default connect_timeout to avoid unlimited retries from signal interruption + opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) + [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].each do |key| next unless opts.key?(key) case key From fba67a97f990116631c3704b2cb74575f8c0734f Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 15 Apr 2014 22:05:24 -0700 Subject: [PATCH 136/783] Readme updates to the Compatibility and Benchmarks section --- README.md | 106 ++++++++++++++++++++++++------------------------------ 1 file changed, 46 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 9b125f58a..5727db10a 100644 --- a/README.md +++ b/README.md @@ -253,7 +253,7 @@ the `:default_file` and `:default_group` paramters. For example: Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') ``` -### init_command +### Initial command on connect and reconnect If you specify the init_command option, the SQL string you provide will be executed after the connection is established. If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect. @@ -400,46 +400,7 @@ There are a few things that need to be kept in mind while using streaming: Read more about the consequences of using `mysql_use_result` (what streaming is implemented with) here: http://dev.mysql.com/doc/refman/5.0/en/mysql-use-result.html. -## Active Record - -To use the Active Record driver (with or without rails), all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2". -That was easy right? :) - -NOTE: as of 0.3.0, and Active Record 3.1 - the Active Record adapter has been pulled out of this gem and into Active Record itself. If you need to use mysql2 with -Rails versions < 3.1 make sure and specify `gem "mysql2", "~> 0.2.7"` in your Gemfile - -## Asynchronous Active Record - -Please see the [em-synchrony](https://github.com/igrigorik/em-synchrony) project for details about using EventMachine with mysql2 and Rails. - -## Sequel - -The Sequel adapter was pulled out into Sequel core (will be part of the next release) and can be used by specifying the "mysql2://" prefix to your connection specification. - -## EventMachine - -The mysql2 EventMachine deferrable api allows you to make async queries using EventMachine, -while specifying callbacks for success for failure. Here's a simple example: - -``` ruby -require 'mysql2/em' - -EM.run do - client1 = Mysql2::EM::Client.new - defer1 = client1.query "SELECT sleep(3) as first_query" - defer1.callback do |result| - puts "Result: #{result.to_a.inspect}" - end - - client2 = Mysql2::EM::Client.new - defer2 = client2.query "SELECT sleep(1) second_query" - defer2.callback do |result| - puts "Result: #{result.to_a.inspect}" - end -end -``` - -## Lazy Everything +### Lazy Everything Well... almost ;) @@ -468,42 +429,67 @@ This gem is tested with the following MySQL and MariaDB versions: * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) * MariaDB 5.5, 10.0 -This gem has two version families: +### Active Record * mysql2 0.2.x includes an Active Record driver compatible with AR 2.3 and 3.0 * mysql2 0.3.x does not include an AR driver because it is included in AR 3.1 and above -## Yeah... but why? +### Asynchronous Active Record -Someone: Dude, the Mysql gem works fiiiiiine. +Please see the [em-synchrony](https://github.com/igrigorik/em-synchrony) project for details about using EventMachine with mysql2 and Rails. -Me: It sure does, but it only hands you nil and strings for field values. Leaving you to convert -them into proper Ruby types in Ruby-land - which is slow as balls. +### Sequel -Someone: OK fine, but do_mysql can already give me back values with Ruby objects mapped to MySQL types. +Sequel includes a mysql2 adapter in all releases since 3.15 (2010-09-01). +Use the prefix "mysql2://" in your connection specification. -Me: Yep, but it's API is considerably more complex *and* can be ~2x slower. +### EventMachine -## Benchmarks +The mysql2 EventMachine deferrable api allows you to make async queries using EventMachine, +while specifying callbacks for success for failure. Here's a simple example: -Performing a basic "SELECT * FROM" query on a table with 30k rows and fields of nearly every Ruby-representable data type, -then iterating over every row using an #each like method yielding a block: +``` ruby +require 'mysql2/em' -These results are from the `query_with_mysql_casting.rb` script in the benchmarks folder +EM.run do + client1 = Mysql2::EM::Client.new + defer1 = client1.query "SELECT sleep(3) as first_query" + defer1.callback do |result| + puts "Result: #{result.to_a.inspect}" + end + + client2 = Mysql2::EM::Client.new + defer2 = client2.query "SELECT sleep(1) second_query" + defer2.callback do |result| + puts "Result: #{result.to_a.inspect}" + end +end +``` + +## Benchmarks and Comparison + +The mysql2 gem converts MySQL field types to Ruby data types in C code, providing a serious speed benefit. + +The do_mysql gem also converts MySQL fields types, but has a considerably more complex API and is still ~2x slower than mysql2. + +The mysql gem returns only nil or string data types, leaving you to convert field values to Ruby types in Ruby-land, which is much slower than mysql2's C code. + +For a comparative benchmark, the script below performs a basic "SELECT * FROM" +query on a table with 30k rows and fields of nearly every Ruby-representable +data type, then iterating over every row using an #each like method yielding a +block: ``` sh - user system total real -Mysql2 - 0.750000 0.180000 0.930000 ( 1.821655) -do_mysql - 1.650000 0.200000 1.850000 ( 2.811357) -Mysql - 7.500000 0.210000 7.710000 ( 8.065871) + user system total real +Mysql2 0.750000 0.180000 0.930000 (1.821655) +do_mysql 1.650000 0.200000 1.850000 (2.811357) +Mysql 7.500000 0.210000 7.710000 (8.065871) ``` +These results are from the `query_with_mysql_casting.rb` script in the benchmarks folder. + ## Development -To run the tests, you can use RVM and Bundler to create a pristine environment for mysql2 development/hacking. Use 'bundle install' to install the necessary development and testing gems: ``` sh From d0a51992b33761cc4d557274f57cf293292d3f5a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 19 Apr 2014 22:28:38 -0700 Subject: [PATCH 137/783] Update specs for Ruby 2.1 Timeout behavior by specifying 'Timeout::Error' as the klass parameter. With thanks to @bpardee: "if you look at the difference between 2.0 doc and 2.1 doc, it specifically adds the following to the description: The exception thrown to terminate the given block cannot be rescued inside the block unless klass is given explicitly." --- spec/mysql2/client_spec.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 75857db2c..533f2391a 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -83,7 +83,6 @@ def connect *args end it "should send init_command after reconnect" do - pending "Ruby 2.1 has changed Timeout behavior." if RUBY_VERSION =~ /2.1/ options = DatabaseCredentials['root'].dup options[:init_command] = "SET @something = 'setting_value';" options[:reconnect] = true @@ -94,7 +93,7 @@ def connect *args # simulate a broken connection begin - Timeout.timeout(1) do + Timeout.timeout(1, Timeout::Error) do client.query("SELECT sleep(2)") end rescue Timeout::Error @@ -449,9 +448,8 @@ def connect *args end it "should close the connection when an exception is raised" do - pending "Ruby 2.1 has changed Timeout behavior." if RUBY_VERSION =~ /2.1/ begin - Timeout.timeout(1) do + Timeout.timeout(1, Timeout::Error) do @client.query("SELECT sleep(2)") end rescue Timeout::Error @@ -463,10 +461,9 @@ def connect *args end it "should handle Timeouts without leaving the connection hanging if reconnect is true" do - pending "Ruby 2.1 has changed Timeout behavior." if RUBY_VERSION =~ /2.1/ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) begin - Timeout.timeout(1) do + Timeout.timeout(1, Timeout::Error) do client.query("SELECT sleep(2)") end rescue Timeout::Error @@ -478,10 +475,9 @@ def connect *args end it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction true" do - pending "Ruby 2.1 has changed Timeout behavior." if RUBY_VERSION =~ /2.1/ client = Mysql2::Client.new(DatabaseCredentials['root']) begin - Timeout.timeout(1) do + Timeout.timeout(1, Timeout::Error) do client.query("SELECT sleep(2)") end rescue Timeout::Error @@ -494,7 +490,7 @@ def connect *args client.reconnect = true begin - Timeout.timeout(1) do + Timeout.timeout(1, Timeout::Error) do client.query("SELECT sleep(2)") end rescue Timeout::Error From a3686dcde724833924b7fb4a4b3bb1ecfa4f3b08 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 13 May 2014 17:17:38 -0700 Subject: [PATCH 138/783] Bump version to 0.3.16 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 6ec8bb6ff..2d1dd39e7 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.3.15" + VERSION = "0.3.16" end From a21b15c2d4ca017662c7b7f5afd8a6e90ff10017 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 14 May 2014 14:22:41 -0700 Subject: [PATCH 139/783] Move the time vars above the GET_CLIENT macro --- ext/mysql2/client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 866f1a25c..28dce6e2c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -326,10 +326,10 @@ static VALUE rb_mysql_info(VALUE self) { static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) { struct nogvl_connect_args args; - VALUE rv; - GET_CLIENT(self); time_t start_time, end_time; unsigned int elapsed_time, connect_timeout; + VALUE rv; + GET_CLIENT(self); args.host = NIL_P(host) ? NULL : StringValuePtr(host); args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket); From 827d782e53b23a06ddb4fc6cc2f47b860667479f Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 15 May 2014 15:23:38 -0700 Subject: [PATCH 140/783] Test Ruby 2.1.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2e540b673..a65111d81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ rvm: - 1.9.2 - 1.9.3 - 2.0.0 - - 2.1.1 + - 2.1.2 - ree matrix: allow_failures: From 3896638ed1df7bb53bf6144dc4a27404658337bd Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 15 May 2014 15:50:30 -0700 Subject: [PATCH 141/783] Timeout values should be unsigned ints --- ext/mysql2/client.c | 6 +++--- ext/mysql2/client.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 28dce6e2c..2ce84d321 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -764,17 +764,17 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { switch(opt) { case MYSQL_OPT_CONNECT_TIMEOUT: - intval = NUM2INT(value); + intval = NUM2UINT(value); retval = &intval; break; case MYSQL_OPT_READ_TIMEOUT: - intval = NUM2INT(value); + intval = NUM2UINT(value); retval = &intval; break; case MYSQL_OPT_WRITE_TIMEOUT: - intval = NUM2INT(value); + intval = NUM2UINT(value); retval = &intval; break; diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 211e509e0..f7af5de67 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -41,7 +41,7 @@ typedef struct { VALUE active_thread; /* rb_thread_current() or Qnil */ long server_version; int reconnect_enabled; - int connect_timeout; + unsigned int connect_timeout; int active; int connected; int initialized; From 9acbbddeeee5de669f320be546ece8121d092cbc Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 15 May 2014 15:50:35 -0700 Subject: [PATCH 142/783] Whitespace --- ext/mysql2/client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 2ce84d321..079644be4 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -349,11 +349,11 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po time(&end_time); /* avoid long connect timeout from system time changes */ if (end_time < start_time) - start_time = end_time; + start_time = end_time; elapsed_time = end_time - start_time; /* avoid an early timeout due to time truncating milliseconds off the start time */ if (elapsed_time > 0) - elapsed_time--; + elapsed_time--; if (elapsed_time >= wrapper->connect_timeout) break; connect_timeout = wrapper->connect_timeout - elapsed_time; From d5a08c06f341591eb1dc2984405f5dc829209f4e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 16 May 2014 17:36:09 -0700 Subject: [PATCH 143/783] Add OS X to the test matrix --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a65111d81..183f760b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,7 @@ language: ruby bundler_args: --without benchmarks script: - bundle exec rake spec -# Temporary workaround for broken Rubygems on Travis before_install: - - gem update --system 2.1.11 - gem --version - | bash -c " # Install MariaDB if DB=mariadb @@ -23,6 +21,9 @@ before_install: fi " - mysqld --version +os: + - linux + - osx rvm: - 1.8.7 - 1.9.2 From 3b6230f004787754f5a47125228c3c9fc8764029 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 16 May 2014 18:03:56 -0700 Subject: [PATCH 144/783] Use rbx-2 instead of rbx on Travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 183f760b2..01f30db83 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,11 +33,11 @@ rvm: - ree matrix: allow_failures: - - rvm: rbx + - rvm: rbx-2 include: - rvm: 2.0.0 env: DB=mariadb55 - rvm: 2.0.0 env: DB=mariadb10 - - rvm: rbx + - rvm: rbx-2 env: RBXOPT=-Xgc.honor_start=true From 0dce7b51a70229b713456fb937c0a84eda272325 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 18 May 2014 22:01:49 -0700 Subject: [PATCH 145/783] Add OS X for RVM 2.0.0 only --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 01f30db83..07b181f63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,6 @@ before_install: - mysqld --version os: - linux - - osx rvm: - 1.8.7 - 1.9.2 @@ -41,3 +40,5 @@ matrix: env: DB=mariadb10 - rvm: rbx-2 env: RBXOPT=-Xgc.honor_start=true + - rvm: 2.0.0 + os: osx From df60f03ba607ea69be1beb0f0cd6da902b829d33 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 May 2014 21:20:56 -0700 Subject: [PATCH 146/783] Install MySQL if OS=darwin --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 07b181f63..e138e63a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,13 @@ before_install: sudo apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -y install mariadb-server libmariadbd-dev fi " + - | + bash -c " # Install MySQL is OS=darwin + if [[ x$OSTYPE =~ ^xdarwin ]]; then + brew install mysql + mysql.server start + fi + " - mysqld --version os: - linux From c099a7ed9f0dacd2a342cdecae978217c42c322e Mon Sep 17 00:00:00 2001 From: Keitaroh Kobayashi Date: Fri, 30 May 2014 09:14:02 +0900 Subject: [PATCH 147/783] Fixed some spelling / grammar in README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5727db10a..b0926c810 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# Mysql2 - A modern, simple and very fast Mysql library for Ruby - binding to libmysql +# Mysql2 - A modern, simple and very fast MySQL library for Ruby - binding to libmysql [![Build Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2) The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results. -Some database libraries out there serve as direct 1:1 mappings of the already complex C API's available. +Some database libraries out there serve as direct 1:1 mappings of the already complex C APIs available. This one is not. It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9, unless Encoding.default_internal is set then it'll convert from UTF-8 to that encoding] and uses encoding-aware MySQL API calls where it can. The API consists of two classes: -`Mysql2::Client` - your connection to the database +`Mysql2::Client` - your connection to the database. `Mysql2::Result` - returned from issuing a #query on the connection. It includes Enumerable. @@ -26,7 +26,7 @@ By default, the mysql2 gem will try to find a copy of MySQL in this order: * Option `--with-mysql-dir`, if provided (see below). * Option `--with-mysql-config`, if provided (see below). -* Several typical paths for `msyql_config` (default for the majority of users). +* Several typical paths for `mysql_config` (default for the majority of users). * The directory `/usr/local`. ### Configuration options From 380bccb910ae6bcf6aebd0620eb9f4c2bfb3c8fc Mon Sep 17 00:00:00 2001 From: David Lee Date: Tue, 10 Jun 2014 12:35:43 -0400 Subject: [PATCH 148/783] Add a note about using :cast and :cast_booleans together. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0926c810..454a4cd55 100644 --- a/README.md +++ b/README.md @@ -335,7 +335,7 @@ result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans = ### Skipping casting -Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false. +Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false. (Note that :cast => false overrides :cast_booleans => true.) ``` ruby client = Mysql2::Client.new From 57f8428266e3e2dd2cecc54a4a5bc3ca9fb67fdc Mon Sep 17 00:00:00 2001 From: zhaoyk10 Date: Fri, 27 Jun 2014 09:37:12 +0800 Subject: [PATCH 149/783] fix #520 datetime fractional seconds part error --- ext/mysql2/result.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 553ce2fd9..a6326e58c 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -294,9 +294,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo case MYSQL_TYPE_DATETIME: { /* DATETIME field */ int tokens; unsigned int year=0, month=0, day=0, hour=0, min=0, sec=0, msec=0; + char msec_char[7] = {'0','0','0','0','0','0','\0'}; uint64_t seconds; - tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6u", &year, &month, &day, &hour, &min, &sec, &msec); + tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6s", &year, &month, &day, &hour, &min, &sec, msec_char); if (tokens < 6) { /* msec might be empty */ val = Qnil; break; @@ -324,7 +325,19 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset); } } - } else { /* use Time, supports microseconds */ + } else { + /* microseconds can be up to 6 digits. Fewer digits must be interpreted from + * the left because the microseconds are to the right of the decimal point. + */ + if (tokens == 7) { + int i; + for (i = 0; i < 6; ++i) { + if (msec_char[i] == '\0') { + msec_char[i] = '0'; + } + } + msec = (unsigned int)strtoul(msec_char, NULL, 10); + } val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); if (!NIL_P(app_timezone)) { if (app_timezone == intern_local) { From cb42b7367b056a2cd403dfebc44c1aba9783d435 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 27 Jun 2014 16:43:08 -0700 Subject: [PATCH 150/783] Refactor the microseconds DATE code to also handle microseconds in a TIME field --- ext/mysql2/result.c | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index a6326e58c..a7e4c719b 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -176,6 +176,20 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e } #endif +/* Interpret microseconds digits left-aligned in fixed-width field. + * e.g. 10.123 seconds means 10 seconds and 123000 microseconds, + * because the microseconds are to the right of the decimal point. + */ +static unsigned int msec_char_to_uint(char *msec_char, size_t len) +{ + int i; + for (i = 0; i < (len - 1); i++) { + if (msec_char[i] == '\0') { + msec_char[i] = '0'; + } + } + return (unsigned int)strtoul(msec_char, NULL, 10); +} static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) { VALUE rowVal; @@ -274,13 +288,16 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo } case MYSQL_TYPE_TIME: { /* TIME field */ int tokens; - unsigned int hour=0, min=0, sec=0; - tokens = sscanf(row[i], "%2u:%2u:%2u", &hour, &min, &sec); + unsigned int hour=0, min=0, sec=0, msec=0; + char msec_char[7] = {'0','0','0','0','0','0','\0'}; + + tokens = sscanf(row[i], "%2u:%2u:%2u.%6s", &hour, &min, &sec, msec_char); if (tokens < 3) { val = Qnil; break; } - val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec)); + msec = msec_char_to_uint(msec_char, sizeof(msec_char)); + val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); if (!NIL_P(app_timezone)) { if (app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); @@ -326,18 +343,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo } } } else { - /* microseconds can be up to 6 digits. Fewer digits must be interpreted from - * the left because the microseconds are to the right of the decimal point. - */ - if (tokens == 7) { - int i; - for (i = 0; i < 6; ++i) { - if (msec_char[i] == '\0') { - msec_char[i] = '0'; - } - } - msec = (unsigned int)strtoul(msec_char, NULL, 10); - } + msec = msec_char_to_uint(msec_char, sizeof(msec_char)); val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); if (!NIL_P(app_timezone)) { if (app_timezone == intern_local) { From 2b39df4a01d40674b731e0daa48e0bdd53d60515 Mon Sep 17 00:00:00 2001 From: Joel Low Date: Sat, 12 Jul 2014 00:12:21 +0800 Subject: [PATCH 151/783] Visual Studio doesn't have unistd.h, and mysql2 compiles just fine. --- ext/mysql2/client.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 079644be4..c22b5b841 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -6,7 +6,9 @@ #include #include #endif +#ifndef _MSC_VER #include +#endif #include #include "wait_for_single_fd.h" From c9a2bdd993ef4614a84076786af9682d2347167c Mon Sep 17 00:00:00 2001 From: Joel Low Date: Sat, 12 Jul 2014 09:34:11 +0800 Subject: [PATCH 152/783] Visual Studio doesn't have unistd.h, and mysql2 compiles just fine. --- ext/mysql2/infile.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mysql2/infile.c b/ext/mysql2/infile.c index b77400b1d..6ff1f1f42 100644 --- a/ext/mysql2/infile.c +++ b/ext/mysql2/infile.c @@ -1,7 +1,9 @@ #include #include +#ifndef _MSC_VER #include +#endif #include #define ERROR_LEN 1024 From 33e28930642484de123dc6e37f9978823cc43d57 Mon Sep 17 00:00:00 2001 From: Jake Douglas Date: Mon, 28 Jul 2014 13:22:58 -0700 Subject: [PATCH 153/783] state the name of the field that contains a bad datetime --- ext/mysql2/result.c | 4 ++-- spec/mysql2/result_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index a7e4c719b..5b8a5b162 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -325,7 +325,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo val = Qnil; } else { if (month < 1 || day < 1) { - rb_raise(cMysql2Error, "Invalid date: %s", row[i]); + rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]); val = Qnil; } else { if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */ @@ -370,7 +370,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo val = Qnil; } else { if (month < 1 || day < 1) { - rb_raise(cMysql2Error, "Invalid date: %s", row[i]); + rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]); val = Qnil; } else { val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day)); diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 56c65aedf..02e53e032 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -335,6 +335,16 @@ @test_result['enum_test'].should eql('val1') end + it "should raise an error given an invalid DATETIME" do + begin + @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each + rescue Mysql2::Error => e + error = e + end + + error.message.should eql("Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") + end + if defined? Encoding context "string encoding for ENUM values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do From de48627ee89b9dfd7d966f3ea747e95a48085792 Mon Sep 17 00:00:00 2001 From: Michael Kruglos Date: Wed, 30 Jul 2014 22:52:50 +0300 Subject: [PATCH 154/783] Added call to mysql_library_init during initialization of the gem This call must be performed before trying to call mysql_init from multiple threads Reference: http://dev.mysql.com/doc/refman/5.1/en/mysql-init.html Minimal reproduction of the problem if mysql_library_init is not called require 'mysql2' def connect Mysql2::Client.new() end threads = [0,1].map { Thread.new { connect } } threads.map(&:join) puts "OK!" --- ext/mysql2/client.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index c22b5b841..71a7a358f 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1235,6 +1235,13 @@ void init_mysql2_client() { } } + /* Initializing mysql library, so different threads could call Client.new */ + /* without race condition in the library */ + if (mysql_library_init(0, NULL, NULL) != 0) { + rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library"); + return; + } + #if 0 mMysql2 = rb_define_module("Mysql2"); Teach RDoc about Mysql2 constant. #endif From 3f93699b43bb5882e1b3efc6e0c896a295567f67 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 31 Jul 2014 04:54:39 -0700 Subject: [PATCH 155/783] Travis update to create test database at runtime --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e138e63a9..8d9c36084 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ before_install: fi " - mysqld --version + - mysql -u root -e "CREATE DATABASE IF NOT EXISTS test" os: - linux rvm: From 4827da7d6b95835dd2a5dfcc0c1873e1c7ae0605 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 20 Aug 2014 05:31:26 -0700 Subject: [PATCH 156/783] Test with SSL on Travis --- .travis.yml | 16 +++------ .travis_mariadb.sh | 15 +++++++++ .travis_ssl.sh | 67 ++++++++++++++++++++++++++++++++++++++ spec/mysql2/client_spec.rb | 19 ++++++----- 4 files changed, 98 insertions(+), 19 deletions(-) create mode 100644 .travis_mariadb.sh create mode 100644 .travis_ssl.sh diff --git a/.travis.yml b/.travis.yml index 8d9c36084..1c959cefb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,17 +7,7 @@ before_install: - | bash -c " # Install MariaDB if DB=mariadb if [[ x$DB =~ xmariadb ]]; then - sudo service mysql stop - sudo apt-get purge '^mysql*' 'libmysql*' - sudo apt-get install python-software-properties - sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db - if [[ x$DB = xmariadb55 ]]; then - sudo add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/5.5/ubuntu precise main' - elif [[ x$DB = xmariadb10 ]]; then - sudo add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/10.0/ubuntu precise main' - fi - sudo apt-get update - sudo apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -y install mariadb-server libmariadbd-dev + sudo bash .travis_mariadb.sh '$DB' fi " - | @@ -27,6 +17,10 @@ before_install: mysql.server start fi " + - | + bash -c " # Configure SSL support + sudo bash .travis_ssl.sh + " - mysqld --version - mysql -u root -e "CREATE DATABASE IF NOT EXISTS test" os: diff --git a/.travis_mariadb.sh b/.travis_mariadb.sh new file mode 100644 index 000000000..c9eaceefa --- /dev/null +++ b/.travis_mariadb.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +service mysql stop +apt-get purge '^mysql*' 'libmysql*' +apt-get install python-software-properties +apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db + +if [[ x$1 = xmariadb55 ]]; then + add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/5.5/ubuntu precise main' +elif [[ x$1 = xmariadb10 ]]; then + add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/10.0/ubuntu precise main' +fi + +apt-get update +apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -y install mariadb-server libmariadbd-dev diff --git a/.travis_ssl.sh b/.travis_ssl.sh new file mode 100644 index 000000000..47580268f --- /dev/null +++ b/.travis_ssl.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Halt the tests on error +set -e + +# Whever MySQL configs live, go there (this is for cross-platform) +cd $(my_print_defaults --help | grep my.cnf | xargs find 2>/dev/null | xargs dirname) + +# Create config files to run openssl in batch mode +# Set the CA startdate to yesterday to avoid "ASN: before date in the future" +# (there can be 90k seconds in a daylight saving change day) + +echo " +[ ca ] +default_startdate = $(ruby -e 'print (Time.now - 90000).strftime("%y%m%d000000Z")') + +[ req ] +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +# If this isn't set, the error is "error, no objects specified in config file" +commonName = Common Name (hostname, IP, or your name) + +countryName_default = US +stateOrProvinceName_default = CA +localityName_default = San Francisco +0.organizationName_default = test_example +organizationalUnitName_default = Testing +emailAddress_default = admin@example.com +" | tee ca.cnf cert.cnf + +# The client and server certs must have a diferent common name than the CA +# to avoid "SSL connection error: error:00000001:lib(0):func(0):reason(1)" + +echo " +commonName_default = ca_name +" >> ca.cnf + +echo " +commonName_default = cert_name +" >> cert.cnf + +# Generate a set of certificates +openssl genrsa -out ca-key.pem 2048 +openssl req -new -x509 -nodes -days 1000 -key ca-key.pem -out ca-cert.pem -batch -config ca.cnf +openssl req -newkey rsa:2048 -days 1000 -nodes -keyout pkcs8-server-key.pem -out server-req.pem -batch -config cert.cnf +openssl x509 -req -in server-req.pem -days 1000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem +openssl req -newkey rsa:2048 -days 1000 -nodes -keyout pkcs8-client-key.pem -out client-req.pem -batch -config cert.cnf +openssl x509 -req -in client-req.pem -days 1000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem + +# Convert format from PKCS#8 to PKCS#1 +openssl rsa -in pkcs8-server-key.pem -out server-key.pem +openssl rsa -in pkcs8-client-key.pem -out client-key.pem + +# Put the configs into the server +echo " +[mysqld] +ssl-ca=/etc/mysql/ca-cert.pem +ssl-cert=/etc/mysql/server-cert.pem +ssl-key=/etc/mysql/server-key.pem +" >> my.cnf + +# FIXME The startdate code above isn't doing the trick, we must wait until the minute moves +ruby -e 'start = Time.now.min; while Time.now.min == start; sleep 2; end' + +# Ok, let's see what we got! +service mysql restart || brew services restart mysql diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 533f2391a..8672881fb 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -109,17 +109,20 @@ def connect *args end it "should be able to connect via SSL options" do - ssl = @client.query "SHOW VARIABLES LIKE 'have_%ssl'" - ssl_enabled = ssl.any? {|x| x['Value'] == 'ENABLED'} - pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") unless ssl_enabled - pending("DON'T WORRY, THIS TEST PASSES - but you must update the SSL cert paths in this test and remove this pending state.") + ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'" + ssl_uncompiled = ssl.any? {|x| x['Value'] == 'OFF'} + pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled + ssl_disabled = ssl.any? {|x| x['Value'] == 'DISABLED'} + pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled + + # You may need to adjust the lines below to match your SSL certificate paths ssl_client = nil lambda { ssl_client = Mysql2::Client.new( - :sslkey => '/path/to/client-key.pem', - :sslcert => '/path/to/client-cert.pem', - :sslca => '/path/to/ca-cert.pem', - :sslcapath => '/path/to/newcerts/', + :sslkey => '/etc/mysql/client-key.pem', + :sslcert => '/etc/mysql/client-cert.pem', + :sslca => '/etc/mysql/ca-cert.pem', + :sslcapath => '/etc/mysql/', :sslcipher => 'DHE-RSA-AES256-SHA' ) }.should_not raise_error(Mysql2::Error) From fc30a7c056e63517f5f66702016941b3902ec0b6 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 24 Aug 2014 01:07:19 -0700 Subject: [PATCH 157/783] Use /dev/null in invalidate_fd to avoid infinite loop in OpenSSL Thanks to Andy Bakun / @thwarted for identifying the issue and suggesting the /dev/null workaround. --- ext/mysql2/client.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 71a7a358f..3743d931a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -169,26 +169,30 @@ static void *nogvl_connect(void *ptr) { #ifndef _WIN32 /* - * Redirect clientfd to a dummy socket for mysql_close to - * write, shutdown, and close on as a no-op. - * We do this hack because we want to call mysql_close to release - * memory, but do not want mysql_close to drop connections in the - * parent if the socket got shared in fork. + * Redirect clientfd to /dev/null for mysql_close and SSL_close to write, + * shutdown, and close. The hack is needed to prevent shutdown() from breaking + * a socket that may be in use by the parent or other processes after fork. + * + * /dev/null is used to absorb writes; previously a dummy socket was used, but + * it could not abosrb writes and caused openssl to go into an infinite loop. + * * Returns Qtrue or Qfalse (success or failure) + * + * Note: if this function is needed on Windows, use "nul" instead of "/dev/null" */ static VALUE invalidate_fd(int clientfd) { #ifdef SOCK_CLOEXEC /* Atomically set CLOEXEC on the new FD in case another thread forks */ - int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC); if (sockfd < 0) { /* Maybe SOCK_CLOEXEC is defined but not available on this kernel */ - int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + int sockfd = open("/dev/null", O_RDWR); fcntl(sockfd, F_SETFD, FD_CLOEXEC); } #else /* Well we don't have SOCK_CLOEXEC, so just set FD_CLOEXEC quickly */ - int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + int sockfd = open("/dev/null", O_RDWR); fcntl(sockfd, F_SETFD, FD_CLOEXEC); #endif From c047e828c5df91ac4748f272a4249d8ea134fbaf Mon Sep 17 00:00:00 2001 From: Abdelkader Boudih Date: Sat, 13 Sep 2014 11:44:03 +0000 Subject: [PATCH 158/783] change sql_state to attr_reader to avoid ruby warning --- lib/mysql2/error.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 57131d75c..8e6ed5b94 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -5,7 +5,8 @@ class Error < StandardError REPLACEMENT_CHAR = '?' ENCODE_OPTS = {:undef => :replace, :invalid => :replace, :replace => REPLACEMENT_CHAR} - attr_accessor :error_number, :sql_state + attr_accessor :error_number + attr_reader :sql_state attr_writer :server_version # Mysql gem compatibility @@ -18,10 +19,8 @@ def initialize(msg, server_version=nil) super(clean_message(msg)) end - if "".respond_to? :encode - def sql_state=(state) - @sql_state = state.encode(ENCODE_OPTS) - end + def sql_state=(state) + @sql_state = ''.respond_to?(:encode) ? state.encode(ENCODE_OPTS) : state end private From e953f0cc51c7f5f4d7a23bfcda2327f376cd1fa2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 19 Sep 2014 22:39:36 -0700 Subject: [PATCH 159/783] Example results instead of an otter --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 454a4cd55..b493e05a4 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,10 @@ results.each do |row| # conveniently, row is a hash # the keys are the fields, as you'd expect # the values are pre-built ruby primitives mapped from their corresponding field types in MySQL - # Here's an otter: http://farm1.static.flickr.com/130/398077070_b8795d0ef3_b.jpg + puts row["id"] # row["id"].class == Fixnum + if row["dne"] # non-existant hash entry is nil + puts row["dne"] + end end ``` From 05c7875b854119ea8e905180e2a8f4e6fd2b818f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Wed, 15 Oct 2014 20:02:05 +0200 Subject: [PATCH 160/783] Add MySQL 5.7 test to CI. --- .travis.yml | 9 +++++++++ .travis_mysql57.sh | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 .travis_mysql57.sh diff --git a/.travis.yml b/.travis.yml index 1c959cefb..e2c2d9004 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,12 @@ script: - bundle exec rake spec before_install: - gem --version + - | + bash -c " # Install MySQL 5.7 if DB=mysql57 + if [[ x$DB =~ mysql57 ]]; then + bash .travis_mysql57.sh + fi + " - | bash -c " # Install MariaDB if DB=mariadb if [[ x$DB =~ xmariadb ]]; then @@ -34,12 +40,15 @@ rvm: - ree matrix: allow_failures: + - env: DB=mysql57 - rvm: rbx-2 include: - rvm: 2.0.0 env: DB=mariadb55 - rvm: 2.0.0 env: DB=mariadb10 + - rvm: 2.0.0 + env: DB=mysql57 - rvm: rbx-2 env: RBXOPT=-Xgc.honor_start=true - rvm: 2.0.0 diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh new file mode 100644 index 000000000..6529bd304 --- /dev/null +++ b/.travis_mysql57.sh @@ -0,0 +1,10 @@ +sudo apt-get remove --purge "^mysql.*" +sudo apt-get autoremove +sudo apt-get autoclean +sudo rm -rf /var/lib/mysql +sudo rm -rf /var/log/mysql +echo mysql-apt-config mysql-apt-config/enable-repo select mysql-5.7-dmr | sudo debconf-set-selections +wget http://dev.mysql.com/get/mysql-apt-config_0.2.1-1ubuntu12.04_all.deb +sudo dpkg --install mysql-apt-config_0.2.1-1ubuntu12.04_all.deb +sudo apt-get update -q +sudo apt-get install -q -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" mysql-server ruby libmysqlclient-dev From fb623dabf42cbc91bf179ad0f32b34d6e3731a81 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 5 Oct 2014 03:08:39 -0700 Subject: [PATCH 161/783] Improve the forced reconnect code in the init_command test. Instead of using a timeout to trigger a connection close, explicitly kill the connection. Instead of testing the return value of ping for reconnect, test that the connection ID changed. --- spec/mysql2/client_spec.rb | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 8672881fb..50cb1571c 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -91,15 +91,26 @@ def connect *args result = client.query("SELECT @something;") result.first['@something'].should eq('setting_value') - # simulate a broken connection + # get the current connection id + result = client.query("SELECT CONNECTION_ID()") + first_conn_id = result.first['CONNECTION_ID()'] + + # break the current connection begin - Timeout.timeout(1, Timeout::Error) do - client.query("SELECT sleep(2)") - end - rescue Timeout::Error + client.query("KILL #{first_conn_id}") + rescue Mysql2::Error end - client.ping.should be_false + client.ping # reconnect now + + # get the new connection id + result = client.query("SELECT CONNECTION_ID()") + second_conn_id = result.first['CONNECTION_ID()'] + + # confirm reconnect by checking the new connection id + first_conn_id.should_not == second_conn_id + + # At last, check that the init command executed result = client.query("SELECT @something;") result.first['@something'].should eq('setting_value') end From 69c741ab5d8268bca71a20ca5ed3c82cf895f3c6 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 23 Oct 2014 03:40:02 +0300 Subject: [PATCH 162/783] Define SECURE_CONNECTION to 0 if not available --- ext/mysql2/client.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 3743d931a..85dfc65a8 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1378,6 +1378,10 @@ void init_mysql2_client() { #ifdef CLIENT_SECURE_CONNECTION rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(CLIENT_SECURE_CONNECTION)); +#else + /* HACK because MySQL5.7 no longer defines this constant, + * but we're using it in our default connection flags. */ + rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0)); #endif #ifdef CLIENT_MULTI_STATEMENTS From 6822ad6aad46a64fe37e65dbede94d7d552bb7d7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 23 Oct 2014 04:02:00 +0300 Subject: [PATCH 163/783] Update MySQL 5.7 and other Travis scripts. --- .travis.yml | 3 ++- .travis_mariadb.sh | 2 +- .travis_mysql57.sh | 26 ++++++++++++++++---------- .travis_ssl.sh | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index e2c2d9004..02915c978 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ before_install: - | bash -c " # Install MySQL 5.7 if DB=mysql57 if [[ x$DB =~ mysql57 ]]; then - bash .travis_mysql57.sh + sudo bash .travis_mysql57.sh fi " - | @@ -29,6 +29,7 @@ before_install: " - mysqld --version - mysql -u root -e "CREATE DATABASE IF NOT EXISTS test" + - mysql -u root -e "CREATE USER '$USER'@'localhost'" || true os: - linux rvm: diff --git a/.travis_mariadb.sh b/.travis_mariadb.sh index c9eaceefa..652804382 100644 --- a/.travis_mariadb.sh +++ b/.travis_mariadb.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh service mysql stop apt-get purge '^mysql*' 'libmysql*' diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index 6529bd304..a9f87dda8 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -1,10 +1,16 @@ -sudo apt-get remove --purge "^mysql.*" -sudo apt-get autoremove -sudo apt-get autoclean -sudo rm -rf /var/lib/mysql -sudo rm -rf /var/log/mysql -echo mysql-apt-config mysql-apt-config/enable-repo select mysql-5.7-dmr | sudo debconf-set-selections -wget http://dev.mysql.com/get/mysql-apt-config_0.2.1-1ubuntu12.04_all.deb -sudo dpkg --install mysql-apt-config_0.2.1-1ubuntu12.04_all.deb -sudo apt-get update -q -sudo apt-get install -q -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" mysql-server ruby libmysqlclient-dev +#!/bin/sh + +service mysql stop + +apt-get purge '^mysql*' 'libmysql*' +apt-get autoclean + +rm -rf /var/lib/mysql +rm -rf /var/log/mysql + +apt-get install python-software-properties +apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x8C718D3B5072E1F5 +add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.7-dmr' + +apt-get update +apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -y install mysql-server libmysqlclient-dev diff --git a/.travis_ssl.sh b/.travis_ssl.sh index 47580268f..d6551cfcf 100644 --- a/.travis_ssl.sh +++ b/.travis_ssl.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # Halt the tests on error set -e From e487e6477b7e9ff7954e75849e4d58742dd6258d Mon Sep 17 00:00:00 2001 From: Gaurish Sharma Date: Sun, 9 Nov 2014 17:52:21 +0530 Subject: [PATCH 164/783] Test Ruby 2.1.4 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 02915c978..b79baade1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ rvm: - 1.9.2 - 1.9.3 - 2.0.0 - - 2.1.2 + - 2.1.4 - ree matrix: allow_failures: From 773c5687dcf8e89732d82ff94675881027a332c9 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 10 Nov 2014 12:31:55 -0800 Subject: [PATCH 165/783] Bump version to 0.3.17 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 2d1dd39e7..db2dda032 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.3.16" + VERSION = "0.3.17" end From 4c15fa176833b923d273cf38a1af6022f24dbb87 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Fri, 14 Nov 2014 08:30:35 -0800 Subject: [PATCH 166/783] Update and rename MIT-LICENSE to LICENSE --- LICENSE | 21 +++++++++++++++++++++ MIT-LICENSE | 20 -------------------- 2 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 LICENSE delete mode 100644 MIT-LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..3a995e6b4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Brian Lopez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MIT-LICENSE b/MIT-LICENSE deleted file mode 100644 index f80c5eedc..000000000 --- a/MIT-LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2010-2011 Brian Lopez - http://github.com/brianmario - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file From e91d8ed43c0b82aacca2510a33e0f19f7e309736 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 24 Nov 2014 05:01:17 -0800 Subject: [PATCH 167/783] Update Travis SSL script for OS X --- .travis_ssl.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis_ssl.sh b/.travis_ssl.sh index d6551cfcf..425ef2c90 100644 --- a/.travis_ssl.sh +++ b/.travis_ssl.sh @@ -3,7 +3,7 @@ # Halt the tests on error set -e -# Whever MySQL configs live, go there (this is for cross-platform) +# Wherever MySQL configs live, go there (this is for cross-platform) cd $(my_print_defaults --help | grep my.cnf | xargs find 2>/dev/null | xargs dirname) # Create config files to run openssl in batch mode @@ -64,4 +64,5 @@ ssl-key=/etc/mysql/server-key.pem ruby -e 'start = Time.now.min; while Time.now.min == start; sleep 2; end' # Ok, let's see what we got! +set +e service mysql restart || brew services restart mysql From 003b4a574d2020aa567bb27e59d95ad09433884e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 30 Nov 2014 18:28:24 -0800 Subject: [PATCH 168/783] Add Ruby 2.2 on Travis, switch to 2.1 latest and remove 1.9.2 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b79baade1..a1a10a3cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,10 +34,10 @@ os: - linux rvm: - 1.8.7 - - 1.9.2 - 1.9.3 - 2.0.0 - - 2.1.4 + - 2.1 + - 2.2 - ree matrix: allow_failures: From dbc02cbce09d7fe05052e51b8ffe176a8cb33b88 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 19 Dec 2014 19:26:06 -0800 Subject: [PATCH 169/783] Detect Darwin before calling brew services --- .travis_ssl.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis_ssl.sh b/.travis_ssl.sh index 425ef2c90..ee9351acf 100644 --- a/.travis_ssl.sh +++ b/.travis_ssl.sh @@ -65,4 +65,9 @@ ruby -e 'start = Time.now.min; while Time.now.min == start; sleep 2; end' # Ok, let's see what we got! set +e -service mysql restart || brew services restart mysql +if [ "$(uname)" == "Darwin" ]; then + brew tap homebrew/boneyard + brew services restart mysql +else + service mysql restart +fi From ba1a246aa9800520d4c806267e01fc003a7a2f3e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 24 Dec 2014 14:55:28 -0800 Subject: [PATCH 170/783] Use an in-memory table to speed up this test --- spec/mysql2/result_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 02e53e032..964bc43b1 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -136,7 +136,7 @@ it "should raise an exception if streaming ended due to a timeout" do # Create an extra client instance, since we're going to time it out client = Mysql2::Client.new DatabaseCredentials['root'] - client.query "CREATE TEMPORARY TABLE streamingTest (val BINARY(255))" + client.query "CREATE TEMPORARY TABLE streamingTest (val BINARY(255)) ENGINE=MEMORY" # Insert enough records to force the result set into multiple reads # (the BINARY type is used simply because it forces full width results) From 054b5f7adc7cf6648ea3a15307e72fe909ffef95 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 24 Dec 2014 15:21:10 -0800 Subject: [PATCH 171/783] Skip SSL setup on OS X, allow OS X to fail --- .travis.yml | 6 +++++- .travis_ssl.sh | 11 +---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index a1a10a3cd..c31eef246 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,10 @@ before_install: " - | bash -c " # Configure SSL support - sudo bash .travis_ssl.sh + if [[ ! x$OSTYPE =~ ^xdarwin ]]; then + sudo bash .travis_ssl.sh + sudo service mysql restart + fi " - mysqld --version - mysql -u root -e "CREATE DATABASE IF NOT EXISTS test" @@ -43,6 +46,7 @@ matrix: allow_failures: - env: DB=mysql57 - rvm: rbx-2 + - os: osx include: - rvm: 2.0.0 env: DB=mariadb55 diff --git a/.travis_ssl.sh b/.travis_ssl.sh index ee9351acf..4a3f52bb2 100644 --- a/.travis_ssl.sh +++ b/.travis_ssl.sh @@ -60,14 +60,5 @@ ssl-cert=/etc/mysql/server-cert.pem ssl-key=/etc/mysql/server-key.pem " >> my.cnf -# FIXME The startdate code above isn't doing the trick, we must wait until the minute moves +# Wait until the minute moves to ensure that the SSL cert is within its valid range ruby -e 'start = Time.now.min; while Time.now.min == start; sleep 2; end' - -# Ok, let's see what we got! -set +e -if [ "$(uname)" == "Darwin" ]; then - brew tap homebrew/boneyard - brew services restart mysql -else - service mysql restart -fi From d198e906ec380f92bf22ecf5919811b2f356f84d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 30 Sep 2014 14:39:40 -0700 Subject: [PATCH 172/783] Use AppVeyor for Windows testing --- appveyor.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..0c0c01acd --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,33 @@ +--- +version: "{build}" +clone_depth: 10 +install: + - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% + - ruby --version + - gem --version + - gem install bundler --quiet --no-ri --no-rdoc + - bundler --version + - bundle install --without benchmarks +build_script: + - bundle exec rake compile +test_script: + - '"C:\Program Files\MySQL\MySQL Server 5.6\bin\mysql" --version' + - > + "C:\Program Files\MySQL\MySQL Server 5.6\bin\mysql" -u root -p"Password12!" -e " + CREATE DATABASE IF NOT EXISTS test; + CREATE USER '%USERNAME%'@'localhost'; + SET PASSWORD = PASSWORD(''); + FLUSH PRIVILEGES; + " + - bundle exec rake spec +# Where do I get Unix find? +#on_failure: +# - find tmp -name "*.log" -exec cat {}; +environment: + matrix: + - ruby_version: "200" + - ruby_version: "200-x64" + - ruby_version: "21" + - ruby_version: "21-x64" +services: + - mysql From 68c41a2606514dad0d8e2537a1133356cce63498 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 30 Sep 2014 16:10:17 -0700 Subject: [PATCH 173/783] Update for MinGW-x64 --- Rakefile | 2 +- tasks/compile.rake | 34 ++++++++++++++++++++++------------ tasks/vendor_mysql.rake | 28 ++++++++++++---------------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/Rakefile b/Rakefile index 98f6ece21..3ff7a8d24 100644 --- a/Rakefile +++ b/Rakefile @@ -2,10 +2,10 @@ require 'rake' # Load custom tasks (careful attention to define tasks before prerequisites) +load 'tasks/vendor_mysql.rake' load 'tasks/rspec.rake' load 'tasks/compile.rake' load 'tasks/generate.rake' load 'tasks/benchmarks.rake' -load 'tasks/vendor_mysql.rake' task :default => :spec diff --git a/tasks/compile.rake b/tasks/compile.rake index e42b5f1b3..53248037b 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -1,21 +1,13 @@ require "rake/extensiontask" -CONNECTOR_VERSION = "6.0.2" #"mysql-connector-c-noinstall-6.0.2-win32.zip" -CONNECTOR_MIRROR = ENV['CONNECTOR_MIRROR'] || ENV['MYSQL_MIRROR'] || "http://mysql.he.net/" - def gemspec @clean_gemspec ||= eval(File.read(File.expand_path('../../mysql2.gemspec', __FILE__))) end Rake::ExtensionTask.new("mysql2", gemspec) do |ext| - # reference where the vendored MySQL got extracted - connector_lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', "mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32")) - - # DRY options feed into compile or cross-compile process - windows_options = [ - "--with-mysql-include=#{connector_lib}/include", - "--with-mysql-lib=#{connector_lib}/lib" - ] + # Expand the path because the build dir is 3-4 levels deep in tmp/platform/version/ + connector_dir = File.expand_path("../../vendor/#{CONNECTOR_DIR}", __FILE__) + windows_options = [ "--with-mysql-dir=#{connector_dir}" ] # automatically add build options to avoid need of manual input if RUBY_PLATFORM =~ /mswin|mingw/ then @@ -39,7 +31,7 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| At the time of building this gem, the necessary DLL files where available in the following download: - http://dev.mysql.com/get/Downloads/Connector-C/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32.zip/from/pick + http://dev.mysql.com/get/Downloads/Connector-C/#{CONNECTOR_ZIP}/from/pick And put lib\\libmysql.dll file in your Ruby bin directory, for example C:\\Ruby\\bin @@ -69,3 +61,21 @@ end if Rake::Task.task_defined?(:cross) Rake::Task[:cross].prerequisites << "lib/mysql2/mysql2.rb" end + +# DevKit task following the example of Luis Lavena's test-ruby-c-extension +task :devkit do + begin + require "devkit" + rescue LoadError => e + abort "Failed to activate RubyInstaller's DevKit required for compilation." + end +end + +if RUBY_PLATFORM =~ /mingw|mswin/ then + Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' + Rake::Task['compile'].prerequisites.unshift 'devkit' +else + if Rake::Task.tasks.map {|t| t.name }.include? 'cross' + Rake::Task['cross'].prerequisites.unshift 'vendor:mysql' + end +end diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index ce7684065..784b185e1 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -1,23 +1,28 @@ require 'rake/clean' require 'rake/extensioncompiler' +CONNECTOR_VERSION = "6.1.5" #"mysql-connector-c-6.1.5-win32.zip" +CONNECTOR_PLATFORM = RUBY_PLATFORM =~ /x64/ ? "winx64" : "win32" +CONNECTOR_DIR = "mysql-connector-c-#{CONNECTOR_VERSION}-#{CONNECTOR_PLATFORM}" +CONNECTOR_ZIP = "mysql-connector-c-#{CONNECTOR_VERSION}-#{CONNECTOR_PLATFORM}.zip" + # download mysql library and headers directory "vendor" -file "vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32.zip" => ["vendor"] do |t| - url = "http://dev.mysql.com/get/Downloads/Connector-C/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32.zip/from/#{CONNECTOR_MIRROR}/" +file "vendor/#{CONNECTOR_ZIP}" => ["vendor"] do |t| + url = "http://cdn.mysql.com/Downloads/Connector-C/#{CONNECTOR_ZIP}" when_writing "downloading #{t.name}" do cd File.dirname(t.name) do - sh "wget -c #{url} || curl -C - -O #{url}" + sh "curl -C - -O #{url} || wget -c #{url}" end end end -file "vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/include/mysql.h" => ["vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32.zip"] do |t| +file "vendor/#{CONNECTOR_DIR}/include/mysql.h" => ["vendor/#{CONNECTOR_ZIP}"] do |t| full_file = File.expand_path(t.prerequisites.last) when_writing "creating #{t.name}" do cd "vendor" do - sh "unzip #{full_file} mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/bin/** mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/include/** mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/lib/**" + sh "unzip #{full_file} #{CONNECTOR_DIR}/bin/** #{CONNECTOR_DIR}/include/** #{CONNECTOR_DIR}/lib/**" end # update file timestamp to avoid Rake perform this extraction again. touch t.name @@ -25,16 +30,7 @@ file "vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/include/mysq end # clobber expanded packages -CLOBBER.include("vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32") +CLOBBER.include("vendor/#{CONNECTOR_DIR}") # vendor:mysql -task 'vendor:mysql' => ["vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/include/mysql.h"] - -# hook into cross compilation vendored mysql dependency -if RUBY_PLATFORM =~ /mingw|mswin/ then - Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' -else - if Rake::Task.tasks.map {|t| t.name }.include? 'cross' - Rake::Task['cross'].prerequisites.unshift 'vendor:mysql' - end -end +task 'vendor:mysql' => "vendor/#{CONNECTOR_DIR}/include/mysql.h" From afe0ae9965150c4caab22c3db717d3de02db1937 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 1 Oct 2014 18:40:52 -0700 Subject: [PATCH 174/783] Skip EM specs on Windows --- Gemfile | 1 + mysql2.gemspec | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index a3b95aed7..a8e9d75bf 100644 --- a/Gemfile +++ b/Gemfile @@ -13,6 +13,7 @@ end group :development do gem 'pry' + gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ end platforms :rbx do diff --git a/mysql2.gemspec b/mysql2.gemspec index 01ad2192d..5b7328dbf 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -15,7 +15,6 @@ Gem::Specification.new do |s| s.test_files = `git ls-files spec examples`.split # tests - s.add_development_dependency 'eventmachine' s.add_development_dependency 'rake-compiler', '~> 0.8.1' s.add_development_dependency 'rake', '~> 0.9.3' s.add_development_dependency 'rspec', '~> 2.8.0' From a399494f0d9eae89deeda5d617b2d9ba85b0493e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 23 Oct 2014 06:48:55 +0300 Subject: [PATCH 175/783] Add support/libmysql.def --- support/libmysql.def | 114 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 support/libmysql.def diff --git a/support/libmysql.def b/support/libmysql.def new file mode 100644 index 000000000..b257c628f --- /dev/null +++ b/support/libmysql.def @@ -0,0 +1,114 @@ +; MySQL's Connector/C ships with a libmysql.dll main library and libmysql.lib +; interface library. However, the interface library is not linkable by MinGW. +; +; At compile time, we generate a libmysql.a interface library with dlltool.exe. +; +; This def file can be re-generated using the reimp.exe or gendef.exe tools. +; +LIBRARY libmysql.dll +EXPORTS +mysql_affected_rows +mysql_change_user +mysql_character_set_name +mysql_close +mysql_data_seek +mysql_debug +mysql_dump_debug_info +mysql_eof +mysql_errno +mysql_error +mysql_escape_string +mysql_fetch_field +mysql_fetch_field_direct +mysql_fetch_fields +mysql_fetch_lengths +mysql_fetch_row +mysql_field_count +mysql_field_seek +mysql_field_tell +mysql_free_result +mysql_get_client_info +mysql_get_client_version +mysql_get_host_info +mysql_get_option +mysql_get_proto_info +mysql_get_server_info +mysql_get_server_version +mysql_get_ssl_cipher +mysql_hex_string +mysql_info +mysql_init +mysql_insert_id +mysql_kill +mysql_library_end +mysql_library_init +mysql_list_dbs +mysql_list_fields +mysql_list_processes +mysql_list_tables +mysql_more_results +mysql_next_result +mysql_num_fields +mysql_num_rows +mysql_options +mysql_options4 +mysql_ping +mysql_query +mysql_read_query_result +mysql_real_connect +mysql_real_escape_string +mysql_real_query +mysql_refresh +mysql_reset_connection +mysql_rollback +mysql_row_seek +mysql_row_tell +mysql_select_db +mysql_send_query +mysql_server_end +mysql_server_init +mysql_session_track_get_first +mysql_session_track_get_next +mysql_set_character_set +mysql_set_local_infile_default +mysql_set_local_infile_handler +mysql_set_server_option +mysql_shutdown +mysql_sqlstate +mysql_ssl_set +mysql_stat +mysql_stmt_affected_rows +mysql_stmt_attr_get +mysql_stmt_attr_set +mysql_stmt_bind_param +mysql_stmt_bind_result +mysql_stmt_close +mysql_stmt_data_seek +mysql_stmt_errno +mysql_stmt_error +mysql_stmt_execute +mysql_stmt_fetch +mysql_stmt_fetch_column +mysql_stmt_field_count +mysql_stmt_free_result +mysql_stmt_init +mysql_stmt_insert_id +mysql_stmt_next_result +mysql_stmt_num_rows +mysql_stmt_param_count +mysql_stmt_param_metadata +mysql_stmt_prepare +mysql_stmt_reset +mysql_stmt_result_metadata +mysql_stmt_row_seek +mysql_stmt_row_tell +mysql_stmt_send_long_data +mysql_stmt_sqlstate +mysql_stmt_store_result +mysql_store_result +mysql_thread_end +mysql_thread_id +mysql_thread_init +mysql_thread_safe +mysql_use_result +mysql_warning_count From 12287c30521cf178a239fba89c8de24a0c5a528c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 24 Oct 2014 06:48:55 +0300 Subject: [PATCH 176/783] Add a mangled copy of each symbol to libmysql.def --- support/libmysql.def | 105 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/support/libmysql.def b/support/libmysql.def index b257c628f..614655a03 100644 --- a/support/libmysql.def +++ b/support/libmysql.def @@ -8,107 +8,212 @@ LIBRARY libmysql.dll EXPORTS mysql_affected_rows +mysql_affected_rows@4 mysql_change_user +mysql_change_user@16 mysql_character_set_name +mysql_character_set_name@4 mysql_close +mysql_close@4 mysql_data_seek +mysql_data_seek@12 mysql_debug +mysql_debug@4 mysql_dump_debug_info +mysql_dump_debug_info@4 mysql_eof +mysql_eof@4 mysql_errno +mysql_errno@4 mysql_error +mysql_error@4 mysql_escape_string +mysql_escape_string@12 mysql_fetch_field +mysql_fetch_field@4 mysql_fetch_field_direct +mysql_fetch_field_direct@8 mysql_fetch_fields +mysql_fetch_fields@4 mysql_fetch_lengths +mysql_fetch_lengths@4 mysql_fetch_row +mysql_fetch_row@4 mysql_field_count +mysql_field_count@4 mysql_field_seek +mysql_field_seek@8 mysql_field_tell +mysql_field_tell@4 mysql_free_result +mysql_free_result@4 mysql_get_client_info +mysql_get_client_info@0 mysql_get_client_version +mysql_get_client_version@0 mysql_get_host_info +mysql_get_host_info@4 mysql_get_option +mysql_get_option@12 mysql_get_proto_info +mysql_get_proto_info@4 mysql_get_server_info +mysql_get_server_info@4 mysql_get_server_version +mysql_get_server_version@4 mysql_get_ssl_cipher +mysql_get_ssl_cipher@4 mysql_hex_string +mysql_hex_string@12 mysql_info +mysql_info@4 mysql_init +mysql_init@4 mysql_insert_id +mysql_insert_id@4 mysql_kill +mysql_kill@8 mysql_library_end +mysql_library_end@0 mysql_library_init +mysql_library_init@12 mysql_list_dbs +mysql_list_dbs@8 mysql_list_fields +mysql_list_fields@12 mysql_list_processes +mysql_list_processes@4 mysql_list_tables +mysql_list_tables@8 mysql_more_results +mysql_more_results@4 mysql_next_result +mysql_next_result@4 mysql_num_fields +mysql_num_fields@4 mysql_num_rows +mysql_num_rows@4 mysql_options +mysql_options@12 mysql_options4 +mysql_options4@16 mysql_ping +mysql_ping@4 mysql_query +mysql_query@8 mysql_read_query_result +mysql_read_query_result@4 mysql_real_connect +mysql_real_connect@32 mysql_real_escape_string +mysql_real_escape_string@16 mysql_real_query +mysql_real_query@12 mysql_refresh +mysql_refresh@8 mysql_reset_connection +mysql_reset_connection@4 mysql_rollback +mysql_rollback@4 mysql_row_seek +mysql_row_seek@8 mysql_row_tell +mysql_row_tell@4 mysql_select_db +mysql_select_db@8 mysql_send_query +mysql_send_query@12 mysql_server_end +mysql_server_end@0 mysql_server_init +mysql_server_init@12 mysql_session_track_get_first +mysql_session_track_get_first@16 mysql_session_track_get_next +mysql_session_track_get_next@16 mysql_set_character_set +mysql_set_character_set@8 mysql_set_local_infile_default +mysql_set_local_infile_default@4 mysql_set_local_infile_handler +mysql_set_local_infile_handler@24 mysql_set_server_option +mysql_set_server_option@8 mysql_shutdown +mysql_shutdown@8 mysql_sqlstate +mysql_sqlstate@4 mysql_ssl_set +mysql_ssl_set@24 mysql_stat +mysql_stat@4 mysql_stmt_affected_rows +mysql_stmt_affected_rows@4 mysql_stmt_attr_get +mysql_stmt_attr_get@12 mysql_stmt_attr_set +mysql_stmt_attr_set@12 mysql_stmt_bind_param +mysql_stmt_bind_param@8 mysql_stmt_bind_result +mysql_stmt_bind_result@8 mysql_stmt_close +mysql_stmt_close@4 mysql_stmt_data_seek +mysql_stmt_data_seek@12 mysql_stmt_errno +mysql_stmt_errno@4 mysql_stmt_error +mysql_stmt_error@4 mysql_stmt_execute +mysql_stmt_execute@4 mysql_stmt_fetch +mysql_stmt_fetch@4 mysql_stmt_fetch_column +mysql_stmt_fetch_column@16 mysql_stmt_field_count +mysql_stmt_field_count@4 mysql_stmt_free_result +mysql_stmt_free_result@4 mysql_stmt_init +mysql_stmt_init@4 mysql_stmt_insert_id +mysql_stmt_insert_id@4 mysql_stmt_next_result +mysql_stmt_next_result@4 mysql_stmt_num_rows +mysql_stmt_num_rows@4 mysql_stmt_param_count +mysql_stmt_param_count@4 mysql_stmt_param_metadata +mysql_stmt_param_metadata@4 mysql_stmt_prepare +mysql_stmt_prepare@12 mysql_stmt_reset +mysql_stmt_reset@4 mysql_stmt_result_metadata +mysql_stmt_result_metadata@4 mysql_stmt_row_seek +mysql_stmt_row_seek@8 mysql_stmt_row_tell +mysql_stmt_row_tell@4 mysql_stmt_send_long_data +mysql_stmt_send_long_data@16 mysql_stmt_sqlstate +mysql_stmt_sqlstate@4 mysql_stmt_store_result +mysql_stmt_store_result@4 mysql_store_result +mysql_store_result@4 mysql_thread_end +mysql_thread_end@0 mysql_thread_id +mysql_thread_id@4 mysql_thread_init +mysql_thread_init@0 mysql_thread_safe +mysql_thread_safe@0 mysql_use_result +mysql_use_result@4 mysql_warning_count +mysql_warning_count@4 From 7366f9ce1974dae29435ce4afe7e240373ddd893 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 27 Oct 2014 02:57:56 +0200 Subject: [PATCH 177/783] Generate interface library libmysql.a from MySQL Connector/C libmysql.lib and the pre-generated libmysql.def included here --- ext/mysql2/extconf.rb | 47 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 9895f5999..4dbbc1374 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -74,10 +74,6 @@ def asplode lib rpath_dir = lib end -if RUBY_PLATFORM =~ /mswin|mingw/ - exit 1 unless have_library('libmysql') -end - if have_header('mysql.h') prefix = nil elsif have_header('mysql/mysql.h') @@ -127,4 +123,47 @@ def asplode lib end end +if RUBY_PLATFORM =~ /mswin|mingw/ + # Build libmysql.a interface link library + require 'rake' + + # Build libmysql.a interface link library + # Use rake to rebuild only if these files change + deffile = File.expand_path('../../../support/libmysql.def', __FILE__) + libfile = File.expand_path(File.join(libdir, 'libmysql.lib')) + file 'libmysql.a' => [deffile, libfile] do |t| + when_writing 'building libmysql.a' do + sh 'dlltool', '--kill-at', + '--dllname', 'libmysql.dll', + '--output-lib', 'libmysql.a', + '--input-def', deffile, libfile + end + end + + Rake::Task['libmysql.a'].invoke + + # Make sure the generated interface library works + $LOCAL_LIBS << ' ' << 'libmysql.a' + abort "-----\nCannot find libmysql.a\n----" unless have_library('libmysql') + abort "-----\nCannot link to libmysql.a (my_init)\n----" unless have_func('my_init') + + # Vendor libmysql.dll + vendordll = File.expand_path('../../../vendor/libmysql.dll', __FILE__) + dllfile = File.expand_path(File.join(libdir, 'libmysql.dll')) + file vendordll => dllfile do |t| + when_writing 'copying libmysql.dll' do + cp dllfile, vendordll + end + end + + # Copy libmysql.dll to the local vendor directory by default + if arg_config('--no-vendor-libmysql') + # Fine, don't. + puts "--no-vendor-libmysql" + else # Default: arg_config('--vendor-libmysql') + # Let's do it! + Rake::Task[vendordll].invoke + end +end + create_makefile('mysql2/mysql2') From 05ba33edc1b86778e72a5e490621ae6c414edbc6 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 27 Oct 2014 03:28:37 -0700 Subject: [PATCH 178/783] Use env var RUBY_MYSQL2_LIBMYSQL_DLL to find libmysql.dll out of path --- lib/mysql2.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 45d0a54ba..3620febed 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -3,6 +3,26 @@ require 'bigdecimal' require 'rational' unless RUBY_VERSION >= '1.9.2' +# Load libmysql.dll before requiring mysql2/mysql2.so +# This gives a chance to be flexible about the load path +# Or to bomb out with a clear error message instead of a linker crash +if RUBY_PLATFORM =~ /mswin|mingw/ + dll_search = [ + File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\'), + File.expand_path('../libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\'), + 'libmysql.dll' # This will use default / system library paths + ] + + # If this environment variable is set, it overrides any other search paths + dll_search = [ ENV['RUBY_MYSQL2_LIBMYSQL_DLL'].dup ] if ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] + + require 'Win32API' + LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I') + unless dll_search.any? { |dll| 0 != LoadLibrary.call(dll) } + abort "Failed to load libmysql.dll from any of #{dll_search.inspect}" + end +end + require 'mysql2/version' unless defined? Mysql2::VERSION require 'mysql2/error' require 'mysql2/mysql2' From 875a62795e630430205e4e3c3c8aa615c23de310 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 29 Oct 2014 18:33:32 -0700 Subject: [PATCH 179/783] Vendor libmysql.dll by default on Windows. Use rpath only on Unix platforms. --- ext/mysql2/extconf.rb | 62 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 4dbbc1374..26b7c202e 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -94,35 +94,6 @@ def asplode lib $CFLAGS << gcc_flags end -case explicit_rpath = with_config('mysql-rpath') -when true - abort "-----\nOption --with-mysql-rpath must have an argument\n-----" -when false - warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" -when String - # The user gave us a value so use it - rpath_flags = " -Wl,-rpath,#{explicit_rpath}" - warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" - $LDFLAGS << rpath_flags -else - if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] - rpath_flags = " -Wl,-rpath,#{libdir}" - if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) - # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. - warn "-----\nSetting rpath to #{libdir}\n-----" - $LDFLAGS << rpath_flags - else - if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? - # If we got here because try_link failed, warn the user - warn "-----\nDon't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load\n-----" - end - # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. - warn "-----\nSetting libpath to #{libdir}\n-----" - $LIBPATH << libdir unless $LIBPATH.include?(libdir) - end - end -end - if RUBY_PLATFORM =~ /mswin|mingw/ # Build libmysql.a interface link library require 'rake' @@ -130,7 +101,7 @@ def asplode lib # Build libmysql.a interface link library # Use rake to rebuild only if these files change deffile = File.expand_path('../../../support/libmysql.def', __FILE__) - libfile = File.expand_path(File.join(libdir, 'libmysql.lib')) + libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) file 'libmysql.a' => [deffile, libfile] do |t| when_writing 'building libmysql.a' do sh 'dlltool', '--kill-at', @@ -149,7 +120,7 @@ def asplode lib # Vendor libmysql.dll vendordll = File.expand_path('../../../vendor/libmysql.dll', __FILE__) - dllfile = File.expand_path(File.join(libdir, 'libmysql.dll')) + dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) file vendordll => dllfile do |t| when_writing 'copying libmysql.dll' do cp dllfile, vendordll @@ -164,6 +135,35 @@ def asplode lib # Let's do it! Rake::Task[vendordll].invoke end +else + case explicit_rpath = with_config('mysql-rpath') + when true + abort "-----\nOption --with-mysql-rpath must have an argument\n-----" + when false + warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" + when String + # The user gave us a value so use it + rpath_flags = " -Wl,-rpath,#{explicit_rpath}" + warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" + $LDFLAGS << rpath_flags + else + if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] + rpath_flags = " -Wl,-rpath,#{libdir}" + if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) + # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. + warn "-----\nSetting rpath to #{libdir}\n-----" + $LDFLAGS << rpath_flags + else + if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? + # If we got here because try_link failed, warn the user + warn "-----\nDon't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load\n-----" + end + # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. + warn "-----\nSetting libpath to #{libdir}\n-----" + $LIBPATH << libdir unless $LIBPATH.include?(libdir) + end + end + end end create_makefile('mysql2/mysql2') From 17a3dc393e4eb8d86b1195adbd684d2e4d6a37e5 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 15 Nov 2014 23:34:59 -0800 Subject: [PATCH 180/783] Find dlltool from RbConfig and update rake-compiler for cross-compilation --- ext/mysql2/extconf.rb | 15 ++++++++++----- mysql2.gemspec | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 26b7c202e..e0cfbdaa3 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -104,7 +104,10 @@ def asplode lib libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) file 'libmysql.a' => [deffile, libfile] do |t| when_writing 'building libmysql.a' do - sh 'dlltool', '--kill-at', + # Ruby kindly shows us where dllwrap is, but that tool does more than we want. + # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. + dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool') + sh dlltool, '--kill-at', '--dllname', 'libmysql.dll', '--output-lib', 'libmysql.a', '--input-def', deffile, libfile @@ -112,11 +115,13 @@ def asplode lib end Rake::Task['libmysql.a'].invoke - - # Make sure the generated interface library works $LOCAL_LIBS << ' ' << 'libmysql.a' - abort "-----\nCannot find libmysql.a\n----" unless have_library('libmysql') - abort "-----\nCannot link to libmysql.a (my_init)\n----" unless have_func('my_init') + + # Make sure the generated interface library works (if cross-compiling, trust without verifying) + unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + abort "-----\nCannot find libmysql.a\n----" unless have_library('libmysql') + abort "-----\nCannot link to libmysql.a (my_init)\n----" unless have_func('my_init') + end # Vendor libmysql.dll vendordll = File.expand_path('../../../vendor/libmysql.dll', __FILE__) diff --git a/mysql2.gemspec b/mysql2.gemspec index 5b7328dbf..13319dc06 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.test_files = `git ls-files spec examples`.split # tests - s.add_development_dependency 'rake-compiler', '~> 0.8.1' + s.add_development_dependency 'rake-compiler', '~> 0.9.5' s.add_development_dependency 'rake', '~> 0.9.3' s.add_development_dependency 'rspec', '~> 2.8.0' end From 6a1665522f6b24ccb3cdf0ebbc6d00e5c141d283 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 24 Dec 2014 16:03:49 -0800 Subject: [PATCH 181/783] Use AppVeyor cache for gems and bundle --- appveyor.yml | 10 ++++++++++ tasks/vendor_mysql.rake | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0c0c01acd..82a9a0709 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -29,5 +29,15 @@ environment: - ruby_version: "200-x64" - ruby_version: "21" - ruby_version: "21-x64" +cache: + - vendor + - C:\Ruby200\lib\ruby\gems\2.0.0 + - C:\Ruby200\bin + - C:\Ruby200-x64\lib\ruby\gems\2.0.0 + - C:\Ruby200-x64\bin + - C:\Ruby21\lib\ruby\gems\2.1.0 + - C:\Ruby21\bin + - C:\Ruby21-x64\lib\ruby\gems\2.1.0 + - C:\Ruby21-x64\bin services: - mysql diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index 784b185e1..2aa736651 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -22,7 +22,7 @@ file "vendor/#{CONNECTOR_DIR}/include/mysql.h" => ["vendor/#{CONNECTOR_ZIP}"] do full_file = File.expand_path(t.prerequisites.last) when_writing "creating #{t.name}" do cd "vendor" do - sh "unzip #{full_file} #{CONNECTOR_DIR}/bin/** #{CONNECTOR_DIR}/include/** #{CONNECTOR_DIR}/lib/**" + sh "unzip -uq #{full_file} #{CONNECTOR_DIR}/bin/** #{CONNECTOR_DIR}/include/** #{CONNECTOR_DIR}/lib/**" end # update file timestamp to avoid Rake perform this extraction again. touch t.name From 4dec034a2df00849e43017f37e1c4de01f5b1dd5 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 27 Dec 2014 21:02:45 -0800 Subject: [PATCH 182/783] Add AppVeyor CI link to the README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b493e05a4..f42968a63 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Mysql2 - A modern, simple and very fast MySQL library for Ruby - binding to libmysql -[![Build Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2) +Travis CI [![Travis CI Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2) +Appveyor CI [![Appveyor CI Status](https://ci.appveyor.com/api/projects/status/github/sodabrew/mysql2)](https://ci.appveyor.com/project/sodabrew/mysql2) The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results. Some database libraries out there serve as direct 1:1 mappings of the already complex C APIs available. From e4899422a36f57d07b65c1a8171b2edd84b16e41 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 2 Jan 2015 00:32:33 +0000 Subject: [PATCH 183/783] Create the vendor dir if needed --- ext/mysql2/extconf.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index e0cfbdaa3..b05b12b86 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -124,9 +124,12 @@ def asplode lib end # Vendor libmysql.dll - vendordll = File.expand_path('../../../vendor/libmysql.dll', __FILE__) + vendordir = File.expand_path('../../../vendor/', __FILE__) + directory vendordir + + vendordll = File.join(vendordir, 'libmysql.dll') dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) - file vendordll => dllfile do |t| + file vendordll => [dllfile, vendordir] do |t| when_writing 'copying libmysql.dll' do cp dllfile, vendordll end From 2f894f47c4a09f19b576adf50f616a63347be072 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 2 Jan 2015 00:47:22 +0000 Subject: [PATCH 184/783] Update the cross-compile task --- tasks/compile.rake | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/tasks/compile.rake b/tasks/compile.rake index 53248037b..450d1b7a9 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -5,21 +5,31 @@ def gemspec end Rake::ExtensionTask.new("mysql2", gemspec) do |ext| - # Expand the path because the build dir is 3-4 levels deep in tmp/platform/version/ - connector_dir = File.expand_path("../../vendor/#{CONNECTOR_DIR}", __FILE__) - windows_options = [ "--with-mysql-dir=#{connector_dir}" ] + # put binaries into lib/mysql2/ or lib/mysql2/x.y/ + ext.lib_dir = File.join 'lib', 'mysql2' + + # clean compiled extension + CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}" - # automatically add build options to avoid need of manual input if RUBY_PLATFORM =~ /mswin|mingw/ then - ext.config_options = windows_options + Rake::Task['vendor:mysql'].invoke + # Expand the path because the build dir is 3-4 levels deep in tmp/platform/version/ + connector_dir = File.expand_path("../../vendor/#{CONNECTOR_DIR}", __FILE__) + ext.config_options = [ "--with-mysql-dir=#{connector_dir}" ] else ext.cross_compile = true - ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60'] - ext.cross_config_options = windows_options + ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60', 'x64-mingw32'] + ext.cross_config_options = { + 'x86-mingw32' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/mysql-connector-c-#{CONNECTOR_VERSION}-win32", __FILE__) ], + 'x86-mswin32-60' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/mysql-connector-c-#{CONNECTOR_VERSION}-win32", __FILE__) ], + 'x64-mingw32' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/mysql-connector-c-#{CONNECTOR_VERSION}-winx64", __FILE__) ], + } - # inject 1.8/1.9 pure-ruby entry point when cross compiling only ext.cross_compiling do |spec| + Rake::Task['lib/mysql2/mysql2.rb'].invoke + Rake::Task['vendor:mysql'].invoke(spec.platform) spec.files << 'lib/mysql2/mysql2.rb' + spec.files << 'vendor/libmysql.dll' spec.post_install_message = <<-POST_INSTALL_MESSAGE ====================================================================================================== @@ -28,7 +38,7 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| It was built using MySQL Connector/C version #{CONNECTOR_VERSION}. It's recommended to use the exact same version to avoid potential issues. - At the time of building this gem, the necessary DLL files where available + At the time of building this gem, the necessary DLL files were available in the following download: http://dev.mysql.com/get/Downloads/Connector-C/#{CONNECTOR_ZIP}/from/pick @@ -40,11 +50,6 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| POST_INSTALL_MESSAGE end end - - ext.lib_dir = File.join 'lib', 'mysql2' - - # clean compiled extension - CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}" end Rake::Task[:spec].prerequisites << :compile @@ -58,10 +63,6 @@ require "#{name}/\#{$1}/#{name}" end end -if Rake::Task.task_defined?(:cross) - Rake::Task[:cross].prerequisites << "lib/mysql2/mysql2.rb" -end - # DevKit task following the example of Luis Lavena's test-ruby-c-extension task :devkit do begin @@ -74,8 +75,4 @@ end if RUBY_PLATFORM =~ /mingw|mswin/ then Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' Rake::Task['compile'].prerequisites.unshift 'devkit' -else - if Rake::Task.tasks.map {|t| t.name }.include? 'cross' - Rake::Task['cross'].prerequisites.unshift 'vendor:mysql' - end end From 0eade9fef32e1ce2a361392c812df7c379de145b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 7 Jan 2015 14:46:59 -0800 Subject: [PATCH 185/783] Adjust the libmysql.dll search path code --- lib/mysql2.rb | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 3620febed..f4ae8b16f 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -7,19 +7,22 @@ # This gives a chance to be flexible about the load path # Or to bomb out with a clear error message instead of a linker crash if RUBY_PLATFORM =~ /mswin|mingw/ - dll_search = [ - File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\'), - File.expand_path('../libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\'), - 'libmysql.dll' # This will use default / system library paths - ] - - # If this environment variable is set, it overrides any other search paths - dll_search = [ ENV['RUBY_MYSQL2_LIBMYSQL_DLL'].dup ] if ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] + dll_path = if ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] + # If this environment variable is set, it overrides any other paths + # The user is advised to use backslashes not forward slashes + ENV['RUBY_MYSQL2_LIBMYSQL_DLL'].dup + elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))) + # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary + File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\') + else + # This will use default / system library paths + 'libmysql.dll' + end require 'Win32API' LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I') - unless dll_search.any? { |dll| 0 != LoadLibrary.call(dll) } - abort "Failed to load libmysql.dll from any of #{dll_search.inspect}" + if 0 == LoadLibrary.call(dll_path) + abort "Failed to load libmysql.dll from #{dll_path}" end end From df58a81057058b3bafb8e91588514da20e5040d0 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 8 Jan 2015 07:07:05 -0800 Subject: [PATCH 186/783] Wrap vendor:mysql tasks with a platform argument --- tasks/compile.rake | 13 +++---- tasks/vendor_mysql.rake | 76 +++++++++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/tasks/compile.rake b/tasks/compile.rake index 450d1b7a9..9b47ec453 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -14,20 +14,21 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| if RUBY_PLATFORM =~ /mswin|mingw/ then Rake::Task['vendor:mysql'].invoke # Expand the path because the build dir is 3-4 levels deep in tmp/platform/version/ - connector_dir = File.expand_path("../../vendor/#{CONNECTOR_DIR}", __FILE__) + connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) ext.config_options = [ "--with-mysql-dir=#{connector_dir}" ] else + Rake::Task['vendor:mysql'].invoke('x86') + Rake::Task['vendor:mysql'].invoke('x64') ext.cross_compile = true ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60', 'x64-mingw32'] ext.cross_config_options = { - 'x86-mingw32' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/mysql-connector-c-#{CONNECTOR_VERSION}-win32", __FILE__) ], - 'x86-mswin32-60' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/mysql-connector-c-#{CONNECTOR_VERSION}-win32", __FILE__) ], - 'x64-mingw32' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/mysql-connector-c-#{CONNECTOR_VERSION}-winx64", __FILE__) ], + 'x86-mingw32' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__) ], + 'x86-mswin32-60' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__) ], + 'x64-mingw32' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x64')}", __FILE__) ], } ext.cross_compiling do |spec| Rake::Task['lib/mysql2/mysql2.rb'].invoke - Rake::Task['vendor:mysql'].invoke(spec.platform) spec.files << 'lib/mysql2/mysql2.rb' spec.files << 'vendor/libmysql.dll' spec.post_install_message = <<-POST_INSTALL_MESSAGE @@ -41,7 +42,7 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| At the time of building this gem, the necessary DLL files were available in the following download: - http://dev.mysql.com/get/Downloads/Connector-C/#{CONNECTOR_ZIP}/from/pick + #{vendor_mysql_url(http://github.com/spec.platform)} And put lib\\libmysql.dll file in your Ruby bin directory, for example C:\\Ruby\\bin diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index 2aa736651..b521300d3 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -1,36 +1,60 @@ require 'rake/clean' require 'rake/extensioncompiler' -CONNECTOR_VERSION = "6.1.5" #"mysql-connector-c-6.1.5-win32.zip" -CONNECTOR_PLATFORM = RUBY_PLATFORM =~ /x64/ ? "winx64" : "win32" -CONNECTOR_DIR = "mysql-connector-c-#{CONNECTOR_VERSION}-#{CONNECTOR_PLATFORM}" -CONNECTOR_ZIP = "mysql-connector-c-#{CONNECTOR_VERSION}-#{CONNECTOR_PLATFORM}.zip" - -# download mysql library and headers -directory "vendor" - -file "vendor/#{CONNECTOR_ZIP}" => ["vendor"] do |t| - url = "http://cdn.mysql.com/Downloads/Connector-C/#{CONNECTOR_ZIP}" - when_writing "downloading #{t.name}" do - cd File.dirname(t.name) do - sh "curl -C - -O #{url} || wget -c #{url}" +CONNECTOR_VERSION = "6.1.5" # NOTE: Track the upstream version from time to time + +def vendor_mysql_platform(platform=nil) + platform ||= RUBY_PLATFORM + platform =~ /x64/ ? "winx64" : "win32" +end + +def vendor_mysql_dir(*args) + "mysql-connector-c-#{CONNECTOR_VERSION}-#{vendor_mysql_platform(*args)}" +end + +def vendor_mysql_zip(*args) + "#{vendor_mysql_dir(*args)}.zip" +end + +def vendor_mysql_url(http://github.com/*args) + "http://cdn.mysql.com/Downloads/Connector-C/#{vendor_mysql_zip(*args)}" +end + +# vendor:mysql +task "vendor:mysql", [:platform] do |t, args| + puts "vendor:mysql for #{vendor_mysql_dir(args[:platform])}" + + # download mysql library and headers + directory "vendor" + + file "vendor/#{vendor_mysql_zip(args[:platform])}" => ["vendor"] do |t| + url = vendor_mysql_url(http://github.com/args[:platform]) + when_writing "downloading #{t.name}" do + cd "vendor" do + sh "curl", "-C", "-", "-O", url do |ok, res| + sh "wget", "-c", url if ! ok + end + end end end -end -file "vendor/#{CONNECTOR_DIR}/include/mysql.h" => ["vendor/#{CONNECTOR_ZIP}"] do |t| - full_file = File.expand_path(t.prerequisites.last) - when_writing "creating #{t.name}" do - cd "vendor" do - sh "unzip -uq #{full_file} #{CONNECTOR_DIR}/bin/** #{CONNECTOR_DIR}/include/** #{CONNECTOR_DIR}/lib/**" + file "vendor/#{vendor_mysql_dir(args[:platform])}/include/mysql.h" => ["vendor/#{vendor_mysql_zip(args[:platform])}"] do |t| + full_file = File.expand_path(t.prerequisites.last) + when_writing "creating #{t.name}" do + cd "vendor" do + sh "unzip", "-uq", full_file, + "#{vendor_mysql_dir(args[:platform])}/bin/**", + "#{vendor_mysql_dir(args[:platform])}/include/**", + "#{vendor_mysql_dir(args[:platform])}/lib/**" + end + # update file timestamp to avoid Rake perform this extraction again. + touch t.name end - # update file timestamp to avoid Rake perform this extraction again. - touch t.name end -end -# clobber expanded packages -CLOBBER.include("vendor/#{CONNECTOR_DIR}") + # clobber expanded packages + CLOBBER.include("vendor/#{vendor_mysql_dir(args[:platform])}") -# vendor:mysql -task 'vendor:mysql' => "vendor/#{CONNECTOR_DIR}/include/mysql.h" + Rake::Task["vendor/#{vendor_mysql_dir(args[:platform])}/include/mysql.h"].invoke + Rake::Task["vendor:mysql"].reenable # allow task to be invoked again (with another platform) +end From f53219f8b8a6739a421d8dff9a4fe971a86b9f8b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 9 Jan 2015 10:14:40 -0800 Subject: [PATCH 187/783] Update the Windows section of the README --- README.md | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f42968a63..7620058d2 100644 --- a/README.md +++ b/README.md @@ -53,24 +53,21 @@ are located somewhere different than on your build system. This overrides any rpath calculated by default or by the options above. ### Windows -First, make sure you have the DevKit installed (http://rubyinstaller.org/downloads/) and its variables -are loaded by running devkit\devktvars.bat . +Make sure that you have Ruby and the DevKit compilers installed. We recommend +the [Ruby Installer](http://rubyinstaller.org) distribution. -Next, you need a MySQL library to link against. If you have MySQL loaded on your development machine, -you can use that. If not, you will need to either copy the MySQL directory from your server, or else -obtain a copy of the MySQL C connector: http://dev.mysql.com/downloads/connector/c/ +By default, the mysql2 gem will download and use MySQL Connector/C from +mysql.com. If you prefer to use a local installation of Connector/C, add the +flag `--with-mysql-dir=c:/mysql-connector-c-x-y-z` (_this path may use forward slashes_). -If you're using the connector, I recommend just getting the .zip file and unzipping it someplace convenient. +By default, the `libmysql.dll` library will be copied into the mysql2 gem +directory. To prevent this, add the flag `--no-vendor-libmysql`. The mysql2 gem +will search for `libmysql.dll` in the following paths, in order: -Now you can install mysql2. You must use the `--with-mysql-dir` option to tell gem where your MySQL library -files are. For example, if you unzipped the connector to c:\mysql-connector-c-6.1.1-win32 you would install -the gem like this: - - gem install mysql2 -- --with-mysql-dir=c:\mysql-connector-c-6.1.1-win32 - -Finally, you must copy libmysql.dll from the lib subdirectory of your MySQL or MySQL connector directory into -your ruby\bin directory. In the above example, libmysql.dll would be located at -c:\mysql-connector-c-6.1.1-win32\lib . +* Environment variable `RUBY_MYSQL2_LIBMYSQL_DLL=C:\path\to\libmysql.dll` + (_note the Windows-style backslashes_). +* In the mysql2 gem's own directory `vendor/libmysql.dll` +* In the system's default library search paths. ## Usage From f05442458bc23bbb49cf17ae98613e23a655ce2b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 6 Feb 2015 22:58:31 -0800 Subject: [PATCH 188/783] Update README as suggested by @ain for OS X and Linux installation notes --- README.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7620058d2..6ed86dd3b 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,16 @@ The API consists of two classes: `Mysql2::Result` - returned from issuing a #query on the connection. It includes Enumerable. ## Installing -### OSX / Linux +### General Instructions ``` sh gem install mysql2 ``` -This gem links against MySQL's `libmysqlclient` C shared library. You may need to install a package such as `libmysqlclient-dev`, `mysql-devel`, or other appropriate package for your system. +This gem links against MySQL's `libmysqlclient` library or `Connector/C` +library, and compatible alternatives such as MariaDB. +You may need to install a package such as `libmysqlclient-dev`, `mysql-devel`, +or other appropriate package for your system. See below for system-specific +instructions. By default, the mysql2 gem will try to find a copy of MySQL in this order: @@ -52,6 +56,19 @@ This may be needed if you deploy to a system where these libraries are located somewhere different than on your build system. This overrides any rpath calculated by default or by the options above. +### Linux and other Unixes + +You may need to install a package such as `libmysqlclient-dev` or `mysql-devel`; +refer to your distribution's package guide to find the particular package. +The most common issue we see is a user who has the library file `libmysqlclient.so` but is +missing the header file `mysql.h` -- double check that you have the _-dev_ packages installed. + +### Mac OS X + +You may use MacPorts, Homebrew, or a native MySQL installer package. The most +common paths will be automatically searched. If you want to select a specific +MySQL directory, use the `--with-mysql-dir` or `--with-mysql-config` options above. + ### Windows Make sure that you have Ruby and the DevKit compilers installed. We recommend the [Ruby Installer](http://rubyinstaller.org) distribution. @@ -420,13 +437,13 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.x (ongoing patch releases) + * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.x, 2.2.x (ongoing patch releases) * Ruby Enterprise Edition (based on MRI 1.8.7) * Rubinius 2.x This gem is tested with the following MySQL and MariaDB versions: - * MySQL 5.0, 5.1, 5.5, 5.6 + * MySQL 5.0, 5.1, 5.5, 5.6, 5.7 * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) * MariaDB 5.5, 10.0 From 0ee177c95a7ff802f9ad9958a45975fdc5b76b4a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 6 Feb 2015 22:58:46 -0800 Subject: [PATCH 189/783] Search more paths for MySQL installations in the 5.x series --- ext/mysql2/extconf.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index b05b12b86..ca0e2c83f 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -20,13 +20,13 @@ def asplode lib /opt /opt/local /opt/local/mysql - /opt/local/lib/mysql5 + /opt/local/lib/mysql5* /usr /usr/mysql /usr/local /usr/local/mysql /usr/local/mysql-* - /usr/local/lib/mysql5 + /usr/local/lib/mysql5* ].map{|dir| "#{dir}/bin" } GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}" From 0fa2721e2fec0414b87fef9704cb7c921c05c8ae Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 6 Feb 2015 23:50:56 -0800 Subject: [PATCH 190/783] On AppVeyor, Bundle gems in vendor/bundle rather than caching several C:\Ruby paths --- appveyor.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 82a9a0709..26eb1f264 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ install: - gem --version - gem install bundler --quiet --no-ri --no-rdoc - bundler --version - - bundle install --without benchmarks + - bundle install --without benchmarks --path vendor/bundle build_script: - bundle exec rake compile test_script: @@ -31,13 +31,5 @@ environment: - ruby_version: "21-x64" cache: - vendor - - C:\Ruby200\lib\ruby\gems\2.0.0 - - C:\Ruby200\bin - - C:\Ruby200-x64\lib\ruby\gems\2.0.0 - - C:\Ruby200-x64\bin - - C:\Ruby21\lib\ruby\gems\2.1.0 - - C:\Ruby21\bin - - C:\Ruby21-x64\lib\ruby\gems\2.1.0 - - C:\Ruby21-x64\bin services: - mysql From 1fcc1e9226f743ed9380c8cfa34633069688d3d8 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 6 Feb 2015 22:40:39 -0800 Subject: [PATCH 191/783] Three fixes to Windows build errors since #473 Fix syntax for cross_config_options. Only invoke vendor:mysql when compiling for Windows. Only invoke vendor:mysql twice when cross-compiling for Windows. --- tasks/compile.rake | 15 ++++++++------- tasks/vendor_mysql.rake | 6 ++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/tasks/compile.rake b/tasks/compile.rake index 9b47ec453..39cb7f4f7 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -12,19 +12,16 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}" if RUBY_PLATFORM =~ /mswin|mingw/ then - Rake::Task['vendor:mysql'].invoke # Expand the path because the build dir is 3-4 levels deep in tmp/platform/version/ connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) ext.config_options = [ "--with-mysql-dir=#{connector_dir}" ] else - Rake::Task['vendor:mysql'].invoke('x86') - Rake::Task['vendor:mysql'].invoke('x64') ext.cross_compile = true ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60', 'x64-mingw32'] - ext.cross_config_options = { - 'x86-mingw32' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__) ], - 'x86-mswin32-60' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__) ], - 'x64-mingw32' => [ "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x64')}", __FILE__) ], + ext.cross_config_options << { + 'x86-mingw32' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__), + 'x86-mswin32-60' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__), + 'x64-mingw32' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x64')}", __FILE__), } ext.cross_compiling do |spec| @@ -76,4 +73,8 @@ end if RUBY_PLATFORM =~ /mingw|mswin/ then Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' Rake::Task['compile'].prerequisites.unshift 'devkit' +else + if Rake::Task.tasks.map {|t| t.name }.include? 'cross' + Rake::Task['cross'].prerequisites.unshift 'vendor:mysql:cross' + end end diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index b521300d3..c0ea1e0f5 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -21,6 +21,12 @@ def vendor_mysql_url(http://github.com/*args) end # vendor:mysql +task "vendor:mysql:cross" do |t| + # When cross-compiling, grab both 32 and 64 bit connectors + Rake::Task['vendor:mysql'].invoke('x86') + Rake::Task['vendor:mysql'].invoke('x64') +end + task "vendor:mysql", [:platform] do |t, args| puts "vendor:mysql for #{vendor_mysql_dir(args[:platform])}" From 14855b9dd877cba4d57bde43c8bd13ecf0038f47 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Tue, 10 Feb 2015 17:25:02 +0900 Subject: [PATCH 192/783] Fix msec is not passed --- ext/mysql2/result.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 5b8a5b162..9054ed545 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -297,7 +297,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo break; } msec = msec_char_to_uint(msec_char, sizeof(msec_char)); - val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); + val = rb_funcall(rb_cTime, db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); if (!NIL_P(app_timezone)) { if (app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); From 9e948adac04e912ee06f352cac1f81537a7b597e Mon Sep 17 00:00:00 2001 From: Vladimir Kochnev Date: Tue, 10 Feb 2015 15:58:20 +0300 Subject: [PATCH 193/783] reimplement #509, add self-explaining specs --- lib/mysql2/em.rb | 11 ++++++++++- spec/em/em_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb index 5a6449901..b21265994 100644 --- a/lib/mysql2/em.rb +++ b/lib/mysql2/em.rb @@ -10,6 +10,7 @@ module Watcher def initialize(client, deferable) @client = client @deferable = deferable + @is_watching = true end def notify_readable @@ -22,11 +23,19 @@ def notify_readable @deferable.succeed(result) end end + + def watching? + @is_watching + end + + def unbind + @is_watching = false + end end def close(*args) if @watch - @watch.detach + @watch.detach if @watch.watching? end super(*args) end diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 7bead7c34..3f26f47d3 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -108,6 +108,27 @@ callbacks_run.should == [:errback] end end + + it "should not raise error when closing client with no query running" do + callbacks_run = [] + EM.run do + client = Mysql2::EM::Client.new DatabaseCredentials['root'] + defer = client.query("select sleep(0.025)") + defer.callback do |result| + callbacks_run << :callback + end + defer.errback do |err| + callbacks_run << :errback + end + EM.add_timer(0.1) do + lambda { + client.close + }.should_not raise_error(/invalid binding to detach/) + EM.stop_event_loop + end + end + callbacks_run.should == [:callback] + end end rescue LoadError puts "EventMachine not installed, skipping the specs that use it" From 7f0d3625c18573e9ee049334da86d2d843c582e5 Mon Sep 17 00:00:00 2001 From: Vladimir Kochnev Date: Tue, 10 Feb 2015 17:11:21 +0300 Subject: [PATCH 194/783] match callbacks_run inside event loop --- spec/em/em_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 3f26f47d3..01d162b0c 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -121,13 +121,13 @@ callbacks_run << :errback end EM.add_timer(0.1) do + callbacks_run.should == [:callback] lambda { client.close }.should_not raise_error(/invalid binding to detach/) EM.stop_event_loop end end - callbacks_run.should == [:callback] end end rescue LoadError From dd9c0e4e0b23cdd84ed0895bb00e9e16e8c38644 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 13 Feb 2015 16:18:43 -0800 Subject: [PATCH 195/783] Whitespace --- ext/mysql2/client.c | 4 ++-- ext/mysql2/result.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 85dfc65a8..f5fdbbd8a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -438,7 +438,7 @@ static void *nogvl_do_result(void *ptr, char use_result) { MYSQL_RES *result; wrapper = (mysql_client_wrapper *)ptr; - if(use_result) { + if (use_result) { result = mysql_use_result(wrapper->client); } else { result = mysql_store_result(wrapper->client); @@ -483,7 +483,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { } is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream); - if(is_streaming == Qtrue) { + if (is_streaming == Qtrue) { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0); } else { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 9054ed545..54dbc1350 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -232,7 +232,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo VALUE val = Qnil; enum enum_field_types type = fields[i].type; - if(!cast) { + if (!cast) { if (type == MYSQL_TYPE_NULL) { val = Qnil; } else { @@ -479,11 +479,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { cast = 0; } - if(rb_hash_aref(opts, sym_stream) == Qtrue) { + if (rb_hash_aref(opts, sym_stream) == Qtrue) { streaming = 1; } - if(streaming && cacheRows) { + if (streaming && cacheRows) { rb_warn("cacheRows is ignored if streaming is true"); } @@ -601,7 +601,7 @@ static VALUE rb_mysql_result_count(VALUE self) { mysql2_result_wrapper *wrapper; GetMysql2Result(self, wrapper); - if(wrapper->resultFreed) { + if (wrapper->resultFreed) { if (wrapper->streamingComplete){ return LONG2NUM(wrapper->numberOfRows); } else { From 2622dc0edb309f950a5515dd84ecb8510af97aae Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 13 Feb 2015 16:18:53 -0800 Subject: [PATCH 196/783] Capitalization --- spec/mysql2/client_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 50cb1571c..27aaa690c 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -564,7 +564,7 @@ def connect *args end it "should raise an exception when one of multiple statements fails" do - result = @multi_client.query("SELECT 1 as 'set_1'; SELECT * FROM invalid_table_name;SELECT 2 as 'set_2';") + result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';") result.first['set_1'].should be(1) lambda { @multi_client.next_result @@ -573,7 +573,7 @@ def connect *args end it "returns multiple result sets" do - @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'").first.should eql({ 'set_1' => 1 }) + @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first.should eql({ 'set_1' => 1 }) @multi_client.next_result.should be_true @multi_client.store_result.first.should eql({ 'set_2' => 2 }) @@ -582,12 +582,12 @@ def connect *args end it "does not interfere with other statements" do - @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'") + @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'") while( @multi_client.next_result ) @multi_client.store_result end - @multi_client.query( "select 3 as 'next'").first.should == { 'next' => 3 } + @multi_client.query("SELECT 3 AS 'next'").first.should == { 'next' => 3 } end it "will raise on query if there are outstanding results to read" do @@ -606,7 +606,7 @@ def connect *args end it "#more_results? should work" do - @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'") + @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'") @multi_client.more_results?.should be_true @multi_client.next_result From dd63399bd439f30799615909514686b101cfc3e2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 16 Feb 2015 16:26:11 -0800 Subject: [PATCH 197/783] Bump version to 0.3.18 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index db2dda032..303c549ce 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.3.17" + VERSION = "0.3.18" end From 41ab20a5fea2fe8643542507bc93be8f00974b01 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 20 Feb 2015 22:29:40 -0800 Subject: [PATCH 198/783] Local changes used to cross-compile the 0.3.18 gem --- tasks/compile.rake | 16 ++++++++++++---- tasks/vendor_mysql.rake | 5 +++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tasks/compile.rake b/tasks/compile.rake index 39cb7f4f7..473ad67c9 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -26,8 +26,11 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| ext.cross_compiling do |spec| Rake::Task['lib/mysql2/mysql2.rb'].invoke + # vendor/libmysql.dll is invoked from extconf.rb + Rake::Task['vendor/README'].invoke spec.files << 'lib/mysql2/mysql2.rb' spec.files << 'vendor/libmysql.dll' + spec.files << 'vendor/README' spec.post_install_message = <<-POST_INSTALL_MESSAGE ====================================================================================================== @@ -36,12 +39,10 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| It was built using MySQL Connector/C version #{CONNECTOR_VERSION}. It's recommended to use the exact same version to avoid potential issues. - At the time of building this gem, the necessary DLL files were available - in the following download: - + At the time of building this gem, the necessary DLL files were retrieved from: #{vendor_mysql_url(http://github.com/spec.platform)} - And put lib\\libmysql.dll file in your Ruby bin directory, for example C:\\Ruby\\bin + This gem *includes* vendor/libmysql.dll with redistribution notice in vendor/README. ====================================================================================================== @@ -51,6 +52,13 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| end Rake::Task[:spec].prerequisites << :compile +file 'vendor/README' do |t| + connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) + when_writing 'copying Connector/C README' do + cp "#{connector_dir}/README", 'vendor/README' + end +end + file 'lib/mysql2/mysql2.rb' do |t| name = gemspec.name File.open(t.name, 'wb') do |f| diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index c0ea1e0f5..a2d94b39d 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -51,9 +51,10 @@ task "vendor:mysql", [:platform] do |t, args| sh "unzip", "-uq", full_file, "#{vendor_mysql_dir(args[:platform])}/bin/**", "#{vendor_mysql_dir(args[:platform])}/include/**", - "#{vendor_mysql_dir(args[:platform])}/lib/**" + "#{vendor_mysql_dir(args[:platform])}/lib/**", + "#{vendor_mysql_dir(args[:platform])}/README" # contains the license info end - # update file timestamp to avoid Rake perform this extraction again. + # update file timestamp to avoid Rake performing this extraction again. touch t.name end end From 2a7d09d288ccb6720e4ffb3608e708c33b56ae44 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 13 Feb 2015 16:19:17 -0800 Subject: [PATCH 199/783] Tests for streaming and stored procedures with multiple result sets --- spec/mysql2/client_spec.rb | 15 +++++++++++++++ spec/mysql2/result_spec.rb | 13 +++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 27aaa690c..7a0737c52 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -614,6 +614,21 @@ def connect *args @multi_client.more_results?.should be_false end + + it "#more_results? should work with stored procedures" do + @multi_client.query("DROP PROCEDURE IF EXISTS test_proc") + @multi_client.query("CREATE PROCEDURE test_proc() BEGIN SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'; END") + @multi_client.query("CALL test_proc()").first.should eql({ 'set_1' => 1 }) + @multi_client.more_results?.should be_true + + @multi_client.next_result + @multi_client.store_result.first.should eql({ 'set_2' => 2 }) + + @multi_client.next_result + @multi_client.store_result.should be_nil # this is the result from CALL itself + + @multi_client.more_results?.should be_false + end end end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 964bc43b1..0b0219c89 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -107,18 +107,19 @@ context "streaming" do it "should maintain a count while streaming" do - result = @client.query('SELECT 1') - - result.count.should eql(1) + result = @client.query('SELECT 1', :stream => true, :cache_rows => false) + result.count.should eql(0) result.each.to_a result.count.should eql(1) end - it "should set the actual count of rows after streaming" do - result = @client.query("SELECT * FROM mysql2_test", :stream => true, :cache_rows => false) + it "should retain the count when mixing first and each" do + result = @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) result.count.should eql(0) - result.each {|r| } + result.first result.count.should eql(1) + result.each.to_a + result.count.should eql(2) end it "should not yield nil at the end of streaming" do From 70bf629a7977b2046d7c29c046c6d89286babc82 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 13 Feb 2015 17:12:50 -0800 Subject: [PATCH 200/783] Since streaming is a query option, process it in rb_mysql_result_to_obj instead of rb_mysql_result_each --- ext/mysql2/result.c | 26 +++++++++++++------------- ext/mysql2/result.h | 1 + 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 54dbc1350..332fdefb3 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -446,7 +446,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { mysql2_result_wrapper * wrapper; unsigned long i; const char * errstr; - int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1, streaming = 0; + int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1; MYSQL_FIELD * fields = NULL; GetMysql2Result(self, wrapper); @@ -479,11 +479,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { cast = 0; } - if (rb_hash_aref(opts, sym_stream) == Qtrue) { - streaming = 1; - } - - if (streaming && cacheRows) { + if (wrapper->is_streaming && cacheRows) { rb_warn("cacheRows is ignored if streaming is true"); } @@ -509,7 +505,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } if (wrapper->lastRowProcessed == 0) { - if (streaming) { + if (wrapper->is_streaming) { /* We can't get number of rows if we're streaming, */ /* until we've finished fetching all rows */ wrapper->numberOfRows = 0; @@ -524,7 +520,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } } - if (streaming) { + if (wrapper->is_streaming) { if (!wrapper->streamingComplete) { VALUE row; @@ -601,12 +597,12 @@ static VALUE rb_mysql_result_count(VALUE self) { mysql2_result_wrapper *wrapper; GetMysql2Result(self, wrapper); + if (wrapper->is_streaming) { + return LONG2NUM(wrapper->numberOfRows); + } + if (wrapper->resultFreed) { - if (wrapper->streamingComplete){ - return LONG2NUM(wrapper->numberOfRows); - } else { - return LONG2NUM(RARRAY_LEN(wrapper->rows)); - } + return LONG2NUM(RARRAY_LEN(wrapper->rows)); } else { return INT2FIX(mysql_num_rows(wrapper->result)); } @@ -634,6 +630,10 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ rb_iv_set(obj, "@query_options", options); + /* Options that cannot be changed in results.each(...) { |row| } + * should be processed here. */ + wrapper->is_streaming = (rb_hash_aref(options, sym_stream) == Qtrue ? 1 : 0); + return obj; } diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 2bb62077e..0bd131d34 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -12,6 +12,7 @@ typedef struct { unsigned int numberOfFields; unsigned long numberOfRows; unsigned long lastRowProcessed; + char is_streaming; char streamingComplete; char resultFreed; MYSQL_RES *result; From 5ae03be33358b8d8b02185406e369d2358a687e2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 13 Feb 2015 17:33:04 -0800 Subject: [PATCH 201/783] When streaming results, continue result.count across calls to result.each Generally .each is passed a block, but handle if it isn't. Important because Enumerator implements its methods in terms of each. --- ext/mysql2/result.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 332fdefb3..ff2575d35 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -504,23 +504,12 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { app_timezone = Qnil; } - if (wrapper->lastRowProcessed == 0) { - if (wrapper->is_streaming) { - /* We can't get number of rows if we're streaming, */ - /* until we've finished fetching all rows */ - wrapper->numberOfRows = 0; + if (wrapper->is_streaming) { + /* When streaming, we will only yield rows, not return them. */ + if (wrapper->rows == Qnil) { wrapper->rows = rb_ary_new(); - } else { - wrapper->numberOfRows = mysql_num_rows(wrapper->result); - if (wrapper->numberOfRows == 0) { - wrapper->rows = rb_ary_new(); - return wrapper->rows; - } - wrapper->rows = rb_ary_new2(wrapper->numberOfRows); } - } - if (wrapper->is_streaming) { if (!wrapper->streamingComplete) { VALUE row; @@ -528,16 +517,15 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { do { row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields); - - if (block != Qnil && row != Qnil) { - rb_yield(row); - wrapper->lastRowProcessed++; + if (row != Qnil) { + wrapper->numberOfRows++; + if (block != Qnil) { + rb_yield(row); + } } } while(row != Qnil); rb_mysql_result_free_result(wrapper); - - wrapper->numberOfRows = wrapper->lastRowProcessed; wrapper->streamingComplete = 1; // Check for errors, the connection might have gone out from under us @@ -550,6 +538,15 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery)."); } } else { + if (wrapper->lastRowProcessed == 0) { + wrapper->numberOfRows = mysql_num_rows(wrapper->result); + if (wrapper->numberOfRows == 0) { + wrapper->rows = rb_ary_new(); + return wrapper->rows; + } + wrapper->rows = rb_ary_new2(wrapper->numberOfRows); + } + if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) { /* we've already read the entire dataset from the C result into our */ /* internal array. Lets hand that over to the user since it's ready to go */ From e78d4969eef34c0ffdd533fc599f716091c2a02d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 14 Feb 2015 00:22:10 -0800 Subject: [PATCH 202/783] Make the result.count return type consistent and match the source type numberOfRows is a platform unsigned long Ruby array lenth is a platform signed long mysql_num_rows returns an unsigned 64-bit long --- ext/mysql2/result.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index ff2575d35..4fdf02cc5 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -595,13 +595,16 @@ static VALUE rb_mysql_result_count(VALUE self) { GetMysql2Result(self, wrapper); if (wrapper->is_streaming) { - return LONG2NUM(wrapper->numberOfRows); + /* This is an unsigned long per result.h */ + return ULONG2NUM(wrapper->numberOfRows); } if (wrapper->resultFreed) { + /* Ruby arrays have platform signed long length */ return LONG2NUM(RARRAY_LEN(wrapper->rows)); } else { - return INT2FIX(mysql_num_rows(wrapper->result)); + /* MySQL returns an unsigned 64-bit long here */ + return ULL2NUM(mysql_num_rows(wrapper->result)); } } From ba5c5a023fbc9327aba0dd6aeb9cd28361e6b67a Mon Sep 17 00:00:00 2001 From: juanxo Date: Thu, 16 Jan 2014 01:40:55 +0100 Subject: [PATCH 203/783] Refactor branching to assignment Cherry-picked from #479 --- ext/mysql2/result.c | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 4fdf02cc5..0a0ac4cda 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -446,7 +446,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { mysql2_result_wrapper * wrapper; unsigned long i; const char * errstr; - int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1; + int symbolizeKeys, asArray, castBool, cacheRows, cast; MYSQL_FIELD * fields = NULL; GetMysql2Result(self, wrapper); @@ -459,25 +459,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { opts = defaults; } - if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) { - symbolizeKeys = 1; - } - - if (rb_hash_aref(opts, sym_as) == sym_array) { - asArray = 1; - } - - if (rb_hash_aref(opts, sym_cast_booleans) == Qtrue) { - castBool = 1; - } - - if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) { - cacheRows = 0; - } - - if (rb_hash_aref(opts, sym_cast) == Qfalse) { - cast = 0; - } + symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys)); + asArray = rb_hash_aref(opts, sym_as) == sym_array; + castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans)); + cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows)); + cast = RTEST(rb_hash_aref(opts, sym_cast)); if (wrapper->is_streaming && cacheRows) { rb_warn("cacheRows is ignored if streaming is true"); From 9877f9663d351c0c2e4db23cfdf6eefc20cf5b90 Mon Sep 17 00:00:00 2001 From: Kir Shatrov Date: Wed, 11 Mar 2015 18:24:47 +0200 Subject: [PATCH 204/783] Friendly ext build error <3 --- ext/mysql2/extconf.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index ca0e2c83f..f5c9aecfc 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -2,7 +2,14 @@ require 'mkmf' def asplode lib - abort "-----\n#{lib} is missing. please check your installation of mysql and try again.\n-----" + if RUBY_PLATFORM =~ /mingw|mswin/ + abort "-----\n#{lib} is missing. please check your installation of mysql and try again.\n-----" + elsif RUBY_PLATFORM =~ /darwin/ + abort "-----\n#{lib} is missing. Try 'brew install mysql', check your installation of mysql and try again.\n-----" + else + abort "-----\n#{lib} is missing. Try 'apt-get install libmysqlclient-dev' or +'yum install mysql-devel', check your installation of mysql and try again.\n-----" + end end # 2.0-only @@ -67,10 +74,14 @@ def asplode lib else inc, lib = dir_config('mysql', '/usr/local') libs = ['m', 'z', 'socket', 'nsl', 'mygcc'] + found = false while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do exit 1 if libs.empty? - have_library(libs.shift) + found ||= have_library(libs.shift) end + + asplode("mysql client") unless found + rpath_dir = lib end From a0372f4e778e2c3b0a80efd0f48b5e11dcbae6dd Mon Sep 17 00:00:00 2001 From: Ryan Stenhouse Date: Tue, 31 Mar 2015 14:45:13 +0900 Subject: [PATCH 205/783] Really silly typo correction contetx -> context --- lib/mysql2/error.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 8e6ed5b94..748abf022 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -30,7 +30,7 @@ def sql_state=(state) # variable. # # See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for - # more contetx. + # more context. # # Before MySQL 5.5 error message template strings are in whatever encoding # is associated with the error message language. From 147c2e581a5a7e65595b89d9dea3ce95e97bc23c Mon Sep 17 00:00:00 2001 From: Dominic Cleal Date: Wed, 27 May 2015 14:03:03 +0100 Subject: [PATCH 206/783] Fix LICENSE filename in gem file list --- mysql2.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql2.gemspec b/mysql2.gemspec index 13319dc06..e9c3a0176 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.rdoc_options = ["--charset=UTF-8"] s.summary = %q{A simple, fast Mysql library for Ruby, binding to libmysql} - s.files = `git ls-files README.md CHANGELOG.md MIT-LICENSE ext lib support`.split + s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split s.test_files = `git ls-files spec examples`.split # tests From a68eb1954ccfaa0caf18c063bb573eb1b09b5a33 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Sun, 2 Nov 2014 01:02:43 +0100 Subject: [PATCH 207/783] init prepared statements (adapted from tenderlove/2ad51dc) --- ext/mysql2/client.c | 54 +++++++++++++++++++++++++++++++++++ ext/mysql2/mysql2_ext.c | 1 + ext/mysql2/mysql2_ext.h | 1 + ext/mysql2/statement.c | 9 ++++++ ext/mysql2/statement.h | 8 ++++++ spec/mysql2/statement_spec.rb | 19 ++++++++++++ 6 files changed, 92 insertions(+) create mode 100644 ext/mysql2/statement.c create mode 100644 ext/mysql2/statement.h create mode 100644 spec/mysql2/statement_spec.rb diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index f5fdbbd8a..fb97e98aa 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -87,6 +87,16 @@ struct nogvl_send_query_args { mysql_client_wrapper *wrapper; }; +/* + * used to pass all arguments to mysql_stmt_prepare while inside + * rb_thread_call_without_gvl + */ +struct nogvl_prepare_statement_args { + MYSQL_STMT *stmt; + const char *sql; + unsigned long sql_len; +}; + /* * used to pass all arguments to mysql_select_db while inside * rb_thread_call_without_gvl @@ -1220,6 +1230,49 @@ static VALUE initialize_ext(VALUE self) { return self; } +static void *nogvl_prepare_statement(void *ptr) { + struct nogvl_prepare_statement_args *args = ptr; + + if (mysql_stmt_prepare(args->stmt, args->sql, args->sql_len)) { + return (void*)Qfalse; + } else { + return (void*)Qtrue; + } +} + +/* call-seq: client.prepare # => Mysql2::Statement + * + * Create a new prepared statement. + */ +static VALUE prepare_statement(VALUE self, VALUE sql) { + GET_CLIENT(self); + struct nogvl_prepare_statement_args args; + MYSQL_STMT *stmt; + VALUE rb_stmt; + my_bool truth = 1; + + stmt = mysql_stmt_init(wrapper->client); + if (stmt == NULL) { + rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory"); + } + + if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) { + rb_raise(cMysql2Error, "Unable to initialize prepared statement"); + } + + rb_stmt = Data_Wrap_Struct(cMysql2Statement, 0, mysql_stmt_close, stmt); + + args.stmt = stmt; + args.sql = StringValuePtr(sql); + args.sql_len = RSTRING_LEN(sql); + + if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + } + + return rb_stmt; +} + void init_mysql2_client() { /* verify the libmysql we're about to use was the version we were built against https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */ @@ -1265,6 +1318,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0); rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0); rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0); + rb_define_method(cMysql2Client, "prepare", prepare_statement, 1); rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0); rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0); rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1); diff --git a/ext/mysql2/mysql2_ext.c b/ext/mysql2/mysql2_ext.c index dcb72f377..a6fe365ac 100644 --- a/ext/mysql2/mysql2_ext.c +++ b/ext/mysql2/mysql2_ext.c @@ -9,4 +9,5 @@ void Init_mysql2() { init_mysql2_client(); init_mysql2_result(); + init_mysql2_statement(); } diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index d6d5fd626..ec781194d 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -40,6 +40,7 @@ typedef unsigned int uint; #include #include +#include #include #endif diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c new file mode 100644 index 000000000..ed33389e3 --- /dev/null +++ b/ext/mysql2/statement.c @@ -0,0 +1,9 @@ +#include + +VALUE cMysql2Statement; +extern VALUE mMysql2, cMysql2Error; + +void init_mysql2_statement() +{ + cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); +} diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h new file mode 100644 index 000000000..cfea18939 --- /dev/null +++ b/ext/mysql2/statement.h @@ -0,0 +1,8 @@ +#ifndef MYSQL2_STATEMENT_H +#define MYSQL2_STATEMENT_H + +extern VALUE cMysql2Statement; + +void init_mysql2_statement(); + +#endif diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb new file mode 100644 index 000000000..dee37fa63 --- /dev/null +++ b/spec/mysql2/statement_spec.rb @@ -0,0 +1,19 @@ +# encoding: UTF-8 +require 'spec_helper' + +describe Mysql2::Statement do + before :each do + @client = Mysql2::Client.new DatabaseCredentials['root'] + end + + it "should create a statement" do + statement = nil + lambda { statement = @client.prepare 'SELECT 1' }.should_not raise_error + statement.should be_kind_of Mysql2::Statement + end + + it "should raise an exception when server disconnects" do + @client.close + lambda { @client.prepare 'SELECT 1' }.should raise_error(Mysql2::Error) + end +end From 1290aa226ee8365814b94564e0979436a6b5216d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 7 Jul 2010 09:47:41 -0700 Subject: [PATCH 208/783] statements can count parameters --- ext/mysql2/statement.c | 14 ++++++++++++++ spec/mysql2/statement_spec.rb | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index ed33389e3..f5666b0bf 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -3,7 +3,21 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error; +/* call-seq: stmt.param_count # => Numeric + * + * Returns the number of parameters the prepared statement expects. + */ +static VALUE param_count(VALUE self) +{ + MYSQL_STMT * stmt; + Data_Get_Struct(self, MYSQL_STMT, stmt); + + return ULL2NUM(mysql_stmt_param_count(stmt)); +} + void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); + + rb_define_method(cMysql2Statement, "param_count", param_count, 0); } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index dee37fa63..376cef380 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -16,4 +16,12 @@ @client.close lambda { @client.prepare 'SELECT 1' }.should raise_error(Mysql2::Error) end + + it "should tell us the param count" do + statement = @client.prepare 'SELECT ?, ?' + statement.param_count.should == 2 + + statement2 = @client.prepare 'SELECT 1' + statement2.param_count.should == 0 + end end From 2f44199182d8fa2bb5ddb32d870abf75767d1dc3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 8 Jul 2010 15:55:16 -0700 Subject: [PATCH 209/783] prepared statements will tell us the field count --- ext/mysql2/statement.c | 13 +++++++++++++ spec/mysql2/statement_spec.rb | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index f5666b0bf..e51daebb4 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -15,9 +15,22 @@ static VALUE param_count(VALUE self) return ULL2NUM(mysql_stmt_param_count(stmt)); } +/* call-seq: stmt.field_count # => Numeric + * + * Returns the number of fields the prepared statement returns. + */ +static VALUE field_count(VALUE self) +{ + MYSQL_STMT * stmt; + Data_Get_Struct(self, MYSQL_STMT, stmt); + + return UINT2NUM(mysql_stmt_field_count(stmt)); +} + void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); rb_define_method(cMysql2Statement, "param_count", param_count, 0); + rb_define_method(cMysql2Statement, "field_count", field_count, 0); } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 376cef380..cdc2a93e0 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -24,4 +24,12 @@ statement2 = @client.prepare 'SELECT 1' statement2.param_count.should == 0 end + + it "should tell us the field count" do + statement = @client.prepare 'SELECT ?, ?' + statement.field_count.should == 2 + + statement2 = @client.prepare 'SELECT 1' + statement2.field_count.should == 1 + end end From 7fb34d99b2353906b9e87c84e48219db01fcd1db Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 8 Jul 2010 16:05:50 -0700 Subject: [PATCH 210/783] execute is defined on statement --- ext/mysql2/statement.c | 14 ++++++++++++++ spec/mysql2/statement_spec.rb | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index e51daebb4..b393c7ced 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -27,10 +27,24 @@ static VALUE field_count(VALUE self) return UINT2NUM(mysql_stmt_field_count(stmt)); } +/* call-seq: stmt.execute + * + * Executes the current prepared statement, returns +stmt+. + */ +static VALUE execute(int argc, VALUE *argv, VALUE self) { + MYSQL_STMT * stmt; + Data_Get_Struct(self, MYSQL_STMT, stmt); + + mysql_stmt_execute(stmt); + + return self; +} + void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); rb_define_method(cMysql2Statement, "param_count", param_count, 0); rb_define_method(cMysql2Statement, "field_count", field_count, 0); + rb_define_method(cMysql2Statement, "execute", execute, -1); } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index cdc2a93e0..107cb06c7 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -32,4 +32,9 @@ statement2 = @client.prepare 'SELECT 1' statement2.field_count.should == 1 end + + it "should let us execute our statement" do + statement = @client.prepare 'SELECT 1' + statement.execute.should == statement + end end From ba4848229fdb24dd90291c85e3824e156cf8035e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 8 Jul 2010 16:09:21 -0700 Subject: [PATCH 211/783] execute raises an exception on error --- ext/mysql2/statement.c | 4 +++- spec/mysql2/statement_spec.rb | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index b393c7ced..664a305b0 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -35,7 +35,9 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { MYSQL_STMT * stmt; Data_Get_Struct(self, MYSQL_STMT, stmt); - mysql_stmt_execute(stmt); + if (mysql_stmt_execute(stmt)) { + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + } return self; } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 107cb06c7..1d39b4ef9 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -37,4 +37,10 @@ statement = @client.prepare 'SELECT 1' statement.execute.should == statement end + + it "should raise an exception without a block" do + statement = @client.prepare 'SELECT 1' + statement.execute + lambda { statement.each }.should raise_error + end end From 4506eab9a64d4c7c689c03d83bbca9b89ee26838 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 9 Jul 2010 10:18:17 -0700 Subject: [PATCH 212/783] adding a class to wrap fields --- lib/mysql2.rb | 1 + lib/mysql2/field.rb | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 lib/mysql2/field.rb diff --git a/lib/mysql2.rb b/lib/mysql2.rb index f4ae8b16f..b4bafd604 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -31,6 +31,7 @@ require 'mysql2/mysql2' require 'mysql2/result' require 'mysql2/client' +require 'mysql2/field' # = Mysql2 # diff --git a/lib/mysql2/field.rb b/lib/mysql2/field.rb new file mode 100644 index 000000000..4ca28ca0a --- /dev/null +++ b/lib/mysql2/field.rb @@ -0,0 +1,4 @@ +module Mysql2 + class Field < Struct.new(:name, :type) + end +end From ca0a61e57f956671443d25bc81fdec0e53054675 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 9 Jul 2010 10:18:39 -0700 Subject: [PATCH 213/783] adding Stmt#fields --- ext/mysql2/statement.c | 39 +++++++++++++++++++++++++++++++++++ spec/mysql2/statement_spec.rb | 9 ++++++++ 2 files changed, 48 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 664a305b0..cec5db076 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -42,6 +42,44 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { return self; } +/* call-seq: stmt.fields -> Array + * + * Returns a list of fields that will be returned by this statement. + */ +static VALUE fields(VALUE self) +{ + MYSQL_STMT * stmt; + MYSQL_FIELD * fields; + MYSQL_RES * metadata; + unsigned int field_count; + unsigned int i; + VALUE field_list; + VALUE cMysql2Field; + + Data_Get_Struct(self, MYSQL_STMT, stmt); + metadata = mysql_stmt_result_metadata(stmt); + fields = mysql_fetch_fields(metadata); + field_count = mysql_stmt_field_count(stmt); + field_list = rb_ary_new2((long)field_count); + + cMysql2Field = rb_const_get(mMysql2, rb_intern("Field")); + + for(i = 0; i < field_count; i++) { + VALUE argv[2]; + VALUE field; + + /* FIXME: encoding. Also, can this return null? */ + argv[0] = rb_str_new2(fields[i].name); + argv[1] = INT2NUM(fields[i].type); + + field = rb_class_new_instance(2, argv, cMysql2Field); + + rb_ary_store(field_list, (long)i, field); + } + + return field_list; +} + void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); @@ -49,4 +87,5 @@ void init_mysql2_statement() rb_define_method(cMysql2Statement, "param_count", param_count, 0); rb_define_method(cMysql2Statement, "field_count", field_count, 0); rb_define_method(cMysql2Statement, "execute", execute, -1); + rb_define_method(cMysql2Statement, "fields", fields, 0); } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 1d39b4ef9..61838a1e9 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -43,4 +43,13 @@ statement.execute lambda { statement.each }.should raise_error end + + it "should tell us about the fields" do + statement = @client.prepare 'SELECT 1 as foo, 2' + statement.execute + list = statement.fields + list.length.should == 2 + list.first.name.should == 'foo' + list[1].name.should == '2' + end end From a7011b5357677234b95efdf8f56b2e56e544791f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 9 Jul 2010 10:18:39 -0700 Subject: [PATCH 214/783] adding Stmt#each --- ext/mysql2/statement.c | 23 +++++++++++++++++++++++ spec/mysql2/statement_spec.rb | 9 +++++++++ 2 files changed, 32 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index cec5db076..76639a282 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -80,6 +80,28 @@ static VALUE fields(VALUE self) return field_list; } +static VALUE each(VALUE self) +{ + MYSQL_STMT * stmt; + MYSQL_FIELD * fields; + MYSQL_RES * metadata; + unsigned int field_count; + unsigned int i; + VALUE block; + + Data_Get_Struct(self, MYSQL_STMT, stmt); + + block = rb_block_proc(); + metadata = mysql_stmt_result_metadata(stmt); + fields = mysql_fetch_fields(metadata); + field_count = mysql_stmt_field_count(stmt); + + for(i = 0; i < field_count; i++) { + } + + return self; +} + void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); @@ -88,4 +110,5 @@ void init_mysql2_statement() rb_define_method(cMysql2Statement, "field_count", field_count, 0); rb_define_method(cMysql2Statement, "execute", execute, -1); rb_define_method(cMysql2Statement, "fields", fields, 0); + rb_define_method(cMysql2Statement, "each", each, 0); } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 61838a1e9..a4914c0d8 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -52,4 +52,13 @@ list.first.name.should == 'foo' list[1].name.should == '2' end + + it "should let us iterate over results" do + statement = @client.prepare 'SELECT 1' + statement.execute + rows = [] + statement.each { |row| rows << row } + pending "not working yet" + rows.should == [[1]] + end end From ba76e6e6146615300030125d6a5725625064da35 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 9 Jul 2010 10:46:47 -0700 Subject: [PATCH 215/783] returning ints from prepared statements works --- ext/mysql2/statement.c | 53 +++++++++++++++++++++++++++++++++++ spec/mysql2/statement_spec.rb | 1 - 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 76639a282..78466c5f1 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -85,6 +85,13 @@ static VALUE each(VALUE self) MYSQL_STMT * stmt; MYSQL_FIELD * fields; MYSQL_RES * metadata; + + MYSQL_BIND * binds; + my_bool * is_null; + my_bool * error; + unsigned long * length; + int int_data; + unsigned int field_count; unsigned int i; VALUE block; @@ -96,9 +103,55 @@ static VALUE each(VALUE self) fields = mysql_fetch_fields(metadata); field_count = mysql_stmt_field_count(stmt); + binds = xcalloc(field_count, sizeof(MYSQL_BIND)); + is_null = xcalloc(field_count, sizeof(my_bool)); + error = xcalloc(field_count, sizeof(my_bool)); + length = xcalloc(field_count, sizeof(unsigned long)); + for(i = 0; i < field_count; i++) { + switch(fields[i].type) { + case MYSQL_TYPE_LONGLONG: + binds[i].buffer_type = MYSQL_TYPE_LONG; + binds[i].buffer = (char *)&int_data; + break; + default: + rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type); + } + + binds[i].is_null = &is_null[i]; + binds[i].length = &length[i]; + binds[i].error = &error[i]; } + /* FIXME: if this raises, we need to free our allocated buffers */ + if(mysql_stmt_bind_result(stmt, binds)) { + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + } + + while(!mysql_stmt_fetch(stmt)) { + VALUE row = rb_ary_new2((long)field_count); + + for(i = 0; i < field_count; i++) { + VALUE column = Qnil; + switch(binds[i].buffer_type) { + case MYSQL_TYPE_LONG: + column = INT2NUM(int_data); + break; + default: + rb_raise(cMysql2Error, "unhandled buffer type: %d", + binds[i].buffer_type); + break; + } + rb_ary_store(row, (long)i, column); + } + rb_yield(row); + } + + xfree(binds); + xfree(is_null); + xfree(error); + xfree(length); + return self; } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index a4914c0d8..30ccc07c7 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -58,7 +58,6 @@ statement.execute rows = [] statement.each { |row| rows << row } - pending "not working yet" rows.should == [[1]] end end From 37dcae4e2db71125e9103c44c10ad2f8012753c5 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 9 Jul 2010 11:10:54 -0700 Subject: [PATCH 216/783] prepared statements can pull MYSQL_TYPE_DATETIME --- ext/mysql2/statement.c | 16 ++++++++++++++++ spec/mysql2/statement_spec.rb | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 78466c5f1..723717b60 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -91,6 +91,7 @@ static VALUE each(VALUE self) my_bool * error; unsigned long * length; int int_data; + MYSQL_TIME ts; unsigned int field_count; unsigned int i; @@ -114,6 +115,10 @@ static VALUE each(VALUE self) binds[i].buffer_type = MYSQL_TYPE_LONG; binds[i].buffer = (char *)&int_data; break; + case MYSQL_TYPE_DATETIME: + binds[i].buffer_type = MYSQL_TYPE_DATETIME; + binds[i].buffer = (char *)&ts; + break; default: rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type); } @@ -137,6 +142,17 @@ static VALUE each(VALUE self) case MYSQL_TYPE_LONG: column = INT2NUM(int_data); break; + /* FIXME: maybe we want to return a datetime in this case? */ + case MYSQL_TYPE_DATETIME: + column = rb_funcall(rb_cTime, + rb_intern("mktime"), 6, + UINT2NUM(ts.year), + UINT2NUM(ts.month), + UINT2NUM(ts.day), + UINT2NUM(ts.hour), + UINT2NUM(ts.minute), + UINT2NUM(ts.second)); + break; default: rb_raise(cMysql2Error, "unhandled buffer type: %d", binds[i].buffer_type); diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 30ccc07c7..06f6ed29f 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -60,4 +60,12 @@ statement.each { |row| rows << row } rows.should == [[1]] end + + it "should select dates" do + statement = @client.prepare 'SELECT NOW()' + statement.execute + rows = [] + statement.each { |row| rows << row } + rows.first.first.should be_kind_of Time + end end From 08b01bbe51c45622b084e4971b0c0b7921bcf5ee Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 20 Aug 2010 09:16:22 -0700 Subject: [PATCH 217/783] xfree or die hard --- ext/mysql2/statement.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 723717b60..aadc2873d 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -128,8 +128,11 @@ static VALUE each(VALUE self) binds[i].error = &error[i]; } - /* FIXME: if this raises, we need to free our allocated buffers */ if(mysql_stmt_bind_result(stmt, binds)) { + xfree(binds); + xfree(is_null); + xfree(error); + xfree(length); rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } From 32ce5d797ade2ad0d6284807e97762292150f283 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 26 Apr 2011 21:37:37 -0700 Subject: [PATCH 218/783] Mysql2::Statement includes Enumerable --- lib/mysql2.rb | 1 + lib/mysql2/statement.rb | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 lib/mysql2/statement.rb diff --git a/lib/mysql2.rb b/lib/mysql2.rb index b4bafd604..5c4e6aab4 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -32,6 +32,7 @@ require 'mysql2/result' require 'mysql2/client' require 'mysql2/field' +require 'mysql2/statement' # = Mysql2 # diff --git a/lib/mysql2/statement.rb b/lib/mysql2/statement.rb new file mode 100644 index 000000000..8f30797c1 --- /dev/null +++ b/lib/mysql2/statement.rb @@ -0,0 +1,5 @@ +module Mysql2 + class Statement + include Enumerable + end +end From e4fa242003d6b0c6148705b551b2462ef85db704 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 5 May 2011 14:38:17 -0700 Subject: [PATCH 219/783] initial fleshing out of bind variable support and casting of ruby values from result value types --- ext/mysql2/result.c | 2 +- ext/mysql2/statement.c | 430 ++++++++++++++++++++++++++-------- spec/mysql2/statement_spec.rb | 4 +- 3 files changed, 337 insertions(+), 99 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 0a0ac4cda..34dcbaba4 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -50,8 +50,8 @@ static rb_encoding *binaryEncoding; #define MYSQL2_MIN_TIME 62171150401ULL #endif +VALUE cBigDecimal, cDateTime, cDate; static VALUE cMysql2Result; -static VALUE cBigDecimal, cDate, cDateTime; static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset; extern VALUE mMysql2, cMysql2Client, cMysql2Error; static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset; diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index aadc2873d..209c689d8 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -1,15 +1,14 @@ #include VALUE cMysql2Statement; -extern VALUE mMysql2, cMysql2Error; +extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; /* call-seq: stmt.param_count # => Numeric * * Returns the number of parameters the prepared statement expects. */ -static VALUE param_count(VALUE self) -{ - MYSQL_STMT * stmt; +static VALUE param_count(VALUE self) { + MYSQL_STMT *stmt; Data_Get_Struct(self, MYSQL_STMT, stmt); return ULL2NUM(mysql_stmt_param_count(stmt)); @@ -19,38 +18,154 @@ static VALUE param_count(VALUE self) * * Returns the number of fields the prepared statement returns. */ -static VALUE field_count(VALUE self) -{ - MYSQL_STMT * stmt; +static VALUE field_count(VALUE self) { + MYSQL_STMT *stmt; Data_Get_Struct(self, MYSQL_STMT, stmt); return UINT2NUM(mysql_stmt_field_count(stmt)); } +static void *nogvl_execute(void *ptr) { + MYSQL_STMT *stmt = ptr; + + if(mysql_stmt_execute(stmt)) { + return (void*)Qfalse; + } else { + return (void*)Qtrue; + } +} + +#define FREE_BINDS \ + for (i = 0; i < argc; i++) { \ + if (bind_buffers[i].buffer) { \ + xfree(bind_buffers[i].buffer); \ + } \ + } \ + xfree(bind_buffers); + /* call-seq: stmt.execute * * Executes the current prepared statement, returns +stmt+. */ static VALUE execute(int argc, VALUE *argv, VALUE self) { - MYSQL_STMT * stmt; + MYSQL_STMT *stmt; + MYSQL_BIND *bind_buffers; + unsigned long bind_count; + long i; + Data_Get_Struct(self, MYSQL_STMT, stmt); - if (mysql_stmt_execute(stmt)) { + bind_count = mysql_stmt_param_count(stmt); + if (argc != (long)bind_count) { + rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, argc); + } + + // setup any bind variables in the query + if (bind_count > 0) { + bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND)); + + for (i = 0; i < argc; i++) { + bind_buffers[i].buffer = NULL; + + switch (TYPE(argv[i])) { + case T_NIL: + bind_buffers[i].buffer_type = MYSQL_TYPE_NULL; + break; + case T_FIXNUM: +#if SIZEOF_INT < SIZEOF_LONG + bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; + bind_buffers[i].buffer = xmalloc(sizeof(long long int)); + *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]); +#else + bind_buffers[i].buffer_type = MYSQL_TYPE_LONG; + bind_buffers[i].buffer = xmalloc(sizeof(int)); + *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]); +#endif + break; + case T_BIGNUM: + bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; + bind_buffers[i].buffer = xmalloc(sizeof(long long int)); + *(LONG_LONG*)(bind_buffers[i].buffer) = rb_big2ll(argv[i]); + break; + case T_FLOAT: + bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE; + bind_buffers[i].buffer = xmalloc(sizeof(double)); + *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]); + break; + case T_STRING: + bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; + bind_buffers[i].buffer = RSTRING_PTR(argv[i]); + bind_buffers[i].buffer_length = RSTRING_LEN(argv[i]); + unsigned long len = RSTRING_LEN(argv[i]); + bind_buffers[i].length = &len; + break; + default: + // TODO: what Ruby type should support MYSQL_TYPE_TIME + if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) { + bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME; + bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); + + MYSQL_TIME t; + VALUE rb_time = argv[i]; + memset(&t, 0, sizeof(MYSQL_TIME)); + t.second_part = 0; + t.neg = 0; + t.second = FIX2INT(rb_funcall(rb_time, rb_intern("sec"), 0)); + t.minute = FIX2INT(rb_funcall(rb_time, rb_intern("min"), 0)); + t.hour = FIX2INT(rb_funcall(rb_time, rb_intern("hour"), 0)); + t.day = FIX2INT(rb_funcall(rb_time, rb_intern("day"), 0)); + t.month = FIX2INT(rb_funcall(rb_time, rb_intern("month"), 0)); + t.year = FIX2INT(rb_funcall(rb_time, rb_intern("year"), 0)); + + *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; + } else if (CLASS_OF(argv[i]) == cDate) { + bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDATE; + bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); + + MYSQL_TIME t; + VALUE rb_time = argv[i]; + memset(&t, 0, sizeof(MYSQL_TIME)); + t.second_part = 0; + t.neg = 0; + t.day = FIX2INT(rb_funcall(rb_time, rb_intern("day"), 0)); + t.month = FIX2INT(rb_funcall(rb_time, rb_intern("month"), 0)); + t.year = FIX2INT(rb_funcall(rb_time, rb_intern("year"), 0)); + + *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; + } else if (CLASS_OF(argv[i]) == cBigDecimal) { + bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL; + } + break; + } + } + + // copies bind_buffers into internal storage + if (mysql_stmt_bind_param(stmt, bind_buffers)) { + FREE_BINDS; + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + } + } + + if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) { + FREE_BINDS; rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } + if (bind_count > 0) { + FREE_BINDS; + } + return self; } -/* call-seq: stmt.fields -> Array +/* call-seq: stmt.fields # => array * * Returns a list of fields that will be returned by this statement. */ -static VALUE fields(VALUE self) -{ - MYSQL_STMT * stmt; - MYSQL_FIELD * fields; - MYSQL_RES * metadata; +static VALUE fields(VALUE self) { + MYSQL_STMT *stmt; + MYSQL_FIELD *fields; + MYSQL_RES *metadata; unsigned int field_count; unsigned int i; VALUE field_list; @@ -65,14 +180,10 @@ static VALUE fields(VALUE self) cMysql2Field = rb_const_get(mMysql2, rb_intern("Field")); for(i = 0; i < field_count; i++) { - VALUE argv[2]; VALUE field; /* FIXME: encoding. Also, can this return null? */ - argv[0] = rb_str_new2(fields[i].name); - argv[1] = INT2NUM(fields[i].type); - - field = rb_class_new_instance(2, argv, cMysql2Field); + field = rb_str_new(fields[i].name, fields[i].name_length); rb_ary_store(field_list, (long)i, field); } @@ -80,102 +191,229 @@ static VALUE fields(VALUE self) return field_list; } -static VALUE each(VALUE self) -{ - MYSQL_STMT * stmt; - MYSQL_FIELD * fields; - MYSQL_RES * metadata; - - MYSQL_BIND * binds; - my_bool * is_null; - my_bool * error; - unsigned long * length; - int int_data; - MYSQL_TIME ts; - - unsigned int field_count; - unsigned int i; +static VALUE each(VALUE self) { VALUE block; + MYSQL_STMT *stmt; + MYSQL_RES *result; Data_Get_Struct(self, MYSQL_STMT, stmt); - block = rb_block_proc(); - metadata = mysql_stmt_result_metadata(stmt); - fields = mysql_fetch_fields(metadata); - field_count = mysql_stmt_field_count(stmt); - - binds = xcalloc(field_count, sizeof(MYSQL_BIND)); - is_null = xcalloc(field_count, sizeof(my_bool)); - error = xcalloc(field_count, sizeof(my_bool)); - length = xcalloc(field_count, sizeof(unsigned long)); + result = mysql_stmt_result_metadata(stmt); + if (result) { + MYSQL_BIND *result_buffers; + my_bool *is_null; + my_bool *error; + unsigned long *length; + MYSQL_FIELD *fields; + unsigned long field_count; + unsigned long i; - for(i = 0; i < field_count; i++) { - switch(fields[i].type) { - case MYSQL_TYPE_LONGLONG: - binds[i].buffer_type = MYSQL_TYPE_LONG; - binds[i].buffer = (char *)&int_data; - break; - case MYSQL_TYPE_DATETIME: - binds[i].buffer_type = MYSQL_TYPE_DATETIME; - binds[i].buffer = (char *)&ts; - break; - default: - rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type); + if (mysql_stmt_store_result(stmt)) { + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } - binds[i].is_null = &is_null[i]; - binds[i].length = &length[i]; - binds[i].error = &error[i]; - } + fields = mysql_fetch_fields(result); + field_count = mysql_num_fields(result); - if(mysql_stmt_bind_result(stmt, binds)) { - xfree(binds); - xfree(is_null); - xfree(error); - xfree(length); - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); - } + result_buffers = xcalloc(field_count, sizeof(MYSQL_BIND)); + is_null = xcalloc(field_count, sizeof(my_bool)); + error = xcalloc(field_count, sizeof(my_bool)); + length = xcalloc(field_count, sizeof(unsigned long)); - while(!mysql_stmt_fetch(stmt)) { - VALUE row = rb_ary_new2((long)field_count); + for (i = 0; i < field_count; i++) { + result_buffers[i].buffer_type = fields[i].type; - for(i = 0; i < field_count; i++) { - VALUE column = Qnil; - switch(binds[i].buffer_type) { - case MYSQL_TYPE_LONG: - column = INT2NUM(int_data); + // mysql type | C type + switch(fields[i].type) { + case MYSQL_TYPE_NULL: // NULL break; - /* FIXME: maybe we want to return a datetime in this case? */ - case MYSQL_TYPE_DATETIME: - column = rb_funcall(rb_cTime, - rb_intern("mktime"), 6, - UINT2NUM(ts.year), - UINT2NUM(ts.month), - UINT2NUM(ts.day), - UINT2NUM(ts.hour), - UINT2NUM(ts.minute), - UINT2NUM(ts.second)); + case MYSQL_TYPE_TINY: // signed char + result_buffers[i].buffer = xcalloc(1, sizeof(signed char)); + result_buffers[i].buffer_length = sizeof(signed char); break; - default: - rb_raise(cMysql2Error, "unhandled buffer type: %d", - binds[i].buffer_type); + case MYSQL_TYPE_SHORT: // short int + result_buffers[i].buffer = xcalloc(1, sizeof(short int)); + result_buffers[i].buffer_length = sizeof(short int); + break; + case MYSQL_TYPE_INT24: // int + case MYSQL_TYPE_LONG: // int + case MYSQL_TYPE_YEAR: // int + result_buffers[i].buffer = xcalloc(1, sizeof(int)); + result_buffers[i].buffer_length = sizeof(int); + break; + case MYSQL_TYPE_LONGLONG: // long long int + result_buffers[i].buffer = xcalloc(1, sizeof(long long int)); + result_buffers[i].buffer_length = sizeof(long long int); break; + case MYSQL_TYPE_FLOAT: // float + case MYSQL_TYPE_DOUBLE: // double + result_buffers[i].buffer = xcalloc(1, sizeof(double)); + result_buffers[i].buffer_length = sizeof(double); + break; + case MYSQL_TYPE_TIME: // MYSQL_TIME + case MYSQL_TYPE_DATE: // MYSQL_TIME + case MYSQL_TYPE_NEWDATE: // MYSQL_TIME + case MYSQL_TYPE_DATETIME: // MYSQL_TIME + case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME + result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME)); + result_buffers[i].buffer_length = sizeof(MYSQL_TIME); + break; + case MYSQL_TYPE_DECIMAL: // char[] + case MYSQL_TYPE_NEWDECIMAL: // char[] + case MYSQL_TYPE_STRING: // char[] + case MYSQL_TYPE_VAR_STRING: // char[] + case MYSQL_TYPE_VARCHAR: // char[] + case MYSQL_TYPE_TINY_BLOB: // char[] + case MYSQL_TYPE_BLOB: // char[] + case MYSQL_TYPE_MEDIUM_BLOB: // char[] + case MYSQL_TYPE_LONG_BLOB: // char[] + case MYSQL_TYPE_BIT: // char[] + case MYSQL_TYPE_SET: // char[] + case MYSQL_TYPE_ENUM: // char[] + case MYSQL_TYPE_GEOMETRY: // char[] + result_buffers[i].buffer = xmalloc(fields[i].max_length); + result_buffers[i].buffer_length = fields[i].max_length; + break; + default: + rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type); } - rb_ary_store(row, (long)i, column); + + result_buffers[i].is_null = &is_null[i]; + result_buffers[i].length = &length[i]; + result_buffers[i].error = &error[i]; + result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0); } - rb_yield(row); - } - xfree(binds); - xfree(is_null); - xfree(error); - xfree(length); + if(mysql_stmt_bind_result(stmt, result_buffers)) { + for(i = 0; i < field_count; i++) { + if (result_buffers[i].buffer) { + xfree(result_buffers[i].buffer); + } + } + xfree(result_buffers); + xfree(is_null); + xfree(error); + xfree(length); + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + } + + block = rb_block_proc(); + + while(!mysql_stmt_fetch(stmt)) { + VALUE row = rb_ary_new2((long)field_count); + + for(i = 0; i < field_count; i++) { + VALUE column = Qnil; + MYSQL_TIME *ts; + + if (is_null[i]) { + column = Qnil; + } else { + switch(result_buffers[i].buffer_type) { + case MYSQL_TYPE_TINY: // signed char + if (result_buffers[i].is_unsigned) { + column = UINT2NUM(*((unsigned char*)result_buffers[i].buffer)); + } else { + column = INT2NUM(*((signed char*)result_buffers[i].buffer)); + } + break; + case MYSQL_TYPE_SHORT: // short int + if (result_buffers[i].is_unsigned) { + column = UINT2NUM(*((unsigned short int*)result_buffers[i].buffer)); + } else { + column = INT2NUM(*((short int*)result_buffers[i].buffer)); + } + break; + case MYSQL_TYPE_INT24: // int + case MYSQL_TYPE_LONG: // int + case MYSQL_TYPE_YEAR: // int + if (result_buffers[i].is_unsigned) { + column = UINT2NUM(*((unsigned int*)result_buffers[i].buffer)); + } else { + column = INT2NUM(*((int*)result_buffers[i].buffer)); + } + break; + case MYSQL_TYPE_LONGLONG: // long long int + if (result_buffers[i].is_unsigned) { + column = ULL2NUM(*((unsigned long long int*)result_buffers[i].buffer)); + } else { + column = LL2NUM(*((long long int*)result_buffers[i].buffer)); + } + break; + case MYSQL_TYPE_FLOAT: // float + column = rb_float_new((double)(*((float*)result_buffers[i].buffer))); + break; + case MYSQL_TYPE_DOUBLE: // double + column = rb_float_new((double)(*((double*)result_buffers[i].buffer))); + break; + case MYSQL_TYPE_DATE: // MYSQL_TIME + ts = (MYSQL_TIME*)result_buffers[i].buffer; + column = rb_funcall(cDate, rb_intern("new"), 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); + break; + case MYSQL_TYPE_TIME: // MYSQL_TIME + ts = (MYSQL_TIME*)result_buffers[i].buffer; + column = rb_funcall(rb_cTime, + rb_intern("mktime"), 6, + UINT2NUM(Qnil), + UINT2NUM(Qnil), + UINT2NUM(Qnil), + UINT2NUM(ts->hour), + UINT2NUM(ts->minute), + UINT2NUM(ts->second)); + break; + case MYSQL_TYPE_NEWDATE: // MYSQL_TIME + case MYSQL_TYPE_DATETIME: // MYSQL_TIME + case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME + ts = (MYSQL_TIME*)result_buffers[i].buffer; + column = rb_funcall(rb_cTime, + rb_intern("mktime"), 6, + UINT2NUM(ts->year), + UINT2NUM(ts->month), + UINT2NUM(ts->day), + UINT2NUM(ts->hour), + UINT2NUM(ts->minute), + UINT2NUM(ts->second)); + break; + case MYSQL_TYPE_DECIMAL: // char[] + case MYSQL_TYPE_NEWDECIMAL: // char[] + column = rb_funcall(cBigDecimal, rb_intern("new"), 1, rb_str_new(result_buffers[i].buffer, *(result_buffers[i].length))); + break; + case MYSQL_TYPE_STRING: // char[] + case MYSQL_TYPE_VAR_STRING: // char[] + case MYSQL_TYPE_VARCHAR: // char[] + case MYSQL_TYPE_TINY_BLOB: // char[] + case MYSQL_TYPE_BLOB: // char[] + case MYSQL_TYPE_MEDIUM_BLOB: // char[] + case MYSQL_TYPE_LONG_BLOB: // char[] + case MYSQL_TYPE_BIT: // char[] + case MYSQL_TYPE_SET: // char[] + case MYSQL_TYPE_ENUM: // char[] + case MYSQL_TYPE_GEOMETRY: // char[] + column = rb_str_new(result_buffers[i].buffer, *(result_buffers[i].length)); + break; + default: + rb_raise(cMysql2Error, "unhandled buffer type: %d", + result_buffers[i].buffer_type); + break; + } + } + + rb_ary_store(row, (long)i, column); + } + + rb_yield(row); + } + + xfree(result_buffers); + xfree(is_null); + xfree(error); + xfree(length); + } return self; } -void init_mysql2_statement() -{ +void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); rb_define_method(cMysql2Statement, "param_count", param_count, 0); diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 06f6ed29f..cb3d51da6 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -49,8 +49,8 @@ statement.execute list = statement.fields list.length.should == 2 - list.first.name.should == 'foo' - list[1].name.should == '2' + list.first.should == 'foo' + list[1].should == '2' end it "should let us iterate over results" do From c67fc7c7424e2215e98d0ab6d4c0023d7d4df923 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 13 Oct 2011 07:24:38 -0700 Subject: [PATCH 220/783] don't use ruby's heap --- ext/mysql2/statement.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 209c689d8..26a2a7961 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -38,10 +38,10 @@ static void *nogvl_execute(void *ptr) { #define FREE_BINDS \ for (i = 0; i < argc; i++) { \ if (bind_buffers[i].buffer) { \ - xfree(bind_buffers[i].buffer); \ + free(bind_buffers[i].buffer); \ } \ } \ - xfree(bind_buffers); + free(bind_buffers); /* call-seq: stmt.execute * @@ -74,22 +74,22 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { case T_FIXNUM: #if SIZEOF_INT < SIZEOF_LONG bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; - bind_buffers[i].buffer = xmalloc(sizeof(long long int)); + bind_buffers[i].buffer = malloc(sizeof(long long int)); *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]); #else bind_buffers[i].buffer_type = MYSQL_TYPE_LONG; - bind_buffers[i].buffer = xmalloc(sizeof(int)); + bind_buffers[i].buffer = malloc(sizeof(int)); *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]); #endif break; case T_BIGNUM: bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; - bind_buffers[i].buffer = xmalloc(sizeof(long long int)); + bind_buffers[i].buffer = malloc(sizeof(long long int)); *(LONG_LONG*)(bind_buffers[i].buffer) = rb_big2ll(argv[i]); break; case T_FLOAT: bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE; - bind_buffers[i].buffer = xmalloc(sizeof(double)); + bind_buffers[i].buffer = malloc(sizeof(double)); *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]); break; case T_STRING: @@ -103,7 +103,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { // TODO: what Ruby type should support MYSQL_TYPE_TIME if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) { bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME; - bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); + bind_buffers[i].buffer = malloc(sizeof(MYSQL_TIME)); MYSQL_TIME t; VALUE rb_time = argv[i]; @@ -120,7 +120,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cDate) { bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDATE; - bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); + bind_buffers[i].buffer = malloc(sizeof(MYSQL_TIME)); MYSQL_TIME t; VALUE rb_time = argv[i]; @@ -271,7 +271,7 @@ static VALUE each(VALUE self) { case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] - result_buffers[i].buffer = xmalloc(fields[i].max_length); + result_buffers[i].buffer = malloc(fields[i].max_length); result_buffers[i].buffer_length = fields[i].max_length; break; default: @@ -287,13 +287,13 @@ static VALUE each(VALUE self) { if(mysql_stmt_bind_result(stmt, result_buffers)) { for(i = 0; i < field_count; i++) { if (result_buffers[i].buffer) { - xfree(result_buffers[i].buffer); + free(result_buffers[i].buffer); } } - xfree(result_buffers); - xfree(is_null); - xfree(error); - xfree(length); + free(result_buffers); + free(is_null); + free(error); + free(length); rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } @@ -404,10 +404,10 @@ static VALUE each(VALUE self) { rb_yield(row); } - xfree(result_buffers); - xfree(is_null); - xfree(error); - xfree(length); + free(result_buffers); + free(is_null); + free(error); + free(length); } return self; From 2f2d10a80a0fb9f77426e14db2f4770b6157eac6 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Sat, 28 Jul 2012 19:43:12 +0900 Subject: [PATCH 221/783] test pass --- ext/mysql2/client.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index fb97e98aa..4ca4b485d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1246,6 +1246,8 @@ static void *nogvl_prepare_statement(void *ptr) { */ static VALUE prepare_statement(VALUE self, VALUE sql) { GET_CLIENT(self); + REQUIRE_CONNECTED(wrapper); + struct nogvl_prepare_statement_args args; MYSQL_STMT *stmt; VALUE rb_stmt; From eb59b4e5008bb1110063952231c5b660b454b1f9 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Sun, 29 Jul 2012 15:37:35 +0900 Subject: [PATCH 222/783] rename prepare_statement func --- ext/mysql2/client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 4ca4b485d..0279a4509 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1244,7 +1244,7 @@ static void *nogvl_prepare_statement(void *ptr) { * * Create a new prepared statement. */ -static VALUE prepare_statement(VALUE self, VALUE sql) { +static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); @@ -1320,7 +1320,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0); rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0); rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0); - rb_define_method(cMysql2Client, "prepare", prepare_statement, 1); + rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1); rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0); rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0); rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1); From 8f2b2e1c30c7dbbf91199cdaa9b345e96b0c063e Mon Sep 17 00:00:00 2001 From: nyaxt Date: Sun, 29 Jul 2012 16:26:47 +0900 Subject: [PATCH 223/783] MYSQL_STMT is now wrapped as mysql_stmt_wrapper. handle field name encoding. --- ext/mysql2/client.c | 56 +--------------- ext/mysql2/client.h | 10 +++ ext/mysql2/statement.c | 142 ++++++++++++++++++++++++++++++++++------- ext/mysql2/statement.h | 7 ++ 4 files changed, 138 insertions(+), 77 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 0279a4509..93715e4cc 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -30,12 +30,6 @@ static VALUE rb_hash_dup(VALUE other) { rb_raise(cMysql2Error, "MySQL client is not initialized"); \ } -#define REQUIRE_CONNECTED(wrapper) \ - REQUIRE_INITIALIZED(wrapper) \ - if (!wrapper->connected && !wrapper->reconnect_enabled) { \ - rb_raise(cMysql2Error, "closed MySQL connection"); \ - } - #define REQUIRE_NOT_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ if (wrapper->connected) { \ @@ -45,10 +39,6 @@ static VALUE rb_hash_dup(VALUE other) { #define MARK_CONN_INACTIVE(conn) \ wrapper->active_thread = Qnil; -#define GET_CLIENT(self) \ - mysql_client_wrapper *wrapper; \ - Data_Get_Struct(self, mysql_client_wrapper, wrapper) - /* * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when @@ -87,16 +77,6 @@ struct nogvl_send_query_args { mysql_client_wrapper *wrapper; }; -/* - * used to pass all arguments to mysql_stmt_prepare while inside - * rb_thread_call_without_gvl - */ -struct nogvl_prepare_statement_args { - MYSQL_STMT *stmt; - const char *sql; - unsigned long sql_len; -}; - /* * used to pass all arguments to mysql_select_db while inside * rb_thread_call_without_gvl @@ -1230,16 +1210,6 @@ static VALUE initialize_ext(VALUE self) { return self; } -static void *nogvl_prepare_statement(void *ptr) { - struct nogvl_prepare_statement_args *args = ptr; - - if (mysql_stmt_prepare(args->stmt, args->sql, args->sql_len)) { - return (void*)Qfalse; - } else { - return (void*)Qtrue; - } -} - /* call-seq: client.prepare # => Mysql2::Statement * * Create a new prepared statement. @@ -1248,31 +1218,7 @@ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); - struct nogvl_prepare_statement_args args; - MYSQL_STMT *stmt; - VALUE rb_stmt; - my_bool truth = 1; - - stmt = mysql_stmt_init(wrapper->client); - if (stmt == NULL) { - rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory"); - } - - if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) { - rb_raise(cMysql2Error, "Unable to initialize prepared statement"); - } - - rb_stmt = Data_Wrap_Struct(cMysql2Statement, 0, mysql_stmt_close, stmt); - - args.stmt = stmt; - args.sql = StringValuePtr(sql); - args.sql_len = RSTRING_LEN(sql); - - if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); - } - - return rb_stmt; + return rb_mysql_stmt_new(self, sql); } void init_mysql2_client() { diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index f7af5de67..db9947faa 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -50,6 +50,16 @@ typedef struct { MYSQL *client; } mysql_client_wrapper; +#define REQUIRE_CONNECTED(wrapper) \ + REQUIRE_INITIALIZED(wrapper) \ + if (!wrapper->connected && !wrapper->reconnect_enabled) { \ + rb_raise(cMysql2Error, "closed MySQL connection"); \ + } + +#define GET_CLIENT(self) \ + mysql_client_wrapper *wrapper; \ + Data_Get_Struct(self, mysql_client_wrapper, wrapper); + void init_mysql2_client(); void decr_mysql2_client(mysql_client_wrapper *wrapper); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 26a2a7961..e3f73a852 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -3,15 +3,96 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; +static void rb_mysql_stmt_mark(void * ptr) { + mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; + if(! stmt_wrapper) return; + + rb_gc_mark(stmt_wrapper->client); +} + +static void rb_mysql_stmt_free(void * ptr) { + mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; + + mysql_stmt_close(stmt_wrapper->stmt); + + xfree(ptr); +} + +/* + * used to pass all arguments to mysql_stmt_prepare while inside + * nogvl_prepare_statement_args + */ +struct nogvl_prepare_statement_args { + MYSQL_STMT *stmt; + const char *sql; + unsigned long sql_len; +}; + +static void *nogvl_prepare_statement(void *ptr) { + struct nogvl_prepare_statement_args *args = ptr; + + if (mysql_stmt_prepare(args->stmt, args->sql, args->sql_len)) { + return (void*)Qfalse; + } else { + return (void*)Qtrue; + } +} + +VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { + mysql_stmt_wrapper* stmt_wrapper; + VALUE rb_stmt; + + rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); + { + stmt_wrapper->client = rb_client; + stmt_wrapper->stmt = NULL; + } + + // instantiate stmt + { + GET_CLIENT(rb_client); + stmt_wrapper->stmt = mysql_stmt_init(wrapper->client); + } + if (stmt_wrapper->stmt == NULL) { + rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory"); + } + + // set STMT_ATTR_UPDATE_MAX_LENGTH attr + { + my_bool truth = 1; + if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) { + rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH"); + } + } + + // call mysql_stmt_prepare w/o gvl + { + struct nogvl_prepare_statement_args args; + args.stmt = stmt_wrapper->stmt; + args.sql = StringValuePtr(sql); + args.sql_len = RSTRING_LEN(sql); + + if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt_wrapper->stmt)); + } + } + + return rb_stmt; +} + +#define GET_STATEMENT(self) \ + mysql_stmt_wrapper *stmt_wrapper; \ + Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper) + + /* call-seq: stmt.param_count # => Numeric * * Returns the number of parameters the prepared statement expects. */ static VALUE param_count(VALUE self) { - MYSQL_STMT *stmt; - Data_Get_Struct(self, MYSQL_STMT, stmt); + GET_STATEMENT(self); - return ULL2NUM(mysql_stmt_param_count(stmt)); + return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt)); } /* call-seq: stmt.field_count # => Numeric @@ -19,10 +100,9 @@ static VALUE param_count(VALUE self) { * Returns the number of fields the prepared statement returns. */ static VALUE field_count(VALUE self) { - MYSQL_STMT *stmt; - Data_Get_Struct(self, MYSQL_STMT, stmt); + GET_STATEMENT(self); - return UINT2NUM(mysql_stmt_field_count(stmt)); + return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt)); } static void *nogvl_execute(void *ptr) { @@ -48,12 +128,13 @@ static void *nogvl_execute(void *ptr) { * Executes the current prepared statement, returns +stmt+. */ static VALUE execute(int argc, VALUE *argv, VALUE self) { - MYSQL_STMT *stmt; MYSQL_BIND *bind_buffers; unsigned long bind_count; long i; + MYSQL_STMT* stmt; + GET_STATEMENT(self); - Data_Get_Struct(self, MYSQL_STMT, stmt); + stmt = stmt_wrapper->stmt; bind_count = mysql_stmt_param_count(stmt); if (argc != (long)bind_count) { @@ -93,6 +174,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]); break; case T_STRING: + // FIXME: convert encoding bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; bind_buffers[i].buffer = RSTRING_PTR(argv[i]); bind_buffers[i].buffer_length = RSTRING_LEN(argv[i]); @@ -163,40 +245,58 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { * Returns a list of fields that will be returned by this statement. */ static VALUE fields(VALUE self) { - MYSQL_STMT *stmt; MYSQL_FIELD *fields; MYSQL_RES *metadata; unsigned int field_count; unsigned int i; VALUE field_list; - VALUE cMysql2Field; + MYSQL_STMT* stmt; +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *default_internal_enc, *conn_enc; +#endif + GET_STATEMENT(self); + stmt = stmt_wrapper->stmt; + +#ifdef HAVE_RUBY_ENCODING_H + default_internal_enc = rb_default_internal_encoding(); + { + GET_CLIENT(stmt_wrapper->client); + conn_enc = rb_to_encoding(wrapper->encoding); + } +#endif - Data_Get_Struct(self, MYSQL_STMT, stmt); metadata = mysql_stmt_result_metadata(stmt); fields = mysql_fetch_fields(metadata); field_count = mysql_stmt_field_count(stmt); field_list = rb_ary_new2((long)field_count); - cMysql2Field = rb_const_get(mMysql2, rb_intern("Field")); - for(i = 0; i < field_count; i++) { - VALUE field; - - /* FIXME: encoding. Also, can this return null? */ - field = rb_str_new(fields[i].name, fields[i].name_length); + VALUE rb_field; + + rb_field = rb_str_new(fields[i].name, fields[i].name_length); +#ifdef HAVE_RUBY_ENCODING_H + rb_enc_associate(rb_field, conn_enc); + if (default_internal_enc) { + rb_field = rb_str_export_to_enc(rb_field, default_internal_enc); + } +#endif - rb_ary_store(field_list, (long)i, field); + rb_ary_store(field_list, (long)i, rb_field); } return field_list; } static VALUE each(VALUE self) { - VALUE block; MYSQL_STMT *stmt; MYSQL_RES *result; + GET_STATEMENT(self); + stmt = stmt_wrapper->stmt; - Data_Get_Struct(self, MYSQL_STMT, stmt); + if(! rb_block_given_p()) + { + rb_raise(cMysql2Error, "FIXME: current limitation: each require block"); + } result = mysql_stmt_result_metadata(stmt); if (result) { @@ -297,8 +397,6 @@ static VALUE each(VALUE self) { rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } - block = rb_block_proc(); - while(!mysql_stmt_fetch(stmt)) { VALUE row = rb_ary_new2((long)field_count); diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index cfea18939..34cfde40a 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -5,4 +5,11 @@ extern VALUE cMysql2Statement; void init_mysql2_statement(); +typedef struct { + VALUE client; + MYSQL_STMT* stmt; +} mysql_stmt_wrapper; + +VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); + #endif From 162e8e7ae31debd35f93a9458717374b589e6841 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Sun, 29 Jul 2012 19:14:28 +0900 Subject: [PATCH 224/783] utf8 fieldnames --- ext/mysql2/statement.c | 21 ++++++++++++++++++--- spec/mysql2/statement_spec.rb | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index e3f73a852..ac67bf487 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -24,14 +24,15 @@ static void rb_mysql_stmt_free(void * ptr) { */ struct nogvl_prepare_statement_args { MYSQL_STMT *stmt; - const char *sql; + VALUE sql; + const char *sql_ptr; unsigned long sql_len; }; static void *nogvl_prepare_statement(void *ptr) { struct nogvl_prepare_statement_args *args = ptr; - if (mysql_stmt_prepare(args->stmt, args->sql, args->sql_len)) { + if (mysql_stmt_prepare(args->stmt, args->sql_ptr, args->sql_len)) { return (void*)Qfalse; } else { return (void*)Qtrue; @@ -41,6 +42,11 @@ static void *nogvl_prepare_statement(void *ptr) { VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { mysql_stmt_wrapper* stmt_wrapper; VALUE rb_stmt; +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *conn_enc; +#endif + + Check_Type(sql, T_STRING); rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); { @@ -52,6 +58,9 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { { GET_CLIENT(rb_client); stmt_wrapper->stmt = mysql_stmt_init(wrapper->client); +#ifdef HAVE_RUBY_ENCODING_H + conn_enc = rb_to_encoding(wrapper->encoding); +#endif } if (stmt_wrapper->stmt == NULL) { rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory"); @@ -69,7 +78,12 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { { struct nogvl_prepare_statement_args args; args.stmt = stmt_wrapper->stmt; - args.sql = StringValuePtr(sql); + args.sql = sql; +#ifdef HAVE_RUBY_ENCODING_H + // ensure the string is in the encoding the connection is expecting + args.sql = rb_str_export_to_enc(args.sql, conn_enc); +#endif + args.sql_ptr = StringValuePtr(sql); args.sql_len = RSTRING_LEN(sql); if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { @@ -487,6 +501,7 @@ static VALUE each(VALUE self) { case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] + // FIXME: handle encoding column = rb_str_new(result_buffers[i].buffer, *(result_buffers[i].length)); break; default: diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index cb3d51da6..e45d46e17 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -68,4 +68,23 @@ statement.each { |row| rows << row } rows.first.first.should be_kind_of Time end + + context "utf8_db_field" do + before(:each) do + @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") + @client.query("CREATE DATABASE test_mysql2_stmt_utf8") + @client.query("USE test_mysql2_stmt_utf8") + @client.query("CREATE TABLE テーブル (整数 int, 文字列 varchar(32)) charset=utf8") + @client.query("INSERT INTO テーブル (整数, 文字列) VALUES (1, 'イチ'), (2, '弐'), (3, 'さん')") + end + + after(:each) do + @client.query("DROP DATABASE test_mysql2_stmt_utf8") + end + + it "should be able to retrieve utf8 field names correctly" do + statement = @client.prepare 'SELECT * FROM `テーブル`' + statement.fields.should == ['整数', '文字列'] + end + end end From c18e7b5a46b40c01cc80b83e61fc0c9617976ea1 Mon Sep 17 00:00:00 2001 From: John Cant Date: Mon, 6 Jan 2014 15:09:44 +0000 Subject: [PATCH 225/783] Test and fix errors with every supported type --- ext/mysql2/statement.c | 25 ++++++++++++++++--------- spec/mysql2/statement_spec.rb | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index ac67bf487..9e06dd8a0 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -129,12 +129,16 @@ static void *nogvl_execute(void *ptr) { } } -#define FREE_BINDS \ - for (i = 0; i < argc; i++) { \ - if (bind_buffers[i].buffer) { \ - free(bind_buffers[i].buffer); \ - } \ - } \ +#define FREE_BINDS \ + for (i = 0; i < argc; i++) { \ + if (bind_buffers[i].buffer) { \ + if(bind_buffers[i].buffer_type == MYSQL_TYPE_STRING) { \ + free(bind_buffers[i].length); \ + } else { \ + free(bind_buffers[i].buffer); \ + } \ + } \ + } \ free(bind_buffers); /* call-seq: stmt.execute @@ -192,8 +196,9 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; bind_buffers[i].buffer = RSTRING_PTR(argv[i]); bind_buffers[i].buffer_length = RSTRING_LEN(argv[i]); - unsigned long len = RSTRING_LEN(argv[i]); - bind_buffers[i].length = &len; + unsigned long *len = malloc(sizeof(long)); + (*len) = RSTRING_LEN(argv[i]); + bind_buffers[i].length = len; break; default: // TODO: what Ruby type should support MYSQL_TYPE_TIME @@ -215,7 +220,9 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cDate) { - bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDATE; + + bind_buffers[i].buffer_type = MYSQL_TYPE_DATE; + bind_buffers[i].buffer = malloc(sizeof(MYSQL_TIME)); MYSQL_TIME t; diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index e45d46e17..547009640 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -69,6 +69,41 @@ rows.first.first.should be_kind_of Time end + it "should pass in nil parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' + statement.execute(nil).should == statement + end + + it "should pass in Bignum parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' + (lambda{ statement.execute(723623523423323223123) }).should raise_error(RangeError) + end + + it "should pass in Float parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT 2.3 as foo, 2) bar WHERE foo = ?' + statement.execute(2.4).should == statement + end + + it "should pass in String parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT "foo" as foo, 2) bar WHERE foo = ?' + statement.execute("blah").should == statement + end + + it "should pass in Time parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' + statement.execute(Time.now).should == statement + end + + it "should pass in DateTime parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' + statement.execute(DateTime.now).should == statement + end + + it "should pass in Date parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_DATE() as foo, 2) bar WHERE foo = ?' + statement.execute(Date.today) + end + context "utf8_db_field" do before(:each) do @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") From 6f1be843beb5831cf6d39a98f8a752bab083ca29 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Tue, 11 Nov 2014 21:44:13 +0100 Subject: [PATCH 226/783] fix whitespace --- ext/mysql2/statement.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 9e06dd8a0..7aba8ed42 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -129,17 +129,17 @@ static void *nogvl_execute(void *ptr) { } } -#define FREE_BINDS \ - for (i = 0; i < argc; i++) { \ - if (bind_buffers[i].buffer) { \ - if(bind_buffers[i].buffer_type == MYSQL_TYPE_STRING) { \ - free(bind_buffers[i].length); \ - } else { \ - free(bind_buffers[i].buffer); \ - } \ - } \ - } \ - free(bind_buffers); +#define FREE_BINDS \ + for (i = 0; i < argc; i++) { \ + if (bind_buffers[i].buffer) { \ + if (bind_buffers[i].buffer_type == MYSQL_TYPE_STRING) { \ + free(bind_buffers[i].length); \ + } else { \ + free(bind_buffers[i].buffer); \ + } \ + } \ + } \ + free(bind_buffers); /* call-seq: stmt.execute * From 8e0e0c4c63c69c3f7268ce561e83e6a8d8265f51 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Tue, 11 Nov 2014 22:23:04 +0100 Subject: [PATCH 227/783] free result set after use --- ext/mysql2/statement.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 7aba8ed42..41d13cccd 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -305,9 +305,11 @@ static VALUE fields(VALUE self) { rb_ary_store(field_list, (long)i, rb_field); } + mysql_free_result(metadata); return field_list; } +// FIXME refactor into Mysql2::Result static VALUE each(VALUE self) { MYSQL_STMT *stmt; MYSQL_RES *result; @@ -524,10 +526,16 @@ static VALUE each(VALUE self) { rb_yield(row); } + for (i = 0; i < field_count; i++) { + if (result_buffers[i].buffer) { + free(result_buffers[i].buffer); + } + } free(result_buffers); free(is_null); free(error); free(length); + mysql_free_result(result); } return self; From 4ddd953a9c329338a847efc47fcdefe31e62a49f Mon Sep 17 00:00:00 2001 From: Justin Case Date: Tue, 11 Nov 2014 22:48:57 +0100 Subject: [PATCH 228/783] FIXME reminder for mysql_stmt_store_result() --- ext/mysql2/statement.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 41d13cccd..24ce40229 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -331,6 +331,15 @@ static VALUE each(VALUE self) { unsigned long field_count; unsigned long i; + // FIXME we are calling mysql_stmt_store_result() *before* instead of *after* + // binding the data buffers with mysql_stmt_bind_result(). Turn into a config + // flag for result sets that require a lot of memory? + // + // From MySQL docs: + // "By default, result sets are fetched unbuffered a row at a time from the + // server. To buffer the entire result set on the client, call + // mysql_stmt_store_result() after binding the data buffers and before + // calling mysql_stmt_fetch()." if (mysql_stmt_store_result(stmt)) { rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } From ea3b2500c2ed0ac0bc48ef2afcc131ec137194be Mon Sep 17 00:00:00 2001 From: Justin Case Date: Mon, 24 Nov 2014 03:12:13 +0100 Subject: [PATCH 229/783] [WIP] working on nyaxt's code --- ext/mysql2/client.c | 25 +++- ext/mysql2/result.c | 12 +- ext/mysql2/result.h | 2 +- ext/mysql2/statement.c | 57 ++++++++- foo.rb | 14 +++ script/console | 2 +- spec/mysql2/error_spec.rb | 166 ++++++++++++------------- spec/mysql2/statement_spec.rb | 221 +++++++++++++++++----------------- 8 files changed, 297 insertions(+), 202 deletions(-) create mode 100644 foo.rb diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 93715e4cc..a038ea7c2 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -236,11 +236,26 @@ static void rb_mysql_client_free(void *ptr) { void decr_mysql2_client(mysql_client_wrapper *wrapper) { + + printf("decr_mysql2_client\n"); + printf(" >> refcount before: %i\n", wrapper->refcount); + wrapper->refcount--; + + printf(" >> refcount after: %i\n", wrapper->refcount); + if (wrapper->refcount == 0) { + printf(" -- wrapper->refcount == 0\n"); + nogvl_close(wrapper); + printf(" -- nogvl_close\n"); + xfree(wrapper->client); + printf(" -- xfree wrapper client\n"); + xfree(wrapper); + printf(" -- xfree wrapper\n"); + printf(" -- refcount: %i\n\n", wrapper->refcount); } } @@ -257,6 +272,9 @@ static VALUE allocate(VALUE klass) { wrapper->initialized = 0; /* means that that the wrapper is initialized */ wrapper->refcount = 1; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); + + printf("init refcount: %i == 1\n", wrapper->refcount); + return obj; } @@ -491,7 +509,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); RB_GC_GUARD(current); Check_Type(current, T_HASH); - resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result); + resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, NULL); return resultObj; } @@ -687,6 +705,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0); + printf("calling rb_mysql_client_async_result\n"); return rb_mysql_client_async_result(self); } else { return Qnil; @@ -1070,7 +1089,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); RB_GC_GUARD(current); Check_Type(current, T_HASH); - resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result); + resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, NULL); return resultObj; } @@ -1218,6 +1237,8 @@ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); + printf("prepare refcount: %i\n", wrapper->refcount); + return rb_mysql_stmt_new(self, sql); } diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 34dcbaba4..5c04f3ef9 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -80,14 +80,17 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { /* this is called during GC */ static void rb_mysql_result_free(void *ptr) { - mysql2_result_wrapper * wrapper = ptr; + mysql2_result_wrapper *wrapper = ptr; rb_mysql_result_free_result(wrapper); + printf("rb_mysql_result_free\n"); + // If the GC gets to client first it will be nil if (wrapper->client != Qnil) { decr_mysql2_client(wrapper->client_wrapper); } + printf("result.c xfree wrapper\n"); xfree(wrapper); } @@ -595,9 +598,11 @@ static VALUE rb_mysql_result_count(VALUE self) { } /* Mysql2::Result */ -VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) { +VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, MYSQL_STMT * s) { VALUE obj; mysql2_result_wrapper * wrapper; + + obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper); wrapper->numberOfFields = 0; wrapper->numberOfRows = 0; @@ -610,7 +615,10 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ wrapper->streamingComplete = 0; wrapper->client = client; wrapper->client_wrapper = DATA_PTR(client); + + printf("refcount++ before: %i\n", wrapper->client_wrapper->refcount); wrapper->client_wrapper->refcount++; + printf("refcount++ after: %i\n", wrapper->client_wrapper->refcount); rb_obj_call_init(obj, 0, NULL); diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 0bd131d34..fa3afbbf8 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -2,7 +2,7 @@ #define MYSQL2_RESULT_H void init_mysql2_result(); -VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r); +VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, MYSQL_STMT * s); typedef struct { VALUE fields; diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 24ce40229..305f0eec4 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -146,10 +146,12 @@ static void *nogvl_execute(void *ptr) { * Executes the current prepared statement, returns +stmt+. */ static VALUE execute(int argc, VALUE *argv, VALUE self) { - MYSQL_BIND *bind_buffers; + MYSQL_BIND *bind_buffers = NULL; unsigned long bind_count; long i; - MYSQL_STMT* stmt; + MYSQL_STMT *stmt; + MYSQL_RES *result; + VALUE resultObj; GET_STATEMENT(self); stmt = stmt_wrapper->stmt; @@ -258,7 +260,56 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { FREE_BINDS; } - return self; + + // =============== + + + result = mysql_stmt_result_metadata(stmt); + if(result == NULL) { + if(mysql_stmt_errno(stmt) != 0) { + // FIXME: MARK_CONN_INACTIVE(wrapper->client); + // FIXME: rb_raise_mysql2_stmt_error(self); + } + // no data and no error, so query was not a SELECT + return Qnil; + } + + + VALUE current; + current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); + GET_CLIENT(stmt_wrapper->client); + + printf("in statement.c: %i\n", wrapper->refcount); + + + resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, result, stmt); + + // resultObj = rb_mysql_result_to_obj(result, stmt); + // rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(stmt_wrapper->client, "@query_options"), rb_intern("dup"), 0)); + + + + // mysql2_result_wrapper* result_wrapper; + + // GET_CLIENT(stmt_wrapper->client); + // GetMysql2Result(resultObj, result_wrapper); + // result_wrapper->encoding = wrapper->encoding; + + + // return self; + // return resultObj; + + // =============== + + + + + // VALUE current; + // current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); + + printf("exiting statement.c\n"); + + return resultObj; } /* call-seq: stmt.fields # => array diff --git a/foo.rb b/foo.rb new file mode 100644 index 000000000..f450362c9 --- /dev/null +++ b/foo.rb @@ -0,0 +1,14 @@ +require 'rspec' +require 'mysql2' +require 'timeout' +require 'yaml' +DatabaseCredentials = YAML.load_file('spec/configuration.yml') + +# client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) +# statement = client.query 'SELECT 1' + +client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) +statement = client.prepare 'SELECT varchar_test, year_test FROM mysql2_test WHERE varchar_test = ?' +result = statement.execute('test') + +statement.each {|x| p x } diff --git a/script/console b/script/console index 9145f1691..ba74d13f6 100755 --- a/script/console +++ b/script/console @@ -4,4 +4,4 @@ set -e cd $(dirname "$0")/.. -exec ruby -S bin/pry -Ilib -r mysql2 -r mysql2/console +exec ruby -S pry -Ilib -r mysql2 -r mysql2/console diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 8b5cb2bce..16b26ce77 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -1,83 +1,83 @@ -# encoding: UTF-8 - -require 'spec_helper' - -describe Mysql2::Error do - let(:client) { Mysql2::Client.new(DatabaseCredentials['root']) } - - let :error do - begin - client.query("HAHAHA") - rescue Mysql2::Error => e - error = e - ensure - client.close - end - - error - end - - it "responds to error_number and sql_state, with aliases" do - error.should respond_to(:error_number) - error.should respond_to(:sql_state) - - # Mysql gem compatibility - error.should respond_to(:errno) - error.should respond_to(:error) - end - - if "".respond_to? :encoding - let :error do - client = Mysql2::Client.new(DatabaseCredentials['root']) - begin - client.query("\xE9\x80\xA0\xE5\xAD\x97") - rescue Mysql2::Error => e - error = e - ensure - client.close - end - - error - end - - let :bad_err do - client = Mysql2::Client.new(DatabaseCredentials['root']) - begin - client.query("\xE5\xC6\x7D\x1F") - rescue Mysql2::Error => e - error = e - ensure - client.close - end - - error - end - - it "returns error messages as UTF-8 by default" do - with_internal_encoding nil do - error.message.encoding.should eql(Encoding::UTF_8) - error.message.valid_encoding? - - bad_err.message.encoding.should eql(Encoding::UTF_8) - bad_err.message.valid_encoding? - - bad_err.message.should include("??}\u001F") - end - end - - it "returns sql state as ASCII" do - error.sql_state.encoding.should eql(Encoding::US_ASCII) - error.sql_state.valid_encoding? - end - - it "returns error messages and sql state in Encoding.default_internal if set" do - with_internal_encoding 'UTF-16LE' do - error.message.encoding.should eql(Encoding.default_internal) - error.message.valid_encoding? - - bad_err.message.encoding.should eql(Encoding.default_internal) - bad_err.message.valid_encoding? - end - end - end -end +# # encoding: UTF-8 + +# require 'spec_helper' + +# describe Mysql2::Error do +# let(:client) { Mysql2::Client.new(DatabaseCredentials['root']) } + +# let :error do +# begin +# client.query("HAHAHA") +# rescue Mysql2::Error => e +# error = e +# ensure +# client.close +# end + +# error +# end + +# it "responds to error_number and sql_state, with aliases" do +# error.should respond_to(:error_number) +# error.should respond_to(:sql_state) + +# # Mysql gem compatibility +# error.should respond_to(:errno) +# error.should respond_to(:error) +# end + +# if "".respond_to? :encoding +# let :error do +# client = Mysql2::Client.new(DatabaseCredentials['root']) +# begin +# client.query("\xE9\x80\xA0\xE5\xAD\x97") +# rescue Mysql2::Error => e +# error = e +# ensure +# client.close +# end + +# error +# end + +# let :bad_err do +# client = Mysql2::Client.new(DatabaseCredentials['root']) +# begin +# client.query("\xE5\xC6\x7D\x1F") +# rescue Mysql2::Error => e +# error = e +# ensure +# client.close +# end + +# error +# end + +# it "returns error messages as UTF-8 by default" do +# with_internal_encoding nil do +# error.message.encoding.should eql(Encoding::UTF_8) +# error.message.valid_encoding? + +# bad_err.message.encoding.should eql(Encoding::UTF_8) +# bad_err.message.valid_encoding? + +# bad_err.message.should include("??}\u001F") +# end +# end + +# it "returns sql state as ASCII" do +# error.sql_state.encoding.should eql(Encoding::US_ASCII) +# error.sql_state.valid_encoding? +# end + +# it "returns error messages and sql state in Encoding.default_internal if set" do +# with_internal_encoding 'UTF-16LE' do +# error.message.encoding.should eql(Encoding.default_internal) +# error.message.valid_encoding? + +# bad_err.message.encoding.should eql(Encoding.default_internal) +# bad_err.message.valid_encoding? +# end +# end +# end +# end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 547009640..f164e5dae 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -1,125 +1,126 @@ # encoding: UTF-8 -require 'spec_helper' +require './spec/spec_helper.rb' describe Mysql2::Statement do before :each do - @client = Mysql2::Client.new DatabaseCredentials['root'] + @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) end - it "should create a statement" do - statement = nil - lambda { statement = @client.prepare 'SELECT 1' }.should_not raise_error - statement.should be_kind_of Mysql2::Statement - end + # it "should create a statement" do + # statement = nil + # lambda { statement = @client.prepare 'SELECT 1' }.should_not raise_error + # statement.should be_kind_of Mysql2::Statement + # end - it "should raise an exception when server disconnects" do - @client.close - lambda { @client.prepare 'SELECT 1' }.should raise_error(Mysql2::Error) - end + # it "should raise an exception when server disconnects" do + # @client.close + # lambda { @client.prepare 'SELECT 1' }.should raise_error(Mysql2::Error) + # end - it "should tell us the param count" do - statement = @client.prepare 'SELECT ?, ?' - statement.param_count.should == 2 + # it "should tell us the param count" do + # statement = @client.prepare 'SELECT ?, ?' + # statement.param_count.should == 2 - statement2 = @client.prepare 'SELECT 1' - statement2.param_count.should == 0 - end + # statement2 = @client.prepare 'SELECT 1' + # statement2.param_count.should == 0 + # end - it "should tell us the field count" do - statement = @client.prepare 'SELECT ?, ?' - statement.field_count.should == 2 + # it "should tell us the field count" do + # statement = @client.prepare 'SELECT ?, ?' + # statement.field_count.should == 2 - statement2 = @client.prepare 'SELECT 1' - statement2.field_count.should == 1 - end + # statement2 = @client.prepare 'SELECT 1' + # statement2.field_count.should == 1 + # end it "should let us execute our statement" do statement = @client.prepare 'SELECT 1' - statement.execute.should == statement - end - - it "should raise an exception without a block" do - statement = @client.prepare 'SELECT 1' - statement.execute - lambda { statement.each }.should raise_error - end - - it "should tell us about the fields" do - statement = @client.prepare 'SELECT 1 as foo, 2' - statement.execute - list = statement.fields - list.length.should == 2 - list.first.should == 'foo' - list[1].should == '2' - end - - it "should let us iterate over results" do - statement = @client.prepare 'SELECT 1' - statement.execute - rows = [] - statement.each { |row| rows << row } - rows.should == [[1]] - end - - it "should select dates" do - statement = @client.prepare 'SELECT NOW()' - statement.execute - rows = [] - statement.each { |row| rows << row } - rows.first.first.should be_kind_of Time - end - - it "should pass in nil parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' - statement.execute(nil).should == statement - end - - it "should pass in Bignum parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' - (lambda{ statement.execute(723623523423323223123) }).should raise_error(RangeError) - end - - it "should pass in Float parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT 2.3 as foo, 2) bar WHERE foo = ?' - statement.execute(2.4).should == statement - end - - it "should pass in String parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT "foo" as foo, 2) bar WHERE foo = ?' - statement.execute("blah").should == statement - end - - it "should pass in Time parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' - statement.execute(Time.now).should == statement - end - - it "should pass in DateTime parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' - statement.execute(DateTime.now).should == statement - end - - it "should pass in Date parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_DATE() as foo, 2) bar WHERE foo = ?' - statement.execute(Date.today) - end - - context "utf8_db_field" do - before(:each) do - @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") - @client.query("CREATE DATABASE test_mysql2_stmt_utf8") - @client.query("USE test_mysql2_stmt_utf8") - @client.query("CREATE TABLE テーブル (整数 int, 文字列 varchar(32)) charset=utf8") - @client.query("INSERT INTO テーブル (整数, 文字列) VALUES (1, 'イチ'), (2, '弐'), (3, 'さん')") - end - - after(:each) do - @client.query("DROP DATABASE test_mysql2_stmt_utf8") - end - - it "should be able to retrieve utf8 field names correctly" do - statement = @client.prepare 'SELECT * FROM `テーブル`' - statement.fields.should == ['整数', '文字列'] - end - end + statement.execute.class.should == Mysql2::Result + GC.start + end + + # it "should raise an exception without a block" do + # statement = @client.prepare 'SELECT 1' + # statement.execute + # lambda { statement.each }.should raise_error + # end + + # it "should tell us about the fields" do + # statement = @client.prepare 'SELECT 1 as foo, 2' + # statement.execute + # list = statement.fields + # list.length.should == 2 + # list.first.should == 'foo' + # list[1].should == '2' + # end + + # it "should let us iterate over results" do + # statement = @client.prepare 'SELECT 1' + # statement.execute + # rows = [] + # statement.each { |row| rows << row } + # rows.should == [[1]] + # end + + # it "should select dates" do + # statement = @client.prepare 'SELECT NOW()' + # statement.execute + # rows = [] + # statement.each { |row| rows << row } + # rows.first.first.should be_kind_of Time + # end + + # it "should pass in nil parameters" do + # statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' + # statement.execute(nil).class.should == Mysql2::Result + # end + + # it "should pass in Bignum parameters" do + # statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' + # (lambda{ statement.execute(723623523423323223123) }).should raise_error(RangeError) + # end + + # it "should pass in Float parameters" do + # statement = @client.prepare 'SELECT * FROM (SELECT 2.3 as foo, 2) bar WHERE foo = ?' + # statement.execute(2.4).class.should == Mysql2::Result + # end + + # it "should pass in String parameters" do + # statement = @client.prepare 'SELECT * FROM (SELECT "foo" as foo, 2) bar WHERE foo = ?' + # statement.execute("blah").class.should == Mysql2::Result + # end + + # it "should pass in Time parameters" do + # statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' + # statement.execute(Time.now).class.should == Mysql2::Result + # end + + # it "should pass in DateTime parameters" do + # statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' + # statement.execute(DateTime.now).class.should == Mysql2::Result + # end + + # it "should pass in Date parameters" do + # statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_DATE() as foo, 2) bar WHERE foo = ?' + # statement.execute(Date.today) + # end + + # context "utf8_db_field" do + # before(:each) do + # @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") + # @client.query("CREATE DATABASE test_mysql2_stmt_utf8") + # @client.query("USE test_mysql2_stmt_utf8") + # @client.query("CREATE TABLE テーブル (整数 int, 文字列 varchar(32)) charset=utf8") + # @client.query("INSERT INTO テーブル (整数, 文字列) VALUES (1, 'イチ'), (2, '弐'), (3, 'さん')") + # end + + # after(:each) do + # @client.query("DROP DATABASE test_mysql2_stmt_utf8") + # end + + # it "should be able to retrieve utf8 field names correctly" do + # statement = @client.prepare 'SELECT * FROM `テーブル`' + # statement.fields.should == ['整数', '文字列'] + # end + # end end From 01b50ea3578a73d8db8d2358e5a28c4ccea1caed Mon Sep 17 00:00:00 2001 From: Justin Case Date: Sat, 6 Dec 2014 21:37:12 +0100 Subject: [PATCH 230/783] first part of 397158f --- ext/mysql2/client.c | 19 ------------------ ext/mysql2/result.c | 7 ------- ext/mysql2/statement.c | 45 +++++++++++------------------------------- 3 files changed, 11 insertions(+), 60 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index a038ea7c2..e37b9aff9 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -236,26 +236,12 @@ static void rb_mysql_client_free(void *ptr) { void decr_mysql2_client(mysql_client_wrapper *wrapper) { - - printf("decr_mysql2_client\n"); - printf(" >> refcount before: %i\n", wrapper->refcount); - wrapper->refcount--; - printf(" >> refcount after: %i\n", wrapper->refcount); - if (wrapper->refcount == 0) { - printf(" -- wrapper->refcount == 0\n"); - nogvl_close(wrapper); - printf(" -- nogvl_close\n"); - xfree(wrapper->client); - printf(" -- xfree wrapper client\n"); - xfree(wrapper); - printf(" -- xfree wrapper\n"); - printf(" -- refcount: %i\n\n", wrapper->refcount); } } @@ -273,8 +259,6 @@ static VALUE allocate(VALUE klass) { wrapper->refcount = 1; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); - printf("init refcount: %i == 1\n", wrapper->refcount); - return obj; } @@ -705,7 +689,6 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0); - printf("calling rb_mysql_client_async_result\n"); return rb_mysql_client_async_result(self); } else { return Qnil; @@ -1237,8 +1220,6 @@ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); - printf("prepare refcount: %i\n", wrapper->refcount); - return rb_mysql_stmt_new(self, sql); } diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 5c04f3ef9..bbd11c3f9 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -83,14 +83,11 @@ static void rb_mysql_result_free(void *ptr) { mysql2_result_wrapper *wrapper = ptr; rb_mysql_result_free_result(wrapper); - printf("rb_mysql_result_free\n"); - // If the GC gets to client first it will be nil if (wrapper->client != Qnil) { decr_mysql2_client(wrapper->client_wrapper); } - printf("result.c xfree wrapper\n"); xfree(wrapper); } @@ -615,13 +612,9 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ wrapper->streamingComplete = 0; wrapper->client = client; wrapper->client_wrapper = DATA_PTR(client); - - printf("refcount++ before: %i\n", wrapper->client_wrapper->refcount); wrapper->client_wrapper->refcount++; - printf("refcount++ after: %i\n", wrapper->client_wrapper->refcount); rb_obj_call_init(obj, 0, NULL); - rb_iv_set(obj, "@query_options", options); /* Options that cannot be changed in results.each(...) { |row| } diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 305f0eec4..5d2bf5ed5 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -260,10 +260,6 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { FREE_BINDS; } - - // =============== - - result = mysql_stmt_result_metadata(stmt); if(result == NULL) { if(mysql_stmt_errno(stmt) != 0) { @@ -274,40 +270,21 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { return Qnil; } - VALUE current; current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); GET_CLIENT(stmt_wrapper->client); - printf("in statement.c: %i\n", wrapper->refcount); - - resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, result, stmt); - // resultObj = rb_mysql_result_to_obj(result, stmt); - // rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(stmt_wrapper->client, "@query_options"), rb_intern("dup"), 0)); - - - - // mysql2_result_wrapper* result_wrapper; - - // GET_CLIENT(stmt_wrapper->client); - // GetMysql2Result(resultObj, result_wrapper); - // result_wrapper->encoding = wrapper->encoding; - - - // return self; - // return resultObj; - - // =============== - - - - - // VALUE current; - // current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); +#ifdef HAVE_RUBY_ENCODING_H + { + mysql2_result_wrapper *result_wrapper; - printf("exiting statement.c\n"); + GET_CLIENT(stmt_wrapper->client); + GetMysql2Result(resultObj, result_wrapper); + result_wrapper->encoding = wrapper->encoding; + } +#endif return resultObj; } @@ -360,10 +337,10 @@ static VALUE fields(VALUE self) { return field_list; } +#if 0 // FIXME refactor into Mysql2::Result static VALUE each(VALUE self) { MYSQL_STMT *stmt; - MYSQL_RES *result; GET_STATEMENT(self); stmt = stmt_wrapper->stmt; @@ -372,7 +349,6 @@ static VALUE each(VALUE self) { rb_raise(cMysql2Error, "FIXME: current limitation: each require block"); } - result = mysql_stmt_result_metadata(stmt); if (result) { MYSQL_BIND *result_buffers; my_bool *is_null; @@ -600,6 +576,7 @@ static VALUE each(VALUE self) { return self; } +#endif void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); @@ -608,5 +585,5 @@ void init_mysql2_statement() { rb_define_method(cMysql2Statement, "field_count", field_count, 0); rb_define_method(cMysql2Statement, "execute", execute, -1); rb_define_method(cMysql2Statement, "fields", fields, 0); - rb_define_method(cMysql2Statement, "each", each, 0); + // rb_define_method(cMysql2Statement, "each", each, 0); } From e69edb3df056d0971698e4f5fb33735fe39227a5 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Sun, 7 Dec 2014 00:26:42 +0100 Subject: [PATCH 231/783] re-enable specs --- spec/mysql2/error_spec.rb | 166 +++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 16b26ce77..8b5cb2bce 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -1,83 +1,83 @@ -# # encoding: UTF-8 - -# require 'spec_helper' - -# describe Mysql2::Error do -# let(:client) { Mysql2::Client.new(DatabaseCredentials['root']) } - -# let :error do -# begin -# client.query("HAHAHA") -# rescue Mysql2::Error => e -# error = e -# ensure -# client.close -# end - -# error -# end - -# it "responds to error_number and sql_state, with aliases" do -# error.should respond_to(:error_number) -# error.should respond_to(:sql_state) - -# # Mysql gem compatibility -# error.should respond_to(:errno) -# error.should respond_to(:error) -# end - -# if "".respond_to? :encoding -# let :error do -# client = Mysql2::Client.new(DatabaseCredentials['root']) -# begin -# client.query("\xE9\x80\xA0\xE5\xAD\x97") -# rescue Mysql2::Error => e -# error = e -# ensure -# client.close -# end - -# error -# end - -# let :bad_err do -# client = Mysql2::Client.new(DatabaseCredentials['root']) -# begin -# client.query("\xE5\xC6\x7D\x1F") -# rescue Mysql2::Error => e -# error = e -# ensure -# client.close -# end - -# error -# end - -# it "returns error messages as UTF-8 by default" do -# with_internal_encoding nil do -# error.message.encoding.should eql(Encoding::UTF_8) -# error.message.valid_encoding? - -# bad_err.message.encoding.should eql(Encoding::UTF_8) -# bad_err.message.valid_encoding? - -# bad_err.message.should include("??}\u001F") -# end -# end - -# it "returns sql state as ASCII" do -# error.sql_state.encoding.should eql(Encoding::US_ASCII) -# error.sql_state.valid_encoding? -# end - -# it "returns error messages and sql state in Encoding.default_internal if set" do -# with_internal_encoding 'UTF-16LE' do -# error.message.encoding.should eql(Encoding.default_internal) -# error.message.valid_encoding? - -# bad_err.message.encoding.should eql(Encoding.default_internal) -# bad_err.message.valid_encoding? -# end -# end -# end -# end +# encoding: UTF-8 + +require 'spec_helper' + +describe Mysql2::Error do + let(:client) { Mysql2::Client.new(DatabaseCredentials['root']) } + + let :error do + begin + client.query("HAHAHA") + rescue Mysql2::Error => e + error = e + ensure + client.close + end + + error + end + + it "responds to error_number and sql_state, with aliases" do + error.should respond_to(:error_number) + error.should respond_to(:sql_state) + + # Mysql gem compatibility + error.should respond_to(:errno) + error.should respond_to(:error) + end + + if "".respond_to? :encoding + let :error do + client = Mysql2::Client.new(DatabaseCredentials['root']) + begin + client.query("\xE9\x80\xA0\xE5\xAD\x97") + rescue Mysql2::Error => e + error = e + ensure + client.close + end + + error + end + + let :bad_err do + client = Mysql2::Client.new(DatabaseCredentials['root']) + begin + client.query("\xE5\xC6\x7D\x1F") + rescue Mysql2::Error => e + error = e + ensure + client.close + end + + error + end + + it "returns error messages as UTF-8 by default" do + with_internal_encoding nil do + error.message.encoding.should eql(Encoding::UTF_8) + error.message.valid_encoding? + + bad_err.message.encoding.should eql(Encoding::UTF_8) + bad_err.message.valid_encoding? + + bad_err.message.should include("??}\u001F") + end + end + + it "returns sql state as ASCII" do + error.sql_state.encoding.should eql(Encoding::US_ASCII) + error.sql_state.valid_encoding? + end + + it "returns error messages and sql state in Encoding.default_internal if set" do + with_internal_encoding 'UTF-16LE' do + error.message.encoding.should eql(Encoding.default_internal) + error.message.valid_encoding? + + bad_err.message.encoding.should eql(Encoding.default_internal) + bad_err.message.valid_encoding? + end + end + end +end From d3c55d21835d54e91a424fae55fc14ecd7f72b57 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Sun, 7 Dec 2014 00:27:49 +0100 Subject: [PATCH 232/783] re-enable more specs --- spec/mysql2/statement_spec.rb | 40 +++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index f164e5dae..52dc62513 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -6,28 +6,32 @@ @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) end - # it "should create a statement" do - # statement = nil - # lambda { statement = @client.prepare 'SELECT 1' }.should_not raise_error - # statement.should be_kind_of Mysql2::Statement - # end + it "should create a statement" do + statement = nil + lambda { statement = @client.prepare 'SELECT 1' }.should_not raise_error + statement.should be_kind_of Mysql2::Statement + end - # it "should raise an exception when server disconnects" do - # @client.close - # lambda { @client.prepare 'SELECT 1' }.should raise_error(Mysql2::Error) - # end + it "should raise an exception when server disconnects" do + @client.close + lambda { @client.prepare 'SELECT 1' }.should raise_error(Mysql2::Error) + end - # it "should tell us the param count" do - # statement = @client.prepare 'SELECT ?, ?' - # statement.param_count.should == 2 + it "should tell us the param count" do + statement = @client.prepare 'SELECT ?, ?' + statement.param_count.should == 2 - # statement2 = @client.prepare 'SELECT 1' - # statement2.param_count.should == 0 - # end + statement2 = @client.prepare 'SELECT 1' + statement2.param_count.should == 0 + end - # it "should tell us the field count" do - # statement = @client.prepare 'SELECT ?, ?' - # statement.field_count.should == 2 + it "should tell us the field count" do + statement = @client.prepare 'SELECT ?, ?' + statement.field_count.should == 2 + + statement2 = @client.prepare 'SELECT 1' + statement2.field_count.should == 1 + end # statement2 = @client.prepare 'SELECT 1' # statement2.field_count.should == 1 From 1b0284b8aa302f9e7248042885b9d26c3911e7d4 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Sun, 7 Dec 2014 00:28:10 +0100 Subject: [PATCH 233/783] use Mysql2::Result --- spec/mysql2/statement_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 52dc62513..588bda811 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -33,9 +33,11 @@ statement2.field_count.should == 1 end - # statement2 = @client.prepare 'SELECT 1' - # statement2.field_count.should == 1 - # end + it "should tell us the result count" do + statement = @client.prepare 'SELECT 1' + result = statement.execute + result.count.should == 1 + end it "should let us execute our statement" do statement = @client.prepare 'SELECT 1' From 9b876fff13adf8916ab48205846a3dbfc93f6add Mon Sep 17 00:00:00 2001 From: Justin Case Date: Sun, 7 Dec 2014 00:28:46 +0100 Subject: [PATCH 234/783] more specs --- spec/mysql2/statement_spec.rb | 171 +++++++++++++++++----------------- 1 file changed, 84 insertions(+), 87 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 588bda811..7d14952bb 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -42,91 +42,88 @@ it "should let us execute our statement" do statement = @client.prepare 'SELECT 1' statement.execute.class.should == Mysql2::Result - GC.start - end - - # it "should raise an exception without a block" do - # statement = @client.prepare 'SELECT 1' - # statement.execute - # lambda { statement.each }.should raise_error - # end - - # it "should tell us about the fields" do - # statement = @client.prepare 'SELECT 1 as foo, 2' - # statement.execute - # list = statement.fields - # list.length.should == 2 - # list.first.should == 'foo' - # list[1].should == '2' - # end - - # it "should let us iterate over results" do - # statement = @client.prepare 'SELECT 1' - # statement.execute - # rows = [] - # statement.each { |row| rows << row } - # rows.should == [[1]] - # end - - # it "should select dates" do - # statement = @client.prepare 'SELECT NOW()' - # statement.execute - # rows = [] - # statement.each { |row| rows << row } - # rows.first.first.should be_kind_of Time - # end - - # it "should pass in nil parameters" do - # statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' - # statement.execute(nil).class.should == Mysql2::Result - # end - - # it "should pass in Bignum parameters" do - # statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' - # (lambda{ statement.execute(723623523423323223123) }).should raise_error(RangeError) - # end - - # it "should pass in Float parameters" do - # statement = @client.prepare 'SELECT * FROM (SELECT 2.3 as foo, 2) bar WHERE foo = ?' - # statement.execute(2.4).class.should == Mysql2::Result - # end - - # it "should pass in String parameters" do - # statement = @client.prepare 'SELECT * FROM (SELECT "foo" as foo, 2) bar WHERE foo = ?' - # statement.execute("blah").class.should == Mysql2::Result - # end - - # it "should pass in Time parameters" do - # statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' - # statement.execute(Time.now).class.should == Mysql2::Result - # end - - # it "should pass in DateTime parameters" do - # statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' - # statement.execute(DateTime.now).class.should == Mysql2::Result - # end - - # it "should pass in Date parameters" do - # statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_DATE() as foo, 2) bar WHERE foo = ?' - # statement.execute(Date.today) - # end - - # context "utf8_db_field" do - # before(:each) do - # @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") - # @client.query("CREATE DATABASE test_mysql2_stmt_utf8") - # @client.query("USE test_mysql2_stmt_utf8") - # @client.query("CREATE TABLE テーブル (整数 int, 文字列 varchar(32)) charset=utf8") - # @client.query("INSERT INTO テーブル (整数, 文字列) VALUES (1, 'イチ'), (2, '弐'), (3, 'さん')") - # end - - # after(:each) do - # @client.query("DROP DATABASE test_mysql2_stmt_utf8") - # end - - # it "should be able to retrieve utf8 field names correctly" do - # statement = @client.prepare 'SELECT * FROM `テーブル`' - # statement.fields.should == ['整数', '文字列'] - # end - # end + end + + it "should raise an exception without a block" do + statement = @client.prepare 'SELECT 1' + statement.execute + lambda { statement.each }.should raise_error + end + + it "should tell us about the fields" do + statement = @client.prepare 'SELECT 1 as foo, 2' + statement.execute + list = statement.fields + list.length.should == 2 + list.first.should == 'foo' + list[1].should == '2' + end + + it "should let us iterate over results" do + statement = @client.prepare 'SELECT 1' + result = statement.execute + rows = [] + result.each { |r| rows << r } + rows.should == [{"1"=>1}] # as: hash + end + + it "should select dates" do + statement = @client.prepare 'SELECT NOW()' + result = statement.execute + result.first.first[1].should be_kind_of Time + end + + it "should pass in nil parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' + statement.execute(nil).class.should == Mysql2::Result + end + + it "should pass in Bignum parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' + (lambda{ statement.execute(723623523423323223123) }).should raise_error(RangeError) + end + + it "should pass in Float parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT 2.3 as foo, 2) bar WHERE foo = ?' + statement.execute(2.4).class.should == Mysql2::Result + end + + it "should pass in String parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT "foo" as foo, 2) bar WHERE foo = ?' + statement.execute("blah").class.should == Mysql2::Result + end + + it "should pass in Time parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' + statement.execute(Time.now).class.should == Mysql2::Result + end + + it "should pass in DateTime parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' + statement.execute(DateTime.now).class.should == Mysql2::Result + end + + it "should pass in Date parameters" do + statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_DATE() as foo, 2) bar WHERE foo = ?' + statement.execute(Date.today) + end + + context "utf8_db_field" do + before(:each) do + @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") + @client.query("CREATE DATABASE test_mysql2_stmt_utf8") + @client.query("USE test_mysql2_stmt_utf8") + @client.query("CREATE TABLE テーブル (整数 int, 文字列 varchar(32)) charset=utf8") + @client.query("INSERT INTO テーブル (整数, 文字列) VALUES (1, 'イチ'), (2, '弐'), (3, 'さん')") + end + + after(:each) do + @client.query("DROP DATABASE test_mysql2_stmt_utf8") + end + + it "should be able to retrieve utf8 field names correctly" do + statement = @client.prepare 'SELECT * FROM `テーブル`' + statement.fields.should == ['整数', '文字列'] + end + end end From 3284fcce6946a957d970742d24720f5216d0060c Mon Sep 17 00:00:00 2001 From: Justin Case Date: Sun, 7 Dec 2014 00:29:15 +0100 Subject: [PATCH 235/783] tmp test file --- foo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foo.rb b/foo.rb index f450362c9..92fa6dc96 100644 --- a/foo.rb +++ b/foo.rb @@ -11,4 +11,4 @@ statement = client.prepare 'SELECT varchar_test, year_test FROM mysql2_test WHERE varchar_test = ?' result = statement.execute('test') -statement.each {|x| p x } +p result.each From f53d856b6055974efe7132bec97f4491d813f982 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Mon, 6 Aug 2012 10:19:04 +0900 Subject: [PATCH 236/783] Mysql2::Result modified to support prepared statement results --- ext/mysql2/result.c | 495 ++++++++++++++++++++++++++++++++++++----- ext/mysql2/result.h | 1 + ext/mysql2/statement.c | 13 +- 3 files changed, 449 insertions(+), 60 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index bbd11c3f9..469cc37bc 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -71,7 +71,12 @@ static void rb_mysql_result_mark(void * wrapper) { /* this may be called manually or during GC */ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { - if (wrapper && wrapper->resultFreed != 1) { + if (!wrapper) return; + + if (wrapper->resultFreed != 1) { + if (wrapper->stmt) { + mysql_stmt_free_result(wrapper->stmt); + } /* FIXME: this may call flush_use_result, which can hit the socket */ mysql_free_result(wrapper->result); wrapper->resultFreed = 1; @@ -191,6 +196,238 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len) return (unsigned int)strtoul(msec_char, NULL, 10); } +static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) { + VALUE rowVal; + mysql2_result_wrapper * wrapper; + unsigned int i = 0; + MYSQL_BIND *result_buffers; // FIXME: don't do this every time + my_bool *is_null; + my_bool *error; + unsigned long *length; + +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *default_internal_enc; + rb_encoding *conn_enc; +#endif + GetMysql2Result(self, wrapper); + +#ifdef HAVE_RUBY_ENCODING_H + default_internal_enc = rb_default_internal_encoding(); + conn_enc = rb_to_encoding(wrapper->encoding); +#endif + + if (asArray) { + rowVal = rb_ary_new2(wrapper->numberOfFields); + } else { + rowVal = rb_hash_new(); + } + if (wrapper->fields == Qnil) { + wrapper->numberOfFields = mysql_num_fields(wrapper->result); + wrapper->fields = rb_ary_new2(wrapper->numberOfFields); + } + + result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND)); + is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); + error = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); + length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long)); + + for (i = 0; i < wrapper->numberOfFields; i++) { + result_buffers[i].buffer_type = fields[i].type; + + // mysql type | C type + switch(fields[i].type) { + case MYSQL_TYPE_NULL: // NULL + break; + case MYSQL_TYPE_TINY: // signed char + result_buffers[i].buffer = xcalloc(1, sizeof(signed char)); + result_buffers[i].buffer_length = sizeof(signed char); + break; + case MYSQL_TYPE_SHORT: // short int + result_buffers[i].buffer = xcalloc(1, sizeof(short int)); + result_buffers[i].buffer_length = sizeof(short int); + break; + case MYSQL_TYPE_INT24: // int + case MYSQL_TYPE_LONG: // int + case MYSQL_TYPE_YEAR: // int + result_buffers[i].buffer = xcalloc(1, sizeof(int)); + result_buffers[i].buffer_length = sizeof(int); + break; + case MYSQL_TYPE_LONGLONG: // long long int + result_buffers[i].buffer = xcalloc(1, sizeof(long long int)); + result_buffers[i].buffer_length = sizeof(long long int); + break; + case MYSQL_TYPE_FLOAT: // float + case MYSQL_TYPE_DOUBLE: // double + result_buffers[i].buffer = xcalloc(1, sizeof(double)); + result_buffers[i].buffer_length = sizeof(double); + break; + case MYSQL_TYPE_TIME: // MYSQL_TIME + case MYSQL_TYPE_DATE: // MYSQL_TIME + case MYSQL_TYPE_NEWDATE: // MYSQL_TIME + case MYSQL_TYPE_DATETIME: // MYSQL_TIME + case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME + result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME)); + result_buffers[i].buffer_length = sizeof(MYSQL_TIME); + break; + case MYSQL_TYPE_DECIMAL: // char[] + case MYSQL_TYPE_NEWDECIMAL: // char[] + case MYSQL_TYPE_STRING: // char[] + case MYSQL_TYPE_VAR_STRING: // char[] + case MYSQL_TYPE_VARCHAR: // char[] + case MYSQL_TYPE_TINY_BLOB: // char[] + case MYSQL_TYPE_BLOB: // char[] + case MYSQL_TYPE_MEDIUM_BLOB: // char[] + case MYSQL_TYPE_LONG_BLOB: // char[] + case MYSQL_TYPE_BIT: // char[] + case MYSQL_TYPE_SET: // char[] + case MYSQL_TYPE_ENUM: // char[] + case MYSQL_TYPE_GEOMETRY: // char[] + result_buffers[i].buffer = malloc(fields[i].max_length); + result_buffers[i].buffer_length = fields[i].max_length; + break; + default: + rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type); + } + + result_buffers[i].is_null = &is_null[i]; + result_buffers[i].length = &length[i]; + result_buffers[i].error = &error[i]; + result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0); + } + + if(mysql_stmt_bind_result(wrapper->stmt, result_buffers)) { + // FIXME: goto this + for(i = 0; i < wrapper->numberOfFields; i++) { + if (result_buffers[i].buffer) { + free(result_buffers[i].buffer); + } + } + free(result_buffers); + free(is_null); + free(error); + free(length); + rb_raise(cMysql2Error, "%s", mysql_stmt_error(wrapper->stmt)); + } + + if(mysql_stmt_fetch(wrapper->stmt)) { + return Qnil; + } + + for (i = 0; i < wrapper->numberOfFields; i++) { + VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys); + VALUE val = Qnil; + MYSQL_TIME *ts; + + if (is_null[i]) { + val = Qnil; + } else { + switch(result_buffers[i].buffer_type) { + case MYSQL_TYPE_TINY: // signed char + if (result_buffers[i].is_unsigned) { + val = UINT2NUM(*((unsigned char*)result_buffers[i].buffer)); + } else { + val = INT2NUM(*((signed char*)result_buffers[i].buffer)); + } + break; + case MYSQL_TYPE_SHORT: // short int + if (result_buffers[i].is_unsigned) { + val = UINT2NUM(*((unsigned short int*)result_buffers[i].buffer)); + } else { + val = INT2NUM(*((short int*)result_buffers[i].buffer)); + } + break; + case MYSQL_TYPE_INT24: // int + case MYSQL_TYPE_LONG: // int + case MYSQL_TYPE_YEAR: // int + if (result_buffers[i].is_unsigned) { + val = UINT2NUM(*((unsigned int*)result_buffers[i].buffer)); + } else { + val = INT2NUM(*((int*)result_buffers[i].buffer)); + } + break; + case MYSQL_TYPE_LONGLONG: // long long int + if (result_buffers[i].is_unsigned) { + val = ULL2NUM(*((unsigned long long int*)result_buffers[i].buffer)); + } else { + val = LL2NUM(*((long long int*)result_buffers[i].buffer)); + } + break; + case MYSQL_TYPE_FLOAT: // float + val = rb_float_new((double)(*((float*)result_buffers[i].buffer))); + break; + case MYSQL_TYPE_DOUBLE: // double + val = rb_float_new((double)(*((double*)result_buffers[i].buffer))); + break; + case MYSQL_TYPE_DATE: // MYSQL_TIME + ts = (MYSQL_TIME*)result_buffers[i].buffer; + val = rb_funcall(cDate, rb_intern("new"), 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); + break; + case MYSQL_TYPE_TIME: // MYSQL_TIME + ts = (MYSQL_TIME*)result_buffers[i].buffer; + val = rb_funcall(rb_cTime, + rb_intern("mktime"), 6, + UINT2NUM(Qnil), + UINT2NUM(Qnil), + UINT2NUM(Qnil), + UINT2NUM(ts->hour), + UINT2NUM(ts->minute), + UINT2NUM(ts->second)); + break; + case MYSQL_TYPE_NEWDATE: // MYSQL_TIME + case MYSQL_TYPE_DATETIME: // MYSQL_TIME + case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME + ts = (MYSQL_TIME*)result_buffers[i].buffer; + val = rb_funcall(rb_cTime, + rb_intern("mktime"), 6, + UINT2NUM(ts->year), + UINT2NUM(ts->month), + UINT2NUM(ts->day), + UINT2NUM(ts->hour), + UINT2NUM(ts->minute), + UINT2NUM(ts->second)); + break; + case MYSQL_TYPE_DECIMAL: // char[] + case MYSQL_TYPE_NEWDECIMAL: // char[] + val = rb_funcall(cBigDecimal, rb_intern("new"), 1, rb_str_new(result_buffers[i].buffer, *(result_buffers[i].length))); + break; + case MYSQL_TYPE_STRING: // char[] + case MYSQL_TYPE_VAR_STRING: // char[] + case MYSQL_TYPE_VARCHAR: // char[] + case MYSQL_TYPE_TINY_BLOB: // char[] + case MYSQL_TYPE_BLOB: // char[] + case MYSQL_TYPE_MEDIUM_BLOB: // char[] + case MYSQL_TYPE_LONG_BLOB: // char[] + case MYSQL_TYPE_BIT: // char[] + case MYSQL_TYPE_SET: // char[] + case MYSQL_TYPE_ENUM: // char[] + case MYSQL_TYPE_GEOMETRY: // char[] + val = rb_str_new(result_buffers[i].buffer, *(result_buffers[i].length)); +#ifdef HAVE_RUBY_ENCODING_H + val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); +#endif + break; + default: + rb_raise(cMysql2Error, "unhandled buffer type: %d", + result_buffers[i].buffer_type); + break; + } + } + + if (asArray) { + rb_ary_push(rowVal, val); + } else { + rb_hash_aset(rowVal, field, val); + } + } + + free(result_buffers); + free(is_null); + free(error); + free(length); + return rowVal; +} + + static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) { VALUE rowVal; mysql2_result_wrapper * wrapper; @@ -440,55 +677,114 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { return wrapper->fields; } -static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { - VALUE defaults, opts, block; - ID db_timezone, app_timezone, dbTz, appTz; - mysql2_result_wrapper * wrapper; +typedef struct { + int symbolizeKeys; + int asArray; + int castBool; + int cacheRows; + int cast; + int streaming; + ID db_timezone; + ID app_timezone; + int block_given; +} result_each_args; + +static VALUE rb_mysql_result_each_nonstmt(VALUE self, const result_each_args* args) { + mysql2_result_wrapper *wrapper; unsigned long i; - const char * errstr; - int symbolizeKeys, asArray, castBool, cacheRows, cast; - MYSQL_FIELD * fields = NULL; + const char *errstr; + MYSQL_FIELD *fields = NULL; GetMysql2Result(self, wrapper); - defaults = rb_iv_get(self, "@query_options"); - Check_Type(defaults, T_HASH); - if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) { - opts = rb_funcall(defaults, intern_merge, 1, opts); - } else { - opts = defaults; - } + if (wrapper->is_streaming) { + /* When streaming, we will only yield rows, not return them. */ + if (wrapper->rows == Qnil) { + wrapper->rows = rb_ary_new(); + } - symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys)); - asArray = rb_hash_aref(opts, sym_as) == sym_array; - castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans)); - cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows)); - cast = RTEST(rb_hash_aref(opts, sym_cast)); + if (!wrapper->streamingComplete) { + VALUE row; - if (wrapper->is_streaming && cacheRows) { - rb_warn("cacheRows is ignored if streaming is true"); - } + fields = mysql_fetch_fields(wrapper->result); - dbTz = rb_hash_aref(opts, sym_database_timezone); - if (dbTz == sym_local) { - db_timezone = intern_local; - } else if (dbTz == sym_utc) { - db_timezone = intern_utc; + do { + row = rb_mysql_result_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, args->cast, fields); + if (row != Qnil) { + wrapper->numberOfRows++; + if (args->block_given != Qnil) { + rb_yield(row); + } + } + } while(row != Qnil); + + rb_mysql_result_free_result(wrapper); + // wrapper->numberOfRows = wrapper->lastRowProcessed; + wrapper->streamingComplete = 1; + + // Check for errors, the connection might have gone out from under us + // mysql_error returns an empty string if there is no error + errstr = mysql_error(wrapper->client_wrapper->client); + if (errstr[0]) { + rb_raise(cMysql2Error, "%s", errstr); + } + } else { + rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery)."); + } } else { - if (!NIL_P(dbTz)) { - rb_warn(":database_timezone option must be :utc or :local - defaulting to :local"); + if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) { + /* we've already read the entire dataset from the C result into our */ + /* internal array. Lets hand that over to the user since it's ready to go */ + for (i = 0; i < wrapper->numberOfRows; i++) { + rb_yield(rb_ary_entry(wrapper->rows, i)); + } + } else { + unsigned long rowsProcessed = 0; + rowsProcessed = RARRAY_LEN(wrapper->rows); + fields = mysql_fetch_fields(wrapper->result); + + for (i = 0; i < wrapper->numberOfRows; i++) { + VALUE row; + if (args->cacheRows && i < rowsProcessed) { + row = rb_ary_entry(wrapper->rows, i); + } else { + row = rb_mysql_result_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, args->cast, fields); + + if (args->cacheRows) { + rb_ary_store(wrapper->rows, i, row); + } + wrapper->lastRowProcessed++; + } + + if (row == Qnil) { + /* we don't need the mysql C dataset around anymore, peace it */ + rb_mysql_result_free_result(wrapper); + return Qnil; + } + + if (args->block_given != Qnil) { + rb_yield(row); + } + } + if (wrapper->lastRowProcessed == wrapper->numberOfRows) { + /* we don't need the mysql C dataset around anymore, peace it */ + rb_mysql_result_free_result(wrapper); + } } - db_timezone = intern_local; } - appTz = rb_hash_aref(opts, sym_application_timezone); - if (appTz == sym_local) { - app_timezone = intern_local; - } else if (appTz == sym_utc) { - app_timezone = intern_utc; - } else { - app_timezone = Qnil; - } + // FIXME return Enumerator instead? + // return rb_ary_each(wrapper->rows); + return wrapper->rows; +} + +static VALUE rb_mysql_result_each_stmt(VALUE self, const result_each_args* args) { + unsigned long i; + const char *errstr; + mysql2_result_wrapper *wrapper; + MYSQL_FIELD *fields = NULL; + + GetMysql2Result(self, wrapper); if (wrapper->is_streaming) { /* When streaming, we will only yield rows, not return them. */ @@ -502,16 +798,17 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { fields = mysql_fetch_fields(wrapper->result); do { - row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields); + row = rb_mysql_result_stmt_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, args->cast, fields); if (row != Qnil) { wrapper->numberOfRows++; - if (block != Qnil) { + if (args->block_given != Qnil) { rb_yield(row); } } } while(row != Qnil); rb_mysql_result_free_result(wrapper); + // wrapper->numberOfRows = wrapper->lastRowProcessed; wrapper->streamingComplete = 1; // Check for errors, the connection might have gone out from under us @@ -524,16 +821,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery)."); } } else { - if (wrapper->lastRowProcessed == 0) { - wrapper->numberOfRows = mysql_num_rows(wrapper->result); - if (wrapper->numberOfRows == 0) { - wrapper->rows = rb_ary_new(); - return wrapper->rows; - } - wrapper->rows = rb_ary_new2(wrapper->numberOfRows); - } - - if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) { + if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) { /* we've already read the entire dataset from the C result into our */ /* internal array. Lets hand that over to the user since it's ready to go */ for (i = 0; i < wrapper->numberOfRows; i++) { @@ -546,11 +834,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { for (i = 0; i < wrapper->numberOfRows; i++) { VALUE row; - if (cacheRows && i < rowsProcessed) { + if (args->cacheRows && i < rowsProcessed) { row = rb_ary_entry(wrapper->rows, i); } else { - row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields); - if (cacheRows) { + row = rb_mysql_result_stmt_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, args->cast, fields); + if (args->cacheRows) { rb_ary_store(wrapper->rows, i, row); } wrapper->lastRowProcessed++; @@ -562,7 +850,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { return Qnil; } - if (block != Qnil) { + if (args->block_given != Qnil) { rb_yield(row); } } @@ -573,9 +861,99 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } } + // FIXME return Enumerator instead? + // return rb_ary_each(wrapper->rows); return wrapper->rows; } +static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { + result_each_args args; + VALUE defaults, opts, block; + ID db_timezone, app_timezone, dbTz, appTz; + mysql2_result_wrapper * wrapper; + unsigned long i; + const char * errstr; + int symbolizeKeys, asArray, castBool, cacheRows, cast; + MYSQL_FIELD * fields = NULL; + + GetMysql2Result(self, wrapper); + + defaults = rb_iv_get(self, "@query_options"); + Check_Type(defaults, T_HASH); + if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) { + opts = rb_funcall(defaults, intern_merge, 1, opts); + } else { + opts = defaults; + } + + symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys)); + asArray = rb_hash_aref(opts, sym_as) == sym_array; + castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans)); + cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows)); + cast = RTEST(rb_hash_aref(opts, sym_cast)); + + if (wrapper->is_streaming && cacheRows) { + rb_warn("cacheRows is ignored if streaming is true"); + } + + dbTz = rb_hash_aref(opts, sym_database_timezone); + if (dbTz == sym_local) { + db_timezone = intern_local; + } else if (dbTz == sym_utc) { + db_timezone = intern_utc; + } else { + if (!NIL_P(dbTz)) { + rb_warn(":database_timezone option must be :utc or :local - defaulting to :local"); + } + db_timezone = intern_local; + } + + appTz = rb_hash_aref(opts, sym_application_timezone); + if (appTz == sym_local) { + app_timezone = intern_local; + } else if (appTz == sym_utc) { + app_timezone = intern_utc; + } else { + app_timezone = Qnil; + } + + if (wrapper->lastRowProcessed == 0) { + if(args.streaming) { + // We can't get number of rows if we're streaming, + // until we've finished fetching all rows + wrapper->numberOfRows = 0; + wrapper->rows = rb_ary_new(); + } else { + wrapper->numberOfRows = wrapper->stmt ? mysql_stmt_num_rows(wrapper->stmt) : mysql_num_rows(wrapper->result); + if (wrapper->numberOfRows == 0) { + wrapper->rows = rb_ary_new(); + return wrapper->rows; + } + wrapper->rows = rb_ary_new2(wrapper->numberOfRows); + } + } + + // Backward compat + args.symbolizeKeys = symbolizeKeys; + args.asArray = asArray; + args.castBool = castBool; + args.cacheRows = cacheRows; + args.cast = cast; + args.db_timezone = db_timezone; + args.app_timezone = app_timezone; + args.block_given = block; + + if(!wrapper->stmt) + { + return rb_mysql_result_each_nonstmt(self, &args); + } + else + { + return rb_mysql_result_each_stmt(self, &args); + } + +} + static VALUE rb_mysql_result_count(VALUE self) { mysql2_result_wrapper *wrapper; @@ -590,7 +968,11 @@ static VALUE rb_mysql_result_count(VALUE self) { return LONG2NUM(RARRAY_LEN(wrapper->rows)); } else { /* MySQL returns an unsigned 64-bit long here */ - return ULL2NUM(mysql_num_rows(wrapper->result)); + if(wrapper->stmt) { + return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt)); + } else { + return ULL2NUM(mysql_num_rows(wrapper->result)); + } } } @@ -613,6 +995,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ wrapper->client = client; wrapper->client_wrapper = DATA_PTR(client); wrapper->client_wrapper->refcount++; + wrapper->stmt = s; rb_obj_call_init(obj, 0, NULL); rb_iv_set(obj, "@query_options", options); diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index fa3afbbf8..2b6a83a4c 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -16,6 +16,7 @@ typedef struct { char streamingComplete; char resultFreed; MYSQL_RES *result; + MYSQL_STMT *stmt; mysql_client_wrapper *client_wrapper; } mysql2_result_wrapper; diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 5d2bf5ed5..14ccd61ff 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -150,7 +150,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { unsigned long bind_count; long i; MYSQL_STMT *stmt; - MYSQL_RES *result; + MYSQL_RES *metadata; VALUE resultObj; GET_STATEMENT(self); @@ -260,8 +260,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { FREE_BINDS; } - result = mysql_stmt_result_metadata(stmt); - if(result == NULL) { + metadata = mysql_stmt_result_metadata(stmt); + if(metadata == NULL) { if(mysql_stmt_errno(stmt) != 0) { // FIXME: MARK_CONN_INACTIVE(wrapper->client); // FIXME: rb_raise_mysql2_stmt_error(self); @@ -274,7 +274,12 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); GET_CLIENT(stmt_wrapper->client); - resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, result, stmt); + // FIXME: don't do this if streaming opt. + if (mysql_stmt_store_result(stmt)) { + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + } + + resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, stmt); #ifdef HAVE_RUBY_ENCODING_H { From fb910950d12dcdfff1368952e9097b0a54f6d4c4 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Wed, 8 Aug 2012 16:29:18 +0900 Subject: [PATCH 237/783] remove Statement#each method --- ext/mysql2/statement.c | 242 ----------------------------------------- 1 file changed, 242 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 14ccd61ff..faa5a2565 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -342,247 +342,6 @@ static VALUE fields(VALUE self) { return field_list; } -#if 0 -// FIXME refactor into Mysql2::Result -static VALUE each(VALUE self) { - MYSQL_STMT *stmt; - GET_STATEMENT(self); - stmt = stmt_wrapper->stmt; - - if(! rb_block_given_p()) - { - rb_raise(cMysql2Error, "FIXME: current limitation: each require block"); - } - - if (result) { - MYSQL_BIND *result_buffers; - my_bool *is_null; - my_bool *error; - unsigned long *length; - MYSQL_FIELD *fields; - unsigned long field_count; - unsigned long i; - - // FIXME we are calling mysql_stmt_store_result() *before* instead of *after* - // binding the data buffers with mysql_stmt_bind_result(). Turn into a config - // flag for result sets that require a lot of memory? - // - // From MySQL docs: - // "By default, result sets are fetched unbuffered a row at a time from the - // server. To buffer the entire result set on the client, call - // mysql_stmt_store_result() after binding the data buffers and before - // calling mysql_stmt_fetch()." - if (mysql_stmt_store_result(stmt)) { - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); - } - - fields = mysql_fetch_fields(result); - field_count = mysql_num_fields(result); - - result_buffers = xcalloc(field_count, sizeof(MYSQL_BIND)); - is_null = xcalloc(field_count, sizeof(my_bool)); - error = xcalloc(field_count, sizeof(my_bool)); - length = xcalloc(field_count, sizeof(unsigned long)); - - for (i = 0; i < field_count; i++) { - result_buffers[i].buffer_type = fields[i].type; - - // mysql type | C type - switch(fields[i].type) { - case MYSQL_TYPE_NULL: // NULL - break; - case MYSQL_TYPE_TINY: // signed char - result_buffers[i].buffer = xcalloc(1, sizeof(signed char)); - result_buffers[i].buffer_length = sizeof(signed char); - break; - case MYSQL_TYPE_SHORT: // short int - result_buffers[i].buffer = xcalloc(1, sizeof(short int)); - result_buffers[i].buffer_length = sizeof(short int); - break; - case MYSQL_TYPE_INT24: // int - case MYSQL_TYPE_LONG: // int - case MYSQL_TYPE_YEAR: // int - result_buffers[i].buffer = xcalloc(1, sizeof(int)); - result_buffers[i].buffer_length = sizeof(int); - break; - case MYSQL_TYPE_LONGLONG: // long long int - result_buffers[i].buffer = xcalloc(1, sizeof(long long int)); - result_buffers[i].buffer_length = sizeof(long long int); - break; - case MYSQL_TYPE_FLOAT: // float - case MYSQL_TYPE_DOUBLE: // double - result_buffers[i].buffer = xcalloc(1, sizeof(double)); - result_buffers[i].buffer_length = sizeof(double); - break; - case MYSQL_TYPE_TIME: // MYSQL_TIME - case MYSQL_TYPE_DATE: // MYSQL_TIME - case MYSQL_TYPE_NEWDATE: // MYSQL_TIME - case MYSQL_TYPE_DATETIME: // MYSQL_TIME - case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME - result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME)); - result_buffers[i].buffer_length = sizeof(MYSQL_TIME); - break; - case MYSQL_TYPE_DECIMAL: // char[] - case MYSQL_TYPE_NEWDECIMAL: // char[] - case MYSQL_TYPE_STRING: // char[] - case MYSQL_TYPE_VAR_STRING: // char[] - case MYSQL_TYPE_VARCHAR: // char[] - case MYSQL_TYPE_TINY_BLOB: // char[] - case MYSQL_TYPE_BLOB: // char[] - case MYSQL_TYPE_MEDIUM_BLOB: // char[] - case MYSQL_TYPE_LONG_BLOB: // char[] - case MYSQL_TYPE_BIT: // char[] - case MYSQL_TYPE_SET: // char[] - case MYSQL_TYPE_ENUM: // char[] - case MYSQL_TYPE_GEOMETRY: // char[] - result_buffers[i].buffer = malloc(fields[i].max_length); - result_buffers[i].buffer_length = fields[i].max_length; - break; - default: - rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type); - } - - result_buffers[i].is_null = &is_null[i]; - result_buffers[i].length = &length[i]; - result_buffers[i].error = &error[i]; - result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0); - } - - if(mysql_stmt_bind_result(stmt, result_buffers)) { - for(i = 0; i < field_count; i++) { - if (result_buffers[i].buffer) { - free(result_buffers[i].buffer); - } - } - free(result_buffers); - free(is_null); - free(error); - free(length); - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); - } - - while(!mysql_stmt_fetch(stmt)) { - VALUE row = rb_ary_new2((long)field_count); - - for(i = 0; i < field_count; i++) { - VALUE column = Qnil; - MYSQL_TIME *ts; - - if (is_null[i]) { - column = Qnil; - } else { - switch(result_buffers[i].buffer_type) { - case MYSQL_TYPE_TINY: // signed char - if (result_buffers[i].is_unsigned) { - column = UINT2NUM(*((unsigned char*)result_buffers[i].buffer)); - } else { - column = INT2NUM(*((signed char*)result_buffers[i].buffer)); - } - break; - case MYSQL_TYPE_SHORT: // short int - if (result_buffers[i].is_unsigned) { - column = UINT2NUM(*((unsigned short int*)result_buffers[i].buffer)); - } else { - column = INT2NUM(*((short int*)result_buffers[i].buffer)); - } - break; - case MYSQL_TYPE_INT24: // int - case MYSQL_TYPE_LONG: // int - case MYSQL_TYPE_YEAR: // int - if (result_buffers[i].is_unsigned) { - column = UINT2NUM(*((unsigned int*)result_buffers[i].buffer)); - } else { - column = INT2NUM(*((int*)result_buffers[i].buffer)); - } - break; - case MYSQL_TYPE_LONGLONG: // long long int - if (result_buffers[i].is_unsigned) { - column = ULL2NUM(*((unsigned long long int*)result_buffers[i].buffer)); - } else { - column = LL2NUM(*((long long int*)result_buffers[i].buffer)); - } - break; - case MYSQL_TYPE_FLOAT: // float - column = rb_float_new((double)(*((float*)result_buffers[i].buffer))); - break; - case MYSQL_TYPE_DOUBLE: // double - column = rb_float_new((double)(*((double*)result_buffers[i].buffer))); - break; - case MYSQL_TYPE_DATE: // MYSQL_TIME - ts = (MYSQL_TIME*)result_buffers[i].buffer; - column = rb_funcall(cDate, rb_intern("new"), 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); - break; - case MYSQL_TYPE_TIME: // MYSQL_TIME - ts = (MYSQL_TIME*)result_buffers[i].buffer; - column = rb_funcall(rb_cTime, - rb_intern("mktime"), 6, - UINT2NUM(Qnil), - UINT2NUM(Qnil), - UINT2NUM(Qnil), - UINT2NUM(ts->hour), - UINT2NUM(ts->minute), - UINT2NUM(ts->second)); - break; - case MYSQL_TYPE_NEWDATE: // MYSQL_TIME - case MYSQL_TYPE_DATETIME: // MYSQL_TIME - case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME - ts = (MYSQL_TIME*)result_buffers[i].buffer; - column = rb_funcall(rb_cTime, - rb_intern("mktime"), 6, - UINT2NUM(ts->year), - UINT2NUM(ts->month), - UINT2NUM(ts->day), - UINT2NUM(ts->hour), - UINT2NUM(ts->minute), - UINT2NUM(ts->second)); - break; - case MYSQL_TYPE_DECIMAL: // char[] - case MYSQL_TYPE_NEWDECIMAL: // char[] - column = rb_funcall(cBigDecimal, rb_intern("new"), 1, rb_str_new(result_buffers[i].buffer, *(result_buffers[i].length))); - break; - case MYSQL_TYPE_STRING: // char[] - case MYSQL_TYPE_VAR_STRING: // char[] - case MYSQL_TYPE_VARCHAR: // char[] - case MYSQL_TYPE_TINY_BLOB: // char[] - case MYSQL_TYPE_BLOB: // char[] - case MYSQL_TYPE_MEDIUM_BLOB: // char[] - case MYSQL_TYPE_LONG_BLOB: // char[] - case MYSQL_TYPE_BIT: // char[] - case MYSQL_TYPE_SET: // char[] - case MYSQL_TYPE_ENUM: // char[] - case MYSQL_TYPE_GEOMETRY: // char[] - // FIXME: handle encoding - column = rb_str_new(result_buffers[i].buffer, *(result_buffers[i].length)); - break; - default: - rb_raise(cMysql2Error, "unhandled buffer type: %d", - result_buffers[i].buffer_type); - break; - } - } - - rb_ary_store(row, (long)i, column); - } - - rb_yield(row); - } - - for (i = 0; i < field_count; i++) { - if (result_buffers[i].buffer) { - free(result_buffers[i].buffer); - } - } - free(result_buffers); - free(is_null); - free(error); - free(length); - mysql_free_result(result); - } - - return self; -} -#endif - void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); @@ -590,5 +349,4 @@ void init_mysql2_statement() { rb_define_method(cMysql2Statement, "field_count", field_count, 0); rb_define_method(cMysql2Statement, "execute", execute, -1); rb_define_method(cMysql2Statement, "fields", fields, 0); - // rb_define_method(cMysql2Statement, "each", each, 0); } From bcefd2983e5aa082c55e810f84dca1848842b174 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Wed, 8 Aug 2012 16:41:53 +0900 Subject: [PATCH 238/783] added failing test: query with param in different encoding --- spec/mysql2/statement_spec.rb | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 7d14952bb..ca7b655ac 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -108,7 +108,7 @@ statement.execute(Date.today) end - context "utf8_db_field" do + context "utf8_db" do before(:each) do @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") @client.query("CREATE DATABASE test_mysql2_stmt_utf8") @@ -124,6 +124,29 @@ it "should be able to retrieve utf8 field names correctly" do statement = @client.prepare 'SELECT * FROM `テーブル`' statement.fields.should == ['整数', '文字列'] + result = statement.execute + + result.to_a.should == [{"整数"=>1, "文字列"=>"イチ"}, {"整数"=>2, "文字列"=>"弐"}, {"整数"=>3, "文字列"=>"さん"}] + end + + it "should be able to retrieve utf8 param query correctly" do + statement = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' + statement.param_count.should == 1 + + result = statement.execute 'イチ' + + result.to_a.should == [{"整数"=>1}] end + + it "should be able to retrieve query with param in different encoding correctly" do + statement = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' + statement.param_count.should == 1 + + param = 'イチ'.encode("EUC-JP") + result = statement.execute param + + result.to_a.should == [{"整数"=>1}] + end + end end From 28a7d661c15458246369161c9b8db01bf7e48493 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Wed, 8 Aug 2012 16:59:54 +0900 Subject: [PATCH 239/783] query params encoded correctly --- ext/mysql2/statement.c | 46 +++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index faa5a2565..2bf11d3ed 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -129,17 +129,13 @@ static void *nogvl_execute(void *ptr) { } } -#define FREE_BINDS \ - for (i = 0; i < argc; i++) { \ - if (bind_buffers[i].buffer) { \ - if (bind_buffers[i].buffer_type == MYSQL_TYPE_STRING) { \ - free(bind_buffers[i].length); \ - } else { \ - free(bind_buffers[i].buffer); \ - } \ - } \ - } \ - free(bind_buffers); +#define FREE_BINDS \ + for (i = 0; i < argc; i++) { \ + if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \ + xfree(bind_buffers[i].buffer); \ + } \ + } \ + xfree(bind_buffers); /* call-seq: stmt.execute * @@ -152,7 +148,18 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { MYSQL_STMT *stmt; MYSQL_RES *metadata; VALUE resultObj; + VALUE *params_enc = alloca(sizeof(VALUE) * argc); + unsigned long* length_buffers = NULL; +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *conn_enc; +#endif GET_STATEMENT(self); +#ifdef HAVE_RUBY_ENCODING_H + { + GET_CLIENT(stmt_wrapper->client); + conn_enc = rb_to_encoding(wrapper->encoding); + } +#endif stmt = stmt_wrapper->stmt; @@ -164,9 +171,11 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { // setup any bind variables in the query if (bind_count > 0) { bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND)); + length_buffers = xcalloc(bind_count, sizeof(unsigned long)); for (i = 0; i < argc; i++) { bind_buffers[i].buffer = NULL; + params_enc[i] = Qnil; switch (TYPE(argv[i])) { case T_NIL: @@ -194,13 +203,14 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]); break; case T_STRING: - // FIXME: convert encoding - bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; - bind_buffers[i].buffer = RSTRING_PTR(argv[i]); - bind_buffers[i].buffer_length = RSTRING_LEN(argv[i]); - unsigned long *len = malloc(sizeof(long)); - (*len) = RSTRING_LEN(argv[i]); - bind_buffers[i].length = len; + { + params_enc[i] = rb_str_export_to_enc(argv[i], conn_enc); + bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; + bind_buffers[i].buffer = RSTRING_PTR(params_enc[i]); + bind_buffers[i].buffer_length = RSTRING_LEN(params_enc[i]); + length_buffers[i] = bind_buffers[i].buffer_length; + bind_buffers[i].length = &length_buffers[i]; + } break; default: // TODO: what Ruby type should support MYSQL_TYPE_TIME From e771aa01bc1f4e45851c69527a3ce988b1938d57 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 11:18:30 +0900 Subject: [PATCH 240/783] reuse result bind buffers --- ext/mysql2/result.c | 185 ++++++++++++++++++++++++-------------------- ext/mysql2/result.h | 5 ++ 2 files changed, 105 insertions(+), 85 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 469cc37bc..562cc9ac7 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -71,11 +71,24 @@ static void rb_mysql_result_mark(void * wrapper) { /* this may be called manually or during GC */ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { + unsigned int i; if (!wrapper) return; if (wrapper->resultFreed != 1) { if (wrapper->stmt) { mysql_stmt_free_result(wrapper->stmt); + + if(wrapper->result_buffers) { + for(i = 0; i < wrapper->numberOfFields; i++) { + if (wrapper->result_buffers[i].buffer) { + free(wrapper->result_buffers[i].buffer); + } + } + free(wrapper->result_buffers); + free(wrapper->is_null); + free(wrapper->error); + free(wrapper->length); + } } /* FIXME: this may call flush_use_result, which can hit the socket */ mysql_free_result(wrapper->result); @@ -196,78 +209,56 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len) return (unsigned int)strtoul(msec_char, NULL, 10); } -static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) { +static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) { + unsigned int i; VALUE rowVal; mysql2_result_wrapper * wrapper; - unsigned int i = 0; - MYSQL_BIND *result_buffers; // FIXME: don't do this every time - my_bool *is_null; - my_bool *error; - unsigned long *length; - -#ifdef HAVE_RUBY_ENCODING_H - rb_encoding *default_internal_enc; - rb_encoding *conn_enc; -#endif GetMysql2Result(self, wrapper); -#ifdef HAVE_RUBY_ENCODING_H - default_internal_enc = rb_default_internal_encoding(); - conn_enc = rb_to_encoding(wrapper->encoding); -#endif - - if (asArray) { - rowVal = rb_ary_new2(wrapper->numberOfFields); - } else { - rowVal = rb_hash_new(); - } - if (wrapper->fields == Qnil) { - wrapper->numberOfFields = mysql_num_fields(wrapper->result); - wrapper->fields = rb_ary_new2(wrapper->numberOfFields); - } + if (wrapper->result_buffers != NULL) return; - result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND)); - is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); - error = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); - length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long)); + wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND)); + wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); + wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); + wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long)); for (i = 0; i < wrapper->numberOfFields; i++) { - result_buffers[i].buffer_type = fields[i].type; + wrapper->result_buffers[i].buffer_type = fields[i].type; // mysql type | C type switch(fields[i].type) { case MYSQL_TYPE_NULL: // NULL break; case MYSQL_TYPE_TINY: // signed char - result_buffers[i].buffer = xcalloc(1, sizeof(signed char)); - result_buffers[i].buffer_length = sizeof(signed char); + wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(signed char)); + wrapper->result_buffers[i].buffer_length = sizeof(signed char); break; case MYSQL_TYPE_SHORT: // short int - result_buffers[i].buffer = xcalloc(1, sizeof(short int)); - result_buffers[i].buffer_length = sizeof(short int); + wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(short int)); + wrapper->result_buffers[i].buffer_length = sizeof(short int); break; case MYSQL_TYPE_INT24: // int case MYSQL_TYPE_LONG: // int case MYSQL_TYPE_YEAR: // int - result_buffers[i].buffer = xcalloc(1, sizeof(int)); - result_buffers[i].buffer_length = sizeof(int); + wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(int)); + wrapper->result_buffers[i].buffer_length = sizeof(int); break; case MYSQL_TYPE_LONGLONG: // long long int - result_buffers[i].buffer = xcalloc(1, sizeof(long long int)); - result_buffers[i].buffer_length = sizeof(long long int); + wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(long long int)); + wrapper->result_buffers[i].buffer_length = sizeof(long long int); break; case MYSQL_TYPE_FLOAT: // float case MYSQL_TYPE_DOUBLE: // double - result_buffers[i].buffer = xcalloc(1, sizeof(double)); - result_buffers[i].buffer_length = sizeof(double); + wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(double)); + wrapper->result_buffers[i].buffer_length = sizeof(double); break; case MYSQL_TYPE_TIME: // MYSQL_TIME case MYSQL_TYPE_DATE: // MYSQL_TIME case MYSQL_TYPE_NEWDATE: // MYSQL_TIME case MYSQL_TYPE_DATETIME: // MYSQL_TIME case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME - result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME)); - result_buffers[i].buffer_length = sizeof(MYSQL_TIME); + wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME)); + wrapper->result_buffers[i].buffer_length = sizeof(MYSQL_TIME); break; case MYSQL_TYPE_DECIMAL: // char[] case MYSQL_TYPE_NEWDECIMAL: // char[] @@ -282,30 +273,52 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] - result_buffers[i].buffer = malloc(fields[i].max_length); - result_buffers[i].buffer_length = fields[i].max_length; + wrapper->result_buffers[i].buffer = malloc(fields[i].max_length); + wrapper->result_buffers[i].buffer_length = fields[i].max_length; break; default: rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type); } - result_buffers[i].is_null = &is_null[i]; - result_buffers[i].length = &length[i]; - result_buffers[i].error = &error[i]; - result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0); + wrapper->result_buffers[i].is_null = &wrapper->is_null[i]; + wrapper->result_buffers[i].length = &wrapper->length[i]; + wrapper->result_buffers[i].error = &wrapper->error[i]; + wrapper->result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0); } +} + +static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD *fields) { + VALUE rowVal; + mysql2_result_wrapper *wrapper; + unsigned int i = 0; + +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *default_internal_enc; + rb_encoding *conn_enc; +#endif + GetMysql2Result(self, wrapper); + +#ifdef HAVE_RUBY_ENCODING_H + default_internal_enc = rb_default_internal_encoding(); + conn_enc = rb_to_encoding(wrapper->encoding); +#endif + + if (asArray) { + rowVal = rb_ary_new2(wrapper->numberOfFields); + } else { + rowVal = rb_hash_new(); + } + if (wrapper->fields == Qnil) { + wrapper->numberOfFields = mysql_num_fields(wrapper->result); + wrapper->fields = rb_ary_new2(wrapper->numberOfFields); + } + + if (wrapper->result_buffers == NULL) { + rb_mysql_result_alloc_result_buffers(self, fields); + } + + if(mysql_stmt_bind_result(wrapper->stmt, wrapper->result_buffers)) { - if(mysql_stmt_bind_result(wrapper->stmt, result_buffers)) { - // FIXME: goto this - for(i = 0; i < wrapper->numberOfFields; i++) { - if (result_buffers[i].buffer) { - free(result_buffers[i].buffer); - } - } - free(result_buffers); - free(is_null); - free(error); - free(length); rb_raise(cMysql2Error, "%s", mysql_stmt_error(wrapper->stmt)); } @@ -318,52 +331,54 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t VALUE val = Qnil; MYSQL_TIME *ts; - if (is_null[i]) { + if (wrapper->is_null[i]) { val = Qnil; } else { - switch(result_buffers[i].buffer_type) { + const MYSQL_BIND* const result_buffer = &wrapper->result_buffers[i]; + + switch(result_buffer->buffer_type) { case MYSQL_TYPE_TINY: // signed char - if (result_buffers[i].is_unsigned) { - val = UINT2NUM(*((unsigned char*)result_buffers[i].buffer)); + if (result_buffer->is_unsigned) { + val = UINT2NUM(*((unsigned char*)result_buffer->buffer)); } else { - val = INT2NUM(*((signed char*)result_buffers[i].buffer)); + val = INT2NUM(*((signed char*)result_buffer->buffer)); } break; case MYSQL_TYPE_SHORT: // short int - if (result_buffers[i].is_unsigned) { - val = UINT2NUM(*((unsigned short int*)result_buffers[i].buffer)); + if (result_buffer->is_unsigned) { + val = UINT2NUM(*((unsigned short int*)result_buffer->buffer)); } else { - val = INT2NUM(*((short int*)result_buffers[i].buffer)); + val = INT2NUM(*((short int*)result_buffer->buffer)); } break; case MYSQL_TYPE_INT24: // int case MYSQL_TYPE_LONG: // int case MYSQL_TYPE_YEAR: // int - if (result_buffers[i].is_unsigned) { - val = UINT2NUM(*((unsigned int*)result_buffers[i].buffer)); + if (result_buffer->is_unsigned) { + val = UINT2NUM(*((unsigned int*)result_buffer->buffer)); } else { - val = INT2NUM(*((int*)result_buffers[i].buffer)); + val = INT2NUM(*((int*)result_buffer->buffer)); } break; case MYSQL_TYPE_LONGLONG: // long long int - if (result_buffers[i].is_unsigned) { - val = ULL2NUM(*((unsigned long long int*)result_buffers[i].buffer)); + if (result_buffer->is_unsigned) { + val = ULL2NUM(*((unsigned long long int*)result_buffer->buffer)); } else { - val = LL2NUM(*((long long int*)result_buffers[i].buffer)); + val = LL2NUM(*((long long int*)result_buffer->buffer)); } break; case MYSQL_TYPE_FLOAT: // float - val = rb_float_new((double)(*((float*)result_buffers[i].buffer))); + val = rb_float_new((double)(*((float*)result_buffer->buffer))); break; case MYSQL_TYPE_DOUBLE: // double - val = rb_float_new((double)(*((double*)result_buffers[i].buffer))); + val = rb_float_new((double)(*((double*)result_buffer->buffer))); break; case MYSQL_TYPE_DATE: // MYSQL_TIME - ts = (MYSQL_TIME*)result_buffers[i].buffer; + ts = (MYSQL_TIME*)result_buffer->buffer; val = rb_funcall(cDate, rb_intern("new"), 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); break; case MYSQL_TYPE_TIME: // MYSQL_TIME - ts = (MYSQL_TIME*)result_buffers[i].buffer; + ts = (MYSQL_TIME*)result_buffer->buffer; val = rb_funcall(rb_cTime, rb_intern("mktime"), 6, UINT2NUM(Qnil), @@ -376,7 +391,7 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t case MYSQL_TYPE_NEWDATE: // MYSQL_TIME case MYSQL_TYPE_DATETIME: // MYSQL_TIME case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME - ts = (MYSQL_TIME*)result_buffers[i].buffer; + ts = (MYSQL_TIME*)result_buffer->buffer; val = rb_funcall(rb_cTime, rb_intern("mktime"), 6, UINT2NUM(ts->year), @@ -388,7 +403,7 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t break; case MYSQL_TYPE_DECIMAL: // char[] case MYSQL_TYPE_NEWDECIMAL: // char[] - val = rb_funcall(cBigDecimal, rb_intern("new"), 1, rb_str_new(result_buffers[i].buffer, *(result_buffers[i].length))); + val = rb_funcall(cBigDecimal, rb_intern("new"), 1, rb_str_new(result_buffer->buffer, *(result_buffer->length))); break; case MYSQL_TYPE_STRING: // char[] case MYSQL_TYPE_VAR_STRING: // char[] @@ -401,14 +416,14 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] - val = rb_str_new(result_buffers[i].buffer, *(result_buffers[i].length)); + val = rb_str_new(result_buffer->buffer, *(result_buffer->length)); #ifdef HAVE_RUBY_ENCODING_H val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); #endif break; default: rb_raise(cMysql2Error, "unhandled buffer type: %d", - result_buffers[i].buffer_type); + result_buffer->buffer_type); break; } } @@ -420,10 +435,6 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t } } - free(result_buffers); - free(is_null); - free(error); - free(length); return rowVal; } @@ -996,6 +1007,10 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ wrapper->client_wrapper = DATA_PTR(client); wrapper->client_wrapper->refcount++; wrapper->stmt = s; + wrapper->result_buffers = NULL; + wrapper->is_null = NULL; + wrapper->error = NULL; + wrapper->length = NULL; rb_obj_call_init(obj, 0, NULL); rb_iv_set(obj, "@query_options", options); diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 2b6a83a4c..b95eae81c 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -18,6 +18,11 @@ typedef struct { MYSQL_RES *result; MYSQL_STMT *stmt; mysql_client_wrapper *client_wrapper; + /* statement result bind buffers */ + MYSQL_BIND *result_buffers; + my_bool *is_null; + my_bool *error; + unsigned long *length; } mysql2_result_wrapper; #define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj)); From a7db7a9e2296ca3123dbef90e9b32a579fed4a80 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 13:27:53 +0900 Subject: [PATCH 241/783] set active_thread for statement.execute --- ext/mysql2/client.c | 42 ++++++++++++++++++++++-------------------- ext/mysql2/client.h | 7 +++++++ ext/mysql2/result.c | 1 - ext/mysql2/statement.c | 13 +++++++------ 4 files changed, 36 insertions(+), 27 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index e37b9aff9..aefbd40dc 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -36,9 +36,6 @@ static VALUE rb_hash_dup(VALUE other) { rb_raise(cMysql2Error, "MySQL connection is already open"); \ } -#define MARK_CONN_INACTIVE(conn) \ - wrapper->active_thread = Qnil; - /* * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when @@ -407,7 +404,7 @@ static VALUE do_send_query(void *args) { mysql_client_wrapper *wrapper = query_args->wrapper; if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ - MARK_CONN_INACTIVE(self); + wrapper->active_thread = Qnil; return rb_raise_mysql2_error(wrapper); } return Qnil; @@ -590,6 +587,25 @@ static VALUE finish_and_mark_inactive(void *args) { } #endif +void rb_mysql_client_set_active_thread(VALUE self) { + VALUE thread_current = rb_thread_current(); + GET_CLIENT(self); + + // see if this connection is still waiting on a result from a previous query + if (NIL_P(wrapper->active_thread)) { + // mark this connection active + wrapper->active_thread = thread_current; + } else if (wrapper->active_thread == thread_current) { + rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result"); + } else { + VALUE inspect = rb_inspect(wrapper->active_thread); + const char *thr = StringValueCStr(inspect); + + rb_raise(cMysql2Error, "This connection is in use by: %s", thr); + RB_GC_GUARD(inspect); + } +} + /* call-seq: * client.abandon_results! * @@ -633,7 +649,6 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { struct nogvl_send_query_args args; int async = 0; VALUE opts, current; - VALUE thread_current = rb_thread_current(); #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; #endif @@ -663,23 +678,10 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { #endif args.sql_ptr = StringValuePtr(args.sql); args.sql_len = RSTRING_LEN(args.sql); - - /* see if this connection is still waiting on a result from a previous query */ - if (NIL_P(wrapper->active_thread)) { - /* mark this connection active */ - wrapper->active_thread = thread_current; - } else if (wrapper->active_thread == thread_current) { - rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result"); - } else { - VALUE inspect = rb_inspect(wrapper->active_thread); - const char *thr = StringValueCStr(inspect); - - rb_raise(cMysql2Error, "This connection is in use by: %s", thr); - RB_GC_GUARD(inspect); - } - args.wrapper = wrapper; + rb_mysql_client_set_active_thread(self); + #ifndef _WIN32 rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0); diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index db9947faa..ec948bff3 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -56,6 +56,13 @@ typedef struct { rb_raise(cMysql2Error, "closed MySQL connection"); \ } +void rb_mysql_client_set_active_thread(VALUE self); + +#define MARK_CONN_INACTIVE(conn) do {\ + GET_CLIENT(conn); \ + wrapper->active_thread = Qnil; \ + } while(0) + #define GET_CLIENT(self) \ mysql_client_wrapper *wrapper; \ Data_Get_Struct(self, mysql_client_wrapper, wrapper); diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 562cc9ac7..7b4a3a53b 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -211,7 +211,6 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len) static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) { unsigned int i; - VALUE rowVal; mysql2_result_wrapper * wrapper; GetMysql2Result(self, wrapper); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 2bf11d3ed..ccc2dda39 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -135,7 +135,7 @@ static void *nogvl_execute(void *ptr) { xfree(bind_buffers[i].buffer); \ } \ } \ - xfree(bind_buffers); + if (argc > 0) xfree(bind_buffers); /* call-seq: stmt.execute * @@ -266,15 +266,15 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } - if (bind_count > 0) { - FREE_BINDS; - } + FREE_BINDS; metadata = mysql_stmt_result_metadata(stmt); if(metadata == NULL) { if(mysql_stmt_errno(stmt) != 0) { - // FIXME: MARK_CONN_INACTIVE(wrapper->client); - // FIXME: rb_raise_mysql2_stmt_error(self); + // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. + + MARK_CONN_INACTIVE(stmt_wrapper->client); + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } // no data and no error, so query was not a SELECT return Qnil; @@ -288,6 +288,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { if (mysql_stmt_store_result(stmt)) { rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } + MARK_CONN_INACTIVE(stmt_wrapper->client); resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, stmt); From ec36692b64c0fe416bd3f3c826ab2ebae8e4ebc1 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 13:39:12 +0900 Subject: [PATCH 242/783] check is_stream --- ext/mysql2/statement.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index ccc2dda39..b1d4006a6 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -2,6 +2,7 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; +static VALUE sym_stream; static void rb_mysql_stmt_mark(void * ptr) { mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; @@ -150,6 +151,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { VALUE resultObj; VALUE *params_enc = alloca(sizeof(VALUE) * argc); unsigned long* length_buffers = NULL; + int is_streaming = 0; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; #endif @@ -160,6 +162,12 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { conn_enc = rb_to_encoding(wrapper->encoding); } #endif + { + VALUE valStreaming = rb_hash_aref(rb_iv_get(stmt_wrapper->client, "@query_options"), sym_stream); + if(valStreaming == Qtrue) { + is_streaming = 1; + } + } stmt = stmt_wrapper->stmt; @@ -284,11 +292,15 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); GET_CLIENT(stmt_wrapper->client); - // FIXME: don't do this if streaming opt. - if (mysql_stmt_store_result(stmt)) { - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + if(is_streaming) { + rb_raise(cMysql2Error, "TODO: streaming stmt execute not yet impl."); + } else { + // recieve the whole result set from ther server + if (mysql_stmt_store_result(stmt)) { + rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + } + MARK_CONN_INACTIVE(stmt_wrapper->client); } - MARK_CONN_INACTIVE(stmt_wrapper->client); resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, stmt); @@ -360,4 +372,6 @@ void init_mysql2_statement() { rb_define_method(cMysql2Statement, "field_count", field_count, 0); rb_define_method(cMysql2Statement, "execute", execute, -1); rb_define_method(cMysql2Statement, "fields", fields, 0); + + sym_stream = ID2SYM(rb_intern("stream")); } From 744f1af28f4fdc8c8b64ed2de995fffe9a262050 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 13:45:11 +0900 Subject: [PATCH 243/783] no gvl mysql_stmt_store_result --- ext/mysql2/statement.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index b1d4006a6..02c571dee 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -130,6 +130,16 @@ static void *nogvl_execute(void *ptr) { } } +static void *nogvl_stmt_store_result(void *ptr) { + MYSQL_STMT *stmt = ptr; + + if(mysql_stmt_store_result(stmt)) { + return (void *)Qfalse; + } else { + return (void *)Qtrue; + } +} + #define FREE_BINDS \ for (i = 0; i < argc; i++) { \ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \ @@ -295,8 +305,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { if(is_streaming) { rb_raise(cMysql2Error, "TODO: streaming stmt execute not yet impl."); } else { - // recieve the whole result set from ther server - if (mysql_stmt_store_result(stmt)) { + // recieve the whole result set from the server + if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) { rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); } MARK_CONN_INACTIVE(stmt_wrapper->client); From b12be74c5c78339e13b0fd96e682e099d049d78c Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 14:21:54 +0900 Subject: [PATCH 244/783] raise stmt errors via rb_raise_mysql2_stmt_error --- ext/mysql2/result.c | 7 +++-- ext/mysql2/statement.c | 68 +++++++++++++++++++++++++++++++++++------- ext/mysql2/statement.h | 7 ++++- 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 7b4a3a53b..480feec61 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -317,8 +317,11 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t } if(mysql_stmt_bind_result(wrapper->stmt, wrapper->result_buffers)) { - - rb_raise(cMysql2Error, "%s", mysql_stmt_error(wrapper->stmt)); + rb_raise_mysql2_stmt_error2(wrapper->stmt +#ifdef HAVE_RUBY_ENCODING_H + , conn_enc +#endif + ); } if(mysql_stmt_fetch(wrapper->stmt)) { diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 02c571dee..2cf744eb4 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -2,7 +2,12 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; -static VALUE sym_stream; +static VALUE sym_stream, intern_error_number_eql, intern_sql_state_eql; + +#define GET_STATEMENT(self) \ + mysql_stmt_wrapper *stmt_wrapper; \ + Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); + static void rb_mysql_stmt_mark(void * ptr) { mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; @@ -19,6 +24,49 @@ static void rb_mysql_stmt_free(void * ptr) { xfree(ptr); } +VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt +#ifdef HAVE_RUBY_ENCODING_H + , rb_encoding *conn_enc +#endif + ) { + VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt)); + VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt)); +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *default_internal_enc = rb_default_internal_encoding(); + + rb_enc_associate(rb_error_msg, conn_enc); + rb_enc_associate(rb_sql_state, conn_enc); + if (default_internal_enc) { + rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc); + rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); + } +#endif + + VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg); + rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_stmt_errno(stmt))); + rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state); + rb_exc_raise(e); + return Qnil; +} + +static void rb_raise_mysql2_stmt_error(VALUE self) { +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *conn_enc; +#endif + GET_STATEMENT(self); + { + GET_CLIENT(stmt_wrapper->client); + conn_enc = rb_to_encoding(wrapper->encoding); + } + + rb_raise_mysql2_stmt_error2(stmt_wrapper->stmt +#ifdef HAVE_RUBY_ENCODING_H + , conn_enc +#endif + ); +} + + /* * used to pass all arguments to mysql_stmt_prepare while inside * nogvl_prepare_statement_args @@ -88,18 +136,13 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { args.sql_len = RSTRING_LEN(sql); if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt_wrapper->stmt)); + rb_raise_mysql2_stmt_error(rb_stmt); } } return rb_stmt; } -#define GET_STATEMENT(self) \ - mysql_stmt_wrapper *stmt_wrapper; \ - Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper) - - /* call-seq: stmt.param_count # => Numeric * * Returns the number of parameters the prepared statement expects. @@ -275,13 +318,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { // copies bind_buffers into internal storage if (mysql_stmt_bind_param(stmt, bind_buffers)) { FREE_BINDS; - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + rb_raise_mysql2_stmt_error(self); } } if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) { FREE_BINDS; - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + rb_raise_mysql2_stmt_error(self); } FREE_BINDS; @@ -292,7 +335,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. MARK_CONN_INACTIVE(stmt_wrapper->client); - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + rb_raise_mysql2_stmt_error(self); } // no data and no error, so query was not a SELECT return Qnil; @@ -307,7 +350,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { } else { // recieve the whole result set from the server if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) { - rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt)); + rb_raise_mysql2_stmt_error(self); } MARK_CONN_INACTIVE(stmt_wrapper->client); } @@ -384,4 +427,7 @@ void init_mysql2_statement() { rb_define_method(cMysql2Statement, "fields", fields, 0); sym_stream = ID2SYM(rb_intern("stream")); + + intern_error_number_eql = rb_intern("error_number="); + intern_sql_state_eql = rb_intern("sql_state="); } diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index 34cfde40a..4c1626495 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -7,9 +7,14 @@ void init_mysql2_statement(); typedef struct { VALUE client; - MYSQL_STMT* stmt; + MYSQL_STMT *stmt; } mysql_stmt_wrapper; VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); +VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt +#ifdef HAVE_RUBY_ENCODING_H + , rb_encoding* conn_enc +#endif + ); #endif From c1dd35ca433afb23058dbf34c003bab64e625eed Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 14:45:58 +0900 Subject: [PATCH 245/783] no gvl mysql_stmt_fetch --- ext/mysql2/result.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 480feec61..c91b36105 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -120,6 +120,14 @@ static void *nogvl_fetch_row(void *ptr) { return mysql_fetch_row(result); } +static void *nogvl_stmt_fetch(void *ptr) { + MYSQL_STMT *stmt = ptr; + uintptr_t r = mysql_stmt_fetch(stmt); + + return (void *)r; +} + + static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) { mysql2_result_wrapper * wrapper; VALUE rb_field; @@ -324,8 +332,28 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t ); } - if(mysql_stmt_fetch(wrapper->stmt)) { - return Qnil; + { + int r = (int)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt, RUBY_UBF_IO, 0); + switch(r) { + case 0: + /* success */ + break; + + case 1: + /* error */ + rb_raise_mysql2_stmt_error2(wrapper->stmt +#ifdef HAVE_RUBY_ENCODING_H + , conn_enc +#endif + ); + + case MYSQL_NO_DATA: + /* no more row */ + return Qnil; + + case MYSQL_DATA_TRUNCATED: + rb_raise(cMysql2Error, "IMPLBUG: caught MYSQL_DATA_TRUNCATED. should not come here as buffer_length is set to fields[i].max_length."); + } } for (i = 0; i < wrapper->numberOfFields; i++) { From 8a141545815b359640ea60222fdaa5d5ea6d9921 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 18:14:25 +0900 Subject: [PATCH 246/783] streaming works! --- ext/mysql2/statement.c | 6 ++---- spec/mysql2/result_spec.rb | 3 ++- spec/mysql2/statement_spec.rb | 20 ++++++++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 2cf744eb4..102702942 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -193,7 +193,7 @@ static void *nogvl_stmt_store_result(void *ptr) { /* call-seq: stmt.execute * - * Executes the current prepared statement, returns +stmt+. + * Executes the current prepared statement, returns +result+. */ static VALUE execute(int argc, VALUE *argv, VALUE self) { MYSQL_BIND *bind_buffers = NULL; @@ -345,9 +345,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); GET_CLIENT(stmt_wrapper->client); - if(is_streaming) { - rb_raise(cMysql2Error, "TODO: streaming stmt execute not yet impl."); - } else { + if (!is_streaming) { // recieve the whole result set from the server if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) { rb_raise_mysql2_stmt_error(self); diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 0b0219c89..a1e8a881e 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -128,9 +128,10 @@ end it "#count should be zero for rows after streaming when there were no results" do + @client.query "USE test" result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false) result.count.should eql(0) - result.each.to_a + result.each {|r| } result.count.should eql(0) end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index ca7b655ac..09dfc7437 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -147,6 +147,26 @@ result.to_a.should == [{"整数"=>1}] end + end + context "streaming result" do + it "should be able to stream query result" do + n = 1 + stmt = @client.prepare("SELECT 1 UNION SELECT 2") + + @client.query_options.merge!({:stream => true, :cache_rows => false, :as => :array}) + + stmt.execute.each do |r| + case n + when 1 + r.should == [1] + when 2 + r.should == [2] + else + violated "returned more than two rows" + end + n += 1 + end + end end end From 96cea275442ba5c687b8ba9e43f971adefdb6590 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 18:29:49 +0900 Subject: [PATCH 247/783] ported row data type mapping test from result_spec.rb / test pass --- ext/mysql2/result.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index c91b36105..d31f8cd3d 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -411,9 +411,9 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t ts = (MYSQL_TIME*)result_buffer->buffer; val = rb_funcall(rb_cTime, rb_intern("mktime"), 6, - UINT2NUM(Qnil), - UINT2NUM(Qnil), - UINT2NUM(Qnil), + opt_time_year, + opt_time_month, + opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second)); From a7694a526330f1dc18d1de4a1c97aeba8bdedf7b Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 19:36:09 +0900 Subject: [PATCH 248/783] castBool impl --- ext/mysql2/result.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index d31f8cd3d..994f6e94f 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -368,6 +368,10 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t switch(result_buffer->buffer_type) { case MYSQL_TYPE_TINY: // signed char + if (castBool && fields[i].length == 1) { + val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse; + break; + } if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned char*)result_buffer->buffer)); } else { From 9199d11bcb57a85d455df2a45a4172916f55b3d4 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Sun, 7 Dec 2014 02:49:44 +0100 Subject: [PATCH 249/783] replace statement tests with nyaxt/5552b90 --- spec/mysql2/statement_spec.rb | 382 ++++++++++++++++++++++++++++------ 1 file changed, 324 insertions(+), 58 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 09dfc7437..eede084a5 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -33,15 +33,9 @@ statement2.field_count.should == 1 end - it "should tell us the result count" do - statement = @client.prepare 'SELECT 1' - result = statement.execute - result.count.should == 1 - end - it "should let us execute our statement" do statement = @client.prepare 'SELECT 1' - statement.execute.class.should == Mysql2::Result + statement.execute.should_not == nil end it "should raise an exception without a block" do @@ -50,21 +44,18 @@ lambda { statement.each }.should raise_error end - it "should tell us about the fields" do - statement = @client.prepare 'SELECT 1 as foo, 2' - statement.execute - list = statement.fields - list.length.should == 2 - list.first.should == 'foo' - list[1].should == '2' + it "should tell us the result count" do + statement = @client.prepare 'SELECT 1' + result = statement.execute + result.count.should == 1 end it "should let us iterate over results" do statement = @client.prepare 'SELECT 1' result = statement.execute rows = [] - result.each { |r| rows << r } - rows.should == [{"1"=>1}] # as: hash + result.each {|r| rows << r} + rows.should == [{"1"=>1}] end it "should select dates" do @@ -73,39 +64,13 @@ result.first.first[1].should be_kind_of Time end - it "should pass in nil parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' - statement.execute(nil).class.should == Mysql2::Result - end - - it "should pass in Bignum parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT 1 as foo, 2) bar WHERE foo = ?' - (lambda{ statement.execute(723623523423323223123) }).should raise_error(RangeError) - end - - it "should pass in Float parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT 2.3 as foo, 2) bar WHERE foo = ?' - statement.execute(2.4).class.should == Mysql2::Result - end - - it "should pass in String parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT "foo" as foo, 2) bar WHERE foo = ?' - statement.execute("blah").class.should == Mysql2::Result - end - - it "should pass in Time parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' - statement.execute(Time.now).class.should == Mysql2::Result - end - - it "should pass in DateTime parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_TIMESTAMP() as foo, 2) bar WHERE foo = ?' - statement.execute(DateTime.now).class.should == Mysql2::Result - end - - it "should pass in Date parameters" do - statement = @client.prepare 'SELECT * FROM (SELECT CURRENT_DATE() as foo, 2) bar WHERE foo = ?' - statement.execute(Date.today) + it "should tell us about the fields" do + statement = @client.prepare 'SELECT 1 as foo, 2' + statement.execute + list = statement.fields + list.length.should == 2 + list.first.should == 'foo' + list[1].should == '2' end context "utf8_db" do @@ -122,31 +87,32 @@ end it "should be able to retrieve utf8 field names correctly" do - statement = @client.prepare 'SELECT * FROM `テーブル`' - statement.fields.should == ['整数', '文字列'] - result = statement.execute + stmt = @client.prepare 'SELECT * FROM `テーブル`' + stmt.fields.should == ['整数', '文字列'] + result = stmt.execute result.to_a.should == [{"整数"=>1, "文字列"=>"イチ"}, {"整数"=>2, "文字列"=>"弐"}, {"整数"=>3, "文字列"=>"さん"}] end it "should be able to retrieve utf8 param query correctly" do - statement = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' - statement.param_count.should == 1 + stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' + stmt.param_count.should == 1 - result = statement.execute 'イチ' + result = stmt.execute 'イチ' result.to_a.should == [{"整数"=>1}] end it "should be able to retrieve query with param in different encoding correctly" do - statement = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' - statement.param_count.should == 1 + stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' + stmt.param_count.should == 1 param = 'イチ'.encode("EUC-JP") - result = statement.execute param + result = stmt.execute param result.to_a.should == [{"整数"=>1}] end + end context "streaming result" do @@ -169,4 +135,304 @@ end end end + + context "row data type mapping" do + before(:each) do + @client.query "USE test" + @test_result = @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute.first + end + + it "should return nil values for NULL and strings for everything else when :cast is false" do + result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast => false).first + result["null_test"].should be_nil + result["tiny_int_test"].should == "1" + result["bool_cast_test"].should == "1" + result["int_test"].should == "10" + result["date_test"].should == "2010-04-04" + result["enum_test"].should == "val1" + end + + it "should return nil for a NULL value" do + @test_result['null_test'].class.should eql(NilClass) + @test_result['null_test'].should eql(nil) + end + + it "should return Fixnum for a BIT value" do + @test_result['bit_test'].class.should eql(String) + @test_result['bit_test'].should eql("\000\000\000\000\000\000\000\005") + end + + it "should return Fixnum for a TINYINT value" do + [Fixnum, Bignum].should include(@test_result['tiny_int_test'].class) + @test_result['tiny_int_test'].should eql(1) + end + + it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do + @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (1)' + id1 = @client.last_id + @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (0)' + id2 = @client.last_id + @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)' + id3 = @client.last_id + + result1 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast_booleans => true + result2 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 0 LIMIT 1', :cast_booleans => true + result3 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = -1 LIMIT 1', :cast_booleans => true + result1.first['bool_cast_test'].should be_true + result2.first['bool_cast_test'].should be_false + result3.first['bool_cast_test'].should be_true + + @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" + end + + it "should return Fixnum for a SMALLINT value" do + [Fixnum, Bignum].should include(@test_result['small_int_test'].class) + @test_result['small_int_test'].should eql(10) + end + + it "should return Fixnum for a MEDIUMINT value" do + [Fixnum, Bignum].should include(@test_result['medium_int_test'].class) + @test_result['medium_int_test'].should eql(10) + end + + it "should return Fixnum for an INT value" do + [Fixnum, Bignum].should include(@test_result['int_test'].class) + @test_result['int_test'].should eql(10) + end + + it "should return Fixnum for a BIGINT value" do + [Fixnum, Bignum].should include(@test_result['big_int_test'].class) + @test_result['big_int_test'].should eql(10) + end + + it "should return Fixnum for a YEAR value" do + [Fixnum, Bignum].should include(@test_result['year_test'].class) + @test_result['year_test'].should eql(2009) + end + + it "should return BigDecimal for a DECIMAL value" do + @test_result['decimal_test'].class.should eql(BigDecimal) + @test_result['decimal_test'].should eql(10.3) + end + + it "should return Float for a FLOAT value" do + @test_result['float_test'].class.should eql(Float) + @test_result['float_test'].should be_within(1e-5).of(10.3) + end + + it "should return Float for a DOUBLE value" do + @test_result['double_test'].class.should eql(Float) + @test_result['double_test'].should be_within(1e-5).of(10.3) + end + + it "should return Time for a DATETIME value when within the supported range" do + @test_result['date_time_test'].class.should eql(Time) + @test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2010-04-04 11:44:00') + end + + if 1.size == 4 # 32bit + if RUBY_VERSION =~ /1.8/ + klass = Time + else + klass = DateTime + end + + it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do + # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 + r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") + r.first['test'].class.should eql(klass) + end + + it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do + # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 + r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") + r.first['test'].class.should eql(klass) + end + elsif 1.size == 8 # 64bit + unless RUBY_VERSION =~ /1.8/ + it "should return Time when timestamp is < 1901-12-13 20:45:52" do + r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") + r.first['test'].class.should eql(Time) + end + + it "should return Time when timestamp is > 2038-01-19T03:14:07" do + r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") + r.first['test'].class.should eql(Time) + end + else + it "should return Time when timestamp is > 0138-12-31 11:59:59" do + r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") + r.first['test'].class.should eql(Time) + end + + it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do + r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") + r.first['test'].class.should eql(DateTime) + end + + it "should return Time when timestamp is > 2038-01-19T03:14:07" do + r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") + r.first['test'].class.should eql(Time) + end + end + end + + it "should return Time for a TIMESTAMP value when within the supported range" do + @test_result['timestamp_test'].class.should eql(Time) + @test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2010-04-04 11:44:00') + end + + it "should return Time for a TIME value" do + @test_result['time_test'].class.should eql(Time) + @test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2000-01-01 11:44:00') + end + + it "should return Date for a DATE value" do + @test_result['date_test'].class.should eql(Date) + @test_result['date_test'].strftime("%Y-%m-%d").should eql('2010-04-04') + end + + it "should return String for an ENUM value" do + @test_result['enum_test'].class.should eql(String) + @test_result['enum_test'].should eql('val1') + end + + if defined? Encoding + context "string encoding for ENUM values" do + it "should default to the connection's encoding if Encoding.default_internal is nil" do + Encoding.default_internal = nil + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['enum_test'].encoding.should eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ascii")) + client2.query "USE test" + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['enum_test'].encoding.should eql(Encoding.find('us-ascii')) + end + + it "should use Encoding.default_internal" do + Encoding.default_internal = Encoding.find('utf-8') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['enum_test'].encoding.should eql(Encoding.default_internal) + Encoding.default_internal = Encoding.find('us-ascii') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['enum_test'].encoding.should eql(Encoding.default_internal) + end + end + end + + it "should return String for a SET value" do + @test_result['set_test'].class.should eql(String) + @test_result['set_test'].should eql('val1,val2') + end + + if defined? Encoding + context "string encoding for SET values" do + it "should default to the connection's encoding if Encoding.default_internal is nil" do + Encoding.default_internal = nil + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['set_test'].encoding.should eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ascii")) + client2.query "USE test" + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['set_test'].encoding.should eql(Encoding.find('us-ascii')) + end + + it "should use Encoding.default_internal" do + Encoding.default_internal = Encoding.find('utf-8') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['set_test'].encoding.should eql(Encoding.default_internal) + Encoding.default_internal = Encoding.find('us-ascii') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['set_test'].encoding.should eql(Encoding.default_internal) + end + end + end + + it "should return String for a BINARY value" do + @test_result['binary_test'].class.should eql(String) + @test_result['binary_test'].should eql("test#{"\000"*6}") + end + + if defined? Encoding + context "string encoding for BINARY values" do + it "should default to binary if Encoding.default_internal is nil" do + Encoding.default_internal = nil + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end + + it "should not use Encoding.default_internal" do + Encoding.default_internal = Encoding.find('utf-8') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + Encoding.default_internal = Encoding.find('us-ascii') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end + end + end + + { + 'char_test' => 'CHAR', + 'varchar_test' => 'VARCHAR', + 'varbinary_test' => 'VARBINARY', + 'tiny_blob_test' => 'TINYBLOB', + 'tiny_text_test' => 'TINYTEXT', + 'blob_test' => 'BLOB', + 'text_test' => 'TEXT', + 'medium_blob_test' => 'MEDIUMBLOB', + 'medium_text_test' => 'MEDIUMTEXT', + 'long_blob_test' => 'LONGBLOB', + 'long_text_test' => 'LONGTEXT' + }.each do |field, type| + it "should return a String for #{type}" do + @test_result[field].class.should eql(String) + @test_result[field].should eql("test") + end + + if defined? Encoding + context "string encoding for #{type} values" do + if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) + it "should default to binary if Encoding.default_internal is nil" do + Encoding.default_internal = nil + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end + + it "should not use Encoding.default_internal" do + Encoding.default_internal = Encoding.find('utf-8') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + Encoding.default_internal = Encoding.find('us-ascii') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result['binary_test'].encoding.should eql(Encoding.find('binary')) + end + else + it "should default to utf-8 if Encoding.default_internal is nil" do + Encoding.default_internal = nil + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result[field].encoding.should eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ascii")) + client2.query "USE test" + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result[field].encoding.should eql(Encoding.find('us-ascii')) + end + + it "should use Encoding.default_internal" do + Encoding.default_internal = Encoding.find('utf-8') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result[field].encoding.should eql(Encoding.default_internal) + Encoding.default_internal = Encoding.find('us-ascii') + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + result[field].encoding.should eql(Encoding.default_internal) + end + end + end + end + end + end + end From 7138a9b5293cc9985374cd790f1b65189938c45a Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 20:03:20 +0900 Subject: [PATCH 250/783] test pass --- ext/mysql2/statement.c | 8 +++++++- spec/mysql2/statement_spec.rb | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 102702942..c15877e06 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -2,7 +2,7 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; -static VALUE sym_stream, intern_error_number_eql, intern_sql_state_eql; +static VALUE sym_stream, intern_error_number_eql, intern_sql_state_eql, intern_each; #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ @@ -365,6 +365,11 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { } #endif + if (!is_streaming) { + // cache all result + rb_funcall(resultObj, intern_each, 0); + } + return resultObj; } @@ -428,4 +433,5 @@ void init_mysql2_statement() { intern_error_number_eql = rb_intern("error_number="); intern_sql_state_eql = rb_intern("sql_state="); + intern_each = rb_intern("each"); } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index eede084a5..a9483b50c 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -58,6 +58,18 @@ rows.should == [{"1"=>1}] end + it "should keep its result after other query" do + @client.query 'USE test' + @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int)' + @client.query 'INSERT INTO mysql2_stmt_q (a) VALUES (1), (2)' + stmt = @client.prepare('SELECT a FROM mysql2_stmt_q WHERE a = ?') + result1 = stmt.execute(1) + result2 = stmt.execute(2) + result2.first.should == {"a"=>2} + result1.first.should == {"a"=>1} + @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' + end + it "should select dates" do statement = @client.prepare 'SELECT NOW()' result = statement.execute From 941e5ed8eaad57f0a21a5985ec8accb433f5911b Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 20:32:58 +0900 Subject: [PATCH 251/783] port #each/#fields test from result_spec.rb / all test pass --- ext/mysql2/result.c | 4 ++ spec/mysql2/statement_spec.rb | 85 +++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 994f6e94f..a4f373949 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -941,6 +941,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { rb_warn("cacheRows is ignored if streaming is true"); } + if(wrapper->stmt && !args.cacheRows && !args.streaming) { + rb_warn("cacheRows is forced for prepared statements (if not streaming)"); + } + dbTz = rb_hash_aref(opts, sym_database_timezone); if (dbTz == sym_local) { db_timezone = intern_local; diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index a9483b50c..099ac22b1 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -148,6 +148,91 @@ end end + context "#each" do + # note: The current impl. of prepared statement requires results to be cached on #execute except for streaming queries + # The drawback of this is that args of Result#each is ignored... + + it "should yield rows as hash's" do + @result = @client.prepare("SELECT 1").execute + @result.each do |row| + row.class.should eql(Hash) + end + end + + it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do + @client.query_options[:symbolize_keys] = true + @result = @client.prepare("SELECT 1").execute + @result.each do |row| + row.keys.first.class.should eql(Symbol) + end + @client.query_options[:symbolize_keys] = false + end + + it "should be able to return results as an array" do + @client.query_options[:as] = :array + + @result = @client.prepare("SELECT 1").execute + @result.each do |row| + row.class.should eql(Array) + end + + @client.query_options[:as] = :hash + end + + it "should cache previously yielded results by default" do + @result = @client.prepare("SELECT 1").execute + @result.first.object_id.should eql(@result.first.object_id) + end + + it "should yield different value for #first if streaming" do + @client.query_options[:stream] = true + @client.query_options[:cache_rows] = false + + result = @client.prepare("SELECT 1 UNION SELECT 2").execute + result.first.should_not eql(result.first) + + @client.query_options[:stream] = false + @client.query_options[:cache_rows] = true + end + + it "should yield the same value for #first if streaming is disabled" do + @client.query_options[:stream] = false + result = @client.prepare("SELECT 1 UNION SELECT 2").execute + result.first.should eql(result.first) + end + + it "should throw an exception if we try to iterate twice when streaming is enabled" do + @client.query_options[:stream] = true + @client.query_options[:cache_rows] = false + + result = @client.prepare("SELECT 1 UNION SELECT 2").execute + + expect { + result.each {} + result.each {} + }.to raise_exception(Mysql2::Error) + + @client.query_options[:stream] = false + @client.query_options[:cache_rows] = true + end + end + + context "#fields" do + before(:each) do + @client.query "USE test" + @test_result = @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute + end + + it "method should exist" do + @test_result.should respond_to(:fields) + end + + it "should return an array of field names in proper order" do + result = @client.prepare("SELECT 'a', 'b', 'c'").execute + result.fields.should eql(['a', 'b', 'c']) + end + end + context "row data type mapping" do before(:each) do @client.query "USE test" From 1281d46675366d67bdc7cd44120264caf1a3dd26 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 21:15:55 +0900 Subject: [PATCH 252/783] support ruby w/o Encoding --- ext/mysql2/statement.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index c15877e06..6b7ad0275 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -265,7 +265,10 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { break; case T_STRING: { - params_enc[i] = rb_str_export_to_enc(argv[i], conn_enc); + params_enc[i] = argv[i]; +#ifdef HAVE_RUBY_ENCODING_H + params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); +#endif bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; bind_buffers[i].buffer = RSTRING_PTR(params_enc[i]); bind_buffers[i].buffer_length = RSTRING_LEN(params_enc[i]); From 1a0a1246aabd1affc850e3a4e30423b86059e3a4 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 21:22:23 +0900 Subject: [PATCH 253/783] compiles on ruby 1.8.7 / but test fail --- ext/mysql2/statement.c | 2 ++ spec/mysql2/statement_spec.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 6b7ad0275..3d8029832 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -56,7 +56,9 @@ static void rb_raise_mysql2_stmt_error(VALUE self) { GET_STATEMENT(self); { GET_CLIENT(stmt_wrapper->client); +#ifdef HAVE_RUBY_ENCODING_H conn_enc = rb_to_encoding(wrapper->encoding); +#endif } rb_raise_mysql2_stmt_error2(stmt_wrapper->stmt diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 099ac22b1..669f06a4c 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -125,7 +125,7 @@ result.to_a.should == [{"整数"=>1}] end - end + end if defined? Encoding context "streaming result" do it "should be able to stream query result" do From 62a8145d6c0ec15c41235cdc23757a98d435c9d8 Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 22:01:30 +0900 Subject: [PATCH 254/783] DateTime conversion is consistent with Client#query results / all test pass on 1.8.7 --- ext/mysql2/result.c | 57 +++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index a4f373949..9eb4c014c 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -408,33 +408,54 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t val = rb_float_new((double)(*((double*)result_buffer->buffer))); break; case MYSQL_TYPE_DATE: // MYSQL_TIME + case MYSQL_TYPE_NEWDATE: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; val = rb_funcall(cDate, rb_intern("new"), 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); break; case MYSQL_TYPE_TIME: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; - val = rb_funcall(rb_cTime, - rb_intern("mktime"), 6, - opt_time_year, - opt_time_month, - opt_time_month, - UINT2NUM(ts->hour), - UINT2NUM(ts->minute), - UINT2NUM(ts->second)); + val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second)); + if (!NIL_P(app_timezone)) { + if (app_timezone == intern_local) { + val = rb_funcall(val, intern_localtime, 0); + } else { // utc + val = rb_funcall(val, intern_utc, 0); + } + } break; - case MYSQL_TYPE_NEWDATE: // MYSQL_TIME case MYSQL_TYPE_DATETIME: // MYSQL_TIME - case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME + case MYSQL_TYPE_TIMESTAMP: { // MYSQL_TIME + uint64_t seconds; + ts = (MYSQL_TIME*)result_buffer->buffer; - val = rb_funcall(rb_cTime, - rb_intern("mktime"), 6, - UINT2NUM(ts->year), - UINT2NUM(ts->month), - UINT2NUM(ts->day), - UINT2NUM(ts->hour), - UINT2NUM(ts->minute), - UINT2NUM(ts->second)); + seconds = (ts->year*31557600ULL) + (ts->month*2592000ULL) + (ts->day*86400ULL) + (ts->hour*3600ULL) + (ts->minute*60ULL) + ts->second; + + if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead + VALUE offset = INT2NUM(0); + if (db_timezone == intern_local) { + offset = rb_funcall(cMysql2Client, intern_local_offset, 0); + } + val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), offset); + if (!NIL_P(app_timezone)) { + if (app_timezone == intern_local) { + offset = rb_funcall(cMysql2Client, intern_local_offset, 0); + val = rb_funcall(val, intern_new_offset, 1, offset); + } else { // utc + val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset); + } + } + } else { + val = rb_funcall(rb_cTime, db_timezone, 6, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second)); + if (!NIL_P(app_timezone)) { + if (app_timezone == intern_local) { + val = rb_funcall(val, intern_localtime, 0); + } else { // utc + val = rb_funcall(val, intern_utc, 0); + } + } + } break; + } case MYSQL_TYPE_DECIMAL: // char[] case MYSQL_TYPE_NEWDECIMAL: // char[] val = rb_funcall(cBigDecimal, rb_intern("new"), 1, rb_str_new(result_buffer->buffer, *(result_buffer->length))); From 6f2fa067bce76de2bea44b13f9a8f779de2cb11e Mon Sep 17 00:00:00 2001 From: nyaxt Date: Fri, 10 Aug 2012 22:28:15 +0900 Subject: [PATCH 255/783] cast is forced for prepared statements --- ext/mysql2/result.c | 18 ++++++++++++------ spec/mysql2/statement_spec.rb | 10 ---------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 9eb4c014c..e1750279c 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -294,7 +294,7 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields } } -static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD *fields) { +static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, MYSQL_FIELD * fields) { VALUE rowVal; mysql2_result_wrapper *wrapper; unsigned int i = 0; @@ -780,12 +780,13 @@ static VALUE rb_mysql_result_each_nonstmt(VALUE self, const result_each_args* ar wrapper->numberOfRows++; if (args->block_given != Qnil) { rb_yield(row); + wrapper->lastRowProcessed++; } } } while(row != Qnil); rb_mysql_result_free_result(wrapper); - // wrapper->numberOfRows = wrapper->lastRowProcessed; + wrapper->numberOfRows = wrapper->lastRowProcessed; wrapper->streamingComplete = 1; // Check for errors, the connection might have gone out from under us @@ -864,17 +865,18 @@ static VALUE rb_mysql_result_each_stmt(VALUE self, const result_each_args* args) fields = mysql_fetch_fields(wrapper->result); do { - row = rb_mysql_result_stmt_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, args->cast, fields); + row = rb_mysql_result_stmt_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, fields); if (row != Qnil) { wrapper->numberOfRows++; if (args->block_given != Qnil) { rb_yield(row); + wrapper->lastRowProcessed++; } } } while(row != Qnil); rb_mysql_result_free_result(wrapper); - // wrapper->numberOfRows = wrapper->lastRowProcessed; + wrapper->numberOfRows = wrapper->lastRowProcessed; wrapper->streamingComplete = 1; // Check for errors, the connection might have gone out from under us @@ -903,7 +905,7 @@ static VALUE rb_mysql_result_each_stmt(VALUE self, const result_each_args* args) if (args->cacheRows && i < rowsProcessed) { row = rb_ary_entry(wrapper->rows, i); } else { - row = rb_mysql_result_stmt_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, args->cast, fields); + row = rb_mysql_result_stmt_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, fields); if (args->cacheRows) { rb_ary_store(wrapper->rows, i, row); } @@ -962,10 +964,14 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { rb_warn("cacheRows is ignored if streaming is true"); } - if(wrapper->stmt && !args.cacheRows && !args.streaming) { + if (wrapper->stmt && !args.cacheRows && !args.streaming) { rb_warn("cacheRows is forced for prepared statements (if not streaming)"); } + if (wrapper->stmt && !args.cast) { + rb_warn("cast is forced for prepared statements"); + } + dbTz = rb_hash_aref(opts, sym_database_timezone); if (dbTz == sym_local) { db_timezone = intern_local; diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 669f06a4c..d0c7df8a5 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -239,16 +239,6 @@ @test_result = @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute.first end - it "should return nil values for NULL and strings for everything else when :cast is false" do - result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast => false).first - result["null_test"].should be_nil - result["tiny_int_test"].should == "1" - result["bool_cast_test"].should == "1" - result["int_test"].should == "10" - result["date_test"].should == "2010-04-04" - result["enum_test"].should == "val1" - end - it "should return nil for a NULL value" do @test_result['null_test'].class.should eql(NilClass) @test_result['null_test'].should eql(nil) From a363729f005e39011c4d1211387160a2ecdeeb13 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Sun, 7 Dec 2014 03:26:26 +0100 Subject: [PATCH 256/783] revert local changes --- foo.rb | 14 -------------- script/console | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 foo.rb diff --git a/foo.rb b/foo.rb deleted file mode 100644 index 92fa6dc96..000000000 --- a/foo.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'rspec' -require 'mysql2' -require 'timeout' -require 'yaml' -DatabaseCredentials = YAML.load_file('spec/configuration.yml') - -# client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) -# statement = client.query 'SELECT 1' - -client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) -statement = client.prepare 'SELECT varchar_test, year_test FROM mysql2_test WHERE varchar_test = ?' -result = statement.execute('test') - -p result.each diff --git a/script/console b/script/console index ba74d13f6..9145f1691 100755 --- a/script/console +++ b/script/console @@ -4,4 +4,4 @@ set -e cd $(dirname "$0")/.. -exec ruby -S pry -Ilib -r mysql2 -r mysql2/console +exec ruby -S bin/pry -Ilib -r mysql2 -r mysql2/console From 7c17137d42dbbf77a7f6f4f661123b6e041d82c4 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Fri, 6 Mar 2015 22:27:04 +0100 Subject: [PATCH 257/783] remove unused variables --- ext/mysql2/result.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index e1750279c..141594a24 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -939,10 +939,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { VALUE defaults, opts, block; ID db_timezone, app_timezone, dbTz, appTz; mysql2_result_wrapper * wrapper; - unsigned long i; - const char * errstr; int symbolizeKeys, asArray, castBool, cacheRows, cast; - MYSQL_FIELD * fields = NULL; GetMysql2Result(self, wrapper); From 556a0e11d02901f47dbc393f846ace5c5352f400 Mon Sep 17 00:00:00 2001 From: Justin Case Date: Fri, 6 Mar 2015 22:27:42 +0100 Subject: [PATCH 258/783] fix variable access --- ext/mysql2/result.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 141594a24..15c0b5209 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -961,11 +961,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { rb_warn("cacheRows is ignored if streaming is true"); } - if (wrapper->stmt && !args.cacheRows && !args.streaming) { + if (wrapper->stmt && !cacheRows && !wrapper->is_streaming) { rb_warn("cacheRows is forced for prepared statements (if not streaming)"); } - if (wrapper->stmt && !args.cast) { + if (wrapper->stmt && !cast) { rb_warn("cast is forced for prepared statements"); } From 563b120c3fd8a21da34502d741a9dc0cd38a411c Mon Sep 17 00:00:00 2001 From: Justin Case Date: Fri, 6 Mar 2015 22:29:44 +0100 Subject: [PATCH 259/783] fix streaming results and row counts --- ext/mysql2/result.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 15c0b5209..f3816276f 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -780,13 +780,11 @@ static VALUE rb_mysql_result_each_nonstmt(VALUE self, const result_each_args* ar wrapper->numberOfRows++; if (args->block_given != Qnil) { rb_yield(row); - wrapper->lastRowProcessed++; } } } while(row != Qnil); rb_mysql_result_free_result(wrapper); - wrapper->numberOfRows = wrapper->lastRowProcessed; wrapper->streamingComplete = 1; // Check for errors, the connection might have gone out from under us @@ -799,6 +797,15 @@ static VALUE rb_mysql_result_each_nonstmt(VALUE self, const result_each_args* ar rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery)."); } } else { + if (wrapper->lastRowProcessed == 0) { + wrapper->numberOfRows = mysql_num_rows(wrapper->result); + if (wrapper->numberOfRows == 0) { + wrapper->rows = rb_ary_new(); + return wrapper->rows; + } + wrapper->rows = rb_ary_new2(wrapper->numberOfRows); + } + if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) { /* we've already read the entire dataset from the C result into our */ /* internal array. Lets hand that over to the user since it's ready to go */ @@ -816,7 +823,6 @@ static VALUE rb_mysql_result_each_nonstmt(VALUE self, const result_each_args* ar row = rb_ary_entry(wrapper->rows, i); } else { row = rb_mysql_result_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, args->cast, fields); - if (args->cacheRows) { rb_ary_store(wrapper->rows, i, row); } @@ -990,20 +996,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { app_timezone = Qnil; } - if (wrapper->lastRowProcessed == 0) { - if(args.streaming) { - // We can't get number of rows if we're streaming, - // until we've finished fetching all rows - wrapper->numberOfRows = 0; + if (wrapper->lastRowProcessed == 0 && !wrapper->is_streaming) { + wrapper->numberOfRows = wrapper->stmt ? mysql_stmt_num_rows(wrapper->stmt) : mysql_num_rows(wrapper->result); + if (wrapper->numberOfRows == 0) { wrapper->rows = rb_ary_new(); - } else { - wrapper->numberOfRows = wrapper->stmt ? mysql_stmt_num_rows(wrapper->stmt) : mysql_num_rows(wrapper->result); - if (wrapper->numberOfRows == 0) { - wrapper->rows = rb_ary_new(); - return wrapper->rows; - } - wrapper->rows = rb_ary_new2(wrapper->numberOfRows); + return wrapper->rows; } + wrapper->rows = rb_ary_new2(wrapper->numberOfRows); } // Backward compat From ae5bbdc9e8e5f3a320c5e85b139b7fd8c940445d Mon Sep 17 00:00:00 2001 From: Justin Case Date: Fri, 6 Mar 2015 23:18:31 +0100 Subject: [PATCH 260/783] reuse simulated rb_hash_dup in statement.c .. fixes tests under 1.8.7 --- ext/mysql2/client.c | 2 +- ext/mysql2/client.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index aefbd40dc..ba09ea63a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -20,7 +20,7 @@ static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_arr static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql; #ifndef HAVE_RB_HASH_DUP -static VALUE rb_hash_dup(VALUE other) { +VALUE rb_hash_dup(VALUE other) { return rb_funcall(rb_cHash, rb_intern("[]"), 1, other); } #endif diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index ec948bff3..94098e21a 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -71,3 +71,7 @@ void init_mysql2_client(); void decr_mysql2_client(mysql_client_wrapper *wrapper); #endif + +#ifndef HAVE_RB_HASH_DUP +VALUE rb_hash_dup(VALUE other); +#endif From 97507decd3331091aedd2341ec91e21edfea909a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 2 Apr 2015 03:42:02 -0700 Subject: [PATCH 261/783] Meld together rb_mysql_result_each_stmt and rb_mysql_result_each_nonstmt --- ext/mysql2/result.c | 208 ++++++++++++-------------------------------- 1 file changed, 57 insertions(+), 151 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index f3816276f..242d09e92 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -50,6 +50,18 @@ static rb_encoding *binaryEncoding; #define MYSQL2_MIN_TIME 62171150401ULL #endif +typedef struct { + int symbolizeKeys; + int asArray; + int castBool; + int cacheRows; + int cast; + int streaming; + ID db_timezone; + ID app_timezone; + int block_given; +} result_each_args; + VALUE cBigDecimal, cDateTime, cDate; static VALUE cMysql2Result; static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset; @@ -294,7 +306,8 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields } } -static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, MYSQL_FIELD * fields) { +static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) +{ VALUE rowVal; mysql2_result_wrapper *wrapper; unsigned int i = 0; @@ -310,7 +323,7 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t conn_enc = rb_to_encoding(wrapper->encoding); #endif - if (asArray) { + if (args->asArray) { rowVal = rb_ary_new2(wrapper->numberOfFields); } else { rowVal = rb_hash_new(); @@ -357,7 +370,7 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t } for (i = 0; i < wrapper->numberOfFields; i++) { - VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys); + VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys); VALUE val = Qnil; MYSQL_TIME *ts; @@ -368,7 +381,7 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t switch(result_buffer->buffer_type) { case MYSQL_TYPE_TINY: // signed char - if (castBool && fields[i].length == 1) { + if (args->castBool && fields[i].length == 1) { val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse; break; } @@ -414,9 +427,9 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t break; case MYSQL_TYPE_TIME: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; - val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second)); - if (!NIL_P(app_timezone)) { - if (app_timezone == intern_local) { + val = rb_funcall(rb_cTime, args->db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second)); + if (!NIL_P(args->app_timezone)) { + if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { // utc val = rb_funcall(val, intern_utc, 0); @@ -432,12 +445,12 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead VALUE offset = INT2NUM(0); - if (db_timezone == intern_local) { + if (args->db_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); } val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), offset); - if (!NIL_P(app_timezone)) { - if (app_timezone == intern_local) { + if (!NIL_P(args->app_timezone)) { + if (args->app_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); val = rb_funcall(val, intern_new_offset, 1, offset); } else { // utc @@ -445,9 +458,9 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t } } } else { - val = rb_funcall(rb_cTime, db_timezone, 6, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second)); - if (!NIL_P(app_timezone)) { - if (app_timezone == intern_local) { + val = rb_funcall(rb_cTime, args->db_timezone, 6, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second)); + if (!NIL_P(args->app_timezone)) { + if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { // utc val = rb_funcall(val, intern_utc, 0); @@ -483,7 +496,7 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t } } - if (asArray) { + if (args->asArray) { rb_ary_push(rowVal, val); } else { rb_hash_aset(rowVal, field, val); @@ -494,7 +507,8 @@ static VALUE rb_mysql_result_stmt_fetch_row(VALUE self, ID db_timezone, ID app_t } -static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) { +static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) +{ VALUE rowVal; mysql2_result_wrapper * wrapper; MYSQL_ROW row; @@ -518,7 +532,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo return Qnil; } - if (asArray) { + if (args->asArray) { rowVal = rb_ary_new2(wrapper->numberOfFields); } else { rowVal = rb_hash_new(); @@ -530,12 +544,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo } for (i = 0; i < wrapper->numberOfFields; i++) { - VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys); + VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys); if (row[i]) { VALUE val = Qnil; enum enum_field_types type = fields[i].type; - if (!cast) { + if (!args->cast) { if (type == MYSQL_TYPE_NULL) { val = Qnil; } else { @@ -550,14 +564,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo val = Qnil; break; case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ - if (castBool && fields[i].length == 1) { + if (args->castBool && fields[i].length == 1) { val = *row[i] == 1 ? Qtrue : Qfalse; }else{ val = rb_str_new(row[i], fieldLengths[i]); } break; case MYSQL_TYPE_TINY: /* TINYINT field */ - if (castBool && fields[i].length == 1) { + if (args->castBool && fields[i].length == 1) { val = *row[i] != '0' ? Qtrue : Qfalse; break; } @@ -600,9 +614,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo break; } msec = msec_char_to_uint(msec_char, sizeof(msec_char)); - val = rb_funcall(rb_cTime, db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); - if (!NIL_P(app_timezone)) { - if (app_timezone == intern_local) { + val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); + if (!NIL_P(args->app_timezone)) { + if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { /* utc */ val = rb_funcall(val, intern_utc, 0); @@ -633,12 +647,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo } else { if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */ VALUE offset = INT2NUM(0); - if (db_timezone == intern_local) { + if (args->db_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); } val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset); - if (!NIL_P(app_timezone)) { - if (app_timezone == intern_local) { + if (!NIL_P(args->app_timezone)) { + if (args->app_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); val = rb_funcall(val, intern_new_offset, 1, offset); } else { /* utc */ @@ -647,9 +661,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo } } else { msec = msec_char_to_uint(msec_char, sizeof(msec_char)); - val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); - if (!NIL_P(app_timezone)) { - if (app_timezone == intern_local) { + val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); + if (!NIL_P(args->app_timezone)) { + if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { /* utc */ val = rb_funcall(val, intern_utc, 0); @@ -699,13 +713,13 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo break; } } - if (asArray) { + if (args->asArray) { rb_ary_push(rowVal, val); } else { rb_hash_aset(rowVal, field, val); } } else { - if (asArray) { + if (args->asArray) { rb_ary_push(rowVal, Qnil); } else { rb_hash_aset(rowVal, field, Qnil); @@ -743,19 +757,10 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { return wrapper->fields; } -typedef struct { - int symbolizeKeys; - int asArray; - int castBool; - int cacheRows; - int cast; - int streaming; - ID db_timezone; - ID app_timezone; - int block_given; -} result_each_args; - -static VALUE rb_mysql_result_each_nonstmt(VALUE self, const result_each_args* args) { +static VALUE rb_mysql_result_each_(VALUE self, + VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args), + const result_each_args *args) +{ mysql2_result_wrapper *wrapper; unsigned long i; const char *errstr; @@ -775,114 +780,16 @@ static VALUE rb_mysql_result_each_nonstmt(VALUE self, const result_each_args* ar fields = mysql_fetch_fields(wrapper->result); do { - row = rb_mysql_result_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, args->cast, fields); - if (row != Qnil) { - wrapper->numberOfRows++; - if (args->block_given != Qnil) { - rb_yield(row); - } - } - } while(row != Qnil); - - rb_mysql_result_free_result(wrapper); - wrapper->streamingComplete = 1; - - // Check for errors, the connection might have gone out from under us - // mysql_error returns an empty string if there is no error - errstr = mysql_error(wrapper->client_wrapper->client); - if (errstr[0]) { - rb_raise(cMysql2Error, "%s", errstr); - } - } else { - rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery)."); - } - } else { - if (wrapper->lastRowProcessed == 0) { - wrapper->numberOfRows = mysql_num_rows(wrapper->result); - if (wrapper->numberOfRows == 0) { - wrapper->rows = rb_ary_new(); - return wrapper->rows; - } - wrapper->rows = rb_ary_new2(wrapper->numberOfRows); - } - - if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) { - /* we've already read the entire dataset from the C result into our */ - /* internal array. Lets hand that over to the user since it's ready to go */ - for (i = 0; i < wrapper->numberOfRows; i++) { - rb_yield(rb_ary_entry(wrapper->rows, i)); - } - } else { - unsigned long rowsProcessed = 0; - rowsProcessed = RARRAY_LEN(wrapper->rows); - fields = mysql_fetch_fields(wrapper->result); - - for (i = 0; i < wrapper->numberOfRows; i++) { - VALUE row; - if (args->cacheRows && i < rowsProcessed) { - row = rb_ary_entry(wrapper->rows, i); - } else { - row = rb_mysql_result_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, args->cast, fields); - if (args->cacheRows) { - rb_ary_store(wrapper->rows, i, row); - } - wrapper->lastRowProcessed++; - } - - if (row == Qnil) { - /* we don't need the mysql C dataset around anymore, peace it */ - rb_mysql_result_free_result(wrapper); - return Qnil; - } - - if (args->block_given != Qnil) { - rb_yield(row); - } - } - if (wrapper->lastRowProcessed == wrapper->numberOfRows) { - /* we don't need the mysql C dataset around anymore, peace it */ - rb_mysql_result_free_result(wrapper); - } - } - } - - // FIXME return Enumerator instead? - // return rb_ary_each(wrapper->rows); - return wrapper->rows; -} - -static VALUE rb_mysql_result_each_stmt(VALUE self, const result_each_args* args) { - unsigned long i; - const char *errstr; - mysql2_result_wrapper *wrapper; - MYSQL_FIELD *fields = NULL; - - GetMysql2Result(self, wrapper); - - if (wrapper->is_streaming) { - /* When streaming, we will only yield rows, not return them. */ - if (wrapper->rows == Qnil) { - wrapper->rows = rb_ary_new(); - } - - if (!wrapper->streamingComplete) { - VALUE row; - - fields = mysql_fetch_fields(wrapper->result); - - do { - row = rb_mysql_result_stmt_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, fields); + row = fetch_row_func(self, fields, args); if (row != Qnil) { wrapper->numberOfRows++; if (args->block_given != Qnil) { rb_yield(row); - wrapper->lastRowProcessed++; } } } while(row != Qnil); rb_mysql_result_free_result(wrapper); - wrapper->numberOfRows = wrapper->lastRowProcessed; wrapper->streamingComplete = 1; // Check for errors, the connection might have gone out from under us @@ -911,7 +818,7 @@ static VALUE rb_mysql_result_each_stmt(VALUE self, const result_each_args* args) if (args->cacheRows && i < rowsProcessed) { row = rb_ary_entry(wrapper->rows, i); } else { - row = rb_mysql_result_stmt_fetch_row(self, args->db_timezone, args->app_timezone, args->symbolizeKeys, args->asArray, args->castBool, fields); + row = fetch_row_func(self, fields, args); if (args->cacheRows) { rb_ary_store(wrapper->rows, i, row); } @@ -1015,15 +922,14 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { args.app_timezone = app_timezone; args.block_given = block; - if(!wrapper->stmt) - { - return rb_mysql_result_each_nonstmt(self, &args); - } - else - { - return rb_mysql_result_each_stmt(self, &args); + VALUE (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); + if (wrapper->stmt) { + fetch_row_func = rb_mysql_result_fetch_row_stmt; + } else { + fetch_row_func = rb_mysql_result_fetch_row; } + return rb_mysql_result_each_(self, fetch_row_func, &args); } static VALUE rb_mysql_result_count(VALUE self) { From ca91bf45954adbcf2d4121b468fbc8fa3d5c0c25 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 3 Apr 2015 21:49:24 -0700 Subject: [PATCH 262/783] Use xfree after xmalloc/xcalloc --- ext/mysql2/result.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 242d09e92..22aad514d 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -90,16 +90,16 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { if (wrapper->stmt) { mysql_stmt_free_result(wrapper->stmt); - if(wrapper->result_buffers) { - for(i = 0; i < wrapper->numberOfFields; i++) { + if (wrapper->result_buffers) { + for (i = 0; i < wrapper->numberOfFields; i++) { if (wrapper->result_buffers[i].buffer) { - free(wrapper->result_buffers[i].buffer); + xfree(wrapper->result_buffers[i].buffer); } } - free(wrapper->result_buffers); - free(wrapper->is_null); - free(wrapper->error); - free(wrapper->length); + xfree(wrapper->result_buffers); + xfree(wrapper->is_null); + xfree(wrapper->error); + xfree(wrapper->length); } } /* FIXME: this may call flush_use_result, which can hit the socket */ @@ -292,7 +292,7 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] - wrapper->result_buffers[i].buffer = malloc(fields[i].max_length); + wrapper->result_buffers[i].buffer = xmalloc(fields[i].max_length); wrapper->result_buffers[i].buffer_length = fields[i].max_length; break; default: From 56b46c8b88c9e720d08b743d9b43154ad09484c3 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 3 Apr 2015 22:09:08 -0700 Subject: [PATCH 263/783] Support for microseconds in prepared statements --- ext/mysql2/result.c | 4 ++-- ext/mysql2/statement.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 22aad514d..4b499a86e 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -427,7 +427,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co break; case MYSQL_TYPE_TIME: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; - val = rb_funcall(rb_cTime, args->db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second)); + val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); @@ -458,7 +458,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } } } else { - val = rb_funcall(rb_cTime, args->db_timezone, 6, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second)); + val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 3d8029832..1e4ddbf7a 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -287,8 +287,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { MYSQL_TIME t; VALUE rb_time = argv[i]; memset(&t, 0, sizeof(MYSQL_TIME)); - t.second_part = 0; t.neg = 0; + t.second_part = FIX2INT(rb_funcall(rb_time, rb_intern("usec"), 0)); t.second = FIX2INT(rb_funcall(rb_time, rb_intern("sec"), 0)); t.minute = FIX2INT(rb_funcall(rb_time, rb_intern("min"), 0)); t.hour = FIX2INT(rb_funcall(rb_time, rb_intern("hour"), 0)); From fe27c8b5ca58163a686b2dece5927c7744e3875a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 3 Apr 2015 22:14:03 -0700 Subject: [PATCH 264/783] Use available intern_new instead of rb_intern("new") from scratch --- ext/mysql2/result.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 4b499a86e..894e5f07f 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -423,7 +423,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co case MYSQL_TYPE_DATE: // MYSQL_TIME case MYSQL_TYPE_NEWDATE: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; - val = rb_funcall(cDate, rb_intern("new"), 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); + val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); break; case MYSQL_TYPE_TIME: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; @@ -471,7 +471,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } case MYSQL_TYPE_DECIMAL: // char[] case MYSQL_TYPE_NEWDECIMAL: // char[] - val = rb_funcall(cBigDecimal, rb_intern("new"), 1, rb_str_new(result_buffer->buffer, *(result_buffer->length))); + val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length))); break; case MYSQL_TYPE_STRING: // char[] case MYSQL_TYPE_VAR_STRING: // char[] From ec45f82ec0d88c3a59920d17a77632541f54c77c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 9 Apr 2015 23:55:11 -0700 Subject: [PATCH 265/783] Use xmalloc to match existing xcalloc and xfree. Remember to free length_buffers. Comment on this. --- ext/mysql2/statement.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 1e4ddbf7a..794e483b4 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -185,13 +185,19 @@ static void *nogvl_stmt_store_result(void *ptr) { } } +/* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means + * the buffer is a Ruby string pointer and not our memory to manage. + */ #define FREE_BINDS \ for (i = 0; i < argc; i++) { \ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \ xfree(bind_buffers[i].buffer); \ } \ } \ - if (argc > 0) xfree(bind_buffers); + if (argc > 0) { \ + xfree(bind_buffers); \ + xfree(length_buffers); \ + } /* call-seq: stmt.execute * @@ -247,22 +253,22 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { case T_FIXNUM: #if SIZEOF_INT < SIZEOF_LONG bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; - bind_buffers[i].buffer = malloc(sizeof(long long int)); + bind_buffers[i].buffer = xmalloc(sizeof(long long int)); *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]); #else bind_buffers[i].buffer_type = MYSQL_TYPE_LONG; - bind_buffers[i].buffer = malloc(sizeof(int)); + bind_buffers[i].buffer = xmalloc(sizeof(int)); *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]); #endif break; case T_BIGNUM: bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; - bind_buffers[i].buffer = malloc(sizeof(long long int)); + bind_buffers[i].buffer = xmalloc(sizeof(long long int)); *(LONG_LONG*)(bind_buffers[i].buffer) = rb_big2ll(argv[i]); break; case T_FLOAT: bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE; - bind_buffers[i].buffer = malloc(sizeof(double)); + bind_buffers[i].buffer = xmalloc(sizeof(double)); *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]); break; case T_STRING: @@ -282,7 +288,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { // TODO: what Ruby type should support MYSQL_TYPE_TIME if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) { bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME; - bind_buffers[i].buffer = malloc(sizeof(MYSQL_TIME)); + bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); MYSQL_TIME t; VALUE rb_time = argv[i]; @@ -301,7 +307,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { bind_buffers[i].buffer_type = MYSQL_TYPE_DATE; - bind_buffers[i].buffer = malloc(sizeof(MYSQL_TIME)); + bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); MYSQL_TIME t; VALUE rb_time = argv[i]; From cf687bad138b8d8ed36d70f7dea46caf88e8cca7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 11 Apr 2015 08:47:44 -0700 Subject: [PATCH 266/783] Use global rb_intern strings instead of point of use --- ext/mysql2/client.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index ba09ea63a..e0de7b124 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -18,10 +18,11 @@ VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error; static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql; +static ID intern_brackets, intern_new; #ifndef HAVE_RB_HASH_DUP VALUE rb_hash_dup(VALUE other) { - return rb_funcall(rb_cHash, rb_intern("[]"), 1, other); + return rb_funcall(rb_cHash, intern_brackets, 1, other); } #endif @@ -123,7 +124,7 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { rb_enc_associate(rb_sql_state, rb_usascii_encoding()); #endif - e = rb_funcall(cMysql2Error, rb_intern("new"), 2, rb_error_msg, LONG2FIX(wrapper->server_version)); + e = rb_funcall(cMysql2Error, intern_new, 2, rb_error_msg, LONG2FIX(wrapper->server_version)); rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client))); rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state); rb_exc_raise(e); @@ -1305,6 +1306,8 @@ void init_mysql2_client() { sym_array = ID2SYM(rb_intern("array")); sym_stream = ID2SYM(rb_intern("stream")); + intern_brackets = rb_intern("[]"); + intern_new = rb_intern("new"); intern_merge = rb_intern("merge"); intern_merge_bang = rb_intern("merge!"); intern_error_number_eql = rb_intern("error_number="); From 538ea5bbc4b8097e65df7b808b5afaa903536c93 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 10 Mar 2015 23:10:23 -0700 Subject: [PATCH 267/783] Use `Thread.handle_interrupt` to protect `query` When available, we prevent `Timeout.timeout` from corrupting connections using `Thread.handle_interrupt`. Fixes #542. Revert "Update specs for Ruby 2.1 Timeout behavior by specifying 'Timeout::Error' as the klass parameter." This reverts commit d0a51992b33761cc4d557274f57cf293292d3f5a. --- ext/mysql2/client.c | 2 +- lib/mysql2/client.rb | 11 +++++++ spec/mysql2/client_spec.rb | 65 ++++++++++++++------------------------ 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index e0de7b124..84f1aeeb3 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1262,7 +1262,6 @@ void init_mysql2_client() { rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1); rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0); - rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1); rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0); rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1); rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0); @@ -1285,6 +1284,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); #endif + rb_define_private_method(cMysql2Client, "_query", rb_mysql_client_query, -1); rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1); rb_define_private_method(cMysql2Client, "write_timeout=", set_write_timeout, 1); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 99a043f9d..9057d7dd9 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -74,6 +74,17 @@ def self.default_query_options @@default_query_options end + if Thread.respond_to?(:handle_interrupt) + def query(*args, &block) + Thread.handle_interrupt(Timeout::ExitException => :never) do + _query(*args, &block) + end + end + else + alias_method :query, :_query + public :query + end + def query_info info = query_info_string return {} unless info diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 7a0737c52..fec8b0712 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -461,59 +461,40 @@ def connect *args }.should raise_error(Mysql2::Error) end - it "should close the connection when an exception is raised" do - begin - Timeout.timeout(1, Timeout::Error) do - @client.query("SELECT sleep(2)") - end - rescue Timeout::Error - end - lambda { - @client.query("SELECT 1") - }.should raise_error(Mysql2::Error, 'closed MySQL connection') + it 'should be impervious to connection-corrupting timeouts ' do + pending('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt) + # attempt to break the connection + expect { Timeout.timeout(0.1) { @client.query('SELECT SLEEP(1)') } }.to raise_error(Timeout::Error) + + # expect the connection to not be broken + expect { @client.query('SELECT 1') }.to_not raise_error end - it "should handle Timeouts without leaving the connection hanging if reconnect is true" do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) - begin - Timeout.timeout(1, Timeout::Error) do - client.query("SELECT sleep(2)") - end - rescue Timeout::Error + context 'when a non-standard exception class is raised' do + it "should close the connection when an exception is raised" do + expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) + expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'closed MySQL connection') end - lambda { - client.query("SELECT 1") - }.should_not raise_error(Mysql2::Error) - end + it "should handle Timeouts without leaving the connection hanging if reconnect is true" do + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) - it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction true" do - client = Mysql2::Client.new(DatabaseCredentials['root']) - begin - Timeout.timeout(1, Timeout::Error) do - client.query("SELECT sleep(2)") - end - rescue Timeout::Error + expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) + expect { client.query('SELECT 1') }.to_not raise_error end - lambda { - client.query("SELECT 1") - }.should raise_error(Mysql2::Error) + it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction true" do + client = Mysql2::Client.new(DatabaseCredentials['root']) - client.reconnect = true + expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) + expect { client.query('SELECT 1') }.to raise_error(Mysql2::Error) - begin - Timeout.timeout(1, Timeout::Error) do - client.query("SELECT sleep(2)") - end - rescue Timeout::Error - end - - lambda { - client.query("SELECT 1") - }.should_not raise_error(Mysql2::Error) + client.reconnect = true + expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) + expect { client.query('SELECT 1') }.to_not raise_error + end end it "threaded queries should be supported" do From 683fa560effaa8cc2faa564d983010367ecb0dbd Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 13 Mar 2015 10:59:55 -0700 Subject: [PATCH 268/783] client.c need not know about `@query_options` --- ext/mysql2/client.c | 24 +++++++----------------- lib/mysql2/client.rb | 9 +++++---- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 84f1aeeb3..54bc6cb4b 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -643,39 +643,29 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) { * Query the database with +sql+, with optional +options+. For the possible * options, see @@default_query_options on the Mysql2::Client class. */ -static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { +static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { #ifndef _WIN32 struct async_query_args async_args; #endif struct nogvl_send_query_args args; int async = 0; - VALUE opts, current; -#ifdef HAVE_RUBY_ENCODING_H - rb_encoding *conn_enc; -#endif GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); args.mysql = wrapper->client; - current = rb_hash_dup(rb_iv_get(self, "@query_options")); RB_GC_GUARD(current); Check_Type(current, T_HASH); rb_iv_set(self, "@current_query_options", current); - if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { - rb_funcall(current, intern_merge_bang, 1, opts); - - if (rb_hash_aref(current, sym_async) == Qtrue) { - async = 1; - } - } + async = rb_hash_aref(current, sym_async) == Qtrue; - Check_Type(args.sql, T_STRING); + Check_Type(sql, T_STRING); #ifdef HAVE_RUBY_ENCODING_H - conn_enc = rb_to_encoding(wrapper->encoding); /* ensure the string is in the encoding the connection is expecting */ - args.sql = rb_str_export_to_enc(args.sql, conn_enc); + args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding)); +#else + args.sql = sql; #endif args.sql_ptr = StringValuePtr(args.sql); args.sql_len = RSTRING_LEN(args.sql); @@ -1284,7 +1274,6 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); #endif - rb_define_private_method(cMysql2Client, "_query", rb_mysql_client_query, -1); rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1); rb_define_private_method(cMysql2Client, "write_timeout=", set_write_timeout, 1); @@ -1297,6 +1286,7 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); rb_define_private_method(cMysql2Client, "connect", rb_connect, 7); + rb_define_private_method(cMysql2Client, "_query", rb_query, 2); sym_id = ID2SYM(rb_intern("id")); sym_version = ID2SYM(rb_intern("version")); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 9057d7dd9..764e410ee 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -75,14 +75,15 @@ def self.default_query_options end if Thread.respond_to?(:handle_interrupt) - def query(*args, &block) + def query(sql, options = {}) Thread.handle_interrupt(Timeout::ExitException => :never) do - _query(*args, &block) + _query(sql, @query_options.merge(options)) end end else - alias_method :query, :_query - public :query + def query(sql, options = {}) + _query(sql, @query_options.merge(options)) + end end def query_info From 37da305212c756ed8d602897106cf74f3221075b Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 13 Mar 2015 12:07:49 -0700 Subject: [PATCH 269/783] Remove redundant local --- ext/mysql2/client.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 54bc6cb4b..1a71cf633 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -648,7 +648,6 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { struct async_query_args async_args; #endif struct nogvl_send_query_args args; - int async = 0; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); @@ -658,8 +657,6 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { Check_Type(current, T_HASH); rb_iv_set(self, "@current_query_options", current); - async = rb_hash_aref(current, sym_async) == Qtrue; - Check_Type(sql, T_STRING); #ifdef HAVE_RUBY_ENCODING_H /* ensure the string is in the encoding the connection is expecting */ @@ -676,15 +673,15 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { #ifndef _WIN32 rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0); - if (!async) { + if (rb_hash_aref(current, sym_async) == Qtrue) { + return Qnil; + } else { async_args.fd = wrapper->client->net.fd; async_args.self = self; rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0); return rb_mysql_client_async_result(self); - } else { - return Qnil; } #else do_send_query(&args); From 52a6d2dc0b0e6518a7787c7044f3b66255dc662b Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 11 Mar 2015 19:33:26 -0700 Subject: [PATCH 270/783] Clarify usage of `Timeout.timeout` --- spec/mysql2/client_spec.rb | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index fec8b0712..3dbbf9e90 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -498,23 +498,22 @@ def connect *args end it "threaded queries should be supported" do - threads, results = [], {} - lock = Mutex.new - connect = lambda{ - Mysql2::Client.new(DatabaseCredentials['root']) - } - Timeout.timeout(0.7) do - 5.times { - threads << Thread.new do - result = connect.call.query("SELECT sleep(0.5) as result") - lock.synchronize do - results[Thread.current.object_id] = result - end - end - } + sleep_time = 0.5 + + # Note that each thread opens its own database connection + threads = 5.times.map do + Thread.new do + client = Mysql2::Client.new(DatabaseCredentials.fetch('http://github.com/root')) + client.query("SELECT SLEEP(#{sleep_time})") + Thread.current.object_id + end end - threads.each{|t| t.join } - results.keys.sort.should eql(threads.map{|t| t.object_id }.sort) + + # This timeout demonstrates that the threads are sleeping concurrently: + # In the serial case, the timeout would fire and the test would fail + values = Timeout.timeout(sleep_time * 1.1) { threads.map(&:value) } + + values.should =~ threads.map(&:object_id) end it "evented async queries should be supported" do From c118d26f6a3ee3208154d2b379d5a1e0a6f3174f Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 11 Mar 2015 19:47:35 -0700 Subject: [PATCH 271/783] Clean up some ugly `rescue`s --- spec/mysql2/client_spec.rb | 14 ++------------ spec/mysql2/result_spec.rb | 9 ++------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 3dbbf9e90..418db29a4 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -96,10 +96,7 @@ def connect *args first_conn_id = result.first['CONNECTION_ID()'] # break the current connection - begin - client.query("KILL #{first_conn_id}") - rescue Mysql2::Error - end + expect { client.query("KILL #{first_conn_id}") }.to raise_error(Mysql2::Error) client.ping # reconnect now @@ -404,13 +401,7 @@ def connect *args thr = Thread.new { @client.query("SELECT 1", :async => true) } thr.join - begin - @client.query("SELECT 1") - rescue Mysql2::Error => e - message = e.message - end - re = Regexp.escape(thr.inspect) - message.should match(Regexp.new(re)) + expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(thr.inspect))) end it "should timeout if we wait longer than :read_timeout" do @@ -461,7 +452,6 @@ def connect *args }.should raise_error(Mysql2::Error) end - it 'should be impervious to connection-corrupting timeouts ' do pending('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt) # attempt to break the connection diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index a1e8a881e..8b07f9e5e 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -338,13 +338,8 @@ end it "should raise an error given an invalid DATETIME" do - begin - @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each - rescue Mysql2::Error => e - error = e - end - - error.message.should eql("Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") + expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ + raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") end if defined? Encoding From e5b05a9e09bfb4e6db0d27f552e1ac7d6dd44e73 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 11 Mar 2015 19:53:09 -0700 Subject: [PATCH 272/783] Speed up some tests --- spec/mysql2/client_spec.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 418db29a4..cdd517d9e 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -405,9 +405,9 @@ def connect *args end it "should timeout if we wait longer than :read_timeout" do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 1)) + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 0)) lambda { - client.query("SELECT sleep(2)") + client.query('SELECT SLEEP(0.1)') }.should raise_error(Mysql2::Error) end @@ -420,18 +420,18 @@ def connect *args begin mark[:START] = Time.now pid = fork do - sleep 1 # wait for client "SELECT sleep(2)" query to start + sleep 0.1 # wait for client query to start Process.kill(:USR1, Process.ppid) sleep # wait for explicit kill to prevent GC disconnect end - @client.query("SELECT sleep(2)") + @client.query('SELECT SLEEP(0.2)') mark[:END] = Time.now mark.include?(:USR1).should be_true - (mark[:USR1] - mark[:START]).should >= 1 - (mark[:USR1] - mark[:START]).should < 1.3 - (mark[:END] - mark[:USR1]).should > 0.9 - (mark[:END] - mark[:START]).should >= 2 - (mark[:END] - mark[:START]).should < 2.3 + (mark[:USR1] - mark[:START]).should >= 0.1 + (mark[:USR1] - mark[:START]).should < 0.13 + (mark[:END] - mark[:USR1]).should > 0.09 + (mark[:END] - mark[:START]).should >= 0.2 + (mark[:END] - mark[:START]).should < 0.23 Process.kill(:TERM, pid) Process.waitpid2(pid) ensure @@ -455,7 +455,7 @@ def connect *args it 'should be impervious to connection-corrupting timeouts ' do pending('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt) # attempt to break the connection - expect { Timeout.timeout(0.1) { @client.query('SELECT SLEEP(1)') } }.to raise_error(Timeout::Error) + expect { Timeout.timeout(0.1) { @client.query('SELECT SLEEP(0.2)') } }.to raise_error(Timeout::Error) # expect the connection to not be broken expect { @client.query('SELECT 1') }.to_not raise_error From 3184108a9dc86d6b284304418408c8496ade7ad7 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 11 Mar 2015 20:06:44 -0700 Subject: [PATCH 273/783] DRY --- spec/mysql2/client_spec.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index cdd517d9e..905389804 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -149,9 +149,14 @@ def connect *args ssl_client.close end - it "should not leave dangling connections after garbage collection" do + def run_gc GC.start - sleep 0.300 # Let GC do its work + sleep(1) if defined?(Rubinius) # Let the Rubinius GC thread do its work + end + + it "should not leave dangling connections after garbage collection" do + run_gc + client = Mysql2::Client.new(DatabaseCredentials['root']) before_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i @@ -161,23 +166,20 @@ def connect *args after_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i after_count.should == before_count + 10 - GC.start - sleep 0.300 # Let GC do its work + run_gc final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i final_count.should == before_count end if Process.respond_to?(:fork) it "should not close connections when running in a child process" do - GC.start - sleep 1 if defined? Rubinius # Let the rbx GC thread do its work + run_gc client = Mysql2::Client.new(DatabaseCredentials['root']) fork do client.query('SELECT 1') client = nil - GC.start - sleep 1 if defined? Rubinius # Let the rbx GC thread do its work + run_gc end Process.wait From 90c7ce91ff2aa7027ccc14d798e1fb30f01571bd Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 11 Mar 2015 20:11:53 -0700 Subject: [PATCH 274/783] Always run the tests on RBX --- spec/mysql2/client_spec.rb | 51 +++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 905389804..d2cfaae6c 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -155,6 +155,8 @@ def run_gc end it "should not leave dangling connections after garbage collection" do + pending('Rubinius misbehaves') if defined?(Rubinius) + run_gc client = Mysql2::Client.new(DatabaseCredentials['root']) @@ -413,32 +415,31 @@ def run_gc }.should raise_error(Mysql2::Error) end - if !defined? Rubinius - # XXX this test is not deterministic (because Unix signal handling is not) - # and may fail on a loaded system - it "should run signal handlers while waiting for a response" do - mark = {} - trap(:USR1) { mark[:USR1] = Time.now } - begin - mark[:START] = Time.now - pid = fork do - sleep 0.1 # wait for client query to start - Process.kill(:USR1, Process.ppid) - sleep # wait for explicit kill to prevent GC disconnect - end - @client.query('SELECT SLEEP(0.2)') - mark[:END] = Time.now - mark.include?(:USR1).should be_true - (mark[:USR1] - mark[:START]).should >= 0.1 - (mark[:USR1] - mark[:START]).should < 0.13 - (mark[:END] - mark[:USR1]).should > 0.09 - (mark[:END] - mark[:START]).should >= 0.2 - (mark[:END] - mark[:START]).should < 0.23 - Process.kill(:TERM, pid) - Process.waitpid2(pid) - ensure - trap(:USR1, 'DEFAULT') + # XXX this test is not deterministic (because Unix signal handling is not) + # and may fail on a loaded system + it "should run signal handlers while waiting for a response" do + pending('Rubinius misbehaves') if defined?(Rubinius) + mark = {} + trap(:USR1) { mark[:USR1] = Time.now } + begin + mark[:START] = Time.now + pid = fork do + sleep 0.1 # wait for client query to start + Process.kill(:USR1, Process.ppid) + sleep # wait for explicit kill to prevent GC disconnect end + @client.query('SELECT SLEEP(0.2)') + mark[:END] = Time.now + mark.include?(:USR1).should be_true + (mark[:USR1] - mark[:START]).should >= 0.1 + (mark[:USR1] - mark[:START]).should < 0.13 + (mark[:END] - mark[:USR1]).should > 0.09 + (mark[:END] - mark[:START]).should >= 0.2 + (mark[:END] - mark[:START]).should < 0.23 + Process.kill(:TERM, pid) + Process.waitpid2(pid) + ensure + trap(:USR1, 'DEFAULT') end end From 92ac2608bdb832241933b7a8d7866f2cba33a253 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 08:51:30 -0700 Subject: [PATCH 275/783] Remove duplicate benchmark --- benchmark/threaded.rb | 44 ------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 benchmark/threaded.rb diff --git a/benchmark/threaded.rb b/benchmark/threaded.rb deleted file mode 100644 index 7b989a2f5..000000000 --- a/benchmark/threaded.rb +++ /dev/null @@ -1,44 +0,0 @@ -# encoding: UTF-8 -$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') - -require 'rubygems' -require 'benchmark' -require 'active_record' - -mysql2_opts = { - :adapter => 'mysql2', - :database => 'test', - :pool => 25 -} -ActiveRecord::Base.establish_connection(mysql2_opts) -x = Benchmark.realtime do - threads = [] - 25.times do - threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") } - end - threads.each {|t| t.join } -end -puts x - -mysql2_opts = { - :adapter => 'mysql', - :database => 'test', - :pool => 25 -} -ActiveRecord::Base.establish_connection(mysql2_opts) -x = Benchmark.realtime do - threads = [] - 25.times do - threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") } - end - threads.each {|t| t.join } -end -puts x - -# these results are similar on 1.8.7, 1.9.2 and rbx-head -# -# $ bundle exec ruby benchmarks/threaded.rb -# 1.0774750709533691 -# -# and using the mysql gem -# 25.099437952041626 \ No newline at end of file From 13e5d59f0124cb57e030552bddcd69df5d32f3ff Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 08:51:13 -0700 Subject: [PATCH 276/783] Use benchmark/ips for ergonomics --- Gemfile | 11 ++-- benchmark/active_record.rb | 50 +++++---------- benchmark/active_record_threaded.rb | 43 ++++--------- benchmark/allocations.rb | 21 +++---- benchmark/escape.rb | 23 +++---- benchmark/query_with_mysql_casting.rb | 78 +++++++++++------------- benchmark/query_without_mysql_casting.rb | 41 ++++++------- benchmark/sequel.rb | 19 +++--- benchmark/setup_db.rb | 13 ++-- 9 files changed, 121 insertions(+), 178 deletions(-) diff --git a/Gemfile b/Gemfile index a8e9d75bf..6aeb1d3bf 100644 --- a/Gemfile +++ b/Gemfile @@ -5,19 +5,20 @@ gemspec # benchmarks group :benchmarks do gem 'activerecord', '>= 3.0' - gem 'mysql' + gem 'benchmark-ips' gem 'do_mysql' - gem 'sequel' gem 'faker' + gem 'mysql' + gem 'sequel' end group :development do - gem 'pry' gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ + gem 'pry' end platforms :rbx do - gem 'rubysl-rake' - gem 'rubysl-drb' gem 'rubysl-bigdecimal' + gem 'rubysl-drb' + gem 'rubysl-rake' end diff --git a/benchmark/active_record.rb b/benchmark/active_record.rb index 942bc52b1..e25df0ce7 100644 --- a/benchmark/active_record.rb +++ b/benchmark/active_record.rb @@ -2,50 +2,32 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' -require 'benchmark' +require 'benchmark/ips' require 'active_record' ActiveRecord::Base.default_timezone = :local ActiveRecord::Base.time_zone_aware_attributes = true -number_of = 10 -mysql2_opts = { - :adapter => 'mysql2', - :database => 'test' -} -mysql_opts = { - :adapter => 'mysql', - :database => 'test' -} - -class Mysql2Model < ActiveRecord::Base - self.table_name = "mysql2_test" -end +opts = { :database => 'test' } -class MysqlModel < ActiveRecord::Base - self.table_name = "mysql2_test" +class TestModel < ActiveRecord::Base + self.table_name = 'mysql2_test' end -Benchmark.bmbm do |x| - x.report "Mysql2" do - Mysql2Model.establish_connection(mysql2_opts) - number_of.times do - Mysql2Model.limit(1000).to_a.each{ |r| - r.attributes.keys.each{ |k| - r.send(k.to_sym) - } - } - end - end +batch_size = 1000 + +Benchmark.ips do |x| + %w(mysql mysql2).each do |adapter| + TestModel.establish_connection(opts.merge(:adapter => adapter)) - x.report "Mysql" do - MysqlModel.establish_connection(mysql_opts) - number_of.times do - MysqlModel.limit(1000).to_a.each{ |r| - r.attributes.keys.each{ |k| + x.report(adapter) do + TestModel.limit(batch_size).to_a.each do |r| + r.attributes.keys.each do |k| r.send(k.to_sym) - } - } + end + end end end + + x.compare! end diff --git a/benchmark/active_record_threaded.rb b/benchmark/active_record_threaded.rb index 8c828bc27..f55f3a10f 100644 --- a/benchmark/active_record_threaded.rb +++ b/benchmark/active_record_threaded.rb @@ -2,41 +2,22 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' -require 'benchmark' +require 'benchmark/ips' require 'active_record' -times = 25 +number_of_threads = 25 +opts = { :database => 'test', :pool => number_of_threads } +Benchmark.ips do |x| + %w(mysql mysql2).each do |adapter| + ActiveRecord::Base.establish_connection(opts.merge(:adapter => adapter)) -# mysql2 -mysql2_opts = { - :adapter => 'mysql2', - :database => 'test', - :pool => times -} -ActiveRecord::Base.establish_connection(mysql2_opts) -x = Benchmark.realtime do - threads = [] - times.times do - threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") } + x.report(adapter) do + number_of_threads.times.map do + Thread.new { ActiveRecord::Base.connection.execute('SELECT SLEEP(1)') } + end.each(&:join) + end end - threads.each {|t| t.join } -end -puts "mysql2: #{x} seconds" - -# mysql -mysql2_opts = { - :adapter => 'mysql', - :database => 'test', - :pool => times -} -ActiveRecord::Base.establish_connection(mysql2_opts) -x = Benchmark.realtime do - threads = [] - times.times do - threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") } - end - threads.each {|t| t.join } + x.compare! end -puts "mysql: #{x} seconds" diff --git a/benchmark/allocations.rb b/benchmark/allocations.rb index b605fdf4c..394c0c7e5 100644 --- a/benchmark/allocations.rb +++ b/benchmark/allocations.rb @@ -2,7 +2,6 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' -require 'benchmark' require 'active_record' raise Mysql2::Error.new("GC allocation benchmarks only supported on Ruby 1.9!") unless RUBY_VERSION > '1.9' @@ -10,24 +9,24 @@ ActiveRecord::Base.default_timezone = :local ActiveRecord::Base.time_zone_aware_attributes = true -class Mysql2Model < ActiveRecord::Base - self.table_name = "mysql2_test" +class TestModel < ActiveRecord::Base + self.table_name = 'mysql2_test' end -def bench_allocations(feature, iterations = 10, &blk) +def bench_allocations(feature, iterations = 10, batch_size = 1000) puts "GC overhead for #{feature}" - Mysql2Model.establish_connection(:adapter => 'mysql2', :database => 'test') + TestModel.establish_connection(:adapter => 'mysql2', :database => 'test') GC::Profiler.clear GC::Profiler.enable - iterations.times{ blk.call } + iterations.times { yield batch_size } GC::Profiler.report(STDOUT) GC::Profiler.disable end -bench_allocations('coercion') do - Mysql2Model.limit(1000).to_a.each{ |r| - r.attributes.keys.each{ |k| +bench_allocations('coercion') do |batch_size| + TestModel.limit(batch_size).to_a.each do |r| + r.attributes.keys.each do |k| r.send(k.to_sym) - } - } + end + end end diff --git a/benchmark/escape.rb b/benchmark/escape.rb index 852d52ac6..2a666decd 100644 --- a/benchmark/escape.rb +++ b/benchmark/escape.rb @@ -2,35 +2,32 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' -require 'benchmark' +require 'benchmark/ips' require 'mysql' require 'mysql2' require 'do_mysql' -def run_escape_benchmarks(str, number_of = 1000) - Benchmark.bmbm do |x| +def run_escape_benchmarks(str) + Benchmark.ips do |x| mysql = Mysql.new("localhost", "root") + x.report "Mysql #{str.inspect}" do - number_of.times do - mysql.quote str - end + mysql.quote str end mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root") x.report "Mysql2 #{str.inspect}" do - number_of.times do - mysql2.escape str - end + mysql2.escape str end do_mysql = DataObjects::Connection.new("mysql://localhost/test") x.report "do_mysql #{str.inspect}" do - number_of.times do - do_mysql.quote_string str - end + do_mysql.quote_string str end + + x.compare! end end run_escape_benchmarks "abc'def\"ghi\0jkl%mno" -run_escape_benchmarks "clean string" \ No newline at end of file +run_escape_benchmarks "clean string" diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb index 5b460dd7e..f2d493fb7 100644 --- a/benchmark/query_with_mysql_casting.rb +++ b/benchmark/query_with_mysql_casting.rb @@ -2,12 +2,11 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' -require 'benchmark' +require 'benchmark/ips' require 'mysql' require 'mysql2' require 'do_mysql' -number_of = 100 database = 'test' sql = "SELECT * FROM mysql2_test LIMIT 100" @@ -17,64 +16,59 @@ class Mysql def mysql_cast(type, value) case type - when Mysql::Field::TYPE_NULL - nil - when Mysql::Field::TYPE_TINY, Mysql::Field::TYPE_SHORT, Mysql::Field::TYPE_LONG, - Mysql::Field::TYPE_INT24, Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_YEAR - value.to_i - when Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_NEWDECIMAL - BigDecimal.new(value) - when Mysql::Field::TYPE_DOUBLE, Mysql::Field::TYPE_FLOAT - value.to_f - when Mysql::Field::TYPE_DATE - Date.parse(value) - when Mysql::Field::TYPE_TIME, Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP - Time.parse(value) - when Mysql::Field::TYPE_BLOB, Mysql::Field::TYPE_BIT, Mysql::Field::TYPE_STRING, - Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET - Mysql::Field::TYPE_ENUM - value - else - value + when Mysql::Field::TYPE_NULL + nil + when Mysql::Field::TYPE_TINY, Mysql::Field::TYPE_SHORT, Mysql::Field::TYPE_LONG, + Mysql::Field::TYPE_INT24, Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_YEAR + value.to_i + when Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_NEWDECIMAL + BigDecimal.new(value) + when Mysql::Field::TYPE_DOUBLE, Mysql::Field::TYPE_FLOAT + value.to_f + when Mysql::Field::TYPE_DATE + Date.parse(value) + when Mysql::Field::TYPE_TIME, Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP + Time.parse(value) + when Mysql::Field::TYPE_BLOB, Mysql::Field::TYPE_BIT, Mysql::Field::TYPE_STRING, + Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET + Mysql::Field::TYPE_ENUM + value + else + value end end -Benchmark.bmbm do |x| +Benchmark.ips do |x| mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root") mysql2.query "USE #{database}" x.report "Mysql2" do - number_of.times do - mysql2_result = mysql2.query sql, :symbolize_keys => true - mysql2_result.each do |res| - # puts res.inspect - end + mysql2_result = mysql2.query sql, :symbolize_keys => true + mysql2_result.each do |res| + # puts res.inspect end end mysql = Mysql.new("localhost", "root") mysql.query "USE #{database}" x.report "Mysql" do - number_of.times do - mysql_result = mysql.query sql - fields = mysql_result.fetch_fields - mysql_result.each do |row| - row_hash = {} - row.each_with_index do |f, j| - row_hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, row[j]) - end - # puts row_hash.inspect + mysql_result = mysql.query sql + fields = mysql_result.fetch_fields + mysql_result.each do |row| + row_hash = row.each_with_index.each_with_object({}) do |(f, j), hash| + hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, row[j]) end + # puts row_hash.inspect end end do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") command = do_mysql.create_command sql x.report "do_mysql" do - number_of.times do - do_result = command.execute_reader - do_result.each do |res| - # puts res.inspect - end + do_result = command.execute_reader + do_result.each do |res| + # puts res.inspect end end -end \ No newline at end of file + + x.compare! +end diff --git a/benchmark/query_without_mysql_casting.rb b/benchmark/query_without_mysql_casting.rb index 3929d15f2..8fcd9a652 100644 --- a/benchmark/query_without_mysql_casting.rb +++ b/benchmark/query_without_mysql_casting.rb @@ -2,55 +2,48 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' -require 'benchmark' +require 'benchmark/ips' require 'mysql' require 'mysql2' require 'do_mysql' -number_of = 100 database = 'test' sql = "SELECT * FROM mysql2_test LIMIT 100" -Benchmark.bmbm do |x| +Benchmark.ips do |x| mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root") mysql2.query "USE #{database}" x.report "Mysql2 (cast: true)" do - number_of.times do - mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => true - mysql2_result.each do |res| - # puts res.inspect - end + mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => true + mysql2_result.each do |res| + # puts res.inspect end end x.report "Mysql2 (cast: false)" do - number_of.times do - mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => false - mysql2_result.each do |res| - # puts res.inspect - end + mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => false + mysql2_result.each do |res| + # puts res.inspect end end mysql = Mysql.new("localhost", "root") mysql.query "USE #{database}" x.report "Mysql" do - number_of.times do - mysql_result = mysql.query sql - mysql_result.each_hash do |res| - # puts res.inspect - end + mysql_result = mysql.query sql + mysql_result.each_hash do |res| + # puts res.inspect end end do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") command = DataObjects::Mysql::Command.new do_mysql, sql x.report "do_mysql" do - number_of.times do - do_result = command.execute_reader - do_result.each do |res| - # puts res.inspect - end + do_result = command.execute_reader + do_result.each do |res| + # puts res.inspect end end -end \ No newline at end of file + + x.compare! +end diff --git a/benchmark/sequel.rb b/benchmark/sequel.rb index 93b2d72d1..470ba36be 100644 --- a/benchmark/sequel.rb +++ b/benchmark/sequel.rb @@ -2,12 +2,11 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' -require 'benchmark' +require 'benchmark/ips' require 'mysql2' require 'sequel' require 'sequel/adapters/do' -number_of = 10 mysql2_opts = "mysql2://root@localhost/test" mysql_opts = "mysql://root@localhost/test" do_mysql_opts = "do:mysql://root@localhost/test" @@ -16,22 +15,18 @@ class Mysql2Model < Sequel::Model(Sequel.connect(mysql2_opts)[:mysql2_test]); en class MysqlModel < Sequel::Model(Sequel.connect(mysql_opts)[:mysql2_test]); end class DOMysqlModel < Sequel::Model(Sequel.connect(do_mysql_opts)[:mysql2_test]); end -Benchmark.bmbm do |x| +Benchmark.ips do |x| x.report "Mysql2" do - number_of.times do - Mysql2Model.limit(1000).all - end + Mysql2Model.limit(1000).all end x.report "do:mysql" do - number_of.times do - DOMysqlModel.limit(1000).all - end + DOMysqlModel.limit(1000).all end x.report "Mysql" do - number_of.times do - MysqlModel.limit(1000).all - end + MysqlModel.limit(1000).all end + + x.compare! end diff --git a/benchmark/setup_db.rb b/benchmark/setup_db.rb index 0d1938914..9ad8518e1 100644 --- a/benchmark/setup_db.rb +++ b/benchmark/setup_db.rb @@ -52,6 +52,7 @@ @client = Mysql2::Client.new :host => "localhost", :username => "root", :database => "test" @client.query create_table_sql +@client.query 'TRUNCATE mysql2_test' def insert_record(args) insert_sql = " @@ -95,12 +96,12 @@ def insert_record(args) :timestamp_test => '2010-4-4 11:44:00', :time_test => '11:44:00', :year_test => Time.now.year, - :char_test => five_words, - :varchar_test => five_words, - :binary_test => five_words, - :varbinary_test => five_words, - :tiny_blob_test => five_words, - :tiny_text_test => Faker::Lorem.paragraph(rand(5)), + :char_test => five_words.join.slice(0, 10), # CHAR(10) + :varchar_test => five_words.join.slice(0, 10), # VARCHAR(10) + :binary_test => five_words.join.byteslice(0, 10), # BINARY(10) + :varbinary_test => five_words.join.byteslice(0, 10), # VARBINARY(10) + :tiny_blob_test => five_words.join.byteslice(0, 255), # TINYBLOB + :tiny_text_test => Faker::Lorem.paragraph(rand(5)).byteslice(0, 255), # TINYTEXT :blob_test => twenty5_paragraphs, :text_test => twenty5_paragraphs, :medium_blob_test => twenty5_paragraphs, From df16fde2cd2c9ffcf96e83d64fcb94c2bc299263 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 09:57:45 -0700 Subject: [PATCH 277/783] `<<` -> `map` --- examples/threaded.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/threaded.rb b/examples/threaded.rb index 7d3b9612a..489edaf9c 100644 --- a/examples/threaded.rb +++ b/examples/threaded.rb @@ -4,17 +4,15 @@ require 'mysql2' require 'timeout' -threads = [] # Should never exceed worst case 3.5 secs across all 20 threads Timeout.timeout(3.5) do - 20.times do - threads << Thread.new do + 20.times.map do + Thread.new do overhead = rand(3) puts ">> thread #{Thread.current.object_id} query, #{overhead} sec overhead" # 3 second overhead per query Mysql2::Client.new(:host => "localhost", :username => "root").query("SELECT sleep(#{overhead}) as result") puts "<< thread #{Thread.current.object_id} result, #{overhead} sec overhead" end - end - threads.each{|t| t.join } -end \ No newline at end of file + end.each(&:join) +end From fdfb24e072fb861a9fe24a4c7e896676a8aa4289 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 10:02:11 -0700 Subject: [PATCH 278/783] Move all development gems to `Gemfile` --- Gemfile | 10 ++++++++-- mysql2.gemspec | 5 ----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index 6aeb1d3bf..3576847ce 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,14 @@ source 'https://rubygems.org/' gemspec -# benchmarks +gem 'rake', '~> 10.4.2' +gem 'rake-compiler', '~> 0.9.5' + +group :test do + gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ + gem 'rspec', '~> 2.8.0' +end + group :benchmarks do gem 'activerecord', '>= 3.0' gem 'benchmark-ips' @@ -13,7 +20,6 @@ group :benchmarks do end group :development do - gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'pry' end diff --git a/mysql2.gemspec b/mysql2.gemspec index e9c3a0176..a9090b8b7 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -13,9 +13,4 @@ Gem::Specification.new do |s| s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split s.test_files = `git ls-files spec examples`.split - - # tests - s.add_development_dependency 'rake-compiler', '~> 0.9.5' - s.add_development_dependency 'rake', '~> 0.9.3' - s.add_development_dependency 'rspec', '~> 2.8.0' end From e95ed0e468e8ff4909e292a045001ff78ad43ff9 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 10:03:29 -0700 Subject: [PATCH 279/783] Convert specs to RSpec 2.99.2 syntax with Transpec This conversion is done by Transpec 3.1.0 with the following command: transpec -b true,false -f For more details: https://github.com/yujinakayama/transpec#supported-conversions --- Gemfile | 2 +- spec/em/em_spec.rb | 24 +-- spec/mysql2/client_spec.rb | 372 +++++++++++++++++----------------- spec/mysql2/error_spec.rb | 20 +- spec/mysql2/result_spec.rb | 212 +++++++++---------- spec/mysql2/statement_spec.rb | 198 +++++++++--------- 6 files changed, 414 insertions(+), 414 deletions(-) diff --git a/Gemfile b/Gemfile index 3576847ce..8643d2d87 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ gem 'rake-compiler', '~> 0.9.5' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ - gem 'rspec', '~> 2.8.0' + gem 'rspec', '~> 2.99' end group :benchmarks do diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 01d162b0c..fd799eb33 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -24,8 +24,8 @@ end end - results[0].keys.should include("second_query") - results[1].keys.should include("first_query") + expect(results[0].keys).to include("second_query") + expect(results[1].keys).to include("first_query") end it "should support queries in callbacks" do @@ -44,12 +44,12 @@ end end - results[0].keys.should include("first_query") - results[1].keys.should include("second_query") + expect(results[0].keys).to include("first_query") + expect(results[1].keys).to include("second_query") end it "should not swallow exceptions raised in callbacks" do - lambda { + expect { EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query "SELECT sleep(0.1) as first_query" @@ -63,13 +63,13 @@ EM.stop_event_loop end end - }.should raise_error + }.to raise_error end context 'when an exception is raised by the client' do let(:client) { Mysql2::EM::Client.new DatabaseCredentials['root'] } let(:error) { StandardError.new('some error') } - before { client.stub(:async_result).and_raise(error) } + before { allow(client).to receive(:async_result).and_raise(error) } it "should swallow exceptions raised in by the client" do errors = [] @@ -85,7 +85,7 @@ EM.stop_event_loop end end - errors.should == [error] + expect(errors).to eq([error]) end it "should fail the deferrable" do @@ -105,7 +105,7 @@ end end end - callbacks_run.should == [:errback] + expect(callbacks_run).to eq([:errback]) end end @@ -121,10 +121,10 @@ callbacks_run << :errback end EM.add_timer(0.1) do - callbacks_run.should == [:callback] - lambda { + expect(callbacks_run).to eq([:callback]) + expect { client.close - }.should_not raise_error(/invalid binding to detach/) + }.not_to raise_error EM.stop_event_loop end end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index d2cfaae6c..23d8c0c58 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -6,42 +6,42 @@ let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) } it "should not raise an exception for valid defaults group" do - lambda { + expect { opts = DatabaseCredentials['root'].merge(:default_file => cnf_file, :default_group => "test") @client = Mysql2::Client.new(opts) - }.should_not raise_error(Mysql2::Error) + }.not_to raise_error end it "should not raise an exception without default group" do - lambda { + expect { @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:default_file => cnf_file)) - }.should_not raise_error(Mysql2::Error) + }.not_to raise_error end end it "should raise an exception upon connection failure" do - lambda { + expect { # The odd local host IP address forces the mysql client library to # use a TCP socket rather than a domain socket. Mysql2::Client.new DatabaseCredentials['root'].merge('host' => '127.0.0.2', 'port' => 999999) - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end if defined? Encoding it "should raise an exception on create for invalid encodings" do - lambda { + expect { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake")) - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end it "should not raise an exception on create for a valid encoding" do - lambda { + expect { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) - }.should_not raise_error(Mysql2::Error) + }.not_to raise_error - lambda { + expect { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) - }.should_not raise_error(Mysql2::Error) + }.not_to raise_error end end @@ -54,7 +54,7 @@ def connect *args end end client = klient.new :flags => Mysql2::Client::FOUND_ROWS - (client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).should be_true + expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to be > 0 end it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do @@ -66,12 +66,12 @@ def connect *args end end client = klient.new - (client.connect_args.last[6] & (Mysql2::Client::REMEMBER_OPTIONS | + expect(client.connect_args.last[6] & (Mysql2::Client::REMEMBER_OPTIONS | Mysql2::Client::LONG_PASSWORD | Mysql2::Client::LONG_FLAG | Mysql2::Client::TRANSACTIONS | Mysql2::Client::PROTOCOL_41 | - Mysql2::Client::SECURE_CONNECTION)).should be_true + Mysql2::Client::SECURE_CONNECTION)).to be > 0 end it "should execute init command" do @@ -79,7 +79,7 @@ def connect *args options[:init_command] = "SET @something = 'setting_value';" client = Mysql2::Client.new(options) result = client.query("SELECT @something;") - result.first['@something'].should eq('setting_value') + expect(result.first['@something']).to eq('setting_value') end it "should send init_command after reconnect" do @@ -89,7 +89,7 @@ def connect *args client = Mysql2::Client.new(options) result = client.query("SELECT @something;") - result.first['@something'].should eq('setting_value') + expect(result.first['@something']).to eq('setting_value') # get the current connection id result = client.query("SELECT CONNECTION_ID()") @@ -105,27 +105,27 @@ def connect *args second_conn_id = result.first['CONNECTION_ID()'] # confirm reconnect by checking the new connection id - first_conn_id.should_not == second_conn_id + expect(first_conn_id).not_to eq(second_conn_id) # At last, check that the init command executed result = client.query("SELECT @something;") - result.first['@something'].should eq('setting_value') + expect(result.first['@something']).to eq('setting_value') end it "should have a global default_query_options hash" do - Mysql2::Client.should respond_to(:default_query_options) + expect(Mysql2::Client).to respond_to(:default_query_options) end it "should be able to connect via SSL options" do ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'" ssl_uncompiled = ssl.any? {|x| x['Value'] == 'OFF'} - pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled + skip("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled ssl_disabled = ssl.any? {|x| x['Value'] == 'DISABLED'} - pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled + skip("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled # You may need to adjust the lines below to match your SSL certificate paths ssl_client = nil - lambda { + expect { ssl_client = Mysql2::Client.new( :sslkey => '/etc/mysql/client-key.pem', :sslcert => '/etc/mysql/client-cert.pem', @@ -133,18 +133,18 @@ def connect *args :sslcapath => '/etc/mysql/', :sslcipher => 'DHE-RSA-AES256-SHA' ) - }.should_not raise_error(Mysql2::Error) + }.not_to raise_error results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a - results[0]['Variable_name'].should eql('Ssl_cipher') - results[0]['Value'].should_not be_nil - results[0]['Value'].should be_kind_of(String) - results[0]['Value'].should_not be_empty + expect(results[0]['Variable_name']).to eql('Ssl_cipher') + expect(results[0]['Value']).not_to be_nil + expect(results[0]['Value']).to be_kind_of(String) + expect(results[0]['Value']).not_to be_empty - results[1]['Variable_name'].should eql('Ssl_version') - results[1]['Value'].should_not be_nil - results[1]['Value'].should be_kind_of(String) - results[1]['Value'].should_not be_empty + expect(results[1]['Variable_name']).to eql('Ssl_version') + expect(results[1]['Value']).not_to be_nil + expect(results[1]['Value']).to be_kind_of(String) + expect(results[1]['Value']).not_to be_empty ssl_client.close end @@ -155,7 +155,7 @@ def run_gc end it "should not leave dangling connections after garbage collection" do - pending('Rubinius misbehaves') if defined?(Rubinius) + skip('Rubinius misbehaves') if defined?(Rubinius) run_gc @@ -166,11 +166,11 @@ def run_gc Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1') end after_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i - after_count.should == before_count + 10 + expect(after_count).to eq(before_count + 10) run_gc final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i - final_count.should == before_count + expect(final_count).to eq(before_count) end if Process.respond_to?(:fork) @@ -193,39 +193,39 @@ def run_gc end it "should be able to connect to database with numeric-only name" do - lambda { + expect { creds = DatabaseCredentials['numericuser'] @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`" @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`" client = Mysql2::Client.new creds @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`" - }.should_not raise_error + }.not_to raise_error end it "should respond to #close" do - @client.should respond_to(:close) + expect(@client).to respond_to(:close) end it "should be able to close properly" do - @client.close.should be_nil - lambda { + expect(@client.close).to be_nil + expect { @client.query "SELECT 1" - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end it "should respond to #query" do - @client.should respond_to(:query) + expect(@client).to respond_to(:query) end it "should respond to #warning_count" do - @client.should respond_to(:warning_count) + expect(@client).to respond_to(:warning_count) end context "#warning_count" do context "when no warnings" do it "should 0" do @client.query('select 1') - @client.warning_count.should == 0 + expect(@client.warning_count).to eq(0) end end context "when has a warnings" do @@ -233,21 +233,21 @@ def run_gc # "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS" # http://dev.mysql.com/doc/refman/5.0/en/explain-extended.html @client.query("explain extended select 1") - @client.warning_count.should > 0 + expect(@client.warning_count).to be > 0 end end end it "should respond to #query_info" do - @client.should respond_to(:query_info) + expect(@client).to respond_to(:query_info) end context "#query_info" do context "when no info present" do it "should 0" do @client.query('select 1') - @client.query_info.should be_empty - @client.query_info_string.should be_nil + expect(@client.query_info).to be_empty + expect(@client.query_info_string).to be_nil end end context "when has some info" do @@ -259,8 +259,8 @@ def run_gc # # Note that mysql_info() returns a non-NULL value for INSERT ... VALUES only for the multiple-row form of the statement (that is, only if multiple value lists are specified). @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)") - @client.query_info.should eql({:records => 2, :duplicates => 0, :warnings => 0}) - @client.query_info_string.should eq('Records: 2 Duplicates: 0 Warnings: 0') + expect(@client.query_info).to eql({:records => 2, :duplicates => 0, :warnings => 0}) + expect(@client.query_info_string).to eq('Records: 2 Duplicates: 0 Warnings: 0') @client.query "DROP TABLE infoTest" end @@ -272,7 +272,7 @@ def run_gc @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true) local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'" local_enabled = local.any? {|x| x['Value'] == 'ON'} - pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled + skip("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled @client_i.query %[ CREATE TABLE IF NOT EXISTS infileTest ( @@ -289,43 +289,43 @@ def run_gc it "should raise an error when local_infile is disabled" do client = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => false) - lambda { + expect { client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" - }.should raise_error(Mysql2::Error, %r{command is not allowed}) + }.to raise_error(Mysql2::Error, /command is not allowed/) end it "should raise an error when a non-existent file is loaded" do - lambda { + expect { @client_i.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest" - }.should_not raise_error(Mysql2::Error, %r{file not found: this/file/is/not/here}) + }.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here') end it "should LOAD DATA LOCAL INFILE" do @client_i.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" info = @client_i.query_info - info.should eql({:records => 1, :deleted => 0, :skipped => 0, :warnings => 0}) + expect(info).to eql({:records => 1, :deleted => 0, :skipped => 0, :warnings => 0}) result = @client_i.query "SELECT * FROM infileTest" - result.first.should eql({'id' => 1, 'foo' => 'Hello', 'bar' => 'World'}) + expect(result.first).to eql({'id' => 1, 'foo' => 'Hello', 'bar' => 'World'}) end end it "should expect connect_timeout to be a positive integer" do - lambda { + expect { Mysql2::Client.new(:connect_timeout => -1) - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end it "should expect read_timeout to be a positive integer" do - lambda { + expect { Mysql2::Client.new(:read_timeout => -1) - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end it "should expect write_timeout to be a positive integer" do - lambda { + expect { Mysql2::Client.new(:write_timeout => -1) - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end context "#query" do @@ -334,7 +334,7 @@ def run_gc expect { @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) - }.to_not raise_exception(Mysql2::Error) + }.to_not raise_error end it "should not let you query again if iterating is not finished when streaming" do @@ -346,59 +346,59 @@ def run_gc end it "should only accept strings as the query parameter" do - lambda { + expect { @client.query ["SELECT 'not right'"] - }.should raise_error(TypeError) + }.to raise_error(TypeError) end it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do result = @client.query "SELECT 1", :something => :else - @client.query_options[:something].should be_nil - result.instance_variable_get('@query_options').should eql(@client.query_options.merge(:something => :else)) - @client.instance_variable_get('@current_query_options').should eql(@client.query_options.merge(:something => :else)) + expect(@client.query_options[:something]).to be_nil + expect(result.instance_variable_get('@query_options')).to eql(@client.query_options.merge(:something => :else)) + expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options.merge(:something => :else)) result = @client.query "SELECT 1" - result.instance_variable_get('@query_options').should eql(@client.query_options) - @client.instance_variable_get('@current_query_options').should eql(@client.query_options) + expect(result.instance_variable_get('@query_options')).to eql(@client.query_options) + expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options) end it "should allow changing query options for subsequent queries" do @client.query_options.merge!(:something => :else) result = @client.query "SELECT 1" - @client.query_options[:something].should eql(:else) - result.instance_variable_get('@query_options')[:something].should eql(:else) + expect(@client.query_options[:something]).to eql(:else) + expect(result.instance_variable_get('@query_options')[:something]).to eql(:else) # Clean up after this test @client.query_options.delete(:something) - @client.query_options[:something].should be_nil + expect(@client.query_options[:something]).to be_nil end it "should return results as a hash by default" do - @client.query("SELECT 1").first.class.should eql(Hash) + expect(@client.query("SELECT 1").first.class).to eql(Hash) end it "should be able to return results as an array" do - @client.query("SELECT 1", :as => :array).first.class.should eql(Array) + expect(@client.query("SELECT 1", :as => :array).first.class).to eql(Array) @client.query("SELECT 1").each(:as => :array) end it "should be able to return results with symbolized keys" do - @client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class.should eql(Symbol) + expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class).to eql(Symbol) end it "should require an open connection" do @client.close - lambda { + expect { @client.query "SELECT 1" - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end if RUBY_PLATFORM !~ /mingw|mswin/ it "should not allow another query to be sent without fetching a result first" do @client.query("SELECT 1", :async => true) - lambda { + expect { @client.query("SELECT 1") - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end it "should describe the thread holding the active query" do @@ -410,15 +410,15 @@ def run_gc it "should timeout if we wait longer than :read_timeout" do client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 0)) - lambda { + expect { client.query('SELECT SLEEP(0.1)') - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end # XXX this test is not deterministic (because Unix signal handling is not) # and may fail on a loaded system it "should run signal handlers while waiting for a response" do - pending('Rubinius misbehaves') if defined?(Rubinius) + skip('Rubinius misbehaves') if defined?(Rubinius) mark = {} trap(:USR1) { mark[:USR1] = Time.now } begin @@ -430,12 +430,12 @@ def run_gc end @client.query('SELECT SLEEP(0.2)') mark[:END] = Time.now - mark.include?(:USR1).should be_true - (mark[:USR1] - mark[:START]).should >= 0.1 - (mark[:USR1] - mark[:START]).should < 0.13 - (mark[:END] - mark[:USR1]).should > 0.09 - (mark[:END] - mark[:START]).should >= 0.2 - (mark[:END] - mark[:START]).should < 0.23 + expect(mark.include?(:USR1)).to be true + expect(mark[:USR1] - mark[:START]).to be >= 0.1 + expect(mark[:USR1] - mark[:START]).to be < 0.13 + expect(mark[:END] - mark[:USR1]).to be > 0.09 + expect(mark[:END] - mark[:START]).to be >= 0.2 + expect(mark[:END] - mark[:START]).to be < 0.23 Process.kill(:TERM, pid) Process.waitpid2(pid) ensure @@ -444,15 +444,15 @@ def run_gc end it "#socket should return a Fixnum (file descriptor from C)" do - @client.socket.class.should eql(Fixnum) - @client.socket.should_not eql(0) + expect(@client.socket.class).to eql(Fixnum) + expect(@client.socket).not_to eql(0) end it "#socket should require an open connection" do @client.close - lambda { + expect { @client.socket - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end it 'should be impervious to connection-corrupting timeouts ' do @@ -506,12 +506,12 @@ def run_gc # In the serial case, the timeout would fire and the test would fail values = Timeout.timeout(sleep_time * 1.1) { threads.map(&:value) } - values.should =~ threads.map(&:object_id) + expect(values).to match_array(threads.map(&:object_id)) end it "evented async queries should be supported" do # should immediately return nil - @client.query("SELECT sleep(0.1)", :async => true).should eql(nil) + expect(@client.query("SELECT sleep(0.1)", :async => true)).to eql(nil) io_wrapper = IO.for_fd(@client.socket) loops = 0 @@ -524,10 +524,10 @@ def run_gc end # make sure we waited some period of time - (loops >= 1).should be_true + expect(loops >= 1).to be true result = @client.async_result - result.class.should eql(Mysql2::Result) + expect(result.class).to eql(Mysql2::Result) end end @@ -538,20 +538,20 @@ def run_gc it "should raise an exception when one of multiple statements fails" do result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';") - result.first['set_1'].should be(1) - lambda { + expect(result.first['set_1']).to be(1) + expect { @multi_client.next_result - }.should raise_error(Mysql2::Error) - @multi_client.next_result.should be_false + }.to raise_error(Mysql2::Error) + expect(@multi_client.next_result).to be false end it "returns multiple result sets" do - @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first.should eql({ 'set_1' => 1 }) + expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql({ 'set_1' => 1 }) - @multi_client.next_result.should be_true - @multi_client.store_result.first.should eql({ 'set_2' => 2 }) + expect(@multi_client.next_result).to be true + expect(@multi_client.store_result.first).to eql({ 'set_2' => 2 }) - @multi_client.next_result.should be_false + expect(@multi_client.next_result).to be false end it "does not interfere with other statements" do @@ -560,223 +560,223 @@ def run_gc @multi_client.store_result end - @multi_client.query("SELECT 3 AS 'next'").first.should == { 'next' => 3 } + expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq({ 'next' => 3 }) end it "will raise on query if there are outstanding results to read" do @multi_client.query("SELECT 1; SELECT 2; SELECT 3") - lambda { + expect { @multi_client.query("SELECT 4") - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end it "#abandon_results! should work" do @multi_client.query("SELECT 1; SELECT 2; SELECT 3") @multi_client.abandon_results! - lambda { + expect { @multi_client.query("SELECT 4") - }.should_not raise_error(Mysql2::Error) + }.not_to raise_error end it "#more_results? should work" do @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'") - @multi_client.more_results?.should be_true + expect(@multi_client.more_results?).to be true @multi_client.next_result @multi_client.store_result - @multi_client.more_results?.should be_false + expect(@multi_client.more_results?).to be false end it "#more_results? should work with stored procedures" do @multi_client.query("DROP PROCEDURE IF EXISTS test_proc") @multi_client.query("CREATE PROCEDURE test_proc() BEGIN SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'; END") - @multi_client.query("CALL test_proc()").first.should eql({ 'set_1' => 1 }) - @multi_client.more_results?.should be_true + expect(@multi_client.query("CALL test_proc()").first).to eql({ 'set_1' => 1 }) + expect(@multi_client.more_results?).to be true @multi_client.next_result - @multi_client.store_result.first.should eql({ 'set_2' => 2 }) + expect(@multi_client.store_result.first).to eql({ 'set_2' => 2 }) @multi_client.next_result - @multi_client.store_result.should be_nil # this is the result from CALL itself + expect(@multi_client.store_result).to be_nil # this is the result from CALL itself - @multi_client.more_results?.should be_false + expect(@multi_client.more_results?).to be false end end end it "should respond to #socket" do - @client.should respond_to(:socket) + expect(@client).to respond_to(:socket) end if RUBY_PLATFORM =~ /mingw|mswin/ it "#socket should raise as it's not supported" do - lambda { + expect { @client.socket - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end end it "should respond to escape" do - Mysql2::Client.should respond_to(:escape) + expect(Mysql2::Client).to respond_to(:escape) end context "escape" do it "should return a new SQL-escape version of the passed string" do - Mysql2::Client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno") + expect(Mysql2::Client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno") end it "should return the passed string if nothing was escaped" do str = "plain" - Mysql2::Client.escape(str).object_id.should eql(str.object_id) + expect(Mysql2::Client.escape(str).object_id).to eql(str.object_id) end it "should not overflow the thread stack" do - lambda { + expect { Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join - }.should_not raise_error(SystemStackError) + }.not_to raise_error end it "should not overflow the process stack" do - lambda { + expect { Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join - }.should_not raise_error(SystemStackError) + }.not_to raise_error end unless RUBY_VERSION =~ /1.8/ it "should carry over the original string's encoding" do str = "abc'def\"ghi\0jkl%mno" escaped = Mysql2::Client.escape(str) - escaped.encoding.should eql(str.encoding) + expect(escaped.encoding).to eql(str.encoding) str.encode!('us-ascii') escaped = Mysql2::Client.escape(str) - escaped.encoding.should eql(str.encoding) + expect(escaped.encoding).to eql(str.encoding) end end end it "should respond to #escape" do - @client.should respond_to(:escape) + expect(@client).to respond_to(:escape) end context "#escape" do it "should return a new SQL-escape version of the passed string" do - @client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno") + expect(@client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno") end it "should return the passed string if nothing was escaped" do str = "plain" - @client.escape(str).object_id.should eql(str.object_id) + expect(@client.escape(str).object_id).to eql(str.object_id) end it "should not overflow the thread stack" do - lambda { + expect { Thread.new { @client.escape("'" * 256 * 1024) }.join - }.should_not raise_error(SystemStackError) + }.not_to raise_error end it "should not overflow the process stack" do - lambda { + expect { Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join - }.should_not raise_error(SystemStackError) + }.not_to raise_error end it "should require an open connection" do @client.close - lambda { + expect { @client.escape "" - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end end it "should respond to #info" do - @client.should respond_to(:info) + expect(@client).to respond_to(:info) end it "#info should return a hash containing the client version ID and String" do info = @client.info - info.class.should eql(Hash) - info.should have_key(:id) - info[:id].class.should eql(Fixnum) - info.should have_key(:version) - info[:version].class.should eql(String) + expect(info.class).to eql(Hash) + expect(info).to have_key(:id) + expect(info[:id].class).to eql(Fixnum) + expect(info).to have_key(:version) + expect(info[:version].class).to eql(String) end if defined? Encoding context "strings returned by #info" do it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do - @client.info[:version].encoding.should eql(Encoding.find('utf-8')) + expect(@client.info[:version].encoding).to eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - client2.info[:version].encoding.should eql(Encoding.find('us-ascii')) + expect(client2.info[:version].encoding).to eql(Encoding.find('us-ascii')) end end it "should use Encoding.default_internal" do with_internal_encoding 'utf-8' do - @client.info[:version].encoding.should eql(Encoding.default_internal) + expect(@client.info[:version].encoding).to eql(Encoding.default_internal) end with_internal_encoding 'us-ascii' do - @client.info[:version].encoding.should eql(Encoding.default_internal) + expect(@client.info[:version].encoding).to eql(Encoding.default_internal) end end end end it "should respond to #server_info" do - @client.should respond_to(:server_info) + expect(@client).to respond_to(:server_info) end it "#server_info should return a hash containing the client version ID and String" do server_info = @client.server_info - server_info.class.should eql(Hash) - server_info.should have_key(:id) - server_info[:id].class.should eql(Fixnum) - server_info.should have_key(:version) - server_info[:version].class.should eql(String) + expect(server_info.class).to eql(Hash) + expect(server_info).to have_key(:id) + expect(server_info[:id].class).to eql(Fixnum) + expect(server_info).to have_key(:version) + expect(server_info[:version].class).to eql(String) end it "#server_info should require an open connection" do @client.close - lambda { + expect { @client.server_info - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end if defined? Encoding context "strings returned by #server_info" do it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do - @client.server_info[:version].encoding.should eql(Encoding.find('utf-8')) + expect(@client.server_info[:version].encoding).to eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii')) + expect(client2.server_info[:version].encoding).to eql(Encoding.find('us-ascii')) end end it "should use Encoding.default_internal" do with_internal_encoding 'utf-8' do - @client.server_info[:version].encoding.should eql(Encoding.default_internal) + expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) end with_internal_encoding 'us-ascii' do - @client.server_info[:version].encoding.should eql(Encoding.default_internal) + expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) end end end end it "should raise a Mysql2::Error exception upon connection failure" do - lambda { + expect { Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42' - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) - lambda { + expect { Mysql2::Client.new DatabaseCredentials['root'] - }.should_not raise_error(Mysql2::Error) + }.not_to raise_error end context 'write operations api' do @@ -790,46 +790,46 @@ def run_gc end it "should respond to #last_id" do - @client.should respond_to(:last_id) + expect(@client).to respond_to(:last_id) end it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do - @client.last_id.should eql(0) + expect(@client.last_id).to eql(0) @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" - @client.last_id.should eql(1) + expect(@client.last_id).to eql(1) end it "should respond to #last_id" do - @client.should respond_to(:last_id) + expect(@client).to respond_to(:last_id) end it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" - @client.affected_rows.should eql(1) + expect(@client.affected_rows).to eql(1) @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1" - @client.affected_rows.should eql(1) + expect(@client.affected_rows).to eql(1) end it "#last_id should handle BIGINT auto-increment ids above 32 bits" do # The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x. # Insert a row with a given ID, this should raise the auto-increment state @client.query "INSERT INTO lastIdTest (id, blah) VALUES (5000000000, 5000)" - @client.last_id.should eql(5000000000) + expect(@client.last_id).to eql(5000000000) @client.query "INSERT INTO lastIdTest (blah) VALUES (5001)" - @client.last_id.should eql(5000000001) + expect(@client.last_id).to eql(5000000001) end end it "should respond to #thread_id" do - @client.should respond_to(:thread_id) + expect(@client).to respond_to(:thread_id) end it "#thread_id should be a Fixnum" do - @client.thread_id.class.should eql(Fixnum) + expect(@client.thread_id.class).to eql(Fixnum) end it "should respond to #ping" do - @client.should respond_to(:ping) + expect(@client).to respond_to(:ping) end context "select_db" do @@ -848,38 +848,38 @@ def run_gc end it "should respond to #select_db" do - @client.should respond_to(:select_db) + expect(@client).to respond_to(:select_db) end it "should switch databases" do @client.select_db("test_selectdb_0") - @client.query("SHOW TABLES").first.values.first.should eql("test0") + expect(@client.query("SHOW TABLES").first.values.first).to eql("test0") @client.select_db("test_selectdb_1") - @client.query("SHOW TABLES").first.values.first.should eql("test1") + expect(@client.query("SHOW TABLES").first.values.first).to eql("test1") @client.select_db("test_selectdb_0") - @client.query("SHOW TABLES").first.values.first.should eql("test0") + expect(@client.query("SHOW TABLES").first.values.first).to eql("test0") end it "should raise a Mysql2::Error when the database doesn't exist" do - lambda { + expect { @client.select_db("nopenothere") - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) end it "should return the database switched to" do - @client.select_db("test_selectdb_1").should eq("test_selectdb_1") + expect(@client.select_db("test_selectdb_1")).to eq("test_selectdb_1") end end it "#thread_id should return a boolean" do - @client.ping.should eql(true) + expect(@client.ping).to eql(true) @client.close - @client.ping.should eql(false) + expect(@client.ping).to eql(false) end unless RUBY_VERSION =~ /1.8/ it "should respond to #encoding" do - @client.should respond_to(:encoding) + expect(@client).to respond_to(:encoding) end end end diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 8b5cb2bce..a41397a53 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -18,12 +18,12 @@ end it "responds to error_number and sql_state, with aliases" do - error.should respond_to(:error_number) - error.should respond_to(:sql_state) + expect(error).to respond_to(:error_number) + expect(error).to respond_to(:sql_state) # Mysql gem compatibility - error.should respond_to(:errno) - error.should respond_to(:error) + expect(error).to respond_to(:errno) + expect(error).to respond_to(:error) end if "".respond_to? :encoding @@ -55,27 +55,27 @@ it "returns error messages as UTF-8 by default" do with_internal_encoding nil do - error.message.encoding.should eql(Encoding::UTF_8) + expect(error.message.encoding).to eql(Encoding::UTF_8) error.message.valid_encoding? - bad_err.message.encoding.should eql(Encoding::UTF_8) + expect(bad_err.message.encoding).to eql(Encoding::UTF_8) bad_err.message.valid_encoding? - bad_err.message.should include("??}\u001F") + expect(bad_err.message).to include("??}\u001F") end end it "returns sql state as ASCII" do - error.sql_state.encoding.should eql(Encoding::US_ASCII) + expect(error.sql_state.encoding).to eql(Encoding::US_ASCII) error.sql_state.valid_encoding? end it "returns error messages and sql state in Encoding.default_internal if set" do with_internal_encoding 'UTF-16LE' do - error.message.encoding.should eql(Encoding.default_internal) + expect(error.message.encoding).to eql(Encoding.default_internal) error.message.valid_encoding? - bad_err.message.encoding.should eql(Encoding.default_internal) + expect(bad_err.message.encoding).to eql(Encoding.default_internal) bad_err.message.valid_encoding? end end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 8b07f9e5e..27975a611 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -7,33 +7,33 @@ end it "should have included Enumerable" do - Mysql2::Result.ancestors.include?(Enumerable).should be_true + expect(Mysql2::Result.ancestors.include?(Enumerable)).to be true end it "should respond to #each" do - @result.should respond_to(:each) + expect(@result).to respond_to(:each) end it "should raise a Mysql2::Error exception upon a bad query" do - lambda { + expect { @client.query "bad sql" - }.should raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error) - lambda { + expect { @client.query "SELECT 1" - }.should_not raise_error(Mysql2::Error) + }.not_to raise_error end it "should respond to #count, which is aliased as #size" do r = @client.query "SELECT 1" - r.should respond_to :count - r.should respond_to :size + expect(r).to respond_to :count + expect(r).to respond_to :size end it "should be able to return the number of rows in the result set" do r = @client.query "SELECT 1" - r.count.should eql(1) - r.size.should eql(1) + expect(r.count).to eql(1) + expect(r.size).to eql(1) end context "metadata queries" do @@ -45,39 +45,39 @@ context "#each" do it "should yield rows as hash's" do @result.each do |row| - row.class.should eql(Hash) + expect(row.class).to eql(Hash) end end it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do @result.each(:symbolize_keys => true) do |row| - row.keys.first.class.should eql(Symbol) + expect(row.keys.first.class).to eql(Symbol) end end it "should be able to return results as an array" do @result.each(:as => :array) do |row| - row.class.should eql(Array) + expect(row.class).to eql(Array) end end it "should cache previously yielded results by default" do - @result.first.object_id.should eql(@result.first.object_id) + expect(@result.first.object_id).to eql(@result.first.object_id) end it "should not cache previously yielded results if cache_rows is disabled" do result = @client.query "SELECT 1", :cache_rows => false - result.first.object_id.should_not eql(result.first.object_id) + expect(result.first.object_id).not_to eql(result.first.object_id) end it "should yield different value for #first if streaming" do result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false - result.first.should_not eql(result.first) + expect(result.first).not_to eql(result.first) end it "should yield the same value for #first if streaming is disabled" do result = @client.query "SELECT 1 UNION SELECT 2", :stream => false - result.first.should eql(result.first) + expect(result.first).to eql(result.first) end it "should throw an exception if we try to iterate twice when streaming is enabled" do @@ -96,43 +96,43 @@ end it "method should exist" do - @test_result.should respond_to(:fields) + expect(@test_result).to respond_to(:fields) end it "should return an array of field names in proper order" do result = @client.query "SELECT 'a', 'b', 'c'" - result.fields.should eql(['a', 'b', 'c']) + expect(result.fields).to eql(['a', 'b', 'c']) end end context "streaming" do it "should maintain a count while streaming" do result = @client.query('SELECT 1', :stream => true, :cache_rows => false) - result.count.should eql(0) + expect(result.count).to eql(0) result.each.to_a - result.count.should eql(1) + expect(result.count).to eql(1) end it "should retain the count when mixing first and each" do result = @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) - result.count.should eql(0) + expect(result.count).to eql(0) result.first - result.count.should eql(1) + expect(result.count).to eql(1) result.each.to_a - result.count.should eql(2) + expect(result.count).to eql(2) end it "should not yield nil at the end of streaming" do result = @client.query('SELECT * FROM mysql2_test', :stream => true, :cache_rows => false) - result.each { |r| r.should_not be_nil} + result.each { |r| expect(r).not_to be_nil} end it "#count should be zero for rows after streaming when there were no results" do @client.query "USE test" result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false) - result.count.should eql(0) - result.each {|r| } - result.count.should eql(0) + expect(result.count).to eql(0) + result.each.to_a + expect(result.count).to eql(0) end it "should raise an exception if streaming ended due to a timeout" do @@ -149,12 +149,12 @@ client.query "SET net_write_timeout = 1" res = client.query "SELECT * FROM streamingTest", :stream => true, :cache_rows => false - lambda { + expect { res.each_with_index do |row, i| # Exhaust the first result packet then trigger a timeout sleep 2 if i > 0 && i % 1000 == 0 end - }.should raise_error(Mysql2::Error, /Lost connection/) + }.to raise_error(Mysql2::Error, /Lost connection/) end end @@ -165,32 +165,32 @@ it "should return nil values for NULL and strings for everything else when :cast is false" do result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast => false).first - result["null_test"].should be_nil - result["tiny_int_test"].should eql("1") - result["bool_cast_test"].should eql("1") - result["int_test"].should eql("10") - result["date_test"].should eql("2010-04-04") - result["enum_test"].should eql("val1") + expect(result["null_test"]).to be_nil + expect(result["tiny_int_test"]).to eql("1") + expect(result["bool_cast_test"]).to eql("1") + expect(result["int_test"]).to eql("10") + expect(result["date_test"]).to eql("2010-04-04") + expect(result["enum_test"]).to eql("val1") end it "should return nil for a NULL value" do - @test_result['null_test'].class.should eql(NilClass) - @test_result['null_test'].should eql(nil) + expect(@test_result['null_test'].class).to eql(NilClass) + expect(@test_result['null_test']).to eql(nil) end it "should return String for a BIT(64) value" do - @test_result['bit_test'].class.should eql(String) - @test_result['bit_test'].should eql("\000\000\000\000\000\000\000\005") + expect(@test_result['bit_test'].class).to eql(String) + expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do - @test_result['single_bit_test'].class.should eql(String) - @test_result['single_bit_test'].should eql("\001") + expect(@test_result['single_bit_test'].class).to eql(String) + expect(@test_result['single_bit_test']).to eql("\001") end it "should return Fixnum for a TINYINT value" do - [Fixnum, Bignum].should include(@test_result['tiny_int_test'].class) - @test_result['tiny_int_test'].should eql(1) + expect([Fixnum, Bignum]).to include(@test_result['tiny_int_test'].class) + expect(@test_result['tiny_int_test']).to eql(1) end it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do @@ -204,9 +204,9 @@ result1 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast_booleans => true result2 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 0 LIMIT 1', :cast_booleans => true result3 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = -1 LIMIT 1', :cast_booleans => true - result1.first['bool_cast_test'].should be_true - result2.first['bool_cast_test'].should be_false - result3.first['bool_cast_test'].should be_true + expect(result1.first['bool_cast_test']).to be true + expect(result2.first['bool_cast_test']).to be false + expect(result3.first['bool_cast_test']).to be true @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" end @@ -219,55 +219,55 @@ result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", :cast_booleans => true result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", :cast_booleans => true - result1.first['single_bit_test'].should be_true - result2.first['single_bit_test'].should be_false + expect(result1.first['single_bit_test']).to be true + expect(result2.first['single_bit_test']).to be false @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" end it "should return Fixnum for a SMALLINT value" do - [Fixnum, Bignum].should include(@test_result['small_int_test'].class) - @test_result['small_int_test'].should eql(10) + expect([Fixnum, Bignum]).to include(@test_result['small_int_test'].class) + expect(@test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do - [Fixnum, Bignum].should include(@test_result['medium_int_test'].class) - @test_result['medium_int_test'].should eql(10) + expect([Fixnum, Bignum]).to include(@test_result['medium_int_test'].class) + expect(@test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do - [Fixnum, Bignum].should include(@test_result['int_test'].class) - @test_result['int_test'].should eql(10) + expect([Fixnum, Bignum]).to include(@test_result['int_test'].class) + expect(@test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do - [Fixnum, Bignum].should include(@test_result['big_int_test'].class) - @test_result['big_int_test'].should eql(10) + expect([Fixnum, Bignum]).to include(@test_result['big_int_test'].class) + expect(@test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do - [Fixnum, Bignum].should include(@test_result['year_test'].class) - @test_result['year_test'].should eql(2009) + expect([Fixnum, Bignum]).to include(@test_result['year_test'].class) + expect(@test_result['year_test']).to eql(2009) end it "should return BigDecimal for a DECIMAL value" do - @test_result['decimal_test'].class.should eql(BigDecimal) - @test_result['decimal_test'].should eql(10.3) + expect(@test_result['decimal_test'].class).to eql(BigDecimal) + expect(@test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do - @test_result['float_test'].class.should eql(Float) - @test_result['float_test'].should eql(10.3) + expect(@test_result['float_test'].class).to eql(Float) + expect(@test_result['float_test']).to eql(10.3) end it "should return Float for a DOUBLE value" do - @test_result['double_test'].class.should eql(Float) - @test_result['double_test'].should eql(10.3) + expect(@test_result['double_test'].class).to eql(Float) + expect(@test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do - @test_result['date_time_test'].class.should eql(Time) - @test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2010-04-04 11:44:00') + expect(@test_result['date_time_test'].class).to eql(Time) + expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end if 1.size == 4 # 32bit @@ -280,61 +280,61 @@ it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - r.first['test'].class.should eql(klass) + expect(r.first['test'].class).to eql(klass) end it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - r.first['test'].class.should eql(klass) + expect(r.first['test'].class).to eql(klass) end elsif 1.size == 8 # 64bit unless RUBY_VERSION =~ /1.8/ it "should return Time when timestamp is < 1901-12-13 20:45:52" do r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - r.first['test'].class.should eql(Time) + expect(r.first['test'].class).to eql(Time) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - r.first['test'].class.should eql(Time) + expect(r.first['test'].class).to eql(Time) end else it "should return Time when timestamp is > 0138-12-31 11:59:59" do r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") - r.first['test'].class.should eql(Time) + expect(r.first['test'].class).to eql(Time) end it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") - r.first['test'].class.should eql(DateTime) + expect(r.first['test'].class).to eql(DateTime) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - r.first['test'].class.should eql(Time) + expect(r.first['test'].class).to eql(Time) end end end it "should return Time for a TIMESTAMP value when within the supported range" do - @test_result['timestamp_test'].class.should eql(Time) - @test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2010-04-04 11:44:00') + expect(@test_result['timestamp_test'].class).to eql(Time) + expect(@test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do - @test_result['time_test'].class.should eql(Time) - @test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2000-01-01 11:44:00') + expect(@test_result['time_test'].class).to eql(Time) + expect(@test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do - @test_result['date_test'].class.should eql(Date) - @test_result['date_test'].strftime("%Y-%m-%d").should eql('2010-04-04') + expect(@test_result['date_test'].class).to eql(Date) + expect(@test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do - @test_result['enum_test'].class.should eql(String) - @test_result['enum_test'].should eql('val1') + expect(@test_result['enum_test'].class).to eql(String) + expect(@test_result['enum_test']).to eql('val1') end it "should raise an error given an invalid DATETIME" do @@ -347,11 +347,11 @@ it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.find('utf-8')) + expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.find('us-ascii')) + expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) client2.close end end @@ -359,20 +359,20 @@ it "should use Encoding.default_internal" do with_internal_encoding 'utf-8' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.default_internal) + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding 'us-ascii' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.default_internal) + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end end end end it "should return String for a SET value" do - @test_result['set_test'].class.should eql(String) - @test_result['set_test'].should eql('val1,val2') + expect(@test_result['set_test'].class).to eql(String) + expect(@test_result['set_test']).to eql('val1,val2') end if defined? Encoding @@ -380,11 +380,11 @@ it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.find('utf-8')) + expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.find('us-ascii')) + expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) client2.close end end @@ -392,20 +392,20 @@ it "should use Encoding.default_internal" do with_internal_encoding 'utf-8' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.default_internal) + expect(result['set_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding 'us-ascii' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.default_internal) + expect(result['set_test'].encoding).to eql(Encoding.default_internal) end end end end it "should return String for a BINARY value" do - @test_result['binary_test'].class.should eql(String) - @test_result['binary_test'].should eql("test#{"\000"*6}") + expect(@test_result['binary_test'].class).to eql(String) + expect(@test_result['binary_test']).to eql("test#{"\000"*6}") end if defined? Encoding @@ -413,19 +413,19 @@ it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end end it "should not use Encoding.default_internal" do with_internal_encoding 'utf-8' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end with_internal_encoding 'us-ascii' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end end end @@ -445,8 +445,8 @@ 'long_text_test' => 'LONGTEXT' }.each do |field, type| it "should return a String for #{type}" do - @test_result[field].class.should eql(String) - @test_result[field].should eql("test") + expect(@test_result[field].class).to eql(String) + expect(@test_result[field]).to eql("test") end if defined? Encoding @@ -455,30 +455,30 @@ it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end end it "should not use Encoding.default_internal" do with_internal_encoding 'utf-8' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end with_internal_encoding 'us-ascii' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end end else it "should default to utf-8 if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.find('utf-8')) + expect(result[field].encoding).to eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.find('us-ascii')) + expect(result[field].encoding).to eql(Encoding.find('us-ascii')) client2.close end end @@ -486,12 +486,12 @@ it "should use Encoding.default_internal" do with_internal_encoding 'utf-8' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.default_internal) + expect(result[field].encoding).to eql(Encoding.default_internal) end with_internal_encoding 'us-ascii' do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.default_internal) + expect(result[field].encoding).to eql(Encoding.default_internal) end end end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index d0c7df8a5..a7bd28218 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -8,46 +8,46 @@ it "should create a statement" do statement = nil - lambda { statement = @client.prepare 'SELECT 1' }.should_not raise_error - statement.should be_kind_of Mysql2::Statement + expect { statement = @client.prepare 'SELECT 1' }.not_to raise_error + expect(statement).to be_kind_of Mysql2::Statement end it "should raise an exception when server disconnects" do @client.close - lambda { @client.prepare 'SELECT 1' }.should raise_error(Mysql2::Error) + expect { @client.prepare 'SELECT 1' }.to raise_error(Mysql2::Error) end it "should tell us the param count" do statement = @client.prepare 'SELECT ?, ?' - statement.param_count.should == 2 + expect(statement.param_count).to eq(2) statement2 = @client.prepare 'SELECT 1' - statement2.param_count.should == 0 + expect(statement2.param_count).to eq(0) end it "should tell us the field count" do statement = @client.prepare 'SELECT ?, ?' - statement.field_count.should == 2 + expect(statement.field_count).to eq(2) statement2 = @client.prepare 'SELECT 1' - statement2.field_count.should == 1 + expect(statement2.field_count).to eq(1) end it "should let us execute our statement" do statement = @client.prepare 'SELECT 1' - statement.execute.should_not == nil + expect(statement.execute).not_to eq(nil) end it "should raise an exception without a block" do statement = @client.prepare 'SELECT 1' statement.execute - lambda { statement.each }.should raise_error + expect { statement.each }.to raise_error end it "should tell us the result count" do statement = @client.prepare 'SELECT 1' result = statement.execute - result.count.should == 1 + expect(result.count).to eq(1) end it "should let us iterate over results" do @@ -55,7 +55,7 @@ result = statement.execute rows = [] result.each {|r| rows << r} - rows.should == [{"1"=>1}] + expect(rows).to eq([{"1"=>1}]) end it "should keep its result after other query" do @@ -65,24 +65,24 @@ stmt = @client.prepare('SELECT a FROM mysql2_stmt_q WHERE a = ?') result1 = stmt.execute(1) result2 = stmt.execute(2) - result2.first.should == {"a"=>2} - result1.first.should == {"a"=>1} + expect(result2.first).to eq({"a"=>2}) + expect(result1.first).to eq({"a"=>1}) @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end it "should select dates" do statement = @client.prepare 'SELECT NOW()' result = statement.execute - result.first.first[1].should be_kind_of Time + expect(result.first.first[1]).to be_kind_of Time end it "should tell us about the fields" do statement = @client.prepare 'SELECT 1 as foo, 2' statement.execute list = statement.fields - list.length.should == 2 - list.first.should == 'foo' - list[1].should == '2' + expect(list.length).to eq(2) + expect(list.first).to eq('foo') + expect(list[1]).to eq('2') end context "utf8_db" do @@ -100,29 +100,29 @@ it "should be able to retrieve utf8 field names correctly" do stmt = @client.prepare 'SELECT * FROM `テーブル`' - stmt.fields.should == ['整数', '文字列'] + expect(stmt.fields).to eq(['整数', '文字列']) result = stmt.execute - result.to_a.should == [{"整数"=>1, "文字列"=>"イチ"}, {"整数"=>2, "文字列"=>"弐"}, {"整数"=>3, "文字列"=>"さん"}] + expect(result.to_a).to eq([{"整数"=>1, "文字列"=>"イチ"}, {"整数"=>2, "文字列"=>"弐"}, {"整数"=>3, "文字列"=>"さん"}]) end it "should be able to retrieve utf8 param query correctly" do stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' - stmt.param_count.should == 1 + expect(stmt.param_count).to eq(1) result = stmt.execute 'イチ' - result.to_a.should == [{"整数"=>1}] + expect(result.to_a).to eq([{"整数"=>1}]) end it "should be able to retrieve query with param in different encoding correctly" do stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' - stmt.param_count.should == 1 + expect(stmt.param_count).to eq(1) param = 'イチ'.encode("EUC-JP") result = stmt.execute param - result.to_a.should == [{"整数"=>1}] + expect(result.to_a).to eq([{"整数"=>1}]) end end if defined? Encoding @@ -137,9 +137,9 @@ stmt.execute.each do |r| case n when 1 - r.should == [1] + expect(r).to eq([1]) when 2 - r.should == [2] + expect(r).to eq([2]) else violated "returned more than two rows" end @@ -155,7 +155,7 @@ it "should yield rows as hash's" do @result = @client.prepare("SELECT 1").execute @result.each do |row| - row.class.should eql(Hash) + expect(row.class).to eql(Hash) end end @@ -163,7 +163,7 @@ @client.query_options[:symbolize_keys] = true @result = @client.prepare("SELECT 1").execute @result.each do |row| - row.keys.first.class.should eql(Symbol) + expect(row.keys.first.class).to eql(Symbol) end @client.query_options[:symbolize_keys] = false end @@ -173,7 +173,7 @@ @result = @client.prepare("SELECT 1").execute @result.each do |row| - row.class.should eql(Array) + expect(row.class).to eql(Array) end @client.query_options[:as] = :hash @@ -181,7 +181,7 @@ it "should cache previously yielded results by default" do @result = @client.prepare("SELECT 1").execute - @result.first.object_id.should eql(@result.first.object_id) + expect(@result.first.object_id).to eql(@result.first.object_id) end it "should yield different value for #first if streaming" do @@ -189,7 +189,7 @@ @client.query_options[:cache_rows] = false result = @client.prepare("SELECT 1 UNION SELECT 2").execute - result.first.should_not eql(result.first) + expect(result.first).not_to eql(result.first) @client.query_options[:stream] = false @client.query_options[:cache_rows] = true @@ -198,7 +198,7 @@ it "should yield the same value for #first if streaming is disabled" do @client.query_options[:stream] = false result = @client.prepare("SELECT 1 UNION SELECT 2").execute - result.first.should eql(result.first) + expect(result.first).to eql(result.first) end it "should throw an exception if we try to iterate twice when streaming is enabled" do @@ -224,12 +224,12 @@ end it "method should exist" do - @test_result.should respond_to(:fields) + expect(@test_result).to respond_to(:fields) end it "should return an array of field names in proper order" do result = @client.prepare("SELECT 'a', 'b', 'c'").execute - result.fields.should eql(['a', 'b', 'c']) + expect(result.fields).to eql(['a', 'b', 'c']) end end @@ -240,18 +240,18 @@ end it "should return nil for a NULL value" do - @test_result['null_test'].class.should eql(NilClass) - @test_result['null_test'].should eql(nil) + expect(@test_result['null_test'].class).to eql(NilClass) + expect(@test_result['null_test']).to eql(nil) end it "should return Fixnum for a BIT value" do - @test_result['bit_test'].class.should eql(String) - @test_result['bit_test'].should eql("\000\000\000\000\000\000\000\005") + expect(@test_result['bit_test'].class).to eql(String) + expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return Fixnum for a TINYINT value" do - [Fixnum, Bignum].should include(@test_result['tiny_int_test'].class) - @test_result['tiny_int_test'].should eql(1) + expect([Fixnum, Bignum]).to include(@test_result['tiny_int_test'].class) + expect(@test_result['tiny_int_test']).to eql(1) end it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do @@ -265,56 +265,56 @@ result1 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast_booleans => true result2 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 0 LIMIT 1', :cast_booleans => true result3 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = -1 LIMIT 1', :cast_booleans => true - result1.first['bool_cast_test'].should be_true - result2.first['bool_cast_test'].should be_false - result3.first['bool_cast_test'].should be_true + expect(result1.first['bool_cast_test']).to be true + expect(result2.first['bool_cast_test']).to be false + expect(result3.first['bool_cast_test']).to be true @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" end it "should return Fixnum for a SMALLINT value" do - [Fixnum, Bignum].should include(@test_result['small_int_test'].class) - @test_result['small_int_test'].should eql(10) + expect([Fixnum, Bignum]).to include(@test_result['small_int_test'].class) + expect(@test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do - [Fixnum, Bignum].should include(@test_result['medium_int_test'].class) - @test_result['medium_int_test'].should eql(10) + expect([Fixnum, Bignum]).to include(@test_result['medium_int_test'].class) + expect(@test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do - [Fixnum, Bignum].should include(@test_result['int_test'].class) - @test_result['int_test'].should eql(10) + expect([Fixnum, Bignum]).to include(@test_result['int_test'].class) + expect(@test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do - [Fixnum, Bignum].should include(@test_result['big_int_test'].class) - @test_result['big_int_test'].should eql(10) + expect([Fixnum, Bignum]).to include(@test_result['big_int_test'].class) + expect(@test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do - [Fixnum, Bignum].should include(@test_result['year_test'].class) - @test_result['year_test'].should eql(2009) + expect([Fixnum, Bignum]).to include(@test_result['year_test'].class) + expect(@test_result['year_test']).to eql(2009) end it "should return BigDecimal for a DECIMAL value" do - @test_result['decimal_test'].class.should eql(BigDecimal) - @test_result['decimal_test'].should eql(10.3) + expect(@test_result['decimal_test'].class).to eql(BigDecimal) + expect(@test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do - @test_result['float_test'].class.should eql(Float) - @test_result['float_test'].should be_within(1e-5).of(10.3) + expect(@test_result['float_test'].class).to eql(Float) + expect(@test_result['float_test']).to be_within(1e-5).of(10.3) end it "should return Float for a DOUBLE value" do - @test_result['double_test'].class.should eql(Float) - @test_result['double_test'].should be_within(1e-5).of(10.3) + expect(@test_result['double_test'].class).to eql(Float) + expect(@test_result['double_test']).to be_within(1e-5).of(10.3) end it "should return Time for a DATETIME value when within the supported range" do - @test_result['date_time_test'].class.should eql(Time) - @test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2010-04-04 11:44:00') + expect(@test_result['date_time_test'].class).to eql(Time) + expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end if 1.size == 4 # 32bit @@ -327,61 +327,61 @@ it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - r.first['test'].class.should eql(klass) + expect(r.first['test'].class).to eql(klass) end it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - r.first['test'].class.should eql(klass) + expect(r.first['test'].class).to eql(klass) end elsif 1.size == 8 # 64bit unless RUBY_VERSION =~ /1.8/ it "should return Time when timestamp is < 1901-12-13 20:45:52" do r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - r.first['test'].class.should eql(Time) + expect(r.first['test'].class).to eql(Time) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - r.first['test'].class.should eql(Time) + expect(r.first['test'].class).to eql(Time) end else it "should return Time when timestamp is > 0138-12-31 11:59:59" do r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") - r.first['test'].class.should eql(Time) + expect(r.first['test'].class).to eql(Time) end it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") - r.first['test'].class.should eql(DateTime) + expect(r.first['test'].class).to eql(DateTime) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - r.first['test'].class.should eql(Time) + expect(r.first['test'].class).to eql(Time) end end end it "should return Time for a TIMESTAMP value when within the supported range" do - @test_result['timestamp_test'].class.should eql(Time) - @test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2010-04-04 11:44:00') + expect(@test_result['timestamp_test'].class).to eql(Time) + expect(@test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do - @test_result['time_test'].class.should eql(Time) - @test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2000-01-01 11:44:00') + expect(@test_result['time_test'].class).to eql(Time) + expect(@test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do - @test_result['date_test'].class.should eql(Date) - @test_result['date_test'].strftime("%Y-%m-%d").should eql('2010-04-04') + expect(@test_result['date_test'].class).to eql(Date) + expect(@test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do - @test_result['enum_test'].class.should eql(String) - @test_result['enum_test'].should eql('val1') + expect(@test_result['enum_test'].class).to eql(String) + expect(@test_result['enum_test']).to eql('val1') end if defined? Encoding @@ -389,28 +389,28 @@ it "should default to the connection's encoding if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.find('utf-8')) + expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ascii")) client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.find('us-ascii')) + expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) end it "should use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.default_internal) + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['enum_test'].encoding.should eql(Encoding.default_internal) + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a SET value" do - @test_result['set_test'].class.should eql(String) - @test_result['set_test'].should eql('val1,val2') + expect(@test_result['set_test'].class).to eql(String) + expect(@test_result['set_test']).to eql('val1,val2') end if defined? Encoding @@ -418,28 +418,28 @@ it "should default to the connection's encoding if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.find('utf-8')) + expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ascii")) client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.find('us-ascii')) + expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) end it "should use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.default_internal) + expect(result['set_test'].encoding).to eql(Encoding.default_internal) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['set_test'].encoding.should eql(Encoding.default_internal) + expect(result['set_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a BINARY value" do - @test_result['binary_test'].class.should eql(String) - @test_result['binary_test'].should eql("test#{"\000"*6}") + expect(@test_result['binary_test'].class).to eql(String) + expect(@test_result['binary_test']).to eql("test#{"\000"*6}") end if defined? Encoding @@ -447,16 +447,16 @@ it "should default to binary if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end it "should not use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end end end @@ -475,8 +475,8 @@ 'long_text_test' => 'LONGTEXT' }.each do |field, type| it "should return a String for #{type}" do - @test_result[field].class.should eql(String) - @test_result[field].should eql("test") + expect(@test_result[field].class).to eql(String) + expect(@test_result[field]).to eql("test") end if defined? Encoding @@ -485,36 +485,36 @@ it "should default to binary if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end it "should not use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result['binary_test'].encoding.should eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end else it "should default to utf-8 if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.find('utf-8')) + expect(result[field].encoding).to eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ascii")) client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.find('us-ascii')) + expect(result[field].encoding).to eql(Encoding.find('us-ascii')) end it "should use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.default_internal) + expect(result[field].encoding).to eql(Encoding.default_internal) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - result[field].encoding.should eql(Encoding.default_internal) + expect(result[field].encoding).to eql(Encoding.default_internal) end end end From acabc42ea43ce5d4679d80a69bdfa2b61127ba2c Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 10:11:16 -0700 Subject: [PATCH 280/783] RSpec 3 and disable monkey patching --- Gemfile | 2 +- spec/em/em_spec.rb | 2 +- spec/mysql2/client_spec.rb | 2 +- spec/mysql2/error_spec.rb | 2 +- spec/mysql2/result_spec.rb | 2 +- spec/mysql2/statement_spec.rb | 2 +- spec/spec_helper.rb | 2 ++ 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 8643d2d87..3ce666658 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ gem 'rake-compiler', '~> 0.9.5' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ - gem 'rspec', '~> 2.99' + gem 'rspec', '~> 3.2' end group :benchmarks do diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index fd799eb33..02fc1af85 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -4,7 +4,7 @@ require 'eventmachine' require 'mysql2/em' - describe Mysql2::EM::Client do + RSpec.describe Mysql2::EM::Client do it "should support async queries" do results = [] EM.run do diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 23d8c0c58..9c54f4f5a 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1,7 +1,7 @@ # encoding: UTF-8 require 'spec_helper' -describe Mysql2::Client do +RSpec.describe Mysql2::Client do context "using defaults file" do let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) } diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index a41397a53..2787809c9 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Mysql2::Error do +RSpec.describe Mysql2::Error do let(:client) { Mysql2::Client.new(DatabaseCredentials['root']) } let :error do diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 27975a611..52fc8a58a 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -1,7 +1,7 @@ # encoding: UTF-8 require 'spec_helper' -describe Mysql2::Result do +RSpec.describe Mysql2::Result do before(:each) do @result = @client.query "SELECT 1" end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index a7bd28218..3f72f38f1 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -1,7 +1,7 @@ # encoding: UTF-8 require './spec/spec_helper.rb' -describe Mysql2::Statement do +RSpec.describe Mysql2::Statement do before :each do @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4bcb702c0..d7835d586 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,8 @@ DatabaseCredentials = YAML.load_file('spec/configuration.yml') RSpec.configure do |config| + config.disable_monkey_patching! + def with_internal_encoding(encoding) old_enc = Encoding.default_internal Encoding.default_internal = encoding From 600eea5eb021fa3739beea9ffe459628f61c2017 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 10:38:33 -0700 Subject: [PATCH 281/783] Emit warnings in tests Also re-paste result_spec into statement_spec, it was stale. --- .rspec | 3 +- spec/mysql2/client_spec.rb | 14 +-- spec/mysql2/error_spec.rb | 5 +- spec/mysql2/statement_spec.rb | 171 +++++++++++++++++++++------------- spec/spec_helper.rb | 5 + 5 files changed, 125 insertions(+), 73 deletions(-) diff --git a/.rspec b/.rspec index 61354bc67..8911ddfaf 100644 --- a/.rspec +++ b/.rspec @@ -1,3 +1,4 @@ ---format documentation --colour --fail-fast +--format documentation +--warnings diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 9c54f4f5a..133cae2e7 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -193,13 +193,13 @@ def run_gc end it "should be able to connect to database with numeric-only name" do - expect { - creds = DatabaseCredentials['numericuser'] - @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`" - @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`" - client = Mysql2::Client.new creds - @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`" - }.not_to raise_error + creds = DatabaseCredentials['numericuser'] + @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`" + @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`" + + expect { Mysql2::Client.new(creds) }.not_to raise_error + + @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`" end it "should respond to #close" do diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 2787809c9..e726b4d55 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -26,7 +26,7 @@ expect(error).to respond_to(:error) end - if "".respond_to? :encoding + context 'encoding' do let :error do client = Mysql2::Client.new(DatabaseCredentials['root']) begin @@ -54,6 +54,7 @@ end it "returns error messages as UTF-8 by default" do + pending('String#encoding is not defined') unless String.public_method_defined?(:encoding) with_internal_encoding nil do expect(error.message.encoding).to eql(Encoding::UTF_8) error.message.valid_encoding? @@ -66,11 +67,13 @@ end it "returns sql state as ASCII" do + pending('String#encoding is not defined') unless String.public_method_defined?(:encoding) expect(error.sql_state.encoding).to eql(Encoding::US_ASCII) error.sql_state.valid_encoding? end it "returns error messages and sql state in Encoding.default_internal if set" do + pending('String#encoding is not defined') unless String.public_method_defined?(:encoding) with_internal_encoding 'UTF-16LE' do expect(error.message.encoding).to eql(Encoding.default_internal) error.message.valid_encoding? diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 3f72f38f1..0393285e9 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -244,11 +244,16 @@ expect(@test_result['null_test']).to eql(nil) end - it "should return Fixnum for a BIT value" do + it "should return String for a BIT(64) value" do expect(@test_result['bit_test'].class).to eql(String) expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end + it "should return String for a BIT(1) value" do + expect(@test_result['single_bit_test'].class).to eql(String) + expect(@test_result['single_bit_test']).to eql("\001") + end + it "should return Fixnum for a TINYINT value" do expect([Fixnum, Bignum]).to include(@test_result['tiny_int_test'].class) expect(@test_result['tiny_int_test']).to eql(1) @@ -272,6 +277,20 @@ @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" end + it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do + @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)' + id1 = @client.last_id + @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)' + id2 = @client.last_id + + result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", :cast_booleans => true + result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", :cast_booleans => true + expect(result1.first['single_bit_test']).to be true + expect(result2.first['single_bit_test']).to be false + + @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" + end + it "should return Fixnum for a SMALLINT value" do expect([Fixnum, Bignum]).to include(@test_result['small_int_test'].class) expect(@test_result['small_int_test']).to eql(10) @@ -309,7 +328,7 @@ it "should return Float for a DOUBLE value" do expect(@test_result['double_test'].class).to eql(Float) - expect(@test_result['double_test']).to be_within(1e-5).of(10.3) + expect(@test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do @@ -318,7 +337,7 @@ end if 1.size == 4 # 32bit - if RUBY_VERSION =~ /1.8/ + unless RUBY_VERSION =~ /1.8/ klass = Time else klass = DateTime @@ -384,26 +403,35 @@ expect(@test_result['enum_test']).to eql('val1') end + it "should raise an error given an invalid DATETIME" do + expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ + raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") + end + if defined? Encoding context "string encoding for ENUM values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ascii")) - client2.query "USE test" - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) + client2.close + end end it "should use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.default_internal) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.default_internal) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) + end end end end @@ -416,23 +444,27 @@ if defined? Encoding context "string encoding for SET values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ascii")) - client2.query "USE test" - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) + client2.close + end end it "should use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.default_internal) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.default_internal) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.default_internal) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.default_internal) + end end end end @@ -445,18 +477,22 @@ if defined? Encoding context "string encoding for BINARY values" do it "should default to binary if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end end it "should not use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end end end end @@ -483,43 +519,50 @@ context "string encoding for #{type} values" do if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) it "should default to binary if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end end it "should not use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end end else it "should default to utf-8 if Encoding.default_internal is nil" do - Encoding.default_internal = nil - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ascii")) - client2.query "USE test" - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('us-ascii')) + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.find('us-ascii')) + client2.close + end end it "should use Encoding.default_internal" do - Encoding.default_internal = Encoding.find('utf-8') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.default_internal) - Encoding.default_internal = Encoding.find('us-ascii') - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.default_internal) + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.default_internal) + end + + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.default_internal) + end end end end end end end - end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d7835d586..73c45819e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,11 +11,16 @@ def with_internal_encoding(encoding) old_enc = Encoding.default_internal + old_verbose = $VERBOSE + $VERBOSE = nil Encoding.default_internal = encoding + $VERBOSE = old_verbose yield ensure + $VERBOSE = nil Encoding.default_internal = old_enc + $VERBOSE = old_verbose end config.before :each do From ded3cc7ecb41c69818781791981f310c7943e419 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 10:41:11 -0700 Subject: [PATCH 282/783] Fix typo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c31eef246..2cd568bdf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: fi " - | - bash -c " # Install MySQL is OS=darwin + bash -c " # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew install mysql mysql.server start From 920f229f028082c591453c0dcddfca4fa9a04be5 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 10:41:21 -0700 Subject: [PATCH 283/783] DRY --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2cd568bdf..7cf3add09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: ruby bundler_args: --without benchmarks -script: - - bundle exec rake spec before_install: - gem --version - | From d78ed02ccfdd939eac7437be2b718af2cd5f2c38 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 10:46:31 -0700 Subject: [PATCH 284/783] Disallow all failures --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7cf3add09..69fdeddc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,11 +40,8 @@ rvm: - 2.1 - 2.2 - ree + - rbx-2 matrix: - allow_failures: - - env: DB=mysql57 - - rvm: rbx-2 - - os: osx include: - rvm: 2.0.0 env: DB=mariadb55 @@ -52,7 +49,5 @@ matrix: env: DB=mariadb10 - rvm: 2.0.0 env: DB=mysql57 - - rvm: rbx-2 - env: RBXOPT=-Xgc.honor_start=true - rvm: 2.0.0 os: osx From 7d7b6e8ea8d16c5715c96866449e972c5cce7a63 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 08:43:53 -0700 Subject: [PATCH 285/783] Update testing claims --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ed86dd3b..ab99cdc68 100644 --- a/README.md +++ b/README.md @@ -437,13 +437,13 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.x, 2.2.x (ongoing patch releases) + * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x * Ruby Enterprise Edition (based on MRI 1.8.7) * Rubinius 2.x This gem is tested with the following MySQL and MariaDB versions: - * MySQL 5.0, 5.1, 5.5, 5.6, 5.7 + * MySQL 5.5, 5.7 * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) * MariaDB 5.5, 10.0 From f9f2fb199c2bfabe19e0f8e8e8e08c0c2b76c2a4 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 12:32:24 -0700 Subject: [PATCH 286/783] Fix implicit conversion warnings --- ext/mysql2/client.c | 7 ++----- ext/mysql2/mysql_enc_name_to_ruby.h | 12 ++++++------ ext/mysql2/result.c | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 1a71cf633..483941aa3 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -322,8 +322,7 @@ static VALUE rb_mysql_info(VALUE self) { static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) { struct nogvl_connect_args args; - time_t start_time, end_time; - unsigned int elapsed_time, connect_timeout; + time_t start_time, end_time, elapsed_time, connect_timeout; VALUE rv; GET_CLIENT(self); @@ -1131,7 +1130,6 @@ static VALUE set_write_timeout(VALUE self, VALUE value) { static VALUE set_charset_name(VALUE self, VALUE value) { char *charset_name; #ifdef HAVE_RUBY_ENCODING_H - size_t charset_name_len; const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb; rb_encoding *enc; VALUE rb_enc; @@ -1141,8 +1139,7 @@ static VALUE set_charset_name(VALUE self, VALUE value) { charset_name = RSTRING_PTR(value); #ifdef HAVE_RUBY_ENCODING_H - charset_name_len = RSTRING_LEN(value); - mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, charset_name_len); + mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value)); if (mysql2rb == NULL || mysql2rb->rb_name == NULL) { VALUE inspect = rb_inspect(value); rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect)); diff --git a/ext/mysql2/mysql_enc_name_to_ruby.h b/ext/mysql2/mysql_enc_name_to_ruby.h index dfabeef1f..60e029baf 100644 --- a/ext/mysql2/mysql_enc_name_to_ruby.h +++ b/ext/mysql2/mysql_enc_name_to_ruby.h @@ -40,9 +40,9 @@ inline #endif #endif static unsigned int -mysql2_mysql_enc_name_to_rb_hash (str, len) +mysql2_mysql_enc_name_to_rb_hash(str, len) register const char *str; - register unsigned int len; + register const unsigned int len; { static const unsigned char asso_values[] = { @@ -83,9 +83,9 @@ __attribute__ ((__gnu_inline__)) #endif #endif const struct mysql2_mysql_enc_name_to_rb_map * -mysql2_mysql_enc_name_to_rb (str, len) +mysql2_mysql_enc_name_to_rb(str, len) register const char *str; - register unsigned int len; + register const unsigned int len; { enum { @@ -154,9 +154,9 @@ mysql2_mysql_enc_name_to_rb (str, len) if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { - register int key = mysql2_mysql_enc_name_to_rb_hash (str, len); + register const unsigned int key = mysql2_mysql_enc_name_to_rb_hash(str, len); - if (key <= MAX_HASH_VALUE && key >= 0) + if (key <= MAX_HASH_VALUE) { register const char *s = wordlist[key].name; diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 894e5f07f..f58ae2c6a 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -220,7 +220,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e */ static unsigned int msec_char_to_uint(char *msec_char, size_t len) { - int i; + size_t i; for (i = 0; i < (len - 1); i++) { if (msec_char[i] == '\0') { msec_char[i] = '0'; From 66323797f8e9a6e261d2b9dfb544f0be9585e7a5 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 14:02:00 -0700 Subject: [PATCH 287/783] Travis doesn't need pry --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 69fdeddc0..53508c0f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: ruby -bundler_args: --without benchmarks +bundler_args: --without benchmarks development before_install: - gem --version - | From ae4a5913a10475e4d92a480755d177955bb0c238 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 14:14:17 -0700 Subject: [PATCH 288/783] Don't fail fast This is super annoying for looking at Travis build results --- .rspec | 1 - 1 file changed, 1 deletion(-) diff --git a/.rspec b/.rspec index 8911ddfaf..96424d24e 100644 --- a/.rspec +++ b/.rspec @@ -1,4 +1,3 @@ --colour ---fail-fast --format documentation --warnings From a94eac1a3822cb118187ba60d2e111c0a1777b93 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 14:33:24 -0700 Subject: [PATCH 289/783] There _is_ a good way to do this in 1.9 --- lib/mysql2/error.rb | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 748abf022..1bcbb8d23 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -2,8 +2,11 @@ module Mysql2 class Error < StandardError - REPLACEMENT_CHAR = '?' - ENCODE_OPTS = {:undef => :replace, :invalid => :replace, :replace => REPLACEMENT_CHAR} + ENCODE_OPTS = { + :undef => :replace, + :invalid => :replace, + :replace => '?'.freeze, + }.freeze attr_accessor :error_number attr_reader :sql_state @@ -20,7 +23,7 @@ def initialize(msg, server_version=nil) end def sql_state=(state) - @sql_state = ''.respond_to?(:encode) ? state.encode(ENCODE_OPTS) : state + @sql_state = state.respond_to?(:encode) ? state.encode(ENCODE_OPTS) : state end private @@ -53,27 +56,12 @@ def sql_state=(state) # # Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8 def clean_message(message) - return message if !message.respond_to?(:encoding) + return message unless message.respond_to?(:encode) if @server_version && @server_version > 50500 message.encode(ENCODE_OPTS) else - if message.respond_to? :scrub - message.scrub(REPLACEMENT_CHAR).encode(ENCODE_OPTS) - else - # This is ugly as hell but Ruby 1.9 doesn't provide a way to clean a string - # and retain it's valid UTF-8 characters, that I know of. - - new_message = "".force_encoding(Encoding::UTF_8) - message.chars.each do |char| - if char.valid_encoding? - new_message << char - else - new_message << REPLACEMENT_CHAR - end - end - new_message.encode(ENCODE_OPTS) - end + message.encode(Encoding::UTF_8, ENCODE_OPTS) end end end From c7725e62ff3df15ea4db4655a0e0c6806ec262ca Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 16:13:51 -0700 Subject: [PATCH 290/783] `pending`, not `skip` --- spec/mysql2/client_spec.rb | 130 ++++++++++++----------- spec/mysql2/result_spec.rb | 188 +++++++++++++++++----------------- spec/mysql2/statement_spec.rb | 188 +++++++++++++++++----------------- 3 files changed, 252 insertions(+), 254 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 133cae2e7..07ce64278 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -27,22 +27,20 @@ }.to raise_error(Mysql2::Error) end - if defined? Encoding - it "should raise an exception on create for invalid encodings" do - expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake")) - }.to raise_error(Mysql2::Error) - end + it "should raise an exception on create for invalid encodings" do + expect { + Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake")) + }.to raise_error(Mysql2::Error) + end - it "should not raise an exception on create for a valid encoding" do - expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) - }.not_to raise_error + it "should not raise an exception on create for a valid encoding" do + expect { + Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) + }.not_to raise_error - expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) - }.not_to raise_error - end + expect { + Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) + }.not_to raise_error end it "should accept connect flags and pass them to #connect" do @@ -119,9 +117,9 @@ def connect *args it "should be able to connect via SSL options" do ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'" ssl_uncompiled = ssl.any? {|x| x['Value'] == 'OFF'} - skip("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled + pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled ssl_disabled = ssl.any? {|x| x['Value'] == 'DISABLED'} - skip("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled + pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled # You may need to adjust the lines below to match your SSL certificate paths ssl_client = nil @@ -150,13 +148,15 @@ def connect *args end def run_gc - GC.start - sleep(1) if defined?(Rubinius) # Let the Rubinius GC thread do its work + if defined?(Rubinius) + GC.run(true) + else + GC.start + end + sleep(0.5) end it "should not leave dangling connections after garbage collection" do - skip('Rubinius misbehaves') if defined?(Rubinius) - run_gc client = Mysql2::Client.new(DatabaseCredentials['root']) @@ -173,23 +173,22 @@ def run_gc expect(final_count).to eq(before_count) end - if Process.respond_to?(:fork) - it "should not close connections when running in a child process" do - run_gc - client = Mysql2::Client.new(DatabaseCredentials['root']) - fork do - client.query('SELECT 1') - client = nil - run_gc - end - - Process.wait + it "should not close connections when running in a child process" do + run_gc + client = Mysql2::Client.new(DatabaseCredentials['root']) - # this will throw an error if the underlying socket was shutdown by the - # child's GC - expect { client.query('SELECT 1') }.to_not raise_exception + fork do + client.query('SELECT 1') + client = nil + run_gc end + + Process.wait + + # this will throw an error if the underlying socket was shutdown by the + # child's GC + expect { client.query('SELECT 1') }.to_not raise_exception end it "should be able to connect to database with numeric-only name" do @@ -272,7 +271,7 @@ def run_gc @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true) local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'" local_enabled = local.any? {|x| x['Value'] == 'ON'} - skip("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled + pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled @client_i.query %[ CREATE TABLE IF NOT EXISTS infileTest ( @@ -418,7 +417,6 @@ def run_gc # XXX this test is not deterministic (because Unix signal handling is not) # and may fail on a loaded system it "should run signal handlers while waiting for a response" do - skip('Rubinius misbehaves') if defined?(Rubinius) mark = {} trap(:USR1) { mark[:USR1] = Time.now } begin @@ -703,25 +701,25 @@ def run_gc expect(info[:version].class).to eql(String) end - if defined? Encoding - context "strings returned by #info" do - it "should default to the connection's encoding if Encoding.default_internal is nil" do - with_internal_encoding nil do - expect(@client.info[:version].encoding).to eql(Encoding.find('utf-8')) + context "strings returned by #info" do + before { pending('Encoding is undefined') unless defined?(Encoding) } - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - expect(client2.info[:version].encoding).to eql(Encoding.find('us-ascii')) - end + it "should default to the connection's encoding if Encoding.default_internal is nil" do + with_internal_encoding nil do + expect(@client.info[:version].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + expect(client2.info[:version].encoding).to eql(Encoding.find('us-ascii')) end + end - it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - expect(@client.info[:version].encoding).to eql(Encoding.default_internal) - end + it "should use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + expect(@client.info[:version].encoding).to eql(Encoding.default_internal) + end - with_internal_encoding 'us-ascii' do - expect(@client.info[:version].encoding).to eql(Encoding.default_internal) - end + with_internal_encoding 'us-ascii' do + expect(@client.info[:version].encoding).to eql(Encoding.default_internal) end end end @@ -746,25 +744,25 @@ def run_gc }.to raise_error(Mysql2::Error) end - if defined? Encoding - context "strings returned by #server_info" do - it "should default to the connection's encoding if Encoding.default_internal is nil" do - with_internal_encoding nil do - expect(@client.server_info[:version].encoding).to eql(Encoding.find('utf-8')) + context "strings returned by #server_info" do + before { pending('Encoding is undefined') unless defined?(Encoding) } - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - expect(client2.server_info[:version].encoding).to eql(Encoding.find('us-ascii')) - end + it "should default to the connection's encoding if Encoding.default_internal is nil" do + with_internal_encoding nil do + expect(@client.server_info[:version].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + expect(client2.server_info[:version].encoding).to eql(Encoding.find('us-ascii')) end + end - it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) - end + it "should use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) + end - with_internal_encoding 'us-ascii' do - expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) - end + with_internal_encoding 'us-ascii' do + expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) end end end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 52fc8a58a..7a33a5b00 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -342,30 +342,30 @@ raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") end - if defined? Encoding - context "string encoding for ENUM values" do - it "should default to the connection's encoding if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) - client2.close - end + context "string encoding for ENUM values" do + before { pending('Encoding is undefined') unless defined?(Encoding) } + + it "should default to the connection's encoding if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) + client2.close end + end - it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.default_internal) - end + it "should use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.default_internal) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end end end @@ -375,30 +375,30 @@ expect(@test_result['set_test']).to eql('val1,val2') end - if defined? Encoding - context "string encoding for SET values" do - it "should default to the connection's encoding if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) + context "string encoding for SET values" do + before { pending('Encoding is undefined') unless defined?(Encoding) } - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) - client2.close - end + it "should default to the connection's encoding if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) + client2.close end + end - it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.default_internal) - end + it "should use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.default_internal) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.default_internal) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.default_internal) end end end @@ -408,25 +408,25 @@ expect(@test_result['binary_test']).to eql("test#{"\000"*6}") end - if defined? Encoding - context "string encoding for BINARY values" do - it "should default to binary if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + context "string encoding for BINARY values" do + before { pending('Encoding is undefined') unless defined?(Encoding) } + + it "should default to binary if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end + end - it "should not use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + it "should not use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end end end @@ -449,50 +449,50 @@ expect(@test_result[field]).to eql("test") end - if defined? Encoding - context "string encoding for #{type} values" do - if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) - it "should default to binary if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + context "string encoding for #{type} values" do + before { pending('Encoding is undefined') unless defined?(Encoding) } + + if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) + it "should default to binary if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end + end - it "should not use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + it "should not use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end - else - it "should default to utf-8 if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('us-ascii')) - client2.close - end + end + else + it "should default to utf-8 if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.find('us-ascii')) + client2.close end + end - it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.default_internal) - end + it "should use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.default_internal) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.default_internal) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.default_internal) end end end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 0393285e9..dcdb36fef 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -408,30 +408,30 @@ raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") end - if defined? Encoding - context "string encoding for ENUM values" do - it "should default to the connection's encoding if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) - client2.close - end + context "string encoding for ENUM values" do + before { pending('Encoding is undefined') unless defined?(Encoding) } + + it "should default to the connection's encoding if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) + client2.close end + end - it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.default_internal) - end + it "should use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.default_internal) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end end end @@ -441,30 +441,30 @@ expect(@test_result['set_test']).to eql('val1,val2') end - if defined? Encoding - context "string encoding for SET values" do - it "should default to the connection's encoding if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) + context "string encoding for SET values" do + before { pending('Encoding is undefined') unless defined?(Encoding) } - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) - client2.close - end + it "should default to the connection's encoding if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) + client2.close end + end - it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.default_internal) - end + it "should use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.default_internal) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.default_internal) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['set_test'].encoding).to eql(Encoding.default_internal) end end end @@ -474,25 +474,25 @@ expect(@test_result['binary_test']).to eql("test#{"\000"*6}") end - if defined? Encoding - context "string encoding for BINARY values" do - it "should default to binary if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + context "string encoding for BINARY values" do + before { pending('Encoding is undefined') unless defined?(Encoding) } + + it "should default to binary if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end + end - it "should not use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + it "should not use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end end end @@ -515,50 +515,50 @@ expect(@test_result[field]).to eql("test") end - if defined? Encoding - context "string encoding for #{type} values" do - if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) - it "should default to binary if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + context "string encoding for #{type} values" do + before { pending('Encoding is undefined') unless defined?(Encoding) } + + if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) + it "should default to binary if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end + end - it "should not use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + it "should not use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) end - else - it "should default to utf-8 if Encoding.default_internal is nil" do - with_internal_encoding nil do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('utf-8')) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('us-ascii')) - client2.close - end + end + else + it "should default to utf-8 if Encoding.default_internal is nil" do + with_internal_encoding nil do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.find('utf-8')) + + client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.find('us-ascii')) + client2.close end + end - it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.default_internal) - end + it "should use Encoding.default_internal" do + with_internal_encoding 'utf-8' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.default_internal) + end - with_internal_encoding 'us-ascii' do - result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.default_internal) - end + with_internal_encoding 'us-ascii' do + result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first + expect(result[field].encoding).to eql(Encoding.default_internal) end end end From 7c084cc0c8190e963beebcbd2f5dd719768d6140 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 16:14:23 -0700 Subject: [PATCH 291/783] Clean up Travis config --- .travis.yml | 27 +-------------------------- .travis_mariadb.sh | 5 +++-- .travis_mysql57.sh | 12 ++++-------- .travis_setup.sh | 25 +++++++++++++++++++++++++ .travis_ssl.sh | 5 ++--- 5 files changed, 35 insertions(+), 39 deletions(-) create mode 100644 .travis_setup.sh diff --git a/.travis.yml b/.travis.yml index 53508c0f7..b3977a5d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,32 +2,7 @@ language: ruby bundler_args: --without benchmarks development before_install: - gem --version - - | - bash -c " # Install MySQL 5.7 if DB=mysql57 - if [[ x$DB =~ mysql57 ]]; then - sudo bash .travis_mysql57.sh - fi - " - - | - bash -c " # Install MariaDB if DB=mariadb - if [[ x$DB =~ xmariadb ]]; then - sudo bash .travis_mariadb.sh '$DB' - fi - " - - | - bash -c " # Install MySQL if OS=darwin - if [[ x$OSTYPE =~ ^xdarwin ]]; then - brew install mysql - mysql.server start - fi - " - - | - bash -c " # Configure SSL support - if [[ ! x$OSTYPE =~ ^xdarwin ]]; then - sudo bash .travis_ssl.sh - sudo service mysql restart - fi - " + - bash .travis_setup.sh - mysqld --version - mysql -u root -e "CREATE DATABASE IF NOT EXISTS test" - mysql -u root -e "CREATE USER '$USER'@'localhost'" || true diff --git a/.travis_mariadb.sh b/.travis_mariadb.sh index 652804382..d69473bb7 100644 --- a/.travis_mariadb.sh +++ b/.travis_mariadb.sh @@ -1,8 +1,9 @@ -#!/bin/sh +#!/usr/bin/env bash + +set -eu service mysql stop apt-get purge '^mysql*' 'libmysql*' -apt-get install python-software-properties apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db if [[ x$1 = xmariadb55 ]]; then diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index a9f87dda8..45a364bd5 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -1,15 +1,11 @@ -#!/bin/sh +#!/usr/bin/env bash -service mysql stop +set -eu +service mysql stop apt-get purge '^mysql*' 'libmysql*' -apt-get autoclean - -rm -rf /var/lib/mysql -rm -rf /var/log/mysql - -apt-get install python-software-properties apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x8C718D3B5072E1F5 + add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.7-dmr' apt-get update diff --git a/.travis_setup.sh b/.travis_setup.sh new file mode 100644 index 000000000..bb48ac1ad --- /dev/null +++ b/.travis_setup.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -eu + +# Install MySQL 5.7 if DB=mysql57 +if [[ -n ${DB-} && x$DB =~ mysql57 ]]; then + sudo bash .travis_mysql57.sh +fi + +# Install MariaDB if DB=mariadb +if [[ -n ${DB-} && x$DB =~ xmariadb ]]; then + sudo bash .travis_mariadb.sh "$DB" +fi + +# Install MySQL if OS=darwin +if [[ x$OSTYPE =~ ^xdarwin ]]; then + brew install mysql + mysql.server start +fi + +# TODO: get SSL working on OS X in Travis +if ! [[ x$OSTYPE =~ ^xdarwin ]]; then + sudo bash .travis_ssl.sh + sudo service mysql restart +fi diff --git a/.travis_ssl.sh b/.travis_ssl.sh index 4a3f52bb2..a2fc029a9 100644 --- a/.travis_ssl.sh +++ b/.travis_ssl.sh @@ -1,7 +1,6 @@ -#!/bin/sh +#!/usr/bin/env bash -# Halt the tests on error -set -e +set -eu # Wherever MySQL configs live, go there (this is for cross-platform) cd $(my_print_defaults --help | grep my.cnf | xargs find 2>/dev/null | xargs dirname) From f59afea65fc6397c8cc2b4f72778e0ecaf1c07e7 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 16:51:28 -0700 Subject: [PATCH 292/783] Signal handling spec is more robust Closes #611. --- spec/mysql2/client_spec.rb | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 07ce64278..7b00f4157 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -417,28 +417,31 @@ def run_gc # XXX this test is not deterministic (because Unix signal handling is not) # and may fail on a loaded system it "should run signal handlers while waiting for a response" do + kill_time = 0.1 + query_time = 2 * kill_time + mark = {} - trap(:USR1) { mark[:USR1] = Time.now } + begin - mark[:START] = Time.now + trap(:USR1) { mark.store(:USR1, Time.now) } pid = fork do - sleep 0.1 # wait for client query to start + sleep kill_time # wait for client query to start Process.kill(:USR1, Process.ppid) sleep # wait for explicit kill to prevent GC disconnect end - @client.query('SELECT SLEEP(0.2)') - mark[:END] = Time.now - expect(mark.include?(:USR1)).to be true - expect(mark[:USR1] - mark[:START]).to be >= 0.1 - expect(mark[:USR1] - mark[:START]).to be < 0.13 - expect(mark[:END] - mark[:USR1]).to be > 0.09 - expect(mark[:END] - mark[:START]).to be >= 0.2 - expect(mark[:END] - mark[:START]).to be < 0.23 + mark.store(:QUERY_START, Time.now) + @client.query("SELECT SLEEP(#{query_time})") + mark.store(:QUERY_END, Time.now) + ensure Process.kill(:TERM, pid) Process.waitpid2(pid) - ensure trap(:USR1, 'DEFAULT') end + + # the query ran uninterrupted + expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.02).of(query_time) + # signals fired while the query was running + expect(mark.fetch(:USR1)).to be_between(mark.fetch(:QUERY_START), mark.fetch(:QUERY_END)) end it "#socket should return a Fixnum (file descriptor from C)" do From 9478363193f994459110d0df4f56e584e68c0d4a Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 16 Apr 2015 10:36:26 -0700 Subject: [PATCH 293/783] MySQL 5.7.7 only talks to the real root --- .travis.yml | 3 +-- .travis_setup.sh | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b3977a5d2..51d1607ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,7 @@ before_install: - gem --version - bash .travis_setup.sh - mysqld --version - - mysql -u root -e "CREATE DATABASE IF NOT EXISTS test" - - mysql -u root -e "CREATE USER '$USER'@'localhost'" || true + - mysql -u $USER -e "CREATE DATABASE IF NOT EXISTS test" os: - linux rvm: diff --git a/.travis_setup.sh b/.travis_setup.sh index bb48ac1ad..68323dc90 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -23,3 +23,5 @@ if ! [[ x$OSTYPE =~ ^xdarwin ]]; then sudo bash .travis_ssl.sh sudo service mysql restart fi + +sudo mysql -e "CREATE USER '$USER'@'localhost'" || true From 85184794315cc52106b109e9c33b3a69d435ac30 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 17 Apr 2015 10:41:43 -0700 Subject: [PATCH 294/783] DRY and add sanity checks --- spec/mysql2/error_spec.rb | 40 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index e726b4d55..dcfba9760 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -5,13 +5,11 @@ RSpec.describe Mysql2::Error do let(:client) { Mysql2::Client.new(DatabaseCredentials['root']) } - let :error do + let(:error) do begin client.query("HAHAHA") rescue Mysql2::Error => e error = e - ensure - client.close end error @@ -27,34 +25,36 @@ end context 'encoding' do - let :error do - client = Mysql2::Client.new(DatabaseCredentials['root']) + let(:valid_utf8) { '造字' } + let(:error) do begin - client.query("\xE9\x80\xA0\xE5\xAD\x97") + client.query(valid_utf8) rescue Mysql2::Error => e - error = e - ensure - client.close + e end - - error end - let :bad_err do - client = Mysql2::Client.new(DatabaseCredentials['root']) + let(:invalid_utf8) { "\xE5\xC6\x7D\x1F" } + let(:bad_err) do begin - client.query("\xE5\xC6\x7D\x1F") + client.query(invalid_utf8) rescue Mysql2::Error => e - error = e - ensure - client.close + e end + end + + before do + pending('String#encoding is not defined') unless String.public_method_defined?(:encoding) - error + # sanity check + expect(valid_utf8.encoding).to eql(Encoding::UTF_8) + expect(valid_utf8).to be_valid_encoding + + expect(invalid_utf8.encoding).to eql(Encoding::UTF_8) + expect(invalid_utf8).to_not be_valid_encoding end it "returns error messages as UTF-8 by default" do - pending('String#encoding is not defined') unless String.public_method_defined?(:encoding) with_internal_encoding nil do expect(error.message.encoding).to eql(Encoding::UTF_8) error.message.valid_encoding? @@ -67,13 +67,11 @@ end it "returns sql state as ASCII" do - pending('String#encoding is not defined') unless String.public_method_defined?(:encoding) expect(error.sql_state.encoding).to eql(Encoding::US_ASCII) error.sql_state.valid_encoding? end it "returns error messages and sql state in Encoding.default_internal if set" do - pending('String#encoding is not defined') unless String.public_method_defined?(:encoding) with_internal_encoding 'UTF-16LE' do expect(error.message.encoding).to eql(Encoding.default_internal) error.message.valid_encoding? From 3c611f99dafafa8ffe38ea8eb65010c81f5cdab0 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 17 Apr 2015 10:41:56 -0700 Subject: [PATCH 295/783] Actually assert on the return value --- spec/mysql2/error_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index dcfba9760..03832a531 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -57,10 +57,10 @@ it "returns error messages as UTF-8 by default" do with_internal_encoding nil do expect(error.message.encoding).to eql(Encoding::UTF_8) - error.message.valid_encoding? + expect(error.message).to be_valid_encoding expect(bad_err.message.encoding).to eql(Encoding::UTF_8) - bad_err.message.valid_encoding? + expect(bad_err.message).to be_valid_encoding expect(bad_err.message).to include("??}\u001F") end @@ -68,16 +68,16 @@ it "returns sql state as ASCII" do expect(error.sql_state.encoding).to eql(Encoding::US_ASCII) - error.sql_state.valid_encoding? + expect(error.sql_state).to be_valid_encoding end it "returns error messages and sql state in Encoding.default_internal if set" do with_internal_encoding 'UTF-16LE' do expect(error.message.encoding).to eql(Encoding.default_internal) - error.message.valid_encoding? + expect(error.message).to be_valid_encoding expect(bad_err.message.encoding).to eql(Encoding.default_internal) - bad_err.message.valid_encoding? + expect(bad_err.message).to be_valid_encoding end end end From 46a818788a6681b9e774422e8907d9e7576b39a8 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 17 Apr 2015 11:11:27 -0700 Subject: [PATCH 296/783] Use constants --- spec/mysql2/client_spec.rb | 16 ++++++------- spec/mysql2/error_spec.rb | 2 +- spec/mysql2/result_spec.rb | 44 +++++++++++++++++------------------ spec/mysql2/statement_spec.rb | 44 +++++++++++++++++------------------ 4 files changed, 53 insertions(+), 53 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 7b00f4157..81a24fe23 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -709,19 +709,19 @@ def run_gc it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do - expect(@client.info[:version].encoding).to eql(Encoding.find('utf-8')) + expect(@client.info[:version].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - expect(client2.info[:version].encoding).to eql(Encoding.find('us-ascii')) + expect(client2.info[:version].encoding).to eql(Encoding::ASCII) end end it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do expect(@client.info[:version].encoding).to eql(Encoding.default_internal) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do expect(@client.info[:version].encoding).to eql(Encoding.default_internal) end end @@ -752,19 +752,19 @@ def run_gc it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do - expect(@client.server_info[:version].encoding).to eql(Encoding.find('utf-8')) + expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - expect(client2.server_info[:version].encoding).to eql(Encoding.find('us-ascii')) + expect(client2.server_info[:version].encoding).to eql(Encoding::ASCII) end end it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) end end diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 03832a531..ea4af54f7 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -72,7 +72,7 @@ end it "returns error messages and sql state in Encoding.default_internal if set" do - with_internal_encoding 'UTF-16LE' do + with_internal_encoding Encoding::UTF_16LE do expect(error.message.encoding).to eql(Encoding.default_internal) expect(error.message).to be_valid_encoding diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 7a33a5b00..16278bca6 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -348,22 +348,22 @@ it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) + expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) + expect(result['enum_test'].encoding).to eql(Encoding::ASCII) client2.close end end it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end @@ -381,22 +381,22 @@ it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) + expect(result['set_test'].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) + expect(result['set_test'].encoding).to eql(Encoding::ASCII) client2.close end end it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end @@ -414,19 +414,19 @@ it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end end @@ -456,41 +456,41 @@ it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end else it "should default to utf-8 if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('utf-8')) + expect(result[field].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('us-ascii')) + expect(result[field].encoding).to eql(Encoding::ASCII) client2.close end end it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index dcdb36fef..f4a764246 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -414,22 +414,22 @@ it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('utf-8')) + expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['enum_test'].encoding).to eql(Encoding.find('us-ascii')) + expect(result['enum_test'].encoding).to eql(Encoding::US_ASCII) client2.close end end it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end @@ -447,22 +447,22 @@ it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('utf-8')) + expect(result['set_test'].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['set_test'].encoding).to eql(Encoding.find('us-ascii')) + expect(result['set_test'].encoding).to eql(Encoding::US_ASCII) client2.close end end it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end @@ -480,19 +480,19 @@ it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end end @@ -522,41 +522,41 @@ it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result['binary_test'].encoding).to eql(Encoding.find('binary')) + expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end else it "should default to utf-8 if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('utf-8')) + expect(result[field].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - expect(result[field].encoding).to eql(Encoding.find('us-ascii')) + expect(result[field].encoding).to eql(Encoding::US_ASCII) client2.close end end it "should use Encoding.default_internal" do - with_internal_encoding 'utf-8' do + with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end - with_internal_encoding 'us-ascii' do + with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end From d5c3f662878657164272f63ec656ed1101c1e796 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 18 Apr 2015 11:22:14 -0700 Subject: [PATCH 297/783] Crank up the compiler warnings `-Weverything` doesn't usually get used because `ruby.h` doesn't compile with it as of 2.2.2. --- ext/mysql2/extconf.rb | 20 +++++++++++++++----- ext/mysql2/result.c | 5 ++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index f5c9aecfc..d4fda2ff7 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -98,11 +98,21 @@ def asplode lib asplode h unless have_header h end -# These gcc style flags are also supported by clang and xcode compilers, -# so we'll use a does-it-work test instead of an is-it-gcc test. -gcc_flags = ' -Wall -funroll-loops' -if try_link('int main() {return 0;}', gcc_flags) - $CFLAGS << gcc_flags +# This is our wishlist. We use whichever flags work on the host. +# -Wall and -Wextra are included by default. +%w( + -Werror + -Weverything + -fsanitize=address + -fsanitize=integer + -fsanitize=thread + -fsanitize=memory + -fsanitize=undefined + -fsanitize=cfi +).each do |flag| + if try_link('int main() {return 0;}', flag) + $CFLAGS << ' ' << flag + end end if RUBY_PLATFORM =~ /mswin|mingw/ diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index f58ae2c6a..04e7d284e 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -59,7 +59,7 @@ typedef struct { int streaming; ID db_timezone; ID app_timezone; - int block_given; + VALUE block_given; } result_each_args; VALUE cBigDecimal, cDateTime, cDate; @@ -346,8 +346,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } { - int r = (int)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt, RUBY_UBF_IO, 0); - switch(r) { + switch((uintptr_t)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt, RUBY_UBF_IO, 0)) { case 0: /* success */ break; From af040c98d7fe00ac8a5cebfba00c1f4343e34fbb Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 1 Jun 2015 19:12:50 -0400 Subject: [PATCH 298/783] Reindent --- ext/mysql2/extconf.rb | 172 +++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index d4fda2ff7..f5554b28c 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -76,107 +76,107 @@ def asplode lib libs = ['m', 'z', 'socket', 'nsl', 'mygcc'] found = false while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do - exit 1 if libs.empty? - found ||= have_library(libs.shift) - end - - asplode("mysql client") unless found + exit 1 if libs.empty? + found ||= have_library(libs.shift) + end - rpath_dir = lib -end + asplode("mysql client") unless found -if have_header('mysql.h') - prefix = nil -elsif have_header('mysql/mysql.h') - prefix = 'mysql' -else - asplode 'mysql.h' -end + rpath_dir = lib + end -%w{ errmsg.h mysqld_error.h }.each do |h| - header = [prefix, h].compact.join '/' - asplode h unless have_header h -end + if have_header('mysql.h') + prefix = nil + elsif have_header('mysql/mysql.h') + prefix = 'mysql' + else + asplode 'mysql.h' + end -# This is our wishlist. We use whichever flags work on the host. -# -Wall and -Wextra are included by default. -%w( - -Werror - -Weverything - -fsanitize=address - -fsanitize=integer - -fsanitize=thread - -fsanitize=memory - -fsanitize=undefined - -fsanitize=cfi -).each do |flag| - if try_link('int main() {return 0;}', flag) - $CFLAGS << ' ' << flag + %w{ errmsg.h mysqld_error.h }.each do |h| + header = [prefix, h].compact.join '/' + asplode h unless have_header h end -end -if RUBY_PLATFORM =~ /mswin|mingw/ - # Build libmysql.a interface link library - require 'rake' - - # Build libmysql.a interface link library - # Use rake to rebuild only if these files change - deffile = File.expand_path('../../../support/libmysql.def', __FILE__) - libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) - file 'libmysql.a' => [deffile, libfile] do |t| - when_writing 'building libmysql.a' do - # Ruby kindly shows us where dllwrap is, but that tool does more than we want. - # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. - dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool') - sh dlltool, '--kill-at', - '--dllname', 'libmysql.dll', - '--output-lib', 'libmysql.a', - '--input-def', deffile, libfile + # This is our wishlist. We use whichever flags work on the host. + # -Wall and -Wextra are included by default. + %w( + -Werror + -Weverything + -fsanitize=address + -fsanitize=integer + -fsanitize=thread + -fsanitize=memory + -fsanitize=undefined + -fsanitize=cfi + ).each do |flag| + if try_link('int main() {return 0;}', flag) + $CFLAGS << ' ' << flag end end - Rake::Task['libmysql.a'].invoke - $LOCAL_LIBS << ' ' << 'libmysql.a' + if RUBY_PLATFORM =~ /mswin|mingw/ + # Build libmysql.a interface link library + require 'rake' + + # Build libmysql.a interface link library + # Use rake to rebuild only if these files change + deffile = File.expand_path('../../../support/libmysql.def', __FILE__) + libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) + file 'libmysql.a' => [deffile, libfile] do |t| + when_writing 'building libmysql.a' do + # Ruby kindly shows us where dllwrap is, but that tool does more than we want. + # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. + dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool') + sh dlltool, '--kill-at', + '--dllname', 'libmysql.dll', + '--output-lib', 'libmysql.a', + '--input-def', deffile, libfile + end + end + + Rake::Task['libmysql.a'].invoke + $LOCAL_LIBS << ' ' << 'libmysql.a' - # Make sure the generated interface library works (if cross-compiling, trust without verifying) - unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ - abort "-----\nCannot find libmysql.a\n----" unless have_library('libmysql') - abort "-----\nCannot link to libmysql.a (my_init)\n----" unless have_func('my_init') - end + # Make sure the generated interface library works (if cross-compiling, trust without verifying) + unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + abort "-----\nCannot find libmysql.a\n----" unless have_library('libmysql') + abort "-----\nCannot link to libmysql.a (my_init)\n----" unless have_func('my_init') + end - # Vendor libmysql.dll - vendordir = File.expand_path('../../../vendor/', __FILE__) - directory vendordir + # Vendor libmysql.dll + vendordir = File.expand_path('../../../vendor/', __FILE__) + directory vendordir - vendordll = File.join(vendordir, 'libmysql.dll') - dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) - file vendordll => [dllfile, vendordir] do |t| - when_writing 'copying libmysql.dll' do - cp dllfile, vendordll + vendordll = File.join(vendordir, 'libmysql.dll') + dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) + file vendordll => [dllfile, vendordir] do |t| + when_writing 'copying libmysql.dll' do + cp dllfile, vendordll + end end - end - # Copy libmysql.dll to the local vendor directory by default - if arg_config('--no-vendor-libmysql') - # Fine, don't. - puts "--no-vendor-libmysql" - else # Default: arg_config('--vendor-libmysql') - # Let's do it! - Rake::Task[vendordll].invoke - end -else - case explicit_rpath = with_config('mysql-rpath') - when true - abort "-----\nOption --with-mysql-rpath must have an argument\n-----" - when false - warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" - when String - # The user gave us a value so use it - rpath_flags = " -Wl,-rpath,#{explicit_rpath}" - warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" - $LDFLAGS << rpath_flags + # Copy libmysql.dll to the local vendor directory by default + if arg_config('--no-vendor-libmysql') + # Fine, don't. + puts "--no-vendor-libmysql" + else # Default: arg_config('--vendor-libmysql') + # Let's do it! + Rake::Task[vendordll].invoke + end else - if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] + case explicit_rpath = with_config('mysql-rpath') + when true + abort "-----\nOption --with-mysql-rpath must have an argument\n-----" + when false + warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" + when String + # The user gave us a value so use it + rpath_flags = " -Wl,-rpath,#{explicit_rpath}" + warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" + $LDFLAGS << rpath_flags + else + if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] rpath_flags = " -Wl,-rpath,#{libdir}" if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. From cc3797e789228a7f988e55690fbc0c0cdc068d63 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 1 Jun 2015 19:13:01 -0400 Subject: [PATCH 299/783] Work around statement.c --- ext/mysql2/extconf.rb | 2 ++ ext/mysql2/result.c | 3 +-- ext/mysql2/statement.c | 25 +++++++++---------------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index f5554b28c..71058a798 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -100,6 +100,7 @@ def asplode lib # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. + # TODO: fix statement.c and remove -Wno-error=declaration-after-statement %w( -Werror -Weverything @@ -109,6 +110,7 @@ def asplode lib -fsanitize=memory -fsanitize=undefined -fsanitize=cfi + -Wno-error=declaration-after-statement ).each do |flag| if try_link('int main() {return 0;}', flag) $CFLAGS << ' ' << flag diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 04e7d284e..0d254623e 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -848,7 +848,7 @@ static VALUE rb_mysql_result_each_(VALUE self, static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; - VALUE defaults, opts, block; + VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); ID db_timezone, app_timezone, dbTz, appTz; mysql2_result_wrapper * wrapper; int symbolizeKeys, asArray, castBool, cacheRows, cast; @@ -921,7 +921,6 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { args.app_timezone = app_timezone; args.block_given = block; - VALUE (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); if (wrapper->stmt) { fetch_row_func = rb_mysql_result_fetch_row_stmt; } else { diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 794e483b4..571a4c722 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -31,6 +31,7 @@ VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt ) { VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt)); VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt)); + VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg); #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc = rb_default_internal_encoding(); @@ -41,8 +42,6 @@ VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); } #endif - - VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg); rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_stmt_errno(stmt))); rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state); rb_exc_raise(e); @@ -209,19 +208,15 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { long i; MYSQL_STMT *stmt; MYSQL_RES *metadata; + VALUE current; VALUE resultObj; VALUE *params_enc = alloca(sizeof(VALUE) * argc); unsigned long* length_buffers = NULL; int is_streaming = 0; -#ifdef HAVE_RUBY_ENCODING_H - rb_encoding *conn_enc; -#endif GET_STATEMENT(self); + GET_CLIENT(stmt_wrapper->client); #ifdef HAVE_RUBY_ENCODING_H - { - GET_CLIENT(stmt_wrapper->client); - conn_enc = rb_to_encoding(wrapper->encoding); - } + rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); #endif { VALUE valStreaming = rb_hash_aref(rb_iv_get(stmt_wrapper->client, "@query_options"), sym_stream); @@ -287,11 +282,12 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { default: // TODO: what Ruby type should support MYSQL_TYPE_TIME if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) { + MYSQL_TIME t; + VALUE rb_time = argv[i]; + bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME; bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); - MYSQL_TIME t; - VALUE rb_time = argv[i]; memset(&t, 0, sizeof(MYSQL_TIME)); t.neg = 0; t.second_part = FIX2INT(rb_funcall(rb_time, rb_intern("usec"), 0)); @@ -304,13 +300,12 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cDate) { + MYSQL_TIME t; + VALUE rb_time = argv[i]; bind_buffers[i].buffer_type = MYSQL_TYPE_DATE; - bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); - MYSQL_TIME t; - VALUE rb_time = argv[i]; memset(&t, 0, sizeof(MYSQL_TIME)); t.second_part = 0; t.neg = 0; @@ -352,9 +347,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { return Qnil; } - VALUE current; current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); - GET_CLIENT(stmt_wrapper->client); if (!is_streaming) { // recieve the whole result set from the server From 77e9e3fe038f068776acdede57d1eaf1351c30bb Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 5 Jun 2015 18:55:39 -0400 Subject: [PATCH 300/783] Fix compilation under RBX --- ext/mysql2/client.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 483941aa3..a9b3c200b 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -488,7 +488,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { } current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); - RB_GC_GUARD(current); + (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, NULL); @@ -602,7 +602,7 @@ void rb_mysql_client_set_active_thread(VALUE self) { const char *thr = StringValueCStr(inspect); rb_raise(cMysql2Error, "This connection is in use by: %s", thr); - RB_GC_GUARD(inspect); + (void)RB_GC_GUARD(inspect); } } @@ -652,7 +652,7 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { REQUIRE_CONNECTED(wrapper); args.mysql = wrapper->client; - RB_GC_GUARD(current); + (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); rb_iv_set(self, "@current_query_options", current); @@ -1059,7 +1059,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) } current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); - RB_GC_GUARD(current); + (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, NULL); From d55d27fe8e2e83c06a0296586a6c812676610be5 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 5 Jun 2015 18:58:21 -0400 Subject: [PATCH 301/783] Run tests in random order --- .rspec | 1 + 1 file changed, 1 insertion(+) diff --git a/.rspec b/.rspec index 96424d24e..49eab5626 100644 --- a/.rspec +++ b/.rspec @@ -1,3 +1,4 @@ --colour --format documentation +--order rand --warnings From 7dffe55058d5a0ef31db0a351bdb125744bae6ec Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 5 Jun 2015 18:59:38 -0400 Subject: [PATCH 302/783] Appease the RBX bugs --- spec/mysql2/client_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 81a24fe23..5e8a33a28 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -178,6 +178,10 @@ def run_gc run_gc client = Mysql2::Client.new(DatabaseCredentials['root']) + # this empty `fork` call fixes this tests on RBX; without it, the next + # `fork` call hangs forever. WTF? + fork { } + fork do client.query('SELECT 1') client = nil From 29d926f2975200378fbd8ff54b09df5ed0c3c90e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 9 Jun 2015 21:23:45 -0700 Subject: [PATCH 303/783] Whitespace --- ext/mysql2/extconf.rb | 176 +++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 71058a798..ad30a5569 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -76,109 +76,109 @@ def asplode lib libs = ['m', 'z', 'socket', 'nsl', 'mygcc'] found = false while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do - exit 1 if libs.empty? - found ||= have_library(libs.shift) - end + exit 1 if libs.empty? + found ||= have_library(libs.shift) + end - asplode("mysql client") unless found + asplode("mysql client") unless found - rpath_dir = lib - end + rpath_dir = lib +end - if have_header('mysql.h') - prefix = nil - elsif have_header('mysql/mysql.h') - prefix = 'mysql' - else - asplode 'mysql.h' - end +if have_header('mysql.h') + prefix = nil +elsif have_header('mysql/mysql.h') + prefix = 'mysql' +else + asplode 'mysql.h' +end - %w{ errmsg.h mysqld_error.h }.each do |h| - header = [prefix, h].compact.join '/' - asplode h unless have_header h - end +%w{ errmsg.h mysqld_error.h }.each do |h| + header = [prefix, h].compact.join '/' + asplode h unless have_header h +end - # This is our wishlist. We use whichever flags work on the host. - # -Wall and -Wextra are included by default. - # TODO: fix statement.c and remove -Wno-error=declaration-after-statement - %w( - -Werror - -Weverything - -fsanitize=address - -fsanitize=integer - -fsanitize=thread - -fsanitize=memory - -fsanitize=undefined - -fsanitize=cfi - -Wno-error=declaration-after-statement - ).each do |flag| - if try_link('int main() {return 0;}', flag) - $CFLAGS << ' ' << flag - end +# This is our wishlist. We use whichever flags work on the host. +# -Wall and -Wextra are included by default. +# TODO: fix statement.c and remove -Wno-error=declaration-after-statement +%w( + -Werror + -Weverything + -fsanitize=address + -fsanitize=integer + -fsanitize=thread + -fsanitize=memory + -fsanitize=undefined + -fsanitize=cfi + -Wno-error=declaration-after-statement +).each do |flag| + if try_link('int main() {return 0;}', flag) + $CFLAGS << ' ' << flag end +end - if RUBY_PLATFORM =~ /mswin|mingw/ - # Build libmysql.a interface link library - require 'rake' - - # Build libmysql.a interface link library - # Use rake to rebuild only if these files change - deffile = File.expand_path('../../../support/libmysql.def', __FILE__) - libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) - file 'libmysql.a' => [deffile, libfile] do |t| - when_writing 'building libmysql.a' do - # Ruby kindly shows us where dllwrap is, but that tool does more than we want. - # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. - dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool') - sh dlltool, '--kill-at', - '--dllname', 'libmysql.dll', - '--output-lib', 'libmysql.a', - '--input-def', deffile, libfile - end +if RUBY_PLATFORM =~ /mswin|mingw/ + # Build libmysql.a interface link library + require 'rake' + + # Build libmysql.a interface link library + # Use rake to rebuild only if these files change + deffile = File.expand_path('../../../support/libmysql.def', __FILE__) + libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) + file 'libmysql.a' => [deffile, libfile] do |t| + when_writing 'building libmysql.a' do + # Ruby kindly shows us where dllwrap is, but that tool does more than we want. + # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. + dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool') + sh dlltool, '--kill-at', + '--dllname', 'libmysql.dll', + '--output-lib', 'libmysql.a', + '--input-def', deffile, libfile end + end - Rake::Task['libmysql.a'].invoke - $LOCAL_LIBS << ' ' << 'libmysql.a' + Rake::Task['libmysql.a'].invoke + $LOCAL_LIBS << ' ' << 'libmysql.a' - # Make sure the generated interface library works (if cross-compiling, trust without verifying) - unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ - abort "-----\nCannot find libmysql.a\n----" unless have_library('libmysql') - abort "-----\nCannot link to libmysql.a (my_init)\n----" unless have_func('my_init') - end + # Make sure the generated interface library works (if cross-compiling, trust without verifying) + unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + abort "-----\nCannot find libmysql.a\n----" unless have_library('libmysql') + abort "-----\nCannot link to libmysql.a (my_init)\n----" unless have_func('my_init') + end - # Vendor libmysql.dll - vendordir = File.expand_path('../../../vendor/', __FILE__) - directory vendordir + # Vendor libmysql.dll + vendordir = File.expand_path('../../../vendor/', __FILE__) + directory vendordir - vendordll = File.join(vendordir, 'libmysql.dll') - dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) - file vendordll => [dllfile, vendordir] do |t| - when_writing 'copying libmysql.dll' do - cp dllfile, vendordll - end + vendordll = File.join(vendordir, 'libmysql.dll') + dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) + file vendordll => [dllfile, vendordir] do |t| + when_writing 'copying libmysql.dll' do + cp dllfile, vendordll end + end - # Copy libmysql.dll to the local vendor directory by default - if arg_config('--no-vendor-libmysql') - # Fine, don't. - puts "--no-vendor-libmysql" - else # Default: arg_config('--vendor-libmysql') - # Let's do it! - Rake::Task[vendordll].invoke - end + # Copy libmysql.dll to the local vendor directory by default + if arg_config('--no-vendor-libmysql') + # Fine, don't. + puts "--no-vendor-libmysql" + else # Default: arg_config('--vendor-libmysql') + # Let's do it! + Rake::Task[vendordll].invoke + end +else + case explicit_rpath = with_config('mysql-rpath') + when true + abort "-----\nOption --with-mysql-rpath must have an argument\n-----" + when false + warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" + when String + # The user gave us a value so use it + rpath_flags = " -Wl,-rpath,#{explicit_rpath}" + warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" + $LDFLAGS << rpath_flags else - case explicit_rpath = with_config('mysql-rpath') - when true - abort "-----\nOption --with-mysql-rpath must have an argument\n-----" - when false - warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" - when String - # The user gave us a value so use it - rpath_flags = " -Wl,-rpath,#{explicit_rpath}" - warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" - $LDFLAGS << rpath_flags - else - if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] + if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] rpath_flags = " -Wl,-rpath,#{libdir}" if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. From c89b21b62f4e19effc0dee3399670b1d6de0b067 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 9 Jun 2015 21:43:59 -0700 Subject: [PATCH 304/783] Adjust compiler flags, remove runtime sanitizers --- ext/mysql2/extconf.rb | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index ad30a5569..28d80e6d4 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -99,22 +99,19 @@ def asplode lib end # This is our wishlist. We use whichever flags work on the host. -# -Wall and -Wextra are included by default. -# TODO: fix statement.c and remove -Wno-error=declaration-after-statement +# TODO: fix statement.c and remove -Wno-declaration-after-statement +# TODO: fix gperf mysql_enc_name_to_ruby.h and remove -Wno-missing-field-initializers %w( + -Wall + -Wextra -Werror - -Weverything - -fsanitize=address - -fsanitize=integer - -fsanitize=thread - -fsanitize=memory - -fsanitize=undefined - -fsanitize=cfi - -Wno-error=declaration-after-statement -).each do |flag| - if try_link('int main() {return 0;}', flag) - $CFLAGS << ' ' << flag - end + -Wno-unused-function + -Wno-declaration-after-statement + -Wno-missing-field-initializers +).select do |flag| + try_link('int main() {return 0;}', flag) +end.each do |flag| + $CFLAGS << ' ' << flag end if RUBY_PLATFORM =~ /mswin|mingw/ From 034dc40b24ef1e680d891c508f8dc1bf6088665c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 9 Jun 2015 21:45:16 -0700 Subject: [PATCH 305/783] Use casts for finicky comparison signedness --- ext/mysql2/client.c | 2 +- ext/mysql2/result.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index a9b3c200b..035d984f5 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -349,7 +349,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po /* avoid an early timeout due to time truncating milliseconds off the start time */ if (elapsed_time > 0) elapsed_time--; - if (elapsed_time >= wrapper->connect_timeout) + if (elapsed_time >= (time_t)wrapper->connect_timeout) break; connect_timeout = wrapper->connect_timeout - elapsed_time; mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout); diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 0d254623e..b30f145d5 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -747,7 +747,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } - if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) { + if ((unsigned)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) { for (i=0; inumberOfFields; i++) { rb_mysql_result_fetch_field(self, i, symbolizeKeys); } From cab7d2e5826f5cf3725f1c96c47fdf345023749a Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 11 Mar 2015 14:36:54 -0700 Subject: [PATCH 306/783] move Mysql2::Client#info to a class method for easier debugging --- ext/mysql2/client.c | 38 ++++++++++++++++---------------------- lib/mysql2/client.rb | 4 ++++ spec/mysql2/client_spec.rb | 23 +++++++++-------------- 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 035d984f5..a2e9546c0 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -16,7 +16,7 @@ VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error; -static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; +static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql; static ID intern_brackets, intern_new; @@ -824,30 +824,23 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { * * Returns a string that represents the client library version. */ -static VALUE rb_mysql_client_info(VALUE self) { - VALUE version, client_info; -#ifdef HAVE_RUBY_ENCODING_H - rb_encoding *default_internal_enc; - rb_encoding *conn_enc; - GET_CLIENT(self); -#endif - version = rb_hash_new(); +static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) { + VALUE version_info, version, header_version; + version_info = rb_hash_new(); -#ifdef HAVE_RUBY_ENCODING_H - default_internal_enc = rb_default_internal_encoding(); - conn_enc = rb_to_encoding(wrapper->encoding); -#endif + version = rb_str_new2(mysql_get_client_info()); + header_version = rb_str_new2(MYSQL_LINK_VERSION); - rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version())); - client_info = rb_str_new2(mysql_get_client_info()); #ifdef HAVE_RUBY_ENCODING_H - rb_enc_associate(client_info, conn_enc); - if (default_internal_enc) { - client_info = rb_str_export_to_enc(client_info, default_internal_enc); - } + rb_enc_associate(version, rb_usascii_encoding()); + rb_enc_associate(header_version, rb_usascii_encoding()); #endif - rb_hash_aset(version, sym_version, client_info); - return version; + + rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version())); + rb_hash_aset(version_info, sym_version, version); + rb_hash_aset(version_info, sym_header_version, header_version); + + return version_info; } /* call-seq: @@ -1244,11 +1237,11 @@ void init_mysql2_client() { rb_define_alloc_func(cMysql2Client, allocate); rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1); + rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0); rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0); rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0); rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1); - rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0); rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0); rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0); rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0); @@ -1284,6 +1277,7 @@ void init_mysql2_client() { sym_id = ID2SYM(rb_intern("id")); sym_version = ID2SYM(rb_intern("version")); + sym_header_version = ID2SYM(rb_intern("header_version")); sym_async = ID2SYM(rb_intern("async")); sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 764e410ee..2481efd1c 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -94,6 +94,10 @@ def query_info info_hash end + def info + self.class.info + end + private def self.local_offset ::Time.local(2010).utc_offset.to_r / 86400 diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 5e8a33a28..1309dd237 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -711,23 +711,18 @@ def run_gc context "strings returned by #info" do before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should default to the connection's encoding if Encoding.default_internal is nil" do - with_internal_encoding nil do - expect(@client.info[:version].encoding).to eql(Encoding::UTF_8) - - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) - expect(client2.info[:version].encoding).to eql(Encoding::ASCII) - end + it "should be tagged as ascii" do + expect(@client.info[:version].encoding).to eql(Encoding::US_ASCII) + expect(@client.info[:header_version].encoding).to eql(Encoding::US_ASCII) end + end - it "should use Encoding.default_internal" do - with_internal_encoding Encoding::UTF_8 do - expect(@client.info[:version].encoding).to eql(Encoding.default_internal) - end + context "strings returned by .info" do + before { pending('Encoding is undefined') unless defined?(Encoding) } - with_internal_encoding Encoding::ASCII do - expect(@client.info[:version].encoding).to eql(Encoding.default_internal) - end + it "should be tagged as ascii" do + expect(Mysql2::Client.info[:version].encoding).to eql(Encoding::US_ASCII) + expect(Mysql2::Client.info[:header_version].encoding).to eql(Encoding::US_ASCII) end end From 434eb31c96f1302c53841501e56cfeb25eb9082a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 9 Jun 2015 22:28:21 -0700 Subject: [PATCH 307/783] Grab the wrapper only if it will be used for Encoding --- ext/mysql2/statement.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 571a4c722..283a14c6d 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -53,12 +53,13 @@ static void rb_raise_mysql2_stmt_error(VALUE self) { rb_encoding *conn_enc; #endif GET_STATEMENT(self); + +#ifdef HAVE_RUBY_ENCODING_H { GET_CLIENT(stmt_wrapper->client); -#ifdef HAVE_RUBY_ENCODING_H conn_enc = rb_to_encoding(wrapper->encoding); -#endif } +#endif rb_raise_mysql2_stmt_error2(stmt_wrapper->stmt #ifdef HAVE_RUBY_ENCODING_H From e84933b3594e5afa099f90f887c66c9410ca4ea1 Mon Sep 17 00:00:00 2001 From: Christos Trochalakis Date: Tue, 5 May 2015 14:53:52 +0300 Subject: [PATCH 308/783] Also search for mariadb_config on compile libmariadb-client-lgpl-dev in newly released Debian stable (jessie) ships `/usr/bin/mariadb_config`. --- ext/mysql2/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 28d80e6d4..fd4c1a608 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -36,7 +36,7 @@ def asplode lib /usr/local/lib/mysql5* ].map{|dir| "#{dir}/bin" } -GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}" +GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}" # If the user has provided a --with-mysql-dir argument, we must respect it or fail. inc, lib = dir_config('mysql') From 18d988a9939453a53b89a8a82e4906302316430b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 5 Jun 2015 06:50:51 -0700 Subject: [PATCH 309/783] Fix a few declarations and variable usage in statement.c --- ext/mysql2/statement.c | 64 ++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 283a14c6d..566092295 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -3,6 +3,7 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; static VALUE sym_stream, intern_error_number_eql, intern_sql_state_eql, intern_each; +static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year; #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ @@ -205,26 +206,28 @@ static void *nogvl_stmt_store_result(void *ptr) { */ static VALUE execute(int argc, VALUE *argv, VALUE self) { MYSQL_BIND *bind_buffers = NULL; + unsigned long *length_buffers = NULL; unsigned long bind_count; long i; MYSQL_STMT *stmt; MYSQL_RES *metadata; VALUE current; VALUE resultObj; - VALUE *params_enc = alloca(sizeof(VALUE) * argc); - unsigned long* length_buffers = NULL; - int is_streaming = 0; + VALUE *params_enc; + int is_streaming; +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *conn_enc; +#endif + GET_STATEMENT(self); GET_CLIENT(stmt_wrapper->client); + #ifdef HAVE_RUBY_ENCODING_H - rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); + conn_enc = rb_to_encoding(wrapper->encoding); #endif - { - VALUE valStreaming = rb_hash_aref(rb_iv_get(stmt_wrapper->client, "@query_options"), sym_stream); - if(valStreaming == Qtrue) { - is_streaming = 1; - } - } + + /* Scratch space for string encoding exports, allocate on the stack. */ + params_enc = alloca(sizeof(VALUE) * argc); stmt = stmt_wrapper->stmt; @@ -291,13 +294,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { memset(&t, 0, sizeof(MYSQL_TIME)); t.neg = 0; - t.second_part = FIX2INT(rb_funcall(rb_time, rb_intern("usec"), 0)); - t.second = FIX2INT(rb_funcall(rb_time, rb_intern("sec"), 0)); - t.minute = FIX2INT(rb_funcall(rb_time, rb_intern("min"), 0)); - t.hour = FIX2INT(rb_funcall(rb_time, rb_intern("hour"), 0)); - t.day = FIX2INT(rb_funcall(rb_time, rb_intern("day"), 0)); - t.month = FIX2INT(rb_funcall(rb_time, rb_intern("month"), 0)); - t.year = FIX2INT(rb_funcall(rb_time, rb_intern("year"), 0)); + t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0)); + t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0)); + t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0)); + t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0)); + t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0)); + t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0)); + t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0)); *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cDate) { @@ -310,9 +313,9 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { memset(&t, 0, sizeof(MYSQL_TIME)); t.second_part = 0; t.neg = 0; - t.day = FIX2INT(rb_funcall(rb_time, rb_intern("day"), 0)); - t.month = FIX2INT(rb_funcall(rb_time, rb_intern("month"), 0)); - t.year = FIX2INT(rb_funcall(rb_time, rb_intern("year"), 0)); + t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0)); + t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0)); + t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0)); *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cBigDecimal) { @@ -349,7 +352,10 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { } current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); + (void)RB_GC_GUARD(current); + Check_Type(current, T_HASH); + is_streaming = (Qtrue == rb_hash_aref(current, sym_stream)); if (!is_streaming) { // recieve the whole result set from the server if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) { @@ -360,16 +366,6 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, stmt); -#ifdef HAVE_RUBY_ENCODING_H - { - mysql2_result_wrapper *result_wrapper; - - GET_CLIENT(stmt_wrapper->client); - GetMysql2Result(resultObj, result_wrapper); - result_wrapper->encoding = wrapper->encoding; - } -#endif - if (!is_streaming) { // cache all result rb_funcall(resultObj, intern_each, 0); @@ -439,4 +435,12 @@ void init_mysql2_statement() { intern_error_number_eql = rb_intern("error_number="); intern_sql_state_eql = rb_intern("sql_state="); intern_each = rb_intern("each"); + + intern_usec = rb_intern("usec"); + intern_sec = rb_intern("sec"); + intern_min = rb_intern("min"); + intern_hour = rb_intern("hour"); + intern_day = rb_intern("day"); + intern_month = rb_intern("month"); + intern_year = rb_intern("year"); } From e45b54a6fb43cf595d210685d3fd3fb77ebfab71 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 10 Jun 2015 00:29:25 -0700 Subject: [PATCH 310/783] Rename macro GetMysql2Result to GET_RESULT Switch from DATA_PTR to Data_Get_Struct. This protects against calling methods that need the result wrapper on hand-built Mysql2::Result objects. They will raise a TypeError instead of segfaulting. --- ext/mysql2/result.c | 29 ++++++++++++----------------- ext/mysql2/result.h | 2 -- spec/mysql2/result_spec.rb | 8 ++++++++ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index b30f145d5..d164dd40a 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -50,6 +50,10 @@ static rb_encoding *binaryEncoding; #define MYSQL2_MIN_TIME 62171150401ULL #endif +#define GET_RESULT(obj) \ + mysql2_result_wrapper *wrapper; \ + Data_Get_Struct(self, mysql2_result_wrapper, wrapper); + typedef struct { int symbolizeKeys; int asArray; @@ -141,9 +145,8 @@ static void *nogvl_stmt_fetch(void *ptr) { static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) { - mysql2_result_wrapper * wrapper; VALUE rb_field; - GetMysql2Result(self, wrapper); + GET_RESULT(self); if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); @@ -231,8 +234,7 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len) static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) { unsigned int i; - mysql2_result_wrapper * wrapper; - GetMysql2Result(self, wrapper); + GET_RESULT(self); if (wrapper->result_buffers != NULL) return; @@ -309,14 +311,13 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) { VALUE rowVal; - mysql2_result_wrapper *wrapper; unsigned int i = 0; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; #endif - GetMysql2Result(self, wrapper); + GET_RESULT(self); #ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); @@ -509,7 +510,6 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) { VALUE rowVal; - mysql2_result_wrapper * wrapper; MYSQL_ROW row; unsigned int i = 0; unsigned long * fieldLengths; @@ -518,7 +518,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r rb_encoding *default_internal_enc; rb_encoding *conn_enc; #endif - GetMysql2Result(self, wrapper); + GET_RESULT(self); #ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); @@ -729,12 +729,11 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r } static VALUE rb_mysql_result_fetch_fields(VALUE self) { - mysql2_result_wrapper * wrapper; unsigned int i = 0; short int symbolizeKeys = 0; VALUE defaults; - GetMysql2Result(self, wrapper); + GET_RESULT(self); defaults = rb_iv_get(self, "@query_options"); Check_Type(defaults, T_HASH); @@ -760,12 +759,11 @@ static VALUE rb_mysql_result_each_(VALUE self, VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args), const result_each_args *args) { - mysql2_result_wrapper *wrapper; unsigned long i; const char *errstr; MYSQL_FIELD *fields = NULL; - GetMysql2Result(self, wrapper); + GET_RESULT(self); if (wrapper->is_streaming) { /* When streaming, we will only yield rows, not return them. */ @@ -850,10 +848,9 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); ID db_timezone, app_timezone, dbTz, appTz; - mysql2_result_wrapper * wrapper; int symbolizeKeys, asArray, castBool, cacheRows, cast; - GetMysql2Result(self, wrapper); + GET_RESULT(self); defaults = rb_iv_get(self, "@query_options"); Check_Type(defaults, T_HASH); @@ -931,9 +928,8 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } static VALUE rb_mysql_result_count(VALUE self) { - mysql2_result_wrapper *wrapper; + GET_RESULT(self); - GetMysql2Result(self, wrapper); if (wrapper->is_streaming) { /* This is an unsigned long per result.h */ return ULONG2NUM(wrapper->numberOfRows); @@ -957,7 +953,6 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ VALUE obj; mysql2_result_wrapper * wrapper; - obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper); wrapper->numberOfFields = 0; wrapper->numberOfRows = 0; diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index b95eae81c..2b8e5b317 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -25,6 +25,4 @@ typedef struct { unsigned long *length; } mysql2_result_wrapper; -#define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj)); - #endif diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 16278bca6..b32939753 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -6,6 +6,14 @@ @result = @client.query "SELECT 1" end + it "should raise a TypeError exception when it doesn't wrap a result set" do + r = Mysql2::Result.new + expect { r.count }.to raise_error(TypeError) + expect { r.fields }.to raise_error(TypeError) + expect { r.size }.to raise_error(TypeError) + expect { r.each }.to raise_error(TypeError) + end + it "should have included Enumerable" do expect(Mysql2::Result.ancestors.include?(Enumerable)).to be true end From c3815232e7f3ea4bb78a8ef1fb122a2f6299f729 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 10 Jun 2015 01:11:08 -0700 Subject: [PATCH 311/783] Convert StringValuePtr to either RSTRING_PTR or StringValueCStr Where a simple nul-terminated C string is required, use StringValueCStr. Where a string pointer and length are used, follow the pattern: Check_Type(str, T_STRING); ptr = RSTRING_PTR(str); len = RSTRING_LEN(str); --- ext/mysql2/client.c | 34 +++++++++++++++++----------------- ext/mysql2/statement.c | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index a2e9546c0..0014a5288 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -277,7 +277,7 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) { oldLen = RSTRING_LEN(str); newStr = xmalloc(oldLen*2+1); - newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen); + newLen = mysql_escape_string((char *)newStr, RSTRING_PTR(str), oldLen); if (newLen == oldLen) { /* no need to return a new ruby string if nothing changed */ xfree(newStr); @@ -326,12 +326,12 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po VALUE rv; GET_CLIENT(self); - args.host = NIL_P(host) ? NULL : StringValuePtr(host); - args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket); + args.host = NIL_P(host) ? NULL : StringValueCStr(host); + args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket); args.port = NIL_P(port) ? 0 : NUM2INT(port); - args.user = NIL_P(user) ? NULL : StringValuePtr(user); - args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass); - args.db = NIL_P(database) ? NULL : StringValuePtr(database); + args.user = NIL_P(user) ? NULL : StringValueCStr(user); + args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass); + args.db = NIL_P(database) ? NULL : StringValueCStr(database); args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); @@ -663,7 +663,7 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { #else args.sql = sql; #endif - args.sql_ptr = StringValuePtr(args.sql); + args.sql_ptr = RSTRING_PTR(args.sql); args.sql_len = RSTRING_LEN(args.sql); args.wrapper = wrapper; @@ -717,7 +717,7 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) { oldLen = RSTRING_LEN(str); newStr = xmalloc(oldLen*2+1); - newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen); + newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen); if (newLen == oldLen) { /* no need to return a new ruby string if nothing changed */ xfree(newStr); @@ -781,17 +781,17 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { break; case MYSQL_READ_DEFAULT_FILE: - charval = (const char *)StringValuePtr(value); + charval = (const char *)StringValueCStr(value); retval = charval; break; case MYSQL_READ_DEFAULT_GROUP: - charval = (const char *)StringValuePtr(value); + charval = (const char *)StringValueCStr(value); retval = charval; break; case MYSQL_INIT_COMMAND: - charval = (const char *)StringValuePtr(value); + charval = (const char *)StringValueCStr(value); retval = charval; break; @@ -961,7 +961,7 @@ static VALUE rb_mysql_client_select_db(VALUE self, VALUE db) REQUIRE_CONNECTED(wrapper); args.mysql = wrapper->client; - args.db = StringValuePtr(db); + args.db = StringValueCStr(db); if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse) rb_raise_mysql2_error(wrapper); @@ -1155,11 +1155,11 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE GET_CLIENT(self); mysql_ssl_set(wrapper->client, - NIL_P(key) ? NULL : StringValuePtr(key), - NIL_P(cert) ? NULL : StringValuePtr(cert), - NIL_P(ca) ? NULL : StringValuePtr(ca), - NIL_P(capath) ? NULL : StringValuePtr(capath), - NIL_P(cipher) ? NULL : StringValuePtr(cipher)); + NIL_P(key) ? NULL : StringValueCStr(key), + NIL_P(cert) ? NULL : StringValueCStr(cert), + NIL_P(ca) ? NULL : StringValueCStr(ca), + NIL_P(capath) ? NULL : StringValueCStr(capath), + NIL_P(cipher) ? NULL : StringValueCStr(cipher)); return self; } diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 566092295..bb15e2b91 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -135,7 +135,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { // ensure the string is in the encoding the connection is expecting args.sql = rb_str_export_to_enc(args.sql, conn_enc); #endif - args.sql_ptr = StringValuePtr(sql); + args.sql_ptr = RSTRING_PTR(sql); args.sql_len = RSTRING_LEN(sql); if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { From e1b3cc516fa8928f5d7d56aaa4be9a46797cca2c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 10 Jun 2015 01:17:23 -0700 Subject: [PATCH 312/783] Whitespace --- ext/mysql2/client.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 0014a5288..ac270401e 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -326,13 +326,13 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po VALUE rv; GET_CLIENT(self); - args.host = NIL_P(host) ? NULL : StringValueCStr(host); - args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket); - args.port = NIL_P(port) ? 0 : NUM2INT(port); - args.user = NIL_P(user) ? NULL : StringValueCStr(user); - args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass); - args.db = NIL_P(database) ? NULL : StringValueCStr(database); - args.mysql = wrapper->client; + args.host = NIL_P(host) ? NULL : StringValueCStr(host); + args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket); + args.port = NIL_P(port) ? 0 : NUM2INT(port); + args.user = NIL_P(user) ? NULL : StringValueCStr(user); + args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass); + args.db = NIL_P(database) ? NULL : StringValueCStr(database); + args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); if (wrapper->connect_timeout) @@ -1155,9 +1155,9 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE GET_CLIENT(self); mysql_ssl_set(wrapper->client, - NIL_P(key) ? NULL : StringValueCStr(key), - NIL_P(cert) ? NULL : StringValueCStr(cert), - NIL_P(ca) ? NULL : StringValueCStr(ca), + NIL_P(key) ? NULL : StringValueCStr(key), + NIL_P(cert) ? NULL : StringValueCStr(cert), + NIL_P(ca) ? NULL : StringValueCStr(ca), NIL_P(capath) ? NULL : StringValueCStr(capath), NIL_P(cipher) ? NULL : StringValueCStr(cipher)); From ed746a1825e751a18794a502dec651040b95b689 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 10 Jun 2015 01:19:37 -0700 Subject: [PATCH 313/783] Use query option symbols rather than internal variable names in rb_warn calls --- ext/mysql2/result.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index d164dd40a..2a1967c1c 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -867,15 +867,15 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { cast = RTEST(rb_hash_aref(opts, sym_cast)); if (wrapper->is_streaming && cacheRows) { - rb_warn("cacheRows is ignored if streaming is true"); + rb_warn(":cache_rows is ignored if :stream is true"); } if (wrapper->stmt && !cacheRows && !wrapper->is_streaming) { - rb_warn("cacheRows is forced for prepared statements (if not streaming)"); + rb_warn(":cache_rows is forced for prepared statements (if not streaming)"); } if (wrapper->stmt && !cast) { - rb_warn("cast is forced for prepared statements"); + rb_warn(":cast is forced for prepared statements"); } dbTz = rb_hash_aref(opts, sym_database_timezone); From c25ff86062b8b2c2433985485dcf00ee2cb42ecb Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 10 Jun 2015 01:31:42 -0700 Subject: [PATCH 314/783] Fix unused variable warning on Windows in rb_mysql_client_socket --- ext/mysql2/client.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index ac270401e..a6accbaff 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -881,14 +881,10 @@ static VALUE rb_mysql_client_server_info(VALUE self) { * Return the file descriptor number for this client. */ static VALUE rb_mysql_client_socket(VALUE self) { - GET_CLIENT(self); #ifndef _WIN32 - { - int fd_set_fd; - REQUIRE_CONNECTED(wrapper); - fd_set_fd = wrapper->client->net.fd; - return INT2NUM(fd_set_fd); - } + GET_CLIENT(self); + REQUIRE_CONNECTED(wrapper); + return INT2NUM(wrapper->client->net.fd); #else rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows"); #endif From 49c383d8e307e8332c014a4f23a0c154eff35069 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 10 Jun 2015 01:57:29 -0700 Subject: [PATCH 315/783] Skip fork tests when fork is not available (e.g. Windows) --- spec/mysql2/client_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 1309dd237..86232cc33 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -173,8 +173,9 @@ def run_gc expect(final_count).to eq(before_count) end - it "should not close connections when running in a child process" do + pending("fork is not available on this platform") unless Process.respond_to?(:fork) + run_gc client = Mysql2::Client.new(DatabaseCredentials['root']) From 791b7af1287bed046e3bdab336b17bdc521dd39d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 10 Jun 2015 02:11:52 -0700 Subject: [PATCH 316/783] Add Ruby 2.2 to the Appveyor matrix --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 26eb1f264..3dff9f8b6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -29,6 +29,8 @@ environment: - ruby_version: "200-x64" - ruby_version: "21" - ruby_version: "21-x64" + - ruby_version: "22" + - ruby_version: "22-x64" cache: - vendor services: From f07b36d10b3407887c8bdadd162fafac672f70f7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 10 Jun 2015 02:12:24 -0700 Subject: [PATCH 317/783] Update to Connector/C 6.1.6 --- tasks/vendor_mysql.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index a2d94b39d..14c73659a 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -1,7 +1,7 @@ require 'rake/clean' require 'rake/extensioncompiler' -CONNECTOR_VERSION = "6.1.5" # NOTE: Track the upstream version from time to time +CONNECTOR_VERSION = "6.1.6" # NOTE: Track the upstream version from time to time def vendor_mysql_platform(platform=nil) platform ||= RUBY_PLATFORM From 8d194ac447afb0974d7c64ad9a5fc900e935c702 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 10 Jun 2015 02:35:06 -0700 Subject: [PATCH 318/783] Test default client flag equality instead of bitwise truthiness --- spec/mysql2/client_spec.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 86232cc33..02129940f 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -64,12 +64,13 @@ def connect *args end end client = klient.new - expect(client.connect_args.last[6] & (Mysql2::Client::REMEMBER_OPTIONS | - Mysql2::Client::LONG_PASSWORD | - Mysql2::Client::LONG_FLAG | - Mysql2::Client::TRANSACTIONS | - Mysql2::Client::PROTOCOL_41 | - Mysql2::Client::SECURE_CONNECTION)).to be > 0 + client_flags = Mysql2::Client::REMEMBER_OPTIONS | + Mysql2::Client::LONG_PASSWORD | + Mysql2::Client::LONG_FLAG | + Mysql2::Client::TRANSACTIONS | + Mysql2::Client::PROTOCOL_41 | + Mysql2::Client::SECURE_CONNECTION + expect(client.connect_args.last[6]).to eql(client_flags) end it "should execute init command" do From 5067cf0067e5681f8cb75dcecdb663f835057376 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 10 Jun 2015 20:40:16 -0400 Subject: [PATCH 319/783] Grammar --- spec/mysql2/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 02129940f..5576073a5 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -484,7 +484,7 @@ def run_gc expect { client.query('SELECT 1') }.to_not raise_error end - it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction true" do + it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction" do client = Mysql2::Client.new(DatabaseCredentials['root']) expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) From a98b7bbfa594730e1c53c0a0591b0fada494fafa Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 10 Jun 2015 10:04:29 -0400 Subject: [PATCH 320/783] Test against mysql55 on OSX and allow failures See #633. --- .travis.yml | 6 ++++-- .travis_setup.sh | 14 ++++++++++++-- ext/mysql2/extconf.rb | 9 ++------- spec/mysql2/client_spec.rb | 8 ++++++++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 51d1607ba..4a2a589cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ bundler_args: --without benchmarks development before_install: - gem --version - bash .travis_setup.sh - - mysqld --version - - mysql -u $USER -e "CREATE DATABASE IF NOT EXISTS test" os: - linux rvm: @@ -24,4 +22,8 @@ matrix: - rvm: 2.0.0 env: DB=mysql57 - rvm: 2.0.0 + env: DB=mysql + os: osx + - rvm: 2.0.0 + env: DB=mysql55 os: osx diff --git a/.travis_setup.sh b/.travis_setup.sh index 68323dc90..72fa32bcc 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -14,8 +14,9 @@ fi # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then - brew install mysql - mysql.server start + brew update + brew install "$DB" + $(brew --prefix "$DB")/bin/mysql.server start fi # TODO: get SSL working on OS X in Travis @@ -25,3 +26,12 @@ if ! [[ x$OSTYPE =~ ^xdarwin ]]; then fi sudo mysql -e "CREATE USER '$USER'@'localhost'" || true + +# Print the MySQL version and create the test DB +if [[ x$OSTYPE =~ ^xdarwin ]]; then + $(brew --prefix "$DB")/bin/mysqld --version + $(brew --prefix "$DB")/bin/mysql -u $USER -e "CREATE DATABASE IF NOT EXISTS test" +else + mysqld --version + mysql -u $USER -e "CREATE DATABASE IF NOT EXISTS test" +fi diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index fd4c1a608..5d85deed5 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -34,6 +34,7 @@ def asplode lib /usr/local/mysql /usr/local/mysql-* /usr/local/lib/mysql5* + /usr/local/opt/mysql5* ].map{|dir| "#{dir}/bin" } GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}" @@ -73,14 +74,8 @@ def asplode lib rpath_dir = libs else inc, lib = dir_config('mysql', '/usr/local') - libs = ['m', 'z', 'socket', 'nsl', 'mygcc'] - found = false - while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do - exit 1 if libs.empty? - found ||= have_library(libs.shift) - end - asplode("mysql client") unless found + asplode("mysql client") unless find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") rpath_dir = lib end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 5576073a5..aaf8ae42e 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -478,6 +478,10 @@ def run_gc end it "should handle Timeouts without leaving the connection hanging if reconnect is true" do + if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.info.fetch(:version).start_with?('5.5') + pending('libmysqlclient 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') + end + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) @@ -485,6 +489,10 @@ def run_gc end it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction" do + if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.info.fetch(:version).start_with?('5.5') + pending('libmysqlclient 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') + end + client = Mysql2::Client.new(DatabaseCredentials['root']) expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) From 9c76acfd2c380b18ec2f18f9fa24e6fb6c86a448 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 21 Jun 2015 21:48:33 -0700 Subject: [PATCH 321/783] Keep a reference from the Result to the Statement This prevents the GC from getting the Statement first, which would call mysql_stmt_close, which calls mysql_stmt_result_free on any outstanding results. When the Result object gets GC next, it also calls mysql_stmt_result_free and that would crash. --- ext/mysql2/client.c | 4 ++-- ext/mysql2/result.c | 16 +++++++++++++--- ext/mysql2/result.h | 3 ++- ext/mysql2/statement.c | 3 ++- ext/mysql2/statement.h | 1 + 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index a6accbaff..0d12188fe 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -490,7 +490,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); - resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, NULL); + resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); return resultObj; } @@ -1050,7 +1050,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); - resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, NULL); + resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); return resultObj; } diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 2a1967c1c..6c482ce5f 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -75,6 +75,7 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_a sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name; static ID intern_merge; +/* Mark any VALUEs that are only referenced in C, so the GC won't get them. */ static void rb_mysql_result_mark(void * wrapper) { mysql2_result_wrapper * w = wrapper; if (w) { @@ -82,12 +83,12 @@ static void rb_mysql_result_mark(void * wrapper) { rb_gc_mark(w->rows); rb_gc_mark(w->encoding); rb_gc_mark(w->client); + rb_gc_mark(w->statement); } } /* this may be called manually or during GC */ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { - unsigned int i; if (!wrapper) return; if (wrapper->resultFreed != 1) { @@ -95,6 +96,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { mysql_stmt_free_result(wrapper->stmt); if (wrapper->result_buffers) { + unsigned int i; for (i = 0; i < wrapper->numberOfFields; i++) { if (wrapper->result_buffers[i].buffer) { xfree(wrapper->result_buffers[i].buffer); @@ -107,6 +109,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { } } /* FIXME: this may call flush_use_result, which can hit the socket */ + /* For prepared statements, wrapper->result is the result metadata */ mysql_free_result(wrapper->result); wrapper->resultFreed = 1; } @@ -949,7 +952,7 @@ static VALUE rb_mysql_result_count(VALUE self) { } /* Mysql2::Result */ -VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, MYSQL_STMT * s) { +VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) { VALUE obj; mysql2_result_wrapper * wrapper; @@ -966,12 +969,19 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ wrapper->client = client; wrapper->client_wrapper = DATA_PTR(client); wrapper->client_wrapper->refcount++; - wrapper->stmt = s; wrapper->result_buffers = NULL; wrapper->is_null = NULL; wrapper->error = NULL; wrapper->length = NULL; + /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */ + wrapper->statement = statement; + if (statement != Qnil) { + mysql_stmt_wrapper *stmt_wrapper = DATA_PTR(statement); + wrapper->stmt = stmt_wrapper->stmt; + stmt_wrapper->refcount++; + } + rb_obj_call_init(obj, 0, NULL); rb_iv_set(obj, "@query_options", options); diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 2b8e5b317..48a96e7e8 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -2,13 +2,14 @@ #define MYSQL2_RESULT_H void init_mysql2_result(); -VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, MYSQL_STMT * s); +VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement); typedef struct { VALUE fields; VALUE rows; VALUE client; VALUE encoding; + VALUE statement; unsigned int numberOfFields; unsigned long numberOfRows; unsigned long lastRowProcessed; diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index bb15e2b91..b475f32d9 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -359,12 +359,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { if (!is_streaming) { // recieve the whole result set from the server if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) { + mysql_free_result(metadata); rb_raise_mysql2_stmt_error(self); } MARK_CONN_INACTIVE(stmt_wrapper->client); } - resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, stmt); + resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self); if (!is_streaming) { // cache all result diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index 4c1626495..a33061820 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -8,6 +8,7 @@ void init_mysql2_statement(); typedef struct { VALUE client; MYSQL_STMT *stmt; + int refcount; } mysql_stmt_wrapper; VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); From 1c0e06c692d3c010174d32212ef6e0f67d6be0f2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 22 Jun 2015 05:38:39 +0000 Subject: [PATCH 322/783] Track the Statement refcount before freeing it --- ext/mysql2/mysql2_ext.h | 2 +- ext/mysql2/result.c | 35 ++++++++++++++++++++--------------- ext/mysql2/result.h | 2 +- ext/mysql2/statement.c | 11 +++++++++-- ext/mysql2/statement.h | 5 +++-- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index ec781194d..381266e0d 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -39,8 +39,8 @@ typedef unsigned int uint; #endif #include -#include #include +#include #include #endif diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 6c482ce5f..b0837514a 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -92,8 +92,8 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { if (!wrapper) return; if (wrapper->resultFreed != 1) { - if (wrapper->stmt) { - mysql_stmt_free_result(wrapper->stmt); + if (wrapper->stmt_wrapper) { + mysql_stmt_free_result(wrapper->stmt_wrapper->stmt); if (wrapper->result_buffers) { unsigned int i; @@ -125,6 +125,10 @@ static void rb_mysql_result_free(void *ptr) { decr_mysql2_client(wrapper->client_wrapper); } + if (wrapper->statement != Qnil) { + decr_mysql2_stmt(wrapper->stmt_wrapper); + } + xfree(wrapper); } @@ -341,8 +345,8 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co rb_mysql_result_alloc_result_buffers(self, fields); } - if(mysql_stmt_bind_result(wrapper->stmt, wrapper->result_buffers)) { - rb_raise_mysql2_stmt_error2(wrapper->stmt + if (mysql_stmt_bind_result(wrapper->stmt_wrapper->stmt, wrapper->result_buffers)) { + rb_raise_mysql2_stmt_error2(wrapper->stmt_wrapper->stmt #ifdef HAVE_RUBY_ENCODING_H , conn_enc #endif @@ -350,14 +354,14 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } { - switch((uintptr_t)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt, RUBY_UBF_IO, 0)) { + switch((uintptr_t)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt_wrapper->stmt, RUBY_UBF_IO, 0)) { case 0: /* success */ break; case 1: /* error */ - rb_raise_mysql2_stmt_error2(wrapper->stmt + rb_raise_mysql2_stmt_error2(wrapper->stmt_wrapper->stmt #ifdef HAVE_RUBY_ENCODING_H , conn_enc #endif @@ -873,11 +877,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { rb_warn(":cache_rows is ignored if :stream is true"); } - if (wrapper->stmt && !cacheRows && !wrapper->is_streaming) { + if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) { rb_warn(":cache_rows is forced for prepared statements (if not streaming)"); } - if (wrapper->stmt && !cast) { + if (wrapper->stmt_wrapper && !cast) { rb_warn(":cast is forced for prepared statements"); } @@ -903,7 +907,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } if (wrapper->lastRowProcessed == 0 && !wrapper->is_streaming) { - wrapper->numberOfRows = wrapper->stmt ? mysql_stmt_num_rows(wrapper->stmt) : mysql_num_rows(wrapper->result); + wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result); if (wrapper->numberOfRows == 0) { wrapper->rows = rb_ary_new(); return wrapper->rows; @@ -921,7 +925,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { args.app_timezone = app_timezone; args.block_given = block; - if (wrapper->stmt) { + if (wrapper->stmt_wrapper) { fetch_row_func = rb_mysql_result_fetch_row_stmt; } else { fetch_row_func = rb_mysql_result_fetch_row; @@ -943,8 +947,8 @@ static VALUE rb_mysql_result_count(VALUE self) { return LONG2NUM(RARRAY_LEN(wrapper->rows)); } else { /* MySQL returns an unsigned 64-bit long here */ - if(wrapper->stmt) { - return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt)); + if (wrapper->stmt_wrapper) { + return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt)); } else { return ULL2NUM(mysql_num_rows(wrapper->result)); } @@ -977,9 +981,10 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */ wrapper->statement = statement; if (statement != Qnil) { - mysql_stmt_wrapper *stmt_wrapper = DATA_PTR(statement); - wrapper->stmt = stmt_wrapper->stmt; - stmt_wrapper->refcount++; + wrapper->stmt_wrapper = DATA_PTR(statement); + wrapper->stmt_wrapper->refcount++; + } else { + wrapper->stmt_wrapper = NULL; } rb_obj_call_init(obj, 0, NULL); diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 48a96e7e8..e8c2cfafb 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -17,7 +17,7 @@ typedef struct { char streamingComplete; char resultFreed; MYSQL_RES *result; - MYSQL_STMT *stmt; + mysql_stmt_wrapper *stmt_wrapper; mysql_client_wrapper *client_wrapper; /* statement result bind buffers */ MYSQL_BIND *result_buffers; diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index b475f32d9..8ef9ac374 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -19,10 +19,16 @@ static void rb_mysql_stmt_mark(void * ptr) { static void rb_mysql_stmt_free(void * ptr) { mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; + decr_mysql2_stmt(stmt_wrapper); +} - mysql_stmt_close(stmt_wrapper->stmt); +void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) { + stmt_wrapper->refcount--; - xfree(ptr); + if (stmt_wrapper->refcount == 0) { + mysql_stmt_close(stmt_wrapper->stmt); + xfree(stmt_wrapper); + } } VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt @@ -103,6 +109,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); { stmt_wrapper->client = rb_client; + stmt_wrapper->refcount = 0; stmt_wrapper->stmt = NULL; } diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index a33061820..ad81bfa12 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -3,14 +3,15 @@ extern VALUE cMysql2Statement; -void init_mysql2_statement(); - typedef struct { VALUE client; MYSQL_STMT *stmt; int refcount; } mysql_stmt_wrapper; +void init_mysql2_statement(); +void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper); + VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt #ifdef HAVE_RUBY_ENCODING_H From b211b21ced390079ee5bc0b9db741f92d7ff6582 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 22 Jun 2015 05:51:52 +0000 Subject: [PATCH 323/783] Start the refcount at 1 --- ext/mysql2/statement.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 8ef9ac374..7e1097613 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -109,7 +109,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); { stmt_wrapper->client = rb_client; - stmt_wrapper->refcount = 0; + stmt_wrapper->refcount = 1; stmt_wrapper->stmt = NULL; } From d11aeb3097136a267afe952d861cad8de40e4376 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 22 Jun 2015 06:08:28 +0000 Subject: [PATCH 324/783] Null out the statement result buffers after freeing them --- ext/mysql2/result.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index b0837514a..c290eb771 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -107,6 +107,8 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { xfree(wrapper->error); xfree(wrapper->length); } + /* Clue that the next statement execute will need to allocate a new result buffer. */ + wrapper->result_buffers = NULL; } /* FIXME: this may call flush_use_result, which can hit the socket */ /* For prepared statements, wrapper->result is the result metadata */ From 00145dca6ad92674d82ff314a18b8b04841b1af8 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 22 Jun 2015 13:41:13 +0000 Subject: [PATCH 325/783] Whitespace --- ext/mysql2/statement.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 7e1097613..28e4cf624 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -12,7 +12,7 @@ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, inter static void rb_mysql_stmt_mark(void * ptr) { mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; - if(! stmt_wrapper) return; + if (!stmt_wrapper) return; rb_gc_mark(stmt_wrapper->client); } @@ -176,7 +176,7 @@ static VALUE field_count(VALUE self) { static void *nogvl_execute(void *ptr) { MYSQL_STMT *stmt = ptr; - if(mysql_stmt_execute(stmt)) { + if (mysql_stmt_execute(stmt)) { return (void*)Qfalse; } else { return (void*)Qtrue; @@ -186,7 +186,7 @@ static void *nogvl_execute(void *ptr) { static void *nogvl_stmt_store_result(void *ptr) { MYSQL_STMT *stmt = ptr; - if(mysql_stmt_store_result(stmt)) { + if (mysql_stmt_store_result(stmt)) { return (void *)Qfalse; } else { return (void *)Qtrue; @@ -347,8 +347,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { FREE_BINDS; metadata = mysql_stmt_result_metadata(stmt); - if(metadata == NULL) { - if(mysql_stmt_errno(stmt) != 0) { + if (metadata == NULL) { + if (mysql_stmt_errno(stmt) != 0) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. MARK_CONN_INACTIVE(stmt_wrapper->client); From f512a77bbef5ef5e175125d07322fda7d8e73205 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 8 Jul 2015 06:52:58 -0700 Subject: [PATCH 326/783] Test for memory errors by reusing a statement 10000 times --- spec/mysql2/statement_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index f4a764246..b14f52fdb 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -70,6 +70,14 @@ @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end + it "should be reusable 10000 times" do + statement = @client.prepare 'SELECT 1' + 10000.times do + result = statement.execute + expect(result.to_a.length).to eq(1) + end + end + it "should select dates" do statement = @client.prepare 'SELECT NOW()' result = statement.execute From ab392620d274f9d08498759dff18dbe675eb24a1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 8 Jul 2015 06:58:34 -0700 Subject: [PATCH 327/783] Test for memory errors by reusing a statement 1000 times --- spec/mysql2/statement_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index b14f52fdb..f3bf69105 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -70,6 +70,14 @@ @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end + it "should be reusable 1000 times" do + statement = @client.prepare 'SELECT 1' + 1000.times do + result = statement.execute + expect(result.to_a.length).to eq(1) + end + end + it "should be reusable 10000 times" do statement = @client.prepare 'SELECT 1' 10000.times do From c0740eb568481132b4b518da78dc0a3d61203e4d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 10 Jul 2015 08:32:14 +0000 Subject: [PATCH 328/783] Set stmt->bind_result_done = 0 when freeing the result buffers that were bound to a statement handle If the statement handle was previously used, and so mysql_stmt_bind_result was called, and if that result set and bind buffers were freed, MySQL still thinks the result set buffer is available and will prefetch the first result in mysql_stmt_execute. This will corrupt or crash the program. By setting bind_result_done back to 0, we make MySQL think that a result set has never been bound to this statement handle before to prevent the prefetch. Is this a MySQL bug or a problem in the workflow, i.e. should a result buffer be allocated and bound once and then never "unbound" from a statement handle? --- ext/mysql2/result.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index c290eb771..7254e8e9c 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -95,6 +95,15 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { if (wrapper->stmt_wrapper) { mysql_stmt_free_result(wrapper->stmt_wrapper->stmt); + /* MySQL BUG? If the statement handle was previously used, and so + * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed, + * MySQL still thinks the result set buffer is available and will prefetch the + * first result in mysql_stmt_execute. This will corrupt or crash the program. + * By setting bind_result_done back to 0, we make MySQL think that a result set + * has never been bound to this statement handle before to prevent the prefetch. + */ + wrapper->stmt_wrapper->stmt->bind_result_done = 0; + if (wrapper->result_buffers) { unsigned int i; for (i = 0; i < wrapper->numberOfFields; i++) { From b3cfa21edf503a9513435ee8551271571d22d11d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 13 Jul 2015 11:18:51 -0700 Subject: [PATCH 329/783] Fix two spec warnings when using expec to raise_error without a matcher --- spec/em/em_spec.rb | 2 +- spec/mysql2/statement_spec.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 02fc1af85..8890e285c 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -63,7 +63,7 @@ EM.stop_event_loop end end - }.to raise_error + }.to raise_error('some error') end context 'when an exception is raised by the client' do diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index f3bf69105..6e3c790b4 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -40,8 +40,7 @@ it "should raise an exception without a block" do statement = @client.prepare 'SELECT 1' - statement.execute - expect { statement.each }.to raise_error + expect { statement.execute.each }.to raise_error(LocalJumpError) end it "should tell us the result count" do From 7757a66fac7c171bb4e675251c1e6db99c54e70c Mon Sep 17 00:00:00 2001 From: Takatoshi Ono Date: Tue, 14 Jul 2015 13:36:10 +0900 Subject: [PATCH 330/783] Fix encoding if nothing was escaped --- ext/mysql2/client.c | 5 +++++ spec/mysql2/client_spec.rb | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 0d12188fe..0941bc074 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -720,6 +720,11 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) { newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen); if (newLen == oldLen) { /* no need to return a new ruby string if nothing changed */ +#ifdef HAVE_RUBY_ENCODING_H + if (default_internal_enc) { + str = rb_str_export_to_enc(str, default_internal_enc); + } +#endif xfree(newStr); return str; } else { diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index aaf8ae42e..07ebe6d23 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -703,6 +703,17 @@ def run_gc @client.escape "" }.to raise_error(Mysql2::Error) end + + context 'when mysql encoding is not utf8' do + let(:client) { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ujis")) } + + it 'should return a internal encoding string if Encoding.default_internal is set' do + with_internal_encoding Encoding::UTF_8 do + expect(client.escape("\u{30C6}\u{30B9}\u{30C8}")).to eq "\u{30C6}\u{30B9}\u{30C8}" + expect(client.escape("\u{30C6}'\u{30B9}\"\u{30C8}")).to eq "\u{30C6}\\'\u{30B9}\\\"\u{30C8}" + end + end + end end it "should respond to #info" do From 088c0fee00bb9edff073405f21aa7d3115bdb06f Mon Sep 17 00:00:00 2001 From: Takatoshi Ono Date: Tue, 14 Jul 2015 19:08:23 +0900 Subject: [PATCH 331/783] pending test if Encoding is not defined --- spec/mysql2/client_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 07ebe6d23..22714535b 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -705,6 +705,8 @@ def run_gc end context 'when mysql encoding is not utf8' do + before { pending('Encoding is undefined') unless defined?(Encoding) } + let(:client) { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ujis")) } it 'should return a internal encoding string if Encoding.default_internal is set' do From 99c57e9b5fe140620dea833b0a648fa54b6d4fec Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Mon, 6 Jul 2015 08:41:49 +0200 Subject: [PATCH 332/783] Add rake-compiler-dock for building Windows binary gems. --- Gemfile | 1 + tasks/compile.rake | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Gemfile b/Gemfile index 3ce666658..27be870e3 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,7 @@ end group :development do gem 'pry' + gem 'rake-compiler-dock', '~> 0.4.2' end platforms :rbx do diff --git a/tasks/compile.rake b/tasks/compile.rake index 473ad67c9..d98773158 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -86,3 +86,12 @@ else Rake::Task['cross'].prerequisites.unshift 'vendor:mysql:cross' end end + +desc "Build the windows binary gems per rake-compiler-dock" +task 'gem:windows' do + require 'rake_compiler_dock' + RakeCompilerDock.sh <<-EOT + bundle install "--without=test benchmarks development rbx" && + rake cross native gem + EOT +end From 672843559f54c75171019d2a59271a798da7b909 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 30 Jul 2015 05:57:21 -0700 Subject: [PATCH 333/783] Call RakeCompilerDock.sh twice, once for 32-bit and once for 64-bit This is a workaround for a 32-bit vendor/libmysql.dll ending up in the 64-bit platform's gem build. The issue seems to be that rake-compiler unwinds the package dependency graph once after compiling all of the targets, so an output file that varies per target doesn't work. was: Local changes used to cross-compile the 0.3.19 gem --- tasks/compile.rake | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tasks/compile.rake b/tasks/compile.rake index d98773158..e5a903017 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -17,7 +17,7 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| ext.config_options = [ "--with-mysql-dir=#{connector_dir}" ] else ext.cross_compile = true - ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60', 'x64-mingw32'] + ext.cross_platform = ENV['CROSS_PLATFORMS'] ? ENV['CROSS_PLATFORMS'].split(':') : ['x86-mingw32', 'x86-mswin32-60', 'x64-mingw32'] ext.cross_config_options << { 'x86-mingw32' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__), 'x86-mswin32-60' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__), @@ -87,11 +87,19 @@ else end end -desc "Build the windows binary gems per rake-compiler-dock" +desc "Build binary gems for Windows with rake-compiler-dock" task 'gem:windows' do require 'rake_compiler_dock' RakeCompilerDock.sh <<-EOT - bundle install "--without=test benchmarks development rbx" && - rake cross native gem + bundle install + rake clean + rm vendor/libmysql.dll + rake cross native gem CROSS_PLATFORMS=x86-mingw32:x86-mswin32-60 + EOT + RakeCompilerDock.sh <<-EOT + bundle install + rake clean + rm vendor/libmysql.dll + rake cross native gem CROSS_PLATFORMS=x64-mingw32 EOT end From 01752679bb4dbca5ca44939f80f6f10a63ecb4f1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 1 Aug 2015 14:14:11 -0700 Subject: [PATCH 334/783] Add new option :sslverify --- README.md | 6 ++++-- lib/mysql2/client.rb | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ab99cdc68..3182975b5 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,8 @@ Setting any of the following options will enable an SSL connection, but only if your MySQL client library and server have been compiled with SSL support. MySQL client library defaults will be used for any parameters that are left out or set to nil. Relative paths are allowed, and may be required by managed -hosting providers such as Heroku. +hosting providers such as Heroku. Set `:sslverify => true` to require that the +server presents a valid certificate. ``` ruby Mysql2::Client.new( @@ -195,7 +196,8 @@ Mysql2::Client.new( :sslcert => '/path/to/client-cert.pem', :sslca => '/path/to/ca-cert.pem', :sslcapath => '/path/to/cacerts', - :sslcipher => 'DHE-RSA-AES256-SHA' + :sslcipher => 'DHE-RSA-AES256-SHA', + :sslverify => true, ) ``` diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 2481efd1c..dc5088424 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -44,6 +44,12 @@ def initialize(opts = {}) ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher) ssl_set(*ssl_options) if ssl_options.any? + # SSL verify is a connection flag rather than a mysql_ssl_set option + flags = 0 + flags |= @query_options[:connect_flags] + flags |= opts[:flags] if opts[:flags] + flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] and ssl_options.any? + if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) } warn "============= WARNING FROM mysql2 =============" warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future." @@ -57,7 +63,6 @@ def initialize(opts = {}) port = opts[:port] database = opts[:database] || opts[:dbname] || opts[:db] socket = opts[:socket] || opts[:sock] - flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags] # Correct the data types before passing these values down to the C level user = user.to_s unless user.nil? From 3907e94b1311d6cccd2a58004b15f570404ca728 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 16 Aug 2015 22:40:32 -0700 Subject: [PATCH 335/783] Check if O_CLOEXEC is defined before using it in open() Previously we checked if SOCK_CLOEXEC was defined before using SOCK_CLOEXEC, but we continued to check for SOCK_CLOEXEC after we switched to using O_CLOEXEC. Also use fcntl(F_GETFD) before fcntl(F_SETFD) to comport with the Standard. --- ext/mysql2/client.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 0941bc074..a94671dac 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -170,23 +170,31 @@ static void *nogvl_connect(void *ptr) { */ static VALUE invalidate_fd(int clientfd) { -#ifdef SOCK_CLOEXEC +#ifdef O_CLOEXEC /* Atomically set CLOEXEC on the new FD in case another thread forks */ int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC); - if (sockfd < 0) { - /* Maybe SOCK_CLOEXEC is defined but not available on this kernel */ - int sockfd = open("/dev/null", O_RDWR); - fcntl(sockfd, F_SETFD, FD_CLOEXEC); - } #else - /* Well we don't have SOCK_CLOEXEC, so just set FD_CLOEXEC quickly */ - int sockfd = open("/dev/null", O_RDWR); - fcntl(sockfd, F_SETFD, FD_CLOEXEC); + /* Well we don't have O_CLOEXEC, trigger the fallback code below */ + int sockfd = -1; #endif if (sockfd < 0) { - /* - * Cannot raise here, because one or both of the following may be true: + /* Either O_CLOEXEC wasn't defined at compile time, or it was defined at + * compile time, but isn't available at run-time. So we'll just be quick + * about setting FD_CLOEXEC now. + */ + int flags; + sockfd = open("/dev/null", O_RDWR); + flags = fcntl(sockfd, F_GETFD); + /* Do the flags dance in case there are more defined flags in the future */ + if (flags != -1) { + flags |= FD_CLOEXEC; + fcntl(sockfd, F_SETFD, flags); + } + } + + if (sockfd < 0) { + /* Cannot raise here, because one or both of the following may be true: * a) we have no GVL (in C Ruby) * b) are running as a GC finalizer */ From 7973f8452d4480b1b1521598cb4b604b5b2901e1 Mon Sep 17 00:00:00 2001 From: Mark Nadig Date: Mon, 31 Aug 2015 08:11:50 -0600 Subject: [PATCH 336/783] Add CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..9a5bd0f25 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +Changes are maintained under [Releases](https://github.com/brianmario/mysql2/releases) From 6c64816c848a20026656e0e6b55defafa85fcb15 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 24 Aug 2015 23:24:44 -0700 Subject: [PATCH 337/783] Minor verbiage updates to extconf.rb --- ext/mysql2/extconf.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 5d85deed5..492766c45 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -3,12 +3,11 @@ def asplode lib if RUBY_PLATFORM =~ /mingw|mswin/ - abort "-----\n#{lib} is missing. please check your installation of mysql and try again.\n-----" + abort "-----\n#{lib} is missing. Check your installation of MySQL or Connector/C, and try again.\n-----" elsif RUBY_PLATFORM =~ /darwin/ - abort "-----\n#{lib} is missing. Try 'brew install mysql', check your installation of mysql and try again.\n-----" + abort "-----\n#{lib} is missing. You may need to 'brew install mysql' or 'port install mysql', and try again.\n-----" else - abort "-----\n#{lib} is missing. Try 'apt-get install libmysqlclient-dev' or -'yum install mysql-devel', check your installation of mysql and try again.\n-----" + abort "-----\n#{lib} is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again.\n-----" end end From 709c83dff1fed32fa1f4ca98624c27c8ed1f07f1 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Sat, 5 Sep 2015 03:45:11 +0000 Subject: [PATCH 338/783] Fix uninitialized constant Timeout Follow-up to 9f006849095b47c8c993c2f5997df589fcc2580c --- lib/mysql2/client.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index dc5088424..595cc8793 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -80,8 +80,10 @@ def self.default_query_options end if Thread.respond_to?(:handle_interrupt) + require 'timeout' + def query(sql, options = {}) - Thread.handle_interrupt(Timeout::ExitException => :never) do + Thread.handle_interrupt(::Timeout::ExitException => :never) do _query(sql, @query_options.merge(options)) end end From eaa402765c46b0a8ddab67194e4417763e7a3f3f Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 2 Jun 2015 07:08:54 -0700 Subject: [PATCH 339/783] Statement spec with comparison and like operators --- spec/mysql2/statement_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 6e3c790b4..daedebadf 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -83,6 +83,20 @@ result = statement.execute expect(result.to_a.length).to eq(1) end + + it "should handle comparisons and likes" do + @client.query 'USE test' + @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int, b varchar(10))' + @client.query 'INSERT INTO mysql2_stmt_q (a, b) VALUES (1, "Hello"), (2, "World")' + statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE a < ?' + results = statement.execute(2) + results.first.should == {"a" => 1, "b" => "Hello"} + + statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE b LIKE ?' + results = statement.execute('%orld') + results.first.should == {"a" => 2, "b" => "World"} + + @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end it "should select dates" do From aeb4f7000790d58d3dae88cdc97938e3f6faa7d8 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 2 Jun 2015 07:09:09 -0700 Subject: [PATCH 340/783] README updates for Prepared Statements --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3182975b5..24b4050a5 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,14 @@ This one is not. It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9, unless Encoding.default_internal is set then it'll convert from UTF-8 to that encoding] and uses encoding-aware MySQL API calls where it can. -The API consists of two classes: +The API consists of three classes: `Mysql2::Client` - your connection to the database. `Mysql2::Result` - returned from issuing a #query on the connection. It includes Enumerable. +`Mysql2::Statement` - returned from issuing a #prepare on the connection. Execute the statement to get a Result. + ## Installing ### General Instructions ``` sh @@ -153,6 +155,20 @@ results.each(:as => :array) do |row| end ``` +Prepared statements are supported, as well. In a prepared statement, use a `?` +in place of each value and then execute the statement to retrieve a result set. +Pass your arguments to the execute method in the same number and order as the +question marks in the statement. + +``` ruby +statement = @client.prepare("SELECT * FROM users WHERE login_count = ?") +result1 = statement.execute(1) +result2 = statement.execute(2) + +statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND location LIKE ?") +result = statement.execute(1, "CA") +``` + ## Connection options You may set the following connection options in Mysql2::Client.new(...): @@ -538,4 +554,7 @@ though. * Yury Korolev (http://github.com/yury) - for TONS of help testing the Active Record adapter * Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness * Mike Perham (http://github.com/mperham) - Async Active Record adapter (uses Fibers and EventMachine) -* Aaron Stone (http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support. +* Aaron Stone (http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support +* Kouhei Ueno (https://github.com/nyaxt) - for the original work on Prepared Statements way back in 2012 +* John Cant (http://github.com/johncant) - polishing and updating Prepared Statements support +* Justin Case (http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged From 2f8034b1a3b8958550206ac8c8417f84d02c641c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 5 Jun 2015 06:40:47 -0700 Subject: [PATCH 341/783] README updates after cleanups branch --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 24b4050a5..390629060 100644 --- a/README.md +++ b/README.md @@ -558,3 +558,4 @@ though. * Kouhei Ueno (https://github.com/nyaxt) - for the original work on Prepared Statements way back in 2012 * John Cant (http://github.com/johncant) - polishing and updating Prepared Statements support * Justin Case (http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged +* Tamir Duberstein (http://github.com/tamird) - for help with timeouts and all around updates and cleanups From 2cd8550c6bddf88a37d43852aaa9c2a08f7a1fd1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 4 Sep 2015 01:42:55 -0700 Subject: [PATCH 342/783] Adding myself as an author --- mysql2.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql2.gemspec b/mysql2.gemspec index a9090b8b7..2132e1a6c 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -3,9 +3,9 @@ require File.expand_path('../lib/mysql2/version', __FILE__) Gem::Specification.new do |s| s.name = %q{mysql2} s.version = Mysql2::VERSION - s.authors = ["Brian Lopez"] + s.authors = ['Brian Lopez', 'Aaron Stone'] s.license = "MIT" - s.email = %q{seniorlopez@gmail.com} + s.email = ['seniorlopez@gmail.com', 'aaron@serendipity.cx'] s.extensions = ["ext/mysql2/extconf.rb"] s.homepage = %q{http://github.com/brianmario/mysql2} s.rdoc_options = ["--charset=UTF-8"] From 4d76557499b762d5a62aebb7f6a56510ad221eab Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 7 Sep 2015 12:11:54 -0700 Subject: [PATCH 343/783] Bump version to 0.4.0 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 303c549ce..554c06fe0 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.3.18" + VERSION = "0.4.0" end From cdc9321b34a82cc58fc9901c3a1bfba4f1db7a47 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 7 Sep 2015 12:50:01 -0700 Subject: [PATCH 344/783] That statement spec needs to be in expect format --- spec/mysql2/statement_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index daedebadf..1a1f6306c 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -83,6 +83,7 @@ result = statement.execute expect(result.to_a.length).to eq(1) end + end it "should handle comparisons and likes" do @client.query 'USE test' @@ -90,11 +91,11 @@ @client.query 'INSERT INTO mysql2_stmt_q (a, b) VALUES (1, "Hello"), (2, "World")' statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE a < ?' results = statement.execute(2) - results.first.should == {"a" => 1, "b" => "Hello"} + expect(results.first).to eq({"a" => 1, "b" => "Hello"}) statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE b LIKE ?' results = statement.execute('%orld') - results.first.should == {"a" => 2, "b" => "World"} + expect(results.first).to eq({"a" => 2, "b" => "World"}) @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end From 094ebd2e4306b01965d4d00fa11f0ed6ba6ad269 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 7 Sep 2015 13:05:14 -0700 Subject: [PATCH 345/783] Build fix for Windows --- ext/mysql2/client.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index a94671dac..ca1cd2956 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -893,15 +893,17 @@ static VALUE rb_mysql_client_server_info(VALUE self) { * * Return the file descriptor number for this client. */ -static VALUE rb_mysql_client_socket(VALUE self) { #ifndef _WIN32 +static VALUE rb_mysql_client_socket(VALUE self) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); return INT2NUM(wrapper->client->net.fd); +} #else +static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) { rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows"); -#endif } +#endif /* call-seq: * client.last_id From fe39b00288d23ca38a3978d2825e894aefc18f02 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 3 Sep 2015 23:14:04 -0700 Subject: [PATCH 346/783] Fix for "Table 'performance_schema.session_variables' doesn't exist" --- .travis_mysql57.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index 45a364bd5..92aa3f39e 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -9,4 +9,6 @@ apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x8C718D3B5072 add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.7-dmr' apt-get update -apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -y install mysql-server libmysqlclient-dev +apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew -y install mysql-server libmysqlclient-dev + +mysql_upgrade -u root --force --upgrade-system-tables From 6b8f494c2d58b4f8b06917d52f3904a606c29cab Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 4 Sep 2015 01:42:13 -0700 Subject: [PATCH 347/783] Merge the SSL settings into the root credentials, turn on sslverify --- spec/mysql2/client_spec.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 22714535b..194a45061 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -126,11 +126,13 @@ def connect *args ssl_client = nil expect { ssl_client = Mysql2::Client.new( - :sslkey => '/etc/mysql/client-key.pem', - :sslcert => '/etc/mysql/client-cert.pem', - :sslca => '/etc/mysql/ca-cert.pem', - :sslcapath => '/etc/mysql/', - :sslcipher => 'DHE-RSA-AES256-SHA' + DatabaseCredentials['root'].merge( + :sslkey => '/etc/mysql/client-key.pem', + :sslcert => '/etc/mysql/client-cert.pem', + :sslca => '/etc/mysql/ca-cert.pem', + :sslcipher => 'DHE-RSA-AES256-SHA', + :sslverify => true + ) ) }.not_to raise_error From ea2a0208ec2e9a94c678395a2571841e08780e80 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 4 Sep 2015 03:12:45 -0700 Subject: [PATCH 348/783] Some apparmor craziness --- .travis_mysql57.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index 92aa3f39e..53f2f1517 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -12,3 +12,9 @@ apt-get update apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew -y install mysql-server libmysqlclient-dev mysql_upgrade -u root --force --upgrade-system-tables + +# Replace the final line of the mysql apparmor, allowing /etc/mysql/*.pem +sed -ie '$ s|}|\ + /etc/mysql/*.pem r,\ +}|' /etc/apparmor.d/usr.sbin.mysqld +service apparmor restart From 4c0e67643eb521a2cbf5714c3496edf7223d5f04 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 4 Sep 2015 02:34:39 -0700 Subject: [PATCH 349/783] Use pre-generated SSL certs with a dummy hostname for the specs --- .travis.yml | 3 ++ .travis_ssl.sh | 55 ++++------------------------------- appveyor.yml | 2 ++ spec/mysql2/client_spec.rb | 1 + spec/ssl/ca-cert.pem | 17 +++++++++++ spec/ssl/ca-key.pem | 27 +++++++++++++++++ spec/ssl/ca.cnf | 22 ++++++++++++++ spec/ssl/cert.cnf | 22 ++++++++++++++ spec/ssl/client-cert.pem | 17 +++++++++++ spec/ssl/client-key.pem | 27 +++++++++++++++++ spec/ssl/client-req.pem | 15 ++++++++++ spec/ssl/gen_certs.sh | 48 ++++++++++++++++++++++++++++++ spec/ssl/pkcs8-client-key.pem | 28 ++++++++++++++++++ spec/ssl/pkcs8-server-key.pem | 28 ++++++++++++++++++ spec/ssl/server-cert.pem | 17 +++++++++++ spec/ssl/server-key.pem | 27 +++++++++++++++++ spec/ssl/server-req.pem | 15 ++++++++++ 17 files changed, 322 insertions(+), 49 deletions(-) create mode 100644 spec/ssl/ca-cert.pem create mode 100644 spec/ssl/ca-key.pem create mode 100644 spec/ssl/ca.cnf create mode 100644 spec/ssl/cert.cnf create mode 100644 spec/ssl/client-cert.pem create mode 100644 spec/ssl/client-key.pem create mode 100644 spec/ssl/client-req.pem create mode 100644 spec/ssl/gen_certs.sh create mode 100644 spec/ssl/pkcs8-client-key.pem create mode 100644 spec/ssl/pkcs8-server-key.pem create mode 100644 spec/ssl/server-cert.pem create mode 100644 spec/ssl/server-key.pem create mode 100644 spec/ssl/server-req.pem diff --git a/.travis.yml b/.travis.yml index 4a2a589cd..1a3eeebd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,9 @@ before_install: - bash .travis_setup.sh os: - linux +addons: + hosts: + - mysql2gem.example.com rvm: - 1.8.7 - 1.9.3 diff --git a/.travis_ssl.sh b/.travis_ssl.sh index a2fc029a9..d022e9570 100644 --- a/.travis_ssl.sh +++ b/.travis_ssl.sh @@ -2,54 +2,14 @@ set -eu -# Wherever MySQL configs live, go there (this is for cross-platform) -cd $(my_print_defaults --help | grep my.cnf | xargs find 2>/dev/null | xargs dirname) - -# Create config files to run openssl in batch mode -# Set the CA startdate to yesterday to avoid "ASN: before date in the future" -# (there can be 90k seconds in a daylight saving change day) - -echo " -[ ca ] -default_startdate = $(ruby -e 'print (Time.now - 90000).strftime("%y%m%d000000Z")') - -[ req ] -distinguished_name = req_distinguished_name - -[ req_distinguished_name ] -# If this isn't set, the error is "error, no objects specified in config file" -commonName = Common Name (hostname, IP, or your name) - -countryName_default = US -stateOrProvinceName_default = CA -localityName_default = San Francisco -0.organizationName_default = test_example -organizationalUnitName_default = Testing -emailAddress_default = admin@example.com -" | tee ca.cnf cert.cnf +# Make sure there is an /etc/mysql +mkdir -p /etc/mysql -# The client and server certs must have a diferent common name than the CA -# to avoid "SSL connection error: error:00000001:lib(0):func(0):reason(1)" +# Copy the local certs to /etc/mysql +cp spec/ssl/*pem /etc/mysql/ -echo " -commonName_default = ca_name -" >> ca.cnf - -echo " -commonName_default = cert_name -" >> cert.cnf - -# Generate a set of certificates -openssl genrsa -out ca-key.pem 2048 -openssl req -new -x509 -nodes -days 1000 -key ca-key.pem -out ca-cert.pem -batch -config ca.cnf -openssl req -newkey rsa:2048 -days 1000 -nodes -keyout pkcs8-server-key.pem -out server-req.pem -batch -config cert.cnf -openssl x509 -req -in server-req.pem -days 1000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem -openssl req -newkey rsa:2048 -days 1000 -nodes -keyout pkcs8-client-key.pem -out client-req.pem -batch -config cert.cnf -openssl x509 -req -in client-req.pem -days 1000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem - -# Convert format from PKCS#8 to PKCS#1 -openssl rsa -in pkcs8-server-key.pem -out server-key.pem -openssl rsa -in pkcs8-client-key.pem -out client-key.pem +# Wherever MySQL configs live, go there (this is for cross-platform) +cd $(my_print_defaults --help | grep my.cnf | xargs find 2>/dev/null | xargs dirname) # Put the configs into the server echo " @@ -58,6 +18,3 @@ ssl-ca=/etc/mysql/ca-cert.pem ssl-cert=/etc/mysql/server-cert.pem ssl-key=/etc/mysql/server-key.pem " >> my.cnf - -# Wait until the minute moves to ensure that the SSL cert is within its valid range -ruby -e 'start = Time.now.min; while Time.now.min == start; sleep 2; end' diff --git a/appveyor.yml b/appveyor.yml index 3dff9f8b6..cd58cb533 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -35,3 +35,5 @@ cache: - vendor services: - mysql +hosts: + mysql2gem.example.com: 127.0.0.1 diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 194a45061..1e74f8079 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -127,6 +127,7 @@ def connect *args expect { ssl_client = Mysql2::Client.new( DatabaseCredentials['root'].merge( + 'host' => 'mysql2gem.example.com', # must match the certificates :sslkey => '/etc/mysql/client-key.pem', :sslcert => '/etc/mysql/client-cert.pem', :sslca => '/etc/mysql/ca-cert.pem', diff --git a/spec/ssl/ca-cert.pem b/spec/ssl/ca-cert.pem new file mode 100644 index 000000000..cf9b8d511 --- /dev/null +++ b/spec/ssl/ca-cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqjCCAZICCQDbDS+Z2mpWkDANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxj +YV9teXNxbDJnZW0wHhcNMTUwOTA5MDQ1NzIxWhcNMjUwNzE4MDQ1NzIxWjAXMRUw +EwYDVQQDDAxjYV9teXNxbDJnZW0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDFnpc22lPFtdPELsIffsDt8cD2Hkt47nGMcKQ9n4U98yAg+fodipyP1Bn0 +2OeaONqpttJIET7HxlGrtugPtV/O8XZHlhfAHrRUDMFZJhgnnqK+c/7fRGeB0Eqw +ljBlRD/dDL3bFq5hVBC9QsGi5k03r+xLPKm5ccAr4WtofcoKXqEbSO6koTSrsGG5 +7inlldM2AVzrY2kXbe0jAyNvYmDL2ycN8G2wObogPWDfITQRhOxfkzKIQiEhQF2Y +/DlhT7IbIarBIm6abf6JxZ6/Sm5XyVNEWOnryXM6rKyVeGktCxLHNmxx5eKYs440 +8hNgURa8pB+aZaiokkwhM1+jmE83AgMBAAEwDQYJKoZIhvcNAQELBQADggEBACrQ +umqygXkkbff5Jqf6AYi30U3c+byX+IButRKXN9Ete2LPcT76o/snS9Lexf3KQsIy +a2Tcc9adak7pBf7FgHdiZkWiQp3MDgx2gJu6Uu6TNzfT8jy2JrHyBWw4ydEvhyA8 +cgelTHSaudafKeQgU4KYc8bqafYFILkWxPzgtwitENIDfx/SHt65BWaQZjYJlFou +zPZXeoT3lAwKGYqIvwPvBTC23cXg/Swt/mcKe3/Xxjx85Dw/9vi6a9+VQwlOojgd +w2o07xkIcJcI0Oxyp3mD0U5wAmBQGI76Yi9ZDROHF65KEXfQ3tYKl2vR7CXpcJ4+ +7+fVsE8+dADJdZIiuaA= +-----END CERTIFICATE----- diff --git a/spec/ssl/ca-key.pem b/spec/ssl/ca-key.pem new file mode 100644 index 000000000..bbdadb7cf --- /dev/null +++ b/spec/ssl/ca-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAxZ6XNtpTxbXTxC7CH37A7fHA9h5LeO5xjHCkPZ+FPfMgIPn6 +HYqcj9QZ9NjnmjjaqbbSSBE+x8ZRq7boD7VfzvF2R5YXwB60VAzBWSYYJ56ivnP+ +30RngdBKsJYwZUQ/3Qy92xauYVQQvULBouZNN6/sSzypuXHAK+FraH3KCl6hG0ju +pKE0q7Bhue4p5ZXTNgFc62NpF23tIwMjb2Jgy9snDfBtsDm6ID1g3yE0EYTsX5My +iEIhIUBdmPw5YU+yGyGqwSJumm3+icWev0puV8lTRFjp68lzOqyslXhpLQsSxzZs +ceXimLOONPITYFEWvKQfmmWoqJJMITNfo5hPNwIDAQABAoIBAQClkmtFRQVdKCum +Ojrg4nVIpv2x983qI3U1YobpLocXUWVA29BIAgOMqfuZXkYlu67Q9OEYCoLcJHf2 +88dYqfD81OfxsHpzuAYESa+RPs6MG2hlQ5BuhcRnShnZ++vOXLFZRjynnEg8OY/Q +0makUmqt1pKWstvNCNYmrbYtFP87UXQCF06zkhM0cZJvVt0gPZGUituWI0uAoE51 +U8+WSwD/T761BHM6BuMn56mfZkP5jeVIFl0iFha9rGR0Z6K8mVQAYQAUtUx9tN/3 +a8fEOcYulq/9R5tMRWtsF8LD8DGQBNkY3e/WKDuZtLw2Dl3L09gxVH9DXCLiYU5d +OG3JmqDpAoGBAP08yq143H4n6yGT9DC8YjaLgN0VoenK21CEqhwtGWipc/kbGooe +/jaHl6bo9v1GOGlJieqSUqsXNltS7FOLhGFAQFwMYZ3V/h15Vx23Z+xkCCHIB6HH +YJZqkQY7Jt86wXcaLU5j9fxM+BY+8Ets4bVhZN9Ai6AnlTz0+d8UJG+bAoGBAMfG +efYrdjTKI5eK9aiVJyoh57BEPOsTsave2U+R8Q+fErQ0QD0UmbWgwYGgkPuDrFYT +owg09EEz88KONv18VZ+mB1qfyQUoOL6rWIGxXC08upy2i9100PaBFiYlkLNoK7yJ +bze0rFSiFclJJXZGzEaVvcEdKnXxfhttaJwQGK6VAoGBAOQEUvJzuwWU5/CqCdvA +JCa84eEv00RxtZwAeDM6oIBO4+/O6cyoL3nmCTTu20YebjjPUHF4IxuOoREFz2lC +XIY8ljbLpzG5N0BOu5Q0SkzdnTzdoZGXtm55se+MX2nsu7qERXsqIpl0rIVLUo53 +kZwCABPNSGuCeKwUYNDukAg1AoGBALiHHSqEVKhIOn4FDgqM0uM49CA9t6NPyqI9 +sq6r2GWcgpNPXDLPL3e0KGlK3gBkTLApbULsXt1HVpZT9HlJ+nD/0/UieHS6BUgh +Txxkrgbe/GQ6vZBuEYJQFBxiQHlm9Fcu/zsOOMvn94W4edD5bkCYmfChtxHAYcKF +2cWlnJbNAoGAWMV4GIY2DYlztXdyMVuPwsjPcSPMmL8Nc2ATWYRfcoG0Zl0yvwPh +2VOu7Q/7bNF2LOe6lPe1hoeB6rT44IYZaWMo3ikY8xW9RztOLSv8E9uE1K9yq8OA +P8QzXmr1Lga+hoEmMHc2biEJNeF6iAcAFfrHj9Sr7w5PC8g4A3PlCvU= +-----END RSA PRIVATE KEY----- diff --git a/spec/ssl/ca.cnf b/spec/ssl/ca.cnf new file mode 100644 index 000000000..07374ad3e --- /dev/null +++ b/spec/ssl/ca.cnf @@ -0,0 +1,22 @@ + +[ ca ] +# January 1, 2015 +default_startdate = 2015010360000Z + +[ req ] +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +# If this isn't set, the error is error, no objects specified in config file +commonName = Common Name (hostname, IP, or your name) + +countryName_default = US +stateOrProvinceName_default = CA +localityName_default = San Francisco +0.organizationName_default = mysql2_gem +organizationalUnitName_default = Mysql2Gem +emailAddress_default = mysql2gem@example.com + + +commonName_default = ca_mysql2gem + diff --git a/spec/ssl/cert.cnf b/spec/ssl/cert.cnf new file mode 100644 index 000000000..ca1dce701 --- /dev/null +++ b/spec/ssl/cert.cnf @@ -0,0 +1,22 @@ + +[ ca ] +# January 1, 2015 +default_startdate = 2015010360000Z + +[ req ] +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +# If this isn't set, the error is error, no objects specified in config file +commonName = Common Name (hostname, IP, or your name) + +countryName_default = US +stateOrProvinceName_default = CA +localityName_default = San Francisco +0.organizationName_default = mysql2_gem +organizationalUnitName_default = Mysql2Gem +emailAddress_default = mysql2gem@example.com + + +commonName_default = mysql2gem.example.com + diff --git a/spec/ssl/client-cert.pem b/spec/ssl/client-cert.pem new file mode 100644 index 000000000..3887246fe --- /dev/null +++ b/spec/ssl/client-cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy +Z2VtMB4XDTE1MDkwOTA0NTcyMVoXDTI1MDcxODA0NTcyMVowIDEeMBwGA1UEAwwV +bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA1ZuBf1FVJqil7/LvnXqPd43ujo0xqbFy7QrqmM5U/UM3ggMCf2Gr2/Wo +ZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMUuH+Uiv9UwmOFl9Em3FXQQ8SG7fV7651u +AUskNgfEqoy+f+uvi1P155rHNDx7Yw6i+wwfpLGTU0boMnLL6cO/KcIbZlx4/2Lq +r5sYbpIqhz46bbG+fIhvepruH9h7WVWqAibTqymYrA3T03O/HWTOqfq03gM7Oe3t +JvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2Ajh7Yx9DQ1gqClTglbPFHNiWPcGACg+W +2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA2 +qTbfgDm0IG8x1qP61ztT9F2WRwG7cp6qHT5oB5wDcOUFes9QJjeB8RoIkB+hRlqG +J6/Tbxs49d7oKhOQ0UaTnfIKC5m0UFYFGc3lUcwxQyggOWx9XV5ZmGb48+RLFnDV +Gfcs/hvfem6Xfpgzr8bGs2ZM9x1j9YnXNJVePmKwktjCPnXPOeHyxNZPA+CWHed/ +dNg1IWuQnnp20LgNRARCTgR/ONAJNUfh2GqRLq2JOf0cyhNlsKQ3epkeUyc72knI +oWVxPluQYvFHN+xif0FMGVLM8lz0b+6uPJDA2Km70B/iorMRVb0vbMeFrMmQ5UgM +4tplX52P2vb6JNnektfR +-----END CERTIFICATE----- diff --git a/spec/ssl/client-key.pem b/spec/ssl/client-key.pem new file mode 100644 index 000000000..d75294225 --- /dev/null +++ b/spec/ssl/client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA1ZuBf1FVJqil7/LvnXqPd43ujo0xqbFy7QrqmM5U/UM3ggMC +f2Gr2/WoZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMUuH+Uiv9UwmOFl9Em3FXQQ8SG +7fV7651uAUskNgfEqoy+f+uvi1P155rHNDx7Yw6i+wwfpLGTU0boMnLL6cO/KcIb +Zlx4/2Lqr5sYbpIqhz46bbG+fIhvepruH9h7WVWqAibTqymYrA3T03O/HWTOqfq0 +3gM7Oe3tJvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2Ajh7Yx9DQ1gqClTglbPFHNiW +PcGACg+W2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQIDAQABAoIBAEWhGjZZWctvQCAW +bbtEv012a6P2LJEnMdJJM6253IRuC8MKnh7NxMq/qjOWK0OX+R+tQ0qt1Udk9H6U +92SAAHAkHaYCmHYywvtWm66gU+2Q34Gnp2AcHFfyinBLgTNHlvkNRe/G8QMWzFrB +3luNt57Tn5b8Hbh+1gpYW8pOF2BMgIsLRK+8b26TKUWrSCc/ZxOSY4wmrNybxkgr +HGt27lwIN0cvJZbmQvHevNzzCn+bYoo2K1MQj34xHbZ2NLqKqFVlSJtr9+BHffAc +fkcf+V+D+FkitUVkha9qXa02wtLzYSF+Q5Ef3kQQs6hs/HOdN16g17l9QC6Mk1vm +a9yV5CECgYEA/9FglQmFimwBCOWEvjkZzoXFusuvRWRgAPU/1c9DAYRS2GfOkjlH +RPAltczdXh4EQ0NkCqHH7JWgrdXGonKg4fcITumdwcYKV5QfmKBO4onAboEM0Wq7 +wjifuga7npQhPnGvkXFDamVz5McQPObvV42VAUwk1N00gOYw/46ryLkCgYEA1cJv +jHAq0DKlUGXKyZ+ixsogRpwTQvND/qUquSLgD/KgfeT+70AnsEF6DbVLKoaJ35CF +ju83VYLfeBljq+E/lgmAyaChplORRXcu+xPQE4rbp0MbsoBOYGNWLFAw3twGsQf9 +iuAtCVxij/hhj4FWRebYHMnV6Min2VPbZdASNnUCgYBIiX8gY3XJPTzB4ArWwWwu +4kGh6NWHEKIkQ2ZZYw615GZ1VGH/llw+EPYwaamvYUWGKRq55QvCat8Hy6EqOOSj +jh99+MIxyszt7mNTLMmRdMvqyY7v5prcxJ+N6RDUM16FzUiiLgKWrbPCACv7iOP+ +6HeCyat77ElR73OfUz4kiQKBgH+2r9cEnU/PMp4ac1KLokGLOkV1srxpg9J89E2w +3JYqrGELlJV1i0DvnfDaxJIf1/hO7L09h537l3C2Gqry5X7LJrtQ0cQCYeVTFCrG +56cFa78/hSjdJ/bG4xGOx+QfKZBT6dQzpDTXkbva9s86w0T4a16n6LowSLi8NXVb +H8aRAoGBAKzlt6deB+ASIrGH6mM0eLxF1OcNTB+rE4AJxoUyO1oAmCv9UeK3IzwP +ohhmo/kEOSCVG6WE+6+r9mojcoHu3ZrobVKl59R7KMdzunMXqxZcXeTqjvqdTtV7 +rWuEz/TKIe7o0Tx19XVGuNftyx2pLuspSAAbZ+YAQJtzmLzsGkss +-----END RSA PRIVATE KEY----- diff --git a/spec/ssl/client-req.pem b/spec/ssl/client-req.pem new file mode 100644 index 000000000..fc077486b --- /dev/null +++ b/spec/ssl/client-req.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ZuBf1FVJqil7/LvnXqPd43u +jo0xqbFy7QrqmM5U/UM3ggMCf2Gr2/WoZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMU +uH+Uiv9UwmOFl9Em3FXQQ8SG7fV7651uAUskNgfEqoy+f+uvi1P155rHNDx7Yw6i ++wwfpLGTU0boMnLL6cO/KcIbZlx4/2Lqr5sYbpIqhz46bbG+fIhvepruH9h7WVWq +AibTqymYrA3T03O/HWTOqfq03gM7Oe3tJvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2 +Ajh7Yx9DQ1gqClTglbPFHNiWPcGACg+W2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQID +AQABoAAwDQYJKoZIhvcNAQELBQADggEBAB05YaBSyAKCgHfBWhpZ1+OOVp1anr2v +TkStnqmNrNM2qBJXjfrythPTX4EJAt7+eNdH/6IVA93qKC/EUQVuMjgfMmMUaM+m +5pqfAo95w7vUY147U9nbC+EIo2u1KOVTNTgl45H372/1vCwTHZYu2atCk4tN3ueO +0O2XW89Kq94/7PDAExN2PhZdeATVX9dPNT+7ZUDNe8cuq9v0YCHy+2JN2WkplxcG +kMyCE3YYLnd96YtWiS9DOUib3+b7FwyGe0dXeLVw1br3NZGCZrybyfmnAQfiouAF +9nMxKIpWFSx00ubGrUefOQqp6nuk27n+scgr4+d6dBXz9efEEvTbLKA= +-----END CERTIFICATE REQUEST----- diff --git a/spec/ssl/gen_certs.sh b/spec/ssl/gen_certs.sh new file mode 100644 index 000000000..d55872d0f --- /dev/null +++ b/spec/ssl/gen_certs.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -eu + +echo " +[ ca ] +# January 1, 2015 +default_startdate = 2015010360000Z + +[ req ] +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +# If this isn't set, the error is "error, no objects specified in config file" +commonName = Common Name (hostname, IP, or your name) + +countryName_default = US +stateOrProvinceName_default = CA +localityName_default = San Francisco +0.organizationName_default = mysql2_gem +organizationalUnitName_default = Mysql2Gem +emailAddress_default = mysql2gem@example.com +" | tee ca.cnf cert.cnf + +# The client and server certs must have a diferent common name than the CA +# to avoid "SSL connection error: error:00000001:lib(0):func(0):reason(1)" + +echo " +commonName_default = ca_mysql2gem +" >> ca.cnf + +echo " +commonName_default = mysql2gem.example.com +" >> cert.cnf + +# Generate a set of certificates +openssl genrsa -out ca-key.pem 2048 +openssl req -new -x509 -nodes -days 3600 -key ca-key.pem -out ca-cert.pem -batch -config ca.cnf +openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-server-key.pem -out server-req.pem -batch -config cert.cnf +openssl x509 -req -in server-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem +openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-client-key.pem -out client-req.pem -batch -config cert.cnf +openssl x509 -req -in client-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem + +# Convert format from PKCS#8 to PKCS#1 +openssl rsa -in pkcs8-server-key.pem -out server-key.pem +openssl rsa -in pkcs8-client-key.pem -out client-key.pem + +echo "done" diff --git a/spec/ssl/pkcs8-client-key.pem b/spec/ssl/pkcs8-client-key.pem new file mode 100644 index 000000000..a4858b4a0 --- /dev/null +++ b/spec/ssl/pkcs8-client-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVm4F/UVUmqKXv +8u+deo93je6OjTGpsXLtCuqYzlT9QzeCAwJ/Yavb9ahkkY9OTU0VqJTIzmaFKWCL +T9IY8Sn0kxS4f5SK/1TCY4WX0SbcVdBDxIbt9XvrnW4BSyQ2B8SqjL5/66+LU/Xn +msc0PHtjDqL7DB+ksZNTRugycsvpw78pwhtmXHj/YuqvmxhukiqHPjptsb58iG96 +mu4f2HtZVaoCJtOrKZisDdPTc78dZM6p+rTeAzs57e0m8lupfYt5xC+LZJtChfxz +YytDpfvEN7YCOHtjH0NDWCoKVOCVs8Uc2JY9wYAKD5bam0Jj9DZJ0/mHV22PlLDl +XAvd28KNAgMBAAECggEARaEaNllZy29AIBZtu0S/TXZro/YskScx0kkzrbnchG4L +wwqeHs3Eyr+qM5YrQ5f5H61DSq3VR2T0fpT3ZIAAcCQdpgKYdjLC+1abrqBT7ZDf +gaenYBwcV/KKcEuBM0eW+Q1F78bxAxbMWsHeW423ntOflvwduH7WClhbyk4XYEyA +iwtEr7xvbpMpRatIJz9nE5JjjCas3JvGSCsca3buXAg3Ry8lluZC8d683PMKf5ti +ijYrUxCPfjEdtnY0uoqoVWVIm2v34Ed98Bx+Rx/5X4P4WSK1RWSFr2pdrTbC0vNh +IX5DkR/eRBCzqGz8c503XqDXuX1ALoyTW+Zr3JXkIQKBgQD/0WCVCYWKbAEI5YS+ +ORnOhcW6y69FZGAA9T/Vz0MBhFLYZ86SOUdE8CW1zN1eHgRDQ2QKocfslaCt1cai +cqDh9whO6Z3BxgpXlB+YoE7iicBugQzRarvCOJ+6BruelCE+ca+RcUNqZXPkxxA8 +5u9XjZUBTCTU3TSA5jD/jqvIuQKBgQDVwm+McCrQMqVQZcrJn6LGyiBGnBNC80P+ +pSq5IuAP8qB95P7vQCewQXoNtUsqhonfkIWO7zdVgt94GWOr4T+WCYDJoKGmU5FF +dy77E9ATitunQxuygE5gY1YsUDDe3AaxB/2K4C0JXGKP+GGPgVZF5tgcydXoyKfZ +U9tl0BI2dQKBgEiJfyBjdck9PMHgCtbBbC7iQaHo1YcQoiRDZlljDrXkZnVUYf+W +XD4Q9jBpqa9hRYYpGrnlC8Jq3wfLoSo45KOOH334wjHKzO3uY1MsyZF0y+rJju/m +mtzEn43pENQzXoXNSKIuApats8IAK/uI4/7od4LJq3vsSVHvc59TPiSJAoGAf7av +1wSdT88ynhpzUouiQYs6RXWyvGmD0nz0TbDcliqsYQuUlXWLQO+d8NrEkh/X+E7s +vT2HnfuXcLYaqvLlfssmu1DRxAJh5VMUKsbnpwVrvz+FKN0n9sbjEY7H5B8pkFPp +1DOkNNeRu9r2zzrDRPhrXqfoujBIuLw1dVsfxpECgYEArOW3p14H4BIisYfqYzR4 +vEXU5w1MH6sTgAnGhTI7WgCYK/1R4rcjPA+iGGaj+QQ5IJUbpYT7r6v2aiNyge7d +muhtUqXn1Hsox3O6cxerFlxd5OqO+p1O1Xuta4TP9Moh7ujRPHX1dUa41+3LHaku +6ylIABtn5gBAm3OYvOwaSyw= +-----END PRIVATE KEY----- diff --git a/spec/ssl/pkcs8-server-key.pem b/spec/ssl/pkcs8-server-key.pem new file mode 100644 index 000000000..c803fba65 --- /dev/null +++ b/spec/ssl/pkcs8-server-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCtyxWYD01zMQby +RwzqzrGypf/x/rhNUT70HChE9TpojPmCe9e38eSvz1+0kGWb4VGKB705mPsY8yry +IO/T2StZI11Ddh7qGbiXJFFeU3s4lrcis3qZTZNvui8hPVmGRjn6TRe6FkX0B/lF +Ip0TV3X2aSAIU7oZpn6qmn4WZFFKgy/EVuDzeaf/RvTrfhnOhbBwhX+93WXt50fK +73YFBEUJ4hOO0VoB34O/555OHY4/FRjTWTusL2LgxcTcE/GRGk9BPBN4cfhmlJHB +hTSiXkGOyLGv8kbrYffockDhkiPJbMSdWIRV+ZpQX3YXf2y3lGOGbWptgMi3ttzh +6RaKmoZJAgMBAAECggEAFhAyLZvDuVwABcH/Yc/bv1JTq+UqgKZP1627bwWy5JMB +Gg+e0ztiTO+GtuWeAKwaLevNmgJR3lkAmryTtdFcL3TN4kKcqhuZ05ZIvjDa89Qu +a7ldVxkCHq0ETrP7KZDAy4X9/SHWv6RDgQNj7ZCs6RtvdZ8rgRYh/oaeezlBGLRZ +OIeWWHTYk8CMhu+tzR+BHZdXUEHo/sZHwmpdhQQ0HtXs87Eo51waVwHL0BgXT+ak +v7pbhe6sGgNgoix59Lu9WbazxXIyRYDMkZfg0fCauRMG05Wvaeuonk/zlC88Eg3M +yQIW8+Boe7M2yI+egRYeM2TCBnr2B7n8fTG/xQEKAQKBgQDe+hgYWboh6ejCarAB +UOd90D//aLtbyu0HBQ6YNiPPUvVmEMfDVscxw6BXTtRWpHYSxsPV+IpUM73St/cu +RmrP1DOTT4sJJf9lvNg35+OceGHRe0hVUU9mVqBMbLz5NOPwxWlq4eSgK+cAKq3g +5lp47IFxX0R+g+dvk7YhSy4+rwKBgQDHiEBLjZahY100PffLygIwvwbTyoK9UMjS +On1sGqOf9aF79zQIIRhEmPN/je9jqnAcf8+ivS1dLYoUboosm7215349Uw6S9C43 +RIn1iLZRGO7Beq3KOrdp4NFnh3QsgoH4jp1gp4w6QyBC2uYBdQXA9GpUR2+Y45BI +KVSHQV4IhwKBgQDQLig48+04pK9QdVOGpwa7DKfzytDCzx+mIi6SJlogw4+ij6Ay +3N51s/QMD+loS3yB41oMeFSOcRCVoHUDm3M2PyU4MFfbXsKpNjuZVsPH3w1VDAlo +vtWm8tIPCKcW9S6sKWRXCjju4o52NWLKS8fEhuwD8bJ9fKGkJwEw7IRsuQKBgQCU +D4feSI+I7InB9WXGI/1iHK49RJ2lS6fpUBu3t0DJtuSAb5x9l8lBRdoSQclsxJFy +pGj4Erbx2JQIu0nu9hZdQA1OBi7fXzBYNJTGzQ60uPKaQaVqVg26FGhvEXVkfedi +ALnJeiq1JRBwa6yXUjXVy8iHB4dJBTwQQBMIVronSwKBgQDOPYklqdzRv+u4XukW +WsvK7GLj9huwdH0NI5gouGMb5OTgfVo66+urq4qYcHRN4vcdxbP9KoJPBvKhdx+D +A36I2ERs//92gIz6lcNsqmdAzHAX1yzp4IyFGsjzaKibnQrft7hb9Kfg9VypBb/G +31x7mm+gZm4RP3jbcBPDn0xU/w== +-----END PRIVATE KEY----- diff --git a/spec/ssl/server-cert.pem b/spec/ssl/server-cert.pem new file mode 100644 index 000000000..9ec690dd3 --- /dev/null +++ b/spec/ssl/server-cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy +Z2VtMB4XDTE1MDkwOTA0NTcyMVoXDTI1MDcxODA0NTcyMVowIDEeMBwGA1UEAwwV +bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArcsVmA9NczEG8kcM6s6xsqX/8f64TVE+9BwoRPU6aIz5gnvXt/Hkr89f +tJBlm+FRige9OZj7GPMq8iDv09krWSNdQ3Ye6hm4lyRRXlN7OJa3IrN6mU2Tb7ov +IT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkgCFO6GaZ+qpp+FmRRSoMvxFbg83mn/0b0 +634ZzoWwcIV/vd1l7edHyu92BQRFCeITjtFaAd+Dv+eeTh2OPxUY01k7rC9i4MXE +3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsixr/JG62H36HJA4ZIjyWzEnViEVfmaUF92 +F39st5Rjhm1qbYDIt7bc4ekWipqGSQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBB +PtRuVmOHiRPH3PmsZPtkgVagznojO+r0GDTxys5bxof8+HcokL6gbb4bRqzUQTdC +98RTsuFPnd/I8FbbaL+UyeXeKjjOEBPFyllOdykmpd67mHCA89574y7Ib7lkvtr1 +nQFMbeKmcz4uLm1vAi/aOdAIA2de4yJU2XnOkVLDgYnQxpWR451WKqt/FtiuCzpQ +E3peEemM2XVxvCMmfBAaroAyLYFrWOhNA7UoWVsubp7Ypf7SYuOh+sU6JLsYSadQ +XVqgvIKG4deUpdl7oUBRz79spAi1OpHWiNmW3b+8nKJoHTfYkKzCebeLdI++xSjX +jXNryv5xK88LFIPKL/7e +-----END CERTIFICATE----- diff --git a/spec/ssl/server-key.pem b/spec/ssl/server-key.pem new file mode 100644 index 000000000..8bc45372b --- /dev/null +++ b/spec/ssl/server-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEArcsVmA9NczEG8kcM6s6xsqX/8f64TVE+9BwoRPU6aIz5gnvX +t/Hkr89ftJBlm+FRige9OZj7GPMq8iDv09krWSNdQ3Ye6hm4lyRRXlN7OJa3IrN6 +mU2Tb7ovIT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkgCFO6GaZ+qpp+FmRRSoMvxFbg +83mn/0b0634ZzoWwcIV/vd1l7edHyu92BQRFCeITjtFaAd+Dv+eeTh2OPxUY01k7 +rC9i4MXE3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsixr/JG62H36HJA4ZIjyWzEnViE +VfmaUF92F39st5Rjhm1qbYDIt7bc4ekWipqGSQIDAQABAoIBABYQMi2bw7lcAAXB +/2HP279SU6vlKoCmT9etu28FsuSTARoPntM7YkzvhrblngCsGi3rzZoCUd5ZAJq8 +k7XRXC90zeJCnKobmdOWSL4w2vPULmu5XVcZAh6tBE6z+ymQwMuF/f0h1r+kQ4ED +Y+2QrOkbb3WfK4EWIf6Gnns5QRi0WTiHllh02JPAjIbvrc0fgR2XV1BB6P7GR8Jq +XYUENB7V7POxKOdcGlcBy9AYF0/mpL+6W4XurBoDYKIsefS7vVm2s8VyMkWAzJGX +4NHwmrkTBtOVr2nrqJ5P85QvPBINzMkCFvPgaHuzNsiPnoEWHjNkwgZ69ge5/H0x +v8UBCgECgYEA3voYGFm6IenowmqwAVDnfdA//2i7W8rtBwUOmDYjz1L1ZhDHw1bH +McOgV07UVqR2EsbD1fiKVDO90rf3LkZqz9Qzk0+LCSX/ZbzYN+fjnHhh0XtIVVFP +ZlagTGy8+TTj8MVpauHkoCvnACqt4OZaeOyBcV9EfoPnb5O2IUsuPq8CgYEAx4hA +S42WoWNdND33y8oCML8G08qCvVDI0jp9bBqjn/Whe/c0CCEYRJjzf43vY6pwHH/P +or0tXS2KFG6KLJu9ted+PVMOkvQuN0SJ9Yi2URjuwXqtyjq3aeDRZ4d0LIKB+I6d +YKeMOkMgQtrmAXUFwPRqVEdvmOOQSClUh0FeCIcCgYEA0C4oOPPtOKSvUHVThqcG +uwyn88rQws8fpiIukiZaIMOPoo+gMtzedbP0DA/paEt8geNaDHhUjnEQlaB1A5tz +Nj8lODBX217CqTY7mVbDx98NVQwJaL7VpvLSDwinFvUurClkVwo47uKOdjViykvH +xIbsA/GyfXyhpCcBMOyEbLkCgYEAlA+H3kiPiOyJwfVlxiP9YhyuPUSdpUun6VAb +t7dAybbkgG+cfZfJQUXaEkHJbMSRcqRo+BK28diUCLtJ7vYWXUANTgYu318wWDSU +xs0OtLjymkGlalYNuhRobxF1ZH3nYgC5yXoqtSUQcGusl1I11cvIhweHSQU8EEAT +CFa6J0sCgYEAzj2JJanc0b/ruF7pFlrLyuxi4/YbsHR9DSOYKLhjG+Tk4H1aOuvr +q6uKmHB0TeL3HcWz/SqCTwbyoXcfgwN+iNhEbP//doCM+pXDbKpnQMxwF9cs6eCM +hRrI82iom50K37e4W/Sn4PVcqQW/xt9ce5pvoGZuET9423ATw59MVP8= +-----END RSA PRIVATE KEY----- diff --git a/spec/ssl/server-req.pem b/spec/ssl/server-req.pem new file mode 100644 index 000000000..94675507d --- /dev/null +++ b/spec/ssl/server-req.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArcsVmA9NczEG8kcM6s6xsqX/ +8f64TVE+9BwoRPU6aIz5gnvXt/Hkr89ftJBlm+FRige9OZj7GPMq8iDv09krWSNd +Q3Ye6hm4lyRRXlN7OJa3IrN6mU2Tb7ovIT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkg +CFO6GaZ+qpp+FmRRSoMvxFbg83mn/0b0634ZzoWwcIV/vd1l7edHyu92BQRFCeIT +jtFaAd+Dv+eeTh2OPxUY01k7rC9i4MXE3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsix +r/JG62H36HJA4ZIjyWzEnViEVfmaUF92F39st5Rjhm1qbYDIt7bc4ekWipqGSQID +AQABoAAwDQYJKoZIhvcNAQELBQADggEBAInWIFsq14b8PhDroMMvi1ma30xyQGjg +KObIxakwXkliSxmCdVqV4+MV6w8hK3z0q7H+NzRFByjo1PnU8BCIa058m5uvbjmM +wGQvpcxmpm1p8VKKoeTqvE8OelbrqHrmyNIq7E/S3UZelVt+HOIPJOOs/aqEzaEg +VL1u+4kCMbHM2rG8dii060oZ5i/gUtMn2TQWcNjSQBvaVztW5FOL74bYkoq0zIwd +MFl2BoYyAnERJEcBmh1f+D7MuxPaqTUKjUmfDbHCMAAyTS1FHr9AnIN0/C2Mxywl +H/zL9/DkfR53KZjITkf2gTH5D/N5oDUwmgCg6UZ0MeTOP27jvgcvb/k= +-----END CERTIFICATE REQUEST----- From 860bc79aed7b057c8e8b26f715f3ec002e95920a Mon Sep 17 00:00:00 2001 From: John Conroy Date: Tue, 8 Sep 2015 18:00:22 -0400 Subject: [PATCH 350/783] Only do version check in Windows environment Unix systems using libtool do not need to do a version check against the client version string as the libraries themselves are versioned. --- ext/mysql2/client.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index ca1cd2956..1b87c2475 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1215,6 +1215,7 @@ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) { } void init_mysql2_client() { +#ifdef _WIN32 /* verify the libmysql we're about to use was the version we were built against https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */ int i; @@ -1232,6 +1233,7 @@ void init_mysql2_client() { return; } } +#endif /* Initializing mysql library, so different threads could call Client.new */ /* without race condition in the library */ From cc4c63daa80e0edd7ec77aef73c06ad39748d352 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sun, 6 Sep 2015 13:39:35 -0400 Subject: [PATCH 351/783] cleanup: use rspec's type matcher --- spec/mysql2/client_spec.rb | 28 +++++++++---------- spec/mysql2/result_spec.rb | 48 ++++++++++++++++---------------- spec/mysql2/statement_spec.rb | 52 +++++++++++++++++------------------ 3 files changed, 64 insertions(+), 64 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 1e74f8079..939bebc54 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -140,12 +140,12 @@ def connect *args results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a expect(results[0]['Variable_name']).to eql('Ssl_cipher') expect(results[0]['Value']).not_to be_nil - expect(results[0]['Value']).to be_kind_of(String) + expect(results[0]['Value']).to be_an_instance_of(String) expect(results[0]['Value']).not_to be_empty expect(results[1]['Variable_name']).to eql('Ssl_version') expect(results[1]['Value']).not_to be_nil - expect(results[1]['Value']).to be_kind_of(String) + expect(results[1]['Value']).to be_an_instance_of(String) expect(results[1]['Value']).not_to be_empty ssl_client.close @@ -382,16 +382,16 @@ def run_gc end it "should return results as a hash by default" do - expect(@client.query("SELECT 1").first.class).to eql(Hash) + expect(@client.query("SELECT 1").first).to be_an_instance_of(Hash) end it "should be able to return results as an array" do - expect(@client.query("SELECT 1", :as => :array).first.class).to eql(Array) + expect(@client.query("SELECT 1", :as => :array).first).to be_an_instance_of(Array) @client.query("SELECT 1").each(:as => :array) end it "should be able to return results with symbolized keys" do - expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class).to eql(Symbol) + expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0]).to be_an_instance_of(Symbol) end it "should require an open connection" do @@ -454,7 +454,7 @@ def run_gc end it "#socket should return a Fixnum (file descriptor from C)" do - expect(@client.socket.class).to eql(Fixnum) + expect(@client.socket).to be_an_instance_of(Fixnum) expect(@client.socket).not_to eql(0) end @@ -545,7 +545,7 @@ def run_gc expect(loops >= 1).to be true result = @client.async_result - expect(result.class).to eql(Mysql2::Result) + expect(result).to be_an_instance_of(Mysql2::Result) end end @@ -727,11 +727,11 @@ def run_gc it "#info should return a hash containing the client version ID and String" do info = @client.info - expect(info.class).to eql(Hash) + expect(info).to be_an_instance_of(Hash) expect(info).to have_key(:id) - expect(info[:id].class).to eql(Fixnum) + expect(info[:id]).to be_an_instance_of(Fixnum) expect(info).to have_key(:version) - expect(info[:version].class).to eql(String) + expect(info[:version]).to be_an_instance_of(String) end context "strings returned by #info" do @@ -758,11 +758,11 @@ def run_gc it "#server_info should return a hash containing the client version ID and String" do server_info = @client.server_info - expect(server_info.class).to eql(Hash) + expect(server_info).to be_an_instance_of(Hash) expect(server_info).to have_key(:id) - expect(server_info[:id].class).to eql(Fixnum) + expect(server_info[:id]).to be_an_instance_of(Fixnum) expect(server_info).to have_key(:version) - expect(server_info[:version].class).to eql(String) + expect(server_info[:version]).to be_an_instance_of(String) end it "#server_info should require an open connection" do @@ -851,7 +851,7 @@ def run_gc end it "#thread_id should be a Fixnum" do - expect(@client.thread_id.class).to eql(Fixnum) + expect(@client.thread_id).to be_an_instance_of(Fixnum) end it "should respond to #ping" do diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index b32939753..85f5c00c3 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -53,19 +53,19 @@ context "#each" do it "should yield rows as hash's" do @result.each do |row| - expect(row.class).to eql(Hash) + expect(row).to be_an_instance_of(Hash) end end it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do @result.each(:symbolize_keys => true) do |row| - expect(row.keys.first.class).to eql(Symbol) + expect(row.keys.first).to be_an_instance_of(Symbol) end end it "should be able to return results as an array" do @result.each(:as => :array) do |row| - expect(row.class).to eql(Array) + expect(row).to be_an_instance_of(Array) end end @@ -182,17 +182,17 @@ end it "should return nil for a NULL value" do - expect(@test_result['null_test'].class).to eql(NilClass) + expect(@test_result['null_test']).to be_an_instance_of(NilClass) expect(@test_result['null_test']).to eql(nil) end it "should return String for a BIT(64) value" do - expect(@test_result['bit_test'].class).to eql(String) + expect(@test_result['bit_test']).to be_an_instance_of(String) expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do - expect(@test_result['single_bit_test'].class).to eql(String) + expect(@test_result['single_bit_test']).to be_an_instance_of(String) expect(@test_result['single_bit_test']).to eql("\001") end @@ -259,22 +259,22 @@ end it "should return BigDecimal for a DECIMAL value" do - expect(@test_result['decimal_test'].class).to eql(BigDecimal) + expect(@test_result['decimal_test']).to be_an_instance_of(BigDecimal) expect(@test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do - expect(@test_result['float_test'].class).to eql(Float) + expect(@test_result['float_test']).to be_an_instance_of(Float) expect(@test_result['float_test']).to eql(10.3) end it "should return Float for a DOUBLE value" do - expect(@test_result['double_test'].class).to eql(Float) + expect(@test_result['double_test']).to be_an_instance_of(Float) expect(@test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do - expect(@test_result['date_time_test'].class).to eql(Time) + expect(@test_result['date_time_test']).to be_an_instance_of(Time) expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end @@ -288,60 +288,60 @@ it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - expect(r.first['test'].class).to eql(klass) + expect(r.first['test']).to be_an_instance_of(klass) end it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - expect(r.first['test'].class).to eql(klass) + expect(r.first['test']).to be_an_instance_of(klass) end elsif 1.size == 8 # 64bit unless RUBY_VERSION =~ /1.8/ it "should return Time when timestamp is < 1901-12-13 20:45:52" do r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - expect(r.first['test'].class).to eql(Time) + expect(r.first['test']).to be_an_instance_of(Time) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - expect(r.first['test'].class).to eql(Time) + expect(r.first['test']).to be_an_instance_of(Time) end else it "should return Time when timestamp is > 0138-12-31 11:59:59" do r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") - expect(r.first['test'].class).to eql(Time) + expect(r.first['test']).to be_an_instance_of(Time) end it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") - expect(r.first['test'].class).to eql(DateTime) + expect(r.first['test']).to be_an_instance_of(DateTime) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - expect(r.first['test'].class).to eql(Time) + expect(r.first['test']).to be_an_instance_of(Time) end end end it "should return Time for a TIMESTAMP value when within the supported range" do - expect(@test_result['timestamp_test'].class).to eql(Time) + expect(@test_result['timestamp_test']).to be_an_instance_of(Time) expect(@test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do - expect(@test_result['time_test'].class).to eql(Time) + expect(@test_result['time_test']).to be_an_instance_of(Time) expect(@test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do - expect(@test_result['date_test'].class).to eql(Date) + expect(@test_result['date_test']).to be_an_instance_of(Date) expect(@test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do - expect(@test_result['enum_test'].class).to eql(String) + expect(@test_result['enum_test']).to be_an_instance_of(String) expect(@test_result['enum_test']).to eql('val1') end @@ -379,7 +379,7 @@ end it "should return String for a SET value" do - expect(@test_result['set_test'].class).to eql(String) + expect(@test_result['set_test']).to be_an_instance_of(String) expect(@test_result['set_test']).to eql('val1,val2') end @@ -412,7 +412,7 @@ end it "should return String for a BINARY value" do - expect(@test_result['binary_test'].class).to eql(String) + expect(@test_result['binary_test']).to be_an_instance_of(String) expect(@test_result['binary_test']).to eql("test#{"\000"*6}") end @@ -453,7 +453,7 @@ 'long_text_test' => 'LONGTEXT' }.each do |field, type| it "should return a String for #{type}" do - expect(@test_result[field].class).to eql(String) + expect(@test_result[field]).to be_an_instance_of(String) expect(@test_result[field]).to eql("test") end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 1a1f6306c..92169d1c1 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -9,7 +9,7 @@ it "should create a statement" do statement = nil expect { statement = @client.prepare 'SELECT 1' }.not_to raise_error - expect(statement).to be_kind_of Mysql2::Statement + expect(statement).to be_an_instance_of(Mysql2::Statement) end it "should raise an exception when server disconnects" do @@ -103,7 +103,7 @@ it "should select dates" do statement = @client.prepare 'SELECT NOW()' result = statement.execute - expect(result.first.first[1]).to be_kind_of Time + expect(result.first.first[1]).to be_an_instance_of(Time) end it "should tell us about the fields" do @@ -185,7 +185,7 @@ it "should yield rows as hash's" do @result = @client.prepare("SELECT 1").execute @result.each do |row| - expect(row.class).to eql(Hash) + expect(row).to be_an_instance_of(Hash) end end @@ -193,7 +193,7 @@ @client.query_options[:symbolize_keys] = true @result = @client.prepare("SELECT 1").execute @result.each do |row| - expect(row.keys.first.class).to eql(Symbol) + expect(row.keys.first).to be_an_instance_of(Symbol) end @client.query_options[:symbolize_keys] = false end @@ -203,7 +203,7 @@ @result = @client.prepare("SELECT 1").execute @result.each do |row| - expect(row.class).to eql(Array) + expect(row).to be_an_instance_of(Array) end @client.query_options[:as] = :hash @@ -270,17 +270,17 @@ end it "should return nil for a NULL value" do - expect(@test_result['null_test'].class).to eql(NilClass) + expect(@test_result['null_test']).to be_an_instance_of(NilClass) expect(@test_result['null_test']).to eql(nil) end it "should return String for a BIT(64) value" do - expect(@test_result['bit_test'].class).to eql(String) + expect(@test_result['bit_test']).to be_an_instance_of(String) expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do - expect(@test_result['single_bit_test'].class).to eql(String) + expect(@test_result['single_bit_test']).to be_an_instance_of(String) expect(@test_result['single_bit_test']).to eql("\001") end @@ -347,22 +347,22 @@ end it "should return BigDecimal for a DECIMAL value" do - expect(@test_result['decimal_test'].class).to eql(BigDecimal) + expect(@test_result['decimal_test']).to be_an_instance_of(BigDecimal) expect(@test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do - expect(@test_result['float_test'].class).to eql(Float) + expect(@test_result['float_test']).to be_an_instance_of(Float) expect(@test_result['float_test']).to be_within(1e-5).of(10.3) end it "should return Float for a DOUBLE value" do - expect(@test_result['double_test'].class).to eql(Float) + expect(@test_result['double_test']).to be_an_instance_of(Float) expect(@test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do - expect(@test_result['date_time_test'].class).to eql(Time) + expect(@test_result['date_time_test']).to be_an_instance_of(Time) expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end @@ -376,60 +376,60 @@ it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - expect(r.first['test'].class).to eql(klass) + expect(r.first['test']).to be_an_instance_of(klass) end it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - expect(r.first['test'].class).to eql(klass) + expect(r.first['test']).to be_an_instance_of(klass) end elsif 1.size == 8 # 64bit unless RUBY_VERSION =~ /1.8/ it "should return Time when timestamp is < 1901-12-13 20:45:52" do r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - expect(r.first['test'].class).to eql(Time) + expect(r.first['test']).to be_an_instance_of(Time) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - expect(r.first['test'].class).to eql(Time) + expect(r.first['test']).to be_an_instance_of(Time) end else it "should return Time when timestamp is > 0138-12-31 11:59:59" do r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") - expect(r.first['test'].class).to eql(Time) + expect(r.first['test']).to be_an_instance_of(Time) end it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") - expect(r.first['test'].class).to eql(DateTime) + expect(r.first['test']).to be_an_instance_of(DateTime) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - expect(r.first['test'].class).to eql(Time) + expect(r.first['test']).to be_an_instance_of(Time) end end end it "should return Time for a TIMESTAMP value when within the supported range" do - expect(@test_result['timestamp_test'].class).to eql(Time) + expect(@test_result['timestamp_test']).to be_an_instance_of(Time) expect(@test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do - expect(@test_result['time_test'].class).to eql(Time) + expect(@test_result['time_test']).to be_an_instance_of(Time) expect(@test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do - expect(@test_result['date_test'].class).to eql(Date) + expect(@test_result['date_test']).to be_an_instance_of(Date) expect(@test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do - expect(@test_result['enum_test'].class).to eql(String) + expect(@test_result['enum_test']).to be_an_instance_of(String) expect(@test_result['enum_test']).to eql('val1') end @@ -467,7 +467,7 @@ end it "should return String for a SET value" do - expect(@test_result['set_test'].class).to eql(String) + expect(@test_result['set_test']).to be_an_instance_of(String) expect(@test_result['set_test']).to eql('val1,val2') end @@ -500,7 +500,7 @@ end it "should return String for a BINARY value" do - expect(@test_result['binary_test'].class).to eql(String) + expect(@test_result['binary_test']).to be_an_instance_of(String) expect(@test_result['binary_test']).to eql("test#{"\000"*6}") end @@ -541,7 +541,7 @@ 'long_text_test' => 'LONGTEXT' }.each do |field, type| it "should return a String for #{type}" do - expect(@test_result[field].class).to eql(String) + expect(@test_result[field]).to be_an_instance_of(String) expect(@test_result[field]).to eql("test") end From 5196160b1d2ed85e626166530135a8fa6a7600f7 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sun, 7 Jun 2015 19:57:44 -0400 Subject: [PATCH 352/783] Generate a static --- ext/mysql2/mysql_enc_name_to_ruby.h | 16 +++++----- ext/mysql2/mysql_enc_to_ruby.h | 47 +++++++++++++++-------------- support/mysql_enc_to_ruby.rb | 3 +- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/ext/mysql2/mysql_enc_name_to_ruby.h b/ext/mysql2/mysql_enc_name_to_ruby.h index 60e029baf..9542e228a 100644 --- a/ext/mysql2/mysql_enc_name_to_ruby.h +++ b/ext/mysql2/mysql_enc_name_to_ruby.h @@ -1,4 +1,4 @@ -/* C code produced by gperf version 3.0.3 */ +/* C code produced by gperf version 3.0.4 */ /* Command-line: gperf */ /* Computed positions: -k'1,3,$' */ @@ -40,9 +40,9 @@ inline #endif #endif static unsigned int -mysql2_mysql_enc_name_to_rb_hash(str, len) +mysql2_mysql_enc_name_to_rb_hash (str, len) register const char *str; - register const unsigned int len; + register unsigned int len; { static const unsigned char asso_values[] = { @@ -78,14 +78,14 @@ mysql2_mysql_enc_name_to_rb_hash(str, len) #ifdef __GNUC__ __inline -#ifdef __GNUC_STDC_INLINE__ +#if defined __GNUC_STDC_INLINE__ || defined __GNUC_GNU_INLINE__ __attribute__ ((__gnu_inline__)) #endif #endif const struct mysql2_mysql_enc_name_to_rb_map * -mysql2_mysql_enc_name_to_rb(str, len) +mysql2_mysql_enc_name_to_rb (str, len) register const char *str; - register const unsigned int len; + register unsigned int len; { enum { @@ -154,9 +154,9 @@ mysql2_mysql_enc_name_to_rb(str, len) if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { - register const unsigned int key = mysql2_mysql_enc_name_to_rb_hash(str, len); + register int key = mysql2_mysql_enc_name_to_rb_hash (str, len); - if (key <= MAX_HASH_VALUE) + if (key <= MAX_HASH_VALUE && key >= 0) { register const char *s = wordlist[key].name; diff --git a/ext/mysql2/mysql_enc_to_ruby.h b/ext/mysql2/mysql_enc_to_ruby.h index 37dbf6f73..df167b2a6 100644 --- a/ext/mysql2/mysql_enc_to_ruby.h +++ b/ext/mysql2/mysql_enc_to_ruby.h @@ -1,4 +1,4 @@ -const char *mysql2_mysql_enc_to_rb[] = { +static const char *mysql2_mysql_enc_to_rb[] = { "Big5", "ISO-8859-2", NULL, @@ -54,13 +54,13 @@ const char *mysql2_mysql_enc_to_rb[] = { "macRoman", "UTF-16", "UTF-16", - NULL, + "", "Windows-1256", "Windows-1257", "Windows-1257", "UTF-32", "UTF-32", - NULL, + "", "ASCII-8BIT", NULL, "US-ASCII", @@ -119,10 +119,10 @@ const char *mysql2_mysql_enc_to_rb[] = { "UTF-16", "UTF-16", "UTF-16", - NULL, - NULL, - NULL, - NULL, + "UTF-16", + "UTF-16", + "UTF-16", + "UTF-16", NULL, NULL, NULL, @@ -146,6 +146,10 @@ const char *mysql2_mysql_enc_to_rb[] = { "UTF-16BE", "UTF-16BE", "UTF-16BE", + "UTF-16BE", + "UTF-16BE", + "UTF-16BE", + "UTF-16BE", NULL, NULL, NULL, @@ -153,11 +157,11 @@ const char *mysql2_mysql_enc_to_rb[] = { NULL, NULL, NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + "UTF-16BE", + "UTF-32", + "UTF-32", + "UTF-32", + "UTF-32", "UTF-32", "UTF-32", "UTF-32", @@ -178,10 +182,6 @@ const char *mysql2_mysql_enc_to_rb[] = { "UTF-32", "UTF-32", "UTF-32", - NULL, - NULL, - NULL, - NULL, NULL, NULL, NULL, @@ -210,6 +210,10 @@ const char *mysql2_mysql_enc_to_rb[] = { "UTF-8", "UTF-8", "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", NULL, NULL, NULL, @@ -217,11 +221,11 @@ const char *mysql2_mysql_enc_to_rb[] = { NULL, NULL, NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", "UTF-8", "UTF-8", "UTF-8", @@ -243,4 +247,3 @@ const char *mysql2_mysql_enc_to_rb[] = { "UTF-8", "UTF-8" }; - diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index 4a3ef70db..39c513228 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -76,7 +76,6 @@ # start printing output -puts "const char *mysql2_mysql_enc_to_rb[] = {" +puts "static const char *mysql2_mysql_enc_to_rb[] = {" puts encodings_with_nil.join(",\n") puts "};" -puts From 1520b2b8b8b7ba544639e2d56f6662efda16fcb7 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 11 Jun 2015 14:42:04 -0400 Subject: [PATCH 353/783] Include encodings only when needed --- ext/mysql2/result.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 7254e8e9c..c3cc00bc9 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -2,9 +2,9 @@ #include +#ifdef HAVE_RUBY_ENCODING_H #include "mysql_enc_to_ruby.h" -#ifdef HAVE_RUBY_ENCODING_H static rb_encoding *binaryEncoding; #endif From 8c90c4c552a1f2c5a9232004fe1083c6063e9494 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 17:03:48 -0700 Subject: [PATCH 354/783] Add RuboCop --- .rubocop.yml | 29 ++++ .rubocop_todo.yml | 330 ++++++++++++++++++++++++++++++++++++++++++++++ Gemfile | 1 + Rakefile | 10 +- 4 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 .rubocop.yml create mode 100644 .rubocop_todo.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..5d3706d73 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,29 @@ +inherit_from: .rubocop_todo.yml + +AllCops: + DisplayCopNames: true + Exclude: + - 'tmp/**/*' + +Lint/EndAlignment: + AlignWith: variable + +Style/CaseIndentation: + IndentWhenRelativeTo: end + +Style/IndentHash: + EnforcedStyle: consistent + +Style/TrailingComma: + EnforcedStyleForMultiline: comma + +Style/TrivialAccessors: + AllowPredicates: true + +# TODO: remove when we end support for < 1.9.3 + +Style/HashSyntax: + EnforcedStyle: hash_rockets + +Style/Lambda: + Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 000000000..97d69cc20 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,330 @@ +# This configuration was generated by `rubocop --auto-gen-config` +# on 2015-03-12 17:39:18 -0700 using RuboCop version 0.29.1. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 2 +# Configuration parameters: AllowSafeAssignment. +Lint/AssignmentInCondition: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Lint/DeprecatedClassMethods: + Enabled: false + +# Offense count: 1 +# Configuration parameters: AlignWith, SupportedStyles. +Lint/EndAlignment: + Enabled: false + +# Offense count: 1 +Lint/Eval: + Enabled: false + +# Offense count: 1 +Lint/RescueException: + Enabled: false + +# Offense count: 2 +Lint/ShadowingOuterLocalVariable: + Enabled: false + +# Offense count: 23 +# Cop supports --auto-correct. +Lint/UnusedBlockArgument: + Enabled: false + +# Offense count: 1 +Lint/UselessAccessModifier: + Enabled: false + +# Offense count: 4 +Lint/UselessAssignment: + Enabled: false + +# Offense count: 1 +Lint/Void: + Enabled: false + +# Offense count: 2 +Metrics/AbcSize: + Max: 64 + +# Offense count: 1 +Metrics/BlockNesting: + Max: 5 + +# Offense count: 2 +Metrics/CyclomaticComplexity: + Max: 21 + +# Offense count: 244 +# Configuration parameters: AllowURI, URISchemes. +Metrics/LineLength: + Max: 232 + +# Offense count: 4 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 40 + +# Offense count: 1 +Metrics/PerceivedComplexity: + Max: 20 + +# Offense count: 40 +# Cop supports --auto-correct. +Style/Blocks: + Enabled: false + +# Offense count: 8 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/BracesAroundHashParameters: + Enabled: false + +# Offense count: 1 +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/ClassAndModuleChildren: + Enabled: false + +# Offense count: 1 +Style/ClassVars: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/CommentIndentation: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/DeprecatedHashMethods: + Enabled: false + +# Offense count: 9 +Style/Documentation: + Enabled: false + +# Offense count: 1 +Style/DoubleNegation: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/ElseAlignment: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/EmptyLines: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/EmptyLinesAroundAccessModifier: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/EmptyLinesAroundModuleBody: + Enabled: false + +# Offense count: 9 +# Configuration parameters: AllowedVariables. +Style/GlobalVars: + Enabled: false + +# Offense count: 4 +# Configuration parameters: MaxLineLength. +Style/IfUnlessModifier: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/IndentationConsistency: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: Width. +Style/IndentationWidth: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/MethodDefParentheses: + Enabled: false + +# Offense count: 1 +Style/MultilineBlockChain: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/MultilineIfThen: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/NegatedIf: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/NegatedWhile: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/Not: + Enabled: false + +# Offense count: 13 +# Cop supports --auto-correct. +Style/NumericLiterals: + MinDigits: 20 + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: PreferredDelimiters. +Style/PercentLiteralDelimiters: + Enabled: false + +# Offense count: 1 +# Configuration parameters: SupportedStyles. +Style/RaiseArgs: + EnforcedStyle: compact + +# Offense count: 1 +# Cop supports --auto-correct. +Style/RedundantSelf: + Enabled: false + +# Offense count: 1 +Style/RegexpLiteral: + MaxSlashes: 0 + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/SignalException: + Enabled: false + +# Offense count: 9 +# Cop supports --auto-correct. +Style/SingleSpaceBeforeFirstArg: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +Style/SpaceAfterComma: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/SpaceAfterControlKeyword: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/SpaceAfterNot: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/SpaceAroundEqualsInParameterDefault: + Enabled: false + +# Offense count: 5 +# Cop supports --auto-correct. +Style/SpaceAroundOperators: + Enabled: false + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/SpaceBeforeBlockBraces: + Enabled: false + +# Offense count: 14 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. +Style/SpaceInsideBlockBraces: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/SpaceInsideBrackets: + Enabled: false + +# Offense count: 8 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles. +Style/SpaceInsideHashLiteralBraces: + Enabled: false + +# Offense count: 4 +# Cop supports --auto-correct. +Style/SpaceInsideParens: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/SpecialGlobalVars: + Enabled: false + +# Offense count: 550 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/StringLiterals: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +Style/SymbolProc: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/TrailingBlankLines: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. +Style/TrailingComma: + Enabled: false + +# Offense count: 2 +Style/UnlessElse: + Enabled: false + +# Offense count: 4 +# Cop supports --auto-correct. +Style/UnneededPercentQ: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/WhileUntilDo: + Enabled: false + +# Offense count: 1 +# Configuration parameters: MaxLineLength. +Style/WhileUntilModifier: + Enabled: false + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: WordRegex. +Style/WordArray: + MinSize: 5 diff --git a/Gemfile b/Gemfile index 27be870e3..502541282 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,7 @@ gem 'rake-compiler', '~> 0.9.5' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' + gem 'rubocop', '~> 0.29.1' unless RUBY_VERSION =~ /1.8/ end group :benchmarks do diff --git a/Rakefile b/Rakefile index 3ff7a8d24..433039c5b 100644 --- a/Rakefile +++ b/Rakefile @@ -8,4 +8,12 @@ load 'tasks/compile.rake' load 'tasks/generate.rake' load 'tasks/benchmarks.rake' -task :default => :spec +# TODO: remove when we end support for < 1.9.3 +if RUBY_VERSION =~ /1.8/ + task :default => :spec +else + require 'rubocop/rake_task' + RuboCop::RakeTask.new + + task :default => [:spec, :rubocop] +end From c5b8a1698d209982711e669a63ec3f833f73e58a Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 17:30:11 -0700 Subject: [PATCH 355/783] Autocorrect the easy stuff --- .rubocop.yml | 2 +- .rubocop_todo.yml | 218 +--------------------------------- Gemfile | 2 +- benchmark/allocations.rb | 2 - benchmark/setup_db.rb | 10 +- examples/eventmachine.rb | 2 +- ext/mysql2/extconf.rb | 29 ++--- lib/mysql2.rb | 24 ++-- lib/mysql2/client.rb | 15 +-- lib/mysql2/em.rb | 4 +- lib/mysql2/error.rb | 6 +- lib/mysql2/field.rb | 3 +- mysql2.gemspec | 6 +- spec/em/em_spec.rb | 2 +- spec/mysql2/client_spec.rb | 32 ++--- spec/mysql2/result_spec.rb | 22 ++-- spec/mysql2/statement_spec.rb | 35 +++--- support/mysql_enc_to_ruby.rb | 4 +- support/ruby_enc_to_mysql.rb | 2 +- tasks/benchmarks.rake | 6 +- tasks/compile.rake | 8 +- tasks/rspec.rake | 24 ++-- tasks/vendor_mysql.rake | 4 +- 23 files changed, 125 insertions(+), 337 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5d3706d73..0b7e938d4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,7 +15,7 @@ Style/IndentHash: EnforcedStyle: consistent Style/TrailingComma: - EnforcedStyleForMultiline: comma + EnforcedStyleForMultiline: consistent_comma Style/TrivialAccessors: AllowPredicates: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 97d69cc20..bdc588623 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-03-12 17:39:18 -0700 using RuboCop version 0.29.1. +# on 2015-03-12 17:46:21 -0700 using RuboCop version 0.29.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -10,16 +10,6 @@ Lint/AssignmentInCondition: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -Lint/DeprecatedClassMethods: - Enabled: false - -# Offense count: 1 -# Configuration parameters: AlignWith, SupportedStyles. -Lint/EndAlignment: - Enabled: false - # Offense count: 1 Lint/Eval: Enabled: false @@ -59,9 +49,9 @@ Metrics/BlockNesting: # Offense count: 2 Metrics/CyclomaticComplexity: - Max: 21 + Max: 23 -# Offense count: 244 +# Offense count: 238 # Configuration parameters: AllowURI, URISchemes. Metrics/LineLength: Max: 232 @@ -77,13 +67,7 @@ Metrics/PerceivedComplexity: # Offense count: 40 # Cop supports --auto-correct. -Style/Blocks: - Enabled: false - -# Offense count: 8 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/BracesAroundHashParameters: +Style/BlockDelimiters: Enabled: false # Offense count: 1 @@ -95,16 +79,6 @@ Style/ClassAndModuleChildren: Style/ClassVars: Enabled: false -# Offense count: 2 -# Cop supports --auto-correct. -Style/CommentIndentation: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/DeprecatedHashMethods: - Enabled: false - # Offense count: 9 Style/Documentation: Enabled: false @@ -113,27 +87,6 @@ Style/Documentation: Style/DoubleNegation: Enabled: false -# Offense count: 2 -# Cop supports --auto-correct. -Style/ElseAlignment: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/EmptyLines: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/EmptyLinesAroundAccessModifier: - Enabled: false - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/EmptyLinesAroundModuleBody: - Enabled: false - # Offense count: 9 # Configuration parameters: AllowedVariables. Style/GlobalVars: @@ -144,187 +97,26 @@ Style/GlobalVars: Style/IfUnlessModifier: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -Style/IndentationConsistency: - Enabled: false - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: Width. -Style/IndentationWidth: - Enabled: false - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/MethodDefParentheses: - Enabled: false - # Offense count: 1 Style/MultilineBlockChain: Enabled: false -# Offense count: 2 -# Cop supports --auto-correct. -Style/MultilineIfThen: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/NegatedIf: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/NegatedWhile: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/Not: - Enabled: false - # Offense count: 13 # Cop supports --auto-correct. Style/NumericLiterals: MinDigits: 20 -# Offense count: 7 -# Cop supports --auto-correct. -# Configuration parameters: PreferredDelimiters. -Style/PercentLiteralDelimiters: - Enabled: false - -# Offense count: 1 -# Configuration parameters: SupportedStyles. -Style/RaiseArgs: - EnforcedStyle: compact - -# Offense count: 1 -# Cop supports --auto-correct. -Style/RedundantSelf: - Enabled: false - -# Offense count: 1 -Style/RegexpLiteral: - MaxSlashes: 0 - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/SignalException: - Enabled: false - -# Offense count: 9 -# Cop supports --auto-correct. -Style/SingleSpaceBeforeFirstArg: - Enabled: false - -# Offense count: 6 -# Cop supports --auto-correct. -Style/SpaceAfterComma: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/SpaceAfterControlKeyword: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/SpaceAfterNot: - Enabled: false - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/SpaceAroundEqualsInParameterDefault: - Enabled: false - -# Offense count: 5 -# Cop supports --auto-correct. -Style/SpaceAroundOperators: - Enabled: false - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/SpaceBeforeBlockBraces: - Enabled: false - -# Offense count: 14 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. -Style/SpaceInsideBlockBraces: - Enabled: false - -# Offense count: 2 -# Cop supports --auto-correct. -Style/SpaceInsideBrackets: - Enabled: false - -# Offense count: 8 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles. -Style/SpaceInsideHashLiteralBraces: - Enabled: false - -# Offense count: 4 -# Cop supports --auto-correct. -Style/SpaceInsideParens: - Enabled: false - -# Offense count: 2 -# Cop supports --auto-correct. -Style/SpecialGlobalVars: - Enabled: false - -# Offense count: 550 +# Offense count: 549 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/StringLiterals: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: IgnoredMethods. -Style/SymbolProc: - Enabled: false - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/TrailingBlankLines: - Enabled: false - -# Offense count: 6 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. -Style/TrailingComma: - Enabled: false - # Offense count: 2 Style/UnlessElse: Enabled: false -# Offense count: 4 -# Cop supports --auto-correct. -Style/UnneededPercentQ: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/WhileUntilDo: - Enabled: false - # Offense count: 1 # Configuration parameters: MaxLineLength. Style/WhileUntilModifier: Enabled: false - -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: WordRegex. -Style/WordArray: - MinSize: 5 diff --git a/Gemfile b/Gemfile index 502541282..117579391 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem 'rake-compiler', '~> 0.9.5' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' - gem 'rubocop', '~> 0.29.1' unless RUBY_VERSION =~ /1.8/ + gem 'rubocop', '~> 0.32.0' unless RUBY_VERSION =~ /1.8/ end group :benchmarks do diff --git a/benchmark/allocations.rb b/benchmark/allocations.rb index 394c0c7e5..4e3579313 100644 --- a/benchmark/allocations.rb +++ b/benchmark/allocations.rb @@ -4,8 +4,6 @@ require 'rubygems' require 'active_record' -raise Mysql2::Error.new("GC allocation benchmarks only supported on Ruby 1.9!") unless RUBY_VERSION > '1.9' - ActiveRecord::Base.default_timezone = :local ActiveRecord::Base.time_zone_aware_attributes = true diff --git a/benchmark/setup_db.rb b/benchmark/setup_db.rb index 9ad8518e1..105d43ebc 100644 --- a/benchmark/setup_db.rb +++ b/benchmark/setup_db.rb @@ -86,10 +86,10 @@ def insert_record(args) :medium_int_test => rand(8388607), :int_test => rand(2147483647), :big_int_test => rand(9223372036854775807), - :float_test => rand(32767)/1.87, + :float_test => rand(32767) / 1.87, :float_zero_test => 0.0, - :double_test => rand(8388607)/1.87, - :decimal_test => rand(8388607)/1.87, + :double_test => rand(8388607) / 1.87, + :decimal_test => rand(8388607) / 1.87, :decimal_zero_test => 0, :date_test => '2010-4-4', :date_time_test => '2010-4-4 11:44:00', @@ -108,8 +108,8 @@ def insert_record(args) :medium_text_test => twenty5_paragraphs, :long_blob_test => twenty5_paragraphs, :long_text_test => twenty5_paragraphs, - :enum_test => ['val1', 'val2'][rand(2)], - :set_test => ['val1', 'val2', 'val1,val2'][rand(3)] + :enum_test => %w(val1 val2).sample, + :set_test => %w(val1 val2 val1,val2).sample, ) if n % 100 == 0 $stdout.putc '.' diff --git a/examples/eventmachine.rb b/examples/eventmachine.rb index a92ca4cb1..273f1bc52 100644 --- a/examples/eventmachine.rb +++ b/examples/eventmachine.rb @@ -18,4 +18,4 @@ defer2.callback do |result| puts "Result: #{result.to_a.inspect}" end -end \ No newline at end of file +end diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 492766c45..452314647 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -1,7 +1,8 @@ # encoding: UTF-8 require 'mkmf' +require 'English' -def asplode lib +def asplode(lib) if RUBY_PLATFORM =~ /mingw|mswin/ abort "-----\n#{lib} is missing. Check your installation of MySQL or Connector/C, and try again.\n-----" elsif RUBY_PLATFORM =~ /darwin/ @@ -22,7 +23,7 @@ def asplode lib # borrowed from mysqlplus # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb -dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[ +dirs = ENV.fetch('http://github.com/PATH').split(File::PATH_SEPARATOR) + %w( /opt /opt/local /opt/local/mysql @@ -34,7 +35,7 @@ def asplode lib /usr/local/mysql-* /usr/local/lib/mysql5* /usr/local/opt/mysql5* -].map{|dir| "#{dir}/bin" } +).map { |dir| dir << '/bin' } GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}" @@ -48,26 +49,26 @@ def asplode lib @libdir_basename = 'lib' inc, lib = dir_config('mysql') end - abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any?{|dir| File.directory?(dir)} - abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any?{|dir| File.directory?(dir)} - warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" + abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } + abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } + warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" rpath_dir = lib elsif mc = (with_config('mysql-config') || Dir[GLOB].first) # If the user has provided a --with-mysql-config argument, we must respect it or fail. # If the user gave --with-mysql-config with no argument means we should try to find it. mc = Dir[GLOB].first if mc == true - abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exists?(mc) + abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exist?(mc) abort "-----\nCannot execute mysql_config at #{mc}\n-----" unless File.executable?(mc) - warn "-----\nUsing mysql_config at #{mc}\n-----" + warn "-----\nUsing mysql_config at #{mc}\n-----" ver = `#{mc} --version`.chomp.to_f includes = `#{mc} --include`.chomp - exit 1 if $? != 0 + abort unless $CHILD_STATUS.success? libs = `#{mc} --libs_r`.chomp # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r). if ver >= 5.5 || libs.empty? libs = `#{mc} --libs`.chomp end - exit 1 if $? != 0 + abort unless $CHILD_STATUS.success? $INCFLAGS += ' ' + includes $libs = libs + " " + $libs rpath_dir = libs @@ -87,7 +88,7 @@ def asplode lib asplode 'mysql.h' end -%w{ errmsg.h mysqld_error.h }.each do |h| +%w(errmsg.h mysqld_error.h).each do |h| header = [prefix, h].compact.join '/' asplode h unless have_header h end @@ -122,9 +123,9 @@ def asplode lib # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool') sh dlltool, '--kill-at', - '--dllname', 'libmysql.dll', - '--output-lib', 'libmysql.a', - '--input-def', deffile, libfile + '--dllname', 'libmysql.dll', + '--output-lib', 'libmysql.a', + '--input-def', deffile, libfile end end diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 5c4e6aab4..92268bea3 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -8,16 +8,16 @@ # Or to bomb out with a clear error message instead of a linker crash if RUBY_PLATFORM =~ /mswin|mingw/ dll_path = if ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] - # If this environment variable is set, it overrides any other paths - # The user is advised to use backslashes not forward slashes - ENV['RUBY_MYSQL2_LIBMYSQL_DLL'].dup - elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))) - # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary - File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\') - else - # This will use default / system library paths - 'libmysql.dll' - end + # If this environment variable is set, it overrides any other paths + # The user is advised to use backslashes not forward slashes + ENV['RUBY_MYSQL2_LIBMYSQL_DLL'].dup + elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))) + # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary + File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\') + else + # This will use default / system library paths + 'libmysql.dll' + end require 'Win32API' LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I') @@ -54,13 +54,11 @@ module Mysql2 # For holding utility methods module Mysql2::Util - # # Rekey a string-keyed hash with equivalent symbols. # def self.key_hash_as_symbols(hash) return nil unless hash - Hash[hash.map { |k,v| [k.to_sym, v] }] + Hash[hash.map { |k, v| [k.to_sym, v] }] end - end diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 595cc8793..f67d78518 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -12,11 +12,11 @@ class Client :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION, :cast => true, :default_file => nil, - :default_group => nil + :default_group => nil, } def initialize(opts = {}) - opts = Mysql2::Util.key_hash_as_symbols( opts ) + opts = Mysql2::Util.key_hash_as_symbols(opts) @read_timeout = nil @query_options = @@default_query_options.dup @query_options.merge! opts @@ -48,9 +48,9 @@ def initialize(opts = {}) flags = 0 flags |= @query_options[:connect_flags] flags |= opts[:flags] if opts[:flags] - flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] and ssl_options.any? + flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] && ssl_options.any? - if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) } + if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) } warn "============= WARNING FROM mysql2 =============" warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future." warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options." @@ -106,8 +106,9 @@ def info end private - def self.local_offset - ::Time.local(2010).utc_offset.to_r / 86400 - end + + def self.local_offset + ::Time.local(2010).utc_offset.to_r / 86400 + end end end diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb index b21265994..cfb389d51 100644 --- a/lib/mysql2/em.rb +++ b/lib/mysql2/em.rb @@ -40,11 +40,11 @@ def close(*args) super(*args) end - def query(sql, opts={}) + def query(sql, opts = {}) if ::EM.reactor_running? super(sql, opts.merge(:async => true)) deferable = ::EM::DefaultDeferrable.new - @watch = ::EM.watch(self.socket, Watcher, self, deferable) + @watch = ::EM.watch(socket, Watcher, self, deferable) @watch.notify_readable = true deferable else diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 1bcbb8d23..5eec6fff0 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -9,14 +9,14 @@ class Error < StandardError }.freeze attr_accessor :error_number - attr_reader :sql_state - attr_writer :server_version + attr_reader :sql_state + attr_writer :server_version # Mysql gem compatibility alias_method :errno, :error_number alias_method :error, :message - def initialize(msg, server_version=nil) + def initialize(msg, server_version = nil) self.server_version = server_version super(clean_message(msg)) diff --git a/lib/mysql2/field.rb b/lib/mysql2/field.rb index 4ca28ca0a..516ec17c2 100644 --- a/lib/mysql2/field.rb +++ b/lib/mysql2/field.rb @@ -1,4 +1,3 @@ module Mysql2 - class Field < Struct.new(:name, :type) - end + Field = Struct.new(:name, :type) end diff --git a/mysql2.gemspec b/mysql2.gemspec index 2132e1a6c..1113b6862 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -1,15 +1,15 @@ require File.expand_path('../lib/mysql2/version', __FILE__) Gem::Specification.new do |s| - s.name = %q{mysql2} + s.name = 'mysql2' s.version = Mysql2::VERSION s.authors = ['Brian Lopez', 'Aaron Stone'] s.license = "MIT" s.email = ['seniorlopez@gmail.com', 'aaron@serendipity.cx'] s.extensions = ["ext/mysql2/extconf.rb"] - s.homepage = %q{http://github.com/brianmario/mysql2} + s.homepage = 'http://github.com/brianmario/mysql2' s.rdoc_options = ["--charset=UTF-8"] - s.summary = %q{A simple, fast Mysql library for Ruby, binding to libmysql} + s.summary = 'A simple, fast Mysql library for Ruby, binding to libmysql' s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split s.test_files = `git ls-files spec examples`.split diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 8890e285c..30e027e9d 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -55,7 +55,7 @@ defer = client.query "SELECT sleep(0.1) as first_query" defer.callback do |result| client.close - raise 'some error' + fail 'some error' end defer.errback do |err| # This _shouldn't_ be run, but it needed to prevent the specs from diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 939bebc54..b84cf7441 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -46,7 +46,7 @@ it "should accept connect flags and pass them to #connect" do klient = Class.new(Mysql2::Client) do attr_reader :connect_args - def connect *args + def connect(*args) @connect_args ||= [] @connect_args << args end @@ -58,7 +58,7 @@ def connect *args it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do klient = Class.new(Mysql2::Client) do attr_reader :connect_args - def connect *args + def connect(*args) @connect_args ||= [] @connect_args << args end @@ -117,9 +117,9 @@ def connect *args it "should be able to connect via SSL options" do ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'" - ssl_uncompiled = ssl.any? {|x| x['Value'] == 'OFF'} + ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' } pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled - ssl_disabled = ssl.any? {|x| x['Value'] == 'DISABLED'} + ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' } pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled # You may need to adjust the lines below to match your SSL certificate paths @@ -132,7 +132,7 @@ def connect *args :sslcert => '/etc/mysql/client-cert.pem', :sslca => '/etc/mysql/ca-cert.pem', :sslcipher => 'DHE-RSA-AES256-SHA', - :sslverify => true + :sslverify => true, ) ) }.not_to raise_error @@ -185,7 +185,7 @@ def run_gc # this empty `fork` call fixes this tests on RBX; without it, the next # `fork` call hangs forever. WTF? - fork { } + fork {} fork do client.query('SELECT 1') @@ -267,7 +267,7 @@ def run_gc # # Note that mysql_info() returns a non-NULL value for INSERT ... VALUES only for the multiple-row form of the statement (that is, only if multiple value lists are specified). @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)") - expect(@client.query_info).to eql({:records => 2, :duplicates => 0, :warnings => 0}) + expect(@client.query_info).to eql(:records => 2, :duplicates => 0, :warnings => 0) expect(@client.query_info_string).to eq('Records: 2 Duplicates: 0 Warnings: 0') @client.query "DROP TABLE infoTest" @@ -279,7 +279,7 @@ def run_gc before(:all) do @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true) local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'" - local_enabled = local.any? {|x| x['Value'] == 'ON'} + local_enabled = local.any? { |x| x['Value'] == 'ON' } pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled @client_i.query %[ @@ -311,10 +311,10 @@ def run_gc it "should LOAD DATA LOCAL INFILE" do @client_i.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" info = @client_i.query_info - expect(info).to eql({:records => 1, :deleted => 0, :skipped => 0, :warnings => 0}) + expect(info).to eql(:records => 1, :deleted => 0, :skipped => 0, :warnings => 0) result = @client_i.query "SELECT * FROM infileTest" - expect(result.first).to eql({'id' => 1, 'foo' => 'Hello', 'bar' => 'World'}) + expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World') end end @@ -564,21 +564,21 @@ def run_gc end it "returns multiple result sets" do - expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql({ 'set_1' => 1 }) + expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql('set_1' => 1) expect(@multi_client.next_result).to be true - expect(@multi_client.store_result.first).to eql({ 'set_2' => 2 }) + expect(@multi_client.store_result.first).to eql('set_2' => 2) expect(@multi_client.next_result).to be false end it "does not interfere with other statements" do @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'") - while( @multi_client.next_result ) + while @multi_client.next_result @multi_client.store_result end - expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq({ 'next' => 3 }) + expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq('next' => 3) end it "will raise on query if there are outstanding results to read" do @@ -609,11 +609,11 @@ def run_gc it "#more_results? should work with stored procedures" do @multi_client.query("DROP PROCEDURE IF EXISTS test_proc") @multi_client.query("CREATE PROCEDURE test_proc() BEGIN SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'; END") - expect(@multi_client.query("CALL test_proc()").first).to eql({ 'set_1' => 1 }) + expect(@multi_client.query("CALL test_proc()").first).to eql('set_1' => 1) expect(@multi_client.more_results?).to be true @multi_client.next_result - expect(@multi_client.store_result.first).to eql({ 'set_2' => 2 }) + expect(@multi_client.store_result.first).to eql('set_2' => 2) @multi_client.next_result expect(@multi_client.store_result).to be_nil # this is the result from CALL itself diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 85f5c00c3..d867765ec 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -109,7 +109,7 @@ it "should return an array of field names in proper order" do result = @client.query "SELECT 'a', 'b', 'c'" - expect(result.fields).to eql(['a', 'b', 'c']) + expect(result.fields).to eql(%w(a b c)) end end @@ -132,7 +132,7 @@ it "should not yield nil at the end of streaming" do result = @client.query('SELECT * FROM mysql2_test', :stream => true, :cache_rows => false) - result.each { |r| expect(r).not_to be_nil} + result.each { |r| expect(r).not_to be_nil } end it "#count should be zero for rows after streaming when there were no results" do @@ -174,11 +174,11 @@ it "should return nil values for NULL and strings for everything else when :cast is false" do result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast => false).first expect(result["null_test"]).to be_nil - expect(result["tiny_int_test"]).to eql("1") + expect(result["tiny_int_test"]).to eql("1") expect(result["bool_cast_test"]).to eql("1") - expect(result["int_test"]).to eql("10") - expect(result["date_test"]).to eql("2010-04-04") - expect(result["enum_test"]).to eql("val1") + expect(result["int_test"]).to eql("10") + expect(result["date_test"]).to eql("2010-04-04") + expect(result["enum_test"]).to eql("val1") end it "should return nil for a NULL value" do @@ -286,13 +286,13 @@ end it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do - # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 + # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(klass) end it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do - # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 + # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(klass) end @@ -413,7 +413,7 @@ it "should return String for a BINARY value" do expect(@test_result['binary_test']).to be_an_instance_of(String) - expect(@test_result['binary_test']).to eql("test#{"\000"*6}") + expect(@test_result['binary_test']).to eql("test#{"\000" * 6}") end context "string encoding for BINARY values" do @@ -450,7 +450,7 @@ 'medium_blob_test' => 'MEDIUMBLOB', 'medium_text_test' => 'MEDIUMTEXT', 'long_blob_test' => 'LONGBLOB', - 'long_text_test' => 'LONGTEXT' + 'long_text_test' => 'LONGTEXT', }.each do |field, type| it "should return a String for #{type}" do expect(@test_result[field]).to be_an_instance_of(String) @@ -460,7 +460,7 @@ context "string encoding for #{type} values" do before { pending('Encoding is undefined') unless defined?(Encoding) } - if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) + if %w(VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB).include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 92169d1c1..8c5647d31 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -53,8 +53,8 @@ statement = @client.prepare 'SELECT 1' result = statement.execute rows = [] - result.each {|r| rows << r} - expect(rows).to eq([{"1"=>1}]) + result.each { |r| rows << r } + expect(rows).to eq([{ "1" => 1 }]) end it "should keep its result after other query" do @@ -64,8 +64,8 @@ stmt = @client.prepare('SELECT a FROM mysql2_stmt_q WHERE a = ?') result1 = stmt.execute(1) result2 = stmt.execute(2) - expect(result2.first).to eq({"a"=>2}) - expect(result1.first).to eq({"a"=>1}) + expect(result2.first).to eq("a" => 2) + expect(result1.first).to eq("a" => 1) @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end @@ -91,11 +91,11 @@ @client.query 'INSERT INTO mysql2_stmt_q (a, b) VALUES (1, "Hello"), (2, "World")' statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE a < ?' results = statement.execute(2) - expect(results.first).to eq({"a" => 1, "b" => "Hello"}) + expect(results.first).to eq("a" => 1, "b" => "Hello") statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE b LIKE ?' results = statement.execute('%orld') - expect(results.first).to eq({"a" => 2, "b" => "World"}) + expect(results.first).to eq("a" => 2, "b" => "World") @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end @@ -130,10 +130,10 @@ it "should be able to retrieve utf8 field names correctly" do stmt = @client.prepare 'SELECT * FROM `テーブル`' - expect(stmt.fields).to eq(['整数', '文字列']) + expect(stmt.fields).to eq(%w(整数 文字列)) result = stmt.execute - expect(result.to_a).to eq([{"整数"=>1, "文字列"=>"イチ"}, {"整数"=>2, "文字列"=>"弐"}, {"整数"=>3, "文字列"=>"さん"}]) + expect(result.to_a).to eq([{ "整数" => 1, "文字列" => "イチ" }, { "整数" => 2, "文字列" => "弐" }, { "整数" => 3, "文字列" => "さん" }]) end it "should be able to retrieve utf8 param query correctly" do @@ -142,7 +142,7 @@ result = stmt.execute 'イチ' - expect(result.to_a).to eq([{"整数"=>1}]) + expect(result.to_a).to eq([{ "整数" => 1 }]) end it "should be able to retrieve query with param in different encoding correctly" do @@ -152,9 +152,8 @@ param = 'イチ'.encode("EUC-JP") result = stmt.execute param - expect(result.to_a).to eq([{"整数"=>1}]) + expect(result.to_a).to eq([{ "整数" => 1 }]) end - end if defined? Encoding context "streaming result" do @@ -162,7 +161,7 @@ n = 1 stmt = @client.prepare("SELECT 1 UNION SELECT 2") - @client.query_options.merge!({:stream => true, :cache_rows => false, :as => :array}) + @client.query_options.merge!(:stream => true, :cache_rows => false, :as => :array) stmt.execute.each do |r| case n @@ -259,7 +258,7 @@ it "should return an array of field names in proper order" do result = @client.prepare("SELECT 'a', 'b', 'c'").execute - expect(result.fields).to eql(['a', 'b', 'c']) + expect(result.fields).to eql(%w(a b c)) end end @@ -374,13 +373,13 @@ end it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do - # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 + # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(klass) end it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do - # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 + # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(klass) end @@ -501,7 +500,7 @@ it "should return String for a BINARY value" do expect(@test_result['binary_test']).to be_an_instance_of(String) - expect(@test_result['binary_test']).to eql("test#{"\000"*6}") + expect(@test_result['binary_test']).to eql("test#{"\000" * 6}") end context "string encoding for BINARY values" do @@ -538,7 +537,7 @@ 'medium_blob_test' => 'MEDIUMBLOB', 'medium_text_test' => 'MEDIUMTEXT', 'long_blob_test' => 'LONGBLOB', - 'long_text_test' => 'LONGTEXT' + 'long_text_test' => 'LONGTEXT', }.each do |field, type| it "should return a String for #{type}" do expect(@test_result[field]).to be_an_instance_of(String) @@ -548,7 +547,7 @@ context "string encoding for #{type} values" do before { pending('Encoding is undefined') unless defined?(Encoding) } - if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) + if %w(VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB).include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index 39c513228..2d5987e9d 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -42,7 +42,7 @@ "binary" => "ASCII-8BIT", "geostd8" => "NULL", "cp932" => "Windows-31J", - "eucjpms" => "eucJP-ms" + "eucjpms" => "eucJP-ms", } client = Mysql2::Client.new(:username => user, :password => pass, :host => host, :port => port.to_i) @@ -53,7 +53,7 @@ collations.each do |collation| mysql_col_idx = collation[2].to_i rb_enc = mysql_to_rb[collation[1]] - encodings[mysql_col_idx-1] = [mysql_col_idx, rb_enc] + encodings[mysql_col_idx - 1] = [mysql_col_idx, rb_enc] end encodings.each_with_index do |encoding, idx| diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb index 112016c94..52603b303 100644 --- a/support/ruby_enc_to_mysql.rb +++ b/support/ruby_enc_to_mysql.rb @@ -37,7 +37,7 @@ "binary" => "ASCII-8BIT", "geostd8" => nil, "cp932" => "Windows-31J", - "eucjpms" => "eucJP-ms" + "eucjpms" => "eucJP-ms", } puts <<-header diff --git a/tasks/benchmarks.rake b/tasks/benchmarks.rake index 33c85686c..79e1dc6a7 100644 --- a/tasks/benchmarks.rake +++ b/tasks/benchmarks.rake @@ -4,8 +4,8 @@ end.select { |x| x != 'setup_db' } namespace :bench do BENCHMARKS.each do |feature| - desc "Run #{feature} benchmarks" - task(feature){ ruby "benchmark/#{feature}.rb" } + desc "Run #{feature} benchmarks" + task(feature) { ruby "benchmark/#{feature}.rb" } end task :all do @@ -17,4 +17,4 @@ namespace :bench do task :setup do ruby 'benchmark/setup_db' end -end \ No newline at end of file +end diff --git a/tasks/compile.rake b/tasks/compile.rake index e5a903017..f7f977228 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -11,10 +11,10 @@ Rake::ExtensionTask.new("mysql2", gemspec) do |ext| # clean compiled extension CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}" - if RUBY_PLATFORM =~ /mswin|mingw/ then + if RUBY_PLATFORM =~ /mswin|mingw/ # Expand the path because the build dir is 3-4 levels deep in tmp/platform/version/ connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) - ext.config_options = [ "--with-mysql-dir=#{connector_dir}" ] + ext.config_options = ["--with-mysql-dir=#{connector_dir}"] else ext.cross_compile = true ext.cross_platform = ENV['CROSS_PLATFORMS'] ? ENV['CROSS_PLATFORMS'].split(':') : ['x86-mingw32', 'x86-mswin32-60', 'x64-mingw32'] @@ -78,11 +78,11 @@ task :devkit do end end -if RUBY_PLATFORM =~ /mingw|mswin/ then +if RUBY_PLATFORM =~ /mingw|mswin/ Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' Rake::Task['compile'].prerequisites.unshift 'devkit' else - if Rake::Task.tasks.map {|t| t.name }.include? 'cross' + if Rake::Task.tasks.map(&:name).include? 'cross' Rake::Task['cross'].prerequisites.unshift 'vendor:mysql:cross' end end diff --git a/tasks/rspec.rake b/tasks/rspec.rake index 123343def..4be4d63dc 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -4,18 +4,18 @@ begin desc " Run all examples with Valgrind" namespace :spec do - task :valgrind do - VALGRIND_OPTS = %w{ - --num-callers=50 - --error-limit=no - --partial-loads-ok=yes - --undef-value-errors=no - --trace-children=yes - } - cmdline = "valgrind #{VALGRIND_OPTS.join(' ')} bundle exec rake spec" - puts cmdline - system cmdline - end + task :valgrind do + VALGRIND_OPTS = %w( + --num-callers=50 + --error-limit=no + --partial-loads-ok=yes + --undef-value-errors=no + --trace-children=yes + ) + cmdline = "valgrind #{VALGRIND_OPTS.join(' ')} bundle exec rake spec" + puts cmdline + system cmdline + end end desc "Run all examples with RCov" diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index 14c73659a..eae206a8e 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -3,7 +3,7 @@ require 'rake/extensioncompiler' CONNECTOR_VERSION = "6.1.6" # NOTE: Track the upstream version from time to time -def vendor_mysql_platform(platform=nil) +def vendor_mysql_platform(platform = nil) platform ||= RUBY_PLATFORM platform =~ /x64/ ? "winx64" : "win32" end @@ -38,7 +38,7 @@ task "vendor:mysql", [:platform] do |t, args| when_writing "downloading #{t.name}" do cd "vendor" do sh "curl", "-C", "-", "-O", url do |ok, res| - sh "wget", "-c", url if ! ok + sh "wget", "-c", url unless ok end end end From bb44a011fcdc4da3401b1380bd8d22f07fc9212a Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 17:36:22 -0700 Subject: [PATCH 356/783] `Lint/Eval` --- .rubocop_todo.yml | 8 ++------ mysql2.gemspec | 2 +- tasks/compile.rake | 8 +++----- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bdc588623..b126f98cd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-03-12 17:46:21 -0700 using RuboCop version 0.29.1. +# on 2015-03-12 17:46:52 -0700 using RuboCop version 0.29.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -10,10 +10,6 @@ Lint/AssignmentInCondition: Enabled: false -# Offense count: 1 -Lint/Eval: - Enabled: false - # Offense count: 1 Lint/RescueException: Enabled: false @@ -51,7 +47,7 @@ Metrics/BlockNesting: Metrics/CyclomaticComplexity: Max: 23 -# Offense count: 238 +# Offense count: 237 # Configuration parameters: AllowURI, URISchemes. Metrics/LineLength: Max: 232 diff --git a/mysql2.gemspec b/mysql2.gemspec index 1113b6862..8596b5eec 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -1,6 +1,6 @@ require File.expand_path('../lib/mysql2/version', __FILE__) -Gem::Specification.new do |s| +Mysql2::GEMSPEC = Gem::Specification.new do |s| s.name = 'mysql2' s.version = Mysql2::VERSION s.authors = ['Brian Lopez', 'Aaron Stone'] diff --git a/tasks/compile.rake b/tasks/compile.rake index f7f977228..b0c2ee9b7 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -1,10 +1,8 @@ require "rake/extensiontask" -def gemspec - @clean_gemspec ||= eval(File.read(File.expand_path('../../mysql2.gemspec', __FILE__))) -end +load File.expand_path('../../mysql2.gemspec', __FILE__) -Rake::ExtensionTask.new("mysql2", gemspec) do |ext| +Rake::ExtensionTask.new("mysql2", Mysql2::GEMSPEC) do |ext| # put binaries into lib/mysql2/ or lib/mysql2/x.y/ ext.lib_dir = File.join 'lib', 'mysql2' @@ -60,7 +58,7 @@ file 'vendor/README' do |t| end file 'lib/mysql2/mysql2.rb' do |t| - name = gemspec.name + name = Mysql2::GEMSPEC.name File.open(t.name, 'wb') do |f| f.write <<-eoruby RUBY_VERSION =~ /(\\d+.\\d+)/ From 8f9eec4183d796246648c72f64578ae55829a409 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 17:38:07 -0700 Subject: [PATCH 357/783] `Lint/AssignmentInCondition` --- .rubocop_todo.yml | 5 ----- ext/mysql2/extconf.rb | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b126f98cd..639afe200 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -5,11 +5,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -# Configuration parameters: AllowSafeAssignment. -Lint/AssignmentInCondition: - Enabled: false - # Offense count: 1 Lint/RescueException: Enabled: false diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 452314647..088e6e9cc 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -53,7 +53,7 @@ def asplode(lib) abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" rpath_dir = lib -elsif mc = (with_config('mysql-config') || Dir[GLOB].first) +elsif (mc = (with_config('mysql-config') || Dir[GLOB].first)) # If the user has provided a --with-mysql-config argument, we must respect it or fail. # If the user gave --with-mysql-config with no argument means we should try to find it. mc = Dir[GLOB].first if mc == true @@ -170,7 +170,7 @@ def asplode(lib) warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" $LDFLAGS << rpath_flags else - if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] + if (libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2]) rpath_flags = " -Wl,-rpath,#{libdir}" if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. From 97ea80d7f4f5b15274ba20526bc159c04d7d0b77 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 17:48:44 -0700 Subject: [PATCH 358/783] `Lint/RescueException` --- .rubocop_todo.yml | 4 ---- lib/mysql2/em.rb | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 639afe200..e0b0db15c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -5,10 +5,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -Lint/RescueException: - Enabled: false - # Offense count: 2 Lint/ShadowingOuterLocalVariable: Enabled: false diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb index cfb389d51..f580f147e 100644 --- a/lib/mysql2/em.rb +++ b/lib/mysql2/em.rb @@ -17,7 +17,7 @@ def notify_readable detach begin result = @client.async_result - rescue Exception => e + rescue => e @deferable.fail(e) else @deferable.succeed(result) From 0f56af3e619626716078a56e97479c383e47f3dd Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 18:02:30 -0700 Subject: [PATCH 359/783] `Lint/UnusedBlockArgument` --- .rubocop_todo.yml | 13 ++----------- benchmark/query_with_mysql_casting.rb | 10 +++------- benchmark/query_without_mysql_casting.rb | 16 ++++------------ ext/mysql2/extconf.rb | 4 ++-- lib/mysql2/console.rb | 2 +- spec/em/em_spec.rb | 14 +++++++------- spec/mysql2/result_spec.rb | 2 +- tasks/compile.rake | 2 +- tasks/vendor_mysql.rake | 6 +++--- 9 files changed, 24 insertions(+), 45 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e0b0db15c..0f5d641af 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,24 +1,15 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-03-12 17:46:52 -0700 using RuboCop version 0.29.1. +# on 2015-03-12 18:00:35 -0700 using RuboCop version 0.29.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -Lint/ShadowingOuterLocalVariable: - Enabled: false - -# Offense count: 23 -# Cop supports --auto-correct. -Lint/UnusedBlockArgument: - Enabled: false - # Offense count: 1 Lint/UselessAccessModifier: Enabled: false -# Offense count: 4 +# Offense count: 10 Lint/UselessAssignment: Enabled: false diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb index f2d493fb7..da42dfe1a 100644 --- a/benchmark/query_with_mysql_casting.rb +++ b/benchmark/query_with_mysql_casting.rb @@ -43,9 +43,7 @@ def mysql_cast(type, value) mysql2.query "USE #{database}" x.report "Mysql2" do mysql2_result = mysql2.query sql, :symbolize_keys => true - mysql2_result.each do |res| - # puts res.inspect - end + # mysql2_result.each { |res| puts res.inspect } end mysql = Mysql.new("localhost", "root") @@ -55,7 +53,7 @@ def mysql_cast(type, value) fields = mysql_result.fetch_fields mysql_result.each do |row| row_hash = row.each_with_index.each_with_object({}) do |(f, j), hash| - hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, row[j]) + hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, f) end # puts row_hash.inspect end @@ -65,9 +63,7 @@ def mysql_cast(type, value) command = do_mysql.create_command sql x.report "do_mysql" do do_result = command.execute_reader - do_result.each do |res| - # puts res.inspect - end + # do_result.each { |res| puts res.inspect } end x.compare! diff --git a/benchmark/query_without_mysql_casting.rb b/benchmark/query_without_mysql_casting.rb index 8fcd9a652..d473b1a27 100644 --- a/benchmark/query_without_mysql_casting.rb +++ b/benchmark/query_without_mysql_casting.rb @@ -15,34 +15,26 @@ mysql2.query "USE #{database}" x.report "Mysql2 (cast: true)" do mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => true - mysql2_result.each do |res| - # puts res.inspect - end + # mysql2_result.each { |res| puts res.inspect } end x.report "Mysql2 (cast: false)" do mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => false - mysql2_result.each do |res| - # puts res.inspect - end + # mysql2_result.each { |res| puts res.inspect } end mysql = Mysql.new("localhost", "root") mysql.query "USE #{database}" x.report "Mysql" do mysql_result = mysql.query sql - mysql_result.each_hash do |res| - # puts res.inspect - end + # mysql_result.each_hash { |res| puts res.inspect } end do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") command = DataObjects::Mysql::Command.new do_mysql, sql x.report "do_mysql" do do_result = command.execute_reader - do_result.each do |res| - # puts res.inspect - end + # do_result.each { |res| puts res.inspect } end x.compare! diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 088e6e9cc..85c3cd3eb 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -117,7 +117,7 @@ def asplode(lib) # Use rake to rebuild only if these files change deffile = File.expand_path('../../../support/libmysql.def', __FILE__) libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) - file 'libmysql.a' => [deffile, libfile] do |t| + file 'libmysql.a' => [deffile, libfile] do when_writing 'building libmysql.a' do # Ruby kindly shows us where dllwrap is, but that tool does more than we want. # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. @@ -144,7 +144,7 @@ def asplode(lib) vendordll = File.join(vendordir, 'libmysql.dll') dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) - file vendordll => [dllfile, vendordir] do |t| + file vendordll => [dllfile, vendordir] do when_writing 'copying libmysql.dll' do cp dllfile, vendordll end diff --git a/lib/mysql2/console.rb b/lib/mysql2/console.rb index cad824375..d8fb9e324 100644 --- a/lib/mysql2/console.rb +++ b/lib/mysql2/console.rb @@ -1,5 +1,5 @@ # Loaded by script/console. Land helpers here. -Pry.config.prompt = lambda do |context, nesting, pry| +Pry.config.prompt = lambda do |context, *| "[mysql2] #{context}> " end diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 30e027e9d..fbb54d655 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -53,11 +53,11 @@ EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query "SELECT sleep(0.1) as first_query" - defer.callback do |result| + defer.callback do client.close fail 'some error' end - defer.errback do |err| + defer.errback do # This _shouldn't_ be run, but it needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop @@ -75,7 +75,7 @@ errors = [] EM.run do defer = client.query "SELECT sleep(0.1) as first_query" - defer.callback do |result| + defer.callback do # This _shouldn't_ be run, but it is needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop @@ -93,13 +93,13 @@ EM.run do defer = client.query "SELECT sleep(0.025) as first_query" EM.add_timer(0.1) do - defer.callback do |result| + defer.callback do callbacks_run << :callback # This _shouldn't_ be run, but it is needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop end - defer.errback do |err| + defer.errback do callbacks_run << :errback EM.stop_event_loop end @@ -114,10 +114,10 @@ EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query("select sleep(0.025)") - defer.callback do |result| + defer.callback do callbacks_run << :callback end - defer.errback do |err| + defer.errback do callbacks_run << :errback end EM.add_timer(0.1) do diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index d867765ec..2d4d0ba50 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -158,7 +158,7 @@ res = client.query "SELECT * FROM streamingTest", :stream => true, :cache_rows => false expect { - res.each_with_index do |row, i| + res.each_with_index do |_, i| # Exhaust the first result packet then trigger a timeout sleep 2 if i > 0 && i % 1000 == 0 end diff --git a/tasks/compile.rake b/tasks/compile.rake index b0c2ee9b7..bd713e25f 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -50,7 +50,7 @@ Rake::ExtensionTask.new("mysql2", Mysql2::GEMSPEC) do |ext| end Rake::Task[:spec].prerequisites << :compile -file 'vendor/README' do |t| +file 'vendor/README' do connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) when_writing 'copying Connector/C README' do cp "#{connector_dir}/README", 'vendor/README' diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index eae206a8e..20e37e22d 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -21,13 +21,13 @@ def vendor_mysql_url(http://github.com/*args) end # vendor:mysql -task "vendor:mysql:cross" do |t| +task "vendor:mysql:cross" do # When cross-compiling, grab both 32 and 64 bit connectors Rake::Task['vendor:mysql'].invoke('x86') Rake::Task['vendor:mysql'].invoke('x64') end -task "vendor:mysql", [:platform] do |t, args| +task "vendor:mysql", [:platform] do |_t, args| puts "vendor:mysql for #{vendor_mysql_dir(args[:platform])}" # download mysql library and headers @@ -37,7 +37,7 @@ task "vendor:mysql", [:platform] do |t, args| url = vendor_mysql_url(http://github.com/args[:platform]) when_writing "downloading #{t.name}" do cd "vendor" do - sh "curl", "-C", "-", "-O", url do |ok, res| + sh "curl", "-C", "-", "-O", url do |ok| sh "wget", "-c", url unless ok end end From a253352b26f07d9926bfd35aada5e8cec73aeb33 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 18:11:57 -0700 Subject: [PATCH 360/783] `Lint/UselessAssignment` --- .rubocop_todo.yml | 4 ---- benchmark/query_with_mysql_casting.rb | 8 +++++--- benchmark/query_without_mysql_casting.rb | 10 ++++++---- ext/mysql2/extconf.rb | 9 +++++---- tasks/compile.rake | 2 +- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0f5d641af..7fbfadeaa 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -9,10 +9,6 @@ Lint/UselessAccessModifier: Enabled: false -# Offense count: 10 -Lint/UselessAssignment: - Enabled: false - # Offense count: 1 Lint/Void: Enabled: false diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb index da42dfe1a..d856f71c1 100644 --- a/benchmark/query_with_mysql_casting.rb +++ b/benchmark/query_with_mysql_casting.rb @@ -38,12 +38,14 @@ def mysql_cast(type, value) end end +debug = ENV['DEBUG'] + Benchmark.ips do |x| mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root") mysql2.query "USE #{database}" x.report "Mysql2" do mysql2_result = mysql2.query sql, :symbolize_keys => true - # mysql2_result.each { |res| puts res.inspect } + mysql2_result.each { |res| puts res.inspect } if debug end mysql = Mysql.new("localhost", "root") @@ -55,7 +57,7 @@ def mysql_cast(type, value) row_hash = row.each_with_index.each_with_object({}) do |(f, j), hash| hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, f) end - # puts row_hash.inspect + puts row_hash.inspect if debug end end @@ -63,7 +65,7 @@ def mysql_cast(type, value) command = do_mysql.create_command sql x.report "do_mysql" do do_result = command.execute_reader - # do_result.each { |res| puts res.inspect } + do_result.each { |res| puts res.inspect } if debug end x.compare! diff --git a/benchmark/query_without_mysql_casting.rb b/benchmark/query_without_mysql_casting.rb index d473b1a27..6b4493506 100644 --- a/benchmark/query_without_mysql_casting.rb +++ b/benchmark/query_without_mysql_casting.rb @@ -10,31 +10,33 @@ database = 'test' sql = "SELECT * FROM mysql2_test LIMIT 100" +debug = ENV['DEBUG'] + Benchmark.ips do |x| mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root") mysql2.query "USE #{database}" x.report "Mysql2 (cast: true)" do mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => true - # mysql2_result.each { |res| puts res.inspect } + mysql2_result.each { |res| puts res.inspect } if debug end x.report "Mysql2 (cast: false)" do mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => false - # mysql2_result.each { |res| puts res.inspect } + mysql2_result.each { |res| puts res.inspect } if debug end mysql = Mysql.new("localhost", "root") mysql.query "USE #{database}" x.report "Mysql" do mysql_result = mysql.query sql - # mysql_result.each_hash { |res| puts res.inspect } + mysql_result.each_hash { |res| puts res.inspect } if debug end do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") command = DataObjects::Mysql::Command.new do_mysql, sql x.report "do_mysql" do do_result = command.execute_reader - # do_result.each { |res| puts res.inspect } + do_result.each { |res| puts res.inspect } if debug end x.compare! diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 85c3cd3eb..6fe06929f 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -42,6 +42,7 @@ def asplode(lib) # If the user has provided a --with-mysql-dir argument, we must respect it or fail. inc, lib = dir_config('mysql') if inc && lib + # TODO: Remove when 2.0.0 is the minimum supported version # Ruby versions not incorporating the mkmf fix at # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 # do not properly search for lib directories, and must be corrected @@ -73,11 +74,11 @@ def asplode(lib) $libs = libs + " " + $libs rpath_dir = libs else - inc, lib = dir_config('mysql', '/usr/local') + _, usr_local_lib = dir_config('mysql', '/usr/local') - asplode("mysql client") unless find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") + asplode("mysql client") unless find_library('mysqlclient', 'mysql_query', usr_local_lib, "#{usr_local_lib}/mysql") - rpath_dir = lib + rpath_dir = usr_local_lib end if have_header('mysql.h') @@ -90,7 +91,7 @@ def asplode(lib) %w(errmsg.h mysqld_error.h).each do |h| header = [prefix, h].compact.join '/' - asplode h unless have_header h + asplode h unless have_header header end # This is our wishlist. We use whichever flags work on the host. diff --git a/tasks/compile.rake b/tasks/compile.rake index bd713e25f..4ffbefca6 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -71,7 +71,7 @@ end task :devkit do begin require "devkit" - rescue LoadError => e + rescue LoadError abort "Failed to activate RubyInstaller's DevKit required for compilation." end end From 5b1d0952b5c42a320203961f9d76470415557cca Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 18:29:10 -0700 Subject: [PATCH 361/783] Add several RuboCop checks --- .rubocop_todo.yml | 50 +++------------------------ benchmark/query_with_mysql_casting.rb | 4 +-- ext/mysql2/client.c | 2 +- ext/mysql2/extconf.rb | 10 ++---- lib/mysql2.rb | 18 +++++----- lib/mysql2/client.rb | 46 ++++++++++++------------ lib/mysql2/em.rb | 5 ++- spec/mysql2/client_spec.rb | 4 +-- spec/mysql2/result_spec.rb | 26 +++++++------- spec/mysql2/statement_spec.rb | 26 +++++++------- support/mysql_enc_to_ruby.rb | 8 ++--- tasks/benchmarks.rake | 2 +- 12 files changed, 79 insertions(+), 122 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7fbfadeaa..28eb54429 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,21 +1,13 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-03-12 18:00:35 -0700 using RuboCop version 0.29.1. +# on 2015-03-12 18:28:12 -0700 using RuboCop version 0.29.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -Lint/UselessAccessModifier: - Enabled: false - -# Offense count: 1 -Lint/Void: - Enabled: false - # Offense count: 2 Metrics/AbcSize: - Max: 64 + Max: 66 # Offense count: 1 Metrics/BlockNesting: @@ -30,7 +22,7 @@ Metrics/CyclomaticComplexity: Metrics/LineLength: Max: 232 -# Offense count: 4 +# Offense count: 5 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 40 @@ -44,53 +36,21 @@ Metrics/PerceivedComplexity: Style/BlockDelimiters: Enabled: false -# Offense count: 1 -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/ClassAndModuleChildren: - Enabled: false - -# Offense count: 1 -Style/ClassVars: - Enabled: false - -# Offense count: 9 +# Offense count: 10 Style/Documentation: Enabled: false -# Offense count: 1 -Style/DoubleNegation: - Enabled: false - # Offense count: 9 # Configuration parameters: AllowedVariables. Style/GlobalVars: Enabled: false -# Offense count: 4 -# Configuration parameters: MaxLineLength. -Style/IfUnlessModifier: - Enabled: false - -# Offense count: 1 -Style/MultilineBlockChain: - Enabled: false - -# Offense count: 13 +# Offense count: 12 # Cop supports --auto-correct. Style/NumericLiterals: MinDigits: 20 -# Offense count: 549 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/StringLiterals: Enabled: false - -# Offense count: 2 -Style/UnlessElse: - Enabled: false - -# Offense count: 1 -# Configuration parameters: MaxLineLength. -Style/WhileUntilModifier: - Enabled: false diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb index d856f71c1..a6bec70c6 100644 --- a/benchmark/query_with_mysql_casting.rb +++ b/benchmark/query_with_mysql_casting.rb @@ -30,8 +30,8 @@ def mysql_cast(type, value) when Mysql::Field::TYPE_TIME, Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP Time.parse(value) when Mysql::Field::TYPE_BLOB, Mysql::Field::TYPE_BIT, Mysql::Field::TYPE_STRING, - Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET - Mysql::Field::TYPE_ENUM + Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET, + Mysql::Field::TYPE_ENUM value else value diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 1b87c2475..f6f03729c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -648,7 +648,7 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) { * client.query(sql, options = {}) * * Query the database with +sql+, with optional +options+. For the possible - * options, see @@default_query_options on the Mysql2::Client class. + * options, see default_query_options on the Mysql2::Client class. */ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { #ifndef _WIN32 diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 6fe06929f..09cefe38e 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -66,9 +66,7 @@ def asplode(lib) abort unless $CHILD_STATUS.success? libs = `#{mc} --libs_r`.chomp # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r). - if ver >= 5.5 || libs.empty? - libs = `#{mc} --libs`.chomp - end + libs = `#{mc} --libs`.chomp if ver >= 5.5 || libs.empty? abort unless $CHILD_STATUS.success? $INCFLAGS += ' ' + includes $libs = libs + " " + $libs @@ -104,10 +102,8 @@ def asplode(lib) -Wno-unused-function -Wno-declaration-after-statement -Wno-missing-field-initializers -).select do |flag| - try_link('int main() {return 0;}', flag) -end.each do |flag| - $CFLAGS << ' ' << flag +).each do |flag| + $CFLAGS << ' ' << flag if try_link('int main() {return 0;}', flag) end if RUBY_PLATFORM =~ /mswin|mingw/ diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 92268bea3..a577c5cf3 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -10,7 +10,7 @@ dll_path = if ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] # If this environment variable is set, it overrides any other paths # The user is advised to use backslashes not forward slashes - ENV['RUBY_MYSQL2_LIBMYSQL_DLL'].dup + ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))) # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\') @@ -53,12 +53,14 @@ module Mysql2 end # For holding utility methods -module Mysql2::Util - # - # Rekey a string-keyed hash with equivalent symbols. - # - def self.key_hash_as_symbols(hash) - return nil unless hash - Hash[hash.map { |k, v| [k.to_sym, v] }] +module Mysql2 + module Util + # + # Rekey a string-keyed hash with equivalent symbols. + # + def self.key_hash_as_symbols(hash) + return nil unless hash + Hash[hash.map { |k, v| [k.to_sym, v] }] + end end end diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index f67d78518..2b60fc242 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -1,24 +1,27 @@ module Mysql2 class Client attr_reader :query_options, :read_timeout - @@default_query_options = { - :as => :hash, # the type of object you want each row back as; also supports :array (an array of values) - :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result - :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby - :symbolize_keys => false, # return field names as symbols instead of strings - :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in - :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller - :cache_rows => true, # tells Mysql2 to use it's internal row cache for results - :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION, - :cast => true, - :default_file => nil, - :default_group => nil, - } + + def self.default_query_options + @default_query_options ||= { + :as => :hash, # the type of object you want each row back as; also supports :array (an array of values) + :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result + :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby + :symbolize_keys => false, # return field names as symbols instead of strings + :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in + :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller + :cache_rows => true, # tells Mysql2 to use it's internal row cache for results + :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION, + :cast => true, + :default_file => nil, + :default_group => nil, + } + end def initialize(opts = {}) opts = Mysql2::Util.key_hash_as_symbols(opts) @read_timeout = nil - @query_options = @@default_query_options.dup + @query_options = self.class.default_query_options.dup @query_options.merge! opts initialize_ext @@ -26,11 +29,12 @@ def initialize(opts = {}) # Set default connect_timeout to avoid unlimited retries from signal interruption opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) + # TODO: stricter validation rather than silent massaging [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].each do |key| next unless opts.key?(key) case key when :reconnect, :local_infile, :secure_auth - send(:"#{key}=", !!opts[key]) + send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation when :connect_timeout, :read_timeout, :write_timeout send(:"#{key}=", opts[key].to_i) else @@ -75,10 +79,6 @@ def initialize(opts = {}) connect user, pass, host, port, database, socket, flags end - def self.default_query_options - @@default_query_options - end - if Thread.respond_to?(:handle_interrupt) require 'timeout' @@ -105,10 +105,12 @@ def info self.class.info end - private + class << self + private - def self.local_offset - ::Time.local(2010).utc_offset.to_r / 86400 + def local_offset + ::Time.local(2010).utc_offset.to_r / 86400 + end end end end diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb index f580f147e..b4210f089 100644 --- a/lib/mysql2/em.rb +++ b/lib/mysql2/em.rb @@ -34,9 +34,8 @@ def unbind end def close(*args) - if @watch - @watch.detach if @watch.watching? - end + @watch.detach if @watch && @watch.watching? + super(*args) end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index b84cf7441..272767ec3 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -574,9 +574,7 @@ def run_gc it "does not interfere with other statements" do @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'") - while @multi_client.next_result - @multi_client.store_result - end + @multi_client.store_result while @multi_client.next_result expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq('next' => 3) end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 2d4d0ba50..6844c88b1 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -279,10 +279,10 @@ end if 1.size == 4 # 32bit - unless RUBY_VERSION =~ /1.8/ - klass = Time + klass = if RUBY_VERSION =~ /1.8/ + DateTime else - klass = DateTime + Time end it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do @@ -297,27 +297,27 @@ expect(r.first['test']).to be_an_instance_of(klass) end elsif 1.size == 8 # 64bit - unless RUBY_VERSION =~ /1.8/ - it "should return Time when timestamp is < 1901-12-13 20:45:52" do - r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") + if RUBY_VERSION =~ /1.8/ + it "should return Time when timestamp is > 0138-12-31 11:59:59" do + r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) end + it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do + r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") + expect(r.first['test']).to be_an_instance_of(DateTime) + end + it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) end else - it "should return Time when timestamp is > 0138-12-31 11:59:59" do - r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") + it "should return Time when timestamp is < 1901-12-13 20:45:52" do + r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) end - it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do - r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") - expect(r.first['test']).to be_an_instance_of(DateTime) - end - it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 8c5647d31..3fb97dcfb 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -366,10 +366,10 @@ end if 1.size == 4 # 32bit - unless RUBY_VERSION =~ /1.8/ - klass = Time + klass = if RUBY_VERSION =~ /1.8/ + DateTime else - klass = DateTime + Time end it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do @@ -384,27 +384,27 @@ expect(r.first['test']).to be_an_instance_of(klass) end elsif 1.size == 8 # 64bit - unless RUBY_VERSION =~ /1.8/ - it "should return Time when timestamp is < 1901-12-13 20:45:52" do - r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") + if RUBY_VERSION =~ /1.8/ + it "should return Time when timestamp is > 0138-12-31 11:59:59" do + r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) end + it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do + r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") + expect(r.first['test']).to be_an_instance_of(DateTime) + end + it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) end else - it "should return Time when timestamp is > 0138-12-31 11:59:59" do - r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") + it "should return Time when timestamp is < 1901-12-13 20:45:52" do + r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) end - it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do - r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") - expect(r.first['test']).to be_an_instance_of(DateTime) - end - it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index 2d5987e9d..fbe1562c3 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -65,10 +65,10 @@ end encodings_with_nil = encodings_with_nil.map do |encoding| - name = "NULL" - - if !encoding.nil? && encoding[1] != "NULL" - name = "\"#{encoding[1]}\"" + name = if encoding.nil? || encoding[1] == 'NULL' + 'NULL' + else + "\"#{encoding[1]}\"" end " #{name}" diff --git a/tasks/benchmarks.rake b/tasks/benchmarks.rake index 79e1dc6a7..b587ecdc0 100644 --- a/tasks/benchmarks.rake +++ b/tasks/benchmarks.rake @@ -1,6 +1,6 @@ BENCHMARKS = Dir["#{File.dirname(__FILE__)}/../benchmark/*.rb"].map do |path| File.basename(path, '.rb') -end.select { |x| x != 'setup_db' } +end - ['setup_db'] namespace :bench do BENCHMARKS.each do |feature| From 3b5b01eeb7f58fb960c28508bb2f5bf40ff66d29 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 20:41:08 -0700 Subject: [PATCH 362/783] 1.8.7 doesn't support trailing commas --- spec/mysql2/client_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 272767ec3..7e3befe26 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -125,6 +125,7 @@ def connect(*args) # You may need to adjust the lines below to match your SSL certificate paths ssl_client = nil expect { + # rubocop:disable Style/TrailingComma ssl_client = Mysql2::Client.new( DatabaseCredentials['root'].merge( 'host' => 'mysql2gem.example.com', # must match the certificates @@ -132,9 +133,10 @@ def connect(*args) :sslcert => '/etc/mysql/client-cert.pem', :sslca => '/etc/mysql/ca-cert.pem', :sslcipher => 'DHE-RSA-AES256-SHA', - :sslverify => true, + :sslverify => true ) ) + # rubocop:enable Style/TrailingComma }.not_to raise_error results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a From 3a531112846449c7a059550c439e9330f0e854a1 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sun, 7 Jun 2015 18:28:26 -0400 Subject: [PATCH 363/783] Updates for Rubocop 0.32.0 --- .rubocop_todo.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 28eb54429..83c498c72 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-03-12 18:28:12 -0700 using RuboCop version 0.29.1. +# on 2015-06-07 18:25:43 -0400 using RuboCop version 0.32.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -17,7 +17,7 @@ Metrics/BlockNesting: Metrics/CyclomaticComplexity: Max: 23 -# Offense count: 237 +# Offense count: 283 # Configuration parameters: AllowURI, URISchemes. Metrics/LineLength: Max: 232 @@ -33,10 +33,11 @@ Metrics/PerceivedComplexity: # Offense count: 40 # Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. Style/BlockDelimiters: Enabled: false -# Offense count: 10 +# Offense count: 12 Style/Documentation: Enabled: false @@ -45,11 +46,12 @@ Style/Documentation: Style/GlobalVars: Enabled: false -# Offense count: 12 +# Offense count: 13 # Cop supports --auto-correct. Style/NumericLiterals: MinDigits: 20 +# Offense count: 673 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/StringLiterals: From dfc63952604a6ff0dae54d07bab25e78a40b8f17 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sun, 7 Jun 2015 18:33:52 -0400 Subject: [PATCH 364/783] Always iterate in benchmarks --- benchmark/query_with_mysql_casting.rb | 4 ++-- benchmark/query_without_mysql_casting.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb index a6bec70c6..7685926fa 100644 --- a/benchmark/query_with_mysql_casting.rb +++ b/benchmark/query_with_mysql_casting.rb @@ -45,7 +45,7 @@ def mysql_cast(type, value) mysql2.query "USE #{database}" x.report "Mysql2" do mysql2_result = mysql2.query sql, :symbolize_keys => true - mysql2_result.each { |res| puts res.inspect } if debug + mysql2_result.each { |res| puts res.inspect if debug } end mysql = Mysql.new("localhost", "root") @@ -65,7 +65,7 @@ def mysql_cast(type, value) command = do_mysql.create_command sql x.report "do_mysql" do do_result = command.execute_reader - do_result.each { |res| puts res.inspect } if debug + do_result.each { |res| puts res.inspect if debug } end x.compare! diff --git a/benchmark/query_without_mysql_casting.rb b/benchmark/query_without_mysql_casting.rb index 6b4493506..1afc7c9d3 100644 --- a/benchmark/query_without_mysql_casting.rb +++ b/benchmark/query_without_mysql_casting.rb @@ -17,26 +17,26 @@ mysql2.query "USE #{database}" x.report "Mysql2 (cast: true)" do mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => true - mysql2_result.each { |res| puts res.inspect } if debug + mysql2_result.each { |res| puts res.inspect if debug } end x.report "Mysql2 (cast: false)" do mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => false - mysql2_result.each { |res| puts res.inspect } if debug + mysql2_result.each { |res| puts res.inspect if debug } end mysql = Mysql.new("localhost", "root") mysql.query "USE #{database}" x.report "Mysql" do mysql_result = mysql.query sql - mysql_result.each_hash { |res| puts res.inspect } if debug + mysql_result.each_hash { |res| puts res.inspect if debug } end do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") command = DataObjects::Mysql::Command.new do_mysql, sql x.report "do_mysql" do do_result = command.execute_reader - do_result.each { |res| puts res.inspect } if debug + do_result.each { |res| puts res.inspect if debug } end x.compare! From ac93121200760a42eb3085c0e50ef12441c2bc97 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 7 Aug 2015 11:09:23 -0400 Subject: [PATCH 365/783] Updates for Rubocop 0.34.0 --- .rubocop_todo.yml | 33 +++++++++++++++++++++++---------- Gemfile | 2 +- lib/mysql2.rb | 2 +- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 83c498c72..5a28c920b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,6 @@ -# This configuration was generated by `rubocop --auto-gen-config` -# on 2015-06-07 18:25:43 -0400 using RuboCop version 0.32.0. +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2015-09-06 13:16:09 -0400 using RuboCop version 0.34.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -7,7 +8,7 @@ # Offense count: 2 Metrics/AbcSize: - Max: 66 + Max: 68 # Offense count: 1 Metrics/BlockNesting: @@ -17,7 +18,7 @@ Metrics/BlockNesting: Metrics/CyclomaticComplexity: Max: 23 -# Offense count: 283 +# Offense count: 290 # Configuration parameters: AllowURI, URISchemes. Metrics/LineLength: Max: 232 @@ -25,11 +26,11 @@ Metrics/LineLength: # Offense count: 5 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 40 + Max: 43 # Offense count: 1 Metrics/PerceivedComplexity: - Max: 20 + Max: 22 # Offense count: 40 # Cop supports --auto-correct. @@ -39,19 +40,31 @@ Style/BlockDelimiters: # Offense count: 12 Style/Documentation: - Enabled: false + Exclude: + - 'benchmark/active_record.rb' + - 'benchmark/allocations.rb' + - 'benchmark/query_with_mysql_casting.rb' + - 'lib/mysql2.rb' + - 'lib/mysql2/client.rb' + - 'lib/mysql2/em.rb' + - 'lib/mysql2/error.rb' + - 'lib/mysql2/field.rb' + - 'lib/mysql2/result.rb' + - 'lib/mysql2/statement.rb' + - 'lib/mysql2/version.rb' # Offense count: 9 # Configuration parameters: AllowedVariables. Style/GlobalVars: - Enabled: false + Exclude: + - 'ext/mysql2/extconf.rb' -# Offense count: 13 +# Offense count: 14 # Cop supports --auto-correct. Style/NumericLiterals: MinDigits: 20 -# Offense count: 673 +# Offense count: 680 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/StringLiterals: diff --git a/Gemfile b/Gemfile index 117579391..f4a9ca4f8 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem 'rake-compiler', '~> 0.9.5' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' - gem 'rubocop', '~> 0.32.0' unless RUBY_VERSION =~ /1.8/ + gem 'rubocop', '~> 0.34.0' unless RUBY_VERSION =~ /1.8/ end group :benchmarks do diff --git a/lib/mysql2.rb b/lib/mysql2.rb index a577c5cf3..3f4335a08 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -13,7 +13,7 @@ ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))) # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary - File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\') + File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).tr('/', '\\') else # This will use default / system library paths 'libmysql.dll' From 18820d113ef95812b91b0c2069d1a54670920f85 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 9 Jun 2015 11:50:11 -0400 Subject: [PATCH 366/783] Use mysql types to avoid conversions --- ext/mysql2/result.c | 6 ++---- ext/mysql2/result.h | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index c3cc00bc9..01b14058d 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -1,10 +1,8 @@ #include -#include - -#ifdef HAVE_RUBY_ENCODING_H #include "mysql_enc_to_ruby.h" +#ifdef HAVE_RUBY_ENCODING_H static rb_encoding *binaryEncoding; #endif @@ -764,7 +762,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } - if ((unsigned)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) { + if ((my_ulonglong)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) { for (i=0; inumberOfFields; i++) { rb_mysql_result_fetch_field(self, i, symbolizeKeys); } diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index e8c2cfafb..19cb881df 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -10,8 +10,8 @@ typedef struct { VALUE client; VALUE encoding; VALUE statement; - unsigned int numberOfFields; - unsigned long numberOfRows; + my_ulonglong numberOfFields; + my_ulonglong numberOfRows; unsigned long lastRowProcessed; char is_streaming; char streamingComplete; From 76dde0e4489d96dc505963b12157e416eaead4a0 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 10 Jun 2015 06:32:48 -0400 Subject: [PATCH 367/783] DRY --- ext/mysql2/client.c | 12 ++++++------ ext/mysql2/client.h | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index f6f03729c..2b77b2e1d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -212,7 +212,7 @@ static void *nogvl_close(void *ptr) { mysql_client_wrapper *wrapper; wrapper = ptr; if (wrapper->connected) { - wrapper->active_thread = Qnil; + MARK_CONN_INACTIVE(self); wrapper->connected = 0; #ifndef _WIN32 /* Invalidate the socket before calling mysql_close(). This prevents @@ -256,7 +256,7 @@ static VALUE allocate(VALUE klass) { mysql_client_wrapper * wrapper; obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; - wrapper->active_thread = Qnil; + MARK_CONN_INACTIVE(self); wrapper->server_version = 0; wrapper->reconnect_enabled = 0; wrapper->connect_timeout = 0; @@ -412,7 +412,7 @@ static VALUE do_send_query(void *args) { mysql_client_wrapper *wrapper = query_args->wrapper; if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ - wrapper->active_thread = Qnil; + MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); } return Qnil; @@ -443,7 +443,7 @@ static void *nogvl_do_result(void *ptr, char use_result) { /* once our result is stored off, this connection is ready for another command to be issued */ - wrapper->active_thread = Qnil; + MARK_CONN_INACTIVE(self); return result; } @@ -512,7 +512,7 @@ struct async_query_args { static VALUE disconnect_and_raise(VALUE self, VALUE error) { GET_CLIENT(self); - wrapper->active_thread = Qnil; + MARK_CONN_INACTIVE(self); wrapper->connected = 0; /* Invalidate the MySQL socket to prevent further communication. @@ -588,7 +588,7 @@ static VALUE finish_and_mark_inactive(void *args) { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); mysql_free_result(result); - wrapper->active_thread = Qnil; + MARK_CONN_INACTIVE(self); } return Qnil; diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 94098e21a..ce6f39214 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -59,7 +59,6 @@ typedef struct { void rb_mysql_client_set_active_thread(VALUE self); #define MARK_CONN_INACTIVE(conn) do {\ - GET_CLIENT(conn); \ wrapper->active_thread = Qnil; \ } while(0) From 110d05c706c28f4495716fda5e937d0a49d04b7d Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sun, 7 Jun 2015 20:59:06 -0400 Subject: [PATCH 368/783] -Weverything and whitelist warnings --- ext/mysql2/client.c | 3 --- ext/mysql2/client.h | 2 +- ext/mysql2/extconf.rb | 39 ++++++++++++++++++++++++++++----------- ext/mysql2/infile.c | 4 ++-- ext/mysql2/mysql2_ext.h | 8 ++------ ext/mysql2/result.c | 4 +--- ext/mysql2/result.h | 2 +- ext/mysql2/statement.h | 2 +- 8 files changed, 36 insertions(+), 28 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 2b77b2e1d..2da7c5765 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -610,7 +610,6 @@ void rb_mysql_client_set_active_thread(VALUE self) { const char *thr = StringValueCStr(inspect); rb_raise(cMysql2Error, "This connection is in use by: %s", thr); - (void)RB_GC_GUARD(inspect); } } @@ -1230,7 +1229,6 @@ void init_mysql2_client() { } if (lib[i] != MYSQL_LINK_VERSION[i]) { rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib); - return; } } #endif @@ -1239,7 +1237,6 @@ void init_mysql2_client() { /* without race condition in the library */ if (mysql_library_init(0, NULL, NULL) != 0) { rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library"); - return; } #if 0 diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index ce6f39214..3e29d6076 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -66,7 +66,7 @@ void rb_mysql_client_set_active_thread(VALUE self); mysql_client_wrapper *wrapper; \ Data_Get_Struct(self, mysql_client_wrapper, wrapper); -void init_mysql2_client(); +void init_mysql2_client(void); void decr_mysql2_client(mysql_client_wrapper *wrapper); #endif diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 09cefe38e..4da5e6687 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -93,19 +93,36 @@ def asplode(lib) end # This is our wishlist. We use whichever flags work on the host. -# TODO: fix statement.c and remove -Wno-declaration-after-statement -# TODO: fix gperf mysql_enc_name_to_ruby.h and remove -Wno-missing-field-initializers -%w( - -Wall - -Wextra - -Werror - -Wno-unused-function - -Wno-declaration-after-statement - -Wno-missing-field-initializers -).each do |flag| - $CFLAGS << ' ' << flag if try_link('int main() {return 0;}', flag) +# -Wall and -Wextra are included by default. +usable_flags = [ + '-fsanitize=address', + '-fsanitize=cfi', + '-fsanitize=integer', + '-fsanitize=memory', + '-fsanitize=thread', + '-fsanitize=undefined', + '-Werror', + '-Weverything', + '-Wno-bad-function-cast', # rb_thread_call_without_gvl returns void * that we cast to VALUE + '-Wno-conditional-uninitialized', # false positive in client.c + '-Wno-covered-switch-default', # result.c -- enum_field_types (when fully covered, e.g. mysql 5.5) + '-Wno-declaration-after-statement', # GET_CLIENT followed by GET_STATEMENT in statement.c + '-Wno-disabled-macro-expansion', # rubby :( + '-Wno-documentation-unknown-command', # rubby :( + '-Wno-missing-field-initializers', # gperf generates bad code + '-Wno-missing-variable-declarations', # missing symbols due to ruby native ext initialization + '-Wno-padded', # mysql :( + '-Wno-sign-conversion', # gperf generates bad code + '-Wno-static-in-inline', # gperf generates bad code + '-Wno-switch-enum', # result.c -- enum_field_types (when not fully covered, e.g. mysql 5.6+) + '-Wno-undef', # rubinius :( + '-Wno-used-but-marked-unused', # rubby :( +].select do |flag| + try_link('int main() {return 0;}', flag) end +$CFLAGS << ' ' << usable_flags.join(' ') + if RUBY_PLATFORM =~ /mswin|mingw/ # Build libmysql.a interface link library require 'rake' diff --git a/ext/mysql2/infile.c b/ext/mysql2/infile.c index 6ff1f1f42..b25934d15 100644 --- a/ext/mysql2/infile.c +++ b/ext/mysql2/infile.c @@ -56,7 +56,7 @@ mysql2_local_infile_init(void **ptr, const char *filename, void *userdata) * < 0 error */ static int -mysql2_local_infile_read(void *ptr, char *buf, uint buf_len) +mysql2_local_infile_read(void *ptr, char *buf, unsigned int buf_len) { int count; mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr; @@ -95,7 +95,7 @@ mysql2_local_infile_end(void *ptr) * Error message number (see http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html) */ static int -mysql2_local_infile_error(void *ptr, char *error_msg, uint error_msg_len) +mysql2_local_infile_error(void *ptr, char *error_msg, unsigned int error_msg_len) { mysql2_local_infile_data *data = (mysql2_local_infile_data *) ptr; diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index 381266e0d..66c33a949 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -1,18 +1,14 @@ #ifndef MYSQL2_EXT #define MYSQL2_EXT +void Init_mysql2(void); + /* tell rbx not to use it's caching compat layer by doing this we're making a promise to RBX that we'll never modify the pointers we get back from RSTRING_PTR */ #define RSTRING_NOT_MODIFIED #include -#ifndef HAVE_UINT -#define HAVE_UINT -typedef unsigned short ushort; -typedef unsigned int uint; -#endif - #ifdef HAVE_MYSQL_H #include #include diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 01b14058d..e33452ae6 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -159,8 +159,7 @@ static void *nogvl_stmt_fetch(void *ptr) { return (void *)r; } - -static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) { +static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) { VALUE rb_field; GET_RESULT(self); @@ -508,7 +507,6 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co default: rb_raise(cMysql2Error, "unhandled buffer type: %d", result_buffer->buffer_type); - break; } } diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 19cb881df..0c25b24b6 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -1,7 +1,7 @@ #ifndef MYSQL2_RESULT_H #define MYSQL2_RESULT_H -void init_mysql2_result(); +void init_mysql2_result(void); VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement); typedef struct { diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index ad81bfa12..f8d44fce6 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -9,7 +9,7 @@ typedef struct { int refcount; } mysql_stmt_wrapper; -void init_mysql2_statement(); +void init_mysql2_statement(void); void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper); VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); From 83d36e24d260acd13f0071db9b0bef071ecb5684 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 9 Sep 2015 21:27:07 -0400 Subject: [PATCH 369/783] move some CFLAGS to CI only --- ext/mysql2/extconf.rb | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 4da5e6687..e38193d53 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -94,14 +94,7 @@ def asplode(lib) # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. -usable_flags = [ - '-fsanitize=address', - '-fsanitize=cfi', - '-fsanitize=integer', - '-fsanitize=memory', - '-fsanitize=thread', - '-fsanitize=undefined', - '-Werror', +wishlist = [ '-Weverything', '-Wno-bad-function-cast', # rb_thread_call_without_gvl returns void * that we cast to VALUE '-Wno-conditional-uninitialized', # false positive in client.c @@ -117,7 +110,21 @@ def asplode(lib) '-Wno-switch-enum', # result.c -- enum_field_types (when not fully covered, e.g. mysql 5.6+) '-Wno-undef', # rubinius :( '-Wno-used-but-marked-unused', # rubby :( -].select do |flag| +] + +if ENV['CI'] + wishlist += [ + '-Werror', + '-fsanitize=address', + '-fsanitize=cfi', + '-fsanitize=integer', + '-fsanitize=memory', + '-fsanitize=thread', + '-fsanitize=undefined', + ] +end + +usable_flags = wishlist.select do |flag| try_link('int main() {return 0;}', flag) end From 355213dda192ec9ab455ea9832c869fe7d467bae Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 9 Sep 2015 17:02:15 -0700 Subject: [PATCH 370/783] Also exclude pkg/**/* and vendor/**/* from Rubocop --- .rubocop.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 0b7e938d4..5ebfb8bdf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,9 @@ inherit_from: .rubocop_todo.yml AllCops: DisplayCopNames: true Exclude: + - 'pkg/**/*' - 'tmp/**/*' + - 'vendor/**/*' Lint/EndAlignment: AlignWith: variable From f9eba44b0519580d2cd52b51d2b902f45186f427 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 13 Sep 2014 21:56:39 -0700 Subject: [PATCH 371/783] Move error_number and sql_state into Mysql2::Error initializer --- ext/mysql2/client.c | 8 +++++--- lib/mysql2/error.rb | 11 ++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index f6f03729c..52b3ab73c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -124,9 +124,11 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { rb_enc_associate(rb_sql_state, rb_usascii_encoding()); #endif - e = rb_funcall(cMysql2Error, intern_new, 2, rb_error_msg, LONG2FIX(wrapper->server_version)); - rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client))); - rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state); + e = rb_funcall(cMysql2Error, intern_new, 4, + rb_error_msg, + LONG2FIX(wrapper->server_version), + UINT2NUM(mysql_errno(wrapper->client)), + rb_sql_state); rb_exc_raise(e); return Qnil; } diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 5eec6fff0..d4636bd3b 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -8,24 +8,21 @@ class Error < StandardError :replace => '?'.freeze, }.freeze - attr_accessor :error_number - attr_reader :sql_state + attr_reader :error_number, :sql_state attr_writer :server_version # Mysql gem compatibility alias_method :errno, :error_number alias_method :error, :message - def initialize(msg, server_version = nil) + def initialize(msg, server_version=nil, error_number=nil, sql_state=nil) self.server_version = server_version + self.error_number = error_number + self.sql_state = sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state super(clean_message(msg)) end - def sql_state=(state) - @sql_state = state.respond_to?(:encode) ? state.encode(ENCODE_OPTS) : state - end - private # In MySQL 5.5+ error messages are always constructed server-side as UTF-8 From 1d4de8963c71ce88772a0e27883066160c3dbbd4 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 14 Sep 2014 07:56:31 -0700 Subject: [PATCH 372/783] Move all args to Mysql2::Error to a separate method --- ext/mysql2/client.c | 8 +++----- lib/mysql2/error.rb | 16 +++++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 52b3ab73c..c01889ba9 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -17,8 +17,7 @@ VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error; static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; -static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql; -static ID intern_brackets, intern_new; +static ID intern_brackets, intern_new, intern_merge, intern_merge_bang, intern_new_with_args; #ifndef HAVE_RB_HASH_DUP VALUE rb_hash_dup(VALUE other) { @@ -124,7 +123,7 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { rb_enc_associate(rb_sql_state, rb_usascii_encoding()); #endif - e = rb_funcall(cMysql2Error, intern_new, 4, + e = rb_funcall(cMysql2Error, intern_new_with_args, 4, rb_error_msg, LONG2FIX(wrapper->server_version), UINT2NUM(mysql_errno(wrapper->client)), @@ -1303,8 +1302,7 @@ void init_mysql2_client() { intern_new = rb_intern("new"); intern_merge = rb_intern("merge"); intern_merge_bang = rb_intern("merge!"); - intern_error_number_eql = rb_intern("error_number="); - intern_sql_state_eql = rb_intern("sql_state="); + intern_new_with_args = rb_intern("new_with_args"); #ifdef CLIENT_LONG_PASSWORD rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index d4636bd3b..f6ffcc425 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -9,20 +9,26 @@ class Error < StandardError }.freeze attr_reader :error_number, :sql_state - attr_writer :server_version # Mysql gem compatibility alias_method :errno, :error_number alias_method :error, :message - def initialize(msg, server_version=nil, error_number=nil, sql_state=nil) - self.server_version = server_version - self.error_number = error_number - self.sql_state = sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state + def initialize(msg) + @server_version ||= nil super(clean_message(msg)) end + def self.new_with_args(msg, server_version, error_number, sql_state) + err = allocate + err.instance_variable_set('@server_version', server_version) + err.instance_variable_set('@error_number', error_number) + err.instance_variable_set('@sql_state', sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state) + err.send(:initialize, msg) + err + end + private # In MySQL 5.5+ error messages are always constructed server-side as UTF-8 From abd06c5063c3ba38c4d4eb49d7d488530d0dcec7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 9 Sep 2015 18:23:20 -0700 Subject: [PATCH 373/783] Meld rb_raise_mysql2_stmt_error2 into rb_raise_mysql2_stmt_error --- ext/mysql2/result.c | 12 ++------ ext/mysql2/statement.c | 62 ++++++++++++++++-------------------------- ext/mysql2/statement.h | 6 +--- 3 files changed, 26 insertions(+), 54 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index c3cc00bc9..9675a8445 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -357,11 +357,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } if (mysql_stmt_bind_result(wrapper->stmt_wrapper->stmt, wrapper->result_buffers)) { - rb_raise_mysql2_stmt_error2(wrapper->stmt_wrapper->stmt -#ifdef HAVE_RUBY_ENCODING_H - , conn_enc -#endif - ); + rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper); } { @@ -372,11 +368,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co case 1: /* error */ - rb_raise_mysql2_stmt_error2(wrapper->stmt_wrapper->stmt -#ifdef HAVE_RUBY_ENCODING_H - , conn_enc -#endif - ); + rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper); case MYSQL_NO_DATA: /* no more row */ diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 28e4cf624..57f45979f 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -2,7 +2,7 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; -static VALUE sym_stream, intern_error_number_eql, intern_sql_state_eql, intern_each; +static VALUE sym_stream, intern_new_with_args, intern_each; static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year; #define GET_STATEMENT(self) \ @@ -31,15 +31,17 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) { } } -VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt -#ifdef HAVE_RUBY_ENCODING_H - , rb_encoding *conn_enc -#endif - ) { - VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt)); - VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt)); - VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg); + +void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) { + VALUE e; + GET_CLIENT(stmt_wrapper->client); + VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt)); + VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt)); + #ifdef HAVE_RUBY_ENCODING_H + rb_encoding *conn_enc; + conn_enc = rb_to_encoding(wrapper->encoding); + rb_encoding *default_internal_enc = rb_default_internal_encoding(); rb_enc_associate(rb_error_msg, conn_enc); @@ -49,30 +51,13 @@ VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); } #endif - rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_stmt_errno(stmt))); - rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state); - rb_exc_raise(e); - return Qnil; -} -static void rb_raise_mysql2_stmt_error(VALUE self) { -#ifdef HAVE_RUBY_ENCODING_H - rb_encoding *conn_enc; -#endif - GET_STATEMENT(self); - -#ifdef HAVE_RUBY_ENCODING_H - { - GET_CLIENT(stmt_wrapper->client); - conn_enc = rb_to_encoding(wrapper->encoding); - } -#endif - - rb_raise_mysql2_stmt_error2(stmt_wrapper->stmt -#ifdef HAVE_RUBY_ENCODING_H - , conn_enc -#endif - ); + e = rb_funcall(cMysql2Error, intern_new_with_args, 4, + rb_error_msg, + LONG2FIX(wrapper->server_version), + UINT2NUM(mysql_stmt_errno(stmt_wrapper->stmt)), + rb_sql_state); + rb_exc_raise(e); } @@ -146,7 +131,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { args.sql_len = RSTRING_LEN(sql); if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { - rb_raise_mysql2_stmt_error(rb_stmt); + rb_raise_mysql2_stmt_error(stmt_wrapper); } } @@ -335,13 +320,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { // copies bind_buffers into internal storage if (mysql_stmt_bind_param(stmt, bind_buffers)) { FREE_BINDS; - rb_raise_mysql2_stmt_error(self); + rb_raise_mysql2_stmt_error(stmt_wrapper); } } if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) { FREE_BINDS; - rb_raise_mysql2_stmt_error(self); + rb_raise_mysql2_stmt_error(stmt_wrapper); } FREE_BINDS; @@ -352,7 +337,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. MARK_CONN_INACTIVE(stmt_wrapper->client); - rb_raise_mysql2_stmt_error(self); + rb_raise_mysql2_stmt_error(stmt_wrapper); } // no data and no error, so query was not a SELECT return Qnil; @@ -367,7 +352,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { // recieve the whole result set from the server if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) { mysql_free_result(metadata); - rb_raise_mysql2_stmt_error(self); + rb_raise_mysql2_stmt_error(stmt_wrapper); } MARK_CONN_INACTIVE(stmt_wrapper->client); } @@ -440,8 +425,7 @@ void init_mysql2_statement() { sym_stream = ID2SYM(rb_intern("stream")); - intern_error_number_eql = rb_intern("error_number="); - intern_sql_state_eql = rb_intern("sql_state="); + intern_new_with_args = rb_intern("new_with_args"); intern_each = rb_intern("each"); intern_usec = rb_intern("usec"); diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index ad81bfa12..d1f9a5a72 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -13,10 +13,6 @@ void init_mysql2_statement(); void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper); VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); -VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt -#ifdef HAVE_RUBY_ENCODING_H - , rb_encoding* conn_enc -#endif - ); +void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper); #endif From f53dfcf298d5c7058fb196dd1ba503ca44db4d92 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 10 Sep 2015 13:54:32 -0400 Subject: [PATCH 374/783] fix c-warnings; fix CI --- ext/mysql2/client.c | 3 --- ext/mysql2/mysql2_ext.h | 2 ++ ext/mysql2/statement.h | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 4ca8e898e..df2e43e7c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -129,7 +129,6 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { UINT2NUM(mysql_errno(wrapper->client)), rb_sql_state); rb_exc_raise(e); - return Qnil; } static void *nogvl_init(void *ptr) { @@ -525,8 +524,6 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) { } rb_exc_raise(error); - - return Qnil; } static VALUE do_query(void *args) { diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index 66c33a949..f53f4b4a1 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -29,8 +29,10 @@ void Init_mysql2(void); #endif #if defined(__GNUC__) && (__GNUC__ >= 3) +#define RB_MYSQL_NORETURN __attribute__ ((noreturn)) #define RB_MYSQL_UNUSED __attribute__ ((unused)) #else +#define RB_MYSQL_NORETURN #define RB_MYSQL_UNUSED #endif diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index 88b567dff..0c1d54c55 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -13,6 +13,6 @@ void init_mysql2_statement(void); void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper); VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); -void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper); +void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) RB_MYSQL_NORETURN; #endif From b1ca5cbb09bf97ed06c4e40e5774144f0b0c2158 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 6 Sep 2015 09:39:55 -0700 Subject: [PATCH 375/783] Test on ruby-head, allow failures --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1a3eeebd0..f95c7e3a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ rvm: - 2.2 - ree - rbx-2 + - ruby-head matrix: include: - rvm: 2.0.0 @@ -30,3 +31,5 @@ matrix: - rvm: 2.0.0 env: DB=mysql55 os: osx + allow_failures: + - rvm: ruby-head From 608c7b738aff16b18abd3f3b2f0ccd92dc63ac7f Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Mon, 7 Sep 2015 21:18:21 -0700 Subject: [PATCH 376/783] Add Mysql2::Statement#last_id and #affected_rows for prepared INSERT/UPDATE/DELETE statements --- ext/mysql2/statement.c | 29 ++++++++++++++++++ spec/mysql2/statement_spec.rb | 58 +++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 57f45979f..c4c111a8f 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -415,6 +415,33 @@ static VALUE fields(VALUE self) { return field_list; } +/* call-seq: + * stmt.last_id + * + * Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE. + */ +static VALUE rb_mysql_stmt_last_id(VALUE self) { + GET_STATEMENT(self); + return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt)); +} + +/* call-seq: + * stmt.affected_rows + * + * Returns the number of rows changed, deleted, or inserted. + */ +static VALUE rb_mysql_stmt_affected_rows(VALUE self) { + my_ulonglong affected; + GET_STATEMENT(self); + + affected = mysql_stmt_affected_rows(stmt_wrapper->stmt); + if (affected == (my_ulonglong)-1) { + rb_raise_mysql2_stmt_error(self); + } + + return ULL2NUM(affected); +} + void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); @@ -422,6 +449,8 @@ void init_mysql2_statement() { rb_define_method(cMysql2Statement, "field_count", field_count, 0); rb_define_method(cMysql2Statement, "execute", execute, -1); rb_define_method(cMysql2Statement, "fields", fields, 0); + rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0); + rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0); sym_stream = ID2SYM(rb_intern("stream")); diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 3fb97dcfb..88e53bc41 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -594,4 +594,62 @@ end end end + + context 'last_id' do + before(:each) do + @client.query "USE test" + @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" + end + + after(:each) do + @client.query "DROP TABLE lastIdTest" + end + + it 'should return last insert id' do + stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' + expect(stmt.last_id).to eq 0 + stmt.execute 1 + expect(stmt.last_id).to eq 1 + end + + it 'should handle bigint ids' do + stmt = @client.prepare 'INSERT INTO lastIdTest (id, blah) VALUES (?, ?)' + stmt.execute 5000000000, 5000 + expect(stmt.last_id).to eql(5000000000) + + stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' + stmt.execute 5001 + expect(stmt.last_id).to eql(5000000001) + end + end + + context 'affected_rows' do + before :each do + @client.query 'DELETE FROM mysql2_test' + @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (1)' + end + + after :each do + @client.query 'DELETE FROM mysql2_test' + end + + it 'should return number of rows affected by an insert' do + stmt = @client.prepare 'INSERT INTO mysql2_test (bool_cast_test) VALUES (?)' + expect(stmt.affected_rows).to eq 0 + stmt.execute 1 + expect(stmt.affected_rows).to eq 1 + end + + it 'should return number of rows affected by an update' do + stmt = @client.prepare 'UPDATE mysql2_test SET bool_cast_test=? WHERE bool_cast_test=?' + stmt.execute 0, 1 + expect(stmt.affected_rows).to eq 1 + end + + it 'should return number of rows affected by a delete' do + stmt = @client.prepare 'DELETE FROM mysql2_test WHERE bool_cast_test=?' + stmt.execute 1 + expect(stmt.affected_rows).to eq 1 + end + end end From d12aaa42db867954a85cddce6c0dd899e6f92ac9 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 11 Sep 2015 09:18:45 -0700 Subject: [PATCH 377/783] Spec fixes for Statement#last_id and #affected_rows --- spec/mysql2/statement_spec.rb | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 88e53bc41..bbb493fce 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -597,12 +597,12 @@ context 'last_id' do before(:each) do - @client.query "USE test" - @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" + @client.query 'USE test' + @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))' end after(:each) do - @client.query "DROP TABLE lastIdTest" + @client.query 'DROP TABLE lastIdTest' end it 'should return last insert id' do @@ -625,29 +625,41 @@ context 'affected_rows' do before :each do - @client.query 'DELETE FROM mysql2_test' - @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (1)' + @client.query 'USE test' + @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))' end after :each do - @client.query 'DELETE FROM mysql2_test' + @client.query 'DROP TABLE lastIdTest' end it 'should return number of rows affected by an insert' do - stmt = @client.prepare 'INSERT INTO mysql2_test (bool_cast_test) VALUES (?)' + stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' expect(stmt.affected_rows).to eq 0 stmt.execute 1 expect(stmt.affected_rows).to eq 1 end it 'should return number of rows affected by an update' do - stmt = @client.prepare 'UPDATE mysql2_test SET bool_cast_test=? WHERE bool_cast_test=?' + stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' + stmt.execute 1 + expect(stmt.affected_rows).to eq 1 + stmt.execute 2 + expect(stmt.affected_rows).to eq 1 + + stmt = @client.prepare 'UPDATE lastIdTest SET blah=? WHERE blah=?' stmt.execute 0, 1 expect(stmt.affected_rows).to eq 1 end it 'should return number of rows affected by a delete' do - stmt = @client.prepare 'DELETE FROM mysql2_test WHERE bool_cast_test=?' + stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' + stmt.execute 1 + expect(stmt.affected_rows).to eq 1 + stmt.execute 2 + expect(stmt.affected_rows).to eq 1 + + stmt = @client.prepare 'DELETE FROM lastIdTest WHERE blah=?' stmt.execute 1 expect(stmt.affected_rows).to eq 1 end From de7c375d12083c2e224c46e1918b43cd76b78c99 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 11 Sep 2015 09:29:05 -0700 Subject: [PATCH 378/783] Missed in the #672 rebase: rb_raise_mysql2_stmt_error takes a stmt_wrapper now --- ext/mysql2/statement.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index c4c111a8f..c8eef3da9 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -436,7 +436,7 @@ static VALUE rb_mysql_stmt_affected_rows(VALUE self) { affected = mysql_stmt_affected_rows(stmt_wrapper->stmt); if (affected == (my_ulonglong)-1) { - rb_raise_mysql2_stmt_error(self); + rb_raise_mysql2_stmt_error(stmt_wrapper); } return ULL2NUM(affected); From ef5d61d873a5bbf3ebbd8a5256fc2d29b3c4eba9 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Tue, 8 Sep 2015 00:58:51 -0700 Subject: [PATCH 379/783] Support prepared_statement.close Useful when caching prepared statements. Evicted statements can release server resources immediately rather than waiting for Ruby GC. --- ext/mysql2/statement.c | 26 +++++++++++++++++++++++++- ext/mysql2/statement.h | 1 + spec/mysql2/statement_spec.rb | 13 +++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index c8eef3da9..0aa36741c 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -17,6 +17,15 @@ static void rb_mysql_stmt_mark(void * ptr) { rb_gc_mark(stmt_wrapper->client); } +static void *nogvl_stmt_close(void *ptr) { + mysql_stmt_wrapper *stmt_wrapper = (mysql_stmt_wrapper *)ptr; + if (stmt_wrapper->closed == 0) { + mysql_stmt_close(stmt_wrapper->stmt); + stmt_wrapper->closed = 1; + } + return NULL; +} + static void rb_mysql_stmt_free(void * ptr) { mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; decr_mysql2_stmt(stmt_wrapper); @@ -26,7 +35,7 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) { stmt_wrapper->refcount--; if (stmt_wrapper->refcount == 0) { - mysql_stmt_close(stmt_wrapper->stmt); + nogvl_stmt_close(stmt_wrapper); xfree(stmt_wrapper); } } @@ -94,6 +103,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); { stmt_wrapper->client = rb_client; + stmt_wrapper->closed = 0; stmt_wrapper->refcount = 1; stmt_wrapper->stmt = NULL; } @@ -442,6 +452,19 @@ static VALUE rb_mysql_stmt_affected_rows(VALUE self) { return ULL2NUM(affected); } +/* call-seq: + * stmt.close + * + * Explicitly closing this will free up server resources immediately rather + * than waiting for the garbage collector. Useful if you're managing your + * own prepared statement cache. + */ +static VALUE rb_mysql_stmt_close(VALUE self) { + GET_STATEMENT(self); + rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0); + return Qnil; +} + void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); @@ -451,6 +474,7 @@ void init_mysql2_statement() { rb_define_method(cMysql2Statement, "fields", fields, 0); rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0); rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0); + rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0); sym_stream = ID2SYM(rb_intern("stream")); diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index 0c1d54c55..65bbbae22 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -6,6 +6,7 @@ extern VALUE cMysql2Statement; typedef struct { VALUE client; MYSQL_STMT *stmt; + int closed; int refcount; } mysql_stmt_wrapper; diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index bbb493fce..47ac6e9ca 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -664,4 +664,17 @@ expect(stmt.affected_rows).to eq 1 end end + + context 'close' do + it 'should free server resources' do + stmt = @client.prepare 'SELECT 1' + expect(stmt.close).to eq nil + end + + it 'should raise an error on subsequent execution' do + stmt = @client.prepare 'SELECT 1' + stmt.close + expect { stmt.execute }.to raise_error(Mysql2::Error) + end + end end From 9c27b20135409981d39317e23220a2c0a8a325b3 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Wed, 2 Sep 2015 06:11:24 +0000 Subject: [PATCH 380/783] Terminate connections correctly when calling close The normal termination procedure is to call mysql_close on a valid MYSQL handle structure. Failing to do so is interpreted as a communication failure at the server, which logs the client/connection as "aborted." Allow the user to decide which handles are valid and when to close them. --- ext/mysql2/client.c | 45 +++++++++++++++++++++----------------- spec/mysql2/client_spec.rb | 8 +++++++ 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index df2e43e7c..71deb991b 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -214,27 +214,13 @@ static void *nogvl_close(void *ptr) { if (wrapper->connected) { MARK_CONN_INACTIVE(self); wrapper->connected = 0; -#ifndef _WIN32 - /* Invalidate the socket before calling mysql_close(). This prevents - * mysql_close() from sending a mysql-QUIT or from calling shutdown() on - * the socket. The difference is that invalidate_fd will drop this - * process's reference to the socket only, while a QUIT or shutdown() - * would render the underlying connection unusable, interrupting other - * processes which share this object across a fork(). - */ - if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { - fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, leaking some memory\n"); - close(wrapper->client->net.fd); - return NULL; - } -#endif - - mysql_close(wrapper->client); /* only used to free memory at this point */ + mysql_close(wrapper->client); } return NULL; } +/* this is called during GC */ static void rb_mysql_client_free(void *ptr) { mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; decr_mysql2_client(wrapper); @@ -245,6 +231,22 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper) wrapper->refcount--; if (wrapper->refcount == 0) { +#ifndef _WIN32 + if (wrapper->connected) { + /* The client is being garbage collected while connected. Prevent + * mysql_close() from sending a mysql-QUIT or from calling shutdown() on + * the socket by invalidating it. invalidate_fd() will drop this + * process's reference to the socket only, while a QUIT or shutdown() + * would render the underlying connection unusable, interrupting other + * processes which share this object across a fork(). + */ + if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { + fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n"); + close(wrapper->client->net.fd); + } + } +#endif + nogvl_close(wrapper); xfree(wrapper->client); xfree(wrapper); @@ -378,10 +380,13 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po } /* - * Immediately disconnect from the server, normally the garbage collector - * will disconnect automatically when a connection is no longer needed. - * Explicitly closing this will free up server resources sooner than waiting - * for the garbage collector. + * Terminate the connection; call this when the connection is no longer needed. + * The garbage collector can close the connection, but doing so emits an + * "Aborted connection" error on the server and increments the Aborted_clients + * status variable. + * + * @see http://dev.mysql.com/doc/en/communication-errors.html + * @return [void] */ static VALUE rb_mysql_client_close(VALUE self) { GET_CLIENT(self); diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 7e3befe26..2ba7acb82 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -162,6 +162,14 @@ def run_gc sleep(0.5) end + it "should terminate connections when calling close" do + expect { + Mysql2::Client.new(DatabaseCredentials['root']).close + }.to_not change { + @client.query("SHOW STATUS LIKE 'Aborted_clients'").first['Value'].to_i + } + end + it "should not leave dangling connections after garbage collection" do run_gc From bcd87704818fd1182a2e907102ef7519e7584105 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 12 Sep 2015 22:45:42 -0700 Subject: [PATCH 381/783] Always check statement handle before using it, set the handle to NULL when closing it --- ext/mysql2/statement.c | 10 +++++----- ext/mysql2/statement.h | 1 - spec/mysql2/statement_spec.rb | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 0aa36741c..a9fbd34e3 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -7,7 +7,8 @@ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, inter #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ - Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); + Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \ + if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } static void rb_mysql_stmt_mark(void * ptr) { @@ -17,11 +18,11 @@ static void rb_mysql_stmt_mark(void * ptr) { rb_gc_mark(stmt_wrapper->client); } -static void *nogvl_stmt_close(void *ptr) { +static void *nogvl_stmt_close(void * ptr) { mysql_stmt_wrapper *stmt_wrapper = (mysql_stmt_wrapper *)ptr; - if (stmt_wrapper->closed == 0) { + if (stmt_wrapper->stmt) { mysql_stmt_close(stmt_wrapper->stmt); - stmt_wrapper->closed = 1; + stmt_wrapper->stmt = NULL; } return NULL; } @@ -103,7 +104,6 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); { stmt_wrapper->client = rb_client; - stmt_wrapper->closed = 0; stmt_wrapper->refcount = 1; stmt_wrapper->stmt = NULL; } diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index 65bbbae22..0c1d54c55 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -6,7 +6,6 @@ extern VALUE cMysql2Statement; typedef struct { VALUE client; MYSQL_STMT *stmt; - int closed; int refcount; } mysql_stmt_wrapper; diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 47ac6e9ca..ce11489ee 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -674,7 +674,7 @@ it 'should raise an error on subsequent execution' do stmt = @client.prepare 'SELECT 1' stmt.close - expect { stmt.execute }.to raise_error(Mysql2::Error) + expect { stmt.execute }.to raise_error(Mysql2::Error, /Invalid statement handle/) end end end From 60b3aa0497e173fdd52e687131f7a513ab18911c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 12 Sep 2015 22:47:24 -0700 Subject: [PATCH 382/783] Remove unused intern_new --- ext/mysql2/client.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index df2e43e7c..a9aa37069 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -17,7 +17,7 @@ VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error; static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; -static ID intern_brackets, intern_new, intern_merge, intern_merge_bang, intern_new_with_args; +static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args; #ifndef HAVE_RB_HASH_DUP VALUE rb_hash_dup(VALUE other) { @@ -1293,7 +1293,6 @@ void init_mysql2_client() { sym_stream = ID2SYM(rb_intern("stream")); intern_brackets = rb_intern("[]"); - intern_new = rb_intern("new"); intern_merge = rb_intern("merge"); intern_merge_bang = rb_intern("merge!"); intern_new_with_args = rb_intern("new_with_args"); From a4b04890d109e8405e756ad2d6c49d1614f6bcdf Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 8 Sep 2015 23:32:17 -0700 Subject: [PATCH 383/783] Fix Timeout interrupt handling on Ruby 2.3 and protect Mysql2::Statement#execute Timeout::ExitException was removed in Ruby 2.3.0, 2.2.3, and 2.1.8, in favor of Timeout::Error. Backwards compatible aliases are provided for Ruby 2.1.x and 2.2.x, but not earlier verions. With thanks to @jeremy for PR #671 and @yui-knk for PR #677, this commit also protects prepared statements from being interrupted, so the compat shim is in Mysql2::Util. --- ext/mysql2/statement.c | 2 +- lib/mysql2.rb | 18 ++++++++++++++++++ lib/mysql2/client.rb | 4 +--- lib/mysql2/statement.rb | 12 ++++++++++++ spec/mysql2/client_spec.rb | 16 +++++++++++++++- 5 files changed, 47 insertions(+), 5 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index a9fbd34e3..3b83feaf2 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -470,7 +470,7 @@ void init_mysql2_statement() { rb_define_method(cMysql2Statement, "param_count", param_count, 0); rb_define_method(cMysql2Statement, "field_count", field_count, 0); - rb_define_method(cMysql2Statement, "execute", execute, -1); + rb_define_method(cMysql2Statement, "_execute", execute, -1); rb_define_method(cMysql2Statement, "fields", fields, 0); rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0); rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0); diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 3f4335a08..a45cace3a 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -62,5 +62,23 @@ def self.key_hash_as_symbols(hash) return nil unless hash Hash[hash.map { |k, v| [k.to_sym, v] }] end + + # + # In Mysql2::Client#query and Mysql2::Statement#execute, + # Thread#handle_interrupt is used to prevent Timeout#timeout + # from interrupting query execution. + # + # Timeout::ExitException was removed in Ruby 2.3.0, 2.2.3, and 2.1.8, + # but is present in earlier 2.1.x and 2.2.x, so we provide a shim. + # + if Thread.respond_to?(:handle_interrupt) + require 'timeout' + # rubocop:disable Style/ConstantName + TimeoutError = if defined?(::Timeout::ExitException) + ::Timeout::ExitException + else + ::Timeout::Error + end + end end end diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 2b60fc242..bc2445ea9 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -80,10 +80,8 @@ def initialize(opts = {}) end if Thread.respond_to?(:handle_interrupt) - require 'timeout' - def query(sql, options = {}) - Thread.handle_interrupt(::Timeout::ExitException => :never) do + Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do _query(sql, @query_options.merge(options)) end end diff --git a/lib/mysql2/statement.rb b/lib/mysql2/statement.rb index 8f30797c1..f392c6ed0 100644 --- a/lib/mysql2/statement.rb +++ b/lib/mysql2/statement.rb @@ -1,5 +1,17 @@ module Mysql2 class Statement include Enumerable + + if Thread.respond_to?(:handle_interrupt) + def execute(*args) + Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do + _execute(*args) + end + end + else + def execute(*args) + _execute(*args) + end + end end end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 7e3befe26..9729bd4f9 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -467,7 +467,7 @@ def run_gc }.to raise_error(Mysql2::Error) end - it 'should be impervious to connection-corrupting timeouts ' do + it 'should be impervious to connection-corrupting timeouts in #query' do pending('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt) # attempt to break the connection expect { Timeout.timeout(0.1) { @client.query('SELECT SLEEP(0.2)') } }.to raise_error(Timeout::Error) @@ -476,6 +476,20 @@ def run_gc expect { @client.query('SELECT 1') }.to_not raise_error end + it 'should be impervious to connection-corrupting timeouts in #execute' do + # the statement handle gets corrupted and will segfault the tests if interrupted, + # so we can't even use pending on this test, really have to skip it on older Rubies. + skip('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt) + + # attempt to break the connection + stmt = @client.prepare('SELECT SLEEP(?)') + expect { Timeout.timeout(0.1) { stmt.execute(0.2) } }.to raise_error(Timeout::Error) + stmt.close + + # expect the connection to not be broken + expect { @client.query('SELECT 1') }.to_not raise_error + end + context 'when a non-standard exception class is raised' do it "should close the connection when an exception is raised" do expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) From 6bf04fa1f95473baa3063f0a2a49f79d7839c5e7 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Mon, 14 Sep 2015 04:25:35 +0000 Subject: [PATCH 384/783] Assert server state when creating and closing prepared statements Follow-up to eca73fea386bff25211f071377551fa72c28263d --- spec/mysql2/statement_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index ce11489ee..69ede67fd 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -8,7 +8,9 @@ it "should create a statement" do statement = nil - expect { statement = @client.prepare 'SELECT 1' }.not_to raise_error + expect { statement = @client.prepare 'SELECT 1' }.to change { + @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i + }.by(1) expect(statement).to be_an_instance_of(Mysql2::Statement) end @@ -668,7 +670,9 @@ context 'close' do it 'should free server resources' do stmt = @client.prepare 'SELECT 1' - expect(stmt.close).to eq nil + expect { stmt.close }.to change { + @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i + }.by(-1) end it 'should raise an error on subsequent execution' do From 7f19dc91c1e747c691d0c4a691e8d8810a497449 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 15 Sep 2015 05:21:11 -0700 Subject: [PATCH 385/783] Don't leak the MYSQL connection handle if there was an exception earlier The function `disconnect_and_raise` set connected = 0, which prevented freeing the MYSQL connection handle. Switch to simply checking whether the pointer to MYSQL handle is NULL or not before freeing it. Thanks to Chris Bandy for noticing this problem while working on Client#close. --- ext/mysql2/client.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 71deb991b..4abc7f16d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -209,12 +209,13 @@ static VALUE invalidate_fd(int clientfd) #endif /* _WIN32 */ static void *nogvl_close(void *ptr) { - mysql_client_wrapper *wrapper; - wrapper = ptr; - if (wrapper->connected) { - MARK_CONN_INACTIVE(self); - wrapper->connected = 0; + mysql_client_wrapper *wrapper = ptr; + + if (wrapper->client) { mysql_close(wrapper->client); + wrapper->client = NULL; + wrapper->connected = 0; + wrapper->active_thread = Qnil; } return NULL; From bba40187897a5d0e07be671d2668a8a9b5af90c5 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 15 Sep 2015 21:39:00 -0700 Subject: [PATCH 386/783] Bump version to 0.4.1 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 554c06fe0..2e4dc57ed 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.0" + VERSION = "0.4.1" end From 45ba8d998f0f05be8096f66783ef460f2758573d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 15 Sep 2015 21:52:21 -0700 Subject: [PATCH 387/783] Local changes to cross-compile the 0.4.1 gem --- Rakefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 433039c5b..1b860f588 100644 --- a/Rakefile +++ b/Rakefile @@ -9,11 +9,12 @@ load 'tasks/generate.rake' load 'tasks/benchmarks.rake' # TODO: remove when we end support for < 1.9.3 -if RUBY_VERSION =~ /1.8/ - task :default => :spec -else +begin require 'rubocop/rake_task' RuboCop::RakeTask.new - task :default => [:spec, :rubocop] + +rescue LoadError + warn 'RuboCop is not available' + task :default => :spec end From 580b6a90e231c770cad526b4a3da00c4336a3e7f Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 19 Oct 2015 01:31:43 +0900 Subject: [PATCH 388/783] Support JSON with prepared statement --- ext/mysql2/result.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 3cfb8ad98..b25d05295 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -309,11 +309,10 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] + default: wrapper->result_buffers[i].buffer = xmalloc(fields[i].max_length); wrapper->result_buffers[i].buffer_length = fields[i].max_length; break; - default: - rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type); } wrapper->result_buffers[i].is_null = &wrapper->is_null[i]; @@ -491,14 +490,12 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] + default: val = rb_str_new(result_buffer->buffer, *(result_buffer->length)); #ifdef HAVE_RUBY_ENCODING_H val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); #endif break; - default: - rb_raise(cMysql2Error, "unhandled buffer type: %d", - result_buffer->buffer_type); } } From b35b18651afddb8b9c87ed62183f4b762c6a93a0 Mon Sep 17 00:00:00 2001 From: Matthias Winkelmann Date: Sat, 7 Nov 2015 18:54:46 +0100 Subject: [PATCH 389/783] minor spelling errors --- ext/mysql2/result.c | 2 +- lib/mysql2/client.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 3cfb8ad98..811100e68 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -203,7 +203,7 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo #ifdef HAVE_RUBY_ENCODING_H static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) { - /* if binary flag is set, respect it's wishes */ + /* if binary flag is set, respect its wishes */ if (field.flags & BINARY_FLAG && field.charsetnr == 63) { rb_enc_associate(val, binaryEncoding); } else if (!field.charsetnr) { diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index bc2445ea9..d286e412c 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -10,7 +10,7 @@ def self.default_query_options :symbolize_keys => false, # return field names as symbols instead of strings :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller - :cache_rows => true, # tells Mysql2 to use it's internal row cache for results + :cache_rows => true, # tells Mysql2 to use its internal row cache for results :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION, :cast => true, :default_file => nil, From 1c48ad5da2e46acb9ddd2780f98d2b800376063b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 16 Sep 2015 00:36:56 -0700 Subject: [PATCH 390/783] Implement easy flags string / array There are some StackOverflow posts suggesting hardcoded magic numbers to use for the flags field. Ick! This provides a plain-text alternative. For example: flags: - FOO - !BAR flags: FOO !BAR --- lib/mysql2/client.rb | 28 +++++++++++++++++++++++++--- spec/mysql2/client_spec.rb | 38 ++++++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index d286e412c..108a26fd0 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -48,10 +48,18 @@ def initialize(opts = {}) ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher) ssl_set(*ssl_options) if ssl_options.any? + case opts[:flags] + when Array + flags = parse_flags_array(opts[:flags], @query_options[:connect_flags]) + when String + flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags]) + when Integer + flags = @query_options[:connect_flags] | opts[:flags] + else + flags = @query_options[:connect_flags] + end + # SSL verify is a connection flag rather than a mysql_ssl_set option - flags = 0 - flags |= @query_options[:connect_flags] - flags |= opts[:flags] if opts[:flags] flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] && ssl_options.any? if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) } @@ -79,6 +87,20 @@ def initialize(opts = {}) connect user, pass, host, port, database, socket, flags end + def parse_flags_array(flags, initial = 0) + flags.reduce(initial) do |memo, f| + # const_defined? does not like a leading ! + if !f.start_with?('!') && Mysql2::Client.const_defined?(f) + memo | Mysql2::Client.const_get(f) + elsif f.start_with?('!') && Mysql2::Client.const_defined?(f[1..-1]) + memo & ~ Mysql2::Client.const_get(f[1..-1]) + else + warn "Unknown MySQL connection flag: #{f}" + memo + end + end + end + if Thread.respond_to?(:handle_interrupt) def query(sql, options = {}) Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index a1af17e9f..498fb0662 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -43,27 +43,33 @@ }.not_to raise_error end - it "should accept connect flags and pass them to #connect" do - klient = Class.new(Mysql2::Client) do - attr_reader :connect_args - def connect(*args) - @connect_args ||= [] - @connect_args << args - end + Klient = Class.new(Mysql2::Client) do + attr_reader :connect_args + def connect(*args) + @connect_args ||= [] + @connect_args << args end - client = klient.new :flags => Mysql2::Client::FOUND_ROWS + end + + it "should accept connect flags and pass them to #connect" do + client = Klient.new :flags => Mysql2::Client::FOUND_ROWS expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to be > 0 end + it "should parse flags array" do + client = Klient.new :flags => %w( FOUND_ROWS !PROTOCOL_41 ) + expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) + expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) + end + + it "should parse flags string" do + client = Klient.new :flags => "FOUND_ROWS !PROTOCOL_41" + expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) + expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) + end + it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do - klient = Class.new(Mysql2::Client) do - attr_reader :connect_args - def connect(*args) - @connect_args ||= [] - @connect_args << args - end - end - client = klient.new + client = Klient.new client_flags = Mysql2::Client::REMEMBER_OPTIONS | Mysql2::Client::LONG_PASSWORD | Mysql2::Client::LONG_FLAG | From 93079404a7fc19a6c3a59d3630e885899d582e8e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 17 Nov 2015 22:44:16 -0800 Subject: [PATCH 391/783] Take this, RuboCop --- .rubocop_todo.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5a28c920b..e252ab24e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -8,15 +8,19 @@ # Offense count: 2 Metrics/AbcSize: - Max: 68 + Max: 80 # Offense count: 1 Metrics/BlockNesting: Max: 5 +# Offense count: 1 +Metrics/ClassLength: + Max: 125 + # Offense count: 2 Metrics/CyclomaticComplexity: - Max: 23 + Max: 25 # Offense count: 290 # Configuration parameters: AllowURI, URISchemes. @@ -26,7 +30,7 @@ Metrics/LineLength: # Offense count: 5 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 43 + Max: 50 # Offense count: 1 Metrics/PerceivedComplexity: From a106037c7f2c16e6de520b549849c3e3a7d26c47 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sun, 23 Aug 2015 19:10:00 +1000 Subject: [PATCH 392/783] Travis: mariadb using addon --- .travis.yml | 9 +++++++-- .travis_mariadb.sh | 16 ---------------- .travis_setup.sh | 8 ++------ 3 files changed, 9 insertions(+), 24 deletions(-) delete mode 100644 .travis_mariadb.sh diff --git a/.travis.yml b/.travis.yml index f95c7e3a5..fa608c3fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,9 +20,14 @@ rvm: matrix: include: - rvm: 2.0.0 - env: DB=mariadb55 + addons: + mariadb: 5.5 - rvm: 2.0.0 - env: DB=mariadb10 + addons: + mariadb: 10.0 + - rvm: 2.0.0 + addons: + mariadb: 10.1 - rvm: 2.0.0 env: DB=mysql57 - rvm: 2.0.0 diff --git a/.travis_mariadb.sh b/.travis_mariadb.sh deleted file mode 100644 index d69473bb7..000000000 --- a/.travis_mariadb.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -service mysql stop -apt-get purge '^mysql*' 'libmysql*' -apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db - -if [[ x$1 = xmariadb55 ]]; then - add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/5.5/ubuntu precise main' -elif [[ x$1 = xmariadb10 ]]; then - add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/10.0/ubuntu precise main' -fi - -apt-get update -apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -y install mariadb-server libmariadbd-dev diff --git a/.travis_setup.sh b/.travis_setup.sh index 72fa32bcc..61cbc7209 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -7,11 +7,6 @@ if [[ -n ${DB-} && x$DB =~ mysql57 ]]; then sudo bash .travis_mysql57.sh fi -# Install MariaDB if DB=mariadb -if [[ -n ${DB-} && x$DB =~ xmariadb ]]; then - sudo bash .travis_mariadb.sh "$DB" -fi - # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update @@ -33,5 +28,6 @@ if [[ x$OSTYPE =~ ^xdarwin ]]; then $(brew --prefix "$DB")/bin/mysql -u $USER -e "CREATE DATABASE IF NOT EXISTS test" else mysqld --version - mysql -u $USER -e "CREATE DATABASE IF NOT EXISTS test" + # IF NOT EXISTS is mariadb-10+ only - https://mariadb.com/kb/en/mariadb/comment-syntax/ + mysql -u $USER -e "CREATE DATABASE /*M!50701 IF NOT EXISTS */ test" fi From 591fdcaec49dfd97d32157e4e569f0c0eaf4520e Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sun, 23 Aug 2015 19:14:58 +1000 Subject: [PATCH 393/783] Travis: sudo required --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index fa608c3fd..ae6fb1f2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: required language: ruby bundler_args: --without benchmarks development before_install: From b5f1922c37fb4bfc124940fc7a08e2af329a7294 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 18 Nov 2015 01:42:05 -0800 Subject: [PATCH 394/783] Travis: mariadb client dev package --- .travis.yml | 3 +++ .travis_setup.sh | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ae6fb1f2f..11509a6fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,12 +21,15 @@ rvm: matrix: include: - rvm: 2.0.0 + env: DB=mariadb55 addons: mariadb: 5.5 - rvm: 2.0.0 + env: DB=mariadb10.0 addons: mariadb: 10.0 - rvm: 2.0.0 + env: DB=mariadb10.1 addons: mariadb: 10.1 - rvm: 2.0.0 diff --git a/.travis_setup.sh b/.travis_setup.sh index 61cbc7209..4aa859e94 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -3,10 +3,15 @@ set -eu # Install MySQL 5.7 if DB=mysql57 -if [[ -n ${DB-} && x$DB =~ mysql57 ]]; then +if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then sudo bash .travis_mysql57.sh fi +# Install MariaDB if DB=mariadb +if [[ -n ${DB-} && x$DB =~ ^xmariadb ]]; then + sudo apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -y install libmariadbclient-dev +fi + # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update From 569aa3d996a794073b0704771e45f78c7dba2182 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 21 Nov 2015 07:59:15 -0800 Subject: [PATCH 395/783] Free the MYSQL client handle after mysql_close to avoid a memory leak. Resolves #700, #701, #702. Updates #663. Thanks to @darix and @tenderlove for reporting and debugging. --- ext/mysql2/client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 6d60647a6..aa10baed3 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -213,6 +213,7 @@ static void *nogvl_close(void *ptr) { if (wrapper->client) { mysql_close(wrapper->client); + xfree(wrapper->client); wrapper->client = NULL; wrapper->connected = 0; wrapper->active_thread = Qnil; @@ -249,7 +250,6 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper) #endif nogvl_close(wrapper); - xfree(wrapper->client); xfree(wrapper); } } From 6913c45277808c5c7ff90ab29f22e39d22bfe30d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 21 Nov 2015 23:46:27 -0800 Subject: [PATCH 396/783] Always set the hosts addon --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 11509a6fe..768eae4a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,14 +24,20 @@ matrix: env: DB=mariadb55 addons: mariadb: 5.5 + hosts: + - mysql2gem.example.com - rvm: 2.0.0 env: DB=mariadb10.0 addons: mariadb: 10.0 + hosts: + - mysql2gem.example.com - rvm: 2.0.0 env: DB=mariadb10.1 addons: mariadb: 10.1 + hosts: + - mysql2gem.example.com - rvm: 2.0.0 env: DB=mysql57 - rvm: 2.0.0 From 206b456ff0177e1f9d8c5cb684b1143487f99cff Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 21 Nov 2015 23:46:53 -0800 Subject: [PATCH 397/783] Travis mariadb addon now provides libmariadbclient-dev --- .travis_setup.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.travis_setup.sh b/.travis_setup.sh index 4aa859e94..ed3698fbd 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -7,11 +7,6 @@ if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then sudo bash .travis_mysql57.sh fi -# Install MariaDB if DB=mariadb -if [[ -n ${DB-} && x$DB =~ ^xmariadb ]]; then - sudo apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -y install libmariadbclient-dev -fi - # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update From 4ec5e84b81c9e26463ac46d28efc67b29137cced Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 22 Nov 2015 00:04:37 -0800 Subject: [PATCH 398/783] MySQL 5.7 is GA now, remove the -DMR suffix from the apt repo --- .travis_mysql57.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index 53f2f1517..b4a353abd 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -6,7 +6,7 @@ service mysql stop apt-get purge '^mysql*' 'libmysql*' apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x8C718D3B5072E1F5 -add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.7-dmr' +add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.7' apt-get update apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew -y install mysql-server libmysqlclient-dev From da8aa77ca40ee64b2595c261056ace0e49a53fef Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 16 Nov 2015 23:59:07 -0800 Subject: [PATCH 399/783] Avoid crashing when Statement#close is called before a Result is garbage collected --- ext/mysql2/result.c | 28 +++++++++++++++++----------- ext/mysql2/statement.c | 5 ++++- ext/mysql2/statement.h | 1 + 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 52820faec..040e9d526 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -48,7 +48,7 @@ static rb_encoding *binaryEncoding; #define MYSQL2_MIN_TIME 62171150401ULL #endif -#define GET_RESULT(obj) \ +#define GET_RESULT(self) \ mysql2_result_wrapper *wrapper; \ Data_Get_Struct(self, mysql2_result_wrapper, wrapper); @@ -91,16 +91,18 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { if (wrapper->resultFreed != 1) { if (wrapper->stmt_wrapper) { - mysql_stmt_free_result(wrapper->stmt_wrapper->stmt); - - /* MySQL BUG? If the statement handle was previously used, and so - * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed, - * MySQL still thinks the result set buffer is available and will prefetch the - * first result in mysql_stmt_execute. This will corrupt or crash the program. - * By setting bind_result_done back to 0, we make MySQL think that a result set - * has never been bound to this statement handle before to prevent the prefetch. - */ - wrapper->stmt_wrapper->stmt->bind_result_done = 0; + if (!wrapper->stmt_wrapper->closed) { + mysql_stmt_free_result(wrapper->stmt_wrapper->stmt); + + /* MySQL BUG? If the statement handle was previously used, and so + * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed, + * MySQL still thinks the result set buffer is available and will prefetch the + * first result in mysql_stmt_execute. This will corrupt or crash the program. + * By setting bind_result_done back to 0, we make MySQL think that a result set + * has never been bound to this statement handle before to prevent the prefetch. + */ + wrapper->stmt_wrapper->stmt->bind_result_done = 0; + } if (wrapper->result_buffers) { unsigned int i; @@ -855,6 +857,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { GET_RESULT(self); + if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) { + rb_raise(cMysql2Error, "Statement handle already closed"); + } + defaults = rb_iv_get(self, "@query_options"); Check_Type(defaults, T_HASH); if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) { diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 3b83feaf2..707e27f88 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -8,7 +8,8 @@ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, inter #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \ - if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } + if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \ + if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); } static void rb_mysql_stmt_mark(void * ptr) { @@ -105,6 +106,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { { stmt_wrapper->client = rb_client; stmt_wrapper->refcount = 1; + stmt_wrapper->closed = 0; stmt_wrapper->stmt = NULL; } @@ -461,6 +463,7 @@ static VALUE rb_mysql_stmt_affected_rows(VALUE self) { */ static VALUE rb_mysql_stmt_close(VALUE self) { GET_STATEMENT(self); + stmt_wrapper->closed = 1; rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0); return Qnil; } diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index 0c1d54c55..63260aab0 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -7,6 +7,7 @@ typedef struct { VALUE client; MYSQL_STMT *stmt; int refcount; + int closed; } mysql_stmt_wrapper; void init_mysql2_statement(void); From fe76f67cf10b6c3b75a522df76b2e7142aef6a48 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 17 Nov 2015 21:48:32 -0800 Subject: [PATCH 400/783] Remove unnecessary casts for void * rvalues --- ext/mysql2/client.c | 14 +++++--------- ext/mysql2/statement.c | 6 +++--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index aa10baed3..7c7f97f1e 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -133,7 +133,7 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { static void *nogvl_init(void *ptr) { MYSQL *client; - mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; + mysql_client_wrapper *wrapper = ptr; /* may initialize embedded server and read /etc/services off disk */ client = mysql_init(wrapper->client); @@ -224,7 +224,7 @@ static void *nogvl_close(void *ptr) { /* this is called during GC */ static void rb_mysql_client_free(void *ptr) { - mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; + mysql_client_wrapper *wrapper = ptr; decr_mysql2_client(wrapper); } @@ -437,10 +437,9 @@ static void *nogvl_read_query_result(void *ptr) { } static void *nogvl_do_result(void *ptr, char use_result) { - mysql_client_wrapper *wrapper; + mysql_client_wrapper *wrapper = ptr; MYSQL_RES *result; - wrapper = (mysql_client_wrapper *)ptr; if (use_result) { result = mysql_use_result(wrapper->client); } else { @@ -533,14 +532,13 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) { } static VALUE do_query(void *args) { - struct async_query_args *async_args; + struct async_query_args *async_args = args; struct timeval tv; struct timeval* tvp; long int sec; int retval; VALUE read_timeout; - async_args = (struct async_query_args *)args; read_timeout = rb_iv_get(async_args->self, "@read_timeout"); tvp = NULL; @@ -578,11 +576,9 @@ static VALUE do_query(void *args) { } #else static VALUE finish_and_mark_inactive(void *args) { - VALUE self; + VALUE self = args; MYSQL_RES *result; - self = (VALUE)args; - GET_CLIENT(self); if (!NIL_P(wrapper->active_thread)) { diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 707e27f88..482234b8f 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -13,14 +13,14 @@ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, inter static void rb_mysql_stmt_mark(void * ptr) { - mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; + mysql_stmt_wrapper* stmt_wrapper = ptr; if (!stmt_wrapper) return; rb_gc_mark(stmt_wrapper->client); } static void *nogvl_stmt_close(void * ptr) { - mysql_stmt_wrapper *stmt_wrapper = (mysql_stmt_wrapper *)ptr; + mysql_stmt_wrapper *stmt_wrapper = ptr; if (stmt_wrapper->stmt) { mysql_stmt_close(stmt_wrapper->stmt); stmt_wrapper->stmt = NULL; @@ -29,7 +29,7 @@ static void *nogvl_stmt_close(void * ptr) { } static void rb_mysql_stmt_free(void * ptr) { - mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; + mysql_stmt_wrapper* stmt_wrapper = ptr; decr_mysql2_stmt(stmt_wrapper); } From ed5613d26b1f54e5eb0629a06cb5136de4ae47cf Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 17 Nov 2015 21:50:49 -0800 Subject: [PATCH 401/783] Style nits on placement of pointer declarations --- ext/mysql2/client.c | 2 +- ext/mysql2/statement.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 7c7f97f1e..b2ac283a4 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -534,7 +534,7 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) { static VALUE do_query(void *args) { struct async_query_args *async_args = args; struct timeval tv; - struct timeval* tvp; + struct timeval *tvp; long int sec; int retval; VALUE read_timeout; diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 482234b8f..fc61e9302 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -13,7 +13,7 @@ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, inter static void rb_mysql_stmt_mark(void * ptr) { - mysql_stmt_wrapper* stmt_wrapper = ptr; + mysql_stmt_wrapper *stmt_wrapper = ptr; if (!stmt_wrapper) return; rb_gc_mark(stmt_wrapper->client); @@ -29,7 +29,7 @@ static void *nogvl_stmt_close(void * ptr) { } static void rb_mysql_stmt_free(void * ptr) { - mysql_stmt_wrapper* stmt_wrapper = ptr; + mysql_stmt_wrapper *stmt_wrapper = ptr; decr_mysql2_stmt(stmt_wrapper); } @@ -94,7 +94,7 @@ static void *nogvl_prepare_statement(void *ptr) { } VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { - mysql_stmt_wrapper* stmt_wrapper; + mysql_stmt_wrapper *stmt_wrapper; VALUE rb_stmt; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; From c973751586dde2855a664d01a4d7e57e5ee8aba7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 24 Nov 2015 10:03:35 -0800 Subject: [PATCH 402/783] Add new option --with-sanitize[=list,of,clang,sanitizers] --- README.md | 7 ++++++ ext/mysql2/extconf.rb | 51 ++++++++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 390629060..4cade7dfb 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,13 @@ This may be needed if you deploy to a system where these libraries are located somewhere different than on your build system. This overrides any rpath calculated by default or by the options above. +* `--with-sanitize[=address,cfi,integer,memory,thread,undefined]` - +Enable sanitizers for Clang / GCC. If no argument is given, try to enable +all sanitizers or fail if none are available. If a command-separated list of +specific sanitizers is given, configure will fail unless they all are available. +Note that the some sanitizers may incur a performance penalty, and the Address +Sanitizer may require a runtime library. + ### Linux and other Unixes You may need to install a package such as `libmysqlclient-dev` or `mysql-devel`; diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index e38193d53..167b0e813 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -105,31 +105,52 @@ def asplode(lib) '-Wno-missing-field-initializers', # gperf generates bad code '-Wno-missing-variable-declarations', # missing symbols due to ruby native ext initialization '-Wno-padded', # mysql :( + '-Wno-reserved-id-macro', # rubby :( '-Wno-sign-conversion', # gperf generates bad code '-Wno-static-in-inline', # gperf generates bad code '-Wno-switch-enum', # result.c -- enum_field_types (when not fully covered, e.g. mysql 5.6+) '-Wno-undef', # rubinius :( + '-Wno-unreachable-code', # rubby :( '-Wno-used-but-marked-unused', # rubby :( ] -if ENV['CI'] - wishlist += [ - '-Werror', - '-fsanitize=address', - '-fsanitize=cfi', - '-fsanitize=integer', - '-fsanitize=memory', - '-fsanitize=thread', - '-fsanitize=undefined', - ] -end - usable_flags = wishlist.select do |flag| - try_link('int main() {return 0;}', flag) + try_link('int main() {return 0;}', "-Werror -Wunknown-warning-option #{flag}") end $CFLAGS << ' ' << usable_flags.join(' ') +enabled_sanitizers = disabled_sanitizers = [] +# Specify a commna-separated list of sanitizers, or try them all by default +sanitizers = with_config('sanitize') +case sanitizers +when true + # Try them all, turn on whatever we can + enabled_sanitizers = %w(address cfi integer memory thread undefined).select do |s| + try_link('int main() {return 0;}', "-Werror -Wunknown-warning-option -fsanitize=#{s}") + end +when String + # Figure out which sanitizers are supported + enabled_sanitizers, disabled_sanitizers = sanitizers.split(',').partition do |s| + try_link('int main() {return 0;}', "-Werror -Wunknown-warning-option -fsanitize=#{s}") + end +end + +unless disabled_sanitizers.empty? + abort "-----\nCould not enable requested sanitizers: #{disabled_sanitizers.join(',')}\n-----" +end + +unless enabled_sanitizers.empty? + warn "-----\nEnabling sanitizers: #{enabled_sanitizers.join(',')}\n-----" + enabled_sanitizers.each do |s| + # address sanitizer requires runtime support + if s == 'address' # rubocop:disable Style/IfUnlessModifier + have_library('asan') || $LDFLAGS << ' -fsanitize=address' + end + $CFLAGS << " -fsanitize=#{s}" + end +end + if RUBY_PLATFORM =~ /mswin|mingw/ # Build libmysql.a interface link library require 'rake' @@ -155,8 +176,8 @@ def asplode(lib) # Make sure the generated interface library works (if cross-compiling, trust without verifying) unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ - abort "-----\nCannot find libmysql.a\n----" unless have_library('libmysql') - abort "-----\nCannot link to libmysql.a (my_init)\n----" unless have_func('my_init') + abort "-----\nCannot find libmysql.a\n-----" unless have_library('libmysql') + abort "-----\nCannot link to libmysql.a (my_init)\n-----" unless have_func('my_init') end # Vendor libmysql.dll From ec48fe6a9a5afa54ba9298607492a2cdb678ae28 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 24 Nov 2015 22:43:15 -0800 Subject: [PATCH 403/783] Remove unknown option -Wunknown-warning-option for improved GCC compatibility --- ext/mysql2/extconf.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 167b0e813..d0b3e6d86 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -115,7 +115,7 @@ def asplode(lib) ] usable_flags = wishlist.select do |flag| - try_link('int main() {return 0;}', "-Werror -Wunknown-warning-option #{flag}") + try_link('int main() {return 0;}', "-Werror #{flag}") end $CFLAGS << ' ' << usable_flags.join(' ') @@ -127,12 +127,12 @@ def asplode(lib) when true # Try them all, turn on whatever we can enabled_sanitizers = %w(address cfi integer memory thread undefined).select do |s| - try_link('int main() {return 0;}', "-Werror -Wunknown-warning-option -fsanitize=#{s}") + try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") end when String # Figure out which sanitizers are supported enabled_sanitizers, disabled_sanitizers = sanitizers.split(',').partition do |s| - try_link('int main() {return 0;}', "-Werror -Wunknown-warning-option -fsanitize=#{s}") + try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") end end From df9f0feec3da6b041302518b90f410cfbab548e7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 24 Nov 2015 22:44:18 -0800 Subject: [PATCH 404/783] It's an error if --with-sanitize given but none could be activated --- ext/mysql2/extconf.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index d0b3e6d86..dccdd48ae 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -129,6 +129,7 @@ def asplode(lib) enabled_sanitizers = %w(address cfi integer memory thread undefined).select do |s| try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") end + abort "-----\nCould not enable any sanitizers!\n-----" if enabled_sanitizers.empty? when String # Figure out which sanitizers are supported enabled_sanitizers, disabled_sanitizers = sanitizers.split(',').partition do |s| From 3c693ff59c56f55bd98f7f7f7e72f26184a5f8f5 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 24 Nov 2015 23:21:39 -0800 Subject: [PATCH 405/783] Add options and instructions for seeing line numbers in sanitizer backtraces --- README.md | 7 +++++++ ext/mysql2/extconf.rb | 2 ++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index 4cade7dfb..e840fa7a2 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,13 @@ all sanitizers or fail if none are available. If a command-separated list of specific sanitizers is given, configure will fail unless they all are available. Note that the some sanitizers may incur a performance penalty, and the Address Sanitizer may require a runtime library. +To see line numbers in backtraces, declare these environment variables +(adjust the llvm-symbolizer path as needed for your system): + +``` sh + export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.4 + export ASAN_OPTIONS=symbolize=1 +``` ### Linux and other Unixes diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index dccdd48ae..ed82355b0 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -150,6 +150,8 @@ def asplode(lib) end $CFLAGS << " -fsanitize=#{s}" end + # Options for line numbers in backtraces + $CFLAGS << ' -g -fno-omit-frame-pointer' end if RUBY_PLATFORM =~ /mswin|mingw/ From 02c8cc65722e4ac7c089c51bc76db815bc69f4c0 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 24 Nov 2015 23:23:14 -0800 Subject: [PATCH 406/783] Remove Travis OS X DB=mysql because it is a moving target --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 768eae4a3..0ec4fee26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,9 +40,6 @@ matrix: - mysql2gem.example.com - rvm: 2.0.0 env: DB=mysql57 - - rvm: 2.0.0 - env: DB=mysql - os: osx - rvm: 2.0.0 env: DB=mysql55 os: osx From 50c572ae302da49d17c129c1dd79f65040983986 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 25 Nov 2015 10:21:26 -0800 Subject: [PATCH 407/783] Add README section for flags parsing and change negated flags to a - (minus) prefix As I was writing the README for this, I found that unquoted !FLAG_FOO was being interpreted by the YAML engine as a Ruby class invocations. This would surely lead to some confusing or downright bad results if used unquoted in database.yml files. --- README.md | 27 +++++++++++++++++++++------ lib/mysql2/client.rb | 10 +++++----- spec/mysql2/client_spec.rb | 4 ++-- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e840fa7a2..c15cbeba9 100644 --- a/README.md +++ b/README.md @@ -272,15 +272,26 @@ Yields: next_result: Unknown column 'A' in 'field list' (Mysql2::Error) ``` -See https://gist.github.com/1367987 for using MULTI_STATEMENTS with Active Record. - ### Secure auth Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). When secure_auth is enabled, the server will refuse a connection if the account password is stored in old pre-MySQL 4.1 format. The MySQL 5.6.5 client library may also refuse to attempt a connection if provided an older format password. -To bypass this restriction in the client, pass the option :secure_auth => false to Mysql2::Client.new(). -If using ActiveRecord, your database.yml might look something like this: +To bypass this restriction in the client, pass the option `:secure_auth => false` to Mysql2::Client.new(). + +### Flags option parsing + +The `:flags` parameter accepts an integer, a string, or an array. The integer +form allows the client to assemble flags from constants defined under +`Mysql2::Client` such as `Mysql2::Client::FOUND_ROWS`. Use a bitwise `|` (OR) +to specify several flags. + +The string form will be split on whitespace and parsed as with the array form: +Plain flags are added to the default flags, while flags prefixed with `-` +(minus) are removed from the default flags. + +This allows easier use with ActiveRecord's database.yml, avoiding the need for magic flag numbers. +For example, to disable protocol compression, and enable multiple statements and result sets: ``` yaml development: @@ -291,13 +302,17 @@ development: password: my_password host: 127.0.0.1 port: 3306 + flags: + - -COMPRESS + - FOUND_ROWS + - MULTI_STATEMENTS secure_auth: false ``` ### Reading a MySQL config file You may read configuration options from a MySQL configuration file by passing -the `:default_file` and `:default_group` paramters. For example: +the `:default_file` and `:default_group` parameters. For example: ``` ruby Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') @@ -305,7 +320,7 @@ Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') ### Initial command on connect and reconnect -If you specify the init_command option, the SQL string you provide will be executed after the connection is established. +If you specify the `:init_command` option, the SQL string you provide will be executed after the connection is established. If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect. It is useful if you want to provide session options which survive reconnection. diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 108a26fd0..7178f2c5b 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -89,13 +89,13 @@ def initialize(opts = {}) def parse_flags_array(flags, initial = 0) flags.reduce(initial) do |memo, f| - # const_defined? does not like a leading ! - if !f.start_with?('!') && Mysql2::Client.const_defined?(f) + fneg = f.start_with?('-') ? f[1..-1] : nil + if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg) + memo & ~ Mysql2::Client.const_get(fneg) + elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f) memo | Mysql2::Client.const_get(f) - elsif f.start_with?('!') && Mysql2::Client.const_defined?(f[1..-1]) - memo & ~ Mysql2::Client.const_get(f[1..-1]) else - warn "Unknown MySQL connection flag: #{f}" + warn "Unknown MySQL connection flag: '#{f}'" memo end end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 498fb0662..a6e9bcd4f 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -57,13 +57,13 @@ def connect(*args) end it "should parse flags array" do - client = Klient.new :flags => %w( FOUND_ROWS !PROTOCOL_41 ) + client = Klient.new :flags => %w( FOUND_ROWS -PROTOCOL_41 ) expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end it "should parse flags string" do - client = Klient.new :flags => "FOUND_ROWS !PROTOCOL_41" + client = Klient.new :flags => "FOUND_ROWS -PROTOCOL_41" expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end From 3854d19ccac834b0e07e0c18d6f26465630daf8c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 25 Nov 2015 13:07:46 -0800 Subject: [PATCH 408/783] Bump version to 0.4.2 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 2e4dc57ed..e97480be1 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.1" + VERSION = "0.4.2" end From 08ad5b700455a45cc6944b03327b18a4bc0fd018 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 4 Dec 2015 11:33:36 -0800 Subject: [PATCH 409/783] Update the Compatibility section of the README --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c15cbeba9..24052cf68 100644 --- a/README.md +++ b/README.md @@ -490,14 +490,15 @@ This gem is tested with the following Ruby versions on Linux and Mac OS X: This gem is tested with the following MySQL and MariaDB versions: - * MySQL 5.5, 5.7 + * MySQL 5.5, 5.6, 5.7 * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) - * MariaDB 5.5, 10.0 + * MariaDB 5.5, 10.0, 10.1 -### Active Record +### Rails / Active Record - * mysql2 0.2.x includes an Active Record driver compatible with AR 2.3 and 3.0 - * mysql2 0.3.x does not include an AR driver because it is included in AR 3.1 and above + * mysql2 0.4.x works with Active Record 4.2.5 and higher. + * mysql2 0.3.x works with Active Record 3.1 and higher (the AR adapter is now included in AR proper). + * mysql2 0.2.x includes an Active Record adapter compatible with AR 2.3 and 3.0, and should not be used with AR 3.1 or higher. ### Asynchronous Active Record From e8fab40752ca959be50b93dec48a553524d9a2d1 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 10 Dec 2015 13:05:52 -0500 Subject: [PATCH 410/783] rake: fix re-definition warning --- tasks/compile.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/compile.rake b/tasks/compile.rake index 4ffbefca6..ce610cdad 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -1,6 +1,6 @@ require "rake/extensiontask" -load File.expand_path('../../mysql2.gemspec', __FILE__) +load File.expand_path('../../mysql2.gemspec', __FILE__) unless defined? Mysql2::GEMSPEC Rake::ExtensionTask.new("mysql2", Mysql2::GEMSPEC) do |ext| # put binaries into lib/mysql2/ or lib/mysql2/x.y/ From fda2f879bfae7f797960c4c6d867b0d1f4da1165 Mon Sep 17 00:00:00 2001 From: Carson Reinke Date: Tue, 1 Dec 2015 13:56:30 -0500 Subject: [PATCH 411/783] Ignore nil timeouts instead of casting to zero --- lib/mysql2/client.rb | 2 +- spec/mysql2/client_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 7178f2c5b..bfc496a29 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -36,7 +36,7 @@ def initialize(opts = {}) when :reconnect, :local_infile, :secure_auth send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation when :connect_timeout, :read_timeout, :write_timeout - send(:"#{key}=", opts[key].to_i) + send(:"#{key}=", opts[key]) unless opts[key].nil? else send(:"#{key}=", opts[key]) end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index a6e9bcd4f..3d800cfc0 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -352,6 +352,12 @@ def run_gc }.to raise_error(Mysql2::Error) end + it "should allow nil read_timeout" do + client = Mysql2::Client.new(:read_timeout => nil) + + expect(client.read_timeout).to be_nil + end + context "#query" do it "should let you query again if iterating is finished when streaming" do @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a From ad49bb247e613883973381cd09d11262a620e9a9 Mon Sep 17 00:00:00 2001 From: Marco Perrando Date: Thu, 14 Jan 2016 14:53:34 +0100 Subject: [PATCH 412/783] Fix: BigDecimal statement parameter is ignored. Added code to treat a parameter of Ruby type BigDecimal. --- ext/mysql2/statement.c | 36 +++++++++++++++++++++++++++++------ spec/mysql2/statement_spec.rb | 11 +++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index fc61e9302..82678fdb5 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -3,7 +3,7 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; static VALUE sym_stream, intern_new_with_args, intern_each; -static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year; +static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year, intern_to_s; #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ @@ -190,6 +190,19 @@ static void *nogvl_stmt_store_result(void *ptr) { } } +static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) { + int length; + + bind_buffer->buffer_type = MYSQL_TYPE_STRING; + bind_buffer->buffer = RSTRING_PTR(string); + + length = RSTRING_LEN(string); + bind_buffer->buffer_length = length; + *length_buffer = length; + + bind_buffer->length = length_buffer; +} + /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means * the buffer is a Ruby string pointer and not our memory to manage. */ @@ -280,11 +293,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { #ifdef HAVE_RUBY_ENCODING_H params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); #endif - bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; - bind_buffers[i].buffer = RSTRING_PTR(params_enc[i]); - bind_buffers[i].buffer_length = RSTRING_LEN(params_enc[i]); - length_buffers[i] = bind_buffers[i].buffer_length; - bind_buffers[i].length = &length_buffers[i]; + set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); } break; default: @@ -324,6 +333,19 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cBigDecimal) { bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL; + + // DECIMAL are represented with the "string representation of the + // original server-side value", see + // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html + // This should be independent of the locale used both on the server + // and the client side. + VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0); + + params_enc[i] = rb_val_as_string; +#ifdef HAVE_RUBY_ENCODING_H + params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); +#endif + set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); } break; } @@ -491,4 +513,6 @@ void init_mysql2_statement() { intern_day = rb_intern("day"); intern_month = rb_intern("month"); intern_year = rb_intern("year"); + + intern_to_s = rb_intern("to_s"); } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 69ede67fd..6358282de 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -117,6 +117,17 @@ expect(list[1]).to eq('2') end + it "should update a DECIMAL value passing a BigDecimal" do + @client.query 'USE test' + @client.query 'DROP TABLE IF EXISTS mysql2_stmt_decimal_test' + @client.query 'CREATE TABLE mysql2_stmt_decimal_test (decimal_test DECIMAL(10,3))' + + @client.prepare("INSERT INTO mysql2_stmt_decimal_test VALUES (?)").execute(BigDecimal.new("123.45")) + + test_result = @client.query("SELECT * FROM mysql2_stmt_decimal_test").first + expect(test_result['decimal_test']).to eql(123.45) + end + context "utf8_db" do before(:each) do @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") From 64d7ff80819c652b24789fd08ff69c7db046b3da Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Wed, 16 Sep 2015 06:08:27 +0000 Subject: [PATCH 413/783] Allow the garbage collector to close connections --- ext/mysql2/client.c | 45 ++++++++++++++++++++++++++---- ext/mysql2/client.h | 1 + lib/mysql2/client.rb | 4 +-- spec/mysql2/client_spec.rb | 56 ++++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 1 + 5 files changed, 99 insertions(+), 8 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 6d60647a6..44b9025fc 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -233,7 +233,7 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper) if (wrapper->refcount == 0) { #ifndef _WIN32 - if (wrapper->connected) { + if (wrapper->connected && !wrapper->automatic_close) { /* The client is being garbage collected while connected. Prevent * mysql_close() from sending a mysql-QUIT or from calling shutdown() on * the socket by invalidating it. invalidate_fd() will drop this @@ -260,6 +260,11 @@ static VALUE allocate(VALUE klass) { obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; MARK_CONN_INACTIVE(self); +#ifndef _WIN32 + wrapper->automatic_close = 0; +#else + wrapper->automatic_close = 1; +#endif wrapper->server_version = 0; wrapper->reconnect_enabled = 0; wrapper->connect_timeout = 0; @@ -382,12 +387,9 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po /* * Terminate the connection; call this when the connection is no longer needed. - * The garbage collector can close the connection, but doing so emits an - * "Aborted connection" error on the server and increments the Aborted_clients - * status variable. + * To have the garbage collector close the connection, enable +automatic_close+. * - * @see http://dev.mysql.com/doc/en/communication-errors.html - * @return [void] + * @return [nil] */ static VALUE rb_mysql_client_close(VALUE self) { GET_CLIENT(self); @@ -1085,6 +1087,35 @@ static VALUE rb_mysql_client_encoding(VALUE self) { } #endif +/* call-seq: + * client.automatic_close? + * + * @return [Boolean] + */ +static VALUE get_automatic_close(VALUE self) { + GET_CLIENT(self); + return wrapper->automatic_close ? Qtrue : Qfalse; +} + +/* call-seq: + * client.automatic_close = true + * + * Set this to +true+ to let the garbage collector close this connection. + */ +static VALUE set_automatic_close(VALUE self, VALUE value) { + GET_CLIENT(self); + if (RTEST(value)) { + wrapper->automatic_close = 1; + } else { +#ifndef _WIN32 + wrapper->automatic_close = 0; +#else + rb_raise(cMysql2Error, "Connections are always closed by garbage collector on Windows"); +#endif + } + return value; +} + /* call-seq: * client.reconnect = true * @@ -1268,6 +1299,8 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0); rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0); rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0); + rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0); + rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1); rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1); rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0); rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0); diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 3e29d6076..672eef974 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -43,6 +43,7 @@ typedef struct { int reconnect_enabled; unsigned int connect_timeout; int active; + int automatic_close; int connected; int initialized; int refcount; diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index bc2445ea9..ebd75670c 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -30,10 +30,10 @@ def initialize(opts = {}) opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) # TODO: stricter validation rather than silent massaging - [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].each do |key| + [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close].each do |key| next unless opts.key?(key) case key - when :reconnect, :local_infile, :secure_auth + when :reconnect, :local_infile, :secure_auth, :automatic_close send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation when :connect_timeout, :read_timeout, :write_timeout send(:"#{key}=", opts[key].to_i) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index a1af17e9f..045d1725b 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -210,6 +210,62 @@ def run_gc expect { client.query('SELECT 1') }.to_not raise_exception end + context "#automatic_close" do + if RUBY_PLATFORM =~ /mingw|mswin/ + it "is enabled by default" do + client = Mysql2::Client.new(DatabaseCredentials['root']) + expect(client.automatic_close?).to be(true) + end + + it "cannot be disabled" do + expect { + Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) + }.to raise_error(Mysql2::Error) + + client = Mysql2::Client.new(DatabaseCredentials['root']) + + expect { client.automatic_close = false }.to raise_error(Mysql2::Error) + expect { client.automatic_close = true }.to_not raise_error + end + else + it "is disabled by default" do + client = Mysql2::Client.new(DatabaseCredentials['root']) + expect(client.automatic_close?).to be(false) + end + + it "can be configured" do + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true)) + expect(client.automatic_close?).to be(true) + end + + it "can be assigned" do + client = Mysql2::Client.new(DatabaseCredentials['root']) + client.automatic_close = true + expect(client.automatic_close?).to be(true) + + client.automatic_close = false + expect(client.automatic_close?).to be(false) + + client.automatic_close = 9 + expect(client.automatic_close?).to be(true) + + client.automatic_close = nil + expect(client.automatic_close?).to be(false) + end + end + + it "should terminate connections during garbage collection" do + run_gc + expect { + Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true)).query('SELECT 1') + run_gc + }.to_not change { + @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + + @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a + } + end + end + it "should be able to connect to database with numeric-only name" do creds = DatabaseCredentials['numericuser'] @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 73c45819e..30d84adbf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -90,5 +90,6 @@ def with_internal_encoding(encoding) "test", "test", 'val1', 'val1,val2' ) ] + client.close end end From 100435c1ea96656bbfe9b79dcba137aa758e547c Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Wed, 16 Sep 2015 06:55:04 +0000 Subject: [PATCH 414/783] Close connections during garbage collection by default --- ext/mysql2/client.c | 18 ++++--- spec/mysql2/client_spec.rb | 105 ++++++++++++++++--------------------- spec/spec_helper.rb | 1 - 3 files changed, 55 insertions(+), 69 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 44b9025fc..c88cad78b 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -260,11 +260,7 @@ static VALUE allocate(VALUE klass) { obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; MARK_CONN_INACTIVE(self); -#ifndef _WIN32 - wrapper->automatic_close = 0; -#else wrapper->automatic_close = 1; -#endif wrapper->server_version = 0; wrapper->reconnect_enabled = 0; wrapper->connect_timeout = 0; @@ -386,8 +382,10 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po } /* - * Terminate the connection; call this when the connection is no longer needed. - * To have the garbage collector close the connection, enable +automatic_close+. + * Immediately disconnect from the server; normally the garbage collector + * will disconnect automatically when a connection is no longer needed. + * Explicitly closing this will free up server resources sooner than waiting + * for the garbage collector. * * @return [nil] */ @@ -1098,9 +1096,13 @@ static VALUE get_automatic_close(VALUE self) { } /* call-seq: - * client.automatic_close = true + * client.automatic_close = false + * + * Set this to +false+ to leave the connection open after it is garbage + * collected. To avoid "Aborted connection" errors on the server, explicitly + * call +close+ when the connection is no longer needed. * - * Set this to +true+ to let the garbage collector close this connection. + * @see http://dev.mysql.com/doc/en/communication-errors.html */ static VALUE set_automatic_close(VALUE self, VALUE value) { GET_CLIENT(self); diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 045d1725b..39137d3ad 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -166,57 +166,36 @@ def run_gc expect { Mysql2::Client.new(DatabaseCredentials['root']).close }.to_not change { - @client.query("SHOW STATUS LIKE 'Aborted_clients'").first['Value'].to_i + @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + + @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a } end it "should not leave dangling connections after garbage collection" do run_gc + expect { + expect { + 10.times do + Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1') + end + }.to change { + @client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i + }.by(10) - client = Mysql2::Client.new(DatabaseCredentials['root']) - before_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i - - 10.times do - Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1') - end - after_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i - expect(after_count).to eq(before_count + 10) - - run_gc - final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i - expect(final_count).to eq(before_count) - end - - it "should not close connections when running in a child process" do - pending("fork is not available on this platform") unless Process.respond_to?(:fork) - - run_gc - client = Mysql2::Client.new(DatabaseCredentials['root']) - - # this empty `fork` call fixes this tests on RBX; without it, the next - # `fork` call hangs forever. WTF? - fork {} - - fork do - client.query('SELECT 1') - client = nil run_gc - end - - Process.wait - - # this will throw an error if the underlying socket was shutdown by the - # child's GC - expect { client.query('SELECT 1') }.to_not raise_exception + }.to_not change { + @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + + @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a + } end context "#automatic_close" do - if RUBY_PLATFORM =~ /mingw|mswin/ - it "is enabled by default" do - client = Mysql2::Client.new(DatabaseCredentials['root']) - expect(client.automatic_close?).to be(true) - end + it "is enabled by default" do + client = Mysql2::Client.new(DatabaseCredentials['root']) + expect(client.automatic_close?).to be(true) + end + if RUBY_PLATFORM =~ /mingw|mswin/ it "cannot be disabled" do expect { Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) @@ -228,41 +207,47 @@ def run_gc expect { client.automatic_close = true }.to_not raise_error end else - it "is disabled by default" do - client = Mysql2::Client.new(DatabaseCredentials['root']) - expect(client.automatic_close?).to be(false) - end - it "can be configured" do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true)) - expect(client.automatic_close?).to be(true) + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) + expect(client.automatic_close?).to be(false) end it "can be assigned" do client = Mysql2::Client.new(DatabaseCredentials['root']) - client.automatic_close = true - expect(client.automatic_close?).to be(true) - client.automatic_close = false expect(client.automatic_close?).to be(false) - client.automatic_close = 9 + client.automatic_close = true expect(client.automatic_close?).to be(true) client.automatic_close = nil expect(client.automatic_close?).to be(false) + + client.automatic_close = 9 + expect(client.automatic_close?).to be(true) end - end - it "should terminate connections during garbage collection" do - run_gc - expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true)).query('SELECT 1') + it "should not close connections when running in a child process" do run_gc - }.to_not change { - @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + - @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a - } + client = Mysql2::Client.new(DatabaseCredentials['root']) + client.automatic_close = false + + # this empty `fork` call fixes this tests on RBX; without it, the next + # `fork` call hangs forever. WTF? + fork {} + + fork do + client.query('SELECT 1') + client = nil + run_gc + end + + Process.wait + + # this will throw an error if the underlying socket was shutdown by the + # child's GC + expect { client.query('SELECT 1') }.to_not raise_exception + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 30d84adbf..73c45819e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -90,6 +90,5 @@ def with_internal_encoding(encoding) "test", "test", 'val1', 'val1,val2' ) ] - client.close end end From 890345e0e4d0829f3656ae9c79fc6dc021c30571 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Sat, 23 Jan 2016 13:26:33 +0000 Subject: [PATCH 415/783] Emit warning warning when changing automatic_close on Windows --- ext/mysql2/client.c | 2 +- spec/mysql2/client_spec.rb | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index c88cad78b..ae1eab582 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1112,7 +1112,7 @@ static VALUE set_automatic_close(VALUE self, VALUE value) { #ifndef _WIN32 wrapper->automatic_close = 0; #else - rb_raise(cMysql2Error, "Connections are always closed by garbage collector on Windows"); + rb_warn("Connections are always closed by garbage collector on Windows"); #endif } return value; diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 39137d3ad..9faad5e10 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1,5 +1,6 @@ # encoding: UTF-8 require 'spec_helper' +require 'stringio' RSpec.describe Mysql2::Client do context "using defaults file" do @@ -197,14 +198,22 @@ def run_gc if RUBY_PLATFORM =~ /mingw|mswin/ it "cannot be disabled" do - expect { + stderr, $stderr = $stderr, StringIO.new + + begin Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) - }.to raise_error(Mysql2::Error) + expect($stderr.string).to include('always closed by garbage collector') + $stderr.reopen - client = Mysql2::Client.new(DatabaseCredentials['root']) + client = Mysql2::Client.new(DatabaseCredentials['root']) + client.automatic_close = false + expect($stderr.string).to include('always closed by garbage collector') + $stderr.reopen - expect { client.automatic_close = false }.to raise_error(Mysql2::Error) - expect { client.automatic_close = true }.to_not raise_error + expect { client.automatic_close = true }.to_not change { $stderr.string } + ensure + $stderr = stderr + end end else it "can be configured" do From 5189cfee45872a10cf54afbfcd721de12b2ae36e Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 29 Nov 2015 13:15:00 +0900 Subject: [PATCH 416/783] Call `rb_mysql_result_free_result` immediately when numberOfRows == 0 --- ext/mysql2/result.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 040e9d526..ad9edd144 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -911,6 +911,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { if (wrapper->lastRowProcessed == 0 && !wrapper->is_streaming) { wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result); if (wrapper->numberOfRows == 0) { + rb_mysql_result_free_result(wrapper); wrapper->rows = rb_ary_new(); return wrapper->rows; } From d4bd03f674c813b96a855bbdd8b347a8556a6585 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 14 Feb 2016 23:13:12 +0900 Subject: [PATCH 417/783] Support `Result#free` --- ext/mysql2/result.c | 16 +++++++++++----- spec/mysql2/result_spec.rb | 4 ++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index ad9edd144..14bc79e66 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -104,6 +104,10 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { wrapper->stmt_wrapper->stmt->bind_result_done = 0; } + if (wrapper->statement != Qnil) { + decr_mysql2_stmt(wrapper->stmt_wrapper); + } + if (wrapper->result_buffers) { unsigned int i; for (i = 0; i < wrapper->numberOfFields; i++) { @@ -136,13 +140,15 @@ static void rb_mysql_result_free(void *ptr) { decr_mysql2_client(wrapper->client_wrapper); } - if (wrapper->statement != Qnil) { - decr_mysql2_stmt(wrapper->stmt_wrapper); - } - xfree(wrapper); } +static VALUE rb_mysql_result_free_(VALUE self) { + GET_RESULT(self); + rb_mysql_result_free_result(wrapper); + return Qnil; +} + /* * for small results, this won't hit the network, but there's no * reliable way for us to tell this so we'll always release the GVL @@ -511,7 +517,6 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co return rowVal; } - static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) { VALUE rowVal; @@ -1008,6 +1013,7 @@ void init_mysql2_result() { cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject); rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0); + rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0); rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0); rb_define_alias(cMysql2Result, "size", "count"); diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 6844c88b1..9633588e9 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -22,6 +22,10 @@ expect(@result).to respond_to(:each) end + it "should respond to #free" do + expect(@result).to respond_to(:free) + end + it "should raise a Mysql2::Error exception upon a bad query" do expect { @client.query "bad sql" From 00ebcc420d7868281504b69f1d6e2cef9c511138 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 30 Dec 2015 08:36:35 -0800 Subject: [PATCH 418/783] Add Ruby 2.3 to the Travis CI matrix and test from newest to oldest release --- .travis.yml | 9 +++++---- appveyor.yml | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0ec4fee26..b7e1424c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,11 +10,12 @@ addons: hosts: - mysql2gem.example.com rvm: - - 1.8.7 - - 1.9.3 - - 2.0.0 - - 2.1 + - 2.3.0 - 2.2 + - 2.1 + - 2.0.0 + - 1.9.3 + - 1.8.7 - ree - rbx-2 - ruby-head diff --git a/appveyor.yml b/appveyor.yml index cd58cb533..7709499b7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,12 +25,12 @@ test_script: # - find tmp -name "*.log" -exec cat {}; environment: matrix: - - ruby_version: "200" - - ruby_version: "200-x64" - - ruby_version: "21" - - ruby_version: "21-x64" - - ruby_version: "22" - ruby_version: "22-x64" + - ruby_version: "22" + - ruby_version: "21-x64" + - ruby_version: "21" + - ruby_version: "200-x64" + - ruby_version: "200" cache: - vendor services: From 69d1bcd649d28a91443704414877cb3f999b1101 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 15 Feb 2016 08:12:59 -0800 Subject: [PATCH 419/783] Temporary workaround for broken Rubygems 2.5 on Travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index b7e1424c1..3e87b6081 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,9 @@ sudo: required language: ruby bundler_args: --without benchmarks development +# Temporary workaround for broken Rubygems on Travis before_install: + - gem update --system 2.4.8 - gem --version - bash .travis_setup.sh os: From c07feb8fd24c67421cf2bf65bf6605be16708078 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 29 Nov 2015 11:47:07 +0900 Subject: [PATCH 420/783] Fix `MARK_CONN_INACTIVE` --- ext/mysql2/client.c | 14 +++++++------- ext/mysql2/client.h | 5 ++--- ext/mysql2/statement.c | 8 ++------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index cb2058020..c475da549 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -259,7 +259,7 @@ static VALUE allocate(VALUE klass) { mysql_client_wrapper * wrapper; obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; - MARK_CONN_INACTIVE(self); + MARK_CONN_INACTIVE(wrapper); wrapper->automatic_close = 1; wrapper->server_version = 0; wrapper->reconnect_enabled = 0; @@ -418,7 +418,7 @@ static VALUE do_send_query(void *args) { mysql_client_wrapper *wrapper = query_args->wrapper; if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ - MARK_CONN_INACTIVE(self); + MARK_CONN_INACTIVE(wrapper); return rb_raise_mysql2_error(wrapper); } return Qnil; @@ -448,7 +448,7 @@ static void *nogvl_do_result(void *ptr, char use_result) { /* once our result is stored off, this connection is ready for another command to be issued */ - MARK_CONN_INACTIVE(self); + MARK_CONN_INACTIVE(wrapper); return result; } @@ -480,7 +480,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { REQUIRE_CONNECTED(wrapper); if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, mark this connection inactive */ - MARK_CONN_INACTIVE(self); + MARK_CONN_INACTIVE(wrapper); return rb_raise_mysql2_error(wrapper); } @@ -493,7 +493,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { - MARK_CONN_INACTIVE(self); + MARK_CONN_INACTIVE(wrapper); rb_raise_mysql2_error(wrapper); } /* no data and no error, so query was not a SELECT */ @@ -517,7 +517,7 @@ struct async_query_args { static VALUE disconnect_and_raise(VALUE self, VALUE error) { GET_CLIENT(self); - MARK_CONN_INACTIVE(self); + MARK_CONN_INACTIVE(wrapper); wrapper->connected = 0; /* Invalidate the MySQL socket to prevent further communication. @@ -588,7 +588,7 @@ static VALUE finish_and_mark_inactive(void *args) { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); mysql_free_result(result); - MARK_CONN_INACTIVE(self); + MARK_CONN_INACTIVE(wrapper); } return Qnil; diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 672eef974..ecab3adee 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -59,9 +59,8 @@ typedef struct { void rb_mysql_client_set_active_thread(VALUE self); -#define MARK_CONN_INACTIVE(conn) do {\ - wrapper->active_thread = Qnil; \ - } while(0) +#define MARK_CONN_INACTIVE(wrapper) \ + wrapper->active_thread = Qnil; #define GET_CLIENT(self) \ mysql_client_wrapper *wrapper; \ diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 82678fdb5..ae4cdef50 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -11,7 +11,6 @@ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, inter if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \ if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); } - static void rb_mysql_stmt_mark(void * ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; if (!stmt_wrapper) return; @@ -42,7 +41,6 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) { } } - void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) { VALUE e; GET_CLIENT(stmt_wrapper->client); @@ -71,7 +69,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) { rb_exc_raise(e); } - /* * used to pass all arguments to mysql_stmt_prepare while inside * nogvl_prepare_statement_args @@ -369,8 +366,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { if (metadata == NULL) { if (mysql_stmt_errno(stmt) != 0) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. - - MARK_CONN_INACTIVE(stmt_wrapper->client); + MARK_CONN_INACTIVE(wrapper); rb_raise_mysql2_stmt_error(stmt_wrapper); } // no data and no error, so query was not a SELECT @@ -388,7 +384,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { mysql_free_result(metadata); rb_raise_mysql2_stmt_error(stmt_wrapper); } - MARK_CONN_INACTIVE(stmt_wrapper->client); + MARK_CONN_INACTIVE(wrapper); } resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self); From edf08ace06363d3a2166d9cc384ecc5418935386 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 17 Feb 2016 06:58:12 +0900 Subject: [PATCH 421/783] `MARK_CONN_INACTIVE` is not needed Simply we can use `wrapper->active_thread = Qnil;`. --- ext/mysql2/client.c | 14 +++++++------- ext/mysql2/client.h | 3 --- ext/mysql2/statement.c | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index c475da549..0d8b49e89 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -259,7 +259,7 @@ static VALUE allocate(VALUE klass) { mysql_client_wrapper * wrapper; obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; - MARK_CONN_INACTIVE(wrapper); + wrapper->active_thread = Qnil; wrapper->automatic_close = 1; wrapper->server_version = 0; wrapper->reconnect_enabled = 0; @@ -418,7 +418,7 @@ static VALUE do_send_query(void *args) { mysql_client_wrapper *wrapper = query_args->wrapper; if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ - MARK_CONN_INACTIVE(wrapper); + wrapper->active_thread = Qnil; return rb_raise_mysql2_error(wrapper); } return Qnil; @@ -448,7 +448,7 @@ static void *nogvl_do_result(void *ptr, char use_result) { /* once our result is stored off, this connection is ready for another command to be issued */ - MARK_CONN_INACTIVE(wrapper); + wrapper->active_thread = Qnil; return result; } @@ -480,7 +480,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { REQUIRE_CONNECTED(wrapper); if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, mark this connection inactive */ - MARK_CONN_INACTIVE(wrapper); + wrapper->active_thread = Qnil; return rb_raise_mysql2_error(wrapper); } @@ -493,7 +493,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { - MARK_CONN_INACTIVE(wrapper); + wrapper->active_thread = Qnil; rb_raise_mysql2_error(wrapper); } /* no data and no error, so query was not a SELECT */ @@ -517,7 +517,7 @@ struct async_query_args { static VALUE disconnect_and_raise(VALUE self, VALUE error) { GET_CLIENT(self); - MARK_CONN_INACTIVE(wrapper); + wrapper->active_thread = Qnil; wrapper->connected = 0; /* Invalidate the MySQL socket to prevent further communication. @@ -588,7 +588,7 @@ static VALUE finish_and_mark_inactive(void *args) { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); mysql_free_result(result); - MARK_CONN_INACTIVE(wrapper); + wrapper->active_thread = Qnil; } return Qnil; diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index ecab3adee..d10274f84 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -59,9 +59,6 @@ typedef struct { void rb_mysql_client_set_active_thread(VALUE self); -#define MARK_CONN_INACTIVE(wrapper) \ - wrapper->active_thread = Qnil; - #define GET_CLIENT(self) \ mysql_client_wrapper *wrapper; \ Data_Get_Struct(self, mysql_client_wrapper, wrapper); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index ae4cdef50..cfd6f681a 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -366,7 +366,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { if (metadata == NULL) { if (mysql_stmt_errno(stmt) != 0) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. - MARK_CONN_INACTIVE(wrapper); + wrapper->active_thread = Qnil; rb_raise_mysql2_stmt_error(stmt_wrapper); } // no data and no error, so query was not a SELECT @@ -384,7 +384,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { mysql_free_result(metadata); rb_raise_mysql2_stmt_error(stmt_wrapper); } - MARK_CONN_INACTIVE(wrapper); + wrapper->active_thread = Qnil; } resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self); From 7cd2b2856c2ef23bbb96e4d8ca2e22396e224c39 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 29 Nov 2015 13:17:06 +0900 Subject: [PATCH 422/783] Fix indention --- ext/mysql2/client.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 0d8b49e89..11c089553 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1011,10 +1011,10 @@ static VALUE rb_mysql_client_ping(VALUE self) { static VALUE rb_mysql_client_more_results(VALUE self) { GET_CLIENT(self); - if (mysql_more_results(wrapper->client) == 0) - return Qfalse; - else - return Qtrue; + if (mysql_more_results(wrapper->client) == 0) + return Qfalse; + else + return Qtrue; } /* call-seq: From 46fa2cf4a578cc0eddf7d3675f702f9961bca01d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 29 Nov 2015 13:20:23 +0900 Subject: [PATCH 423/783] Remove needless return --- ext/mysql2/client.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 11c089553..86063e616 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -373,7 +373,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po if (wrapper->connect_timeout) mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout); if (rv == Qfalse) - return rb_raise_mysql2_error(wrapper); + rb_raise_mysql2_error(wrapper); } wrapper->server_version = mysql_get_server_version(wrapper->client); @@ -419,7 +419,7 @@ static VALUE do_send_query(void *args) { if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ wrapper->active_thread = Qnil; - return rb_raise_mysql2_error(wrapper); + rb_raise_mysql2_error(wrapper); } return Qnil; } @@ -481,7 +481,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, mark this connection inactive */ wrapper->active_thread = Qnil; - return rb_raise_mysql2_error(wrapper); + rb_raise_mysql2_error(wrapper); } is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream); @@ -1228,7 +1228,7 @@ static VALUE initialize_ext(VALUE self) { if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) { /* TODO: warning - not enough memory? */ - return rb_raise_mysql2_error(wrapper); + rb_raise_mysql2_error(wrapper); } wrapper->initialized = 1; From 19d93b5b2364df2742f873718ae443915f71b442 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 29 Nov 2015 13:22:31 +0900 Subject: [PATCH 424/783] Avoid GC run between `mysql_stmt_execute` and `mysql_stmt_store_result` Fixes #694. --- ext/mysql2/statement.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index cfd6f681a..381f90fa3 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -177,16 +177,6 @@ static void *nogvl_execute(void *ptr) { } } -static void *nogvl_stmt_store_result(void *ptr) { - MYSQL_STMT *stmt = ptr; - - if (mysql_stmt_store_result(stmt)) { - return (void *)Qfalse; - } else { - return (void *)Qtrue; - } -} - static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) { int length; @@ -380,7 +370,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { is_streaming = (Qtrue == rb_hash_aref(current, sym_stream)); if (!is_streaming) { // recieve the whole result set from the server - if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) { + if (mysql_stmt_store_result(stmt)) { mysql_free_result(metadata); rb_raise_mysql2_stmt_error(stmt_wrapper); } From 5923e396b7385bfba2d1ebaa1073405bfd59729b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 17 Feb 2016 10:37:24 -0800 Subject: [PATCH 425/783] Escape double stars when unzipping Connector/C --- tasks/vendor_mysql.rake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index 20e37e22d..611b98378 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -49,9 +49,9 @@ task "vendor:mysql", [:platform] do |_t, args| when_writing "creating #{t.name}" do cd "vendor" do sh "unzip", "-uq", full_file, - "#{vendor_mysql_dir(args[:platform])}/bin/**", - "#{vendor_mysql_dir(args[:platform])}/include/**", - "#{vendor_mysql_dir(args[:platform])}/lib/**", + "#{vendor_mysql_dir(args[:platform])}/bin/\\*\\*", + "#{vendor_mysql_dir(args[:platform])}/include/\\*\\*", + "#{vendor_mysql_dir(args[:platform])}/lib/\\*\\*", "#{vendor_mysql_dir(args[:platform])}/README" # contains the license info end # update file timestamp to avoid Rake performing this extraction again. From a2e84581b9bcaad7a7f464b097a98bdde2511e0f Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 18 Feb 2016 04:01:09 -0800 Subject: [PATCH 426/783] Fix data type for RSTRING_LEN --- ext/mysql2/statement.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 381f90fa3..1461e0b76 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -178,7 +178,7 @@ static void *nogvl_execute(void *ptr) { } static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) { - int length; + unsigned long length; bind_buffer->buffer_type = MYSQL_TYPE_STRING; bind_buffer->buffer = RSTRING_PTR(string); From 32e8883cfb3dc04b91d93b88be5a3aed0a0e8272 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 18 Feb 2016 11:48:35 -0800 Subject: [PATCH 427/783] Bump up the allowed complexity for Client#initialize --- .rubocop_todo.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e252ab24e..437148100 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -20,7 +20,7 @@ Metrics/ClassLength: # Offense count: 2 Metrics/CyclomaticComplexity: - Max: 25 + Max: 26 # Offense count: 290 # Configuration parameters: AllowURI, URISchemes. @@ -34,7 +34,7 @@ Metrics/MethodLength: # Offense count: 1 Metrics/PerceivedComplexity: - Max: 22 + Max: 26 # Offense count: 40 # Cop supports --auto-correct. From b0d44c73cb4d1f403bb0b2d88f4d7950bf13f287 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 18 Feb 2016 03:58:57 -0800 Subject: [PATCH 428/783] Add method Client#ssl_cipher to expose mysql_get_ssl_cipher --- ext/mysql2/client.c | 21 +++++++++++++++++++++ spec/mysql2/client_spec.rb | 16 ++++++---------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 86063e616..54a7d6278 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -332,6 +332,26 @@ static VALUE rb_mysql_info(VALUE self) { return rb_str; } +static VALUE rb_mysql_get_ssl_cipher(VALUE self) +{ + const char *cipher; + VALUE rb_str; + GET_CLIENT(self); + + cipher = mysql_get_ssl_cipher(wrapper->client); + + if (cipher == NULL) { + return Qnil; + } + + rb_str = rb_str_new2(cipher); +#ifdef HAVE_RUBY_ENCODING_H + rb_enc_associate(rb_str, rb_utf8_encoding()); +#endif + + return rb_str; +} + static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) { struct nogvl_connect_args args; time_t start_time, end_time, elapsed_time, connect_timeout; @@ -1302,6 +1322,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1); rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0); rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0); + rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0); #ifdef HAVE_RUBY_ENCODING_H rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); #endif diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 692d8f555..67e213825 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -146,16 +146,12 @@ def connect(*args) # rubocop:enable Style/TrailingComma }.not_to raise_error - results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a - expect(results[0]['Variable_name']).to eql('Ssl_cipher') - expect(results[0]['Value']).not_to be_nil - expect(results[0]['Value']).to be_an_instance_of(String) - expect(results[0]['Value']).not_to be_empty - - expect(results[1]['Variable_name']).to eql('Ssl_version') - expect(results[1]['Value']).not_to be_nil - expect(results[1]['Value']).to be_an_instance_of(String) - expect(results[1]['Value']).not_to be_empty + results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }] + expect(results['Ssl_cipher']).not_to be_empty + expect(results['Ssl_version']).not_to be_empty + + expect(ssl_client.ssl_cipher).not_to be_empty + expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher) ssl_client.close end From e29f53527f98e100f62cc5b42bd1db2c8967bcbf Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 24 Feb 2016 10:28:12 -0800 Subject: [PATCH 429/783] Update to rake-compiler-dock 0.5.1 --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index f4a9ca4f8..86a7ddb11 100644 --- a/Gemfile +++ b/Gemfile @@ -22,7 +22,7 @@ end group :development do gem 'pry' - gem 'rake-compiler-dock', '~> 0.4.2' + gem 'rake-compiler-dock', '~> 0.5.1' end platforms :rbx do From e62bc55119ae4d3a22fd639d40422e0235a1dd7f Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 24 Feb 2016 10:28:33 -0800 Subject: [PATCH 430/783] Bump version to 0.4.3 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index e97480be1..d92374c61 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.2" + VERSION = "0.4.3" end From 5a844aff3e0b3b500b3ff7d741ddde2e50e97077 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 24 Feb 2016 10:47:39 -0800 Subject: [PATCH 431/783] Remove Travis os: linux as recommended by Travis Support --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3e87b6081..2c059cc7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,6 @@ before_install: - gem update --system 2.4.8 - gem --version - bash .travis_setup.sh -os: - - linux addons: hosts: - mysql2gem.example.com From 0be220427394c7167c84989c3559b7b91c113d6c Mon Sep 17 00:00:00 2001 From: Arlan Jaska Date: Wed, 24 Feb 2016 15:58:06 -0800 Subject: [PATCH 432/783] Fix some tests for non-default creds --- spec/mysql2/client_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 67e213825..2561ce813 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -382,24 +382,24 @@ def run_gc it "should expect connect_timeout to be a positive integer" do expect { - Mysql2::Client.new(:connect_timeout => -1) + Mysql2::Client.new(DatabaseCredentials['root'].merge(:connect_timeout => -1)) }.to raise_error(Mysql2::Error) end it "should expect read_timeout to be a positive integer" do expect { - Mysql2::Client.new(:read_timeout => -1) + Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => -1)) }.to raise_error(Mysql2::Error) end it "should expect write_timeout to be a positive integer" do expect { - Mysql2::Client.new(:write_timeout => -1) + Mysql2::Client.new(DatabaseCredentials['root'].merge(:write_timeout => -1)) }.to raise_error(Mysql2::Error) end it "should allow nil read_timeout" do - client = Mysql2::Client.new(:read_timeout => nil) + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => nil)) expect(client.read_timeout).to be_nil end From 3431c874a3dfb5e6427f35db7300a1e244919899 Mon Sep 17 00:00:00 2001 From: Arlan Jaska Date: Fri, 26 Feb 2016 11:39:54 -0800 Subject: [PATCH 433/783] Add a :cache_rows => false test for statements --- spec/mysql2/statement_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 6358282de..db9ffc3c7 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -128,6 +128,14 @@ expect(test_result['decimal_test']).to eql(123.45) end + it "should warn but still work if cache_rows is set to false" do + @client.query_options.merge!(:cache_rows => false) + statement = @client.prepare 'SELECT 1' + result = nil + expect { result = statement.execute.to_a }.to output(/:cache_rows is forced for prepared statements/).to_stderr + expect(result.length).to eq(1) + end + context "utf8_db" do before(:each) do @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") From f8b95a35a2d245364a2e8e617f8b4cb6da5997a3 Mon Sep 17 00:00:00 2001 From: Arlan Jaska Date: Fri, 26 Feb 2016 12:03:52 -0800 Subject: [PATCH 434/783] Actually force cacheRows if using a statement --- ext/mysql2/result.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 14bc79e66..5003c5863 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -886,6 +886,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) { rb_warn(":cache_rows is forced for prepared statements (if not streaming)"); + cacheRows = 1; } if (wrapper->stmt_wrapper && !cast) { From 2cabc76ab4de17fea9cde13237823ba44243d120 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 6 Mar 2016 19:30:34 +0900 Subject: [PATCH 435/783] Fix condition for already rows cached `mysql_num_rows(wrapper->result)` returns 0 when result rows is 0. --- ext/mysql2/result.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 14bc79e66..55e2789e9 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -913,7 +913,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { app_timezone = Qnil; } - if (wrapper->lastRowProcessed == 0 && !wrapper->is_streaming) { + if (wrapper->rows == Qnil && !wrapper->is_streaming) { wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result); if (wrapper->numberOfRows == 0) { rb_mysql_result_free_result(wrapper); From 6637c9f42672136ac20ebbc1422ef41445f5878a Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 6 Mar 2016 19:44:58 +0900 Subject: [PATCH 436/783] Should initialize `fields` before `rb_mysql_result_free_result` --- ext/mysql2/result.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 55e2789e9..6e3074ca6 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -346,15 +346,15 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co conn_enc = rb_to_encoding(wrapper->encoding); #endif + if (wrapper->fields == Qnil) { + wrapper->numberOfFields = mysql_num_fields(wrapper->result); + wrapper->fields = rb_ary_new2(wrapper->numberOfFields); + } if (args->asArray) { rowVal = rb_ary_new2(wrapper->numberOfFields); } else { rowVal = rb_hash_new(); } - if (wrapper->fields == Qnil) { - wrapper->numberOfFields = mysql_num_fields(wrapper->result); - wrapper->fields = rb_ary_new2(wrapper->numberOfFields); - } if (wrapper->result_buffers == NULL) { rb_mysql_result_alloc_result_buffers(self, fields); @@ -541,16 +541,16 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r return Qnil; } + if (wrapper->fields == Qnil) { + wrapper->numberOfFields = mysql_num_fields(wrapper->result); + wrapper->fields = rb_ary_new2(wrapper->numberOfFields); + } if (args->asArray) { rowVal = rb_ary_new2(wrapper->numberOfFields); } else { rowVal = rb_hash_new(); } fieldLengths = mysql_fetch_lengths(wrapper->result); - if (wrapper->fields == Qnil) { - wrapper->numberOfFields = mysql_num_fields(wrapper->result); - wrapper->fields = rb_ary_new2(wrapper->numberOfFields); - } for (i = 0; i < wrapper->numberOfFields; i++) { VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys); @@ -915,11 +915,6 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { if (wrapper->rows == Qnil && !wrapper->is_streaming) { wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result); - if (wrapper->numberOfRows == 0) { - rb_mysql_result_free_result(wrapper); - wrapper->rows = rb_ary_new(); - return wrapper->rows; - } wrapper->rows = rb_ary_new2(wrapper->numberOfRows); } From 0faa0a7dd5666f94ba5e61b8d9e83168fc68183a Mon Sep 17 00:00:00 2001 From: Arlan Jaska Date: Thu, 10 Mar 2016 10:54:07 -0800 Subject: [PATCH 437/783] Test for iterating twice with :cache_rows => false --- spec/mysql2/result_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 9633588e9..ce797baa2 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -82,6 +82,11 @@ expect(result.first.object_id).not_to eql(result.first.object_id) end + it "should be able to iterate a second time even if cache_rows is disabled" do + result = @client.query "SELECT 1 UNION SELECT 2", :cache_rows => false + expect(result.to_a).to eql(result.to_a) + end + it "should yield different value for #first if streaming" do result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false expect(result.first).not_to eql(result.first) From 873302966c1110dbbce5c9e17e458ca61d94de4c Mon Sep 17 00:00:00 2001 From: Arlan Jaska Date: Thu, 10 Mar 2016 11:42:41 -0800 Subject: [PATCH 438/783] Reset rows on second iteration (and seek to beginning) --- ext/mysql2/result.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index a24d28c82..b51c46ac4 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -834,7 +834,9 @@ static VALUE rb_mysql_result_each_(VALUE self, if (row == Qnil) { /* we don't need the mysql C dataset around anymore, peace it */ - rb_mysql_result_free_result(wrapper); + if (args->cacheRows) { + rb_mysql_result_free_result(wrapper); + } return Qnil; } @@ -842,7 +844,7 @@ static VALUE rb_mysql_result_each_(VALUE self, rb_yield(row); } } - if (wrapper->lastRowProcessed == wrapper->numberOfRows) { + if (wrapper->lastRowProcessed == wrapper->numberOfRows && args->cacheRows) { /* we don't need the mysql C dataset around anymore, peace it */ rb_mysql_result_free_result(wrapper); } @@ -917,6 +919,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { if (wrapper->rows == Qnil && !wrapper->is_streaming) { wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result); wrapper->rows = rb_ary_new2(wrapper->numberOfRows); + } else if (wrapper->rows && !cacheRows) { + mysql_data_seek(wrapper->result, 0); + wrapper->lastRowProcessed = 0; + wrapper->rows = rb_ary_new2(wrapper->numberOfRows); } // Backward compat From e3728927a3a9184cb5dc5f21f83cb8d12efeb762 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 24 Nov 2015 12:30:56 -0800 Subject: [PATCH 439/783] Use the Travis Trusty image for Ruby 1.9.3 and higher and the MariaDB Travis addon --- .travis.yml | 51 +++++++++++++++++++++++++++++++++++----------- .travis_mysql57.sh | 2 +- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c059cc7c..814617792 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ sudo: required +dist: trusty language: ruby bundler_args: --without benchmarks development # Temporary workaround for broken Rubygems on Travis @@ -9,38 +10,64 @@ before_install: addons: hosts: - mysql2gem.example.com + apt: + packages: + - mysql-server-5.5 + - mysql-client-core-5.5 + - mysql-client-5.5 rvm: - 2.3.0 - 2.2 - 2.1 - 2.0.0 - 1.9.3 - - 1.8.7 - - ree - - rbx-2 - ruby-head matrix: include: + - rvm: 1.8.7 + dist: precise + - rvm: ree + dist: precise + - rvm: rbx-2 + dist: precise - rvm: 2.0.0 env: DB=mariadb55 addons: - mariadb: 5.5 - hosts: - - mysql2gem.example.com + mariadb: 5.5 + hosts: + - mysql2gem.example.com - rvm: 2.0.0 env: DB=mariadb10.0 addons: - mariadb: 10.0 - hosts: - - mysql2gem.example.com + mariadb: 10.0 + hosts: + - mysql2gem.example.com - rvm: 2.0.0 env: DB=mariadb10.1 addons: - mariadb: 10.1 - hosts: - - mysql2gem.example.com + mariadb: 10.1 + hosts: + - mysql2gem.example.com + - rvm: 2.0.0 + env: DB=mysql56 + addons: + hosts: + - mysql2gem.example.com + apt: + packages: + - mysql-server-5.6 + - mysql-client-core-5.6 + - mysql-client-5.6 - rvm: 2.0.0 env: DB=mysql57 + addons: + hosts: + - mysql2gem.example.com + apt: + packages: + - mysql-server-5.7 + - mysql-client-core-5.7 + - mysql-client-5.7 - rvm: 2.0.0 env: DB=mysql55 os: osx diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index b4a353abd..e7e6f0a45 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -6,7 +6,7 @@ service mysql stop apt-get purge '^mysql*' 'libmysql*' apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x8C718D3B5072E1F5 -add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.7' +add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ trusty mysql-5.7' apt-get update apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew -y install mysql-server libmysqlclient-dev From f6ff68100325ce6fdb978d0e1f154617ab0f4855 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 24 Feb 2016 22:02:42 -0800 Subject: [PATCH 440/783] Create a MySQL travis user and grant all privileges --- .travis_setup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis_setup.sh b/.travis_setup.sh index ed3698fbd..42d56cd79 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -20,7 +20,8 @@ if ! [[ x$OSTYPE =~ ^xdarwin ]]; then sudo service mysql restart fi -sudo mysql -e "CREATE USER '$USER'@'localhost'" || true +sudo mysql -u root -e "CREATE USER '$USER'@'localhost'" || true +sudo mysql -u root -e "GRANT ALL ON test.* TO '$USER'@'localhost'" || true # Print the MySQL version and create the test DB if [[ x$OSTYPE =~ ^xdarwin ]]; then From 6b42b77e6f0313ed394e862c4512550f3cdf4a49 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 13 Apr 2016 05:44:23 -0700 Subject: [PATCH 441/783] Clarify the Ruby on Rails / Active Record Compatibility section of the README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 24052cf68..201a35c30 100644 --- a/README.md +++ b/README.md @@ -484,9 +484,9 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x + * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x * Ruby Enterprise Edition (based on MRI 1.8.7) - * Rubinius 2.x + * Rubinius 2.x, 3.x This gem is tested with the following MySQL and MariaDB versions: @@ -494,11 +494,11 @@ This gem is tested with the following MySQL and MariaDB versions: * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) * MariaDB 5.5, 10.0, 10.1 -### Rails / Active Record +### Ruby on Rails / Active Record - * mysql2 0.4.x works with Active Record 4.2.5 and higher. - * mysql2 0.3.x works with Active Record 3.1 and higher (the AR adapter is now included in AR proper). - * mysql2 0.2.x includes an Active Record adapter compatible with AR 2.3 and 3.0, and should not be used with AR 3.1 or higher. + * mysql2 0.4.x works with Rails / Active Record 4.2.5 - 5.0 and higher. + * mysql2 0.3.x works with Rails / Active Record 3.1, 3.2, 4.x, 5.0. + * mysql2 0.2.x works with Rails / Active Record 2.3 - 3.0. ### Asynchronous Active Record From 6924e7649dbd08d1c2d1cedd545b75139e57b7aa Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 19 Apr 2016 04:21:50 -0700 Subject: [PATCH 442/783] Bump version to 0.4.4 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index d92374c61..f257aca75 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.3" + VERSION = "0.4.4" end From 0e5b2d6cff070030384e14826acd8721d5036720 Mon Sep 17 00:00:00 2001 From: "NARUSE, Yui" Date: Tue, 28 Jun 2016 19:36:01 +0900 Subject: [PATCH 443/783] Suppress rubocop's invalid UTF-8 literal warning --- spec/mysql2/error_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index ea4af54f7..b70ffc867 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -34,7 +34,7 @@ end end - let(:invalid_utf8) { "\xE5\xC6\x7D\x1F" } + let(:invalid_utf8) { ["e5c67d1f"].pack('H*').force_encoding(Encoding::UTF_8) } let(:bad_err) do begin client.query(invalid_utf8) From a787899fe93ee7405aff3353ea861c669c2c0726 Mon Sep 17 00:00:00 2001 From: Kubo Takehiro Date: Sat, 11 Jun 2016 18:24:01 +0900 Subject: [PATCH 444/783] Fix SEGV when `wrapper->result` is used after it is freed. --- ext/mysql2/result.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index b51c46ac4..9841f7409 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -920,6 +920,9 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result); wrapper->rows = rb_ary_new2(wrapper->numberOfRows); } else if (wrapper->rows && !cacheRows) { + if (wrapper->resultFreed) { + rb_raise(cMysql2Error, "Result set has already been freed"); + } mysql_data_seek(wrapper->result, 0); wrapper->lastRowProcessed = 0; wrapper->rows = rb_ary_new2(wrapper->numberOfRows); From 6bf7c34048952ae3227ee19511fc323e66e62998 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 6 Jul 2016 23:37:18 -0700 Subject: [PATCH 445/783] Add Ruby 2.4.0-preview1, update Rubies and Rubygems in Travis CI --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 814617792..b0343d457 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ sudo: required dist: trusty language: ruby bundler_args: --without benchmarks development -# Temporary workaround for broken Rubygems on Travis +# Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - - gem update --system 2.4.8 + - gem update --system 2.6.4 - gem --version - bash .travis_setup.sh addons: @@ -16,7 +16,8 @@ addons: - mysql-client-core-5.5 - mysql-client-5.5 rvm: - - 2.3.0 + - 2.4.0-preview1 + - 2.3.1 - 2.2 - 2.1 - 2.0.0 From 42075f8921aabba5d088c5788467fa66a2dfa252 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 28 Jun 2016 13:54:48 -0400 Subject: [PATCH 446/783] Avoid RuboCop on RBX; it likes to SIGSEV in Travis --- Rakefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 1b860f588..bf6ac08bd 100644 --- a/Rakefile +++ b/Rakefile @@ -8,13 +8,13 @@ load 'tasks/compile.rake' load 'tasks/generate.rake' load 'tasks/benchmarks.rake' -# TODO: remove when we end support for < 1.9.3 -begin +# TODO: remove engine check when rubinius stops crashing on RuboCop +# TODO: remove defined?(Encoding) when we end support for < 1.9.3 +if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && defined?(Encoding) require 'rubocop/rake_task' RuboCop::RakeTask.new task :default => [:spec, :rubocop] - -rescue LoadError +else warn 'RuboCop is not available' task :default => :spec end From ba805cea4cffcaf3f45e6f24945e42d1cbd8a114 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 28 Jun 2016 12:03:26 -0400 Subject: [PATCH 447/783] DRY --- spec/mysql2/statement_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index db9ffc3c7..b5e00fcc3 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -6,11 +6,13 @@ @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) end + def stmt_count + @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i + end + it "should create a statement" do statement = nil - expect { statement = @client.prepare 'SELECT 1' }.to change { - @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i - }.by(1) + expect { statement = @client.prepare 'SELECT 1' }.to change(&method(:stmt_count)).by(1) expect(statement).to be_an_instance_of(Mysql2::Statement) end @@ -689,9 +691,7 @@ context 'close' do it 'should free server resources' do stmt = @client.prepare 'SELECT 1' - expect { stmt.close }.to change { - @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i - }.by(-1) + expect { stmt.close }.to change(&method(:stmt_count)).by(-1) end it 'should raise an error on subsequent execution' do From 39901daa90caf51e44cd85aedbeccd10f763fd4a Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 28 Jun 2016 11:20:27 -0400 Subject: [PATCH 448/783] Travis: install mysql-5.7 "manually" See https://github.com/travis-ci/travis-ci/issues/5122 --- .travis.yml | 11 ++++++----- .travis_mysql57.sh | 22 +++++++--------------- .travis_setup.sh | 9 +++------ .travis_ssl.sh | 2 +- spec/configuration.yml.example | 6 ------ spec/mysql2/client_spec.rb | 11 ++++++----- spec/ssl/gen_certs.sh | 2 +- 7 files changed, 24 insertions(+), 39 deletions(-) diff --git a/.travis.yml b/.travis.yml index b0343d457..4c86ff305 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,11 +64,12 @@ matrix: addons: hosts: - mysql2gem.example.com - apt: - packages: - - mysql-server-5.7 - - mysql-client-core-5.7 - - mysql-client-5.7 + # https://github.com/travis-ci/travis-ci/issues/5122 + # apt: + # packages: + # - mysql-server-5.7 + # - mysql-client-core-5.7 + # - mysql-client-5.7 - rvm: 2.0.0 env: DB=mysql55 os: osx diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index e7e6f0a45..96183ea27 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -1,20 +1,12 @@ #!/usr/bin/env bash -set -eu - -service mysql stop -apt-get purge '^mysql*' 'libmysql*' -apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x8C718D3B5072E1F5 +set -eux +apt-get purge -qq '^mysql*' '^libmysql*' +apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5 add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ trusty mysql-5.7' +apt-get update -qq +apt-get install -qq mysql-server libmysqlclient-dev -apt-get update -apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew -y install mysql-server libmysqlclient-dev - -mysql_upgrade -u root --force --upgrade-system-tables - -# Replace the final line of the mysql apparmor, allowing /etc/mysql/*.pem -sed -ie '$ s|}|\ - /etc/mysql/*.pem r,\ -}|' /etc/apparmor.d/usr.sbin.mysqld -service apparmor restart +# https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ +mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" diff --git a/.travis_setup.sh b/.travis_setup.sh index 42d56cd79..5b562f192 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -eu +set -eux # Install MySQL 5.7 if DB=mysql57 if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then @@ -20,15 +20,12 @@ if ! [[ x$OSTYPE =~ ^xdarwin ]]; then sudo service mysql restart fi -sudo mysql -u root -e "CREATE USER '$USER'@'localhost'" || true -sudo mysql -u root -e "GRANT ALL ON test.* TO '$USER'@'localhost'" || true - # Print the MySQL version and create the test DB if [[ x$OSTYPE =~ ^xdarwin ]]; then $(brew --prefix "$DB")/bin/mysqld --version - $(brew --prefix "$DB")/bin/mysql -u $USER -e "CREATE DATABASE IF NOT EXISTS test" + $(brew --prefix "$DB")/bin/mysql -u root -e 'CREATE DATABASE IF NOT EXISTS test' else mysqld --version # IF NOT EXISTS is mariadb-10+ only - https://mariadb.com/kb/en/mariadb/comment-syntax/ - mysql -u $USER -e "CREATE DATABASE /*M!50701 IF NOT EXISTS */ test" + mysql -u root -e 'CREATE DATABASE /*M!50701 IF NOT EXISTS */ test' fi diff --git a/.travis_ssl.sh b/.travis_ssl.sh index d022e9570..e98f27a44 100644 --- a/.travis_ssl.sh +++ b/.travis_ssl.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -eu +set -eux # Make sure there is an /etc/mysql mkdir -p /etc/mysql diff --git a/spec/configuration.yml.example b/spec/configuration.yml.example index 5a4406fc7..6024a1ce9 100644 --- a/spec/configuration.yml.example +++ b/spec/configuration.yml.example @@ -9,9 +9,3 @@ user: username: LOCALUSERNAME password: database: mysql2_test - -numericuser: - host: localhost - username: LOCALUSERNAME - password: - database: 12345 diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 2561ce813..15813bce4 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -263,13 +263,14 @@ def run_gc end it "should be able to connect to database with numeric-only name" do - creds = DatabaseCredentials['numericuser'] - @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`" - @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`" + database = 1235 + @client.query "CREATE DATABASE IF NOT EXISTS `#{database}`" - expect { Mysql2::Client.new(creds) }.not_to raise_error + expect { + Mysql2::Client.new(DatabaseCredentials['root'].merge('database' => database)) + }.not_to raise_error - @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`" + @client.query "DROP DATABASE IF EXISTS `#{database}`" end it "should respond to #close" do diff --git a/spec/ssl/gen_certs.sh b/spec/ssl/gen_certs.sh index d55872d0f..728748126 100644 --- a/spec/ssl/gen_certs.sh +++ b/spec/ssl/gen_certs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -eu +set -eux echo " [ ca ] From 4d7fe4e7cc20a3acf0888c9d22cbfc63560243d8 Mon Sep 17 00:00:00 2001 From: Jonas Helgemo Date: Tue, 11 Oct 2016 16:16:50 +0200 Subject: [PATCH 449/783] Convert connect_timeout, read_timeout and write_timeout Why: * When using a DATABASE_URL env variable with one or more of these params, a type-error is triggered --- lib/mysql2/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 81a2c77ad..be72e7dc0 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -36,7 +36,7 @@ def initialize(opts = {}) when :reconnect, :local_infile, :secure_auth, :automatic_close send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation when :connect_timeout, :read_timeout, :write_timeout - send(:"#{key}=", opts[key]) unless opts[key].nil? + send(:"#{key}=", opts[key].to_i) unless opts[key].nil? else send(:"#{key}=", opts[key]) end From 079860b464d350a46117551fae1eb4a743d11a4a Mon Sep 17 00:00:00 2001 From: Matt Sanders Date: Tue, 5 Jul 2016 17:19:17 -0700 Subject: [PATCH 450/783] Fix indent in README Fix indent in README headers/columns example --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 201a35c30..75d690135 100644 --- a/README.md +++ b/README.md @@ -164,8 +164,8 @@ by the query like this: ``` ruby headers = results.fields # <= that's an array of field names, in order results.each(:as => :array) do |row| -# Each row is an array, ordered the same as the query results -# An otter's den is called a "holt" or "couch" + # Each row is an array, ordered the same as the query results + # An otter's den is called a "holt" or "couch" end ``` From deda0c7b612142f5cec11ddb29383a6068fdb8d8 Mon Sep 17 00:00:00 2001 From: Adam Palmblad Date: Tue, 18 Oct 2016 11:09:02 -0700 Subject: [PATCH 451/783] Add ability to set ssl_mode. (#793) This PR adds support for the new MySQL 5.7.3 ssl_mode option and additional ssl_mode options in MySQL 5.7.11 and higher. See the MySQL docs for details: http://dev.mysql.com/doc/refman/5.7/en/mysql-options.html --- README.md | 1 + ext/mysql2/client.c | 69 +++++++++++++++++++++++++++++++++++++++++++ ext/mysql2/extconf.rb | 12 ++++++++ lib/mysql2/client.rb | 12 ++++++++ 4 files changed, 94 insertions(+) diff --git a/README.md b/README.md index 75d690135..e6dde760c 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,7 @@ Mysql2::Client.new( :reconnect = true/false, :local_infile = true/false, :secure_auth = true/false, + :ssl_mode = :disabled / :preferred / :required / :verify_ca / :verify_identity, :default_file = '/path/to/my.cfg', :default_group = 'my.cfg section', :init_command => sql diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 54a7d6278..1e976f97a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -83,6 +83,43 @@ struct nogvl_select_db_args { char *db; }; +static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { + unsigned long version = mysql_get_client_version(); + + if (version < 50703) { + rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." ); + return Qnil; + } +#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE + GET_CLIENT(self); + int val = NUM2INT( setting ); + if (version >= 50703 && version < 50711) { + if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { + bool b = ( val == SSL_MODE_REQUIRED ); + int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); + return INT2NUM(result); + + } else { + rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" ); + return Qnil; + } + } +#endif +#ifdef FULL_SSL_MODE_SUPPORT + GET_CLIENT(self); + int val = NUM2INT( setting ); + + if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) { + rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val ); + } + int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val ); + + return INT2NUM(result); +#endif +#ifdef NO_SSL_MODE_SUPPORT + return Qnil; +#endif +} /* * non-blocking mysql_*() functions that we won't be wrapping since * they do not appear to hit the network nor issue any interruptible @@ -1337,6 +1374,7 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1); rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); + rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); rb_define_private_method(cMysql2Client, "connect", rb_connect, 7); rb_define_private_method(cMysql2Client, "_query", rb_query, 2); @@ -1464,4 +1502,35 @@ void init_mysql2_client() { rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"), LONG2NUM(CLIENT_BASIC_FLAGS)); #endif +#ifdef FULL_SSL_MODE_SUPPORT + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED)); + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED)); + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA)); + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY)); +#endif +#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE + #define SSL_MODE_DISABLED 1 + #define SSL_MODE_REQUIRED 3 + #define HAVE_CONST_SSL_MODE_DISABLED + #define HAVE_CONST_SSL_MODE_REQUIRED + + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED)); +#endif +#ifndef HAVE_CONST_SSL_MODE_DISABLED + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0)); +#endif +#ifndef HAVE_CONST_SSL_MODE_PREFERRED + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(0)); +#endif +#ifndef HAVE_CONST_SSL_MODE_REQUIRED + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(0)); +#endif +#ifndef HAVE_CONST_SSL_MODE_VERIFY_CA + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(0)); +#endif +#ifndef HAVE_CONST_SSL_MODE_VERIFY_IDENTITY + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0)); +#endif } diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index ed82355b0..b8f9c5a65 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -12,6 +12,16 @@ def asplode(lib) end end +def add_ssl_defines(header) + all_modes_found = %w(SSL_MODE_DISABLED SSL_MODE_PREFERRED SSL_MODE_REQUIRED SSL_MODE_VERIFY_CA SSL_MODE_VERIFY_IDENTITY).inject(true) do |m, ssl_mode| + m && have_const(ssl_mode, header) + end + $CFLAGS << ' -DFULL_SSL_MODE_SUPPORT' if all_modes_found + # if we only have ssl toggle (--ssl,--disable-ssl) from 5.7.3 to 5.7.10 + has_no_support = all_modes_found ? false : !have_const('MYSQL_OPT_SSL_ENFORCE', header) + $CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if has_no_support +end + # 2.0-only have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h') @@ -87,6 +97,8 @@ def asplode(lib) asplode 'mysql.h' end +add_ssl_defines([prefix, 'mysql.h'].compact.join('/')) + %w(errmsg.h mysqld_error.h).each do |h| header = [prefix, h].compact.join '/' asplode h unless have_header header diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index be72e7dc0..7478a48a7 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -47,6 +47,7 @@ def initialize(opts = {}) ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher) ssl_set(*ssl_options) if ssl_options.any? + self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode] case opts[:flags] when Array @@ -87,6 +88,17 @@ def initialize(opts = {}) connect user, pass, host, port, database, socket, flags end + def parse_ssl_mode(mode) + m = mode.to_s.upcase + if m.start_with?('SSL_MODE_') + return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m) + else + x = 'SSL_MODE_' + m + return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x) + end + warn "Unknown MySQL ssl_mode flag: #{mode}" + end + def parse_flags_array(flags, initial = 0) flags.reduce(initial) do |memo, f| fneg = f.start_with?('-') ? f[1..-1] : nil From cb564e3ed5c167f3fcc5b8b01d77cbd260d65f32 Mon Sep 17 00:00:00 2001 From: Braxton Date: Tue, 18 Oct 2016 11:10:53 -0700 Subject: [PATCH 452/783] Add parameter type check when initializing Mysql2::Client (#792) Fix for segfault when initializing a Mysql2::Client reported in issue #782 --- .rubocop.yml | 9 +++++++++ lib/mysql2/client.rb | 1 + 2 files changed, 10 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 5ebfb8bdf..3c0115dd0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -22,6 +22,15 @@ Style/TrailingComma: Style/TrivialAccessors: AllowPredicates: true +Metrics/AbcSize: + Max: 82 + +Metrics/CyclomaticComplexity: + Max: 28 + +Metrics/MethodLength: + Max: 55 + # TODO: remove when we end support for < 1.9.3 Style/HashSyntax: diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 7478a48a7..8c50e33e8 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -19,6 +19,7 @@ def self.default_query_options end def initialize(opts = {}) + fail Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash opts = Mysql2::Util.key_hash_as_symbols(opts) @read_timeout = nil @query_options = self.class.default_query_options.dup From f16b3b09e39d172e30d366495b8484bfd2f1ab2b Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 19 Oct 2016 03:13:58 +0900 Subject: [PATCH 453/783] Fix a BigDecimal value binding (#783) Currently a BigDecimal value is bound as a string because `set_buffer_for_string` overwrites `buffer_type` to `MYSQL_TYPE_STRING`. This commit fixes to a BigDecimal value is bound as a decimal. --- ext/mysql2/statement.c | 11 +++++------ spec/mysql2/statement_spec.rb | 7 +++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 1461e0b76..954a6ce41 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -180,7 +180,6 @@ static void *nogvl_execute(void *ptr) { static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) { unsigned long length; - bind_buffer->buffer_type = MYSQL_TYPE_STRING; bind_buffer->buffer = RSTRING_PTR(string); length = RSTRING_LEN(string); @@ -275,13 +274,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]); break; case T_STRING: - { - params_enc[i] = argv[i]; + bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; + + params_enc[i] = argv[i]; #ifdef HAVE_RUBY_ENCODING_H - params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); + params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); #endif - set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); - } + set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); break; default: // TODO: what Ruby type should support MYSQL_TYPE_TIME diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index b5e00fcc3..0458445ff 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -119,6 +119,13 @@ def stmt_count expect(list[1]).to eq('2') end + it "should handle as a decimal binding a BigDecimal" do + stmt = @client.prepare('SELECT ? AS decimal_test') + test_result = stmt.execute(BigDecimal.new("123.45")).first + expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal) + expect(test_result['decimal_test']).to eql(123.45) + end + it "should update a DECIMAL value passing a BigDecimal" do @client.query 'USE test' @client.query 'DROP TABLE IF EXISTS mysql2_stmt_decimal_test' From 9da086413fa1a33318b343c8eab53ec0a481be47 Mon Sep 17 00:00:00 2001 From: getapp Date: Tue, 18 Oct 2016 22:16:15 +0200 Subject: [PATCH 454/783] Add a note about casting boolean columns (#770) Explains why "SELECT true" doesn't get converted to a boolean with cast_booleans: true. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index e6dde760c..291f97f4c 100644 --- a/README.md +++ b/README.md @@ -399,6 +399,15 @@ client = Mysql2::Client.new result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true) ``` +Keep in mind that this works only with fields and not with computed values, e.g. this result will contain `1`, not `true`: + +``` ruby +client = Mysql2::Client.new +result = client.query("SELECT true", :cast_booleans => true) +``` + +CAST function wouldn't help here as there's no way to cast to TINYINT(1). Apparently the only way to solve this is to use a stored procedure with return type set to TINYINT(1). + ### Skipping casting Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false. (Note that :cast => false overrides :cast_booleans => true.) From f660d51a6ccf9c6ce3fd81f874074aca32505f4a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 18 Oct 2016 18:36:18 -0700 Subject: [PATCH 455/783] Bump up RuboCop limits --- .rubocop.yml | 9 --------- .rubocop_todo.yml | 6 +++--- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3c0115dd0..5ebfb8bdf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -22,15 +22,6 @@ Style/TrailingComma: Style/TrivialAccessors: AllowPredicates: true -Metrics/AbcSize: - Max: 82 - -Metrics/CyclomaticComplexity: - Max: 28 - -Metrics/MethodLength: - Max: 55 - # TODO: remove when we end support for < 1.9.3 Style/HashSyntax: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 437148100..d522ccc8f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -8,7 +8,7 @@ # Offense count: 2 Metrics/AbcSize: - Max: 80 + Max: 85 # Offense count: 1 Metrics/BlockNesting: @@ -20,7 +20,7 @@ Metrics/ClassLength: # Offense count: 2 Metrics/CyclomaticComplexity: - Max: 26 + Max: 30 # Offense count: 290 # Configuration parameters: AllowURI, URISchemes. @@ -30,7 +30,7 @@ Metrics/LineLength: # Offense count: 5 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 50 + Max: 60 # Offense count: 1 Metrics/PerceivedComplexity: From 5e2b3a65f98654b86505a3e65d1cfc07ea216e9c Mon Sep 17 00:00:00 2001 From: "NARUSE, Yui" Date: Wed, 19 Oct 2016 22:56:25 +0900 Subject: [PATCH 456/783] Avoid RangeError on integers larger than LONG_LONG (#764) * Use rb_absint_size and rb_absint_singlebit_p on Ruby 2.1 or later. They don't allocate new objects and fast. * Use rb_big_cmp on Ruby 1.9.x and 2.0.0 * Use rb_funcall(bignum, rb_intern("<=>")) on Ruby 1.8 and REE Note that this works on Ruby 2.4 with unified Integer / Fixnum / Bignum as well. --- ext/mysql2/extconf.rb | 5 +++ ext/mysql2/statement.c | 70 +++++++++++++++++++++++++++++++++-- spec/mysql2/statement_spec.rb | 20 ++++++++++ 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index b8f9c5a65..78d02d6b2 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -22,6 +22,10 @@ def add_ssl_defines(header) $CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if has_no_support end +# 2.1+ +have_func('rb_absint_size') +have_func('rb_absint_singlebit_p') + # 2.0-only have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h') @@ -30,6 +34,7 @@ def add_ssl_defines(header) have_func('rb_wait_for_single_fd') have_func('rb_hash_dup') have_func('rb_intern3') +have_func('rb_big_cmp') # borrowed from mysqlplus # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 954a6ce41..c1b67e5ea 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -4,6 +4,9 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; static VALUE sym_stream, intern_new_with_args, intern_each; static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year, intern_to_s; +#ifndef HAVE_RB_BIG_CMP +static ID id_cmp; +#endif #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ @@ -203,6 +206,50 @@ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length xfree(length_buffers); \ } +/* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */ +static int my_big2ll(VALUE bignum, LONG_LONG *ptr) +{ + unsigned LONG_LONG num; + size_t len; +#ifdef HAVE_RB_ABSINT_SIZE + int nlz_bits = 0; + len = rb_absint_size(bignum, &nlz_bits); +#else + len = RBIGNUM_LEN(bignum) * SIZEOF_BDIGITS; +#endif + if (len > sizeof(LONG_LONG)) goto overflow; + if (RBIGNUM_POSITIVE_P(bignum)) { + num = rb_big2ull(bignum); + if (num > LLONG_MAX) + goto overflow; + *ptr = num; + } + else { + if (len == 8 && +#ifdef HAVE_RB_ABSINT_SIZE + nlz_bits == 0 && +#endif +#if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P) + /* Optimized to avoid object allocation for Ruby 2.1+ + * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0` + */ + !rb_absint_singlebit_p(bignum) +#elif defined(HAVE_RB_BIG_CMP) + rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1) +#else + /* Ruby 1.8.7 and REE doesn't have rb_big_cmp */ + rb_funcall(bignum, id_cmp, 1, LL2NUM(LLONG_MIN)) == INT2FIX(-1) +#endif + ) { + goto overflow; + } + *ptr = rb_big2ll(bignum); + } + return 0; +overflow: + return 1; +} + /* call-seq: stmt.execute * * Executes the current prepared statement, returns +result+. @@ -264,9 +311,23 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { #endif break; case T_BIGNUM: - bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; - bind_buffers[i].buffer = xmalloc(sizeof(long long int)); - *(LONG_LONG*)(bind_buffers[i].buffer) = rb_big2ll(argv[i]); + { + LONG_LONG num; + if (my_big2ll(argv[i], &num) == 0) { + bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; + bind_buffers[i].buffer = xmalloc(sizeof(long long int)); + *(LONG_LONG*)(bind_buffers[i].buffer) = num; + } else { + /* The bignum was larger than we can fit in LONG_LONG, send it as a string */ + VALUE rb_val_as_string = rb_big2str(argv[i], 10); + bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL; + params_enc[i] = rb_val_as_string; +#ifdef HAVE_RUBY_ENCODING_H + params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); +#endif + set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); + } + } break; case T_FLOAT: bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE; @@ -500,4 +561,7 @@ void init_mysql2_statement() { intern_year = rb_intern("year"); intern_to_s = rb_intern("to_s"); +#ifndef HAVE_RB_BIG_CMP + id_cmp = rb_intern("<=>"); +#endif } diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 0458445ff..3d005359e 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -61,6 +61,26 @@ def stmt_count expect(rows).to eq([{ "1" => 1 }]) end + it "should handle bignum but in int64_t" do + stmt = @client.prepare('SELECT ? AS max, ? AS min') + int64_max = (1 << 63) - 1 + int64_min = -(1 << 63) + result = stmt.execute(int64_max, int64_min) + expect(result.to_a).to eq(['max' => int64_max, 'min' => int64_min]) + end + + it "should handle bignum but beyond int64_t" do + stmt = @client.prepare('SELECT ? AS max1, ? AS max2, ? AS max3, ? AS min1, ? AS min2, ? AS min3') + int64_max1 = (1 << 63) + int64_max2 = (1 << 64) - 1 + int64_max3 = 1 << 64 + int64_min1 = -(1 << 63) - 1 + int64_min2 = -(1 << 64) + 1 + int64_min3 = -0xC000000000000000 + result = stmt.execute(int64_max1, int64_max2, int64_max3, int64_min1, int64_min2, int64_min3) + expect(result.to_a).to eq(['max1' => int64_max1, 'max2' => int64_max2, 'max3' => int64_max3, 'min1' => int64_min1, 'min2' => int64_min2, 'min3' => int64_min3]) + end + it "should keep its result after other query" do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int)' From 436b8ce11f3b97d8ea8d583de3fa8f4a4822f5e1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 19 Oct 2016 18:35:54 -0700 Subject: [PATCH 457/783] Prepared Statement support for DateTime#sec_fraction (#797) --- ext/mysql2/statement.c | 13 ++++++++++--- spec/mysql2/statement_spec.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index c1b67e5ea..4482cf4c3 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -2,8 +2,8 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; -static VALUE sym_stream, intern_new_with_args, intern_each; -static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year, intern_to_s; +static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s; +static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year; #ifndef HAVE_RB_BIG_CMP static ID id_cmp; #endif @@ -354,7 +354,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { memset(&t, 0, sizeof(MYSQL_TIME)); t.neg = 0; - t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0)); + + if (CLASS_OF(argv[i]) == rb_cTime) { + t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0)); + } else if (CLASS_OF(argv[i]) == cDateTime) { + t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000; + } + t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0)); t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0)); t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0)); @@ -552,6 +558,7 @@ void init_mysql2_statement() { intern_new_with_args = rb_intern("new_with_args"); intern_each = rb_intern("each"); + intern_sec_fraction = rb_intern("sec_fraction"); intern_usec = rb_intern("usec"); intern_sec = rb_intern("sec"); intern_min = rb_intern("min"); diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 3d005359e..a96f0050a 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -130,6 +130,35 @@ def stmt_count expect(result.first.first[1]).to be_an_instance_of(Time) end + it "should prepare Date values" do + now = Date.today + statement = @client.prepare('SELECT ? AS a') + result = statement.execute(now) + expect(result.first['a'].to_s).to eql(now.strftime('%F')) + end + + it "should prepare Time values with microseconds" do + now = Time.now + statement = @client.prepare('SELECT ? AS a') + result = statement.execute(now) + if RUBY_VERSION =~ /1.8/ + expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) + else + expect(result.first['a'].strftime('%F %T.%6N %z')).to eql(now.strftime('%F %T.%6N %z')) + end + end + + it "should prepare DateTime values with microseconds" do + now = DateTime.now + statement = @client.prepare('SELECT ? AS a') + result = statement.execute(now) + if RUBY_VERSION =~ /1.8/ + expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) + else + expect(result.first['a'].strftime('%F %T.%6N %z')).to eql(now.strftime('%F %T.%6N %z')) + end + end + it "should tell us about the fields" do statement = @client.prepare 'SELECT 1 as foo, 2' statement.execute From f14023fcfee9e85e6fc1b0e568048811518f8c23 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 19 Oct 2016 18:55:17 -0700 Subject: [PATCH 458/783] Move macro REQUIRE_CONNECTED to client.c (#798) --- ext/mysql2/client.c | 6 ++++++ ext/mysql2/client.h | 6 ------ spec/mysql2/client_spec.rb | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 1e976f97a..fa5af1e9e 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -30,6 +30,12 @@ VALUE rb_hash_dup(VALUE other) { rb_raise(cMysql2Error, "MySQL client is not initialized"); \ } +#define REQUIRE_CONNECTED(wrapper) \ + REQUIRE_INITIALIZED(wrapper) \ + if (!wrapper->connected && !wrapper->reconnect_enabled) { \ + rb_raise(cMysql2Error, "MySQL client is not connected"); \ + } + #define REQUIRE_NOT_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ if (wrapper->connected) { \ diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index d10274f84..a8fdb895b 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -51,12 +51,6 @@ typedef struct { MYSQL *client; } mysql_client_wrapper; -#define REQUIRE_CONNECTED(wrapper) \ - REQUIRE_INITIALIZED(wrapper) \ - if (!wrapper->connected && !wrapper->reconnect_enabled) { \ - rb_raise(cMysql2Error, "closed MySQL connection"); \ - } - void rb_mysql_client_set_active_thread(VALUE self); #define GET_CLIENT(self) \ diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 15813bce4..5a8e9c91f 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -560,7 +560,7 @@ def run_gc context 'when a non-standard exception class is raised' do it "should close the connection when an exception is raised" do expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) - expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'closed MySQL connection') + expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'MySQL client is not connected') end it "should handle Timeouts without leaving the connection hanging if reconnect is true" do From 00156f60d9dce3f6a98af91ab44a769506626acb Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 19 Oct 2016 18:41:23 -0700 Subject: [PATCH 459/783] Travis CI Ruby 2.4.0-preview2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4c86ff305..d668ac135 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ addons: - mysql-client-core-5.5 - mysql-client-5.5 rvm: - - 2.4.0-preview1 + - 2.4.0-preview2 - 2.3.1 - 2.2 - 2.1 From db2f93d362e71261abbe0efb9048f099cc19c59a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 19 Oct 2016 19:16:54 -0700 Subject: [PATCH 460/783] Raise an exception but don't crash on non-string encodings (#799) --- ext/mysql2/client.c | 1 + spec/mysql2/client_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index fa5af1e9e..df24fb441 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1235,6 +1235,7 @@ static VALUE set_charset_name(VALUE self, VALUE value) { #endif GET_CLIENT(self); + Check_Type(value, T_STRING); charset_name = RSTRING_PTR(value); #ifdef HAVE_RUBY_ENCODING_H diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 5a8e9c91f..e6f1e17ab 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -34,6 +34,12 @@ }.to raise_error(Mysql2::Error) end + it "should raise an exception on non-string encodings" do + expect { + Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => :fake)) + }.to raise_error(TypeError) + end + it "should not raise an exception on create for a valid encoding" do expect { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) From 1363f4b279103f1805553d9a7d1d916693d6b1df Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 21 Oct 2016 12:38:33 -0700 Subject: [PATCH 461/783] Compatibility for ssl_mode with Connector/C 6.1.x and MySQL 5.7.3-5.7.10 --- ext/mysql2/client.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index df24fb441..b3fed67e6 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -53,6 +53,16 @@ VALUE rb_hash_dup(VALUE other) { #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION #endif +/* + * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10. + */ +#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE + #define SSL_MODE_DISABLED 1 + #define SSL_MODE_REQUIRED 3 + #define HAVE_CONST_SSL_MODE_DISABLED + #define HAVE_CONST_SSL_MODE_REQUIRED +#endif + /* * used to pass all arguments to mysql_real_connect while inside * rb_thread_call_without_gvl @@ -101,7 +111,7 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { int val = NUM2INT( setting ); if (version >= 50703 && version < 50711) { if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { - bool b = ( val == SSL_MODE_REQUIRED ); + my_bool b = ( val == SSL_MODE_REQUIRED ); int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); return INT2NUM(result); @@ -1509,22 +1519,18 @@ void init_mysql2_client() { rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"), LONG2NUM(CLIENT_BASIC_FLAGS)); #endif -#ifdef FULL_SSL_MODE_SUPPORT + +#if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY)); -#endif -#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE - #define SSL_MODE_DISABLED 1 - #define SSL_MODE_REQUIRED 3 - #define HAVE_CONST_SSL_MODE_DISABLED - #define HAVE_CONST_SSL_MODE_REQUIRED - +#elif defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) // MySQL 5.7.3 - 5.7.10 rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED)); #endif + #ifndef HAVE_CONST_SSL_MODE_DISABLED rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0)); #endif From 4a4e9acc425c6d3e1e9cec34f14c62570ebcb328 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 21 Oct 2016 12:47:31 -0700 Subject: [PATCH 462/783] Rescue LoadError if RuboCop is not available --- Rakefile | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index bf6ac08bd..cd986675f 100644 --- a/Rakefile +++ b/Rakefile @@ -10,11 +10,16 @@ load 'tasks/benchmarks.rake' # TODO: remove engine check when rubinius stops crashing on RuboCop # TODO: remove defined?(Encoding) when we end support for < 1.9.3 -if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && defined?(Encoding) - require 'rubocop/rake_task' - RuboCop::RakeTask.new - task :default => [:spec, :rubocop] -else +has_rubocop = if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && defined?(Encoding) + begin + require 'rubocop/rake_task' + RuboCop::RakeTask.new + task :default => [:spec, :rubocop] + rescue LoadError + end +end + +if !has_rubocop warn 'RuboCop is not available' task :default => :spec end From 07acd99d8ec102cbd475d824b0b04414b142d8b1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 21 Oct 2016 13:45:49 -0700 Subject: [PATCH 463/783] RuboCop has_rubocop --- Rakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index cd986675f..75d0fbb0a 100644 --- a/Rakefile +++ b/Rakefile @@ -15,11 +15,11 @@ has_rubocop = if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && defined?(Enco require 'rubocop/rake_task' RuboCop::RakeTask.new task :default => [:spec, :rubocop] - rescue LoadError + rescue LoadError # rubocop:disable Lint/HandleExceptions end end -if !has_rubocop +unless has_rubocop warn 'RuboCop is not available' task :default => :spec end From 68e9d1bb5a76e9cd54affb8604792dd4edf6ddca Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 21 Oct 2016 13:33:15 -0700 Subject: [PATCH 464/783] AppVeyor upgraded to MySQL 5.7 --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 7709499b7..9fa58052b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,9 +11,9 @@ install: build_script: - bundle exec rake compile test_script: - - '"C:\Program Files\MySQL\MySQL Server 5.6\bin\mysql" --version' + - '"C:\Program Files\MySQL\MySQL Server 5.7\bin\mysql" --version' - > - "C:\Program Files\MySQL\MySQL Server 5.6\bin\mysql" -u root -p"Password12!" -e " + "C:\Program Files\MySQL\MySQL Server 5.7\bin\mysql" -u root -p"Password12!" -e " CREATE DATABASE IF NOT EXISTS test; CREATE USER '%USERNAME%'@'localhost'; SET PASSWORD = PASSWORD(''); From 1df20eb4aa8666ceea664c01e76eac402332f039 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 20 Oct 2016 12:02:41 -0700 Subject: [PATCH 465/783] Travis CI updates for MySQL 5.6 and 8.0 --- .travis.yml | 58 ++++++++++++++++++++++++++++++++-------------- .travis_mysql80.sh | 12 ++++++++++ .travis_setup.sh | 5 ++++ 3 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 .travis_mysql80.sh diff --git a/.travis.yml b/.travis.yml index d668ac135..13fd0ee67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,9 @@ language: ruby bundler_args: --without benchmarks development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - - gem update --system 2.6.4 + - gem --version + - gem update --system 2.6.7 + - gem update bundler - gem --version - bash .travis_setup.sh addons: @@ -12,9 +14,9 @@ addons: - mysql2gem.example.com apt: packages: - - mysql-server-5.5 - - mysql-client-core-5.5 - - mysql-client-5.5 + - mysql-server-5.6 + - mysql-client-core-5.6 + - mysql-client-5.6 rvm: - 2.4.0-preview2 - 2.3.1 @@ -27,10 +29,26 @@ matrix: include: - rvm: 1.8.7 dist: precise + env: SPEC_OPTS="--order rand:11" + addons: + hosts: + - mysql2gem.example.com + apt: + packages: + - mysql-server-5.5 + - mysql-client-core-5.5 + - mysql-client-5.5 - rvm: ree dist: precise - - rvm: rbx-2 - dist: precise + env: SPEC_OPTS="--order rand:11" + addons: + hosts: + - mysql2gem.example.com + apt: + packages: + - mysql-server-5.5 + - mysql-client-core-5.5 + - mysql-client-5.5 - rvm: 2.0.0 env: DB=mariadb55 addons: @@ -50,28 +68,34 @@ matrix: hosts: - mysql2gem.example.com - rvm: 2.0.0 - env: DB=mysql56 + env: DB=mysql55 addons: hosts: - mysql2gem.example.com apt: packages: - - mysql-server-5.6 - - mysql-client-core-5.6 - - mysql-client-5.6 + - mysql-server-5.5 + - mysql-client-core-5.5 + - mysql-client-5.5 - rvm: 2.0.0 env: DB=mysql57 addons: hosts: - mysql2gem.example.com - # https://github.com/travis-ci/travis-ci/issues/5122 - # apt: - # packages: - # - mysql-server-5.7 - # - mysql-client-core-5.7 - # - mysql-client-5.7 - rvm: 2.0.0 - env: DB=mysql55 + env: DB=mysql80 + addons: + hosts: + - mysql2gem.example.com + - rvm: 2.0.0 + env: DB=mysql56 os: osx + addons: + hosts: + - mysql2gem.example.com + fast_finish: true allow_failures: - rvm: ruby-head + - rvm: 2.0.0 + env: DB=mysql56 + os: osx diff --git a/.travis_mysql80.sh b/.travis_mysql80.sh new file mode 100644 index 000000000..df5ebc1c2 --- /dev/null +++ b/.travis_mysql80.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -eux + +apt-get purge -qq '^mysql*' '^libmysql*' +apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5 +add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ trusty mysql-8.0' +apt-get update -qq +apt-get install -qq mysql-server libmysqlclient-dev + +# https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ +mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" diff --git a/.travis_setup.sh b/.travis_setup.sh index 5b562f192..1d17957c5 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -7,6 +7,11 @@ if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then sudo bash .travis_mysql57.sh fi +# Install MySQL 8.0 if DB=mysql80 +if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then + sudo bash .travis_mysql80.sh +fi + # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update From a3884a0e7dc494041dfd95337b93653bd1445d93 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 21 Oct 2016 15:48:13 -0700 Subject: [PATCH 466/783] README update Ruby and MySQL compatibility sections --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 291f97f4c..af50251be 100644 --- a/README.md +++ b/README.md @@ -494,13 +494,13 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x + * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x * Ruby Enterprise Edition (based on MRI 1.8.7) - * Rubinius 2.x, 3.x + * Rubinius 2.x and 3.x do work but may fail under some workloads This gem is tested with the following MySQL and MariaDB versions: - * MySQL 5.5, 5.6, 5.7 + * MySQL 5.5, 5.6, 5.7, 8.0 * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) * MariaDB 5.5, 10.0, 10.1 From df7a3704ea6310f4a7de0cfed7711568d3a7888b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 21 Oct 2016 16:59:28 -0700 Subject: [PATCH 467/783] Use RSpec expect output instead of StringIO (#747) --- spec/mysql2/client_spec.rb | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index e6f1e17ab..343a57af5 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1,6 +1,5 @@ # encoding: UTF-8 require 'spec_helper' -require 'stringio' RSpec.describe Mysql2::Client do context "using defaults file" do @@ -206,22 +205,21 @@ def run_gc if RUBY_PLATFORM =~ /mingw|mswin/ it "cannot be disabled" do - stderr, $stderr = $stderr, StringIO.new - - begin - Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) - expect($stderr.string).to include('always closed by garbage collector') - $stderr.reopen - - client = Mysql2::Client.new(DatabaseCredentials['root']) + expect do + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) + expect(client.automatic_close?).to be(true) + end.to output(/always closed by garbage collector/).to_stderr + + expect do + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true)) + expect(client.automatic_close?).to be(true) + end.to_not output(/always closed by garbage collector/).to_stderr + + expect do + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true)) client.automatic_close = false - expect($stderr.string).to include('always closed by garbage collector') - $stderr.reopen - - expect { client.automatic_close = true }.to_not change { $stderr.string } - ensure - $stderr = stderr - end + expect(client.automatic_close?).to be(true) + end.to output(/always closed by garbage collector/).to_stderr end else it "can be configured" do From cd06bc5175a3dc1f75bcece82bb304a817456e45 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 21 Oct 2016 22:24:54 -0700 Subject: [PATCH 468/783] Find non-default Homebrew installations in $HOMEBREW_ROOT Fixes #788 --- ext/mysql2/extconf.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 78d02d6b2..69075cf9a 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -52,6 +52,9 @@ def add_ssl_defines(header) /usr/local/opt/mysql5* ).map { |dir| dir << '/bin' } +# For those without HOMEBREW_ROOT in PATH +dirs << "#{ENV['HOMEBREW_ROOT']}/bin" if ENV['HOMEBREW_ROOT'] + GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}" # If the user has provided a --with-mysql-dir argument, we must respect it or fail. From 40cae130b6d183dd4a1911dbc41791b04f1930aa Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 21 Oct 2016 22:41:54 -0700 Subject: [PATCH 469/783] Bump version to 0.4.5 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index f257aca75..afd7d7915 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.4" + VERSION = "0.4.5" end From 301b14b04752fa10acd53e0785d5476cf98607e7 Mon Sep 17 00:00:00 2001 From: Ryoji Yoshioka Date: Tue, 13 Dec 2016 18:38:59 +0900 Subject: [PATCH 470/783] Run tests with latest ruby versions 2.3 and 2.4 (#812) --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 13fd0ee67..7f3fde383 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,8 @@ addons: - mysql-client-core-5.6 - mysql-client-5.6 rvm: - - 2.4.0-preview2 - - 2.3.1 + - 2.4 + - 2.3 - 2.2 - 2.1 - 2.0.0 From 9c14d3239526ee051218754c0a07a216282f11b7 Mon Sep 17 00:00:00 2001 From: Dmytro Shteflyuk Date: Sun, 1 Jan 2017 23:28:16 -0500 Subject: [PATCH 471/783] Only invalidate file descriptor if client is initialized --- ext/mysql2/client.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index b3fed67e6..e4e7d89e7 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -596,9 +596,11 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) { /* Invalidate the MySQL socket to prevent further communication. * The GC will come along later and call mysql_close to free it. */ - if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { - fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n"); - close(wrapper->client->net.fd); + if (wrapper->client) { + if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { + fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n"); + close(wrapper->client->net.fd); + } } rb_exc_raise(error); From 110284835d5088b409d146f5adeaa7525a696caa Mon Sep 17 00:00:00 2001 From: Takeshi SHINODA Date: Tue, 3 Jan 2017 16:52:44 +0900 Subject: [PATCH 472/783] Delete existing mysql data at installation MySQL 5.7. --- .travis_mysql57.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index 96183ea27..493143e94 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -3,6 +3,8 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' +rm -fr /etc/mysql +rm -fr /var/lib/mysql apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5 add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ trusty mysql-5.7' apt-get update -qq From cbe3ec074950d563b4e994cb0755c5ed661eb01a Mon Sep 17 00:00:00 2001 From: yui-knk Date: Wed, 4 Jan 2017 09:58:58 +0900 Subject: [PATCH 473/783] Delete existing mysql data at installation MySQL 8.0. This follow up 110284835d5088b409d146f5adeaa7525a696caa --- .travis_mysql80.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis_mysql80.sh b/.travis_mysql80.sh index df5ebc1c2..085ca8c75 100644 --- a/.travis_mysql80.sh +++ b/.travis_mysql80.sh @@ -3,6 +3,8 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' +rm -fr /etc/mysql +rm -fr /var/lib/mysql apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5 add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ trusty mysql-8.0' apt-get update -qq From a73907ab496c8e6f3fc2bcebf3b504f594e087f6 Mon Sep 17 00:00:00 2001 From: Takeshi SHINODA Date: Wed, 4 Jan 2017 19:23:55 +0900 Subject: [PATCH 474/783] Only MySQL 5.5 tests run with precise(12.04). --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 7f3fde383..c313c6fe6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -69,6 +69,7 @@ matrix: - mysql2gem.example.com - rvm: 2.0.0 env: DB=mysql55 + dist: precise addons: hosts: - mysql2gem.example.com From 53d8a5a0b1386a477bb6ea93dbf0e439c20d0ce3 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Wed, 26 Apr 2017 11:54:41 -0700 Subject: [PATCH 475/783] Handle being terminated while reading query result (#811) The problem is that if execution is terminated while reading the result set, the next attempt to execute a query on the connection fails with "This connection is still waiting for a result, try again once you have the result". This error persists even if reconnect is enabled. The issue stems from reading the result set using rb_thread_call_without_gvl. If the calling thread is terminated inside rb_thread_call_without_gvl, the function does not return and the caller does not get a chance to reset the active thread associated with the connection - which normally happens after the result set has been read. The solution is to disconnect and mark the connection as inactive if the thread is terminated while reading a result set. --- ext/mysql2/client.c | 26 +++++++++++++------------- spec/mysql2/client_spec.rb | 9 --------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index e4e7d89e7..62dc3438d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -649,26 +649,26 @@ static VALUE do_query(void *args) { return Qnil; } -#else -static VALUE finish_and_mark_inactive(void *args) { - VALUE self = args; - MYSQL_RES *result; +#endif +static VALUE disconnect_and_mark_inactive(VALUE self) { GET_CLIENT(self); + /* Check if execution terminated while result was still being read. */ if (!NIL_P(wrapper->active_thread)) { - /* if we got here, the result hasn't been read off the wire yet - so lets do that and then throw it away because we have no way - of getting it back up to the caller from here */ - result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); - mysql_free_result(result); - + /* Invalidate the MySQL socket to prevent further communication. */ + if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { + rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n"); + close(wrapper->client->net.fd); + } + /* Skip mysql client check performed before command execution. */ + wrapper->client->status = MYSQL_STATUS_READY; wrapper->active_thread = Qnil; + wrapper->connected = 0; } return Qnil; } -#endif void rb_mysql_client_set_active_thread(VALUE self) { VALUE thread_current = rb_thread_current(); @@ -762,13 +762,13 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0); - return rb_mysql_client_async_result(self); + return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self); } #else do_send_query(&args); /* this will just block until the result is ready */ - return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self); + return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self); #endif } diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 343a57af5..dab403646 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -538,15 +538,6 @@ def run_gc }.to raise_error(Mysql2::Error) end - it 'should be impervious to connection-corrupting timeouts in #query' do - pending('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt) - # attempt to break the connection - expect { Timeout.timeout(0.1) { @client.query('SELECT SLEEP(0.2)') } }.to raise_error(Timeout::Error) - - # expect the connection to not be broken - expect { @client.query('SELECT 1') }.to_not raise_error - end - it 'should be impervious to connection-corrupting timeouts in #execute' do # the statement handle gets corrupted and will segfault the tests if interrupted, # so we can't even use pending on this test, really have to skip it on older Rubies. From 017bf6d7965e3940e51fc48ab448b6cf9ad2a6dc Mon Sep 17 00:00:00 2001 From: Alan Gardner Date: Wed, 26 Apr 2017 19:55:16 +0100 Subject: [PATCH 476/783] Reminder for Mac users to install xcode-select (#832) Without these tools (or an equivalent) the build will fail. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index af50251be..6b51be431 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,9 @@ You may use MacPorts, Homebrew, or a native MySQL installer package. The most common paths will be automatically searched. If you want to select a specific MySQL directory, use the `--with-mysql-dir` or `--with-mysql-config` options above. +If you have not done so already, you will need to install the XCode select tools by running +`xcode-select --install`. + ### Windows Make sure that you have Ruby and the DevKit compilers installed. We recommend the [Ruby Installer](http://rubyinstaller.org) distribution. From 18ec94af06f949e73edfd7834d296582bf3b0bf2 Mon Sep 17 00:00:00 2001 From: Jonas Helgemo Date: Wed, 26 Apr 2017 20:57:45 +0200 Subject: [PATCH 477/783] Better error messages when the connection options have errors (#831) Why: * To avoid cryptic errors and to give better error messages back to the user. Say the user uses this connection string: DATABASE_URL="mysql2://:@:/?encoding=utf8&pool=1&connect_timeout=2read_timeout=5&write_timeout=5" With the current code, the `connect_timeout=2` and `read_timeout` will be ignored (as the connection string is missing a `&`). A better approach would be to evaluate the input as an integer and output an error message if it isn't one. For more info see the discussion in the PR below. This change addresses the need by: * https://github.com/brianmario/mysql2/pull/791 --- lib/mysql2/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 8c50e33e8..935aa27af 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -37,7 +37,7 @@ def initialize(opts = {}) when :reconnect, :local_infile, :secure_auth, :automatic_close send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation when :connect_timeout, :read_timeout, :write_timeout - send(:"#{key}=", opts[key].to_i) unless opts[key].nil? + send(:"#{key}=", Integer(opts[key])) unless opts[key].nil? else send(:"#{key}=", opts[key]) end From 5889d04cba68531edd1ccf1479f2aab6ae1244fb Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 26 Apr 2017 11:58:52 -0700 Subject: [PATCH 478/783] Use `bool` instead of `my_bool` which has been removed since MySQL 8.0.1 (#840) Refer "Changes in MySQL 8.0.1 (2017-04-10, Development Milestone)" https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-1.html > Incompatible Change: The mysql.h header file now requires a C++ or C99 compiler to compile. > The my_bool type is no longer used in MySQL source code. > Any third-party code that used this type to represent C boolean variables > should use the bool or int C type instead. (Bug #25597667) --- ext/mysql2/client.c | 6 +++--- ext/mysql2/result.c | 4 ++-- ext/mysql2/result.h | 5 +++-- ext/mysql2/statement.c | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 62dc3438d..0d8db4a7d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -111,7 +111,7 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { int val = NUM2INT( setting ); if (version >= 50703 && version < 50711) { if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { - my_bool b = ( val == SSL_MODE_REQUIRED ); + bool b = ( val == SSL_MODE_REQUIRED ); int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); return INT2NUM(result); @@ -504,7 +504,7 @@ static VALUE do_send_query(void *args) { */ static void *nogvl_read_query_result(void *ptr) { MYSQL * client = ptr; - my_bool res = mysql_read_query_result(client); + bool res = mysql_read_query_result(client); return (void *)(res == 0 ? Qtrue : Qfalse); } @@ -827,7 +827,7 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { const void *retval = NULL; unsigned int intval = 0; const char * charval = NULL; - my_bool boolval; + bool boolval; GET_CLIENT(self); diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 9841f7409..c227c41fa 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -262,8 +262,8 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields if (wrapper->result_buffers != NULL) return; wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND)); - wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); - wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); + wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(bool)); + wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(bool)); wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long)); for (i = 0; i < wrapper->numberOfFields; i++) { diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 0c25b24b6..525a2188b 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -1,5 +1,6 @@ #ifndef MYSQL2_RESULT_H #define MYSQL2_RESULT_H +#include void init_mysql2_result(void); VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement); @@ -21,8 +22,8 @@ typedef struct { mysql_client_wrapper *client_wrapper; /* statement result bind buffers */ MYSQL_BIND *result_buffers; - my_bool *is_null; - my_bool *error; + bool *is_null; + bool *error; unsigned long *length; } mysql2_result_wrapper; diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 4482cf4c3..346648132 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -124,7 +124,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { // set STMT_ATTR_UPDATE_MAX_LENGTH attr { - my_bool truth = 1; + bool truth = 1; if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) { rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH"); } From 6263568ec02d380aef3e76c8c4875e387e3dd50d Mon Sep 17 00:00:00 2001 From: Yuichiro Kaneko Date: Thu, 27 Apr 2017 04:45:19 +0900 Subject: [PATCH 479/783] Change default CI Ruby version to 2.4 (#820) --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index c313c6fe6..f1f6c59b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,25 +49,25 @@ matrix: - mysql-server-5.5 - mysql-client-core-5.5 - mysql-client-5.5 - - rvm: 2.0.0 + - rvm: 2.4 env: DB=mariadb55 addons: mariadb: 5.5 hosts: - mysql2gem.example.com - - rvm: 2.0.0 + - rvm: 2.4 env: DB=mariadb10.0 addons: mariadb: 10.0 hosts: - mysql2gem.example.com - - rvm: 2.0.0 + - rvm: 2.4 env: DB=mariadb10.1 addons: mariadb: 10.1 hosts: - mysql2gem.example.com - - rvm: 2.0.0 + - rvm: 2.4 env: DB=mysql55 dist: precise addons: @@ -78,17 +78,17 @@ matrix: - mysql-server-5.5 - mysql-client-core-5.5 - mysql-client-5.5 - - rvm: 2.0.0 + - rvm: 2.4 env: DB=mysql57 addons: hosts: - mysql2gem.example.com - - rvm: 2.0.0 + - rvm: 2.4 env: DB=mysql80 addons: hosts: - mysql2gem.example.com - - rvm: 2.0.0 + - rvm: 2.4 env: DB=mysql56 os: osx addons: @@ -97,6 +97,6 @@ matrix: fast_finish: true allow_failures: - rvm: ruby-head - - rvm: 2.0.0 + - rvm: 2.4 env: DB=mysql56 os: osx From ffca2e7cb0290261bc8ff68bd2d9dd75ff754741 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 2 May 2017 05:24:46 -0700 Subject: [PATCH 480/783] Travis CI matrix add MariaDB 10.2, change Ruby versions on Precise and Mac OS X --- .travis.yml | 27 ++++++++++++++++----------- .travis_setup.sh | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index f1f6c59b1..a5f4ae753 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ bundler_args: --without benchmarks development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - gem --version - - gem update --system 2.6.7 + - gem update --system 2.6.12 - gem update bundler - gem --version - bash .travis_setup.sh @@ -49,12 +49,6 @@ matrix: - mysql-server-5.5 - mysql-client-core-5.5 - mysql-client-5.5 - - rvm: 2.4 - env: DB=mariadb55 - addons: - mariadb: 5.5 - hosts: - - mysql2gem.example.com - rvm: 2.4 env: DB=mariadb10.0 addons: @@ -68,6 +62,12 @@ matrix: hosts: - mysql2gem.example.com - rvm: 2.4 + env: DB=mariadb10.2 + addons: + mariadb: 10.2 + hosts: + - mysql2gem.example.com + - rvm: 2.0.0 env: DB=mysql55 dist: precise addons: @@ -88,15 +88,20 @@ matrix: addons: hosts: - mysql2gem.example.com - - rvm: 2.4 + - os: osx + rvm: system env: DB=mysql56 - os: osx addons: hosts: - mysql2gem.example.com + before_install: + # Use the system RubyGem with the system Ruby + - gem --version + - sudo gem install bundler + - bash .travis_setup.sh fast_finish: true allow_failures: - rvm: ruby-head - - rvm: 2.4 + - os: osx + rvm: system env: DB=mysql56 - os: osx diff --git a/.travis_setup.sh b/.travis_setup.sh index 1d17957c5..504a5f43a 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -15,7 +15,7 @@ fi # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update - brew install "$DB" + brew install "$DB" mariadb-connector-c $(brew --prefix "$DB")/bin/mysql.server start fi From 81637fb95c3e938a13f1a2e7cbbbddfff5200fa5 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 3 May 2017 22:39:31 -0700 Subject: [PATCH 481/783] Fix for Windows following up from #811 --- ext/mysql2/client.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 0d8db4a7d..2f20512b9 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -657,10 +657,14 @@ static VALUE disconnect_and_mark_inactive(VALUE self) { /* Check if execution terminated while result was still being read. */ if (!NIL_P(wrapper->active_thread)) { /* Invalidate the MySQL socket to prevent further communication. */ +#ifndef _WIN32 if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n"); close(wrapper->client->net.fd); } +#else + close(wrapper->client->net.fd); +#endif /* Skip mysql client check performed before command execution. */ wrapper->client->status = MYSQL_STATUS_READY; wrapper->active_thread = Qnil; From f271bdcb8930cdbb5f60123764683571ad5ca3e7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 3 May 2017 22:44:42 -0700 Subject: [PATCH 482/783] Add Ruby 2.3 to AppVeyor matrix --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 9fa58052b..6b5068085 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,6 +25,8 @@ test_script: # - find tmp -name "*.log" -exec cat {}; environment: matrix: + - ruby_version: "23-x64" + - ruby_version: "23" - ruby_version: "22-x64" - ruby_version: "22" - ruby_version: "21-x64" From db1ac5ce115922edd81af08a7ff1a45a381dc112 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 3 May 2017 22:50:19 -0700 Subject: [PATCH 483/783] Update MySQL Connector/C version for Windows --- tasks/vendor_mysql.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index 611b98378..ee067081f 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -1,7 +1,7 @@ require 'rake/clean' require 'rake/extensioncompiler' -CONNECTOR_VERSION = "6.1.6" # NOTE: Track the upstream version from time to time +CONNECTOR_VERSION = "6.1.9" # NOTE: Track the upstream version from time to time def vendor_mysql_platform(platform = nil) platform ||= RUBY_PLATFORM From 18673e8d8663a56213a980212e1092c2220faa92 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 3 May 2017 23:04:11 -0700 Subject: [PATCH 484/783] Bump version to 0.4.6 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index afd7d7915..e60bb3323 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.5" + VERSION = "0.4.6" end From ab32c1d329c3f17ef3034f84491191501c3d707c Mon Sep 17 00:00:00 2001 From: denisenkom Date: Mon, 15 May 2017 14:59:49 -0400 Subject: [PATCH 485/783] Adding support for MYSQL_ENABLE_CLEARTEXT_PLUGIN flag (#845) This will allow usage of AWS IAM user login for MySQL database. --- ext/mysql2/client.c | 10 ++++++++++ lib/mysql2/client.rb | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 2f20512b9..9aa68cd01 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -886,6 +886,11 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = charval; break; + case MYSQL_ENABLE_CLEARTEXT_PLUGIN: + boolval = (value == Qfalse ? 0 : 1); + retval = &boolval; + break; + default: return Qfalse; } @@ -1303,6 +1308,10 @@ static VALUE set_init_command(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_INIT_COMMAND, value); } +static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) { + return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value); +} + static VALUE initialize_ext(VALUE self) { GET_CLIENT(self); @@ -1398,6 +1407,7 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1); + rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); rb_define_private_method(cMysql2Client, "connect", rb_connect, 7); rb_define_private_method(cMysql2Client, "_query", rb_query, 2); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 935aa27af..f6238b197 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -31,10 +31,10 @@ def initialize(opts = {}) opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) # TODO: stricter validation rather than silent massaging - [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close].each do |key| + [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close, :enable_cleartext_plugin].each do |key| next unless opts.key?(key) case key - when :reconnect, :local_infile, :secure_auth, :automatic_close + when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation when :connect_timeout, :read_timeout, :write_timeout send(:"#{key}=", Integer(opts[key])) unless opts[key].nil? From 0e0144c459014f28997ae92f2873370046dd2353 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Wed, 31 May 2017 02:38:16 -0400 Subject: [PATCH 486/783] Avoid invalidating re-assigned socket fd by fixing connected check (#847) Previously the gem kept a field in the client's struct to keep track of whether or not it is connected, but this could easily get out of sync with the MYSQL struct. When invalidating the socket fd, it was getting the fd from the MYSQL struct, which doesn't get it cleared in any way when libmysqlclient closes the socket. This means that invalidating the socket fd can easily result in an unrelated fd being invalidated (e.g. another connection) --- ext/mysql2/client.c | 36 +++++++++++++++++++----------------- ext/mysql2/client.h | 1 - spec/mysql2/client_spec.rb | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 9aa68cd01..6305be6d3 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -30,15 +30,17 @@ VALUE rb_hash_dup(VALUE other) { rb_raise(cMysql2Error, "MySQL client is not initialized"); \ } +#define CONNECTED(wrapper) (wrapper->client && wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1) + #define REQUIRE_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ - if (!wrapper->connected && !wrapper->reconnect_enabled) { \ + if (!CONNECTED(wrapper) && !wrapper->reconnect_enabled) { \ rb_raise(cMysql2Error, "MySQL client is not connected"); \ } #define REQUIRE_NOT_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ - if (wrapper->connected) { \ + if (CONNECTED(wrapper)) { \ rb_raise(cMysql2Error, "MySQL connection is already open"); \ } @@ -268,7 +270,6 @@ static void *nogvl_close(void *ptr) { mysql_close(wrapper->client); xfree(wrapper->client); wrapper->client = NULL; - wrapper->connected = 0; wrapper->active_thread = Qnil; } @@ -287,7 +288,7 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper) if (wrapper->refcount == 0) { #ifndef _WIN32 - if (wrapper->connected && !wrapper->automatic_close) { + if (CONNECTED(wrapper) && !wrapper->automatic_close) { /* The client is being garbage collected while connected. Prevent * mysql_close() from sending a mysql-QUIT or from calling shutdown() on * the socket by invalidating it. invalidate_fd() will drop this @@ -299,6 +300,7 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper) fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n"); close(wrapper->client->net.fd); } + wrapper->client->net.fd = -1; } #endif @@ -317,7 +319,6 @@ static VALUE allocate(VALUE klass) { wrapper->server_version = 0; wrapper->reconnect_enabled = 0; wrapper->connect_timeout = 0; - wrapper->connected = 0; /* means that a database connection is open */ wrapper->initialized = 0; /* means that that the wrapper is initialized */ wrapper->refcount = 1; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); @@ -450,7 +451,6 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po } wrapper->server_version = mysql_get_server_version(wrapper->client); - wrapper->connected = 1; return self; } @@ -465,7 +465,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po static VALUE rb_mysql_client_close(VALUE self) { GET_CLIENT(self); - if (wrapper->connected) { + if (wrapper->client) { rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0); } @@ -591,16 +591,16 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) { GET_CLIENT(self); wrapper->active_thread = Qnil; - wrapper->connected = 0; /* Invalidate the MySQL socket to prevent further communication. * The GC will come along later and call mysql_close to free it. */ - if (wrapper->client) { + if (wrapper->client && CONNECTED(wrapper)) { if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n"); close(wrapper->client->net.fd); } + wrapper->client->net.fd = -1; } rb_exc_raise(error); @@ -656,19 +656,21 @@ static VALUE disconnect_and_mark_inactive(VALUE self) { /* Check if execution terminated while result was still being read. */ if (!NIL_P(wrapper->active_thread)) { - /* Invalidate the MySQL socket to prevent further communication. */ + if (CONNECTED(wrapper)) { + /* Invalidate the MySQL socket to prevent further communication. */ #ifndef _WIN32 - if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { - rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n"); - close(wrapper->client->net.fd); - } + if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { + rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n"); + close(wrapper->client->net.fd); + } #else - close(wrapper->client->net.fd); + close(wrapper->client->net.fd); #endif + wrapper->client->net.fd = -1; + } /* Skip mysql client check performed before command execution. */ wrapper->client->status = MYSQL_STATUS_READY; wrapper->active_thread = Qnil; - wrapper->connected = 0; } return Qnil; @@ -1080,7 +1082,7 @@ static void *nogvl_ping(void *ptr) { static VALUE rb_mysql_client_ping(VALUE self) { GET_CLIENT(self); - if (!wrapper->connected) { + if (!CONNECTED(wrapper)) { return Qfalse; } else { return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0); diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index a8fdb895b..9cad2bae5 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -44,7 +44,6 @@ typedef struct { unsigned int connect_timeout; int active; int automatic_close; - int connected; int initialized; int refcount; int freed; diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index dab403646..0e46e6397 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -474,6 +474,20 @@ def run_gc }.to raise_error(Mysql2::Error) end + it "should detect closed connection on query read error" do + connection_id = @client.thread_id + Thread.new do + sleep(0.1) + Mysql2::Client.new(DatabaseCredentials['root']).tap do |supervisor| + supervisor.query("KILL #{connection_id}") + end.close + end + expect { + @client.query("SELECT SLEEP(1)") + }.to raise_error(Mysql2::Error, /Lost connection to MySQL server/) + expect { @client.socket }.to raise_error(Mysql2::Error, 'MySQL client is not connected') + end + if RUBY_PLATFORM !~ /mingw|mswin/ it "should not allow another query to be sent without fetching a result first" do @client.query("SELECT 1", :async => true) From 71097b1ec18d6067ee391ebc412a59f9e6089ae3 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Wed, 31 May 2017 02:42:03 -0400 Subject: [PATCH 487/783] Wait for close to be processed by server in test for Threads_connected (#850) --- spec/mysql2/client_spec.rb | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 0e46e6397..2154cb135 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -172,10 +172,21 @@ def run_gc it "should terminate connections when calling close" do expect { - Mysql2::Client.new(DatabaseCredentials['root']).close + client = Mysql2::Client.new(DatabaseCredentials['root']) + connection_id = client.thread_id + client.close + + # mysql_close sends a quit command without waiting for a response + # so give the server some time to handle the detect the closed connection + closed = false + 10.times do + closed = @client.query("SHOW PROCESSLIST").none? { |row| row['Id'] == connection_id } + break if closed + sleep(0.1) + end + expect(closed).to eq(true) }.to_not change { - @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + - @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a + @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a } end From 876a8a56f30820a6df8422f2809bdf22bd2f7dc2 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Wed, 31 May 2017 02:48:15 -0400 Subject: [PATCH 488/783] Fix flaky test that used Process.wait without specifying a pid (#849) --- spec/mysql2/client_spec.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 2154cb135..0135a7022 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -258,17 +258,13 @@ def run_gc client = Mysql2::Client.new(DatabaseCredentials['root']) client.automatic_close = false - # this empty `fork` call fixes this tests on RBX; without it, the next - # `fork` call hangs forever. WTF? - fork {} - - fork do + child = fork do client.query('SELECT 1') client = nil run_gc end - Process.wait + Process.wait(child) # this will throw an error if the underlying socket was shutdown by the # child's GC From a3d8e4b27516cbcd04936c8eb68b1234aa069b59 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Wed, 31 May 2017 04:27:19 -0400 Subject: [PATCH 489/783] Add a Mysql2::Client#closed? method (#796) --- ext/mysql2/client.c | 11 +++++++++++ spec/mysql2/client_spec.rb | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 6305be6d3..9184d534b 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -472,6 +472,16 @@ static VALUE rb_mysql_client_close(VALUE self) { return Qnil; } +/* call-seq: + * client.closed? + * + * @return [Boolean] + */ +static VALUE rb_mysql_client_closed(VALUE self) { + GET_CLIENT(self); + return CONNECTED(wrapper) ? Qfalse : Qtrue; +} + /* * mysql_send_query is unlikely to block since most queries are small * enough to fit in a socket buffer, but sometimes large UPDATE and @@ -1374,6 +1384,7 @@ void init_mysql2_client() { rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0); rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0); + rb_define_method(cMysql2Client, "closed?", rb_mysql_client_closed, 0); rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0); rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1); rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0); diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 0135a7022..c07a4d4f4 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -295,6 +295,17 @@ def run_gc }.to raise_error(Mysql2::Error) end + context "#closed?" do + it "should return false when connected" do + expect(@client.closed?).to eql(false) + end + + it "should return true after close" do + @client.close + expect(@client.closed?).to eql(true) + end + end + it "should respond to #query" do expect(@client).to respond_to(:query) end From 7879f1ee4f976e8b31a4378925f3b7621eb1b431 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Thu, 1 Jun 2017 02:35:20 -0400 Subject: [PATCH 490/783] Fix use after free of client field when closing with reconnect option (#846) * Fix use after free of client field when closing with reconnect option * Delay freeing the MYSQL struct until garbage collection * Add a closed flag to avoid calling mysql_close more than once Although it looks safe to call it more than once, it looks like we might have had problems in the past with making this assumption based on commit e7924df06ace8937b3d4f42728f92d1bf4032990. This commit also removes a freed field which is no longer used --- ext/mysql2/client.c | 12 +++++++----- ext/mysql2/client.h | 2 +- spec/mysql2/client_spec.rb | 8 ++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 9184d534b..d591afd30 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -30,7 +30,7 @@ VALUE rb_hash_dup(VALUE other) { rb_raise(cMysql2Error, "MySQL client is not initialized"); \ } -#define CONNECTED(wrapper) (wrapper->client && wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1) +#define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1) #define REQUIRE_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ @@ -266,10 +266,10 @@ static VALUE invalidate_fd(int clientfd) static void *nogvl_close(void *ptr) { mysql_client_wrapper *wrapper = ptr; - if (wrapper->client) { + if (!wrapper->closed) { mysql_close(wrapper->client); - xfree(wrapper->client); - wrapper->client = NULL; + wrapper->closed = 1; + wrapper->reconnect_enabled = 0; wrapper->active_thread = Qnil; } @@ -305,6 +305,7 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper) #endif nogvl_close(wrapper); + xfree(wrapper->client); xfree(wrapper); } } @@ -321,6 +322,7 @@ static VALUE allocate(VALUE klass) { wrapper->connect_timeout = 0; wrapper->initialized = 0; /* means that that the wrapper is initialized */ wrapper->refcount = 1; + wrapper->closed = 0; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); return obj; @@ -605,7 +607,7 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) { /* Invalidate the MySQL socket to prevent further communication. * The GC will come along later and call mysql_close to free it. */ - if (wrapper->client && CONNECTED(wrapper)) { + if (CONNECTED(wrapper)) { if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n"); close(wrapper->client->net.fd); diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 9cad2bae5..c00fb863f 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -46,7 +46,7 @@ typedef struct { int automatic_close; int initialized; int refcount; - int freed; + int closed; MYSQL *client; } mysql_client_wrapper; diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index c07a4d4f4..2e57fe57f 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -306,6 +306,14 @@ def run_gc end end + it "should not try to query closed mysql connection" do + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) + expect(client.close).to be_nil + expect { + client.query "SELECT 1" + }.to raise_error(Mysql2::Error) + end + it "should respond to #query" do expect(@client).to respond_to(:query) end From ee0781061559e62a50e977eca220b048d34f58dc Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Thu, 1 Jun 2017 02:35:32 -0400 Subject: [PATCH 491/783] Fix leaky test that caused a re-assigned socket to be closed on GC. (#853) --- spec/mysql2/client_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 2e57fe57f..312ef09fa 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -646,10 +646,11 @@ def run_gc end it "evented async queries should be supported" do + skip("ruby 1.8 doesn't support IO.for_fd options") if RUBY_VERSION.start_with?("1.8.") # should immediately return nil expect(@client.query("SELECT sleep(0.1)", :async => true)).to eql(nil) - io_wrapper = IO.for_fd(@client.socket) + io_wrapper = IO.for_fd(@client.socket, :autoclose => false) loops = 0 loop do if IO.select([io_wrapper], nil, nil, 0.05) From c180594fb80897dfed96a19d913e14036c02cbd4 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Thu, 1 Jun 2017 16:39:34 -0400 Subject: [PATCH 492/783] Close several unclosed clients in tests (#848) * Close several unclosed clients in tests * Add a new_client test helper to simplify client creation and closing --- spec/em/em_spec.rb | 1 + spec/mysql2/client_spec.rb | 125 ++++++++++++++++---------------- spec/mysql2/error_spec.rb | 8 +-- spec/mysql2/result_spec.rb | 19 ++--- spec/mysql2/statement_spec.rb | 11 ++- spec/spec_helper.rb | 132 +++++++++++++++++++--------------- 6 files changed, 151 insertions(+), 145 deletions(-) diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index fbb54d655..d676e2f93 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -70,6 +70,7 @@ let(:client) { Mysql2::EM::Client.new DatabaseCredentials['root'] } let(:error) { StandardError.new('some error') } before { allow(client).to receive(:async_result).and_raise(error) } + after { client.close } it "should swallow exceptions raised in by the client" do errors = [] diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 312ef09fa..856cc83c5 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -7,14 +7,13 @@ it "should not raise an exception for valid defaults group" do expect { - opts = DatabaseCredentials['root'].merge(:default_file => cnf_file, :default_group => "test") - @client = Mysql2::Client.new(opts) + new_client(:default_file => cnf_file, :default_group => "test") }.not_to raise_error end it "should not raise an exception without default group" do expect { - @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:default_file => cnf_file)) + new_client(:default_file => cnf_file) }.not_to raise_error end end @@ -23,29 +22,29 @@ expect { # The odd local host IP address forces the mysql client library to # use a TCP socket rather than a domain socket. - Mysql2::Client.new DatabaseCredentials['root'].merge('host' => '127.0.0.2', 'port' => 999999) + new_client('host' => '127.0.0.2', 'port' => 999999) }.to raise_error(Mysql2::Error) end it "should raise an exception on create for invalid encodings" do expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake")) + new_client(:encoding => "fake") }.to raise_error(Mysql2::Error) end it "should raise an exception on non-string encodings" do expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => :fake)) + new_client(:encoding => :fake) }.to raise_error(TypeError) end it "should not raise an exception on create for a valid encoding" do expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) + new_client(:encoding => "utf8") }.not_to raise_error expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) + new_client(DatabaseCredentials['root'].merge(:encoding => "big5")) }.not_to raise_error end @@ -88,7 +87,7 @@ def connect(*args) it "should execute init command" do options = DatabaseCredentials['root'].dup options[:init_command] = "SET @something = 'setting_value';" - client = Mysql2::Client.new(options) + client = new_client(options) result = client.query("SELECT @something;") expect(result.first['@something']).to eq('setting_value') end @@ -97,7 +96,7 @@ def connect(*args) options = DatabaseCredentials['root'].dup options[:init_command] = "SET @something = 'setting_value';" options[:reconnect] = true - client = Mysql2::Client.new(options) + client = new_client(options) result = client.query("SELECT @something;") expect(result.first['@something']).to eq('setting_value') @@ -138,15 +137,13 @@ def connect(*args) ssl_client = nil expect { # rubocop:disable Style/TrailingComma - ssl_client = Mysql2::Client.new( - DatabaseCredentials['root'].merge( - 'host' => 'mysql2gem.example.com', # must match the certificates - :sslkey => '/etc/mysql/client-key.pem', - :sslcert => '/etc/mysql/client-cert.pem', - :sslca => '/etc/mysql/ca-cert.pem', - :sslcipher => 'DHE-RSA-AES256-SHA', - :sslverify => true - ) + ssl_client = new_client( + 'host' => 'mysql2gem.example.com', # must match the certificates + :sslkey => '/etc/mysql/client-key.pem', + :sslcert => '/etc/mysql/client-cert.pem', + :sslca => '/etc/mysql/ca-cert.pem', + :sslcipher => 'DHE-RSA-AES256-SHA', + :sslverify => true ) # rubocop:enable Style/TrailingComma }.not_to raise_error @@ -157,8 +154,6 @@ def connect(*args) expect(ssl_client.ssl_cipher).not_to be_empty expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher) - - ssl_client.close end def run_gc @@ -210,36 +205,35 @@ def run_gc context "#automatic_close" do it "is enabled by default" do - client = Mysql2::Client.new(DatabaseCredentials['root']) - expect(client.automatic_close?).to be(true) + expect(new_client.automatic_close?).to be(true) end if RUBY_PLATFORM =~ /mingw|mswin/ it "cannot be disabled" do expect do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) + client = new_client(:automatic_close => false) expect(client.automatic_close?).to be(true) end.to output(/always closed by garbage collector/).to_stderr expect do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true)) + client = new_client(:automatic_close => true) expect(client.automatic_close?).to be(true) end.to_not output(/always closed by garbage collector/).to_stderr expect do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true)) + client = new_client(:automatic_close => true) client.automatic_close = false expect(client.automatic_close?).to be(true) end.to output(/always closed by garbage collector/).to_stderr end else it "can be configured" do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) + client = new_client(:automatic_close => false) expect(client.automatic_close?).to be(false) end it "can be assigned" do - client = Mysql2::Client.new(DatabaseCredentials['root']) + client = new_client client.automatic_close = false expect(client.automatic_close?).to be(false) @@ -269,6 +263,7 @@ def run_gc # this will throw an error if the underlying socket was shutdown by the # child's GC expect { client.query('SELECT 1') }.to_not raise_exception + client.close end end end @@ -278,7 +273,7 @@ def run_gc @client.query "CREATE DATABASE IF NOT EXISTS `#{database}`" expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge('database' => database)) + new_client('database' => database) }.not_to raise_error @client.query "DROP DATABASE IF EXISTS `#{database}`" @@ -307,7 +302,7 @@ def run_gc end it "should not try to query closed mysql connection" do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) + client = new_client(:reconnect => true) expect(client.close).to be_nil expect { client.query "SELECT 1" @@ -370,67 +365,72 @@ def run_gc context ":local_infile" do before(:all) do - @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true) - local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'" - local_enabled = local.any? { |x| x['Value'] == 'ON' } - pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled - - @client_i.query %[ - CREATE TABLE IF NOT EXISTS infileTest ( - id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY, - foo VARCHAR(10), - bar MEDIUMTEXT - ) - ] + new_client(:local_infile => true) do |client| + local = client.query "SHOW VARIABLES LIKE 'local_infile'" + local_enabled = local.any? { |x| x['Value'] == 'ON' } + pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled + + client.query %[ + CREATE TABLE IF NOT EXISTS infileTest ( + id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + foo VARCHAR(10), + bar MEDIUMTEXT + ) + ] + end end after(:all) do - @client_i.query "DROP TABLE infileTest" + new_client do |client| + client.query "DROP TABLE infileTest" + end end it "should raise an error when local_infile is disabled" do - client = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => false) + client = new_client(:local_infile => false) expect { client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" }.to raise_error(Mysql2::Error, /command is not allowed/) end it "should raise an error when a non-existent file is loaded" do + client = new_client(:local_infile => true) expect { - @client_i.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest" + client.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest" }.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here') end it "should LOAD DATA LOCAL INFILE" do - @client_i.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" - info = @client_i.query_info + client = new_client(:local_infile => true) + client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" + info = client.query_info expect(info).to eql(:records => 1, :deleted => 0, :skipped => 0, :warnings => 0) - result = @client_i.query "SELECT * FROM infileTest" + result = client.query "SELECT * FROM infileTest" expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World') end end it "should expect connect_timeout to be a positive integer" do expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:connect_timeout => -1)) + new_client(:connect_timeout => -1) }.to raise_error(Mysql2::Error) end it "should expect read_timeout to be a positive integer" do expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => -1)) + new_client(:read_timeout => -1) }.to raise_error(Mysql2::Error) end it "should expect write_timeout to be a positive integer" do expect { - Mysql2::Client.new(DatabaseCredentials['root'].merge(:write_timeout => -1)) + new_client(:write_timeout => -1) }.to raise_error(Mysql2::Error) end it "should allow nil read_timeout" do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => nil)) + client = new_client(:read_timeout => nil) expect(client.read_timeout).to be_nil end @@ -530,7 +530,7 @@ def run_gc end it "should timeout if we wait longer than :read_timeout" do - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 0)) + client = new_client(:read_timeout => 0) expect { client.query('SELECT SLEEP(0.1)') }.to raise_error(Mysql2::Error) @@ -603,7 +603,7 @@ def run_gc pending('libmysqlclient 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end - client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) + client = new_client(:reconnect => true) expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { client.query('SELECT 1') }.to_not raise_error @@ -614,7 +614,7 @@ def run_gc pending('libmysqlclient 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end - client = Mysql2::Client.new(DatabaseCredentials['root']) + client = new_client expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { client.query('SELECT 1') }.to raise_error(Mysql2::Error) @@ -632,8 +632,9 @@ def run_gc # Note that each thread opens its own database connection threads = 5.times.map do Thread.new do - client = Mysql2::Client.new(DatabaseCredentials.fetch('http://github.com/root')) - client.query("SELECT SLEEP(#{sleep_time})") + new_client do |client| + client.query("SELECT SLEEP(#{sleep_time})") + end Thread.current.object_id end end @@ -670,7 +671,7 @@ def run_gc context "Multiple results sets" do before(:each) do - @multi_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:flags => Mysql2::Client::MULTI_STATEMENTS)) + @multi_client = new_client(:flags => Mysql2::Client::MULTI_STATEMENTS) end it "should raise an exception when one of multiple statements fails" do @@ -827,7 +828,7 @@ def run_gc context 'when mysql encoding is not utf8' do before { pending('Encoding is undefined') unless defined?(Encoding) } - let(:client) { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ujis")) } + let(:client) { new_client(:encoding => "ujis") } it 'should return a internal encoding string if Encoding.default_internal is set' do with_internal_encoding Encoding::UTF_8 do @@ -896,7 +897,7 @@ def run_gc with_internal_encoding nil do expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8) - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + client2 = new_client(:encoding => 'ascii') expect(client2.server_info[:version].encoding).to eql(Encoding::ASCII) end end @@ -914,11 +915,11 @@ def run_gc it "should raise a Mysql2::Error exception upon connection failure" do expect { - Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42' + new_client(:host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42') }.to raise_error(Mysql2::Error) expect { - Mysql2::Client.new DatabaseCredentials['root'] + new_client(DatabaseCredentials['root']) }.not_to raise_error end diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index b70ffc867..32ba19e1c 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -3,11 +3,9 @@ require 'spec_helper' RSpec.describe Mysql2::Error do - let(:client) { Mysql2::Client.new(DatabaseCredentials['root']) } - let(:error) do begin - client.query("HAHAHA") + @client.query("HAHAHA") rescue Mysql2::Error => e error = e end @@ -28,7 +26,7 @@ let(:valid_utf8) { '造字' } let(:error) do begin - client.query(valid_utf8) + @client.query(valid_utf8) rescue Mysql2::Error => e e end @@ -37,7 +35,7 @@ let(:invalid_utf8) { ["e5c67d1f"].pack('H*').force_encoding(Encoding::UTF_8) } let(:bad_err) do begin - client.query(invalid_utf8) + @client.query(invalid_utf8) rescue Mysql2::Error => e e end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index ce797baa2..e869827db 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -153,18 +153,16 @@ end it "should raise an exception if streaming ended due to a timeout" do - # Create an extra client instance, since we're going to time it out - client = Mysql2::Client.new DatabaseCredentials['root'] - client.query "CREATE TEMPORARY TABLE streamingTest (val BINARY(255)) ENGINE=MEMORY" + @client.query "CREATE TEMPORARY TABLE streamingTest (val BINARY(255)) ENGINE=MEMORY" # Insert enough records to force the result set into multiple reads # (the BINARY type is used simply because it forces full width results) 10000.times do |i| - client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')" + @client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')" end - client.query "SET net_write_timeout = 1" - res = client.query "SELECT * FROM streamingTest", :stream => true, :cache_rows => false + @client.query "SET net_write_timeout = 1" + res = @client.query "SELECT * FROM streamingTest", :stream => true, :cache_rows => false expect { res.each_with_index do |_, i| @@ -367,10 +365,9 @@ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + client2 = new_client(:encoding => 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::ASCII) - client2.close end end @@ -400,10 +397,9 @@ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::UTF_8) - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + client2 = new_client(:encoding => 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::ASCII) - client2.close end end @@ -494,10 +490,9 @@ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::UTF_8) - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + client2 = new_client(:encoding => 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::ASCII) - client2.close end end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index a96f0050a..d8fd61475 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Mysql2::Statement do before :each do - @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) + @client = new_client(:encoding => "utf8") end def stmt_count @@ -524,10 +524,9 @@ def stmt_count result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + client2 = new_client(:encoding => 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::US_ASCII) - client2.close end end @@ -557,10 +556,9 @@ def stmt_count result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::UTF_8) - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + client2 = new_client(:encoding => 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::US_ASCII) - client2.close end end @@ -651,10 +649,9 @@ def stmt_count result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::UTF_8) - client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) + client2 = new_client(:encoding => 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::US_ASCII) - client2.close end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 73c45819e..53c098afb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,72 +23,86 @@ def with_internal_encoding(encoding) $VERBOSE = old_verbose end + def new_client(option_overrides = {}) + client = Mysql2::Client.new(DatabaseCredentials['root'].merge(option_overrides)) + @clients ||= [] + @clients << client + return client unless block_given? + begin + yield client + ensure + client.close + @clients.delete(client) + end + end + config.before :each do - @client = Mysql2::Client.new DatabaseCredentials['root'] + @client = new_client end config.after :each do - @client.close + @clients.each(&:close) end config.before(:all) do - client = Mysql2::Client.new DatabaseCredentials['root'] - client.query %[ - CREATE TABLE IF NOT EXISTS mysql2_test ( - id MEDIUMINT NOT NULL AUTO_INCREMENT, - null_test VARCHAR(10), - bit_test BIT(64), - single_bit_test BIT(1), - tiny_int_test TINYINT, - bool_cast_test TINYINT(1), - small_int_test SMALLINT, - medium_int_test MEDIUMINT, - int_test INT, - big_int_test BIGINT, - float_test FLOAT(10,3), - float_zero_test FLOAT(10,3), - double_test DOUBLE(10,3), - decimal_test DECIMAL(10,3), - decimal_zero_test DECIMAL(10,3), - date_test DATE, - date_time_test DATETIME, - timestamp_test TIMESTAMP, - time_test TIME, - year_test YEAR(4), - char_test CHAR(10), - varchar_test VARCHAR(10), - binary_test BINARY(10), - varbinary_test VARBINARY(10), - tiny_blob_test TINYBLOB, - tiny_text_test TINYTEXT, - blob_test BLOB, - text_test TEXT, - medium_blob_test MEDIUMBLOB, - medium_text_test MEDIUMTEXT, - long_blob_test LONGBLOB, - long_text_test LONGTEXT, - enum_test ENUM('val1', 'val2'), - set_test SET('val1', 'val2'), - PRIMARY KEY (id) - ) - ] - client.query "DELETE FROM mysql2_test;" - client.query %[ - INSERT INTO mysql2_test ( - null_test, bit_test, single_bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test, - float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test, - year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test, - tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test, - long_blob_test, long_text_test, enum_test, set_test - ) + new_client do |client| + client.query %[ + CREATE TABLE IF NOT EXISTS mysql2_test ( + id MEDIUMINT NOT NULL AUTO_INCREMENT, + null_test VARCHAR(10), + bit_test BIT(64), + single_bit_test BIT(1), + tiny_int_test TINYINT, + bool_cast_test TINYINT(1), + small_int_test SMALLINT, + medium_int_test MEDIUMINT, + int_test INT, + big_int_test BIGINT, + float_test FLOAT(10,3), + float_zero_test FLOAT(10,3), + double_test DOUBLE(10,3), + decimal_test DECIMAL(10,3), + decimal_zero_test DECIMAL(10,3), + date_test DATE, + date_time_test DATETIME, + timestamp_test TIMESTAMP, + time_test TIME, + year_test YEAR(4), + char_test CHAR(10), + varchar_test VARCHAR(10), + binary_test BINARY(10), + varbinary_test VARBINARY(10), + tiny_blob_test TINYBLOB, + tiny_text_test TINYTEXT, + blob_test BLOB, + text_test TEXT, + medium_blob_test MEDIUMBLOB, + medium_text_test MEDIUMTEXT, + long_blob_test LONGBLOB, + long_text_test LONGTEXT, + enum_test ENUM('val1', 'val2'), + set_test SET('val1', 'val2'), + PRIMARY KEY (id) + ) + ] + client.query "DELETE FROM mysql2_test;" + client.query %[ + INSERT INTO mysql2_test ( + null_test, bit_test, single_bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test, + float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test, + year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test, + tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test, + long_blob_test, long_text_test, enum_test, set_test + ) - VALUES ( - NULL, b'101', b'1', 1, 1, 10, 10, 10, 10, - 10.3, 0, 10.3, 10.3, 0, '2010-4-4', '2010-4-4 11:44:00', '2010-4-4 11:44:00', '11:44:00', - 2009, "test", "test", "test", "test", "test", - "test", "test", "test", "test", "test", - "test", "test", 'val1', 'val1,val2' - ) - ] + VALUES ( + NULL, b'101', b'1', 1, 1, 10, 10, 10, 10, + 10.3, 0, 10.3, 10.3, 0, '2010-4-4', '2010-4-4 11:44:00', '2010-4-4 11:44:00', '11:44:00', + 2009, "test", "test", "test", "test", "test", + "test", "test", "test", "test", "test", + "test", "test", 'val1', 'val1,val2' + ) + ] + end end end From 96f29c6b1397a06ab0fa3a76bb42f529f3cb30c0 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 2 Jun 2017 05:45:35 -0700 Subject: [PATCH 493/783] Fix spec that expected Client#socket on all platforms, it's not available on Windows Updates PR #847 - 0e0144c459014f28997ae92f2873370046dd2353 --- spec/mysql2/client_spec.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 856cc83c5..ad5fe757f 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -511,7 +511,12 @@ def run_gc expect { @client.query("SELECT SLEEP(1)") }.to raise_error(Mysql2::Error, /Lost connection to MySQL server/) - expect { @client.socket }.to raise_error(Mysql2::Error, 'MySQL client is not connected') + + if RUBY_PLATFORM !~ /mingw|mswin/ + expect { + @client.socket + }.to raise_error(Mysql2::Error, 'MySQL client is not connected') + end end if RUBY_PLATFORM !~ /mingw|mswin/ @@ -749,7 +754,7 @@ def run_gc it "#socket should raise as it's not supported" do expect { @client.socket - }.to raise_error(Mysql2::Error) + }.to raise_error(Mysql2::Error, /Raw access to the mysql file descriptor isn't supported on Windows/) end end From 30f1d6ade7fcfbb605000546c1342144075309c6 Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Tue, 6 Jun 2017 13:21:50 -0400 Subject: [PATCH 494/783] Docs: Fix link in readme (#854) [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b51be431..eb2cb7bb8 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Connect to a database: ``` ruby # this takes a hash of options, almost all of which map directly # to the familiar database.yml in rails -# See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html +# See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html client = Mysql2::Client.new(:host => "localhost", :username => "root") ``` From a1c198ee4c8d4d32dfa79f207ec7d0524c5f7bcc Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Tue, 6 Jun 2017 15:48:55 -0400 Subject: [PATCH 495/783] English grammar: Fix incorrect use of word "deprecated" (#855) Sorry for the grammar nit-picking, but by the following definition, these options are **already** deprecated. > deprecated. Adjective. (computing) Obsolescent; said of a construct in a computing language considered old, and planned to be phased out, but still available for use. > https://en.wiktionary.org/wiki/deprecated --- lib/mysql2/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index f6238b197..fb18be316 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -66,7 +66,7 @@ def initialize(opts = {}) if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) } warn "============= WARNING FROM mysql2 =============" - warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future." + warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future." warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options." warn "============= END WARNING FROM mysql2 =========" end From 8ad9293795a9fce9ef2ccd650b2af62518ada476 Mon Sep 17 00:00:00 2001 From: "Scott M. Likens" Date: Wed, 21 Jun 2017 08:34:34 -0700 Subject: [PATCH 496/783] Minimal required changes to make it work for mariadb 10.2 (#857) Always include mysql_version.h Signed-off-by: Scott M. Likens --- ext/mysql2/mysql2_ext.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index f53f4b4a1..23ae0670d 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -14,11 +14,13 @@ void Init_mysql2(void); #include #include #include +#include #else #include #include #include #include +#include #endif #ifdef HAVE_RUBY_ENCODING_H From 068976fc497544de44262073d162d59a56072908 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 28 Jun 2017 05:08:23 -0400 Subject: [PATCH 497/783] Fix segfault for Mysql2::Statement#fields on non-SELECT queries Updates #860. Thanks to @naoki-k for the initial fix. --- ext/mysql2/statement.c | 15 +++++++++++++-- spec/mysql2/statement_spec.rb | 17 +++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 346648132..743389da0 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -468,6 +468,7 @@ static VALUE fields(VALUE self) { rb_encoding *default_internal_enc, *conn_enc; #endif GET_STATEMENT(self); + GET_CLIENT(stmt_wrapper->client); stmt = stmt_wrapper->stmt; #ifdef HAVE_RUBY_ENCODING_H @@ -478,12 +479,22 @@ static VALUE fields(VALUE self) { } #endif - metadata = mysql_stmt_result_metadata(stmt); + metadata = mysql_stmt_result_metadata(stmt); + if (metadata == NULL) { + if (mysql_stmt_errno(stmt) != 0) { + // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. + wrapper->active_thread = Qnil; + rb_raise_mysql2_stmt_error(stmt_wrapper); + } + // no data and no error, so query was not a SELECT + return Qnil; + } + fields = mysql_fetch_fields(metadata); field_count = mysql_stmt_field_count(stmt); field_list = rb_ary_new2((long)field_count); - for(i = 0; i < field_count; i++) { + for (i = 0; i < field_count; i++) { VALUE rb_field; rb_field = rb_str_new(fields[i].name, fields[i].name_length); diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index d8fd61475..8b4a714fd 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -326,18 +326,19 @@ def stmt_count end context "#fields" do - before(:each) do - @client.query "USE test" - @test_result = @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute - end - it "method should exist" do - expect(@test_result).to respond_to(:fields) + stmt = @client.prepare("SELECT 1") + expect(stmt).to respond_to(:fields) end it "should return an array of field names in proper order" do - result = @client.prepare("SELECT 'a', 'b', 'c'").execute - expect(result.fields).to eql(%w(a b c)) + stmt = @client.prepare("SELECT 'a', 'b', 'c'") + expect(stmt.fields).to eql(%w(a b c)) + end + + it "should return nil for statement with no result fields" do + stmt = @client.prepare("INSERT INTO mysql2_test () VALUES ()") + expect(stmt.fields).to eql(nil) end end From ab490a30fbd7d9f7e5d0e80d7f332ef59ecaa9b5 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 28 Jun 2017 11:19:20 -0400 Subject: [PATCH 498/783] Fix for name change of struct NET.vio to .pvio in MariaDB Connector/C 3.x Updates #864. Thanks to @snood1205 for the initial fix. --- ext/mysql2/client.c | 6 +++++- ext/mysql2/extconf.rb | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index d591afd30..27128cbc3 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -30,7 +30,11 @@ VALUE rb_hash_dup(VALUE other) { rb_raise(cMysql2Error, "MySQL client is not initialized"); \ } -#define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1) +#if defined(HAVE_MYSQL_NET_VIO) || defined(HAVE_ST_NET_VIO) + #define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1) +#elif defined(HAVE_MYSQL_NET_PVIO) || defined(HAVE_ST_NET_PVIO) + #define CONNECTED(wrapper) (wrapper->client->net.pvio != NULL && wrapper->client->net.fd != -1) +#endif #define REQUIRE_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 69075cf9a..4fe27562c 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -105,13 +105,16 @@ def add_ssl_defines(header) asplode 'mysql.h' end -add_ssl_defines([prefix, 'mysql.h'].compact.join('/')) - %w(errmsg.h mysqld_error.h).each do |h| header = [prefix, h].compact.join '/' asplode h unless have_header header end +mysql_h = [prefix, 'mysql.h'].compact.join('/') +add_ssl_defines(mysql_h) +have_struct_member('MYSQL', 'net.vio', mysql_h) +have_struct_member('MYSQL', 'net.pvio', mysql_h) + # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. wishlist = [ From b44cb3d40153cafdb69e4a92f41e09ee1416b5ed Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 28 Jun 2017 23:00:12 -0400 Subject: [PATCH 499/783] Fix for MariaDB 10.2 which does not define CLIENT_LONG_PASSWORD --- ext/mysql2/client.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 27128cbc3..a29be36e0 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1448,6 +1448,10 @@ void init_mysql2_client() { #ifdef CLIENT_LONG_PASSWORD rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), LONG2NUM(CLIENT_LONG_PASSWORD)); +#else + /* HACK because MariaDB 10.2 no longer defines this constant, + * but we're using it in our default connection flags. */ + rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), INT2NUM(0)); #endif #ifdef CLIENT_FOUND_ROWS From 2b4f12e4db8aec102ca495b5976133cb49217dd4 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 30 Jun 2017 00:08:08 -0400 Subject: [PATCH 500/783] Travis CI workaround for installing MariaDB 10.2 --- .travis_setup.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis_setup.sh b/.travis_setup.sh index 504a5f43a..ffe5295b5 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -12,6 +12,12 @@ if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then sudo bash .travis_mysql80.sh fi +# Install MariaDB 10.2 if DB=mariadb10.2 +# NOTE this is a workaround until Travis CI merges a fix to its mariadb addon. +if [[ -n ${DB-} && x$DB =~ ^xmariadb10.2 ]]; then + sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.2 libmariadbclient18 +fi + # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update From 014d1089048f9f56efd1cfcb3f92267a1a3e7673 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 29 Jun 2017 06:07:11 -0400 Subject: [PATCH 501/783] Whitespace --- ext/mysql2/client.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index a29be36e0..97231ff17 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -113,14 +113,13 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { return Qnil; } #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE - GET_CLIENT(self); + GET_CLIENT(self); int val = NUM2INT( setting ); if (version >= 50703 && version < 50711) { if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { bool b = ( val == SSL_MODE_REQUIRED ); int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); return INT2NUM(result); - } else { rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" ); return Qnil; @@ -128,7 +127,7 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { } #endif #ifdef FULL_SSL_MODE_SUPPORT - GET_CLIENT(self); + GET_CLIENT(self); int val = NUM2INT( setting ); if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) { From 3b2550ae0333b928cbb140db849c930ecee46fe1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 30 Jun 2017 00:52:01 -0400 Subject: [PATCH 502/783] Add README section to discuss the special meaning of localhost Resolves #858. --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index eb2cb7bb8..9d87fda43 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,21 @@ Mysql2::Client.new( ) ``` +### Connecting to localhost + +The underlying MySQL client library has a special interpretation of the "localhost" default connection host name: + + 1. Attempt to connect via local socket (as specified in your distribution's my.cnf or `default_file` config file). + 2. But if the socket is not available, look up "localhost" to find an IP address... + * The file "/etc/hosts" is generally the source of truth for "localhost", and can override its value. + * Systems with IPv4 use "127.0.0.1" + * Systems with IPv6 use "::1" + * Systems with both IPv4 and IPv6 can be configured to prefer one or the other. + 3. If an address is found for "localhost", connect to it over TCP/IP. + +You can be explicit about using either a local socket or a TCP/IP connection, +and whether to use IPv4 or IPv6, by specifying a value other than "localhost" +for the `host` or `socket` connection options. ### SSL options From 17cecc265369bb454c415491f1283d1ade0ca3cd Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 30 Jun 2017 00:54:14 -0400 Subject: [PATCH 503/783] Move the section for Multple Result Sets --- README.md | 82 +++++++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 9d87fda43..b1c8500d2 100644 --- a/README.md +++ b/README.md @@ -250,47 +250,6 @@ Mysql2::Client.new( ) ``` -### Multiple result sets - -You can also retrieve multiple result sets. For this to work you need to -connect with flags `Mysql2::Client::MULTI_STATEMENTS`. Multiple result sets can -be used with stored procedures that return more than one result set, and for -bundling several SQL statements into a single call to `client.query`. - -``` ruby -client = Mysql2::Client.new(:host => "localhost", :username => "root", :flags => Mysql2::Client::MULTI_STATEMENTS) -result = client.query('CALL sp_customer_list( 25, 10 )') -# result now contains the first result set -while client.next_result - result = client.store_result - # result now contains the next result set -end -``` - -Repeated calls to `client.next_result` will return true, false, or raise an -exception if the respective query erred. When `client.next_result` returns true, -call `client.store_result` to retrieve a result object. Exceptions are not -raised until `client.next_result` is called to find the status of the respective -query. Subsequent queries are not executed if an earlier query raised an -exception. Subsequent calls to `client.next_result` will return false. - -``` ruby -result = client.query('SELECT 1; SELECT 2; SELECT A; SELECT 3') -p result.first - -while client.next_result - result = client.store_result - p result.first -end -``` - -Yields: -``` -{"1"=>1} -{"2"=>2} -next_result: Unknown column 'A' in 'field list' (Mysql2::Error) -``` - ### Secure auth Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). @@ -347,6 +306,47 @@ It is useful if you want to provide session options which survive reconnection. Mysql2::Client.new(:init_command => "SET @@SESSION.sql_mode = 'STRICT_ALL_TABLES'") ``` +### Multiple result sets + +You can also retrieve multiple result sets. For this to work you need to +connect with flags `Mysql2::Client::MULTI_STATEMENTS`. Multiple result sets can +be used with stored procedures that return more than one result set, and for +bundling several SQL statements into a single call to `client.query`. + +``` ruby +client = Mysql2::Client.new(:host => "localhost", :username => "root", :flags => Mysql2::Client::MULTI_STATEMENTS) +result = client.query('CALL sp_customer_list( 25, 10 )') +# result now contains the first result set +while client.next_result + result = client.store_result + # result now contains the next result set +end +``` + +Repeated calls to `client.next_result` will return true, false, or raise an +exception if the respective query erred. When `client.next_result` returns true, +call `client.store_result` to retrieve a result object. Exceptions are not +raised until `client.next_result` is called to find the status of the respective +query. Subsequent queries are not executed if an earlier query raised an +exception. Subsequent calls to `client.next_result` will return false. + +``` ruby +result = client.query('SELECT 1; SELECT 2; SELECT A; SELECT 3') +p result.first + +while client.next_result + result = client.store_result + p result.first +end +``` + +Yields: +``` +{"1"=>1} +{"2"=>2} +next_result: Unknown column 'A' in 'field list' (Mysql2::Error) +``` + ## Cascading config The default config hash is at: From ee750067ab608f42462312a3d86118777aafb27b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 1 Jul 2017 10:53:20 -0400 Subject: [PATCH 504/783] Update MySQL Connector/C version for Windows --- tasks/vendor_mysql.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index ee067081f..d44efcc25 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -1,7 +1,7 @@ require 'rake/clean' require 'rake/extensioncompiler' -CONNECTOR_VERSION = "6.1.9" # NOTE: Track the upstream version from time to time +CONNECTOR_VERSION = "6.1.10" # NOTE: Track the upstream version from time to time def vendor_mysql_platform(platform = nil) platform ||= RUBY_PLATFORM From a96d1f490db02482b55f7f6e1d6d97b8e3f9dd2a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 1 Jul 2017 19:04:07 -0400 Subject: [PATCH 505/783] Change the quoting for unzipping MySQL Connector/C for Windows --- tasks/vendor_mysql.rake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index d44efcc25..a6aa6f376 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -49,9 +49,9 @@ task "vendor:mysql", [:platform] do |_t, args| when_writing "creating #{t.name}" do cd "vendor" do sh "unzip", "-uq", full_file, - "#{vendor_mysql_dir(args[:platform])}/bin/\\*\\*", - "#{vendor_mysql_dir(args[:platform])}/include/\\*\\*", - "#{vendor_mysql_dir(args[:platform])}/lib/\\*\\*", + "#{vendor_mysql_dir(args[:platform])}/bin/**", + "#{vendor_mysql_dir(args[:platform])}/include/**", + "#{vendor_mysql_dir(args[:platform])}/lib/**", "#{vendor_mysql_dir(args[:platform])}/README" # contains the license info end # update file timestamp to avoid Rake performing this extraction again. From 26e08bea73c3263296317da20351be35292ff620 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 1 Jul 2017 10:53:37 -0400 Subject: [PATCH 506/783] Bump version to 0.4.7 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index e60bb3323..d5430e672 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.6" + VERSION = "0.4.7" end From 71b5eab6829671cad72869fa0a33fbb1ef6ff447 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 2 Jul 2017 18:54:49 -0400 Subject: [PATCH 507/783] Add ifdefs for MySQL 5.1 without MYSQL_ENABLE_CLEARTEXT_PLUGIN --- ext/mysql2/client.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 97231ff17..05fd07032 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -903,10 +903,12 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = charval; break; +#ifdef MYSQL_ENABLE_CLEARTEXT_PLUGIN case MYSQL_ENABLE_CLEARTEXT_PLUGIN: boolval = (value == Qfalse ? 0 : 1); retval = &boolval; break; +#endif default: return Qfalse; @@ -1326,7 +1328,11 @@ static VALUE set_init_command(VALUE self, VALUE value) { } static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) { +#ifdef MYSQL_ENABLE_CLEARTEXT_PLUGIN return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value); +#else + rb_raise(cMysql2Error, "enable-cleartext-plugin is not available, you may need a newer MySQL client library"); +#endif } static VALUE initialize_ext(VALUE self) { From f56638413b3ac0cf869cd16357e5d84f49ca0934 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 3 Jul 2017 11:06:21 -0400 Subject: [PATCH 508/783] Travis CI add Percona MySQL 5.1 to the matrix --- .travis.yml | 9 +++++++++ .travis_mysql51.sh | 11 +++++++++++ .travis_setup.sh | 5 +++++ 3 files changed, 25 insertions(+) create mode 100644 .travis_mysql51.sh diff --git a/.travis.yml b/.travis.yml index a5f4ae753..07fb7e175 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,6 +67,12 @@ matrix: mariadb: 10.2 hosts: - mysql2gem.example.com + - rvm: 2.0.0 + env: DB=mysql51 + dist: precise + addons: + hosts: + - mysql2gem.example.com - rvm: 2.0.0 env: DB=mysql55 dist: precise @@ -105,3 +111,6 @@ matrix: - os: osx rvm: system env: DB=mysql56 + - rvm: 2.0.0 + env: DB=mysql51 + dist: precise diff --git a/.travis_mysql51.sh b/.travis_mysql51.sh new file mode 100644 index 000000000..bc3da3dd6 --- /dev/null +++ b/.travis_mysql51.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -eux + +apt-get purge -qq '^mysql*' '^libmysql*' +rm -fr /etc/mysql +rm -fr /var/lib/mysql +apt-key adv --keyserver keys.gnupg.net --recv-keys 8507EFA5 +add-apt-repository 'deb http://repo.percona.com/apt precise main' +apt-get update -qq +apt-get install -qq percona-server-server-5.1 percona-server-client-5.1 libmysqlclient16-dev diff --git a/.travis_setup.sh b/.travis_setup.sh index ffe5295b5..fe114c68e 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -2,6 +2,11 @@ set -eux +# Install MySQL 5.1 if DB=mysql51 +if [[ -n ${DB-} && x$DB =~ ^xmysql51 ]]; then + sudo bash .travis_mysql51.sh +fi + # Install MySQL 5.7 if DB=mysql57 if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then sudo bash .travis_mysql57.sh From fdbd7a50ab8ad431c33d61e30f790671ea365440 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 10 Jul 2017 13:40:07 -0700 Subject: [PATCH 509/783] Bump version to 0.4.8 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index d5430e672..639c9239f 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.7" + VERSION = "0.4.8" end From a700aea7dc69ff7b62d47b37e33c4e94ddec2662 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 11 Jul 2017 12:27:28 -0700 Subject: [PATCH 510/783] Update README section to discuss the special meaning of localhost --- README.md | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b1c8500d2..6139232d4 100644 --- a/README.md +++ b/README.md @@ -213,21 +213,18 @@ Mysql2::Client.new( ) ``` -### Connecting to localhost - -The underlying MySQL client library has a special interpretation of the "localhost" default connection host name: - - 1. Attempt to connect via local socket (as specified in your distribution's my.cnf or `default_file` config file). - 2. But if the socket is not available, look up "localhost" to find an IP address... - * The file "/etc/hosts" is generally the source of truth for "localhost", and can override its value. - * Systems with IPv4 use "127.0.0.1" - * Systems with IPv6 use "::1" - * Systems with both IPv4 and IPv6 can be configured to prefer one or the other. - 3. If an address is found for "localhost", connect to it over TCP/IP. - -You can be explicit about using either a local socket or a TCP/IP connection, -and whether to use IPv4 or IPv6, by specifying a value other than "localhost" -for the `host` or `socket` connection options. +### Connecting to MySQL on localhost and elsewhere + +The underlying MySQL client library uses the `:host` parameter to determine the +type of connection to make, with special interpretation you should be aware of: + +* An empty value or `"localhost"` will attempt a local connection: + * On Unix, connect to the default local socket path. (To set a custom socket + path, use the `:socket` parameter). + * On Windows, connect using a shared-memory connection, if enabled, or TCP. +* A value of `"."` on Windows specifies a named-pipe connection. +* An IPv4 or IPv6 address will result in a TCP connection. +* Any other value will be looked up as a hostname for a TCP connection. ### SSL options From 7e8f0e146be283446475d725c18ab98cd92fae56 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Tue, 8 Aug 2017 19:50:10 +0900 Subject: [PATCH 511/783] Prepared statements should handle booleans properly (#871) Without this fix, booleans always be bound as `0.0` even if `true`. ``` Failures: 1) Mysql2::Statement should handle booleans Failure/Error: expect(result.to_a).to eq(['true' => 1, 'false' => 0]) expected: [{"true"=>1, "false"=>0}] got: [{"true"=>0.0 (#), "false"=>0.0 (#)}] (compared using ==) Diff: @@ -1,2 +1,3 @@ -[{"true"=>1, "false"=>0}] +[{"true"=>0.0 (#), + "false"=>0.0 (#)}] # ./spec/mysql2/statement_spec.rb:67:in `block (2 levels) in ' ``` --- ext/mysql2/statement.c | 10 ++++++++++ spec/mysql2/statement_spec.rb | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 743389da0..18c603ce3 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -343,6 +343,16 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { #endif set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); break; + case T_TRUE: + bind_buffers[i].buffer_type = MYSQL_TYPE_TINY; + bind_buffers[i].buffer = xmalloc(sizeof(signed char)); + *(signed char*)(bind_buffers[i].buffer) = 1; + break; + case T_FALSE: + bind_buffers[i].buffer_type = MYSQL_TYPE_TINY; + bind_buffers[i].buffer = xmalloc(sizeof(signed char)); + *(signed char*)(bind_buffers[i].buffer) = 0; + break; default: // TODO: what Ruby type should support MYSQL_TYPE_TIME if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) { diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 8b4a714fd..79da4d85d 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -61,6 +61,12 @@ def stmt_count expect(rows).to eq([{ "1" => 1 }]) end + it "should handle booleans" do + stmt = @client.prepare('SELECT ? AS `true`, ? AS `false`') + result = stmt.execute(true, false) + expect(result.to_a).to eq(['true' => 1, 'false' => 0]) + end + it "should handle bignum but in int64_t" do stmt = @client.prepare('SELECT ? AS max, ? AS min') int64_max = (1 << 63) - 1 From a3217239848ac421e30692413a47c6b080eda940 Mon Sep 17 00:00:00 2001 From: denisenkom Date: Tue, 8 Aug 2017 06:50:50 -0400 Subject: [PATCH 512/783] Fixed enable_cleartext_plugin mode (#874) --- ext/mysql2/client.c | 4 ++-- ext/mysql2/extconf.rb | 1 + spec/mysql2/client_spec.rb | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 05fd07032..24b906b14 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -903,7 +903,7 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = charval; break; -#ifdef MYSQL_ENABLE_CLEARTEXT_PLUGIN +#ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN case MYSQL_ENABLE_CLEARTEXT_PLUGIN: boolval = (value == Qfalse ? 0 : 1); retval = &boolval; @@ -1328,7 +1328,7 @@ static VALUE set_init_command(VALUE self, VALUE value) { } static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) { -#ifdef MYSQL_ENABLE_CLEARTEXT_PLUGIN +#ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value); #else rb_raise(cMysql2Error, "enable-cleartext-plugin is not available, you may need a newer MySQL client library"); diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 4fe27562c..e7ccef5ec 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -114,6 +114,7 @@ def add_ssl_defines(header) add_ssl_defines(mysql_h) have_struct_member('MYSQL', 'net.vio', mysql_h) have_struct_member('MYSQL', 'net.pvio', mysql_h) +have_const('MYSQL_ENABLE_CLEARTEXT_PLUGIN', mysql_h) # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index ad5fe757f..e3a616029 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1026,6 +1026,11 @@ def run_gc expect(@client.ping).to eql(false) end + it "should be able to connect using plaintext password" do + client = new_client(:enable_cleartext_plugin => true) + client.query('SELECT 1') + end + unless RUBY_VERSION =~ /1.8/ it "should respond to #encoding" do expect(@client).to respond_to(:encoding) From 9c2d277cf07b7f481e5c665e745985115bc6f434 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 10 Aug 2017 21:23:14 -0700 Subject: [PATCH 513/783] Bump version to 0.4.9 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 639c9239f..5b5ad1d68 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.8" + VERSION = "0.4.9" end From 5ebb89955a59f397368036d3fad8c7d857e32f98 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 10 Aug 2017 21:35:29 -0700 Subject: [PATCH 514/783] Update MySQL Connector/C version for Windows --- tasks/vendor_mysql.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index a6aa6f376..d88b6f177 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -1,7 +1,7 @@ require 'rake/clean' require 'rake/extensioncompiler' -CONNECTOR_VERSION = "6.1.10" # NOTE: Track the upstream version from time to time +CONNECTOR_VERSION = "6.1.11" # NOTE: Track the upstream version from time to time def vendor_mysql_platform(platform = nil) platform ||= RUBY_PLATFORM From 58d0a0d4cf595ac164a1207d7e44b301455f118d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 23 Aug 2017 02:32:33 -0700 Subject: [PATCH 515/783] Switch from pending to skip because of rspec 3 changes --- spec/mysql2/client_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index e3a616029..391449ca2 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -368,7 +368,7 @@ def run_gc new_client(:local_infile => true) do |client| local = client.query "SHOW VARIABLES LIKE 'local_infile'" local_enabled = local.any? { |x| x['Value'] == 'ON' } - pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled + skip("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled client.query %[ CREATE TABLE IF NOT EXISTS infileTest ( @@ -382,7 +382,7 @@ def run_gc after(:all) do new_client do |client| - client.query "DROP TABLE infileTest" + client.query "DROP TABLE IF EXISTS infileTest" end end From ac34e68fe041edae722bcebaa09dcbf0f621c742 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sat, 11 Nov 2017 09:33:37 -0500 Subject: [PATCH 516/783] MYSQL_SECURE_AUTH has been removed in MySQL 8.0.3 RC (#892) https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-3.html#mysqld-8-0-3-capi > The deprecated secure_auth system variable and --secure-auth client option have been removed. > The MYSQL_SECURE_AUTH option for the mysql_options() C API function was removed. --- ext/mysql2/client.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 24b906b14..2d850cd20 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -883,10 +883,12 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = &boolval; break; +#if defined(MYSQL_SECURE_AUTH) case MYSQL_SECURE_AUTH: boolval = (value == Qfalse ? 0 : 1); retval = &boolval; break; +#endif case MYSQL_READ_DEFAULT_FILE: charval = (const char *)StringValueCStr(value); @@ -1312,7 +1314,10 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE } static VALUE set_secure_auth(VALUE self, VALUE value) { +/* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */ +#if defined(MYSQL_SECURE_AUTH) return _mysql_client_options(self, MYSQL_SECURE_AUTH, value); +#endif } static VALUE set_read_default_file(VALUE self, VALUE value) { From f5709fd4265e4efb013b6d9f250f94af87f93ad1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 11 Nov 2017 06:34:54 -0800 Subject: [PATCH 517/783] Return false on deprecated set secure auth --- ext/mysql2/client.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 2d850cd20..afbaff3ba 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1317,6 +1317,8 @@ static VALUE set_secure_auth(VALUE self, VALUE value) { /* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */ #if defined(MYSQL_SECURE_AUTH) return _mysql_client_options(self, MYSQL_SECURE_AUTH, value); +#else + return Qfalse; #endif } From b2cbe3e3d8fff33b8c6a1e1a64ce4e45666ff766 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sat, 11 Nov 2017 09:46:13 -0500 Subject: [PATCH 518/783] extended keyword for the explain statement has been removed (#894) https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-3.html > The deprecated EXTENDED and PARTITIONS keywords for the EXPLAIN statement have been removed. These keywords are unnecessary because their effect is always enabled. Attempting to drop a non-existent table produces warnings on all versions of MySQL. --- spec/mysql2/client_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 391449ca2..c5ae66107 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -327,8 +327,8 @@ def run_gc context "when has a warnings" do it "should > 0" do # "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS" - # http://dev.mysql.com/doc/refman/5.0/en/explain-extended.html - @client.query("explain extended select 1") + # https://dev.mysql.com/doc/refman/5.7/en/show-warnings.html + @client.query('DROP TABLE IF EXISTS test.no_such_table') expect(@client.warning_count).to be > 0 end end From f987eec1cbfe77bb6b30f17d4454cd2a5db114d4 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 11 Nov 2017 08:34:05 -0800 Subject: [PATCH 519/783] More work towards MariaDB version string compatibility Revert the mysql_version.h include, and use MARIADB_CLIENT_VERSION_STR instead. --- ext/mysql2/client.c | 4 +++- ext/mysql2/mysql2_ext.h | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index afbaff3ba..34ec9a69c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -53,7 +53,9 @@ VALUE rb_hash_dup(VALUE other) { * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when * linking against the server itself */ -#ifdef LIBMYSQL_VERSION +#if defined(MARIADB_CLIENT_VERSION_STR) + #define MYSQL_LINK_VERSION MARIADB_CLIENT_VERSION_STR +#elif defined(LIBMYSQL_VERSION) #define MYSQL_LINK_VERSION LIBMYSQL_VERSION #else #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index 23ae0670d..f53f4b4a1 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -14,13 +14,11 @@ void Init_mysql2(void); #include #include #include -#include #else #include #include #include #include -#include #endif #ifdef HAVE_RUBY_ENCODING_H From 80f743502d5d96cad98cbe44582822cf672d8592 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 11 Nov 2017 08:35:04 -0800 Subject: [PATCH 520/783] ifdef consistency --- ext/mysql2/client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 34ec9a69c..224581452 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -885,7 +885,7 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = &boolval; break; -#if defined(MYSQL_SECURE_AUTH) +#ifdef MYSQL_SECURE_AUTH case MYSQL_SECURE_AUTH: boolval = (value == Qfalse ? 0 : 1); retval = &boolval; @@ -1317,7 +1317,7 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE static VALUE set_secure_auth(VALUE self, VALUE value) { /* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */ -#if defined(MYSQL_SECURE_AUTH) +#ifdef MYSQL_SECURE_AUTH return _mysql_client_options(self, MYSQL_SECURE_AUTH, value); #else return Qfalse; From a2e62e096bd07ffe7c21cba227855b46c0761088 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 11 Nov 2017 08:56:01 -0800 Subject: [PATCH 521/783] Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x --- .travis_setup.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis_setup.sh b/.travis_setup.sh index fe114c68e..ab47c2eb9 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -17,6 +17,16 @@ if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then sudo bash .travis_mysql80.sh fi +# Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x +if [[ -n ${DB-} && x$DB =~ ^xmariadb10.0 ]]; then + sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev +fi + +# Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x +if [[ -n ${DB-} && x$DB =~ ^xmariadb10.1 ]]; then + sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev +fi + # Install MariaDB 10.2 if DB=mariadb10.2 # NOTE this is a workaround until Travis CI merges a fix to its mariadb addon. if [[ -n ${DB-} && x$DB =~ ^xmariadb10.2 ]]; then From 8d8fbf94c3fc783a1becafb9efa6db8f4d0985f1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 11 Nov 2017 19:29:56 -0800 Subject: [PATCH 522/783] Include mysql_com.h has been here for ages but was superfluous --- ext/mysql2/mysql2_ext.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index f53f4b4a1..3995c3698 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -11,12 +11,10 @@ void Init_mysql2(void); #ifdef HAVE_MYSQL_H #include -#include #include #include #else #include -#include #include #include #endif From 830dd7377baeb3d64457a74d670dfb026905be31 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 11 Nov 2017 19:48:04 -0800 Subject: [PATCH 523/783] Check the server version instead of the client version of MySQL on OS X specs --- spec/mysql2/client_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index c5ae66107..44c311db7 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -604,8 +604,8 @@ def run_gc end it "should handle Timeouts without leaving the connection hanging if reconnect is true" do - if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.info.fetch(:version).start_with?('5.5') - pending('libmysqlclient 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') + if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.server_info.fetch(:version).start_with?('5.5') + pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end client = new_client(:reconnect => true) @@ -615,8 +615,8 @@ def run_gc end it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction" do - if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.info.fetch(:version).start_with?('5.5') - pending('libmysqlclient 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') + if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.server_info.fetch(:version).start_with?('5.5') + pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end client = new_client From 92dbef3a50da812666ac7543213452d6e0776d5e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 12 Nov 2017 10:28:46 -0800 Subject: [PATCH 524/783] Include mysqld_error.h has been here for ages but was superfluous --- ext/mysql2/extconf.rb | 4 ++-- ext/mysql2/mysql2_ext.h | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index e7ccef5ec..043d001ed 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -105,8 +105,8 @@ def add_ssl_defines(header) asplode 'mysql.h' end -%w(errmsg.h mysqld_error.h).each do |h| - header = [prefix, h].compact.join '/' +%w(errmsg.h).each do |h| + header = [prefix, h].compact.join('/') asplode h unless have_header header end diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index 3995c3698..80063ed04 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -12,11 +12,9 @@ void Init_mysql2(void); #ifdef HAVE_MYSQL_H #include #include -#include #else #include #include -#include #endif #ifdef HAVE_RUBY_ENCODING_H From 8a8246670339fadbf05012fbf8ceefd7016d0cd2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 12 Nov 2017 10:47:25 -0800 Subject: [PATCH 525/783] Use keyserver.ubuntu.com for the Percona key --- .travis_mysql51.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis_mysql51.sh b/.travis_mysql51.sh index bc3da3dd6..d106b6464 100644 --- a/.travis_mysql51.sh +++ b/.travis_mysql51.sh @@ -5,7 +5,7 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key adv --keyserver keys.gnupg.net --recv-keys 8507EFA5 +apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key 9334A25F8507EFA5 add-apt-repository 'deb http://repo.percona.com/apt precise main' apt-get update -qq apt-get install -qq percona-server-server-5.1 percona-server-client-5.1 libmysqlclient16-dev From cffb76dd0eb7a54bea7282aa209ce88b5f0b1cd0 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 14 Nov 2017 08:30:59 +0100 Subject: [PATCH 526/783] Make sure ssl is enabled if only :sslverify is set (#889) Previously, when "sslverify: false/true" was the only ssl related options passed to the constructor, the module skipped the call to "mysql_ssl_set". It seems however that for some variants for the mysql client libraries calling "mysql_ssl_set" is the only way to enable SSL for the client connections. (E.g. the libraries shipped as part of mariadb 10.1 still lack support for MYSQL_OPT_SSL_ENFORCE and MYSQL_OPT_SSL_MODE) This change allows enabling ssl with default values for all other options by just passing "sslverify: true" or "sslverify: false" to the constructor. (Depending on whether server certificate verification is wanted or not) --- lib/mysql2/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index fb18be316..a9049d998 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -47,7 +47,7 @@ def initialize(opts = {}) self.charset_name = opts[:encoding] || 'utf8' ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher) - ssl_set(*ssl_options) if ssl_options.any? + ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify) self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode] case opts[:flags] @@ -62,7 +62,7 @@ def initialize(opts = {}) end # SSL verify is a connection flag rather than a mysql_ssl_set option - flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] && ssl_options.any? + flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) } warn "============= WARNING FROM mysql2 =============" From 255c759a86de02970eec1cb335a0ce3e3386dd62 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 13 Nov 2017 18:16:26 -0800 Subject: [PATCH 527/783] Test prepared statements in the prepared statements tests: date & time --- spec/mysql2/statement_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 79da4d85d..9db9d4fc7 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -460,39 +460,39 @@ def stmt_count it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 - r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") + r = @client.prepare("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test").execute expect(r.first['test']).to be_an_instance_of(klass) end it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 - r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") + r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute expect(r.first['test']).to be_an_instance_of(klass) end elsif 1.size == 8 # 64bit if RUBY_VERSION =~ /1.8/ it "should return Time when timestamp is > 0138-12-31 11:59:59" do - r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") + r = @client.prepare("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test").execute expect(r.first['test']).to be_an_instance_of(Time) end it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do - r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") + r = @client.prepare("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test").execute expect(r.first['test']).to be_an_instance_of(DateTime) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do - r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") + r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute expect(r.first['test']).to be_an_instance_of(Time) end else it "should return Time when timestamp is < 1901-12-13 20:45:52" do - r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") + r = @client.prepare("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test").execute expect(r.first['test']).to be_an_instance_of(Time) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do - r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") + r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute expect(r.first['test']).to be_an_instance_of(Time) end end From 35a4f6bf5f3604bc888475535ff6716bead945f3 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 13 Nov 2017 18:48:29 -0800 Subject: [PATCH 528/783] Test prepared statements in the prepared statements tests: boolean --- spec/mysql2/statement_spec.rb | 67 +++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 9db9d4fc7..9c10c6775 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -374,36 +374,43 @@ def stmt_count expect(@test_result['tiny_int_test']).to eql(1) end - it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do - @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (1)' - id1 = @client.last_id - @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (0)' - id2 = @client.last_id - @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)' - id3 = @client.last_id - - result1 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast_booleans => true - result2 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 0 LIMIT 1', :cast_booleans => true - result3 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = -1 LIMIT 1', :cast_booleans => true - expect(result1.first['bool_cast_test']).to be true - expect(result2.first['bool_cast_test']).to be false - expect(result3.first['bool_cast_test']).to be true - - @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" - end - - it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do - @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)' - id1 = @client.last_id - @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)' - id2 = @client.last_id - - result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", :cast_booleans => true - result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", :cast_booleans => true - expect(result1.first['single_bit_test']).to be true - expect(result2.first['single_bit_test']).to be false - - @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" + context "cast booleans for TINYINY if :cast_booleans is enabled" do + let(:client) { new_client(:cast_booleans => true) } + let (:id1) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; client.last_id } + let (:id2) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; client.last_id } + let (:id3) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; client.last_id } + + after do + client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" + end + + it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do + query = client.prepare 'SELECT bool_cast_test FROM mysql2_test WHERE id = ?' + result1 = query.execute id1 + result2 = query.execute id2 + result3 = query.execute id3 + expect(result1.first['bool_cast_test']).to be true + expect(result2.first['bool_cast_test']).to be false + expect(result3.first['bool_cast_test']).to be true + end + end + + context "cast booleans for BIT(1) if :cast_booleans is enabled" do + let(:client) { new_client(:cast_booleans => true) } + let (:id1) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; client.last_id } + let (:id2) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; client.last_id } + + after do + client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" + end + + it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do + query = client.prepare 'SELECT single_bit_test FROM mysql2_test WHERE id = ?' + result1 = query.execute id1 + result2 = query.execute id2 + expect(result1.first['single_bit_test']).to be true + expect(result2.first['single_bit_test']).to be false + end end it "should return Fixnum for a SMALLINT value" do From 934a4a8fa587fbad910bdd09915df032df9a3df2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 13 Nov 2017 19:05:00 -0800 Subject: [PATCH 529/783] Cast the BIT(1) type when :cast_booleans is true for prepared statements --- ext/mysql2/result.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index c227c41fa..ccb49a5b7 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -405,6 +405,13 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co val = INT2NUM(*((signed char*)result_buffer->buffer)); } break; + case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ + if (args->castBool && fields[i].length == 1) { + val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse; + }else{ + val = rb_str_new(result_buffer->buffer, *(result_buffer->length)); + } + break; case MYSQL_TYPE_SHORT: // short int if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned short int*)result_buffer->buffer)); @@ -494,7 +501,6 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co case MYSQL_TYPE_BLOB: // char[] case MYSQL_TYPE_MEDIUM_BLOB: // char[] case MYSQL_TYPE_LONG_BLOB: // char[] - case MYSQL_TYPE_BIT: // char[] case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] From 975c30e524bb9104f1aa0705dae869dfa20c6213 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 13 Nov 2017 22:38:36 -0800 Subject: [PATCH 530/783] Fix rubocop --- spec/mysql2/statement_spec.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 9c10c6775..c0aa46981 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -375,10 +375,12 @@ def stmt_count end context "cast booleans for TINYINY if :cast_booleans is enabled" do + # rubocop:disable Style/Semicolon let(:client) { new_client(:cast_booleans => true) } - let (:id1) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; client.last_id } - let (:id2) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; client.last_id } - let (:id3) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; client.last_id } + let(:id1) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; client.last_id } + let(:id2) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; client.last_id } + let(:id3) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; client.last_id } + # rubocop:enable Style/Semicolon after do client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" @@ -396,9 +398,11 @@ def stmt_count end context "cast booleans for BIT(1) if :cast_booleans is enabled" do + # rubocop:disable Style/Semicolon let(:client) { new_client(:cast_booleans => true) } - let (:id1) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; client.last_id } - let (:id2) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; client.last_id } + let(:id1) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; client.last_id } + let(:id2) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; client.last_id } + # rubocop:enable Style/Semicolon after do client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" From e52fc0ae593f7e33bcbd94bd8866ba388b10252d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 13 Nov 2017 22:38:55 -0800 Subject: [PATCH 531/783] Fix server_info --- spec/mysql2/client_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 44c311db7..dfb92a2df 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -604,7 +604,7 @@ def run_gc end it "should handle Timeouts without leaving the connection hanging if reconnect is true" do - if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.server_info.fetch(:version).start_with?('5.5') + if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5') pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end @@ -615,7 +615,7 @@ def run_gc end it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction" do - if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.server_info.fetch(:version).start_with?('5.5') + if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5') pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end From f971f369366f25b8029baf233b92b1613d847de7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 14 Nov 2017 00:51:16 -0800 Subject: [PATCH 532/783] Backport the cast_booleans test structure to results spec --- spec/mysql2/result_spec.rb | 67 +++++++++++++++++++---------------- spec/mysql2/statement_spec.rb | 2 +- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index e869827db..35da16b91 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -208,36 +208,43 @@ expect(@test_result['tiny_int_test']).to eql(1) end - it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do - @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (1)' - id1 = @client.last_id - @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (0)' - id2 = @client.last_id - @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)' - id3 = @client.last_id - - result1 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast_booleans => true - result2 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 0 LIMIT 1', :cast_booleans => true - result3 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = -1 LIMIT 1', :cast_booleans => true - expect(result1.first['bool_cast_test']).to be true - expect(result2.first['bool_cast_test']).to be false - expect(result3.first['bool_cast_test']).to be true - - @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" - end - - it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do - @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)' - id1 = @client.last_id - @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)' - id2 = @client.last_id - - result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", :cast_booleans => true - result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", :cast_booleans => true - expect(result1.first['single_bit_test']).to be true - expect(result2.first['single_bit_test']).to be false - - @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" + context "cast booleans for TINYINT if :cast_booleans is enabled" do + # rubocop:disable Style/Semicolon + let(:id1) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; @client.last_id } + let(:id2) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; @client.last_id } + let(:id3) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; @client.last_id } + # rubocop:enable Style/Semicolon + + after do + @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" + end + + it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do + result1 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id1} LIMIT 1", :cast_booleans => true + result2 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id2} LIMIT 1", :cast_booleans => true + result3 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id3} LIMIT 1", :cast_booleans => true + expect(result1.first['bool_cast_test']).to be true + expect(result2.first['bool_cast_test']).to be false + expect(result3.first['bool_cast_test']).to be true + end + end + + context "cast booleans for BIT(1) if :cast_booleans is enabled" do + # rubocop:disable Style/Semicolon + let(:id1) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; @client.last_id } + let(:id2) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; @client.last_id } + # rubocop:enable Style/Semicolon + + after do + @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" + end + + it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do + result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", :cast_booleans => true + result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", :cast_booleans => true + expect(result1.first['single_bit_test']).to be true + expect(result2.first['single_bit_test']).to be false + end end it "should return Fixnum for a SMALLINT value" do diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index c0aa46981..c0a16af47 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -374,7 +374,7 @@ def stmt_count expect(@test_result['tiny_int_test']).to eql(1) end - context "cast booleans for TINYINY if :cast_booleans is enabled" do + context "cast booleans for TINYINT if :cast_booleans is enabled" do # rubocop:disable Style/Semicolon let(:client) { new_client(:cast_booleans => true) } let(:id1) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; client.last_id } From ad4cd99983533c39fb5ab32d42a4cc270b7613a5 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 14 Nov 2017 00:51:40 -0800 Subject: [PATCH 533/783] Test five significant figures of microseconds to reduce off-by-one-microsecond errors --- spec/mysql2/result_spec.rb | 24 ++++++++++++++++++++++++ spec/mysql2/statement_spec.rb | 6 ++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 35da16b91..c8e26c530 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -292,6 +292,30 @@ expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end + it "should return Time values with microseconds" do + now = Time.now + if RUBY_VERSION =~ /1.8/ || @client.server_info[:id] / 100 < 506 + result = @client.query("SELECT CAST('#{now.strftime('%F %T %z')}' AS DATETIME) AS a") + expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) + else + result = @client.query("SELECT CAST('#{now.strftime('%F %T.%6N %z')}' AS DATETIME(6)) AS a") + # microseconds is 6 digits after the decimal, but only test on 5 significant figures + expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) + end + end + + it "should return DateTime values with microseconds" do + now = DateTime.now + if RUBY_VERSION =~ /1.8/ || @client.server_info[:id] / 100 < 506 + result = @client.query("SELECT CAST('#{now.strftime('%F %T %z')}' AS DATETIME) AS a") + expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) + else + result = @client.query("SELECT CAST('#{now.strftime('%F %T.%6N %z')}' AS DATETIME(6)) AS a") + # microseconds is 6 digits after the decimal, but only test on 5 significant figures + expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) + end + end + if 1.size == 4 # 32bit klass = if RUBY_VERSION =~ /1.8/ DateTime diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index c0a16af47..e0fccad53 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -150,7 +150,8 @@ def stmt_count if RUBY_VERSION =~ /1.8/ expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) else - expect(result.first['a'].strftime('%F %T.%6N %z')).to eql(now.strftime('%F %T.%6N %z')) + # microseconds is six digits after the decimal, but only test on 5 significant figures + expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) end end @@ -161,7 +162,8 @@ def stmt_count if RUBY_VERSION =~ /1.8/ expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) else - expect(result.first['a'].strftime('%F %T.%6N %z')).to eql(now.strftime('%F %T.%6N %z')) + # microseconds is six digits after the decimal, but only test on 5 significant figures + expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) end end From f8ee23acebad84fbbabe062d4bfc25f25e8d02c3 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 14 Nov 2017 08:57:12 -0800 Subject: [PATCH 534/783] Stash the mysql-build@oss.oracle.com key locally instead of calling pgp.mit.edu --- .travis_mysql57.sh | 2 +- .travis_mysql80.sh | 2 +- support/5072E1F5.asc | 432 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 434 insertions(+), 2 deletions(-) create mode 100644 support/5072E1F5.asc diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index 493143e94..ab0df12c4 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -5,7 +5,7 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5 +apt-key add - < support/5072E1F5.asc add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ trusty mysql-5.7' apt-get update -qq apt-get install -qq mysql-server libmysqlclient-dev diff --git a/.travis_mysql80.sh b/.travis_mysql80.sh index 085ca8c75..70b6c8f86 100644 --- a/.travis_mysql80.sh +++ b/.travis_mysql80.sh @@ -5,7 +5,7 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5 +apt-key add - < support/5072E1F5.asc add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ trusty mysql-8.0' apt-get update -qq apt-get install -qq mysql-server libmysqlclient-dev diff --git a/support/5072E1F5.asc b/support/5072E1F5.asc new file mode 100644 index 000000000..6c9fd8dec --- /dev/null +++ b/support/5072E1F5.asc @@ -0,0 +1,432 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.5 (GNU/Linux) + +mQGiBD4+owwRBAC14GIfUfCyEDSIePvEW3SAFUdJBtoQHH/nJKZyQT7h9bPlUWC3 +RODjQReyCITRrdwyrKUGku2FmeVGwn2u2WmDMNABLnpprWPkBdCk96+OmSLN9brZ +fw2vOUgCmYv2hW0hyDHuvYlQA/BThQoADgj8AW6/0Lo7V1W9/8VuHP0gQwCgvzV3 +BqOxRznNCRCRxAuAuVztHRcEAJooQK1+iSiunZMYD1WufeXfshc57S/+yeJkegNW +hxwR9pRWVArNYJdDRT+rf2RUe3vpquKNQU/hnEIUHJRQqYHo8gTxvxXNQc7fJYLV +K2HtkrPbP72vwsEKMYhhr0eKCbtLGfls9krjJ6sBgACyP/Vb7hiPwxh6rDZ7ITnE +kYpXBACmWpP8NJTkamEnPCia2ZoOHODANwpUkP43I7jsDmgtobZX9qnrAXw+uNDI +QJEXM6FSbi0LLtZciNlYsafwAPEOMDKpMqAK6IyisNtPvaLd8lH0bPAnWqcyefep +rv0sxxqUEMcM3o7wwgfN83POkDasDbs3pjwPhxvhz6//62zQJ7Q2TXlTUUwgUmVs +ZWFzZSBFbmdpbmVlcmluZyA8bXlzcWwtYnVpbGRAb3NzLm9yYWNsZS5jb20+iGwE +ExECACwCGyMCHgECF4ACGQEGCwkIBwMCBhUKCQgCAwUWAgMBAAUCWKcFIAUJHirJ +FAAKCRCMcY07UHLh9VcFAJ46pUyVd8BZ2r5CppMC1tmyQ3ceRgCfVPwuVsiS0VER +5WUqtAQDt+DoetCIaQQTEQIAKQIbIwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAhkB +BQJTAdRmBQkaZsvLAAoJEIxxjTtQcuH1X4MAoKNLWAbCBUj96637kv6Xa/fJuX5m +AJwPtmgDfjUe2iuhXdTrFEPT19SB6ohmBBMRAgAmAhsjBgsJCAcDAgQVAggDBBYC +AwECHgECF4AFAk53PioFCRP7AhUACgkQjHGNO1By4fUmzACeJdfqgc9gWTUhgmcM +AOmG4RjwuxcAoKfM+U8yMOGELi+TRif7MtKEms6piGkEExECACkCGyMGCwkIBwMC +BBUCCAMEFgIDAQIeAQIXgAIZAQUCUZSROgUJFTchqgAKCRCMcY07UHLh9YtAAJ9X +rA/ymlmozPZn+A9ls8/uwMcTsQCfaQMNq1dNkhH2kyByc3Rx9/W2xfqJARwEEAEC +AAYFAlAS6+UACgkQ8aIC+GoXHivrWwf/dtLk/x+NC2VMDlg+vOeM0qgG1IlhXZfi +NsEisvvGaz4m8fSFRGe+1bvvfDoKRhxiGXU48RusjixzvBb6KTMuY6JpOVfz9Dj3 +H9spYriHa+i6rYySXZIpOhfLiMnTy7NH2OvYCyNzSS/ciIUACIfH/2NH8zNT5CNF +1uPNRs7HsHzzz7pOlTjtTWiF4cq/Ij6Z6CNrmdj+SiMvjYN9u6sdEKGtoNtpycgD +5HGKR+I7Nd/7v56yhaUe4FpuvsNXig86K9tI6MUFS8CUyy7Hj3kVBZOUWVBM053k +nGdALSygQr50DA3jMGKVl4ZnHje2RVWRmFTr5YWoRTMxUSQPMLpBNIkBHAQQAQIA +BgUCU1B+vQAKCRAohbcD0zcc8dWwCACWXXWDXIcAWRUw+j3ph8dr9u3SItljn3wB +c7clpclKWPuLvTz7lGgzlVB0s8hH4xgkSA+zLzl6u56mpUzskFl7f1I3Ac9GGpM4 +0M5vmmR9hwlD1HdZtGfbD+wkjlqgitNLoRcGdRf/+U7x09GhSS7Bf339sunIX6sM +gXSC4L32D3zDjF5icGdb0kj+3lCrRmp853dGyA3ff9yUiBkxcKNawpi7Vz3D2ddU +pOF3BP+8NKPg4P2+srKgkFbd4HidcISQCt3rY4vaTkEkLKg0nNA6U4r0YgOa7wIT +SsxFlntMMzaRg53QtK0+YkH0KuZR3GY8B7pi+tlgycyVR7mIFo7riQEcBBABCAAG +BQJWgVd0AAoJEEZu4b/gk4UKk9MH/Rnt7EccPjSJC5CrB2AU5LY2Dsr+PePI2ubP +WsEdG82qSjjGpbhIH8LSg/PzQoGHiFWMmmZWJktRT+dcgLbs3b2VwCNAwCE8jOHd +UkQhEowgomdNvHiBHKHjP4/lF68KOPiO/2mxYYkmpM7BWf3kB57DJ5CTi3/JLoN7 +zF40qIs/p09ePvnwStpglbbtUn7XPO+1/Ee8VHzimABom52PkQIuxNiVUzLVn3bS +Wqrd5ecuqLk6yzjPXd2XhDHWC9Twpl68GePru6EzQtusi0m6S/sHgEXqh/IxrFZV +JlljF75JvosZq5zeulr0i6kOij+Y1p6MFffihITZ1gTmk+CLvK2JASIEEAECAAwF +Ak53QS4FAwASdQAACgkQlxC4m8pXrXwJ8Qf/be/UO9mqfoc2sMyhwMpN4/fdBWwf +LkA12FXQDOQMvwH9HsmEjnfUgYKXschZRi+DuHXe1P7l8G2aQLubhBsQf9ejKvRF +TzuWMQkdIq+6Koulxv6ofkCcv3d1xtO2W7nb5yxcpVBPrRfGFGebJvZa58DymCNg +yGtAU6AOz4veavNmI2+GIDQsY66+tYDvZ+CxwzdYu+HDV9HmrJfc6deM0mnBn7SR +jqzxJPgoTQhihTav6q/R5/2p5NvQ/H84OgS6GjosfGc2duUDzCP/kheMRKfzuyKC +OHQPtJuIj8++gfpHtEU7IDUX1So3c9n0PdpeBvclsDbpRnCNxQWU4mBot4kBIgQQ +AQIADAUCToi2GQUDABJ1AAAKCRCXELibyletfLZAB/9oRqx+NC98UQD/wlxCRytz +vi/MuPnbgQUPLHEap10tvEi33S/H/xDR/tcGofY4cjAvo5skZXXeWq93Av7PACUb +zkg0X0eSr2oL6wy66xfov72AwSuX+iUK68qtKaLqRLitM02y8aNRV/ggKvt7UMvG +mOvs5yLaYlobyvGaFC2ClfkNOt2MlVnQZCmnYBCwOktPGkExiu2yZMifcYGxQcpH +KVFG59KeF2cM2d4xYM8HJqkSGGW306LFVSyeRwG+wbttgLpD5bM/T2b3fF/J35ra +CSMLZearRTq8aygPl+XM7MM2eR946aw6jmOsgNBErbvvIdQj6LudAZj+8imcXV2K +iQEiBBABAgAMBQJOmdnRBQMAEnUAAAoJEJcQuJvKV618AvIIAIEF1ZJ+Ry7WOdKF +5oeQ/ynaYUigzN92fW/9zB8yuQlngkFJGidYMbci1tR1siziIVJFusR3ZonqAPGK +/SUta9Y6KWLhmc7c5UnEHklq/NfdMZ2WVSIykXlctqw0sbb+z1ecEd4G8u9j5ill +MO1B36rQayYAPoeXLX8dY4VyFLVGaQ00rWQBYFZrpw16ATWbWGJP332NSfCk4zZq +6kXEW07q0st3YBgAAGdNQyEeZCa4d4pBRSX6189Kjg6GDnIcaiOF6HO6PLr9fRlL +r5ObCgU+G9gEhfiVwDEV9E+7/Bq2pYZ9whhkBqWQzdpXTNTM24uaEhE01EPO5zeC +O214q6mJASIEEAECAAwFAk6rpgEFAwASdQAACgkQlxC4m8pXrXzAhwf/f9O99z16 +3Y5FZVIxexyqXQ/Mct9uKHuXEVnRFYbA49dQLD4S73N+zN7gn9jFeQcBo4w8qVUV +94U/ta/VbLkdtNREyplPM4XY8YE5Wfd9bfyg3q1PbEiVjk995sBF+2+To99YYKst +gXPqjlH0jUfEyDmexOj+hsp8Rc63kvkIx36VBa4ONRYFefGAhKDMigL2YAhc1UkG +tkGTuLmlCGwIV6lviDZD3RJf5375VFnaHv7eXfwQxCwE+BxG3CURrjfxjaxMTmMP +yAG2rhDp5oTUEvqDYNbko5UxYOmrSjvF4FzXwqerElXJUkUzSh0pp7RxHB/1lCxD +s7D1F1hlgFQuNIkBIgQQAQIADAUCTrzZHAUDABJ1AAAKCRCXELibyletfMUpB/4s +07dREULIBnA1D6qr3fHsQJNZqbAuyDlvgGGLWzoyEDs+1JMFFlaa+EeLIo1386GU +2DammDC23p3IB79uQhJeD2Z1TcVg4cA64SfF/CHca5coeRSrdAiudzU/cgLGtXIP +/OaFamXgdMxAhloLFbSHPCZkyb00phVa8+xeIVDrK1HByZsNIXy/SSK8U26S2PVZ +2o14fWvKbJ1Aga8N6DuWY/D8P2mi3RAbiuZgfzkmKL5idH/wSKfnFKdTgJzssdCc +1jZEGVk5rFYcWOrJARHeP/tsnb/UxKBEsNtO7e3N2e/rLVnEykVIO066hz7xZK/V +NBSpx3k3qj4XPK41IHy2iQEiBBABAgAMBQJOzqO8BQMAEnUAAAoJEJcQuJvKV618 +2twH/0IzjXLxN45nvIfEjC75a+i9ZSLlqR8lsHL4GpEScFKI0a0lT4IVAIY2RKG+ +MAs2eHm0UfKuwGs5jluRZ9RqKrc61sY0XQV9/7znY9Db16ghX04JjknOKs/fPi87 +rvKkB/QxJWS8qbb/erRmW+cPNjbRxTFPS5JIwFWHA16ieFEpvdAgKV6nfvJVTq1r +jPDcnIA9CJN2SmUFx9Qx3SRc6ITbam1hjFnY6sCh6AUhxLI2f1mq1xH9PqEy42Um +68prRqTyJ7Iox1g/UDDkeeUcAg7T1viTz7uXpS3Wrq4zzo4yOpaJfLDR3pI5g2Zk +SNGTMo6aySE4OABt8i1Pc1Pm6AmJASIEEAECAAwFAk7yPFYFAwASdQAACgkQlxC4 +m8pXrXzXiAf9FrXe0lgcPM+tYOWMLhv5gXJi2VUBaLxpyRXm/kJcmxInKq1GCd3y +D4/FLHNu3ZcCz/uklPAbZXWI0O6ewq0LWsRtklmJjWiedH+hGyaTv95VklojRIBd +8nBaJ6M98rljMBHTFwWvjQFVf4FLRJQZqHlvjcCkq2Dd9BWJpGXvr/gpKkmMJYNK +/ftfZRcChb35NI19WRpOhj9u808OPcqKVvZBcPwFGV5cEBzmAC94J7JcD8+S8Ik8 +iUJMQGGL3QcmZOBozovh86hj7KTSEBHlLXl832z89H1hLeuLbnXoGLv3zeUFSxkv +1h35LhZLqIMDQRXLuUzxGHMBpLhPyGWRJ4kBIgQQAQIADAUCTwQJFwUDABJ1AAAK +CRCXELibyletfABvB/9Cy69cjOqLGywITs3Cpg//40jmdhSAVxilJivP6J5bubFH +DJlVTx541Dv5h4hTG2BQuueQ4q1VCpSGW+rHcdhPyvmZGRz1rxdQQGh1Dv0Bod2c +3PJVSYPSrRSwCZJkJHOtVRBdjK4mkZb5aFTza+Tor9kxzj4FcXVd4KAS+hHQHYHc +Ar8tt2eOLzqdEFTULeGiSoNn+PVzvzdfhndphK+8F2jfQ2UKuc01O7k0Yn9xZVx0 +OG6fE1gStzLv7C5amWLRd8+xh+MN0G8MgNglpBoExsEMMlPBYSUHa6lxpdMNMuib +rIyVncE9X8QOhImt8K0sNn/EdbuldJNGYbDLt7O4iQEiBBABAgAMBQJPFdTcBQMA +EnUAAAoJEJcQuJvKV6184owH+wZ/uLpezXnSxigeH1sig72QEXMrNd5DVHCJdig3 +bo+K5YmmN710/m5z+63XKUEWpd6/knajObgckThzWftNeK1SSFQGPmoYZP9EZnSU +7L+/dSUpExbj842G5LYagrCyMGtlxRywWEmbi72TKS/JOK0jLiOdvVy+PHrZSu0D +TVQ7cJh1BmPsbz7zzxjmcI5l+7B7K7RHZHq45nDLoIabwDacj7BXvBK0Ajqz4QyJ +GQUjXC7q+88I+ptPvOXlE5nI/NbiCJOMI6d/bWN1KwYrC80fZuFaznfQFcPyUaDw +yRaun+K3kEji2wXecq+yMmLUEp01TKsUeOL50HD6hHH07W+JASIEEAECAAwFAk85 +bQsFAwASdQAACgkQlxC4m8pXrXwKPQgAlkbUsTr7nkq+haOk0jKpaHWEbRMEGMrB +I3F7E+RDO6V/8y4Jtn04EYDc8GgZMBah+mOgeINq3y8jRMYV5jVtZXv2MWYFUcjM +kVBKeqhi/pGEjmUdmdt3DlPv3Z+fMTMRmAocI981iY/go8PVPg/+nrR6cFK2xxnO +R8TacikJBFeSfkkORg1tDzjjYv1B5ZIEkpplepl5ahJBBq7cpYhTdY6Yk0Sz0J8w +EdffLSaNxrRuWLrRhWzZU7p9bFzfb/7OHc21dJnB7wKv5VvtgE+jiQw9tOKaf5hc +SgRYuF6heu+B25gc5Uu88lo409mZ7oxQ6hDCn7JHvzh0rhmSN+Kid4kBIgQQAQIA +DAUCT0qQrQUDABJ1AAAKCRCXELibyletfC9UB/4o2ggJYM0CLxEpP0GU8UKOh3+/ +zm1DN7Qe4kY2iCtF1plKHQaTgt5FlgRCFaiXcVv7WzGz/FnmxonR1leLl+kfRlwy +PPnoI/AWPCy/NO4Cl5KnjsSmsdDUpObwZ4KYsdilZR7ViJu2swdAIgnXBUwrlRJR +7CK4TAKrTeonRgVSrVx8Vt//8/cYj73CLq8oY/KK0iHiQrSwo44uyhdiFIAssjyX +n6/2E+w0zgvPexNSNNROHQ8pjbq+NTY6GwKIGsaej3UTRwQ7psvKXz8y7xdzmOAr +/khGvxB5gjkx02pimjeia8v66aH6rbnojJMAovNUS4EHdHnulv4rovC8Kf9iiQEi +BBABAgAMBQJPVdsaBQMAEnUAAAoJEJcQuJvKV618vVEIALFXPBzcAO1SnQarBLzy +YMVZZumPvSXKnUHAO+6kjApXPJ+qFRdUaSNshZxVKY9Zryblu4ol/fLUTt0CliSD +IxD6L4GXEm4VYYCl4lPO3bVsJnGITLFwQGHM27EmjVoTiD8Ch7kPq2EXr3dMRgzj +pdz+6aHGSUfOdLTPXufDvW83bEWGaRVuTJKw+wIrcuRqQ+ucWJgJGwcE4zeHjZad +Jx1XUm1X+BbI73uiQussyjhhQVVNU7QEdrjyuscaZ/H38wjUwNbylxDPB4I8quC1 +knQ0wSHr7gKpM+E9nhiS14poRqU18u78/sJ2MUPXnQA6533IC238/LP8JgqB+BiQ +BTSJASIEEAECAAwFAk9ng3cFAwASdQAACgkQlxC4m8pXrXxQRAf/UZlkkpFJj1om +9hIRz7gS+l7YvTaKSzpo+TBcx3C7aqKJpir6TlMK9cb9HGTHo2Xp1N3FtQL72NvO +6CcJpBURbvSyb4i0hrm/YcbUC4Y3eajWhkRS3iVfGNFbc/rHthViz0r6Y5lhXX16 +aVkDv5CIFWaF3BiUK0FnHrZiy4FPacUXCwEjv3uf8MpxV5oEmo8Vs1h4TL3obyUz +qrImFrEMYE/12lkE8iR5KWCaF8eFyl56HL3PPl90JMQBXzhwsFoWCPuwjfM5w6sW +Ll//zynwxtlJ9CRz9c2vK6aJ8DRu3OfBKN1iiEcNEynksDnNXErn5xXKz3p5pYdq +e9BLzUQCDYkBIgQQAQIADAUCT3inRgUDABJ1AAAKCRCXELibyletfGMKCADJ97qk +geBntQ+tZtKSFyXznAugYQmbzJld8U6eGSQnQkM40Vd62UZLdA8MjlWKS8y4A4L2 +0cI14zs5tKG9Q72BxQOw5xkxlLASw1/8WeYEbw7ZA+sPG//q9v3kIkru3sv64mMA +enZtxsykexRGyCumxLjzlAcL1drWJGUYE2Kl6uzQS7jb+3PNBloQvz6nb3YRZ+Cg +Ly9D41SIK+fpnV8r4iqhu7r4LmAQ7Q1DF9aoGaYvn2+xLGyWHxJAUet4xkMNOLp6 +k9RF1nbNe4I/sqeCB25CZhCTEvHdjSGTD2yJR5jfoWkwO9w8DZG1Q9WrWqki4hSB +l0cmcvO34pC1SJYziQEiBBABAgAMBQJPinQFBQMAEnUAAAoJEJcQuJvKV618CFEI +AJp5BbcV7+JBMRSvkoUcAWDoJSP2ug9zGw5FB8J90PDefKWCKs5Tjayf2TvM5ntq +5DE9SGaXbloIwa74FoZlgqlhMZ4AtY9Br+oyPJ5S844wpAmWMFc6NnEPFaHQkQ+b +dJYpRVNd9lzagJP261P3S+S9T2UeHVdOJBgWIq9Mbs4lnZzWsnZfQ4Lsz0aPqe48 +tkU8hw+nflby994qIwNOlk/u+I/lJbNz5zDY91oscXTRl2jV1qBgKYwwCXxyB3j9 +fyVpRl+7QnqbTWcCICVFL+uuYpP0HjdoKNqhzEguAUQQLOB9msPTXfa2hG+32ZYg +5pzI5V7GCHq0KO6u5Ctj3TGJASIEEAECAAwFAk+cQEEFAwASdQAACgkQlxC4m8pX +rXzi7AgAx8wJzNdD7UlgdKmrAK//YqH7arSssb33Xf45sVHDpUVA454DXeBrZpi+ +zEuo03o5BhAuf38cwfbkV6jN1mC2N0FZfpy4v7RxHKLYr7tr6r+DRn1L1giX5ybx +CgY0fLAxkwscWUKGKABWxkz9b/beEXaO2rMt+7DBUdpAOP5FNRQ8WLRWBcMGQiaT +S4YcNDAiNkrSP8CMLQP+04hQjahxwCgBnksylciqz3Y5/MreybNnTOrdjVDsF0Oe +t0uLOiWXUZV1FfaGIdb/oBQLg+e1B74p5+q3aF8YI97qAZpPa1qiQzWIDX8LX9QX +EFyZ3mvqzGrxkFoocXleNPgWT8fRuokBIgQQAQIADAUCT64N/QUDABJ1AAAKCRCX +ELibyletfDOGCACKfcjQlSxrWlEUrYYZpoBP7DE+YdlIGumt5l6vBmxmt/5OEhqr ++dWwuoiyC5tm9CvJbuZup8anWfFzTTJmPRPsmE4z7Ek+3CNMVM2wIynsLOt1pRFK +4/5RNjRLbwI6EtoCQfpLcZJ//SB56sK4DoFKH28Ok4cplESPnoMqA3QafdSEA/FL +qvZV/iPgtTz7vjQkMgrXAIUM4fvKe3iXkAExGXtmgdXHVFoKmHrxJ2DTSvM7/19z +jGJeu2MhIKHyqEmCk6hLjxyCE5pAH59KlbAQOP1bS28xlRskBApm2wN+LOZWzC62 +HhEReQ50inCGuuubK0PqUQnyYc+lUFxrFpcliQEiBBABAgAMBQJPv9lVBQMAEnUA +AAoJEJcQuJvKV618AzgH/iRFFCi4qjvoqji1fi7yNPZVOMMO2H13Ks+AfcjRtHuV +aa30u50ND7TH+XQe6yerTapLh3aAm/sNP99aTxIuwRSlyKEoDs93+XVSgRqPBgbF +/vxv0ykok3p6L9DxFO/w5cL8JrBhMZoJrEkIBFkwN8tWlcXPRFQvcdBYv3M3DTZU +qY+UHnOxHvSzsl+LJ0S9Xcd9C5bvYfabmYJvG5eRS3pj1L/y3a6yw6hvY+JtnQAk +t05TdeHMIgQH/zb8V9wxDzmE0un8LyoC2Jx5TpikQsJSejwK6b3coxVBlngku6+C +qDAimObZLw6H9xYYIK0FoJs7j5bQZEwUO7OLBgjcMOqJASIEEAECAAwFAk/Rpc8F +AwASdQAACgkQlxC4m8pXrXw49Qf/TdNbun2htQ+cRWarszOx8BLEiW/x6PVyUQpZ +nV/0qvhKzlJUjM9hQPcA0AsOjhqtCN6Cy8KXbK/TvPm9D/Nk6HWwD1PomzrJVFk2 +ywGFIuTR+lluKSp7mzm5ym0wJs5cPq731Im31RUQU8ndjLrq9YOf5FVL8NqmcOAU +4E8d68BbmVCQC5MMr0901FKwKznShfpy7VYN25/BASj8dhnynBYQErqToOJB6Cnd +JhdTlbfR4SirqAYZZg3XeqGhByytEHE1x7FMWWFYhdNtsnAVhYBbWqAzBs8lF9Jd +Mhaf0VQU/4z10gVrRtXLR/ixrCi+P4cM/fOQkqd6pwqWkaXt6okBIgQQAQIADAUC +T+NxIAUDABJ1AAAKCRCXELibyletfFBBCAC6+0TUJDcNaqOxOG1KViY6KYg9NCL8 +pwNK+RKNK/N1V+WGJQH7qDMwRoOn3yogrHax4xIeOWiILrvHK0O6drS1DjsymIhR +Sm2XbE/8pYmEbuJ9vHh3b/FTChmSAO7dDjSKdWD3dvaY8lSsuDDqPdTX8FzOfrXC +M22C/YPg7oUG2A5svE1b+yismP4KmVNWAepEuPZcnEMPFgop3haHg9X2+mj/btDB +Yr6p9kAgIY17nigtNTNjtI0dMLu43aIzedCYHqOlNHiB049jkJs54fMGBjF9qPtc +m0k44xyKd1/JXWMdNUmtwKsChAXJS3YOciMgIx6tqYUTndrP4I6q1rfriQEiBBAB +AgAMBQJP9T1VBQMAEnUAAAoJEJcQuJvKV618J9wIAI1lId9SMbEHF6PKXRe154lE +pap5imMU/lGTj+9ZcXmlf8o2PoMMmb3/E1k+EZUaeSBoOmjS8C2gwd5XFwRrlwAD +RlK/pG5XsL4h5wmN2fj1ororrJXvqH427PLRQK9yzdwG4+9HTBOxjoS8qZT9plyK +AJZzAydAMqyseRHgNo0vMwlgrs4ojo+GcFGQHrF3IaUjvVfUPOmIj7afopFdIZmI +GaSF0TXBzqcZ1chFv/eTBcIuIKRvlaDee5FgV7+nLH2nKOARCLvV/+8uDi2zbr83 +Ip5x2tD3XuUZ0ZWxD0AQWcrLdmGb4lkxbGxvCtsaJHaLXWQ2m760RjIUcwVMEBKJ +ASIEEAECAAwFAlAGYWsFAwASdQAACgkQlxC4m8pXrXwyVAgAvuvEl6yuGkniWOlv +uHEusUv/+2GCBg6qV+IEpVtbTCCgiFjYR5GasSp1gpZ5r4BocOlbGdjdJGHTpyK8 +xD1i+6qZWUYhNRg2POXUVzcNEl2hhouwPLOifcmTwAKU76TEv3L5STviL3hWgUR2 +yEUZ3Ut0IGVV6uPER9jpR3qd6O3PeuFkwf+NaGTye4jioLAy3aYwtZCUXzvYmNLP +90K4y+5yauZteLmNeq26miKC/NQu4snNFClPbGRjHD1ex9KDiAMttOgN4WEq7srT +rYgtT531WY4deHpNgoPlHPuAfC0H+S6YWuMbgfcb6dV+Rrd8Ij6zM3B/PcjmsYUf +OPdPtIkBIgQQAQIADAUCUBgtfQUDABJ1AAAKCRCXELibyletfAm3CACQlw21Lfeg +d8RmIITsfnFG/sfM3MvZcjVfEAtsY3fTK9NiyU0B3yX0PU3ei37qEW+50BzqiStf +5VhNvLfbZR+yPou7o2MAP31mq3Uc6grpTV64BRIkCmRWg40WMjNI1hv7AN/0atgj +ATYQXgnEw7mfFb0XZtMTD6cmrz/A9nTPVgZDxzopOMgCCC1ZK4Vpq9FKdCYUaHpX +3sqnDf+gpVIHkTCMgWLYQOeX5Nl+fgnq6JppaQ3ySZRUDr+uFUs0uvDRvI/cn+ur +ri92wdDnczjFumKvz/cLJAg5TG2Jv1Jx3wecALsVqQ3gL7f7vr1OMaqhI5FEBqdN +29L9cZe/ZmkriQEiBBIBCgAMBQJVoNxyBYMHhh+AAAoJEEoz7NUmyPxLD1EH/2eh +7a4+8A1lPLy2L9xcNt2bifLfFP2pEjcG6ulBoMKpHvuTCgtX6ZPdHpM7uUOje/F1 +CCN0IPB533U1NIoWIKndwNUJjughtoRM+caMUdYyc4kQm29Se6hMPDfyswXE5Bwe +PmoOm4xWPVOH/cVN04zyLuxdlQZNQF/nJg6PMsz4w5z+K6NGGm24NEPcc72iv+6R +Uc/ry/7v5cVu4hO5+r104mmNV5yLecQF13cHy2JlngIHXPSlxTZbeJX7qqxE7TQh +5nviSPgdk89oB5jFSx4g1efXiwtLlP7lbDlxHduomyQuH9yqmPZMbkJt9uZDc8Zz +MYsDDwlc7BIe5bGKfjqJAhwEEAECAAYFAlSanFIACgkQdzHqU52lcqLdvg//cAEP +qdN5VTKWEoDFjDS4I6t8+0KzdDWDacVFwKJ8RAo1M2SklDxnIvnzysZd2VHp5Pq7 +i4LYCZo5lDkertQ6LwaQxc4X6myKY4LTA652ObFqsSfgh9kW+aJBBAyeahPQ8CDD ++Yl23+MY5wTsj4qt7KffNzy78vLbYnVnvRQ3/CboVix0SRzg0I3Oi7n3B0lihvXy +5goy9ikjzZevejMEfjfeRCgoryy9j5RvHH9PF3fJVtUtHCS4f+kxLmbQJ1XqNDVD +hlFzjz8oUzz/8YXy3im5MY7Zuq4P4wWiI7rkIFMjTYSpz/evxkVlkR74qOngT2pY +VHLyJkqwh56i0aXcjMZiuu2cymUt2LB9IsaMyWBNJjXr2doRGMAfjuR5ZaittmML +yZwix9mWVk7tkwlIxmT/IW6Np0qMhDZcWYqPRpf7+MqY3ZYMK4552b8aDMjhXrnO +OwLsz+UI4bZa1r9dguIWIt2C2b5C1RQ9AsQBPwg7h5P+HhRuFAuDKK+vgV8FRuzR +JeKkFqwB4y0Nv7BzKbFKmP+V+/krRv+/Dyz9Bz/jyAQgw02u1tPupH9BGhlRyluN +yCJFTSNj7G+OLU0/l4XNph5OOC7sy+AMZcsL/gsT/TXCizRcCuApNTPDaenACpbv +g8OoIzmNWhh4LXbAUHCKmY//hEw9PvTZA1xKHgyJAhwEEgECAAYFAlJYsKQACgkQ +oirk60MpxUV2XQ//b2/uvThkkbeOegusDC4AZfjnL/V3mgk4iYy4AC9hum0R9oNl +XDR51P1TEw9mC1btHj+7m7Iq1a5ke5wIC7ENZiilr0yPqeWgL5+LC98dz/L85hqA +wIoGeOfMhrlaVbAZEj4yQTAJDA35vZHVsQmp87il0m+fZX04OBLXBzw86EoAAZ7Q +EoH4qFcT9k1T363tvNnIm3mEvkQ5WjE1R9uchJa1g7hdlNQlVkjFmPZrJK9fl4z5 +6Dto89Po4Sge48jDH0pias4HATYHsxW819nz5jZzGcxLnFRRR5iITVZi9qzsHP7N +bUh3qxuWCHS9xziXpOcSZY848xXw63Y5jDJfpzupzu/KHj6CzXYJUEEqp9MluoGb +/BCCEPzdZ0ovyxFutM/BRcc6DvE6sTDF/UES21ROqfuwtJ6qJYWX+lBIgyCJvj4o +RdbzxUleePuzqCzmwrIXtoOKW0Rlj4SCeF9yCwUMBTGW5/nCLmN4dwf1KW2RP2Eg +4ERbuUy7QnwRP5UCl+0ISZJyYUISfg8fmPIdQsetUK9Cj+Q5jpB2GXwELXWnIK6h +K/6jXp+EGEXSqdIE53vAFe7LwfHiP/D5M71D2h62sdIOmUm3lm7xMOnM5tKlBiV+ +4jJSUmriCT62zo710+6iLGqmUUYlEll6Ppvo8yuanXkYRCFJpSSP7VP0bBqIZgQT +EQIAJgUCTnc9dgIbIwUJEPPzpwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEIxx +jTtQcuH1Ut4AoIKjhdf70899d+7JFq3LD7zeeyI0AJ9Z+YyE1HZSnzYi73brScil +bIV6sbQ7TXlTUUwgUGFja2FnZSBzaWduaW5nIGtleSAod3d3Lm15c3FsLmNvbSkg +PGJ1aWxkQG15c3FsLmNvbT6IbwQwEQIALwUCTnc9rSgdIGJ1aWxkQG15c3FsLmNv +bSB3aWxsIHN0b3Agd29ya2luZyBzb29uAAoJEIxxjTtQcuH1tT0An3EMrSjEkUv2 +9OX05JkLiVfQr0DPAJwKtL1ycnLPv15pGMvSzav8JyWN3IhlBBMRAgAdBQJHrJS0 +BQkNMFioBQsHCgMEAxUDAgMWAgECF4AAEgkQjHGNO1By4fUHZUdQRwABAa6SAJ9/ +PgZQSPNeQ6LvVVzCALEBJOBt7QCffgs+vWP18JutdZc7XiawgAN9vmmITAQTEQIA +DAUCPj6j0QWDCWYAuwAKCRBJUOEqsnKR8iThAJ9ZsR4o37dNGyl77nEqP6RAlJqa +YgCeNTPTEVY+VXHR/yjfyo0bVurRxT2ITAQTEQIADAUCPkKCAwWDCWIiiQAKCRC2 +9c1NxrokP5aRAKCIaaegaMyiPKenmmm8xeTJSR+fKQCgrv0TqHyvCRINmi6LPucx +GKwfy7KIRgQQEQIABgUCP6zjrwAKCRCvxSNIeIN0D/aWAKDbUiEgwwAFNh2n8gGJ +Sw/8lAuISgCdHMzLAS26NDP8T2iejsfUOR5sNriIRgQQEQIABgUCP7RDdwAKCRCF +lq+rMHNOZsbDAJ0WoPV+tWILtZG3wYqg5LuHM03faQCeKuVvCmdPtro06xDzeeTX +VrZ14+GIRgQQEQIABgUCQ1uz6gAKCRCL2C5vMLlLXH90AJ0QsqhdAqTAk3SBnO2w +zuSOwiDIUwCdFExsdDtXf1cL3Q4ilo+OTdrTW2CIRgQTEQIABgUCRPEzJgAKCRD2 +ScT0YJNTDApxAKCJtqT9LCHFYfWKNGGBgKjka0zi9wCcCG3MvnvBzDUqDVebudUZ +61Sont+ITAQQEQIADAUCQYHLAQWDBiLZiwAKCRAYWdAfZ3uh7EKNAJwPywk0Nz+Z +Lybw4YNQ7H1UxZycaQCePVhY4P5CHGjeYj9SX2gQCE2SNx+ITAQQEQIADAUCQYHL +NAWDBiLZWAAKCRCBwvfr4hO2kiIjAJ0VU1VQHzF7yYVeg+bh31nng9OOkwCeJI8D +9mx8neg4wspqvgXRA8+t2saITAQQEQIADAUCQYHLYgWDBiLZKgAKCRBrcOzZXcP0 +cwmqAJsFjOvkY9c5eA/zyMrOZ1uPB6pd4QCdGyzgbYb/eoPu6FMvVI9PVIeNZReI +TAQQEQIADAUCQdCTJAWDBdQRaAAKCRB9JcoKwSmnwmJVAKCG9a+Q+qjCzDzDtZKx +5NzDW1+W+QCeL68seX8OoiXLQuRlifmPMrV2m9+ITAQQEQIADAUCQitbugWDBXlI +0gAKCRDmG6SJFeu5q/MTAKCTMvlCQtLKlzD0sYdwVLHXJrRUvgCffmdeS6aDpwIn +U0/yvYjg1xlYiuqITAQSEQIADAUCQCpZOgWDB3pLUgAKCRA8oR80lPr4YSZcAJwP +4DncDk4YzvDvnRbXW6SriJn1yQCdEy+d0CqfdhM7HGUs+PZQ9mJKBKqITAQSEQIA +DAUCQD36ugWDB2ap0gAKCRDy11xj45xlnLLfAKC0NzCVqrbTDRw25cUss14RRoUV +PACeLpEc3zSahJUB0NNGTNlpwlTczlCITAQSEQIADAUCQQ4KhAWDBpaaCAAKCRA5 +yiv0PWqKX/zdAJ4hNn3AijtcAyMLrLhlZQvib551mwCgw6FEhGLjZ+as0W681luc +wZ6PzW+ITAQSEQIADAUCQoClNAWDBSP/WAAKCRAEDcCFfIOfqOMkAJwPUDhS1eTz +gnXclDKgf353LbjvXgCeLCWyyj/2d0gIk6SqzaPl2UcWrqiITAQTEQIADAUCPk1N +hAWDCVdXCAAKCRAtu3a/rdTJMwUMAKCVPkbk1Up/kyPrlsVKU/Nv3bOTZACfW5za +HX38jDCuxsjIr/084n4kw/uITAQTEQIADAUCQdeAdgWDBc0kFgAKCRBm79vIzYL9 +Pj+8AJ9d7rvGJIcHzTCSYVnaStv6jP+AEACeNHa5yltqieRBCCcLcacGqYK81omI +TAQTEQIADAUCQhiBDgWDBYwjfgAKCRB2wQMcojFuoaDuAJ9CLYdysef7IsW42UfW +hI6HjxkzSgCfeEpXS4hEmmGicdpRiJQ/W21aB0GIZQQTEQIAHQULBwoDBAMVAwID +FgIBAheABQJLcC/KBQkQ8/OnABIHZUdQRwABAQkQjHGNO1By4fWw2wCeJilgEarL +8eEyfDdYTyRdqE45HkoAnjFSZY8Zg/iXeErHI0r04BRukNVgiHsEMBECADsFAkJ3 +NfU0HQBPb3BzLi4uIHNob3VsZCBoYXZlIGJlZW4gbG9jYWwhIEknbSAqc28qIHN0 +dXBpZC4uLgAKCRA5yiv0PWqKX+9HAJ0WjTx/rqgouK4QCrOV/2IOU+jMQQCfYSC8 +JgsIIeN8aiyuStTdYrk0VWCIjwQwEQIATwUCRW8Av0gdAFNob3VsZCBoYXZlIGJl +ZW4gYSBsb2NhbCBzaWduYXR1cmUsIG9yIHNvbWV0aGluZyAtIFdURiB3YXMgSSB0 +aGlua2luZz8ACgkQOcor9D1qil+g+wCfcFWoo5qUl4XTE9K8tH3Q+xGWeYYAnjii +KxjtOXc0ls+BlqXxbfZ9uqBsiQIiBBABAgAMBQJBgcuFBYMGItkHAAoJEKrj5s5m +oURoqC8QAIISudocbJRhrTAROOPoMsReyp46Jdp3iL1oFDGcPfkZSBwWh8L+cJjh +dycIwwSeZ1D2h9S5Tc4EnoE0khsS6wBpuAuih5s//coRqIIiLKEdhTmNqulkCH5m +imCzc5zXWZDW0hpLr2InGsZMuh2QCwAkB4RTBM+r18cUXMLV4YHKyjIVaDhsiPP/ +MKUj6rJNsUDmDq1GiJdOjySjtCFjYADlQYSD7zcd1vpqQLThnZBESvEoCqumEfOP +xemNU6xAB0CL+pUpB40pE6Un6Krr5h6yZxYZ/N5vzt0Y3B5UUMkgYDSpjbulNvaU +TFiOxEU3gJvXc1+h0BsxM7FwBZnuMA8LEA+UdQb76YcyuFBcROhmcEUTiducLu84 +E2BZ2NSBdymRQKSinhvXsEWlH6Txm1gtJLynYsvPi4B4JxKbb+awnFPusL8W+gfz +jbygeKdyqzYgKj3M79R3geaY7Q75Kxl1UogiOKcbI5VZvg47OQCWeeERnejqEAdx +EQiwGA/ARhVOP/1l0LQA7jg2P1xTtrBqqC2ufDB+v+jhXaCXxstKSW1lTbv/b0d6 +454UaOUV7RisN39pE2zFvJvY7bwfiwbUJVmYLm4rWJAEOJLIDtDRtt2h8JahDObm +3CWkpadjw57S5v1c/mn+xV9yTgVx5YUfC/788L1HNKXfeVDq8zbAiQIiBBMBAgAM +BQJCnwocBYMFBZpwAAoJENjCCglaJFfPIT4P/25zvPp8ixqV85igs3rRqMBtBsj+ +5EoEW6DJnlGhoi26yf1nasC2frVasWG7i4JIm0U3WfLZERGDjR/nqlOCEqsP5gS3 +43N7r4UpDkBsYh0WxH/ZtST5llFK3zd7XgtxvqKL98l/OSgijH2W2SJ9DGpjtO+T +iegq7igtJzw7Vax9z/LQH2xhRQKZR9yernwMSYaJ72i9SyWbK3k0+e95fGnlR5pF +zlGq320rYHgD7v9yoQ2t1klsAxK6e3b7Z+RiJG6cAU8o8F0kGxjWzF4v8D1op7S+ +IoRdB0Bap01ko0KLyt3+g4/33/2UxsW50BtfqcvYNJvU4bZns1YSqAgDOOanBhg8 +Ip5XPlDxH6J/3997n5JNj/nk5ojfd8nYfe/5TjflWNiput6tZ7frEki1wl6pTNbv +V9C1eLUJMSXfDZyHtUXmiP9DKNpsucCUeBKWRKLqnsHLkLYydsIeUJ8+ciKc+EWh +FxEY+Ml72cXAaz5BuW9L8KHNzZZfez/ZJabiARQpFfjOwAnmhzJ9r++TEKRLEr96 +taUI9/8nVPvT6LnBpcM38Td6dJ639YvuH3ilAqmPPw50YvglIEe4BUYD5r52Seqc +8XQowouGOuBX4vs7zgWFuYA/s9ebfGaIw+uJd/56Xl9ll6q5CghqB/yt1EceFEnF +CAjQc2SeRo6qzx22iEYEEBECAAYFAkSAbycACgkQCywYeUxD5vWDcACfQsVk/XGi +ITFyFVQ3IR/3Wt7zqBMAoNhso/cX8VUfs2BzxPvvGS3y+5Q9iEYEEBECAAYFAkUw +ntcACgkQOI4l6LNBlYkyFgCbBcw5gIii0RTDJsdNiuJDcu/NPqEAniSq9iTaLjgF +HZbaizUU8arsVCB5iEYEEBECAAYFAkWho2sACgkQu9u2hBuwKr6bjwCfa7ZK6O+X +mT08Sysg4DEoZnK4L9UAoLWgHuYg35wbZYx+ZUTh98diGU/miF0EExECAB0FAj4+ +owwFCQlmAYAFCwcKAwQDFQMCAxYCAQIXgAAKCRCMcY07UHLh9XGOAJ4pVME15/DG +rUDohtGv2z8a7yv4AgCeKIp0jWUWE525QocBWms7ezxd6syIXQQTEQIAHQUCR6yU +zwUJDTBYqAULBwoDBAMVAwIDFgIBAheAAAoJEIxxjTtQcuH1dCoAoLC6RtsD9K3N +7NOxcp3PYOzH2oqzAKCFHn0jSqxk7E8by3sh+Ay8yVv0BYhdBBMRAgAdBQsHCgME +AxUDAgMWAgECF4AFAkequSEFCQ0ufRUACgkQjHGNO1By4fUdtwCfRNcueXikBMy7 +tE2BbfwEyTLBTFAAnifQGbkmcARVS7nqauGhe1ED/vdgiF0EExECAB0FCwcKAwQD +FQMCAxYCAQIXgAUCS3AuZQUJEPPyWQAKCRCMcY07UHLh9aA+AKCHDkOBKBrGb8tO +g9BIub3LFhMvHQCeIOOot1hHHUlsTIXAUrD8+ubIeZaJARwEEgECAAYFAkvCIgMA +CgkQ3PTrHsNvDi8eQgf/dSx0R9Klozz8iK79w00NOsdoJY0Na0NTFmTbqHg30XJo +G62cXYgc3+TJnd+pYhYi5gyBixF/L8k/kPVPzX9W0YfwChZDsfTw0iDVmGxOswiN +jzSo0lhWq86/nEL30Khl9AhCC1XFNRw8WZYq9Z1qUXHHJ2rDARaedvpKHOjzRY0N +dx6R2zNyHDx2mlfCQ9wDchWEuJdAv0uHrQ0HV9+xq7lW/Q3L/V5AuU0tiowyAbBL +PPYrB6x9vt2ZcXS7BOy8SfQ1i8W2QDQ/Toork4YwBiv6WCW/ociy7paAoPOWV/Nf +2S6hDispeecbk7wqpbUj5klDmwrlgB/jmoAXWEnbsYkBIgQQAQIADAUCSSpooAUD +ABJ1AAAKCRCXELibyletfFOMCACpP+OVZ7lH/cNY+373c4FnSI0/S5PXS0ABgdd4 +BFWRFWKrWBeXBGc8sZfHOzVEwkzV96iyHbpddeAOAkEA4OVPW1MMFCmlHxi2s9/N +JrSrTPVfQOH5fR9hn7Hbpq/ETw0IoX1FKo7vndMnHZnFEnI+PDXLcdMYQgljYzhT +xER4vYY0UKu8ekSshUy4zOX7XSJxwqPUvps8qs/TvojIF+vDJvgFYHVkgvS+shp8 +Oh/exg9vKETBlgU87Jgsqn/SN2LrR/Jhl0aLd0G0iQ+/wHmVYdQUMFaCZwk/BKNa +XPzmGZEUZ3RNbYa19Mo7hcE3js76nh5YMxFvxbTggVu4kdFkiQEiBBABAgAMBQJK +M06IBQMAEnUAAAoJEJcQuJvKV618F4gH/innejIHffGMk8jYix4ZZT7pW6ApyoI+ +N9Iy85H4L+8rVQrtcTHyq0VkcN3wPSwtfZszUF/0qP6P8sLJNJ1BtrHxLORYjJPm +gveeyHPzA2oJl6imqWUTiW822fyjY/azwhvZFzxmvbFJ+r5N/Z57+Ia4t9LTSqTN +HzMUYaXKDaAqzZeK7P0E6XUaaeygbjWjBLQ1O0ezozAy+Kk/gXApmDCGFuHSFe7Z +mgtFcbXLM2XFQpMUooETD2R8MUsd+xnQsff/k6pQOLxi+jUEsWSr/iqmvlk6gZ4D +pemBjuhcXYlxJYjUaX9Zmn5s+ofF4GFxRqXoY7l9Z+tCM9AX37lm6S+JASIEEAEC +AAwFAkpEcgoFAwASdQAACgkQlxC4m8pXrXz2mgf/RQkpmMM+5r8znx2TpRAGHi5w +ktvdFxlvPaOBWE28NDwTrpcoMqo9kzAiuvEQjVNihbP21wR3kvnQ84rTAH0mlC2I +uyybggpqwzOUl+Wi0o+vk8ZA0A0dStWRN8uqneCsd1XnqDe1rvqC4/9yY223tLmA +kPvz54ka2vX9GdJ3kxMWewhrVQSLCktQpygU0dujGTDqJtnk0WcBhVF9T87lv3W2 +eGdPielzHU5trXezmGFj21d56G5ZFK8co7RrTt4qdznt80glh1BTGmhLlzjMPLTe +dcMusm3D1QB9ITogcG94ghSf9tEKmmRJ6OnnWM5Kn9KcL63E5oj2/lY9H54wSYkB +IgQQAQIADAUCSlY+RwUDABJ1AAAKCRCXELibyletfOOQB/0dyJBiBjgf+8d3yNID +pDktLhZYw8crIjPBVdOgX12xaUYBTGcQITRVHSggzffDA5BQXeUuWhpL4QB0uz1c +EPPwSMiWiXlBtwF5q6RVf3PZGJ9fmFuTkPRO7SruZeVDo9WP8HjbQtOLukYf566e +grzAYR9p74UgWftpDtmrqrRTobiuvsFBxosbeRCvEQCrN0n+p5D9hCVB88tUPHnO +WA4mlduAFZDxQWTApKQ92frHiBqy+M1JFezz2OM3fYN+Dqo/Cb7ZwOAA/2dbwS7o +y4sXEHbfWonjskgPQwFYB23tsFUuM4uZwVEbJg+bveglDsDStbDlfgArXSL/0+ak +lFcHiQEiBBABAgAMBQJKaAqEBQMAEnUAAAoJEJcQuJvKV618rH0H/iCciD4U6YZN +JBj0GN7/Xt851t9FWocmcaC+qtuXnkFhplXkxZVOCU4VBMs4GBoqfIvagbBTyfV4 +Di+W8Uxr+/1jiu3l/HvoFxwdwNkGG6zNBhWSjdwQpGwPvh5ryV1OfLX/mgQgdDmx +vqz5+kFDUj4m7uLaeuU2j1T0lR4zU0yAsbt7J3hwfqJCXHOc9bm5nvJwMrSm+sdC +TP5HjUlwHr9mTe8xuZvj6sO/w0P4AqIMxjC9W7pT9q0ofG2KSTwt7wFbh05sbG4U +QYOJe4+Soh3+KjAa1c0cvmIh4cKX9qfCWwhhdeNfh1A9VTHhnl5zTv/UjvnQtjhl +H/Fq1eBSKcSJASIEEAECAAwFAkp5LgoFAwASdQAACgkQlxC4m8pXrXwY6wgAg3f8 +76L3qDZTYlFAWs3pXBl8GsUr1DEkTlEDZMZKDM3wPmhaWBR1hMA3y6p3aaCUyJIJ +BEneXzgyU9uqCxXpC78d5qc3xs/Jd/SswzNYuvuzLYOw5wN5L31SLmQTQ8KqE0uo +RynBmtDCQ4M2UKifSnv+0+3mPh85LVAS481GNpL+VVfCYtKesWNu40+98Yg6L9NG +WwRTfsQbcdokZo44Jz7Y7f81ObC4r/X1DgPj2+d4AU/plzDcdrbINOyprs+7340e +cnaGO4Lsgd19b1CvcgJgltRquu3kRvd+Ero2RYpDv6GVK8Ea0Lto4+b/Ae8cLXAh +QnaWQCEWmw+AU4Jbz4kBIgQQAQIADAUCSo5fvQUDABJ1AAAKCRCXELibyletfA08 +B/9w8yJdc8K+k07U30wR/RUg3Yb2lBDygmy091mVsyB0RGixBDXEPOXBqGKAXiV1 +QSMAXM2VKRsuKahY2HFkPbyhZtjbdTa7Pr/bSnPvRhAh9GNWvvRg2Kp3qXDdjv9x +ywEghKVxcEIVXtNRvpbqRoKmHzIExvUQck5DM1VwfREeYIoxgs4035WADhVMdngQ +S2Gt8P2WaU/p8EZhFGg6X8KtOlD68zGboaJe0hj2VDc+Jc+KdjRfE3fW5IToid/o +DkUaIW6tB3WkXb0g6D/2hrEJbX3headChHKSB8eQdOR9bcCJDhhU8csd501qmrhC +ctmvlpeWQZdIQdk6sABPWeeCiQEiBBABAgAMBQJKoBJHBQMAEnUAAAoJEJcQuJvK +V618Ml8H/1D88/g/p9fSVor4Wu5WlMbg8zEAik3BIxQruEFWda6nART6M9E7e+P1 +++UHZsWYs6l9ROpWxRLG1Yy9jLec2Y3nUtb20m65p+IVeKR2a9PHW35WZDV9dOYP +GZabKkO1clLeWLVgp9LRjZ+AeRG+ljHqsULXro1dwewLTB/gg9I2vgNv6dKxyKak +nM/GrqZLATAq2KoaE/u/6lzRFZIzZnLtjZh8X7+nS+V8v9IiY4ntrpkrbvFk30U6 +WJp79oBIWwnW/84RbxutRoEwSar/TLwVRkcZyRXeJTapbnLGnQ/lDO1o1d7+Vbjd +q/Sg/cKHHf7NthCwkQNsCnHL0f51gZCJASIEEAECAAwFAkqoEAAFAwASdQAACgkQ +lxC4m8pXrXwE/Af/XD4R/A5R6Ir/nCvKwCTKJmalajssuAcLEa2pMnFZYO/8rzLO ++Gp8p0qFH9C4LFwA0NvR5q6X/swuROf4zxljSvNcdlQVaAfJ2ZDEgJ5GXzsPplrv +SAI9jS3LL7fSWDZgKuUe0a4qx7A0NgyGMUYGhP+QlRFa8vWEBI9fANd/0mMqAeBV +qQyOH0X1FiW1Ca2Jn4NKfuMy9GEvRddVIbB1LvoNVtXPNzeeKMyNb9Jdx1MFWssy +COBP2DayJKTmjvqPEc/YOjOowoN5sJ/jn4mVSTvvlTooLiReSs6GSCAjMVxN7eYS +/Oyq6Iu1JDcJvmB8N2WixAZtAVgF8OA7CWXKVYkBIgQQAQIADAUCSrnHiQUDABJ1 +AAAKCRCXELibyletfPChB/9uECti1dZeNuFsd0/RuGyRUVlrrhJE6WCcOrLO9par +rPbewbKBmjSzB0MygJXGvcC06mPNuquJ7/WpxKsFmfg4vJBPlADFKtgRUy9BLzjC +eotWchPHFBVW9ftPbaQViSUu7d89NLjDDM5xrh80puDIApxoQLDoIrh3T1kpZx56 +jSWv0gelFUMbXAzmqkJSyL4Xdh1aqzgUbREd7Xf2ICzuh0sV6V7c/AwWtjWEGEsA +HZaiQDywZwbC18GwrMLiAzGWb/AScFDQRCZKJDjL+Ql8YT6z+ZMVr8gb7CIU5PKY +dhiIf2UVTQwLAoW7lNRCQQAqcGjK3IMIz7SO/yk4HmVUiQEiBBABAgAMBQJK3gjG +BQMAEnUAAAoJEJcQuJvKV618jkEH+wb0Zv9z7xQgpLMowVuBFQVu8/z7P5ASumyB +PUO3+0JVxSHBhlCKQK7n11m1fhuGt2fCxXhSU6LzXj36rsKRY53lGZ9QhvqFUtQH +3Xb2IQLIJC4UKjG2jSSCdcuA/x98bwp2v7O03rn7ndCS16CwXnRV3geQoNipRKMS +DajKPpZv1RiZm8pMKqEb8WSw352xWoOcxuffjlsOEwvJ85SEGCAZ9tmIlkZOc7Ai +QONDvii9b8AYhQ60RIQC0HP2ASSmK0V92VeFPxHmAygdDQgZNVtbVxgnnt7oTNEu +VRXNY+z4OfBArp7R+cTsvijDRZY4kML1n22hUybwoxUEvjqZV2+JASIEEAECAAwF +AkrvOlQFAwASdQAACgkQlxC4m8pXrXxrPAgArXiNgZirNuBhfNCXlkzkCHLx5wnV +e4SmTpbWzTwWw7+qk7d4l9hlWtdImISORINzo7f4ShSUzJX2GciNaXhaHRo7+y5O +Zbu82jQb09aQQj/nibKYuqxqUrobTEm+DuYz3JUQZm2PsPcHLS8mX9cxvrJUncPG +nXEV0DRaq71SGWDprtkvBbp6i38aY3sIhYgz8wM5m1szKDtjywmBYcFehIdozt9z +hm7wZshzRWQX1+Rf/pIsnk+OzBIa34crSemTnacbV/B7278z2XAyziPNFuqz0xu+ +iltOmYmayfNWAmumuw9NcuwWMlth6Mc2HLrpo0ZBheJ6iuDMPsHnwqdB/4kBIgQQ +AQIADAUCSwBd2gUDABJ1AAAKCRCXELibyletfP6tB/4m1w0BtlkJgtS6E+B/ns14 +z4A4PGors+n+MYm05qzvi+EnDF/sytCmVcKeimrtvDcfoDtKAFFvJjcYXfnJdGWm +Pu0SJMRL5KKCirAKwZmU/saxOgoB5QLNw+DHPteJ3w9GmWlGxIqG1r15WC5duzBC +y3FsnjJYG3jaLnHOO9yXXb5h0kUTORfUKdvAr1gxF2KoatZWqGoaPPnHoqb88rjt +zk8I7gDqoXnzh8wLxa0ZYvfTC/McxdWTrwXLft+krmMQ18iIZEne2hvVLNJVuluU +oiWLeHA8iNCQ4W4WTdLc1mCnCjGTMX/MN41uLH0C9Ka4R6wEaqj4lPDk1B/1TV+Q +iQEiBBABAgAMBQJLEYGrBQMAEnUAAAoJEJcQuJvKV618naIH/2t9aH5mBTKBN6fU +qhrf79vIsjtI/QNS5qisBISZMX3/1/0Gu6WnxkPSfdCUJMWCjMcnVj7KU2wxTHHG +VpAStd9r2afUNxRyqZwzwyytktuZok0XngAEDYDDBS3ssu2R4uWLCsC2ysXEqO/5 +tI5YrTWJZrfeIphTaYP5hxrMujvqy3kEwKKbiMz91cDeiLS+YCBcalj5n/1dMYf7 +8U8C6ieurxAg/L8h6x25VM4Ilx4MmG2T8QGtkkUXd+Fd/KYWmf0LE5LLPknf0Hhw +oVslPXeinp4FsHK/5wzviv4YZpzuTqs9NlKcMsa4IuuPOB0FDf0pn+OFQbEg9QwY +2gCozK+JASIEEAECAAwFAksjTdQFAwASdQAACgkQlxC4m8pXrXwlogf/XBGbXRVX +LMaRN4SczOjwT3/tUCriTkb3v+zKjRG90zFhYAccjn7w+7jKQicjq6quQG1EH2X4 +/Su6ps1lDLqGHHhiJW3ZhxQScLZmhdAYsh2qG4GP/UW3QjXG7c61t+H3olvWg2cr +wqCxxFZAgkAAkr9xcHWFZJEQeXoob6cCZObaUnHSANdmC6s5lUxXYa2bmL7Q3UB4 +4KCzDvAfbPZKJOw9k0qb3lc11zx+vGdyZFbm4R0+3LPp/vT0b3GlSbbF9lU1GOXh +VaphrgFFa76dmjfHCkPplXAkK1VSIU/aPGAefduTFMdlSZpdMtJ5AULjGcszBDlR +pLlPxvqVa0ZpgIkBIgQQAQIADAUCSycmkgUDABJ1AAAKCRCXELibyletfHlNCACp +1YespiHfQt2alcscE5zgfETEHHic8Ai6pNkU9HT4TeWcFHEDe5QqfYcpjLrQvBXS +kSvxEittbyRdv+e+j5Z+HyHjiG8nAQBL6qy9eHqQE4+d7gYs6DTk7sG9ZMYphREb +ltzD+F4hVCQdLT8LNr0eVFN7ehqECScDaCG8/Qyti+l/0M902/Yn+mz0ilOiUdWJ +9x6LPaIINtb1gsYDEylLjwGIZmI0r5Kh9wYoV4vnNezFbxO1uRiW0B7iaPjIEsbt +OOKp7wx2aX+DM3N9F3BtaIY8XnzcnomNm83SNsgmgrZljpQltUnNqIhNM8DupQ+I +WOV5gtl6pTC7CgeVTVyRiQEiBBABAgAMBQJLOGXuBQMAEnUAAAoJEJcQuJvKV618 +ll4IAKJ9mm4jb0c8fe9+uDI8eCJRbzNbVXm8zWzpA8GUtQAakwxoKv332QP1Wa1P +odni/e3EMhsSREOZJJv79YqGxGRBTE9Kb/VjM34nas4XSnXKW28XWhKyIw+XwQAi +nY2swFHh+83Htr/mwTdJfS2aEYl2zboBvd/JZCdhOGU2GH737S/3uEczoKkfVQ/w +OTM8X1xWwlYWqx23k/DsGcuDs9lA2g7Mx7DSqBtVjaTkn9h0zATzXLDkmP4SAUVj +cZ83WDpFre5WnizZjdXlBMM5OCexp5WpmzyHLTnaBFK4jEmnsk5C2Rnoyp8Ivz6g +Ecg1tRbEXijRw++d2TFYlJwLKtiJASIEEAECAAwFAktKMicFAwASdQAACgkQlxC4 +m8pXrXxqHQgAuYY5scKrh0m/GS9EYnyC9494lOlO6iytU0CpE6oBC31M3hfX/Dbj +UbcS5szZNU+2CPYo4ujQLZ7suN7+tTjG6pZFfMevajT9+jsL+NPMF8RLdLOVYmbl +TmSQGNO+XGEYaKYH5oZIeIW5AKCgi2ozkdFlBBLAx7Kqo/FyybhkURFEcvEyVmgf +3KLV7IIiX/fYLfoCMCJ/Lcm9/llSFB1n8Nvg66Xd533DKoHjueD3jyaNAVlo2mq/ +sIAv++kntvOiB3GDK5pfwHZ78WWiCpsWZpE5gzAnzJ1Y0WEigRo0PVLu3cLO0jLG +23d+H/CbfZ8rkajHJeCDQF7YVmP0t0nYpYkBIgQQAQIADAUCS1v+ZgUDABJ1AAAK +CRCXELibyletfNS/CACqt2TkB86mjqM+cJ74+dWBvJ2aFuURuxzm95i9Q/W/hU08 +2iMbC3+0k2oD8CrTOe61P+3oRyLjv/UEDUNzLncNe2YsA9JeV+4hvPwH5Vp3Om13 +089fCKZUbqslXNKkHiWYU+zAaZJXEuGRmRz0HbQIeAMOWF4oa226uo1e4ws1Jhc+ +F3E/ApCRyFBqBUdL05hapQLditYpsBjIdiBGpjzidMLE2wX2W4ZpAdN0U6BIyIqR +mTPjbSkvzS9kSWFmfhQgnBDKEYJpVZgE1sN52rYC1sDeGeiuKxlzjVov9MMhYMWa +Zo3R5o3F2iIM/BK6FbC252lf/Mhu3ICuXujNBZNYiQEiBBABAgAMBQJLbSH4BQMA +EnUAAAoJEJcQuJvKV618kd0IAJLLwDH6gvgAlBFklQJXqQxUdcSOOVMAWtlHgWOy +ozjgomZZBkRL8dtCDr9YBMcj5czcQ3qpmLJdppXhKB+kJV2iUXfDMSFXwJ4wLfIs +8FNnXw8H5U01oBkGH/Ku6ngL9Vwt+MjYHtCWkw9QueUKZnDudX9qIzLAIt+mwSTu +A6+fY4VWIg40AA0v3exaQM55YR/UhlKunpGG9o8Qkq77dMEbTMpOmBoLbOMRB3Dd +MAvVU6G2l6Pcb7KobVCuOBnb6batXARV/G8sw+nzfJ16fr/KobZT2A6m+Jrqk4dl +F14ljLbz16O5JGUPAryN2G2ddBdSAy7dtFSVhWWiWC9n88q5Ag0EPj6jHRAIAO/h +iX8WzHWOMLJT54x/axeDdqn1rBDf5cWmaCWHN2ujNNlgpx5emoU9v7QStsNUCOGB +bXkeO4Ar7YG+jtSR33zqNh3y5kQ0YkY3dQ0wh6nsl+wh4XIIY/3TUZVtmdJeUBRH +JlfVNFYad2hX1guFI37Ny1PoZAFsxO82g+XB/Se8r/+sbmVcONdcdIeFKrE3FjLt +IjNQcxC6l9Q2Oy8KDxG/zvUZG3+H5i3tdRMyGgmuD6gEV0GXOHYUopzLeit1+Aa0 +bCk36Mwbu+BeOw/CJW3+b0mB27hOaf9aCA855IP6fJFvtxcblq8nHIqhU3Dc9tec +sl9/S1xZ5S8ylG/xeRsAAwUH/i8KqmvAhq0X7DgCcYputwh37cuZlHOa1Ep07JRm +BCDgkdQXkGrsj2Wzw7Aw/TGdWWkmn2pxb8BRui5cfcZFO7c6vryi6FpJuLucX975 ++eVY50ndWkPXkJ1HF4i+HJwRqE2zliN/RHMs4LJcwXQvvjD43EE3AO6eiVFbD+qA +AdxUFoOeLblKNBHPG7DPG9xL+Ni5rkE+TXShxsB7F0z7ZdJJZOG0JODmox7IstQT +GoaU9u41oyZTIiXPiFidJoIZCh7fdurP8pn3X+R5HUNXMr7M+ba8lSNxce/F3kmH +0L7rsKqdh9d/aVxhJINJ+inVDnrXWVoXu9GBjT8Nco1iU9SIVAQYEQIADAUCTnc9 +7QUJE/sBuAASB2VHUEcAAQEJEIxxjTtQcuH1FJsAmwWK9vmwRJ/y9gTnJ8PWf0BV +roUTAKClYAhZuX2nUNwH4vlEJQHDqYa5yQ== +=HfUN +-----END PGP PUBLIC KEY BLOCK----- From 59e446d99df3b7f7f13f08cc3c82952afb6737fc Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 14 Nov 2017 12:05:02 -0800 Subject: [PATCH 535/783] Bump version to 0.4.10 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 5b5ad1d68..f7d30593d 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.9" + VERSION = "0.4.10" end From 4323fb57aba4953a992b6de9a92fd42f4fd35309 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 28 Jun 2016 12:03:46 -0400 Subject: [PATCH 536/783] Deflake statement_spec by preventing GC during assertion --- spec/mysql2/statement_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index e0fccad53..feaaae865 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -764,7 +764,9 @@ def stmt_count context 'close' do it 'should free server resources' do stmt = @client.prepare 'SELECT 1' + GC.disable expect { stmt.close }.to change(&method(:stmt_count)).by(-1) + GC.enable end it 'should raise an error on subsequent execution' do From 22b0d3c368ebcd6c16cdde6cdae7098869268b67 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 12 Mar 2015 12:05:41 -0700 Subject: [PATCH 537/783] Remove `{1.8.7,ree}` support --- .travis.yml | 22 ---------- Gemfile | 2 +- README.md | 3 +- Rakefile | 3 +- ext/mysql2/client.c | 42 ------------------ ext/mysql2/client.h | 33 -------------- ext/mysql2/extconf.rb | 6 +-- ext/mysql2/mysql2_ext.h | 2 - ext/mysql2/result.c | 60 -------------------------- ext/mysql2/statement.c | 31 +------------- lib/mysql2.rb | 4 +- lib/mysql2/client.rb | 2 +- lib/mysql2/error.rb | 8 +--- lib/mysql2/statement.rb | 2 +- mysql2.gemspec | 1 + spec/mysql2/client_spec.rb | 16 +------ spec/mysql2/error_spec.rb | 2 - spec/mysql2/result_spec.rb | 81 +++-------------------------------- spec/mysql2/statement_spec.rb | 61 ++++---------------------- tasks/compile.rake | 4 +- 20 files changed, 29 insertions(+), 356 deletions(-) diff --git a/.travis.yml b/.travis.yml index 07fb7e175..92f10ad4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,28 +27,6 @@ rvm: - ruby-head matrix: include: - - rvm: 1.8.7 - dist: precise - env: SPEC_OPTS="--order rand:11" - addons: - hosts: - - mysql2gem.example.com - apt: - packages: - - mysql-server-5.5 - - mysql-client-core-5.5 - - mysql-client-5.5 - - rvm: ree - dist: precise - env: SPEC_OPTS="--order rand:11" - addons: - hosts: - - mysql2gem.example.com - apt: - packages: - - mysql-server-5.5 - - mysql-client-core-5.5 - - mysql-client-5.5 - rvm: 2.4 env: DB=mariadb10.0 addons: diff --git a/Gemfile b/Gemfile index 86a7ddb11..34bbdcef7 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem 'rake-compiler', '~> 0.9.5' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' - gem 'rubocop', '~> 0.34.0' unless RUBY_VERSION =~ /1.8/ + gem 'rubocop', '~> 0.34.0' end group :benchmarks do diff --git a/README.md b/README.md index 6139232d4..92bfc9e66 100644 --- a/README.md +++ b/README.md @@ -509,8 +509,7 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x - * Ruby Enterprise Edition (based on MRI 1.8.7) + * Ruby MRI 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x * Rubinius 2.x and 3.x do work but may fail under some workloads This gem is tested with the following MySQL and MariaDB versions: diff --git a/Rakefile b/Rakefile index 75d0fbb0a..3d988cbc8 100644 --- a/Rakefile +++ b/Rakefile @@ -9,8 +9,7 @@ load 'tasks/generate.rake' load 'tasks/benchmarks.rake' # TODO: remove engine check when rubinius stops crashing on RuboCop -# TODO: remove defined?(Encoding) when we end support for < 1.9.3 -has_rubocop = if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && defined?(Encoding) +has_rubocop = if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' begin require 'rubocop/rake_task' RuboCop::RakeTask.new diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 224581452..a826b3258 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -19,12 +19,6 @@ extern VALUE mMysql2, cMysql2Error; static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args; -#ifndef HAVE_RB_HASH_DUP -VALUE rb_hash_dup(VALUE other) { - return rb_funcall(rb_cHash, intern_brackets, 1, other); -} -#endif - #define REQUIRE_INITIALIZED(wrapper) \ if (!wrapper->initialized) { \ rb_raise(cMysql2Error, "MySQL client is not initialized"); \ @@ -178,10 +172,8 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client)); VALUE e; -#ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(rb_error_msg, rb_utf8_encoding()); rb_enc_associate(rb_sql_state, rb_usascii_encoding()); -#endif e = rb_funcall(cMysql2Error, intern_new_with_args, 4, rb_error_msg, @@ -357,9 +349,7 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) { return str; } else { rb_str = rb_str_new((const char*)newStr, newLen); -#ifdef HAVE_RUBY_ENCODING_H rb_enc_copy(rb_str, str); -#endif xfree(newStr); return rb_str; } @@ -386,9 +376,7 @@ static VALUE rb_mysql_info(VALUE self) { } rb_str = rb_str_new2(info); -#ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(rb_str, rb_utf8_encoding()); -#endif return rb_str; } @@ -406,9 +394,7 @@ static VALUE rb_mysql_get_ssl_cipher(VALUE self) } rb_str = rb_str_new2(cipher); -#ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(rb_str, rb_utf8_encoding()); -#endif return rb_str; } @@ -762,12 +748,8 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { rb_iv_set(self, "@current_query_options", current); Check_Type(sql, T_STRING); -#ifdef HAVE_RUBY_ENCODING_H /* ensure the string is in the encoding the connection is expecting */ args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding)); -#else - args.sql = sql; -#endif args.sql_ptr = RSTRING_PTR(args.sql); args.sql_len = RSTRING_LEN(args.sql); args.wrapper = wrapper; @@ -804,20 +786,16 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) { unsigned char *newStr; VALUE rb_str; unsigned long newLen, oldLen; -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; -#endif GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); Check_Type(str, T_STRING); -#ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); /* ensure the string is in the encoding the connection is expecting */ str = rb_str_export_to_enc(str, conn_enc); -#endif oldLen = RSTRING_LEN(str); newStr = xmalloc(oldLen*2+1); @@ -825,21 +803,17 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) { newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen); if (newLen == oldLen) { /* no need to return a new ruby string if nothing changed */ -#ifdef HAVE_RUBY_ENCODING_H if (default_internal_enc) { str = rb_str_export_to_enc(str, default_internal_enc); } -#endif xfree(newStr); return str; } else { rb_str = rb_str_new((const char*)newStr, newLen); -#ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(rb_str, conn_enc); if (default_internal_enc) { rb_str = rb_str_export_to_enc(rb_str, default_internal_enc); } -#endif xfree(newStr); return rb_str; } @@ -950,10 +924,8 @@ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) { version = rb_str_new2(mysql_get_client_info()); header_version = rb_str_new2(MYSQL_LINK_VERSION); -#ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(version, rb_usascii_encoding()); rb_enc_associate(header_version, rb_usascii_encoding()); -#endif rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version())); rb_hash_aset(version_info, sym_version, version); @@ -969,27 +941,21 @@ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) { */ static VALUE rb_mysql_client_server_info(VALUE self) { VALUE version, server_info; -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; -#endif GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); -#ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); -#endif version = rb_hash_new(); rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client))); server_info = rb_str_new2(mysql_get_server_info(wrapper->client)); -#ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(server_info, conn_enc); if (default_internal_enc) { server_info = rb_str_export_to_enc(server_info, default_internal_enc); } -#endif rb_hash_aset(version, sym_version, server_info); return version; } @@ -1176,7 +1142,6 @@ static VALUE rb_mysql_client_store_result(VALUE self) return resultObj; } -#ifdef HAVE_RUBY_ENCODING_H /* call-seq: * client.encoding * @@ -1186,7 +1151,6 @@ static VALUE rb_mysql_client_encoding(VALUE self) { GET_CLIENT(self); return wrapper->encoding; } -#endif /* call-seq: * client.automatic_close? @@ -1272,17 +1236,14 @@ static VALUE set_write_timeout(VALUE self, VALUE value) { static VALUE set_charset_name(VALUE self, VALUE value) { char *charset_name; -#ifdef HAVE_RUBY_ENCODING_H const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb; rb_encoding *enc; VALUE rb_enc; -#endif GET_CLIENT(self); Check_Type(value, T_STRING); charset_name = RSTRING_PTR(value); -#ifdef HAVE_RUBY_ENCODING_H mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value)); if (mysql2rb == NULL || mysql2rb->rb_name == NULL) { VALUE inspect = rb_inspect(value); @@ -1292,7 +1253,6 @@ static VALUE set_charset_name(VALUE self, VALUE value) { rb_enc = rb_enc_from_encoding(enc); wrapper->encoding = rb_enc; } -#endif if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) { /* TODO: warning - unable to set charset */ @@ -1425,9 +1385,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0); rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0); rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0); -#ifdef HAVE_RUBY_ENCODING_H rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); -#endif rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1); diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index c00fb863f..cd5797f46 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -2,38 +2,9 @@ #define MYSQL2_CLIENT_H #ifndef HAVE_RB_THREAD_CALL_WITHOUT_GVL -#ifdef HAVE_RB_THREAD_BLOCKING_REGION - /* emulate rb_thread_call_without_gvl with rb_thread_blocking_region */ #define rb_thread_call_without_gvl(func, data1, ubf, data2) \ rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2) - -#else /* ! HAVE_RB_THREAD_BLOCKING_REGION */ -/* - * partial emulation of the 2.0 rb_thread_call_without_gvl under 1.8, - * this is enough for dealing with blocking I/O functions in the - * presence of threads. - */ - -#include -#define RUBY_UBF_IO ((rb_unblock_function_t *)-1) -typedef void rb_unblock_function_t(void *); -static void * -rb_thread_call_without_gvl( - void *(*func)(void *), void *data1, - RB_MYSQL_UNUSED rb_unblock_function_t *ubf, - RB_MYSQL_UNUSED void *data2) -{ - void *rv; - - TRAP_BEG; - rv = func(data1); - TRAP_END; - - return rv; -} - -#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ #endif /* ! HAVE_RB_THREAD_CALL_WITHOUT_GVL */ typedef struct { @@ -60,7 +31,3 @@ void init_mysql2_client(void); void decr_mysql2_client(mysql_client_wrapper *wrapper); #endif - -#ifndef HAVE_RB_HASH_DUP -VALUE rb_hash_dup(VALUE other); -#endif diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 043d001ed..48764697d 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -29,12 +29,8 @@ def add_ssl_defines(header) # 2.0-only have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h') -# 1.9-only -have_func('rb_thread_blocking_region') +# Missing in RBX (https://github.com/rubinius/rubinius/issues/3771) have_func('rb_wait_for_single_fd') -have_func('rb_hash_dup') -have_func('rb_intern3') -have_func('rb_big_cmp') # borrowed from mysqlplus # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index 80063ed04..bc74fc0d0 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -17,9 +17,7 @@ void Init_mysql2(void); #include #endif -#ifdef HAVE_RUBY_ENCODING_H #include -#endif #ifdef HAVE_RUBY_THREAD_H #include #endif diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index ccb49a5b7..9224e6548 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -2,51 +2,19 @@ #include "mysql_enc_to_ruby.h" -#ifdef HAVE_RUBY_ENCODING_H static rb_encoding *binaryEncoding; -#endif -#if (SIZEOF_INT < SIZEOF_LONG) || defined(HAVE_RUBY_ENCODING_H) /* on 64bit platforms we can handle dates way outside 2038-01-19T03:14:07 * * (9999*31557600) + (12*2592000) + (31*86400) + (11*3600) + (59*60) + 59 */ #define MYSQL2_MAX_TIME 315578267999ULL -#else -/** - * On 32bit platforms the maximum date the Time class can handle is 2038-01-19T03:14:07 - * 2038 years + 1 month + 19 days + 3 hours + 14 minutes + 7 seconds = 64318634047 seconds - * - * (2038*31557600) + (1*2592000) + (19*86400) + (3*3600) + (14*60) + 7 - */ -#define MYSQL2_MAX_TIME 64318634047ULL -#endif -#if defined(HAVE_RUBY_ENCODING_H) /* 0000-1-1 00:00:00 UTC * * (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0 */ #define MYSQL2_MIN_TIME 2678400ULL -#elif SIZEOF_INT < SIZEOF_LONG /* 64bit Ruby 1.8 */ -/* 0139-1-1 00:00:00 UTC - * - * (139*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0 - */ -#define MYSQL2_MIN_TIME 4389184800ULL -#elif defined(NEGATIVE_TIME_T) -/* 1901-12-13 20:45:52 UTC : The oldest time in 32-bit signed time_t. - * - * (1901*31557600) + (12*2592000) + (13*86400) + (20*3600) + (45*60) + 52 - */ -#define MYSQL2_MIN_TIME 60023299552ULL -#else -/* 1970-01-01 00:00:01 UTC : The Unix epoch - the oldest time in portable time_t. - * - * (1970*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 1 - */ -#define MYSQL2_MIN_TIME 62171150401ULL -#endif #define GET_RESULT(self) \ mysql2_result_wrapper *wrapper; \ @@ -179,29 +147,19 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo rb_field = rb_ary_entry(wrapper->fields, idx); if (rb_field == Qnil) { MYSQL_FIELD *field = NULL; -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc = rb_default_internal_encoding(); rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); -#endif field = mysql_fetch_field_direct(wrapper->result, idx); if (symbolize_keys) { -#ifdef HAVE_RB_INTERN3 rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding()); rb_field = ID2SYM(rb_field); -#else - VALUE colStr; - colStr = rb_str_new(field->name, field->name_length); - rb_field = ID2SYM(rb_to_id(colStr)); -#endif } else { rb_field = rb_str_new(field->name, field->name_length); -#ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(rb_field, conn_enc); if (default_internal_enc) { rb_field = rb_str_export_to_enc(rb_field, default_internal_enc); } -#endif } rb_ary_store(wrapper->fields, idx, rb_field); } @@ -209,7 +167,6 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo return rb_field; } -#ifdef HAVE_RUBY_ENCODING_H static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) { /* if binary flag is set, respect its wishes */ if (field.flags & BINARY_FLAG && field.charsetnr == 63) { @@ -238,7 +195,6 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e } return val; } -#endif /* Interpret microseconds digits left-aligned in fixed-width field. * e.g. 10.123 seconds means 10 seconds and 123000 microseconds, @@ -335,16 +291,12 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co VALUE rowVal; unsigned int i = 0; -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; -#endif GET_RESULT(self); -#ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); -#endif if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); @@ -506,9 +458,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co case MYSQL_TYPE_GEOMETRY: // char[] default: val = rb_str_new(result_buffer->buffer, *(result_buffer->length)); -#ifdef HAVE_RUBY_ENCODING_H val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); -#endif break; } } @@ -530,16 +480,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r unsigned int i = 0; unsigned long * fieldLengths; void * ptr; -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; -#endif GET_RESULT(self); -#ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); -#endif ptr = wrapper->result; row = (MYSQL_ROW)rb_thread_call_without_gvl(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0); @@ -569,9 +515,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r val = Qnil; } else { val = rb_str_new(row[i], fieldLengths[i]); -#ifdef HAVE_RUBY_ENCODING_H val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); -#endif } } else { switch(type) { @@ -722,9 +666,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r case MYSQL_TYPE_GEOMETRY: /* Spatial fielda */ default: val = rb_str_new(row[i], fieldLengths[i]); -#ifdef HAVE_RUBY_ENCODING_H val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); -#endif break; } } @@ -1058,7 +1000,5 @@ void init_mysql2_result() { opt_time_month = INT2NUM(1); opt_utc_offset = INT2NUM(0); -#ifdef HAVE_RUBY_ENCODING_H binaryEncoding = rb_enc_find("binary"); -#endif } diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 18c603ce3..892cbeaf7 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -50,7 +50,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) { VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt)); VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt)); -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; conn_enc = rb_to_encoding(wrapper->encoding); @@ -62,7 +61,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) { rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc); rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); } -#endif e = rb_funcall(cMysql2Error, intern_new_with_args, 4, rb_error_msg, @@ -96,9 +94,7 @@ static void *nogvl_prepare_statement(void *ptr) { VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { mysql_stmt_wrapper *stmt_wrapper; VALUE rb_stmt; -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; -#endif Check_Type(sql, T_STRING); @@ -114,9 +110,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { { GET_CLIENT(rb_client); stmt_wrapper->stmt = mysql_stmt_init(wrapper->client); -#ifdef HAVE_RUBY_ENCODING_H conn_enc = rb_to_encoding(wrapper->encoding); -#endif } if (stmt_wrapper->stmt == NULL) { rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory"); @@ -134,11 +128,8 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { { struct nogvl_prepare_statement_args args; args.stmt = stmt_wrapper->stmt; - args.sql = sql; -#ifdef HAVE_RUBY_ENCODING_H // ensure the string is in the encoding the connection is expecting - args.sql = rb_str_export_to_enc(args.sql, conn_enc); -#endif + args.sql = rb_str_export_to_enc(sql, conn_enc); args.sql_ptr = RSTRING_PTR(sql); args.sql_len = RSTRING_LEN(sql); @@ -265,16 +256,12 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { VALUE resultObj; VALUE *params_enc; int is_streaming; -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; -#endif GET_STATEMENT(self); GET_CLIENT(stmt_wrapper->client); -#ifdef HAVE_RUBY_ENCODING_H conn_enc = rb_to_encoding(wrapper->encoding); -#endif /* Scratch space for string encoding exports, allocate on the stack. */ params_enc = alloca(sizeof(VALUE) * argc); @@ -319,12 +306,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { *(LONG_LONG*)(bind_buffers[i].buffer) = num; } else { /* The bignum was larger than we can fit in LONG_LONG, send it as a string */ - VALUE rb_val_as_string = rb_big2str(argv[i], 10); bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL; - params_enc[i] = rb_val_as_string; -#ifdef HAVE_RUBY_ENCODING_H - params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); -#endif + params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc); set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); } } @@ -338,9 +321,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; params_enc[i] = argv[i]; -#ifdef HAVE_RUBY_ENCODING_H params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); -#endif set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); break; case T_TRUE: @@ -405,9 +386,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0); params_enc[i] = rb_val_as_string; -#ifdef HAVE_RUBY_ENCODING_H params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); -#endif set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); } break; @@ -474,20 +453,16 @@ static VALUE fields(VALUE self) { unsigned int i; VALUE field_list; MYSQL_STMT* stmt; -#ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc, *conn_enc; -#endif GET_STATEMENT(self); GET_CLIENT(stmt_wrapper->client); stmt = stmt_wrapper->stmt; -#ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); { GET_CLIENT(stmt_wrapper->client); conn_enc = rb_to_encoding(wrapper->encoding); } -#endif metadata = mysql_stmt_result_metadata(stmt); if (metadata == NULL) { @@ -508,12 +483,10 @@ static VALUE fields(VALUE self) { VALUE rb_field; rb_field = rb_str_new(fields[i].name, fields[i].name_length); -#ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(rb_field, conn_enc); if (default_internal_enc) { rb_field = rb_str_export_to_enc(rb_field, default_internal_enc); } -#endif rb_ary_store(field_list, (long)i, rb_field); } diff --git a/lib/mysql2.rb b/lib/mysql2.rb index a45cace3a..4581e5b0b 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -1,7 +1,6 @@ # encoding: UTF-8 require 'date' require 'bigdecimal' -require 'rational' unless RUBY_VERSION >= '1.9.2' # Load libmysql.dll before requiring mysql2/mysql2.so # This gives a chance to be flexible about the load path @@ -73,8 +72,7 @@ def self.key_hash_as_symbols(hash) # if Thread.respond_to?(:handle_interrupt) require 'timeout' - # rubocop:disable Style/ConstantName - TimeoutError = if defined?(::Timeout::ExitException) + TIMEOUT_ERROR_CLASS = if defined?(::Timeout::ExitException) ::Timeout::ExitException else ::Timeout::Error diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index a9049d998..769536679 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -116,7 +116,7 @@ def parse_flags_array(flags, initial = 0) if Thread.respond_to?(:handle_interrupt) def query(sql, options = {}) - Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do + Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do _query(sql, @query_options.merge(options)) end end diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index f6ffcc425..d75a0ca84 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -24,7 +24,7 @@ def self.new_with_args(msg, server_version, error_number, sql_state) err = allocate err.instance_variable_set('@server_version', server_version) err.instance_variable_set('@error_number', error_number) - err.instance_variable_set('@sql_state', sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state) + err.instance_variable_set('@sql_state', sql_state.encode(ENCODE_OPTS)) err.send(:initialize, msg) err end @@ -55,12 +55,8 @@ def self.new_with_args(msg, server_version, error_number, sql_state) # encoding, we'll assume UTF-8 and clean the string of anything that's not a # valid UTF-8 character. # - # Except for if we're on 1.8, where we'll do nothing ;) - # - # Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8 + # Returns a valid UTF-8 string. def clean_message(message) - return message unless message.respond_to?(:encode) - if @server_version && @server_version > 50500 message.encode(ENCODE_OPTS) else diff --git a/lib/mysql2/statement.rb b/lib/mysql2/statement.rb index f392c6ed0..5b3752651 100644 --- a/lib/mysql2/statement.rb +++ b/lib/mysql2/statement.rb @@ -4,7 +4,7 @@ class Statement if Thread.respond_to?(:handle_interrupt) def execute(*args) - Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do + Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do _execute(*args) end end diff --git a/mysql2.gemspec b/mysql2.gemspec index 8596b5eec..b13f0ce61 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -10,6 +10,7 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s| s.homepage = 'http://github.com/brianmario/mysql2' s.rdoc_options = ["--charset=UTF-8"] s.summary = 'A simple, fast Mysql library for Ruby, binding to libmysql' + s.required_ruby_version = '>= 1.9.3' s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split s.test_files = `git ls-files spec examples`.split diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index dfb92a2df..8dc89d96f 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -136,16 +136,14 @@ def connect(*args) # You may need to adjust the lines below to match your SSL certificate paths ssl_client = nil expect { - # rubocop:disable Style/TrailingComma ssl_client = new_client( 'host' => 'mysql2gem.example.com', # must match the certificates :sslkey => '/etc/mysql/client-key.pem', :sslcert => '/etc/mysql/client-cert.pem', :sslca => '/etc/mysql/ca-cert.pem', :sslcipher => 'DHE-RSA-AES256-SHA', - :sslverify => true + :sslverify => true, ) - # rubocop:enable Style/TrailingComma }.not_to raise_error results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }] @@ -784,7 +782,6 @@ def run_gc }.not_to raise_error end - unless RUBY_VERSION =~ /1.8/ it "should carry over the original string's encoding" do str = "abc'def\"ghi\0jkl%mno" escaped = Mysql2::Client.escape(str) @@ -795,7 +792,6 @@ def run_gc expect(escaped.encoding).to eql(str.encoding) end end - end it "should respond to #escape" do expect(@client).to respond_to(:escape) @@ -831,8 +827,6 @@ def run_gc end context 'when mysql encoding is not utf8' do - before { pending('Encoding is undefined') unless defined?(Encoding) } - let(:client) { new_client(:encoding => "ujis") } it 'should return a internal encoding string if Encoding.default_internal is set' do @@ -858,8 +852,6 @@ def run_gc end context "strings returned by #info" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should be tagged as ascii" do expect(@client.info[:version].encoding).to eql(Encoding::US_ASCII) expect(@client.info[:header_version].encoding).to eql(Encoding::US_ASCII) @@ -867,8 +859,6 @@ def run_gc end context "strings returned by .info" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should be tagged as ascii" do expect(Mysql2::Client.info[:version].encoding).to eql(Encoding::US_ASCII) expect(Mysql2::Client.info[:header_version].encoding).to eql(Encoding::US_ASCII) @@ -896,8 +886,6 @@ def run_gc end context "strings returned by #server_info" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8) @@ -1031,9 +1019,7 @@ def run_gc client.query('SELECT 1') end - unless RUBY_VERSION =~ /1.8/ it "should respond to #encoding" do expect(@client).to respond_to(:encoding) end end -end diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index 32ba19e1c..be3154be9 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -42,8 +42,6 @@ end before do - pending('String#encoding is not defined') unless String.public_method_defined?(:encoding) - # sanity check expect(valid_utf8.encoding).to eql(Encoding::UTF_8) expect(valid_utf8).to be_valid_encoding diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index c8e26c530..b804b3f35 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -292,75 +292,14 @@ expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end - it "should return Time values with microseconds" do - now = Time.now - if RUBY_VERSION =~ /1.8/ || @client.server_info[:id] / 100 < 506 - result = @client.query("SELECT CAST('#{now.strftime('%F %T %z')}' AS DATETIME) AS a") - expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) - else - result = @client.query("SELECT CAST('#{now.strftime('%F %T.%6N %z')}' AS DATETIME(6)) AS a") - # microseconds is 6 digits after the decimal, but only test on 5 significant figures - expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) - end - end - - it "should return DateTime values with microseconds" do - now = DateTime.now - if RUBY_VERSION =~ /1.8/ || @client.server_info[:id] / 100 < 506 - result = @client.query("SELECT CAST('#{now.strftime('%F %T %z')}' AS DATETIME) AS a") - expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) - else - result = @client.query("SELECT CAST('#{now.strftime('%F %T.%6N %z')}' AS DATETIME(6)) AS a") - # microseconds is 6 digits after the decimal, but only test on 5 significant figures - expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) - end + it "should return Time when timestamp is < 1901-12-13 20:45:52" do + r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") + expect(r.first['test']).to be_an_instance_of(Time) end - if 1.size == 4 # 32bit - klass = if RUBY_VERSION =~ /1.8/ - DateTime - else - Time - end - - it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do - # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 - r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - expect(r.first['test']).to be_an_instance_of(klass) - end - - it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do - # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 - r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - expect(r.first['test']).to be_an_instance_of(klass) - end - elsif 1.size == 8 # 64bit - if RUBY_VERSION =~ /1.8/ - it "should return Time when timestamp is > 0138-12-31 11:59:59" do - r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") - expect(r.first['test']).to be_an_instance_of(Time) - end - - it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do - r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") - expect(r.first['test']).to be_an_instance_of(DateTime) - end - - it "should return Time when timestamp is > 2038-01-19T03:14:07" do - r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - expect(r.first['test']).to be_an_instance_of(Time) - end - else - it "should return Time when timestamp is < 1901-12-13 20:45:52" do - r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") - expect(r.first['test']).to be_an_instance_of(Time) - end - - it "should return Time when timestamp is > 2038-01-19T03:14:07" do - r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") - expect(r.first['test']).to be_an_instance_of(Time) - end - end + it "should return Time when timestamp is > 2038-01-19T03:14:07" do + r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") + expect(r.first['test']).to be_an_instance_of(Time) end it "should return Time for a TIMESTAMP value when within the supported range" do @@ -389,8 +328,6 @@ end context "string encoding for ENUM values" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first @@ -421,8 +358,6 @@ end context "string encoding for SET values" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first @@ -453,8 +388,6 @@ end context "string encoding for BINARY values" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first @@ -494,8 +427,6 @@ end context "string encoding for #{type} values" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - if %w(VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB).include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index feaaae865..9f187d6f5 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -241,7 +241,7 @@ def stmt_count expect(result.to_a).to eq([{ "整数" => 1 }]) end - end if defined? Encoding + end context "streaming result" do it "should be able to stream query result" do @@ -464,51 +464,14 @@ def stmt_count expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end - if 1.size == 4 # 32bit - klass = if RUBY_VERSION =~ /1.8/ - DateTime - else - Time - end - - it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do - # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 - r = @client.prepare("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test").execute - expect(r.first['test']).to be_an_instance_of(klass) - end - - it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do - # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 - r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute - expect(r.first['test']).to be_an_instance_of(klass) - end - elsif 1.size == 8 # 64bit - if RUBY_VERSION =~ /1.8/ - it "should return Time when timestamp is > 0138-12-31 11:59:59" do - r = @client.prepare("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test").execute - expect(r.first['test']).to be_an_instance_of(Time) - end - - it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do - r = @client.prepare("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test").execute - expect(r.first['test']).to be_an_instance_of(DateTime) - end - - it "should return Time when timestamp is > 2038-01-19T03:14:07" do - r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute - expect(r.first['test']).to be_an_instance_of(Time) - end - else - it "should return Time when timestamp is < 1901-12-13 20:45:52" do - r = @client.prepare("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test").execute - expect(r.first['test']).to be_an_instance_of(Time) - end + it "should return Time when timestamp is < 1901-12-13 20:45:52" do + r = @client.prepare("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test").execute + expect(r.first['test']).to be_an_instance_of(Time) + end - it "should return Time when timestamp is > 2038-01-19T03:14:07" do - r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute - expect(r.first['test']).to be_an_instance_of(Time) - end - end + it "should return Time when timestamp is > 2038-01-19T03:14:07" do + r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute + expect(r.first['test']).to be_an_instance_of(Time) end it "should return Time for a TIMESTAMP value when within the supported range" do @@ -537,8 +500,6 @@ def stmt_count end context "string encoding for ENUM values" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first @@ -569,8 +530,6 @@ def stmt_count end context "string encoding for SET values" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first @@ -601,8 +560,6 @@ def stmt_count end context "string encoding for BINARY values" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first @@ -642,8 +599,6 @@ def stmt_count end context "string encoding for #{type} values" do - before { pending('Encoding is undefined') unless defined?(Encoding) } - if %w(VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB).include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do diff --git a/tasks/compile.rake b/tasks/compile.rake index ce610cdad..660d73c16 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -60,10 +60,10 @@ end file 'lib/mysql2/mysql2.rb' do |t| name = Mysql2::GEMSPEC.name File.open(t.name, 'wb') do |f| - f.write <<-eoruby + f.write <<-END_OF_RUBY RUBY_VERSION =~ /(\\d+.\\d+)/ require "#{name}/\#{$1}/#{name}" - eoruby + END_OF_RUBY end end From 0e4fcc3906c55a564c77ccac317bb7bb82a300dc Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Sat, 25 Nov 2017 20:10:10 +0100 Subject: [PATCH 538/783] Suppress Fixnum and Bignum warnings on Ruby 2.4. (#907) --- spec/mysql2/client_spec.rb | 8 ++++---- spec/mysql2/result_spec.rb | 12 ++++++------ spec/mysql2/statement_spec.rb | 12 ++++++------ spec/spec_helper.rb | 4 ++++ 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 8dc89d96f..de7d2b22b 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -570,7 +570,7 @@ def run_gc end it "#socket should return a Fixnum (file descriptor from C)" do - expect(@client.socket).to be_an_instance_of(Fixnum) + expect(@client.socket).to be_an_instance_of(0.class) expect(@client.socket).not_to eql(0) end @@ -846,7 +846,7 @@ def run_gc info = @client.info expect(info).to be_an_instance_of(Hash) expect(info).to have_key(:id) - expect(info[:id]).to be_an_instance_of(Fixnum) + expect(info[:id]).to be_an_instance_of(0.class) expect(info).to have_key(:version) expect(info[:version]).to be_an_instance_of(String) end @@ -873,7 +873,7 @@ def run_gc server_info = @client.server_info expect(server_info).to be_an_instance_of(Hash) expect(server_info).to have_key(:id) - expect(server_info[:id]).to be_an_instance_of(Fixnum) + expect(server_info[:id]).to be_an_instance_of(0.class) expect(server_info).to have_key(:version) expect(server_info[:version]).to be_an_instance_of(String) end @@ -962,7 +962,7 @@ def run_gc end it "#thread_id should be a Fixnum" do - expect(@client.thread_id).to be_an_instance_of(Fixnum) + expect(@client.thread_id).to be_an_instance_of(0.class) end it "should respond to #ping" do diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index b804b3f35..9effd7257 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -204,7 +204,7 @@ end it "should return Fixnum for a TINYINT value" do - expect([Fixnum, Bignum]).to include(@test_result['tiny_int_test'].class) + expect(num_classes).to include(@test_result['tiny_int_test'].class) expect(@test_result['tiny_int_test']).to eql(1) end @@ -248,27 +248,27 @@ end it "should return Fixnum for a SMALLINT value" do - expect([Fixnum, Bignum]).to include(@test_result['small_int_test'].class) + expect(num_classes).to include(@test_result['small_int_test'].class) expect(@test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do - expect([Fixnum, Bignum]).to include(@test_result['medium_int_test'].class) + expect(num_classes).to include(@test_result['medium_int_test'].class) expect(@test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do - expect([Fixnum, Bignum]).to include(@test_result['int_test'].class) + expect(num_classes).to include(@test_result['int_test'].class) expect(@test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do - expect([Fixnum, Bignum]).to include(@test_result['big_int_test'].class) + expect(num_classes).to include(@test_result['big_int_test'].class) expect(@test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do - expect([Fixnum, Bignum]).to include(@test_result['year_test'].class) + expect(num_classes).to include(@test_result['year_test'].class) expect(@test_result['year_test']).to eql(2009) end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 9f187d6f5..812bd32e8 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -372,7 +372,7 @@ def stmt_count end it "should return Fixnum for a TINYINT value" do - expect([Fixnum, Bignum]).to include(@test_result['tiny_int_test'].class) + expect(num_classes).to include(@test_result['tiny_int_test'].class) expect(@test_result['tiny_int_test']).to eql(1) end @@ -420,27 +420,27 @@ def stmt_count end it "should return Fixnum for a SMALLINT value" do - expect([Fixnum, Bignum]).to include(@test_result['small_int_test'].class) + expect(num_classes).to include(@test_result['small_int_test'].class) expect(@test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do - expect([Fixnum, Bignum]).to include(@test_result['medium_int_test'].class) + expect(num_classes).to include(@test_result['medium_int_test'].class) expect(@test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do - expect([Fixnum, Bignum]).to include(@test_result['int_test'].class) + expect(num_classes).to include(@test_result['int_test'].class) expect(@test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do - expect([Fixnum, Bignum]).to include(@test_result['big_int_test'].class) + expect(num_classes).to include(@test_result['big_int_test'].class) expect(@test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do - expect([Fixnum, Bignum]).to include(@test_result['year_test'].class) + expect(num_classes).to include(@test_result['year_test'].class) expect(@test_result['year_test']).to eql(2009) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 53c098afb..045e78374 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -36,6 +36,10 @@ def new_client(option_overrides = {}) end end + def num_classes + 0.class == Integer ? [Integer] : [Fixnum, Bignum] + end + config.before :each do @client = new_client end From 811a57a19eb7b58947da19a51705c2efa9259303 Mon Sep 17 00:00:00 2001 From: Cees de Groot Date: Fri, 20 May 2016 14:09:42 -0400 Subject: [PATCH 539/783] Make server_status available on Mysql2::Result objects (#755) --- ext/mysql2/client.c | 33 +++++++++++++++++++++++++++++++++ ext/mysql2/client.h | 1 + ext/mysql2/statement.c | 2 ++ lib/mysql2/result.rb | 2 ++ spec/mysql2/result_spec.rb | 16 ++++++++++++++++ 5 files changed, 54 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index a826b3258..f4b61aa5d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -17,6 +17,7 @@ VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error; static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; +static VALUE sym_no_good_index_used, sym_no_index_used, sym_query_was_slow; static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args; #define REQUIRE_INITIALIZED(wrapper) \ @@ -581,6 +582,8 @@ static VALUE rb_mysql_client_async_result(VALUE self) { Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); + rb_mysql_set_server_query_flags(wrapper->client, resultObj); + return resultObj; } @@ -1412,6 +1415,10 @@ void init_mysql2_client() { sym_array = ID2SYM(rb_intern("array")); sym_stream = ID2SYM(rb_intern("stream")); + sym_no_good_index_used = ID2SYM(rb_intern("no_good_index_used")); + sym_no_index_used = ID2SYM(rb_intern("no_index_used")); + sym_query_was_slow = ID2SYM(rb_intern("query_was_slow")); + intern_brackets = rb_intern("[]"); intern_merge = rb_intern("merge"); intern_merge_bang = rb_intern("merge!"); @@ -1558,3 +1565,29 @@ void init_mysql2_client() { rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0)); #endif } + +#define flag_to_bool(f) ((client->server_status & f) ? Qtrue : Qfalse) + +void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result) { + VALUE server_flags = rb_hash_new(); + +#ifdef SERVER_QUERY_NO_GOOD_INDEX_USED + rb_hash_aset(server_flags, sym_no_good_index_used, flag_to_bool(SERVER_QUERY_NO_GOOD_INDEX_USED)); +#else + rb_hash_aset(server_flags, sym_no_good_index_used, Qnil); +#endif + +#ifdef SERVER_QUERY_NO_INDEX_USED + rb_hash_aset(server_flags, sym_no_index_used, flag_to_bool(SERVER_QUERY_NO_INDEX_USED)); +#else + rb_hash_aset(server_flags, sym_no_index_used, Qnil); +#endif + +#ifdef SERVER_QUERY_WAS_SLOW + rb_hash_aset(server_flags, sym_query_was_slow, flag_to_bool(SERVER_QUERY_WAS_SLOW)); +#else + rb_hash_aset(server_flags, sym_query_was_slow, Qnil); +#endif + + rb_iv_set(result, "@server_flags", server_flags); +} diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index cd5797f46..e5afe985f 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -22,6 +22,7 @@ typedef struct { } mysql_client_wrapper; void rb_mysql_client_set_active_thread(VALUE self); +void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result); #define GET_CLIENT(self) \ mysql_client_wrapper *wrapper; \ diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 892cbeaf7..4787742e3 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -434,6 +434,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self); + rb_mysql_set_server_query_flags(wrapper->client, resultObj); + if (!is_streaming) { // cache all result rb_funcall(resultObj, intern_each, 0); diff --git a/lib/mysql2/result.rb b/lib/mysql2/result.rb index e005e817c..585104e0b 100644 --- a/lib/mysql2/result.rb +++ b/lib/mysql2/result.rb @@ -1,5 +1,7 @@ module Mysql2 class Result + attr_reader :server_flags + include Enumerable end end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 9effd7257..f3b5a9350 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -473,4 +473,20 @@ end end end + + context "server flags" do + before(:each) do + @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY null_test DESC LIMIT 1") + end + + it "should set a definitive value for query_was_slow" do + expect(@test_result.server_flags[:query_was_slow]).to eql(false) + end + it "should set a definitive value for no_index_used" do + expect(@test_result.server_flags[:no_index_used]).to eql(true) + end + it "should set a definitive value for no_good_index_used" do + expect(@test_result.server_flags[:no_good_index_used]).to eql(false) + end + end end From 8d557b051960e7c7fcbec06be0773c1d43a9e1fd Mon Sep 17 00:00:00 2001 From: Kubo Takehiro Date: Sat, 11 Jun 2016 18:21:52 +0900 Subject: [PATCH 540/783] Support connect attributes and set the script name to `program_name` attribute if it is not specified. (#760) --- README.md | 1 + ext/mysql2/client.c | 22 ++++++++++++++++++++-- ext/mysql2/extconf.rb | 1 + lib/mysql2/client.rb | 10 +++++++++- spec/mysql2/client_spec.rb | 20 ++++++++++++++++++++ 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 92bfc9e66..c385f2932 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,7 @@ Mysql2::Client.new( :read_timeout = seconds, :write_timeout = seconds, :connect_timeout = seconds, + :connect_attrs = {:program_name => $PROGRAM_NAME, ...}, :reconnect = true/false, :local_infile = true/false, :secure_auth = true/false, diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index f4b61aa5d..cca56194a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -400,7 +400,20 @@ static VALUE rb_mysql_get_ssl_cipher(VALUE self) return rb_str; } -static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) { +#ifdef HAVE_CONST_MYSQL_OPT_CONNECT_ATTR_ADD +static int opt_connect_attr_add_i(VALUE key, VALUE value, VALUE arg) +{ + mysql_client_wrapper *wrapper = (mysql_client_wrapper *)arg; + rb_encoding *enc = rb_to_encoding(wrapper->encoding); + key = rb_str_export_to_enc(key, enc); + value = rb_str_export_to_enc(value, enc); + + mysql_options4(wrapper->client, MYSQL_OPT_CONNECT_ATTR_ADD, StringValueCStr(key), StringValueCStr(value)); + return ST_CONTINUE; +} +#endif + +static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags, VALUE conn_attrs) { struct nogvl_connect_args args; time_t start_time, end_time, elapsed_time, connect_timeout; VALUE rv; @@ -415,6 +428,11 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); +#ifdef HAVE_CONST_MYSQL_OPT_CONNECT_ATTR_ADD + mysql_options(wrapper->client, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + rb_hash_foreach(conn_attrs, opt_connect_attr_add_i, (VALUE)wrapper); +#endif + if (wrapper->connect_timeout) time(&start_time); rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); @@ -1403,7 +1421,7 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1); rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); - rb_define_private_method(cMysql2Client, "connect", rb_connect, 7); + rb_define_private_method(cMysql2Client, "connect", rb_connect, 8); rb_define_private_method(cMysql2Client, "_query", rb_query, 2); sym_id = ID2SYM(rb_intern("id")); diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 48764697d..8c0020390 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -111,6 +111,7 @@ def add_ssl_defines(header) have_struct_member('MYSQL', 'net.vio', mysql_h) have_struct_member('MYSQL', 'net.pvio', mysql_h) have_const('MYSQL_ENABLE_CLEARTEXT_PLUGIN', mysql_h) +have_const('MYSQL_OPT_CONNECT_ATTR_ADD', mysql_h) # for mysql_options4 # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 769536679..8b63bc8ec 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -64,6 +64,11 @@ def initialize(opts = {}) # SSL verify is a connection flag rather than a mysql_ssl_set option flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] + # Set default program_name in performance_schema.session_connect_attrs + # and performance_schema.session_account_connect_attrs + conn_attrs = opts[:connect_attrs] || {} + conn_attrs[:program_name] = $PROGRAM_NAME unless conn_attrs.key?(:program_name) + if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) } warn "============= WARNING FROM mysql2 =============" warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future." @@ -85,8 +90,11 @@ def initialize(opts = {}) port = port.to_i unless port.nil? database = database.to_s unless database.nil? socket = socket.to_s unless socket.nil? + conn_attrs = conn_attrs.each_with_object({}) do |(key, value), hash| + hash[key.to_s] = value.to_s + end - connect user, pass, host, port, database, socket, flags + connect user, pass, host, port, database, socket, flags, conn_attrs end def parse_ssl_mode(mode) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index de7d2b22b..fa7c003a6 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -433,6 +433,26 @@ def run_gc expect(client.read_timeout).to be_nil end + it "should set default program_name in connect_attrs" do + client = new_client + if Mysql2::Client.info[:version] < '5.6' or client.info[:version] < '5.6' + pending('Both client and server versions must be MySQL 5.6 or later.') + end + result = client.query("SELECT attr_value FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id() AND attr_name = 'program_name'") + expect(result.first['attr_value']).to eq($0) + end + + it "should set custom connect_attrs" do + client = new_client(:connect_attrs => {:program_name => 'my_program_name', :foo => 'fooval', :bar => 'barval'}) + if Mysql2::Client.info[:version] < '5.6' or client.info[:version] < '5.6' + pending('Both client and server versions must be MySQL 5.6 or later.') + end + results = Hash[client.query("SELECT * FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id()").map { |x| x.values_at('ATTR_NAME', 'ATTR_VALUE') }] + expect(results['program_name']).to eq('my_program_name') + expect(results['foo']).to eq('fooval') + expect(results['bar']).to eq('barval') + end + context "#query" do it "should let you query again if iterating is finished when streaming" do @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a From d0c1b23b7c8606f96fae8bf3c2eebaa768a7d0d1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 25 Nov 2017 12:59:16 -0800 Subject: [PATCH 541/783] Cleanups per RuboCop --- spec/mysql2/client_spec.rb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index fa7c003a6..c628acc82 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -435,16 +435,16 @@ def run_gc it "should set default program_name in connect_attrs" do client = new_client - if Mysql2::Client.info[:version] < '5.6' or client.info[:version] < '5.6' + if Mysql2::Client.info[:version] < '5.6' || client.info[:version] < '5.6' pending('Both client and server versions must be MySQL 5.6 or later.') end result = client.query("SELECT attr_value FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id() AND attr_name = 'program_name'") - expect(result.first['attr_value']).to eq($0) + expect(result.first['attr_value']).to eq($PROGRAM_NAME) end it "should set custom connect_attrs" do - client = new_client(:connect_attrs => {:program_name => 'my_program_name', :foo => 'fooval', :bar => 'barval'}) - if Mysql2::Client.info[:version] < '5.6' or client.info[:version] < '5.6' + client = new_client(:connect_attrs => { :program_name => 'my_program_name', :foo => 'fooval', :bar => 'barval' }) + if Mysql2::Client.info[:version] < '5.6' || client.info[:version] < '5.6' pending('Both client and server versions must be MySQL 5.6 or later.') end results = Hash[client.query("SELECT * FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id()").map { |x| x.values_at('ATTR_NAME', 'ATTR_VALUE') }] @@ -802,16 +802,16 @@ def run_gc }.not_to raise_error end - it "should carry over the original string's encoding" do - str = "abc'def\"ghi\0jkl%mno" - escaped = Mysql2::Client.escape(str) - expect(escaped.encoding).to eql(str.encoding) + it "should carry over the original string's encoding" do + str = "abc'def\"ghi\0jkl%mno" + escaped = Mysql2::Client.escape(str) + expect(escaped.encoding).to eql(str.encoding) - str.encode!('us-ascii') - escaped = Mysql2::Client.escape(str) - expect(escaped.encoding).to eql(str.encoding) - end + str.encode!('us-ascii') + escaped = Mysql2::Client.escape(str) + expect(escaped.encoding).to eql(str.encoding) end + end it "should respond to #escape" do expect(@client).to respond_to(:escape) @@ -1039,7 +1039,7 @@ def run_gc client.query('SELECT 1') end - it "should respond to #encoding" do - expect(@client).to respond_to(:encoding) - end + it "should respond to #encoding" do + expect(@client).to respond_to(:encoding) end +end From 948e6f3027532bc96fa091bd4d637cf04178fa5d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 25 Nov 2017 12:59:48 -0800 Subject: [PATCH 542/783] Silences for RuboCop for Mysql2::Client#initialize --- .rubocop_todo.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d522ccc8f..92b70061b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -8,7 +8,7 @@ # Offense count: 2 Metrics/AbcSize: - Max: 85 + Max: 93 # Offense count: 1 Metrics/BlockNesting: @@ -34,7 +34,7 @@ Metrics/MethodLength: # Offense count: 1 Metrics/PerceivedComplexity: - Max: 26 + Max: 27 # Offense count: 40 # Cop supports --auto-correct. From a50e08165e12f6b3fee5140c5cbb63119fea93c8 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 25 Nov 2017 13:18:46 -0800 Subject: [PATCH 543/783] The server_status flags are enums in MySQL 8.0 --- ext/mysql2/client.c | 6 +++--- ext/mysql2/extconf.rb | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index cca56194a..7c51d1347 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1589,19 +1589,19 @@ void init_mysql2_client() { void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result) { VALUE server_flags = rb_hash_new(); -#ifdef SERVER_QUERY_NO_GOOD_INDEX_USED +#ifdef HAVE_CONST_SERVER_QUERY_NO_GOOD_INDEX_USED rb_hash_aset(server_flags, sym_no_good_index_used, flag_to_bool(SERVER_QUERY_NO_GOOD_INDEX_USED)); #else rb_hash_aset(server_flags, sym_no_good_index_used, Qnil); #endif -#ifdef SERVER_QUERY_NO_INDEX_USED +#ifdef HAVE_CONST_SERVER_QUERY_NO_INDEX_USED rb_hash_aset(server_flags, sym_no_index_used, flag_to_bool(SERVER_QUERY_NO_INDEX_USED)); #else rb_hash_aset(server_flags, sym_no_index_used, Qnil); #endif -#ifdef SERVER_QUERY_WAS_SLOW +#ifdef HAVE_CONST_SERVER_QUERY_WAS_SLOW rb_hash_aset(server_flags, sym_query_was_slow, flag_to_bool(SERVER_QUERY_WAS_SLOW)); #else rb_hash_aset(server_flags, sym_query_was_slow, Qnil); diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 8c0020390..a0eb125b4 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -110,7 +110,11 @@ def add_ssl_defines(header) add_ssl_defines(mysql_h) have_struct_member('MYSQL', 'net.vio', mysql_h) have_struct_member('MYSQL', 'net.pvio', mysql_h) +# These constants are actually enums, so they cannot be detected by #ifdef in C code. have_const('MYSQL_ENABLE_CLEARTEXT_PLUGIN', mysql_h) +have_const('SERVER_QUERY_NO_GOOD_INDEX_USED', mysql_h) +have_const('SERVER_QUERY_NO_INDEX_USED', mysql_h) +have_const('SERVER_QUERY_WAS_SLOW', mysql_h) have_const('MYSQL_OPT_CONNECT_ATTR_ADD', mysql_h) # for mysql_options4 # This is our wishlist. We use whichever flags work on the host. From a2fadb667cc0a05660c18a7a3e10baa675205af9 Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Sun, 26 Nov 2017 00:06:11 +0100 Subject: [PATCH 544/783] Fix compat with RubyInstaller-2.4 on Windows (#875) Since RubyInstaller-2.4+ is bundled with MSYS2 and the libmariadbclient can be installed per gemspec library dependency, it is easy to build the mysql2 gem in Windows. The MSYS2/MINGW dependency feature is documented here: https://github.com/oneclick/rubyinstaller2/wiki/For-gem-developers#msys2-library-dependency This also adds ruby-2.4 binaries, so that the mysql2 is still usabel as a binary gem. Fixes #861 The change in the spec is required for mariadbclient. It throws an error if no query was executed. Due to the stdcall convention on i686, the mysql_query() function check fails, so that it is omitted, now. --- Gemfile | 4 ++-- appveyor.yml | 11 ++++++++--- ext/mysql2/extconf.rb | 4 ++-- lib/mysql2.rb | 14 +++++++++----- mysql2.gemspec | 2 ++ spec/mysql2/statement_spec.rb | 1 - tasks/compile.rake | 8 ++++++-- 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Gemfile b/Gemfile index 34bbdcef7..846e0fd53 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org/' gemspec gem 'rake', '~> 10.4.2' -gem 'rake-compiler', '~> 0.9.5' +gem 'rake-compiler', '~> 1.0' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ @@ -22,7 +22,7 @@ end group :development do gem 'pry' - gem 'rake-compiler-dock', '~> 0.5.1' + gem 'rake-compiler-dock', '~> 0.6.0' end platforms :rbx do diff --git a/appveyor.yml b/appveyor.yml index 6b5068085..798534f5c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,12 +2,14 @@ version: "{build}" clone_depth: 10 install: + - SET PATH=C:\MinGW\msys\1.0\bin;%PATH% - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - ruby --version - gem --version - gem install bundler --quiet --no-ri --no-rdoc - bundler --version - bundle install --without benchmarks --path vendor/bundle + - IF DEFINED MINGW_PACKAGE_PREFIX (ridk exec pacman -S --noconfirm --needed %MINGW_PACKAGE_PREFIX%-libmariadbclient) build_script: - bundle exec rake compile test_script: @@ -20,11 +22,14 @@ test_script: FLUSH PRIVILEGES; " - bundle exec rake spec -# Where do I get Unix find? -#on_failure: -# - find tmp -name "*.log" -exec cat {}; +on_failure: + - find tmp -name "*.log" | xargs cat environment: matrix: + - ruby_version: "24-x64" + MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" + - ruby_version: "24" + MINGW_PACKAGE_PREFIX: "mingw-w64-i686" - ruby_version: "23-x64" - ruby_version: "23" - ruby_version: "22-x64" diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index a0eb125b4..c9350b45a 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -88,7 +88,7 @@ def add_ssl_defines(header) else _, usr_local_lib = dir_config('mysql', '/usr/local') - asplode("mysql client") unless find_library('mysqlclient', 'mysql_query', usr_local_lib, "#{usr_local_lib}/mysql") + asplode("mysql client") unless find_library('mysqlclient', nil, usr_local_lib, "#{usr_local_lib}/mysql") rpath_dir = usr_local_lib end @@ -179,7 +179,7 @@ def add_ssl_defines(header) $CFLAGS << ' -g -fno-omit-frame-pointer' end -if RUBY_PLATFORM =~ /mswin|mingw/ +if RUBY_PLATFORM =~ /mswin|mingw/ && !defined?(RubyInstaller) # Build libmysql.a interface link library require 'rake' diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 4581e5b0b..9414a693d 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -12,16 +12,20 @@ ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))) # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary - File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).tr('/', '\\') + File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)) + elsif defined?(RubyInstaller) + # RubyInstaller-2.4+ native build doesn't need DLL preloading else # This will use default / system library paths 'libmysql.dll' end - require 'Win32API' - LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I') - if 0 == LoadLibrary.call(dll_path) - abort "Failed to load libmysql.dll from #{dll_path}" + if dll_path + require 'Win32API' + LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I') + if 0 == LoadLibrary.call(dll_path) + abort "Failed to load libmysql.dll from #{dll_path}" + end end end diff --git a/mysql2.gemspec b/mysql2.gemspec index b13f0ce61..1c5b42f0f 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -14,4 +14,6 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s| s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split s.test_files = `git ls-files spec examples`.split + + s.metadata['msys2_mingw_dependencies'] = 'libmariadbclient' end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 812bd32e8..76e20b5bf 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -686,7 +686,6 @@ def stmt_count it 'should return number of rows affected by an insert' do stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' - expect(stmt.affected_rows).to eq 0 stmt.execute 1 expect(stmt.affected_rows).to eq 1 end diff --git a/tasks/compile.rake b/tasks/compile.rake index 660d73c16..fb339580e 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -9,7 +9,7 @@ Rake::ExtensionTask.new("mysql2", Mysql2::GEMSPEC) do |ext| # clean compiled extension CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}" - if RUBY_PLATFORM =~ /mswin|mingw/ + if RUBY_PLATFORM =~ /mswin|mingw/ && !defined?(RubyInstaller) # Expand the path because the build dir is 3-4 levels deep in tmp/platform/version/ connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) ext.config_options = ["--with-mysql-dir=#{connector_dir}"] @@ -26,6 +26,10 @@ Rake::ExtensionTask.new("mysql2", Mysql2::GEMSPEC) do |ext| Rake::Task['lib/mysql2/mysql2.rb'].invoke # vendor/libmysql.dll is invoked from extconf.rb Rake::Task['vendor/README'].invoke + + # only the source gem has a package dependency - the binary gem ships it's own DLL version + spec.metadata.delete('msys2_mingw_dependencies') + spec.files << 'lib/mysql2/mysql2.rb' spec.files << 'vendor/libmysql.dll' spec.files << 'vendor/README' @@ -77,7 +81,7 @@ task :devkit do end if RUBY_PLATFORM =~ /mingw|mswin/ - Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' + Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' unless defined?(RubyInstaller) Rake::Task['compile'].prerequisites.unshift 'devkit' else if Rake::Task.tasks.map(&:name).include? 'cross' From 36b037d43634404090621760318f38c81322d544 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 19:55:13 -0500 Subject: [PATCH 545/783] Bump RuboCop This is the terminal version with Ruby 1.9 support. --- .rubocop.yml | 27 +++--- .rubocop_todo.yml | 231 ++++++++++++++++++++++++++++++++++++++++++---- Gemfile | 4 +- Rakefile | 16 +--- 4 files changed, 235 insertions(+), 43 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5ebfb8bdf..114bb7551 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,31 +1,28 @@ inherit_from: .rubocop_todo.yml AllCops: + TargetRubyVersion: 1.9 + DisplayCopNames: true Exclude: - 'pkg/**/*' - 'tmp/**/*' - 'vendor/**/*' -Lint/EndAlignment: - AlignWith: variable - -Style/CaseIndentation: - IndentWhenRelativeTo: end +Layout/CaseIndentation: + EnforcedStyle: end -Style/IndentHash: +Layout/IndentHash: EnforcedStyle: consistent -Style/TrailingComma: +Lint/EndAlignment: + EnforcedStyleAlignWith: variable + +Style/TrailingCommaInArguments: + EnforcedStyleForMultiline: consistent_comma + +Style/TrailingCommaInLiteral: EnforcedStyleForMultiline: consistent_comma Style/TrivialAccessors: AllowPredicates: true - -# TODO: remove when we end support for < 1.9.3 - -Style/HashSyntax: - EnforcedStyle: hash_rockets - -Style/Lambda: - Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 92b70061b..312ed558d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,50 +1,174 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2015-09-06 13:16:09 -0400 using RuboCop version 0.34.0. +# on 2017-11-25 19:54:28 -0500 using RuboCop version 0.50.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 15 +# Cop supports --auto-correct. +Layout/EmptyLineAfterMagicComment: + Exclude: + - 'Rakefile' + - 'benchmark/active_record.rb' + - 'benchmark/active_record_threaded.rb' + - 'benchmark/allocations.rb' + - 'benchmark/escape.rb' + - 'benchmark/query_with_mysql_casting.rb' + - 'benchmark/query_without_mysql_casting.rb' + - 'benchmark/sequel.rb' + - 'benchmark/setup_db.rb' + - 'ext/mysql2/extconf.rb' + - 'lib/mysql2.rb' + - 'spec/em/em_spec.rb' + - 'spec/mysql2/client_spec.rb' + - 'spec/mysql2/result_spec.rb' + - 'spec/mysql2/statement_spec.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent +Layout/IndentHeredoc: + Exclude: + - 'support/ruby_enc_to_mysql.rb' + - 'tasks/compile.rake' + +# Offense count: 2 +# Cop supports --auto-correct. +Layout/SpaceInsidePercentLiteralDelimiters: + Exclude: + - 'spec/mysql2/client_spec.rb' + +# Offense count: 2 +Lint/AmbiguousBlockAssociation: + Exclude: + - 'spec/mysql2/client_spec.rb' + +# Offense count: 1 +Lint/RescueWithoutErrorClass: + Exclude: + - 'lib/mysql2/em.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Lint/UnifiedInteger: + Exclude: + - 'spec/spec_helper.rb' + # Offense count: 2 Metrics/AbcSize: - Max: 93 + Max: 90 + +# Offense count: 31 +# Configuration parameters: CountComments, ExcludedMethods. +Metrics/BlockLength: + Max: 825 # Offense count: 1 +# Configuration parameters: CountBlocks. Metrics/BlockNesting: Max: 5 # Offense count: 1 +# Configuration parameters: CountComments. Metrics/ClassLength: Max: 125 -# Offense count: 2 +# Offense count: 3 Metrics/CyclomaticComplexity: Max: 30 -# Offense count: 290 -# Configuration parameters: AllowURI, URISchemes. +# Offense count: 313 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https Metrics/LineLength: Max: 232 -# Offense count: 5 +# Offense count: 6 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 60 + Max: 57 -# Offense count: 1 +# Offense count: 2 Metrics/PerceivedComplexity: Max: 27 -# Offense count: 40 +# Offense count: 1 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: lowercase, uppercase +Naming/HeredocDelimiterCase: + Exclude: + - 'support/ruby_enc_to_mysql.rb' + +# Offense count: 3 +# Configuration parameters: Blacklist. +# Blacklist: END, (?-mix:EO[A-Z]{1}) +Naming/HeredocDelimiterNaming: + Exclude: + - 'tasks/compile.rake' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect. +Performance/HashEachMethods: + Exclude: + - 'benchmark/active_record.rb' + - 'benchmark/allocations.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: MaxKeyValuePairs. +Performance/RedundantMerge: + Exclude: + - 'spec/mysql2/client_spec.rb' + - 'spec/mysql2/statement_spec.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect. +Performance/TimesMap: + Exclude: + - 'benchmark/active_record_threaded.rb' + - 'examples/threaded.rb' + - 'spec/mysql2/client_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: prefer_alias, prefer_alias_method +Style/Alias: + Exclude: + - 'lib/mysql2/error.rb' + +# Offense count: 48 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. +# SupportedStyles: line_count_based, semantic, braces_for_chaining +# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object +# FunctionalMethods: let, let!, subject, watch +# IgnoredMethods: lambda, proc, it Style/BlockDelimiters: - Enabled: false + Exclude: + - 'spec/em/em_spec.rb' + - 'spec/mysql2/client_spec.rb' + - 'spec/mysql2/result_spec.rb' + - 'spec/mysql2/statement_spec.rb' -# Offense count: 12 +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Exclude: + - 'support/ruby_enc_to_mysql.rb' + +# Offense count: 10 Style/Documentation: Exclude: + - 'spec/**/*' + - 'test/**/*' - 'benchmark/active_record.rb' - 'benchmark/allocations.rb' - 'benchmark/query_with_mysql_casting.rb' @@ -52,24 +176,99 @@ Style/Documentation: - 'lib/mysql2/client.rb' - 'lib/mysql2/em.rb' - 'lib/mysql2/error.rb' - - 'lib/mysql2/field.rb' - 'lib/mysql2/result.rb' - 'lib/mysql2/statement.rb' - - 'lib/mysql2/version.rb' -# Offense count: 9 +# Offense count: 21 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AutoCorrectEncodingComment. +# SupportedStyles: when_needed, always, never +Style/Encoding: + Enabled: false + +# Offense count: 14 # Configuration parameters: AllowedVariables. Style/GlobalVars: Exclude: - 'ext/mysql2/extconf.rb' -# Offense count: 14 +# Offense count: 1 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Exclude: + - 'spec/mysql2/client_spec.rb' + +# Offense count: 175 # Cop supports --auto-correct. +# Configuration parameters: SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. +# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys +Style/HashSyntax: + EnforcedStyle: hash_rockets + +# Offense count: 1 +Style/IfInsideElse: + Exclude: + - 'tasks/compile.rake' + +# Offense count: 4 +# Cop supports --auto-correct. +Style/MutableConstant: + Exclude: + - 'ext/mysql2/extconf.rb' + - 'lib/mysql2/version.rb' + - 'tasks/rspec.rake' + - 'tasks/vendor_mysql.rake' + +# Offense count: 17 +# Cop supports --auto-correct. +# Configuration parameters: Strict. Style/NumericLiterals: MinDigits: 20 -# Offense count: 680 +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'benchmark/setup_db.rb' + - 'lib/mysql2.rb' + +# Offense count: 15 +# Cop supports --auto-correct. +# Configuration parameters: PreferredDelimiters. +Style/PercentLiteralDelimiters: + Exclude: + - 'benchmark/active_record.rb' + - 'benchmark/active_record_threaded.rb' + - 'benchmark/setup_db.rb' + - 'ext/mysql2/extconf.rb' + - 'spec/mysql2/client_spec.rb' + - 'spec/mysql2/result_spec.rb' + - 'spec/mysql2/statement_spec.rb' + - 'tasks/rspec.rake' + +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: only_raise, only_fail, semantic +Style/SignalException: + Exclude: + - 'lib/mysql2/client.rb' + - 'spec/em/em_spec.rb' + +# Offense count: 726 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes Style/StringLiterals: Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: all_comparison_operators, equality_operators_only +Style/YodaCondition: + Exclude: + - 'lib/mysql2.rb' diff --git a/Gemfile b/Gemfile index 846e0fd53..d7badd2c5 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,9 @@ gem 'rake-compiler', '~> 1.0' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' - gem 'rubocop', '~> 0.34.0' + # https://github.com/bbatsov/rubocop/pull/3328 + # https://github.com/bbatsov/rubocop/pull/4789 + gem 'rubocop', '~> 0.50.0' unless RUBY_VERSION =~ /1.9/ end group :benchmarks do diff --git a/Rakefile b/Rakefile index 3d988cbc8..90749df21 100644 --- a/Rakefile +++ b/Rakefile @@ -8,17 +8,11 @@ load 'tasks/compile.rake' load 'tasks/generate.rake' load 'tasks/benchmarks.rake' -# TODO: remove engine check when rubinius stops crashing on RuboCop -has_rubocop = if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' - begin - require 'rubocop/rake_task' - RuboCop::RakeTask.new - task :default => [:spec, :rubocop] - rescue LoadError # rubocop:disable Lint/HandleExceptions - end -end - -unless has_rubocop +begin + require 'rubocop/rake_task' + RuboCop::RakeTask.new + task :default => [:spec, :rubocop] +rescue LoadError warn 'RuboCop is not available' task :default => :spec end From f389e1814f5ee1736d6763279e859db6903a57d6 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:00:28 -0500 Subject: [PATCH 546/783] Fix Lint/RescueWithoutErrorClass --- .rubocop_todo.yml | 5 ----- lib/mysql2/em.rb | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 312ed558d..4960d45c6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -46,11 +46,6 @@ Lint/AmbiguousBlockAssociation: Exclude: - 'spec/mysql2/client_spec.rb' -# Offense count: 1 -Lint/RescueWithoutErrorClass: - Exclude: - - 'lib/mysql2/em.rb' - # Offense count: 2 # Cop supports --auto-correct. Lint/UnifiedInteger: diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb index b4210f089..683b69544 100644 --- a/lib/mysql2/em.rb +++ b/lib/mysql2/em.rb @@ -17,7 +17,7 @@ def notify_readable detach begin result = @client.async_result - rescue => e + rescue StandardError => e @deferable.fail(e) else @deferable.succeed(result) From 54c20a676eb7fe32058ed163bff976734dc96142 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:03:36 -0500 Subject: [PATCH 547/783] Style/ConditionalAssignment --- .rubocop_todo.yml | 8 -------- lib/mysql2/client.rb | 10 +++++----- support/ruby_enc_to_mysql.rb | 6 +++--- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4960d45c6..95eeff41f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -151,14 +151,6 @@ Style/BlockDelimiters: - 'spec/mysql2/result_spec.rb' - 'spec/mysql2/statement_spec.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions. -# SupportedStyles: assign_to_condition, assign_inside_condition -Style/ConditionalAssignment: - Exclude: - - 'support/ruby_enc_to_mysql.rb' - # Offense count: 10 Style/Documentation: Exclude: diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 8b63bc8ec..c0c62b4a4 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -50,15 +50,15 @@ def initialize(opts = {}) ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify) self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode] - case opts[:flags] + flags = case opts[:flags] when Array - flags = parse_flags_array(opts[:flags], @query_options[:connect_flags]) + parse_flags_array(opts[:flags], @query_options[:connect_flags]) when String - flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags]) + parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags]) when Integer - flags = @query_options[:connect_flags] | opts[:flags] + @query_options[:connect_flags] | opts[:flags] else - flags = @query_options[:connect_flags] + @query_options[:connect_flags] end # SSL verify is a connection flag rather than a mysql_ssl_set option diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb index 52603b303..daea2bab3 100644 --- a/support/ruby_enc_to_mysql.rb +++ b/support/ruby_enc_to_mysql.rb @@ -51,10 +51,10 @@ header mysql_to_rb.each do |mysql, ruby| - if ruby.nil? - name = "NULL" + name = if ruby.nil? + "NULL" else - name = "\"#{ruby}\"" + "\"#{ruby}\"" end puts "#{mysql}, #{name}" From 72f50f3ae3b66ea11f1549da97d2147f6366d2de Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:11:33 -0500 Subject: [PATCH 548/783] Layout/IndentHeredoc --- .rubocop.yml | 3 +++ .rubocop_todo.yml | 9 --------- support/ruby_enc_to_mysql.rb | 16 ++++++++-------- tasks/compile.rake | 24 ++++++++++++------------ 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 114bb7551..27f6b5eec 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,6 +15,9 @@ Layout/CaseIndentation: Layout/IndentHash: EnforcedStyle: consistent +Layout/IndentHeredoc: + EnforcedStyle: powerpack + Lint/EndAlignment: EnforcedStyleAlignWith: variable diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 95eeff41f..fa24852f9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -26,15 +26,6 @@ Layout/EmptyLineAfterMagicComment: - 'spec/mysql2/result_spec.rb' - 'spec/mysql2/statement_spec.rb' -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent -Layout/IndentHeredoc: - Exclude: - - 'support/ruby_enc_to_mysql.rb' - - 'tasks/compile.rake' - # Offense count: 2 # Cop supports --auto-correct. Layout/SpaceInsidePercentLiteralDelimiters: diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb index daea2bab3..5dd90229d 100644 --- a/support/ruby_enc_to_mysql.rb +++ b/support/ruby_enc_to_mysql.rb @@ -40,14 +40,14 @@ "eucjpms" => "eucJP-ms", } -puts <<-header -%readonly-tables -%enum -%define lookup-function-name mysql2_mysql_enc_name_to_rb -%define hash-function-name mysql2_mysql_enc_name_to_rb_hash -%struct-type -struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; } -%% +puts <<-header.strip_indent + %readonly-tables + %enum + %define lookup-function-name mysql2_mysql_enc_name_to_rb + %define hash-function-name mysql2_mysql_enc_name_to_rb_hash + %struct-type + struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; } + %% header mysql_to_rb.each do |mysql, ruby| diff --git a/tasks/compile.rake b/tasks/compile.rake index fb339580e..87bb421e3 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -33,20 +33,20 @@ Rake::ExtensionTask.new("mysql2", Mysql2::GEMSPEC) do |ext| spec.files << 'lib/mysql2/mysql2.rb' spec.files << 'vendor/libmysql.dll' spec.files << 'vendor/README' - spec.post_install_message = <<-POST_INSTALL_MESSAGE + spec.post_install_message = <<-POST_INSTALL_MESSAGE.strip_indent -====================================================================================================== + ====================================================================================================== - You've installed the binary version of #{spec.name}. - It was built using MySQL Connector/C version #{CONNECTOR_VERSION}. - It's recommended to use the exact same version to avoid potential issues. + You've installed the binary version of #{spec.name}. + It was built using MySQL Connector/C version #{CONNECTOR_VERSION}. + It's recommended to use the exact same version to avoid potential issues. - At the time of building this gem, the necessary DLL files were retrieved from: - #{vendor_mysql_url(http://github.com/spec.platform)} + At the time of building this gem, the necessary DLL files were retrieved from: + #{vendor_mysql_url(http://github.com/spec.platform)} - This gem *includes* vendor/libmysql.dll with redistribution notice in vendor/README. + This gem *includes* vendor/libmysql.dll with redistribution notice in vendor/README. -====================================================================================================== + ====================================================================================================== POST_INSTALL_MESSAGE end @@ -64,9 +64,9 @@ end file 'lib/mysql2/mysql2.rb' do |t| name = Mysql2::GEMSPEC.name File.open(t.name, 'wb') do |f| - f.write <<-END_OF_RUBY -RUBY_VERSION =~ /(\\d+.\\d+)/ -require "#{name}/\#{$1}/#{name}" + f.write <<-END_OF_RUBY.strip_indent + RUBY_VERSION =~ /(\\d+.\\d+)/ + require "#{name}/\#{$1}/#{name}" END_OF_RUBY end end From e864fd69143fb7ff2153b9cba2cc8d0a1a0d2c43 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:12:53 -0500 Subject: [PATCH 549/783] Layout/EmptyLineAfterMagicComment --- .rubocop_todo.yml | 20 -------------------- Rakefile | 1 + benchmark/active_record.rb | 1 + benchmark/active_record_threaded.rb | 1 + benchmark/allocations.rb | 1 + benchmark/escape.rb | 1 + benchmark/query_with_mysql_casting.rb | 1 + benchmark/query_without_mysql_casting.rb | 1 + benchmark/sequel.rb | 1 + benchmark/setup_db.rb | 1 + ext/mysql2/extconf.rb | 1 + lib/mysql2.rb | 1 + spec/em/em_spec.rb | 1 + spec/mysql2/client_spec.rb | 1 + spec/mysql2/result_spec.rb | 1 + spec/mysql2/statement_spec.rb | 1 + 16 files changed, 15 insertions(+), 20 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index fa24852f9..7d718788f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,26 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 15 -# Cop supports --auto-correct. -Layout/EmptyLineAfterMagicComment: - Exclude: - - 'Rakefile' - - 'benchmark/active_record.rb' - - 'benchmark/active_record_threaded.rb' - - 'benchmark/allocations.rb' - - 'benchmark/escape.rb' - - 'benchmark/query_with_mysql_casting.rb' - - 'benchmark/query_without_mysql_casting.rb' - - 'benchmark/sequel.rb' - - 'benchmark/setup_db.rb' - - 'ext/mysql2/extconf.rb' - - 'lib/mysql2.rb' - - 'spec/em/em_spec.rb' - - 'spec/mysql2/client_spec.rb' - - 'spec/mysql2/result_spec.rb' - - 'spec/mysql2/statement_spec.rb' - # Offense count: 2 # Cop supports --auto-correct. Layout/SpaceInsidePercentLiteralDelimiters: diff --git a/Rakefile b/Rakefile index 90749df21..e3a45bd4a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,5 @@ # encoding: UTF-8 + require 'rake' # Load custom tasks (careful attention to define tasks before prerequisites) diff --git a/benchmark/active_record.rb b/benchmark/active_record.rb index e25df0ce7..af17dbda8 100644 --- a/benchmark/active_record.rb +++ b/benchmark/active_record.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/active_record_threaded.rb b/benchmark/active_record_threaded.rb index f55f3a10f..d0eaf2d8e 100644 --- a/benchmark/active_record_threaded.rb +++ b/benchmark/active_record_threaded.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/allocations.rb b/benchmark/allocations.rb index 4e3579313..02d8f467e 100644 --- a/benchmark/allocations.rb +++ b/benchmark/allocations.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/escape.rb b/benchmark/escape.rb index 2a666decd..5df93b341 100644 --- a/benchmark/escape.rb +++ b/benchmark/escape.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb index 7685926fa..9d0c1a4ef 100644 --- a/benchmark/query_with_mysql_casting.rb +++ b/benchmark/query_with_mysql_casting.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/query_without_mysql_casting.rb b/benchmark/query_without_mysql_casting.rb index 1afc7c9d3..0613c83b6 100644 --- a/benchmark/query_without_mysql_casting.rb +++ b/benchmark/query_without_mysql_casting.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/sequel.rb b/benchmark/sequel.rb index 470ba36be..afd67654c 100644 --- a/benchmark/sequel.rb +++ b/benchmark/sequel.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/setup_db.rb b/benchmark/setup_db.rb index 105d43ebc..698fc386b 100644 --- a/benchmark/setup_db.rb +++ b/benchmark/setup_db.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') # This script is for generating psudo-random data into a single table consisting of nearly every diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index c9350b45a..8b4e77674 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + require 'mkmf' require 'English' diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 9414a693d..1b56b7e7f 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + require 'date' require 'bigdecimal' diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index d676e2f93..2dd67a281 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + require 'spec_helper' begin require 'eventmachine' diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index c628acc82..cbd6590f4 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + require 'spec_helper' RSpec.describe Mysql2::Client do diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index f3b5a9350..815490713 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + require 'spec_helper' RSpec.describe Mysql2::Result do diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 76e20b5bf..d1ef1ba56 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 + require './spec/spec_helper.rb' RSpec.describe Mysql2::Statement do From 9ff6a03c57e998fcb2176407ea3befbddfcdea10 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:13:24 -0500 Subject: [PATCH 550/783] Layout/SpaceInsidePercentLiteralDelimiters --- .rubocop_todo.yml | 6 ------ spec/mysql2/client_spec.rb | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7d718788f..88553f90c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,12 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -# Cop supports --auto-correct. -Layout/SpaceInsidePercentLiteralDelimiters: - Exclude: - - 'spec/mysql2/client_spec.rb' - # Offense count: 2 Lint/AmbiguousBlockAssociation: Exclude: diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index cbd6590f4..812d09f13 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -63,7 +63,7 @@ def connect(*args) end it "should parse flags array" do - client = Klient.new :flags => %w( FOUND_ROWS -PROTOCOL_41 ) + client = Klient.new :flags => %w(FOUND_ROWS -PROTOCOL_41) expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end From e6cc0a104a05b02b7d8b2bbf4574e80d422013db Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:19:07 -0500 Subject: [PATCH 551/783] Lint/AmbiguousBlockAssociation --- .rubocop_todo.yml | 5 ----- spec/mysql2/client_spec.rb | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 88553f90c..d55a3c96e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,11 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -Lint/AmbiguousBlockAssociation: - Exclude: - - 'spec/mysql2/client_spec.rb' - # Offense count: 2 # Cop supports --auto-correct. Lint/UnifiedInteger: diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 812d09f13..fd58ff492 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -165,6 +165,7 @@ def run_gc end it "should terminate connections when calling close" do + # rubocop:disable Lint/AmbiguousBlockAssociation expect { client = Mysql2::Client.new(DatabaseCredentials['root']) connection_id = client.thread_id @@ -182,10 +183,12 @@ def run_gc }.to_not change { @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a } + # rubocop:enable Lint/AmbiguousBlockAssociation end it "should not leave dangling connections after garbage collection" do run_gc + # rubocop:disable Lint/AmbiguousBlockAssociation expect { expect { 10.times do @@ -200,6 +203,7 @@ def run_gc @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a } + # rubocop:enable Lint/AmbiguousBlockAssociation end context "#automatic_close" do From d0d8d0666fdf41e1e4416ce8005e306155bacaad Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:20:40 -0500 Subject: [PATCH 552/783] Lint/UnifiedInteger --- .rubocop_todo.yml | 6 ------ spec/spec_helper.rb | 2 ++ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d55a3c96e..641a06f1b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,12 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -# Cop supports --auto-correct. -Lint/UnifiedInteger: - Exclude: - - 'spec/spec_helper.rb' - # Offense count: 2 Metrics/AbcSize: Max: 90 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 045e78374..32e3b0069 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -37,7 +37,9 @@ def new_client(option_overrides = {}) end def num_classes + # rubocop:disable Lint/UnifiedInteger 0.class == Integer ? [Integer] : [Fixnum, Bignum] + # rubocop:enable Lint/UnifiedInteger end config.before :each do From 5272b3d676d77406f95f848bf9593607d37e96d0 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:21:34 -0500 Subject: [PATCH 553/783] Naming/HeredocDelimiterCase --- .rubocop_todo.yml | 7 ------- support/ruby_enc_to_mysql.rb | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 641a06f1b..0bc6798b1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -44,13 +44,6 @@ Metrics/MethodLength: Metrics/PerceivedComplexity: Max: 27 -# Offense count: 1 -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: lowercase, uppercase -Naming/HeredocDelimiterCase: - Exclude: - - 'support/ruby_enc_to_mysql.rb' - # Offense count: 3 # Configuration parameters: Blacklist. # Blacklist: END, (?-mix:EO[A-Z]{1}) diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb index 5dd90229d..4749ead75 100644 --- a/support/ruby_enc_to_mysql.rb +++ b/support/ruby_enc_to_mysql.rb @@ -40,7 +40,7 @@ "eucjpms" => "eucJP-ms", } -puts <<-header.strip_indent +puts <<-HEADER.strip_indent %readonly-tables %enum %define lookup-function-name mysql2_mysql_enc_name_to_rb @@ -48,7 +48,7 @@ %struct-type struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; } %% -header +HEADER mysql_to_rb.each do |mysql, ruby| name = if ruby.nil? From 13dfc463d1772bd6f3de10d9502bd346328f1357 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:50:17 -0500 Subject: [PATCH 554/783] Performance/HashEachMethods --- .rubocop_todo.yml | 8 -------- benchmark/active_record.rb | 2 +- benchmark/allocations.rb | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0bc6798b1..65d9c7a86 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,14 +51,6 @@ Naming/HeredocDelimiterNaming: Exclude: - 'tasks/compile.rake' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect. -Performance/HashEachMethods: - Exclude: - - 'benchmark/active_record.rb' - - 'benchmark/allocations.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: MaxKeyValuePairs. diff --git a/benchmark/active_record.rb b/benchmark/active_record.rb index af17dbda8..dbac66201 100644 --- a/benchmark/active_record.rb +++ b/benchmark/active_record.rb @@ -23,7 +23,7 @@ class TestModel < ActiveRecord::Base x.report(adapter) do TestModel.limit(batch_size).to_a.each do |r| - r.attributes.keys.each do |k| + r.attributes.each_key do |k| r.send(k.to_sym) end end diff --git a/benchmark/allocations.rb b/benchmark/allocations.rb index 02d8f467e..19b7c5a1d 100644 --- a/benchmark/allocations.rb +++ b/benchmark/allocations.rb @@ -24,7 +24,7 @@ def bench_allocations(feature, iterations = 10, batch_size = 1000) bench_allocations('coercion') do |batch_size| TestModel.limit(batch_size).to_a.each do |r| - r.attributes.keys.each do |k| + r.attributes.each_key do |k| r.send(k.to_sym) end end From b63cb77505355d47e4caf021b7bef772dd6b03f7 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:50:53 -0500 Subject: [PATCH 555/783] Performance/RedundantMerge --- .rubocop_todo.yml | 8 -------- spec/mysql2/client_spec.rb | 2 +- spec/mysql2/statement_spec.rb | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 65d9c7a86..2d325a6a1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,14 +51,6 @@ Naming/HeredocDelimiterNaming: Exclude: - 'tasks/compile.rake' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: MaxKeyValuePairs. -Performance/RedundantMerge: - Exclude: - - 'spec/mysql2/client_spec.rb' - - 'spec/mysql2/statement_spec.rb' - # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect. diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index fd58ff492..e0a88c78a 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -493,7 +493,7 @@ def run_gc end it "should allow changing query options for subsequent queries" do - @client.query_options.merge!(:something => :else) + @client.query_options[:something] = :else result = @client.query "SELECT 1" expect(@client.query_options[:something]).to eql(:else) expect(result.instance_variable_get('@query_options')[:something]).to eql(:else) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index d1ef1ba56..1a9c8ffd4 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -196,7 +196,7 @@ def stmt_count end it "should warn but still work if cache_rows is set to false" do - @client.query_options.merge!(:cache_rows => false) + @client.query_options[:cache_rows] = false statement = @client.prepare 'SELECT 1' result = nil expect { result = statement.execute.to_a }.to output(/:cache_rows is forced for prepared statements/).to_stderr From c90c47181130cf52c3d77cc890292648ec6b798d Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:53:08 -0500 Subject: [PATCH 556/783] Performance/TimesMap --- .rubocop_todo.yml | 9 --------- benchmark/active_record_threaded.rb | 2 +- examples/threaded.rb | 2 +- spec/mysql2/client_spec.rb | 2 +- 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2d325a6a1..247f1cb26 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,15 +51,6 @@ Naming/HeredocDelimiterNaming: Exclude: - 'tasks/compile.rake' -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect. -Performance/TimesMap: - Exclude: - - 'benchmark/active_record_threaded.rb' - - 'examples/threaded.rb' - - 'spec/mysql2/client_spec.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. diff --git a/benchmark/active_record_threaded.rb b/benchmark/active_record_threaded.rb index d0eaf2d8e..d64a1cebe 100644 --- a/benchmark/active_record_threaded.rb +++ b/benchmark/active_record_threaded.rb @@ -14,7 +14,7 @@ ActiveRecord::Base.establish_connection(opts.merge(:adapter => adapter)) x.report(adapter) do - number_of_threads.times.map do + Array.new(number_of_threads) do Thread.new { ActiveRecord::Base.connection.execute('SELECT SLEEP(1)') } end.each(&:join) end diff --git a/examples/threaded.rb b/examples/threaded.rb index 489edaf9c..e8b238098 100644 --- a/examples/threaded.rb +++ b/examples/threaded.rb @@ -6,7 +6,7 @@ # Should never exceed worst case 3.5 secs across all 20 threads Timeout.timeout(3.5) do - 20.times.map do + Array.new(20) do Thread.new do overhead = rand(3) puts ">> thread #{Thread.current.object_id} query, #{overhead} sec overhead" diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index e0a88c78a..b4fe0de55 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -658,7 +658,7 @@ def run_gc sleep_time = 0.5 # Note that each thread opens its own database connection - threads = 5.times.map do + threads = Array.new(5) do Thread.new do new_client do |client| client.query("SELECT SLEEP(#{sleep_time})") From 9d9d02d8cfad8f0f1c423f396601e6273bfe09a9 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 20:56:53 -0500 Subject: [PATCH 557/783] Style/Alias --- .rubocop_todo.yml | 8 -------- lib/mysql2/error.rb | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 247f1cb26..5faa49c04 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,14 +51,6 @@ Naming/HeredocDelimiterNaming: Exclude: - 'tasks/compile.rake' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: prefer_alias, prefer_alias_method -Style/Alias: - Exclude: - - 'lib/mysql2/error.rb' - # Offense count: 48 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index d75a0ca84..159865b8a 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -11,8 +11,8 @@ class Error < StandardError attr_reader :error_number, :sql_state # Mysql gem compatibility - alias_method :errno, :error_number - alias_method :error, :message + alias errno error_number + alias error message def initialize(msg) @server_version ||= nil From 1e2b3ab8c446c797894446ef4ccafff0b86dd3f3 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 21:07:53 -0500 Subject: [PATCH 558/783] Style/BlockDelimiters --- .rubocop_todo.yml | 14 --- spec/em/em_spec.rb | 8 +- spec/mysql2/client_spec.rb | 164 +++++++++++++++++----------------- spec/mysql2/result_spec.rb | 16 ++-- spec/mysql2/statement_spec.rb | 4 +- 5 files changed, 96 insertions(+), 110 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5faa49c04..71a153860 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -51,20 +51,6 @@ Naming/HeredocDelimiterNaming: Exclude: - 'tasks/compile.rake' -# Offense count: 48 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. -# SupportedStyles: line_count_based, semantic, braces_for_chaining -# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object -# FunctionalMethods: let, let!, subject, watch -# IgnoredMethods: lambda, proc, it -Style/BlockDelimiters: - Exclude: - - 'spec/em/em_spec.rb' - - 'spec/mysql2/client_spec.rb' - - 'spec/mysql2/result_spec.rb' - - 'spec/mysql2/statement_spec.rb' - # Offense count: 10 Style/Documentation: Exclude: diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 2dd67a281..35e381a54 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -50,7 +50,7 @@ end it "should not swallow exceptions raised in callbacks" do - expect { + expect do EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query "SELECT sleep(0.1) as first_query" @@ -64,7 +64,7 @@ EM.stop_event_loop end end - }.to raise_error('some error') + end.to raise_error('some error') end context 'when an exception is raised by the client' do @@ -124,9 +124,9 @@ end EM.add_timer(0.1) do expect(callbacks_run).to eq([:callback]) - expect { + expect do client.close - }.not_to raise_error + end.not_to raise_error EM.stop_event_loop end end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index b4fe0de55..7841b905d 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -7,46 +7,46 @@ let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) } it "should not raise an exception for valid defaults group" do - expect { + expect do new_client(:default_file => cnf_file, :default_group => "test") - }.not_to raise_error + end.not_to raise_error end it "should not raise an exception without default group" do - expect { + expect do new_client(:default_file => cnf_file) - }.not_to raise_error + end.not_to raise_error end end it "should raise an exception upon connection failure" do - expect { + expect do # The odd local host IP address forces the mysql client library to # use a TCP socket rather than a domain socket. new_client('host' => '127.0.0.2', 'port' => 999999) - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "should raise an exception on create for invalid encodings" do - expect { + expect do new_client(:encoding => "fake") - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "should raise an exception on non-string encodings" do - expect { + expect do new_client(:encoding => :fake) - }.to raise_error(TypeError) + end.to raise_error(TypeError) end it "should not raise an exception on create for a valid encoding" do - expect { + expect do new_client(:encoding => "utf8") - }.not_to raise_error + end.not_to raise_error - expect { + expect do new_client(DatabaseCredentials['root'].merge(:encoding => "big5")) - }.not_to raise_error + end.not_to raise_error end Klient = Class.new(Mysql2::Client) do @@ -136,7 +136,7 @@ def connect(*args) # You may need to adjust the lines below to match your SSL certificate paths ssl_client = nil - expect { + expect do ssl_client = new_client( 'host' => 'mysql2gem.example.com', # must match the certificates :sslkey => '/etc/mysql/client-key.pem', @@ -145,7 +145,7 @@ def connect(*args) :sslcipher => 'DHE-RSA-AES256-SHA', :sslverify => true, ) - }.not_to raise_error + end.not_to raise_error results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }] expect(results['Ssl_cipher']).not_to be_empty @@ -166,7 +166,7 @@ def run_gc it "should terminate connections when calling close" do # rubocop:disable Lint/AmbiguousBlockAssociation - expect { + expect do client = Mysql2::Client.new(DatabaseCredentials['root']) connection_id = client.thread_id client.close @@ -180,7 +180,7 @@ def run_gc sleep(0.1) end expect(closed).to eq(true) - }.to_not change { + end.to_not change { @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a } # rubocop:enable Lint/AmbiguousBlockAssociation @@ -189,17 +189,17 @@ def run_gc it "should not leave dangling connections after garbage collection" do run_gc # rubocop:disable Lint/AmbiguousBlockAssociation - expect { - expect { + expect do + expect do 10.times do Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1') end - }.to change { + end.to change { @client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i }.by(10) run_gc - }.to_not change { + end.to_not change { @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a } @@ -275,9 +275,9 @@ def run_gc database = 1235 @client.query "CREATE DATABASE IF NOT EXISTS `#{database}`" - expect { + expect do new_client('database' => database) - }.not_to raise_error + end.not_to raise_error @client.query "DROP DATABASE IF EXISTS `#{database}`" end @@ -288,9 +288,9 @@ def run_gc it "should be able to close properly" do expect(@client.close).to be_nil - expect { + expect do @client.query "SELECT 1" - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end context "#closed?" do @@ -307,9 +307,9 @@ def run_gc it "should not try to query closed mysql connection" do client = new_client(:reconnect => true) expect(client.close).to be_nil - expect { + expect do client.query "SELECT 1" - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "should respond to #query" do @@ -391,16 +391,16 @@ def run_gc it "should raise an error when local_infile is disabled" do client = new_client(:local_infile => false) - expect { + expect do client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" - }.to raise_error(Mysql2::Error, /command is not allowed/) + end.to raise_error(Mysql2::Error, /command is not allowed/) end it "should raise an error when a non-existent file is loaded" do client = new_client(:local_infile => true) - expect { + expect do client.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest" - }.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here') + end.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here') end it "should LOAD DATA LOCAL INFILE" do @@ -415,21 +415,21 @@ def run_gc end it "should expect connect_timeout to be a positive integer" do - expect { + expect do new_client(:connect_timeout => -1) - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "should expect read_timeout to be a positive integer" do - expect { + expect do new_client(:read_timeout => -1) - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "should expect write_timeout to be a positive integer" do - expect { + expect do new_client(:write_timeout => -1) - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "should allow nil read_timeout" do @@ -462,23 +462,23 @@ def run_gc it "should let you query again if iterating is finished when streaming" do @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a - expect { + expect do @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) - }.to_not raise_error + end.to_not raise_error end it "should not let you query again if iterating is not finished when streaming" do @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).first - expect { + expect do @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) - }.to raise_exception(Mysql2::Error) + end.to raise_exception(Mysql2::Error) end it "should only accept strings as the query parameter" do - expect { + expect do @client.query ["SELECT 'not right'"] - }.to raise_error(TypeError) + end.to raise_error(TypeError) end it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do @@ -518,9 +518,9 @@ def run_gc it "should require an open connection" do @client.close - expect { + expect do @client.query "SELECT 1" - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "should detect closed connection on query read error" do @@ -531,23 +531,23 @@ def run_gc supervisor.query("KILL #{connection_id}") end.close end - expect { + expect do @client.query("SELECT SLEEP(1)") - }.to raise_error(Mysql2::Error, /Lost connection to MySQL server/) + end.to raise_error(Mysql2::Error, /Lost connection to MySQL server/) if RUBY_PLATFORM !~ /mingw|mswin/ - expect { + expect do @client.socket - }.to raise_error(Mysql2::Error, 'MySQL client is not connected') + end.to raise_error(Mysql2::Error, 'MySQL client is not connected') end end if RUBY_PLATFORM !~ /mingw|mswin/ it "should not allow another query to be sent without fetching a result first" do @client.query("SELECT 1", :async => true) - expect { + expect do @client.query("SELECT 1") - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "should describe the thread holding the active query" do @@ -559,9 +559,9 @@ def run_gc it "should timeout if we wait longer than :read_timeout" do client = new_client(:read_timeout => 0) - expect { + expect do client.query('SELECT SLEEP(0.1)') - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end # XXX this test is not deterministic (because Unix signal handling is not) @@ -601,9 +601,9 @@ def run_gc it "#socket should require an open connection" do @client.close - expect { + expect do @client.socket - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it 'should be impervious to connection-corrupting timeouts in #execute' do @@ -705,9 +705,9 @@ def run_gc it "should raise an exception when one of multiple statements fails" do result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';") expect(result.first['set_1']).to be(1) - expect { + expect do @multi_client.next_result - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) expect(@multi_client.next_result).to be false end @@ -729,17 +729,17 @@ def run_gc it "will raise on query if there are outstanding results to read" do @multi_client.query("SELECT 1; SELECT 2; SELECT 3") - expect { + expect do @multi_client.query("SELECT 4") - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "#abandon_results! should work" do @multi_client.query("SELECT 1; SELECT 2; SELECT 3") @multi_client.abandon_results! - expect { + expect do @multi_client.query("SELECT 4") - }.not_to raise_error + end.not_to raise_error end it "#more_results? should work" do @@ -775,9 +775,9 @@ def run_gc if RUBY_PLATFORM =~ /mingw|mswin/ it "#socket should raise as it's not supported" do - expect { + expect do @client.socket - }.to raise_error(Mysql2::Error, /Raw access to the mysql file descriptor isn't supported on Windows/) + end.to raise_error(Mysql2::Error, /Raw access to the mysql file descriptor isn't supported on Windows/) end end @@ -796,15 +796,15 @@ def run_gc end it "should not overflow the thread stack" do - expect { + expect do Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join - }.not_to raise_error + end.not_to raise_error end it "should not overflow the process stack" do - expect { + expect do Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join - }.not_to raise_error + end.not_to raise_error end it "should carry over the original string's encoding" do @@ -833,22 +833,22 @@ def run_gc end it "should not overflow the thread stack" do - expect { + expect do Thread.new { @client.escape("'" * 256 * 1024) }.join - }.not_to raise_error + end.not_to raise_error end it "should not overflow the process stack" do - expect { + expect do Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join - }.not_to raise_error + end.not_to raise_error end it "should require an open connection" do @client.close - expect { + expect do @client.escape "" - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end context 'when mysql encoding is not utf8' do @@ -905,9 +905,9 @@ def run_gc it "#server_info should require an open connection" do @client.close - expect { + expect do @client.server_info - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end context "strings returned by #server_info" do @@ -932,13 +932,13 @@ def run_gc end it "should raise a Mysql2::Error exception upon connection failure" do - expect { + expect do new_client(:host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42') - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) - expect { + expect do new_client(DatabaseCredentials['root']) - }.not_to raise_error + end.not_to raise_error end context 'write operations api' do @@ -1023,9 +1023,9 @@ def run_gc end it "should raise a Mysql2::Error when the database doesn't exist" do - expect { + expect do @client.select_db("nopenothere") - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) end it "should return the database switched to" do diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 815490713..65b1076ba 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -28,13 +28,13 @@ end it "should raise a Mysql2::Error exception upon a bad query" do - expect { + expect do @client.query "bad sql" - }.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error) - expect { + expect do @client.query "SELECT 1" - }.not_to raise_error + end.not_to raise_error end it "should respond to #count, which is aliased as #size" do @@ -101,10 +101,10 @@ it "should throw an exception if we try to iterate twice when streaming is enabled" do result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false - expect { + expect do result.each.to_a result.each.to_a - }.to raise_exception(Mysql2::Error) + end.to raise_exception(Mysql2::Error) end end @@ -165,12 +165,12 @@ @client.query "SET net_write_timeout = 1" res = @client.query "SELECT * FROM streamingTest", :stream => true, :cache_rows => false - expect { + expect do res.each_with_index do |_, i| # Exhaust the first result packet then trigger a timeout sleep 2 if i > 0 && i % 1000 == 0 end - }.to raise_error(Mysql2::Error, /Lost connection/) + end.to raise_error(Mysql2::Error, /Lost connection/) end end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 1a9c8ffd4..fc56bcd02 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -324,10 +324,10 @@ def stmt_count result = @client.prepare("SELECT 1 UNION SELECT 2").execute - expect { + expect do result.each {} result.each {} - }.to raise_exception(Mysql2::Error) + end.to raise_exception(Mysql2::Error) @client.query_options[:stream] = false @client.query_options[:cache_rows] = true From 972585e5dc95f1bea1b7c020456b6bc53cdda02b Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 21:21:47 -0500 Subject: [PATCH 559/783] Style/Encoding --- .rubocop.yml | 4 ++++ .rubocop_todo.yml | 7 ------- Gemfile | 2 ++ lib/mysql2/client.rb | 2 ++ lib/mysql2/console.rb | 2 ++ lib/mysql2/field.rb | 2 ++ lib/mysql2/result.rb | 2 ++ lib/mysql2/statement.rb | 2 ++ lib/mysql2/version.rb | 2 ++ mysql2.gemspec | 2 ++ support/mysql_enc_to_ruby.rb | 2 ++ support/ruby_enc_to_mysql.rb | 2 ++ tasks/benchmarks.rake | 2 ++ tasks/compile.rake | 2 ++ tasks/generate.rake | 2 ++ tasks/rspec.rake | 2 ++ tasks/vendor_mysql.rake | 2 ++ 17 files changed, 34 insertions(+), 7 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 27f6b5eec..8f634d507 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,10 @@ Layout/IndentHeredoc: Lint/EndAlignment: EnforcedStyleAlignWith: variable +Style/Encoding: + AutoCorrectEncodingComment: '# encoding: UTF-8' + EnforcedStyle: always + Style/TrailingCommaInArguments: EnforcedStyleForMultiline: consistent_comma diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 71a153860..6e065b9c7 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -66,13 +66,6 @@ Style/Documentation: - 'lib/mysql2/result.rb' - 'lib/mysql2/statement.rb' -# Offense count: 21 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, AutoCorrectEncodingComment. -# SupportedStyles: when_needed, always, never -Style/Encoding: - Enabled: false - # Offense count: 14 # Configuration parameters: AllowedVariables. Style/GlobalVars: diff --git a/Gemfile b/Gemfile index d7badd2c5..3f64bcc33 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# encoding: UTF-8 + source 'https://rubygems.org/' gemspec diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index c0c62b4a4..5eb53293b 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + module Mysql2 class Client attr_reader :query_options, :read_timeout diff --git a/lib/mysql2/console.rb b/lib/mysql2/console.rb index d8fb9e324..1e3885ffa 100644 --- a/lib/mysql2/console.rb +++ b/lib/mysql2/console.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + # Loaded by script/console. Land helpers here. Pry.config.prompt = lambda do |context, *| diff --git a/lib/mysql2/field.rb b/lib/mysql2/field.rb index 516ec17c2..94a006739 100644 --- a/lib/mysql2/field.rb +++ b/lib/mysql2/field.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + module Mysql2 Field = Struct.new(:name, :type) end diff --git a/lib/mysql2/result.rb b/lib/mysql2/result.rb index 585104e0b..255026a2b 100644 --- a/lib/mysql2/result.rb +++ b/lib/mysql2/result.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + module Mysql2 class Result attr_reader :server_flags diff --git a/lib/mysql2/statement.rb b/lib/mysql2/statement.rb index 5b3752651..482ccce19 100644 --- a/lib/mysql2/statement.rb +++ b/lib/mysql2/statement.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + module Mysql2 class Statement include Enumerable diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index f7d30593d..1426f2519 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + module Mysql2 VERSION = "0.4.10" end diff --git a/mysql2.gemspec b/mysql2.gemspec index 1c5b42f0f..3e9d4e5e3 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -1,3 +1,5 @@ +# encoding: UTF-8 + require File.expand_path('../lib/mysql2/version', __FILE__) Mysql2::GEMSPEC = Gem::Specification.new do |s| diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index fbe1562c3..183429518 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'mysql2' diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb index 4749ead75..120431e6e 100644 --- a/support/ruby_enc_to_mysql.rb +++ b/support/ruby_enc_to_mysql.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + mysql_to_rb = { "big5" => "Big5", "dec8" => nil, diff --git a/tasks/benchmarks.rake b/tasks/benchmarks.rake index b587ecdc0..8302f62a3 100644 --- a/tasks/benchmarks.rake +++ b/tasks/benchmarks.rake @@ -1,3 +1,5 @@ +# encoding: UTF-8 + BENCHMARKS = Dir["#{File.dirname(__FILE__)}/../benchmark/*.rb"].map do |path| File.basename(path, '.rb') end - ['setup_db'] diff --git a/tasks/compile.rake b/tasks/compile.rake index 87bb421e3..adaf0e941 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -1,3 +1,5 @@ +# encoding: UTF-8 + require "rake/extensiontask" load File.expand_path('../../mysql2.gemspec', __FILE__) unless defined? Mysql2::GEMSPEC diff --git a/tasks/generate.rake b/tasks/generate.rake index 6e5eebd99..2712eeaee 100644 --- a/tasks/generate.rake +++ b/tasks/generate.rake @@ -1,3 +1,5 @@ +# encoding: UTF-8 + task :encodings do sh "ruby support/mysql_enc_to_ruby.rb > ./ext/mysql2/mysql_enc_to_ruby.h" sh "ruby support/ruby_enc_to_mysql.rb | gperf > ./ext/mysql2/mysql_enc_name_to_ruby.h" diff --git a/tasks/rspec.rake b/tasks/rspec.rake index 4be4d63dc..7a08ff676 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -1,3 +1,5 @@ +# encoding: UTF-8 + begin require 'rspec' require 'rspec/core/rake_task' diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index d88b6f177..37a052997 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -1,3 +1,5 @@ +# encoding: UTF-8 + require 'rake/clean' require 'rake/extensioncompiler' From 03b2587baf13836b4acd7271576ae8c6d6bd6c4c Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 21:33:51 -0500 Subject: [PATCH 560/783] Style/GuardClause --- .rubocop_todo.yml | 6 ------ spec/mysql2/client_spec.rb | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6e065b9c7..922b0998b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -72,12 +72,6 @@ Style/GlobalVars: Exclude: - 'ext/mysql2/extconf.rb' -# Offense count: 1 -# Configuration parameters: MinBodyLength. -Style/GuardClause: - Exclude: - - 'spec/mysql2/client_spec.rb' - # Offense count: 175 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 7841b905d..9240d765d 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -681,13 +681,7 @@ def run_gc io_wrapper = IO.for_fd(@client.socket, :autoclose => false) loops = 0 - loop do - if IO.select([io_wrapper], nil, nil, 0.05) - break - else - loops += 1 - end - end + loops += 1 until IO.select([io_wrapper], nil, nil, 0.05) # make sure we waited some period of time expect(loops >= 1).to be true From ac81a7c98284292aaf95f39505bfc179e45c3ca6 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 21:34:48 -0500 Subject: [PATCH 561/783] Style/HashSyntax --- .rubocop_todo.yml | 7 -- Rakefile | 4 +- benchmark/active_record.rb | 4 +- benchmark/active_record_threaded.rb | 4 +- benchmark/allocations.rb | 2 +- benchmark/escape.rb | 2 +- benchmark/query_with_mysql_casting.rb | 4 +- benchmark/query_without_mysql_casting.rb | 6 +- benchmark/setup_db.rb | 62 ++++++++-------- examples/threaded.rb | 2 +- lib/mysql2/client.rb | 22 +++--- lib/mysql2/em.rb | 2 +- lib/mysql2/error.rb | 6 +- spec/mysql2/client_spec.rb | 92 ++++++++++++------------ spec/mysql2/result_spec.rb | 42 +++++------ spec/mysql2/statement_spec.rb | 14 ++-- support/mysql_enc_to_ruby.rb | 4 +- 17 files changed, 136 insertions(+), 143 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 922b0998b..65ced07e0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -72,13 +72,6 @@ Style/GlobalVars: Exclude: - 'ext/mysql2/extconf.rb' -# Offense count: 175 -# Cop supports --auto-correct. -# Configuration parameters: SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. -# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -Style/HashSyntax: - EnforcedStyle: hash_rockets - # Offense count: 1 Style/IfInsideElse: Exclude: diff --git a/Rakefile b/Rakefile index e3a45bd4a..1b7a093cf 100644 --- a/Rakefile +++ b/Rakefile @@ -12,8 +12,8 @@ load 'tasks/benchmarks.rake' begin require 'rubocop/rake_task' RuboCop::RakeTask.new - task :default => [:spec, :rubocop] + task default: [:spec, :rubocop] rescue LoadError warn 'RuboCop is not available' - task :default => :spec + task default: :spec end diff --git a/benchmark/active_record.rb b/benchmark/active_record.rb index dbac66201..099091b04 100644 --- a/benchmark/active_record.rb +++ b/benchmark/active_record.rb @@ -9,7 +9,7 @@ ActiveRecord::Base.default_timezone = :local ActiveRecord::Base.time_zone_aware_attributes = true -opts = { :database => 'test' } +opts = { database: 'test' } class TestModel < ActiveRecord::Base self.table_name = 'mysql2_test' @@ -19,7 +19,7 @@ class TestModel < ActiveRecord::Base Benchmark.ips do |x| %w(mysql mysql2).each do |adapter| - TestModel.establish_connection(opts.merge(:adapter => adapter)) + TestModel.establish_connection(opts.merge(adapter: adapter)) x.report(adapter) do TestModel.limit(batch_size).to_a.each do |r| diff --git a/benchmark/active_record_threaded.rb b/benchmark/active_record_threaded.rb index d64a1cebe..7aa3e1990 100644 --- a/benchmark/active_record_threaded.rb +++ b/benchmark/active_record_threaded.rb @@ -7,11 +7,11 @@ require 'active_record' number_of_threads = 25 -opts = { :database => 'test', :pool => number_of_threads } +opts = { database: 'test', pool: number_of_threads } Benchmark.ips do |x| %w(mysql mysql2).each do |adapter| - ActiveRecord::Base.establish_connection(opts.merge(:adapter => adapter)) + ActiveRecord::Base.establish_connection(opts.merge(adapter: adapter)) x.report(adapter) do Array.new(number_of_threads) do diff --git a/benchmark/allocations.rb b/benchmark/allocations.rb index 19b7c5a1d..372ffb9a2 100644 --- a/benchmark/allocations.rb +++ b/benchmark/allocations.rb @@ -14,7 +14,7 @@ class TestModel < ActiveRecord::Base def bench_allocations(feature, iterations = 10, batch_size = 1000) puts "GC overhead for #{feature}" - TestModel.establish_connection(:adapter => 'mysql2', :database => 'test') + TestModel.establish_connection(adapter: 'mysql2', database: 'test') GC::Profiler.clear GC::Profiler.enable iterations.times { yield batch_size } diff --git a/benchmark/escape.rb b/benchmark/escape.rb index 5df93b341..fad2190e6 100644 --- a/benchmark/escape.rb +++ b/benchmark/escape.rb @@ -16,7 +16,7 @@ def run_escape_benchmarks(str) mysql.quote str end - mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root") + mysql2 = Mysql2::Client.new(host: "localhost", username: "root") x.report "Mysql2 #{str.inspect}" do mysql2.escape str end diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb index 9d0c1a4ef..33fa024f2 100644 --- a/benchmark/query_with_mysql_casting.rb +++ b/benchmark/query_with_mysql_casting.rb @@ -42,10 +42,10 @@ def mysql_cast(type, value) debug = ENV['DEBUG'] Benchmark.ips do |x| - mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root") + mysql2 = Mysql2::Client.new(host: "localhost", username: "root") mysql2.query "USE #{database}" x.report "Mysql2" do - mysql2_result = mysql2.query sql, :symbolize_keys => true + mysql2_result = mysql2.query sql, symbolize_keys: true mysql2_result.each { |res| puts res.inspect if debug } end diff --git a/benchmark/query_without_mysql_casting.rb b/benchmark/query_without_mysql_casting.rb index 0613c83b6..1fc6af235 100644 --- a/benchmark/query_without_mysql_casting.rb +++ b/benchmark/query_without_mysql_casting.rb @@ -14,15 +14,15 @@ debug = ENV['DEBUG'] Benchmark.ips do |x| - mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root") + mysql2 = Mysql2::Client.new(host: "localhost", username: "root") mysql2.query "USE #{database}" x.report "Mysql2 (cast: true)" do - mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => true + mysql2_result = mysql2.query sql, symbolize_keys: true, cast: true mysql2_result.each { |res| puts res.inspect if debug } end x.report "Mysql2 (cast: false)" do - mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => false + mysql2_result = mysql2.query sql, symbolize_keys: true, cast: false mysql2_result.each { |res| puts res.inspect if debug } end diff --git a/benchmark/setup_db.rb b/benchmark/setup_db.rb index 698fc386b..711fada03 100644 --- a/benchmark/setup_db.rb +++ b/benchmark/setup_db.rb @@ -50,7 +50,7 @@ ] # connect to localhost by default, pass options as needed -@client = Mysql2::Client.new :host => "localhost", :username => "root", :database => "test" +@client = Mysql2::Client.new host: "localhost", username: "root", database: "test" @client.query create_table_sql @client.query 'TRUNCATE mysql2_test' @@ -81,36 +81,36 @@ def insert_record(args) five_words = Faker::Lorem.words(rand(5)) twenty5_paragraphs = Faker::Lorem.paragraphs(rand(25)) insert_record( - :bit_test => 1, - :tiny_int_test => rand(128), - :small_int_test => rand(32767), - :medium_int_test => rand(8388607), - :int_test => rand(2147483647), - :big_int_test => rand(9223372036854775807), - :float_test => rand(32767) / 1.87, - :float_zero_test => 0.0, - :double_test => rand(8388607) / 1.87, - :decimal_test => rand(8388607) / 1.87, - :decimal_zero_test => 0, - :date_test => '2010-4-4', - :date_time_test => '2010-4-4 11:44:00', - :timestamp_test => '2010-4-4 11:44:00', - :time_test => '11:44:00', - :year_test => Time.now.year, - :char_test => five_words.join.slice(0, 10), # CHAR(10) - :varchar_test => five_words.join.slice(0, 10), # VARCHAR(10) - :binary_test => five_words.join.byteslice(0, 10), # BINARY(10) - :varbinary_test => five_words.join.byteslice(0, 10), # VARBINARY(10) - :tiny_blob_test => five_words.join.byteslice(0, 255), # TINYBLOB - :tiny_text_test => Faker::Lorem.paragraph(rand(5)).byteslice(0, 255), # TINYTEXT - :blob_test => twenty5_paragraphs, - :text_test => twenty5_paragraphs, - :medium_blob_test => twenty5_paragraphs, - :medium_text_test => twenty5_paragraphs, - :long_blob_test => twenty5_paragraphs, - :long_text_test => twenty5_paragraphs, - :enum_test => %w(val1 val2).sample, - :set_test => %w(val1 val2 val1,val2).sample, + bit_test: 1, + tiny_int_test: rand(128), + small_int_test: rand(32767), + medium_int_test: rand(8388607), + int_test: rand(2147483647), + big_int_test: rand(9223372036854775807), + float_test: rand(32767) / 1.87, + float_zero_test: 0.0, + double_test: rand(8388607) / 1.87, + decimal_test: rand(8388607) / 1.87, + decimal_zero_test: 0, + date_test: '2010-4-4', + date_time_test: '2010-4-4 11:44:00', + timestamp_test: '2010-4-4 11:44:00', + time_test: '11:44:00', + year_test: Time.now.year, + char_test: five_words.join.slice(0, 10), # CHAR(10) + varchar_test: five_words.join.slice(0, 10), # VARCHAR(10) + binary_test: five_words.join.byteslice(0, 10), # BINARY(10) + varbinary_test: five_words.join.byteslice(0, 10), # VARBINARY(10) + tiny_blob_test: five_words.join.byteslice(0, 255), # TINYBLOB + tiny_text_test: Faker::Lorem.paragraph(rand(5)).byteslice(0, 255), # TINYTEXT + blob_test: twenty5_paragraphs, + text_test: twenty5_paragraphs, + medium_blob_test: twenty5_paragraphs, + medium_text_test: twenty5_paragraphs, + long_blob_test: twenty5_paragraphs, + long_text_test: twenty5_paragraphs, + enum_test: %w(val1 val2).sample, + set_test: %w(val1 val2 val1,val2).sample, ) if n % 100 == 0 $stdout.putc '.' diff --git a/examples/threaded.rb b/examples/threaded.rb index e8b238098..eaf769f8b 100644 --- a/examples/threaded.rb +++ b/examples/threaded.rb @@ -11,7 +11,7 @@ overhead = rand(3) puts ">> thread #{Thread.current.object_id} query, #{overhead} sec overhead" # 3 second overhead per query - Mysql2::Client.new(:host => "localhost", :username => "root").query("SELECT sleep(#{overhead}) as result") + Mysql2::Client.new(host: "localhost", username: "root").query("SELECT sleep(#{overhead}) as result") puts "<< thread #{Thread.current.object_id} result, #{overhead} sec overhead" end end.each(&:join) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 5eb53293b..1fc59ede6 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -6,17 +6,17 @@ class Client def self.default_query_options @default_query_options ||= { - :as => :hash, # the type of object you want each row back as; also supports :array (an array of values) - :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result - :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby - :symbolize_keys => false, # return field names as symbols instead of strings - :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in - :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller - :cache_rows => true, # tells Mysql2 to use its internal row cache for results - :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION, - :cast => true, - :default_file => nil, - :default_group => nil, + as: :hash, # the type of object you want each row back as; also supports :array (an array of values) + async: false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result + cast_booleans: false, # cast tinyint(1) fields as true/false in ruby + symbolize_keys: false, # return field names as symbols instead of strings + database_timezone: :local, # timezone Mysql2 will assume datetime objects are stored in + application_timezone: nil, # timezone Mysql2 will convert to before handing the object back to the caller + cache_rows: true, # tells Mysql2 to use its internal row cache for results + connect_flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION, + cast: true, + default_file: nil, + default_group: nil, } end diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb index 683b69544..381a2a739 100644 --- a/lib/mysql2/em.rb +++ b/lib/mysql2/em.rb @@ -41,7 +41,7 @@ def close(*args) def query(sql, opts = {}) if ::EM.reactor_running? - super(sql, opts.merge(:async => true)) + super(sql, opts.merge(async: true)) deferable = ::EM::DefaultDeferrable.new @watch = ::EM.watch(socket, Watcher, self, deferable) @watch.notify_readable = true diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 159865b8a..a66fce45d 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -3,9 +3,9 @@ module Mysql2 class Error < StandardError ENCODE_OPTS = { - :undef => :replace, - :invalid => :replace, - :replace => '?'.freeze, + undef: :replace, + invalid: :replace, + replace: '?'.freeze, }.freeze attr_reader :error_number, :sql_state diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 9240d765d..259e2b831 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -8,13 +8,13 @@ it "should not raise an exception for valid defaults group" do expect do - new_client(:default_file => cnf_file, :default_group => "test") + new_client(default_file: cnf_file, default_group: "test") end.not_to raise_error end it "should not raise an exception without default group" do expect do - new_client(:default_file => cnf_file) + new_client(default_file: cnf_file) end.not_to raise_error end end @@ -29,23 +29,23 @@ it "should raise an exception on create for invalid encodings" do expect do - new_client(:encoding => "fake") + new_client(encoding: "fake") end.to raise_error(Mysql2::Error) end it "should raise an exception on non-string encodings" do expect do - new_client(:encoding => :fake) + new_client(encoding: :fake) end.to raise_error(TypeError) end it "should not raise an exception on create for a valid encoding" do expect do - new_client(:encoding => "utf8") + new_client(encoding: "utf8") end.not_to raise_error expect do - new_client(DatabaseCredentials['root'].merge(:encoding => "big5")) + new_client(DatabaseCredentials['root'].merge(encoding: "big5")) end.not_to raise_error end @@ -58,18 +58,18 @@ def connect(*args) end it "should accept connect flags and pass them to #connect" do - client = Klient.new :flags => Mysql2::Client::FOUND_ROWS + client = Klient.new flags: Mysql2::Client::FOUND_ROWS expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to be > 0 end it "should parse flags array" do - client = Klient.new :flags => %w(FOUND_ROWS -PROTOCOL_41) + client = Klient.new flags: %w(FOUND_ROWS -PROTOCOL_41) expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end it "should parse flags string" do - client = Klient.new :flags => "FOUND_ROWS -PROTOCOL_41" + client = Klient.new flags: "FOUND_ROWS -PROTOCOL_41" expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end @@ -214,24 +214,24 @@ def run_gc if RUBY_PLATFORM =~ /mingw|mswin/ it "cannot be disabled" do expect do - client = new_client(:automatic_close => false) + client = new_client(automatic_close: false) expect(client.automatic_close?).to be(true) end.to output(/always closed by garbage collector/).to_stderr expect do - client = new_client(:automatic_close => true) + client = new_client(automatic_close: true) expect(client.automatic_close?).to be(true) end.to_not output(/always closed by garbage collector/).to_stderr expect do - client = new_client(:automatic_close => true) + client = new_client(automatic_close: true) client.automatic_close = false expect(client.automatic_close?).to be(true) end.to output(/always closed by garbage collector/).to_stderr end else it "can be configured" do - client = new_client(:automatic_close => false) + client = new_client(automatic_close: false) expect(client.automatic_close?).to be(false) end @@ -305,7 +305,7 @@ def run_gc end it "should not try to query closed mysql connection" do - client = new_client(:reconnect => true) + client = new_client(reconnect: true) expect(client.close).to be_nil expect do client.query "SELECT 1" @@ -358,7 +358,7 @@ def run_gc # # Note that mysql_info() returns a non-NULL value for INSERT ... VALUES only for the multiple-row form of the statement (that is, only if multiple value lists are specified). @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)") - expect(@client.query_info).to eql(:records => 2, :duplicates => 0, :warnings => 0) + expect(@client.query_info).to eql(records: 2, duplicates: 0, warnings: 0) expect(@client.query_info_string).to eq('Records: 2 Duplicates: 0 Warnings: 0') @client.query "DROP TABLE infoTest" @@ -368,7 +368,7 @@ def run_gc context ":local_infile" do before(:all) do - new_client(:local_infile => true) do |client| + new_client(local_infile: true) do |client| local = client.query "SHOW VARIABLES LIKE 'local_infile'" local_enabled = local.any? { |x| x['Value'] == 'ON' } skip("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled @@ -390,24 +390,24 @@ def run_gc end it "should raise an error when local_infile is disabled" do - client = new_client(:local_infile => false) + client = new_client(local_infile: false) expect do client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" end.to raise_error(Mysql2::Error, /command is not allowed/) end it "should raise an error when a non-existent file is loaded" do - client = new_client(:local_infile => true) + client = new_client(local_infile: true) expect do client.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest" end.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here') end it "should LOAD DATA LOCAL INFILE" do - client = new_client(:local_infile => true) + client = new_client(local_infile: true) client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" info = client.query_info - expect(info).to eql(:records => 1, :deleted => 0, :skipped => 0, :warnings => 0) + expect(info).to eql(records: 1, deleted: 0, skipped: 0, warnings: 0) result = client.query "SELECT * FROM infileTest" expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World') @@ -416,24 +416,24 @@ def run_gc it "should expect connect_timeout to be a positive integer" do expect do - new_client(:connect_timeout => -1) + new_client(connect_timeout: -1) end.to raise_error(Mysql2::Error) end it "should expect read_timeout to be a positive integer" do expect do - new_client(:read_timeout => -1) + new_client(read_timeout: -1) end.to raise_error(Mysql2::Error) end it "should expect write_timeout to be a positive integer" do expect do - new_client(:write_timeout => -1) + new_client(write_timeout: -1) end.to raise_error(Mysql2::Error) end it "should allow nil read_timeout" do - client = new_client(:read_timeout => nil) + client = new_client(read_timeout: nil) expect(client.read_timeout).to be_nil end @@ -448,7 +448,7 @@ def run_gc end it "should set custom connect_attrs" do - client = new_client(:connect_attrs => { :program_name => 'my_program_name', :foo => 'fooval', :bar => 'barval' }) + client = new_client(connect_attrs: { program_name: 'my_program_name', foo: 'fooval', bar: 'barval' }) if Mysql2::Client.info[:version] < '5.6' || client.info[:version] < '5.6' pending('Both client and server versions must be MySQL 5.6 or later.') end @@ -460,18 +460,18 @@ def run_gc context "#query" do it "should let you query again if iterating is finished when streaming" do - @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a + @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false).each.to_a expect do - @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) + @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false) end.to_not raise_error end it "should not let you query again if iterating is not finished when streaming" do - @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).first + @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false).first expect do - @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) + @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false) end.to raise_exception(Mysql2::Error) end @@ -482,10 +482,10 @@ def run_gc end it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do - result = @client.query "SELECT 1", :something => :else + result = @client.query "SELECT 1", something: :else expect(@client.query_options[:something]).to be_nil - expect(result.instance_variable_get('@query_options')).to eql(@client.query_options.merge(:something => :else)) - expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options.merge(:something => :else)) + expect(result.instance_variable_get('@query_options')).to eql(@client.query_options.merge(something: :else)) + expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options.merge(something: :else)) result = @client.query "SELECT 1" expect(result.instance_variable_get('@query_options')).to eql(@client.query_options) @@ -508,12 +508,12 @@ def run_gc end it "should be able to return results as an array" do - expect(@client.query("SELECT 1", :as => :array).first).to be_an_instance_of(Array) - @client.query("SELECT 1").each(:as => :array) + expect(@client.query("SELECT 1", as: :array).first).to be_an_instance_of(Array) + @client.query("SELECT 1").each(as: :array) end it "should be able to return results with symbolized keys" do - expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0]).to be_an_instance_of(Symbol) + expect(@client.query("SELECT 1", symbolize_keys: true).first.keys[0]).to be_an_instance_of(Symbol) end it "should require an open connection" do @@ -544,21 +544,21 @@ def run_gc if RUBY_PLATFORM !~ /mingw|mswin/ it "should not allow another query to be sent without fetching a result first" do - @client.query("SELECT 1", :async => true) + @client.query("SELECT 1", async: true) expect do @client.query("SELECT 1") end.to raise_error(Mysql2::Error) end it "should describe the thread holding the active query" do - thr = Thread.new { @client.query("SELECT 1", :async => true) } + thr = Thread.new { @client.query("SELECT 1", async: true) } thr.join expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(thr.inspect))) end it "should timeout if we wait longer than :read_timeout" do - client = new_client(:read_timeout => 0) + client = new_client(read_timeout: 0) expect do client.query('SELECT SLEEP(0.1)') end.to raise_error(Mysql2::Error) @@ -631,7 +631,7 @@ def run_gc pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end - client = new_client(:reconnect => true) + client = new_client(reconnect: true) expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { client.query('SELECT 1') }.to_not raise_error @@ -677,9 +677,9 @@ def run_gc it "evented async queries should be supported" do skip("ruby 1.8 doesn't support IO.for_fd options") if RUBY_VERSION.start_with?("1.8.") # should immediately return nil - expect(@client.query("SELECT sleep(0.1)", :async => true)).to eql(nil) + expect(@client.query("SELECT sleep(0.1)", async: true)).to eql(nil) - io_wrapper = IO.for_fd(@client.socket, :autoclose => false) + io_wrapper = IO.for_fd(@client.socket, autoclose: false) loops = 0 loops += 1 until IO.select([io_wrapper], nil, nil, 0.05) @@ -693,7 +693,7 @@ def run_gc context "Multiple results sets" do before(:each) do - @multi_client = new_client(:flags => Mysql2::Client::MULTI_STATEMENTS) + @multi_client = new_client(flags: Mysql2::Client::MULTI_STATEMENTS) end it "should raise an exception when one of multiple statements fails" do @@ -846,7 +846,7 @@ def run_gc end context 'when mysql encoding is not utf8' do - let(:client) { new_client(:encoding => "ujis") } + let(:client) { new_client(encoding: "ujis") } it 'should return a internal encoding string if Encoding.default_internal is set' do with_internal_encoding Encoding::UTF_8 do @@ -909,7 +909,7 @@ def run_gc with_internal_encoding nil do expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8) - client2 = new_client(:encoding => 'ascii') + client2 = new_client(encoding: 'ascii') expect(client2.server_info[:version].encoding).to eql(Encoding::ASCII) end end @@ -927,7 +927,7 @@ def run_gc it "should raise a Mysql2::Error exception upon connection failure" do expect do - new_client(:host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42') + new_client(host: "localhost", username: 'asdfasdf8d2h', password: 'asdfasdfw42') end.to raise_error(Mysql2::Error) expect do @@ -1034,7 +1034,7 @@ def run_gc end it "should be able to connect using plaintext password" do - client = new_client(:enable_cleartext_plugin => true) + client = new_client(enable_cleartext_plugin: true) client.query('SELECT 1') end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 65b1076ba..1a15f3bd1 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -63,13 +63,13 @@ end it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do - @result.each(:symbolize_keys => true) do |row| + @result.each(symbolize_keys: true) do |row| expect(row.keys.first).to be_an_instance_of(Symbol) end end it "should be able to return results as an array" do - @result.each(:as => :array) do |row| + @result.each(as: :array) do |row| expect(row).to be_an_instance_of(Array) end end @@ -79,27 +79,27 @@ end it "should not cache previously yielded results if cache_rows is disabled" do - result = @client.query "SELECT 1", :cache_rows => false + result = @client.query "SELECT 1", cache_rows: false expect(result.first.object_id).not_to eql(result.first.object_id) end it "should be able to iterate a second time even if cache_rows is disabled" do - result = @client.query "SELECT 1 UNION SELECT 2", :cache_rows => false + result = @client.query "SELECT 1 UNION SELECT 2", cache_rows: false expect(result.to_a).to eql(result.to_a) end it "should yield different value for #first if streaming" do - result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false + result = @client.query "SELECT 1 UNION SELECT 2", stream: true, cache_rows: false expect(result.first).not_to eql(result.first) end it "should yield the same value for #first if streaming is disabled" do - result = @client.query "SELECT 1 UNION SELECT 2", :stream => false + result = @client.query "SELECT 1 UNION SELECT 2", stream: false expect(result.first).to eql(result.first) end it "should throw an exception if we try to iterate twice when streaming is enabled" do - result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false + result = @client.query "SELECT 1 UNION SELECT 2", stream: true, cache_rows: false expect do result.each.to_a @@ -125,14 +125,14 @@ context "streaming" do it "should maintain a count while streaming" do - result = @client.query('SELECT 1', :stream => true, :cache_rows => false) + result = @client.query('SELECT 1', stream: true, cache_rows: false) expect(result.count).to eql(0) result.each.to_a expect(result.count).to eql(1) end it "should retain the count when mixing first and each" do - result = @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) + result = @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false) expect(result.count).to eql(0) result.first expect(result.count).to eql(1) @@ -141,13 +141,13 @@ end it "should not yield nil at the end of streaming" do - result = @client.query('SELECT * FROM mysql2_test', :stream => true, :cache_rows => false) + result = @client.query('SELECT * FROM mysql2_test', stream: true, cache_rows: false) result.each { |r| expect(r).not_to be_nil } end it "#count should be zero for rows after streaming when there were no results" do @client.query "USE test" - result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false) + result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", stream: true, cache_rows: false) expect(result.count).to eql(0) result.each.to_a expect(result.count).to eql(0) @@ -163,7 +163,7 @@ end @client.query "SET net_write_timeout = 1" - res = @client.query "SELECT * FROM streamingTest", :stream => true, :cache_rows => false + res = @client.query "SELECT * FROM streamingTest", stream: true, cache_rows: false expect do res.each_with_index do |_, i| @@ -180,7 +180,7 @@ end it "should return nil values for NULL and strings for everything else when :cast is false" do - result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast => false).first + result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', cast: false).first expect(result["null_test"]).to be_nil expect(result["tiny_int_test"]).to eql("1") expect(result["bool_cast_test"]).to eql("1") @@ -221,9 +221,9 @@ end it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do - result1 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id1} LIMIT 1", :cast_booleans => true - result2 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id2} LIMIT 1", :cast_booleans => true - result3 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id3} LIMIT 1", :cast_booleans => true + result1 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id1} LIMIT 1", cast_booleans: true + result2 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id2} LIMIT 1", cast_booleans: true + result3 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id3} LIMIT 1", cast_booleans: true expect(result1.first['bool_cast_test']).to be true expect(result2.first['bool_cast_test']).to be false expect(result3.first['bool_cast_test']).to be true @@ -241,8 +241,8 @@ end it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do - result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", :cast_booleans => true - result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", :cast_booleans => true + result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", cast_booleans: true + result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", cast_booleans: true expect(result1.first['single_bit_test']).to be true expect(result2.first['single_bit_test']).to be false end @@ -334,7 +334,7 @@ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) - client2 = new_client(:encoding => 'ascii') + client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::ASCII) end @@ -364,7 +364,7 @@ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::UTF_8) - client2 = new_client(:encoding => 'ascii') + client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::ASCII) end @@ -453,7 +453,7 @@ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::UTF_8) - client2 = new_client(:encoding => 'ascii') + client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::ASCII) end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index fc56bcd02..4bb6afec0 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -4,7 +4,7 @@ RSpec.describe Mysql2::Statement do before :each do - @client = new_client(:encoding => "utf8") + @client = new_client(encoding: "utf8") end def stmt_count @@ -249,7 +249,7 @@ def stmt_count n = 1 stmt = @client.prepare("SELECT 1 UNION SELECT 2") - @client.query_options.merge!(:stream => true, :cache_rows => false, :as => :array) + @client.query_options.merge!(stream: true, cache_rows: false, as: :array) stmt.execute.each do |r| case n @@ -379,7 +379,7 @@ def stmt_count context "cast booleans for TINYINT if :cast_booleans is enabled" do # rubocop:disable Style/Semicolon - let(:client) { new_client(:cast_booleans => true) } + let(:client) { new_client(cast_booleans: true) } let(:id1) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; client.last_id } let(:id2) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; client.last_id } let(:id3) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; client.last_id } @@ -402,7 +402,7 @@ def stmt_count context "cast booleans for BIT(1) if :cast_booleans is enabled" do # rubocop:disable Style/Semicolon - let(:client) { new_client(:cast_booleans => true) } + let(:client) { new_client(cast_booleans: true) } let(:id1) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; client.last_id } let(:id2) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; client.last_id } # rubocop:enable Style/Semicolon @@ -506,7 +506,7 @@ def stmt_count result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) - client2 = new_client(:encoding => 'ascii') + client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::US_ASCII) end @@ -536,7 +536,7 @@ def stmt_count result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::UTF_8) - client2 = new_client(:encoding => 'ascii') + client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::US_ASCII) end @@ -625,7 +625,7 @@ def stmt_count result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::UTF_8) - client2 = new_client(:encoding => 'ascii') + client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::US_ASCII) end diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index 183429518..d0a5a4b81 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -47,8 +47,8 @@ "eucjpms" => "eucJP-ms", } -client = Mysql2::Client.new(:username => user, :password => pass, :host => host, :port => port.to_i) -collations = client.query "SHOW COLLATION", :as => :array +client = Mysql2::Client.new(username: user, password: pass, host: host, port: port.to_i) +collations = client.query "SHOW COLLATION", as: :array encodings = Array.new(collations.to_a.last[2].to_i) encodings_with_nil = Array.new(encodings.size) From 0e77b9bdc9e711516ef00db5b6d95a930420c67f Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 21:35:42 -0500 Subject: [PATCH 562/783] Style/IfInsideElse --- .rubocop_todo.yml | 5 ----- tasks/compile.rake | 6 ++---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 65ced07e0..ddea663d0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -72,11 +72,6 @@ Style/GlobalVars: Exclude: - 'ext/mysql2/extconf.rb' -# Offense count: 1 -Style/IfInsideElse: - Exclude: - - 'tasks/compile.rake' - # Offense count: 4 # Cop supports --auto-correct. Style/MutableConstant: diff --git a/tasks/compile.rake b/tasks/compile.rake index adaf0e941..ce33554f5 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -85,10 +85,8 @@ end if RUBY_PLATFORM =~ /mingw|mswin/ Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' unless defined?(RubyInstaller) Rake::Task['compile'].prerequisites.unshift 'devkit' -else - if Rake::Task.tasks.map(&:name).include? 'cross' - Rake::Task['cross'].prerequisites.unshift 'vendor:mysql:cross' - end +elsif Rake::Task.tasks.map(&:name).include? 'cross' + Rake::Task['cross'].prerequisites.unshift 'vendor:mysql:cross' end desc "Build binary gems for Windows with rake-compiler-dock" From 3f4187e1946eb21e40ec3bb8f40e7599e86c07a7 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 21:36:06 -0500 Subject: [PATCH 563/783] Style/MutableConstant --- .rubocop_todo.yml | 9 --------- ext/mysql2/extconf.rb | 2 +- lib/mysql2/version.rb | 2 +- tasks/rspec.rake | 2 +- tasks/vendor_mysql.rake | 2 +- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ddea663d0..178209605 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -72,15 +72,6 @@ Style/GlobalVars: Exclude: - 'ext/mysql2/extconf.rb' -# Offense count: 4 -# Cop supports --auto-correct. -Style/MutableConstant: - Exclude: - - 'ext/mysql2/extconf.rb' - - 'lib/mysql2/version.rb' - - 'tasks/rspec.rake' - - 'tasks/vendor_mysql.rake' - # Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: Strict. diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 8b4e77674..52eaacbf0 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -52,7 +52,7 @@ def add_ssl_defines(header) # For those without HOMEBREW_ROOT in PATH dirs << "#{ENV['HOMEBREW_ROOT']}/bin" if ENV['HOMEBREW_ROOT'] -GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}" +GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}".freeze # If the user has provided a --with-mysql-dir argument, we must respect it or fail. inc, lib = dir_config('mysql') diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 1426f2519..2afa42fc0 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,5 +1,5 @@ # encoding: UTF-8 module Mysql2 - VERSION = "0.4.10" + VERSION = "0.4.10".freeze end diff --git a/tasks/rspec.rake b/tasks/rspec.rake index 7a08ff676..4350723fa 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -13,7 +13,7 @@ begin --partial-loads-ok=yes --undef-value-errors=no --trace-children=yes - ) + ).freeze cmdline = "valgrind #{VALGRIND_OPTS.join(' ')} bundle exec rake spec" puts cmdline system cmdline diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index 37a052997..6d77be208 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -3,7 +3,7 @@ require 'rake/clean' require 'rake/extensioncompiler' -CONNECTOR_VERSION = "6.1.11" # NOTE: Track the upstream version from time to time +CONNECTOR_VERSION = "6.1.11".freeze # NOTE: Track the upstream version from time to time def vendor_mysql_platform(platform = nil) platform ||= RUBY_PLATFORM From 6a4d443689cc4834aea62d32edd0349d3d952f9d Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 21:37:08 -0500 Subject: [PATCH 564/783] Style/NumericPredicate --- .rubocop_todo.yml | 18 ------------------ benchmark/setup_db.rb | 2 +- lib/mysql2.rb | 2 +- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 178209605..a675c181c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -78,16 +78,6 @@ Style/GlobalVars: Style/NumericLiterals: MinDigits: 20 -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. -# SupportedStyles: predicate, comparison -Style/NumericPredicate: - Exclude: - - 'spec/**/*' - - 'benchmark/setup_db.rb' - - 'lib/mysql2.rb' - # Offense count: 15 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. @@ -117,11 +107,3 @@ Style/SignalException: # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: all_comparison_operators, equality_operators_only -Style/YodaCondition: - Exclude: - - 'lib/mysql2.rb' diff --git a/benchmark/setup_db.rb b/benchmark/setup_db.rb index 711fada03..76c7399af 100644 --- a/benchmark/setup_db.rb +++ b/benchmark/setup_db.rb @@ -112,7 +112,7 @@ def insert_record(args) enum_test: %w(val1 val2).sample, set_test: %w(val1 val2 val1,val2).sample, ) - if n % 100 == 0 + if (n % 100).zero? $stdout.putc '.' $stdout.flush end diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 1b56b7e7f..4ec3198f6 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -24,7 +24,7 @@ if dll_path require 'Win32API' LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I') - if 0 == LoadLibrary.call(dll_path) + if LoadLibrary.call(dll_path).zero? abort "Failed to load libmysql.dll from #{dll_path}" end end From b38d0c8d43154f6e589363400a58f5d9303cf14e Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 21:37:42 -0500 Subject: [PATCH 565/783] Style/PercentLiteralDelimiters --- .rubocop_todo.yml | 14 -------------- benchmark/active_record.rb | 2 +- benchmark/active_record_threaded.rb | 2 +- benchmark/setup_db.rb | 4 ++-- ext/mysql2/extconf.rb | 10 +++++----- spec/mysql2/client_spec.rb | 2 +- spec/mysql2/result_spec.rb | 4 ++-- spec/mysql2/statement_spec.rb | 6 +++--- tasks/rspec.rake | 4 ++-- 9 files changed, 17 insertions(+), 31 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a675c181c..a4fd899d4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -78,20 +78,6 @@ Style/GlobalVars: Style/NumericLiterals: MinDigits: 20 -# Offense count: 15 -# Cop supports --auto-correct. -# Configuration parameters: PreferredDelimiters. -Style/PercentLiteralDelimiters: - Exclude: - - 'benchmark/active_record.rb' - - 'benchmark/active_record_threaded.rb' - - 'benchmark/setup_db.rb' - - 'ext/mysql2/extconf.rb' - - 'spec/mysql2/client_spec.rb' - - 'spec/mysql2/result_spec.rb' - - 'spec/mysql2/statement_spec.rb' - - 'tasks/rspec.rake' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. diff --git a/benchmark/active_record.rb b/benchmark/active_record.rb index 099091b04..90191dcff 100644 --- a/benchmark/active_record.rb +++ b/benchmark/active_record.rb @@ -18,7 +18,7 @@ class TestModel < ActiveRecord::Base batch_size = 1000 Benchmark.ips do |x| - %w(mysql mysql2).each do |adapter| + %w[mysql mysql2].each do |adapter| TestModel.establish_connection(opts.merge(adapter: adapter)) x.report(adapter) do diff --git a/benchmark/active_record_threaded.rb b/benchmark/active_record_threaded.rb index 7aa3e1990..013b2d57c 100644 --- a/benchmark/active_record_threaded.rb +++ b/benchmark/active_record_threaded.rb @@ -10,7 +10,7 @@ opts = { database: 'test', pool: number_of_threads } Benchmark.ips do |x| - %w(mysql mysql2).each do |adapter| + %w[mysql mysql2].each do |adapter| ActiveRecord::Base.establish_connection(opts.merge(adapter: adapter)) x.report(adapter) do diff --git a/benchmark/setup_db.rb b/benchmark/setup_db.rb index 76c7399af..6ba3033dd 100644 --- a/benchmark/setup_db.rb +++ b/benchmark/setup_db.rb @@ -109,8 +109,8 @@ def insert_record(args) medium_text_test: twenty5_paragraphs, long_blob_test: twenty5_paragraphs, long_text_test: twenty5_paragraphs, - enum_test: %w(val1 val2).sample, - set_test: %w(val1 val2 val1,val2).sample, + enum_test: %w[val1 val2].sample, + set_test: %w[val1 val2 val1,val2].sample, ) if (n % 100).zero? $stdout.putc '.' diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 52eaacbf0..bb08556a2 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -14,7 +14,7 @@ def asplode(lib) end def add_ssl_defines(header) - all_modes_found = %w(SSL_MODE_DISABLED SSL_MODE_PREFERRED SSL_MODE_REQUIRED SSL_MODE_VERIFY_CA SSL_MODE_VERIFY_IDENTITY).inject(true) do |m, ssl_mode| + all_modes_found = %w[SSL_MODE_DISABLED SSL_MODE_PREFERRED SSL_MODE_REQUIRED SSL_MODE_VERIFY_CA SSL_MODE_VERIFY_IDENTITY].inject(true) do |m, ssl_mode| m && have_const(ssl_mode, header) end $CFLAGS << ' -DFULL_SSL_MODE_SUPPORT' if all_modes_found @@ -35,7 +35,7 @@ def add_ssl_defines(header) # borrowed from mysqlplus # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb -dirs = ENV.fetch('http://github.com/PATH').split(File::PATH_SEPARATOR) + %w( +dirs = ENV.fetch('http://github.com/PATH').split(File::PATH_SEPARATOR) + %w[ /opt /opt/local /opt/local/mysql @@ -47,7 +47,7 @@ def add_ssl_defines(header) /usr/local/mysql-* /usr/local/lib/mysql5* /usr/local/opt/mysql5* -).map { |dir| dir << '/bin' } +].map { |dir| dir << '/bin' } # For those without HOMEBREW_ROOT in PATH dirs << "#{ENV['HOMEBREW_ROOT']}/bin" if ENV['HOMEBREW_ROOT'] @@ -102,7 +102,7 @@ def add_ssl_defines(header) asplode 'mysql.h' end -%w(errmsg.h).each do |h| +%w[errmsg.h].each do |h| header = [prefix, h].compact.join('/') asplode h unless have_header header end @@ -152,7 +152,7 @@ def add_ssl_defines(header) case sanitizers when true # Try them all, turn on whatever we can - enabled_sanitizers = %w(address cfi integer memory thread undefined).select do |s| + enabled_sanitizers = %w[address cfi integer memory thread undefined].select do |s| try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") end abort "-----\nCould not enable any sanitizers!\n-----" if enabled_sanitizers.empty? diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 259e2b831..a11bf8bde 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -63,7 +63,7 @@ def connect(*args) end it "should parse flags array" do - client = Klient.new flags: %w(FOUND_ROWS -PROTOCOL_41) + client = Klient.new flags: %w[FOUND_ROWS -PROTOCOL_41] expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 1a15f3bd1..f1386b16c 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -119,7 +119,7 @@ it "should return an array of field names in proper order" do result = @client.query "SELECT 'a', 'b', 'c'" - expect(result.fields).to eql(%w(a b c)) + expect(result.fields).to eql(%w[a b c]) end end @@ -428,7 +428,7 @@ end context "string encoding for #{type} values" do - if %w(VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB).include?(type) + if %w[VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB].include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 4bb6afec0..327d4eb5c 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -218,7 +218,7 @@ def stmt_count it "should be able to retrieve utf8 field names correctly" do stmt = @client.prepare 'SELECT * FROM `テーブル`' - expect(stmt.fields).to eq(%w(整数 文字列)) + expect(stmt.fields).to eq(%w[整数 文字列]) result = stmt.execute expect(result.to_a).to eq([{ "整数" => 1, "文字列" => "イチ" }, { "整数" => 2, "文字列" => "弐" }, { "整数" => 3, "文字列" => "さん" }]) @@ -342,7 +342,7 @@ def stmt_count it "should return an array of field names in proper order" do stmt = @client.prepare("SELECT 'a', 'b', 'c'") - expect(stmt.fields).to eql(%w(a b c)) + expect(stmt.fields).to eql(%w[a b c]) end it "should return nil for statement with no result fields" do @@ -600,7 +600,7 @@ def stmt_count end context "string encoding for #{type} values" do - if %w(VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB).include?(type) + if %w[VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB].include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first diff --git a/tasks/rspec.rake b/tasks/rspec.rake index 4350723fa..a7bbfb098 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -7,13 +7,13 @@ begin desc " Run all examples with Valgrind" namespace :spec do task :valgrind do - VALGRIND_OPTS = %w( + VALGRIND_OPTS = %w[ --num-callers=50 --error-limit=no --partial-loads-ok=yes --undef-value-errors=no --trace-children=yes - ).freeze + ].freeze cmdline = "valgrind #{VALGRIND_OPTS.join(' ')} bundle exec rake spec" puts cmdline system cmdline From d89f24133e20f93c6cedcde9de8c45552c8b67fb Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 21:38:09 -0500 Subject: [PATCH 566/783] Style/SignalException --- .rubocop_todo.yml | 9 --------- lib/mysql2/client.rb | 2 +- spec/em/em_spec.rb | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a4fd899d4..e675d69af 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -78,15 +78,6 @@ Style/GlobalVars: Style/NumericLiterals: MinDigits: 20 -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: only_raise, only_fail, semantic -Style/SignalException: - Exclude: - - 'lib/mysql2/client.rb' - - 'spec/em/em_spec.rb' - # Offense count: 726 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline. diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 1fc59ede6..2c3a92d72 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -21,7 +21,7 @@ def self.default_query_options end def initialize(opts = {}) - fail Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash + raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash opts = Mysql2::Util.key_hash_as_symbols(opts) @read_timeout = nil @query_options = self.class.default_query_options.dup diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 35e381a54..4daab27bc 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -56,7 +56,7 @@ defer = client.query "SELECT sleep(0.1) as first_query" defer.callback do client.close - fail 'some error' + raise 'some error' end defer.errback do # This _shouldn't_ be run, but it needed to prevent the specs from From 105c9f0280e4fe4dcf282758e38d5b6d160e1cc3 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 25 Nov 2017 23:16:57 -0500 Subject: [PATCH 567/783] Remove more 1.8.7, REE compatibility shims (#908) --- ext/mysql2/mysql2_ext.h | 6 ++++++ ext/mysql2/statement.c | 9 +++++---- ext/mysql2/wait_for_single_fd.h | 3 ++- spec/mysql2/client_spec.rb | 1 - spec/mysql2/statement_spec.rb | 16 ++++------------ 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index bc74fc0d0..d94f29483 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -18,6 +18,12 @@ void Init_mysql2(void); #endif #include +// ruby/thread.h was added in 2.0.0. See: +// https://github.com/ruby/ruby/commit/c51a826 +// +// Rubinius doesn't define this, but it ships an empty thread.h (the symbols we +// care about are in ruby.h); this is safe to remove when < 2.0.0 is no longer +// supported. #ifdef HAVE_RUBY_THREAD_H #include #endif diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 4787742e3..0c93c857b 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -202,6 +202,8 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr) { unsigned LONG_LONG num; size_t len; +// rb_absint_size was added in 2.1.0. See: +// https://github.com/ruby/ruby/commit/9fea875 #ifdef HAVE_RB_ABSINT_SIZE int nlz_bits = 0; len = rb_absint_size(bignum, &nlz_bits); @@ -220,16 +222,15 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr) #ifdef HAVE_RB_ABSINT_SIZE nlz_bits == 0 && #endif +// rb_absint_singlebit_p was added in 2.1.0. See: +// https://github.com/ruby/ruby/commit/e5ff9d5 #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P) /* Optimized to avoid object allocation for Ruby 2.1+ * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0` */ !rb_absint_singlebit_p(bignum) -#elif defined(HAVE_RB_BIG_CMP) - rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1) #else - /* Ruby 1.8.7 and REE doesn't have rb_big_cmp */ - rb_funcall(bignum, id_cmp, 1, LL2NUM(LLONG_MIN)) == INT2FIX(-1) + rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1) #endif ) { goto overflow; diff --git a/ext/mysql2/wait_for_single_fd.h b/ext/mysql2/wait_for_single_fd.h index c99eec76f..8f74ad05e 100644 --- a/ext/mysql2/wait_for_single_fd.h +++ b/ext/mysql2/wait_for_single_fd.h @@ -1,5 +1,6 @@ /* - * backwards compatibility for pre-1.9.3 C API + * backwards compatibility for Rubinius. See + * https://github.com/rubinius/rubinius/issues/3771. * * Ruby 1.9.3 provides this API which allows the use of ppoll() on Linux * to minimize select() and malloc() overhead on high-numbered FDs. diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index c628acc82..eff5ab7ee 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -670,7 +670,6 @@ def run_gc end it "evented async queries should be supported" do - skip("ruby 1.8 doesn't support IO.for_fd options") if RUBY_VERSION.start_with?("1.8.") # should immediately return nil expect(@client.query("SELECT sleep(0.1)", :async => true)).to eql(nil) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 76e20b5bf..b62c581fd 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -147,24 +147,16 @@ def stmt_count now = Time.now statement = @client.prepare('SELECT ? AS a') result = statement.execute(now) - if RUBY_VERSION =~ /1.8/ - expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) - else - # microseconds is six digits after the decimal, but only test on 5 significant figures - expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) - end + # microseconds is six digits after the decimal, but only test on 5 significant figures + expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) end it "should prepare DateTime values with microseconds" do now = DateTime.now statement = @client.prepare('SELECT ? AS a') result = statement.execute(now) - if RUBY_VERSION =~ /1.8/ - expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z')) - else - # microseconds is six digits after the decimal, but only test on 5 significant figures - expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) - end + # microseconds is six digits after the decimal, but only test on 5 significant figures + expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) end it "should tell us about the fields" do From 66db2dd0e173cc1bfa0e0ff1c500070271a21a8f Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 11 Jul 2015 12:03:14 -0700 Subject: [PATCH 568/783] Prefix the methods in statement.c with rb_mysql_ --- ext/mysql2/statement.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 0c93c857b..674eb415a 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -145,7 +145,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { * * Returns the number of parameters the prepared statement expects. */ -static VALUE param_count(VALUE self) { +static VALUE rb_mysql_stmt_param_count(VALUE self) { GET_STATEMENT(self); return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt)); @@ -155,13 +155,13 @@ static VALUE param_count(VALUE self) { * * Returns the number of fields the prepared statement returns. */ -static VALUE field_count(VALUE self) { +static VALUE rb_mysql_stmt_field_count(VALUE self) { GET_STATEMENT(self); return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt)); } -static void *nogvl_execute(void *ptr) { +static void *nogvl_stmt_execute(void *ptr) { MYSQL_STMT *stmt = ptr; if (mysql_stmt_execute(stmt)) { @@ -246,7 +246,7 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr) * * Executes the current prepared statement, returns +result+. */ -static VALUE execute(int argc, VALUE *argv, VALUE self) { +static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { MYSQL_BIND *bind_buffers = NULL; unsigned long *length_buffers = NULL; unsigned long bind_count; @@ -401,7 +401,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { } } - if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) { + if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) { FREE_BINDS; rb_raise_mysql2_stmt_error(stmt_wrapper); } @@ -449,7 +449,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) { * * Returns a list of fields that will be returned by this statement. */ -static VALUE fields(VALUE self) { +static VALUE rb_mysql_stmt_fields(VALUE self) { MYSQL_FIELD *fields; MYSQL_RES *metadata; unsigned int field_count; @@ -542,10 +542,10 @@ static VALUE rb_mysql_stmt_close(VALUE self) { void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); - rb_define_method(cMysql2Statement, "param_count", param_count, 0); - rb_define_method(cMysql2Statement, "field_count", field_count, 0); - rb_define_method(cMysql2Statement, "_execute", execute, -1); - rb_define_method(cMysql2Statement, "fields", fields, 0); + rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0); + rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0); + rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1); + rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0); rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0); rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0); rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0); From dcd5d1220e1fe899d72d4470b8e3f030aaa1539a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 12 Jul 2015 09:19:56 -0700 Subject: [PATCH 569/783] Prefix the methods in client.c with rb_mysql_ --- ext/mysql2/client.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 7c51d1347..f2e289d4c 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -413,7 +413,7 @@ static int opt_connect_attr_add_i(VALUE key, VALUE value, VALUE arg) } #endif -static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags, VALUE conn_attrs) { +static VALUE rb_mysql_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags, VALUE conn_attrs) { struct nogvl_connect_args args; time_t start_time, end_time, elapsed_time, connect_timeout; VALUE rv; @@ -754,7 +754,7 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) { * Query the database with +sql+, with optional +options+. For the possible * options, see default_query_options on the Mysql2::Client class. */ -static VALUE rb_query(VALUE self, VALUE sql, VALUE current) { +static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) { #ifndef _WIN32 struct async_query_args async_args; #endif @@ -1421,8 +1421,8 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1); rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); - rb_define_private_method(cMysql2Client, "connect", rb_connect, 8); - rb_define_private_method(cMysql2Client, "_query", rb_query, 2); + rb_define_private_method(cMysql2Client, "connect", rb_mysql_connect, 8); + rb_define_private_method(cMysql2Client, "_query", rb_mysql_query, 2); sym_id = ID2SYM(rb_intern("id")); sym_version = ID2SYM(rb_intern("version")); From 7a684a7e6597e387fa78a59373ede8239445bec2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 13 Jul 2015 11:30:58 -0700 Subject: [PATCH 570/783] Minor whitespace/variable rewording --- ext/mysql2/statement.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 674eb415a..25afab17f 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -21,7 +21,7 @@ static void rb_mysql_stmt_mark(void * ptr) { rb_gc_mark(stmt_wrapper->client); } -static void *nogvl_stmt_close(void * ptr) { +static void *nogvl_stmt_close(void *ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; if (stmt_wrapper->stmt) { mysql_stmt_close(stmt_wrapper->stmt); @@ -30,7 +30,7 @@ static void *nogvl_stmt_close(void * ptr) { return NULL; } -static void rb_mysql_stmt_free(void * ptr) { +static void rb_mysql_stmt_free(void *ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; decr_mysql2_stmt(stmt_wrapper); } From 245c3d18bb789df747284e84eccad6eb77682385 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 27 Nov 2017 20:45:48 -0800 Subject: [PATCH 571/783] Remove leftover ifdef HAVE_RB_BIG_CMP --- ext/mysql2/statement.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 25afab17f..3a022e661 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -4,9 +4,6 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s; static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year; -#ifndef HAVE_RB_BIG_CMP -static ID id_cmp; -#endif #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ @@ -565,7 +562,4 @@ void init_mysql2_statement() { intern_year = rb_intern("year"); intern_to_s = rb_intern("to_s"); -#ifndef HAVE_RB_BIG_CMP - id_cmp = rb_intern("<=>"); -#endif } From 5fa7700e945fc6b2d9807b23f238ca111396aa89 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 29 Nov 2017 20:30:12 -0500 Subject: [PATCH 572/783] Drop 1.9.3 support (#913) --- .rubocop.yml | 6 +----- .travis.yml | 1 - Gemfile | 5 +---- README.md | 4 ++-- Rakefile | 4 +--- benchmark/active_record.rb | 2 -- benchmark/active_record_threaded.rb | 2 -- benchmark/allocations.rb | 2 -- benchmark/escape.rb | 2 -- benchmark/query_with_mysql_casting.rb | 2 -- benchmark/query_without_mysql_casting.rb | 2 -- benchmark/sequel.rb | 2 -- benchmark/setup_db.rb | 2 -- examples/eventmachine.rb | 2 -- examples/threaded.rb | 2 -- ext/mysql2/client.h | 6 ------ ext/mysql2/extconf.rb | 13 ------------- ext/mysql2/mysql2_ext.h | 8 -------- lib/mysql2.rb | 14 +++++--------- lib/mysql2/client.rb | 16 ++++------------ lib/mysql2/console.rb | 2 -- lib/mysql2/em.rb | 2 -- lib/mysql2/error.rb | 2 -- lib/mysql2/field.rb | 2 -- lib/mysql2/result.rb | 2 -- lib/mysql2/statement.rb | 12 ++---------- lib/mysql2/version.rb | 2 -- mysql2.gemspec | 4 +--- spec/em/em_spec.rb | 2 -- spec/mysql2/client_spec.rb | 6 ------ spec/mysql2/error_spec.rb | 2 -- spec/mysql2/result_spec.rb | 2 -- spec/mysql2/statement_spec.rb | 2 -- spec/spec_helper.rb | 2 -- support/mysql_enc_to_ruby.rb | 2 -- support/ruby_enc_to_mysql.rb | 2 -- tasks/benchmarks.rake | 2 -- tasks/compile.rake | 2 -- tasks/generate.rake | 2 -- tasks/rspec.rake | 2 -- tasks/vendor_mysql.rake | 2 -- 41 files changed, 17 insertions(+), 138 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 8f634d507..a9cd803dc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,7 @@ inherit_from: .rubocop_todo.yml AllCops: - TargetRubyVersion: 1.9 + TargetRubyVersion: 2.0 DisplayCopNames: true Exclude: @@ -21,10 +21,6 @@ Layout/IndentHeredoc: Lint/EndAlignment: EnforcedStyleAlignWith: variable -Style/Encoding: - AutoCorrectEncodingComment: '# encoding: UTF-8' - EnforcedStyle: always - Style/TrailingCommaInArguments: EnforcedStyleForMultiline: consistent_comma diff --git a/.travis.yml b/.travis.yml index 92f10ad4a..7c3c594cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,6 @@ rvm: - 2.2 - 2.1 - 2.0.0 - - 1.9.3 - ruby-head matrix: include: diff --git a/Gemfile b/Gemfile index 3f64bcc33..5d9c98491 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,3 @@ -# encoding: UTF-8 - source 'https://rubygems.org/' gemspec @@ -10,9 +8,8 @@ gem 'rake-compiler', '~> 1.0' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' - # https://github.com/bbatsov/rubocop/pull/3328 # https://github.com/bbatsov/rubocop/pull/4789 - gem 'rubocop', '~> 0.50.0' unless RUBY_VERSION =~ /1.9/ + gem 'rubocop', '~> 0.50.0' end group :benchmarks do diff --git a/README.md b/README.md index c385f2932..7284777dc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The Mysql2 gem is meant to serve the extremely common use-case of connecting, qu Some database libraries out there serve as direct 1:1 mappings of the already complex C APIs available. This one is not. -It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9, unless Encoding.default_internal is set then it'll convert from UTF-8 to that encoding] and uses encoding-aware MySQL API calls where it can. +It also forces the use of UTF-8 [or binary] for the connection and uses encoding-aware MySQL API calls where it can. The API consists of three classes: @@ -510,7 +510,7 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x + * Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x * Rubinius 2.x and 3.x do work but may fail under some workloads This gem is tested with the following MySQL and MariaDB versions: diff --git a/Rakefile b/Rakefile index 1b7a093cf..4164a7ad2 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require 'rake' # Load custom tasks (careful attention to define tasks before prerequisites) @@ -12,7 +10,7 @@ load 'tasks/benchmarks.rake' begin require 'rubocop/rake_task' RuboCop::RakeTask.new - task default: [:spec, :rubocop] + task default: %i[spec rubocop] rescue LoadError warn 'RuboCop is not available' task default: :spec diff --git a/benchmark/active_record.rb b/benchmark/active_record.rb index 90191dcff..0ae442c25 100644 --- a/benchmark/active_record.rb +++ b/benchmark/active_record.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/active_record_threaded.rb b/benchmark/active_record_threaded.rb index 013b2d57c..22c3f01be 100644 --- a/benchmark/active_record_threaded.rb +++ b/benchmark/active_record_threaded.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/allocations.rb b/benchmark/allocations.rb index 372ffb9a2..7926a837d 100644 --- a/benchmark/allocations.rb +++ b/benchmark/allocations.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/escape.rb b/benchmark/escape.rb index fad2190e6..0e2320e2b 100644 --- a/benchmark/escape.rb +++ b/benchmark/escape.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb index 33fa024f2..caf7eb011 100644 --- a/benchmark/query_with_mysql_casting.rb +++ b/benchmark/query_with_mysql_casting.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/query_without_mysql_casting.rb b/benchmark/query_without_mysql_casting.rb index 1fc6af235..4080e22d1 100644 --- a/benchmark/query_without_mysql_casting.rb +++ b/benchmark/query_without_mysql_casting.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/sequel.rb b/benchmark/sequel.rb index afd67654c..a9555fa6e 100644 --- a/benchmark/sequel.rb +++ b/benchmark/sequel.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' diff --git a/benchmark/setup_db.rb b/benchmark/setup_db.rb index 6ba3033dd..497406700 100644 --- a/benchmark/setup_db.rb +++ b/benchmark/setup_db.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') # This script is for generating psudo-random data into a single table consisting of nearly every diff --git a/examples/eventmachine.rb b/examples/eventmachine.rb index 273f1bc52..dd8419cf2 100644 --- a/examples/eventmachine.rb +++ b/examples/eventmachine.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - $LOAD_PATH.unshift 'lib' require 'rubygems' diff --git a/examples/threaded.rb b/examples/threaded.rb index eaf769f8b..440a0081b 100644 --- a/examples/threaded.rb +++ b/examples/threaded.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - $LOAD_PATH.unshift 'lib' require 'mysql2' require 'timeout' diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index e5afe985f..5e0ebe3f0 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -1,12 +1,6 @@ #ifndef MYSQL2_CLIENT_H #define MYSQL2_CLIENT_H -#ifndef HAVE_RB_THREAD_CALL_WITHOUT_GVL -/* emulate rb_thread_call_without_gvl with rb_thread_blocking_region */ -#define rb_thread_call_without_gvl(func, data1, ubf, data2) \ - rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2) -#endif /* ! HAVE_RB_THREAD_CALL_WITHOUT_GVL */ - typedef struct { VALUE encoding; VALUE active_thread; /* rb_thread_current() or Qnil */ diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index bb08556a2..b73774536 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require 'mkmf' require 'English' @@ -27,9 +25,6 @@ def add_ssl_defines(header) have_func('rb_absint_size') have_func('rb_absint_singlebit_p') -# 2.0-only -have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h') - # Missing in RBX (https://github.com/rubinius/rubinius/issues/3771) have_func('rb_wait_for_single_fd') @@ -57,14 +52,6 @@ def add_ssl_defines(header) # If the user has provided a --with-mysql-dir argument, we must respect it or fail. inc, lib = dir_config('mysql') if inc && lib - # TODO: Remove when 2.0.0 is the minimum supported version - # Ruby versions not incorporating the mkmf fix at - # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 - # do not properly search for lib directories, and must be corrected - unless lib && lib[-3, 3] == 'lib' - @libdir_basename = 'lib' - inc, lib = dir_config('mysql') - end abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index d94f29483..f76ea45ec 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -18,15 +18,7 @@ void Init_mysql2(void); #endif #include -// ruby/thread.h was added in 2.0.0. See: -// https://github.com/ruby/ruby/commit/c51a826 -// -// Rubinius doesn't define this, but it ships an empty thread.h (the symbols we -// care about are in ruby.h); this is safe to remove when < 2.0.0 is no longer -// supported. -#ifdef HAVE_RUBY_THREAD_H #include -#endif #if defined(__GNUC__) && (__GNUC__ >= 3) #define RB_MYSQL_NORETURN __attribute__ ((noreturn)) diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 4ec3198f6..4bf75364a 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require 'date' require 'bigdecimal' @@ -75,13 +73,11 @@ def self.key_hash_as_symbols(hash) # Timeout::ExitException was removed in Ruby 2.3.0, 2.2.3, and 2.1.8, # but is present in earlier 2.1.x and 2.2.x, so we provide a shim. # - if Thread.respond_to?(:handle_interrupt) - require 'timeout' - TIMEOUT_ERROR_CLASS = if defined?(::Timeout::ExitException) - ::Timeout::ExitException - else - ::Timeout::Error - end + require 'timeout' + TIMEOUT_ERROR_CLASS = if defined?(::Timeout::ExitException) + ::Timeout::ExitException + else + ::Timeout::Error end end end diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 2c3a92d72..2cc0953cf 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - module Mysql2 class Client attr_reader :query_options, :read_timeout @@ -33,7 +31,7 @@ def initialize(opts = {}) opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) # TODO: stricter validation rather than silent massaging - [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close, :enable_cleartext_plugin].each do |key| + %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin].each do |key| next unless opts.key?(key) case key when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin @@ -71,7 +69,7 @@ def initialize(opts = {}) conn_attrs = opts[:connect_attrs] || {} conn_attrs[:program_name] = $PROGRAM_NAME unless conn_attrs.key?(:program_name) - if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) } + if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) } warn "============= WARNING FROM mysql2 =============" warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future." warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options." @@ -124,14 +122,8 @@ def parse_flags_array(flags, initial = 0) end end - if Thread.respond_to?(:handle_interrupt) - def query(sql, options = {}) - Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do - _query(sql, @query_options.merge(options)) - end - end - else - def query(sql, options = {}) + def query(sql, options = {}) + Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do _query(sql, @query_options.merge(options)) end end diff --git a/lib/mysql2/console.rb b/lib/mysql2/console.rb index 1e3885ffa..d8fb9e324 100644 --- a/lib/mysql2/console.rb +++ b/lib/mysql2/console.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - # Loaded by script/console. Land helpers here. Pry.config.prompt = lambda do |context, *| diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb index 381a2a739..329b3080e 100644 --- a/lib/mysql2/em.rb +++ b/lib/mysql2/em.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - require 'eventmachine' require 'mysql2' diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index a66fce45d..093dc65c2 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - module Mysql2 class Error < StandardError ENCODE_OPTS = { diff --git a/lib/mysql2/field.rb b/lib/mysql2/field.rb index 94a006739..516ec17c2 100644 --- a/lib/mysql2/field.rb +++ b/lib/mysql2/field.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - module Mysql2 Field = Struct.new(:name, :type) end diff --git a/lib/mysql2/result.rb b/lib/mysql2/result.rb index 255026a2b..585104e0b 100644 --- a/lib/mysql2/result.rb +++ b/lib/mysql2/result.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - module Mysql2 class Result attr_reader :server_flags diff --git a/lib/mysql2/statement.rb b/lib/mysql2/statement.rb index 482ccce19..f9741d075 100644 --- a/lib/mysql2/statement.rb +++ b/lib/mysql2/statement.rb @@ -1,17 +1,9 @@ -# encoding: UTF-8 - module Mysql2 class Statement include Enumerable - if Thread.respond_to?(:handle_interrupt) - def execute(*args) - Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do - _execute(*args) - end - end - else - def execute(*args) + def execute(*args) + Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do _execute(*args) end end diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 2afa42fc0..d7a169357 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - module Mysql2 VERSION = "0.4.10".freeze end diff --git a/mysql2.gemspec b/mysql2.gemspec index 3e9d4e5e3..1ec27428f 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require File.expand_path('../lib/mysql2/version', __FILE__) Mysql2::GEMSPEC = Gem::Specification.new do |s| @@ -12,7 +10,7 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s| s.homepage = 'http://github.com/brianmario/mysql2' s.rdoc_options = ["--charset=UTF-8"] s.summary = 'A simple, fast Mysql library for Ruby, binding to libmysql' - s.required_ruby_version = '>= 1.9.3' + s.required_ruby_version = '>= 2.0.0' s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split s.test_files = `git ls-files spec examples`.split diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 4daab27bc..c57d474dd 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require 'spec_helper' begin require 'eventmachine' diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 3c72c2a99..502d72af5 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require 'spec_helper' RSpec.describe Mysql2::Client do @@ -607,10 +605,6 @@ def run_gc end it 'should be impervious to connection-corrupting timeouts in #execute' do - # the statement handle gets corrupted and will segfault the tests if interrupted, - # so we can't even use pending on this test, really have to skip it on older Rubies. - skip('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt) - # attempt to break the connection stmt = @client.prepare('SELECT SLEEP(?)') expect { Timeout.timeout(0.1) { stmt.execute(0.2) } }.to raise_error(Timeout::Error) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index be3154be9..c94a31961 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require 'spec_helper' RSpec.describe Mysql2::Error do diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index f1386b16c..775bcf8ad 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require 'spec_helper' RSpec.describe Mysql2::Result do diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 7d856cbd3..40feb634e 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require './spec/spec_helper.rb' RSpec.describe Mysql2::Statement do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 32e3b0069..12b48df67 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require 'rspec' require 'mysql2' require 'timeout' diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index d0a5a4b81..602c5447a 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'mysql2' diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb index 120431e6e..4749ead75 100644 --- a/support/ruby_enc_to_mysql.rb +++ b/support/ruby_enc_to_mysql.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - mysql_to_rb = { "big5" => "Big5", "dec8" => nil, diff --git a/tasks/benchmarks.rake b/tasks/benchmarks.rake index 8302f62a3..b587ecdc0 100644 --- a/tasks/benchmarks.rake +++ b/tasks/benchmarks.rake @@ -1,5 +1,3 @@ -# encoding: UTF-8 - BENCHMARKS = Dir["#{File.dirname(__FILE__)}/../benchmark/*.rb"].map do |path| File.basename(path, '.rb') end - ['setup_db'] diff --git a/tasks/compile.rake b/tasks/compile.rake index ce33554f5..a9b7eb8a6 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require "rake/extensiontask" load File.expand_path('../../mysql2.gemspec', __FILE__) unless defined? Mysql2::GEMSPEC diff --git a/tasks/generate.rake b/tasks/generate.rake index 2712eeaee..6e5eebd99 100644 --- a/tasks/generate.rake +++ b/tasks/generate.rake @@ -1,5 +1,3 @@ -# encoding: UTF-8 - task :encodings do sh "ruby support/mysql_enc_to_ruby.rb > ./ext/mysql2/mysql_enc_to_ruby.h" sh "ruby support/ruby_enc_to_mysql.rb | gperf > ./ext/mysql2/mysql_enc_name_to_ruby.h" diff --git a/tasks/rspec.rake b/tasks/rspec.rake index a7bbfb098..ea8dc2258 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -1,5 +1,3 @@ -# encoding: UTF-8 - begin require 'rspec' require 'rspec/core/rake_task' diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake index 6d77be208..85e88fe68 100644 --- a/tasks/vendor_mysql.rake +++ b/tasks/vendor_mysql.rake @@ -1,5 +1,3 @@ -# encoding: UTF-8 - require 'rake/clean' require 'rake/extensioncompiler' From d203086bed53005bf406751529ada8140fecc940 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 29 Nov 2017 17:53:03 -0800 Subject: [PATCH 573/783] Accept query options on Statement#execute (#912) --- ext/mysql2/client.c | 2 + ext/mysql2/statement.c | 26 ++++++++--- lib/mysql2/statement.rb | 4 +- spec/mysql2/statement_spec.rb | 82 +++++++++++++++-------------------- 4 files changed, 58 insertions(+), 56 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index f2e289d4c..ff93751b3 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -595,6 +595,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { return Qnil; } + // Duplicate the options hash and put the copy in the Result object current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); @@ -1155,6 +1156,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) return Qnil; } + // Duplicate the options hash and put the copy in the Result object current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 3a022e661..506a79192 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -2,7 +2,7 @@ VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; -static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s; +static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang; static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year; #define GET_STATEMENT(self) \ @@ -184,7 +184,7 @@ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length * the buffer is a Ruby string pointer and not our memory to manage. */ #define FREE_BINDS \ - for (i = 0; i < argc; i++) { \ + for (i = 0; i < c; i++) { \ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \ xfree(bind_buffers[i].buffer); \ } \ @@ -248,8 +248,10 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { unsigned long *length_buffers = NULL; unsigned long bind_count; long i; + int c; MYSQL_STMT *stmt; MYSQL_RES *metadata; + VALUE opts; VALUE current; VALUE resultObj; VALUE *params_enc; @@ -261,14 +263,17 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { conn_enc = rb_to_encoding(wrapper->encoding); - /* Scratch space for string encoding exports, allocate on the stack. */ - params_enc = alloca(sizeof(VALUE) * argc); + // Get count of ordinary arguments, and extract hash opts/keyword arguments + c = rb_scan_args(argc, argv, "*:", NULL, &opts); + + // Scratch space for string encoding exports, allocate on the stack + params_enc = alloca(sizeof(VALUE) * c); stmt = stmt_wrapper->stmt; bind_count = mysql_stmt_param_count(stmt); - if (argc != (long)bind_count) { - rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, argc); + if (c != (long)bind_count) { + rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c); } // setup any bind variables in the query @@ -276,7 +281,7 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND)); length_buffers = xcalloc(bind_count, sizeof(unsigned long)); - for (i = 0; i < argc; i++) { + for (i = 0; i < c; i++) { bind_buffers[i].buffer = NULL; params_enc[i] = Qnil; @@ -416,10 +421,16 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { return Qnil; } + // Duplicate the options hash, merge! extra opts, put the copy into the Result object current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); + // Merge in hash opts/keyword arguments + if (!NIL_P(opts)) { + rb_funcall(current, intern_merge_bang, 1, opts); + } + is_streaming = (Qtrue == rb_hash_aref(current, sym_stream)); if (!is_streaming) { // recieve the whole result set from the server @@ -562,4 +573,5 @@ void init_mysql2_statement() { intern_year = rb_intern("year"); intern_to_s = rb_intern("to_s"); + intern_merge_bang = rb_intern("merge!"); } diff --git a/lib/mysql2/statement.rb b/lib/mysql2/statement.rb index f9741d075..2f9f09a05 100644 --- a/lib/mysql2/statement.rb +++ b/lib/mysql2/statement.rb @@ -2,9 +2,9 @@ module Mysql2 class Statement include Enumerable - def execute(*args) + def execute(*args, **kwargs) Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do - _execute(*args) + _execute(*args, **kwargs) end end end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 40feb634e..e36e88b8b 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -86,6 +86,20 @@ def stmt_count expect(result.to_a).to eq(['max1' => int64_max1, 'max2' => int64_max2, 'max3' => int64_max3, 'min1' => int64_min1, 'min2' => int64_min2, 'min3' => int64_min3]) end + it "should accept keyword arguments on statement execute" do + stmt = @client.prepare 'SELECT 1 AS a' + + expect(stmt.execute(as: :hash).first).to eq("a" => 1) + expect(stmt.execute(as: :array).first).to eq([1]) + end + + it "should accept bind arguments and keyword arguments on statement execute" do + stmt = @client.prepare 'SELECT ? AS a' + + expect(stmt.execute(1, as: :hash).first).to eq("a" => 1) + expect(stmt.execute(1, as: :array).first).to eq([1]) + end + it "should keep its result after other query" do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int)' @@ -186,10 +200,9 @@ def stmt_count end it "should warn but still work if cache_rows is set to false" do - @client.query_options[:cache_rows] = false statement = @client.prepare 'SELECT 1' result = nil - expect { result = statement.execute.to_a }.to output(/:cache_rows is forced for prepared statements/).to_stderr + expect { result = statement.execute(cache_rows: false).to_a }.to output(/:cache_rows is forced for prepared statements/).to_stderr expect(result.length).to eq(1) end @@ -238,10 +251,7 @@ def stmt_count it "should be able to stream query result" do n = 1 stmt = @client.prepare("SELECT 1 UNION SELECT 2") - - @client.query_options.merge!(stream: true, cache_rows: false, as: :array) - - stmt.execute.each do |r| + stmt.execute(stream: true, cache_rows: false, as: :array).each do |r| case n when 1 expect(r).to eq([1]) @@ -267,23 +277,17 @@ def stmt_count end it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do - @client.query_options[:symbolize_keys] = true - @result = @client.prepare("SELECT 1").execute + @result = @client.prepare("SELECT 1").execute(symbolize_keys: true) @result.each do |row| expect(row.keys.first).to be_an_instance_of(Symbol) end - @client.query_options[:symbolize_keys] = false end it "should be able to return results as an array" do - @client.query_options[:as] = :array - - @result = @client.prepare("SELECT 1").execute + @result = @client.prepare("SELECT 1").execute(as: :array) @result.each do |row| expect(row).to be_an_instance_of(Array) end - - @client.query_options[:as] = :hash end it "should cache previously yielded results by default" do @@ -292,35 +296,21 @@ def stmt_count end it "should yield different value for #first if streaming" do - @client.query_options[:stream] = true - @client.query_options[:cache_rows] = false - - result = @client.prepare("SELECT 1 UNION SELECT 2").execute + result = @client.prepare("SELECT 1 UNION SELECT 2").execute(stream: true, cache_rows: true) expect(result.first).not_to eql(result.first) - - @client.query_options[:stream] = false - @client.query_options[:cache_rows] = true end it "should yield the same value for #first if streaming is disabled" do - @client.query_options[:stream] = false - result = @client.prepare("SELECT 1 UNION SELECT 2").execute + result = @client.prepare("SELECT 1 UNION SELECT 2").execute(stream: false) expect(result.first).to eql(result.first) end it "should throw an exception if we try to iterate twice when streaming is enabled" do - @client.query_options[:stream] = true - @client.query_options[:cache_rows] = false - - result = @client.prepare("SELECT 1 UNION SELECT 2").execute - + result = @client.prepare("SELECT 1 UNION SELECT 2").execute(stream: true, cache_rows: false) expect do result.each {} result.each {} end.to raise_exception(Mysql2::Error) - - @client.query_options[:stream] = false - @client.query_options[:cache_rows] = true end end @@ -369,21 +359,20 @@ def stmt_count context "cast booleans for TINYINT if :cast_booleans is enabled" do # rubocop:disable Style/Semicolon - let(:client) { new_client(cast_booleans: true) } - let(:id1) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; client.last_id } - let(:id2) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; client.last_id } - let(:id3) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; client.last_id } + let(:id1) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; @client.last_id } + let(:id2) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; @client.last_id } + let(:id3) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; @client.last_id } # rubocop:enable Style/Semicolon after do - client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" + @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" end it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do - query = client.prepare 'SELECT bool_cast_test FROM mysql2_test WHERE id = ?' - result1 = query.execute id1 - result2 = query.execute id2 - result3 = query.execute id3 + query = @client.prepare 'SELECT bool_cast_test FROM mysql2_test WHERE id = ?' + result1 = query.execute id1, cast_booleans: true + result2 = query.execute id2, cast_booleans: true + result3 = query.execute id3, cast_booleans: true expect(result1.first['bool_cast_test']).to be true expect(result2.first['bool_cast_test']).to be false expect(result3.first['bool_cast_test']).to be true @@ -392,19 +381,18 @@ def stmt_count context "cast booleans for BIT(1) if :cast_booleans is enabled" do # rubocop:disable Style/Semicolon - let(:client) { new_client(cast_booleans: true) } - let(:id1) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; client.last_id } - let(:id2) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; client.last_id } + let(:id1) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; @client.last_id } + let(:id2) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; @client.last_id } # rubocop:enable Style/Semicolon after do - client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" + @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" end it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do - query = client.prepare 'SELECT single_bit_test FROM mysql2_test WHERE id = ?' - result1 = query.execute id1 - result2 = query.execute id2 + query = @client.prepare 'SELECT single_bit_test FROM mysql2_test WHERE id = ?' + result1 = query.execute id1, cast_booleans: true + result2 = query.execute id2, cast_booleans: true expect(result1.first['single_bit_test']).to be true expect(result2.first['single_bit_test']).to be false end From 6ec1566336f8dbefc4c9b0f99035c0647c6aa92a Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 29 Nov 2017 18:20:37 -0800 Subject: [PATCH 574/783] README text for query options on Statement#execute --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7284777dc..efb47bc7e 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,8 @@ end Prepared statements are supported, as well. In a prepared statement, use a `?` in place of each value and then execute the statement to retrieve a result set. Pass your arguments to the execute method in the same number and order as the -question marks in the statement. +question marks in the statement. Query options can be passed as keyword arguments +to the execute method. ``` ruby statement = @client.prepare("SELECT * FROM users WHERE login_count = ?") @@ -184,6 +185,9 @@ result2 = statement.execute(2) statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND location LIKE ?") result = statement.execute(1, "CA") + +statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND location LIKE ?") +result = statement.execute(1, "CA", :as => :array) ``` ## Connection options @@ -382,6 +386,15 @@ c = Mysql2::Client.new c.query(sql, :symbolize_keys => true) ``` +or + +``` ruby +# this will set the options for the Mysql2::Result instance returned from the #execute method +c = Mysql2::Client.new +s = c.prepare(sql) +s.execute(arg1, args2, :symbolize_keys => true) +``` + ## Result types ### Array of Arrays From d8daa9f9123db6f44b69c3216ae7e03d23b63c9e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 29 Nov 2017 17:54:30 -0800 Subject: [PATCH 575/783] Travis CI matrix add Ruby 2.5 and switch Mac OS X to Ruby 2.3 (ala High Sierra) --- .travis.yml | 7 ++++--- README.md | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7c3c594cd..56b51dba3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ bundler_args: --without benchmarks development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - gem --version - - gem update --system 2.6.12 + - gem update --system 2.7.3 - gem update bundler - gem --version - bash .travis_setup.sh @@ -18,6 +18,7 @@ addons: - mysql-client-core-5.6 - mysql-client-5.6 rvm: + - 2.5 - 2.4 - 2.3 - 2.2 @@ -72,7 +73,7 @@ matrix: hosts: - mysql2gem.example.com - os: osx - rvm: system + rvm: 2.3 env: DB=mysql56 addons: hosts: @@ -86,7 +87,7 @@ matrix: allow_failures: - rvm: ruby-head - os: osx - rvm: system + rvm: 2.3 env: DB=mysql56 - rvm: 2.0.0 env: DB=mysql51 diff --git a/README.md b/README.md index efb47bc7e..234fe4562 100644 --- a/README.md +++ b/README.md @@ -523,7 +523,7 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x + * Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x, 2.5.x * Rubinius 2.x and 3.x do work but may fail under some workloads This gem is tested with the following MySQL and MariaDB versions: From 1813043bf900f271d94a8970be83ac17075c58d7 Mon Sep 17 00:00:00 2001 From: Jean byroot Boussier Date: Thu, 30 Nov 2017 16:09:04 +0100 Subject: [PATCH 576/783] More specific exception classes (#911) * Simplify Mysql2::Error construction * Implement more specific exception classes * Raise specialized exception for connection and timeout errors --- ext/mysql2/client.c | 4 +-- ext/mysql2/mysql2_ext.c | 3 ++- ext/mysql2/statement.c | 2 +- lib/mysql2/error.rb | 51 ++++++++++++++++++++++++++++++++------ spec/mysql2/client_spec.rb | 12 ++++----- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index ff93751b3..665147a2a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -15,7 +15,7 @@ #include "mysql_enc_name_to_ruby.h" VALUE cMysql2Client; -extern VALUE mMysql2, cMysql2Error; +extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError; static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; static VALUE sym_no_good_index_used, sym_no_index_used, sym_query_was_slow; static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args; @@ -660,7 +660,7 @@ static VALUE do_query(void *args) { retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp); if (retval == 0) { - rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout)); + rb_raise(cMysql2TimeoutError, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout)); } if (retval < 0) { diff --git a/ext/mysql2/mysql2_ext.c b/ext/mysql2/mysql2_ext.c index a6fe365ac..2eb8b6d94 100644 --- a/ext/mysql2/mysql2_ext.c +++ b/ext/mysql2/mysql2_ext.c @@ -1,11 +1,12 @@ #include -VALUE mMysql2, cMysql2Error; +VALUE mMysql2, cMysql2Error, cMysql2TimeoutError; /* Ruby Extension initializer */ void Init_mysql2() { mMysql2 = rb_define_module("Mysql2"); cMysql2Error = rb_const_get(mMysql2, rb_intern("Error")); + cMysql2TimeoutError = rb_const_get(cMysql2Error, rb_intern("TimeoutError")); init_mysql2_client(); init_mysql2_result(); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 506a79192..3e3da0188 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -1,7 +1,7 @@ #include VALUE cMysql2Statement; -extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; +extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError, cBigDecimal, cDateTime, cDate; static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang; static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year; diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 093dc65c2..758f01a98 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -6,25 +6,60 @@ class Error < StandardError replace: '?'.freeze, }.freeze + ConnectionError = Class.new(Error) + TimeoutError = Class.new(Error) + + CODES = { + 1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT + + 1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR + 1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR + 1152 => ConnectionError, # ER_ABORTING_CONNECTION + 1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE + 1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE + 1155 => ConnectionError, # ER_NET_FCNTL_ERROR + 1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER + 1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR + 1158 => ConnectionError, # ER_NET_READ_ERROR + 1159 => ConnectionError, # ER_NET_READ_INTERRUPTED + 1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE + 1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED + + 2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR + 2002 => ConnectionError, # CR_CONNECTION_ERROR + 2003 => ConnectionError, # CR_CONN_HOST_ERROR + 2004 => ConnectionError, # CR_IPSOCK_ERROR + 2005 => ConnectionError, # CR_UNKNOWN_HOST + 2006 => ConnectionError, # CR_SERVER_GONE_ERROR + 2007 => ConnectionError, # CR_VERSION_ERROR + 2009 => ConnectionError, # CR_WRONG_HOST_INFO + 2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR + 2013 => ConnectionError, # CR_SERVER_LOST + 2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE + 2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR + 2027 => ConnectionError, # CR_MALFORMED_PACKET + 2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL + 2048 => ConnectionError, # CR_INVALID_CONN_HANDLE + 2049 => ConnectionError, # CR_UNUSED_1 + }.freeze + attr_reader :error_number, :sql_state # Mysql gem compatibility alias errno error_number alias error message - def initialize(msg) - @server_version ||= nil + def initialize(msg, server_version = nil, error_number = nil, sql_state = nil) + @server_version = server_version + @error_number = error_number + @sql_state = sql_state ? sql_state.encode(ENCODE_OPTS) : nil super(clean_message(msg)) end def self.new_with_args(msg, server_version, error_number, sql_state) - err = allocate - err.instance_variable_set('@server_version', server_version) - err.instance_variable_set('@error_number', error_number) - err.instance_variable_set('@sql_state', sql_state.encode(ENCODE_OPTS)) - err.send(:initialize, msg) - err + error_class = CODES.fetch(error_number, self) + error_class.new(msg, server_version, error_number, sql_state) end private diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 502d72af5..b61bd7e03 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -17,12 +17,12 @@ end end - it "should raise an exception upon connection failure" do + it "should raise a Mysql::Error::ConnectionError upon connection failure" do expect do # The odd local host IP address forces the mysql client library to # use a TCP socket rather than a domain socket. new_client('host' => '127.0.0.2', 'port' => 999999) - end.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error::ConnectionError) end it "should raise an exception on create for invalid encodings" do @@ -559,7 +559,7 @@ def run_gc client = new_client(read_timeout: 0) expect do client.query('SELECT SLEEP(0.1)') - end.to raise_error(Mysql2::Error) + end.to raise_error(Mysql2::Error::TimeoutError) end # XXX this test is not deterministic (because Unix signal handling is not) @@ -918,10 +918,10 @@ def run_gc end end - it "should raise a Mysql2::Error exception upon connection failure" do + it "should raise a Mysql2::Error::ConnectionError exception upon connection failure due to invalid credentials" do expect do - new_client(host: "localhost", username: 'asdfasdf8d2h', password: 'asdfasdfw42') - end.to raise_error(Mysql2::Error) + new_client(host: 'localhost', username: 'asdfasdf8d2h', password: 'asdfasdfw42') + end.to raise_error(Mysql2::Error::ConnectionError) expect do new_client(DatabaseCredentials['root']) From 79fa4d71fd0c93dd13af6b5da3556aa3a47b6244 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 3 Dec 2017 08:48:19 -0800 Subject: [PATCH 577/783] Travis CI for Mac OS X builds use the same steps again --- .travis.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 56b51dba3..768848f2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -78,11 +78,6 @@ matrix: addons: hosts: - mysql2gem.example.com - before_install: - # Use the system RubyGem with the system Ruby - - gem --version - - sudo gem install bundler - - bash .travis_setup.sh fast_finish: true allow_failures: - rvm: ruby-head From 973390f33a2a91eb4a3d88c23de53b4828e3e87f Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 3 Dec 2017 13:39:18 -0800 Subject: [PATCH 578/783] Small spec improvement for RSpec style --- spec/mysql2/result_spec.rb | 100 ++++++++++++++++------------------ spec/mysql2/statement_spec.rb | 85 ++++++++++++++--------------- 2 files changed, 88 insertions(+), 97 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 775bcf8ad..89744fc2f 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -107,12 +107,10 @@ end context "#fields" do - before(:each) do - @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") - end + let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") } it "method should exist" do - expect(@test_result).to respond_to(:fields) + expect(test_result).to respond_to(:fields) end it "should return an array of field names in proper order" do @@ -173,9 +171,7 @@ end context "row data type mapping" do - before(:each) do - @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first - end + let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first } it "should return nil values for NULL and strings for everything else when :cast is false" do result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', cast: false).first @@ -188,23 +184,23 @@ end it "should return nil for a NULL value" do - expect(@test_result['null_test']).to be_an_instance_of(NilClass) - expect(@test_result['null_test']).to eql(nil) + expect(test_result['null_test']).to be_an_instance_of(NilClass) + expect(test_result['null_test']).to eql(nil) end it "should return String for a BIT(64) value" do - expect(@test_result['bit_test']).to be_an_instance_of(String) - expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") + expect(test_result['bit_test']).to be_an_instance_of(String) + expect(test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do - expect(@test_result['single_bit_test']).to be_an_instance_of(String) - expect(@test_result['single_bit_test']).to eql("\001") + expect(test_result['single_bit_test']).to be_an_instance_of(String) + expect(test_result['single_bit_test']).to eql("\001") end it "should return Fixnum for a TINYINT value" do - expect(num_classes).to include(@test_result['tiny_int_test'].class) - expect(@test_result['tiny_int_test']).to eql(1) + expect(num_classes).to include(test_result['tiny_int_test'].class) + expect(test_result['tiny_int_test']).to eql(1) end context "cast booleans for TINYINT if :cast_booleans is enabled" do @@ -247,48 +243,48 @@ end it "should return Fixnum for a SMALLINT value" do - expect(num_classes).to include(@test_result['small_int_test'].class) - expect(@test_result['small_int_test']).to eql(10) + expect(num_classes).to include(test_result['small_int_test'].class) + expect(test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do - expect(num_classes).to include(@test_result['medium_int_test'].class) - expect(@test_result['medium_int_test']).to eql(10) + expect(num_classes).to include(test_result['medium_int_test'].class) + expect(test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do - expect(num_classes).to include(@test_result['int_test'].class) - expect(@test_result['int_test']).to eql(10) + expect(num_classes).to include(test_result['int_test'].class) + expect(test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do - expect(num_classes).to include(@test_result['big_int_test'].class) - expect(@test_result['big_int_test']).to eql(10) + expect(num_classes).to include(test_result['big_int_test'].class) + expect(test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do - expect(num_classes).to include(@test_result['year_test'].class) - expect(@test_result['year_test']).to eql(2009) + expect(num_classes).to include(test_result['year_test'].class) + expect(test_result['year_test']).to eql(2009) end it "should return BigDecimal for a DECIMAL value" do - expect(@test_result['decimal_test']).to be_an_instance_of(BigDecimal) - expect(@test_result['decimal_test']).to eql(10.3) + expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal) + expect(test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do - expect(@test_result['float_test']).to be_an_instance_of(Float) - expect(@test_result['float_test']).to eql(10.3) + expect(test_result['float_test']).to be_an_instance_of(Float) + expect(test_result['float_test']).to eql(10.3) end it "should return Float for a DOUBLE value" do - expect(@test_result['double_test']).to be_an_instance_of(Float) - expect(@test_result['double_test']).to eql(10.3) + expect(test_result['double_test']).to be_an_instance_of(Float) + expect(test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do - expect(@test_result['date_time_test']).to be_an_instance_of(Time) - expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') + expect(test_result['date_time_test']).to be_an_instance_of(Time) + expect(test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time when timestamp is < 1901-12-13 20:45:52" do @@ -302,23 +298,23 @@ end it "should return Time for a TIMESTAMP value when within the supported range" do - expect(@test_result['timestamp_test']).to be_an_instance_of(Time) - expect(@test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') + expect(test_result['timestamp_test']).to be_an_instance_of(Time) + expect(test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do - expect(@test_result['time_test']).to be_an_instance_of(Time) - expect(@test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') + expect(test_result['time_test']).to be_an_instance_of(Time) + expect(test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do - expect(@test_result['date_test']).to be_an_instance_of(Date) - expect(@test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') + expect(test_result['date_test']).to be_an_instance_of(Date) + expect(test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do - expect(@test_result['enum_test']).to be_an_instance_of(String) - expect(@test_result['enum_test']).to eql('val1') + expect(test_result['enum_test']).to be_an_instance_of(String) + expect(test_result['enum_test']).to eql('val1') end it "should raise an error given an invalid DATETIME" do @@ -352,8 +348,8 @@ end it "should return String for a SET value" do - expect(@test_result['set_test']).to be_an_instance_of(String) - expect(@test_result['set_test']).to eql('val1,val2') + expect(test_result['set_test']).to be_an_instance_of(String) + expect(test_result['set_test']).to eql('val1,val2') end context "string encoding for SET values" do @@ -382,8 +378,8 @@ end it "should return String for a BINARY value" do - expect(@test_result['binary_test']).to be_an_instance_of(String) - expect(@test_result['binary_test']).to eql("test#{"\000" * 6}") + expect(test_result['binary_test']).to be_an_instance_of(String) + expect(test_result['binary_test']).to eql("test#{"\000" * 6}") end context "string encoding for BINARY values" do @@ -421,8 +417,8 @@ 'long_text_test' => 'LONGTEXT', }.each do |field, type| it "should return a String for #{type}" do - expect(@test_result[field]).to be_an_instance_of(String) - expect(@test_result[field]).to eql("test") + expect(test_result[field]).to be_an_instance_of(String) + expect(test_result[field]).to eql("test") end context "string encoding for #{type} values" do @@ -474,18 +470,16 @@ end context "server flags" do - before(:each) do - @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY null_test DESC LIMIT 1") - end + let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY null_test DESC LIMIT 1") } it "should set a definitive value for query_was_slow" do - expect(@test_result.server_flags[:query_was_slow]).to eql(false) + expect(test_result.server_flags[:query_was_slow]).to eql(false) end it "should set a definitive value for no_index_used" do - expect(@test_result.server_flags[:no_index_used]).to eql(true) + expect(test_result.server_flags[:no_index_used]).to eql(true) end it "should set a definitive value for no_good_index_used" do - expect(@test_result.server_flags[:no_good_index_used]).to eql(false) + expect(test_result.server_flags[:no_good_index_used]).to eql(false) end end end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index e36e88b8b..5259c8076 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -332,29 +332,26 @@ def stmt_count end context "row data type mapping" do - before(:each) do - @client.query "USE test" - @test_result = @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute.first - end + let(:test_result) { @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute.first } it "should return nil for a NULL value" do - expect(@test_result['null_test']).to be_an_instance_of(NilClass) - expect(@test_result['null_test']).to eql(nil) + expect(test_result['null_test']).to be_an_instance_of(NilClass) + expect(test_result['null_test']).to eql(nil) end it "should return String for a BIT(64) value" do - expect(@test_result['bit_test']).to be_an_instance_of(String) - expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") + expect(test_result['bit_test']).to be_an_instance_of(String) + expect(test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do - expect(@test_result['single_bit_test']).to be_an_instance_of(String) - expect(@test_result['single_bit_test']).to eql("\001") + expect(test_result['single_bit_test']).to be_an_instance_of(String) + expect(test_result['single_bit_test']).to eql("\001") end it "should return Fixnum for a TINYINT value" do - expect(num_classes).to include(@test_result['tiny_int_test'].class) - expect(@test_result['tiny_int_test']).to eql(1) + expect(num_classes).to include(test_result['tiny_int_test'].class) + expect(test_result['tiny_int_test']).to eql(1) end context "cast booleans for TINYINT if :cast_booleans is enabled" do @@ -399,48 +396,48 @@ def stmt_count end it "should return Fixnum for a SMALLINT value" do - expect(num_classes).to include(@test_result['small_int_test'].class) - expect(@test_result['small_int_test']).to eql(10) + expect(num_classes).to include(test_result['small_int_test'].class) + expect(test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do - expect(num_classes).to include(@test_result['medium_int_test'].class) - expect(@test_result['medium_int_test']).to eql(10) + expect(num_classes).to include(test_result['medium_int_test'].class) + expect(test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do - expect(num_classes).to include(@test_result['int_test'].class) - expect(@test_result['int_test']).to eql(10) + expect(num_classes).to include(test_result['int_test'].class) + expect(test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do - expect(num_classes).to include(@test_result['big_int_test'].class) - expect(@test_result['big_int_test']).to eql(10) + expect(num_classes).to include(test_result['big_int_test'].class) + expect(test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do - expect(num_classes).to include(@test_result['year_test'].class) - expect(@test_result['year_test']).to eql(2009) + expect(num_classes).to include(test_result['year_test'].class) + expect(test_result['year_test']).to eql(2009) end it "should return BigDecimal for a DECIMAL value" do - expect(@test_result['decimal_test']).to be_an_instance_of(BigDecimal) - expect(@test_result['decimal_test']).to eql(10.3) + expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal) + expect(test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do - expect(@test_result['float_test']).to be_an_instance_of(Float) - expect(@test_result['float_test']).to be_within(1e-5).of(10.3) + expect(test_result['float_test']).to be_an_instance_of(Float) + expect(test_result['float_test']).to be_within(1e-5).of(10.3) end it "should return Float for a DOUBLE value" do - expect(@test_result['double_test']).to be_an_instance_of(Float) - expect(@test_result['double_test']).to eql(10.3) + expect(test_result['double_test']).to be_an_instance_of(Float) + expect(test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do - expect(@test_result['date_time_test']).to be_an_instance_of(Time) - expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') + expect(test_result['date_time_test']).to be_an_instance_of(Time) + expect(test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time when timestamp is < 1901-12-13 20:45:52" do @@ -454,23 +451,23 @@ def stmt_count end it "should return Time for a TIMESTAMP value when within the supported range" do - expect(@test_result['timestamp_test']).to be_an_instance_of(Time) - expect(@test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') + expect(test_result['timestamp_test']).to be_an_instance_of(Time) + expect(test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do - expect(@test_result['time_test']).to be_an_instance_of(Time) - expect(@test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') + expect(test_result['time_test']).to be_an_instance_of(Time) + expect(test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do - expect(@test_result['date_test']).to be_an_instance_of(Date) - expect(@test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') + expect(test_result['date_test']).to be_an_instance_of(Date) + expect(test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do - expect(@test_result['enum_test']).to be_an_instance_of(String) - expect(@test_result['enum_test']).to eql('val1') + expect(test_result['enum_test']).to be_an_instance_of(String) + expect(test_result['enum_test']).to eql('val1') end it "should raise an error given an invalid DATETIME" do @@ -504,8 +501,8 @@ def stmt_count end it "should return String for a SET value" do - expect(@test_result['set_test']).to be_an_instance_of(String) - expect(@test_result['set_test']).to eql('val1,val2') + expect(test_result['set_test']).to be_an_instance_of(String) + expect(test_result['set_test']).to eql('val1,val2') end context "string encoding for SET values" do @@ -534,8 +531,8 @@ def stmt_count end it "should return String for a BINARY value" do - expect(@test_result['binary_test']).to be_an_instance_of(String) - expect(@test_result['binary_test']).to eql("test#{"\000" * 6}") + expect(test_result['binary_test']).to be_an_instance_of(String) + expect(test_result['binary_test']).to eql("test#{"\000" * 6}") end context "string encoding for BINARY values" do @@ -573,8 +570,8 @@ def stmt_count 'long_text_test' => 'LONGTEXT', }.each do |field, type| it "should return a String for #{type}" do - expect(@test_result[field]).to be_an_instance_of(String) - expect(@test_result[field]).to eql("test") + expect(test_result[field]).to be_an_instance_of(String) + expect(test_result[field]).to eql("test") end context "string encoding for #{type} values" do From e9c1e1f4f4e4d82b08a6de55e26f3f6a2580708e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 3 Dec 2017 14:18:40 -0800 Subject: [PATCH 579/783] Avoid 0 byte allocation call when statement takes no parameters --- ext/mysql2/statement.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 3e3da0188..fec9aeba8 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -266,9 +266,6 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { // Get count of ordinary arguments, and extract hash opts/keyword arguments c = rb_scan_args(argc, argv, "*:", NULL, &opts); - // Scratch space for string encoding exports, allocate on the stack - params_enc = alloca(sizeof(VALUE) * c); - stmt = stmt_wrapper->stmt; bind_count = mysql_stmt_param_count(stmt); @@ -278,6 +275,8 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { // setup any bind variables in the query if (bind_count > 0) { + // Scratch space for string encoding exports, allocate on the stack + params_enc = alloca(sizeof(VALUE) * c); bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND)); length_buffers = xcalloc(bind_count, sizeof(unsigned long)); From 51665eb02d9028d5107bde83053dc08fd441dc1d Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Wed, 6 Dec 2017 20:45:13 +0100 Subject: [PATCH 580/783] Fix wrong value of type YEAR on big endian environment. (#921) --- ext/mysql2/result.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 9224e6548..f93a52f84 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -234,12 +234,12 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields wrapper->result_buffers[i].buffer_length = sizeof(signed char); break; case MYSQL_TYPE_SHORT: // short int + case MYSQL_TYPE_YEAR: // short int wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(short int)); wrapper->result_buffers[i].buffer_length = sizeof(short int); break; case MYSQL_TYPE_INT24: // int case MYSQL_TYPE_LONG: // int - case MYSQL_TYPE_YEAR: // int wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(int)); wrapper->result_buffers[i].buffer_length = sizeof(int); break; @@ -365,6 +365,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } break; case MYSQL_TYPE_SHORT: // short int + case MYSQL_TYPE_YEAR: // short int if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned short int*)result_buffer->buffer)); } else { @@ -373,7 +374,6 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co break; case MYSQL_TYPE_INT24: // int case MYSQL_TYPE_LONG: // int - case MYSQL_TYPE_YEAR: // int if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned int*)result_buffer->buffer)); } else { From e52e9ce47bf64afe55bef4618188fa9d96d57345 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 7 Dec 2017 17:26:04 +0900 Subject: [PATCH 581/783] GitHub is HTTPS by default (#922) --- mysql2.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql2.gemspec b/mysql2.gemspec index 1ec27428f..98a89b95a 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -7,7 +7,7 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s| s.license = "MIT" s.email = ['seniorlopez@gmail.com', 'aaron@serendipity.cx'] s.extensions = ["ext/mysql2/extconf.rb"] - s.homepage = 'http://github.com/brianmario/mysql2' + s.homepage = 'https://github.com/brianmario/mysql2' s.rdoc_options = ["--charset=UTF-8"] s.summary = 'A simple, fast Mysql library for Ruby, binding to libmysql' s.required_ruby_version = '>= 2.0.0' From c0ba3d3f5915f9d8d3b5d6357a80f11f17d1e3f8 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 20 Dec 2017 11:14:12 -0800 Subject: [PATCH 582/783] Call BigDecimal(num) instead of BigDecimal.new(num) (#928) Call BigDecimal(num) instead of BigDecimal.new(num) to be ready for this deprecation in Ruby 2.6, and eliminate a deprecation warning now. Reduce the number of extern classes - while developing this change, I was chasing a NULL pointer on cBigDecimal from statement.c because I removed the class lookup from result.c, but extern meant the compiler did not flag it as an uninitialized value. --- benchmark/query_with_mysql_casting.rb | 2 +- ext/mysql2/result.c | 22 +++++++++++----------- ext/mysql2/statement.c | 9 ++++++--- ext/mysql2/statement.h | 2 -- spec/mysql2/statement_spec.rb | 4 ++-- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb index caf7eb011..e63bd2fb6 100644 --- a/benchmark/query_with_mysql_casting.rb +++ b/benchmark/query_with_mysql_casting.rb @@ -21,7 +21,7 @@ def mysql_cast(type, value) Mysql::Field::TYPE_INT24, Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_YEAR value.to_i when Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_NEWDECIMAL - BigDecimal.new(value) + BigDecimal(value) when Mysql::Field::TYPE_DOUBLE, Mysql::Field::TYPE_FLOAT value.to_f when Mysql::Field::TYPE_DATE diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index f93a52f84..857de908e 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -32,14 +32,14 @@ typedef struct { VALUE block_given; } result_each_args; -VALUE cBigDecimal, cDateTime, cDate; -static VALUE cMysql2Result; -static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset; extern VALUE mMysql2, cMysql2Client, cMysql2Error; -static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset; -static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone, - sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name; -static ID intern_merge; +static VALUE cMysql2Result, cDateTime, cDate; +static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset; +static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, + intern_civil, intern_new_offset, intern_merge, intern_BigDecimal; +static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, + sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, + sym_cache_rows, sym_cast, sym_stream, sym_name; /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */ static void rb_mysql_result_mark(void * wrapper) { @@ -444,7 +444,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } case MYSQL_TYPE_DECIMAL: // char[] case MYSQL_TYPE_NEWDECIMAL: // char[] - val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length))); + val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length))); break; case MYSQL_TYPE_STRING: // char[] case MYSQL_TYPE_VAR_STRING: // char[] @@ -546,9 +546,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r if (fields[i].decimals == 0) { val = rb_cstr2inum(row[i], 10); } else if (strtod(row[i], NULL) == 0.000000){ - val = rb_funcall(cBigDecimal, intern_new, 1, opt_decimal_zero); + val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero); }else{ - val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i])); + val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(row[i], fieldLengths[i])); } break; case MYSQL_TYPE_FLOAT: /* FLOAT field */ @@ -959,7 +959,6 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ } void init_mysql2_result() { - cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal")); cDate = rb_const_get(rb_cObject, rb_intern("Date")); cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); @@ -978,6 +977,7 @@ void init_mysql2_result() { intern_local_offset = rb_intern("local_offset"); intern_civil = rb_intern("civil"); intern_new_offset = rb_intern("new_offset"); + intern_BigDecimal = rb_intern("BigDecimal"); sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index fec9aeba8..3bfba62d5 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -1,7 +1,7 @@ #include -VALUE cMysql2Statement; -extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError, cBigDecimal, cDateTime, cDate; +extern VALUE mMysql2, cMysql2Error; +static VALUE cMysql2Statement, cBigDecimal, cDateTime, cDate; static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang; static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year; @@ -547,8 +547,11 @@ static VALUE rb_mysql_stmt_close(VALUE self) { } void init_mysql2_statement() { - cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); + cDate = rb_const_get(rb_cObject, rb_intern("Date")); + cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); + cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal")); + cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0); rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0); rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1); diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index 63260aab0..e48510676 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -1,8 +1,6 @@ #ifndef MYSQL2_STATEMENT_H #define MYSQL2_STATEMENT_H -extern VALUE cMysql2Statement; - typedef struct { VALUE client; MYSQL_STMT *stmt; diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 5259c8076..6d617eab0 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -183,7 +183,7 @@ def stmt_count it "should handle as a decimal binding a BigDecimal" do stmt = @client.prepare('SELECT ? AS decimal_test') - test_result = stmt.execute(BigDecimal.new("123.45")).first + test_result = stmt.execute(BigDecimal("123.45")).first expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal) expect(test_result['decimal_test']).to eql(123.45) end @@ -193,7 +193,7 @@ def stmt_count @client.query 'DROP TABLE IF EXISTS mysql2_stmt_decimal_test' @client.query 'CREATE TABLE mysql2_stmt_decimal_test (decimal_test DECIMAL(10,3))' - @client.prepare("INSERT INTO mysql2_stmt_decimal_test VALUES (?)").execute(BigDecimal.new("123.45")) + @client.prepare("INSERT INTO mysql2_stmt_decimal_test VALUES (?)").execute(BigDecimal("123.45")) test_result = @client.query("SELECT * FROM mysql2_stmt_decimal_test").first expect(test_result['decimal_test']).to eql(123.45) From 41c59bc00296ab1f1c031be85d45a30f4c9e07e6 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 29 Nov 2017 18:26:30 -0800 Subject: [PATCH 583/783] Fix Ruby 2.4 on Appveyor --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 798534f5c..274bc6d80 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,6 @@ install: - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - ruby --version - gem --version - - gem install bundler --quiet --no-ri --no-rdoc - bundler --version - bundle install --without benchmarks --path vendor/bundle - IF DEFINED MINGW_PACKAGE_PREFIX (ridk exec pacman -S --noconfirm --needed %MINGW_PACKAGE_PREFIX%-libmariadbclient) From efa47a935447bb96f178c11ce32834f877c36b77 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 16 Dec 2017 22:15:31 -0800 Subject: [PATCH 584/783] Travis CI drop tests for MySQL 5.1 on Ubuntu Precise --- .travis.yml | 9 --------- .travis_mysql51.sh | 11 ----------- .travis_setup.sh | 5 ----- 3 files changed, 25 deletions(-) delete mode 100644 .travis_mysql51.sh diff --git a/.travis.yml b/.travis.yml index 768848f2b..dde67d598 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,12 +45,6 @@ matrix: mariadb: 10.2 hosts: - mysql2gem.example.com - - rvm: 2.0.0 - env: DB=mysql51 - dist: precise - addons: - hosts: - - mysql2gem.example.com - rvm: 2.0.0 env: DB=mysql55 dist: precise @@ -84,6 +78,3 @@ matrix: - os: osx rvm: 2.3 env: DB=mysql56 - - rvm: 2.0.0 - env: DB=mysql51 - dist: precise diff --git a/.travis_mysql51.sh b/.travis_mysql51.sh deleted file mode 100644 index d106b6464..000000000 --- a/.travis_mysql51.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -apt-get purge -qq '^mysql*' '^libmysql*' -rm -fr /etc/mysql -rm -fr /var/lib/mysql -apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key 9334A25F8507EFA5 -add-apt-repository 'deb http://repo.percona.com/apt precise main' -apt-get update -qq -apt-get install -qq percona-server-server-5.1 percona-server-client-5.1 libmysqlclient16-dev diff --git a/.travis_setup.sh b/.travis_setup.sh index ab47c2eb9..a8132283f 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -2,11 +2,6 @@ set -eux -# Install MySQL 5.1 if DB=mysql51 -if [[ -n ${DB-} && x$DB =~ ^xmysql51 ]]; then - sudo bash .travis_mysql51.sh -fi - # Install MySQL 5.7 if DB=mysql57 if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then sudo bash .travis_mysql57.sh From 929125b80aa99a128f673b98225159a96717e432 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 16 Dec 2017 22:15:36 -0800 Subject: [PATCH 585/783] README note that MariaDB 10.2 is supported --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 234fe4562..a8bb2085a 100644 --- a/README.md +++ b/README.md @@ -530,7 +530,7 @@ This gem is tested with the following MySQL and MariaDB versions: * MySQL 5.5, 5.6, 5.7, 8.0 * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) - * MariaDB 5.5, 10.0, 10.1 + * MariaDB 5.5, 10.0, 10.1, 10.2 ### Ruby on Rails / Active Record From 84bc37c697f7d4de2dc0316f2d8d1af33ea57f88 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 29 Nov 2017 21:04:24 -0800 Subject: [PATCH 586/783] Use CLIENT_CONNECT_ATTRS flag to test the connection attributes feature --- ext/mysql2/client.c | 14 ++++++++++++-- ext/mysql2/extconf.rb | 1 - lib/mysql2/client.rb | 22 +++++++++++++--------- spec/mysql2/client_spec.rb | 11 ++++++----- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 665147a2a..937077964 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -400,7 +400,7 @@ static VALUE rb_mysql_get_ssl_cipher(VALUE self) return rb_str; } -#ifdef HAVE_CONST_MYSQL_OPT_CONNECT_ATTR_ADD +#ifdef CLIENT_CONNECT_ATTRS static int opt_connect_attr_add_i(VALUE key, VALUE value, VALUE arg) { mysql_client_wrapper *wrapper = (mysql_client_wrapper *)arg; @@ -428,7 +428,7 @@ static VALUE rb_mysql_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VA args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); -#ifdef HAVE_CONST_MYSQL_OPT_CONNECT_ATTR_ADD +#ifdef CLIENT_CONNECT_ATTRS mysql_options(wrapper->client, MYSQL_OPT_CONNECT_ATTR_RESET, 0); rb_hash_foreach(conn_attrs, opt_connect_attr_add_i, (VALUE)wrapper); #endif @@ -1558,6 +1558,16 @@ void init_mysql2_client() { LONG2NUM(CLIENT_BASIC_FLAGS)); #endif +#ifdef CLIENT_CONNECT_ATTRS + rb_const_set(cMysql2Client, rb_intern("CONNECT_ATTRS"), + LONG2NUM(CLIENT_CONNECT_ATTRS)); +#else + /* HACK because MySQL 5.5 and earlier don't define this constant, + * but we're using it in our default connection flags. */ + rb_const_set(cMysql2Client, rb_intern("CONNECT_ATTRS"), + INT2NUM(0)); +#endif + #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED)); diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index b73774536..baa24d438 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -103,7 +103,6 @@ def add_ssl_defines(header) have_const('SERVER_QUERY_NO_GOOD_INDEX_USED', mysql_h) have_const('SERVER_QUERY_NO_INDEX_USED', mysql_h) have_const('SERVER_QUERY_WAS_SLOW', mysql_h) -have_const('MYSQL_OPT_CONNECT_ATTR_ADD', mysql_h) # for mysql_options4 # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 2cc0953cf..7e28768d5 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -11,7 +11,7 @@ def self.default_query_options database_timezone: :local, # timezone Mysql2 will assume datetime objects are stored in application_timezone: nil, # timezone Mysql2 will convert to before handing the object back to the caller cache_rows: true, # tells Mysql2 to use its internal row cache for results - connect_flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION, + connect_flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | CONNECT_ATTRS, cast: true, default_file: nil, default_group: nil, @@ -64,11 +64,6 @@ def initialize(opts = {}) # SSL verify is a connection flag rather than a mysql_ssl_set option flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] - # Set default program_name in performance_schema.session_connect_attrs - # and performance_schema.session_account_connect_attrs - conn_attrs = opts[:connect_attrs] || {} - conn_attrs[:program_name] = $PROGRAM_NAME unless conn_attrs.key?(:program_name) - if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) } warn "============= WARNING FROM mysql2 =============" warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future." @@ -90,9 +85,7 @@ def initialize(opts = {}) port = port.to_i unless port.nil? database = database.to_s unless database.nil? socket = socket.to_s unless socket.nil? - conn_attrs = conn_attrs.each_with_object({}) do |(key, value), hash| - hash[key.to_s] = value.to_s - end + conn_attrs = parse_connect_attrs(opts[:connect_attrs]) connect user, pass, host, port, database, socket, flags, conn_attrs end @@ -122,6 +115,17 @@ def parse_flags_array(flags, initial = 0) end end + # Set default program_name in performance_schema.session_connect_attrs + # and performance_schema.session_account_connect_attrs + def parse_connect_attrs(conn_attrs) + return {} if Mysql2::Client::CONNECT_ATTRS.zero? + conn_attrs ||= {} + conn_attrs[:program_name] ||= $PROGRAM_NAME + conn_attrs.each_with_object({}) do |(key, value), hash| + hash[key.to_s] = value.to_s + end + end + def query(sql, options = {}) Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do _query(sql, @query_options.merge(options)) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index b61bd7e03..f82f13b3d 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -79,7 +79,8 @@ def connect(*args) Mysql2::Client::LONG_FLAG | Mysql2::Client::TRANSACTIONS | Mysql2::Client::PROTOCOL_41 | - Mysql2::Client::SECURE_CONNECTION + Mysql2::Client::SECURE_CONNECTION | + Mysql2::Client::CONNECT_ATTRS expect(client.connect_args.last[6]).to eql(client_flags) end @@ -438,8 +439,8 @@ def run_gc it "should set default program_name in connect_attrs" do client = new_client - if Mysql2::Client.info[:version] < '5.6' || client.info[:version] < '5.6' - pending('Both client and server versions must be MySQL 5.6 or later.') + if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/) + pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.') end result = client.query("SELECT attr_value FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id() AND attr_name = 'program_name'") expect(result.first['attr_value']).to eq($PROGRAM_NAME) @@ -447,8 +448,8 @@ def run_gc it "should set custom connect_attrs" do client = new_client(connect_attrs: { program_name: 'my_program_name', foo: 'fooval', bar: 'barval' }) - if Mysql2::Client.info[:version] < '5.6' || client.info[:version] < '5.6' - pending('Both client and server versions must be MySQL 5.6 or later.') + if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/) + pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.') end results = Hash[client.query("SELECT * FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id()").map { |x| x.values_at('ATTR_NAME', 'ATTR_VALUE') }] expect(results['program_name']).to eq('my_program_name') From 827dc516817f383afd30af7392047213b05c8e41 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 17 Dec 2017 20:08:20 -0800 Subject: [PATCH 587/783] Looks like Windows Ruby 2.0 still has the buggy mkmf --- ext/mysql2/extconf.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index baa24d438..8755cb091 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -52,6 +52,13 @@ def add_ssl_defines(header) # If the user has provided a --with-mysql-dir argument, we must respect it or fail. inc, lib = dir_config('mysql') if inc && lib + # Ruby versions below 2.0 on Unix and below 2.1 on Windows + # do not properly search for lib directories, and must be corrected: + # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 + unless lib && lib[-3, 3] == 'lib' + @libdir_basename = 'lib' + inc, lib = dir_config('mysql') + end abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" From b2cfca1fabe8ef698c9d0ea3db31cb2ca8485ddc Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 20 Dec 2017 11:39:23 -0800 Subject: [PATCH 588/783] Travis CI should run MySQL 5.5 tests on Trusty --- .travis.yml | 12 +++--------- .travis_mysql55.sh | 8 ++++++++ .travis_setup.sh | 5 +++++ 3 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 .travis_mysql55.sh diff --git a/.travis.yml b/.travis.yml index dde67d598..9a3bb367b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,17 +45,11 @@ matrix: mariadb: 10.2 hosts: - mysql2gem.example.com - - rvm: 2.0.0 + - rvm: 2.4 env: DB=mysql55 - dist: precise addons: hosts: - mysql2gem.example.com - apt: - packages: - - mysql-server-5.5 - - mysql-client-core-5.5 - - mysql-client-5.5 - rvm: 2.4 env: DB=mysql57 addons: @@ -67,7 +61,7 @@ matrix: hosts: - mysql2gem.example.com - os: osx - rvm: 2.3 + rvm: 2.4 env: DB=mysql56 addons: hosts: @@ -76,5 +70,5 @@ matrix: allow_failures: - rvm: ruby-head - os: osx - rvm: 2.3 + rvm: 2.4 env: DB=mysql56 diff --git a/.travis_mysql55.sh b/.travis_mysql55.sh new file mode 100644 index 000000000..771250dc0 --- /dev/null +++ b/.travis_mysql55.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -eux + +apt-get purge -qq '^mysql*' '^libmysql*' +rm -fr /etc/mysql +rm -fr /var/lib/mysql +apt-get install -qq mysql-server-5.5 mysql-client-core-5.5 mysql-client-5.5 libmysqlclient-dev diff --git a/.travis_setup.sh b/.travis_setup.sh index a8132283f..6b68bdacd 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -2,6 +2,11 @@ set -eux +# Install MySQL 5.5 if DB=mysql55 +if [[ -n ${DB-} && x$DB =~ ^xmysql55 ]]; then + sudo bash .travis_mysql55.sh +fi + # Install MySQL 5.7 if DB=mysql57 if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then sudo bash .travis_mysql57.sh From 503429ccf385cb2e68ad4e0f50de868b1d0afa45 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Thu, 15 Feb 2018 00:51:10 +0900 Subject: [PATCH 589/783] Update usage section of README.md (#939) Follow up of #907. Since `Fixnum` is a subclass of `Integer`, I thought it would be OK to replace it with `Integer`. Especially, It will be a calm expression for Ruby 2.4 or higher users. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8bb2085a..00bf85873 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ results.each do |row| # conveniently, row is a hash # the keys are the fields, as you'd expect # the values are pre-built ruby primitives mapped from their corresponding field types in MySQL - puts row["id"] # row["id"].class == Fixnum + puts row["id"] # row["id"].is_a? Integer if row["dne"] # non-existant hash entry is nil puts row["dne"] end From 9d971db1782117f30cc1a6231b1ee0b363f244da Mon Sep 17 00:00:00 2001 From: Edouard Chin Date: Thu, 1 Mar 2018 18:09:02 -0500 Subject: [PATCH 590/783] Expose the `mysql_set_server_option`: (#943) - Use case: I'd like to be able to do multiple statements query without having to reconnect to the db first. Without this feature, if I want to do a multi statement query **after** the connection is established without the `MULTI_STATEMENTS` flag, I'd have to set the flag on the connection and reconnect - One of the main motivation for this is because Rails is now inserting fixtures inside a multi-statements query. We used the workaround I described above, but it would be great if we could use the mysql function mysql_set_server_option . For more context [Ref](https://github.com/rails/rails/pull/31422#discussion_r162201979) - Ref https://dev.mysql.com/doc/refman/5.5/en/mysql-set-server-option.html --- ext/mysql2/client.c | 25 +++++++++++++++++++++++++ spec/mysql2/client_spec.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 937077964..1742cad97 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1098,6 +1098,23 @@ static VALUE rb_mysql_client_ping(VALUE self) { } } +/* call-seq: + * client.set_server_option(value) + * + * Enables or disables an option for the connection. + * Read https://dev.mysql.com/doc/refman/5.7/en/mysql-set-server-option.html + * for more information. + */ +static VALUE rb_mysql_client_set_server_option(VALUE self, VALUE value) { + GET_CLIENT(self); + + if (mysql_set_server_option(wrapper->client, NUM2INT(value)) == 0) { + return Qtrue; + } else { + return Qfalse; + } +} + /* call-seq: * client.more_results? * @@ -1399,6 +1416,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0); rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0); rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1); + rb_define_method(cMysql2Client, "set_server_option", rb_mysql_client_set_server_option, 1); rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0); rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0); rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0); @@ -1528,6 +1546,13 @@ void init_mysql2_client() { rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0)); #endif +#if MYSQL_VERSION_ID >= 40101 + rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_ON"), + LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_ON)); + rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_OFF"), + LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_OFF)); +#endif + #ifdef CLIENT_MULTI_STATEMENTS rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"), LONG2NUM(CLIENT_MULTI_STATEMENTS)); diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index f82f13b3d..00d9c17db 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -205,6 +205,40 @@ def run_gc # rubocop:enable Lint/AmbiguousBlockAssociation end + context "#set_server_option" do + let(:client) do + new_client.tap do |client| + client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON) + end + end + + it 'returns true when multi_statements is enable' do + expect(client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)).to be true + end + + it 'returns true when multi_statements is disable' do + expect(client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)).to be true + end + + it 'returns false when multi_statements is neither OPTION_MULTI_STATEMENTS_OFF or OPTION_MULTI_STATEMENTS_ON' do + expect(client.set_server_option(344)).to be false + end + + it 'enables multiple-statement' do + client.query("SELECT 1;SELECT 2;") + + expect(client.next_result).to be true + expect(client.store_result.first).to eql('2' => 2) + expect(client.next_result).to be false + end + + it 'disables multiple-statement' do + client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF) + + expect { client.query("SELECT 1;SELECT 2;") }.to raise_error(Mysql2::Error) + end + end + context "#automatic_close" do it "is enabled by default" do expect(new_client.automatic_close?).to be(true) From a2440a71453577b365e24bdfbb508f2f4dd5313c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 Mar 2018 16:27:43 -0700 Subject: [PATCH 591/783] Style nit for #943 --- ext/mysql2/client.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 1742cad97..47fbe8dc0 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1546,9 +1546,12 @@ void init_mysql2_client() { rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0)); #endif -#if MYSQL_VERSION_ID >= 40101 +#ifdef MYSQL_OPTION_MULTI_STATEMENTS_ON rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_ON"), LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_ON)); +#endif + +#ifdef MYSQL_OPTION_MULTI_STATEMENTS_OFF rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_OFF"), LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_OFF)); #endif From 62b57ea4b827e92255b7b724bd04532ab51fa381 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 3 Dec 2017 08:01:23 -0800 Subject: [PATCH 592/783] Revert "Use `bool` instead of `my_bool` which has been removed since MySQL 8.0.1 (#840)" This reverts commit 5889d04cba68531edd1ccf1479f2aab6ae1244fb. --- ext/mysql2/client.c | 6 +++--- ext/mysql2/result.c | 4 ++-- ext/mysql2/result.h | 5 ++--- ext/mysql2/statement.c | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 47fbe8dc0..00b50fd46 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -114,7 +114,7 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { int val = NUM2INT( setting ); if (version >= 50703 && version < 50711) { if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { - bool b = ( val == SSL_MODE_REQUIRED ); + my_bool b = ( val == SSL_MODE_REQUIRED ); int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); return INT2NUM(result); } else { @@ -526,7 +526,7 @@ static VALUE do_send_query(void *args) { */ static void *nogvl_read_query_result(void *ptr) { MYSQL * client = ptr; - bool res = mysql_read_query_result(client); + my_bool res = mysql_read_query_result(client); return (void *)(res == 0 ? Qtrue : Qfalse); } @@ -846,7 +846,7 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { const void *retval = NULL; unsigned int intval = 0; const char * charval = NULL; - bool boolval; + my_bool boolval; GET_CLIENT(self); diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 857de908e..a4957ee42 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -218,8 +218,8 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields if (wrapper->result_buffers != NULL) return; wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND)); - wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(bool)); - wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(bool)); + wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); + wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long)); for (i = 0; i < wrapper->numberOfFields; i++) { diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 525a2188b..0c25b24b6 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -1,6 +1,5 @@ #ifndef MYSQL2_RESULT_H #define MYSQL2_RESULT_H -#include void init_mysql2_result(void); VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement); @@ -22,8 +21,8 @@ typedef struct { mysql_client_wrapper *client_wrapper; /* statement result bind buffers */ MYSQL_BIND *result_buffers; - bool *is_null; - bool *error; + my_bool *is_null; + my_bool *error; unsigned long *length; } mysql2_result_wrapper; diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 3bfba62d5..fdd3699cd 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -115,7 +115,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { // set STMT_ATTR_UPDATE_MAX_LENGTH attr { - bool truth = 1; + my_bool truth = 1; if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) { rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH"); } From a799b0bd7668e53209920461061f5e07470d77df Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 3 Dec 2017 08:14:44 -0800 Subject: [PATCH 593/783] Use a typedef my_bool to improve compatibility across MySQL versions MySQL 8.0 replaces my_bool with C99 bool. Earlier versions of MySQL had a typedef to char. Gem users reported failures on big endian systems when using C99 bool types with older MySQLs due to mismatched behavior. --- ext/mysql2/extconf.rb | 5 +++++ ext/mysql2/mysql2_ext.h | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 8755cb091..05535a449 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -105,12 +105,17 @@ def add_ssl_defines(header) add_ssl_defines(mysql_h) have_struct_member('MYSQL', 'net.vio', mysql_h) have_struct_member('MYSQL', 'net.pvio', mysql_h) + # These constants are actually enums, so they cannot be detected by #ifdef in C code. have_const('MYSQL_ENABLE_CLEARTEXT_PLUGIN', mysql_h) have_const('SERVER_QUERY_NO_GOOD_INDEX_USED', mysql_h) have_const('SERVER_QUERY_NO_INDEX_USED', mysql_h) have_const('SERVER_QUERY_WAS_SLOW', mysql_h) +# my_bool is replaced by C99 bool in MySQL 8.0, but we want +# to retain compatibility with the typedef in earlier MySQLs. +have_type('my_bool', mysql_h) + # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. wishlist = [ diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index f76ea45ec..e1ce0e943 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -28,6 +28,14 @@ void Init_mysql2(void); #define RB_MYSQL_UNUSED #endif +/* MySQL 8.0 replaces my_bool with C99 bool. Earlier versions of MySQL had + * a typedef to char. Gem users reported failures on big endian systems when + * using C99 bool types with older MySQLs due to mismatched behavior. */ +#ifndef HAVE_TYPE_MY_BOOL +#include +typedef bool my_bool; +#endif + #include #include #include From b3fe727b56ba5c8cb5678200f2e99a7119bc41cc Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 Mar 2018 16:50:33 -0700 Subject: [PATCH 594/783] Style nit fix for #943 --- ext/mysql2/client.c | 4 ++-- ext/mysql2/extconf.rb | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 00b50fd46..d12f2c180 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1546,12 +1546,12 @@ void init_mysql2_client() { rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0)); #endif -#ifdef MYSQL_OPTION_MULTI_STATEMENTS_ON +#ifdef HAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_ON rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_ON"), LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_ON)); #endif -#ifdef MYSQL_OPTION_MULTI_STATEMENTS_OFF +#ifdef HAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_OFF rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_OFF"), LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_OFF)); #endif diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 05535a449..7f28cf930 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -111,6 +111,8 @@ def add_ssl_defines(header) have_const('SERVER_QUERY_NO_GOOD_INDEX_USED', mysql_h) have_const('SERVER_QUERY_NO_INDEX_USED', mysql_h) have_const('SERVER_QUERY_WAS_SLOW', mysql_h) +have_const('MYSQL_OPTION_MULTI_STATEMENTS_ON', mysql_h) +have_const('MYSQL_OPTION_MULTI_STATEMENTS_OFF', mysql_h) # my_bool is replaced by C99 bool in MySQL 8.0, but we want # to retain compatibility with the typedef in earlier MySQLs. From 7f9569edeeaabcf51d29dd9489c9b294395e7f45 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 Mar 2018 16:54:14 -0700 Subject: [PATCH 595/783] Bump RuboCop limits for spec file length --- .rubocop_todo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e675d69af..ee0f3d82d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -13,7 +13,7 @@ Metrics/AbcSize: # Offense count: 31 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: - Max: 825 + Max: 850 # Offense count: 1 # Configuration parameters: CountBlocks. From 620a0551e46523a424a68695f4e8f7b91e39fb31 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 Mar 2018 17:32:07 -0700 Subject: [PATCH 596/783] Revert "Layout/IndentHeredoc" This reverts commit 72f50f3ae3b66ea11f1549da97d2147f6366d2de. --- .rubocop.yml | 3 --- .rubocop_todo.yml | 9 +++++++++ support/ruby_enc_to_mysql.rb | 16 ++++++++-------- tasks/compile.rake | 24 ++++++++++++------------ 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a9cd803dc..d41d8d923 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,9 +15,6 @@ Layout/CaseIndentation: Layout/IndentHash: EnforcedStyle: consistent -Layout/IndentHeredoc: - EnforcedStyle: powerpack - Lint/EndAlignment: EnforcedStyleAlignWith: variable diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ee0f3d82d..3da4399a2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,6 +6,15 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent +Layout/IndentHeredoc: + Exclude: + - 'support/ruby_enc_to_mysql.rb' + - 'tasks/compile.rake' + # Offense count: 2 Metrics/AbcSize: Max: 90 diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb index 4749ead75..734d4be90 100644 --- a/support/ruby_enc_to_mysql.rb +++ b/support/ruby_enc_to_mysql.rb @@ -40,14 +40,14 @@ "eucjpms" => "eucJP-ms", } -puts <<-HEADER.strip_indent - %readonly-tables - %enum - %define lookup-function-name mysql2_mysql_enc_name_to_rb - %define hash-function-name mysql2_mysql_enc_name_to_rb_hash - %struct-type - struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; } - %% +puts <<-HEADER +%readonly-tables +%enum +%define lookup-function-name mysql2_mysql_enc_name_to_rb +%define hash-function-name mysql2_mysql_enc_name_to_rb_hash +%struct-type +struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; } +%% HEADER mysql_to_rb.each do |mysql, ruby| diff --git a/tasks/compile.rake b/tasks/compile.rake index a9b7eb8a6..07aa1abea 100644 --- a/tasks/compile.rake +++ b/tasks/compile.rake @@ -33,20 +33,20 @@ Rake::ExtensionTask.new("mysql2", Mysql2::GEMSPEC) do |ext| spec.files << 'lib/mysql2/mysql2.rb' spec.files << 'vendor/libmysql.dll' spec.files << 'vendor/README' - spec.post_install_message = <<-POST_INSTALL_MESSAGE.strip_indent + spec.post_install_message = <<-POST_INSTALL_MESSAGE - ====================================================================================================== +====================================================================================================== - You've installed the binary version of #{spec.name}. - It was built using MySQL Connector/C version #{CONNECTOR_VERSION}. - It's recommended to use the exact same version to avoid potential issues. + You've installed the binary version of #{spec.name}. + It was built using MySQL Connector/C version #{CONNECTOR_VERSION}. + It's recommended to use the exact same version to avoid potential issues. - At the time of building this gem, the necessary DLL files were retrieved from: - #{vendor_mysql_url(http://github.com/spec.platform)} + At the time of building this gem, the necessary DLL files were retrieved from: + #{vendor_mysql_url(http://github.com/spec.platform)} - This gem *includes* vendor/libmysql.dll with redistribution notice in vendor/README. + This gem *includes* vendor/libmysql.dll with redistribution notice in vendor/README. - ====================================================================================================== +====================================================================================================== POST_INSTALL_MESSAGE end @@ -64,9 +64,9 @@ end file 'lib/mysql2/mysql2.rb' do |t| name = Mysql2::GEMSPEC.name File.open(t.name, 'wb') do |f| - f.write <<-END_OF_RUBY.strip_indent - RUBY_VERSION =~ /(\\d+.\\d+)/ - require "#{name}/\#{$1}/#{name}" + f.write <<-END_OF_RUBY +RUBY_VERSION =~ /(\\d+.\\d+)/ +require "#{name}/\#{$1}/#{name}" END_OF_RUBY end end From a0ca592c03c021262a62a1b068e20c5c37e7e804 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 Mar 2018 17:37:23 -0700 Subject: [PATCH 597/783] Use a local scope to avoid leaking the temporary variable for bind_count --- ext/mysql2/statement.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index fdd3699cd..bf5be11a7 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -184,7 +184,7 @@ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length * the buffer is a Ruby string pointer and not our memory to manage. */ #define FREE_BINDS \ - for (i = 0; i < c; i++) { \ + for (i = 0; i < bind_count; i++) { \ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \ xfree(bind_buffers[i].buffer); \ } \ @@ -247,8 +247,7 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { MYSQL_BIND *bind_buffers = NULL; unsigned long *length_buffers = NULL; unsigned long bind_count; - long i; - int c; + unsigned long i; MYSQL_STMT *stmt; MYSQL_RES *metadata; VALUE opts; @@ -263,24 +262,26 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { conn_enc = rb_to_encoding(wrapper->encoding); - // Get count of ordinary arguments, and extract hash opts/keyword arguments - c = rb_scan_args(argc, argv, "*:", NULL, &opts); - stmt = stmt_wrapper->stmt; - bind_count = mysql_stmt_param_count(stmt); - if (c != (long)bind_count) { - rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c); + + // Get count of ordinary arguments, and extract hash opts/keyword arguments + // Use a local scope to avoid leaking the temporary count variable + { + int c = rb_scan_args(argc, argv, "*:", NULL, &opts); + if (c != (long)bind_count) { + rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c); + } } // setup any bind variables in the query if (bind_count > 0) { // Scratch space for string encoding exports, allocate on the stack - params_enc = alloca(sizeof(VALUE) * c); + params_enc = alloca(sizeof(VALUE) * bind_count); bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND)); length_buffers = xcalloc(bind_count, sizeof(unsigned long)); - for (i = 0; i < c; i++) { + for (i = 0; i < bind_count; i++) { bind_buffers[i].buffer = NULL; params_enc[i] = Qnil; From 1ea606c274c789c47997630cf3b72e1b4e820d38 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 Mar 2018 17:37:51 -0700 Subject: [PATCH 598/783] Travis CI add Ruby 2.6 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9a3bb367b..e303be684 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ bundler_args: --without benchmarks development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - gem --version - - gem update --system 2.7.3 + - gem update --system 2.7.6 --quiet - gem update bundler - gem --version - bash .travis_setup.sh @@ -18,6 +18,7 @@ addons: - mysql-client-core-5.6 - mysql-client-5.6 rvm: + - 2.6 - 2.5 - 2.4 - 2.3 From 7879790c3bc6f2c923ca23c23e3f27002e93f35f Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 Mar 2018 17:45:58 -0700 Subject: [PATCH 599/783] Travis CI add MariaDB 10.3 --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index e303be684..a8661f785 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,12 @@ matrix: mariadb: 10.2 hosts: - mysql2gem.example.com + - rvm: 2.4 + env: DB=mariadb10.3 + addons: + mariadb: 10.3 + hosts: + - mysql2gem.example.com - rvm: 2.4 env: DB=mysql55 addons: From eca4d2552f605e14d8d935d67c36d7f83b2bf89b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 Mar 2018 19:22:05 -0700 Subject: [PATCH 600/783] Initialize params_enc variable to resolve a warning --- ext/mysql2/statement.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index bf5be11a7..5fcfd34d7 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -253,7 +253,7 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { VALUE opts; VALUE current; VALUE resultObj; - VALUE *params_enc; + VALUE *params_enc = NULL; int is_streaming; rb_encoding *conn_enc; From 420dbe6fbbcd3d39e2d0ee66a9fc6aa7ccf37c75 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 19 Mar 2018 19:36:52 -0700 Subject: [PATCH 601/783] README note that MariaDB 10.3 and Ruby 2.6 are supported --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 00bf85873..eee59a3f9 100644 --- a/README.md +++ b/README.md @@ -523,14 +523,14 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x, 2.5.x + * Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x, 2.5.x, 2.6.x * Rubinius 2.x and 3.x do work but may fail under some workloads This gem is tested with the following MySQL and MariaDB versions: * MySQL 5.5, 5.6, 5.7, 8.0 * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) - * MariaDB 5.5, 10.0, 10.1, 10.2 + * MariaDB 5.5, 10.0, 10.1, 10.2, 10.3 ### Ruby on Rails / Active Record From bf227ace695b5e1b836d5b788a87dd98f7acb546 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 20 Mar 2018 03:49:28 -0700 Subject: [PATCH 602/783] Bump version to 0.5.0 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index d7a169357..1533dc2ac 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.4.10".freeze + VERSION = "0.5.0".freeze end From ed9aa8441497b3cf3ec06edf1d5a19650605638b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 6 Apr 2018 20:00:08 -0700 Subject: [PATCH 603/783] Prevent command out of sync errors with Prepared Statements (#958) This commit fixes two error scenarios. The first is to avoid GC runs between `mysql_stmt_execute` and `mysql_stmt_store_result`. This fixes a regression caused by #912 due to calling `rb_funcall` between `mysql_stmt_execute` and `mysql_stmt_store_result`. The error in this case is: Commands out of sync; you can't run this command now Thanks to @kamipo for diagnosing the problem and drafting the first PR. The second problem is that in streaming mode, rows are returned to Ruby space one at a time, so garbage collection will naturally occur at any time. By requesting a cursor into the result set, other MySQL commands can be sent on the wire between row fetches. The error in this case is: Row retrieval was canceled by mysql_stmt_close Fixes #956, updates #957. --- ext/mysql2/statement.c | 43 +++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 5fcfd34d7..1462e7860 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -403,6 +403,38 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { } } + // Duplicate the options hash, merge! extra opts, put the copy into the Result object + current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); + (void)RB_GC_GUARD(current); + Check_Type(current, T_HASH); + + // Merge in hash opts/keyword arguments + if (!NIL_P(opts)) { + rb_funcall(current, intern_merge_bang, 1, opts); + } + + is_streaming = (Qtrue == rb_hash_aref(current, sym_stream)); + + // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no + // Ruby API calls are allowed so that GC is not invoked. If the connection is + // in results-streaming-mode for Statement A, and in the middle Statement B + // gets garbage collected, a message will be sent to the server notifying it + // to release Statement B, resulting in the following error: + // Commands out of sync; you can't run this command now + // + // In streaming mode, statement execute must return a cursor because we + // cannot prevent other Statement objects from being garbage collected + // between fetches of each row of the result set. The following error + // occurs if cursor mode is not set: + // Row retrieval was canceled by mysql_stmt_close + + if (is_streaming) { + unsigned long type = CURSOR_TYPE_READ_ONLY; + if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) { + rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY"); + } + } + if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) { FREE_BINDS; rb_raise_mysql2_stmt_error(stmt_wrapper); @@ -421,17 +453,6 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { return Qnil; } - // Duplicate the options hash, merge! extra opts, put the copy into the Result object - current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); - (void)RB_GC_GUARD(current); - Check_Type(current, T_HASH); - - // Merge in hash opts/keyword arguments - if (!NIL_P(opts)) { - rb_funcall(current, intern_merge_bang, 1, opts); - } - - is_streaming = (Qtrue == rb_hash_aref(current, sym_stream)); if (!is_streaming) { // recieve the whole result set from the server if (mysql_stmt_store_result(stmt)) { From e30a3e0e8bac30f730007b5fcbc13d46fe65cc26 Mon Sep 17 00:00:00 2001 From: TOMITA Masahiro Date: Sat, 7 Apr 2018 12:01:12 +0900 Subject: [PATCH 604/783] Fix with --with-mysql-dir (#952) --- ext/mysql2/extconf.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 7f28cf930..190e930e1 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -63,6 +63,7 @@ def add_ssl_defines(header) abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" rpath_dir = lib + have_library('mysqlclient') elsif (mc = (with_config('mysql-config') || Dir[GLOB].first)) # If the user has provided a --with-mysql-config argument, we must respect it or fail. # If the user gave --with-mysql-config with no argument means we should try to find it. From 4410099f45f8e263adfbc259f8bb95f103057fbf Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 7 Apr 2018 06:45:03 -0700 Subject: [PATCH 605/783] Add missing FREE_BINDS to prepared statement streaming error case (#958) --- ext/mysql2/statement.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 1462e7860..22e22ecfd 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -431,6 +431,7 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { if (is_streaming) { unsigned long type = CURSOR_TYPE_READ_ONLY; if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) { + FREE_BINDS; rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY"); } } From 40c0d6dae3efee740db60c6824ecbdffb012c5f6 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 7 Apr 2018 21:45:55 -0700 Subject: [PATCH 606/783] README be sure to read about the known limitations of prepared statements --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index eee59a3f9..a48ac0377 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,9 @@ Pass your arguments to the execute method in the same number and order as the question marks in the statement. Query options can be passed as keyword arguments to the execute method. +Be sure to read about the known limitations of prepared statements at +https://dev.mysql.com/doc/refman/5.6/en/c-api-prepared-statement-problems.html + ``` ruby statement = @client.prepare("SELECT * FROM users WHERE login_count = ?") result1 = statement.execute(1) From 0496233c7309a5ab1d632b6c16b626e40f7173e3 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 7 Apr 2018 21:48:40 -0700 Subject: [PATCH 607/783] README mysql2 0.5.x works with Rails 5.0.7, 5.1.6, and higher --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a48ac0377..479e790e1 100644 --- a/README.md +++ b/README.md @@ -537,6 +537,7 @@ This gem is tested with the following MySQL and MariaDB versions: ### Ruby on Rails / Active Record + * mysql2 0.5.x works with Rails / Active Record 5.0.7, 5.1.6, and higher. * mysql2 0.4.x works with Rails / Active Record 4.2.5 - 5.0 and higher. * mysql2 0.3.x works with Rails / Active Record 3.1, 3.2, 4.x, 5.0. * mysql2 0.2.x works with Rails / Active Record 2.3 - 3.0. From 36ab5886a0b4dfb44438daea03e949e9dc300238 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 10 Apr 2018 19:26:26 -0700 Subject: [PATCH 608/783] Use the prepared statement performance schema if available (#960) Possibly resolves #937 --- spec/mysql2/statement_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 6d617eab0..dbc185e6b 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -6,6 +6,10 @@ end def stmt_count + # Use the performance schema in MySQL 5.7 and above + @client.query("SELECT COUNT(1) AS count FROM performance_schema.prepared_statements_instances").first['count'].to_i + rescue Mysql2::Error + # Fall back to the global prepapred statement counter @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i end From 857394c91ccedef0966b66fd3562d3779d97e373 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 10 Apr 2018 19:29:14 -0700 Subject: [PATCH 609/783] Bump version to 0.5.1 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 1533dc2ac..474616c3f 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.5.0".freeze + VERSION = "0.5.1".freeze end From 4a20d58a5a5b8096da3a220a3994853ca26c79c7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 26 Apr 2018 01:11:58 -0700 Subject: [PATCH 610/783] Add default-libmysqlclient-dev to the likely packages list Resolves #964 --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 479e790e1..bd7b5dc64 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,11 @@ To see line numbers in backtraces, declare these environment variables ### Linux and other Unixes -You may need to install a package such as `libmysqlclient-dev` or `mysql-devel`; -refer to your distribution's package guide to find the particular package. -The most common issue we see is a user who has the library file `libmysqlclient.so` but is -missing the header file `mysql.h` -- double check that you have the _-dev_ packages installed. +You may need to install a package such as `libmysqlclient-dev`, `mysql-devel`, +or `default-libmysqlclient-dev`; refer to your distribution's package guide to +find the particular package. The most common issue we see is a user who has +the library file `libmysqlclient.so` but is missing the header file `mysql.h` +-- double check that you have the _-dev_ packages installed. ### Mac OS X From 46d6851d8b82862965e3fd01aaa27996b50daa58 Mon Sep 17 00:00:00 2001 From: Ethan Mahintorabi Date: Fri, 18 May 2018 14:21:48 -0700 Subject: [PATCH 611/783] Updating the mysql2_mysql_enc_to_rb conversion table to 8.0 List (#976) Also add a check to make sure we do not go out of bounds. --- .travis.yml | 1 + ext/mysql2/mysql_enc_to_ruby.h | 10 ++++++++++ ext/mysql2/result.c | 3 ++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a8661f785..396e00929 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ addons: hosts: - mysql2gem.example.com apt: + update: true packages: - mysql-server-5.6 - mysql-client-core-5.6 diff --git a/ext/mysql2/mysql_enc_to_ruby.h b/ext/mysql2/mysql_enc_to_ruby.h index df167b2a6..985fdfeeb 100644 --- a/ext/mysql2/mysql_enc_to_ruby.h +++ b/ext/mysql2/mysql_enc_to_ruby.h @@ -245,5 +245,15 @@ static const char *mysql2_mysql_enc_to_rb[] = { "UTF-8", "UTF-8", "UTF-8", + "UTF-8", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, "UTF-8" }; + +#define CHARSETNR_SIZE (sizeof(mysql2_mysql_enc_to_rb)/sizeof(mysql2_mysql_enc_to_rb[0])) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index a4957ee42..0ab38aabe 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -179,7 +179,8 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e const char *enc_name; int enc_index; - enc_name = mysql2_mysql_enc_to_rb[field.charsetnr-1]; + enc_name = (field.charsetnr-1 < CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL; + if (enc_name != NULL) { /* use the field encoding we were able to match */ enc_index = rb_enc_find_index(enc_name); From 95bfea3d430187fda31caea44ded0a8ab606525d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 19 May 2018 09:42:02 -0700 Subject: [PATCH 612/783] Travis apt-get update for MySQL 5.5 install --- .travis.yml | 1 - .travis_mysql55.sh | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 396e00929..a8661f785 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ addons: hosts: - mysql2gem.example.com apt: - update: true packages: - mysql-server-5.6 - mysql-client-core-5.6 diff --git a/.travis_mysql55.sh b/.travis_mysql55.sh index 771250dc0..ffc2f9571 100644 --- a/.travis_mysql55.sh +++ b/.travis_mysql55.sh @@ -5,4 +5,5 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql +apt-get update -qq apt-get install -qq mysql-server-5.5 mysql-client-core-5.5 mysql-client-5.5 libmysqlclient-dev From 3b9a26708fa86aba23763626331eb317ed457cc1 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 4 Jul 2018 08:14:13 -0700 Subject: [PATCH 613/783] Bump version to 0.5.2 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 474616c3f..152a6227c 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.5.1".freeze + VERSION = "0.5.2".freeze end From bc73887e67a91bc33c9e69bda15fc079657bccc3 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Tue, 31 Jul 2018 01:50:54 +0200 Subject: [PATCH 614/783] Add CentOS on Travis CI. (#989) --- .rubocop_todo.yml | 2 +- .travis.yml | 6 ++++++ .travis_Dockerfile_centos | 23 +++++++++++++++++++++++ .travis_centos.sh | 13 +++++++++++++ .travis_setup_centos.sh | 16 ++++++++++++++++ spec/mysql2/client_spec.rb | 23 +++++++++++++++-------- spec/mysql2/result_spec.rb | 2 +- 7 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 .travis_Dockerfile_centos create mode 100644 .travis_centos.sh create mode 100644 .travis_setup_centos.sh diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3da4399a2..4117fa862 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -22,7 +22,7 @@ Metrics/AbcSize: # Offense count: 31 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: - Max: 850 + Max: 860 # Offense count: 1 # Configuration parameters: CountBlocks. diff --git a/.travis.yml b/.travis.yml index a8661f785..c8f3503b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ sudo: required dist: trusty +services: docker language: ruby bundler_args: --without benchmarks development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. @@ -73,6 +74,11 @@ matrix: addons: hosts: - mysql2gem.example.com + - rvm: 2.4 + env: DOCKER=centos + before_install: true + install: docker build -t mysql2 -f .travis_Dockerfile_centos . + script: docker run --add-host=mysql2gem.example.com:127.0.0.1 -t mysql2 fast_finish: true allow_failures: - rvm: ruby-head diff --git a/.travis_Dockerfile_centos b/.travis_Dockerfile_centos new file mode 100644 index 000000000..b6268f679 --- /dev/null +++ b/.travis_Dockerfile_centos @@ -0,0 +1,23 @@ +FROM centos:7 + +WORKDIR /build +COPY . . + +RUN yum -y update +RUN yum -y install epel-release +# The options are to install faster. +RUN yum -y install \ + --setopt=deltarpm=0 \ + --setopt=install_weak_deps=false \ + --setopt=tsflags=nodocs \ + mariadb-server \ + mariadb-devel \ + ruby-devel \ + git \ + gcc \ + gcc-c++ \ + make +RUN gem update --system > /dev/null +RUN gem install bundler + +CMD sh .travis_centos.sh diff --git a/.travis_centos.sh b/.travis_centos.sh new file mode 100644 index 000000000..09e4c4a5c --- /dev/null +++ b/.travis_centos.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -eux + +# Start mysqld service. +sh .travis_setup_centos.sh + +bundle install --path vendor/bundle --without benchmarks development + +# USER environment value is not set as a default in the container environment. +export USER=root + +bundle exec rake diff --git a/.travis_setup_centos.sh b/.travis_setup_centos.sh new file mode 100644 index 000000000..631fbb9ec --- /dev/null +++ b/.travis_setup_centos.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -eux + +MYSQL_TEST_LOG="$(pwd)/mysql.log" + +mysql_install_db \ + --log-error="${MYSQL_TEST_LOG}" +/usr/libexec/mysqld \ + --user=root \ + --log-error="${MYSQL_TEST_LOG}" \ + --ssl & +sleep 3 +cat ${MYSQL_TEST_LOG} + +mysql -u root -e 'CREATE DATABASE IF NOT EXISTS test' diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 00d9c17db..e9f09ec0a 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -135,15 +135,22 @@ def connect(*args) # You may need to adjust the lines below to match your SSL certificate paths ssl_client = nil + option_overrides = { + 'host' => 'mysql2gem.example.com', # must match the certificates + :sslkey => '/etc/mysql/client-key.pem', + :sslcert => '/etc/mysql/client-cert.pem', + :sslca => '/etc/mysql/ca-cert.pem', + :sslcipher => 'DHE-RSA-AES256-SHA', + :sslverify => true, + } + %i[sslkey sslcert sslca].each do |item| + unless File.exist?(option_overrides[item]) + pending("DON'T WORRY, THIS TEST PASSES - but #{option_overrides[item]} does not exist.") + break + end + end expect do - ssl_client = new_client( - 'host' => 'mysql2gem.example.com', # must match the certificates - :sslkey => '/etc/mysql/client-key.pem', - :sslcert => '/etc/mysql/client-cert.pem', - :sslca => '/etc/mysql/ca-cert.pem', - :sslcipher => 'DHE-RSA-AES256-SHA', - :sslverify => true, - ) + ssl_client = new_client(option_overrides) end.not_to raise_error results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }] diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 89744fc2f..a70b38ef0 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -164,7 +164,7 @@ expect do res.each_with_index do |_, i| # Exhaust the first result packet then trigger a timeout - sleep 2 if i > 0 && i % 1000 == 0 + sleep 4 if i > 0 && i % 1000 == 0 end end.to raise_error(Mysql2::Error, /Lost connection/) end From c578718cb9adc49117358162256420d977381060 Mon Sep 17 00:00:00 2001 From: Giovanni Cappellotto Date: Fri, 14 Sep 2018 14:54:47 -0400 Subject: [PATCH 615/783] Fix code snippet (#1002) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd7b5dc64..e006f1d7d 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ end How about with symbolized keys? ``` ruby -client.query("SELECT * FROM users WHERE group='githubbers'", :symbolize_keys => true) do |row| +client.query("SELECT * FROM users WHERE group='githubbers'", :symbolize_keys => true).each do |row| # do something with row, it's ready to rock end ``` From 98304cbbc0c9a964c833474a06f55c433ec26432 Mon Sep 17 00:00:00 2001 From: "Kanayo Software, Inc" Date: Sun, 6 Jan 2019 15:28:32 -0400 Subject: [PATCH 616/783] Expose windows client authentication (#1018) --- README.md | 1 + ext/mysql2/client.c | 10 ++++++++++ lib/mysql2/client.rb | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e006f1d7d..3d902c1d6 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,7 @@ Mysql2::Client.new( :ssl_mode = :disabled / :preferred / :required / :verify_ca / :verify_identity, :default_file = '/path/to/my.cfg', :default_group = 'my.cfg section', + :default_auth = 'authentication_windows_client' :init_command => sql ) ``` diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index d12f2c180..c4b6c44cf 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -903,6 +903,11 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = charval; break; + case MYSQL_DEFAULT_AUTH: + charval = (const char *)StringValueCStr(value); + retval = charval; + break; + #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN case MYSQL_ENABLE_CLEARTEXT_PLUGIN: boolval = (value == Qfalse ? 0 : 1); @@ -1336,6 +1341,10 @@ static VALUE set_init_command(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_INIT_COMMAND, value); } +static VALUE set_default_auth(VALUE self, VALUE value) { + return _mysql_client_options(self, MYSQL_DEFAULT_AUTH, value); +} + static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) { #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value); @@ -1437,6 +1446,7 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1); rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1); rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1); + rb_define_private_method(cMysql2Client, "default_auth=", set_default_auth, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1); rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 7e28768d5..bbe6777a1 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -31,7 +31,7 @@ def initialize(opts = {}) opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) # TODO: stricter validation rather than silent massaging - %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin].each do |key| + %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth].each do |key| next unless opts.key?(key) case key when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin From 624409d9e493d009653c75f5e550997fa0a93baf Mon Sep 17 00:00:00 2001 From: qudongfang Date: Sun, 21 Apr 2019 04:16:47 +0800 Subject: [PATCH 617/783] Debian 9 released with MariaDB as the only MySQL variant since 2017 (#1038) --- README.md | 54 +++++++++++++++++++++++-------------------- ext/mysql2/extconf.rb | 2 +- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 3d902c1d6..04b5dad42 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,18 @@ The API consists of three classes: `Mysql2::Statement` - returned from issuing a #prepare on the connection. Execute the statement to get a Result. ## Installing + ### General Instructions + ``` sh gem install mysql2 ``` This gem links against MySQL's `libmysqlclient` library or `Connector/C` library, and compatible alternatives such as MariaDB. -You may need to install a package such as `libmysqlclient-dev`, `mysql-devel`, -or other appropriate package for your system. See below for system-specific -instructions. +You may need to install a package such as `libmariadb-dev`, `libmysqlclient-dev`, +`mysql-devel`, or other appropriate package for your system. See below for +system-specific instructions. By default, the mysql2 gem will try to find a copy of MySQL in this order: @@ -74,8 +76,8 @@ To see line numbers in backtraces, declare these environment variables ### Linux and other Unixes -You may need to install a package such as `libmysqlclient-dev`, `mysql-devel`, -or `default-libmysqlclient-dev`; refer to your distribution's package guide to +You may need to install a package such as `libmariadb-dev`, `libmysqlclient-dev`, +`mysql-devel`, or `default-libmysqlclient-dev`; refer to your distribution's package guide to find the particular package. The most common issue we see is a user who has the library file `libmysqlclient.so` but is missing the header file `mysql.h` -- double check that you have the _-dev_ packages installed. @@ -90,6 +92,7 @@ If you have not done so already, you will need to install the XCode select tools `xcode-select --install`. ### Windows + Make sure that you have Ruby and the DevKit compilers installed. We recommend the [Ruby Installer](http://rubyinstaller.org) distribution. @@ -180,7 +183,7 @@ question marks in the statement. Query options can be passed as keyword argument to the execute method. Be sure to read about the known limitations of prepared statements at -https://dev.mysql.com/doc/refman/5.6/en/c-api-prepared-statement-problems.html +[https://dev.mysql.com/doc/refman/5.6/en/c-api-prepared-statement-problems.html](https://dev.mysql.com/doc/refman/5.6/en/c-api-prepared-statement-problems.html) ``` ruby statement = @client.prepare("SELECT * FROM users WHERE login_count = ?") @@ -348,7 +351,8 @@ end ``` Yields: -``` + +```ruby {"1"=>1} {"2"=>2} next_result: Unknown column 'A' in 'field list' (Mysql2::Error) @@ -507,7 +511,7 @@ There are a few things that need to be kept in mind while using streaming: * `:cache_rows` is ignored currently. (if you want to use `:cache_rows` you probably don't want to be using `:stream`) * You must fetch all rows in the result set of your query before you can make new queries. (i.e. with `Mysql2::Result#each`) -Read more about the consequences of using `mysql_use_result` (what streaming is implemented with) here: http://dev.mysql.com/doc/refman/5.0/en/mysql-use-result.html. +Read more about the consequences of using `mysql_use_result` (what streaming is implemented with) here: [http://dev.mysql.com/doc/refman/5.0/en/mysql-use-result.html](http://dev.mysql.com/doc/refman/5.0/en/mysql-use-result.html). ### Lazy Everything @@ -528,21 +532,21 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: - * Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x, 2.5.x, 2.6.x - * Rubinius 2.x and 3.x do work but may fail under some workloads +* Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x, 2.5.x, 2.6.x +* Rubinius 2.x and 3.x do work but may fail under some workloads This gem is tested with the following MySQL and MariaDB versions: - * MySQL 5.5, 5.6, 5.7, 8.0 - * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) - * MariaDB 5.5, 10.0, 10.1, 10.2, 10.3 +* MySQL 5.5, 5.6, 5.7, 8.0 +* MySQL Connector/C 6.0 and 6.1 (primarily on Windows) +* MariaDB 5.5, 10.0, 10.1, 10.2, 10.3 ### Ruby on Rails / Active Record - * mysql2 0.5.x works with Rails / Active Record 5.0.7, 5.1.6, and higher. - * mysql2 0.4.x works with Rails / Active Record 4.2.5 - 5.0 and higher. - * mysql2 0.3.x works with Rails / Active Record 3.1, 3.2, 4.x, 5.0. - * mysql2 0.2.x works with Rails / Active Record 2.3 - 3.0. +* mysql2 0.5.x works with Rails / Active Record 5.0.7, 5.1.6, and higher. +* mysql2 0.4.x works with Rails / Active Record 4.2.5 - 5.0 and higher. +* mysql2 0.3.x works with Rails / Active Record 3.1, 3.2, 4.x, 5.0. +* mysql2 0.2.x works with Rails / Active Record 2.3 - 3.0. ### Asynchronous Active Record @@ -625,11 +629,11 @@ though. ## Special Thanks * Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude -* Yury Korolev (http://github.com/yury) - for TONS of help testing the Active Record adapter -* Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness -* Mike Perham (http://github.com/mperham) - Async Active Record adapter (uses Fibers and EventMachine) -* Aaron Stone (http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support -* Kouhei Ueno (https://github.com/nyaxt) - for the original work on Prepared Statements way back in 2012 -* John Cant (http://github.com/johncant) - polishing and updating Prepared Statements support -* Justin Case (http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged -* Tamir Duberstein (http://github.com/tamird) - for help with timeouts and all around updates and cleanups +* [Yury Korolev](http://github.com/yury) - for TONS of help testing the Active Record adapter +* [Aaron Patterson](http://github.com/tenderlove) - tons of contributions, suggestions and general badassness +* [Mike Perham](http://github.com/mperham) - Async Active Record adapter (uses Fibers and EventMachine) +* [Aaron Stone](http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support +* [Kouhei Ueno](https://github.com/nyaxt) - for the original work on Prepared Statements way back in 2012 +* [John Cant](http://github.com/johncant) - polishing and updating Prepared Statements support +* [Justin Case](http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged +* [Tamir Duberstein](http://github.com/tamird) - for help with timeouts and all around updates and cleanups diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 190e930e1..1d0928604 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -7,7 +7,7 @@ def asplode(lib) elsif RUBY_PLATFORM =~ /darwin/ abort "-----\n#{lib} is missing. You may need to 'brew install mysql' or 'port install mysql', and try again.\n-----" else - abort "-----\n#{lib} is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again.\n-----" + abort "-----\n#{lib} is missing. You may need to 'sudo apt-get install libmariadb-dev', 'sudo apt-get install libmysqlclient-dev' or 'sudo yum install mysql-devel', and try again.\n-----" end end From 2e5e334e1ca6fcb70aaa3450de9cb3af54202d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sat, 27 Apr 2019 01:26:10 +0200 Subject: [PATCH 618/783] Remove CHARSETNR_SIZE from generated headers and prefix it with MYSQL2_ to match existing definitions. --- ext/mysql2/mysql_enc_to_ruby.h | 2 -- ext/mysql2/result.c | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/mysql_enc_to_ruby.h b/ext/mysql2/mysql_enc_to_ruby.h index 985fdfeeb..37547aea5 100644 --- a/ext/mysql2/mysql_enc_to_ruby.h +++ b/ext/mysql2/mysql_enc_to_ruby.h @@ -255,5 +255,3 @@ static const char *mysql2_mysql_enc_to_rb[] = { NULL, "UTF-8" }; - -#define CHARSETNR_SIZE (sizeof(mysql2_mysql_enc_to_rb)/sizeof(mysql2_mysql_enc_to_rb[0])) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 0ab38aabe..74851892a 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -1,6 +1,7 @@ #include #include "mysql_enc_to_ruby.h" +#define MYSQL2_CHARSETNR_SIZE (sizeof(mysql2_mysql_enc_to_rb)/sizeof(mysql2_mysql_enc_to_rb[0])) static rb_encoding *binaryEncoding; @@ -179,7 +180,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e const char *enc_name; int enc_index; - enc_name = (field.charsetnr-1 < CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL; + enc_name = (field.charsetnr-1 < MYSQL2_CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL; if (enc_name != NULL) { /* use the field encoding we were able to match */ From 4d0942a115489b520b3b23ee901f66a1416c0ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sat, 27 Apr 2019 01:51:40 +0200 Subject: [PATCH 619/783] Warn about missing MySQL encoding mappings and fix a bug mapping them to "" instead of NULL. --- support/mysql_enc_to_ruby.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index 602c5447a..29ef0ea86 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -52,7 +52,10 @@ collations.each do |collation| mysql_col_idx = collation[2].to_i - rb_enc = mysql_to_rb[collation[1]] + rb_enc = mysql_to_rb.fetch(collation[1]) do |mysql_enc| + $stderr.puts "WARNING: Missing mapping for collation \"#{collation[0]}\" with encoding \"#{mysql_enc}\" and id #{mysql_col_idx}, assuming NULL" + "NULL" + end encodings[mysql_col_idx - 1] = [mysql_col_idx, rb_enc] end From 58b117032ccd95ee8bc65fd2192a0dff38eb062a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sat, 27 Apr 2019 02:21:59 +0200 Subject: [PATCH 620/783] Fix empty string encoding mappings to NULL --- ext/mysql2/mysql_enc_to_ruby.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/mysql_enc_to_ruby.h b/ext/mysql2/mysql_enc_to_ruby.h index 37547aea5..425d5dcb0 100644 --- a/ext/mysql2/mysql_enc_to_ruby.h +++ b/ext/mysql2/mysql_enc_to_ruby.h @@ -54,13 +54,13 @@ static const char *mysql2_mysql_enc_to_rb[] = { "macRoman", "UTF-16", "UTF-16", - "", + NULL, "Windows-1256", "Windows-1257", "Windows-1257", "UTF-32", "UTF-32", - "", + NULL, "ASCII-8BIT", NULL, "US-ASCII", From a11ac3b267032d4e57814f1cf8842f029208a1e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Fri, 26 Apr 2019 22:06:18 +0200 Subject: [PATCH 621/783] Add encoding mappings for utf16le and gb18030 --- support/mysql_enc_to_ruby.rb | 2 ++ support/ruby_enc_to_mysql.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index 29ef0ea86..33c878885 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -43,6 +43,8 @@ "geostd8" => "NULL", "cp932" => "Windows-31J", "eucjpms" => "eucJP-ms", + "utf16le" => "UTF-16LE", + "gb18030" => "GB18030", } client = Mysql2::Client.new(username: user, password: pass, host: host, port: port.to_i) diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb index 734d4be90..72b68eb25 100644 --- a/support/ruby_enc_to_mysql.rb +++ b/support/ruby_enc_to_mysql.rb @@ -38,6 +38,8 @@ "geostd8" => nil, "cp932" => "Windows-31J", "eucjpms" => "eucJP-ms", + "utf16le" => "UTF-16LE", + "gb18030" => "GB18030", } puts <<-HEADER From 4158af5153d874376946cf058b774044f26cd621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sat, 27 Apr 2019 02:25:42 +0200 Subject: [PATCH 622/783] Update encoding tables from MySQL 8.0.16 --- ext/mysql2/mysql_enc_name_to_ruby.h | 116 ++++++++++++++-------------- ext/mysql2/mysql_enc_to_ruby.h | 59 +++++++++++++- 2 files changed, 116 insertions(+), 59 deletions(-) diff --git a/ext/mysql2/mysql_enc_name_to_ruby.h b/ext/mysql2/mysql_enc_name_to_ruby.h index 9542e228a..b50146d36 100644 --- a/ext/mysql2/mysql_enc_name_to_ruby.h +++ b/ext/mysql2/mysql_enc_name_to_ruby.h @@ -30,7 +30,7 @@ error "gperf generated tables don't work with this execution character set. Plea #endif struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; }; -/* maximum key range = 66, duplicates = 0 */ +/* maximum key range = 71, duplicates = 0 */ #ifdef __GNUC__ __inline @@ -46,32 +46,32 @@ mysql2_mysql_enc_name_to_rb_hash (str, len) { static const unsigned char asso_values[] = { - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 40, 5, - 0, 69, 0, 40, 25, 20, 10, 55, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 35, 5, 0, - 10, 0, 20, 0, 5, 5, 69, 0, 10, 15, - 0, 0, 69, 69, 25, 5, 5, 0, 69, 30, - 69, 0, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69 + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 15, 5, + 0, 74, 5, 25, 40, 10, 20, 50, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 40, 5, 0, + 15, 10, 0, 0, 0, 5, 74, 0, 25, 5, + 0, 5, 74, 74, 20, 5, 5, 0, 74, 45, + 74, 0, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74 }; return len + asso_values[(unsigned char)str[2]] + asso_values[(unsigned char)str[0]] + asso_values[(unsigned char)str[len - 1]]; } @@ -89,11 +89,11 @@ mysql2_mysql_enc_name_to_rb (str, len) { enum { - TOTAL_KEYWORDS = 39, + TOTAL_KEYWORDS = 41, MIN_WORD_LENGTH = 3, MAX_WORD_LENGTH = 8, MIN_HASH_VALUE = 3, - MAX_HASH_VALUE = 68 + MAX_HASH_VALUE = 73 }; static const struct mysql2_mysql_enc_name_to_rb_map wordlist[] = @@ -101,54 +101,58 @@ mysql2_mysql_enc_name_to_rb (str, len) {""}, {""}, {""}, {"gbk", "GBK"}, {""}, - {"greek", "ISO-8859-7"}, + {"utf32", "UTF-32"}, {"gb2312", "GB2312"}, {"keybcs2", NULL}, {""}, {"ucs2", "UTF-16BE"}, {"koi8u", "KOI8-R"}, {"binary", "ASCII-8BIT"}, - {"eucjpms", "eucJP-ms"}, - {""}, + {"utf8mb4", "UTF-8"}, + {"macroman", "macRoman"}, {"ujis", "eucJP-ms"}, - {"cp852", "CP852"}, + {"greek", "ISO-8859-7"}, {"cp1251", "Windows-1251"}, - {"geostd8", NULL}, + {"utf16le", "UTF-16LE"}, {""}, {"sjis", "Shift_JIS"}, {"macce", "macCentEuro"}, - {"latin2", "ISO-8859-2"}, + {"cp1257", "Windows-1257"}, + {"eucjpms", "eucJP-ms"}, + {""}, + {"utf8", "UTF-8"}, + {"cp852", "CP852"}, + {"cp1250", "Windows-1250"}, + {"gb18030", "GB18030"}, {""}, - {"macroman", "macRoman"}, - {"dec8", NULL}, - {"utf32", "UTF-32"}, - {"latin1", "ISO-8859-1"}, - {"utf8mb4", "UTF-8"}, - {"hp8", NULL}, {"swe7", NULL}, + {"koi8r", "KOI8-R"}, + {"tis620", "TIS-620"}, + {"geostd8", NULL}, + {""}, + {"big5", "Big5"}, {"euckr", "EUC-KR"}, - {"cp1257", "Windows-1257"}, + {"latin2", "ISO-8859-2"}, {""}, {""}, - {"utf8", "UTF-8"}, - {"koi8r", "KOI8-R"}, - {"cp1256", "Windows-1256"}, - {""}, {""}, {""}, - {"cp866", "IBM866"}, + {"dec8", NULL}, + {"cp850", "CP850"}, + {"latin1", "ISO-8859-1"}, + {""}, + {"hp8", NULL}, + {""}, + {"utf16", "UTF-16"}, {"latin7", "ISO-8859-13"}, {""}, {""}, {""}, {"ascii", "US-ASCII"}, - {"hebrew", "ISO-8859-8"}, - {""}, {""}, - {"big5", "Big5"}, - {"utf16", "UTF-16"}, - {"cp1250", "Windows-1250"}, - {""}, {""}, {""}, - {"cp850", "CP850"}, - {"tis620", "TIS-620"}, + {"cp1256", "Windows-1256"}, {""}, {""}, {""}, {"cp932", "Windows-31J"}, + {"hebrew", "ISO-8859-8"}, + {""}, {""}, {""}, {""}, {"latin5", "ISO-8859-9"}, - {""}, {""}, {""}, {""}, {""}, {""}, + {""}, {""}, {""}, + {"cp866", "IBM866"}, + {""}, {""}, {""}, {""}, {""}, {""}, {""}, {"armscii8", NULL} }; diff --git a/ext/mysql2/mysql_enc_to_ruby.h b/ext/mysql2/mysql_enc_to_ruby.h index 425d5dcb0..4c36e4a42 100644 --- a/ext/mysql2/mysql_enc_to_ruby.h +++ b/ext/mysql2/mysql_enc_to_ruby.h @@ -54,13 +54,13 @@ static const char *mysql2_mysql_enc_to_rb[] = { "macRoman", "UTF-16", "UTF-16", - NULL, + "UTF-16LE", "Windows-1256", "Windows-1257", "Windows-1257", "UTF-32", "UTF-32", - NULL, + "UTF-16LE", "ASCII-8BIT", NULL, "US-ASCII", @@ -74,7 +74,7 @@ static const char *mysql2_mysql_enc_to_rb[] = { NULL, "KOI8-R", "KOI8-R", - NULL, + "UTF-8", "ISO-8859-2", "ISO-8859-9", "ISO-8859-13", @@ -246,12 +246,65 @@ static const char *mysql2_mysql_enc_to_rb[] = { "UTF-8", "UTF-8", "UTF-8", + "GB18030", + "GB18030", + "GB18030", NULL, NULL, NULL, NULL, + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + NULL, + "UTF-8", + "UTF-8", + "UTF-8", + NULL, + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + NULL, + "UTF-8", + "UTF-8", + "UTF-8", NULL, + "UTF-8", NULL, NULL, + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", "UTF-8" }; From b5b18a1a26a810ec6e10e9d32d029a3ea00175ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sat, 27 Apr 2019 02:41:18 +0200 Subject: [PATCH 623/783] Update expired MySQL APT signing key --- support/5072E1F5.asc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/support/5072E1F5.asc b/support/5072E1F5.asc index 6c9fd8dec..281e134fb 100644 --- a/support/5072E1F5.asc +++ b/support/5072E1F5.asc @@ -1,5 +1,5 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.5 (GNU/Linux) +Version: GnuPG v1 mQGiBD4+owwRBAC14GIfUfCyEDSIePvEW3SAFUdJBtoQHH/nJKZyQT7h9bPlUWC3 RODjQReyCITRrdwyrKUGku2FmeVGwn2u2WmDMNABLnpprWPkBdCk96+OmSLN9brZ @@ -11,9 +11,9 @@ kYpXBACmWpP8NJTkamEnPCia2ZoOHODANwpUkP43I7jsDmgtobZX9qnrAXw+uNDI QJEXM6FSbi0LLtZciNlYsafwAPEOMDKpMqAK6IyisNtPvaLd8lH0bPAnWqcyefep rv0sxxqUEMcM3o7wwgfN83POkDasDbs3pjwPhxvhz6//62zQJ7Q2TXlTUUwgUmVs ZWFzZSBFbmdpbmVlcmluZyA8bXlzcWwtYnVpbGRAb3NzLm9yYWNsZS5jb20+iGwE -ExECACwCGyMCHgECF4ACGQEGCwkIBwMCBhUKCQgCAwUWAgMBAAUCWKcFIAUJHirJ -FAAKCRCMcY07UHLh9VcFAJ46pUyVd8BZ2r5CppMC1tmyQ3ceRgCfVPwuVsiS0VER -5WUqtAQDt+DoetCIaQQTEQIAKQIbIwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAhkB +ExECACwCGyMCHgECF4ACGQEGCwkIBwMCBhUKCQgCAwUWAgMBAAUCXEBY+wUJI87e +5AAKCRCMcY07UHLh9RZPAJ9uvm0zlzfCN+DHxHVaoFLFjdVYTQCfborsC9tmEZYa +whhogjeBkZkorbyIaQQTEQIAKQIbIwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAhkB BQJTAdRmBQkaZsvLAAoJEIxxjTtQcuH1X4MAoKNLWAbCBUj96637kv6Xa/fJuX5m AJwPtmgDfjUe2iuhXdTrFEPT19SB6ohmBBMRAgAmAhsjBgsJCAcDAgQVAggDBBYC AwECHgECF4AFAk53PioFCRP7AhUACgkQjHGNO1By4fUmzACeJdfqgc9gWTUhgmcM @@ -428,5 +428,5 @@ GoaU9u41oyZTIiXPiFidJoIZCh7fdurP8pn3X+R5HUNXMr7M+ba8lSNxce/F3kmH 0L7rsKqdh9d/aVxhJINJ+inVDnrXWVoXu9GBjT8Nco1iU9SIVAQYEQIADAUCTnc9 7QUJE/sBuAASB2VHUEcAAQEJEIxxjTtQcuH1FJsAmwWK9vmwRJ/y9gTnJ8PWf0BV roUTAKClYAhZuX2nUNwH4vlEJQHDqYa5yQ== -=HfUN +=ghXk -----END PGP PUBLIC KEY BLOCK----- From ce91d5c7c3020af0c7f1ea65e425d87b0d05e8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sat, 27 Apr 2019 03:18:54 +0200 Subject: [PATCH 624/783] Autodetect ubuntu dist in mysql install scripts --- .travis_mysql57.sh | 4 ++-- .travis_mysql80.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index ab0df12c4..69ba5c67e 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -5,8 +5,8 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key add - < support/5072E1F5.asc -add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ trusty mysql-5.7' +apt-key add support/5072E1F5.asc +add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-5.7' apt-get update -qq apt-get install -qq mysql-server libmysqlclient-dev diff --git a/.travis_mysql80.sh b/.travis_mysql80.sh index 70b6c8f86..c658d3c2a 100644 --- a/.travis_mysql80.sh +++ b/.travis_mysql80.sh @@ -5,8 +5,8 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key add - < support/5072E1F5.asc -add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ trusty mysql-8.0' +apt-key add support/5072E1F5.asc +add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-8.0' apt-get update -qq apt-get install -qq mysql-server libmysqlclient-dev From 1228a503b4dad57e0745bf0e358cd7aff128c889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sat, 27 Apr 2019 03:19:39 +0200 Subject: [PATCH 625/783] Fix MySQL 8.0 CI installing MySQL 5.5 MySQL 8.0 Community APT packages are only available from 16.04, so we need to use "dist: xenial" on Travis CI. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c8f3503b9..f9b098ca6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,6 +65,7 @@ matrix: - mysql2gem.example.com - rvm: 2.4 env: DB=mysql80 + dist: xenial addons: hosts: - mysql2gem.example.com From d0cc2d32480799d1799cd7743e727acbe39ec356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sat, 27 Apr 2019 03:39:33 +0200 Subject: [PATCH 626/783] Fix incompatible gem versions in DOCKER=centos CI * gem update --system tries to use rubygems-update 3.0 which requires ruby >= 2.3.0, so use ~> 2.7 * latest bundler 2.x also requires ruby >= 2.3.0, so use ~> 1.17 --- .travis_Dockerfile_centos | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis_Dockerfile_centos b/.travis_Dockerfile_centos index b6268f679..50a23e98c 100644 --- a/.travis_Dockerfile_centos +++ b/.travis_Dockerfile_centos @@ -17,7 +17,7 @@ RUN yum -y install \ gcc \ gcc-c++ \ make -RUN gem update --system > /dev/null -RUN gem install bundler +RUN gem install --no-document "rubygems-update:~>2.7" && update_rubygems +RUN gem install --no-document "bundler:~>1.17" CMD sh .travis_centos.sh From c8c346db72b5505740065d9de79b4a52081d5a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20B=C3=BCnemann?= Date: Sat, 27 Apr 2019 22:35:56 +0200 Subject: [PATCH 627/783] Fix fragile specs (#1041) * Use monotonic time if possible to guard against clock skew * Relax time comparisons to make test failures under load less likely * Fix memory corruption segfaults on macOS due to mixing Threads and Timeout Without the timeout thread safety fix, the specs always crash in the "threaded queries should be supported" spec for various reasons on macOS 10.14.4 with ruby 2.6.1 / MySQL 8.0.15. Some observed errors: malloc: *** error for object 0x7fcbba90ef80: pointer being freed was not allocated malloc: *** set a breakpoint in malloc_error_break to debug lib/mysql2/client.rb:131:in `_query': Bad file descriptor (Errno::EBADF) Now specs run fine, even during Prime95 load test on i9-8950HK (6c/12t). --- spec/mysql2/client_spec.rb | 21 ++++++++++++--------- spec/spec_helper.rb | 11 +++++++++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index e9f09ec0a..5a3b8a242 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -607,21 +607,21 @@ def run_gc # XXX this test is not deterministic (because Unix signal handling is not) # and may fail on a loaded system it "should run signal handlers while waiting for a response" do - kill_time = 0.1 - query_time = 2 * kill_time + kill_time = 0.25 + query_time = 4 * kill_time mark = {} begin - trap(:USR1) { mark.store(:USR1, Time.now) } + trap(:USR1) { mark.store(:USR1, clock_time) } pid = fork do sleep kill_time # wait for client query to start Process.kill(:USR1, Process.ppid) sleep # wait for explicit kill to prevent GC disconnect end - mark.store(:QUERY_START, Time.now) + mark.store(:QUERY_START, clock_time) @client.query("SELECT SLEEP(#{query_time})") - mark.store(:QUERY_END, Time.now) + mark.store(:QUERY_END, clock_time) ensure Process.kill(:TERM, pid) Process.waitpid2(pid) @@ -629,7 +629,7 @@ def run_gc end # the query ran uninterrupted - expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.02).of(query_time) + expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.1).of(query_time) # signals fired while the query was running expect(mark.fetch(:USR1)).to be_between(mark.fetch(:QUERY_START), mark.fetch(:QUERY_END)) end @@ -694,6 +694,7 @@ def run_gc sleep_time = 0.5 # Note that each thread opens its own database connection + start = clock_time threads = Array.new(5) do Thread.new do new_client do |client| @@ -702,10 +703,12 @@ def run_gc Thread.current.object_id end end + values = threads.map(&:value) + stop = clock_time - # This timeout demonstrates that the threads are sleeping concurrently: - # In the serial case, the timeout would fire and the test would fail - values = Timeout.timeout(sleep_time * 1.1) { threads.map(&:value) } + # This check demonstrates that the threads are sleeping concurrently: + # In the serial case, the difference would be a multiple of sleep time + expect(stop - start).to be_within(0.1).of(sleep_time) expect(values).to match_array(threads.map(&:object_id)) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 12b48df67..08b660b89 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -40,6 +40,17 @@ def num_classes # rubocop:enable Lint/UnifiedInteger end + # Use monotonic time if possible (ruby >= 2.1.0) + if defined?(Process::CLOCK_MONOTONIC) + def clock_time + Process.clock_gettime Process::CLOCK_MONOTONIC + end + else + def clock_time + Time.now.to_f + end + end + config.before :each do @client = new_client end From d47996975a9b9f30a8397ecca018d5ccb53ee114 Mon Sep 17 00:00:00 2001 From: Watson Date: Sat, 18 May 2019 02:00:57 +0900 Subject: [PATCH 628/783] Use rb_ivar_get/rb_ivar_set to improve performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By convert C-string to Ruby string ID each time, it will take a slightly time to call method each time. This patch will catch the Ruby string ID to cut off the time. * Before ``` Warming up -------------------------------------- query 886.000 i/100ms each 67.339k i/100ms fields 195.612k i/100ms Calculating ------------------------------------- query 9.385k (± 4.5%) i/s - 46.958k in 5.016539s each 901.633k (± 0.2%) i/s - 4.512M in 5.003951s fields 3.779M (± 0.2%) i/s - 18.974M in 5.021533s ``` * After ``` Warming up -------------------------------------- query 845.000 i/100ms each 86.916k i/100ms fields 231.527k i/100ms Calculating ------------------------------------- query 9.553k (± 2.0%) i/s - 48.320k in 5.059947s each 1.133M (± 0.3%) i/s - 5.736M in 5.062606s fields 6.319M (± 0.1%) i/s - 31.719M in 5.019960s ``` --- ext/mysql2/client.c | 17 ++++++++++------- ext/mysql2/result.c | 10 ++++++---- ext/mysql2/statement.c | 6 ++++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index c4b6c44cf..292657199 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -18,7 +18,8 @@ VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError; static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; static VALUE sym_no_good_index_used, sym_no_index_used, sym_query_was_slow; -static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args; +static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args, + intern_current_query_options, intern_read_timeout; #define REQUIRE_INITIALIZED(wrapper) \ if (!wrapper->initialized) { \ @@ -579,7 +580,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { rb_raise_mysql2_error(wrapper); } - is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream); + is_streaming = rb_hash_aref(rb_ivar_get(self, intern_current_query_options), sym_stream); if (is_streaming == Qtrue) { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0); } else { @@ -596,7 +597,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { } // Duplicate the options hash and put the copy in the Result object - current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); + current = rb_hash_dup(rb_ivar_get(self, intern_current_query_options)); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); @@ -639,7 +640,7 @@ static VALUE do_query(void *args) { int retval; VALUE read_timeout; - read_timeout = rb_iv_get(async_args->self, "@read_timeout"); + read_timeout = rb_ivar_get(async_args->self, intern_read_timeout); tvp = NULL; if (!NIL_P(read_timeout)) { @@ -767,7 +768,7 @@ static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) { (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); - rb_iv_set(self, "@current_query_options", current); + rb_ivar_set(self, intern_current_query_options, current); Check_Type(sql, T_STRING); /* ensure the string is in the encoding the connection is expecting */ @@ -1179,7 +1180,7 @@ static VALUE rb_mysql_client_store_result(VALUE self) } // Duplicate the options hash and put the copy in the Result object - current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); + current = rb_hash_dup(rb_ivar_get(self, intern_current_query_options)); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); @@ -1265,7 +1266,7 @@ static VALUE set_read_timeout(VALUE self, VALUE value) { /* Set the instance variable here even though _mysql_client_options might not succeed, because the timeout is used in other ways elsewhere */ - rb_iv_set(self, "@read_timeout", value); + rb_ivar_set(self, intern_read_timeout, value); return _mysql_client_options(self, MYSQL_OPT_READ_TIMEOUT, value); } @@ -1471,6 +1472,8 @@ void init_mysql2_client() { intern_merge = rb_intern("merge"); intern_merge_bang = rb_intern("merge!"); intern_new_with_args = rb_intern("new_with_args"); + intern_current_query_options = rb_intern("@current_query_options"); + intern_read_timeout = rb_intern("@read_timeout"); #ifdef CLIENT_LONG_PASSWORD rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 74851892a..32fbe0af2 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -37,7 +37,8 @@ extern VALUE mMysql2, cMysql2Client, cMysql2Error; static VALUE cMysql2Result, cDateTime, cDate; static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset; static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, - intern_civil, intern_new_offset, intern_merge, intern_BigDecimal; + intern_civil, intern_new_offset, intern_merge, intern_BigDecimal, + intern_query_options; static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name; @@ -695,7 +696,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { GET_RESULT(self); - defaults = rb_iv_get(self, "@query_options"); + defaults = rb_ivar_get(self, intern_query_options); Check_Type(defaults, T_HASH); if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) { symbolizeKeys = 1; @@ -818,7 +819,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { rb_raise(cMysql2Error, "Statement handle already closed"); } - defaults = rb_iv_get(self, "@query_options"); + defaults = rb_ivar_get(self, intern_query_options); Check_Type(defaults, T_HASH); if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) { opts = rb_funcall(defaults, intern_merge, 1, opts); @@ -951,7 +952,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ } rb_obj_call_init(obj, 0, NULL); - rb_iv_set(obj, "@query_options", options); + rb_ivar_set(obj, intern_query_options, options); /* Options that cannot be changed in results.each(...) { |row| } * should be processed here. */ @@ -980,6 +981,7 @@ void init_mysql2_result() { intern_civil = rb_intern("civil"); intern_new_offset = rb_intern("new_offset"); intern_BigDecimal = rb_intern("BigDecimal"); + intern_query_options = rb_intern("@query_options"); sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 22e22ecfd..e51fb9fd0 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -3,7 +3,8 @@ extern VALUE mMysql2, cMysql2Error; static VALUE cMysql2Statement, cBigDecimal, cDateTime, cDate; static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang; -static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year; +static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year, + intern_query_options; #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ @@ -404,7 +405,7 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { } // Duplicate the options hash, merge! extra opts, put the copy into the Result object - current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); + current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_query_options)); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); @@ -599,4 +600,5 @@ void init_mysql2_statement() { intern_to_s = rb_intern("to_s"); intern_merge_bang = rb_intern("merge!"); + intern_query_options = rb_intern("@query_options"); } From a7349893f0ff1ed7e8b601a0879f6f6972f36962 Mon Sep 17 00:00:00 2001 From: Watson Date: Sat, 18 May 2019 02:15:32 +0900 Subject: [PATCH 629/783] Check only whether block was given MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `rb_scan_args(argc, argv, "01&", ...)` will generate `Proc` object from block. However, the object has used to only check whether block was given. To remove redundant object generating, this patch will use `rb_block_given_p()` to check whether block was given. * Before ``` Warming up -------------------------------------- query 845.000 i/100ms each 86.916k i/100ms fields 231.527k i/100ms Calculating ------------------------------------- query 9.553k (± 2.0%) i/s - 48.320k in 5.059947s each 1.133M (± 0.3%) i/s - 5.736M in 5.062606s fields 6.319M (± 0.1%) i/s - 31.719M in 5.019960s ``` * After ``` Warming up -------------------------------------- query 864.000 i/100ms each 106.916k i/100ms fields 251.255k i/100ms Calculating ------------------------------------- query 9.457k (± 3.8%) i/s - 47.520k in 5.032949s each 1.550M (± 0.3%) i/s - 7.805M in 5.037029s fields 6.233M (± 0.1%) i/s - 31.407M in 5.039049s ``` --- ext/mysql2/result.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 32fbe0af2..fbff98137 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -30,7 +30,7 @@ typedef struct { int streaming; ID db_timezone; ID app_timezone; - VALUE block_given; + int block_given; /* boolean */ } result_each_args; extern VALUE mMysql2, cMysql2Client, cMysql2Error; @@ -741,7 +741,7 @@ static VALUE rb_mysql_result_each_(VALUE self, row = fetch_row_func(self, fields, args); if (row != Qnil) { wrapper->numberOfRows++; - if (args->block_given != Qnil) { + if (args->block_given) { rb_yield(row); } } @@ -791,7 +791,7 @@ static VALUE rb_mysql_result_each_(VALUE self, return Qnil; } - if (args->block_given != Qnil) { + if (args->block_given) { rb_yield(row); } } @@ -809,7 +809,7 @@ static VALUE rb_mysql_result_each_(VALUE self, static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; - VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); + VALUE defaults, opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); ID db_timezone, app_timezone, dbTz, appTz; int symbolizeKeys, asArray, castBool, cacheRows, cast; @@ -821,7 +821,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { defaults = rb_ivar_get(self, intern_query_options); Check_Type(defaults, T_HASH); - if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) { + + // A block can be passed to this method, but since we don't call the block directly from C, + // we don't need to capture it into a variable here with the "&" scan arg. + if (rb_scan_args(argc, argv, "01", &opts) == 1) { opts = rb_funcall(defaults, intern_merge, 1, opts); } else { opts = defaults; @@ -887,7 +890,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { args.cast = cast; args.db_timezone = db_timezone; args.app_timezone = app_timezone; - args.block_given = block; + args.block_given = rb_block_given_p(); if (wrapper->stmt_wrapper) { fetch_row_func = rb_mysql_result_fetch_row_stmt; From 98045516853d0004c022624f865df846a1929728 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 5 Jun 2019 19:54:36 -0700 Subject: [PATCH 630/783] Update README to clarify the protocol value in DATABASE_URL Resolves #1047 --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 04b5dad42..69563c4df 100644 --- a/README.md +++ b/README.md @@ -278,8 +278,10 @@ The string form will be split on whitespace and parsed as with the array form: Plain flags are added to the default flags, while flags prefixed with `-` (minus) are removed from the default flags. -This allows easier use with ActiveRecord's database.yml, avoiding the need for magic flag numbers. -For example, to disable protocol compression, and enable multiple statements and result sets: +### Using Active Record's database.yml + +Active Record typically reads its configuration from a file named `database.yml` or an environment variable `DATABASE_URL`. +Use the value `mysql2` as the adapter name. For example: ``` yaml development: @@ -297,6 +299,15 @@ development: secure_auth: false ``` +### Using Active Record's DATABASE_URL + +Active Record typically reads its configuration from a file named `database.yml` or an environment variable `DATABASE_URL`. +Use the value `mysql2` as the protocol name. For example: + +``` shell +DATABASE_URL=mysql2://sql_user:sql_pass@sql_host_name:port/sql_db_name?option1=value1&option2=value2 +``` + ### Reading a MySQL config file You may read configuration options from a MySQL configuration file by passing From fd32e287be908bfa1a522dc05ed9c75d150b85c4 Mon Sep 17 00:00:00 2001 From: MSP-Greg Date: Wed, 2 Oct 2019 17:25:16 -0500 Subject: [PATCH 631/783] Replace Win32API with Fiddle, update appveyor.yml (#1053) --- appveyor.yml | 4 ++++ lib/mysql2.rb | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 274bc6d80..0ce822d22 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,6 +25,10 @@ on_failure: - find tmp -name "*.log" | xargs cat environment: matrix: + - ruby_version: "26-x64" + MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" + - ruby_version: "25-x64" + MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" - ruby_version: "24-x64" MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" - ruby_version: "24" diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 4bf75364a..6fcbe1f03 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -20,9 +20,12 @@ end if dll_path - require 'Win32API' - LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I') - if LoadLibrary.call(dll_path).zero? + require 'fiddle' + kernel32 = Fiddle.dlopen 'kernel32' + load_library = Fiddle::Function.new( + kernel32['LoadLibraryW'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT, + ) + if load_library.call(dll_path.encode('utf-16le')).zero? abort "Failed to load libmysql.dll from #{dll_path}" end end From 46369c0e6773c2ae91aac9f01bfbf71482f1cbeb Mon Sep 17 00:00:00 2001 From: Ian Kottman Date: Wed, 2 Oct 2019 17:28:30 -0500 Subject: [PATCH 632/783] Remove spec from gem (#1044) --- mysql2.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/mysql2.gemspec b/mysql2.gemspec index 98a89b95a..ef6401cb9 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -13,7 +13,6 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s| s.required_ruby_version = '>= 2.0.0' s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split - s.test_files = `git ls-files spec examples`.split s.metadata['msys2_mingw_dependencies'] = 'libmariadbclient' end From fee3c71dd1d8c91d9e0b89ee08b8673f4666cf64 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 22 Nov 2019 06:03:20 +0900 Subject: [PATCH 633/783] Keyword arguments have to be explicitly double-splatted in Ruby 2.7+ The Ruby core team decided to introduce a slight incompatibility to keyword arguments from Ruby 3.0, i.e. complete separation between keyword arguments literal and Hash literal. https://bugs.ruby-lang.org/issues/14183 https://github.com/ruby/ruby/pull/2395 With that, current Ruby master warns when a Hash object was passed in as keyword arguments: ``` $ ruby -ve 'def f(x: nil) p x; end; hash = {x: 1}; f(hash)' ruby 2.7.0dev (2019-09-02T05:20:05Z master 83498854eb) [x86_64-darwin18] warning: The last argument is used as the keyword parameter warning: for `f' defined here 1 ``` To eliminate this warning, we need to prefix a "double splat" (**) to avoid ambiguity: ``` $ ruby -ve 'def f(x: nil) p x;end; hash = {x: 1}; f(**hash)' ruby 2.7.0dev (2019-09-02T05:20:05Z master 83498854eb) [x86_64-darwin18] 1 ``` See also: * https://github.com/ruby-i18n/i18n/pull/486 * https://buildkite.com/rails/rails/builds/63974#7fb9ad05-a745-4022-b634-aa3eb9042b11/6-1865 ``` /usr/local/lib/ruby/gems/2.7.0/gems/mysql2-0.5.2/lib/mysql2/error.rb:55: warning: The last argument is used as the keyword parameter /usr/local/lib/ruby/gems/2.7.0/gems/mysql2-0.5.2/lib/mysql2/error.rb:94: warning: The last argument is used as the keyword parameter ``` --- lib/mysql2/error.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 758f01a98..3c182218d 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -52,7 +52,7 @@ class Error < StandardError def initialize(msg, server_version = nil, error_number = nil, sql_state = nil) @server_version = server_version @error_number = error_number - @sql_state = sql_state ? sql_state.encode(ENCODE_OPTS) : nil + @sql_state = sql_state ? sql_state.encode(**ENCODE_OPTS) : nil super(clean_message(msg)) end @@ -91,9 +91,9 @@ def self.new_with_args(msg, server_version, error_number, sql_state) # Returns a valid UTF-8 string. def clean_message(message) if @server_version && @server_version > 50500 - message.encode(ENCODE_OPTS) + message.encode(**ENCODE_OPTS) else - message.encode(Encoding::UTF_8, ENCODE_OPTS) + message.encode(Encoding::UTF_8, **ENCODE_OPTS) end end end From 6abae9ab1065d3a6e57ee2dca0e80f2f9e483ece Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 22 Nov 2019 18:09:22 +0100 Subject: [PATCH 634/783] Fix mysql-5.7 test case's error in Travis. See https://travis-ci.org/brianmario/mysql2/jobs/615263122#L798 ``` + add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-5.7' + apt-get install -qq mysql-server libmysqlclient-dev ``` After adding trusty mysql-5.7 repository, mysql-5.5 is installed instead of mysql-5.7. because the Packages file is empty (zero byte). http://repo.mysql.com/apt/ubuntu/dists/trusty/mysql-5.7/binary-amd64/ xenial mysql-5.7 repository is still available. The Packages file is not empty. http://repo.mysql.com/apt/ubuntu/dists/xenial/mysql-5.7/binary-amd64/ --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f9b098ca6..8dd50f9c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,6 +60,7 @@ matrix: - mysql2gem.example.com - rvm: 2.4 env: DB=mysql57 + dist: xenial addons: hosts: - mysql2gem.example.com From 12a5e9656ef937e3f9c76a8b97a160cdc8b22f7c Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 22 Nov 2019 19:04:04 +0100 Subject: [PATCH 635/783] Fix CentOS case error in Travis. As the `mysql_install_db` script in CentOS refers wrong path of resolveip command: `/usr/libexec/resolveip`, set `ln -s /usr/bin/resolveip /usr/libexec/resolveip` as a workflow. See https://travis-ci.org/brianmario/mysql2/jobs/615263124#L2840 --- .travis_setup_centos.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis_setup_centos.sh b/.travis_setup_centos.sh index 631fbb9ec..78a9954c9 100644 --- a/.travis_setup_centos.sh +++ b/.travis_setup_centos.sh @@ -4,6 +4,11 @@ set -eux MYSQL_TEST_LOG="$(pwd)/mysql.log" +# mysql_install_db uses wrong path for resolveip +# https://jira.mariadb.org/browse/MDEV-18563 +# https://travis-ci.org/brianmario/mysql2/jobs/615263124#L2840 +ln -s "$(command -v resolveip)" /usr/libexec/resolveip + mysql_install_db \ --log-error="${MYSQL_TEST_LOG}" /usr/libexec/mysqld \ From d2a273f40c86ffa977954b3cd100525e8e7b3041 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 27 Nov 2019 10:47:18 +0900 Subject: [PATCH 636/783] Update older rubygems to address to newer Ruby's warnings e.g. https://github.com/rubygems/rubygems/pull/2494 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f9b098ca6..4aeb3e621 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ bundler_args: --without benchmarks development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - gem --version - - gem update --system 2.7.6 --quiet + - gem update --system --quiet - gem update bundler - gem --version - bash .travis_setup.sh From b700550c9cf42fad0f4239f7f7161444cb51ded0 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 27 Nov 2019 11:11:41 +0900 Subject: [PATCH 637/783] Fallback to older rubygems for staled Ruby Looks like rubygems >= 3.0.0 no longer support staled Ruby < 2.3.0. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4aeb3e621..86456d111 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ bundler_args: --without benchmarks development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - gem --version - - gem update --system --quiet + - gem update --system --quiet || gem update --system 2.7.10 --quiet - gem update bundler - gem --version - bash .travis_setup.sh From dee108d7be5704f7bb12577b4fcc286bd9f84423 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 26 Nov 2019 23:45:47 -0800 Subject: [PATCH 638/783] Bump rake-compiler-dock to pick up Ruby 2.6, drop older ones With this version bump, the precompiled mysql2 gem for Windows supports Ruby 2.2, 2.3, 2.4, 2.5, 2.6. Windows Ruby 2.0 and 2.1 are dropped. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 5d9c98491..2cec989fc 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,7 @@ end group :development do gem 'pry' - gem 'rake-compiler-dock', '~> 0.6.0' + gem 'rake-compiler-dock', '~> 0.7.0' end platforms :rbx do From ff05239196eb8c209ca4aceff807d52c41a59670 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 27 Nov 2019 00:34:12 -0800 Subject: [PATCH 639/783] Fix control flow in rb_set_ssl_mode_option for some client library versions (#1088) MySQL Connector/C 6.1.3 and above configure as HAVE_CONST_MYSQL_OPT_SSL_ENFORCE, but their client library version is a different range than MySQL 5.7.3 - 5.7.10 that also have this option. And generally there's a compiler warning here because of an incomplete control flow if the library version doesn't match and exits the function without an explicit return as required, so add a warning in this case. Closes #1062 --- ext/mysql2/client.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 292657199..13fd2dd35 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -113,7 +113,8 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE GET_CLIENT(self); int val = NUM2INT( setting ); - if (version >= 50703 && version < 50711) { + // Either MySQL 5.7.3 - 5.7.10, or Connector/C 6.1.3 - 6.1.x + if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200)) { if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { my_bool b = ( val == SSL_MODE_REQUIRED ); int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); @@ -122,6 +123,9 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" ); return Qnil; } + } else { + rb_warn( "Your mysql client library does not support ssl_mode as expected." ); + return Qnil; } #endif #ifdef FULL_SSL_MODE_SUPPORT From db6733aa8f130866c30dbeccd18e81113556bb4b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 27 Nov 2019 00:35:19 -0800 Subject: [PATCH 640/783] Bump version to 0.5.3 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 152a6227c..d97f4390d 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.5.2".freeze + VERSION = "0.5.3".freeze end From 785969fbceadd96d495ec9fecc67efee03d0056c Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 2 Dec 2019 04:23:16 +0900 Subject: [PATCH 641/783] The taint mechanism will be deprecated in Ruby 2.7 (#1083) The Ruby core team decided to deprecate the taint mechanism in Ruby 2.7 and will remove that in Ruby 3. https://bugs.ruby-lang.org/issues/16131 https://github.com/ruby/ruby/pull/2476 In Ruby 2.7, `Object#{taint,untaint,trust,untrust}` and related functions in the C-API no longer have an effect (all objects are always considered untainted), and are now warned deprecation message. --- ext/mysql2/client.c | 2 +- ext/mysql2/statement.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 13fd2dd35..e80b555e7 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -175,7 +175,7 @@ static void rb_mysql_client_mark(void * wrapper) { static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client)); - VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client)); + VALUE rb_sql_state = rb_str_new2(mysql_sqlstate(wrapper->client)); VALUE e; rb_enc_associate(rb_error_msg, rb_utf8_encoding()); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index e51fb9fd0..cd6adf83f 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -46,7 +46,7 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) { VALUE e; GET_CLIENT(stmt_wrapper->client); VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt)); - VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt)); + VALUE rb_sql_state = rb_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt)); rb_encoding *conn_enc; conn_enc = rb_to_encoding(wrapper->encoding); From f62edc0d17b3c31fdf2d5581a82feec8393239d2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 28 Dec 2019 01:34:48 +0900 Subject: [PATCH 642/783] Made argument types strict for ruby 2.7 (#1096) As function pointer arguments are declared strictly in Ruby 2.7, `do_send_query` and `do_query` cause warnings. ``` cd tmp/x86_64-darwin19/mysql2/2.7.0 /opt/local/bin/gmake compiling ../../../../ext/mysql2/client.c ../../../../ext/mysql2/client.c:787:14: warning: incompatible pointer types passing 'VALUE (void *)' (aka 'unsigned long (void *)') to parameter of type 'VALUE (*)(VALUE)' (aka 'unsigned long (*)(unsigned long)') [-Wincompatible-pointer-types] rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0); ^~~~~~~~~~~~~ /opt/local/include/ruby-2.7.0/ruby/ruby.h:1988:25: note: passing argument to parameter here VALUE rb_rescue2(VALUE(*)(VALUE),VALUE,VALUE(*)(VALUE,VALUE),VALUE,...); ^ ../../../../ext/mysql2/client.c:795:16: warning: incompatible pointer types passing 'VALUE (void *)' (aka 'unsigned long (void *)') to parameter of type 'VALUE (*)(VALUE)' (aka 'unsigned long (*)(unsigned long)') [-Wincompatible-pointer-types] rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0); ^~~~~~~~ /opt/local/include/ruby-2.7.0/ruby/ruby.h:1988:25: note: passing argument to parameter here VALUE rb_rescue2(VALUE(*)(VALUE),VALUE,VALUE(*)(VALUE,VALUE),VALUE,...); ^ 2 warnings generated. ``` --- ext/mysql2/client.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index e80b555e7..b63078288 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -513,10 +513,10 @@ static void *nogvl_send_query(void *ptr) { return (void*)(rv == 0 ? Qtrue : Qfalse); } -static VALUE do_send_query(void *args) { - struct nogvl_send_query_args *query_args = args; +static VALUE do_send_query(VALUE args) { + struct nogvl_send_query_args *query_args = (void *)args; mysql_client_wrapper *wrapper = query_args->wrapper; - if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { + if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, query_args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ wrapper->active_thread = Qnil; rb_raise_mysql2_error(wrapper); @@ -636,8 +636,8 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) { rb_exc_raise(error); } -static VALUE do_query(void *args) { - struct async_query_args *async_args = args; +static VALUE do_query(VALUE args) { + struct async_query_args *async_args = (void *)args; struct timeval tv; struct timeval *tvp; long int sec; @@ -797,7 +797,7 @@ static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) { return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self); } #else - do_send_query(&args); + do_send_query((VALUE)&args); /* this will just block until the result is ready */ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self); From 475c450c7698aab76b26e7d12dd273fb5f5948a9 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 2 Jan 2020 17:49:37 +0100 Subject: [PATCH 643/783] Update rake (and rake-compiler) to suppress the warning. (#1099) To suppress the warning: /home/travis/.rvm/gems/ruby-head/gems/rake-10.4.2/lib/rake/application.rb:381: warning: deprecated Object#=~ is called on Proc; it always returns nil in the process of "bundle exec rake compile". --- Gemfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 2cec989fc..847a77fc1 100644 --- a/Gemfile +++ b/Gemfile @@ -2,8 +2,12 @@ source 'https://rubygems.org/' gemspec -gem 'rake', '~> 10.4.2' -gem 'rake-compiler', '~> 1.0' +gem 'rake', if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2") + '~> 13.0.1' + else + '< 13' + end +gem 'rake-compiler', '~> 1.1.0' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ From d25db07e25b9974981b86938c00206c4380618c2 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 4 Jan 2020 13:30:29 -0800 Subject: [PATCH 644/783] For local debugging, irb is Gemified since Ruby 2.6 --- Gemfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index 847a77fc1..79b8fbc37 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,9 @@ gem 'rake', if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2") end gem 'rake-compiler', '~> 1.1.0' +# For local debugging, irb is Gemified since Ruby 2.6 +gem 'irb', require: false + group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' From b52f27e7cafcc264eff99a048ef3b881fb9f2888 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sat, 4 Jan 2020 17:25:33 -0800 Subject: [PATCH 645/783] Travis CI add Ruby 2.7 to the matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index bac1190e6..d83915a6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ addons: - mysql-client-core-5.6 - mysql-client-5.6 rvm: + - 2.7 - 2.6 - 2.5 - 2.4 From f8560c551bf1999baf7df43290e8f89471e77af4 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 9 Jan 2020 07:51:50 -0800 Subject: [PATCH 646/783] Fix crash if a Mysql2::Client object is allocated but never connected (#1101) Reproducible with any older version of the mysql2 gem: ruby -r mysql2 -I lib -e 'Mysql2::Client.allocate.close' Before this fix, the line above would crash out with a memory error: malloc: *** error for object 0x...: pointer being freed was not allocated malloc: *** set a breakpoint in malloc_error_break to debug Abort trap: 6 --- ext/mysql2/client.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index b63078288..2917bcfdd 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -323,9 +323,9 @@ static VALUE allocate(VALUE klass) { wrapper->server_version = 0; wrapper->reconnect_enabled = 0; wrapper->connect_timeout = 0; - wrapper->initialized = 0; /* means that that the wrapper is initialized */ + wrapper->initialized = 0; /* will be set true after calling mysql_init */ + wrapper->closed = 1; /* will be set false after calling mysql_real_connect */ wrapper->refcount = 1; - wrapper->closed = 0; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); return obj; @@ -467,6 +467,7 @@ static VALUE rb_mysql_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VA rb_raise_mysql2_error(wrapper); } + wrapper->closed = 0; wrapper->server_version = mysql_get_server_version(wrapper->client); return self; } From 982dbdb1d521c668547cfd4ab927558c689b8bfd Mon Sep 17 00:00:00 2001 From: Adam Crownoble Date: Wed, 5 Feb 2020 15:35:08 -0700 Subject: [PATCH 647/783] Remove Enumerable include from Statement class (#1104) I'm honestly not sure why this was here in the first place. Git blame seems to suggest that maybe it was added when the class was first created in anticipation for something that never materialized or has since been deprecated. As far as I can tell, `Enumerable` offers no advantages to the class since `Statement` doesn't implement `#each`. Here's an example of what I'm referring to ```ruby client = Mysql2::Client.new statement = client.prepare('select 1') statement.first statement.any? statement.min ``` Also all test pass with `Enumerable` gone, so it seems reasonable to remove it. --- lib/mysql2/statement.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/mysql2/statement.rb b/lib/mysql2/statement.rb index 2f9f09a05..f5ca35b86 100644 --- a/lib/mysql2/statement.rb +++ b/lib/mysql2/statement.rb @@ -1,7 +1,5 @@ module Mysql2 class Statement - include Enumerable - def execute(*args, **kwargs) Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do _execute(*args, **kwargs) From 1b15d78a113b5c4f89ecd00e82ac40c63b8c7b55 Mon Sep 17 00:00:00 2001 From: Orien Madgwick <_@orien.io> Date: Tue, 10 Mar 2020 10:01:21 +1100 Subject: [PATCH 648/783] Add project metadata to the gemspec (#1089) As per https://guides.rubygems.org/specification-reference/#metadata, add metadata to the gemspec file. This'll allow people to more easily access the source code, raise issues and read the changelog. These bug_tracker_uri, changelog_uri, documentation_uri, homepage_uri and source_code_uri links will appear on the rubygems page at https://rubygems.org/gems/mysql2 and be available via the rubygems API after the next release. --- mysql2.gemspec | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mysql2.gemspec b/mysql2.gemspec index ef6401cb9..cc9a55e48 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -10,6 +10,13 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s| s.homepage = 'https://github.com/brianmario/mysql2' s.rdoc_options = ["--charset=UTF-8"] s.summary = 'A simple, fast Mysql library for Ruby, binding to libmysql' + s.metadata = { + 'bug_tracker_uri' => "#{s.homepage}/issues", + 'changelog_uri' => "#{s.homepage}/releases/tag/#{s.version}", + 'documentation_uri' => "https://www.rubydoc.info/gems/mysql2/#{s.version}", + 'homepage_uri' => s.homepage, + 'source_code_uri' => "#{s.homepage}/tree/#{s.version}", + } s.required_ruby_version = '>= 2.0.0' s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split From 48172b579343a8c2cd3a4306ae110e06518d1039 Mon Sep 17 00:00:00 2001 From: Dennis Taylor Date: Mon, 9 Mar 2020 16:02:17 -0700 Subject: [PATCH 649/783] Don't call mysql_close if mysql_init wasn't called. (#1111) --- ext/mysql2/client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 2917bcfdd..3ba393f52 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -269,7 +269,7 @@ static VALUE invalidate_fd(int clientfd) static void *nogvl_close(void *ptr) { mysql_client_wrapper *wrapper = ptr; - if (!wrapper->closed) { + if (wrapper->initialized && !wrapper->closed) { mysql_close(wrapper->client); wrapper->closed = 1; wrapper->reconnect_enabled = 0; From cb9e9411a885ece422a73508876d601fda6fae40 Mon Sep 17 00:00:00 2001 From: Aaron Brady Date: Mon, 9 Mar 2020 19:06:33 -0400 Subject: [PATCH 650/783] Client session tracking (#1092) * Client session tracking * Add session tracking enumeration * Conditionally add the session_track method * Add tests for client.session_track * Add session track information to Readme * Disable rubocop block length * Allow compilation with mariadb-connector-c Co-authored-by: Bibek Shrestha --- README.md | 17 ++++++++++++ ext/mysql2/client.c | 53 ++++++++++++++++++++++++++++++++++++++ spec/mysql2/client_spec.rb | 45 +++++++++++++++++++++++++++++++- 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 69563c4df..4094062f6 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,23 @@ statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND locat result = statement.execute(1, "CA", :as => :array) ``` +Session Tracking information can be accessed with + +```ruby +c = Mysql2::Client.new( + host: "127.0.0.1", + username: "root", + flags: "SESSION_TRACK", + init_command: "SET @@SESSION.session_track_schema=ON" +) +c.query("INSERT INTO test VALUES (1)") +session_track_type = Mysql2::Client::SESSION_TRACK_SCHEMA +session_track_data = c.session_track(session_track_type) +``` + +The types of session track types can be found at +[https://dev.mysql.com/doc/refman/5.7/en/mysql-session-track-get-first.html](https://dev.mysql.com/doc/refman/5.7/en/mysql-session-track-get-first.html) + ## Connection options You may set the following connection options in Mysql2::Client.new(...): diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 3ba393f52..8f3d403b7 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -57,6 +57,17 @@ static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION #endif +/* + * mariadb-connector-c defines CLIENT_SESSION_TRACKING and SESSION_TRACK_TRANSACTION_TYPE + * while mysql-connector-c defines CLIENT_SESSION_TRACK and SESSION_TRACK_TRANSACTION_STATE + * This is a hack to take care of both clients. + */ +#if defined(CLIENT_SESSION_TRACK) +#elif defined(CLIENT_SESSION_TRACKING) + #define CLIENT_SESSION_TRACK CLIENT_SESSION_TRACKING + #define SESSION_TRACK_TRANSACTION_STATE SESSION_TRACK_TRANSACTION_TYPE +#endif + /* * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10. */ @@ -1022,6 +1033,36 @@ static VALUE rb_mysql_client_last_id(VALUE self) { return ULL2NUM(mysql_insert_id(wrapper->client)); } +/* call-seq: + * client.session_track + * + * Returns information about changes to the session state on the server. + */ +static VALUE rb_mysql_client_session_track(VALUE self, VALUE type) { +#ifdef CLIENT_SESSION_TRACK + const char *data; + size_t length; + my_ulonglong retVal; + GET_CLIENT(self); + + REQUIRE_CONNECTED(wrapper); + retVal = mysql_session_track_get_first(wrapper->client, NUM2INT(type), &data, &length); + if (retVal != 0) { + return Qnil; + } + VALUE rbAry = rb_ary_new(); + VALUE rbFirst = rb_str_new(data, length); + rb_ary_push(rbAry, rbFirst); + while(mysql_session_track_get_next(wrapper->client, NUM2INT(type), &data, &length) == 0) { + VALUE rbNext = rb_str_new(data, length); + rb_ary_push(rbAry, rbNext); + } + return rbAry; +#else + return Qnil; +#endif +} + /* call-seq: * client.affected_rows * @@ -1442,6 +1483,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0); rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0); rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); + rb_define_method(cMysql2Client, "session_track", rb_mysql_client_session_track, 1); rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1); @@ -1614,6 +1656,17 @@ void init_mysql2_client() { INT2NUM(0)); #endif +#ifdef CLIENT_SESSION_TRACK + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK"), INT2NUM(CLIENT_SESSION_TRACK)); + /* From mysql_com.h -- but stable from at least 5.7.4 through 8.0.20 */ + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_SYSTEM_VARIABLES"), INT2NUM(SESSION_TRACK_SYSTEM_VARIABLES)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_SCHEMA"), INT2NUM(SESSION_TRACK_SCHEMA)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_STATE_CHANGE"), INT2NUM(SESSION_TRACK_STATE_CHANGE)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_GTIDS"), INT2NUM(SESSION_TRACK_GTIDS)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_TRANSACTION_CHARACTERISTICS"), INT2NUM(SESSION_TRACK_TRANSACTION_CHARACTERISTICS)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_TRANSACTION_STATE"), INT2NUM(SESSION_TRACK_TRANSACTION_STATE)); +#endif + #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED)); diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 5a3b8a242..0bdd65c9f 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -RSpec.describe Mysql2::Client do +RSpec.describe Mysql2::Client do # rubocop:disable Metrics/BlockLength context "using defaults file" do let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) } @@ -1026,6 +1026,49 @@ def run_gc expect(@client).to respond_to(:ping) end + context "session_track" do + before(:each) do + unless Mysql2::Client.const_defined?(:SESSION_TRACK) + skip('Server versions must be MySQL 5.7 later.') + end + @client.query("SET @@SESSION.session_track_system_variables='*';") + end + + it "returns changes system variables for SESSION_TRACK_SYSTEM_VARIABLES" do + @client.query("SET @@SESSION.session_track_state_change=ON;") + res = @client.session_track(Mysql2::Client::SESSION_TRACK_SYSTEM_VARIABLES) + expect(res).to eq(%w[session_track_state_change ON]) + end + + it "returns database name for SESSION_TRACK_SCHEMA" do + @client.query("USE information_schema") + res = @client.session_track(Mysql2::Client::SESSION_TRACK_SCHEMA) + expect(res).to eq(["information_schema"]) + end + + it "returns multiple session track type values when available" do + @client.query("SET @@SESSION.session_track_transaction_info='CHARACTERISTICS'") + + res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_STATE) + expect(res).to eq(["________"]) + + res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_CHARACTERISTICS) + expect(res).to eq([""]) + + res = @client.session_track(Mysql2::Client::SESSION_TRACK_STATE_CHANGE) + expect(res).to be_nil + + res = @client.session_track(Mysql2::Client::SESSION_TRACK_SYSTEM_VARIABLES) + expect(res).to eq(%w[session_track_transaction_info CHARACTERISTICS]) + end + + it "returns empty array if session track type not found" do + @client.query("SET @@SESSION.session_track_state_change=ON;") + res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_CHARACTERISTICS) + expect(res).to be_nil + end + end + context "select_db" do before(:each) do 2.times do |i| From c66eb365a9772f730434edaa62102eb79b5ce372 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Tue, 10 Mar 2020 09:04:33 +0900 Subject: [PATCH 651/783] Avoid a hash object allocation per each `query`/`execute` call (#1112) This is a same optimization approach with https://github.com/ruby/ruby/pull/2393. --- lib/mysql2.rb | 1 + lib/mysql2/client.rb | 2 +- lib/mysql2/statement.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 6fcbe1f03..e49060208 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -82,5 +82,6 @@ def self.key_hash_as_symbols(hash) else ::Timeout::Error end + TIMEOUT_ERROR_NEVER = { TIMEOUT_ERROR_CLASS => :never }.freeze end end diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index bbe6777a1..607ad6d6a 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -127,7 +127,7 @@ def parse_connect_attrs(conn_attrs) end def query(sql, options = {}) - Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do + Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do _query(sql, @query_options.merge(options)) end end diff --git a/lib/mysql2/statement.rb b/lib/mysql2/statement.rb index f5ca35b86..c50ed1f96 100644 --- a/lib/mysql2/statement.rb +++ b/lib/mysql2/statement.rb @@ -1,7 +1,7 @@ module Mysql2 class Statement def execute(*args, **kwargs) - Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do + Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do _execute(*args, **kwargs) end end From c3d1abc1e0be37084feea06b631c5c43d2b0e504 Mon Sep 17 00:00:00 2001 From: Huynh Tan Date: Tue, 17 Mar 2020 04:39:02 +0700 Subject: [PATCH 652/783] Implementing type reflection from mysql result (#1068) --- README.md | 3 +- ext/mysql2/result.c | 199 ++++++++++++++++++++++++++++++++++++- ext/mysql2/result.h | 1 + spec/mysql2/result_spec.rb | 85 ++++++++++++++++ 4 files changed, 286 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4094062f6..ca0a7b7dd 100644 --- a/README.md +++ b/README.md @@ -165,11 +165,12 @@ client.query("SELECT * FROM users WHERE group='githubbers'", :symbolize_keys => end ``` -You can get the headers and the columns in the order that they were returned +You can get the headers, columns, and the field types in the order that they were returned by the query like this: ``` ruby headers = results.fields # <= that's an array of field names, in order +types = results.field_types # <= that's an array of field types, in order results.each(:as => :array) do |row| # Each row is an array, ordered the same as the query results # An otter's den is called a "holt" or "couch" diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index fbff98137..d1ae3628d 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -17,6 +17,20 @@ static rb_encoding *binaryEncoding; */ #define MYSQL2_MIN_TIME 2678400ULL +#define MYSQL2_MAX_BYTES_PER_CHAR 3 + +/* From Mysql documentations: + * To distinguish between binary and nonbinary data for string data types, + * check whether the charsetnr value is 63. If so, the character set is binary, + * which indicates binary rather than nonbinary data. This enables you to distinguish BINARY + * from CHAR, VARBINARY from VARCHAR, and the BLOB types from the TEXT types. + */ +#define MYSQL2_BINARY_CHARSET 63 + +#ifndef MYSQL_TYPE_JSON +#define MYSQL_TYPE_JSON 245 +#endif + #define GET_RESULT(self) \ mysql2_result_wrapper *wrapper; \ Data_Get_Struct(self, mysql2_result_wrapper, wrapper); @@ -169,9 +183,171 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo return rb_field; } +static VALUE rb_mysql_result_fetch_field_type(VALUE self, unsigned int idx) { + VALUE rb_field_type; + GET_RESULT(self); + + if (wrapper->fieldTypes == Qnil) { + wrapper->numberOfFields = mysql_num_fields(wrapper->result); + wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields); + } + + rb_field_type = rb_ary_entry(wrapper->fieldTypes, idx); + if (rb_field_type == Qnil) { + MYSQL_FIELD *field = NULL; + rb_encoding *default_internal_enc = rb_default_internal_encoding(); + rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); + int precision; + + field = mysql_fetch_field_direct(wrapper->result, idx); + + switch(field->type) { + case MYSQL_TYPE_NULL: // NULL + rb_field_type = rb_str_new_cstr("null"); + break; + case MYSQL_TYPE_TINY: // signed char + rb_field_type = rb_sprintf("tinyint(%ld)", field->length); + break; + case MYSQL_TYPE_SHORT: // short int + rb_field_type = rb_sprintf("smallint(%ld)", field->length); + break; + case MYSQL_TYPE_YEAR: // short int + rb_field_type = rb_sprintf("year(%ld)", field->length); + break; + case MYSQL_TYPE_INT24: // int + rb_field_type = rb_sprintf("mediumint(%ld)", field->length); + break; + case MYSQL_TYPE_LONG: // int + rb_field_type = rb_sprintf("int(%ld)", field->length); + break; + case MYSQL_TYPE_LONGLONG: // long long int + rb_field_type = rb_sprintf("bigint(%ld)", field->length); + break; + case MYSQL_TYPE_FLOAT: // float + rb_field_type = rb_sprintf("float(%ld,%d)", field->length, field->decimals); + break; + case MYSQL_TYPE_DOUBLE: // double + rb_field_type = rb_sprintf("double(%ld,%d)", field->length, field->decimals); + break; + case MYSQL_TYPE_TIME: // MYSQL_TIME + rb_field_type = rb_str_new_cstr("time"); + break; + case MYSQL_TYPE_DATE: // MYSQL_TIME + case MYSQL_TYPE_NEWDATE: // MYSQL_TIME + rb_field_type = rb_str_new_cstr("date"); + break; + case MYSQL_TYPE_DATETIME: // MYSQL_TIME + rb_field_type = rb_str_new_cstr("datetime"); + break; + case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME + rb_field_type = rb_str_new_cstr("timestamp"); + break; + case MYSQL_TYPE_DECIMAL: // char[] + case MYSQL_TYPE_NEWDECIMAL: // char[] + /* + Handle precision similar to this line from mysql's code: + https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/field.cc#L2246 + */ + precision = field->length - (field->decimals > 0 ? 2 : 1); + rb_field_type = rb_sprintf("decimal(%ld,%d)", precision, field->decimals); + break; + case MYSQL_TYPE_STRING: // char[] + if (field->flags & ENUM_FLAG) { + rb_field_type = rb_str_new_cstr("enum"); + } else if (field->flags & SET_FLAG) { + rb_field_type = rb_str_new_cstr("set"); + } else { + if (field->charsetnr == MYSQL2_BINARY_CHARSET) { + rb_field_type = rb_sprintf("binary(%ld)", field->length); + } else { + rb_field_type = rb_sprintf("char(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR); + } + } + break; + case MYSQL_TYPE_VAR_STRING: // char[] + if (field->charsetnr == MYSQL2_BINARY_CHARSET) { + rb_field_type = rb_sprintf("varbinary(%ld)", field->length); + } else { + rb_field_type = rb_sprintf("varchar(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR); + } + break; + case MYSQL_TYPE_VARCHAR: // char[] + rb_field_type = rb_sprintf("varchar(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR); + break; + case MYSQL_TYPE_TINY_BLOB: // char[] + rb_field_type = rb_str_new_cstr("tinyblob"); + break; + case MYSQL_TYPE_BLOB: // char[] + if (field->charsetnr == MYSQL2_BINARY_CHARSET) { + switch(field->length) { + case 255: + rb_field_type = rb_str_new_cstr("tinyblob"); + break; + case 65535: + rb_field_type = rb_str_new_cstr("blob"); + break; + case 16777215: + rb_field_type = rb_str_new_cstr("mediumblob"); + break; + case 4294967295: + rb_field_type = rb_str_new_cstr("longblob"); + default: + break; + } + } else { + if (field->length == (255 * MYSQL2_MAX_BYTES_PER_CHAR)) { + rb_field_type = rb_str_new_cstr("tinytext"); + } else if (field->length == (65535 * MYSQL2_MAX_BYTES_PER_CHAR)) { + rb_field_type = rb_str_new_cstr("text"); + } else if (field->length == (16777215 * MYSQL2_MAX_BYTES_PER_CHAR)) { + rb_field_type = rb_str_new_cstr("mediumtext"); + } else if (field->length == 4294967295) { + rb_field_type = rb_str_new_cstr("longtext"); + } else { + rb_field_type = rb_sprintf("text(%ld)", field->length); + } + } + break; + case MYSQL_TYPE_MEDIUM_BLOB: // char[] + rb_field_type = rb_str_new_cstr("mediumblob"); + break; + case MYSQL_TYPE_LONG_BLOB: // char[] + rb_field_type = rb_str_new_cstr("longblob"); + break; + case MYSQL_TYPE_BIT: // char[] + rb_field_type = rb_sprintf("bit(%ld)", field->length); + break; + case MYSQL_TYPE_SET: // char[] + rb_field_type = rb_str_new_cstr("set"); + break; + case MYSQL_TYPE_ENUM: // char[] + rb_field_type = rb_str_new_cstr("enum"); + break; + case MYSQL_TYPE_GEOMETRY: // char[] + rb_field_type = rb_str_new_cstr("geometry"); + break; + case MYSQL_TYPE_JSON: // json + rb_field_type = rb_str_new_cstr("json"); + break; + default: + rb_field_type = rb_str_new_cstr("unknown"); + break; + } + + rb_enc_associate(rb_field_type, conn_enc); + if (default_internal_enc) { + rb_field_type = rb_str_export_to_enc(rb_field_type, default_internal_enc); + } + + rb_ary_store(wrapper->fieldTypes, idx, rb_field_type); + } + + return rb_field_type; +} + static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) { /* if binary flag is set, respect its wishes */ - if (field.flags & BINARY_FLAG && field.charsetnr == 63) { + if (field.flags & BINARY_FLAG && field.charsetnr == MYSQL2_BINARY_CHARSET) { rb_enc_associate(val, binaryEncoding); } else if (!field.charsetnr) { /* MySQL 4.x may not provide an encoding, binary will get the bytes through */ @@ -716,6 +892,25 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { return wrapper->fields; } +static VALUE rb_mysql_result_fetch_field_types(VALUE self) { + unsigned int i = 0; + + GET_RESULT(self); + + if (wrapper->fieldTypes == Qnil) { + wrapper->numberOfFields = mysql_num_fields(wrapper->result); + wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields); + } + + if ((my_ulonglong)RARRAY_LEN(wrapper->fieldTypes) != wrapper->numberOfFields) { + for (i=0; inumberOfFields; i++) { + rb_mysql_result_fetch_field_type(self, i); + } + } + + return wrapper->fieldTypes; +} + static VALUE rb_mysql_result_each_(VALUE self, VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args), const result_each_args *args) @@ -934,6 +1129,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ wrapper->resultFreed = 0; wrapper->result = r; wrapper->fields = Qnil; + wrapper->fieldTypes = Qnil; wrapper->rows = Qnil; wrapper->encoding = encoding; wrapper->streamingComplete = 0; @@ -971,6 +1167,7 @@ void init_mysql2_result() { cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject); rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0); + rb_define_method(cMysql2Result, "field_types", rb_mysql_result_fetch_field_types, 0); rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0); rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0); rb_define_alias(cMysql2Result, "size", "count"); diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 0c25b24b6..3f58b1005 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -6,6 +6,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ typedef struct { VALUE fields; + VALUE fieldTypes; VALUE rows; VALUE client; VALUE encoding; diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index a70b38ef0..cc09838f5 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -9,6 +9,7 @@ r = Mysql2::Result.new expect { r.count }.to raise_error(TypeError) expect { r.fields }.to raise_error(TypeError) + expect { r.field_types }.to raise_error(TypeError) expect { r.size }.to raise_error(TypeError) expect { r.each }.to raise_error(TypeError) end @@ -119,6 +120,90 @@ end end + context "#field_types" do + let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") } + + it "method should exist" do + expect(test_result).to respond_to(:field_types) + end + + it "should return correct types" do + expected_types = %w[ + mediumint(9) + varchar(10) + bit(64) + bit(1) + tinyint(4) + tinyint(1) + smallint(6) + mediumint(9) + int(11) + bigint(20) + float(10,3) + float(10,3) + double(10,3) + decimal(10,3) + decimal(10,3) + date + datetime + timestamp + time + year(4) + char(10) + varchar(10) + binary(10) + varbinary(10) + tinyblob + tinytext + blob + text + mediumblob + mediumtext + longblob + longtext + enum + set + ] + + expect(test_result.field_types).to eql(expected_types) + end + + it "should return an array of field types in proper order" do + result = @client.query( + "SELECT cast('a' as char), " \ + "cast(1.2 as decimal(15, 2)), " \ + "cast(1.2 as decimal(15, 5)), " \ + "cast(1.2 as decimal(15, 4)), " \ + "cast(1.2 as decimal(15, 10)), " \ + "cast(1.2 as decimal(14, 0)), " \ + "cast(1.2 as decimal(15, 0)), " \ + "cast(1.2 as decimal(16, 0)), " \ + "cast(1.0 as decimal(16, 1))", + ) + + expected_types = %w[ + varchar(1) + decimal(15,2) + decimal(15,5) + decimal(15,4) + decimal(15,10) + decimal(14,0) + decimal(15,0) + decimal(16,0) + decimal(16,1) + ] + + expect(result.field_types).to eql(expected_types) + end + + it "should return json type on mysql 8.0" do + next unless /8.\d+.\d+/ =~ @client.server_info[:version] + + result = @client.query("SELECT JSON_OBJECT('key', 'value')") + expect(result.field_types).to eql(['json']) + end + end + context "streaming" do it "should maintain a count while streaming" do result = @client.query('SELECT 1', stream: true, cache_rows: false) From ca08712c6c8ea672df658bb25b931fea22555f27 Mon Sep 17 00:00:00 2001 From: Jean byroot Boussier Date: Sun, 5 Apr 2020 01:25:45 +0100 Subject: [PATCH 653/783] Register C global variables to Ruby GC (#1115) Since Ruby 2.7, C global variables holding references to Ruby objects must be declared to the GC even if they are also held from the Ruby side, otherwise a call to GC.compact might move them to another location. Co-authored-by: Jean Boussier --- ext/mysql2/client.c | 1 + ext/mysql2/mysql2_ext.c | 7 ++++++- ext/mysql2/result.c | 4 ++++ ext/mysql2/statement.c | 7 +++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 8f3d403b7..f64f74fdd 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1453,6 +1453,7 @@ void init_mysql2_client() { mMysql2 = rb_define_module("Mysql2"); Teach RDoc about Mysql2 constant. #endif cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject); + rb_global_variable(&cMysql2Client); rb_define_alloc_func(cMysql2Client, allocate); diff --git a/ext/mysql2/mysql2_ext.c b/ext/mysql2/mysql2_ext.c index 2eb8b6d94..8c887a867 100644 --- a/ext/mysql2/mysql2_ext.c +++ b/ext/mysql2/mysql2_ext.c @@ -4,9 +4,14 @@ VALUE mMysql2, cMysql2Error, cMysql2TimeoutError; /* Ruby Extension initializer */ void Init_mysql2() { - mMysql2 = rb_define_module("Mysql2"); + mMysql2 = rb_define_module("Mysql2"); + rb_global_variable(&mMysql2); + cMysql2Error = rb_const_get(mMysql2, rb_intern("Error")); + rb_global_variable(&cMysql2Error); + cMysql2TimeoutError = rb_const_get(cMysql2Error, rb_intern("TimeoutError")); + rb_global_variable(&cMysql2TimeoutError); init_mysql2_client(); init_mysql2_result(); diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index d1ae3628d..af0b7b4e6 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -1162,9 +1162,13 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ void init_mysql2_result() { cDate = rb_const_get(rb_cObject, rb_intern("Date")); + rb_global_variable(&cDate); cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); + rb_global_variable(&cDateTime); cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject); + rb_global_variable(&cMysql2Result); + rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0); rb_define_method(cMysql2Result, "field_types", rb_mysql_result_fetch_field_types, 0); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index cd6adf83f..0ade6c493 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -572,10 +572,17 @@ static VALUE rb_mysql_stmt_close(VALUE self) { void init_mysql2_statement() { cDate = rb_const_get(rb_cObject, rb_intern("Date")); + rb_global_variable(&cDate); + cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); + rb_global_variable(&cDateTime); + cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal")); + rb_global_variable(&cBigDecimal); cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); + rb_global_variable(&cMysql2Statement); + rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0); rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0); rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1); From 05bbe75f60c411c29223d265079269e30f25624a Mon Sep 17 00:00:00 2001 From: Mazin Power Date: Wed, 5 Aug 2020 22:06:09 +0200 Subject: [PATCH 654/783] Update README.md (#1131) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ca0a7b7dd..ba1cfff8a 100644 --- a/README.md +++ b/README.md @@ -572,7 +572,7 @@ This gem is tested with the following MySQL and MariaDB versions: ### Ruby on Rails / Active Record -* mysql2 0.5.x works with Rails / Active Record 5.0.7, 5.1.6, and higher. +* mysql2 0.5.x works with Rails / Active Record 4.2.11, 5.0.7, 5.1.6, and higher. * mysql2 0.4.x works with Rails / Active Record 4.2.5 - 5.0 and higher. * mysql2 0.3.x works with Rails / Active Record 3.1, 3.2, 4.x, 5.0. * mysql2 0.2.x works with Rails / Active Record 2.3 - 3.0. From e2503dc6e8ad02f8c2f4fc71006f9840694e319c Mon Sep 17 00:00:00 2001 From: Stefan Sundin Date: Tue, 8 Sep 2020 12:16:01 -0700 Subject: [PATCH 655/783] Improve Homebrew compatibility (#1135) * Allow linking to Homebrew's openssl. * Add more search paths for better Homebrew compatibility. --- ext/mysql2/extconf.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 1d0928604..70cb3c999 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -21,6 +21,9 @@ def add_ssl_defines(header) $CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if has_no_support end +# Homebrew openssl +$LDFLAGS << ' -L/usr/local/opt/openssl/lib' if RUBY_PLATFORM =~ /darwin/ + # 2.1+ have_func('rb_absint_size') have_func('rb_absint_singlebit_p') @@ -42,6 +45,9 @@ def add_ssl_defines(header) /usr/local/mysql-* /usr/local/lib/mysql5* /usr/local/opt/mysql5* + /usr/local/opt/mysql@* + /usr/local/opt/mysql-client + /usr/local/opt/mysql-client@* ].map { |dir| dir << '/bin' } # For those without HOMEBREW_ROOT in PATH From ad94a7956ec7de1ee5ec0f84d2577361227c24ac Mon Sep 17 00:00:00 2001 From: Felix Wolfsteller Date: Tue, 9 Feb 2021 16:01:05 +0100 Subject: [PATCH 656/783] Tiny README syntax markup for consistency (#1164) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba1cfff8a..efaf85304 100644 --- a/README.md +++ b/README.md @@ -441,7 +441,7 @@ Pass the `:as => :array` option to any of the above methods of configuration ### Array of Hashes -The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash +The default result type is set to `:hash`, but you can override a previous setting to something else with `:as => :hash` ### Timezones From c5fa553293465b142e9ea92bf38b9e217b41f358 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Tue, 23 Feb 2021 16:35:29 +0100 Subject: [PATCH 657/783] Add GitHub Actions. (#1154) Remove the CI cases on Travis migrated to GitHub Actions. --- .github/workflows/test.yml | 56 ++++++++++++++++++++++++++++ .travis.yml | 24 +----------- .travis_mysql57.sh | 5 +-- .travis_mysql80.sh | 5 +-- .travis_setup.sh | 76 +++++++++++++++++++++++++++++++++++++- Gemfile | 11 +++--- 6 files changed, 141 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..bd7a5ad65 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,56 @@ +name: Test +on: [push, pull_request] +jobs: + build: + name: >- + ${{ matrix.os }} ruby ${{ matrix.ruby }} ${{ matrix.db }} + # Run all the tests on the new environment as much as possible. + # https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.allow-failure }} + strategy: + matrix: + os: + # Use ubuntu-18.04 instead of ubuntu-20.04 temporarily, due to a failing test on mysql 8.0. + # https://github.com/brianmario/mysql2/issues/1165 + # - ubuntu-20.04 # focal + - ubuntu-18.04 # bionic + # - ubuntu-16.04 # xenial + ruby: + - '3.0' + - 2.7 + - 2.6 + - 2.5 + - 2.4 + - 2.3 + - 2.2 + - 2.1 + db: [''] + allow-failure: [false] + include: + # Allow failure due to Mysql2::Error: Unknown system variable 'session_track_system_variables'. + - {os: ubuntu-16.04, ruby: 2.4, db: mariadb10.0, allow-failure: true} + # Comment out due to .travis_setup.sh stucking. + # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1, allow-failure: false} + # Allow failure due to the issue #1165. + - {os: ubuntu-20.04, ruby: 2.4, db: mariadb10.3, allow-failure: true} + - {os: ubuntu-18.04, ruby: 2.4, db: mysql57, allow-failure: false} + # `service mysql restart` fails. + - {os: ubuntu-20.04, ruby: 2.4, db: mysql80, allow-failure: true} + - {os: ubuntu-18.04, ruby: 'head', db: '', allow-failure: true} + # On the fail-fast: true, it cancels all in-progress jobs + # if any matrix job fails unlike Travis fast_finish. + fail-fast: false + steps: + - uses: actions/checkout@v2 + # https://github.com/ruby/setup-ruby + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - run: ruby -v + - run: bundle install --without benchmarks development + - if: matrix.db != '' + run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV + - run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts + - run: bash .travis_setup.sh + - run: bundle exec rake diff --git a/.travis.yml b/.travis.yml index d83915a6d..aea04c68c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,18 +18,9 @@ addons: - mysql-server-5.6 - mysql-client-core-5.6 - mysql-client-5.6 -rvm: - - 2.7 - - 2.6 - - 2.5 - - 2.4 - - 2.3 - - 2.2 - - 2.1 - - 2.0.0 - - ruby-head matrix: include: + - rvm: 2.0.0 - rvm: 2.4 env: DB=mariadb10.0 addons: @@ -59,18 +50,6 @@ matrix: addons: hosts: - mysql2gem.example.com - - rvm: 2.4 - env: DB=mysql57 - dist: xenial - addons: - hosts: - - mysql2gem.example.com - - rvm: 2.4 - env: DB=mysql80 - dist: xenial - addons: - hosts: - - mysql2gem.example.com - os: osx rvm: 2.4 env: DB=mysql56 @@ -84,7 +63,6 @@ matrix: script: docker run --add-host=mysql2gem.example.com:127.0.0.1 -t mysql2 fast_finish: true allow_failures: - - rvm: ruby-head - os: osx rvm: 2.4 env: DB=mysql56 diff --git a/.travis_mysql57.sh b/.travis_mysql57.sh index 69ba5c67e..c8d2cd1a1 100644 --- a/.travis_mysql57.sh +++ b/.travis_mysql57.sh @@ -6,9 +6,8 @@ apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/5072E1F5.asc +# Verify the repository as add-apt-repository does not. +wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-5.7 add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-5.7' apt-get update -qq apt-get install -qq mysql-server libmysqlclient-dev - -# https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ -mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" diff --git a/.travis_mysql80.sh b/.travis_mysql80.sh index c658d3c2a..b4921b1f8 100644 --- a/.travis_mysql80.sh +++ b/.travis_mysql80.sh @@ -6,9 +6,8 @@ apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/5072E1F5.asc +# Verify the repository as add-apt-repository does not. +wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-8.0 add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-8.0' apt-get update -qq apt-get install -qq mysql-server libmysqlclient-dev - -# https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ -mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" diff --git a/.travis_setup.sh b/.travis_setup.sh index 6b68bdacd..50ecd5e48 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -2,6 +2,30 @@ set -eux +CHANGED_PWD=false +# Change the password recreating the root user on mariadb < 10.2 +# where ALTER USER is not available. +# https://stackoverflow.com/questions/56052177/ +CHANGED_PWD_BY_RECREATE=false + +# Install the default used DB if DB is not set. +if [[ -n ${GITHUB_ACTION-} && -z ${DB-} ]]; then + if command -v lsb_release > /dev/null; then + case "$(lsb_release -cs)" in + xenial | bionic) + sudo apt-get install -qq mysql-server-5.7 mysql-client-core-5.7 mysql-client-5.7 + CHANGED_PWD=true + ;; + focal) + sudo apt-get install -qq mysql-server-8.0 mysql-client-core-8.0 mysql-client-8.0 + CHANGED_PWD=true + ;; + *) + ;; + esac + fi +fi + # Install MySQL 5.5 if DB=mysql55 if [[ -n ${DB-} && x$DB =~ ^xmysql55 ]]; then sudo bash .travis_mysql55.sh @@ -10,21 +34,33 @@ fi # Install MySQL 5.7 if DB=mysql57 if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then sudo bash .travis_mysql57.sh + CHANGED_PWD=true fi # Install MySQL 8.0 if DB=mysql80 if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then sudo bash .travis_mysql80.sh + CHANGED_PWD=true fi # Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x if [[ -n ${DB-} && x$DB =~ ^xmariadb10.0 ]]; then - sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev + if [[ -n ${GITHUB_ACTION-} ]]; then + sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.0 libmariadb2 + CHANGED_PWD_BY_RECREATE=true + else + sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev + fi fi # Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x if [[ -n ${DB-} && x$DB =~ ^xmariadb10.1 ]]; then - sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev + if [[ -n ${GITHUB_ACTION-} ]]; then + sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.1 libmariadb-dev + CHANGED_PWD_BY_RECREATE=true + else + sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev + fi fi # Install MariaDB 10.2 if DB=mariadb10.2 @@ -33,6 +69,12 @@ if [[ -n ${DB-} && x$DB =~ ^xmariadb10.2 ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.2 libmariadbclient18 fi +# Install MariaDB 10.3 if DB=mariadb10.3 +if [[ -n ${GITHUB_ACTION-} && -n ${DB-} && x$DB =~ ^xmariadb10.3 ]]; then + sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.3 libmariadb-dev + CHANGED_PWD=true +fi + # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update @@ -52,6 +94,36 @@ if [[ x$OSTYPE =~ ^xdarwin ]]; then $(brew --prefix "$DB")/bin/mysql -u root -e 'CREATE DATABASE IF NOT EXISTS test' else mysqld --version + + if [[ -n ${GITHUB_ACTION-} && -f /etc/mysql/debian.cnf ]]; then + MYSQL_OPTS='--defaults-extra-file=/etc/mysql/debian.cnf' + # Install from packages in OS official packages. + if sudo grep -q debian-sys-maint /etc/mysql/debian.cnf; then + # bionic, focal + DB_SYS_USER=debian-sys-maint + else + # xenial + DB_SYS_USER=root + fi + else + # Install from official mysql packages. + MYSQL_OPTS='' + DB_SYS_USER=root + fi + + if [ "${CHANGED_PWD}" = true ]; then + # https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ + sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" \ + -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" + elif [ "${CHANGED_PWD_BY_RECREATE}" = true ]; then + sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" < 0.7.0' end -platforms :rbx do - gem 'rubysl-bigdecimal' - gem 'rubysl-drb' - gem 'rubysl-rake' -end +# On MRI Ruby >= 3.0, rubysl-rake causes the conflict on GitHub Actions. +# platforms :rbx do +# gem 'rubysl-bigdecimal' +# gem 'rubysl-drb' +# gem 'rubysl-rake' +# end From 0ee20536501848a354f1c3a007333167120c7457 Mon Sep 17 00:00:00 2001 From: Jean byroot Boussier Date: Tue, 23 Feb 2021 17:28:20 +0100 Subject: [PATCH 658/783] Run GC.verify_compaction_references on CI (#1155) Co-authored-by: Jean Boussier --- spec/spec_helper.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 08b660b89..b0bf598aa 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,12 @@ require 'yaml' DatabaseCredentials = YAML.load_file('spec/configuration.yml') +if GC.respond_to?(:verify_compaction_references) + # This method was added in Ruby 3.0.0. Calling it this way asks the GC to + # move objects around, helping to find object movement bugs. + GC.verify_compaction_references(double_heap: true, toward: :empty) +end + RSpec.configure do |config| config.disable_monkey_patching! From d4bb7309c908b0fca3ecd8689fe958ed7576076c Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Wed, 24 Feb 2021 08:08:54 +0100 Subject: [PATCH 659/783] GitHub Actions: Add CentOS/Fedora cases. (#1168) * Migrate CentOS case in Travis to GitHub Actions. * Add Fedora cases as allow failure. --- .dockerignore | 6 ++++ .github/workflows/container.yml | 33 +++++++++++++++++++ .github/workflows/{test.yml => ubuntu.yml} | 2 +- .travis.yml | 6 ---- .travis_Dockerfile_centos | 24 +++++++------- .travis_Dockerfile_fedora | 25 ++++++++++++++ .travis_centos.sh => .travis_container.sh | 8 ++--- ...up_centos.sh => .travis_setup_container.sh | 8 ++--- tasks/rspec.rake | 8 +++-- 9 files changed, 89 insertions(+), 31 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/container.yml rename .github/workflows/{test.yml => ubuntu.yml} (99%) create mode 100644 .travis_Dockerfile_fedora rename .travis_centos.sh => .travis_container.sh (53%) rename .travis_setup_centos.sh => .travis_setup_container.sh (54%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..6bb8db45f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +*.bundle +Gemfile.lock +spec/configuration.yml +spec/my.cnf +tmp +vendor diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 000000000..103f760c7 --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,33 @@ +# Test Linux distributions which do not exist on GitHub Actions +# by the containers. +name: Container +on: [push, pull_request] +jobs: + build: + name: >- + ${{ matrix.distro }} ${{ matrix.image }} + runs-on: ubuntu-20.04 # focal + continue-on-error: ${{ matrix.allow-failure }} + strategy: + matrix: + include: + - {distro: centos, image: 'centos:7', allow-failure: false} + # Fedora latest stable version + # Allow failure due to the following test failures. + # https://github.com/brianmario/mysql2/issues/965 + - {distro: fedora, image: 'fedora:latest', allow-failure: true} + # Fedora development version + # Allow failure due to the following test failures. + # https://github.com/brianmario/mysql2/issues/1152 + - {distro: fedora, image: 'fedora:rawhide', allow-failure: true} + # On the fail-fast: true, it cancels all in-progress jobs + # if any matrix job fails unlike Travis fast_finish. + fail-fast: false + steps: + - uses: actions/checkout@v2 + - run: docker build -t mysql2 -f .travis_Dockerfile_${{ matrix.distro }} --build-arg IMAGE=${{ matrix.image }} . + # Add the "--cap-add=... --security-opt seccomp=..." options + # as a temporary workaround to avoid the following issue + # in the Fedora >= 34 containers. + # https://bugzilla.redhat.com/show_bug.cgi?id=1900021 + - run: docker run --add-host=mysql2gem.example.com:127.0.0.1 -t --cap-add=SYS_PTRACE --security-opt seccomp=unconfined mysql2 diff --git a/.github/workflows/test.yml b/.github/workflows/ubuntu.yml similarity index 99% rename from .github/workflows/test.yml rename to .github/workflows/ubuntu.yml index bd7a5ad65..cd90bbeac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/ubuntu.yml @@ -1,4 +1,4 @@ -name: Test +name: Ubuntu on: [push, pull_request] jobs: build: diff --git a/.travis.yml b/.travis.yml index aea04c68c..dfcee6e0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ sudo: required dist: trusty -services: docker language: ruby bundler_args: --without benchmarks development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. @@ -56,11 +55,6 @@ matrix: addons: hosts: - mysql2gem.example.com - - rvm: 2.4 - env: DOCKER=centos - before_install: true - install: docker build -t mysql2 -f .travis_Dockerfile_centos . - script: docker run --add-host=mysql2gem.example.com:127.0.0.1 -t mysql2 fast_finish: true allow_failures: - os: osx diff --git a/.travis_Dockerfile_centos b/.travis_Dockerfile_centos index 50a23e98c..2adf7ffb9 100644 --- a/.travis_Dockerfile_centos +++ b/.travis_Dockerfile_centos @@ -1,23 +1,25 @@ -FROM centos:7 +ARG IMAGE=centos:7 +FROM ${IMAGE} WORKDIR /build COPY . . -RUN yum -y update -RUN yum -y install epel-release +RUN cat /etc/redhat-release +RUN yum -yq update +RUN yum -yq install epel-release # The options are to install faster. -RUN yum -y install \ +RUN yum -yq install \ --setopt=deltarpm=0 \ --setopt=install_weak_deps=false \ --setopt=tsflags=nodocs \ - mariadb-server \ - mariadb-devel \ - ruby-devel \ - git \ gcc \ gcc-c++ \ - make -RUN gem install --no-document "rubygems-update:~>2.7" && update_rubygems + git \ + make \ + mariadb-devel \ + mariadb-server \ + ruby-devel +RUN gem install --no-document "rubygems-update:~>2.7" && update_rubygems > /dev/null RUN gem install --no-document "bundler:~>1.17" -CMD sh .travis_centos.sh +CMD bash .travis_container.sh diff --git a/.travis_Dockerfile_fedora b/.travis_Dockerfile_fedora new file mode 100644 index 000000000..2598fbc8a --- /dev/null +++ b/.travis_Dockerfile_fedora @@ -0,0 +1,25 @@ +ARG IMAGE=fedora:latest +FROM ${IMAGE} + +WORKDIR /build +COPY . . + +RUN cat /etc/fedora-release +RUN dnf -yq update +# The options are to install faster. +RUN dnf -yq install \ + --setopt=deltarpm=0 \ + --setopt=install_weak_deps=false \ + --setopt=tsflags=nodocs \ + gcc \ + gcc-c++ \ + git \ + make \ + mariadb-connector-c-devel \ + mariadb-server \ + redhat-rpm-config \ + ruby-devel \ + rubygem-bigdecimal \ + rubygem-bundler + +CMD bash .travis_container.sh diff --git a/.travis_centos.sh b/.travis_container.sh similarity index 53% rename from .travis_centos.sh rename to .travis_container.sh index 09e4c4a5c..2b216b4a5 100644 --- a/.travis_centos.sh +++ b/.travis_container.sh @@ -2,12 +2,10 @@ set -eux -# Start mysqld service. -sh .travis_setup_centos.sh - +ruby -v bundle install --path vendor/bundle --without benchmarks development -# USER environment value is not set as a default in the container environment. -export USER=root +# Start mysqld service. +bash .travis_setup_container.sh bundle exec rake diff --git a/.travis_setup_centos.sh b/.travis_setup_container.sh similarity index 54% rename from .travis_setup_centos.sh rename to .travis_setup_container.sh index 78a9954c9..6bed4d579 100644 --- a/.travis_setup_centos.sh +++ b/.travis_setup_container.sh @@ -4,18 +4,14 @@ set -eux MYSQL_TEST_LOG="$(pwd)/mysql.log" -# mysql_install_db uses wrong path for resolveip -# https://jira.mariadb.org/browse/MDEV-18563 -# https://travis-ci.org/brianmario/mysql2/jobs/615263124#L2840 -ln -s "$(command -v resolveip)" /usr/libexec/resolveip - mysql_install_db \ --log-error="${MYSQL_TEST_LOG}" /usr/libexec/mysqld \ - --user=root \ + --user="$(id -un)" \ --log-error="${MYSQL_TEST_LOG}" \ --ssl & sleep 3 cat ${MYSQL_TEST_LOG} +/usr/libexec/mysqld --version mysql -u root -e 'CREATE DATABASE IF NOT EXISTS test' diff --git a/tasks/rspec.rake b/tasks/rspec.rake index ea8dc2258..efff7a25c 100644 --- a/tasks/rspec.rake +++ b/tasks/rspec.rake @@ -30,6 +30,10 @@ rescue LoadError puts "rspec, or one of its dependencies, is not available. Install it with: sudo gem install rspec" end +# Get the value from `id` command as the environment variable USER is +# not defined in a container. +user_name = ENV['USER'] || `id -un`.rstrip + file 'spec/configuration.yml' => 'spec/configuration.yml.example' do |task| CLEAN.exclude task.name src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) @@ -37,7 +41,7 @@ file 'spec/configuration.yml' => 'spec/configuration.yml.example' do |task| File.open(dst_path, 'w') do |dst_file| File.open(src_path).each_line do |line| - dst_file.write line.gsub(/LOCALUSERNAME/, ENV['USER']) + dst_file.write line.gsub(/LOCALUSERNAME/, user_name) end end end @@ -49,7 +53,7 @@ file 'spec/my.cnf' => 'spec/my.cnf.example' do |task| File.open(dst_path, 'w') do |dst_file| File.open(src_path).each_line do |line| - dst_file.write line.gsub(/LOCALUSERNAME/, ENV['USER']) + dst_file.write line.gsub(/LOCALUSERNAME/, user_name) end end end From 88fddbc58725909c739c51a69b4f6f3239445a1c Mon Sep 17 00:00:00 2001 From: usa Date: Wed, 24 Feb 2021 17:15:04 +0900 Subject: [PATCH 660/783] Guard sql from GC (#1150) In `rb_mysql_query()`, the raw pointer of the sql string is extracted, and it is passed to `do_send_query()` via `args`. `do_send_query()` internally releases the GVL, then ruby might do GC in the function. Then, the sql string may be GC'ed, and causes SEGV. Therefore, should guard the sql string until `do_send_query()` ends. --- ext/mysql2/client.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index f64f74fdd..267d2b067 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -797,6 +797,7 @@ static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) { #ifndef _WIN32 rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0); + (void)RB_GC_GUARD(sql); if (rb_hash_aref(current, sym_async) == Qtrue) { return Qnil; @@ -810,6 +811,7 @@ static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) { } #else do_send_query((VALUE)&args); + (void)RB_GC_GUARD(sql); /* this will just block until the result is ready */ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self); From 557bd5f410f3d496927c1ab1bf3fc2456dac3e4d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 24 Feb 2021 11:57:21 -0800 Subject: [PATCH 661/783] Migrating CI is no small feat, thank you @junaruga! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index efaf85304..0f781b44e 100644 --- a/README.md +++ b/README.md @@ -666,3 +666,4 @@ though. * [John Cant](http://github.com/johncant) - polishing and updating Prepared Statements support * [Justin Case](http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged * [Tamir Duberstein](http://github.com/tamird) - for help with timeouts and all around updates and cleanups +* [Jun Aruga](http://github.com/junaruga) - for migrating CI tests to GitHub Actions and other improvements From 3dfdeed34668d83debf4252fdbf8aa359e762593 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 26 Feb 2021 11:26:45 +0100 Subject: [PATCH 662/783] Rename GITHUB_ACTION to GITHUB_ACTIONS. GITHUB_ACTIONS showing always true on GitHub Actions environment is a right environment variable See https://docs.github.com/en/actions/reference/environment-variables . --- .travis_setup.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis_setup.sh b/.travis_setup.sh index 50ecd5e48..23b88bcfb 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -9,7 +9,7 @@ CHANGED_PWD=false CHANGED_PWD_BY_RECREATE=false # Install the default used DB if DB is not set. -if [[ -n ${GITHUB_ACTION-} && -z ${DB-} ]]; then +if [[ -n ${GITHUB_ACTIONS-} && -z ${DB-} ]]; then if command -v lsb_release > /dev/null; then case "$(lsb_release -cs)" in xenial | bionic) @@ -45,7 +45,7 @@ fi # Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x if [[ -n ${DB-} && x$DB =~ ^xmariadb10.0 ]]; then - if [[ -n ${GITHUB_ACTION-} ]]; then + if [[ -n ${GITHUB_ACTIONS-} ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.0 libmariadb2 CHANGED_PWD_BY_RECREATE=true else @@ -55,7 +55,7 @@ fi # Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x if [[ -n ${DB-} && x$DB =~ ^xmariadb10.1 ]]; then - if [[ -n ${GITHUB_ACTION-} ]]; then + if [[ -n ${GITHUB_ACTIONS-} ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.1 libmariadb-dev CHANGED_PWD_BY_RECREATE=true else @@ -70,7 +70,7 @@ if [[ -n ${DB-} && x$DB =~ ^xmariadb10.2 ]]; then fi # Install MariaDB 10.3 if DB=mariadb10.3 -if [[ -n ${GITHUB_ACTION-} && -n ${DB-} && x$DB =~ ^xmariadb10.3 ]]; then +if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.3 ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.3 libmariadb-dev CHANGED_PWD=true fi @@ -95,7 +95,7 @@ if [[ x$OSTYPE =~ ^xdarwin ]]; then else mysqld --version - if [[ -n ${GITHUB_ACTION-} && -f /etc/mysql/debian.cnf ]]; then + if [[ -n ${GITHUB_ACTIONS-} && -f /etc/mysql/debian.cnf ]]; then MYSQL_OPTS='--defaults-extra-file=/etc/mysql/debian.cnf' # Install from packages in OS official packages. if sudo grep -q debian-sys-maint /etc/mysql/debian.cnf; then From 123186315c1dfc6c7cd7564d7092939a4e1c42f8 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 26 Feb 2021 11:30:26 +0100 Subject: [PATCH 663/783] Rename CHANGED_PWD to CHANGED_PASSWORD. It's better to know the meaning. --- .travis_setup.sh | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/.travis_setup.sh b/.travis_setup.sh index 23b88bcfb..cbe490be0 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -2,11 +2,12 @@ set -eux -CHANGED_PWD=false -# Change the password recreating the root user on mariadb < 10.2 +# Change the password to be empty. +CHANGED_PASSWORD=false +# Change the password to be empty, recreating the root user on mariadb < 10.2 # where ALTER USER is not available. # https://stackoverflow.com/questions/56052177/ -CHANGED_PWD_BY_RECREATE=false +CHANGED_PASSWORD_BY_RECREATE=false # Install the default used DB if DB is not set. if [[ -n ${GITHUB_ACTIONS-} && -z ${DB-} ]]; then @@ -14,11 +15,11 @@ if [[ -n ${GITHUB_ACTIONS-} && -z ${DB-} ]]; then case "$(lsb_release -cs)" in xenial | bionic) sudo apt-get install -qq mysql-server-5.7 mysql-client-core-5.7 mysql-client-5.7 - CHANGED_PWD=true + CHANGED_PASSWORD=true ;; focal) sudo apt-get install -qq mysql-server-8.0 mysql-client-core-8.0 mysql-client-8.0 - CHANGED_PWD=true + CHANGED_PASSWORD=true ;; *) ;; @@ -34,20 +35,20 @@ fi # Install MySQL 5.7 if DB=mysql57 if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then sudo bash .travis_mysql57.sh - CHANGED_PWD=true + CHANGED_PASSWORD=true fi # Install MySQL 8.0 if DB=mysql80 if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then sudo bash .travis_mysql80.sh - CHANGED_PWD=true + CHANGED_PASSWORD=true fi # Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x if [[ -n ${DB-} && x$DB =~ ^xmariadb10.0 ]]; then if [[ -n ${GITHUB_ACTIONS-} ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.0 libmariadb2 - CHANGED_PWD_BY_RECREATE=true + CHANGED_PASSWORD_BY_RECREATE=true else sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev fi @@ -57,7 +58,7 @@ fi if [[ -n ${DB-} && x$DB =~ ^xmariadb10.1 ]]; then if [[ -n ${GITHUB_ACTIONS-} ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.1 libmariadb-dev - CHANGED_PWD_BY_RECREATE=true + CHANGED_PASSWORD_BY_RECREATE=true else sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev fi @@ -72,7 +73,7 @@ fi # Install MariaDB 10.3 if DB=mariadb10.3 if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.3 ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.3 libmariadb-dev - CHANGED_PWD=true + CHANGED_PASSWORD=true fi # Install MySQL if OS=darwin @@ -111,11 +112,11 @@ else DB_SYS_USER=root fi - if [ "${CHANGED_PWD}" = true ]; then + if [ "${CHANGED_PASSWORD}" = true ]; then # https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" \ -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" - elif [ "${CHANGED_PWD_BY_RECREATE}" = true ]; then + elif [ "${CHANGED_PASSWORD_BY_RECREATE}" = true ]; then sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" < Date: Wed, 24 Feb 2021 12:44:38 +0100 Subject: [PATCH 664/783] GitHub Actions: Add macOS cases. * Migrate macOS cases in Travis to GitHub Actions. * Increase the expected time in the test to avoid the following random failures on macOS. ``` 1) Mysql2::Client#query threaded queries should be supported Failure/Error: expect(stop - start).to be_within(0.1).of(sleep_time) expected 0.632204999999999 to be within 0.1 of 0.5 # ./spec/mysql2/client_spec.rb:711:in `block (3 levels) in ' ``` ``` 3) Mysql2::Client#query should run signal handlers while waiting for a response Failure/Error: expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.1).of(query_time) expected 1.1042130000000157 to be within 0.1 of 1.0 # ./spec/mysql2/client_spec.rb:632:in `block (3 levels) in ' ``` --- .github/workflows/{ubuntu.yml => build.yml} | 8 +++- .travis.yml | 9 ---- .travis_setup.sh | 51 +++++++++++---------- spec/mysql2/client_spec.rb | 4 +- 4 files changed, 37 insertions(+), 35 deletions(-) rename .github/workflows/{ubuntu.yml => build.yml} (83%) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/build.yml similarity index 83% rename from .github/workflows/ubuntu.yml rename to .github/workflows/build.yml index cd90bbeac..f6291fcf3 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Ubuntu +name: Build on: [push, pull_request] jobs: build: @@ -38,6 +38,12 @@ jobs: # `service mysql restart` fails. - {os: ubuntu-20.04, ruby: 2.4, db: mysql80, allow-failure: true} - {os: ubuntu-18.04, ruby: 'head', db: '', allow-failure: true} + # db: the DB's brew package name in macOS case. + # Allow failure due to the following test failures. + # https://github.com/brianmario/mysql2/issues/965 + - {os: macos-latest, ruby: 2.4, db: 'mariadb@10.4', allow-failure: true} + # Allow failure due to Mysql2::Error: Unknown system variable 'session_track_system_variables'. + - {os: macos-latest, ruby: 2.4, db: 'mysql@5.6', allow-failure: true} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. fail-fast: false diff --git a/.travis.yml b/.travis.yml index dfcee6e0c..03129303b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,14 +49,5 @@ matrix: addons: hosts: - mysql2gem.example.com - - os: osx - rvm: 2.4 - env: DB=mysql56 - addons: - hosts: - - mysql2gem.example.com fast_finish: true allow_failures: - - os: osx - rvm: 2.4 - env: DB=mysql56 diff --git a/.travis_setup.sh b/.travis_setup.sh index cbe490be0..30ed9445c 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -76,11 +76,23 @@ if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.3 ]]; then CHANGED_PASSWORD=true fi -# Install MySQL if OS=darwin +# Install MySQL/MariaDB if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then - brew update + brew update > /dev/null + + # Check available packages. + for KEYWORD in mysql mariadb; do + brew search "${KEYWORD}" + done + brew install "$DB" mariadb-connector-c - $(brew --prefix "$DB")/bin/mysql.server start + DB_PREFIX="$(brew --prefix "${DB}")" + export PATH="${DB_PREFIX}/bin:${PATH}" + export LDFLAGS="-L${DB_PREFIX}/lib" + export CPPFLAGS="-I${DB_PREFIX}/include" + + mysql.server start + CHANGED_PASSWORD_BY_RECREATE=true fi # TODO: get SSL working on OS X in Travis @@ -89,13 +101,11 @@ if ! [[ x$OSTYPE =~ ^xdarwin ]]; then sudo service mysql restart fi -# Print the MySQL version and create the test DB -if [[ x$OSTYPE =~ ^xdarwin ]]; then - $(brew --prefix "$DB")/bin/mysqld --version - $(brew --prefix "$DB")/bin/mysql -u root -e 'CREATE DATABASE IF NOT EXISTS test' -else - mysqld --version +mysqld --version +MYSQL_OPTS='' +DB_SYS_USER=root +if ! [[ x$OSTYPE =~ ^xdarwin ]]; then if [[ -n ${GITHUB_ACTIONS-} && -f /etc/mysql/debian.cnf ]]; then MYSQL_OPTS='--defaults-extra-file=/etc/mysql/debian.cnf' # Install from packages in OS official packages. @@ -106,25 +116,20 @@ else # xenial DB_SYS_USER=root fi - else - # Install from official mysql packages. - MYSQL_OPTS='' - DB_SYS_USER=root fi +fi - if [ "${CHANGED_PASSWORD}" = true ]; then - # https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ - sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" \ - -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" - elif [ "${CHANGED_PASSWORD_BY_RECREATE}" = true ]; then - sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" < Date: Wed, 24 Feb 2021 22:45:50 +0100 Subject: [PATCH 665/783] Use MySQL/MariaDB latest versions on macOS. --- .github/workflows/build.yml | 12 ++++++++---- .travis_setup.sh | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6291fcf3..1e57afa5a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,12 +38,16 @@ jobs: # `service mysql restart` fails. - {os: ubuntu-20.04, ruby: 2.4, db: mysql80, allow-failure: true} - {os: ubuntu-18.04, ruby: 'head', db: '', allow-failure: true} - # db: the DB's brew package name in macOS case. + # db: A DB's brew package name in macOS case. + # Set a name "db: 'name@X.Y'" when using an old version. + # MariaDB lastet version # Allow failure due to the following test failures. # https://github.com/brianmario/mysql2/issues/965 - - {os: macos-latest, ruby: 2.4, db: 'mariadb@10.4', allow-failure: true} - # Allow failure due to Mysql2::Error: Unknown system variable 'session_track_system_variables'. - - {os: macos-latest, ruby: 2.4, db: 'mysql@5.6', allow-failure: true} + # https://github.com/brianmario/mysql2/issues/1152 + - {os: macos-latest, ruby: 2.4, db: mariadb, allow-failure: true} + # MySQL latest version + # Allow failure due to the issue #1165. + - {os: macos-latest, ruby: 2.4, db: mysql, allow-failure: true} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. fail-fast: false diff --git a/.travis_setup.sh b/.travis_setup.sh index 30ed9445c..09f25a1d2 100644 --- a/.travis_setup.sh +++ b/.travis_setup.sh @@ -85,7 +85,8 @@ if [[ x$OSTYPE =~ ^xdarwin ]]; then brew search "${KEYWORD}" done - brew install "$DB" mariadb-connector-c + brew info "$DB" + brew install "$DB" DB_PREFIX="$(brew --prefix "${DB}")" export PATH="${DB_PREFIX}/bin:${PATH}" export LDFLAGS="-L${DB_PREFIX}/lib" From dc0b626e7ccfdf6007609ba356112883cb4326f7 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Tue, 2 Mar 2021 07:37:14 +0100 Subject: [PATCH 666/783] Add GitHub Actions badges. (#1172) --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f781b44e..72ff37ff0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # Mysql2 - A modern, simple and very fast MySQL library for Ruby - binding to libmysql -Travis CI [![Travis CI Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2) -Appveyor CI [![Appveyor CI Status](https://ci.appveyor.com/api/projects/status/github/sodabrew/mysql2)](https://ci.appveyor.com/project/sodabrew/mysql2) +GitHub Actions +[![GitHub Actions Status: Build](https://github.com/brianmario/mysql2/actions/workflows/build.yml/badge.svg)](https://github.com/brianmario/mysql2/actions/workflows/build.yml) +[![GitHub Actions Status: Container](https://github.com/brianmario/mysql2/actions/workflows/container.yml/badge.svg)](https://github.com/brianmario/mysql2/actions/workflows/container.yml) +Travis CI +[![Travis CI Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2) +Appveyor CI +[![Appveyor CI Status](https://ci.appveyor.com/api/projects/status/github/sodabrew/mysql2)](https://ci.appveyor.com/project/sodabrew/mysql2) The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results. Some database libraries out there serve as direct 1:1 mappings of the already complex C APIs available. From cab1d34aabd7515053429203e7397cdd1f2ac234 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Tue, 2 Mar 2021 19:21:13 +0100 Subject: [PATCH 667/783] Rename .travis_* files to ci/*. (#1171) The "ci" is the commonly used keyword not depending on the specific CI services. --- .github/workflows/build.yml | 4 ++-- .github/workflows/container.yml | 2 +- .travis.yml | 2 +- .travis_Dockerfile_centos => ci/Dockerfile_centos | 2 +- .travis_Dockerfile_fedora => ci/Dockerfile_fedora | 2 +- .travis_container.sh => ci/container.sh | 2 +- .travis_mysql55.sh => ci/mysql55.sh | 0 .travis_mysql57.sh => ci/mysql57.sh | 0 .travis_mysql80.sh => ci/mysql80.sh | 0 .travis_setup.sh => ci/setup.sh | 8 ++++---- .travis_setup_container.sh => ci/setup_container.sh | 0 .travis_ssl.sh => ci/ssl.sh | 0 12 files changed, 11 insertions(+), 11 deletions(-) rename .travis_Dockerfile_centos => ci/Dockerfile_centos (94%) rename .travis_Dockerfile_fedora => ci/Dockerfile_fedora (93%) rename .travis_container.sh => ci/container.sh (82%) rename .travis_mysql55.sh => ci/mysql55.sh (100%) rename .travis_mysql57.sh => ci/mysql57.sh (100%) rename .travis_mysql80.sh => ci/mysql80.sh (100%) rename .travis_setup.sh => ci/setup.sh (97%) rename .travis_setup_container.sh => ci/setup_container.sh (100%) rename .travis_ssl.sh => ci/ssl.sh (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e57afa5a..b16e79417 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: include: # Allow failure due to Mysql2::Error: Unknown system variable 'session_track_system_variables'. - {os: ubuntu-16.04, ruby: 2.4, db: mariadb10.0, allow-failure: true} - # Comment out due to .travis_setup.sh stucking. + # Comment out due to ci/setup.sh stucking. # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1, allow-failure: false} # Allow failure due to the issue #1165. - {os: ubuntu-20.04, ruby: 2.4, db: mariadb10.3, allow-failure: true} @@ -62,5 +62,5 @@ jobs: - if: matrix.db != '' run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV - run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts - - run: bash .travis_setup.sh + - run: bash ci/setup.sh - run: bundle exec rake diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 103f760c7..d431cd957 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -25,7 +25,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v2 - - run: docker build -t mysql2 -f .travis_Dockerfile_${{ matrix.distro }} --build-arg IMAGE=${{ matrix.image }} . + - run: docker build -t mysql2 -f ci/Dockerfile_${{ matrix.distro }} --build-arg IMAGE=${{ matrix.image }} . # Add the "--cap-add=... --security-opt seccomp=..." options # as a temporary workaround to avoid the following issue # in the Fedora >= 34 containers. diff --git a/.travis.yml b/.travis.yml index 03129303b..6a2c40550 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ before_install: - gem update --system --quiet || gem update --system 2.7.10 --quiet - gem update bundler - gem --version - - bash .travis_setup.sh + - bash ci/setup.sh addons: hosts: - mysql2gem.example.com diff --git a/.travis_Dockerfile_centos b/ci/Dockerfile_centos similarity index 94% rename from .travis_Dockerfile_centos rename to ci/Dockerfile_centos index 2adf7ffb9..2e07e31ea 100644 --- a/.travis_Dockerfile_centos +++ b/ci/Dockerfile_centos @@ -22,4 +22,4 @@ RUN yum -yq install \ RUN gem install --no-document "rubygems-update:~>2.7" && update_rubygems > /dev/null RUN gem install --no-document "bundler:~>1.17" -CMD bash .travis_container.sh +CMD bash ci/container.sh diff --git a/.travis_Dockerfile_fedora b/ci/Dockerfile_fedora similarity index 93% rename from .travis_Dockerfile_fedora rename to ci/Dockerfile_fedora index 2598fbc8a..46c4b26a6 100644 --- a/.travis_Dockerfile_fedora +++ b/ci/Dockerfile_fedora @@ -22,4 +22,4 @@ RUN dnf -yq install \ rubygem-bigdecimal \ rubygem-bundler -CMD bash .travis_container.sh +CMD bash ci/container.sh diff --git a/.travis_container.sh b/ci/container.sh similarity index 82% rename from .travis_container.sh rename to ci/container.sh index 2b216b4a5..00e2ecbdb 100644 --- a/.travis_container.sh +++ b/ci/container.sh @@ -6,6 +6,6 @@ ruby -v bundle install --path vendor/bundle --without benchmarks development # Start mysqld service. -bash .travis_setup_container.sh +bash ci/setup_container.sh bundle exec rake diff --git a/.travis_mysql55.sh b/ci/mysql55.sh similarity index 100% rename from .travis_mysql55.sh rename to ci/mysql55.sh diff --git a/.travis_mysql57.sh b/ci/mysql57.sh similarity index 100% rename from .travis_mysql57.sh rename to ci/mysql57.sh diff --git a/.travis_mysql80.sh b/ci/mysql80.sh similarity index 100% rename from .travis_mysql80.sh rename to ci/mysql80.sh diff --git a/.travis_setup.sh b/ci/setup.sh similarity index 97% rename from .travis_setup.sh rename to ci/setup.sh index 09f25a1d2..97e89818a 100644 --- a/.travis_setup.sh +++ b/ci/setup.sh @@ -29,18 +29,18 @@ fi # Install MySQL 5.5 if DB=mysql55 if [[ -n ${DB-} && x$DB =~ ^xmysql55 ]]; then - sudo bash .travis_mysql55.sh + sudo bash ci/mysql55.sh fi # Install MySQL 5.7 if DB=mysql57 if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then - sudo bash .travis_mysql57.sh + sudo bash ci/mysql57.sh CHANGED_PASSWORD=true fi # Install MySQL 8.0 if DB=mysql80 if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then - sudo bash .travis_mysql80.sh + sudo bash ci/mysql80.sh CHANGED_PASSWORD=true fi @@ -98,7 +98,7 @@ fi # TODO: get SSL working on OS X in Travis if ! [[ x$OSTYPE =~ ^xdarwin ]]; then - sudo bash .travis_ssl.sh + sudo bash ci/ssl.sh sudo service mysql restart fi diff --git a/.travis_setup_container.sh b/ci/setup_container.sh similarity index 100% rename from .travis_setup_container.sh rename to ci/setup_container.sh diff --git a/.travis_ssl.sh b/ci/ssl.sh similarity index 100% rename from .travis_ssl.sh rename to ci/ssl.sh From 692adc6fdbbb12c63fa5df1f65b014802dc72e12 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Mon, 15 Mar 2021 21:25:29 +0100 Subject: [PATCH 668/783] Make the benchmarks group optional. (#1173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's for a contributor's smooth onboarding. This commit fixes the failed installation of the benchmarks group on Ruby >= 2.4 by the following error. ``` $ bundle install --without development ... Gem::Ext::BuildError: ERROR: Failed to build gem native extension. ... current directory: /opt/hostedtoolcache/Ruby/2.4.10/x64/lib/ruby/gems/2.4.0/gems/mysql-2.9.1/ext/mysql_api make "DESTDIR=" compiling mysql.c mysql.c: In function ‘stmt_bind_result’: mysql.c:1320:74: error: ‘rb_cFixnum’ undeclared (first use in this function); did you mean ‘rb_isalnum’? else if (argv[i] == rb_cNumeric || argv[i] == rb_cInteger || argv[i] == rb_cFixnum) ^~~~~~~~~~ rb_isalnum ... ``` You can install the benchmarks group like this. ``` $ bundle install --with benchmarks ``` See https://bundler.io/man/gemfile.5.html#BLOCK-FORM-OF-SOURCE-GIT-PATH-GROUP-and-PLATFORMS . --- .github/workflows/build.yml | 2 +- .travis.yml | 2 +- Gemfile | 5 +++-- appveyor.yml | 2 +- ci/container.sh | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b16e79417..26edcc54e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,7 +58,7 @@ jobs: with: ruby-version: ${{ matrix.ruby }} - run: ruby -v - - run: bundle install --without benchmarks development + - run: bundle install --without development - if: matrix.db != '' run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV - run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts diff --git a/.travis.yml b/.travis.yml index 6a2c40550..d5a64afc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ sudo: required dist: trusty language: ruby -bundler_args: --without benchmarks development +bundler_args: --without development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - gem --version diff --git a/Gemfile b/Gemfile index 4257eb65b..3dac0c0b1 100644 --- a/Gemfile +++ b/Gemfile @@ -19,12 +19,13 @@ group :test do gem 'rubocop', '~> 0.50.0' end -group :benchmarks do +group :benchmarks, optional: true do gem 'activerecord', '>= 3.0' gem 'benchmark-ips' gem 'do_mysql' gem 'faker' - gem 'mysql' + # The installation of the mysql latest version 2.9.1 fails on Ruby >= 2.4. + gem 'mysql' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4') gem 'sequel' end diff --git a/appveyor.yml b/appveyor.yml index 0ce822d22..cde312693 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ install: - ruby --version - gem --version - bundler --version - - bundle install --without benchmarks --path vendor/bundle + - bundle install --path vendor/bundle - IF DEFINED MINGW_PACKAGE_PREFIX (ridk exec pacman -S --noconfirm --needed %MINGW_PACKAGE_PREFIX%-libmariadbclient) build_script: - bundle exec rake compile diff --git a/ci/container.sh b/ci/container.sh index 00e2ecbdb..e07678699 100644 --- a/ci/container.sh +++ b/ci/container.sh @@ -3,7 +3,7 @@ set -eux ruby -v -bundle install --path vendor/bundle --without benchmarks development +bundle install --path vendor/bundle --without development # Start mysqld service. bash ci/setup_container.sh From 706a43bebd00eae24f3c3ea2a3bf3db383b69561 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Mon, 15 Mar 2021 21:28:54 +0100 Subject: [PATCH 669/783] Verify the testing database before running tests. (#1174) To avoid seeing a lot of test failures on the database connection error, and guide the user how to fix. Sort the before after blocks in the following actually called order. See https://relishapp.com/rspec/rspec-core/v/2-99/docs/hooks/before-and-after-hooks --- spec/spec_helper.rb | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b0bf598aa..7a2a36196 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -57,12 +57,21 @@ def clock_time end end - config.before :each do - @client = new_client - end - - config.after :each do - @clients.each(&:close) + config.before(:suite) do + begin + new_client + rescue Mysql2::Error => e + username = DatabaseCredentials['root']['username'] + database = DatabaseCredentials['root']['database'] + message = %( +An error occurred while connecting to the testing database server. +Make sure that the database server is running. +Make sure that `mysql -u #{username} [options] #{database}` succeeds by the root user config in spec/configuration.yml. +Make sure that the testing database '#{database}' exists. If it does not exist, create it. +) + warn message + raise e + end end config.before(:all) do @@ -126,4 +135,12 @@ def clock_time ] end end + + config.before(:each) do + @client = new_client + end + + config.after(:each) do + @clients.each(&:close) + end end From 9c8cf072cf9c1284790cbe1f1ff2adcc9e27a7b3 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 4 Mar 2021 15:05:20 +0100 Subject: [PATCH 670/783] Fix wrong comments. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26edcc54e..bc2eca10c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,10 +32,10 @@ jobs: - {os: ubuntu-16.04, ruby: 2.4, db: mariadb10.0, allow-failure: true} # Comment out due to ci/setup.sh stucking. # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1, allow-failure: false} - # Allow failure due to the issue #1165. + # `service mysql restart` fails. - {os: ubuntu-20.04, ruby: 2.4, db: mariadb10.3, allow-failure: true} - {os: ubuntu-18.04, ruby: 2.4, db: mysql57, allow-failure: false} - # `service mysql restart` fails. + # Allow failure due to the issue #1165. - {os: ubuntu-20.04, ruby: 2.4, db: mysql80, allow-failure: true} - {os: ubuntu-18.04, ruby: 'head', db: '', allow-failure: true} # db: A DB's brew package name in macOS case. From 70ab0dc405c76381748483b3854057ba078a0d93 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 4 Mar 2021 15:11:20 +0100 Subject: [PATCH 671/783] GitHub Actions: Set the default value to allow-failure. --- .github/workflows/build.yml | 7 +++---- .github/workflows/container.yml | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bc2eca10c..7edefac63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ jobs: # Run all the tests on the new environment as much as possible. # https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.allow-failure }} + continue-on-error: ${{ matrix.allow-failure || false }} strategy: matrix: os: @@ -26,15 +26,14 @@ jobs: - 2.2 - 2.1 db: [''] - allow-failure: [false] include: # Allow failure due to Mysql2::Error: Unknown system variable 'session_track_system_variables'. - {os: ubuntu-16.04, ruby: 2.4, db: mariadb10.0, allow-failure: true} # Comment out due to ci/setup.sh stucking. - # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1, allow-failure: false} + # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1} # `service mysql restart` fails. - {os: ubuntu-20.04, ruby: 2.4, db: mariadb10.3, allow-failure: true} - - {os: ubuntu-18.04, ruby: 2.4, db: mysql57, allow-failure: false} + - {os: ubuntu-18.04, ruby: 2.4, db: mysql57} # Allow failure due to the issue #1165. - {os: ubuntu-20.04, ruby: 2.4, db: mysql80, allow-failure: true} - {os: ubuntu-18.04, ruby: 'head', db: '', allow-failure: true} diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index d431cd957..6776197ba 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -7,11 +7,11 @@ jobs: name: >- ${{ matrix.distro }} ${{ matrix.image }} runs-on: ubuntu-20.04 # focal - continue-on-error: ${{ matrix.allow-failure }} + continue-on-error: ${{ matrix.allow-failure || false }} strategy: matrix: include: - - {distro: centos, image: 'centos:7', allow-failure: false} + - {distro: centos, image: 'centos:7'} # Fedora latest stable version # Allow failure due to the following test failures. # https://github.com/brianmario/mysql2/issues/965 From ec1dac4f58e95e2725a0f9febc994e9c2d06b7f9 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 4 Mar 2021 15:22:32 +0100 Subject: [PATCH 672/783] Migrate Ruby 2.0.0 case in Travis to GitHub Actions. As the CentOS 7 system Ruby is the fixed version 2.0.0, we can remove the Ruby 2.0.0 case in Travis. Remove `sudo: required` syntax in Travis, as the key `sudo` is deprecated and it has no effect anymore according to the Travis "View config" page. --- .github/workflows/container.yml | 5 +++-- .travis.yml | 10 ---------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 6776197ba..39a13bbce 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -5,13 +5,14 @@ on: [push, pull_request] jobs: build: name: >- - ${{ matrix.distro }} ${{ matrix.image }} + ${{ matrix.distro }} ${{ matrix.image }} ${{ matrix.name_extra || '' }} runs-on: ubuntu-20.04 # focal continue-on-error: ${{ matrix.allow-failure || false }} strategy: matrix: include: - - {distro: centos, image: 'centos:7'} + # CentOS 7 system Ruby is the fixed version 2.0.0. + - {distro: centos, image: 'centos:7', name_extra: 'ruby 2.0.0'} # Fedora latest stable version # Allow failure due to the following test failures. # https://github.com/brianmario/mysql2/issues/965 diff --git a/.travis.yml b/.travis.yml index d5a64afc6..d897ded28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -sudo: required dist: trusty language: ruby bundler_args: --without development @@ -9,17 +8,8 @@ before_install: - gem update bundler - gem --version - bash ci/setup.sh -addons: - hosts: - - mysql2gem.example.com - apt: - packages: - - mysql-server-5.6 - - mysql-client-core-5.6 - - mysql-client-5.6 matrix: include: - - rvm: 2.0.0 - rvm: 2.4 env: DB=mariadb10.0 addons: From b439a895ef6b289e1bc5e07303fc3952713fb948 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Mon, 22 Mar 2021 22:56:26 +0100 Subject: [PATCH 673/783] Rename the before/after hooks's symbol on RSpec 2 to the one on RSpec 3. (#1177) Rename :all to :context . Rename :each to :example . There is no used around hook. https://relishapp.com/rspec/rspec-core/v/2-99/docs/hooks/before-and-after-hooks https://relishapp.com/rspec/rspec-core/v/3-10/docs/hooks/before-and-after-hooks --- spec/mysql2/client_spec.rb | 16 ++++++++-------- spec/mysql2/result_spec.rb | 2 +- spec/mysql2/statement_spec.rb | 14 +++++++------- spec/spec_helper.rb | 6 +++--- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index fcc8cc44e..5e330f648 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -407,7 +407,7 @@ def run_gc end context ":local_infile" do - before(:all) do + before(:context) do new_client(local_infile: true) do |client| local = client.query "SHOW VARIABLES LIKE 'local_infile'" local_enabled = local.any? { |x| x['Value'] == 'ON' } @@ -423,7 +423,7 @@ def run_gc end end - after(:all) do + after(:context) do new_client do |client| client.query "DROP TABLE IF EXISTS infileTest" end @@ -730,7 +730,7 @@ def run_gc end context "Multiple results sets" do - before(:each) do + before(:example) do @multi_client = new_client(flags: Mysql2::Client::MULTI_STATEMENTS) end @@ -974,12 +974,12 @@ def run_gc end context 'write operations api' do - before(:each) do + before(:example) do @client.query "USE test" @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" end - after(:each) do + after(:example) do @client.query "DROP TABLE lastIdTest" end @@ -1027,7 +1027,7 @@ def run_gc end context "session_track" do - before(:each) do + before(:example) do unless Mysql2::Client.const_defined?(:SESSION_TRACK) skip('Server versions must be MySQL 5.7 later.') end @@ -1070,7 +1070,7 @@ def run_gc end context "select_db" do - before(:each) do + before(:example) do 2.times do |i| @client.query("CREATE DATABASE test_selectdb_#{i}") @client.query("USE test_selectdb_#{i}") @@ -1078,7 +1078,7 @@ def run_gc end end - after(:each) do + after(:example) do 2.times do |i| @client.query("DROP DATABASE test_selectdb_#{i}") end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index cc09838f5..5be0326f5 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' RSpec.describe Mysql2::Result do - before(:each) do + before(:example) do @result = @client.query "SELECT 1" end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index dbc185e6b..eb4e8b8ff 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -1,7 +1,7 @@ require './spec/spec_helper.rb' RSpec.describe Mysql2::Statement do - before :each do + before(:example) do @client = new_client(encoding: "utf8") end @@ -211,7 +211,7 @@ def stmt_count end context "utf8_db" do - before(:each) do + before(:example) do @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") @client.query("CREATE DATABASE test_mysql2_stmt_utf8") @client.query("USE test_mysql2_stmt_utf8") @@ -219,7 +219,7 @@ def stmt_count @client.query("INSERT INTO テーブル (整数, 文字列) VALUES (1, 'イチ'), (2, '弐'), (3, 'さん')") end - after(:each) do + after(:example) do @client.query("DROP DATABASE test_mysql2_stmt_utf8") end @@ -627,12 +627,12 @@ def stmt_count end context 'last_id' do - before(:each) do + before(:example) do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))' end - after(:each) do + after(:example) do @client.query 'DROP TABLE lastIdTest' end @@ -655,12 +655,12 @@ def stmt_count end context 'affected_rows' do - before :each do + before(:example) do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))' end - after :each do + after(:example) do @client.query 'DROP TABLE lastIdTest' end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7a2a36196..2e86e112c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -74,7 +74,7 @@ def clock_time end end - config.before(:all) do + config.before(:context) do new_client do |client| client.query %[ CREATE TABLE IF NOT EXISTS mysql2_test ( @@ -136,11 +136,11 @@ def clock_time end end - config.before(:each) do + config.before(:example) do @client = new_client end - config.after(:each) do + config.after(:example) do @clients.each(&:close) end end From 22fce00a93b19f3d4eadd29d1a60775433aac7bf Mon Sep 17 00:00:00 2001 From: Clemens Fuchslocher Date: Sat, 10 Apr 2021 15:46:04 +0200 Subject: [PATCH 674/783] Can't enable SSL with MariaDB driver library. (#1182) --- ext/mysql2/client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 267d2b067..4e85f05fb 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -124,8 +124,8 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE GET_CLIENT(self); int val = NUM2INT( setting ); - // Either MySQL 5.7.3 - 5.7.10, or Connector/C 6.1.3 - 6.1.x - if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200)) { + // Either MySQL 5.7.3 - 5.7.10, or Connector/C 6.1.3 - 6.1.x, or MariaDB 10.x + if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200) || (version >= 100000 && version < 110000)) { if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { my_bool b = ( val == SSL_MODE_REQUIRED ); int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); From f86514ade30a45d99e42c055e25c8c8135b68075 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 16 Apr 2021 14:50:36 +0200 Subject: [PATCH 675/783] Use performance schema only if it is enabled. Fix the test failures in statement_spec.rb on MariaDB 10.5. On MariaDB >= 10.0, the peformance schema is disabled. https://jira.mariadb.org/browse/MDEV-6726 And on MariaDB 10.5, the followwing code used in the test doesn't raise `Mysql2:Error`, just returning 0, even when if the performance_schema is disabled. ``` @client.query("SELECT COUNT(1) AS count FROM performance_schema.prepared_statements_instances") ``` --- spec/mysql2/statement_spec.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index eb4e8b8ff..77c86b550 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -5,12 +5,19 @@ @client = new_client(encoding: "utf8") end + let(:performance_schema_enabled) do + performance_schema = @client.query "SHOW VARIABLES LIKE 'performance_schema'" + performance_schema.any? { |x| x['Value'] == 'ON' } + end + def stmt_count # Use the performance schema in MySQL 5.7 and above - @client.query("SELECT COUNT(1) AS count FROM performance_schema.prepared_statements_instances").first['count'].to_i - rescue Mysql2::Error - # Fall back to the global prepapred statement counter - @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i + if performance_schema_enabled + @client.query("SELECT COUNT(1) AS count FROM performance_schema.prepared_statements_instances").first['count'].to_i + else + # Fall back to the global prepapred statement counter + @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i + end end it "should create a statement" do From b85b6bbb0fe783465172bb2d7cb990cd4cf62c44 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 16 Apr 2021 15:40:37 +0200 Subject: [PATCH 676/783] Skip tests using performance_schema if the it is not enabled. Fix the test failures in client_spec.rb on MariaDB. On MariaDB >= 10.0, the setting of the peformance schema is disabled by defult. https://jira.mariadb.org/browse/MDEV-6726 You can comfirm the status by the SQL `SHOW VARIABLES LIKE 'performance_schema`. --- spec/mysql2/client_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 5e330f648..7fe4e22b3 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1,6 +1,11 @@ require 'spec_helper' RSpec.describe Mysql2::Client do # rubocop:disable Metrics/BlockLength + let(:performance_schema_enabled) do + performance_schema = @client.query "SHOW VARIABLES LIKE 'performance_schema'" + performance_schema.any? { |x| x['Value'] == 'ON' } + end + context "using defaults file" do let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) } @@ -479,6 +484,7 @@ def run_gc end it "should set default program_name in connect_attrs" do + skip("DON'T WORRY, THIS TEST PASSES - but PERFORMNCE SCHEMA is not enabled in your MySQL daemon.") unless performance_schema_enabled client = new_client if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/) pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.') @@ -488,6 +494,7 @@ def run_gc end it "should set custom connect_attrs" do + skip("DON'T WORRY, THIS TEST PASSES - but PERFORMNCE SCHEMA is not enabled in your MySQL daemon.") unless performance_schema_enabled client = new_client(connect_attrs: { program_name: 'my_program_name', foo: 'fooval', bar: 'barval' }) if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/) pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.') From e5fbf05b6c67d34977b09590e6c6145fe634da60 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 16 Apr 2021 16:04:46 +0200 Subject: [PATCH 677/783] Fix an error message test in error_spec.rb on MariaDB 10.5. MariaDB 10.5 returns a little different error message unlike MySQL and other old MariaDBs. https://jira.mariadb.org/browse/MDEV-25400 . --- spec/mysql2/error_spec.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb index c94a31961..efa3437f8 100644 --- a/spec/mysql2/error_spec.rb +++ b/spec/mysql2/error_spec.rb @@ -39,6 +39,10 @@ end end + let(:server_info) do + @client.server_info + end + before do # sanity check expect(valid_utf8.encoding).to eql(Encoding::UTF_8) @@ -56,7 +60,15 @@ expect(bad_err.message.encoding).to eql(Encoding::UTF_8) expect(bad_err.message).to be_valid_encoding - expect(bad_err.message).to include("??}\u001F") + # MariaDB 10.5 returns a little different error message unlike MySQL + # and other old MariaDBs. + # https://jira.mariadb.org/browse/MDEV-25400 + err_str = if server_info[:version].match(/MariaDB/) && server_info[:id] >= 100500 + "??}\\001F" + else + "??}\u001F" + end + expect(bad_err.message).to include(err_str) end end From 4a47e8ab08dad5cc1d487691a29dff8a9b7d31bf Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 16 Apr 2021 16:13:29 +0200 Subject: [PATCH 678/783] Fix failures on Fedora. Install rubygem-json RPM package in Fedora container as it is used in rake. ``` LoadError: cannot load such file -- json /build/vendor/bundle/ruby/3.0.0/gems/rubocop-0.50.0/lib/rubocop/formatter/json_formatter.rb:3:in `require' /build/vendor/bundle/ruby/3.0.0/gems/rubocop-0.50.0/lib/rubocop/formatter/json_formatter.rb:3:in `' /build/vendor/bundle/ruby/3.0.0/gems/rubocop-0.50.0/lib/rubocop.rb:524:in `require' /build/vendor/bundle/ruby/3.0.0/gems/rubocop-0.50.0/lib/rubocop.rb:524:in `' /build/vendor/bundle/ruby/3.0.0/gems/rubocop-0.50.0/lib/rubocop/rake_task.rb:44:in `require' /build/vendor/bundle/ruby/3.0.0/gems/rubocop-0.50.0/lib/rubocop/rake_task.rb:44:in `run_cli' /build/vendor/bundle/ruby/3.0.0/gems/rubocop-0.50.0/lib/rubocop/rake_task.rb:36:in `run_main_task' /build/vendor/bundle/ruby/3.0.0/gems/rubocop-0.50.0/lib/rubocop/rake_task.rb:28:in `block (2 levels) in initialize' /build/vendor/bundle/ruby/3.0.0/gems/rubocop-0.50.0/lib/rubocop/rake_task.rb:26:in `block in initialize' /build/vendor/bundle/ruby/3.0.0/gems/rake-13.0.3/exe/rake:27:in `' ``` --- ci/Dockerfile_fedora | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/Dockerfile_fedora b/ci/Dockerfile_fedora index 46c4b26a6..644b5974f 100644 --- a/ci/Dockerfile_fedora +++ b/ci/Dockerfile_fedora @@ -20,6 +20,7 @@ RUN dnf -yq install \ redhat-rpm-config \ ruby-devel \ rubygem-bigdecimal \ - rubygem-bundler + rubygem-bundler \ + rubygem-json CMD bash ci/container.sh From 7b563b860d60c42f0c370b6cc0010dce7c897a12 Mon Sep 17 00:00:00 2001 From: Clemens Fuchslocher Date: Thu, 15 Apr 2021 18:01:10 +0200 Subject: [PATCH 679/783] Fix MariaDB 10.3 installation under Ubuntu 20.04. (#1184) --- .github/workflows/build.yml | 2 +- ci/setup.sh | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7edefac63..7769f00f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: - {os: ubuntu-16.04, ruby: 2.4, db: mariadb10.0, allow-failure: true} # Comment out due to ci/setup.sh stucking. # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1} - # `service mysql restart` fails. + # Allow failure due to the issue #965, #1165. - {os: ubuntu-20.04, ruby: 2.4, db: mariadb10.3, allow-failure: true} - {os: ubuntu-18.04, ruby: 2.4, db: mysql57} # Allow failure due to the issue #1165. diff --git a/ci/setup.sh b/ci/setup.sh index 97e89818a..a43599c65 100644 --- a/ci/setup.sh +++ b/ci/setup.sh @@ -72,8 +72,13 @@ fi # Install MariaDB 10.3 if DB=mariadb10.3 if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.3 ]]; then + sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/ + sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld + sudo apt-get purge -y 'mysql-common*' 'mysql-client*' 'mysql-server*' + sudo mv /etc/mysql "/etc/mysql-$(date +%Y%m%d-%H%M%S)" + sudo mv /var/lib/mysql "/var/lib/mysql-$(date +%Y%m%d-%H%M%S)" sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.3 libmariadb-dev - CHANGED_PASSWORD=true + CHANGED_PASSWORD_BY_RECREATE=true fi # Install MySQL/MariaDB if OS=darwin From ef0efec640481c806b3d110a87a6958980aa490c Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 15 Apr 2021 19:32:38 +0200 Subject: [PATCH 680/783] Add tests to verify ssl_mode option. Add tests to verify the following commmit. Can't enable SSL with MariaDB driver library. --- spec/mysql2/client_spec.rb | 77 ++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 5e330f648..6a7ce2b99 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -126,39 +126,58 @@ def connect(*args) expect(Mysql2::Client).to respond_to(:default_query_options) end - it "should be able to connect via SSL options" do - ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'" - ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' } - pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled - ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' } - pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled - - # You may need to adjust the lines below to match your SSL certificate paths - ssl_client = nil - option_overrides = { - 'host' => 'mysql2gem.example.com', # must match the certificates - :sslkey => '/etc/mysql/client-key.pem', - :sslcert => '/etc/mysql/client-cert.pem', - :sslca => '/etc/mysql/ca-cert.pem', - :sslcipher => 'DHE-RSA-AES256-SHA', - :sslverify => true, - } - %i[sslkey sslcert sslca].each do |item| - unless File.exist?(option_overrides[item]) - pending("DON'T WORRY, THIS TEST PASSES - but #{option_overrides[item]} does not exist.") - break + context "SSL" do + before(:example) do + ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'" + ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' } + pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled + ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' } + pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled + + %i[sslkey sslcert sslca].each do |item| + unless File.exist?(option_overrides[item]) + pending("DON'T WORRY, THIS TEST PASSES - but #{option_overrides[item]} does not exist.") + break + end end end - expect do - ssl_client = new_client(option_overrides) - end.not_to raise_error - results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }] - expect(results['Ssl_cipher']).not_to be_empty - expect(results['Ssl_version']).not_to be_empty + let(:option_overrides) do + { + 'host' => 'mysql2gem.example.com', # must match the certificates + :sslkey => '/etc/mysql/client-key.pem', + :sslcert => '/etc/mysql/client-cert.pem', + :sslca => '/etc/mysql/ca-cert.pem', + :sslcipher => 'DHE-RSA-AES256-SHA', + :sslverify => true, + } + end + + let(:ssl_client) do + new_client(option_overrides) + end - expect(ssl_client.ssl_cipher).not_to be_empty - expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher) + %i[disabled preferred required verify_ca verify_identity].each do |ssl_mode| + it "should set ssl_mode option #{ssl_mode}" do + options = { + ssl_mode: ssl_mode, + } + options.merge!(option_overrides) + expect do + new_client(options) + end.to_not output.to_stderr + end + end + + it "should be able to connect via SSL options" do + # You may need to adjust the lines below to match your SSL certificate paths + results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }] + expect(results['Ssl_cipher']).not_to be_empty + expect(results['Ssl_version']).not_to be_empty + + expect(ssl_client.ssl_cipher).not_to be_empty + expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher) + end end def run_gc From 7dcd371bf816ab69892b88fbdbe9a467bb28c6c6 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 16 Apr 2021 10:42:55 +0200 Subject: [PATCH 681/783] Fix SSL tests. * Relax the matching condition. It's better to verify a warning by checking stderr. But for now, just relax the matching condition, due to complex conditions. ``` expect do new_client(options) end.to_not output.to_stderr ``` * Change pending to skip in SSL tests. The skip method is right in this context. Because the pending method requires the test to fail. But in some cases the test passes. --- spec/mysql2/client_spec.rb | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 6a7ce2b99..fce3b1396 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -130,14 +130,17 @@ def connect(*args) before(:example) do ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'" ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' } - pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' } - pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled - - %i[sslkey sslcert sslca].each do |item| - unless File.exist?(option_overrides[item]) - pending("DON'T WORRY, THIS TEST PASSES - but #{option_overrides[item]} does not exist.") - break + if ssl_uncompiled + skip("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") + elsif ssl_disabled + skip("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") + else + %i[sslkey sslcert sslca].each do |item| + unless File.exist?(option_overrides[item]) + skip("DON'T WORRY, THIS TEST PASSES - but #{option_overrides[item]} does not exist.") + break + end end end end @@ -163,9 +166,11 @@ def connect(*args) ssl_mode: ssl_mode, } options.merge!(option_overrides) + # Relax the matching condition by checking if an error is not raised. + # TODO: Verify warnings by checking stderr. expect do new_client(options) - end.to_not output.to_stderr + end.not_to raise_error end end From 33713f96ea2b5bd6d516ffd45113af86d47d1b38 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 16 Apr 2021 16:22:33 +0200 Subject: [PATCH 682/783] GitHub Actions: Remove allow-failure on Fedora MariaDB cases. The tests pass now. Still keep allow-failure on macOS MariaDB case due to test failures that rarely happens. --- .github/workflows/build.yml | 5 ++--- .github/workflows/container.yml | 8 ++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7edefac63..05895f7b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,9 +40,8 @@ jobs: # db: A DB's brew package name in macOS case. # Set a name "db: 'name@X.Y'" when using an old version. # MariaDB lastet version - # Allow failure due to the following test failures. - # https://github.com/brianmario/mysql2/issues/965 - # https://github.com/brianmario/mysql2/issues/1152 + # Allow failure due to the following test failures that rarely happens. + # https://github.com/brianmario/mysql2/issues/1194 - {os: macos-latest, ruby: 2.4, db: mariadb, allow-failure: true} # MySQL latest version # Allow failure due to the issue #1165. diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 39a13bbce..9ddfe42b9 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -14,13 +14,9 @@ jobs: # CentOS 7 system Ruby is the fixed version 2.0.0. - {distro: centos, image: 'centos:7', name_extra: 'ruby 2.0.0'} # Fedora latest stable version - # Allow failure due to the following test failures. - # https://github.com/brianmario/mysql2/issues/965 - - {distro: fedora, image: 'fedora:latest', allow-failure: true} + - {distro: fedora, image: 'fedora:latest'} # Fedora development version - # Allow failure due to the following test failures. - # https://github.com/brianmario/mysql2/issues/1152 - - {distro: fedora, image: 'fedora:rawhide', allow-failure: true} + - {distro: fedora, image: 'fedora:rawhide'} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. fail-fast: false From d22a4da44363d3e1021ab2004b297dd3d32c887c Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 16 Apr 2021 22:24:18 +0200 Subject: [PATCH 683/783] Travis: add allow-failures to mysql55 case due to a package repository not found. https://travis-ci.org/github/brianmario/mysql2/jobs/767276558#L1255 ``` E: Failed to fetch https://downloads.apache.org/cassandra/debian/dists/39x/main/binary-amd64/Packages 404 Not Found E: Failed to fetch https://downloads.apache.org/cassandra/debian/dists/39x/main/binary-i386/Packages 404 Not Found ``` --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index d897ded28..b3f49dacb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,3 +41,6 @@ matrix: - mysql2gem.example.com fast_finish: true allow_failures: + # Allow failure due to a package repository not found for now. + # https://travis-ci.org/github/brianmario/mysql2/jobs/767276558#L1255 + - env: DB=mysql55 From 620e15fa1c99725287a07d759f8f3ccd65907268 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 19 May 2021 09:26:55 -0700 Subject: [PATCH 684/783] Apply suggestions from code review --- spec/mysql2/client_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 7fe4e22b3..f356c1519 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -484,7 +484,7 @@ def run_gc end it "should set default program_name in connect_attrs" do - skip("DON'T WORRY, THIS TEST PASSES - but PERFORMNCE SCHEMA is not enabled in your MySQL daemon.") unless performance_schema_enabled + skip("DON'T WORRY, THIS TEST PASSES - but PERFORMANCE SCHEMA is not enabled in your MySQL daemon.") unless performance_schema_enabled client = new_client if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/) pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.') @@ -494,7 +494,7 @@ def run_gc end it "should set custom connect_attrs" do - skip("DON'T WORRY, THIS TEST PASSES - but PERFORMNCE SCHEMA is not enabled in your MySQL daemon.") unless performance_schema_enabled + skip("DON'T WORRY, THIS TEST PASSES - but PERFORMANCE SCHEMA is not enabled in your MySQL daemon.") unless performance_schema_enabled client = new_client(connect_attrs: { program_name: 'my_program_name', foo: 'fooval', bar: 'barval' }) if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/) pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.') From 926f85382b0e6e8b051531e77f373cfbcea7365c Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Thu, 20 May 2021 01:30:41 +0900 Subject: [PATCH 685/783] Fix some typos [ci skip] (#1195) --- README.md | 4 ++-- ext/mysql2/client.c | 2 +- ext/mysql2/extconf.rb | 2 +- ext/mysql2/statement.c | 2 +- spec/ssl/gen_certs.sh | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 72ff37ff0..bdf3dd5c0 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ results.each do |row| # the keys are the fields, as you'd expect # the values are pre-built ruby primitives mapped from their corresponding field types in MySQL puts row["id"] # row["id"].is_a? Integer - if row["dne"] # non-existant hash entry is nil + if row["dne"] # non-existent hash entry is nil puts row["dne"] end end @@ -285,7 +285,7 @@ Mysql2::Client.new( ### Secure auth -Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). +Starting with MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). When secure_auth is enabled, the server will refuse a connection if the account password is stored in old pre-MySQL 4.1 format. The MySQL 5.6.5 client library may also refuse to attempt a connection if provided an older format password. To bypass this restriction in the client, pass the option `:secure_auth => false` to Mysql2::Client.new(). diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 267d2b067..98eb6e857 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -45,7 +45,7 @@ static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args } /* - * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct + * compatibility with mysql-connector-c, where LIBMYSQL_VERSION is the correct * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when * linking against the server itself */ diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 70cb3c999..be2654cbb 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -154,7 +154,7 @@ def add_ssl_defines(header) $CFLAGS << ' ' << usable_flags.join(' ') enabled_sanitizers = disabled_sanitizers = [] -# Specify a commna-separated list of sanitizers, or try them all by default +# Specify a comma-separated list of sanitizers, or try them all by default sanitizers = with_config('sanitize') case sanitizers when true diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 0ade6c493..137268afb 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -456,7 +456,7 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { } if (!is_streaming) { - // recieve the whole result set from the server + // receive the whole result set from the server if (mysql_stmt_store_result(stmt)) { mysql_free_result(metadata); rb_raise_mysql2_stmt_error(stmt_wrapper); diff --git a/spec/ssl/gen_certs.sh b/spec/ssl/gen_certs.sh index 728748126..3d48da014 100644 --- a/spec/ssl/gen_certs.sh +++ b/spec/ssl/gen_certs.sh @@ -22,7 +22,7 @@ organizationalUnitName_default = Mysql2Gem emailAddress_default = mysql2gem@example.com " | tee ca.cnf cert.cnf -# The client and server certs must have a diferent common name than the CA +# The client and server certs must have a different common name than the CA # to avoid "SSL connection error: error:00000001:lib(0):func(0):reason(1)" echo " From cab93046e462927bbae216e3f5c8883992c76462 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Wed, 19 May 2021 18:32:44 +0200 Subject: [PATCH 686/783] Travis: Remove the command to pin Rubygems. (#1188) On the Travis log the command costs around 160 seconds. If we see an issue, we can enable the command again. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b3f49dacb..05f847a79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,8 @@ dist: trusty language: ruby bundler_args: --without development -# Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - gem --version - - gem update --system --quiet || gem update --system 2.7.10 --quiet - gem update bundler - gem --version - bash ci/setup.sh From ca883e1a359a10f48ac5d0ce649827b424110cd7 Mon Sep 17 00:00:00 2001 From: Jean byroot Boussier Date: Wed, 19 May 2021 19:43:29 +0200 Subject: [PATCH 687/783] Make Result#fields return interned strings in Ruby 3+ (#1181) --- ext/mysql2/extconf.rb | 2 ++ ext/mysql2/result.c | 13 ++++++++++--- spec/mysql2/result_spec.rb | 7 +++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index be2654cbb..0a4b8fcfe 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -31,6 +31,8 @@ def add_ssl_defines(header) # Missing in RBX (https://github.com/rubinius/rubinius/issues/3771) have_func('rb_wait_for_single_fd') +have_func("rb_enc_interned_str", "ruby.h") + # borrowed from mysqlplus # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb dirs = ENV.fetch('http://github.com/PATH').split(File::PATH_SEPARATOR) + %w[ diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index af0b7b4e6..66c9fdd12 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -171,11 +171,18 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding()); rb_field = ID2SYM(rb_field); } else { - rb_field = rb_str_new(field->name, field->name_length); - rb_enc_associate(rb_field, conn_enc); - if (default_internal_enc) { +#ifdef HAVE_RB_ENC_INTERNED_STR + rb_field = rb_enc_interned_str(field->name, field->name_length, conn_enc); + if (default_internal_enc && default_internal_enc != conn_enc) { + rb_field = rb_str_to_interned_str(rb_str_export_to_enc(rb_field, default_internal_enc)); + } +#else + rb_field = rb_enc_str_new(field->name, field->name_length, conn_enc); + if (default_internal_enc && default_internal_enc != conn_enc) { rb_field = rb_str_export_to_enc(rb_field, default_internal_enc); } + rb_obj_freeze(rb_field); +#endif } rb_ary_store(wrapper->fields, idx, rb_field); } diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 5be0326f5..47a4a6de6 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -118,6 +118,13 @@ result = @client.query "SELECT 'a', 'b', 'c'" expect(result.fields).to eql(%w[a b c]) end + + it "should return an array of frozen strings" do + result = @client.query "SELECT 'a', 'b', 'c'" + result.fields.each do |f| + expect(f).to be_frozen + end + end end context "#field_types" do From d136164687740f9e08fcb2b3ae8c98b7275de36d Mon Sep 17 00:00:00 2001 From: Dirkjan Bussink Date: Fri, 27 Aug 2021 11:27:07 +0200 Subject: [PATCH 688/783] Setup default CA path if not provided This adds setup of a default CA path if there's no path provided by the user. This enables easier configuration of system level CA validation if the MySQL server has a certificate signed by a system root. On more and more cloud based MySQL platforms system signed CA certificates are used and this hides the issue of selecting the appropriate path from the user. The real longer term answer here is that this is a default that changes in libmysqlclient itself. The current situation here is mixed. When using MariaDB (including the changes in #1205), the default system roots are already loaded and used if no CA is provided. On MySQL itself on the other hand, a CA path is required today. I have also opened a PR to improve that, see https://github.com/mysql/mysql-server/pull/358 & https://bugs.mysql.com/bug.php?id=104649. --- lib/mysql2/client.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 607ad6d6a..8a4a3e11b 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -46,9 +46,14 @@ def initialize(opts = {}) # force the encoding to utf8 self.charset_name = opts[:encoding] || 'utf8' + mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode] + if (mode == SSL_MODE_VERIFY_CA || mode == SSL_MODE_VERIFY_IDENTITY) && !opts[:sslca] + opts[:sslca] = find_default_ca_path + end + ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher) ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify) - self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode] + self.ssl_mode = mode if mode flags = case opts[:flags] when Array @@ -115,6 +120,18 @@ def parse_flags_array(flags, initial = 0) end end + # Find any default system CA paths to handle system roots + # by default if stricter validation is requested and no + # path is provide. + def find_default_ca_path + [ + "/etc/ssl/certs/ca-certificates.crt", + "/etc/pki/tls/certs/ca-bundle.crt", + "/etc/ssl/ca-bundle.pem", + "/etc/ssl/cert.pem", + ].find { |f| File.exist?(f) } + end + # Set default program_name in performance_schema.session_connect_attrs # and performance_schema.session_account_connect_attrs def parse_connect_attrs(conn_attrs) From 6ae1d9a6aa1fa9b2d5e1ae9e3e1ef96c672c11e5 Mon Sep 17 00:00:00 2001 From: Dirkjan Bussink Date: Fri, 27 Aug 2021 11:35:47 +0200 Subject: [PATCH 689/783] Update Rubocop todo items --- .rubocop_todo.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4117fa862..bc34785a7 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -17,7 +17,7 @@ Layout/IndentHeredoc: # Offense count: 2 Metrics/AbcSize: - Max: 90 + Max: 91 # Offense count: 31 # Configuration parameters: CountComments, ExcludedMethods. @@ -32,11 +32,11 @@ Metrics/BlockNesting: # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 125 + Max: 135 # Offense count: 3 Metrics/CyclomaticComplexity: - Max: 30 + Max: 32 # Offense count: 313 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. @@ -51,7 +51,7 @@ Metrics/MethodLength: # Offense count: 2 Metrics/PerceivedComplexity: - Max: 27 + Max: 29 # Offense count: 3 # Configuration parameters: Blacklist. From ef28e60be54d9e59d5736d873aea44562eceba09 Mon Sep 17 00:00:00 2001 From: Gaurish Sharma Date: Wed, 1 Sep 2021 19:46:08 -0500 Subject: [PATCH 690/783] Fix broken URL [ci skip] (#1207) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bdf3dd5c0..257746d08 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ session_track_data = c.session_track(session_track_type) ``` The types of session track types can be found at -[https://dev.mysql.com/doc/refman/5.7/en/mysql-session-track-get-first.html](https://dev.mysql.com/doc/refman/5.7/en/mysql-session-track-get-first.html) +[https://dev.mysql.com/doc/refman/5.7/en/session-state-tracking.html](https://dev.mysql.com/doc/refman/5.7/en/session-state-tracking.html) ## Connection options From 673d5a774151721f914dfee91e0090ea0280e2b2 Mon Sep 17 00:00:00 2001 From: Olivier Lacan Date: Mon, 6 Sep 2021 15:09:57 -0400 Subject: [PATCH 691/783] Dynamically set Homebrew-installed OpenSSL flag (#1204) This is a follow-up to #1135 which added the OpenSSL flag assuming that if the `RUBY_PLATFORM` is `darwin` (macOS): - Homebrew is installed - OpenSSL is installed via Homebrew This PR: - no longer assumes Homebrew is installed if we're on macOS - no longer assumes OpenSSL is installed via Homebrew - asks Homebrew for the openssl location (which will also work with the newer openssl@1.1 recipe) Should prevent issues like these when running bundle install on the rails codebase: Bundle Install error due to: ld: warning: directory not found for option '-L/usr/local/opt/openssl/lib' --- ext/mysql2/extconf.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 0a4b8fcfe..8b78aeb52 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -22,7 +22,12 @@ def add_ssl_defines(header) end # Homebrew openssl -$LDFLAGS << ' -L/usr/local/opt/openssl/lib' if RUBY_PLATFORM =~ /darwin/ +if RUBY_PLATFORM =~ /darwin/ && system("command -v brew") + openssl_location = `brew --prefix openssl`.strip + if openssl_location + $LDFLAGS << " -L#{openssl_location}/lib" + end +end # 2.1+ have_func('rb_absint_size') From 6652da20010ddfbbe6bceb8e41666d05e512346c Mon Sep 17 00:00:00 2001 From: Dirkjan Bussink Date: Mon, 20 Sep 2021 19:20:54 +0200 Subject: [PATCH 692/783] Allow setting VERIFY_IDENTITY for MariaDB (#1205) This adds support for setting the VERIFY_IDENTITY mode with MariaDB. On MariaDB, the `MYSQL_OPT_SSL_VERIFY_SERVER_CERT` option is available which is equivalent to `VERIFY_IDENTITY`. Also removed the check for a potential MariaDB 11.x since there's no indication that this behavior will change in MariaDB. Many containers with Ruby apps are based on Debian where MariaDB is the standard provided, so this improves support there significantly. --- ext/mysql2/client.c | 34 ++++++++++++++++++++++++++-------- ext/mysql2/extconf.rb | 17 ++++++++++------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 084c7cb14..04bf2cc83 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -69,8 +69,12 @@ static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args #endif /* - * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10. + * compatibility with mysql-connector-c 6.1.x, MySQL 5.7.3 - 5.7.10 & with MariaDB 10.x and later. */ +#ifdef HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT + #define SSL_MODE_VERIFY_IDENTITY 5 + #define HAVE_CONST_SSL_MODE_VERIFY_IDENTITY +#endif #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE #define SSL_MODE_DISABLED 1 #define SSL_MODE_REQUIRED 3 @@ -121,19 +125,27 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." ); return Qnil; } -#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE +#if defined(HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT) || defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) GET_CLIENT(self); int val = NUM2INT( setting ); - // Either MySQL 5.7.3 - 5.7.10, or Connector/C 6.1.3 - 6.1.x, or MariaDB 10.x - if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200) || (version >= 100000 && version < 110000)) { + // Either MySQL 5.7.3 - 5.7.10, or Connector/C 6.1.3 - 6.1.x, or MariaDB 10.x and later + if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200) || version >= 100000) { +#ifdef HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT + if (val == SSL_MODE_VERIFY_IDENTITY) { + my_bool b = 1; + int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &b ); + return INT2NUM(result); + } +#endif +#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { my_bool b = ( val == SSL_MODE_REQUIRED ); int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); return INT2NUM(result); - } else { - rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" ); - return Qnil; } +#endif + rb_warn( "Your mysql client library does not support ssl_mode %d.", val ); + return Qnil; } else { rb_warn( "Your mysql client library does not support ssl_mode as expected." ); return Qnil; @@ -151,6 +163,7 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { return INT2NUM(result); #endif #ifdef NO_SSL_MODE_SUPPORT + rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." ); return Qnil; #endif } @@ -1676,10 +1689,15 @@ void init_mysql2_client() { rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY)); -#elif defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) // MySQL 5.7.3 - 5.7.10 +#else +#ifdef HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT // MySQL 5.7.3 - 5.7.10 & MariaDB 10.x and later + rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY)); +#endif +#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE // MySQL 5.7.3 - 5.7.10 & MariaDB 10.x and later rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED)); #endif +#endif #ifndef HAVE_CONST_SSL_MODE_DISABLED rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0)); diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 8b78aeb52..2818a9344 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -15,18 +15,21 @@ def add_ssl_defines(header) all_modes_found = %w[SSL_MODE_DISABLED SSL_MODE_PREFERRED SSL_MODE_REQUIRED SSL_MODE_VERIFY_CA SSL_MODE_VERIFY_IDENTITY].inject(true) do |m, ssl_mode| m && have_const(ssl_mode, header) end - $CFLAGS << ' -DFULL_SSL_MODE_SUPPORT' if all_modes_found - # if we only have ssl toggle (--ssl,--disable-ssl) from 5.7.3 to 5.7.10 - has_no_support = all_modes_found ? false : !have_const('MYSQL_OPT_SSL_ENFORCE', header) - $CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if has_no_support + if all_modes_found + $CFLAGS << ' -DFULL_SSL_MODE_SUPPORT' + else + # if we only have ssl toggle (--ssl,--disable-ssl) from 5.7.3 to 5.7.10 + # and the verify server cert option. This is also the case for MariaDB. + has_verify_support = have_const('MYSQL_OPT_SSL_VERIFY_SERVER_CERT', header) + has_enforce_support = have_const('MYSQL_OPT_SSL_ENFORCE', header) + $CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if !has_verify_support && !has_enforce_support + end end # Homebrew openssl if RUBY_PLATFORM =~ /darwin/ && system("command -v brew") openssl_location = `brew --prefix openssl`.strip - if openssl_location - $LDFLAGS << " -L#{openssl_location}/lib" - end + $LDFLAGS << " -L#{openssl_location}/lib" if openssl_location end # 2.1+ From 0e1d64c3d43508c4f56f3c10acb979f5408da2e0 Mon Sep 17 00:00:00 2001 From: Thomas <1258170+ThomasSevestre@users.noreply.github.com> Date: Tue, 26 Oct 2021 10:15:39 +0200 Subject: [PATCH 693/783] add connection killed error mapping --- lib/mysql2/error.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mysql2/error.rb b/lib/mysql2/error.rb index 3c182218d..8cfa23ffb 100644 --- a/lib/mysql2/error.rb +++ b/lib/mysql2/error.rb @@ -24,6 +24,7 @@ class Error < StandardError 1159 => ConnectionError, # ER_NET_READ_INTERRUPTED 1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE 1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED + 1927 => ConnectionError, # ER_CONNECTION_KILLED 2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR 2002 => ConnectionError, # CR_CONNECTION_ERROR From 4dce466f3ff1c90099d3dc1bf610bfab840d3631 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 26 Nov 2021 13:17:05 +0100 Subject: [PATCH 694/783] Update Mysql2::Result spec for Ruby 3.1 Ruby 3.1 immediately raise a TypeError if you try to instantiate a class that doesn't have an allocator, which is what we want anyways. --- spec/mysql2/result_spec.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 47a4a6de6..2af6e609d 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -6,12 +6,17 @@ end it "should raise a TypeError exception when it doesn't wrap a result set" do - r = Mysql2::Result.new - expect { r.count }.to raise_error(TypeError) - expect { r.fields }.to raise_error(TypeError) - expect { r.field_types }.to raise_error(TypeError) - expect { r.size }.to raise_error(TypeError) - expect { r.each }.to raise_error(TypeError) + if RUBY_VERSION >= "3.1" + expect { Mysql2::Result.new }.to raise_error(TypeError) + expect { Mysql2::Result.allocate }.to raise_error(TypeError) + else + r = Mysql2::Result.new + expect { r.count }.to raise_error(TypeError) + expect { r.fields }.to raise_error(TypeError) + expect { r.field_types }.to raise_error(TypeError) + expect { r.size }.to raise_error(TypeError) + expect { r.each }.to raise_error(TypeError) + end end it "should have included Enumerable" do From d79638e6883acb52b6a32aed93b197f42f290256 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 26 Nov 2021 15:26:04 +0100 Subject: [PATCH 695/783] Update DATETIME casting tests for mysql 8.0 With Mysql 5, the invalid datetime was returned. However with 8.0 the returned row is `NULL`, so we can no longer detect this case and raise. --- spec/mysql2/result_spec.rb | 9 +++++++-- spec/mysql2/statement_spec.rb | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 47a4a6de6..86752e130 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -410,8 +410,13 @@ end it "should raise an error given an invalid DATETIME" do - expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ - raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") + if @client.info[:version] < "8.0" + expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ + raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") + else + expect(@client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").to_a.first).to \ + eql("bad_datetime" => nil) + end end context "string encoding for ENUM values" do diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 77c86b550..3c00d0ffc 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -482,8 +482,13 @@ def stmt_count end it "should raise an error given an invalid DATETIME" do - expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ - raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") + if @client.info[:version] < "8.0" + expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ + raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") + else + expect(@client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").to_a.first).to \ + eql("bad_datetime" => nil) + end end context "string encoding for ENUM values" do From 366bd0de190f44f2e1c1da7048921dd33b64f187 Mon Sep 17 00:00:00 2001 From: Aaron Brady Date: Mon, 29 Nov 2021 21:13:41 -0500 Subject: [PATCH 696/783] Fix session tracking tests --- spec/mysql2/client_spec.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 3ec52db4b..1519e0f5d 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1078,19 +1078,24 @@ def run_gc end it "returns multiple session track type values when available" do - @client.query("SET @@SESSION.session_track_transaction_info='CHARACTERISTICS'") + @client.query("SET @@SESSION.session_track_transaction_info='CHARACTERISTICS';") - res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_STATE) - expect(res).to eq(["________"]) + res = @client.session_track(Mysql2::Client::SESSION_TRACK_SYSTEM_VARIABLES) + expect(res).to eq(%w[session_track_transaction_info CHARACTERISTICS]) + + res = @client.session_track(Mysql2::Client::SESSION_TRACK_STATE_CHANGE) + expect(res).to be_nil res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_CHARACTERISTICS) expect(res).to eq([""]) + end - res = @client.session_track(Mysql2::Client::SESSION_TRACK_STATE_CHANGE) - expect(res).to be_nil + it "returns valid transaction state inside a transaction" do + @client.query("SET @@SESSION.session_track_transaction_info='CHARACTERISTICS';") + @client.query("START TRANSACTION;") - res = @client.session_track(Mysql2::Client::SESSION_TRACK_SYSTEM_VARIABLES) - expect(res).to eq(%w[session_track_transaction_info CHARACTERISTICS]) + res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_STATE) + expect(res).to eq(["T_______"]) end it "returns empty array if session track type not found" do From 87074a0fff6261f982592f21afb43fdebd5267d3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 30 Nov 2021 11:20:02 -0800 Subject: [PATCH 697/783] Fix syntax Just some syntax fixes that should have been in the original PR. Co-Authored-By: Aaron Stone --- spec/mysql2/client_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 1519e0f5d..a668fd154 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1091,8 +1091,8 @@ def run_gc end it "returns valid transaction state inside a transaction" do - @client.query("SET @@SESSION.session_track_transaction_info='CHARACTERISTICS';") - @client.query("START TRANSACTION;") + @client.query("SET @@SESSION.session_track_transaction_info='CHARACTERISTICS'") + @client.query("START TRANSACTION") res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_STATE) expect(res).to eq(["T_______"]) From 1e0f462519b92390e3f7775c225d9bdc6d758db2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 30 Nov 2021 12:43:23 -0800 Subject: [PATCH 698/783] install openssl on macos --- .github/workflows/build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 756185a24..085de73c4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,6 +55,11 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} + - name: Install openssl + if: matrix.os == 'macos-latest' + run: | + brew update + brew install openssl - run: ruby -v - run: bundle install --without development - if: matrix.db != '' From cca57b97ad6d1b1b985376be110b89d2b487dea6 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 30 Nov 2021 12:59:43 -0800 Subject: [PATCH 699/783] fix assertion on maria downgrade psych --- Gemfile | 3 +++ spec/mysql2/client_spec.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 3dac0c0b1..d61c57aa4 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,9 @@ gem 'irb', require: false group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' + + # Downgrade psych because old RuboCop can't use new Psych + gem 'psych', '~> 3.3.2' # https://github.com/bbatsov/rubocop/pull/4789 gem 'rubocop', '~> 0.50.0' end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 1519e0f5d..41fb834bf 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -604,7 +604,7 @@ def run_gc end expect do @client.query("SELECT SLEEP(1)") - end.to raise_error(Mysql2::Error, /Lost connection to MySQL server/) + end.to raise_error(Mysql2::Error, /Lost connection/) if RUBY_PLATFORM !~ /mingw|mswin/ expect do From ae93ec53010b00ab7026a8fe6b285eb85edda76c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 30 Nov 2021 13:06:37 -0800 Subject: [PATCH 700/783] downgrade psych --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index d61c57aa4..6de48b0d1 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ group :test do gem 'rspec', '~> 3.2' # Downgrade psych because old RuboCop can't use new Psych - gem 'psych', '~> 3.3.2' + gem 'psych', '< 4.0.0' # https://github.com/bbatsov/rubocop/pull/4789 gem 'rubocop', '~> 0.50.0' end From 99d680615fe195efda62ac60ef2318cfb2111e56 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 1 Dec 2021 09:42:43 +0100 Subject: [PATCH 701/783] Move rubocop to a separate CI job We use a very old version of rubocop to support ruby 2.0, however that version is not compatible with ruby-head. Either way there's not point running rubocop on every ruby version a distinct CI job is more efficient. --- .github/workflows/build.yml | 2 +- .github/workflows/rubocop.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/rubocop.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 756185a24..1dfb4dda9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,4 +61,4 @@ jobs: run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV - run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts - run: bash ci/setup.sh - - run: bundle exec rake + - run: bundle exec rake spec diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 000000000..913c158e9 --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,27 @@ +name: RuboCop + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby 2.4 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.4 + - name: Cache gems + uses: actions/cache@v1 + with: + path: vendor/bundle + key: ${{ runner.os }}-rubocop-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-rubocop- + - name: Install gems + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Run RuboCop + run: bundle exec rubocop From 148dbc9ba2867b85458ca3048300fa3d1f4b9eb9 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 1 Dec 2021 11:14:51 +0100 Subject: [PATCH 702/783] Fix a mismatching size warning ``` ext/mysql2/result.c:259:55: warning: format specifies type 'long' but the argument has type 'int' [-Wformat] rb_field_type = rb_sprintf("decimal(%ld,%d)", precision, field->decimals); ``` --- ext/mysql2/result.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 66c9fdd12..5e03c90ba 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -256,7 +256,7 @@ static VALUE rb_mysql_result_fetch_field_type(VALUE self, unsigned int idx) { https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/field.cc#L2246 */ precision = field->length - (field->decimals > 0 ? 2 : 1); - rb_field_type = rb_sprintf("decimal(%ld,%d)", precision, field->decimals); + rb_field_type = rb_sprintf("decimal(%d,%d)", precision, field->decimals); break; case MYSQL_TYPE_STRING: // char[] if (field->flags & ENUM_FLAG) { From 23106c0af77e0e5bdd8e44e784fb77c36d75f9b6 Mon Sep 17 00:00:00 2001 From: Jean byroot Boussier Date: Mon, 17 Jan 2022 22:15:14 +0100 Subject: [PATCH 703/783] Undefine T_DATA allocators for Ruby 3.2 compatibility (#1236) Ref: https://bugs.ruby-lang.org/issues/18007 Co-authored-by: Jean Boussier --- ext/mysql2/result.c | 1 + ext/mysql2/statement.c | 1 + spec/mysql2/result_spec.rb | 13 ++----------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 5e03c90ba..314cecdaa 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -1174,6 +1174,7 @@ void init_mysql2_result() { rb_global_variable(&cDateTime); cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject); + rb_undef_alloc_func(cMysql2Result); rb_global_variable(&cMysql2Result); rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 137268afb..b31efeb27 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -581,6 +581,7 @@ void init_mysql2_statement() { rb_global_variable(&cBigDecimal); cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); + rb_undef_alloc_func(cMysql2Statement); rb_global_variable(&cMysql2Statement); rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0); diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 73fd69967..614cafc1a 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -6,17 +6,8 @@ end it "should raise a TypeError exception when it doesn't wrap a result set" do - if RUBY_VERSION >= "3.1" - expect { Mysql2::Result.new }.to raise_error(TypeError) - expect { Mysql2::Result.allocate }.to raise_error(TypeError) - else - r = Mysql2::Result.new - expect { r.count }.to raise_error(TypeError) - expect { r.fields }.to raise_error(TypeError) - expect { r.field_types }.to raise_error(TypeError) - expect { r.size }.to raise_error(TypeError) - expect { r.each }.to raise_error(TypeError) - end + expect { Mysql2::Result.new }.to raise_error(TypeError) + expect { Mysql2::Result.allocate }.to raise_error(TypeError) end it "should have included Enumerable" do From 33ee746f5af293a58c060a057a6bf94e4deb2051 Mon Sep 17 00:00:00 2001 From: Jean byroot Boussier Date: Mon, 17 Jan 2022 22:15:33 +0100 Subject: [PATCH 704/783] Add Ruby 3.1 to the CI matrix (#1235) Co-authored-by: Jean Boussier --- .github/workflows/build.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f01c3f8c..4c6d7514e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,35 +17,36 @@ jobs: - ubuntu-18.04 # bionic # - ubuntu-16.04 # xenial ruby: + - '3.1' - '3.0' - - 2.7 - - 2.6 - - 2.5 - - 2.4 - - 2.3 - - 2.2 - - 2.1 + - '2.7' + - '2.6' + - '2.5' + - '2.4' + - '2.3' + - '2.2' + - '2.1' db: [''] include: # Allow failure due to Mysql2::Error: Unknown system variable 'session_track_system_variables'. - - {os: ubuntu-16.04, ruby: 2.4, db: mariadb10.0, allow-failure: true} + - {os: ubuntu-16.04, ruby: '2.4', db: mariadb10.0, allow-failure: true} # Comment out due to ci/setup.sh stucking. # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1} # Allow failure due to the issue #965, #1165. - - {os: ubuntu-20.04, ruby: 2.4, db: mariadb10.3, allow-failure: true} - - {os: ubuntu-18.04, ruby: 2.4, db: mysql57} + - {os: ubuntu-20.04, ruby: '2.4', db: mariadb10.3, allow-failure: true} + - {os: ubuntu-18.04, ruby: '2.4', db: mysql57} # Allow failure due to the issue #1165. - - {os: ubuntu-20.04, ruby: 2.4, db: mysql80, allow-failure: true} + - {os: ubuntu-20.04, ruby: '2.4', db: mysql80, allow-failure: true} - {os: ubuntu-18.04, ruby: 'head', db: '', allow-failure: true} # db: A DB's brew package name in macOS case. # Set a name "db: 'name@X.Y'" when using an old version. # MariaDB lastet version # Allow failure due to the following test failures that rarely happens. # https://github.com/brianmario/mysql2/issues/1194 - - {os: macos-latest, ruby: 2.4, db: mariadb, allow-failure: true} + - {os: macos-latest, ruby: '2.4', db: mariadb, allow-failure: true} # MySQL latest version # Allow failure due to the issue #1165. - - {os: macos-latest, ruby: 2.4, db: mysql, allow-failure: true} + - {os: macos-latest, ruby: '2.4', db: mysql, allow-failure: true} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. fail-fast: false From 8193dc412c6a266045b9d13a9da36c16750939a4 Mon Sep 17 00:00:00 2001 From: akira yamada Date: Tue, 18 Jan 2022 06:49:36 +0900 Subject: [PATCH 705/783] Fix to build with MySQL 5.1 (#1197) --- ext/mysql2/client.c | 6 ++++++ ext/mysql2/extconf.rb | 1 + 2 files changed, 7 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 04bf2cc83..5b699e924 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -935,10 +935,12 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = charval; break; +#ifdef HAVE_MYSQL_DEFAULT_AUTH case MYSQL_DEFAULT_AUTH: charval = (const char *)StringValueCStr(value); retval = charval; break; +#endif #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN case MYSQL_ENABLE_CLEARTEXT_PLUGIN: @@ -1404,7 +1406,11 @@ static VALUE set_init_command(VALUE self, VALUE value) { } static VALUE set_default_auth(VALUE self, VALUE value) { +#ifdef HAVE_MYSQL_DEFAULT_AUTH return _mysql_client_options(self, MYSQL_DEFAULT_AUTH, value); +#else + rb_raise(cMysql2Error, "pluggable authentication is not available, you may need a newer MySQL client library"); +#endif } static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) { diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 2818a9344..75fc8c0ab 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -124,6 +124,7 @@ def add_ssl_defines(header) have_struct_member('MYSQL', 'net.pvio', mysql_h) # These constants are actually enums, so they cannot be detected by #ifdef in C code. +have_const('MYSQL_DEFAULT_AUTH', mysql_h) have_const('MYSQL_ENABLE_CLEARTEXT_PLUGIN', mysql_h) have_const('SERVER_QUERY_NO_GOOD_INDEX_USED', mysql_h) have_const('SERVER_QUERY_NO_INDEX_USED', mysql_h) From 57152003ebdbabdcc3a30d3ae994542b9dc357d9 Mon Sep 17 00:00:00 2001 From: Neal Harris <1023165+nealharris@users.noreply.github.com> Date: Mon, 14 Mar 2022 12:35:47 -0700 Subject: [PATCH 706/783] fix typo in README (#1247) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 257746d08..c5d2aead5 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,7 @@ development: host: 127.0.0.1 port: 3306 flags: - - -COMPRESS + - COMPRESS - FOUND_ROWS - MULTI_STATEMENTS secure_auth: false From 6fee79fbd4efdbd6883f685de7cdec0231788b84 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 14 Mar 2022 12:36:40 -0700 Subject: [PATCH 707/783] Revert "fix typo in README (#1247)" This reverts commit 57152003ebdbabdcc3a30d3ae994542b9dc357d9. Erroneously applied this PR, this is not a typo. You can provide a "-FLAGNAME" to negate a flag. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5d2aead5..257746d08 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,7 @@ development: host: 127.0.0.1 port: 3306 flags: - - COMPRESS + - -COMPRESS - FOUND_ROWS - MULTI_STATEMENTS secure_auth: false From 56c0ced2afe2cabe0b3f1a05e8e5e86713ebbc87 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 14 Mar 2022 12:40:12 -0700 Subject: [PATCH 708/783] README: Prose explanation for the -FLAGNAME option --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 257746d08..aa41fb572 100644 --- a/README.md +++ b/README.md @@ -322,6 +322,8 @@ development: secure_auth: false ``` +In this example, the compression flag is negated with `-COMPRESS`. + ### Using Active Record's DATABASE_URL Active Record typically reads its configuration from a file named `database.yml` or an environment variable `DATABASE_URL`. From cd8dd7783fe956e5856930c85b58f96f12dfc771 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 18 Apr 2022 10:58:35 -0700 Subject: [PATCH 709/783] Add signing key for MySQL 5.7.37 / 8.0.28 and higher (#1254) --- ci/mysql57.sh | 3 ++- ci/mysql80.sh | 3 ++- support/3A79BD29.asc | 49 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 support/3A79BD29.asc diff --git a/ci/mysql57.sh b/ci/mysql57.sh index c8d2cd1a1..6f12cc1e0 100644 --- a/ci/mysql57.sh +++ b/ci/mysql57.sh @@ -5,7 +5,8 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key add support/5072E1F5.asc +apt-key add support/5072E1F5.asc # old signing key +apt-key add support/3A79BD29.asc # 5.7.37 and higher # Verify the repository as add-apt-repository does not. wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-5.7 add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-5.7' diff --git a/ci/mysql80.sh b/ci/mysql80.sh index b4921b1f8..c9c2d529d 100644 --- a/ci/mysql80.sh +++ b/ci/mysql80.sh @@ -5,7 +5,8 @@ set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key add support/5072E1F5.asc +apt-key add support/5072E1F5.asc # old signing key +apt-key add support/3A79BD29.asc # 8.0.28 and higher # Verify the repository as add-apt-repository does not. wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-8.0 add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-8.0' diff --git a/support/3A79BD29.asc b/support/3A79BD29.asc new file mode 100644 index 000000000..d9e7a76f5 --- /dev/null +++ b/support/3A79BD29.asc @@ -0,0 +1,49 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.1.6 +Comment: Hostname: pgp.mit.edu + +mQINBGG4urcBEACrbsRa7tSSyxSfFkB+KXSbNM9rxYqoB78u107skReefq4/+Y72TpDvlDZL +mdv/lK0IpLa3bnvsM9IE1trNLrfi+JES62kaQ6hePPgn2RqxyIirt2seSi3Z3n3jlEg+mSdh +AvW+b+hFnqxo+TY0U+RBwDi4oO0YzHefkYPSmNPdlxRPQBMv4GPTNfxERx6XvVSPcL1+jQ4R +2cQFBryNhidBFIkoCOszjWhm+WnbURsLheBp757lqEyrpCufz77zlq2gEi+wtPHItfqsx3rz +xSRqatztMGYZpNUHNBJkr13npZtGW+kdN/xu980QLZxN+bZ88pNoOuzD6dKcpMJ0LkdUmTx5 +z9ewiFiFbUDzZ7PECOm2g3veJrwr79CXDLE1+39Hr8rDM2kDhSr9tAlPTnHVDcaYIGgSNIBc +YfLmt91133klHQHBIdWCNVtWJjq5YcLQJ9TxG9GQzgABPrm6NDd1t9j7w1L7uwBvMB1wgpir +RTPVfnUSCd+025PEF+wTcBhfnzLtFj5xD7mNsmDmeHkF/sDfNOfAzTE1v2wq0ndYU60xbL6/ +yl/Nipyr7WiQjCG0m3WfkjjVDTfs7/DXUqHFDOu4WMF9v+oqwpJXmAeGhQTWZC/QhWtrjrNJ +AgwKpp263gDSdW70ekhRzsok1HJwX1SfxHJYCMFs2aH6ppzNsQARAQABtDZNeVNRTCBSZWxl +YXNlIEVuZ2luZWVyaW5nIDxteXNxbC1idWlsZEBvc3Mub3JhY2xlLmNvbT6JAlQEEwEIAD4W +IQSFm+jXxYb1OEMLGcJGe5QtOnm9KQUCYbi6twIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgID +AQIeAQIXgAAKCRBGe5QtOnm9KUewD/992sS31WLGoUQ6NoL7qOB4CErkqXtMzpJAKKg2jtBG +G3rKE1/0VAg1D8AwEK4LcCO407wohnH0hNiUbeDck5x20pgS5SplQpuXX1K9vPzHeL/WNTb9 +8S3H2Mzj4o9obED6Ey52tTupttMF8pC9TJ93LxbJlCHIKKwCA1cXud3GycRN72eqSqZfJGds +aeWLmFmHf6oee27d8XLoNjbyAxna/4jdWoTqmp8oT3bgv/TBco23NzqUSVPi+7ljS1hHvcJu +oJYqaztGrAEf/lWIGdfl/kLEh8IYx8OBNUojh9mzCDlwbs83CBqoUdlzLNDdwmzu34Aw7xK1 +4RAVinGFCpo/7EWoX6weyB/zqevUIIE89UABTeFoGih/hx2jdQV/NQNthWTW0jH0hmPnajBV +AJPYwAuO82rx2pnZCxDATMn0elOkTue3PCmzHBF/GT6c65aQC4aojj0+Veh787QllQ9FrWbw +nTz+4fNzU/MBZtyLZ4JnsiWUs9eJ2V1g/A+RiIKu357Qgy1ytLqlgYiWfzHFlYjdtbPYKjDa +ScnvtY8VO2Rktm7XiV4zKFKiaWp+vuVYpR0/7Adgnlj5Jt9lQQGOr+Z2VYx8SvBcC+by3XAt +YkRHtX5u4MLlVS3gcoWfDiWwCpvqdK21EsXjQJxRr3dbSn0HaVj4FJZX0QQ7WZm6WLkCDQRh +uLq3ARAA6RYjqfC0YcLGKvHhoBnsX29vy9Wn1y2JYpEnPUIB8X0VOyz5/ALv4Hqtl4THkH+m +mMuhtndoq2BkCCk508jWBvKS1S+Bd2esB45BDDmIhuX3ozu9Xza4i1FsPnLkQ0uMZJv30ls2 +pXFmskhYyzmo6aOmH2536LdtPSlXtywfNV1HEr69V/AHbrEzfoQkJ/qvPzELBOjfjwtDPDeP +iVgW9LhktzVzn/BjO7XlJxw4PGcxJG6VApsXmM3t2fPN9eIHDUq8ocbHdJ4en8/bJDXZd9eb +QoILUuCg46hE3p6nTXfnPwSRnIRnsgCzeAz4rxDR4/Gv1Xpzv5wqpL21XQi3nvZKlcv7J1IR +VdphK66De9GpVQVTqC102gqJUErdjGmxmyCA1OOORqEPfKTrXz5YUGsWwpH+4xCuNQP0qmre +Rw3ghrH8potIr0iOVXFic5vJfBTgtcuEB6E6ulAN+3jqBGTaBML0jxgj3Z5VC5HKVbpg2DbB +/wMrLwFHNAbzV5hj2Os5Zmva0ySP1YHB26pAW8dwB38GBaQvfZq3ezM4cRAo/iJ/GsVE98dZ +EBO+Ml+0KYj+ZG+vyxzo20sweun7ZKT+9qZM90f6cQ3zqX6IfXZHHmQJBNv73mcZWNhDQOHs +4wBoq+FGQWNqLU9xaZxdXw80r1viDAwOy13EUtcVbTkAEQEAAYkCPAQYAQgAJhYhBIWb6NfF +hvU4QwsZwkZ7lC06eb0pBQJhuLq3AhsMBQkDwmcAAAoJEEZ7lC06eb0pSi8P/iy+dNnxrtiE +Nn9vkkA7AmZ8RsvPXYVeDCDSsL7UfhbS77r2L1qTa2aB3gAZUDIOXln51lSxMeeLtOequLME +V2Xi5km70rdtnja5SmWfc9fyExunXnsOhg6UG872At5CGEZU0c2Nt/hlGtOR3xbt3O/Uwl+d +ErQPA4BUbW5K1T7OC6oPvtlKfF4bGZFloHgt2yE9YSNWZsTPe6XJSapemHZLPOxJLnhs3VBi +rWE31QS0bRl5AzlO/fg7ia65vQGMOCOTLpgChTbcZHtozeFqva4IeEgE4xN+6r8WtgSYeGGD +RmeMEVjPM9dzQObf+SvGd58u2z9f2agPK1H32c69RLoA0mHRe7Wkv4izeJUc5tumUY0e8Ojd +enZZjT3hjLh6tM+mrp2oWnQIoed4LxUw1dhMOj0rYXv6laLGJ1FsW5eSke7ohBLcfBBTKnMC +BohROHy2E63Wggfsdn3UYzfqZ8cfbXetkXuLS/OM3MXbiNjg+ElYzjgWrkayu7yLakZx+mx6 +sHPIJYm2hzkniMG29d5mGl7ZT9emP9b+CfqGUxoXJkjs0gnDl44bwGJ0dmIBu3ajVAaHODXy +Y/zdDMGjskfEYbNXCAY2FRZSE58tgTvPKD++Kd2KGplMU2EIFT7JYfKhHAB5DGMkx92HUMid +sTSKHe+QnnnoFmu4gnmDU31i +=Xqbo +-----END PGP PUBLIC KEY BLOCK----- From 96f6102c292daf64b1b3c7c5f79466c96b684b68 Mon Sep 17 00:00:00 2001 From: mishina <32959831+mishina2228@users.noreply.github.com> Date: Wed, 20 Apr 2022 01:31:51 +0900 Subject: [PATCH 710/783] Remove ubuntu-16.04 from workflows (#1257) GitHub Actions no longer supports Ubuntu 16.04. https://github.blog/changelog/2021-04-29-github-actions-ubuntu-16-04-lts-virtual-environment-will-be-removed-on-september-20-2021/ --- .github/workflows/build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c6d7514e..4ad8e4954 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,6 @@ jobs: # https://github.com/brianmario/mysql2/issues/1165 # - ubuntu-20.04 # focal - ubuntu-18.04 # bionic - # - ubuntu-16.04 # xenial ruby: - '3.1' - '3.0' @@ -28,8 +27,6 @@ jobs: - '2.1' db: [''] include: - # Allow failure due to Mysql2::Error: Unknown system variable 'session_track_system_variables'. - - {os: ubuntu-16.04, ruby: '2.4', db: mariadb10.0, allow-failure: true} # Comment out due to ci/setup.sh stucking. # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1} # Allow failure due to the issue #965, #1165. From ffdd8c6c671650a0806256a92966cbd7041c6372 Mon Sep 17 00:00:00 2001 From: mishina <32959831+mishina2228@users.noreply.github.com> Date: Wed, 27 Apr 2022 04:36:01 +0900 Subject: [PATCH 711/783] Update GitHub Actions workflows (#1253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update GitHub Actions from actions/checkout from v2 to v3 * Enable bundler-cache in ruby/setup-ruby * Pass `BUNDLE_WITHOUT: development` to skip gems for development Co-authored-by: Aaron Stone Co-authored-by: Josef Šimánek --- .github/workflows/build.yml | 17 +++++++++-------- .github/workflows/container.yml | 2 +- .github/workflows/rubocop.yml | 17 ++++------------- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ad8e4954..556ccc898 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,19 +47,20 @@ jobs: # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. fail-fast: false + env: + BUNDLE_WITHOUT: development steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - name: Install openssl + if: matrix.os == 'macos-latest' + run: | + brew update + brew install openssl # https://github.com/ruby/setup-ruby - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - - name: Install openssl - if: matrix.os == 'macos-latest' - run: | - brew update - brew install openssl - - run: ruby -v - - run: bundle install --without development + bundler-cache: true # runs 'bundle install' and caches installed gems automatically - if: matrix.db != '' run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV - run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 9ddfe42b9..bd14debf2 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -21,7 +21,7 @@ jobs: # if any matrix job fails unlike Travis fast_finish. fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: docker build -t mysql2 -f ci/Dockerfile_${{ matrix.distro }} --build-arg IMAGE=${{ matrix.image }} . # Add the "--cap-add=... --security-opt seccomp=..." options # as a temporary workaround to avoid the following issue diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 913c158e9..8a7725851 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -5,23 +5,14 @@ on: [push, pull_request] jobs: build: runs-on: ubuntu-latest - + env: + BUNDLE_WITHOUT: development steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Ruby 2.4 uses: ruby/setup-ruby@v1 with: ruby-version: 2.4 - - name: Cache gems - uses: actions/cache@v1 - with: - path: vendor/bundle - key: ${{ runner.os }}-rubocop-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-rubocop- - - name: Install gems - run: | - bundle config path vendor/bundle - bundle install --jobs 4 --retry 3 + bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run RuboCop run: bundle exec rubocop From e9c662912dc3bd3707e6c7f0c75e591294cffe12 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 3 May 2022 08:58:05 -0700 Subject: [PATCH 712/783] bump version --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index d97f4390d..6728d4434 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.5.3".freeze + VERSION = "0.5.4".freeze end From 989cc257360e1686287e5db8b56c522e58b21100 Mon Sep 17 00:00:00 2001 From: mishina Date: Mon, 30 May 2022 18:43:40 +0900 Subject: [PATCH 713/783] Upgrade RuboCop to the latest --- .rubocop.yml | 9 ++++++--- .rubocop_todo.yml | 4 ++-- Gemfile | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index d41d8d923..a9478b791 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -12,16 +12,19 @@ AllCops: Layout/CaseIndentation: EnforcedStyle: end -Layout/IndentHash: +Layout/FirstHashElementIndentation: EnforcedStyle: consistent -Lint/EndAlignment: +Layout/EndAlignment: EnforcedStyleAlignWith: variable Style/TrailingCommaInArguments: EnforcedStyleForMultiline: consistent_comma -Style/TrailingCommaInLiteral: +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: consistent_comma + +Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: consistent_comma Style/TrivialAccessors: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bc34785a7..f52e30cb8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -10,7 +10,7 @@ # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent -Layout/IndentHeredoc: +Layout/HeredocIndentation: Exclude: - 'support/ruby_enc_to_mysql.rb' - 'tasks/compile.rake' @@ -41,7 +41,7 @@ Metrics/CyclomaticComplexity: # Offense count: 313 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https -Metrics/LineLength: +Layout/LineLength: Max: 232 # Offense count: 6 diff --git a/Gemfile b/Gemfile index 6de48b0d1..ee1e1097c 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ group :test do # Downgrade psych because old RuboCop can't use new Psych gem 'psych', '< 4.0.0' # https://github.com/bbatsov/rubocop/pull/4789 - gem 'rubocop', '~> 0.50.0' + gem 'rubocop', '~> 1.30' end group :benchmarks, optional: true do From d238d7fb7f11aa1f7050eab28a31a2a91d256067 Mon Sep 17 00:00:00 2001 From: mishina Date: Mon, 30 May 2022 23:20:50 +0900 Subject: [PATCH 714/783] Install RuboCop only on Ruby 2.6+ Run RuboCop on Ruby 2.6 --- .github/workflows/rubocop.yml | 4 ++-- Gemfile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 8a7725851..d66266dd6 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -9,10 +9,10 @@ jobs: BUNDLE_WITHOUT: development steps: - uses: actions/checkout@v3 - - name: Set up Ruby 2.4 + - name: Set up Ruby 2.6 uses: ruby/setup-ruby@v1 with: - ruby-version: 2.4 + ruby-version: 2.6 bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run RuboCop run: bundle exec rubocop diff --git a/Gemfile b/Gemfile index ee1e1097c..e6575bc1d 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ group :test do # Downgrade psych because old RuboCop can't use new Psych gem 'psych', '< 4.0.0' # https://github.com/bbatsov/rubocop/pull/4789 - gem 'rubocop', '~> 1.30' + gem 'rubocop', '~> 1.30' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') end group :benchmarks, optional: true do From a9517accd9627da2174828a14e52d4fae7ba8140 Mon Sep 17 00:00:00 2001 From: mishina Date: Mon, 30 May 2022 23:26:06 +0900 Subject: [PATCH 715/783] Fix some simple cops --- .rubocop.yml | 3 +++ Gemfile | 10 +++++----- benchmark/allocations.rb | 2 +- lib/mysql2.rb | 1 + lib/mysql2/client.rb | 4 ++++ spec/mysql2/client_spec.rb | 2 ++ spec/mysql2/result_spec.rb | 18 +++++++++--------- spec/mysql2/statement_spec.rb | 24 ++++++++++++------------ spec/spec_helper.rb | 3 ++- support/mysql_enc_to_ruby.rb | 2 +- 10 files changed, 40 insertions(+), 29 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a9478b791..74224c6b4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -18,6 +18,9 @@ Layout/FirstHashElementIndentation: Layout/EndAlignment: EnforcedStyleAlignWith: variable +Layout/HashAlignment: + EnforcedHashRocketStyle: table + Style/TrailingCommaInArguments: EnforcedStyleForMultiline: consistent_comma diff --git a/Gemfile b/Gemfile index e6575bc1d..96429bf8a 100644 --- a/Gemfile +++ b/Gemfile @@ -2,11 +2,11 @@ source 'https://rubygems.org/' gemspec -gem 'rake', if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2") - '~> 13.0.1' - else - '< 13' - end +if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2") + gem 'rake', '~> 13.0.1' +else + gem 'rake', '< 13' +end gem 'rake-compiler', '~> 1.1.0' # For local debugging, irb is Gemified since Ruby 2.6 diff --git a/benchmark/allocations.rb b/benchmark/allocations.rb index 7926a837d..cc6df4d18 100644 --- a/benchmark/allocations.rb +++ b/benchmark/allocations.rb @@ -16,7 +16,7 @@ def bench_allocations(feature, iterations = 10, batch_size = 1000) GC::Profiler.clear GC::Profiler.enable iterations.times { yield batch_size } - GC::Profiler.report(STDOUT) + GC::Profiler.report($stdout) GC::Profiler.disable end diff --git a/lib/mysql2.rb b/lib/mysql2.rb index e49060208..9461846e9 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -65,6 +65,7 @@ module Util # def self.key_hash_as_symbols(hash) return nil unless hash + Hash[hash.map { |k, v| [k.to_sym, v] }] end diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 8a4a3e11b..582b6e305 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -20,6 +20,7 @@ def self.default_query_options def initialize(opts = {}) raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash + opts = Mysql2::Util.key_hash_as_symbols(opts) @read_timeout = nil @query_options = self.class.default_query_options.dup @@ -33,6 +34,7 @@ def initialize(opts = {}) # TODO: stricter validation rather than silent massaging %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth].each do |key| next unless opts.key?(key) + case key when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation @@ -136,6 +138,7 @@ def find_default_ca_path # and performance_schema.session_account_connect_attrs def parse_connect_attrs(conn_attrs) return {} if Mysql2::Client::CONNECT_ATTRS.zero? + conn_attrs ||= {} conn_attrs[:program_name] ||= $PROGRAM_NAME conn_attrs.each_with_object({}) do |(key, value), hash| @@ -152,6 +155,7 @@ def query(sql, options = {}) def query_info info = query_info_string return {} unless info + info_hash = {} info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i } info_hash diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 5861882c6..2f757815e 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -54,6 +54,7 @@ Klient = Class.new(Mysql2::Client) do attr_reader :connect_args + def connect(*args) @connect_args ||= [] @connect_args << args @@ -212,6 +213,7 @@ def run_gc 10.times do closed = @client.query("SHOW PROCESSLIST").none? { |row| row['Id'] == connection_id } break if closed + sleep(0.1) end expect(closed).to eq(true) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 614cafc1a..ed3e9d262 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -497,17 +497,17 @@ end { - 'char_test' => 'CHAR', - 'varchar_test' => 'VARCHAR', - 'varbinary_test' => 'VARBINARY', - 'tiny_blob_test' => 'TINYBLOB', - 'tiny_text_test' => 'TINYTEXT', - 'blob_test' => 'BLOB', - 'text_test' => 'TEXT', + 'char_test' => 'CHAR', + 'varchar_test' => 'VARCHAR', + 'varbinary_test' => 'VARBINARY', + 'tiny_blob_test' => 'TINYBLOB', + 'tiny_text_test' => 'TINYTEXT', + 'blob_test' => 'BLOB', + 'text_test' => 'TEXT', 'medium_blob_test' => 'MEDIUMBLOB', 'medium_text_test' => 'MEDIUMTEXT', - 'long_blob_test' => 'LONGBLOB', - 'long_text_test' => 'LONGTEXT', + 'long_blob_test' => 'LONGBLOB', + 'long_text_test' => 'LONGTEXT', }.each do |field, type| it "should return a String for #{type}" do expect(test_result[field]).to be_an_instance_of(String) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 3c00d0ffc..57e590804 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -1,4 +1,4 @@ -require './spec/spec_helper.rb' +require './spec/spec_helper' RSpec.describe Mysql2::Statement do before(:example) do @@ -277,7 +277,7 @@ def stmt_count end context "#each" do - # note: The current impl. of prepared statement requires results to be cached on #execute except for streaming queries + # NOTE: The current impl. of prepared statement requires results to be cached on #execute except for streaming queries # The drawback of this is that args of Result#each is ignored... it "should yield rows as hash's" do @@ -320,7 +320,7 @@ def stmt_count result = @client.prepare("SELECT 1 UNION SELECT 2").execute(stream: true, cache_rows: false) expect do result.each {} - result.each {} + result.each {} # rubocop:disable Style/CombinableLoops end.to raise_exception(Mysql2::Error) end end @@ -573,17 +573,17 @@ def stmt_count end { - 'char_test' => 'CHAR', - 'varchar_test' => 'VARCHAR', - 'varbinary_test' => 'VARBINARY', - 'tiny_blob_test' => 'TINYBLOB', - 'tiny_text_test' => 'TINYTEXT', - 'blob_test' => 'BLOB', - 'text_test' => 'TEXT', + 'char_test' => 'CHAR', + 'varchar_test' => 'VARCHAR', + 'varbinary_test' => 'VARBINARY', + 'tiny_blob_test' => 'TINYBLOB', + 'tiny_text_test' => 'TINYTEXT', + 'blob_test' => 'BLOB', + 'text_test' => 'TEXT', 'medium_blob_test' => 'MEDIUMBLOB', 'medium_text_test' => 'MEDIUMTEXT', - 'long_blob_test' => 'LONGBLOB', - 'long_text_test' => 'LONGTEXT', + 'long_blob_test' => 'LONGBLOB', + 'long_text_test' => 'LONGTEXT', }.each do |field, type| it "should return a String for #{type}" do expect(test_result[field]).to be_an_instance_of(String) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2e86e112c..594e7d339 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,6 +32,7 @@ def new_client(option_overrides = {}) @clients ||= [] @clients << client return client unless block_given? + begin yield client ensure @@ -42,7 +43,7 @@ def new_client(option_overrides = {}) def num_classes # rubocop:disable Lint/UnifiedInteger - 0.class == Integer ? [Integer] : [Fixnum, Bignum] + 0.instance_of?(Integer) ? [Integer] : [Fixnum, Bignum] # rubocop:enable Lint/UnifiedInteger end diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index 33c878885..4db703409 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -55,7 +55,7 @@ collations.each do |collation| mysql_col_idx = collation[2].to_i rb_enc = mysql_to_rb.fetch(collation[1]) do |mysql_enc| - $stderr.puts "WARNING: Missing mapping for collation \"#{collation[0]}\" with encoding \"#{mysql_enc}\" and id #{mysql_col_idx}, assuming NULL" + warn "WARNING: Missing mapping for collation \"#{collation[0]}\" with encoding \"#{mysql_enc}\" and id #{mysql_col_idx}, assuming NULL" "NULL" end encodings[mysql_col_idx - 1] = [mysql_col_idx, rb_enc] From 75665fade70b6202ea4608612cb2547e60a8be45 Mon Sep 17 00:00:00 2001 From: mishina Date: Mon, 30 May 2022 22:52:34 +0900 Subject: [PATCH 716/783] Regenerate .rubocop_todo.yml to let rubocop pass --- .rubocop_todo.yml | 143 +++++++++++++++++++++++++++++++++--------- ext/mysql2/extconf.rb | 2 +- 2 files changed, 113 insertions(+), 32 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f52e30cb8..623647eff 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,28 +1,41 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2017-11-25 19:54:28 -0500 using RuboCop version 0.50.0. +# on 2022-05-30 13:48:55 UTC using RuboCop version 1.30.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent +# This cop supports safe autocorrection (--autocorrect). Layout/HeredocIndentation: Exclude: - 'support/ruby_enc_to_mysql.rb' - 'tasks/compile.rake' # Offense count: 2 +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: + Exclude: + - 'spec/mysql2/client_spec.rb' + - 'tasks/rspec.rake' + +# Offense count: 1 +Lint/MissingSuper: + Exclude: + - 'lib/mysql2/em.rb' + +# Offense count: 2 +# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: - Max: 91 + Max: 94 -# Offense count: 31 -# Configuration parameters: CountComments, ExcludedMethods. +# Offense count: 34 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. +# IgnoredMethods: refine Metrics/BlockLength: - Max: 860 + Max: 592 # Offense count: 1 # Configuration parameters: CountBlocks. @@ -30,66 +43,134 @@ Metrics/BlockNesting: Max: 5 # Offense count: 1 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 135 # Offense count: 3 +# Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: - Max: 32 - -# Offense count: 313 -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Layout/LineLength: - Max: 232 + Max: 34 # Offense count: 6 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Max: 57 # Offense count: 2 +# Configuration parameters: IgnoredMethods. Metrics/PerceivedComplexity: - Max: 29 + Max: 32 -# Offense count: 3 -# Configuration parameters: Blacklist. -# Blacklist: END, (?-mix:EO[A-Z]{1}) +# Offense count: 2 +# Configuration parameters: ForbiddenDelimiters. +# ForbiddenDelimiters: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) Naming/HeredocDelimiterNaming: Exclude: - 'tasks/compile.rake' -# Offense count: 10 +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/CaseLikeIf: + Exclude: + - 'ext/mysql2/extconf.rb' + +# Offense count: 8 +# Configuration parameters: AllowedConstants. Style/Documentation: Exclude: - 'spec/**/*' - 'test/**/*' - 'benchmark/active_record.rb' - 'benchmark/allocations.rb' - - 'benchmark/query_with_mysql_casting.rb' - 'lib/mysql2.rb' - 'lib/mysql2/client.rb' - 'lib/mysql2/em.rb' - 'lib/mysql2/error.rb' - - 'lib/mysql2/result.rb' - 'lib/mysql2/statement.rb' -# Offense count: 14 +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). +Style/ExpandPathArguments: + Exclude: + - 'ext/mysql2/extconf.rb' + - 'mysql2.gemspec' + - 'spec/mysql2/client_spec.rb' + - 'support/mysql_enc_to_ruby.rb' + - 'tasks/compile.rake' + +# Offense count: 15 # Configuration parameters: AllowedVariables. Style/GlobalVars: Exclude: - 'ext/mysql2/extconf.rb' -# Offense count: 17 -# Cop supports --auto-correct. -# Configuration parameters: Strict. +# Offense count: 8 +# This cop supports safe autocorrection (--autocorrect). +Style/IfUnlessModifier: + Exclude: + - 'lib/mysql2.rb' + - 'lib/mysql2/client.rb' + - 'spec/mysql2/client_spec.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMethodComparison. +Style/MultipleComparison: + Exclude: + - 'lib/mysql2/client.rb' + +# Offense count: 18 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Strict, AllowedNumbers. Style/NumericLiterals: MinDigits: 20 -# Offense count: 726 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline. +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. +# AllowedMethods: present?, blank?, presence, try, try! +Style/SafeNavigation: + Exclude: + - 'benchmark/setup_db.rb' + - 'ext/mysql2/extconf.rb' + - 'lib/mysql2/em.rb' + +# Offense count: 14 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Mode. +Style/StringConcatenation: + Exclude: + - 'benchmark/active_record.rb' + - 'benchmark/active_record_threaded.rb' + - 'benchmark/allocations.rb' + - 'benchmark/escape.rb' + - 'benchmark/query_with_mysql_casting.rb' + - 'benchmark/query_without_mysql_casting.rb' + - 'benchmark/sequel.rb' + - 'benchmark/setup_db.rb' + - 'ext/mysql2/extconf.rb' + - 'lib/mysql2/client.rb' + - 'tasks/compile.rake' + +# Offense count: 782 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: Enabled: false + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: WordRegex. +# SupportedStyles: percent, brackets +Style/WordArray: + EnforcedStyle: percent + MinSize: 4 + +# Offense count: 32 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 232 diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 75fc8c0ab..ea9f448e6 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -181,7 +181,7 @@ def add_ssl_defines(header) end end -unless disabled_sanitizers.empty? +unless disabled_sanitizers.empty? # rubocop:disable Style/IfUnlessModifier abort "-----\nCould not enable requested sanitizers: #{disabled_sanitizers.join(',')}\n-----" end From 0d9622db9903f0481c4ebde6562335e4f1489246 Mon Sep 17 00:00:00 2001 From: mishina Date: Mon, 30 May 2022 22:37:22 +0900 Subject: [PATCH 717/783] Opt out NewCops and RuboCop extensions --- .rubocop.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 74224c6b4..856e99da9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,6 +2,8 @@ inherit_from: .rubocop_todo.yml AllCops: TargetRubyVersion: 2.0 + SuggestExtensions: false + NewCops: disable DisplayCopNames: true Exclude: From 80e0001f87ffd3974835478d2e9b068cc5d37f71 Mon Sep 17 00:00:00 2001 From: mishina <32959831+mishina2228@users.noreply.github.com> Date: Sat, 4 Jun 2022 06:38:49 +0900 Subject: [PATCH 718/783] Do not run RuboCop in Container CI (#1259) --- ci/container.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/container.sh b/ci/container.sh index e07678699..90552a919 100644 --- a/ci/container.sh +++ b/ci/container.sh @@ -8,4 +8,4 @@ bundle install --path vendor/bundle --without development # Start mysqld service. bash ci/setup_container.sh -bundle exec rake +bundle exec rake spec From ad9d23b3a82b1c70038062aee1e34150e4efeb22 Mon Sep 17 00:00:00 2001 From: mishina Date: Sat, 4 Jun 2022 08:06:18 +0900 Subject: [PATCH 719/783] Remove the extra `psych` for older versions of RuboCop --- Gemfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Gemfile b/Gemfile index 96429bf8a..6147ee62d 100644 --- a/Gemfile +++ b/Gemfile @@ -16,8 +16,6 @@ group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' - # Downgrade psych because old RuboCop can't use new Psych - gem 'psych', '< 4.0.0' # https://github.com/bbatsov/rubocop/pull/4789 gem 'rubocop', '~> 1.30' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') end From aee1fcb65df4505277de7e6c894b115509982a0a Mon Sep 17 00:00:00 2001 From: mishina Date: Mon, 6 Jun 2022 22:58:32 +0900 Subject: [PATCH 720/783] Upgrade RuboCop to >= 1.30.1 RuboCop v1.30.1 fixed a false positive for Style/SafeNavigation --- .rubocop_todo.yml | 10 ---------- Gemfile | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 623647eff..bc717490d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -126,16 +126,6 @@ Style/MultipleComparison: Style/NumericLiterals: MinDigits: 20 -# Offense count: 4 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. -# AllowedMethods: present?, blank?, presence, try, try! -Style/SafeNavigation: - Exclude: - - 'benchmark/setup_db.rb' - - 'ext/mysql2/extconf.rb' - - 'lib/mysql2/em.rb' - # Offense count: 14 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Mode. diff --git a/Gemfile b/Gemfile index 6147ee62d..7b4e1b8a7 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ group :test do gem 'rspec', '~> 3.2' # https://github.com/bbatsov/rubocop/pull/4789 - gem 'rubocop', '~> 1.30' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') + gem 'rubocop', '~> 1.30', '>= 1.30.1' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') end group :benchmarks, optional: true do From ba4d46551d132492b34205cdb8fa224c92765bef Mon Sep 17 00:00:00 2001 From: Pat Leamon Date: Wed, 24 Aug 2022 04:31:37 +1000 Subject: [PATCH 721/783] Add path to support homebrew on arm based macbook (#1278) --- ext/mysql2/extconf.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index ea9f448e6..592e02486 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -48,6 +48,7 @@ def add_ssl_defines(header) /opt/local /opt/local/mysql /opt/local/lib/mysql5* + /opt/homebrew/opt/mysql* /usr /usr/mysql /usr/local From 213f008c7fbbb3e0b0a0637cee354bfc9267775b Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 21 Nov 2022 10:20:06 +0100 Subject: [PATCH 722/783] Lock on the current Fiber rather than current Thread Applications using fiber are able to do concurrent queries from the same thread. --- ext/mysql2/client.c | 36 ++++++++++++++++++------------------ ext/mysql2/client.h | 3 +-- ext/mysql2/statement.c | 6 +++--- spec/mysql2/client_spec.rb | 9 ++++++--- spec/spec_helper.rb | 2 ++ 5 files changed, 30 insertions(+), 26 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 5b699e924..c200b3649 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -193,7 +193,7 @@ static void rb_mysql_client_mark(void * wrapper) { mysql_client_wrapper * w = wrapper; if (w) { rb_gc_mark(w->encoding); - rb_gc_mark(w->active_thread); + rb_gc_mark(w->active_fiber); } } @@ -297,7 +297,7 @@ static void *nogvl_close(void *ptr) { mysql_close(wrapper->client); wrapper->closed = 1; wrapper->reconnect_enabled = 0; - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; } return NULL; @@ -342,7 +342,7 @@ static VALUE allocate(VALUE klass) { mysql_client_wrapper * wrapper; obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; wrapper->automatic_close = 1; wrapper->server_version = 0; wrapper->reconnect_enabled = 0; @@ -543,7 +543,7 @@ static VALUE do_send_query(VALUE args) { mysql_client_wrapper *wrapper = query_args->wrapper; if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, query_args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; rb_raise_mysql2_error(wrapper); } return Qnil; @@ -573,7 +573,7 @@ static void *nogvl_do_result(void *ptr, char use_result) { /* once our result is stored off, this connection is ready for another command to be issued */ - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; return result; } @@ -599,13 +599,13 @@ static VALUE rb_mysql_client_async_result(VALUE self) { GET_CLIENT(self); /* if we're not waiting on a result, do nothing */ - if (NIL_P(wrapper->active_thread)) + if (NIL_P(wrapper->active_fiber)) return Qnil; REQUIRE_CONNECTED(wrapper); if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, mark this connection inactive */ - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; rb_raise_mysql2_error(wrapper); } @@ -618,7 +618,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; rb_raise_mysql2_error(wrapper); } /* no data and no error, so query was not a SELECT */ @@ -645,7 +645,7 @@ struct async_query_args { static VALUE disconnect_and_raise(VALUE self, VALUE error) { GET_CLIENT(self); - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; /* Invalidate the MySQL socket to prevent further communication. * The GC will come along later and call mysql_close to free it. @@ -710,7 +710,7 @@ static VALUE disconnect_and_mark_inactive(VALUE self) { GET_CLIENT(self); /* Check if execution terminated while result was still being read. */ - if (!NIL_P(wrapper->active_thread)) { + if (!NIL_P(wrapper->active_fiber)) { if (CONNECTED(wrapper)) { /* Invalidate the MySQL socket to prevent further communication. */ #ifndef _WIN32 @@ -725,24 +725,24 @@ static VALUE disconnect_and_mark_inactive(VALUE self) { } /* Skip mysql client check performed before command execution. */ wrapper->client->status = MYSQL_STATUS_READY; - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; } return Qnil; } -void rb_mysql_client_set_active_thread(VALUE self) { - VALUE thread_current = rb_thread_current(); +static void rb_mysql_client_set_active_fiber(VALUE self) { + VALUE fiber_current = rb_fiber_current(); GET_CLIENT(self); // see if this connection is still waiting on a result from a previous query - if (NIL_P(wrapper->active_thread)) { + if (NIL_P(wrapper->active_fiber)) { // mark this connection active - wrapper->active_thread = thread_current; - } else if (wrapper->active_thread == thread_current) { + wrapper->active_fiber = fiber_current; + } else if (wrapper->active_fiber == fiber_current) { rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result"); } else { - VALUE inspect = rb_inspect(wrapper->active_thread); + VALUE inspect = rb_inspect(wrapper->active_fiber); const char *thr = StringValueCStr(inspect); rb_raise(cMysql2Error, "This connection is in use by: %s", thr); @@ -806,7 +806,7 @@ static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) { args.sql_len = RSTRING_LEN(args.sql); args.wrapper = wrapper; - rb_mysql_client_set_active_thread(self); + rb_mysql_client_set_active_fiber(self); #ifndef _WIN32 rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0); diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 5e0ebe3f0..0a9faaf94 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -3,7 +3,7 @@ typedef struct { VALUE encoding; - VALUE active_thread; /* rb_thread_current() or Qnil */ + VALUE active_fiber; /* rb_fiber_current() or Qnil */ long server_version; int reconnect_enabled; unsigned int connect_timeout; @@ -15,7 +15,6 @@ typedef struct { MYSQL *client; } mysql_client_wrapper; -void rb_mysql_client_set_active_thread(VALUE self); void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result); #define GET_CLIENT(self) \ diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index b31efeb27..fd506de0f 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -448,7 +448,7 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { if (metadata == NULL) { if (mysql_stmt_errno(stmt) != 0) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; rb_raise_mysql2_stmt_error(stmt_wrapper); } // no data and no error, so query was not a SELECT @@ -461,7 +461,7 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { mysql_free_result(metadata); rb_raise_mysql2_stmt_error(stmt_wrapper); } - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; } resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self); @@ -502,7 +502,7 @@ static VALUE rb_mysql_stmt_fields(VALUE self) { if (metadata == NULL) { if (mysql_stmt_errno(stmt) != 0) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. - wrapper->active_thread = Qnil; + wrapper->active_fiber = Qnil; rb_raise_mysql2_stmt_error(stmt_wrapper); } // no data and no error, so query was not a SELECT diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 2f757815e..ede316b76 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -624,10 +624,13 @@ def run_gc end it "should describe the thread holding the active query" do - thr = Thread.new { @client.query("SELECT 1", async: true) } + thr = Thread.new do + @client.query("SELECT 1", async: true) + Fiber.current + end - thr.join - expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(thr.inspect))) + fiber = thr.value + expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(fiber.inspect))) end it "should timeout if we wait longer than :read_timeout" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 594e7d339..edfac4d64 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,8 @@ require 'mysql2' require 'timeout' require 'yaml' +require 'fiber' + DatabaseCredentials = YAML.load_file('spec/configuration.yml') if GC.respond_to?(:verify_compaction_references) From 890342a5aa2554530299ccfa37bcc2eae28e4d02 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 16 Dec 2022 16:28:01 -0800 Subject: [PATCH 723/783] CI: macOS 12 and 13 ship with Ruby 2.6 (#1291) --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 556ccc898..34cc9bbdd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,10 +40,10 @@ jobs: # MariaDB lastet version # Allow failure due to the following test failures that rarely happens. # https://github.com/brianmario/mysql2/issues/1194 - - {os: macos-latest, ruby: '2.4', db: mariadb, allow-failure: true} + - {os: macos-latest, ruby: '2.6', db: mariadb, allow-failure: true} # MySQL latest version # Allow failure due to the issue #1165. - - {os: macos-latest, ruby: '2.4', db: mysql, allow-failure: true} + - {os: macos-latest, ruby: '2.6', db: mysql, allow-failure: true} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. fail-fast: false From 7f6f33a6e0bd652d5e7087edb6e5d00df248d35b Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Sat, 17 Dec 2022 01:34:00 +0100 Subject: [PATCH 724/783] Fix SSL errors on the container Fedora cases. (#1290) This commit fixes the SSL related failures in the CI container Fedora cases. The MariaDB 10.5.18 mysqld failed to start in the CI, container "fedora:rawhide" (Fedora 38) and "fedora:latest" (Fedora 37) cases with the SSL error below. So, we set the SSL configurations manually as well as MacOSX case. ``` + /usr/libexec/mysqld --user=root --log-error=/build/mysql.log --ssl 2022-12-16 17:49:58 0 [Note] /usr/libexec/mysqld (mysqld 10.5.18-MariaDB) starting as process 724 ... + cat /build/mysql.log 2022-12-16 17:49:58 0 [Note] InnoDB: Uses event mutexes 2022-12-16 17:49:58 0 [Note] InnoDB: Compressed tables use zlib 1.2.12 2022-12-16 17:49:58 0 [Note] InnoDB: Number of pools: 1 2022-12-16 17:49:58 0 [Note] InnoDB: Using crc32 + pclmulqdq instructions 2022-12-16 17:49:58 0 [Note] mysqld: O_TMPFILE is not supported on /var/tmp (disabling future attempts) 2022-12-16 17:49:58 0 [Note] InnoDB: Using Linux native AIO 2022-12-16 17:49:58 0 [Note] InnoDB: Initializing buffer pool, total size = 134217728, chunk size = 134217728 2022-12-16 17:49:58 0 [Note] InnoDB: Completed initialization of buffer pool 2022-12-16 17:49:58 0 [Note] InnoDB: 128 rollback segments are active. 2022-12-16 17:49:58 0 [Note] InnoDB: Creating shared tablespace for temporary tables 2022-12-16 17:49:58 0 [Note] InnoDB: Setting file './ibtmp1' size to 12 MB. Physically writing the file full; Please wait ... 2022-12-16 17:49:58 0 [Note] InnoDB: File './ibtmp1' size is now 12 MB. 2022-12-16 17:49:58 0 [Note] InnoDB: 10.5.18 started; log sequence number 45079; transaction id 20 2022-12-16 17:49:58 0 [Note] Plugin 'FEEDBACK' is disabled. 2022-12-16 17:49:58 0 [Note] InnoDB: Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool 2022-12-16 17:49:58 0 [Note] InnoDB: Buffer pool(s) load completed at 221216 17:49:58 SSL error: Private key does not match the certificate public key Error: -16 17:49:58 0 [ERROR] Failed to setup SSL Error: -16 17:49:58 0 [ERROR] SSL error: Private key does not match the certificate public key Error: -16 17:49:58 0 [ERROR] Aborting ``` After fixing the issue, we also saw the following failing tests with the error below. So, we recreated the db user. ``` expected no Exception, got # with backtrace: ``` Note this issue didn't happened on the container CentOS case. But as we applied the fix to all the container cases to simplify the logic. --- ci/setup_container.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ci/setup_container.sh b/ci/setup_container.sh index 6bed4d579..6feacd64d 100644 --- a/ci/setup_container.sh +++ b/ci/setup_container.sh @@ -4,6 +4,8 @@ set -eux MYSQL_TEST_LOG="$(pwd)/mysql.log" +bash ci/ssl.sh + mysql_install_db \ --log-error="${MYSQL_TEST_LOG}" /usr/libexec/mysqld \ @@ -14,4 +16,12 @@ sleep 3 cat ${MYSQL_TEST_LOG} /usr/libexec/mysqld --version + +mysql -u root < Date: Fri, 23 Dec 2022 01:23:27 +0100 Subject: [PATCH 725/783] rubocop: Style/Documentation: Exclude lib/mysql2/result.rb. (#1295) The rubocop 1.41.1 complained with the message below. This commit suppresses the message, and makes the CI pass. https://github.com/brianmario/mysql2/actions/runs/3758719510/jobs/6387395566#step:4:12 ``` lib/mysql2/result.rb:2:3: C: Style/Documentation: Missing top-level documentation comment for class Mysql2::Result. class Result ^^^^^^^^^^^^ ``` --- .rubocop_todo.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bc717490d..eba2091dc 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -87,6 +87,7 @@ Style/Documentation: - 'lib/mysql2/client.rb' - 'lib/mysql2/em.rb' - 'lib/mysql2/error.rb' + - 'lib/mysql2/result.rb' - 'lib/mysql2/statement.rb' # Offense count: 6 From 6525c0202d2e291c50206d55315c35469eed023e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 27 Dec 2022 16:39:15 -0800 Subject: [PATCH 726/783] CI: Add Ruby 3.2 (#1298) --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34cc9bbdd..856e563ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,7 @@ jobs: # - ubuntu-20.04 # focal - ubuntu-18.04 # bionic ruby: + - '3.2' - '3.1' - '3.0' - '2.7' From 79c6263b491b7311242ec1179410575e7009bd8e Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Wed, 28 Dec 2022 02:12:49 +0100 Subject: [PATCH 727/783] CI: Drop allow-failure option in some Ubuntu cases. (#1292) As we fixed some of the issues, we can drop the allow-failure option in some cases. #1165 was resolved by mysql2 0.5.4, while #1194 needs attention. --- .github/workflows/build.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 856e563ac..77701d7e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,12 +30,10 @@ jobs: include: # Comment out due to ci/setup.sh stucking. # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1} - # Allow failure due to the issue #965, #1165. - - {os: ubuntu-20.04, ruby: '2.4', db: mariadb10.3, allow-failure: true} + - {os: ubuntu-20.04, ruby: '2.4', db: mariadb10.3} - {os: ubuntu-18.04, ruby: '2.4', db: mysql57} - # Allow failure due to the issue #1165. - - {os: ubuntu-20.04, ruby: '2.4', db: mysql80, allow-failure: true} - - {os: ubuntu-18.04, ruby: 'head', db: '', allow-failure: true} + - {os: ubuntu-20.04, ruby: '2.4', db: mysql80} + - {os: ubuntu-18.04, ruby: 'head', db: ''} # db: A DB's brew package name in macOS case. # Set a name "db: 'name@X.Y'" when using an old version. # MariaDB lastet version @@ -43,7 +41,7 @@ jobs: # https://github.com/brianmario/mysql2/issues/1194 - {os: macos-latest, ruby: '2.6', db: mariadb, allow-failure: true} # MySQL latest version - # Allow failure due to the issue #1165. + # Allow failure due to the issue #1194. - {os: macos-latest, ruby: '2.6', db: mysql, allow-failure: true} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. From bab4969264250abca836e85a9c332c42be573dfc Mon Sep 17 00:00:00 2001 From: Ryan Tin Date: Thu, 15 Oct 2020 11:43:45 +0800 Subject: [PATCH 728/783] Updated README SSL documentation (#1142) Co-Authored-By: Aaron Stone --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aa41fb572..61e76297b 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,6 @@ Mysql2::Client.new( :reconnect = true/false, :local_infile = true/false, :secure_auth = true/false, - :ssl_mode = :disabled / :preferred / :required / :verify_ca / :verify_identity, :default_file = '/path/to/my.cfg', :default_group = 'my.cfg section', :default_auth = 'authentication_windows_client' @@ -268,8 +267,13 @@ Setting any of the following options will enable an SSL connection, but only if your MySQL client library and server have been compiled with SSL support. MySQL client library defaults will be used for any parameters that are left out or set to nil. Relative paths are allowed, and may be required by managed -hosting providers such as Heroku. Set `:sslverify => true` to require that the -server presents a valid certificate. +hosting providers such as Heroku. + +For MySQL versions 5.7.11 and higher, use `:ssl_mode` to prefer or require an +SSL connection and certificate validation. For earlier versions of MySQL, use +the `:sslverify` boolean. For details on each of the `:ssl_mode` options, see +[https://dev.mysql.com/doc/refman/8.0/en/connection-options.html](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-mode). + ``` ruby Mysql2::Client.new( @@ -279,7 +283,8 @@ Mysql2::Client.new( :sslca => '/path/to/ca-cert.pem', :sslcapath => '/path/to/cacerts', :sslcipher => 'DHE-RSA-AES256-SHA', - :sslverify => true, + :sslverify => true, # Removed in MySQL 8.0 + :ssl_mode = :disabled / :preferred / :required / :verify_ca / :verify_identity, ) ``` From c0410ab74c9a2d7b73ea3f6d0a0c52559a697edb Mon Sep 17 00:00:00 2001 From: fatkodima Date: Wed, 18 Jan 2023 23:20:58 +0200 Subject: [PATCH 729/783] Support for Ruby GC compaction (#1192) Ruby 2.7+ introduced GC compaction via GC.compact. --- ext/mysql2/client.c | 50 ++++++++++++++++++++++++++------ ext/mysql2/client.h | 8 ++++++ ext/mysql2/extconf.rb | 3 ++ ext/mysql2/mysql2_ext.h | 13 +++++++++ ext/mysql2/result.c | 64 +++++++++++++++++++++++++++++++++++------ ext/mysql2/statement.c | 51 +++++++++++++++++++++++++++----- 6 files changed, 166 insertions(+), 23 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index c200b3649..4f62e463d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -192,11 +192,47 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { static void rb_mysql_client_mark(void * wrapper) { mysql_client_wrapper * w = wrapper; if (w) { - rb_gc_mark(w->encoding); - rb_gc_mark(w->active_fiber); + rb_gc_mark_movable(w->encoding); + rb_gc_mark_movable(w->active_fiber); } } +/* this is called during GC */ +static void rb_mysql_client_free(void *ptr) { + mysql_client_wrapper *wrapper = ptr; + decr_mysql2_client(wrapper); +} + +static size_t rb_mysql_client_memsize(const void * wrapper) { + const mysql_client_wrapper * w = wrapper; + return sizeof(*w); +} + +static void rb_mysql_client_compact(void * wrapper) { + mysql_client_wrapper * w = wrapper; + if (w) { + rb_mysql2_gc_location(w->encoding); + rb_mysql2_gc_location(w->active_fiber); + } +} + +const rb_data_type_t rb_mysql_client_type = { + "rb_mysql_client", + { + rb_mysql_client_mark, + rb_mysql_client_free, + rb_mysql_client_memsize, +#ifdef HAVE_RB_GC_MARK_MOVABLE + rb_mysql_client_compact, +#endif + }, + 0, + 0, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY + RUBY_TYPED_FREE_IMMEDIATELY, +#endif +}; + static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client)); VALUE rb_sql_state = rb_str_new2(mysql_sqlstate(wrapper->client)); @@ -303,12 +339,6 @@ static void *nogvl_close(void *ptr) { return NULL; } -/* this is called during GC */ -static void rb_mysql_client_free(void *ptr) { - mysql_client_wrapper *wrapper = ptr; - decr_mysql2_client(wrapper); -} - void decr_mysql2_client(mysql_client_wrapper *wrapper) { wrapper->refcount--; @@ -340,7 +370,11 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper) static VALUE allocate(VALUE klass) { VALUE obj; mysql_client_wrapper * wrapper; +#ifdef NEW_TYPEDDATA_WRAPPER + obj = TypedData_Make_Struct(klass, mysql_client_wrapper, &rb_mysql_client_type, wrapper); +#else obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); +#endif wrapper->encoding = Qnil; wrapper->active_fiber = Qnil; wrapper->automatic_close = 1; diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index 0a9faaf94..ad6ce8aa9 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -17,9 +17,17 @@ typedef struct { void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result); +extern const rb_data_type_t rb_mysql_client_type; + +#ifdef NEW_TYPEDDATA_WRAPPER +#define GET_CLIENT(self) \ + mysql_client_wrapper *wrapper; \ + TypedData_Get_Struct(self, mysql_client_wrapper, &rb_mysql_client_type, wrapper); +#else #define GET_CLIENT(self) \ mysql_client_wrapper *wrapper; \ Data_Get_Struct(self, mysql_client_wrapper, wrapper); +#endif void init_mysql2_client(void); void decr_mysql2_client(mysql_client_wrapper *wrapper); diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 592e02486..a6417acf8 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -36,6 +36,9 @@ def add_ssl_defines(header) have_func('rb_absint_size') have_func('rb_absint_singlebit_p') +# 2.7+ +have_func('rb_gc_mark_movable') + # Missing in RBX (https://github.com/rubinius/rubinius/issues/3771) have_func('rb_wait_for_single_fd') diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h index e1ce0e943..f82c47e5e 100644 --- a/ext/mysql2/mysql2_ext.h +++ b/ext/mysql2/mysql2_ext.h @@ -36,6 +36,19 @@ void Init_mysql2(void); typedef bool my_bool; #endif +// ruby 2.7+ +#ifdef HAVE_RB_GC_MARK_MOVABLE +#define rb_mysql2_gc_location(ptr) ptr = rb_gc_location(ptr) +#else +#define rb_gc_mark_movable(ptr) rb_gc_mark(ptr) +#define rb_mysql2_gc_location(ptr) +#endif + +// ruby 2.2+ +#ifdef TypedData_Make_Struct +#define NEW_TYPEDDATA_WRAPPER 1 +#endif + #include #include #include diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 314cecdaa..5cacd5ac6 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -31,9 +31,13 @@ static rb_encoding *binaryEncoding; #define MYSQL_TYPE_JSON 245 #endif +#ifndef NEW_TYPEDDATA_WRAPPER +#define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval) +#endif + #define GET_RESULT(self) \ mysql2_result_wrapper *wrapper; \ - Data_Get_Struct(self, mysql2_result_wrapper, wrapper); + TypedData_Get_Struct(self, mysql2_result_wrapper, &rb_mysql_result_type, wrapper); typedef struct { int symbolizeKeys; @@ -61,11 +65,11 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, static void rb_mysql_result_mark(void * wrapper) { mysql2_result_wrapper * w = wrapper; if (w) { - rb_gc_mark(w->fields); - rb_gc_mark(w->rows); - rb_gc_mark(w->encoding); - rb_gc_mark(w->client); - rb_gc_mark(w->statement); + rb_gc_mark_movable(w->fields); + rb_gc_mark_movable(w->rows); + rb_gc_mark_movable(w->encoding); + rb_gc_mark_movable(w->client); + rb_gc_mark_movable(w->statement); } } @@ -127,6 +131,46 @@ static void rb_mysql_result_free(void *ptr) { xfree(wrapper); } +static size_t rb_mysql_result_memsize(const void * wrapper) { + const mysql2_result_wrapper * w = wrapper; + size_t memsize = sizeof(*w); + if (w->stmt_wrapper) { + memsize += sizeof(*w->stmt_wrapper); + } + if (w->client_wrapper) { + memsize += sizeof(*w->client_wrapper); + } + return memsize; +} + +static void rb_mysql_result_compact(void * wrapper) { + mysql2_result_wrapper * w = wrapper; + if (w) { + rb_mysql2_gc_location(w->fields); + rb_mysql2_gc_location(w->rows); + rb_mysql2_gc_location(w->encoding); + rb_mysql2_gc_location(w->client); + rb_mysql2_gc_location(w->statement); + } +} + +static const rb_data_type_t rb_mysql_result_type = { + "rb_mysql_result", + { + rb_mysql_result_mark, + rb_mysql_result_free, + rb_mysql_result_memsize, +#ifdef HAVE_RB_GC_MARK_MOVABLE + rb_mysql_result_compact, +#endif + }, + 0, + 0, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY + RUBY_TYPED_FREE_IMMEDIATELY, +#endif +}; + static VALUE rb_mysql_result_free_(VALUE self) { GET_RESULT(self); rb_mysql_result_free_result(wrapper); @@ -365,7 +409,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e int enc_index; enc_name = (field.charsetnr-1 < MYSQL2_CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL; - + if (enc_name != NULL) { /* use the field encoding we were able to match */ enc_index = rb_enc_find_index(enc_name); @@ -1129,7 +1173,11 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ VALUE obj; mysql2_result_wrapper * wrapper; +#ifdef NEW_TYPEDDATA_WRAPPER + obj = TypedData_Make_Struct(cMysql2Result, mysql2_result_wrapper, &rb_mysql_result_type, wrapper); +#else obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper); +#endif wrapper->numberOfFields = 0; wrapper->numberOfRows = 0; wrapper->lastRowProcessed = 0; @@ -1176,7 +1224,7 @@ void init_mysql2_result() { cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject); rb_undef_alloc_func(cMysql2Result); rb_global_variable(&cMysql2Result); - + rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0); rb_define_method(cMysql2Result, "field_types", rb_mysql_result_fetch_field_types, 0); diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index fd506de0f..2af1c4866 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -6,9 +6,13 @@ static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_ static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year, intern_query_options; +#ifndef NEW_TYPEDDATA_WRAPPER +#define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval) +#endif + #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ - Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \ + TypedData_Get_Struct(self, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper); \ if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \ if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); } @@ -16,9 +20,43 @@ static void rb_mysql_stmt_mark(void * ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; if (!stmt_wrapper) return; - rb_gc_mark(stmt_wrapper->client); + rb_gc_mark_movable(stmt_wrapper->client); +} + +static void rb_mysql_stmt_free(void *ptr) { + mysql_stmt_wrapper *stmt_wrapper = ptr; + decr_mysql2_stmt(stmt_wrapper); +} + +static size_t rb_mysql_stmt_memsize(const void * ptr) { + const mysql_stmt_wrapper *stmt_wrapper = ptr; + return sizeof(*stmt_wrapper); +} + +static void rb_mysql_stmt_compact(void * ptr) { + mysql_stmt_wrapper *stmt_wrapper = ptr; + if (!stmt_wrapper) return; + + rb_mysql2_gc_location(stmt_wrapper->client); } +static const rb_data_type_t rb_mysql_statement_type = { + "rb_mysql_statement", + { + rb_mysql_stmt_mark, + rb_mysql_stmt_free, + rb_mysql_stmt_memsize, +#ifdef HAVE_RB_GC_MARK_MOVABLE + rb_mysql_stmt_compact, +#endif + }, + 0, + 0, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY + RUBY_TYPED_FREE_IMMEDIATELY, +#endif +}; + static void *nogvl_stmt_close(void *ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; if (stmt_wrapper->stmt) { @@ -28,11 +66,6 @@ static void *nogvl_stmt_close(void *ptr) { return NULL; } -static void rb_mysql_stmt_free(void *ptr) { - mysql_stmt_wrapper *stmt_wrapper = ptr; - decr_mysql2_stmt(stmt_wrapper); -} - void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) { stmt_wrapper->refcount--; @@ -96,7 +129,11 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { Check_Type(sql, T_STRING); +#ifdef NEW_TYPEDDATA_WRAPPER + rb_stmt = TypedData_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper); +#else rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); +#endif { stmt_wrapper->client = rb_client; stmt_wrapper->refcount = 1; From a89fd5b5948e03f38d2a15678cc2615b3840777d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 19 Jan 2023 13:31:35 -0800 Subject: [PATCH 730/783] Improve options for linking with OpenSSL especially on MacOS (#1303) Starting a few MacOS majors ago, OpenSSL was no longer included in a way that applications could link against. Even the system Ruby at /usr/bin/ruby was modified to use a MacOS internal SSL implementation. The most common workaround is to use Homebrew to install OpenSSL. Using GitHub Actions as the project's CI tool, we found that both openssl@1.1 and openssl@3 were installed in the default image, and that openssl@3 was returned by default but this mismatched the version the MySQL client libraries were compiled against. While the quick workaround might be to look for openssl@1.1 instead of openssl, a more general improvement is to provide an option for users to specify where OpenSSL is installed. Indeed this issue has been the cause of many postings on GH issues and Stack Overflow over the years. Hopefully this PR improves the situation for a broad swath of users! Unlike the existing option `--with-opt-dir`, the new option `--with-openssl-dir` will fail if the argument is not a valid path rather than producing unexpected results at runtime. This is the default behavior on MacOS: --with-openssl-dir=$(brew --prefix openssl) If you have both openssl@1.1 and openssl@3 installed, be explicit: --with-openssl-dir=$(brew --prefix openssl@1.1) The option is available on all platforms and may be helpful for non-default OpenSSL installations on Linux or FreeBSD as well. Co-authored-by: Jun Aruga --- .github/workflows/build.yml | 21 +++++++++--------- ext/mysql2/extconf.rb | 43 +++++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77701d7e2..fe694d84e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,42 +26,41 @@ jobs: - '2.3' - '2.2' - '2.1' - db: [''] include: # Comment out due to ci/setup.sh stucking. # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1} - {os: ubuntu-20.04, ruby: '2.4', db: mariadb10.3} - {os: ubuntu-18.04, ruby: '2.4', db: mysql57} - {os: ubuntu-20.04, ruby: '2.4', db: mysql80} - - {os: ubuntu-18.04, ruby: 'head', db: ''} + - {os: ubuntu-18.04, ruby: 'head'} # db: A DB's brew package name in macOS case. # Set a name "db: 'name@X.Y'" when using an old version. # MariaDB lastet version # Allow failure due to the following test failures that rarely happens. # https://github.com/brianmario/mysql2/issues/1194 - - {os: macos-latest, ruby: '2.6', db: mariadb, allow-failure: true} + - {os: macos-latest, ruby: '2.6', db: mariadb, ssl: openssl@1.1, allow-failure: true} # MySQL latest version # Allow failure due to the issue #1194. - - {os: macos-latest, ruby: '2.6', db: mysql, allow-failure: true} + - {os: macos-latest, ruby: '2.6', db: mysql, ssl: openssl@1.1, allow-failure: true} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. fail-fast: false env: BUNDLE_WITHOUT: development + # reduce MacOS CI time, don't need to clean a runtime that isn't saved + HOMEBREW_NO_INSTALL_CLEANUP: 1 + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 steps: - uses: actions/checkout@v3 - - name: Install openssl - if: matrix.os == 'macos-latest' - run: | - brew update - brew install openssl # https://github.com/ruby/setup-ruby - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically - - if: matrix.db != '' + - if: matrix.db run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV - run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts - run: bash ci/setup.sh - - run: bundle exec rake spec + - if: matrix.ssl + run: echo "rake_spec_opts=--with-openssl-dir=$(brew --prefix ${{ matrix.ssl }})" >> $GITHUB_ENV + - run: bundle exec rake spec -- $rake_spec_opts diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index a6417acf8..7a07639c6 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -1,6 +1,8 @@ require 'mkmf' require 'English' +### Some helper functions + def asplode(lib) if RUBY_PLATFORM =~ /mingw|mswin/ abort "-----\n#{lib} is missing. Check your installation of MySQL or Connector/C, and try again.\n-----" @@ -26,11 +28,7 @@ def add_ssl_defines(header) end end -# Homebrew openssl -if RUBY_PLATFORM =~ /darwin/ && system("command -v brew") - openssl_location = `brew --prefix openssl`.strip - $LDFLAGS << " -L#{openssl_location}/lib" if openssl_location -end +### Check for Ruby C extention interfaces # 2.1+ have_func('rb_absint_size') @@ -42,7 +40,33 @@ def add_ssl_defines(header) # Missing in RBX (https://github.com/rubinius/rubinius/issues/3771) have_func('rb_wait_for_single_fd') -have_func("rb_enc_interned_str", "ruby.h") +# 3.0+ +have_func('rb_enc_interned_str', 'ruby.h') + +### Find OpenSSL library + +# User-specified OpenSSL if explicitly specified +if with_config('openssl-dir') + _, lib = dir_config('openssl') + if lib + # Ruby versions below 2.0 on Unix and below 2.1 on Windows + # do not properly search for lib directories, and must be corrected: + # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 + unless lib && lib[-3, 3] == 'lib' + @libdir_basename = 'lib' + _, lib = dir_config('openssl') + end + abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } + warn "-----\nUsing --with-openssl-dir=#{File.dirname lib}\n-----" + $LDFLAGS << " -L#{lib}" + end +# Homebrew OpenSSL on MacOS +elsif RUBY_PLATFORM =~ /darwin/ && system('command -v brew') + openssl_location = `brew --prefix openssl`.strip + $LDFLAGS << " -L#{openssl_location}/lib" if openssl_location +end + +### Find MySQL client library # borrowed from mysqlplus # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb @@ -140,10 +164,13 @@ def add_ssl_defines(header) # to retain compatibility with the typedef in earlier MySQLs. have_type('my_bool', mysql_h) +### Compiler flags to help catch errors + # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. wishlist = [ '-Weverything', + '-Wno-compound-token-split-by-macro', # Fixed in Ruby 2.7+ at https://bugs.ruby-lang.org/issues/17865 '-Wno-bad-function-cast', # rb_thread_call_without_gvl returns void * that we cast to VALUE '-Wno-conditional-uninitialized', # false positive in client.c '-Wno-covered-switch-default', # result.c -- enum_field_types (when fully covered, e.g. mysql 5.5) @@ -168,6 +195,8 @@ def add_ssl_defines(header) $CFLAGS << ' ' << usable_flags.join(' ') +### Sanitizers to help with debugging -- many are available on both Clang/LLVM and GCC + enabled_sanitizers = disabled_sanitizers = [] # Specify a comma-separated list of sanitizers, or try them all by default sanitizers = with_config('sanitize') @@ -202,6 +231,8 @@ def add_ssl_defines(header) $CFLAGS << ' -g -fno-omit-frame-pointer' end +### Find MySQL Client on Windows, set RPATH to find the library at runtime + if RUBY_PLATFORM =~ /mswin|mingw/ && !defined?(RubyInstaller) # Build libmysql.a interface link library require 'rake' From ad56ca4a31d626d5fd822589b04aa981f1be7513 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 20 Jan 2023 19:04:28 -0800 Subject: [PATCH 731/783] CI: update runtime environments (#1299) --- .github/workflows/build.yml | 68 +++++++++++------------ ci/mariadb106.sh | 10 ++++ ci/setup.sh | 41 +++----------- support/C74CD1D8.asc | 104 ++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 68 deletions(-) create mode 100644 ci/mariadb106.sh create mode 100644 support/C74CD1D8.asc diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe694d84e..7815ed33d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,46 +4,45 @@ jobs: build: name: >- ${{ matrix.os }} ruby ${{ matrix.ruby }} ${{ matrix.db }} - # Run all the tests on the new environment as much as possible. - # https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.allow-failure || false }} strategy: matrix: - os: - # Use ubuntu-18.04 instead of ubuntu-20.04 temporarily, due to a failing test on mysql 8.0. - # https://github.com/brianmario/mysql2/issues/1165 - # - ubuntu-20.04 # focal - - ubuntu-18.04 # bionic - ruby: - - '3.2' - - '3.1' - - '3.0' - - '2.7' - - '2.6' - - '2.5' - - '2.4' - - '2.3' - - '2.2' - - '2.1' include: - # Comment out due to ci/setup.sh stucking. - # - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1} - - {os: ubuntu-20.04, ruby: '2.4', db: mariadb10.3} - - {os: ubuntu-18.04, ruby: '2.4', db: mysql57} + # Ruby 3.x on Ubuntu 22.04 LTS (latest at this time) + - {os: ubuntu-22.04, ruby: 'head', db: mysql80} + - {os: ubuntu-22.04, ruby: '3.2', db: mysql80} + - {os: ubuntu-22.04, ruby: '3.1', db: mysql80} + - {os: ubuntu-22.04, ruby: '3.0', db: mysql80} + + # Ruby 2.x on Ubuntu 20.04 LTS + - {os: ubuntu-20.04, ruby: '2.7', db: mysql80} + - {os: ubuntu-20.04, ruby: '2.6', db: mysql80} + - {os: ubuntu-20.04, ruby: '2.5', db: mysql80} - {os: ubuntu-20.04, ruby: '2.4', db: mysql80} - - {os: ubuntu-18.04, ruby: 'head'} - # db: A DB's brew package name in macOS case. - # Set a name "db: 'name@X.Y'" when using an old version. - # MariaDB lastet version - # Allow failure due to the following test failures that rarely happens. + - {os: ubuntu-20.04, ruby: '2.3', db: mysql80} + - {os: ubuntu-20.04, ruby: '2.2', db: mysql80} + - {os: ubuntu-20.04, ruby: '2.1', db: mysql80} + - {os: ubuntu-20.04, ruby: '2.0', db: mysql80} + + # db: on Linux, ci/setup.sh installs the specified packages + # db: on MacOS, installs a Homebrew package use "name@X.Y" to specify a version + + - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.6} + - {os: ubuntu-20.04, ruby: '2.7', db: mariadb10.6} + - {os: ubuntu-20.04, ruby: '2.7', db: mysql80} + - {os: ubuntu-18.04, ruby: '2.7', db: mysql57} + + # TODO - Windows CI + # - {os: windows-2022, ruby: '3.2', db: mysql80} + # - {os: windows-2022, ruby: '2.7', db: mysql80} + + # Allow failure due to this issue: # https://github.com/brianmario/mysql2/issues/1194 - {os: macos-latest, ruby: '2.6', db: mariadb, ssl: openssl@1.1, allow-failure: true} - # MySQL latest version - # Allow failure due to the issue #1194. - {os: macos-latest, ruby: '2.6', db: mysql, ssl: openssl@1.1, allow-failure: true} # On the fail-fast: true, it cancels all in-progress jobs - # if any matrix job fails unlike Travis fast_finish. + # if any matrix job fails, which we don't want. fail-fast: false env: BUNDLE_WITHOUT: development @@ -52,14 +51,15 @@ jobs: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 steps: - uses: actions/checkout@v3 - # https://github.com/ruby/setup-ruby - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically - - if: matrix.db - run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV - - run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts + - if: runner.os == 'Linux' || runner.os == 'macOS' + run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts + - if: runner.os == 'Windows' + run: echo "127.0.0.1 mysql2gem.example.com" | tee -a C:/Windows/System32/drivers/etc/hosts + - run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV - run: bash ci/setup.sh - if: matrix.ssl run: echo "rake_spec_opts=--with-openssl-dir=$(brew --prefix ${{ matrix.ssl }})" >> $GITHUB_ENV diff --git a/ci/mariadb106.sh b/ci/mariadb106.sh new file mode 100644 index 000000000..b8855ffb1 --- /dev/null +++ b/ci/mariadb106.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eux + +apt purge -qq '^mysql*' '^libmysql*' +rm -fr /etc/mysql +rm -fr /var/lib/mysql + +apt-key add support/C74CD1D8.asc +add-apt-repository "deb https://downloads.mariadb.com/MariaDB/mariadb-10.6/repo/ubuntu $(lsb_release -cs) main" +apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server-10.6 libmariadb-dev diff --git a/ci/setup.sh b/ci/setup.sh index a43599c65..eda169274 100644 --- a/ci/setup.sh +++ b/ci/setup.sh @@ -21,6 +21,10 @@ if [[ -n ${GITHUB_ACTIONS-} && -z ${DB-} ]]; then sudo apt-get install -qq mysql-server-8.0 mysql-client-core-8.0 mysql-client-8.0 CHANGED_PASSWORD=true ;; + jammy) + sudo apt-get install -qq mysql-server-8.0 mysql-client-core-8.0 mysql-client-8.0 + CHANGED_PASSWORD=true + ;; *) ;; esac @@ -44,40 +48,9 @@ if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then CHANGED_PASSWORD=true fi -# Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x -if [[ -n ${DB-} && x$DB =~ ^xmariadb10.0 ]]; then - if [[ -n ${GITHUB_ACTIONS-} ]]; then - sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.0 libmariadb2 - CHANGED_PASSWORD_BY_RECREATE=true - else - sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev - fi -fi - -# Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x -if [[ -n ${DB-} && x$DB =~ ^xmariadb10.1 ]]; then - if [[ -n ${GITHUB_ACTIONS-} ]]; then - sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.1 libmariadb-dev - CHANGED_PASSWORD_BY_RECREATE=true - else - sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev - fi -fi - -# Install MariaDB 10.2 if DB=mariadb10.2 -# NOTE this is a workaround until Travis CI merges a fix to its mariadb addon. -if [[ -n ${DB-} && x$DB =~ ^xmariadb10.2 ]]; then - sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.2 libmariadbclient18 -fi - -# Install MariaDB 10.3 if DB=mariadb10.3 -if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.3 ]]; then - sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/ - sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld - sudo apt-get purge -y 'mysql-common*' 'mysql-client*' 'mysql-server*' - sudo mv /etc/mysql "/etc/mysql-$(date +%Y%m%d-%H%M%S)" - sudo mv /var/lib/mysql "/var/lib/mysql-$(date +%Y%m%d-%H%M%S)" - sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.3 libmariadb-dev +# Install MariaDB 10.6 if DB=mariadb10.6 +if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.6 ]]; then + sudo bash ci/mariadb106.sh CHANGED_PASSWORD_BY_RECREATE=true fi diff --git a/support/C74CD1D8.asc b/support/C74CD1D8.asc new file mode 100644 index 000000000..d35c5a492 --- /dev/null +++ b/support/C74CD1D8.asc @@ -0,0 +1,104 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsFNBFb8EKsBEADwGmleOSVThrbCyCVUdCreMTKpmD5p5aPz/0jc66050MAb71Hv +TVcfuMqHYO8O66qXLpEdqZpuk4D+rw1oKyC+d8uPD2PSHRqBXnR0Qf+LVTZvtO92 +3R7pYnC2x6V6iVGpKQYFP8cwh2B1qgIa+9y/N8cQIqfD+0ghyiUjjTYek3YFBnqa +L/2h2V0Mt0DkBrDK80LqEY10PAFDfJjINAW9XNHZzi2KqUx5w1z8rItokXV6fYE5 +ItyGMR6WVajJg5D4VCiZd0ymuQP2bGkrRbl6FH5vofVSkahKMJeHs2lbvMvNyS3c +n8vxoBvbbcwSAV1gvB1uzXXxv0kdkFZjhU1Tss4+Dak8qeEmIrC5qYycLxIdVEhT +Z8N8+P7Dll+QGOZKu9+OzhQ+byzpLFhUHKys53eXo/HrfWtw3DdP21yyb5P3QcgF +scxfZHzZtFNUL6XaVnauZM2lqquUW+lMNdKKGCBJ6co4QxjocsxfISyarcFj6ZR0 +5Hf6VU3Y7AyuFZdL0SQWPv9BSu/swBOimrSiiVHbtE49Nx1x/d1wn1peYl07WRUv +C10eF36ZoqEuSGmDz59mWlwB3daIYAsAAiBwgcmN7aSB8XD4ZPUVSEZvwSm/IwuS +Rkpde+kIhTLjyv5bRGqU2P/Mi56dB4VFmMJaF26CiRXatxhXOAIAF9dXCwARAQAB +zS1NYXJpYURCIFNpZ25pbmcgS2V5IDxzaWduaW5nLWtleUBtYXJpYWRiLm9yZz7C +wXgEEwEIACIFAlb8EKsCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEPFl +byTHTNHYJZ0P/2Z2RURRkSTHLKZ/GqSvPReReeB7AI+ZrDapkpG/26xp1Yw1isCO +y99pvQ7hjTFhdZQ7xSRUiT/e27wJxR7s4G/ck5VOVjuJzGnByNLmwMjdN1ONIO9P +hQAs2iF3uoIbVTxzXof2F8C0WSbKgEWbtqlCWlaapDpN8jKAWdsQsNMdXcdpJ2os +WiacQRxLREBGjVRkAiqdjYkegQ4BZ0GtPULKjZWCUNkaat51b7O7V19nSy/T7MM7 +n+kqYQLMIHCF8LGd3QQsNppRnolWVRzXMdtR2+9iI21qv6gtHcMiAg6QcKA7halL +kCdIS2nWR8g7nZeZjq5XhckeNGrGX/3w/m/lwczYjMUer+qs2ww5expZJ7qhtSta +lE3EtL/l7zE4RlknqwDZ0IXtxCNPu2UovCzZmdZm8UWfMSKk/3VgL8HgzYRr8fo0 +yj0XkckJ7snXvuhoviW2tjm46PyHPWRKgW4iEzUrB+hiXpy3ikt4rLRg/iMqKjyf +mvcE/VdmFVtsfbfRVvlaWiIWCndRTVBkAaTu8DwrGyugQsbjEcK+4E25/SaKIJIw +qfxpyBVhru21ypgEMAw1Y8KC7KntB7jzpFotE4wpv1jZKUZuy71ofr7g3/2O+7nW +LrR1mncbuT6yXo316r56dfKzOxQJBnYFwTjXfa65yBArjQBUCPNYOKr0wkYEEhEI +AAYFAlb8JFYACgkQy8sIKhu5Q9snYACgh3id41CYTHELOQ/ymj4tiuFt1lcAn3JU +9wH3pihM9ISvoeuGnwwHhcKnwsFcBBIBCAAGBQJW/CSEAAoJEJFxGJmV5Fqe11cP +/A3QhvqleuRaXoS5apIY3lrDL79Wo0bkydM3u2Ft9EqVVG5zZvlmWaXbw5wkPhza +7YUjrD7ylaE754lHI48jJp3KY7RosClY/Kuk56GJI/SoMKx4v518pAboZ4hjY9MY +gmiAuZEYx5Ibv1pj0+hkzRI78+f6+d5QTQ6y/35ZjSSJcBgCMAr/JRsmOkHu6cY6 +qOpq4g8mvRAX5ivRm4UxE2gnxZyd2LjY2/S2kCZvHWVaZuiTD0EU1jYPoOo6fhc8 +zjs5FWS56C1vp7aFOGBvsH3lwYAYi1K2S+/B4nqpitYJz/T0zFzzyYe7ZG77DXKD +/XajD22IzRGKjoeVPFBx+2V0YCCpWZkqkfZ2Dt3QVW//QIpVsOJnmaqolDg1sxoa +BEYBtCtovU0wh1pXWwfn7IgjIkPNl0AU8mW8Ll91WF+Lss/oMrUJMKVDenTJ6/ZO +06c+JFlP7dS3YGMsifwgy5abA4Xy4GWpAsyEM68mqsJUc7ZANZcQAKr6+DryzSfI +Olsn3kJzOtb/c3JhVmblEO6XzdfZJK/axPOp3mF1oEBoJ56fGwO2usgVwQDyLt3J +iluJrCvMSBL9KtBZWrTZH5t3rTMN0NUALy4Etd6Y8V94i8c5NixMDyjRU7aKJAAw +tUvxLd12dqtaXsuvGyzLbR4EDT/Q5DfLC1DZWpgtUtCVwsFcBBIBCAAGBQJW/CS2 +AAoJEEHdwLQNpW8iMUoP/AjFKyZ+inQTI2jJJBBtrLjxaxZSG5ggCovowWn8NWv6 +bQBm2VurYVKhvY1xUyxoLY8KN+MvoeTdpB3u7z+M6x+CdfoTGqWQ2yapOC0eEJBF +O+GFho2WE0msiO0IaVJrzdFTPE0EYR2BHziLu0DDSZADe1WYEqkkrZsCNgi6EMng +mX2h+DK2GlC3W2tY9sc63DsgzjcMBO9uYmpHj6nizsIrETqouVNUCLT0t8iETa25 +Mehq/I92I70Qfebv7R4eMrs+tWXKyPU0OjV+8b8saZsv1xn98UkeXwYx4JI04OTw +nBeJG8yPrGDBO5iucmtaCvwGQ3c76qBivrA8eFz3azRxQYWWiFrkElTg+C/E83JQ +WgqPvPZkI5UHvBwBqcoIXG15AJoXA/ZWIB8nPKWKaV5KDnY3DBuA4rh5Mhy3xwcC +/22E/CmZMXjUUvDnlPgXCYAYU0FBbGk7JpSYawtNfdAN2XBRPq5sDKLLxftx7D8u +ESJXXAlPxoRh7x1ArdGM+EowlJJ0xpINBaT0Z/Hk0jxNIFEak796/WeGqewdOIki +dAs4tppUfzosla5K+qXfWwmhcKmpwA4oynE8wIaoXptoi8+rxaw4N6wAXlSrVxeC +VTnb7+UY/BT2Wx6IQ10C9jrsj6XIffMvngIinCD9Czvadmr7BEIxKt1LP+gGA8Zg +wsFcBBIBCgAGBQJYE6oDAAoJEL7YRJ/O6NqIJ24P+QFNa2O+Q1rLKrQiuPw4Q73o +7/blUpFNudZfeCDpDbUgJ01u1RHnWOyLcyknartAosFDJIpgcXY5I8jsBIO5IZPR +C/UKxZB3RYOhj49bySD9RNapHyq+Y56j9JUoz6tkKFBd+6g85Ej8d924xM1UnRCS +9cfI9W0fSunbCi2CXLbXFF7V+m3Ou1SVYGIAxpMn4RXyYfuqeB5wROR2GA5Ef6T3 +S5byh1dRSEgnrBToENtp5n7Jwsc9pDofjtaUkO854l45IqFarGjCHZwtNRKd2lcK +FMnd1jS0nfGkUbn3qNJam1qaGWx4gXaT845VsYYVTbxtkKi+qPUIoOyYx4NEm6fC +ZywH72oP+fmUT/fbfSHa5j137dRqokkR6RFjnEMBl6WHwgqqUqeIT6t9uV6WWzX9 +lNroZFAFL/de7H31iIRuZcm38DUZOfjVf9glweu4yFvuJ7cQtyQydFQJV4LGDT/C +8e9TWrV1/gWMyMGQlZsRWa+h+FfFUccQtfSdXpvSxtXfop+fVQmJgUUl92jh4K9j +c9a6rIp5v1Q1yEgs2iS50/V/NMSmEcE1XMOxFt9fX9T+XmKAWZ8L25lpILsHT3mB +VWrpHdbawUaiBp9elxhn6tFiTFR7qA7dlUyWrI+MMlINwSZ2AAXvmA2IajH/UIlh +xotxmSNiZYIQ6UbD3fk4wsFzBBABCgAdFiEEmy/52H2krRdju+d2+GQcuhDvLUgF +Ally44wACgkQ+GQcuhDvLUgkjQ//c3mBxfJm6yLAJD4s4OgsPv4pcp/EKmPcdztm +W0/glwopUZmq9oNo3VMMCGtusrQgpACzfUlesu9NWlPCB3olZkeGugygo0zuQBKs +55eG7bPzMLyfSqLKyogYocaGc4lpf4lbvlvxy37YGVrGpwT9i8t2REtM6iPKDcMM +sgVtNlqFdq3Fs2Haqt0m1EksX6/GSIrjK4LZEcPklrGPvUS3S+qkwuaGE/jXxncE +4jFQR9SYH6AHr6Vkt1CG9Dgpr+Ph0I9n0JRknBYoUZ1q51WdF946NplXkCskdzWG +RHgMUCz3ZehF1FzpKgfO9Zd0YZsmivV/g6frUw/TayP9gxKPt7z2Lsxzyh8X7cg6 +TAvdG9JbG0PyPJT1TZ8qpjP/PtqPclHsHQQIbGSDFWzRM5znhS+5sgyw8FWInjw8 +JjxoOWMa50464EfGeb2jZfwtRimJAJLWEf/JnvO779nXf5YbvUZgfXaX7k/cvCVk +U8M7oC7x8o6F0P2Lh6FgonklKEeIRtZBUNZ0Lk9OShVqlU9/v16MHq/Eyu/Mbs0D +en3vYgiYxOBR8czD1Wh4vsKiGfOzQ6oWti/DCURV+iTYhJc7mSWM6STzUFr0nCnF +x6W0j/zH6ZgiFAGOyIXW2DwfjFvYRcBL1RWAEKsiFwYrNV+MDonjKXjpVB1Ra90o +lLrZXAXCwHMEEgEKAB0WIQRMRw//78TT3Fl3hlXOGj3V48lPSQUCXAAgOgAKCRDO +Gj3V48lPSQxAB/43qoWteVZEiN3JW4FnHg+S60TnHSP69FKV+363XYKDa23pNpv4 +tiJumo9Kvb4UoDft766/URHm5RKyPtrxy+wqotamrkGJUTtP2a68h7C31VX+pf6i +iQKmxRQz4zmW0pA5X01+AgpvcDH++Fv5NLBpnjqPdTh5b0gvr89E0zMNldNYOZu1 +0H/mukrnGlFDu/osBuy+XJtP2MeasazVMLvjKs+hr//E+iLI9DZOwFBK6AX5gkkI +UEHkSeb4//AHwvanUMin9un9+F9iR+qDuDEKxuevYzM0owuoVcK5pAsRnRQJlnHW +/0BQ6FtNGpmljhvUk8a/l3xFf3z/uJG5vVKVzsFNBFb8EKsBEADDfCMsu2U1CdJh +r4xp6z4J89/tMnpCQASC8DQhtZ6bWG/ksyKt2DnDQ050XBEng+7epzHWA2UgT0li +Y05zZmFs1X7QeZr16B7JANq6fnHOdZB0ThS7JEYbProkMxcqAFLAZJCpZT534Gpz +W7qHwzjV+d13IziCHdi6+DD5eavYzBqY8QzjlOXbmIlY7dJUCwXTECUfirc6kH86 +CS8fXZTke4QYZ55VnrOomB4QGqP371kwBETnhlhi74+pvi3jW05Z5x1tVMwuugyz +zkseZp1VYmJq5SHNFZ/pnAQLE9gUDTb6UWcPBwQh9Sw+7ahSK74lJKYm3wktyvZh +zAxbNyzs1M56yeFP6uFwJTBfNByyMAa6TGUhNkxlLcYjxKbVmoAnKCVM8t41TlLv +/a0ki8iQxqvphVLufksR9IpN6d3F15j6GeyVtxBEv04iv4vbuKthWytb+gjX4bI8 +CAo9jGHevmtdiw/SbeKx2YBM1MF6eua37rFMooOBj4X7VfQCyS+crNsOQn8nJGah +YbzUDCCgnX+pqN9iZvXisMS79wVyD5DyISFDvT/5jY7IXxPibxr10P/8lfW1d72u +xyI2UiZKZpyHCt4k47yMq4KQGLGuhxJ6q6O3bi2aXRuz8bLqTBLca9dmx9wZFvRh +6jS/SKEg7eFcY0xbb6RVIv1UwGDYfQARAQABwsFfBBgBCAAJBQJW/BCrAhsMAAoJ +EPFlbyTHTNHYEBIQAJhFTh1u34Q+5bnfiM2dAdCr6T6w4Y1v9ePiIYdSImeseJS2 +yRglpLcMjW0uEA9KXiRtC/Nm/ClnqYJzCKeIaweHqH6dIgJKaXZFt1Uaia7X9tDD +wqALGu97irUrrV1Kh9IkM0J29Vid5amakrdS4mwt2uEISSnCi7pfVoEro+S7tYQ9 +iH6APVIwqWvcaty3cANdwKWfUQZ6a9IQ08xqzaMhMp2VzhVrWkq3B0j2aRoZR7BN +LH2I7Z0giIM8ARjZs99aTRL+SfMEQ3sUxNLb3KWP/n1lSFbrk4HGzqUBBfczESlN +c0970C6znK0H0HD11/3BTkMuPqww+Tzex4dpMQllMEKZ3wEyd9v6ba+nj/P1FHSE +y/VN6IXzd82s1lYOonKTdmXAIROcHnb0QUzwsd/mhB3jKhEDOV2ZcBTD3yHv8m7C +9G9y4hV+7yQlnPlSg3DjBp3SS5r+sOObCIy2Ad32upoXkilWa9g7GZSuhY9kyKqe +Eba1lgXXaQykEeqx0pexkWavNnb9JaPrAZHDjUGcXrREmjEyXyElRoD4CrWXySe4 +6jCuNhVVlkLGo7osefynXa/+PNjQjURtx8en7M9A1FkQuRAxE8KIZgZzYxkGl5o5 +POSFCA4JUoRPDcrl/sI3fuq2dIOE/BJ2r8dV+LddiR+iukhXRwJXH8RVVEUS +=mCOI +-----END PGP PUBLIC KEY BLOCK----- From 1bf5a3b86797a719e3c26c17c398ab2d5d0b90e0 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 20 Jan 2023 15:59:46 -0800 Subject: [PATCH 732/783] MySQL 5.6.36 and above also has full SSL support --- ext/mysql2/client.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 4f62e463d..fbc735728 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -121,8 +121,8 @@ struct nogvl_select_db_args { static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { unsigned long version = mysql_get_client_version(); - if (version < 50703) { - rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." ); + if (version < 50630 || (version >= 50700 && version < 50703)) { + rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.6.36+, 5.7.11+, 8.0+" ); return Qnil; } #if defined(HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT) || defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) @@ -1723,7 +1723,7 @@ void init_mysql2_client() { rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_TRANSACTION_STATE"), INT2NUM(SESSION_TRACK_TRANSACTION_STATE)); #endif -#if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above +#if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.6.36 and MySQL 5.7.11 and above rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED)); From e9decbe3a7afa881d50a324e72307c0d36bf821f Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Fri, 20 Jan 2023 16:07:22 -0800 Subject: [PATCH 733/783] Resolve unused method warnings for GC compaction methods on older Ruby versions --- ext/mysql2/result.c | 2 ++ ext/mysql2/statement.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 5cacd5ac6..23ec611ee 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -143,6 +143,7 @@ static size_t rb_mysql_result_memsize(const void * wrapper) { return memsize; } +#ifdef HAVE_RB_GC_MARK_MOVABLE static void rb_mysql_result_compact(void * wrapper) { mysql2_result_wrapper * w = wrapper; if (w) { @@ -153,6 +154,7 @@ static void rb_mysql_result_compact(void * wrapper) { rb_mysql2_gc_location(w->statement); } } +#endif static const rb_data_type_t rb_mysql_result_type = { "rb_mysql_result", diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 2af1c4866..5506526ee 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -33,12 +33,14 @@ static size_t rb_mysql_stmt_memsize(const void * ptr) { return sizeof(*stmt_wrapper); } +#ifdef HAVE_RB_GC_MARK_MOVABLE static void rb_mysql_stmt_compact(void * ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; if (!stmt_wrapper) return; rb_mysql2_gc_location(stmt_wrapper->client); } +#endif static const rb_data_type_t rb_mysql_statement_type = { "rb_mysql_statement", From d021cbda719690aadf22a91d022cbb1fe26bdc3d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 22 Jan 2023 13:14:43 -0800 Subject: [PATCH 734/783] Update README and improve handling of ssl_mode settings for MySQL and MariaDB versions (#1306) Print the client version number in SSL warning. Add comments to explain the ssl mode setting function. Add MariaDB Connector/C 3.x to the relevant SSL mode code path. With thanks to Jun Aruga for identifying this issue and reviewing changes. --- README.md | 94 ++++++++++++++++++++++++++++++-------- ext/mysql2/client.c | 53 +++++++++++++++------ spec/mysql2/client_spec.rb | 18 ++++++-- 3 files changed, 128 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 61e76297b..54e0b2d8c 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,11 @@ This may be needed if you deploy to a system where these libraries are located somewhere different than on your build system. This overrides any rpath calculated by default or by the options above. +* `--with-openssl-dir[=/path/to/openssl]` - Specify the directory where OpenSSL +is installed. In most cases, the Ruby runtime and MySQL client libraries will +link against a system-installed OpenSSL library and this option is not needed. +Use this option when non-default library paths are needed. + * `--with-sanitize[=address,cfi,integer,memory,thread,undefined]` - Enable sanitizers for Clang / GCC. If no argument is given, try to enable all sanitizers or fail if none are available. If a command-separated list of @@ -89,13 +94,48 @@ the library file `libmysqlclient.so` but is missing the header file `mysql.h` ### Mac OS X -You may use MacPorts, Homebrew, or a native MySQL installer package. The most +You may use Homebew, MacPorts, or a native MySQL installer package. The most common paths will be automatically searched. If you want to select a specific MySQL directory, use the `--with-mysql-dir` or `--with-mysql-config` options above. If you have not done so already, you will need to install the XCode select tools by running `xcode-select --install`. +Later versions of MacOS no longer distribute a linkable OpenSSL library. It is +common to use Homebrew or MacPorts to install OpenSSL. Make sure that both the +Ruby runtime and MySQL client libraries are compiled with the same OpenSSL +family, 1.0 or 1.1 or 3.0, since only one can be loaded at runtime. + +``` sh +$ brew install openssl@1.1 +$ gem install mysql2 -- --with-openssl-dir=$(brew --prefix openssl@1.1) + +or + +$ sudo port install openssl11 +``` + +Since most Ruby projects use Bundler, you can set build options in the Bundler +config rather than manually installing a global mysql2 gem. This example shows +how to set build arguments with [Bundler config](https://bundler.io/man/bundle-config.1.html): + +``` sh +$ bundle config --local build.mysql2 -- --with-openssl-dir=$(brew --prefix openssl@1.1) +``` + +Another helpful trick is to use the same OpenSSL library that your Ruby was +built with, if it was built with an alternate OpenSSL path. This example finds +the argument `--with-openssl-dir=/some/path` from the Ruby build and adds that +to the [Bundler config](https://bundler.io/man/bundle-config.1.html): + +``` sh +$ bundle config --local build.mysql2 -- $(ruby -r rbconfig -e 'puts RbConfig::CONFIG["configure_args"]' | xargs -n1 | grep with-openssl-dir) +``` + +Note the additional double dashes (`--`) these separate command-line arguments +that `gem` or `bundler` interpret from the addiitonal arguments that are passed +to the mysql2 build process. + ### Windows Make sure that you have Ruby and the DevKit compilers installed. We recommend @@ -205,7 +245,7 @@ result = statement.execute(1, "CA", :as => :array) Session Tracking information can be accessed with -```ruby +``` ruby c = Mysql2::Client.new( host: "127.0.0.1", username: "root", @@ -261,19 +301,13 @@ type of connection to make, with special interpretation you should be aware of: * An IPv4 or IPv6 address will result in a TCP connection. * Any other value will be looked up as a hostname for a TCP connection. -### SSL options - -Setting any of the following options will enable an SSL connection, but only if -your MySQL client library and server have been compiled with SSL support. -MySQL client library defaults will be used for any parameters that are left out -or set to nil. Relative paths are allowed, and may be required by managed -hosting providers such as Heroku. - -For MySQL versions 5.7.11 and higher, use `:ssl_mode` to prefer or require an -SSL connection and certificate validation. For earlier versions of MySQL, use -the `:sslverify` boolean. For details on each of the `:ssl_mode` options, see -[https://dev.mysql.com/doc/refman/8.0/en/connection-options.html](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-mode). +### SSL/TLS options +Setting any of the following options will enable an SSL/TLS connection, but +only if your MySQL client library and server have been compiled with SSL +support. MySQL client library defaults will be used for any parameters that are +left out or set to nil. Relative paths are allowed, and may be required by +managed hosting providers such as Heroku. ``` ruby Mysql2::Client.new( @@ -284,10 +318,28 @@ Mysql2::Client.new( :sslcapath => '/path/to/cacerts', :sslcipher => 'DHE-RSA-AES256-SHA', :sslverify => true, # Removed in MySQL 8.0 - :ssl_mode = :disabled / :preferred / :required / :verify_ca / :verify_identity, + :ssl_mode => :disabled / :preferred / :required / :verify_ca / :verify_identity, ) ``` +For MySQL versions 5.7.11 and higher, use `:ssl_mode` to prefer or require an +SSL connection and certificate validation. For earlier versions of MySQL, use +the `:sslverify` boolean. For details on each of the `:ssl_mode` options, see +[https://dev.mysql.com/doc/refman/8.0/en/connection-options.html](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-mode). + +The `:ssl_mode` option will also set the appropriate MariaDB connection flags: + +| `:ssl_mode` | MariaDB option value | +| --- | --- | +| `:disabled` | MYSQL_OPT_SSL_ENFORCE = 0 | +| `:required` | MYSQL_OPT_SSL_ENFORCE = 1 | +| `:verify_identity` | MYSQL_OPT_SSL_VERIFY_SERVER_CERT = 1 | + +MariaDB does not support the `:preferred` or `:verify_ca` options. For more +information about SSL/TLS in MariaDB, see +[https://mariadb.com/kb/en/securing-connections-for-client-and-server/](https://mariadb.com/kb/en/securing-connections-for-client-and-server/) +and [https://mariadb.com/kb/en/mysql_optionsv/#tls-options](https://mariadb.com/kb/en/mysql_optionsv/#tls-options) + ### Secure auth Starting with MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). @@ -334,7 +386,7 @@ In this example, the compression flag is negated with `-COMPRESS`. Active Record typically reads its configuration from a file named `database.yml` or an environment variable `DATABASE_URL`. Use the value `mysql2` as the protocol name. For example: -``` shell +``` sh DATABASE_URL=mysql2://sql_user:sql_pass@sql_host_name:port/sql_db_name?option1=value1&option2=value2 ``` @@ -393,7 +445,7 @@ end Yields: -```ruby +``` ruby {"1"=>1} {"2"=>2} next_result: Unknown column 'A' in 'field list' (Mysql2::Error) @@ -573,14 +625,16 @@ As for field values themselves, I'm workin on it - but expect that soon. This gem is tested with the following Ruby versions on Linux and Mac OS X: -* Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x, 2.5.x, 2.6.x +* Ruby MRI 2.0 through 2.7 (all versions to date) +* Ruby MRI 3.0, 3.1, 3.2 (all versions to date) * Rubinius 2.x and 3.x do work but may fail under some workloads This gem is tested with the following MySQL and MariaDB versions: * MySQL 5.5, 5.6, 5.7, 8.0 -* MySQL Connector/C 6.0 and 6.1 (primarily on Windows) -* MariaDB 5.5, 10.0, 10.1, 10.2, 10.3 +* MySQL Connector/C 6.0, 6.1, 8.0 (primarily on Windows) +* MariaDB 5.5, 10.x, with a focus on 10.6 LTS and 10.11 LTS +* MariaDB Connector/C 2.x, 3.x ### Ruby on Rails / Active Record diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index fbc735728..a49b3c34d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -120,53 +120,80 @@ struct nogvl_select_db_args { static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { unsigned long version = mysql_get_client_version(); + const char *version_str = mysql_get_client_info(); - if (version < 50630 || (version >= 50700 && version < 50703)) { - rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.6.36+, 5.7.11+, 8.0+" ); + /* Warn about versions that are known to be incomplete; these are pretty + * ancient, we want people to upgrade if they need SSL/TLS to work + * + * MySQL 5.x before 5.6.30 -- ssl_mode introduced but not fully working until 5.6.36) + * MySQL 5.7 before 5.7.3 -- ssl_mode introduced but not fully working until 5.7.11) + */ + if ((version >= 50000 && version < 50630) || (version >= 50700 && version < 50703)) { + rb_warn("Your mysql client library version %s does not support setting ssl_mode; full support comes with 5.6.36+, 5.7.11+, 8.0+", version_str); return Qnil; } + + /* For these versions, map from the options we're exposing to Ruby to the constant available: + * ssl_mode: :verify_identity to MYSQL_OPT_SSL_VERIFY_SERVER_CERT = 1 + * ssl_mode: :required to MYSQL_OPT_SSL_ENFORCE = 1 + * ssl_mode: :disabled to MYSQL_OPT_SSL_ENFORCE = 0 + */ #if defined(HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT) || defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) GET_CLIENT(self); - int val = NUM2INT( setting ); - // Either MySQL 5.7.3 - 5.7.10, or Connector/C 6.1.3 - 6.1.x, or MariaDB 10.x and later - if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200) || version >= 100000) { + int val = NUM2INT(setting); + + /* Expected code path for MariaDB 10.x and MariaDB Connector/C 3.x + * Workaround code path for MySQL 5.7.3 - 5.7.10 and MySQL Connector/C 6.1.3 - 6.1.x + */ + if (version >= 100000 // MariaDB (all versions numbered 10.x) + || (version >= 30000 && version < 40000) // MariaDB Connector/C (all versions numbered 3.x) + || (version >= 50703 && version < 50711) // Workaround for MySQL 5.7.3 - 5.7.10 + || (version >= 60103 && version < 60200)) { // Workaround for MySQL Connector/C 6.1.3 - 6.1.x #ifdef HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT if (val == SSL_MODE_VERIFY_IDENTITY) { my_bool b = 1; - int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &b ); + int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &b); return INT2NUM(result); } #endif #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { - my_bool b = ( val == SSL_MODE_REQUIRED ); - int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); + my_bool b = (val == SSL_MODE_REQUIRED); + int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b); return INT2NUM(result); } #endif - rb_warn( "Your mysql client library does not support ssl_mode %d.", val ); + rb_warn("Your mysql client library version %s does not support ssl_mode %d", version_str, val); return Qnil; } else { - rb_warn( "Your mysql client library does not support ssl_mode as expected." ); + rb_warn("Your mysql client library version %s does not support ssl_mode as expected", version_str); return Qnil; } #endif + + /* For other versions -- known to be MySQL 5.6.36+, 5.7.11+, 8.0+ + * pass the value of the argument to MYSQL_OPT_SSL_MODE -- note the code + * mapping from atoms / constants is in the MySQL::Client Ruby class + */ #ifdef FULL_SSL_MODE_SUPPORT GET_CLIENT(self); - int val = NUM2INT( setting ); + int val = NUM2INT(setting); if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) { rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val ); } - int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val ); + int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_MODE, &val); return INT2NUM(result); #endif + + // Warn if we get this far #ifdef NO_SSL_MODE_SUPPORT - rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." ); + rb_warn("Your mysql client library does not support setting ssl_mode"); return Qnil; #endif } + /* * non-blocking mysql_*() functions that we won't be wrapping since * they do not appear to hit the network nor issue any interruptible diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index ede316b76..ead83291d 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -166,16 +166,26 @@ def connect(*args) new_client(option_overrides) end - %i[disabled preferred required verify_ca verify_identity].each do |ssl_mode| + # 'preferred' or 'verify_ca' are only in MySQL 5.6.36+, 5.7.11+, 8.0+ + version = Mysql2::Client.info + ssl_modes = case version + when 50636...50700, 50711...50800, 80000...90000 + %i[disabled preferred required verifa_ca verify_identity] + else + %i[disabled required verify_identity] + end + + # MySQL and MariaDB and all versions of Connector/C + ssl_modes.each do |ssl_mode| it "should set ssl_mode option #{ssl_mode}" do options = { ssl_mode: ssl_mode, } options.merge!(option_overrides) - # Relax the matching condition by checking if an error is not raised. - # TODO: Verify warnings by checking stderr. expect do - new_client(options) + expect do + new_client(options) + end.not_to output(/does not support ssl_mode/).to_stderr end.not_to raise_error end end From 3dad16c7f765914f5f1150f3db71829df41d51a9 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 22 Jan 2023 13:19:50 -0800 Subject: [PATCH 735/783] CI: Add MariaDB 10.11 LTS to the build matrix --- .github/workflows/build.yml | 2 ++ ci/mariadb1011.sh | 10 ++++++++++ ci/setup.sh | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 ci/mariadb1011.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7815ed33d..a2df4aeaa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,8 @@ jobs: # db: on Linux, ci/setup.sh installs the specified packages # db: on MacOS, installs a Homebrew package use "name@X.Y" to specify a version + - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.11} + - {os: ubuntu-22.04, ruby: '2.7', db: mariadb10.11} - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.6} - {os: ubuntu-20.04, ruby: '2.7', db: mariadb10.6} - {os: ubuntu-20.04, ruby: '2.7', db: mysql80} diff --git a/ci/mariadb1011.sh b/ci/mariadb1011.sh new file mode 100644 index 000000000..05de344b8 --- /dev/null +++ b/ci/mariadb1011.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eux + +apt purge -qq '^mysql*' '^libmysql*' +rm -fr /etc/mysql +rm -fr /var/lib/mysql + +apt-key add support/C74CD1D8.asc +add-apt-repository "deb https://downloads.mariadb.com/MariaDB/mariadb-10.11/repo/ubuntu $(lsb_release -cs) main" +apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server-10.11 libmariadb-dev diff --git a/ci/setup.sh b/ci/setup.sh index eda169274..956608c69 100644 --- a/ci/setup.sh +++ b/ci/setup.sh @@ -54,6 +54,12 @@ if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.6 ]]; then CHANGED_PASSWORD_BY_RECREATE=true fi +# Install MariaDB 10.11 if DB=mariadb10.11 +if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.11 ]]; then + sudo bash ci/mariadb1011.sh + CHANGED_PASSWORD_BY_RECREATE=true +fi + # Install MySQL/MariaDB if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update > /dev/null From a2873cdaa8516d6049ca9c69018c2d6b32989539 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 22 Jan 2023 13:24:06 -0800 Subject: [PATCH 736/783] Bump version to 0.5.5 --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index 6728d4434..bd815bfbb 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.5.4".freeze + VERSION = "0.5.5".freeze end From 89b4f150ded158d344847fc4ec39401ccfda45f9 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Mon, 23 Jan 2023 17:47:13 +0100 Subject: [PATCH 737/783] CI: Set the verbose option in the Makefile to print compiling commands. (#1187) Enable the verbose option in the Makefile used to compile the extension to print the compiling command lines in the log. Note that we use MAKEFLAGS rather than GNUMAKEFLAGS[1], because the GNUMAKEFLAGS doesn't work for the make used in CI MacOS cases. We don't need to set the verbose option to the CI Fedora and CentOS cases (`.github/workflows/container.yml`). Because the Rubies from the Ruby RPM package used in Fedora and CentOS cases are already enabling the verbose option. [1] https://www.gnu.org/software/make/manual/html_node/Options_002fRecursion.html --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a2df4aeaa..446ee3341 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,6 +63,8 @@ jobs: run: echo "127.0.0.1 mysql2gem.example.com" | tee -a C:/Windows/System32/drivers/etc/hosts - run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV - run: bash ci/setup.sh + # Set the verbose option in the Makefile to print compiling command lines. + - run: echo "MAKEFLAGS=V=1" >> $GITHUB_ENV - if: matrix.ssl run: echo "rake_spec_opts=--with-openssl-dir=$(brew --prefix ${{ matrix.ssl }})" >> $GITHUB_ENV - run: bundle exec rake spec -- $rake_spec_opts From 1edda51cb6b895b5c8383882d2cf5b5cbbf2425e Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 22 Dec 2022 15:15:11 +0100 Subject: [PATCH 738/783] Add an option to set a custom SSL certificates pem files directory in test. In the Fedora project, we are running the mysql2 tests on the build environment with a user permission, without root permission and without `sudo`. In this case, we couldn't set up the pem files required to run SSL tests in the `/etc/mysql`. This custom SSL directory option gives an option to run the SSL tests executed in the environment. How to use: ``` $ TEST_RUBY_MYSQL2_SSL_CERT_DIR=/tmp/mysql2 \ bundle exec rake spec ``` --- .github/workflows/container.yml | 10 ++++++++-- ci/ssl.sh | 13 ++++++++----- spec/mysql2/client_spec.rb | 6 +++--- spec/spec_helper.rb | 13 +++++++++++++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index bd14debf2..af57ef136 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -16,7 +16,7 @@ jobs: # Fedora latest stable version - {distro: fedora, image: 'fedora:latest'} # Fedora development version - - {distro: fedora, image: 'fedora:rawhide'} + - {distro: fedora, image: 'fedora:rawhide', ssl_cert_dir: '/tmp/mysql2'} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. fail-fast: false @@ -27,4 +27,10 @@ jobs: # as a temporary workaround to avoid the following issue # in the Fedora >= 34 containers. # https://bugzilla.redhat.com/show_bug.cgi?id=1900021 - - run: docker run --add-host=mysql2gem.example.com:127.0.0.1 -t --cap-add=SYS_PTRACE --security-opt seccomp=unconfined mysql2 + - run: | + docker run \ + --add-host=mysql2gem.example.com:127.0.0.1 \ + -t \ + -e TEST_RUBY_MYSQL2_SSL_CERT_DIR="${{ matrix.ssl_cert_dir || '' }}" \ + --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ + mysql2 diff --git a/ci/ssl.sh b/ci/ssl.sh index e98f27a44..a0cb3c5ee 100644 --- a/ci/ssl.sh +++ b/ci/ssl.sh @@ -2,11 +2,14 @@ set -eux +# TEST_RUBY_MYSQL2_SSL_CERT_DIR: custom SSL certs directory. +SSL_CERT_DIR=${TEST_RUBY_MYSQL2_SSL_CERT_DIR:-/etc/mysql} + # Make sure there is an /etc/mysql -mkdir -p /etc/mysql +mkdir -p "${SSL_CERT_DIR}" # Copy the local certs to /etc/mysql -cp spec/ssl/*pem /etc/mysql/ +cp spec/ssl/*pem "${SSL_CERT_DIR}" # Wherever MySQL configs live, go there (this is for cross-platform) cd $(my_print_defaults --help | grep my.cnf | xargs find 2>/dev/null | xargs dirname) @@ -14,7 +17,7 @@ cd $(my_print_defaults --help | grep my.cnf | xargs find 2>/dev/null | xargs dir # Put the configs into the server echo " [mysqld] -ssl-ca=/etc/mysql/ca-cert.pem -ssl-cert=/etc/mysql/server-cert.pem -ssl-key=/etc/mysql/server-key.pem +ssl-ca=${SSL_CERT_DIR}/ca-cert.pem +ssl-cert=${SSL_CERT_DIR}/server-cert.pem +ssl-key=${SSL_CERT_DIR}/server-key.pem " >> my.cnf diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index ead83291d..db7c4b88b 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -154,9 +154,9 @@ def connect(*args) let(:option_overrides) do { 'host' => 'mysql2gem.example.com', # must match the certificates - :sslkey => '/etc/mysql/client-key.pem', - :sslcert => '/etc/mysql/client-cert.pem', - :sslca => '/etc/mysql/ca-cert.pem', + :sslkey => "#{ssl_cert_dir}/client-key.pem", + :sslcert => "#{ssl_cert_dir}/client-cert.pem", + :sslca => "#{ssl_cert_dir}/ca-cert.pem", :sslcipher => 'DHE-RSA-AES256-SHA', :sslverify => true, } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index edfac4d64..2e924eb42 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -60,6 +60,19 @@ def clock_time end end + # A directory where SSL certificates pem files exist. + def ssl_cert_dir + return @ssl_cert_dir if @ssl_cert_dir + + dir = ENV['TEST_RUBY_MYSQL2_SSL_CERT_DIR'] + @ssl_cert_dir = if dir && !dir.empty? + dir + else + '/etc/mysql' + end + @ssl_cert_dir + end + config.before(:suite) do begin new_client From 6cf5e1d732b4a6ee5485def98637f100cbc86f1b Mon Sep 17 00:00:00 2001 From: Ryunosuke Sato Date: Fri, 3 Feb 2023 13:10:54 +0900 Subject: [PATCH 739/783] Fix a typo (#1308) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54e0b2d8c..cf35222d5 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ the library file `libmysqlclient.so` but is missing the header file `mysql.h` ### Mac OS X -You may use Homebew, MacPorts, or a native MySQL installer package. The most +You may use Homebrew, MacPorts, or a native MySQL installer package. The most common paths will be automatically searched. If you want to select a specific MySQL directory, use the `--with-mysql-dir` or `--with-mysql-config` options above. From c0227014ae7127b0f867805bc26b41c5d9395e1f Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 2 Aug 2023 18:21:54 -0700 Subject: [PATCH 740/783] Session tracking: account for MySQL 8 reporting statement_id changes along with other system variables (#1324) --- spec/mysql2/client_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index db7c4b88b..14f446df9 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1083,7 +1083,7 @@ def run_gc it "returns changes system variables for SESSION_TRACK_SYSTEM_VARIABLES" do @client.query("SET @@SESSION.session_track_state_change=ON;") res = @client.session_track(Mysql2::Client::SESSION_TRACK_SYSTEM_VARIABLES) - expect(res).to eq(%w[session_track_state_change ON]) + expect(res).to include("session_track_state_change", "ON") end it "returns database name for SESSION_TRACK_SCHEMA" do @@ -1096,13 +1096,13 @@ def run_gc @client.query("SET @@SESSION.session_track_transaction_info='CHARACTERISTICS';") res = @client.session_track(Mysql2::Client::SESSION_TRACK_SYSTEM_VARIABLES) - expect(res).to eq(%w[session_track_transaction_info CHARACTERISTICS]) + expect(res).to include("session_track_transaction_info", "CHARACTERISTICS") res = @client.session_track(Mysql2::Client::SESSION_TRACK_STATE_CHANGE) expect(res).to be_nil res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_CHARACTERISTICS) - expect(res).to eq([""]) + expect(res).to include("") end it "returns valid transaction state inside a transaction" do @@ -1110,7 +1110,7 @@ def run_gc @client.query("START TRANSACTION") res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_STATE) - expect(res).to eq(["T_______"]) + expect(res).to include("T_______") end it "returns empty array if session track type not found" do From a5ff23f2df09af499093e56a0d51ff030afce255 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 2 Aug 2023 18:23:36 -0700 Subject: [PATCH 741/783] Support utf8mb3 charset naming for MySQL 8 and MariaDB 10.6 (#1323) MySQL and MariaDB have long aliased the `utf8` charset to the underlying `utf8mb3`. They both switched to report the underlying charset name instead of the alias name, allowing `utf8` to target `utf8mb4` in the future. That means we need to explicitly map `utf8mb3` for folks running newer MySQL/MariaDB with older `utf8` dbs. --- ext/mysql2/mysql_enc_name_to_ruby.h | 7 ++++--- ext/mysql2/mysql_enc_to_ruby.h | 15 +++++++++++++++ support/mysql_enc_to_ruby.rb | 1 + support/ruby_enc_to_mysql.rb | 1 + 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/ext/mysql2/mysql_enc_name_to_ruby.h b/ext/mysql2/mysql_enc_name_to_ruby.h index b50146d36..95609a7fe 100644 --- a/ext/mysql2/mysql_enc_name_to_ruby.h +++ b/ext/mysql2/mysql_enc_name_to_ruby.h @@ -51,7 +51,7 @@ mysql2_mysql_enc_name_to_rb_hash (str, len) 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 15, 5, - 0, 74, 5, 25, 40, 10, 20, 50, 74, 74, + 0, 30, 5, 25, 40, 10, 20, 50, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, @@ -89,7 +89,7 @@ mysql2_mysql_enc_name_to_rb (str, len) { enum { - TOTAL_KEYWORDS = 41, + TOTAL_KEYWORDS = 42, MIN_WORD_LENGTH = 3, MAX_WORD_LENGTH = 8, MIN_HASH_VALUE = 3, @@ -133,7 +133,8 @@ mysql2_mysql_enc_name_to_rb (str, len) {"big5", "Big5"}, {"euckr", "EUC-KR"}, {"latin2", "ISO-8859-2"}, - {""}, {""}, + {"utf8mb3", "UTF-8"}, + {""}, {"dec8", NULL}, {"cp850", "CP850"}, {"latin1", "ISO-8859-1"}, diff --git a/ext/mysql2/mysql_enc_to_ruby.h b/ext/mysql2/mysql_enc_to_ruby.h index 4c36e4a42..915d9db03 100644 --- a/ext/mysql2/mysql_enc_to_ruby.h +++ b/ext/mysql2/mysql_enc_to_ruby.h @@ -306,5 +306,20 @@ static const char *mysql2_mysql_enc_to_rb[] = { "UTF-8", "UTF-8", "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", + "UTF-8", "UTF-8" }; diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb index 4db703409..7207981b9 100644 --- a/support/mysql_enc_to_ruby.rb +++ b/support/mysql_enc_to_ruby.rb @@ -33,6 +33,7 @@ "macroman" => "macRoman", "cp852" => "CP852", "latin7" => "ISO-8859-13", + "utf8mb3" => "UTF-8", "utf8mb4" => "UTF-8", "cp1251" => "Windows-1251", "utf16" => "UTF-16", diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb index 72b68eb25..30dfe2651 100644 --- a/support/ruby_enc_to_mysql.rb +++ b/support/ruby_enc_to_mysql.rb @@ -28,6 +28,7 @@ "macroman" => "macRoman", "cp852" => "CP852", "latin7" => "ISO-8859-13", + "utf8mb3" => "UTF-8", "utf8mb4" => "UTF-8", "cp1251" => "Windows-1251", "utf16" => "UTF-16", From 79f78f940685396f1b5f30ec502544bb7e3ba9cf Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 2 Aug 2023 21:14:38 -0700 Subject: [PATCH 742/783] CI: fix defunct MariaDB package names and repo URLs (#1326) 10.6 repo URL was moved. 10.11 package name no longer includes version suffix. Switch both to deb.mariadb.org mirrors and use suffix-less package names. --- ci/mariadb1011.sh | 4 ++-- ci/mariadb106.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ci/mariadb1011.sh b/ci/mariadb1011.sh index 05de344b8..3122bb279 100644 --- a/ci/mariadb1011.sh +++ b/ci/mariadb1011.sh @@ -6,5 +6,5 @@ rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/C74CD1D8.asc -add-apt-repository "deb https://downloads.mariadb.com/MariaDB/mariadb-10.11/repo/ubuntu $(lsb_release -cs) main" -apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server-10.11 libmariadb-dev +add-apt-repository "deb https://deb.mariadb.org/10.11/ubuntu $(lsb_release -cs) main" +apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server libmariadb-dev diff --git a/ci/mariadb106.sh b/ci/mariadb106.sh index b8855ffb1..b6c7153bc 100644 --- a/ci/mariadb106.sh +++ b/ci/mariadb106.sh @@ -6,5 +6,5 @@ rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/C74CD1D8.asc -add-apt-repository "deb https://downloads.mariadb.com/MariaDB/mariadb-10.6/repo/ubuntu $(lsb_release -cs) main" -apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server-10.6 libmariadb-dev +add-apt-repository "deb https://deb.mariadb.org/10.6/ubuntu $(lsb_release -cs) main" +apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server libmariadb-dev From 2957c64ef9146688c9274ca15524eee24998d845 Mon Sep 17 00:00:00 2001 From: Vitaliy Serov Date: Fri, 5 Jan 2024 13:12:58 +0200 Subject: [PATCH 743/783] Fix key verification for mysql repos in ubuntu --- ci/mysql57.sh | 1 + ci/mysql80.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/ci/mysql57.sh b/ci/mysql57.sh index 6f12cc1e0..8c8928315 100644 --- a/ci/mysql57.sh +++ b/ci/mysql57.sh @@ -7,6 +7,7 @@ rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/5072E1F5.asc # old signing key apt-key add support/3A79BD29.asc # 5.7.37 and higher +apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B7B3B788A8D3785C # Verify the repository as add-apt-repository does not. wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-5.7 add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-5.7' diff --git a/ci/mysql80.sh b/ci/mysql80.sh index c9c2d529d..ae7e88313 100644 --- a/ci/mysql80.sh +++ b/ci/mysql80.sh @@ -7,6 +7,7 @@ rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/5072E1F5.asc # old signing key apt-key add support/3A79BD29.asc # 8.0.28 and higher +apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B7B3B788A8D3785C # Verify the repository as add-apt-repository does not. wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-8.0 add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-8.0' From 6f554003ba9fbba832251719e24658aab71e6081 Mon Sep 17 00:00:00 2001 From: Vitaliy Serov Date: Fri, 5 Jan 2024 13:41:37 +0200 Subject: [PATCH 744/783] Fix missing yaml.h for intalling gems --- ci/Dockerfile_fedora | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/Dockerfile_fedora b/ci/Dockerfile_fedora index 644b5974f..5d595f847 100644 --- a/ci/Dockerfile_fedora +++ b/ci/Dockerfile_fedora @@ -14,6 +14,7 @@ RUN dnf -yq install \ gcc \ gcc-c++ \ git \ + libyaml-devel \ make \ mariadb-connector-c-devel \ mariadb-server \ From 1fb604f7483f3b7a6e301da1a882cba5675a9e1b Mon Sep 17 00:00:00 2001 From: Carlos Palhares Date: Tue, 30 Jan 2024 12:09:53 -0300 Subject: [PATCH 745/783] use mysql_options if mysql_ssl_set isn't available for mysql 8.3 support Co-authored-by: Mike Dalessio --- ext/mysql2/client.c | 19 +++++++++++++++++++ ext/mysql2/extconf.rb | 3 +++ 2 files changed, 22 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index a49b3c34d..74f56549f 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1435,12 +1435,31 @@ static VALUE set_charset_name(VALUE self, VALUE value) { static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) { GET_CLIENT(self); +#ifdef HAVE_MYSQL_SSL_SET mysql_ssl_set(wrapper->client, NIL_P(key) ? NULL : StringValueCStr(key), NIL_P(cert) ? NULL : StringValueCStr(cert), NIL_P(ca) ? NULL : StringValueCStr(ca), NIL_P(capath) ? NULL : StringValueCStr(capath), NIL_P(cipher) ? NULL : StringValueCStr(cipher)); +#else + /* mysql 8.3 does not provide mysql_ssl_set */ + if (NIL_P(key)) { + mysql_options(wrapper->client, MYSQL_OPT_SSL_KEY, StringValueCStr(key)); + } + if (NIL_P(cert)) { + mysql_options(wrapper->client, MYSQL_OPT_SSL_CERT, StringValueCStr(cert)); + } + if (NIL_P(ca)) { + mysql_options(wrapper->client, MYSQL_OPT_SSL_CA, StringValueCStr(ca)); + } + if (NIL_P(capath)) { + mysql_options(wrapper->client, MYSQL_OPT_SSL_CAPATH, StringValueCStr(capath)); + } + if (NIL_P(cipher)) { + mysql_options(wrapper->client, MYSQL_OPT_SSL_CIPHER, StringValueCStr(cipher)); + } +#endif return self; } diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 7a07639c6..bee775854 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -164,6 +164,9 @@ def add_ssl_defines(header) # to retain compatibility with the typedef in earlier MySQLs. have_type('my_bool', mysql_h) +# detect mysql functions +have_func('mysql_ssl_set', mysql_h) + ### Compiler flags to help catch errors # This is our wishlist. We use whichever flags work on the host. From 1b40e54188ad371427cc5d7ceffc9ac21ae1eb00 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 8 Feb 2024 10:20:39 -0500 Subject: [PATCH 746/783] fix: mysql 8.3 ssl settings --- ext/mysql2/client.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 74f56549f..5a2fcc1bd 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1444,19 +1444,19 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE NIL_P(cipher) ? NULL : StringValueCStr(cipher)); #else /* mysql 8.3 does not provide mysql_ssl_set */ - if (NIL_P(key)) { + if (!NIL_P(key)) { mysql_options(wrapper->client, MYSQL_OPT_SSL_KEY, StringValueCStr(key)); } - if (NIL_P(cert)) { + if (!NIL_P(cert)) { mysql_options(wrapper->client, MYSQL_OPT_SSL_CERT, StringValueCStr(cert)); } - if (NIL_P(ca)) { + if (!NIL_P(ca)) { mysql_options(wrapper->client, MYSQL_OPT_SSL_CA, StringValueCStr(ca)); } - if (NIL_P(capath)) { + if (!NIL_P(capath)) { mysql_options(wrapper->client, MYSQL_OPT_SSL_CAPATH, StringValueCStr(capath)); } - if (NIL_P(cipher)) { + if (!NIL_P(cipher)) { mysql_options(wrapper->client, MYSQL_OPT_SSL_CIPHER, StringValueCStr(cipher)); } #endif From 43ea8af635f5e23f054294ef7759320d47f30e5f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 8 Feb 2024 14:32:57 -0800 Subject: [PATCH 747/783] bump version --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index bd815bfbb..d84abfc72 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.5.5".freeze + VERSION = "0.5.6".freeze end From 936aec30047df31e34ec7200acb6df59f627b871 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 7 May 2024 01:05:33 +0900 Subject: [PATCH 748/783] Ruby 3.2 deprecates `double_heap` option to `GC.verify_compaction_references` (#1365) This commit uses newly supported `expand_heap` option if Ruby version is 3.2 or higher. - Warning message fixed by this commit: ``` $ bundle exec rake spec ... snip ... :286: warning: double_heap is deprecated, please use expand_heap instead ... snip ... ``` Refer to https://github.com/ruby/ruby/commit/a6dd859affc42b667279e513bb94fb75cfb133c1 --- spec/spec_helper.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2e924eb42..677c34829 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,7 +9,11 @@ if GC.respond_to?(:verify_compaction_references) # This method was added in Ruby 3.0.0. Calling it this way asks the GC to # move objects around, helping to find object movement bugs. - GC.verify_compaction_references(double_heap: true, toward: :empty) + if RUBY_VERSION >= "3.2" + GC.verify_compaction_references(expand_heap: true, toward: :empty) + else + GC.verify_compaction_references(double_heap: true, toward: :empty) + end end RSpec.configure do |config| From 58c8190dd423779eae230e2ff803e2d0c465f67b Mon Sep 17 00:00:00 2001 From: Vitaliy Serov <154601125+VitaliySerov@users.noreply.github.com> Date: Mon, 6 May 2024 19:06:08 +0300 Subject: [PATCH 749/783] Add `ruby-3.3` to CI (#1340) --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 446ee3341..34a04ca4d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,7 @@ jobs: include: # Ruby 3.x on Ubuntu 22.04 LTS (latest at this time) - {os: ubuntu-22.04, ruby: 'head', db: mysql80} + - {os: ubuntu-22.04, ruby: '3.3', db: mysql80} - {os: ubuntu-22.04, ruby: '3.2', db: mysql80} - {os: ubuntu-22.04, ruby: '3.1', db: mysql80} - {os: ubuntu-22.04, ruby: '3.0', db: mysql80} From 19778897cd318752ee38f2054d16f59d50739d8f Mon Sep 17 00:00:00 2001 From: Takuma Ishikawa Date: Wed, 19 Jun 2024 08:59:59 +0900 Subject: [PATCH 750/783] Add bigdecimal to runtime dependencies (#1367) Ruby 3.4 will promote bigdecimal gem to a bundled gem in order to improve maintenancebility. To migrate to Ruby 3.4 easily, Ruby 3.3 warns a use of bigdecimal without adding it to dependencies. > /build/lib/mysql2.rb:2: warning: bigdecimal was loaded from the standard library, but will no longer be part of the default gems since Ruby 3.4.0. Add bigdecimal to your Gemfile or gemspec. So this patch adds bigdecimal to runtime dependencies. I know bigdecimal is not always necessary, but I'd like to prevent a lot of users from adding the gem to their Gemfile manually. Ref. https://bugs.ruby-lang.org/issues/20187 --- mysql2.gemspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mysql2.gemspec b/mysql2.gemspec index cc9a55e48..93e790ae0 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -22,4 +22,6 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s| s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split s.metadata['msys2_mingw_dependencies'] = 'libmariadbclient' + + s.add_runtime_dependency 'bigdecimal' end From b2b6d7b1498316058ce00c993bcb98788a8d1a92 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Tue, 18 Jun 2024 17:02:19 -0700 Subject: [PATCH 751/783] CI: Remove inactive config for Travis CI --- .travis.yml | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 05f847a79..000000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -dist: trusty -language: ruby -bundler_args: --without development -before_install: - - gem --version - - gem update bundler - - gem --version - - bash ci/setup.sh -matrix: - include: - - rvm: 2.4 - env: DB=mariadb10.0 - addons: - mariadb: 10.0 - hosts: - - mysql2gem.example.com - - rvm: 2.4 - env: DB=mariadb10.1 - addons: - mariadb: 10.1 - hosts: - - mysql2gem.example.com - - rvm: 2.4 - env: DB=mariadb10.2 - addons: - mariadb: 10.2 - hosts: - - mysql2gem.example.com - - rvm: 2.4 - env: DB=mariadb10.3 - addons: - mariadb: 10.3 - hosts: - - mysql2gem.example.com - - rvm: 2.4 - env: DB=mysql55 - addons: - hosts: - - mysql2gem.example.com - fast_finish: true - allow_failures: - # Allow failure due to a package repository not found for now. - # https://travis-ci.org/github/brianmario/mysql2/jobs/767276558#L1255 - - env: DB=mysql55 From 00d6920800d3b53574cdb9fc20d6fce37df5f368 Mon Sep 17 00:00:00 2001 From: Dirkjan Bussink Date: Thu, 5 Sep 2024 19:03:19 +0200 Subject: [PATCH 752/783] Add vector type added in MySQL 9.0 (#1375) This type was added in MySQL 9.0, so ensure we can parse it here. See also: https://dev.mysql.com/doc/dev/mysql-server/latest/field__types_8h.html Signed-off-by: Dirkjan Bussink --- ext/mysql2/result.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 23ec611ee..3e94f341a 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -27,6 +27,10 @@ static rb_encoding *binaryEncoding; */ #define MYSQL2_BINARY_CHARSET 63 +#ifndef MYSQL_TYPE_VECTOR +#define MYSQL_TYPE_VECTOR 242 +#endif + #ifndef MYSQL_TYPE_JSON #define MYSQL_TYPE_JSON 245 #endif @@ -382,6 +386,9 @@ static VALUE rb_mysql_result_fetch_field_type(VALUE self, unsigned int idx) { case MYSQL_TYPE_JSON: // json rb_field_type = rb_str_new_cstr("json"); break; + case MYSQL_TYPE_VECTOR: // vector + rb_field_type = rb_str_new_cstr("vector"); + break; default: rb_field_type = rb_str_new_cstr("unknown"); break; From 450d0704189a8b28dfb1c7658637fda40cbf36da Mon Sep 17 00:00:00 2001 From: Pat Allan Date: Thu, 5 Sep 2024 19:03:54 +0200 Subject: [PATCH 753/783] Avoid modifying frozen string literals (#1364) In particular, the extconf.rb build script. But there's also one test as well. --- ext/mysql2/extconf.rb | 2 +- spec/mysql2/client_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index bee775854..a1f6cb240 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -86,7 +86,7 @@ def add_ssl_defines(header) /usr/local/opt/mysql@* /usr/local/opt/mysql-client /usr/local/opt/mysql-client@* -].map { |dir| dir << '/bin' } +].map { |dir| "#{dir}/bin" } # For those without HOMEBREW_ROOT in PATH dirs << "#{ENV['HOMEBREW_ROOT']}/bin" if ENV['HOMEBREW_ROOT'] diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 14f446df9..a8f90da24 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -886,7 +886,7 @@ def run_gc end it "should carry over the original string's encoding" do - str = "abc'def\"ghi\0jkl%mno" + str = "abc'def\"ghi\0jkl%mno".dup escaped = Mysql2::Client.escape(str) expect(escaped.encoding).to eql(str.encoding) From 16c40a5552a6a1d5c7bf3e4a3f7db0f96adb9381 Mon Sep 17 00:00:00 2001 From: Jarek Prokop <33065078+jackorp@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:07:12 +0200 Subject: [PATCH 754/783] Set CA:TRUE for ca-cert.pem used in SSL tests. (#1357) Since OpenSSL 3.2, setting CA:TRUE seems to be required, otherwise we will get an error when trying to use CA file without the field. Example of such error using the openssl verify command: ``` $ openssl verify -CAfile ca-cert.pem client-cert.pem CN=ca_mysql2gem error 79 at 1 depth lookup: invalid CA certificate error client-cert.pem: verification failed ``` --- spec/ssl/ca-cert.pem | 31 ++++++++++---------- spec/ssl/ca-key.pem | 55 ++++++++++++++++++----------------- spec/ssl/ca.cnf | 4 +++ spec/ssl/cert.cnf | 4 +++ spec/ssl/client-cert.pem | 26 ++++++++--------- spec/ssl/client-key.pem | 55 ++++++++++++++++++----------------- spec/ssl/client-req.pem | 24 +++++++-------- spec/ssl/gen_certs.sh | 6 +++- spec/ssl/pkcs8-client-key.pem | 52 ++++++++++++++++----------------- spec/ssl/pkcs8-server-key.pem | 52 ++++++++++++++++----------------- spec/ssl/server-cert.pem | 26 ++++++++--------- spec/ssl/server-key.pem | 55 ++++++++++++++++++----------------- spec/ssl/server-req.pem | 24 +++++++-------- 13 files changed, 215 insertions(+), 199 deletions(-) diff --git a/spec/ssl/ca-cert.pem b/spec/ssl/ca-cert.pem index cf9b8d511..632cdeb71 100644 --- a/spec/ssl/ca-cert.pem +++ b/spec/ssl/ca-cert.pem @@ -1,17 +1,18 @@ -----BEGIN CERTIFICATE----- -MIICqjCCAZICCQDbDS+Z2mpWkDANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxj -YV9teXNxbDJnZW0wHhcNMTUwOTA5MDQ1NzIxWhcNMjUwNzE4MDQ1NzIxWjAXMRUw -EwYDVQQDDAxjYV9teXNxbDJnZW0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQDFnpc22lPFtdPELsIffsDt8cD2Hkt47nGMcKQ9n4U98yAg+fodipyP1Bn0 -2OeaONqpttJIET7HxlGrtugPtV/O8XZHlhfAHrRUDMFZJhgnnqK+c/7fRGeB0Eqw -ljBlRD/dDL3bFq5hVBC9QsGi5k03r+xLPKm5ccAr4WtofcoKXqEbSO6koTSrsGG5 -7inlldM2AVzrY2kXbe0jAyNvYmDL2ycN8G2wObogPWDfITQRhOxfkzKIQiEhQF2Y -/DlhT7IbIarBIm6abf6JxZ6/Sm5XyVNEWOnryXM6rKyVeGktCxLHNmxx5eKYs440 -8hNgURa8pB+aZaiokkwhM1+jmE83AgMBAAEwDQYJKoZIhvcNAQELBQADggEBACrQ -umqygXkkbff5Jqf6AYi30U3c+byX+IButRKXN9Ete2LPcT76o/snS9Lexf3KQsIy -a2Tcc9adak7pBf7FgHdiZkWiQp3MDgx2gJu6Uu6TNzfT8jy2JrHyBWw4ydEvhyA8 -cgelTHSaudafKeQgU4KYc8bqafYFILkWxPzgtwitENIDfx/SHt65BWaQZjYJlFou -zPZXeoT3lAwKGYqIvwPvBTC23cXg/Swt/mcKe3/Xxjx85Dw/9vi6a9+VQwlOojgd -w2o07xkIcJcI0Oxyp3mD0U5wAmBQGI76Yi9ZDROHF65KEXfQ3tYKl2vR7CXpcJ4+ -7+fVsE8+dADJdZIiuaA= +MIIC7jCCAdagAwIBAgIUf1RggahC+P3zuvdDnArIrPylegwwDQYJKoZIhvcNAQEL +BQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwyZ2VtMB4XDTI0MDIwOTE1NDkyOFoXDTMz +MTIxODE1NDkyOFowFzEVMBMGA1UEAwwMY2FfbXlzcWwyZ2VtMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuEAFH4rhTMGs1bIHJgWvbsgNZBc2TrjPX0Jf +3kdHvq1u0bHuaYFUyY/yXoOchHbDvRYx1WL2jSkJuc2JMYN1V+j4EUtG9KAt4dqx +LNTy6SxpQeMKEqtiTNc9aMR8cAxliSZSj/Qn6JpcSJPE/loIPdEC/MTo7ONcJ0xQ +5LymZqnuKZGw8L2UzZ+Zof3cYr2nPLoZDGtBsDDf5W184nl0MqTonu6/+raL/4C+ +3Smy/5IOcJzRfvw6Nc/bvi9eWkypNZzG3XaSO6K5d399KLn0mf9ZbXy5bq9klCUI +seIhmA77vzaBOwdQUJKKijKGqlTahfoAiUV3AgcoxAnytKraVwIDAQABozIwMDAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBScsUBgsDMWnhPO1njE2uD6P7gr9TAN +BgkqhkiG9w0BAQsFAAOCAQEAQbKVJ2eDt/99lnHGr3uFgVzWr26QomGfWysHhwHf +9pgS2KKbT7u/MGgWMi2jpXAJFCeRD2v/b5lRpeI03ZGuTJ0zqlItXzlBY6bND3KB +AyJ5orfJu0NVwhjFZdnGH1IQVWjMW8pt8WzopYRyyfnqpbwE2e8wJUgOo9LDgJm8 +mK4bcpRVbvS2fo+g+CZ9HXzOXpL0m4gbsnPjeulmtSTXFX1/t00Hw+Gt2POB2A0h +VNFKxS08uohPq49+MNeaTA0CjQdpG09lh7Cua/mSgzmYvWF9iYJpsBggzDUi7hab +T07GzSz0fpfb35RAtsghgCyxaW7M3fAaztVJaKPDB4SfUw== -----END CERTIFICATE----- diff --git a/spec/ssl/ca-key.pem b/spec/ssl/ca-key.pem index bbdadb7cf..5deca5740 100644 --- a/spec/ssl/ca-key.pem +++ b/spec/ssl/ca-key.pem @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAxZ6XNtpTxbXTxC7CH37A7fHA9h5LeO5xjHCkPZ+FPfMgIPn6 -HYqcj9QZ9NjnmjjaqbbSSBE+x8ZRq7boD7VfzvF2R5YXwB60VAzBWSYYJ56ivnP+ -30RngdBKsJYwZUQ/3Qy92xauYVQQvULBouZNN6/sSzypuXHAK+FraH3KCl6hG0ju -pKE0q7Bhue4p5ZXTNgFc62NpF23tIwMjb2Jgy9snDfBtsDm6ID1g3yE0EYTsX5My -iEIhIUBdmPw5YU+yGyGqwSJumm3+icWev0puV8lTRFjp68lzOqyslXhpLQsSxzZs -ceXimLOONPITYFEWvKQfmmWoqJJMITNfo5hPNwIDAQABAoIBAQClkmtFRQVdKCum -Ojrg4nVIpv2x983qI3U1YobpLocXUWVA29BIAgOMqfuZXkYlu67Q9OEYCoLcJHf2 -88dYqfD81OfxsHpzuAYESa+RPs6MG2hlQ5BuhcRnShnZ++vOXLFZRjynnEg8OY/Q -0makUmqt1pKWstvNCNYmrbYtFP87UXQCF06zkhM0cZJvVt0gPZGUituWI0uAoE51 -U8+WSwD/T761BHM6BuMn56mfZkP5jeVIFl0iFha9rGR0Z6K8mVQAYQAUtUx9tN/3 -a8fEOcYulq/9R5tMRWtsF8LD8DGQBNkY3e/WKDuZtLw2Dl3L09gxVH9DXCLiYU5d -OG3JmqDpAoGBAP08yq143H4n6yGT9DC8YjaLgN0VoenK21CEqhwtGWipc/kbGooe -/jaHl6bo9v1GOGlJieqSUqsXNltS7FOLhGFAQFwMYZ3V/h15Vx23Z+xkCCHIB6HH -YJZqkQY7Jt86wXcaLU5j9fxM+BY+8Ets4bVhZN9Ai6AnlTz0+d8UJG+bAoGBAMfG -efYrdjTKI5eK9aiVJyoh57BEPOsTsave2U+R8Q+fErQ0QD0UmbWgwYGgkPuDrFYT -owg09EEz88KONv18VZ+mB1qfyQUoOL6rWIGxXC08upy2i9100PaBFiYlkLNoK7yJ -bze0rFSiFclJJXZGzEaVvcEdKnXxfhttaJwQGK6VAoGBAOQEUvJzuwWU5/CqCdvA -JCa84eEv00RxtZwAeDM6oIBO4+/O6cyoL3nmCTTu20YebjjPUHF4IxuOoREFz2lC -XIY8ljbLpzG5N0BOu5Q0SkzdnTzdoZGXtm55se+MX2nsu7qERXsqIpl0rIVLUo53 -kZwCABPNSGuCeKwUYNDukAg1AoGBALiHHSqEVKhIOn4FDgqM0uM49CA9t6NPyqI9 -sq6r2GWcgpNPXDLPL3e0KGlK3gBkTLApbULsXt1HVpZT9HlJ+nD/0/UieHS6BUgh -Txxkrgbe/GQ6vZBuEYJQFBxiQHlm9Fcu/zsOOMvn94W4edD5bkCYmfChtxHAYcKF -2cWlnJbNAoGAWMV4GIY2DYlztXdyMVuPwsjPcSPMmL8Nc2ATWYRfcoG0Zl0yvwPh -2VOu7Q/7bNF2LOe6lPe1hoeB6rT44IYZaWMo3ikY8xW9RztOLSv8E9uE1K9yq8OA -P8QzXmr1Lga+hoEmMHc2biEJNeF6iAcAFfrHj9Sr7w5PC8g4A3PlCvU= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4QAUfiuFMwazV +sgcmBa9uyA1kFzZOuM9fQl/eR0e+rW7Rse5pgVTJj/Jeg5yEdsO9FjHVYvaNKQm5 +zYkxg3VX6PgRS0b0oC3h2rEs1PLpLGlB4woSq2JM1z1oxHxwDGWJJlKP9CfomlxI +k8T+Wgg90QL8xOjs41wnTFDkvKZmqe4pkbDwvZTNn5mh/dxivac8uhkMa0GwMN/l +bXzieXQypOie7r/6tov/gL7dKbL/kg5wnNF+/Do1z9u+L15aTKk1nMbddpI7orl3 +f30oufSZ/1ltfLlur2SUJQix4iGYDvu/NoE7B1BQkoqKMoaqVNqF+gCJRXcCByjE +CfK0qtpXAgMBAAECggEAM5T8ujFr1MzN4b+m/66MyDNqiFB1TEGyEKWo6DZFcCzm +vv8U02W5QnqxrGMlMPJ85xVtGyPLCYbpKaLQm1OFyPg4ZsMP2NF1Nus+OeJeJQhh +aWgx/DsN2JxTnV6Qxd+6l1RqvdFpUNXSKyFvf5PeBcxbjT9lRFh8hqX3aaok3c2P +Z4sROHNqgXe/mnCXv3nkUQXct7oFgdeMR6Zy8GFNxMaxvFhCmT+Q0NFf0hqp6/at +b/P0+tZYjp7PkvCkCo2Z7MCdsnI1BNn5ggtdqhLs9tu1f6iwMH0z3tKkWXkX1dJC +Ad0G30qfv6A9IOEWevwJyuVcp9onRmcUaaVNSascbQKBgQD4ITxJcIE3MAtK1W7q +vvEm64i2ay5XfBhoi2oik3+MVlzY2dRiGOEffvysK1Xls8VlLT/RSZVWLjJ+EvrH +DJZmbl972UFvYt5JGEv53YYD0kJ1fQvkLA9//Xkg6Wdac+7BzHxc09fV1k2mUvpH +jJs7JSgLDwVvvU9+vs3eFWWAiwKBgQC+GBgARwb2NKEfXRtyT+X7Z0jmQoQCJDr7 +34D7p5u6lRShlzZNFvwbc5YAIXNNuveqTsrKL57yo4vzNHpDzpXj+kdFIKGAXzXN +BoohGCtIQrrXYGONUubvk3njlOrppdD9kMN+ioHVp/Gm9Dxrn+0HKtzALn7pgZa7 +IzA/3Mpa5QKBgEOfozOMotqskFdLxdfaRBTMWk0E9vNG0cwkOr/DnR5dJx6+dyBp +EWmpDSnLAbUBgomphFwAht+e5YnwmEIJTzAJYqJ5OlkmA9i9827cjbqa4hvtAYGk +9HB4Xzu2AMHpGKfemAIghhE0P6NVt/op+uBqpvgkluG2IWU0kRy2jhwzAoGAbWl+ +vwIirqkiJ+Q2PPhh3e7X1bhpNLZXwMsm+THCf4T5J/zZw0s8dix0JMUcEZxQmpTZ +QcBhEzUxAx2sVcTdHyfZx579dd7XH5fo/x1jJCdMVVTkV95kj3ZpzKTVBQBsptWg +v//GtQwCGd8vu56EFgEEqBTa9VmiQToCtm9FhUUCgYEAwMtvoKM7N9WawAVEeUWG +oDoJhhbnCHvg7ohNEymfsfHaHNUs/WXgk9mHTqY9lkMrnEmO71z8LPw0q67UbiIx +58MWCpDcOzzNrmXKEbZQ380qYezOFffuvkDKAAl2OmxSAbKLvV/5/vAAEXyBJe2u +brUtxFuKTvZbuCZU+L04y8I= +-----END PRIVATE KEY----- diff --git a/spec/ssl/ca.cnf b/spec/ssl/ca.cnf index 07374ad3e..6e98bd981 100644 --- a/spec/ssl/ca.cnf +++ b/spec/ssl/ca.cnf @@ -6,6 +6,10 @@ default_startdate = 2015010360000Z [ req ] distinguished_name = req_distinguished_name +# Root CA certificate extensions +[ v3_ca ] +basicConstraints = critical, CA:true + [ req_distinguished_name ] # If this isn't set, the error is error, no objects specified in config file commonName = Common Name (hostname, IP, or your name) diff --git a/spec/ssl/cert.cnf b/spec/ssl/cert.cnf index ca1dce701..832ec1197 100644 --- a/spec/ssl/cert.cnf +++ b/spec/ssl/cert.cnf @@ -6,6 +6,10 @@ default_startdate = 2015010360000Z [ req ] distinguished_name = req_distinguished_name +# Root CA certificate extensions +[ v3_ca ] +basicConstraints = critical, CA:true + [ req_distinguished_name ] # If this isn't set, the error is error, no objects specified in config file commonName = Common Name (hostname, IP, or your name) diff --git a/spec/ssl/client-cert.pem b/spec/ssl/client-cert.pem index 3887246fe..6c46e2d7c 100644 --- a/spec/ssl/client-cert.pem +++ b/spec/ssl/client-cert.pem @@ -1,17 +1,17 @@ -----BEGIN CERTIFICATE----- MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy -Z2VtMB4XDTE1MDkwOTA0NTcyMVoXDTI1MDcxODA0NTcyMVowIDEeMBwGA1UEAwwV +Z2VtMB4XDTI0MDIwOTE1NDkyOFoXDTMzMTIxODE1NDkyOFowIDEeMBwGA1UEAwwV bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEA1ZuBf1FVJqil7/LvnXqPd43ujo0xqbFy7QrqmM5U/UM3ggMCf2Gr2/Wo -ZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMUuH+Uiv9UwmOFl9Em3FXQQ8SG7fV7651u -AUskNgfEqoy+f+uvi1P155rHNDx7Yw6i+wwfpLGTU0boMnLL6cO/KcIbZlx4/2Lq -r5sYbpIqhz46bbG+fIhvepruH9h7WVWqAibTqymYrA3T03O/HWTOqfq03gM7Oe3t -JvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2Ajh7Yx9DQ1gqClTglbPFHNiWPcGACg+W -2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA2 -qTbfgDm0IG8x1qP61ztT9F2WRwG7cp6qHT5oB5wDcOUFes9QJjeB8RoIkB+hRlqG -J6/Tbxs49d7oKhOQ0UaTnfIKC5m0UFYFGc3lUcwxQyggOWx9XV5ZmGb48+RLFnDV -Gfcs/hvfem6Xfpgzr8bGs2ZM9x1j9YnXNJVePmKwktjCPnXPOeHyxNZPA+CWHed/ -dNg1IWuQnnp20LgNRARCTgR/ONAJNUfh2GqRLq2JOf0cyhNlsKQ3epkeUyc72knI -oWVxPluQYvFHN+xif0FMGVLM8lz0b+6uPJDA2Km70B/iorMRVb0vbMeFrMmQ5UgM -4tplX52P2vb6JNnektfR +CgKCAQEAnaBhN4wLvmzVFg91HZH2PmacZVpb3I51qoiGQS7i8oiLAyvqCIjJVoCG +CWfBk8s6WNAPiE9pqvKyUXTfpvZBlW9t4hwQImRD15Sr55/yWWnKO7SwHhY8BTeo +QVlSBrfPnKMIjByudi/G7fR9naXoeQREAP2hl8h/gh7UBAm6kNTFxxD/1Byal/iW +BwoYqQQYVUuYThdI0C17qYn7dthtjjGRbexouVRwN446qR9dC71A27VgNUmlFb/E +ygmfEFPhJRNf9fSVAABkNHPhHtg5tURjcZi9ldZMr8o6643hY1kW1xyS3VFLIcP6 +2Cing0f0/Cjh5jg4l5uEcN/AUpbw+QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA2 +4Hh2Qf3eDcKZCvjHmwgCXkhFpZtgpBTgEyPfl0UKwrgkdBiqGY6jywCIsDvp0DrN +OL7Ybk+tdDE95HUfC4W7MBa2wS5WCksIjq+8N4Q61np8gine01IUofjeBSlkNgkF +phtOVeadCky3UlBBGXBSwaPC+uyHlXEOlmfm1YOP0QboqzMorEl4ZECxFVtkyKbu +tud9BDitIcb7x2JjrLlmnltE3JQ+WI9iyL0EAXDloIPUkeyf123lFGNSXDCEj2Ru +8wzQjNDGYiMrwYFib+6d0UE1ED2KLn7TN5lHhgC/H3RUv3dAIgciuysqepTC25DV +soLMzebCsw4UprXFCUbz -----END CERTIFICATE----- diff --git a/spec/ssl/client-key.pem b/spec/ssl/client-key.pem index d75294225..ad4af278c 100644 --- a/spec/ssl/client-key.pem +++ b/spec/ssl/client-key.pem @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEA1ZuBf1FVJqil7/LvnXqPd43ujo0xqbFy7QrqmM5U/UM3ggMC -f2Gr2/WoZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMUuH+Uiv9UwmOFl9Em3FXQQ8SG -7fV7651uAUskNgfEqoy+f+uvi1P155rHNDx7Yw6i+wwfpLGTU0boMnLL6cO/KcIb -Zlx4/2Lqr5sYbpIqhz46bbG+fIhvepruH9h7WVWqAibTqymYrA3T03O/HWTOqfq0 -3gM7Oe3tJvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2Ajh7Yx9DQ1gqClTglbPFHNiW -PcGACg+W2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQIDAQABAoIBAEWhGjZZWctvQCAW -bbtEv012a6P2LJEnMdJJM6253IRuC8MKnh7NxMq/qjOWK0OX+R+tQ0qt1Udk9H6U -92SAAHAkHaYCmHYywvtWm66gU+2Q34Gnp2AcHFfyinBLgTNHlvkNRe/G8QMWzFrB -3luNt57Tn5b8Hbh+1gpYW8pOF2BMgIsLRK+8b26TKUWrSCc/ZxOSY4wmrNybxkgr -HGt27lwIN0cvJZbmQvHevNzzCn+bYoo2K1MQj34xHbZ2NLqKqFVlSJtr9+BHffAc -fkcf+V+D+FkitUVkha9qXa02wtLzYSF+Q5Ef3kQQs6hs/HOdN16g17l9QC6Mk1vm -a9yV5CECgYEA/9FglQmFimwBCOWEvjkZzoXFusuvRWRgAPU/1c9DAYRS2GfOkjlH -RPAltczdXh4EQ0NkCqHH7JWgrdXGonKg4fcITumdwcYKV5QfmKBO4onAboEM0Wq7 -wjifuga7npQhPnGvkXFDamVz5McQPObvV42VAUwk1N00gOYw/46ryLkCgYEA1cJv -jHAq0DKlUGXKyZ+ixsogRpwTQvND/qUquSLgD/KgfeT+70AnsEF6DbVLKoaJ35CF -ju83VYLfeBljq+E/lgmAyaChplORRXcu+xPQE4rbp0MbsoBOYGNWLFAw3twGsQf9 -iuAtCVxij/hhj4FWRebYHMnV6Min2VPbZdASNnUCgYBIiX8gY3XJPTzB4ArWwWwu -4kGh6NWHEKIkQ2ZZYw615GZ1VGH/llw+EPYwaamvYUWGKRq55QvCat8Hy6EqOOSj -jh99+MIxyszt7mNTLMmRdMvqyY7v5prcxJ+N6RDUM16FzUiiLgKWrbPCACv7iOP+ -6HeCyat77ElR73OfUz4kiQKBgH+2r9cEnU/PMp4ac1KLokGLOkV1srxpg9J89E2w -3JYqrGELlJV1i0DvnfDaxJIf1/hO7L09h537l3C2Gqry5X7LJrtQ0cQCYeVTFCrG -56cFa78/hSjdJ/bG4xGOx+QfKZBT6dQzpDTXkbva9s86w0T4a16n6LowSLi8NXVb -H8aRAoGBAKzlt6deB+ASIrGH6mM0eLxF1OcNTB+rE4AJxoUyO1oAmCv9UeK3IzwP -ohhmo/kEOSCVG6WE+6+r9mojcoHu3ZrobVKl59R7KMdzunMXqxZcXeTqjvqdTtV7 -rWuEz/TKIe7o0Tx19XVGuNftyx2pLuspSAAbZ+YAQJtzmLzsGkss ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCdoGE3jAu+bNUW +D3UdkfY+ZpxlWlvcjnWqiIZBLuLyiIsDK+oIiMlWgIYJZ8GTyzpY0A+IT2mq8rJR +dN+m9kGVb23iHBAiZEPXlKvnn/JZaco7tLAeFjwFN6hBWVIGt8+cowiMHK52L8bt +9H2dpeh5BEQA/aGXyH+CHtQECbqQ1MXHEP/UHJqX+JYHChipBBhVS5hOF0jQLXup +ift22G2OMZFt7Gi5VHA3jjqpH10LvUDbtWA1SaUVv8TKCZ8QU+ElE1/19JUAAGQ0 +c+Ee2Dm1RGNxmL2V1kyvyjrrjeFjWRbXHJLdUUshw/rYKKeDR/T8KOHmODiXm4Rw +38BSlvD5AgMBAAECggEABwUuaexqq3uqY+CisoNJMySby5HM1z5gXuIqDzt42nIa +tYnzqEH7VvHDcSYgriXrfoAfE6RBzF4hyKn1ZLjBWVf3Tg46PmXhIE1b+KVxhD+c +xQWk9boUyJYJgDDrtnviRb4nHSJmjMH6UKfGc8qArOnJOOgS6zEqqKTEhDzbWnmQ +Q5GjZGKjTMfbC6u9r6nwcAlPax2tzRmPNtQFYMSjcHmvN0IuhwVlZlULsdPnpDpS +3LeR8b/75Og8BbqXJNdLAXQq3Athu9ADuxfS/rOBxWwEKElV80gFMo/qiWZ/DVqw +UUExYuDAx8Jnf4j8Vb2UcY2bpxvGHLrD10gRJmUxLwKBgQDLzd95e0SXxuWEHBdA +oHfermVwUzs1aa5zqs/KJEICmFE94bJbWQKbW1TDQggIr45XylQNsQuh0RS1USsd +YweA7z00HPcj5ON7O+5tKwfP+C+jl1s1yfYPvaSiRKwLde6DdVGos29C5OpoIQ+E +hjC02n2d84F/lrjfHn3MQH/2vwKBgQDF/u6jNwfHsfgOnKGJv9BKfmLg4saYUvMv +bclkQS0qrSgrloMSZs1QcpZJWneiO0vjsxoTeLcUIcsuhfxpMsN60nb/QncB749C +ts9SuV9Rdv4+7U1tqg2ZQ52zjMliZvEDriPZ40vhONBXYyKtx1HZ9OexDOLKUzzY +c1MMobj+RwKBgBFWPwdvhANBSS720MePnwLTZQ+sFOJTTiLKyghRE0hzOp4AABMj +PESI/WnqyRIsFPjE3meXwvyN86wE7pz+WpoOP++Z8zAbfXpzO7IPsgdv/mV1L64g +swzdvg6LtvL2okaOiVbHhNR08rfO8Cn+3E/WMk9ocoCvCqT4TA0/A2OzAoGBAILO +TpgzxgcPM6NrpWkc+R4N64NJLwz5WEJQVMnQKWfVaAGL+WIR2ri4S0OA6iKa7CMt +cx/EE6fQP6ynxj8102F0ZDt1jKwRuWLI5aVwZGGsrIGkQxAdVciYnDo/29gPzFCz +HmpXuQy9fR8Olp2aXiARpXQZ4EbswPj7D7X7rf0HAoGAQhhg0xfIuOrLRXrhfFEg +NzASVKvtCEMDbJD4mWCOuqe7Jlvi/qmXZ9YnuKOmGukGV0/ePtLI1+Hcstr4kexb +lUJYZQlB9e0Ft87UD16TuGHAMBKdtXQKuBjMeI0C43kSBq0XuiyY9b44ADH/up07 +vkqwfAOUVKCHp4KOOruW9cc= +-----END PRIVATE KEY----- diff --git a/spec/ssl/client-req.pem b/spec/ssl/client-req.pem index fc077486b..38774a53e 100644 --- a/spec/ssl/client-req.pem +++ b/spec/ssl/client-req.pem @@ -1,15 +1,15 @@ -----BEGIN CERTIFICATE REQUEST----- MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB -IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ZuBf1FVJqil7/LvnXqPd43u -jo0xqbFy7QrqmM5U/UM3ggMCf2Gr2/WoZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMU -uH+Uiv9UwmOFl9Em3FXQQ8SG7fV7651uAUskNgfEqoy+f+uvi1P155rHNDx7Yw6i -+wwfpLGTU0boMnLL6cO/KcIbZlx4/2Lqr5sYbpIqhz46bbG+fIhvepruH9h7WVWq -AibTqymYrA3T03O/HWTOqfq03gM7Oe3tJvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2 -Ajh7Yx9DQ1gqClTglbPFHNiWPcGACg+W2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQID -AQABoAAwDQYJKoZIhvcNAQELBQADggEBAB05YaBSyAKCgHfBWhpZ1+OOVp1anr2v -TkStnqmNrNM2qBJXjfrythPTX4EJAt7+eNdH/6IVA93qKC/EUQVuMjgfMmMUaM+m -5pqfAo95w7vUY147U9nbC+EIo2u1KOVTNTgl45H372/1vCwTHZYu2atCk4tN3ueO -0O2XW89Kq94/7PDAExN2PhZdeATVX9dPNT+7ZUDNe8cuq9v0YCHy+2JN2WkplxcG -kMyCE3YYLnd96YtWiS9DOUib3+b7FwyGe0dXeLVw1br3NZGCZrybyfmnAQfiouAF -9nMxKIpWFSx00ubGrUefOQqp6nuk27n+scgr4+d6dBXz9efEEvTbLKA= +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnaBhN4wLvmzVFg91HZH2Pmac +ZVpb3I51qoiGQS7i8oiLAyvqCIjJVoCGCWfBk8s6WNAPiE9pqvKyUXTfpvZBlW9t +4hwQImRD15Sr55/yWWnKO7SwHhY8BTeoQVlSBrfPnKMIjByudi/G7fR9naXoeQRE +AP2hl8h/gh7UBAm6kNTFxxD/1Byal/iWBwoYqQQYVUuYThdI0C17qYn7dthtjjGR +bexouVRwN446qR9dC71A27VgNUmlFb/EygmfEFPhJRNf9fSVAABkNHPhHtg5tURj +cZi9ldZMr8o6643hY1kW1xyS3VFLIcP62Cing0f0/Cjh5jg4l5uEcN/AUpbw+QID +AQABoAAwDQYJKoZIhvcNAQELBQADggEBACPgLE417R3dHf9eiwVtoOSzm8ltNBbz +5dRqiHDMEXuH+aGiNtTI1BI9akXrgjyN+nXWK09jZsWJ/+8mj+NcGS8JfdQdVn0c +Ov/kmxoVwNEzj3mboL0amM7kQv6zRa1hKk7l5ZE+5G/EvWU2xF0qrHvGLvphgL0D ++/PBJomMHAMYF5MUEOLtnwdslMS0OyAQCHu/swDhIj8jSCkoz4M8gpB2cV+VOHCG +hW2sxlF67cgXLBXCgU0TP6bZbByPb8pEVax2808+wl3fCiM2IwcRtM3WTkSbESuN +CjFO7OeUq4o1Dtiw2uBfNpe+dviGvDeyIBfUmkzqJH3BBpQyGHWU9YE= -----END CERTIFICATE REQUEST----- diff --git a/spec/ssl/gen_certs.sh b/spec/ssl/gen_certs.sh index 3d48da014..4996b845d 100644 --- a/spec/ssl/gen_certs.sh +++ b/spec/ssl/gen_certs.sh @@ -10,6 +10,10 @@ default_startdate = 2015010360000Z [ req ] distinguished_name = req_distinguished_name +# Root CA certificate extensions +[ v3_ca ] +basicConstraints = critical, CA:true + [ req_distinguished_name ] # If this isn't set, the error is "error, no objects specified in config file" commonName = Common Name (hostname, IP, or your name) @@ -35,7 +39,7 @@ commonName_default = mysql2gem.example.com # Generate a set of certificates openssl genrsa -out ca-key.pem 2048 -openssl req -new -x509 -nodes -days 3600 -key ca-key.pem -out ca-cert.pem -batch -config ca.cnf +openssl req -new -x509 -nodes -extensions v3_ca -days 3600 -key ca-key.pem -out ca-cert.pem -batch -config ca.cnf openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-server-key.pem -out server-req.pem -batch -config cert.cnf openssl x509 -req -in server-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-client-key.pem -out client-req.pem -batch -config cert.cnf diff --git a/spec/ssl/pkcs8-client-key.pem b/spec/ssl/pkcs8-client-key.pem index a4858b4a0..ad4af278c 100644 --- a/spec/ssl/pkcs8-client-key.pem +++ b/spec/ssl/pkcs8-client-key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVm4F/UVUmqKXv -8u+deo93je6OjTGpsXLtCuqYzlT9QzeCAwJ/Yavb9ahkkY9OTU0VqJTIzmaFKWCL -T9IY8Sn0kxS4f5SK/1TCY4WX0SbcVdBDxIbt9XvrnW4BSyQ2B8SqjL5/66+LU/Xn -msc0PHtjDqL7DB+ksZNTRugycsvpw78pwhtmXHj/YuqvmxhukiqHPjptsb58iG96 -mu4f2HtZVaoCJtOrKZisDdPTc78dZM6p+rTeAzs57e0m8lupfYt5xC+LZJtChfxz -YytDpfvEN7YCOHtjH0NDWCoKVOCVs8Uc2JY9wYAKD5bam0Jj9DZJ0/mHV22PlLDl -XAvd28KNAgMBAAECggEARaEaNllZy29AIBZtu0S/TXZro/YskScx0kkzrbnchG4L -wwqeHs3Eyr+qM5YrQ5f5H61DSq3VR2T0fpT3ZIAAcCQdpgKYdjLC+1abrqBT7ZDf -gaenYBwcV/KKcEuBM0eW+Q1F78bxAxbMWsHeW423ntOflvwduH7WClhbyk4XYEyA -iwtEr7xvbpMpRatIJz9nE5JjjCas3JvGSCsca3buXAg3Ry8lluZC8d683PMKf5ti -ijYrUxCPfjEdtnY0uoqoVWVIm2v34Ed98Bx+Rx/5X4P4WSK1RWSFr2pdrTbC0vNh -IX5DkR/eRBCzqGz8c503XqDXuX1ALoyTW+Zr3JXkIQKBgQD/0WCVCYWKbAEI5YS+ -ORnOhcW6y69FZGAA9T/Vz0MBhFLYZ86SOUdE8CW1zN1eHgRDQ2QKocfslaCt1cai -cqDh9whO6Z3BxgpXlB+YoE7iicBugQzRarvCOJ+6BruelCE+ca+RcUNqZXPkxxA8 -5u9XjZUBTCTU3TSA5jD/jqvIuQKBgQDVwm+McCrQMqVQZcrJn6LGyiBGnBNC80P+ -pSq5IuAP8qB95P7vQCewQXoNtUsqhonfkIWO7zdVgt94GWOr4T+WCYDJoKGmU5FF -dy77E9ATitunQxuygE5gY1YsUDDe3AaxB/2K4C0JXGKP+GGPgVZF5tgcydXoyKfZ -U9tl0BI2dQKBgEiJfyBjdck9PMHgCtbBbC7iQaHo1YcQoiRDZlljDrXkZnVUYf+W -XD4Q9jBpqa9hRYYpGrnlC8Jq3wfLoSo45KOOH334wjHKzO3uY1MsyZF0y+rJju/m -mtzEn43pENQzXoXNSKIuApats8IAK/uI4/7od4LJq3vsSVHvc59TPiSJAoGAf7av -1wSdT88ynhpzUouiQYs6RXWyvGmD0nz0TbDcliqsYQuUlXWLQO+d8NrEkh/X+E7s -vT2HnfuXcLYaqvLlfssmu1DRxAJh5VMUKsbnpwVrvz+FKN0n9sbjEY7H5B8pkFPp -1DOkNNeRu9r2zzrDRPhrXqfoujBIuLw1dVsfxpECgYEArOW3p14H4BIisYfqYzR4 -vEXU5w1MH6sTgAnGhTI7WgCYK/1R4rcjPA+iGGaj+QQ5IJUbpYT7r6v2aiNyge7d -muhtUqXn1Hsox3O6cxerFlxd5OqO+p1O1Xuta4TP9Moh7ujRPHX1dUa41+3LHaku -6ylIABtn5gBAm3OYvOwaSyw= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCdoGE3jAu+bNUW +D3UdkfY+ZpxlWlvcjnWqiIZBLuLyiIsDK+oIiMlWgIYJZ8GTyzpY0A+IT2mq8rJR +dN+m9kGVb23iHBAiZEPXlKvnn/JZaco7tLAeFjwFN6hBWVIGt8+cowiMHK52L8bt +9H2dpeh5BEQA/aGXyH+CHtQECbqQ1MXHEP/UHJqX+JYHChipBBhVS5hOF0jQLXup +ift22G2OMZFt7Gi5VHA3jjqpH10LvUDbtWA1SaUVv8TKCZ8QU+ElE1/19JUAAGQ0 +c+Ee2Dm1RGNxmL2V1kyvyjrrjeFjWRbXHJLdUUshw/rYKKeDR/T8KOHmODiXm4Rw +38BSlvD5AgMBAAECggEABwUuaexqq3uqY+CisoNJMySby5HM1z5gXuIqDzt42nIa +tYnzqEH7VvHDcSYgriXrfoAfE6RBzF4hyKn1ZLjBWVf3Tg46PmXhIE1b+KVxhD+c +xQWk9boUyJYJgDDrtnviRb4nHSJmjMH6UKfGc8qArOnJOOgS6zEqqKTEhDzbWnmQ +Q5GjZGKjTMfbC6u9r6nwcAlPax2tzRmPNtQFYMSjcHmvN0IuhwVlZlULsdPnpDpS +3LeR8b/75Og8BbqXJNdLAXQq3Athu9ADuxfS/rOBxWwEKElV80gFMo/qiWZ/DVqw +UUExYuDAx8Jnf4j8Vb2UcY2bpxvGHLrD10gRJmUxLwKBgQDLzd95e0SXxuWEHBdA +oHfermVwUzs1aa5zqs/KJEICmFE94bJbWQKbW1TDQggIr45XylQNsQuh0RS1USsd +YweA7z00HPcj5ON7O+5tKwfP+C+jl1s1yfYPvaSiRKwLde6DdVGos29C5OpoIQ+E +hjC02n2d84F/lrjfHn3MQH/2vwKBgQDF/u6jNwfHsfgOnKGJv9BKfmLg4saYUvMv +bclkQS0qrSgrloMSZs1QcpZJWneiO0vjsxoTeLcUIcsuhfxpMsN60nb/QncB749C +ts9SuV9Rdv4+7U1tqg2ZQ52zjMliZvEDriPZ40vhONBXYyKtx1HZ9OexDOLKUzzY +c1MMobj+RwKBgBFWPwdvhANBSS720MePnwLTZQ+sFOJTTiLKyghRE0hzOp4AABMj +PESI/WnqyRIsFPjE3meXwvyN86wE7pz+WpoOP++Z8zAbfXpzO7IPsgdv/mV1L64g +swzdvg6LtvL2okaOiVbHhNR08rfO8Cn+3E/WMk9ocoCvCqT4TA0/A2OzAoGBAILO +TpgzxgcPM6NrpWkc+R4N64NJLwz5WEJQVMnQKWfVaAGL+WIR2ri4S0OA6iKa7CMt +cx/EE6fQP6ynxj8102F0ZDt1jKwRuWLI5aVwZGGsrIGkQxAdVciYnDo/29gPzFCz +HmpXuQy9fR8Olp2aXiARpXQZ4EbswPj7D7X7rf0HAoGAQhhg0xfIuOrLRXrhfFEg +NzASVKvtCEMDbJD4mWCOuqe7Jlvi/qmXZ9YnuKOmGukGV0/ePtLI1+Hcstr4kexb +lUJYZQlB9e0Ft87UD16TuGHAMBKdtXQKuBjMeI0C43kSBq0XuiyY9b44ADH/up07 +vkqwfAOUVKCHp4KOOruW9cc= -----END PRIVATE KEY----- diff --git a/spec/ssl/pkcs8-server-key.pem b/spec/ssl/pkcs8-server-key.pem index c803fba65..95eeb77c6 100644 --- a/spec/ssl/pkcs8-server-key.pem +++ b/spec/ssl/pkcs8-server-key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCtyxWYD01zMQby -RwzqzrGypf/x/rhNUT70HChE9TpojPmCe9e38eSvz1+0kGWb4VGKB705mPsY8yry -IO/T2StZI11Ddh7qGbiXJFFeU3s4lrcis3qZTZNvui8hPVmGRjn6TRe6FkX0B/lF -Ip0TV3X2aSAIU7oZpn6qmn4WZFFKgy/EVuDzeaf/RvTrfhnOhbBwhX+93WXt50fK -73YFBEUJ4hOO0VoB34O/555OHY4/FRjTWTusL2LgxcTcE/GRGk9BPBN4cfhmlJHB -hTSiXkGOyLGv8kbrYffockDhkiPJbMSdWIRV+ZpQX3YXf2y3lGOGbWptgMi3ttzh -6RaKmoZJAgMBAAECggEAFhAyLZvDuVwABcH/Yc/bv1JTq+UqgKZP1627bwWy5JMB -Gg+e0ztiTO+GtuWeAKwaLevNmgJR3lkAmryTtdFcL3TN4kKcqhuZ05ZIvjDa89Qu -a7ldVxkCHq0ETrP7KZDAy4X9/SHWv6RDgQNj7ZCs6RtvdZ8rgRYh/oaeezlBGLRZ -OIeWWHTYk8CMhu+tzR+BHZdXUEHo/sZHwmpdhQQ0HtXs87Eo51waVwHL0BgXT+ak -v7pbhe6sGgNgoix59Lu9WbazxXIyRYDMkZfg0fCauRMG05Wvaeuonk/zlC88Eg3M -yQIW8+Boe7M2yI+egRYeM2TCBnr2B7n8fTG/xQEKAQKBgQDe+hgYWboh6ejCarAB -UOd90D//aLtbyu0HBQ6YNiPPUvVmEMfDVscxw6BXTtRWpHYSxsPV+IpUM73St/cu -RmrP1DOTT4sJJf9lvNg35+OceGHRe0hVUU9mVqBMbLz5NOPwxWlq4eSgK+cAKq3g -5lp47IFxX0R+g+dvk7YhSy4+rwKBgQDHiEBLjZahY100PffLygIwvwbTyoK9UMjS -On1sGqOf9aF79zQIIRhEmPN/je9jqnAcf8+ivS1dLYoUboosm7215349Uw6S9C43 -RIn1iLZRGO7Beq3KOrdp4NFnh3QsgoH4jp1gp4w6QyBC2uYBdQXA9GpUR2+Y45BI -KVSHQV4IhwKBgQDQLig48+04pK9QdVOGpwa7DKfzytDCzx+mIi6SJlogw4+ij6Ay -3N51s/QMD+loS3yB41oMeFSOcRCVoHUDm3M2PyU4MFfbXsKpNjuZVsPH3w1VDAlo -vtWm8tIPCKcW9S6sKWRXCjju4o52NWLKS8fEhuwD8bJ9fKGkJwEw7IRsuQKBgQCU -D4feSI+I7InB9WXGI/1iHK49RJ2lS6fpUBu3t0DJtuSAb5x9l8lBRdoSQclsxJFy -pGj4Erbx2JQIu0nu9hZdQA1OBi7fXzBYNJTGzQ60uPKaQaVqVg26FGhvEXVkfedi -ALnJeiq1JRBwa6yXUjXVy8iHB4dJBTwQQBMIVronSwKBgQDOPYklqdzRv+u4XukW -WsvK7GLj9huwdH0NI5gouGMb5OTgfVo66+urq4qYcHRN4vcdxbP9KoJPBvKhdx+D -A36I2ERs//92gIz6lcNsqmdAzHAX1yzp4IyFGsjzaKibnQrft7hb9Kfg9VypBb/G -31x7mm+gZm4RP3jbcBPDn0xU/w== +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrvY7yq+BT9ZSP +9yOdAVfgx12f3lwcO/m6F+R/Z+0bVSvVtbYiO1xn+3l8rz8wT6+fBVVrIxpupFUP +Zalb0BP07jmqhC7YgBjgad0vkUph1peCJ+ntJYtixpAZOZj70aT/2CKjuGkmWFnL +ryrxBmpplHea1BwLJtrWgQEJcaFqiJ1lbGjinudo8dY5tZ7iyyZ+dk8+TKDsV/1p +yuAOemKYDY34jUf1LE525pSR/BcDGYCdzpBsOLsqRKf+5d8cZK05I6oYYKkAwoM3 +QwmwpLSm9poFUlo83uMDU5FH76hWFwkd5PE8oSbdKcp6d0AMh/L0TS6V9lGM58jW +9g71/v2LAgMBAAECggEAOnxjBox26FDNR5vb4neXJELwxOVWS/02xeOmGqdbTYAb +Xfu0a4L4rKas0EPkCoFQpyCLXuGE+mH3X7d4zf4WFcbdF49NXsh88EvNGgpqINiS +Hy6VkP/EsJ47a4O8cCGMhd5mqYe/M2JKLj3Yq11KdusrMiyC4l9Yjk0/e6ZZWKxe +/htw3fMPnHOMfoUB9jPy+SrhbFt42bZ7+JA2Aihf8RCUb/R7OjhASKeRLPkefJTA +Z6kJUoXoCBogjdkLCuVw1zjXF92R5gy+i5o9VhELHpg1D2If7CmeEM2FfVDfWjlF +iYlIR750OsKaeWB0LVKwh+07oyIlmO1TOECXEKt+gQKBgQDpdUfImWMrrkJ5AeCL +0NmO0JIZciGFBDTHRLOdzQiWKdq87i6/LWStK/gT+eb8WVLJ8vk5KrXFIy7TpYce +4jRr9u9MG14hjVemLMMoLhPkruPoulIp+Aj0mnhKephpJQ+Khd77g/GT6ZAqxkxi +drhTfKlSou0oEEd34ZuK7d6mCwKBgQC8UrbCErc+r4Ff48/0BfbJWZ7sN/HP/QtI +R2V9v3VpGqMX93v+YhQepxpd4PSkxluEmbbwYkA81un1ot7ndNkLIN0x59P9tVR0 +ghXuLmLwxExM5ekrfPt7gbkhwUCwRTogjocm6VoF541tn+ll724XtBdewhyXEUm7 +IG0/tLU2gQKBgQCx3avaNprq7bIxVW/Jtk361AdroZvOJx068KnUQSEYnzzLEsDE +4QXCNiyks5H7kuZTfG3K0zJ3xs1nbMacjgUYeKNqnbNC5tfvgE0TsL9xTJnRdxsg +ZJwWGBYr0GmMOjMz+7iecbE9WwZ+wGPz5LWcze6HSiBblMOOn3GNEJvAbwKBgHXS +3ksgAIv0rGH9Gz9Wd+fT7Y1nFyCE9gkbulDpd6DxrHazPV2TqXjgHav8sbNh8yJM +NdvB7OTjpW8snn97aMwAnMO7grOqPpPCS8xAM2Dlv8Mg2Th/Mqw8JkMLMNjYBx0V +b1OWDd/B1odu1E0VdvDXmQONOOv/Qf0UtaV0/yeBAoGAaUO9xrblsqEmP+2lqsiv +qwNLk5PICaoXwIacFdhc8YhC5OoXxRmLlyJmz72aKLTnNH+ZYsNYcJlhexhxpxq5 +vsxdk7rO741EDuFAk1Hsx1Q2G2+q6VWIWQhsn5eTGdas2qdNrKoNwN3wuALPPeeQ +l7yIJxi7Qn24xh+Bjdp1CDU= -----END PRIVATE KEY----- diff --git a/spec/ssl/server-cert.pem b/spec/ssl/server-cert.pem index 9ec690dd3..746bd5446 100644 --- a/spec/ssl/server-cert.pem +++ b/spec/ssl/server-cert.pem @@ -1,17 +1,17 @@ -----BEGIN CERTIFICATE----- MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy -Z2VtMB4XDTE1MDkwOTA0NTcyMVoXDTI1MDcxODA0NTcyMVowIDEeMBwGA1UEAwwV +Z2VtMB4XDTI0MDIwOTE1NDkyOFoXDTMzMTIxODE1NDkyOFowIDEeMBwGA1UEAwwV bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEArcsVmA9NczEG8kcM6s6xsqX/8f64TVE+9BwoRPU6aIz5gnvXt/Hkr89f -tJBlm+FRige9OZj7GPMq8iDv09krWSNdQ3Ye6hm4lyRRXlN7OJa3IrN6mU2Tb7ov -IT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkgCFO6GaZ+qpp+FmRRSoMvxFbg83mn/0b0 -634ZzoWwcIV/vd1l7edHyu92BQRFCeITjtFaAd+Dv+eeTh2OPxUY01k7rC9i4MXE -3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsixr/JG62H36HJA4ZIjyWzEnViEVfmaUF92 -F39st5Rjhm1qbYDIt7bc4ekWipqGSQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBB -PtRuVmOHiRPH3PmsZPtkgVagznojO+r0GDTxys5bxof8+HcokL6gbb4bRqzUQTdC -98RTsuFPnd/I8FbbaL+UyeXeKjjOEBPFyllOdykmpd67mHCA89574y7Ib7lkvtr1 -nQFMbeKmcz4uLm1vAi/aOdAIA2de4yJU2XnOkVLDgYnQxpWR451WKqt/FtiuCzpQ -E3peEemM2XVxvCMmfBAaroAyLYFrWOhNA7UoWVsubp7Ypf7SYuOh+sU6JLsYSadQ -XVqgvIKG4deUpdl7oUBRz79spAi1OpHWiNmW3b+8nKJoHTfYkKzCebeLdI++xSjX -jXNryv5xK88LFIPKL/7e +CgKCAQEAq72O8qvgU/WUj/cjnQFX4Mddn95cHDv5uhfkf2ftG1Ur1bW2IjtcZ/t5 +fK8/ME+vnwVVayMabqRVD2WpW9AT9O45qoQu2IAY4GndL5FKYdaXgifp7SWLYsaQ +GTmY+9Gk/9gio7hpJlhZy68q8QZqaZR3mtQcCyba1oEBCXGhaoidZWxo4p7naPHW +ObWe4ssmfnZPPkyg7Ff9acrgDnpimA2N+I1H9SxOduaUkfwXAxmAnc6QbDi7KkSn +/uXfHGStOSOqGGCpAMKDN0MJsKS0pvaaBVJaPN7jA1ORR++oVhcJHeTxPKEm3SnK +endADIfy9E0ulfZRjOfI1vYO9f79iwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBc +sRGEk10OWCm8MlfyWLmj3dAokO/LC1Ya6wP9gCtepvkum4hISKFmJpLYokUXpyOa +GnUJ96eyHVg5OKz2r1rEra2E6oiP6FW6WCe8tVQEfsV6B7LkJ0O2X5us1kY+gmo6 +ch2/BDWROhjV5LgSPkuCNfNS2mkKo0vEg3xovYBNlqBveyrRnkPcR1qANt3RV3JR +ADfqPGNU6IFoKiFZhK5wjwjUl2p1a12aw6C3e/O2UeJDsSEucN6yjEa/KZhlBfpH +4RSSRpSuWeGu2ndSaVwFYX44//v8vnW7+pFDWB0LFbv+Jd9qji0chaFWh3jJKNas +vALqzCG44enapVb/7m4i -----END CERTIFICATE----- diff --git a/spec/ssl/server-key.pem b/spec/ssl/server-key.pem index 8bc45372b..95eeb77c6 100644 --- a/spec/ssl/server-key.pem +++ b/spec/ssl/server-key.pem @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEArcsVmA9NczEG8kcM6s6xsqX/8f64TVE+9BwoRPU6aIz5gnvX -t/Hkr89ftJBlm+FRige9OZj7GPMq8iDv09krWSNdQ3Ye6hm4lyRRXlN7OJa3IrN6 -mU2Tb7ovIT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkgCFO6GaZ+qpp+FmRRSoMvxFbg -83mn/0b0634ZzoWwcIV/vd1l7edHyu92BQRFCeITjtFaAd+Dv+eeTh2OPxUY01k7 -rC9i4MXE3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsixr/JG62H36HJA4ZIjyWzEnViE -VfmaUF92F39st5Rjhm1qbYDIt7bc4ekWipqGSQIDAQABAoIBABYQMi2bw7lcAAXB -/2HP279SU6vlKoCmT9etu28FsuSTARoPntM7YkzvhrblngCsGi3rzZoCUd5ZAJq8 -k7XRXC90zeJCnKobmdOWSL4w2vPULmu5XVcZAh6tBE6z+ymQwMuF/f0h1r+kQ4ED -Y+2QrOkbb3WfK4EWIf6Gnns5QRi0WTiHllh02JPAjIbvrc0fgR2XV1BB6P7GR8Jq -XYUENB7V7POxKOdcGlcBy9AYF0/mpL+6W4XurBoDYKIsefS7vVm2s8VyMkWAzJGX -4NHwmrkTBtOVr2nrqJ5P85QvPBINzMkCFvPgaHuzNsiPnoEWHjNkwgZ69ge5/H0x -v8UBCgECgYEA3voYGFm6IenowmqwAVDnfdA//2i7W8rtBwUOmDYjz1L1ZhDHw1bH -McOgV07UVqR2EsbD1fiKVDO90rf3LkZqz9Qzk0+LCSX/ZbzYN+fjnHhh0XtIVVFP -ZlagTGy8+TTj8MVpauHkoCvnACqt4OZaeOyBcV9EfoPnb5O2IUsuPq8CgYEAx4hA -S42WoWNdND33y8oCML8G08qCvVDI0jp9bBqjn/Whe/c0CCEYRJjzf43vY6pwHH/P -or0tXS2KFG6KLJu9ted+PVMOkvQuN0SJ9Yi2URjuwXqtyjq3aeDRZ4d0LIKB+I6d -YKeMOkMgQtrmAXUFwPRqVEdvmOOQSClUh0FeCIcCgYEA0C4oOPPtOKSvUHVThqcG -uwyn88rQws8fpiIukiZaIMOPoo+gMtzedbP0DA/paEt8geNaDHhUjnEQlaB1A5tz -Nj8lODBX217CqTY7mVbDx98NVQwJaL7VpvLSDwinFvUurClkVwo47uKOdjViykvH -xIbsA/GyfXyhpCcBMOyEbLkCgYEAlA+H3kiPiOyJwfVlxiP9YhyuPUSdpUun6VAb -t7dAybbkgG+cfZfJQUXaEkHJbMSRcqRo+BK28diUCLtJ7vYWXUANTgYu318wWDSU -xs0OtLjymkGlalYNuhRobxF1ZH3nYgC5yXoqtSUQcGusl1I11cvIhweHSQU8EEAT -CFa6J0sCgYEAzj2JJanc0b/ruF7pFlrLyuxi4/YbsHR9DSOYKLhjG+Tk4H1aOuvr -q6uKmHB0TeL3HcWz/SqCTwbyoXcfgwN+iNhEbP//doCM+pXDbKpnQMxwF9cs6eCM -hRrI82iom50K37e4W/Sn4PVcqQW/xt9ce5pvoGZuET9423ATw59MVP8= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrvY7yq+BT9ZSP +9yOdAVfgx12f3lwcO/m6F+R/Z+0bVSvVtbYiO1xn+3l8rz8wT6+fBVVrIxpupFUP +Zalb0BP07jmqhC7YgBjgad0vkUph1peCJ+ntJYtixpAZOZj70aT/2CKjuGkmWFnL +ryrxBmpplHea1BwLJtrWgQEJcaFqiJ1lbGjinudo8dY5tZ7iyyZ+dk8+TKDsV/1p +yuAOemKYDY34jUf1LE525pSR/BcDGYCdzpBsOLsqRKf+5d8cZK05I6oYYKkAwoM3 +QwmwpLSm9poFUlo83uMDU5FH76hWFwkd5PE8oSbdKcp6d0AMh/L0TS6V9lGM58jW +9g71/v2LAgMBAAECggEAOnxjBox26FDNR5vb4neXJELwxOVWS/02xeOmGqdbTYAb +Xfu0a4L4rKas0EPkCoFQpyCLXuGE+mH3X7d4zf4WFcbdF49NXsh88EvNGgpqINiS +Hy6VkP/EsJ47a4O8cCGMhd5mqYe/M2JKLj3Yq11KdusrMiyC4l9Yjk0/e6ZZWKxe +/htw3fMPnHOMfoUB9jPy+SrhbFt42bZ7+JA2Aihf8RCUb/R7OjhASKeRLPkefJTA +Z6kJUoXoCBogjdkLCuVw1zjXF92R5gy+i5o9VhELHpg1D2If7CmeEM2FfVDfWjlF +iYlIR750OsKaeWB0LVKwh+07oyIlmO1TOECXEKt+gQKBgQDpdUfImWMrrkJ5AeCL +0NmO0JIZciGFBDTHRLOdzQiWKdq87i6/LWStK/gT+eb8WVLJ8vk5KrXFIy7TpYce +4jRr9u9MG14hjVemLMMoLhPkruPoulIp+Aj0mnhKephpJQ+Khd77g/GT6ZAqxkxi +drhTfKlSou0oEEd34ZuK7d6mCwKBgQC8UrbCErc+r4Ff48/0BfbJWZ7sN/HP/QtI +R2V9v3VpGqMX93v+YhQepxpd4PSkxluEmbbwYkA81un1ot7ndNkLIN0x59P9tVR0 +ghXuLmLwxExM5ekrfPt7gbkhwUCwRTogjocm6VoF541tn+ll724XtBdewhyXEUm7 +IG0/tLU2gQKBgQCx3avaNprq7bIxVW/Jtk361AdroZvOJx068KnUQSEYnzzLEsDE +4QXCNiyks5H7kuZTfG3K0zJ3xs1nbMacjgUYeKNqnbNC5tfvgE0TsL9xTJnRdxsg +ZJwWGBYr0GmMOjMz+7iecbE9WwZ+wGPz5LWcze6HSiBblMOOn3GNEJvAbwKBgHXS +3ksgAIv0rGH9Gz9Wd+fT7Y1nFyCE9gkbulDpd6DxrHazPV2TqXjgHav8sbNh8yJM +NdvB7OTjpW8snn97aMwAnMO7grOqPpPCS8xAM2Dlv8Mg2Th/Mqw8JkMLMNjYBx0V +b1OWDd/B1odu1E0VdvDXmQONOOv/Qf0UtaV0/yeBAoGAaUO9xrblsqEmP+2lqsiv +qwNLk5PICaoXwIacFdhc8YhC5OoXxRmLlyJmz72aKLTnNH+ZYsNYcJlhexhxpxq5 +vsxdk7rO741EDuFAk1Hsx1Q2G2+q6VWIWQhsn5eTGdas2qdNrKoNwN3wuALPPeeQ +l7yIJxi7Qn24xh+Bjdp1CDU= +-----END PRIVATE KEY----- diff --git a/spec/ssl/server-req.pem b/spec/ssl/server-req.pem index 94675507d..1600bfe65 100644 --- a/spec/ssl/server-req.pem +++ b/spec/ssl/server-req.pem @@ -1,15 +1,15 @@ -----BEGIN CERTIFICATE REQUEST----- MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB -IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArcsVmA9NczEG8kcM6s6xsqX/ -8f64TVE+9BwoRPU6aIz5gnvXt/Hkr89ftJBlm+FRige9OZj7GPMq8iDv09krWSNd -Q3Ye6hm4lyRRXlN7OJa3IrN6mU2Tb7ovIT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkg -CFO6GaZ+qpp+FmRRSoMvxFbg83mn/0b0634ZzoWwcIV/vd1l7edHyu92BQRFCeIT -jtFaAd+Dv+eeTh2OPxUY01k7rC9i4MXE3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsix -r/JG62H36HJA4ZIjyWzEnViEVfmaUF92F39st5Rjhm1qbYDIt7bc4ekWipqGSQID -AQABoAAwDQYJKoZIhvcNAQELBQADggEBAInWIFsq14b8PhDroMMvi1ma30xyQGjg -KObIxakwXkliSxmCdVqV4+MV6w8hK3z0q7H+NzRFByjo1PnU8BCIa058m5uvbjmM -wGQvpcxmpm1p8VKKoeTqvE8OelbrqHrmyNIq7E/S3UZelVt+HOIPJOOs/aqEzaEg -VL1u+4kCMbHM2rG8dii060oZ5i/gUtMn2TQWcNjSQBvaVztW5FOL74bYkoq0zIwd -MFl2BoYyAnERJEcBmh1f+D7MuxPaqTUKjUmfDbHCMAAyTS1FHr9AnIN0/C2Mxywl -H/zL9/DkfR53KZjITkf2gTH5D/N5oDUwmgCg6UZ0MeTOP27jvgcvb/k= +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq72O8qvgU/WUj/cjnQFX4Mdd +n95cHDv5uhfkf2ftG1Ur1bW2IjtcZ/t5fK8/ME+vnwVVayMabqRVD2WpW9AT9O45 +qoQu2IAY4GndL5FKYdaXgifp7SWLYsaQGTmY+9Gk/9gio7hpJlhZy68q8QZqaZR3 +mtQcCyba1oEBCXGhaoidZWxo4p7naPHWObWe4ssmfnZPPkyg7Ff9acrgDnpimA2N ++I1H9SxOduaUkfwXAxmAnc6QbDi7KkSn/uXfHGStOSOqGGCpAMKDN0MJsKS0pvaa +BVJaPN7jA1ORR++oVhcJHeTxPKEm3SnKendADIfy9E0ulfZRjOfI1vYO9f79iwID +AQABoAAwDQYJKoZIhvcNAQELBQADggEBAIAzuYloX5Pwi+5n73yhaw5V+jMABiuw +rYI2LziLBqw4vuvjEqvyr80Y9H2fLHOfVRaFnU6PaMkkH/p8d8YBD4/ZRGSd3oHX +hlNltqyTCx1LNIUCXkl18jfPK3sVwwC07cLqSxQP8diauaDE59F6lsP3L0Gbwntd +brSVJcY+5JGuWMx2BDXECxu6E7D/8drvPQa6EXDJjk1WVF/69I3TWhfX4/a5zIrc +bJDTRBl5yQA0dpPmr/d8Di4mqAqeecVPNXi/CWkDQl3PoBp7O69T6VG3R00krgQr +rXzbPJsEDLm7ynu/TWMamSCOUiMH5CBVBVXJSTVevGFK+gdjXf8LJ/0= -----END CERTIFICATE REQUEST----- From 4e18d7969abc379de89cc52618408bb38a2c1857 Mon Sep 17 00:00:00 2001 From: Satoshi MITANI Date: Fri, 6 Sep 2024 09:20:00 +0900 Subject: [PATCH 755/783] Support get_server_public_key option (#1377) * caching_sha2_password requests secure connection if cache is not ready on server-side. get_server_public_key option enables clients to create secure connection automatically even if connection is not SSL. * return error if get_server_public_key option is not supported --- README.md | 1 + ext/mysql2/client.c | 16 ++++++++++++++++ ext/mysql2/extconf.rb | 1 + lib/mysql2/client.rb | 4 ++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cf35222d5..59ae2bc54 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,7 @@ Mysql2::Client.new( :reconnect = true/false, :local_infile = true/false, :secure_auth = true/false, + :get_server_public_key = true/false, :default_file = '/path/to/my.cfg', :default_group = 'my.cfg section', :default_auth = 'authentication_windows_client' diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 5a2fcc1bd..8fe33ea39 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -996,6 +996,13 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { retval = charval; break; +#ifdef HAVE_CONST_MYSQL_OPT_GET_SERVER_PUBLIC_KEY + case MYSQL_OPT_GET_SERVER_PUBLIC_KEY: + boolval = (value == Qfalse ? 0 : 1); + retval = &boolval; + break; +#endif + #ifdef HAVE_MYSQL_DEFAULT_AUTH case MYSQL_DEFAULT_AUTH: charval = (const char *)StringValueCStr(value); @@ -1485,6 +1492,14 @@ static VALUE set_init_command(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_INIT_COMMAND, value); } +static VALUE set_get_server_public_key(VALUE self, VALUE value) { +#ifdef HAVE_CONST_MYSQL_OPT_GET_SERVER_PUBLIC_KEY + return _mysql_client_options(self, MYSQL_OPT_GET_SERVER_PUBLIC_KEY, value); +#else + rb_raise(cMysql2Error, "get-server-public-key is not available, you may need a newer MySQL client library"); +#endif +} + static VALUE set_default_auth(VALUE self, VALUE value) { #ifdef HAVE_MYSQL_DEFAULT_AUTH return _mysql_client_options(self, MYSQL_DEFAULT_AUTH, value); @@ -1596,6 +1611,7 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1); rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1); rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1); + rb_define_private_method(cMysql2Client, "get_server_public_key=", set_get_server_public_key, 1); rb_define_private_method(cMysql2Client, "default_auth=", set_default_auth, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1); diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index a1f6cb240..68e68bcb1 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -159,6 +159,7 @@ def add_ssl_defines(header) have_const('SERVER_QUERY_WAS_SLOW', mysql_h) have_const('MYSQL_OPTION_MULTI_STATEMENTS_ON', mysql_h) have_const('MYSQL_OPTION_MULTI_STATEMENTS_OFF', mysql_h) +have_const('MYSQL_OPT_GET_SERVER_PUBLIC_KEY', mysql_h) # my_bool is replaced by C99 bool in MySQL 8.0, but we want # to retain compatibility with the typedef in earlier MySQLs. diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 582b6e305..d952cec17 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -32,11 +32,11 @@ def initialize(opts = {}) opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) # TODO: stricter validation rather than silent massaging - %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth].each do |key| + %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth get_server_public_key].each do |key| next unless opts.key?(key) case key - when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin + when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin, :get_server_public_key send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation when :connect_timeout, :read_timeout, :write_timeout send(:"#{key}=", Integer(opts[key])) unless opts[key].nil? From 7be15fec746e3099eef98297a2b5fa3e8ac5f8d5 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 5 Sep 2024 17:21:16 -0700 Subject: [PATCH 756/783] Delete appveyor.yml No longer using this CI service. --- appveyor.yml | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index cde312693..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,49 +0,0 @@ ---- -version: "{build}" -clone_depth: 10 -install: - - SET PATH=C:\MinGW\msys\1.0\bin;%PATH% - - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - - ruby --version - - gem --version - - bundler --version - - bundle install --path vendor/bundle - - IF DEFINED MINGW_PACKAGE_PREFIX (ridk exec pacman -S --noconfirm --needed %MINGW_PACKAGE_PREFIX%-libmariadbclient) -build_script: - - bundle exec rake compile -test_script: - - '"C:\Program Files\MySQL\MySQL Server 5.7\bin\mysql" --version' - - > - "C:\Program Files\MySQL\MySQL Server 5.7\bin\mysql" -u root -p"Password12!" -e " - CREATE DATABASE IF NOT EXISTS test; - CREATE USER '%USERNAME%'@'localhost'; - SET PASSWORD = PASSWORD(''); - FLUSH PRIVILEGES; - " - - bundle exec rake spec -on_failure: - - find tmp -name "*.log" | xargs cat -environment: - matrix: - - ruby_version: "26-x64" - MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" - - ruby_version: "25-x64" - MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" - - ruby_version: "24-x64" - MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" - - ruby_version: "24" - MINGW_PACKAGE_PREFIX: "mingw-w64-i686" - - ruby_version: "23-x64" - - ruby_version: "23" - - ruby_version: "22-x64" - - ruby_version: "22" - - ruby_version: "21-x64" - - ruby_version: "21" - - ruby_version: "200-x64" - - ruby_version: "200" -cache: - - vendor -services: - - mysql -hosts: - mysql2gem.example.com: 127.0.0.1 From f6a9b68b42a51d1a370403f11eb88527dcb42dc6 Mon Sep 17 00:00:00 2001 From: Yuki Fukuma Date: Sat, 7 Sep 2024 00:11:51 +0900 Subject: [PATCH 757/783] Fix some typos (#1378) --- README.md | 2 +- ext/mysql2/client.c | 2 +- ext/mysql2/extconf.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 59ae2bc54..77e5c0bc9 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ $ bundle config --local build.mysql2 -- $(ruby -r rbconfig -e 'puts RbConfig::CO ``` Note the additional double dashes (`--`) these separate command-line arguments -that `gem` or `bundler` interpret from the addiitonal arguments that are passed +that `gem` or `bundler` interpret from the additional arguments that are passed to the mysql2 build process. ### Windows diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 8fe33ea39..ad31c280a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -307,7 +307,7 @@ static void *nogvl_connect(void *ptr) { * a socket that may be in use by the parent or other processes after fork. * * /dev/null is used to absorb writes; previously a dummy socket was used, but - * it could not abosrb writes and caused openssl to go into an infinite loop. + * it could not absorb writes and caused openssl to go into an infinite loop. * * Returns Qtrue or Qfalse (success or failure) * diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 68e68bcb1..aaf6b255f 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -28,7 +28,7 @@ def add_ssl_defines(header) end end -### Check for Ruby C extention interfaces +### Check for Ruby C extension interfaces # 2.1+ have_func('rb_absint_size') From 25836619e3b8a09d114aff1bb45a5750fd420082 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 2 Dec 2024 18:36:49 +0100 Subject: [PATCH 758/783] 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. --- ext/mysql2/client.c | 8 +++++--- ext/mysql2/client.h | 1 + spec/mysql2/client_spec.rb | 38 ++++++++++++++++++++++++++++++-------- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index ad31c280a..25a350299 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -411,6 +411,7 @@ static VALUE allocate(VALUE klass) { wrapper->initialized = 0; /* will be set true after calling mysql_init */ wrapper->closed = 1; /* will be set false after calling mysql_real_connect */ wrapper->refcount = 1; + wrapper->affected_rows = -1; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); return obj; @@ -669,6 +670,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) { wrapper->active_fiber = Qnil; rb_raise_mysql2_error(wrapper); } + wrapper->affected_rows = mysql_affected_rows(wrapper->client); is_streaming = rb_hash_aref(rb_ivar_get(self, intern_current_query_options), sym_stream); if (is_streaming == Qtrue) { @@ -1155,12 +1157,12 @@ static VALUE rb_mysql_client_session_track(VALUE self, VALUE type) { * if it was an UPDATE, DELETE, or INSERT. */ static VALUE rb_mysql_client_affected_rows(VALUE self) { - my_ulonglong retVal; + uint64_t retVal; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); - retVal = mysql_affected_rows(wrapper->client); - if (retVal == (my_ulonglong)-1) { + retVal = wrapper->affected_rows; + if (retVal == (uint64_t)-1) { rb_raise_mysql2_error(wrapper); } return ULL2NUM(retVal); diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h index ad6ce8aa9..6a8227bd1 100644 --- a/ext/mysql2/client.h +++ b/ext/mysql2/client.h @@ -12,6 +12,7 @@ typedef struct { int initialized; int refcount; int closed; + uint64_t affected_rows; MYSQL *client; } mysql_client_wrapper; diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index a8f90da24..e9230e601 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1033,7 +1033,7 @@ def run_gc expect(@client).to respond_to(:last_id) end - it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do + it "#last_id should return a Fixnum, from the last INSERT/UPDATE" do expect(@client.last_id).to eql(0) @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" expect(@client.last_id).to eql(1) @@ -1043,13 +1043,6 @@ def run_gc expect(@client).to respond_to(:last_id) end - it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do - @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" - expect(@client.affected_rows).to eql(1) - @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1" - expect(@client.affected_rows).to eql(1) - end - it "#last_id should handle BIGINT auto-increment ids above 32 bits" do # The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x. # Insert a row with a given ID, this should raise the auto-increment state @@ -1058,6 +1051,35 @@ def run_gc @client.query "INSERT INTO lastIdTest (blah) VALUES (5001)" expect(@client.last_id).to eql(5000000001) end + + it "#last_id isn't cleared by Statement#close" do + stmt = @client.prepare("INSERT INTO lastIdTest (blah) VALUES (1234)") + + @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" + expect(@client.last_id).to eql(1) + + stmt.close + + expect(@client.last_id).to eql(1) + end + + it "#affected_rows should return a Fixnum, from the last INSERT/UPDATE" do + @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" + expect(@client.affected_rows).to eql(1) + @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1" + expect(@client.affected_rows).to eql(1) + end + + it "#affected_rows isn't cleared by Statement#close" do + stmt = @client.prepare("INSERT INTO lastIdTest (blah) VALUES (1234)") + + @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" + expect(@client.affected_rows).to eql(1) + + stmt.close + + expect(@client.affected_rows).to eql(1) + end end it "should respond to #thread_id" do From f0c5d11d1d66cdbb3ec1eb316c03c2a1e554ce3e Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 3 Dec 2024 09:55:56 +0100 Subject: [PATCH 759/783] extconf.rb: locate zstd when using homebrew (#1384) Fix compilation issues on my system. --- ext/mysql2/extconf.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index aaf6b255f..147b998b9 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -63,7 +63,12 @@ def add_ssl_defines(header) # Homebrew OpenSSL on MacOS elsif RUBY_PLATFORM =~ /darwin/ && system('command -v brew') openssl_location = `brew --prefix openssl`.strip - $LDFLAGS << " -L#{openssl_location}/lib" if openssl_location + $LIBPATH << "#{openssl_location}/lib" unless openssl_location.empty? +end + +if RUBY_PLATFORM =~ /darwin/ && system('command -v brew') + zstd_location = `brew --prefix zstd`.strip + $LIBPATH << "#{zstd_location}/lib" unless zstd_location.empty? end ### Find MySQL client library From 03ac203ff077591171e54f65190071a3ef84525a Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 3 Dec 2024 04:00:44 -0500 Subject: [PATCH 760/783] Redact password from query_options (#1334) Redact password from query_options to avoid leaking credentials in exceptions via #inspect. Minor refactor to make RuboCop happy about client.rb complexity. Closes #1049 --- .rubocop_todo.yml | 2 +- lib/mysql2/client.rb | 22 ++++++++++++++++------ spec/mysql2/client_spec.rb | 18 ++++++++++++++++++ spec/mysql2/statement_spec.rb | 2 +- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index eba2091dc..02cfbae17 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -45,7 +45,7 @@ Metrics/BlockNesting: # Offense count: 1 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 135 + Max: 141 # Offense count: 3 # Configuration parameters: IgnoredMethods. diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index d952cec17..9811835e9 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -71,12 +71,7 @@ def initialize(opts = {}) # SSL verify is a connection flag rather than a mysql_ssl_set option flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] - if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) } - warn "============= WARNING FROM mysql2 =============" - warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future." - warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options." - warn "============= END WARNING FROM mysql2 =========" - end + check_and_clean_query_options user = opts[:username] || opts[:user] pass = opts[:password] || opts[:pass] @@ -165,6 +160,21 @@ def info self.class.info end + private + + def check_and_clean_query_options + if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) } + warn "============= WARNING FROM mysql2 =============" + warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future." + warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options." + warn "============= END WARNING FROM mysql2 =========" + end + + # avoid logging sensitive data via #inspect + @query_options.delete(:password) + @query_options.delete(:pass) + end + class << self private diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index e9230e601..865d6d119 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1195,4 +1195,22 @@ def run_gc it "should respond to #encoding" do expect(@client).to respond_to(:encoding) end + + it "should not include the password in the output of #inspect" do + client_class = Class.new(Mysql2::Client) do + def connect(*args); end + end + + client = client_class.new(password: "secretsecret") + + expect(client.inspect).not_to include("password") + expect(client.inspect).not_to include("secretsecret") + + expect do + client = client_class.new(pass: "secretsecret") + end.to output(/WARNING/).to_stderr + + expect(client.inspect).not_to include("pass") + expect(client.inspect).not_to include("secretsecret") + end end diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index 57e590804..c3ebf9bb3 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -320,7 +320,7 @@ def stmt_count result = @client.prepare("SELECT 1 UNION SELECT 2").execute(stream: true, cache_rows: false) expect do result.each {} - result.each {} # rubocop:disable Style/CombinableLoops + result.each {} end.to raise_exception(Mysql2::Error) end end From 15f8e6e291ee1f5274285eafeca77db1d777ab66 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 4 Dec 2024 22:38:36 +0100 Subject: [PATCH 761/783] Improve prepared statement ergonomics (#1385) While working on Mysql2 prepared statement support in Rails, I found them very hard to use. Most notably we sometimes need to close them eagerly, but it's complicated by the fact that you can only call `close` once, any subsequent call raises an error. There was also no way to check if a statement was closed or not. This change noops on repeat calls to `close` and adds `closed?` --- Gemfile | 3 ++- ext/mysql2/statement.c | 27 +++++++++++++++++++++++---- spec/mysql2/statement_spec.rb | 16 +++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 7b4e1b8a7..f7a0900a0 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,8 @@ group :test do gem 'rspec', '~> 3.2' # https://github.com/bbatsov/rubocop/pull/4789 - gem 'rubocop', '~> 1.30', '>= 1.30.1' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') + # 1.51 is the last version supporting Ruby 2.6 + gem 'rubocop', '>= 1.30.1', '< 1.51' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') end group :benchmarks, optional: true do diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index 5506526ee..fa3b660cd 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -10,9 +10,12 @@ static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_ho #define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval) #endif -#define GET_STATEMENT(self) \ +#define RAW_GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ TypedData_Get_Struct(self, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper); \ + +#define GET_STATEMENT(self) \ + RAW_GET_STATEMENT(self) \ if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \ if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); } @@ -603,12 +606,27 @@ static VALUE rb_mysql_stmt_affected_rows(VALUE self) { * own prepared statement cache. */ static VALUE rb_mysql_stmt_close(VALUE self) { - GET_STATEMENT(self); - stmt_wrapper->closed = 1; - rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0); + RAW_GET_STATEMENT(self); + + if (!stmt_wrapper->closed) { + stmt_wrapper->closed = 1; + rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0); + } + return Qnil; } +/* call-seq: + * stmt.closed? + * + * Returns wheter or not the statement have been closed. + */ +static VALUE rb_mysql_stmt_closed_p(VALUE self) { + RAW_GET_STATEMENT(self); + + return stmt_wrapper->closed ? Qtrue : Qfalse; +} + void init_mysql2_statement() { cDate = rb_const_get(rb_cObject, rb_intern("Date")); rb_global_variable(&cDate); @@ -630,6 +648,7 @@ void init_mysql2_statement() { rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0); rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0); rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0); + rb_define_method(cMysql2Statement, "closed?", rb_mysql_stmt_closed_p, 0); sym_stream = ID2SYM(rb_intern("stream")); diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index c3ebf9bb3..8c4d97e68 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -1,6 +1,6 @@ require './spec/spec_helper' -RSpec.describe Mysql2::Statement do +RSpec.describe Mysql2::Statement do # rubocop:disable Metrics/BlockLength before(:example) do @client = new_client(encoding: "utf8") end @@ -319,8 +319,8 @@ def stmt_count it "should throw an exception if we try to iterate twice when streaming is enabled" do result = @client.prepare("SELECT 1 UNION SELECT 2").execute(stream: true, cache_rows: false) expect do - result.each {} - result.each {} + result.to_a + result.to_a end.to raise_exception(Mysql2::Error) end end @@ -720,5 +720,15 @@ def stmt_count stmt.close expect { stmt.execute }.to raise_error(Mysql2::Error, /Invalid statement handle/) end + + it 'should not raise if called multiple times' do + stmt = @client.prepare 'SELECT 1' + expect(stmt).to_not be_closed + + 3.times do + expect { stmt.close }.to_not raise_error + expect(stmt).to be_closed + end + end end end From d7d2eee40d0b5c08bab117b1dfc5d97309a66302 Mon Sep 17 00:00:00 2001 From: David Siaw <874280+davidsiaw@users.noreply.github.com> Date: Thu, 5 Dec 2024 06:43:01 +0900 Subject: [PATCH 762/783] CI: fixes and improvements (#1371) * Add bigdecimal dependency, no longer installed by default in Ruby 3.4 * CI: remove Ubuntu 18.04 image because it doesn't exist anymore * CI: brew install zstd on MacOS Co-authored-by: Aaron Stone --- .github/workflows/build.yml | 2 +- README.md | 2 +- ci/setup.sh | 2 +- mysql2.gemspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34a04ca4d..79f7d10fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.6} - {os: ubuntu-20.04, ruby: '2.7', db: mariadb10.6} - {os: ubuntu-20.04, ruby: '2.7', db: mysql80} - - {os: ubuntu-18.04, ruby: '2.7', db: mysql57} + - {os: ubuntu-20.04, ruby: '2.7', db: mysql57} # TODO - Windows CI # - {os: windows-2022, ruby: '3.2', db: mysql80} diff --git a/README.md b/README.md index 77e5c0bc9..77fd29ecb 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Ruby runtime and MySQL client libraries are compiled with the same OpenSSL family, 1.0 or 1.1 or 3.0, since only one can be loaded at runtime. ``` sh -$ brew install openssl@1.1 +$ brew install openssl@1.1 zstd $ gem install mysql2 -- --with-openssl-dir=$(brew --prefix openssl@1.1) or diff --git a/ci/setup.sh b/ci/setup.sh index 956608c69..938f0c8de 100644 --- a/ci/setup.sh +++ b/ci/setup.sh @@ -70,7 +70,7 @@ if [[ x$OSTYPE =~ ^xdarwin ]]; then done brew info "$DB" - brew install "$DB" + brew install "$DB" zstd DB_PREFIX="$(brew --prefix "${DB}")" export PATH="${DB_PREFIX}/bin:${PATH}" export LDFLAGS="-L${DB_PREFIX}/lib" diff --git a/mysql2.gemspec b/mysql2.gemspec index 93e790ae0..28fd1c301 100644 --- a/mysql2.gemspec +++ b/mysql2.gemspec @@ -23,5 +23,5 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s| s.metadata['msys2_mingw_dependencies'] = 'libmariadbclient' - s.add_runtime_dependency 'bigdecimal' + s.add_dependency 'bigdecimal' end From 58f8d009a0d107776dd6d24c9906426fe0f7b856 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Thu, 5 Dec 2024 06:45:21 +0900 Subject: [PATCH 763/783] Set charset name utf8mb4 by default (#1157) mysql2 gem dropped testing for MySQL 5.1 which was released in Dec 2008 and already EOL in Dec 2013 at efa47a935447bb96f178c11ce32834f877c36b77. If we no longer support MySQL 5.1, utf8 (utf8mb3) is not appropriate default encoding for MySQL 5.5 or higher, utf8mb4 should be set for that. FYI, Rails 6.0 no longer support MySQL 5.1 and set utf8mb4 by default. https://github.com/rails/rails/pull/33608 https://github.com/rails/rails/pull/33853 --- README.md | 4 ++-- lib/mysql2/client.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 77fd29ecb..564dbb75c 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ Mysql2::Client.new( :database, :socket = '/path/to/mysql.sock', :flags = REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | MULTI_STATEMENTS, - :encoding = 'utf8', + :encoding = 'utf8mb4', :read_timeout = seconds, :write_timeout = seconds, :connect_timeout = seconds, @@ -367,7 +367,7 @@ Use the value `mysql2` as the adapter name. For example: ``` yaml development: adapter: mysql2 - encoding: utf8 + encoding: utf8mb4 database: my_db_name username: root password: my_password diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index 9811835e9..2bb81a87f 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -45,8 +45,8 @@ def initialize(opts = {}) end end - # force the encoding to utf8 - self.charset_name = opts[:encoding] || 'utf8' + # force the encoding to utf8mb4 + self.charset_name = opts[:encoding] || 'utf8mb4' mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode] if (mode == SSL_MODE_VERIFY_CA || mode == SSL_MODE_VERIFY_IDENTITY) && !opts[:sslca] From e7f7f1acfcdda0f9db5085690b92dd345d07261b Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 26 Dec 2024 17:42:18 -0800 Subject: [PATCH 764/783] CI: Add Ruby 3.4, MySQL 8.4, MariaDB 11.4, Ubuntu 24.04 --- .github/workflows/build.yml | 7 +++- ci/mariadb114.sh | 10 ++++++ ci/mysql57.sh | 2 +- ci/mysql80.sh | 2 +- ci/mysql84.sh | 13 +++++++ ci/setup.sh | 12 +++++++ support/B7B3B788A8D3785C.asc | 66 ++++++++++++++++++++++++++++++++++++ 7 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 ci/mariadb114.sh create mode 100644 ci/mysql84.sh create mode 100644 support/B7B3B788A8D3785C.asc diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79f7d10fe..2b0cca4a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,11 @@ jobs: matrix: include: # Ruby 3.x on Ubuntu 22.04 LTS (latest at this time) - - {os: ubuntu-22.04, ruby: 'head', db: mysql80} + - {os: ubuntu-24.04, ruby: 'head', db: mysql84} + - {os: ubuntu-24.04, ruby: '3.4', db: mysql84} + + # Ruby 3.x on Ubuntu 22.04 LTS (latest at this time) + - {os: ubuntu-22.04, ruby: '3.4', db: mysql80} - {os: ubuntu-22.04, ruby: '3.3', db: mysql80} - {os: ubuntu-22.04, ruby: '3.2', db: mysql80} - {os: ubuntu-22.04, ruby: '3.1', db: mysql80} @@ -29,6 +33,7 @@ jobs: # db: on Linux, ci/setup.sh installs the specified packages # db: on MacOS, installs a Homebrew package use "name@X.Y" to specify a version + - {os: ubuntu-24.04, ruby: '3.4', db: mariadb11.4} - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.11} - {os: ubuntu-22.04, ruby: '2.7', db: mariadb10.11} - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.6} diff --git a/ci/mariadb114.sh b/ci/mariadb114.sh new file mode 100644 index 000000000..6225972a2 --- /dev/null +++ b/ci/mariadb114.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eux + +apt purge -qq '^mysql*' '^libmysql*' +rm -fr /etc/mysql +rm -fr /var/lib/mysql + +apt-key add support/C74CD1D8.asc +add-apt-repository "deb https://deb.mariadb.org/11.4/ubuntu $(lsb_release -cs) main" +apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server libmariadb-dev diff --git a/ci/mysql57.sh b/ci/mysql57.sh index 8c8928315..9cd681ff3 100644 --- a/ci/mysql57.sh +++ b/ci/mysql57.sh @@ -7,7 +7,7 @@ rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/5072E1F5.asc # old signing key apt-key add support/3A79BD29.asc # 5.7.37 and higher -apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B7B3B788A8D3785C +apt-key add support/B7B3B788A8D3785C.asc # 8.1 and higher # Verify the repository as add-apt-repository does not. wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-5.7 add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-5.7' diff --git a/ci/mysql80.sh b/ci/mysql80.sh index ae7e88313..814412c69 100644 --- a/ci/mysql80.sh +++ b/ci/mysql80.sh @@ -7,7 +7,7 @@ rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/5072E1F5.asc # old signing key apt-key add support/3A79BD29.asc # 8.0.28 and higher -apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B7B3B788A8D3785C +apt-key add support/B7B3B788A8D3785C.asc # 8.1 and higher # Verify the repository as add-apt-repository does not. wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-8.0 add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-8.0' diff --git a/ci/mysql84.sh b/ci/mysql84.sh new file mode 100644 index 000000000..5c78da558 --- /dev/null +++ b/ci/mysql84.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -eux + +apt-get purge -qq '^mysql*' '^libmysql*' +rm -fr /etc/mysql +rm -fr /var/lib/mysql +apt-key add support/B7B3B788A8D3785C.asc # 8.1 and higher +# Verify the repository as add-apt-repository does not. +wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-8.4-lts +add-apt-repository 'http://repo.mysql.com/apt/ubuntu%20mysql-8.4-lts' +apt-get update -qq +apt-get install -qq mysql-server libmysqlclient-dev diff --git a/ci/setup.sh b/ci/setup.sh index 938f0c8de..b54dedba0 100644 --- a/ci/setup.sh +++ b/ci/setup.sh @@ -48,6 +48,12 @@ if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then CHANGED_PASSWORD=true fi +# Install MySQL 8.4 if DB=mysql84 +if [[ -n ${DB-} && x$DB =~ ^xmysql84 ]]; then + sudo bash ci/mysql84.sh + CHANGED_PASSWORD=true +fi + # Install MariaDB 10.6 if DB=mariadb10.6 if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.6 ]]; then sudo bash ci/mariadb106.sh @@ -60,6 +66,12 @@ if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.11 ]]; then CHANGED_PASSWORD_BY_RECREATE=true fi +# Install MariaDB 11.4 if DB=mariadb11.4 +if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb11.4 ]]; then + sudo bash ci/mariadb114.sh + CHANGED_PASSWORD_BY_RECREATE=true +fi + # Install MySQL/MariaDB if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update > /dev/null diff --git a/support/B7B3B788A8D3785C.asc b/support/B7B3B788A8D3785C.asc new file mode 100644 index 000000000..9c9f79203 --- /dev/null +++ b/support/B7B3B788A8D3785C.asc @@ -0,0 +1,66 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: Hostname: +Version: Hockeypuck 2.2 + +xsFNBGU2rNoBEACSi5t0nL6/Hj3d0PwsbdnbY+SqLUIZ3uWZQm6tsNhvTnahvPPZ +BGdl99iWYTt2KmXp0KeN2s9pmLKkGAbacQP1RqzMFnoHawSMf0qTUVjAvhnI4+qz +MDjTNSBq9fa3nHmOYxownnrRkpiQUM/yD7/JmVENgwWb6akZeGYrXch9jd4XV3t8 +OD6TGzTedTki0TDNr6YZYhC7jUm9fK9Zs299pzOXSxRRNGd+3H9gbXizrBu4L/3l +UrNf//rM7OvV9Ho7u9YYyAQ3L3+OABK9FKHNhrpi8Q0cbhvWkD4oCKJ+YZ54XrOG +0YTg/YUAs5/3//FATI1sWdtLjJ5pSb0onV3LIbarRTN8lC4Le/5kd3lcot9J8b3E +MXL5p9OGW7wBfmNVRSUI74Vmwt+v9gyp0Hd0keRCUn8lo/1V0YD9i92KsE+/IqoY +Tjnya/5kX41jB8vr1ebkHFuJ404+G6ETd0owwxq64jLIcsp/GBZHGU0RKKAo9DRL +H7rpQ7PVlnw8TDNlOtWt5EJlBXFcPL+NgWbqkADAyA/XSNeWlqonvPlYfmasnAHA +pMd9NhPQhC7hJTjCiAwG8UyWpV8Dj07DHFQ5xBbkTnKH2OrJtguPqSNYtTASbsWz +09S8ujoTDXFT17NbFM2dMIiq0a4VQB3SzH13H2io9Cbg/TzJrJGmwgoXgwARAQAB +zTZNeVNRTCBSZWxlYXNlIEVuZ2luZWVyaW5nIDxteXNxbC1idWlsZEBvc3Mub3Jh +Y2xlLmNvbT7CwZQEEwEIAD4WIQS8pDQXw7SF3RKOxtS3s7eIqNN4XAUCZTas2gIb +AwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC3s7eIqNN4XLzoD/9P +lpWtfHlI8eQTHwGsGIwFA+fgipyDElapHw3MO+K9VOEYRZCZSuBXHJe9kjGEVCGU +DrfImvgTuNuqYmVUV+wyhP+w46W/cWVkqZKAW0hNp0TTvu3eDwap7gdk80VF24Y2 +Wo0bbiGkpPiPmB59oybGKaJ756JlKXIL4hTtK3/hjIPFnb64Ewe4YLZyoJu0fQOy +A8gXuBoalHhUQTbRpXI0XI3tpZiQemNbfBfJqXo6LP3/LgChAuOfHIQ8alvnhCwx +hNUSYGIRqx+BEbJw1X99Az8XvGcZ36VOQAZztkW7mEfH9NDPz7MXwoEvduc61xwl +MvEsUIaSfn6SGLFzWPClA98UMSJgF6sKb+JNoNbzKaZ8V5w13msLb/pq7hab72HH +99XJbyKNliYj3+KA3q0YLf+Hgt4Y4EhIJ8x2+g690Np7zJF4KXNFbi1BGloLGm78 +akY1rQlzpndKSpZq5KWw8FY/1PEXORezg/BPD3Etp0AVKff4YdrDlOkNB7zoHRfF +HAvEuuqti8aMBrbRnRSG0xunMUOEhbYS/wOOTl0g3bF9NpAkfU1Fun57N96Us2T9 +gKo9AiOY5DxMe+IrBg4zaydEOovgqNi2wbU0MOBQb23Puhj7ZCIXcpILvcx9ygjk +ONr75w+XQrFDNeux4Znzay3ibXtAPqEykPMZHsZ2scLBcwQQAQgAHRYhBCanscff +/ZHKcAbFAa2ndo70TprLBQJmRTb5AAoJEK2ndo70TprLATkP/3BF1ZRs4c6Z22c9 +b2W6CX+fuKAuD/3BHcjCWLsSRpGiXw9I4NnTBy9nwS5OlUYrAKM8OMLcBwzNUOXw +tFyUP004LKs2urEXt0caqHHGgPSCutYyGOm2tYzLNZzcdIUcrgXZqG1ce66J4Obz +KrOUsM4R+Ccvpn5/vZXN24c5uyT/KW36UN+/8B5FcM7j+08SEzCPFVCuDdQIw+mk +V4RL7G8SntwiV7Cdq49Q6ztssJBEcGnjrPMPAzsX5dsxUbMS23J1+/t5Y52SEo7U +2odzytyNYQjed0tulDiZkAq5CHE1vFFn7PNYpUFxgOfXgKlJ29TPbGcuKT6JTkiP +d+9cTaWKR9OpNlP/+5lCySpQlmYv0XI6HOoV5YbMvM8lVaazhZw0qTMEEONpV37Y +mwn0Bc8VO6KDClo+YiK+N6I21G33hfBMH2FSjiD2OGBpOQ4zR6m6pPQimuXm4aA2 +Kq3XtQ8tfIoD3AmbPlKGeDvbUaHD7+F2n/L6Mx0O3Eh4sb+VN2s2Qld76t7/+afw +lDGw9fALdk64VBBHy/2aG6448oXLYf/xOYZTHh7MCle7j8+adwWs+hLqoKEtpL5I +gRlPN7egeTqRpnk7Dhjn30tkEpQymRQM16uOUWBi92F3bcWzYzik7FVSw8EUhIbB +YYBm4cZI0TQIT/WaMStGwK/b9EXazsFNBGU2rNoBEACx28GjxZGpnlZVWTqVF4Px +vpnHzd4lSRXbnhhf3Ofm3woNGNg7JLBLvmYkhpkuy/RhCMmT7mu3XS16PIKskgWj +0Iy8KaNQq1VuCaF9Ln59QNGtgIRkEFJrQO+frwQEuIe6Cv5I9cXqjWFcRSp0wKkH +qhWnpfjklVCugIogfm+wK3DaNTxLb8iONXRX4T/OK0YKJlqhnV/o0bujPIV6nUJI +BF5m7+yyyTSkIuV8J5tF31HPdCNKtCFZi4lr54maIXihqGelQaS3EwPrfYj1ob4g +x+O00k21ffYxs75J75wK1VzdzFJr+lH7z1rdxv0gEDm0UXZCh6SGqj/WaYuL3def +q4NSGXm1XFOcHbXt0FPbu3D6nSGN32FdlFBushlRPKHf7wQx+YCM1Ih5H62HzrFF +31cVGv0Q6qvJ2cAs5Sv1xPtN9dYYSQW+fqWNBft/hG+Mk5NtziMmBUXK7wr5VTq7 +U2cUmAOI0axa+djEB/uAMNtRJcS4LZqeFa/E++ksaayymeCB7jKX7ee/5Spn2ybo +sJ5tH/tRnru1jPenrHMA5WBixhzGghS4RleMdC2xh9NlmGuNRYEnT2Osy+UpQcud +LjftItuChdVhmZyrABalU04tl/58WbggTloYEbkOGjYJnq6OeBb1mSf3xPV3g3Qw +TDBdrZXpPWKPNyoPsYCllQARAQABwsF8BBgBCAAmFiEEvKQ0F8O0hd0SjsbUt7O3 +iKjTeFwFAmU2rNoCGwwFCQPCZwAACgkQt7O3iKjTeFxeow//TVo9PcDdKDuhNCgd +0LGPTgQuTOt7M1YYz5jBtIqtHYuhdHzN/a0EXNzb9OX3xXT7rx/94K+S+oK462rj +f3Y+zbeP1bevlcb4YM7AOzHSCXQT5CTDTunB0ly0Dp5+yadGSMXZhU7Q30yIkDW1 +zw1s1ekQQclnsXxGLlylCsZTP8BjR5p7ZvtB5/I/iulQQukxk+Nzw/Hf0V7UPNFt +P7kTX1NluulvCVJizWILNLlgYWakJHJlwspejfNLo3bb7zZydEFI8+KmI7pZpBrB +xUyVA7VJfSCIH7f8OvJ831W4hh3URYIZBrc7QxW7qjkpUfA+CX1HU/rE9mG6uQOk +B3RvWzh8XGKf4x3HCYbtGyVkD0JdE+nOjv98Ixbyxg4fXkM/5h9RySgSt4G37N7M +HFshfYIYZYRX2/dQFdp1G3DFhDqw31upbiObVvjW80DXtvoJUfqxWC1Td437lj1q +fV7mMsPqQVjH44h6oggh39MSrBLrVyxj1pq/iPgos5kUIY1TQVWOLs1B7BKl6lNw +nB8kM7Oa2IM/i+iXUCkkYtHBlln08HrCw6AM6g/qyvRisMj801fZHJdduCWdDXIl +lVIff/d6jqScbapO2FQocJEM0p3L1CpzXHhZZa1JGOH7NfwC8krarWtUsfb/eKXF +73BwBlSVqPeJ3dPGq4CW53iVYPM= +=mheB +-----END PGP PUBLIC KEY BLOCK----- From 8dbc5b5a4f08e2ff897824f434e818212a5a7e25 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 26 Dec 2024 17:45:38 -0800 Subject: [PATCH 765/783] Spec: adjust text field lengths following utf8mb4 change --- spec/mysql2/result_spec.rb | 12 ++++++------ spec/spec_helper.rb | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index ed3e9d262..ac42f2dea 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -133,7 +133,7 @@ it "should return correct types" do expected_types = %w[ mediumint(9) - varchar(10) + varchar(13) bit(64) bit(1) tinyint(4) @@ -152,16 +152,16 @@ timestamp time year(4) - char(10) - varchar(10) + char(13) + varchar(13) binary(10) varbinary(10) tinyblob - tinytext + text(1020) blob - text + text(262140) mediumblob - mediumtext + text(67108860) longblob longtext enum diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 677c34829..754c25dea 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,6 +19,10 @@ RSpec.configure do |config| config.disable_monkey_patching! + config.expect_with :rspec do |expectations| + expectations.max_formatted_output_length = 1200 + end + def with_internal_encoding(encoding) old_enc = Encoding.default_internal old_verbose = $VERBOSE From 83ca80df3565a8739b770321ecc5263ba4890970 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 26 Dec 2024 21:42:13 -0800 Subject: [PATCH 766/783] CI: Use caching_sha2_password in MySQL 8.0 and newer --- ci/setup.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ci/setup.sh b/ci/setup.sh index b54dedba0..104241a1c 100644 --- a/ci/setup.sh +++ b/ci/setup.sh @@ -4,6 +4,7 @@ set -eux # Change the password to be empty. CHANGED_PASSWORD=false +CHANGED_PASSWORD_SHA2=false # Change the password to be empty, recreating the root user on mariadb < 10.2 # where ALTER USER is not available. # https://stackoverflow.com/questions/56052177/ @@ -45,13 +46,13 @@ fi # Install MySQL 8.0 if DB=mysql80 if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then sudo bash ci/mysql80.sh - CHANGED_PASSWORD=true + CHANGED_PASSWORD_SHA2=true fi # Install MySQL 8.4 if DB=mysql84 if [[ -n ${DB-} && x$DB =~ ^xmysql84 ]]; then sudo bash ci/mysql84.sh - CHANGED_PASSWORD=true + CHANGED_PASSWORD_SHA2=true fi # Install MariaDB 10.6 if DB=mariadb10.6 @@ -120,6 +121,11 @@ if [ "${CHANGED_PASSWORD}" = true ]; then # https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" \ -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" +elif [ "${CHANGED_PASSWORD_SHA2}" = true ]; then + # In MySQL 5.7, the default authentication plugin is mysql_native_password. + # As of MySQL 8.0, the default authentication plugin is changed to caching_sha2_password. + sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" \ + -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY ''" elif [ "${CHANGED_PASSWORD_BY_RECREATE}" = true ]; then sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" < Date: Thu, 26 Dec 2024 21:50:29 -0800 Subject: [PATCH 767/783] CI: There are no MySQL 5.7 builds for Ubuntu 20.04, this combo would have run as a container --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b0cca4a6..bdad7932d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,9 @@ jobs: - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.6} - {os: ubuntu-20.04, ruby: '2.7', db: mariadb10.6} - {os: ubuntu-20.04, ruby: '2.7', db: mysql80} - - {os: ubuntu-20.04, ruby: '2.7', db: mysql57} + + # MySQL 5.7 packages stopped after Ubuntu 18.04 Bionic + # - {os: ubuntu-18.04, ruby: '2.7', db: mysql57} # TODO - Windows CI # - {os: windows-2022, ruby: '3.2', db: mysql80} From 457ffd951375bb7a8f0bbfea44410700a3862a98 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 26 Dec 2024 21:58:04 -0800 Subject: [PATCH 768/783] CI: pin MacOS brew package versions --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bdad7932d..85414d3b9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,8 +49,9 @@ jobs: # Allow failure due to this issue: # https://github.com/brianmario/mysql2/issues/1194 - - {os: macos-latest, ruby: '2.6', db: mariadb, ssl: openssl@1.1, allow-failure: true} - - {os: macos-latest, ruby: '2.6', db: mysql, ssl: openssl@1.1, allow-failure: true} + - {os: macos-latest, ruby: '3.4', db: mariadb@11.4, ssl: openssl@3, allow-failure: true} + - {os: macos-latest, ruby: '3.4', db: mysql@8.4, ssl: openssl@3, allow-failure: true} + - {os: macos-latest, ruby: '2.6', db: mysql@8.0, ssl: openssl@1.1, allow-failure: true} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails, which we don't want. fail-fast: false From 01f5d71cb2e89a38d0e2621ed500420735297c67 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 26 Dec 2024 22:20:04 -0800 Subject: [PATCH 769/783] CI: brew link kegged MariaDB 11.4 LTS --- ci/setup.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/setup.sh b/ci/setup.sh index 104241a1c..0089a384b 100644 --- a/ci/setup.sh +++ b/ci/setup.sh @@ -84,6 +84,7 @@ if [[ x$OSTYPE =~ ^xdarwin ]]; then brew info "$DB" brew install "$DB" zstd + brew link "$DB" # explicitly activate in case of kegged LTS versions DB_PREFIX="$(brew --prefix "${DB}")" export PATH="${DB_PREFIX}/bin:${PATH}" export LDFLAGS="-L${DB_PREFIX}/lib" From 078067af164e7dd61af80d6601a364e5c4b9c31c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 26 Dec 2024 22:26:11 -0800 Subject: [PATCH 770/783] CI: remove duplicate jobs, improve comments --- .github/workflows/build.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 85414d3b9..90d122bb8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,11 +9,11 @@ jobs: strategy: matrix: include: - # Ruby 3.x on Ubuntu 22.04 LTS (latest at this time) + # Ruby 3.x on Ubuntu 24.04 LTS - {os: ubuntu-24.04, ruby: 'head', db: mysql84} - {os: ubuntu-24.04, ruby: '3.4', db: mysql84} - # Ruby 3.x on Ubuntu 22.04 LTS (latest at this time) + # Ruby 3.x on Ubuntu 22.04 LTS - {os: ubuntu-22.04, ruby: '3.4', db: mysql80} - {os: ubuntu-22.04, ruby: '3.3', db: mysql80} - {os: ubuntu-22.04, ruby: '3.2', db: mysql80} @@ -30,6 +30,10 @@ jobs: - {os: ubuntu-20.04, ruby: '2.1', db: mysql80} - {os: ubuntu-20.04, ruby: '2.0', db: mysql80} + # MySQL 5.7 packages stopped after Ubuntu 18.04 Bionic + # - {os: ubuntu-18.04, ruby: '2.7', db: mysql57} + + # MariaDB LTS versions # db: on Linux, ci/setup.sh installs the specified packages # db: on MacOS, installs a Homebrew package use "name@X.Y" to specify a version @@ -38,10 +42,6 @@ jobs: - {os: ubuntu-22.04, ruby: '2.7', db: mariadb10.11} - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.6} - {os: ubuntu-20.04, ruby: '2.7', db: mariadb10.6} - - {os: ubuntu-20.04, ruby: '2.7', db: mysql80} - - # MySQL 5.7 packages stopped after Ubuntu 18.04 Bionic - # - {os: ubuntu-18.04, ruby: '2.7', db: mysql57} # TODO - Windows CI # - {os: windows-2022, ruby: '3.2', db: mysql80} @@ -52,6 +52,7 @@ jobs: - {os: macos-latest, ruby: '3.4', db: mariadb@11.4, ssl: openssl@3, allow-failure: true} - {os: macos-latest, ruby: '3.4', db: mysql@8.4, ssl: openssl@3, allow-failure: true} - {os: macos-latest, ruby: '2.6', db: mysql@8.0, ssl: openssl@1.1, allow-failure: true} + # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails, which we don't want. fail-fast: false From 57b8df188c963ae0e4d4e1123d3e9de2bbcab637 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 26 Dec 2024 22:38:05 -0800 Subject: [PATCH 771/783] README: Remove link to Travis CI --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 564dbb75c..f28dc8b23 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ GitHub Actions [![GitHub Actions Status: Build](https://github.com/brianmario/mysql2/actions/workflows/build.yml/badge.svg)](https://github.com/brianmario/mysql2/actions/workflows/build.yml) [![GitHub Actions Status: Container](https://github.com/brianmario/mysql2/actions/workflows/container.yml/badge.svg)](https://github.com/brianmario/mysql2/actions/workflows/container.yml) -Travis CI -[![Travis CI Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2) Appveyor CI [![Appveyor CI Status](https://ci.appveyor.com/api/projects/status/github/sodabrew/mysql2)](https://ci.appveyor.com/project/sodabrew/mysql2) From f10e76758470e8dc7226dd84b1b94302035d3fcf Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 6 Apr 2025 19:31:29 -0700 Subject: [PATCH 772/783] CI: Allow newer rake, rake-compiler dependencies --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index f7a0900a0..abf415624 100644 --- a/Gemfile +++ b/Gemfile @@ -3,11 +3,11 @@ source 'https://rubygems.org/' gemspec if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2") - gem 'rake', '~> 13.0.1' + gem 'rake', '~> 13.0' else gem 'rake', '< 13' end -gem 'rake-compiler', '~> 1.1.0' +gem 'rake-compiler', '~> 1.2.0' # For local debugging, irb is Gemified since Ruby 2.6 gem 'irb', require: false From cd6fc60c3b30e12deaa594e09501db746f1f4573 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Sun, 6 Apr 2025 19:45:00 -0700 Subject: [PATCH 773/783] CI: fixes for centos 7 dockerfile --- ci/Dockerfile_centos | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/ci/Dockerfile_centos b/ci/Dockerfile_centos index 2e07e31ea..dd8434782 100644 --- a/ci/Dockerfile_centos +++ b/ci/Dockerfile_centos @@ -4,22 +4,31 @@ FROM ${IMAGE} WORKDIR /build COPY . . -RUN cat /etc/redhat-release -RUN yum -yq update -RUN yum -yq install epel-release -# The options are to install faster. -RUN yum -yq install \ - --setopt=deltarpm=0 \ - --setopt=install_weak_deps=false \ - --setopt=tsflags=nodocs \ - gcc \ - gcc-c++ \ - git \ - make \ - mariadb-devel \ - mariadb-server \ - ruby-devel -RUN gem install --no-document "rubygems-update:~>2.7" && update_rubygems > /dev/null -RUN gem install --no-document "bundler:~>1.17" +# mirrorlist.centos.org no longer exists, see +# https://serverfault.com/questions/1161816/mirrorlist-centos-org-no-longer-resolve/1161847#1161847 +# +# The --setopt flags to yum enable faster installs +# +RUN cat /etc/redhat-release \ + && sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/CentOS-*.repo \ + && sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/CentOS-*.repo \ + && sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/CentOS-*.repo \ + && yum -y -q update \ + && yum -y -q install epel-release \ + && yum -y -q install \ + --setopt=deltarpm=0 \ + --setopt=install_weak_deps=false \ + --setopt=tsflags=nodocs \ + gcc \ + gcc-c++ \ + git \ + make \ + mariadb-devel \ + mariadb-server \ + ruby-devel + +RUN gem install --no-document "rubygems-update:~>2.7" \ + && update_rubygems > /dev/null \ + && gem install --no-document "bundler:~>1.17" CMD bash ci/container.sh From 3adb5311c25782716a9591224e5c147e98c1c112 Mon Sep 17 00:00:00 2001 From: YujiSoftware Date: Mon, 7 Apr 2025 11:54:59 +0900 Subject: [PATCH 774/783] rb_mysql_result_free_result is now executed if the result is 0 rows. (#1399) When there are 0 rows in the result, the condition to load the cache from the beginning (lastRowProcessed == numberOfRows) is met, and the else route is not entered. To resolve this, the else route is always entered first. --- ext/mysql2/result.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 3e94f341a..9f9b473b8 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -1015,7 +1015,7 @@ static VALUE rb_mysql_result_each_(VALUE self, rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery)."); } } else { - if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) { + if (args->cacheRows && wrapper->resultFreed) { /* we've already read the entire dataset from the C result into our */ /* internal array. Lets hand that over to the user since it's ready to go */ for (i = 0; i < wrapper->numberOfRows; i++) { From 99a6cc34823a038368cbdc992f751acb0b2845d6 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Mon, 7 Apr 2025 05:05:21 +0200 Subject: [PATCH 775/783] CI: Add an option to use SSL certifications generated from specific host. (#1310) In the Fedora project, we are running the mysql2 tests on the build environment with a user permission, without root permission and without `sudo`. In this case, we couldn't set up the custom domain "mysql2gem.example.com" to run SSL tests. The feature to create a set of the SSL certifications from the localhost gives an option to run the SSL tests executed in the environment. How to generate the certificaton files: ``` $ cd spec/ssl/ $ TEST_RUBY_MYSQL2_SSL_CERT_HOST=localhost bash gen_certs.sh ``` The files are generated in the `spec/ssl` directory. How to use: ``` $ TEST_RUBY_MYSQL2_SSL_CERT_HOST=localhost \ bundle exec rake spec ``` --- .github/workflows/container.yml | 5 +++-- ci/Dockerfile_fedora | 1 + ci/container.sh | 7 +++++++ spec/mysql2/client_spec.rb | 2 +- spec/spec_helper.rb | 13 +++++++++++++ spec/ssl/gen_certs.sh | 6 +++++- 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index af57ef136..d53c78d2f 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -16,7 +16,7 @@ jobs: # Fedora latest stable version - {distro: fedora, image: 'fedora:latest'} # Fedora development version - - {distro: fedora, image: 'fedora:rawhide', ssl_cert_dir: '/tmp/mysql2'} + - {distro: fedora, image: 'fedora:rawhide', ssl_cert_dir: '/tmp/mysql2', ssl_cert_host: 'localhost'} # On the fail-fast: true, it cancels all in-progress jobs # if any matrix job fails unlike Travis fast_finish. fail-fast: false @@ -29,8 +29,9 @@ jobs: # https://bugzilla.redhat.com/show_bug.cgi?id=1900021 - run: | docker run \ - --add-host=mysql2gem.example.com:127.0.0.1 \ + --add-host=${{ matrix.ssl_cert_host || 'mysql2gem.example.com' }}:127.0.0.1 \ -t \ -e TEST_RUBY_MYSQL2_SSL_CERT_DIR="${{ matrix.ssl_cert_dir || '' }}" \ + -e TEST_RUBY_MYSQL2_SSL_CERT_HOST="${{ matrix.ssl_cert_host || '' }}" \ --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ mysql2 diff --git a/ci/Dockerfile_fedora b/ci/Dockerfile_fedora index 5d595f847..cc645d481 100644 --- a/ci/Dockerfile_fedora +++ b/ci/Dockerfile_fedora @@ -18,6 +18,7 @@ RUN dnf -yq install \ make \ mariadb-connector-c-devel \ mariadb-server \ + openssl \ redhat-rpm-config \ ruby-devel \ rubygem-bigdecimal \ diff --git a/ci/container.sh b/ci/container.sh index 90552a919..88764a389 100644 --- a/ci/container.sh +++ b/ci/container.sh @@ -5,6 +5,13 @@ set -eux ruby -v bundle install --path vendor/bundle --without development +# Regenerate the SSL certification files from the specified host. +if [ -n "${TEST_RUBY_MYSQL2_SSL_CERT_HOST}" ]; then + pushd spec/ssl + bash gen_certs.sh + popd +fi + # Start mysqld service. bash ci/setup_container.sh diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 865d6d119..257b56c99 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -153,7 +153,7 @@ def connect(*args) let(:option_overrides) do { - 'host' => 'mysql2gem.example.com', # must match the certificates + 'host' => ssl_cert_host, # must match the certificates :sslkey => "#{ssl_cert_dir}/client-key.pem", :sslcert => "#{ssl_cert_dir}/client-cert.pem", :sslca => "#{ssl_cert_dir}/ca-cert.pem", diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 754c25dea..f7ac9a8fe 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -81,6 +81,19 @@ def ssl_cert_dir @ssl_cert_dir end + # A host used to create the certificates pem files. + def ssl_cert_host + return @ssl_cert_host if @ssl_cert_host + + host = ENV['TEST_RUBY_MYSQL2_SSL_CERT_HOST'] + @ssl_cert_host = if host && !host.empty? + host + else + 'mysql2gem.example.com' + end + @ssl_cert_host + end + config.before(:suite) do begin new_client diff --git a/spec/ssl/gen_certs.sh b/spec/ssl/gen_certs.sh index 4996b845d..41818d4a2 100644 --- a/spec/ssl/gen_certs.sh +++ b/spec/ssl/gen_certs.sh @@ -2,6 +2,10 @@ set -eux +# TEST_RUBY_MYSQL2_SSL_CERT_HOST: custom host for the SSL certificates. +SSL_CERT_HOST=${TEST_RUBY_MYSQL2_SSL_CERT_HOST:-mysql2gem.example.com} +echo "Generating the SSL certifications from the host ${SSL_CERT_HOST}.." + echo " [ ca ] # January 1, 2015 @@ -34,7 +38,7 @@ commonName_default = ca_mysql2gem " >> ca.cnf echo " -commonName_default = mysql2gem.example.com +commonName_default = ${SSL_CERT_HOST} " >> cert.cnf # Generate a set of certificates From 441b104f8f120788e86086702d96963aecec3172 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 7 Apr 2025 00:00:49 -0700 Subject: [PATCH 776/783] CI: select better MariaDB mirrors because some block GitHub Actions (#1401) --- ci/mariadb1011.sh | 17 +++++++++++++++-- ci/mariadb106.sh | 19 ++++++++++++++++--- ci/mariadb114.sh | 17 +++++++++++++++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/ci/mariadb1011.sh b/ci/mariadb1011.sh index 3122bb279..6eef2b5f3 100644 --- a/ci/mariadb1011.sh +++ b/ci/mariadb1011.sh @@ -5,6 +5,19 @@ apt purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key add support/C74CD1D8.asc -add-apt-repository "deb https://deb.mariadb.org/10.11/ubuntu $(lsb_release -cs) main" +RELEASE=$(lsb_release -cs) +VERSION=10.11 + +tee <<- EOF > /etc/apt/sources.list.d/mariadb.sources + X-Repolib-Name: MariaDB + Types: deb + # URIs: https://deb.mariadb.org/$VERSION/ubuntu + URIs: https://mirror.rackspace.com/mariadb/repo/$VERSION/ubuntu + Suites: $RELEASE + Components: main main/debug + Signed-By: /etc/apt/keyrings/mariadb-keyring.asc +EOF + +cp support/C74CD1D8.asc /etc/apt/keyrings/mariadb-keyring.asc +apt update apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server libmariadb-dev diff --git a/ci/mariadb106.sh b/ci/mariadb106.sh index b6c7153bc..82b1db693 100644 --- a/ci/mariadb106.sh +++ b/ci/mariadb106.sh @@ -5,6 +5,19 @@ apt purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key add support/C74CD1D8.asc -add-apt-repository "deb https://deb.mariadb.org/10.6/ubuntu $(lsb_release -cs) main" -apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server libmariadb-dev +RELEASE=$(lsb_release -cs) +VERSION=10.6 + +tee <<- EOF > /etc/apt/sources.list.d/mariadb.sources + X-Repolib-Name: MariaDB + Types: deb + # URIs: https://deb.mariadb.org/$VERSION/ubuntu + URIs: https://mirror.rackspace.com/mariadb/repo/$VERSION/ubuntu + Suites: $RELEASE + Components: main main/debug + Signed-By: /etc/apt/keyrings/mariadb-keyring.asc +EOF + +cp support/C74CD1D8.asc /etc/apt/keyrings/mariadb-keyring.asc +apt update +apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server-$VERSION libmariadb-dev diff --git a/ci/mariadb114.sh b/ci/mariadb114.sh index 6225972a2..767dd451c 100644 --- a/ci/mariadb114.sh +++ b/ci/mariadb114.sh @@ -5,6 +5,19 @@ apt purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql -apt-key add support/C74CD1D8.asc -add-apt-repository "deb https://deb.mariadb.org/11.4/ubuntu $(lsb_release -cs) main" +RELEASE=$(lsb_release -cs) +VERSION=11.4 + +tee <<- EOF > /etc/apt/sources.list.d/mariadb.sources + X-Repolib-Name: MariaDB + Types: deb + # URIs: https://deb.mariadb.org/$VERSION/ubuntu + URIs: https://mirror.rackspace.com/mariadb/repo/$VERSION/ubuntu + Suites: $RELEASE + Components: main main/debug + Signed-By: /etc/apt/keyrings/mariadb-keyring.asc +EOF + +cp support/C74CD1D8.asc /etc/apt/keyrings/mariadb-keyring.asc +apt update apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server libmariadb-dev From b63d2e8e99bbc92484289e0f246e2733808443d7 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Tue, 29 Jul 2025 10:47:51 +1000 Subject: [PATCH 777/783] Correct MariaDB runtime library verison check (#1406) The MariaDB C/C version used by mysql_get_client_info() version is different from the server version exposed as MARIADB_CLIENT_VERSION_STR. The correct C/C version will be MARIADB_PACKAGE_VERSION. Fixes #1391. --- ext/mysql2/client.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 25a350299..c9b0add24 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -48,9 +48,12 @@ static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args * compatibility with mysql-connector-c, where LIBMYSQL_VERSION is the correct * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when * linking against the server itself + * + * MariaDB exposes its client version independently to the server version as + * MARIADB_PACKAGE_VERSION. */ -#if defined(MARIADB_CLIENT_VERSION_STR) - #define MYSQL_LINK_VERSION MARIADB_CLIENT_VERSION_STR +#if defined(MARIADB_PACKAGE_VERSION) + #define MYSQL_LINK_VERSION MARIADB_PACKAGE_VERSION #elif defined(LIBMYSQL_VERSION) #define MYSQL_LINK_VERSION LIBMYSQL_VERSION #else From 0f38974ead068daa5388013cd01f49f74e439805 Mon Sep 17 00:00:00 2001 From: Richard Larocque Date: Mon, 28 Jul 2025 21:16:24 -0700 Subject: [PATCH 778/783] Expose `db` attribute of `MYSQL` client struct (#1245) Adds an accessor method that returns the current value of the `MSYQL` client struct's `db` attribute. The `MYSQL` client struct includes a field `char *db`. When the `session_track_schema` setting is enabled, this field will be updated using information from server-provided "OK" packets, keeping it in sync as the client switches between databases. --- ext/mysql2/client.c | 21 +++++++++++++++++++ spec/mysql2/client_spec.rb | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index c9b0add24..2ad71bd9d 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1334,6 +1334,26 @@ static VALUE rb_mysql_client_encoding(VALUE self) { return wrapper->encoding; } +/* call-seq: + * client.database + * + * Returns the currently selected database. + * + * The result may be stale if `session_track_schema` is disabled. Read + * https://dev.mysql.com/doc/refman/5.7/en/session-state-tracking.html for more + * information. + */ +static VALUE rb_mysql_client_database(VALUE self) { + GET_CLIENT(self); + + char *db = wrapper->client->db; + if (!db) { + return Qnil; + } + + return rb_str_new_cstr(wrapper->client->db); +} + /* call-seq: * client.automatic_close? * @@ -1606,6 +1626,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0); rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); rb_define_method(cMysql2Client, "session_track", rb_mysql_client_session_track, 1); + rb_define_method(cMysql2Client, "database", rb_mysql_client_database, 0); rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1); diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 257b56c99..460fd1320 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1181,6 +1181,49 @@ def run_gc end end + context 'database' do + before(:example) do + 2.times do |i| + @client.query("CREATE DATABASE test_db#{i}") + end + end + + after(:example) do + 2.times do |i| + @client.query("DROP DATABASE test_db#{i}") + end + end + + it "should be `nil` when no database is selected" do + client = new_client(database: nil) + expect(client.database).to eq(nil) + end + + it "should reflect the initially connected database" do + client = new_client(database: 'test_db0') + expect(client.database).to eq('test_db0') + end + + context "when session tracking is on" do + it "should change to reflect currently selected database" do + client = new_client(database: 'test_db0') + client.query('SET session_track_schema=on') + expect { client.query('USE test_db1') }.to change { + client.database + }.from('test_db0').to('test_db1') + end + end + + context "when session tracking is off" do + it "does not change when a new database is selected" do + client = new_client(database: 'test_db0') + client.query('SET session_track_schema=off') + expect(client.database).to eq('test_db0') + expect { client.query('USE test_db1') }.not_to(change { client.database }) + end + end + end + it "#thread_id should return a boolean" do expect(@client.ping).to eql(true) @client.close From c79b3c14fd47e195f00bb6fae739928549b23097 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 22 Sep 2025 09:02:05 -0700 Subject: [PATCH 779/783] bump version --- lib/mysql2/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb index d84abfc72..cf7beba92 100644 --- a/lib/mysql2/version.rb +++ b/lib/mysql2/version.rb @@ -1,3 +1,3 @@ module Mysql2 - VERSION = "0.5.6".freeze + VERSION = "0.5.7".freeze end From 4463cac6d037ce52b79293556709c4b4790fa5d1 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 3 Oct 2025 21:53:21 +0200 Subject: [PATCH 780/783] Update `affected_rows` after calling `mysql_next_result` (#1412) Fix: https://github.com/brianmario/mysql2/issues/1411 Followup: https://github.com/brianmario/mysql2/pull/1383 When using multi statement, we need to update the affected rows after each call to `mysql_next_result`. --- ext/mysql2/client.c | 1 + spec/mysql2/client_spec.rb | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 2ad71bd9d..693def4f6 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -1282,6 +1282,7 @@ static VALUE rb_mysql_client_next_result(VALUE self) int ret; GET_CLIENT(self); ret = mysql_next_result(wrapper->client); + wrapper->affected_rows = mysql_affected_rows(wrapper->client); if (ret > 0) { rb_raise_mysql2_error(wrapper); return Qfalse; diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 460fd1320..34a8f1166 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1064,12 +1064,23 @@ def run_gc end it "#affected_rows should return a Fixnum, from the last INSERT/UPDATE" do - @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" - expect(@client.affected_rows).to eql(1) + @client.query "INSERT INTO lastIdTest (blah) VALUES (1234), (5678)" + expect(@client.affected_rows).to eql(2) @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1" expect(@client.affected_rows).to eql(1) end + it "#affected_rows with multi statements returns the last result's affected_rows" do + @client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON) + + @client.query("INSERT INTO lastIdTest (blah) VALUES (1234), (5678); UPDATE lastIdTest SET blah=4321 WHERE id=1") + expect(@client.affected_rows).to eq(2) + expect(@client.next_result).to eq(true) + expect(@client.affected_rows).to eq(1) + ensure + @client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF) + end + it "#affected_rows isn't cleared by Statement#close" do stmt = @client.prepare("INSERT INTO lastIdTest (blah) VALUES (1234)") From 84c99f6e5a3b19d697630bd7045dddf13531365e Mon Sep 17 00:00:00 2001 From: Takuya N Date: Thu, 9 Oct 2025 08:54:37 +0900 Subject: [PATCH 781/783] doc: update macOS OpenSSL instructions to use OpenSSL 3.x (#1409) - https://formulae.brew.sh/formula/openssl@3 - https://ports.macports.org/port/openssl3/ Signed-off-by: Takuya Noguchi --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f28dc8b23..c35b0b49b 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,8 @@ find the particular package. The most common issue we see is a user who has the library file `libmysqlclient.so` but is missing the header file `mysql.h` -- double check that you have the _-dev_ packages installed. -### Mac OS X +### macOS + You may use Homebrew, MacPorts, or a native MySQL installer package. The most common paths will be automatically searched. If you want to select a specific @@ -102,15 +103,15 @@ If you have not done so already, you will need to install the XCode select tools Later versions of MacOS no longer distribute a linkable OpenSSL library. It is common to use Homebrew or MacPorts to install OpenSSL. Make sure that both the Ruby runtime and MySQL client libraries are compiled with the same OpenSSL -family, 1.0 or 1.1 or 3.0, since only one can be loaded at runtime. +family, 3.x, since only one can be loaded at runtime. ``` sh -$ brew install openssl@1.1 zstd -$ gem install mysql2 -- --with-openssl-dir=$(brew --prefix openssl@1.1) +$ brew install openssl@3 zstd +$ gem install mysql2 -- --with-openssl-dir=$(brew --prefix openssl@3) or -$ sudo port install openssl11 +$ sudo port install openssl3 ``` Since most Ruby projects use Bundler, you can set build options in the Bundler @@ -118,7 +119,7 @@ config rather than manually installing a global mysql2 gem. This example shows how to set build arguments with [Bundler config](https://bundler.io/man/bundle-config.1.html): ``` sh -$ bundle config --local build.mysql2 -- --with-openssl-dir=$(brew --prefix openssl@1.1) +$ bundle config --local build.mysql2 -- --with-openssl-dir=$(brew --prefix openssl@3) ``` Another helpful trick is to use the same OpenSSL library that your Ruby was From 387e13a58bdfc3964dcbb0aa7a0fb823f6d93195 Mon Sep 17 00:00:00 2001 From: Takuya N Date: Thu, 9 Oct 2025 09:03:54 +0900 Subject: [PATCH 782/783] chore(style): use Ruby 3.4 to run RuboCop with the latest version (#1408) - Update .rubocop_todo.yml by `rubocop --auto-gen-config`. - One offense is fixed by `--autocorrect`. - Update .github/workflows/rubocop.yml to use Ruby 3.4, and actions/checkout@v5. - Update Gemfile to use the latest RuboCop (1.80.2). Signed-off-by: Takuya Noguchi --- .github/workflows/rubocop.yml | 6 ++-- .rubocop_todo.yml | 60 ++++++++++++++--------------------- Gemfile | 4 +-- ext/mysql2/extconf.rb | 2 +- 4 files changed, 29 insertions(+), 43 deletions(-) diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index d66266dd6..ad8ef10ef 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -8,11 +8,11 @@ jobs: env: BUNDLE_WITHOUT: development steps: - - uses: actions/checkout@v3 - - name: Set up Ruby 2.6 + - uses: actions/checkout@v5 + - name: Set up Ruby 3.4 uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6 + ruby-version: 3.4 bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run RuboCop run: bundle exec rubocop diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 02cfbae17..9a0c4b62b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,18 +1,11 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-05-30 13:48:55 UTC using RuboCop version 1.30.0. +# on 2025-09-23 00:00:00 UTC using RuboCop version 1.80.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -Layout/HeredocIndentation: - Exclude: - - 'support/ruby_enc_to_mysql.rb' - - 'tasks/compile.rake' - # Offense count: 2 # Configuration parameters: AllowedMethods. # AllowedMethods: enums @@ -22,23 +15,24 @@ Lint/ConstantDefinitionInBlock: - 'tasks/rspec.rake' # Offense count: 1 +# Configuration parameters: AllowedParentClasses. Lint/MissingSuper: Exclude: - 'lib/mysql2/em.rb' # Offense count: 2 -# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: - Max: 94 + Max: 89 # Offense count: 34 -# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. -# IgnoredMethods: refine +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +# AllowedMethods: refine Metrics/BlockLength: - Max: 592 + Max: 477 # Offense count: 1 -# Configuration parameters: CountBlocks. +# Configuration parameters: CountBlocks, CountModifierForms. Metrics/BlockNesting: Max: 5 @@ -48,34 +42,28 @@ Metrics/ClassLength: Max: 141 # Offense count: 3 -# Configuration parameters: IgnoredMethods. +# Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/CyclomaticComplexity: - Max: 34 + Max: 32 # Offense count: 6 -# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Max: 57 + Max: 52 # Offense count: 2 -# Configuration parameters: IgnoredMethods. +# Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: - Max: 32 + Max: 30 # Offense count: 2 # Configuration parameters: ForbiddenDelimiters. -# ForbiddenDelimiters: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) +# ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$)) Naming/HeredocDelimiterNaming: Exclude: - 'tasks/compile.rake' -# Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/CaseLikeIf: - Exclude: - - 'ext/mysql2/extconf.rb' - -# Offense count: 8 +# Offense count: 9 # Configuration parameters: AllowedConstants. Style/Documentation: Exclude: @@ -100,13 +88,13 @@ Style/ExpandPathArguments: - 'support/mysql_enc_to_ruby.rb' - 'tasks/compile.rake' -# Offense count: 15 +# Offense count: 17 # Configuration parameters: AllowedVariables. Style/GlobalVars: Exclude: - 'ext/mysql2/extconf.rb' -# Offense count: 8 +# Offense count: 7 # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Exclude: @@ -116,14 +104,14 @@ Style/IfUnlessModifier: # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowMethodComparison. +# Configuration parameters: AllowMethodComparison, ComparisonsThreshold. Style/MultipleComparison: Exclude: - 'lib/mysql2/client.rb' -# Offense count: 18 +# Offense count: 24 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Strict, AllowedNumbers. +# Configuration parameters: Strict, AllowedNumbers, AllowedPatterns. Style/NumericLiterals: MinDigits: 20 @@ -144,7 +132,7 @@ Style/StringConcatenation: - 'lib/mysql2/client.rb' - 'tasks/compile.rake' -# Offense count: 782 +# Offense count: 805 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes @@ -159,9 +147,9 @@ Style/WordArray: EnforcedStyle: percent MinSize: 4 -# Offense count: 32 +# Offense count: 43 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns. +# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. # URISchemes: http, https Layout/LineLength: Max: 232 diff --git a/Gemfile b/Gemfile index abf415624..b8335fd20 100644 --- a/Gemfile +++ b/Gemfile @@ -16,9 +16,7 @@ group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' - # https://github.com/bbatsov/rubocop/pull/4789 - # 1.51 is the last version supporting Ruby 2.6 - gem 'rubocop', '>= 1.30.1', '< 1.51' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') + gem 'rubocop' end group :benchmarks, optional: true do diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 147b998b9..449147298 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -113,7 +113,7 @@ def add_ssl_defines(header) warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" rpath_dir = lib have_library('mysqlclient') -elsif (mc = (with_config('mysql-config') || Dir[GLOB].first)) +elsif (mc = with_config('mysql-config') || Dir[GLOB].first) # If the user has provided a --with-mysql-config argument, we must respect it or fail. # If the user gave --with-mysql-config with no argument means we should try to find it. mc = Dir[GLOB].first if mc == true From b009d7e114729cbae5bef069a1033dd78acf7745 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 22 Oct 2025 20:49:16 +0200 Subject: [PATCH 783/783] Fix Client#affected_rows for queries with no affected rows (#1417) Fix: https://github.com/brianmario/mysql2/issues/1414 e.g. `SELECT sleep(0.1)` or `SELECT 1`. --- ext/mysql2/client.c | 5 +++-- spec/mysql2/client_spec.rb | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 693def4f6..10e0c9253 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -673,7 +673,6 @@ static VALUE rb_mysql_client_async_result(VALUE self) { wrapper->active_fiber = Qnil; rb_raise_mysql2_error(wrapper); } - wrapper->affected_rows = mysql_affected_rows(wrapper->client); is_streaming = rb_hash_aref(rb_ivar_get(self, intern_current_query_options), sym_stream); if (is_streaming == Qtrue) { @@ -682,6 +681,8 @@ static VALUE rb_mysql_client_async_result(VALUE self) { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); } + wrapper->affected_rows = mysql_affected_rows(wrapper->client); + if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { wrapper->active_fiber = Qnil; @@ -1165,7 +1166,7 @@ static VALUE rb_mysql_client_affected_rows(VALUE self) { REQUIRE_CONNECTED(wrapper); retVal = wrapper->affected_rows; - if (retVal == (uint64_t)-1) { + if (retVal == (my_ulonglong)-1) { rb_raise_mysql2_error(wrapper); } return ULL2NUM(retVal); diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 34a8f1166..ad7d25323 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1093,6 +1093,11 @@ def run_gc end end + it "#affected_rows when no rows were affected returns 1" do + @client.query "SELECT sleep(0.01)" + expect(@client.affected_rows).to eq(1) + end + it "should respond to #thread_id" do expect(@client).to respond_to(:thread_id) end