├── spec ├── test_data ├── rcov.opts ├── my.cnf.example ├── configuration.yml.example ├── ssl │ ├── ca.cnf │ ├── cert.cnf │ ├── client-req.pem │ ├── server-req.pem │ ├── client-cert.pem │ ├── server-cert.pem │ ├── ca-cert.pem │ ├── ca-key.pem │ ├── client-key.pem │ ├── server-key.pem │ ├── pkcs8-client-key.pem │ ├── pkcs8-server-key.pem │ └── gen_certs.sh ├── mysql2 │ ├── error_spec.rb │ └── result_spec.rb ├── em │ └── em_spec.rb └── spec_helper.rb ├── .rspec ├── lib ├── mysql2 │ ├── version.rb │ ├── field.rb │ ├── result.rb │ ├── console.rb │ ├── statement.rb │ ├── em.rb │ ├── error.rb │ └── client.rb └── mysql2.rb ├── ext └── mysql2 │ ├── infile.h │ ├── statement.h │ ├── mysql2_ext.c │ ├── result.h │ ├── client.h │ ├── wait_for_single_fd.h │ ├── mysql2_ext.h │ ├── infile.c │ ├── mysql_enc_to_ruby.h │ ├── mysql_enc_name_to_ruby.h │ ├── extconf.rb │ └── statement.c ├── CHANGELOG.md ├── .dockerignore ├── script ├── bootstrap └── console ├── tasks ├── generate.rake ├── benchmarks.rake ├── rspec.rake ├── vendor_mysql.rake └── compile.rake ├── .gitignore ├── ci ├── mysql55.sh ├── container.sh ├── mysql84.sh ├── mysql57.sh ├── mysql80.sh ├── Dockerfile_fedora ├── setup_container.sh ├── mariadb114.sh ├── mariadb1011.sh ├── ssl.sh ├── mariadb106.sh ├── Dockerfile_centos └── setup.sh ├── Rakefile ├── .github └── workflows │ ├── rubocop.yml │ ├── container.yml │ └── build.yml ├── examples ├── eventmachine.rb └── threaded.rb ├── benchmark ├── active_record_threaded.rb ├── active_record.rb ├── escape.rb ├── allocations.rb ├── sequel.rb ├── query_without_mysql_casting.rb ├── query_with_mysql_casting.rb └── setup_db.rb ├── .rubocop.yml ├── Gemfile ├── LICENSE ├── mysql2.gemspec ├── support ├── ruby_enc_to_mysql.rb ├── mysql_enc_to_ruby.rb ├── 3A79BD29.asc ├── B7B3B788A8D3785C.asc ├── libmysql.def └── C74CD1D8.asc └── .rubocop_todo.yml /spec/test_data: -------------------------------------------------------------------------------- 1 | \N Hello World 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --format documentation 3 | --order rand 4 | --warnings 5 | -------------------------------------------------------------------------------- /lib/mysql2/version.rb: -------------------------------------------------------------------------------- 1 | module Mysql2 2 | VERSION = "0.5.7".freeze 3 | end 4 | -------------------------------------------------------------------------------- /ext/mysql2/infile.h: -------------------------------------------------------------------------------- 1 | void mysql2_set_local_infile(MYSQL *mysql, void *userdata); 2 | -------------------------------------------------------------------------------- /lib/mysql2/field.rb: -------------------------------------------------------------------------------- 1 | module Mysql2 2 | Field = Struct.new(:name, :type) 3 | end 4 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude spec,gem 2 | --text-summary 3 | --sort coverage --sort-reverse 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changes are maintained under [Releases](https://github.com/brianmario/mysql2/releases) 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | *.bundle 2 | Gemfile.lock 3 | spec/configuration.yml 4 | spec/my.cnf 5 | tmp 6 | vendor 7 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$(dirname "$0")/.." 5 | exec bundle install --binstubs --path vendor/gems "$@" 6 | -------------------------------------------------------------------------------- /lib/mysql2/result.rb: -------------------------------------------------------------------------------- 1 | module Mysql2 2 | class Result 3 | attr_reader :server_flags 4 | 5 | include Enumerable 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /script/console: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run a Ruby REPL. 3 | 4 | set -e 5 | 6 | cd $(dirname "$0")/.. 7 | exec ruby -S bin/pry -Ilib -r mysql2 -r mysql2/console 8 | -------------------------------------------------------------------------------- /lib/mysql2/console.rb: -------------------------------------------------------------------------------- 1 | # Loaded by script/console. Land helpers here. 2 | 3 | Pry.config.prompt = lambda do |context, *| 4 | "[mysql2] #{context}> " 5 | end 6 | -------------------------------------------------------------------------------- /spec/my.cnf.example: -------------------------------------------------------------------------------- 1 | [root] 2 | host=localhost 3 | user=LOCALUSERNAME 4 | password= 5 | 6 | [client] 7 | host=localhost 8 | user=LOCALUSERNAME 9 | password= 10 | -------------------------------------------------------------------------------- /tasks/generate.rake: -------------------------------------------------------------------------------- 1 | task :encodings do 2 | sh "ruby support/mysql_enc_to_ruby.rb > ./ext/mysql2/mysql_enc_to_ruby.h" 3 | sh "ruby support/ruby_enc_to_mysql.rb | gperf > ./ext/mysql2/mysql_enc_name_to_ruby.h" 4 | end 5 | -------------------------------------------------------------------------------- /spec/configuration.yml.example: -------------------------------------------------------------------------------- 1 | root: 2 | host: localhost 3 | username: root 4 | password: 5 | database: test 6 | 7 | user: 8 | host: localhost 9 | username: LOCALUSERNAME 10 | password: 11 | database: mysql2_test 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | Makefile 3 | *.dSYM 4 | *.o 5 | *.bundle 6 | *.so 7 | *.a 8 | *.rbc 9 | mkmf.log 10 | pkg/ 11 | tmp 12 | vendor 13 | lib/mysql2/mysql2.rb 14 | spec/configuration.yml 15 | spec/my.cnf 16 | Gemfile.lock 17 | .ruby-version 18 | .rvmrc 19 | -------------------------------------------------------------------------------- /lib/mysql2/statement.rb: -------------------------------------------------------------------------------- 1 | module Mysql2 2 | class Statement 3 | def execute(*args, **kwargs) 4 | Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do 5 | _execute(*args, **kwargs) 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ci/mysql55.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | apt-get purge -qq '^mysql*' '^libmysql*' 6 | rm -fr /etc/mysql 7 | rm -fr /var/lib/mysql 8 | apt-get update -qq 9 | apt-get install -qq mysql-server-5.5 mysql-client-core-5.5 mysql-client-5.5 libmysqlclient-dev 10 | -------------------------------------------------------------------------------- /ci/container.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | ruby -v 6 | bundle install --path vendor/bundle --without development 7 | 8 | # Regenerate the SSL certification files from the specified host. 9 | if [ -n "${TEST_RUBY_MYSQL2_SSL_CERT_HOST}" ]; then 10 | pushd spec/ssl 11 | bash gen_certs.sh 12 | popd 13 | fi 14 | 15 | # Start mysqld service. 16 | bash ci/setup_container.sh 17 | 18 | bundle exec rake spec 19 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | 3 | # Load custom tasks (careful attention to define tasks before prerequisites) 4 | load 'tasks/vendor_mysql.rake' 5 | load 'tasks/rspec.rake' 6 | load 'tasks/compile.rake' 7 | load 'tasks/generate.rake' 8 | load 'tasks/benchmarks.rake' 9 | 10 | begin 11 | require 'rubocop/rake_task' 12 | RuboCop::RakeTask.new 13 | task default: %i[spec rubocop] 14 | rescue LoadError 15 | warn 'RuboCop is not available' 16 | task default: :spec 17 | end 18 | -------------------------------------------------------------------------------- /ext/mysql2/statement.h: -------------------------------------------------------------------------------- 1 | #ifndef MYSQL2_STATEMENT_H 2 | #define MYSQL2_STATEMENT_H 3 | 4 | typedef struct { 5 | VALUE client; 6 | MYSQL_STMT *stmt; 7 | int refcount; 8 | int closed; 9 | } mysql_stmt_wrapper; 10 | 11 | void init_mysql2_statement(void); 12 | void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper); 13 | 14 | VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); 15 | void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) RB_MYSQL_NORETURN; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /.github/workflows/rubocop.yml: -------------------------------------------------------------------------------- 1 | name: RuboCop 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | env: 9 | BUNDLE_WITHOUT: development 10 | steps: 11 | - uses: actions/checkout@v5 12 | - name: Set up Ruby 3.4 13 | uses: ruby/setup-ruby@v1 14 | with: 15 | ruby-version: 3.4 16 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 17 | - name: Run RuboCop 18 | run: bundle exec rubocop 19 | -------------------------------------------------------------------------------- /ci/mysql84.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | apt-get purge -qq '^mysql*' '^libmysql*' 6 | rm -fr /etc/mysql 7 | rm -fr /var/lib/mysql 8 | apt-key add support/B7B3B788A8D3785C.asc # 8.1 and higher 9 | # Verify the repository as add-apt-repository does not. 10 | wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-8.4-lts 11 | add-apt-repository 'http://repo.mysql.com/apt/ubuntu mysql-8.4-lts' 12 | apt-get update -qq 13 | apt-get install -qq mysql-server libmysqlclient-dev 14 | -------------------------------------------------------------------------------- /tasks/benchmarks.rake: -------------------------------------------------------------------------------- 1 | BENCHMARKS = Dir["#{File.dirname(__FILE__)}/../benchmark/*.rb"].map do |path| 2 | File.basename(path, '.rb') 3 | end - ['setup_db'] 4 | 5 | namespace :bench do 6 | BENCHMARKS.each do |feature| 7 | desc "Run #{feature} benchmarks" 8 | task(feature) { ruby "benchmark/#{feature}.rb" } 9 | end 10 | 11 | task :all do 12 | BENCHMARKS.each do |feature| 13 | ruby "benchmark/#{feature}.rb" 14 | end 15 | end 16 | 17 | task :setup do 18 | ruby 'benchmark/setup_db' 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /examples/eventmachine.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift 'lib' 2 | 3 | require 'rubygems' 4 | require 'eventmachine' 5 | require 'mysql2/em' 6 | 7 | EM.run do 8 | client1 = Mysql2::EM::Client.new 9 | defer1 = client1.query "SELECT sleep(3) as first_query" 10 | defer1.callback do |result| 11 | puts "Result: #{result.to_a.inspect}" 12 | end 13 | 14 | client2 = Mysql2::EM::Client.new 15 | defer2 = client2.query "SELECT sleep(1) second_query" 16 | defer2.callback do |result| 17 | puts "Result: #{result.to_a.inspect}" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /ext/mysql2/mysql2_ext.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | VALUE mMysql2, cMysql2Error, cMysql2TimeoutError; 4 | 5 | /* Ruby Extension initializer */ 6 | void Init_mysql2() { 7 | mMysql2 = rb_define_module("Mysql2"); 8 | rb_global_variable(&mMysql2); 9 | 10 | cMysql2Error = rb_const_get(mMysql2, rb_intern("Error")); 11 | rb_global_variable(&cMysql2Error); 12 | 13 | cMysql2TimeoutError = rb_const_get(cMysql2Error, rb_intern("TimeoutError")); 14 | rb_global_variable(&cMysql2TimeoutError); 15 | 16 | init_mysql2_client(); 17 | init_mysql2_result(); 18 | init_mysql2_statement(); 19 | } 20 | -------------------------------------------------------------------------------- /ci/mysql57.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | apt-get purge -qq '^mysql*' '^libmysql*' 6 | rm -fr /etc/mysql 7 | rm -fr /var/lib/mysql 8 | apt-key add support/5072E1F5.asc # old signing key 9 | apt-key add support/3A79BD29.asc # 5.7.37 and higher 10 | apt-key add support/B7B3B788A8D3785C.asc # 8.1 and higher 11 | # Verify the repository as add-apt-repository does not. 12 | wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-5.7 13 | add-apt-repository 'http://repo.mysql.com/apt/ubuntu mysql-5.7' 14 | apt-get update -qq 15 | apt-get install -qq mysql-server libmysqlclient-dev 16 | -------------------------------------------------------------------------------- /ci/mysql80.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | apt-get purge -qq '^mysql*' '^libmysql*' 6 | rm -fr /etc/mysql 7 | rm -fr /var/lib/mysql 8 | apt-key add support/5072E1F5.asc # old signing key 9 | apt-key add support/3A79BD29.asc # 8.0.28 and higher 10 | apt-key add support/B7B3B788A8D3785C.asc # 8.1 and higher 11 | # Verify the repository as add-apt-repository does not. 12 | wget -q --spider http://repo.mysql.com/apt/ubuntu/dists/$(lsb_release -cs)/mysql-8.0 13 | add-apt-repository 'http://repo.mysql.com/apt/ubuntu mysql-8.0' 14 | apt-get update -qq 15 | apt-get install -qq mysql-server libmysqlclient-dev 16 | -------------------------------------------------------------------------------- /examples/threaded.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift 'lib' 2 | require 'mysql2' 3 | require 'timeout' 4 | 5 | # Should never exceed worst case 3.5 secs across all 20 threads 6 | Timeout.timeout(3.5) do 7 | Array.new(20) do 8 | Thread.new do 9 | overhead = rand(3) 10 | puts ">> thread #{Thread.current.object_id} query, #{overhead} sec overhead" 11 | # 3 second overhead per query 12 | Mysql2::Client.new(host: "localhost", username: "root").query("SELECT sleep(#{overhead}) as result") 13 | puts "<< thread #{Thread.current.object_id} result, #{overhead} sec overhead" 14 | end 15 | end.each(&:join) 16 | end 17 | -------------------------------------------------------------------------------- /ci/Dockerfile_fedora: -------------------------------------------------------------------------------- 1 | ARG IMAGE=fedora:latest 2 | FROM ${IMAGE} 3 | 4 | WORKDIR /build 5 | COPY . . 6 | 7 | RUN cat /etc/fedora-release 8 | RUN dnf -yq update 9 | # The options are to install faster. 10 | RUN dnf -yq install \ 11 | --setopt=deltarpm=0 \ 12 | --setopt=install_weak_deps=false \ 13 | --setopt=tsflags=nodocs \ 14 | gcc \ 15 | gcc-c++ \ 16 | git \ 17 | libyaml-devel \ 18 | make \ 19 | mariadb-connector-c-devel \ 20 | mariadb-server \ 21 | openssl \ 22 | redhat-rpm-config \ 23 | ruby-devel \ 24 | rubygem-bigdecimal \ 25 | rubygem-bundler \ 26 | rubygem-json 27 | 28 | CMD bash ci/container.sh 29 | -------------------------------------------------------------------------------- /ci/setup_container.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | MYSQL_TEST_LOG="$(pwd)/mysql.log" 6 | 7 | bash ci/ssl.sh 8 | 9 | mysql_install_db \ 10 | --log-error="${MYSQL_TEST_LOG}" 11 | /usr/libexec/mysqld \ 12 | --user="$(id -un)" \ 13 | --log-error="${MYSQL_TEST_LOG}" \ 14 | --ssl & 15 | sleep 3 16 | cat ${MYSQL_TEST_LOG} 17 | 18 | /usr/libexec/mysqld --version 19 | 20 | mysql -u root < /etc/apt/sources.list.d/mariadb.sources 12 | X-Repolib-Name: MariaDB 13 | Types: deb 14 | # URIs: https://deb.mariadb.org/$VERSION/ubuntu 15 | URIs: https://mirror.rackspace.com/mariadb/repo/$VERSION/ubuntu 16 | Suites: $RELEASE 17 | Components: main main/debug 18 | Signed-By: /etc/apt/keyrings/mariadb-keyring.asc 19 | EOF 20 | 21 | cp support/C74CD1D8.asc /etc/apt/keyrings/mariadb-keyring.asc 22 | apt update 23 | apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server libmariadb-dev 24 | -------------------------------------------------------------------------------- /ci/mariadb1011.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eux 3 | 4 | apt purge -qq '^mysql*' '^libmysql*' 5 | rm -fr /etc/mysql 6 | rm -fr /var/lib/mysql 7 | 8 | RELEASE=$(lsb_release -cs) 9 | VERSION=10.11 10 | 11 | tee <<- EOF > /etc/apt/sources.list.d/mariadb.sources 12 | X-Repolib-Name: MariaDB 13 | Types: deb 14 | # URIs: https://deb.mariadb.org/$VERSION/ubuntu 15 | URIs: https://mirror.rackspace.com/mariadb/repo/$VERSION/ubuntu 16 | Suites: $RELEASE 17 | Components: main main/debug 18 | Signed-By: /etc/apt/keyrings/mariadb-keyring.asc 19 | EOF 20 | 21 | cp support/C74CD1D8.asc /etc/apt/keyrings/mariadb-keyring.asc 22 | apt update 23 | apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server libmariadb-dev 24 | -------------------------------------------------------------------------------- /ci/ssl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | # TEST_RUBY_MYSQL2_SSL_CERT_DIR: custom SSL certs directory. 6 | SSL_CERT_DIR=${TEST_RUBY_MYSQL2_SSL_CERT_DIR:-/etc/mysql} 7 | 8 | # Make sure there is an /etc/mysql 9 | mkdir -p "${SSL_CERT_DIR}" 10 | 11 | # Copy the local certs to /etc/mysql 12 | cp spec/ssl/*pem "${SSL_CERT_DIR}" 13 | 14 | # Wherever MySQL configs live, go there (this is for cross-platform) 15 | cd $(my_print_defaults --help | grep my.cnf | xargs find 2>/dev/null | xargs dirname) 16 | 17 | # Put the configs into the server 18 | echo " 19 | [mysqld] 20 | ssl-ca=${SSL_CERT_DIR}/ca-cert.pem 21 | ssl-cert=${SSL_CERT_DIR}/server-cert.pem 22 | ssl-key=${SSL_CERT_DIR}/server-key.pem 23 | " >> my.cnf 24 | -------------------------------------------------------------------------------- /ci/mariadb106.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eux 3 | 4 | apt purge -qq '^mysql*' '^libmysql*' 5 | rm -fr /etc/mysql 6 | rm -fr /var/lib/mysql 7 | 8 | RELEASE=$(lsb_release -cs) 9 | VERSION=10.6 10 | 11 | tee <<- EOF > /etc/apt/sources.list.d/mariadb.sources 12 | X-Repolib-Name: MariaDB 13 | Types: deb 14 | # URIs: https://deb.mariadb.org/$VERSION/ubuntu 15 | URIs: https://mirror.rackspace.com/mariadb/repo/$VERSION/ubuntu 16 | Suites: $RELEASE 17 | Components: main main/debug 18 | Signed-By: /etc/apt/keyrings/mariadb-keyring.asc 19 | EOF 20 | 21 | cp support/C74CD1D8.asc /etc/apt/keyrings/mariadb-keyring.asc 22 | apt update 23 | apt install -y -o Dpkg::Options::='--force-confnew' mariadb-server-$VERSION libmariadb-dev 24 | -------------------------------------------------------------------------------- /spec/ssl/ca.cnf: -------------------------------------------------------------------------------- 1 | 2 | [ ca ] 3 | # January 1, 2015 4 | default_startdate = 2015010360000Z 5 | 6 | [ req ] 7 | distinguished_name = req_distinguished_name 8 | 9 | # Root CA certificate extensions 10 | [ v3_ca ] 11 | basicConstraints = critical, CA:true 12 | 13 | [ req_distinguished_name ] 14 | # If this isn't set, the error is error, no objects specified in config file 15 | commonName = Common Name (hostname, IP, or your name) 16 | 17 | countryName_default = US 18 | stateOrProvinceName_default = CA 19 | localityName_default = San Francisco 20 | 0.organizationName_default = mysql2_gem 21 | organizationalUnitName_default = Mysql2Gem 22 | emailAddress_default = mysql2gem@example.com 23 | 24 | 25 | commonName_default = ca_mysql2gem 26 | 27 | -------------------------------------------------------------------------------- /spec/ssl/cert.cnf: -------------------------------------------------------------------------------- 1 | 2 | [ ca ] 3 | # January 1, 2015 4 | default_startdate = 2015010360000Z 5 | 6 | [ req ] 7 | distinguished_name = req_distinguished_name 8 | 9 | # Root CA certificate extensions 10 | [ v3_ca ] 11 | basicConstraints = critical, CA:true 12 | 13 | [ req_distinguished_name ] 14 | # If this isn't set, the error is error, no objects specified in config file 15 | commonName = Common Name (hostname, IP, or your name) 16 | 17 | countryName_default = US 18 | stateOrProvinceName_default = CA 19 | localityName_default = San Francisco 20 | 0.organizationName_default = mysql2_gem 21 | organizationalUnitName_default = Mysql2Gem 22 | emailAddress_default = mysql2gem@example.com 23 | 24 | 25 | commonName_default = mysql2gem.example.com 26 | 27 | -------------------------------------------------------------------------------- /benchmark/active_record.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') 2 | 3 | require 'rubygems' 4 | require 'benchmark/ips' 5 | require 'active_record' 6 | 7 | ActiveRecord::Base.default_timezone = :local 8 | ActiveRecord::Base.time_zone_aware_attributes = true 9 | 10 | opts = { database: 'test' } 11 | 12 | class TestModel < ActiveRecord::Base 13 | self.table_name = 'mysql2_test' 14 | end 15 | 16 | batch_size = 1000 17 | 18 | Benchmark.ips do |x| 19 | %w[mysql mysql2].each do |adapter| 20 | TestModel.establish_connection(opts.merge(adapter: adapter)) 21 | 22 | x.report(adapter) do 23 | TestModel.limit(batch_size).to_a.each do |r| 24 | r.attributes.each_key do |k| 25 | r.send(k.to_sym) 26 | end 27 | end 28 | end 29 | end 30 | 31 | x.compare! 32 | end 33 | -------------------------------------------------------------------------------- /ext/mysql2/result.h: -------------------------------------------------------------------------------- 1 | #ifndef MYSQL2_RESULT_H 2 | #define MYSQL2_RESULT_H 3 | 4 | void init_mysql2_result(void); 5 | VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement); 6 | 7 | typedef struct { 8 | VALUE fields; 9 | VALUE fieldTypes; 10 | VALUE rows; 11 | VALUE client; 12 | VALUE encoding; 13 | VALUE statement; 14 | my_ulonglong numberOfFields; 15 | my_ulonglong numberOfRows; 16 | unsigned long lastRowProcessed; 17 | char is_streaming; 18 | char streamingComplete; 19 | char resultFreed; 20 | MYSQL_RES *result; 21 | mysql_stmt_wrapper *stmt_wrapper; 22 | mysql_client_wrapper *client_wrapper; 23 | /* statement result bind buffers */ 24 | MYSQL_BIND *result_buffers; 25 | my_bool *is_null; 26 | my_bool *error; 27 | unsigned long *length; 28 | } mysql2_result_wrapper; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | TargetRubyVersion: 2.0 5 | SuggestExtensions: false 6 | NewCops: disable 7 | 8 | DisplayCopNames: true 9 | Exclude: 10 | - 'pkg/**/*' 11 | - 'tmp/**/*' 12 | - 'vendor/**/*' 13 | 14 | Layout/CaseIndentation: 15 | EnforcedStyle: end 16 | 17 | Layout/FirstHashElementIndentation: 18 | EnforcedStyle: consistent 19 | 20 | Layout/EndAlignment: 21 | EnforcedStyleAlignWith: variable 22 | 23 | Layout/HashAlignment: 24 | EnforcedHashRocketStyle: table 25 | 26 | Style/TrailingCommaInArguments: 27 | EnforcedStyleForMultiline: consistent_comma 28 | 29 | Style/TrailingCommaInArrayLiteral: 30 | EnforcedStyleForMultiline: consistent_comma 31 | 32 | Style/TrailingCommaInHashLiteral: 33 | EnforcedStyleForMultiline: consistent_comma 34 | 35 | Style/TrivialAccessors: 36 | AllowPredicates: true 37 | -------------------------------------------------------------------------------- /benchmark/escape.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') 2 | 3 | require 'rubygems' 4 | require 'benchmark/ips' 5 | require 'mysql' 6 | require 'mysql2' 7 | require 'do_mysql' 8 | 9 | def run_escape_benchmarks(str) 10 | Benchmark.ips do |x| 11 | mysql = Mysql.new("localhost", "root") 12 | 13 | x.report "Mysql #{str.inspect}" do 14 | mysql.quote str 15 | end 16 | 17 | mysql2 = Mysql2::Client.new(host: "localhost", username: "root") 18 | x.report "Mysql2 #{str.inspect}" do 19 | mysql2.escape str 20 | end 21 | 22 | do_mysql = DataObjects::Connection.new("mysql://localhost/test") 23 | x.report "do_mysql #{str.inspect}" do 24 | do_mysql.quote_string str 25 | end 26 | 27 | x.compare! 28 | end 29 | end 30 | 31 | run_escape_benchmarks "abc'def\"ghi\0jkl%mno" 32 | run_escape_benchmarks "clean string" 33 | -------------------------------------------------------------------------------- /benchmark/allocations.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') 2 | 3 | require 'rubygems' 4 | require 'active_record' 5 | 6 | ActiveRecord::Base.default_timezone = :local 7 | ActiveRecord::Base.time_zone_aware_attributes = true 8 | 9 | class TestModel < ActiveRecord::Base 10 | self.table_name = 'mysql2_test' 11 | end 12 | 13 | def bench_allocations(feature, iterations = 10, batch_size = 1000) 14 | puts "GC overhead for #{feature}" 15 | TestModel.establish_connection(adapter: 'mysql2', database: 'test') 16 | GC::Profiler.clear 17 | GC::Profiler.enable 18 | iterations.times { yield batch_size } 19 | GC::Profiler.report($stdout) 20 | GC::Profiler.disable 21 | end 22 | 23 | bench_allocations('coercion') do |batch_size| 24 | TestModel.limit(batch_size).to_a.each do |r| 25 | r.attributes.each_key do |k| 26 | r.send(k.to_sym) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /benchmark/sequel.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') 2 | 3 | require 'rubygems' 4 | require 'benchmark/ips' 5 | require 'mysql2' 6 | require 'sequel' 7 | require 'sequel/adapters/do' 8 | 9 | mysql2_opts = "mysql2://root@localhost/test" 10 | mysql_opts = "mysql://root@localhost/test" 11 | do_mysql_opts = "do:mysql://root@localhost/test" 12 | 13 | class Mysql2Model < Sequel::Model(Sequel.connect(mysql2_opts)[:mysql2_test]); end 14 | class MysqlModel < Sequel::Model(Sequel.connect(mysql_opts)[:mysql2_test]); end 15 | class DOMysqlModel < Sequel::Model(Sequel.connect(do_mysql_opts)[:mysql2_test]); end 16 | 17 | Benchmark.ips do |x| 18 | x.report "Mysql2" do 19 | Mysql2Model.limit(1000).all 20 | end 21 | 22 | x.report "do:mysql" do 23 | DOMysqlModel.limit(1000).all 24 | end 25 | 26 | x.report "Mysql" do 27 | MysqlModel.limit(1000).all 28 | end 29 | 30 | x.compare! 31 | end 32 | -------------------------------------------------------------------------------- /spec/ssl/client-req.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB 3 | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnaBhN4wLvmzVFg91HZH2Pmac 4 | ZVpb3I51qoiGQS7i8oiLAyvqCIjJVoCGCWfBk8s6WNAPiE9pqvKyUXTfpvZBlW9t 5 | 4hwQImRD15Sr55/yWWnKO7SwHhY8BTeoQVlSBrfPnKMIjByudi/G7fR9naXoeQRE 6 | AP2hl8h/gh7UBAm6kNTFxxD/1Byal/iWBwoYqQQYVUuYThdI0C17qYn7dthtjjGR 7 | bexouVRwN446qR9dC71A27VgNUmlFb/EygmfEFPhJRNf9fSVAABkNHPhHtg5tURj 8 | cZi9ldZMr8o6643hY1kW1xyS3VFLIcP62Cing0f0/Cjh5jg4l5uEcN/AUpbw+QID 9 | AQABoAAwDQYJKoZIhvcNAQELBQADggEBACPgLE417R3dHf9eiwVtoOSzm8ltNBbz 10 | 5dRqiHDMEXuH+aGiNtTI1BI9akXrgjyN+nXWK09jZsWJ/+8mj+NcGS8JfdQdVn0c 11 | Ov/kmxoVwNEzj3mboL0amM7kQv6zRa1hKk7l5ZE+5G/EvWU2xF0qrHvGLvphgL0D 12 | +/PBJomMHAMYF5MUEOLtnwdslMS0OyAQCHu/swDhIj8jSCkoz4M8gpB2cV+VOHCG 13 | hW2sxlF67cgXLBXCgU0TP6bZbByPb8pEVax2808+wl3fCiM2IwcRtM3WTkSbESuN 14 | CjFO7OeUq4o1Dtiw2uBfNpe+dviGvDeyIBfUmkzqJH3BBpQyGHWU9YE= 15 | -----END CERTIFICATE REQUEST----- 16 | -------------------------------------------------------------------------------- /spec/ssl/server-req.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB 3 | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq72O8qvgU/WUj/cjnQFX4Mdd 4 | n95cHDv5uhfkf2ftG1Ur1bW2IjtcZ/t5fK8/ME+vnwVVayMabqRVD2WpW9AT9O45 5 | qoQu2IAY4GndL5FKYdaXgifp7SWLYsaQGTmY+9Gk/9gio7hpJlhZy68q8QZqaZR3 6 | mtQcCyba1oEBCXGhaoidZWxo4p7naPHWObWe4ssmfnZPPkyg7Ff9acrgDnpimA2N 7 | +I1H9SxOduaUkfwXAxmAnc6QbDi7KkSn/uXfHGStOSOqGGCpAMKDN0MJsKS0pvaa 8 | BVJaPN7jA1ORR++oVhcJHeTxPKEm3SnKendADIfy9E0ulfZRjOfI1vYO9f79iwID 9 | AQABoAAwDQYJKoZIhvcNAQELBQADggEBAIAzuYloX5Pwi+5n73yhaw5V+jMABiuw 10 | rYI2LziLBqw4vuvjEqvyr80Y9H2fLHOfVRaFnU6PaMkkH/p8d8YBD4/ZRGSd3oHX 11 | hlNltqyTCx1LNIUCXkl18jfPK3sVwwC07cLqSxQP8diauaDE59F6lsP3L0Gbwntd 12 | brSVJcY+5JGuWMx2BDXECxu6E7D/8drvPQa6EXDJjk1WVF/69I3TWhfX4/a5zIrc 13 | bJDTRBl5yQA0dpPmr/d8Di4mqAqeecVPNXi/CWkDQl3PoBp7O69T6VG3R00krgQr 14 | rXzbPJsEDLm7ynu/TWMamSCOUiMH5CBVBVXJSTVevGFK+gdjXf8LJ/0= 15 | -----END CERTIFICATE REQUEST----- 16 | -------------------------------------------------------------------------------- /ext/mysql2/client.h: -------------------------------------------------------------------------------- 1 | #ifndef MYSQL2_CLIENT_H 2 | #define MYSQL2_CLIENT_H 3 | 4 | typedef struct { 5 | VALUE encoding; 6 | VALUE active_fiber; /* rb_fiber_current() or Qnil */ 7 | long server_version; 8 | int reconnect_enabled; 9 | unsigned int connect_timeout; 10 | int active; 11 | int automatic_close; 12 | int initialized; 13 | int refcount; 14 | int closed; 15 | uint64_t affected_rows; 16 | MYSQL *client; 17 | } mysql_client_wrapper; 18 | 19 | void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result); 20 | 21 | extern const rb_data_type_t rb_mysql_client_type; 22 | 23 | #ifdef NEW_TYPEDDATA_WRAPPER 24 | #define GET_CLIENT(self) \ 25 | mysql_client_wrapper *wrapper; \ 26 | TypedData_Get_Struct(self, mysql_client_wrapper, &rb_mysql_client_type, wrapper); 27 | #else 28 | #define GET_CLIENT(self) \ 29 | mysql_client_wrapper *wrapper; \ 30 | Data_Get_Struct(self, mysql_client_wrapper, wrapper); 31 | #endif 32 | 33 | void init_mysql2_client(void); 34 | void decr_mysql2_client(mysql_client_wrapper *wrapper); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /spec/ssl/client-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy 3 | Z2VtMB4XDTI0MDIwOTE1NDkyOFoXDTMzMTIxODE1NDkyOFowIDEeMBwGA1UEAwwV 4 | bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 5 | CgKCAQEAnaBhN4wLvmzVFg91HZH2PmacZVpb3I51qoiGQS7i8oiLAyvqCIjJVoCG 6 | CWfBk8s6WNAPiE9pqvKyUXTfpvZBlW9t4hwQImRD15Sr55/yWWnKO7SwHhY8BTeo 7 | QVlSBrfPnKMIjByudi/G7fR9naXoeQREAP2hl8h/gh7UBAm6kNTFxxD/1Byal/iW 8 | BwoYqQQYVUuYThdI0C17qYn7dthtjjGRbexouVRwN446qR9dC71A27VgNUmlFb/E 9 | ygmfEFPhJRNf9fSVAABkNHPhHtg5tURjcZi9ldZMr8o6643hY1kW1xyS3VFLIcP6 10 | 2Cing0f0/Cjh5jg4l5uEcN/AUpbw+QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA2 11 | 4Hh2Qf3eDcKZCvjHmwgCXkhFpZtgpBTgEyPfl0UKwrgkdBiqGY6jywCIsDvp0DrN 12 | OL7Ybk+tdDE95HUfC4W7MBa2wS5WCksIjq+8N4Q61np8gine01IUofjeBSlkNgkF 13 | phtOVeadCky3UlBBGXBSwaPC+uyHlXEOlmfm1YOP0QboqzMorEl4ZECxFVtkyKbu 14 | tud9BDitIcb7x2JjrLlmnltE3JQ+WI9iyL0EAXDloIPUkeyf123lFGNSXDCEj2Ru 15 | 8wzQjNDGYiMrwYFib+6d0UE1ED2KLn7TN5lHhgC/H3RUv3dAIgciuysqepTC25DV 16 | soLMzebCsw4UprXFCUbz 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /spec/ssl/server-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy 3 | Z2VtMB4XDTI0MDIwOTE1NDkyOFoXDTMzMTIxODE1NDkyOFowIDEeMBwGA1UEAwwV 4 | bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 5 | CgKCAQEAq72O8qvgU/WUj/cjnQFX4Mddn95cHDv5uhfkf2ftG1Ur1bW2IjtcZ/t5 6 | fK8/ME+vnwVVayMabqRVD2WpW9AT9O45qoQu2IAY4GndL5FKYdaXgifp7SWLYsaQ 7 | GTmY+9Gk/9gio7hpJlhZy68q8QZqaZR3mtQcCyba1oEBCXGhaoidZWxo4p7naPHW 8 | ObWe4ssmfnZPPkyg7Ff9acrgDnpimA2N+I1H9SxOduaUkfwXAxmAnc6QbDi7KkSn 9 | /uXfHGStOSOqGGCpAMKDN0MJsKS0pvaaBVJaPN7jA1ORR++oVhcJHeTxPKEm3SnK 10 | endADIfy9E0ulfZRjOfI1vYO9f79iwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBc 11 | sRGEk10OWCm8MlfyWLmj3dAokO/LC1Ya6wP9gCtepvkum4hISKFmJpLYokUXpyOa 12 | GnUJ96eyHVg5OKz2r1rEra2E6oiP6FW6WCe8tVQEfsV6B7LkJ0O2X5us1kY+gmo6 13 | ch2/BDWROhjV5LgSPkuCNfNS2mkKo0vEg3xovYBNlqBveyrRnkPcR1qANt3RV3JR 14 | ADfqPGNU6IFoKiFZhK5wjwjUl2p1a12aw6C3e/O2UeJDsSEucN6yjEa/KZhlBfpH 15 | 4RSSRpSuWeGu2ndSaVwFYX44//v8vnW7+pFDWB0LFbv+Jd9qji0chaFWh3jJKNas 16 | vALqzCG44enapVb/7m4i 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /ext/mysql2/wait_for_single_fd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * backwards compatibility for Rubinius. See 3 | * https://github.com/rubinius/rubinius/issues/3771. 4 | * 5 | * Ruby 1.9.3 provides this API which allows the use of ppoll() on Linux 6 | * to minimize select() and malloc() overhead on high-numbered FDs. 7 | */ 8 | #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD 9 | # include 10 | #else 11 | # define RB_WAITFD_IN 0x001 12 | # define RB_WAITFD_PRI 0x002 13 | # define RB_WAITFD_OUT 0x004 14 | 15 | static int my_wait_for_single_fd(int fd, int events, struct timeval *tvp) 16 | { 17 | fd_set fdset; 18 | fd_set *rfds = NULL; 19 | fd_set *wfds = NULL; 20 | fd_set *efds = NULL; 21 | 22 | FD_ZERO(&fdset); 23 | FD_SET(fd, &fdset); 24 | 25 | if (events & RB_WAITFD_IN) 26 | rfds = &fdset; 27 | if (events & RB_WAITFD_OUT) 28 | wfds = &fdset; 29 | if (events & RB_WAITFD_PRI) 30 | efds = &fdset; 31 | 32 | return rb_thread_select(fd + 1, rfds, wfds, efds, tvp); 33 | } 34 | 35 | #define rb_wait_for_single_fd(fd,events,tvp) \ 36 | my_wait_for_single_fd((fd),(events),(tvp)) 37 | #endif 38 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2") 6 | gem 'rake', '~> 13.0' 7 | else 8 | gem 'rake', '< 13' 9 | end 10 | gem 'rake-compiler', '~> 1.2.0' 11 | 12 | # For local debugging, irb is Gemified since Ruby 2.6 13 | gem 'irb', require: false 14 | 15 | group :test do 16 | gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ 17 | gem 'rspec', '~> 3.2' 18 | 19 | gem 'rubocop' 20 | end 21 | 22 | group :benchmarks, optional: true do 23 | gem 'activerecord', '>= 3.0' 24 | gem 'benchmark-ips' 25 | gem 'do_mysql' 26 | gem 'faker' 27 | # The installation of the mysql latest version 2.9.1 fails on Ruby >= 2.4. 28 | gem 'mysql' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4') 29 | gem 'sequel' 30 | end 31 | 32 | group :development do 33 | gem 'pry' 34 | gem 'rake-compiler-dock', '~> 0.7.0' 35 | end 36 | 37 | # On MRI Ruby >= 3.0, rubysl-rake causes the conflict on GitHub Actions. 38 | # platforms :rbx do 39 | # gem 'rubysl-bigdecimal' 40 | # gem 'rubysl-drb' 41 | # gem 'rubysl-rake' 42 | # end 43 | -------------------------------------------------------------------------------- /ci/Dockerfile_centos: -------------------------------------------------------------------------------- 1 | ARG IMAGE=centos:7 2 | FROM ${IMAGE} 3 | 4 | WORKDIR /build 5 | COPY . . 6 | 7 | # mirrorlist.centos.org no longer exists, see 8 | # https://serverfault.com/questions/1161816/mirrorlist-centos-org-no-longer-resolve/1161847#1161847 9 | # 10 | # The --setopt flags to yum enable faster installs 11 | # 12 | RUN cat /etc/redhat-release \ 13 | && sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/CentOS-*.repo \ 14 | && sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/CentOS-*.repo \ 15 | && sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/CentOS-*.repo \ 16 | && yum -y -q update \ 17 | && yum -y -q install epel-release \ 18 | && yum -y -q install \ 19 | --setopt=deltarpm=0 \ 20 | --setopt=install_weak_deps=false \ 21 | --setopt=tsflags=nodocs \ 22 | gcc \ 23 | gcc-c++ \ 24 | git \ 25 | make \ 26 | mariadb-devel \ 27 | mariadb-server \ 28 | ruby-devel 29 | 30 | RUN gem install --no-document "rubygems-update:~>2.7" \ 31 | && update_rubygems > /dev/null \ 32 | && gem install --no-document "bundler:~>1.17" 33 | 34 | CMD bash ci/container.sh 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Brian Lopez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mysql2.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path('../lib/mysql2/version', __FILE__) 2 | 3 | Mysql2::GEMSPEC = Gem::Specification.new do |s| 4 | s.name = 'mysql2' 5 | s.version = Mysql2::VERSION 6 | s.authors = ['Brian Lopez', 'Aaron Stone'] 7 | s.license = "MIT" 8 | s.email = ['seniorlopez@gmail.com', 'aaron@serendipity.cx'] 9 | s.extensions = ["ext/mysql2/extconf.rb"] 10 | s.homepage = 'https://github.com/brianmario/mysql2' 11 | s.rdoc_options = ["--charset=UTF-8"] 12 | s.summary = 'A simple, fast Mysql library for Ruby, binding to libmysql' 13 | s.metadata = { 14 | 'bug_tracker_uri' => "#{s.homepage}/issues", 15 | 'changelog_uri' => "#{s.homepage}/releases/tag/#{s.version}", 16 | 'documentation_uri' => "https://www.rubydoc.info/gems/mysql2/#{s.version}", 17 | 'homepage_uri' => s.homepage, 18 | 'source_code_uri' => "#{s.homepage}/tree/#{s.version}", 19 | } 20 | s.required_ruby_version = '>= 2.0.0' 21 | 22 | s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split 23 | 24 | s.metadata['msys2_mingw_dependencies'] = 'libmariadbclient' 25 | 26 | s.add_dependency 'bigdecimal' 27 | end 28 | -------------------------------------------------------------------------------- /spec/ssl/ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC7jCCAdagAwIBAgIUf1RggahC+P3zuvdDnArIrPylegwwDQYJKoZIhvcNAQEL 3 | BQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwyZ2VtMB4XDTI0MDIwOTE1NDkyOFoXDTMz 4 | MTIxODE1NDkyOFowFzEVMBMGA1UEAwwMY2FfbXlzcWwyZ2VtMIIBIjANBgkqhkiG 5 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuEAFH4rhTMGs1bIHJgWvbsgNZBc2TrjPX0Jf 6 | 3kdHvq1u0bHuaYFUyY/yXoOchHbDvRYx1WL2jSkJuc2JMYN1V+j4EUtG9KAt4dqx 7 | LNTy6SxpQeMKEqtiTNc9aMR8cAxliSZSj/Qn6JpcSJPE/loIPdEC/MTo7ONcJ0xQ 8 | 5LymZqnuKZGw8L2UzZ+Zof3cYr2nPLoZDGtBsDDf5W184nl0MqTonu6/+raL/4C+ 9 | 3Smy/5IOcJzRfvw6Nc/bvi9eWkypNZzG3XaSO6K5d399KLn0mf9ZbXy5bq9klCUI 10 | seIhmA77vzaBOwdQUJKKijKGqlTahfoAiUV3AgcoxAnytKraVwIDAQABozIwMDAP 11 | BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBScsUBgsDMWnhPO1njE2uD6P7gr9TAN 12 | BgkqhkiG9w0BAQsFAAOCAQEAQbKVJ2eDt/99lnHGr3uFgVzWr26QomGfWysHhwHf 13 | 9pgS2KKbT7u/MGgWMi2jpXAJFCeRD2v/b5lRpeI03ZGuTJ0zqlItXzlBY6bND3KB 14 | AyJ5orfJu0NVwhjFZdnGH1IQVWjMW8pt8WzopYRyyfnqpbwE2e8wJUgOo9LDgJm8 15 | mK4bcpRVbvS2fo+g+CZ9HXzOXpL0m4gbsnPjeulmtSTXFX1/t00Hw+Gt2POB2A0h 16 | VNFKxS08uohPq49+MNeaTA0CjQdpG09lh7Cua/mSgzmYvWF9iYJpsBggzDUi7hab 17 | T07GzSz0fpfb35RAtsghgCyxaW7M3fAaztVJaKPDB4SfUw== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /lib/mysql2/em.rb: -------------------------------------------------------------------------------- 1 | require 'eventmachine' 2 | require 'mysql2' 3 | 4 | module Mysql2 5 | module EM 6 | class Client < ::Mysql2::Client 7 | module Watcher 8 | def initialize(client, deferable) 9 | @client = client 10 | @deferable = deferable 11 | @is_watching = true 12 | end 13 | 14 | def notify_readable 15 | detach 16 | begin 17 | result = @client.async_result 18 | rescue StandardError => e 19 | @deferable.fail(e) 20 | else 21 | @deferable.succeed(result) 22 | end 23 | end 24 | 25 | def watching? 26 | @is_watching 27 | end 28 | 29 | def unbind 30 | @is_watching = false 31 | end 32 | end 33 | 34 | def close(*args) 35 | @watch.detach if @watch && @watch.watching? 36 | 37 | super(*args) 38 | end 39 | 40 | def query(sql, opts = {}) 41 | if ::EM.reactor_running? 42 | super(sql, opts.merge(async: true)) 43 | deferable = ::EM::DefaultDeferrable.new 44 | @watch = ::EM.watch(socket, Watcher, self, deferable) 45 | @watch.notify_readable = true 46 | deferable 47 | else 48 | super(sql, opts) 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /benchmark/query_without_mysql_casting.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') 2 | 3 | require 'rubygems' 4 | require 'benchmark/ips' 5 | require 'mysql' 6 | require 'mysql2' 7 | require 'do_mysql' 8 | 9 | database = 'test' 10 | sql = "SELECT * FROM mysql2_test LIMIT 100" 11 | 12 | debug = ENV['DEBUG'] 13 | 14 | Benchmark.ips do |x| 15 | mysql2 = Mysql2::Client.new(host: "localhost", username: "root") 16 | mysql2.query "USE #{database}" 17 | x.report "Mysql2 (cast: true)" do 18 | mysql2_result = mysql2.query sql, symbolize_keys: true, cast: true 19 | mysql2_result.each { |res| puts res.inspect if debug } 20 | end 21 | 22 | x.report "Mysql2 (cast: false)" do 23 | mysql2_result = mysql2.query sql, symbolize_keys: true, cast: false 24 | mysql2_result.each { |res| puts res.inspect if debug } 25 | end 26 | 27 | mysql = Mysql.new("localhost", "root") 28 | mysql.query "USE #{database}" 29 | x.report "Mysql" do 30 | mysql_result = mysql.query sql 31 | mysql_result.each_hash { |res| puts res.inspect if debug } 32 | end 33 | 34 | do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") 35 | command = DataObjects::Mysql::Command.new do_mysql, sql 36 | x.report "do_mysql" do 37 | do_result = command.execute_reader 38 | do_result.each { |res| puts res.inspect if debug } 39 | end 40 | 41 | x.compare! 42 | end 43 | -------------------------------------------------------------------------------- /ext/mysql2/mysql2_ext.h: -------------------------------------------------------------------------------- 1 | #ifndef MYSQL2_EXT 2 | #define MYSQL2_EXT 3 | 4 | void Init_mysql2(void); 5 | 6 | /* tell rbx not to use it's caching compat layer 7 | by doing this we're making a promise to RBX that 8 | we'll never modify the pointers we get back from RSTRING_PTR */ 9 | #define RSTRING_NOT_MODIFIED 10 | #include 11 | 12 | #ifdef HAVE_MYSQL_H 13 | #include 14 | #include 15 | #else 16 | #include 17 | #include 18 | #endif 19 | 20 | #include 21 | #include 22 | 23 | #if defined(__GNUC__) && (__GNUC__ >= 3) 24 | #define RB_MYSQL_NORETURN __attribute__ ((noreturn)) 25 | #define RB_MYSQL_UNUSED __attribute__ ((unused)) 26 | #else 27 | #define RB_MYSQL_NORETURN 28 | #define RB_MYSQL_UNUSED 29 | #endif 30 | 31 | /* MySQL 8.0 replaces my_bool with C99 bool. Earlier versions of MySQL had 32 | * a typedef to char. Gem users reported failures on big endian systems when 33 | * using C99 bool types with older MySQLs due to mismatched behavior. */ 34 | #ifndef HAVE_TYPE_MY_BOOL 35 | #include 36 | typedef bool my_bool; 37 | #endif 38 | 39 | // ruby 2.7+ 40 | #ifdef HAVE_RB_GC_MARK_MOVABLE 41 | #define rb_mysql2_gc_location(ptr) ptr = rb_gc_location(ptr) 42 | #else 43 | #define rb_gc_mark_movable(ptr) rb_gc_mark(ptr) 44 | #define rb_mysql2_gc_location(ptr) 45 | #endif 46 | 47 | // ruby 2.2+ 48 | #ifdef TypedData_Make_Struct 49 | #define NEW_TYPEDDATA_WRAPPER 1 50 | #endif 51 | 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /spec/ssl/ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4QAUfiuFMwazV 3 | sgcmBa9uyA1kFzZOuM9fQl/eR0e+rW7Rse5pgVTJj/Jeg5yEdsO9FjHVYvaNKQm5 4 | zYkxg3VX6PgRS0b0oC3h2rEs1PLpLGlB4woSq2JM1z1oxHxwDGWJJlKP9CfomlxI 5 | k8T+Wgg90QL8xOjs41wnTFDkvKZmqe4pkbDwvZTNn5mh/dxivac8uhkMa0GwMN/l 6 | bXzieXQypOie7r/6tov/gL7dKbL/kg5wnNF+/Do1z9u+L15aTKk1nMbddpI7orl3 7 | f30oufSZ/1ltfLlur2SUJQix4iGYDvu/NoE7B1BQkoqKMoaqVNqF+gCJRXcCByjE 8 | CfK0qtpXAgMBAAECggEAM5T8ujFr1MzN4b+m/66MyDNqiFB1TEGyEKWo6DZFcCzm 9 | vv8U02W5QnqxrGMlMPJ85xVtGyPLCYbpKaLQm1OFyPg4ZsMP2NF1Nus+OeJeJQhh 10 | aWgx/DsN2JxTnV6Qxd+6l1RqvdFpUNXSKyFvf5PeBcxbjT9lRFh8hqX3aaok3c2P 11 | Z4sROHNqgXe/mnCXv3nkUQXct7oFgdeMR6Zy8GFNxMaxvFhCmT+Q0NFf0hqp6/at 12 | b/P0+tZYjp7PkvCkCo2Z7MCdsnI1BNn5ggtdqhLs9tu1f6iwMH0z3tKkWXkX1dJC 13 | Ad0G30qfv6A9IOEWevwJyuVcp9onRmcUaaVNSascbQKBgQD4ITxJcIE3MAtK1W7q 14 | vvEm64i2ay5XfBhoi2oik3+MVlzY2dRiGOEffvysK1Xls8VlLT/RSZVWLjJ+EvrH 15 | DJZmbl972UFvYt5JGEv53YYD0kJ1fQvkLA9//Xkg6Wdac+7BzHxc09fV1k2mUvpH 16 | jJs7JSgLDwVvvU9+vs3eFWWAiwKBgQC+GBgARwb2NKEfXRtyT+X7Z0jmQoQCJDr7 17 | 34D7p5u6lRShlzZNFvwbc5YAIXNNuveqTsrKL57yo4vzNHpDzpXj+kdFIKGAXzXN 18 | BoohGCtIQrrXYGONUubvk3njlOrppdD9kMN+ioHVp/Gm9Dxrn+0HKtzALn7pgZa7 19 | IzA/3Mpa5QKBgEOfozOMotqskFdLxdfaRBTMWk0E9vNG0cwkOr/DnR5dJx6+dyBp 20 | EWmpDSnLAbUBgomphFwAht+e5YnwmEIJTzAJYqJ5OlkmA9i9827cjbqa4hvtAYGk 21 | 9HB4Xzu2AMHpGKfemAIghhE0P6NVt/op+uBqpvgkluG2IWU0kRy2jhwzAoGAbWl+ 22 | vwIirqkiJ+Q2PPhh3e7X1bhpNLZXwMsm+THCf4T5J/zZw0s8dix0JMUcEZxQmpTZ 23 | QcBhEzUxAx2sVcTdHyfZx579dd7XH5fo/x1jJCdMVVTkV95kj3ZpzKTVBQBsptWg 24 | v//GtQwCGd8vu56EFgEEqBTa9VmiQToCtm9FhUUCgYEAwMtvoKM7N9WawAVEeUWG 25 | oDoJhhbnCHvg7ohNEymfsfHaHNUs/WXgk9mHTqY9lkMrnEmO71z8LPw0q67UbiIx 26 | 58MWCpDcOzzNrmXKEbZQ380qYezOFffuvkDKAAl2OmxSAbKLvV/5/vAAEXyBJe2u 27 | brUtxFuKTvZbuCZU+L04y8I= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /spec/ssl/client-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCdoGE3jAu+bNUW 3 | D3UdkfY+ZpxlWlvcjnWqiIZBLuLyiIsDK+oIiMlWgIYJZ8GTyzpY0A+IT2mq8rJR 4 | dN+m9kGVb23iHBAiZEPXlKvnn/JZaco7tLAeFjwFN6hBWVIGt8+cowiMHK52L8bt 5 | 9H2dpeh5BEQA/aGXyH+CHtQECbqQ1MXHEP/UHJqX+JYHChipBBhVS5hOF0jQLXup 6 | ift22G2OMZFt7Gi5VHA3jjqpH10LvUDbtWA1SaUVv8TKCZ8QU+ElE1/19JUAAGQ0 7 | c+Ee2Dm1RGNxmL2V1kyvyjrrjeFjWRbXHJLdUUshw/rYKKeDR/T8KOHmODiXm4Rw 8 | 38BSlvD5AgMBAAECggEABwUuaexqq3uqY+CisoNJMySby5HM1z5gXuIqDzt42nIa 9 | tYnzqEH7VvHDcSYgriXrfoAfE6RBzF4hyKn1ZLjBWVf3Tg46PmXhIE1b+KVxhD+c 10 | xQWk9boUyJYJgDDrtnviRb4nHSJmjMH6UKfGc8qArOnJOOgS6zEqqKTEhDzbWnmQ 11 | Q5GjZGKjTMfbC6u9r6nwcAlPax2tzRmPNtQFYMSjcHmvN0IuhwVlZlULsdPnpDpS 12 | 3LeR8b/75Og8BbqXJNdLAXQq3Athu9ADuxfS/rOBxWwEKElV80gFMo/qiWZ/DVqw 13 | UUExYuDAx8Jnf4j8Vb2UcY2bpxvGHLrD10gRJmUxLwKBgQDLzd95e0SXxuWEHBdA 14 | oHfermVwUzs1aa5zqs/KJEICmFE94bJbWQKbW1TDQggIr45XylQNsQuh0RS1USsd 15 | YweA7z00HPcj5ON7O+5tKwfP+C+jl1s1yfYPvaSiRKwLde6DdVGos29C5OpoIQ+E 16 | hjC02n2d84F/lrjfHn3MQH/2vwKBgQDF/u6jNwfHsfgOnKGJv9BKfmLg4saYUvMv 17 | bclkQS0qrSgrloMSZs1QcpZJWneiO0vjsxoTeLcUIcsuhfxpMsN60nb/QncB749C 18 | ts9SuV9Rdv4+7U1tqg2ZQ52zjMliZvEDriPZ40vhONBXYyKtx1HZ9OexDOLKUzzY 19 | c1MMobj+RwKBgBFWPwdvhANBSS720MePnwLTZQ+sFOJTTiLKyghRE0hzOp4AABMj 20 | PESI/WnqyRIsFPjE3meXwvyN86wE7pz+WpoOP++Z8zAbfXpzO7IPsgdv/mV1L64g 21 | swzdvg6LtvL2okaOiVbHhNR08rfO8Cn+3E/WMk9ocoCvCqT4TA0/A2OzAoGBAILO 22 | TpgzxgcPM6NrpWkc+R4N64NJLwz5WEJQVMnQKWfVaAGL+WIR2ri4S0OA6iKa7CMt 23 | cx/EE6fQP6ynxj8102F0ZDt1jKwRuWLI5aVwZGGsrIGkQxAdVciYnDo/29gPzFCz 24 | HmpXuQy9fR8Olp2aXiARpXQZ4EbswPj7D7X7rf0HAoGAQhhg0xfIuOrLRXrhfFEg 25 | NzASVKvtCEMDbJD4mWCOuqe7Jlvi/qmXZ9YnuKOmGukGV0/ePtLI1+Hcstr4kexb 26 | lUJYZQlB9e0Ft87UD16TuGHAMBKdtXQKuBjMeI0C43kSBq0XuiyY9b44ADH/up07 27 | vkqwfAOUVKCHp4KOOruW9cc= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /spec/ssl/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrvY7yq+BT9ZSP 3 | 9yOdAVfgx12f3lwcO/m6F+R/Z+0bVSvVtbYiO1xn+3l8rz8wT6+fBVVrIxpupFUP 4 | Zalb0BP07jmqhC7YgBjgad0vkUph1peCJ+ntJYtixpAZOZj70aT/2CKjuGkmWFnL 5 | ryrxBmpplHea1BwLJtrWgQEJcaFqiJ1lbGjinudo8dY5tZ7iyyZ+dk8+TKDsV/1p 6 | yuAOemKYDY34jUf1LE525pSR/BcDGYCdzpBsOLsqRKf+5d8cZK05I6oYYKkAwoM3 7 | QwmwpLSm9poFUlo83uMDU5FH76hWFwkd5PE8oSbdKcp6d0AMh/L0TS6V9lGM58jW 8 | 9g71/v2LAgMBAAECggEAOnxjBox26FDNR5vb4neXJELwxOVWS/02xeOmGqdbTYAb 9 | Xfu0a4L4rKas0EPkCoFQpyCLXuGE+mH3X7d4zf4WFcbdF49NXsh88EvNGgpqINiS 10 | Hy6VkP/EsJ47a4O8cCGMhd5mqYe/M2JKLj3Yq11KdusrMiyC4l9Yjk0/e6ZZWKxe 11 | /htw3fMPnHOMfoUB9jPy+SrhbFt42bZ7+JA2Aihf8RCUb/R7OjhASKeRLPkefJTA 12 | Z6kJUoXoCBogjdkLCuVw1zjXF92R5gy+i5o9VhELHpg1D2If7CmeEM2FfVDfWjlF 13 | iYlIR750OsKaeWB0LVKwh+07oyIlmO1TOECXEKt+gQKBgQDpdUfImWMrrkJ5AeCL 14 | 0NmO0JIZciGFBDTHRLOdzQiWKdq87i6/LWStK/gT+eb8WVLJ8vk5KrXFIy7TpYce 15 | 4jRr9u9MG14hjVemLMMoLhPkruPoulIp+Aj0mnhKephpJQ+Khd77g/GT6ZAqxkxi 16 | drhTfKlSou0oEEd34ZuK7d6mCwKBgQC8UrbCErc+r4Ff48/0BfbJWZ7sN/HP/QtI 17 | R2V9v3VpGqMX93v+YhQepxpd4PSkxluEmbbwYkA81un1ot7ndNkLIN0x59P9tVR0 18 | ghXuLmLwxExM5ekrfPt7gbkhwUCwRTogjocm6VoF541tn+ll724XtBdewhyXEUm7 19 | IG0/tLU2gQKBgQCx3avaNprq7bIxVW/Jtk361AdroZvOJx068KnUQSEYnzzLEsDE 20 | 4QXCNiyks5H7kuZTfG3K0zJ3xs1nbMacjgUYeKNqnbNC5tfvgE0TsL9xTJnRdxsg 21 | ZJwWGBYr0GmMOjMz+7iecbE9WwZ+wGPz5LWcze6HSiBblMOOn3GNEJvAbwKBgHXS 22 | 3ksgAIv0rGH9Gz9Wd+fT7Y1nFyCE9gkbulDpd6DxrHazPV2TqXjgHav8sbNh8yJM 23 | NdvB7OTjpW8snn97aMwAnMO7grOqPpPCS8xAM2Dlv8Mg2Th/Mqw8JkMLMNjYBx0V 24 | b1OWDd/B1odu1E0VdvDXmQONOOv/Qf0UtaV0/yeBAoGAaUO9xrblsqEmP+2lqsiv 25 | qwNLk5PICaoXwIacFdhc8YhC5OoXxRmLlyJmz72aKLTnNH+ZYsNYcJlhexhxpxq5 26 | vsxdk7rO741EDuFAk1Hsx1Q2G2+q6VWIWQhsn5eTGdas2qdNrKoNwN3wuALPPeeQ 27 | l7yIJxi7Qn24xh+Bjdp1CDU= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /spec/ssl/pkcs8-client-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCdoGE3jAu+bNUW 3 | D3UdkfY+ZpxlWlvcjnWqiIZBLuLyiIsDK+oIiMlWgIYJZ8GTyzpY0A+IT2mq8rJR 4 | dN+m9kGVb23iHBAiZEPXlKvnn/JZaco7tLAeFjwFN6hBWVIGt8+cowiMHK52L8bt 5 | 9H2dpeh5BEQA/aGXyH+CHtQECbqQ1MXHEP/UHJqX+JYHChipBBhVS5hOF0jQLXup 6 | ift22G2OMZFt7Gi5VHA3jjqpH10LvUDbtWA1SaUVv8TKCZ8QU+ElE1/19JUAAGQ0 7 | c+Ee2Dm1RGNxmL2V1kyvyjrrjeFjWRbXHJLdUUshw/rYKKeDR/T8KOHmODiXm4Rw 8 | 38BSlvD5AgMBAAECggEABwUuaexqq3uqY+CisoNJMySby5HM1z5gXuIqDzt42nIa 9 | tYnzqEH7VvHDcSYgriXrfoAfE6RBzF4hyKn1ZLjBWVf3Tg46PmXhIE1b+KVxhD+c 10 | xQWk9boUyJYJgDDrtnviRb4nHSJmjMH6UKfGc8qArOnJOOgS6zEqqKTEhDzbWnmQ 11 | Q5GjZGKjTMfbC6u9r6nwcAlPax2tzRmPNtQFYMSjcHmvN0IuhwVlZlULsdPnpDpS 12 | 3LeR8b/75Og8BbqXJNdLAXQq3Athu9ADuxfS/rOBxWwEKElV80gFMo/qiWZ/DVqw 13 | UUExYuDAx8Jnf4j8Vb2UcY2bpxvGHLrD10gRJmUxLwKBgQDLzd95e0SXxuWEHBdA 14 | oHfermVwUzs1aa5zqs/KJEICmFE94bJbWQKbW1TDQggIr45XylQNsQuh0RS1USsd 15 | YweA7z00HPcj5ON7O+5tKwfP+C+jl1s1yfYPvaSiRKwLde6DdVGos29C5OpoIQ+E 16 | hjC02n2d84F/lrjfHn3MQH/2vwKBgQDF/u6jNwfHsfgOnKGJv9BKfmLg4saYUvMv 17 | bclkQS0qrSgrloMSZs1QcpZJWneiO0vjsxoTeLcUIcsuhfxpMsN60nb/QncB749C 18 | ts9SuV9Rdv4+7U1tqg2ZQ52zjMliZvEDriPZ40vhONBXYyKtx1HZ9OexDOLKUzzY 19 | c1MMobj+RwKBgBFWPwdvhANBSS720MePnwLTZQ+sFOJTTiLKyghRE0hzOp4AABMj 20 | PESI/WnqyRIsFPjE3meXwvyN86wE7pz+WpoOP++Z8zAbfXpzO7IPsgdv/mV1L64g 21 | swzdvg6LtvL2okaOiVbHhNR08rfO8Cn+3E/WMk9ocoCvCqT4TA0/A2OzAoGBAILO 22 | TpgzxgcPM6NrpWkc+R4N64NJLwz5WEJQVMnQKWfVaAGL+WIR2ri4S0OA6iKa7CMt 23 | cx/EE6fQP6ynxj8102F0ZDt1jKwRuWLI5aVwZGGsrIGkQxAdVciYnDo/29gPzFCz 24 | HmpXuQy9fR8Olp2aXiARpXQZ4EbswPj7D7X7rf0HAoGAQhhg0xfIuOrLRXrhfFEg 25 | NzASVKvtCEMDbJD4mWCOuqe7Jlvi/qmXZ9YnuKOmGukGV0/ePtLI1+Hcstr4kexb 26 | lUJYZQlB9e0Ft87UD16TuGHAMBKdtXQKuBjMeI0C43kSBq0XuiyY9b44ADH/up07 27 | vkqwfAOUVKCHp4KOOruW9cc= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /spec/ssl/pkcs8-server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrvY7yq+BT9ZSP 3 | 9yOdAVfgx12f3lwcO/m6F+R/Z+0bVSvVtbYiO1xn+3l8rz8wT6+fBVVrIxpupFUP 4 | Zalb0BP07jmqhC7YgBjgad0vkUph1peCJ+ntJYtixpAZOZj70aT/2CKjuGkmWFnL 5 | ryrxBmpplHea1BwLJtrWgQEJcaFqiJ1lbGjinudo8dY5tZ7iyyZ+dk8+TKDsV/1p 6 | yuAOemKYDY34jUf1LE525pSR/BcDGYCdzpBsOLsqRKf+5d8cZK05I6oYYKkAwoM3 7 | QwmwpLSm9poFUlo83uMDU5FH76hWFwkd5PE8oSbdKcp6d0AMh/L0TS6V9lGM58jW 8 | 9g71/v2LAgMBAAECggEAOnxjBox26FDNR5vb4neXJELwxOVWS/02xeOmGqdbTYAb 9 | Xfu0a4L4rKas0EPkCoFQpyCLXuGE+mH3X7d4zf4WFcbdF49NXsh88EvNGgpqINiS 10 | Hy6VkP/EsJ47a4O8cCGMhd5mqYe/M2JKLj3Yq11KdusrMiyC4l9Yjk0/e6ZZWKxe 11 | /htw3fMPnHOMfoUB9jPy+SrhbFt42bZ7+JA2Aihf8RCUb/R7OjhASKeRLPkefJTA 12 | Z6kJUoXoCBogjdkLCuVw1zjXF92R5gy+i5o9VhELHpg1D2If7CmeEM2FfVDfWjlF 13 | iYlIR750OsKaeWB0LVKwh+07oyIlmO1TOECXEKt+gQKBgQDpdUfImWMrrkJ5AeCL 14 | 0NmO0JIZciGFBDTHRLOdzQiWKdq87i6/LWStK/gT+eb8WVLJ8vk5KrXFIy7TpYce 15 | 4jRr9u9MG14hjVemLMMoLhPkruPoulIp+Aj0mnhKephpJQ+Khd77g/GT6ZAqxkxi 16 | drhTfKlSou0oEEd34ZuK7d6mCwKBgQC8UrbCErc+r4Ff48/0BfbJWZ7sN/HP/QtI 17 | R2V9v3VpGqMX93v+YhQepxpd4PSkxluEmbbwYkA81un1ot7ndNkLIN0x59P9tVR0 18 | ghXuLmLwxExM5ekrfPt7gbkhwUCwRTogjocm6VoF541tn+ll724XtBdewhyXEUm7 19 | IG0/tLU2gQKBgQCx3avaNprq7bIxVW/Jtk361AdroZvOJx068KnUQSEYnzzLEsDE 20 | 4QXCNiyks5H7kuZTfG3K0zJ3xs1nbMacjgUYeKNqnbNC5tfvgE0TsL9xTJnRdxsg 21 | ZJwWGBYr0GmMOjMz+7iecbE9WwZ+wGPz5LWcze6HSiBblMOOn3GNEJvAbwKBgHXS 22 | 3ksgAIv0rGH9Gz9Wd+fT7Y1nFyCE9gkbulDpd6DxrHazPV2TqXjgHav8sbNh8yJM 23 | NdvB7OTjpW8snn97aMwAnMO7grOqPpPCS8xAM2Dlv8Mg2Th/Mqw8JkMLMNjYBx0V 24 | b1OWDd/B1odu1E0VdvDXmQONOOv/Qf0UtaV0/yeBAoGAaUO9xrblsqEmP+2lqsiv 25 | qwNLk5PICaoXwIacFdhc8YhC5OoXxRmLlyJmz72aKLTnNH+ZYsNYcJlhexhxpxq5 26 | vsxdk7rO741EDuFAk1Hsx1Q2G2+q6VWIWQhsn5eTGdas2qdNrKoNwN3wuALPPeeQ 27 | l7yIJxi7Qn24xh+Bjdp1CDU= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /support/ruby_enc_to_mysql.rb: -------------------------------------------------------------------------------- 1 | mysql_to_rb = { 2 | "big5" => "Big5", 3 | "dec8" => nil, 4 | "cp850" => "CP850", 5 | "hp8" => nil, 6 | "koi8r" => "KOI8-R", 7 | "latin1" => "ISO-8859-1", 8 | "latin2" => "ISO-8859-2", 9 | "swe7" => nil, 10 | "ascii" => "US-ASCII", 11 | "ujis" => "eucJP-ms", 12 | "sjis" => "Shift_JIS", 13 | "hebrew" => "ISO-8859-8", 14 | "tis620" => "TIS-620", 15 | "euckr" => "EUC-KR", 16 | "koi8u" => "KOI8-R", 17 | "gb2312" => "GB2312", 18 | "greek" => "ISO-8859-7", 19 | "cp1250" => "Windows-1250", 20 | "gbk" => "GBK", 21 | "latin5" => "ISO-8859-9", 22 | "armscii8" => nil, 23 | "utf8" => "UTF-8", 24 | "ucs2" => "UTF-16BE", 25 | "cp866" => "IBM866", 26 | "keybcs2" => nil, 27 | "macce" => "macCentEuro", 28 | "macroman" => "macRoman", 29 | "cp852" => "CP852", 30 | "latin7" => "ISO-8859-13", 31 | "utf8mb3" => "UTF-8", 32 | "utf8mb4" => "UTF-8", 33 | "cp1251" => "Windows-1251", 34 | "utf16" => "UTF-16", 35 | "cp1256" => "Windows-1256", 36 | "cp1257" => "Windows-1257", 37 | "utf32" => "UTF-32", 38 | "binary" => "ASCII-8BIT", 39 | "geostd8" => nil, 40 | "cp932" => "Windows-31J", 41 | "eucjpms" => "eucJP-ms", 42 | "utf16le" => "UTF-16LE", 43 | "gb18030" => "GB18030", 44 | } 45 | 46 | puts <<-HEADER 47 | %readonly-tables 48 | %enum 49 | %define lookup-function-name mysql2_mysql_enc_name_to_rb 50 | %define hash-function-name mysql2_mysql_enc_name_to_rb_hash 51 | %struct-type 52 | struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; } 53 | %% 54 | HEADER 55 | 56 | mysql_to_rb.each do |mysql, ruby| 57 | name = if ruby.nil? 58 | "NULL" 59 | else 60 | "\"#{ruby}\"" 61 | end 62 | 63 | puts "#{mysql}, #{name}" 64 | end 65 | -------------------------------------------------------------------------------- /.github/workflows/container.yml: -------------------------------------------------------------------------------- 1 | # Test Linux distributions which do not exist on GitHub Actions 2 | # by the containers. 3 | name: Container 4 | on: [push, pull_request] 5 | jobs: 6 | build: 7 | name: >- 8 | ${{ matrix.distro }} ${{ matrix.image }} ${{ matrix.name_extra || '' }} 9 | runs-on: ubuntu-20.04 # focal 10 | continue-on-error: ${{ matrix.allow-failure || false }} 11 | strategy: 12 | matrix: 13 | include: 14 | # CentOS 7 system Ruby is the fixed version 2.0.0. 15 | - {distro: centos, image: 'centos:7', name_extra: 'ruby 2.0.0'} 16 | # Fedora latest stable version 17 | - {distro: fedora, image: 'fedora:latest'} 18 | # Fedora development version 19 | - {distro: fedora, image: 'fedora:rawhide', ssl_cert_dir: '/tmp/mysql2', ssl_cert_host: 'localhost'} 20 | # On the fail-fast: true, it cancels all in-progress jobs 21 | # if any matrix job fails unlike Travis fast_finish. 22 | fail-fast: false 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: docker build -t mysql2 -f ci/Dockerfile_${{ matrix.distro }} --build-arg IMAGE=${{ matrix.image }} . 26 | # Add the "--cap-add=... --security-opt seccomp=..." options 27 | # as a temporary workaround to avoid the following issue 28 | # in the Fedora >= 34 containers. 29 | # https://bugzilla.redhat.com/show_bug.cgi?id=1900021 30 | - run: | 31 | docker run \ 32 | --add-host=${{ matrix.ssl_cert_host || 'mysql2gem.example.com' }}:127.0.0.1 \ 33 | -t \ 34 | -e TEST_RUBY_MYSQL2_SSL_CERT_DIR="${{ matrix.ssl_cert_dir || '' }}" \ 35 | -e TEST_RUBY_MYSQL2_SSL_CERT_HOST="${{ matrix.ssl_cert_host || '' }}" \ 36 | --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ 37 | mysql2 38 | -------------------------------------------------------------------------------- /tasks/rspec.rake: -------------------------------------------------------------------------------- 1 | begin 2 | require 'rspec' 3 | require 'rspec/core/rake_task' 4 | 5 | desc " Run all examples with Valgrind" 6 | namespace :spec do 7 | task :valgrind do 8 | VALGRIND_OPTS = %w[ 9 | --num-callers=50 10 | --error-limit=no 11 | --partial-loads-ok=yes 12 | --undef-value-errors=no 13 | --trace-children=yes 14 | ].freeze 15 | cmdline = "valgrind #{VALGRIND_OPTS.join(' ')} bundle exec rake spec" 16 | puts cmdline 17 | system cmdline 18 | end 19 | end 20 | 21 | desc "Run all examples with RCov" 22 | RSpec::Core::RakeTask.new('spec:rcov') do |t| 23 | t.rcov = true 24 | end 25 | 26 | RSpec::Core::RakeTask.new('spec') do |t| 27 | t.verbose = true 28 | end 29 | rescue LoadError 30 | puts "rspec, or one of its dependencies, is not available. Install it with: sudo gem install rspec" 31 | end 32 | 33 | # Get the value from `id` command as the environment variable USER is 34 | # not defined in a container. 35 | user_name = ENV['USER'] || `id -un`.rstrip 36 | 37 | file 'spec/configuration.yml' => 'spec/configuration.yml.example' do |task| 38 | CLEAN.exclude task.name 39 | src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) 40 | dst_path = File.expand_path("../../#{task.name}", __FILE__) 41 | 42 | File.open(dst_path, 'w') do |dst_file| 43 | File.open(src_path).each_line do |line| 44 | dst_file.write line.gsub(/LOCALUSERNAME/, user_name) 45 | end 46 | end 47 | end 48 | 49 | file 'spec/my.cnf' => 'spec/my.cnf.example' do |task| 50 | CLEAN.exclude task.name 51 | src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) 52 | dst_path = File.expand_path("../../#{task.name}", __FILE__) 53 | 54 | File.open(dst_path, 'w') do |dst_file| 55 | File.open(src_path).each_line do |line| 56 | dst_file.write line.gsub(/LOCALUSERNAME/, user_name) 57 | end 58 | end 59 | end 60 | 61 | Rake::Task[:spec].prerequisites << :'spec/configuration.yml' 62 | Rake::Task[:spec].prerequisites << :'spec/my.cnf' 63 | -------------------------------------------------------------------------------- /spec/ssl/gen_certs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | # TEST_RUBY_MYSQL2_SSL_CERT_HOST: custom host for the SSL certificates. 6 | SSL_CERT_HOST=${TEST_RUBY_MYSQL2_SSL_CERT_HOST:-mysql2gem.example.com} 7 | echo "Generating the SSL certifications from the host ${SSL_CERT_HOST}.." 8 | 9 | echo " 10 | [ ca ] 11 | # January 1, 2015 12 | default_startdate = 2015010360000Z 13 | 14 | [ req ] 15 | distinguished_name = req_distinguished_name 16 | 17 | # Root CA certificate extensions 18 | [ v3_ca ] 19 | basicConstraints = critical, CA:true 20 | 21 | [ req_distinguished_name ] 22 | # If this isn't set, the error is "error, no objects specified in config file" 23 | commonName = Common Name (hostname, IP, or your name) 24 | 25 | countryName_default = US 26 | stateOrProvinceName_default = CA 27 | localityName_default = San Francisco 28 | 0.organizationName_default = mysql2_gem 29 | organizationalUnitName_default = Mysql2Gem 30 | emailAddress_default = mysql2gem@example.com 31 | " | tee ca.cnf cert.cnf 32 | 33 | # The client and server certs must have a different common name than the CA 34 | # to avoid "SSL connection error: error:00000001:lib(0):func(0):reason(1)" 35 | 36 | echo " 37 | commonName_default = ca_mysql2gem 38 | " >> ca.cnf 39 | 40 | echo " 41 | commonName_default = ${SSL_CERT_HOST} 42 | " >> cert.cnf 43 | 44 | # Generate a set of certificates 45 | openssl genrsa -out ca-key.pem 2048 46 | openssl req -new -x509 -nodes -extensions v3_ca -days 3600 -key ca-key.pem -out ca-cert.pem -batch -config ca.cnf 47 | openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-server-key.pem -out server-req.pem -batch -config cert.cnf 48 | openssl x509 -req -in server-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem 49 | openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-client-key.pem -out client-req.pem -batch -config cert.cnf 50 | openssl x509 -req -in client-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem 51 | 52 | # Convert format from PKCS#8 to PKCS#1 53 | openssl rsa -in pkcs8-server-key.pem -out server-key.pem 54 | openssl rsa -in pkcs8-client-key.pem -out client-key.pem 55 | 56 | echo "done" 57 | -------------------------------------------------------------------------------- /benchmark/query_with_mysql_casting.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') 2 | 3 | require 'rubygems' 4 | require 'benchmark/ips' 5 | require 'mysql' 6 | require 'mysql2' 7 | require 'do_mysql' 8 | 9 | database = 'test' 10 | sql = "SELECT * FROM mysql2_test LIMIT 100" 11 | 12 | class Mysql 13 | include Enumerable 14 | end 15 | 16 | def mysql_cast(type, value) 17 | case type 18 | when Mysql::Field::TYPE_NULL 19 | nil 20 | when Mysql::Field::TYPE_TINY, Mysql::Field::TYPE_SHORT, Mysql::Field::TYPE_LONG, 21 | Mysql::Field::TYPE_INT24, Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_YEAR 22 | value.to_i 23 | when Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_NEWDECIMAL 24 | BigDecimal(value) 25 | when Mysql::Field::TYPE_DOUBLE, Mysql::Field::TYPE_FLOAT 26 | value.to_f 27 | when Mysql::Field::TYPE_DATE 28 | Date.parse(value) 29 | when Mysql::Field::TYPE_TIME, Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP 30 | Time.parse(value) 31 | when Mysql::Field::TYPE_BLOB, Mysql::Field::TYPE_BIT, Mysql::Field::TYPE_STRING, 32 | Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET, 33 | Mysql::Field::TYPE_ENUM 34 | value 35 | else 36 | value 37 | end 38 | end 39 | 40 | debug = ENV['DEBUG'] 41 | 42 | Benchmark.ips do |x| 43 | mysql2 = Mysql2::Client.new(host: "localhost", username: "root") 44 | mysql2.query "USE #{database}" 45 | x.report "Mysql2" do 46 | mysql2_result = mysql2.query sql, symbolize_keys: true 47 | mysql2_result.each { |res| puts res.inspect if debug } 48 | end 49 | 50 | mysql = Mysql.new("localhost", "root") 51 | mysql.query "USE #{database}" 52 | x.report "Mysql" do 53 | mysql_result = mysql.query sql 54 | fields = mysql_result.fetch_fields 55 | mysql_result.each do |row| 56 | row_hash = row.each_with_index.each_with_object({}) do |(f, j), hash| 57 | hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, f) 58 | end 59 | puts row_hash.inspect if debug 60 | end 61 | end 62 | 63 | do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") 64 | command = do_mysql.create_command sql 65 | x.report "do_mysql" do 66 | do_result = command.execute_reader 67 | do_result.each { |res| puts res.inspect if debug } 68 | end 69 | 70 | x.compare! 71 | end 72 | -------------------------------------------------------------------------------- /tasks/vendor_mysql.rake: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | require 'rake/extensioncompiler' 3 | 4 | CONNECTOR_VERSION = "6.1.11".freeze # NOTE: Track the upstream version from time to time 5 | 6 | def vendor_mysql_platform(platform = nil) 7 | platform ||= RUBY_PLATFORM 8 | platform =~ /x64/ ? "winx64" : "win32" 9 | end 10 | 11 | def vendor_mysql_dir(*args) 12 | "mysql-connector-c-#{CONNECTOR_VERSION}-#{vendor_mysql_platform(*args)}" 13 | end 14 | 15 | def vendor_mysql_zip(*args) 16 | "#{vendor_mysql_dir(*args)}.zip" 17 | end 18 | 19 | def vendor_mysql_url(*args) 20 | "http://cdn.mysql.com/Downloads/Connector-C/#{vendor_mysql_zip(*args)}" 21 | end 22 | 23 | # vendor:mysql 24 | task "vendor:mysql:cross" do 25 | # When cross-compiling, grab both 32 and 64 bit connectors 26 | Rake::Task['vendor:mysql'].invoke('x86') 27 | Rake::Task['vendor:mysql'].invoke('x64') 28 | end 29 | 30 | task "vendor:mysql", [:platform] do |_t, args| 31 | puts "vendor:mysql for #{vendor_mysql_dir(args[:platform])}" 32 | 33 | # download mysql library and headers 34 | directory "vendor" 35 | 36 | file "vendor/#{vendor_mysql_zip(args[:platform])}" => ["vendor"] do |t| 37 | url = vendor_mysql_url(args[:platform]) 38 | when_writing "downloading #{t.name}" do 39 | cd "vendor" do 40 | sh "curl", "-C", "-", "-O", url do |ok| 41 | sh "wget", "-c", url unless ok 42 | end 43 | end 44 | end 45 | end 46 | 47 | file "vendor/#{vendor_mysql_dir(args[:platform])}/include/mysql.h" => ["vendor/#{vendor_mysql_zip(args[:platform])}"] do |t| 48 | full_file = File.expand_path(t.prerequisites.last) 49 | when_writing "creating #{t.name}" do 50 | cd "vendor" do 51 | sh "unzip", "-uq", full_file, 52 | "#{vendor_mysql_dir(args[:platform])}/bin/**", 53 | "#{vendor_mysql_dir(args[:platform])}/include/**", 54 | "#{vendor_mysql_dir(args[:platform])}/lib/**", 55 | "#{vendor_mysql_dir(args[:platform])}/README" # contains the license info 56 | end 57 | # update file timestamp to avoid Rake performing this extraction again. 58 | touch t.name 59 | end 60 | end 61 | 62 | # clobber expanded packages 63 | CLOBBER.include("vendor/#{vendor_mysql_dir(args[:platform])}") 64 | 65 | Rake::Task["vendor/#{vendor_mysql_dir(args[:platform])}/include/mysql.h"].invoke 66 | Rake::Task["vendor:mysql"].reenable # allow task to be invoked again (with another platform) 67 | end 68 | -------------------------------------------------------------------------------- /support/mysql_enc_to_ruby.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'mysql2' 3 | 4 | user, pass, host, port = ENV.values_at('user', 'pass', 'host', 'port') 5 | 6 | mysql_to_rb = { 7 | "big5" => "Big5", 8 | "dec8" => "NULL", 9 | "cp850" => "CP850", 10 | "hp8" => "NULL", 11 | "koi8r" => "KOI8-R", 12 | "latin1" => "ISO-8859-1", 13 | "latin2" => "ISO-8859-2", 14 | "swe7" => "NULL", 15 | "ascii" => "US-ASCII", 16 | "ujis" => "eucJP-ms", 17 | "sjis" => "Shift_JIS", 18 | "hebrew" => "ISO-8859-8", 19 | "tis620" => "TIS-620", 20 | "euckr" => "EUC-KR", 21 | "koi8u" => "KOI8-R", 22 | "gb2312" => "GB2312", 23 | "greek" => "ISO-8859-7", 24 | "cp1250" => "Windows-1250", 25 | "gbk" => "GBK", 26 | "latin5" => "ISO-8859-9", 27 | "armscii8" => "NULL", 28 | "utf8" => "UTF-8", 29 | "ucs2" => "UTF-16BE", 30 | "cp866" => "IBM866", 31 | "keybcs2" => "NULL", 32 | "macce" => "macCentEuro", 33 | "macroman" => "macRoman", 34 | "cp852" => "CP852", 35 | "latin7" => "ISO-8859-13", 36 | "utf8mb3" => "UTF-8", 37 | "utf8mb4" => "UTF-8", 38 | "cp1251" => "Windows-1251", 39 | "utf16" => "UTF-16", 40 | "cp1256" => "Windows-1256", 41 | "cp1257" => "Windows-1257", 42 | "utf32" => "UTF-32", 43 | "binary" => "ASCII-8BIT", 44 | "geostd8" => "NULL", 45 | "cp932" => "Windows-31J", 46 | "eucjpms" => "eucJP-ms", 47 | "utf16le" => "UTF-16LE", 48 | "gb18030" => "GB18030", 49 | } 50 | 51 | client = Mysql2::Client.new(username: user, password: pass, host: host, port: port.to_i) 52 | collations = client.query "SHOW COLLATION", as: :array 53 | encodings = Array.new(collations.to_a.last[2].to_i) 54 | encodings_with_nil = Array.new(encodings.size) 55 | 56 | collations.each do |collation| 57 | mysql_col_idx = collation[2].to_i 58 | rb_enc = mysql_to_rb.fetch(collation[1]) do |mysql_enc| 59 | warn "WARNING: Missing mapping for collation \"#{collation[0]}\" with encoding \"#{mysql_enc}\" and id #{mysql_col_idx}, assuming NULL" 60 | "NULL" 61 | end 62 | encodings[mysql_col_idx - 1] = [mysql_col_idx, rb_enc] 63 | end 64 | 65 | encodings.each_with_index do |encoding, idx| 66 | encodings_with_nil[idx] = (encoding || [idx, "NULL"]) 67 | end 68 | 69 | encodings_with_nil.sort! do |a, b| 70 | a[0] <=> b[0] 71 | end 72 | 73 | encodings_with_nil = encodings_with_nil.map do |encoding| 74 | name = if encoding.nil? || encoding[1] == 'NULL' 75 | 'NULL' 76 | else 77 | "\"#{encoding[1]}\"" 78 | end 79 | 80 | " #{name}" 81 | end 82 | 83 | # start printing output 84 | 85 | puts "static const char *mysql2_mysql_enc_to_rb[] = {" 86 | puts encodings_with_nil.join(",\n") 87 | puts "};" 88 | -------------------------------------------------------------------------------- /spec/mysql2/error_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Mysql2::Error do 4 | let(:error) do 5 | begin 6 | @client.query("HAHAHA") 7 | rescue Mysql2::Error => e 8 | error = e 9 | end 10 | 11 | error 12 | end 13 | 14 | it "responds to error_number and sql_state, with aliases" do 15 | expect(error).to respond_to(:error_number) 16 | expect(error).to respond_to(:sql_state) 17 | 18 | # Mysql gem compatibility 19 | expect(error).to respond_to(:errno) 20 | expect(error).to respond_to(:error) 21 | end 22 | 23 | context 'encoding' do 24 | let(:valid_utf8) { '造字' } 25 | let(:error) do 26 | begin 27 | @client.query(valid_utf8) 28 | rescue Mysql2::Error => e 29 | e 30 | end 31 | end 32 | 33 | let(:invalid_utf8) { ["e5c67d1f"].pack('H*').force_encoding(Encoding::UTF_8) } 34 | let(:bad_err) do 35 | begin 36 | @client.query(invalid_utf8) 37 | rescue Mysql2::Error => e 38 | e 39 | end 40 | end 41 | 42 | let(:server_info) do 43 | @client.server_info 44 | end 45 | 46 | before do 47 | # sanity check 48 | expect(valid_utf8.encoding).to eql(Encoding::UTF_8) 49 | expect(valid_utf8).to be_valid_encoding 50 | 51 | expect(invalid_utf8.encoding).to eql(Encoding::UTF_8) 52 | expect(invalid_utf8).to_not be_valid_encoding 53 | end 54 | 55 | it "returns error messages as UTF-8 by default" do 56 | with_internal_encoding nil do 57 | expect(error.message.encoding).to eql(Encoding::UTF_8) 58 | expect(error.message).to be_valid_encoding 59 | 60 | expect(bad_err.message.encoding).to eql(Encoding::UTF_8) 61 | expect(bad_err.message).to be_valid_encoding 62 | 63 | # MariaDB 10.5 returns a little different error message unlike MySQL 64 | # and other old MariaDBs. 65 | # https://jira.mariadb.org/browse/MDEV-25400 66 | err_str = if server_info[:version].match(/MariaDB/) && server_info[:id] >= 100500 67 | "??}\\001F" 68 | else 69 | "??}\u001F" 70 | end 71 | expect(bad_err.message).to include(err_str) 72 | end 73 | end 74 | 75 | it "returns sql state as ASCII" do 76 | expect(error.sql_state.encoding).to eql(Encoding::US_ASCII) 77 | expect(error.sql_state).to be_valid_encoding 78 | end 79 | 80 | it "returns error messages and sql state in Encoding.default_internal if set" do 81 | with_internal_encoding Encoding::UTF_16LE do 82 | expect(error.message.encoding).to eql(Encoding.default_internal) 83 | expect(error.message).to be_valid_encoding 84 | 85 | expect(bad_err.message.encoding).to eql(Encoding.default_internal) 86 | expect(bad_err.message).to be_valid_encoding 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/mysql2.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | require 'bigdecimal' 3 | 4 | # Load libmysql.dll before requiring mysql2/mysql2.so 5 | # This gives a chance to be flexible about the load path 6 | # Or to bomb out with a clear error message instead of a linker crash 7 | if RUBY_PLATFORM =~ /mswin|mingw/ 8 | dll_path = if ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] 9 | # If this environment variable is set, it overrides any other paths 10 | # The user is advised to use backslashes not forward slashes 11 | ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] 12 | elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))) 13 | # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary 14 | File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)) 15 | elsif defined?(RubyInstaller) 16 | # RubyInstaller-2.4+ native build doesn't need DLL preloading 17 | else 18 | # This will use default / system library paths 19 | 'libmysql.dll' 20 | end 21 | 22 | if dll_path 23 | require 'fiddle' 24 | kernel32 = Fiddle.dlopen 'kernel32' 25 | load_library = Fiddle::Function.new( 26 | kernel32['LoadLibraryW'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT, 27 | ) 28 | if load_library.call(dll_path.encode('utf-16le')).zero? 29 | abort "Failed to load libmysql.dll from #{dll_path}" 30 | end 31 | end 32 | end 33 | 34 | require 'mysql2/version' unless defined? Mysql2::VERSION 35 | require 'mysql2/error' 36 | require 'mysql2/mysql2' 37 | require 'mysql2/result' 38 | require 'mysql2/client' 39 | require 'mysql2/field' 40 | require 'mysql2/statement' 41 | 42 | # = Mysql2 43 | # 44 | # A modern, simple and very fast Mysql library for Ruby - binding to libmysql 45 | module Mysql2 46 | end 47 | 48 | if defined?(ActiveRecord::VERSION::STRING) && ActiveRecord::VERSION::STRING < "3.1" 49 | begin 50 | require 'active_record/connection_adapters/mysql2_adapter' 51 | rescue LoadError 52 | warn "============= WARNING FROM mysql2 =============" 53 | warn "This version of mysql2 (#{Mysql2::VERSION}) doesn't ship with the ActiveRecord adapter." 54 | warn "In Rails version 3.1.0 and up, the mysql2 ActiveRecord adapter is included with rails." 55 | warn "If you want to use the mysql2 gem with Rails <= 3.0.x, please use the latest mysql2 in the 0.2.x series." 56 | warn "============= END WARNING FROM mysql2 =============" 57 | end 58 | end 59 | 60 | # For holding utility methods 61 | module Mysql2 62 | module Util 63 | # 64 | # Rekey a string-keyed hash with equivalent symbols. 65 | # 66 | def self.key_hash_as_symbols(hash) 67 | return nil unless hash 68 | 69 | Hash[hash.map { |k, v| [k.to_sym, v] }] 70 | end 71 | 72 | # 73 | # In Mysql2::Client#query and Mysql2::Statement#execute, 74 | # Thread#handle_interrupt is used to prevent Timeout#timeout 75 | # from interrupting query execution. 76 | # 77 | # Timeout::ExitException was removed in Ruby 2.3.0, 2.2.3, and 2.1.8, 78 | # but is present in earlier 2.1.x and 2.2.x, so we provide a shim. 79 | # 80 | require 'timeout' 81 | TIMEOUT_ERROR_CLASS = if defined?(::Timeout::ExitException) 82 | ::Timeout::ExitException 83 | else 84 | ::Timeout::Error 85 | end 86 | TIMEOUT_ERROR_NEVER = { TIMEOUT_ERROR_CLASS => :never }.freeze 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /support/3A79BD29.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: SKS 1.1.6 3 | Comment: Hostname: pgp.mit.edu 4 | 5 | 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 47 | sTSKHe+QnnnoFmu4gnmDU31i 48 | =Xqbo 49 | -----END PGP PUBLIC KEY BLOCK----- 50 | -------------------------------------------------------------------------------- /ext/mysql2/infile.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #ifndef _MSC_VER 5 | #include 6 | #endif 7 | #include 8 | 9 | #define ERROR_LEN 1024 10 | typedef struct 11 | { 12 | int fd; 13 | char *filename; 14 | char error[ERROR_LEN]; 15 | mysql_client_wrapper *wrapper; 16 | } mysql2_local_infile_data; 17 | 18 | /* MySQL calls this function when a user begins a LOAD DATA LOCAL INFILE query. 19 | * 20 | * Allocate a data struct and pass it back through the data pointer. 21 | * 22 | * Returns: 23 | * 0 on success 24 | * 1 on error 25 | */ 26 | static int 27 | mysql2_local_infile_init(void **ptr, const char *filename, void *userdata) 28 | { 29 | mysql2_local_infile_data *data = malloc(sizeof(mysql2_local_infile_data)); 30 | if (!data) return 1; 31 | 32 | *ptr = data; 33 | data->error[0] = 0; 34 | data->wrapper = userdata; 35 | 36 | data->filename = strdup(filename); 37 | if (!data->filename) { 38 | snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename); 39 | return 1; 40 | } 41 | 42 | data->fd = open(filename, O_RDONLY); 43 | if (data->fd < 0) { 44 | snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename); 45 | return 1; 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | /* MySQL calls this function to read data from the local file. 52 | * 53 | * Returns: 54 | * > 0 number of bytes read 55 | * == 0 end of file 56 | * < 0 error 57 | */ 58 | static int 59 | mysql2_local_infile_read(void *ptr, char *buf, unsigned int buf_len) 60 | { 61 | int count; 62 | mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr; 63 | 64 | count = (int)read(data->fd, buf, buf_len); 65 | if (count < 0) { 66 | snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), data->filename); 67 | } 68 | 69 | return count; 70 | } 71 | 72 | /* MySQL calls this function when we're done with the LOCAL INFILE query. 73 | * 74 | * ptr will be null if the init function failed. 75 | */ 76 | static void 77 | mysql2_local_infile_end(void *ptr) 78 | { 79 | mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr; 80 | if (data) { 81 | if (data->fd >= 0) 82 | close(data->fd); 83 | if (data->filename) 84 | free(data->filename); 85 | free(data); 86 | } 87 | } 88 | 89 | /* MySQL calls this function if any of the functions above returned an error. 90 | * 91 | * This function is called even if init failed, with whatever ptr value 92 | * init has set, regardless of the return value of the init function. 93 | * 94 | * Returns: 95 | * Error message number (see http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html) 96 | */ 97 | static int 98 | mysql2_local_infile_error(void *ptr, char *error_msg, unsigned int error_msg_len) 99 | { 100 | mysql2_local_infile_data *data = (mysql2_local_infile_data *) ptr; 101 | 102 | if (data) { 103 | snprintf(error_msg, error_msg_len, "%s", data->error); 104 | return CR_UNKNOWN_ERROR; 105 | } 106 | 107 | snprintf(error_msg, error_msg_len, "Out of memory"); 108 | return CR_OUT_OF_MEMORY; 109 | } 110 | 111 | /* Tell MySQL Client to use our own local_infile functions. 112 | * This is both due to bugginess in the default handlers, 113 | * and to improve the Rubyness of the handlers here. 114 | */ 115 | void mysql2_set_local_infile(MYSQL *mysql, void *userdata) 116 | { 117 | mysql_set_local_infile_handler(mysql, 118 | mysql2_local_infile_init, 119 | mysql2_local_infile_read, 120 | mysql2_local_infile_end, 121 | mysql2_local_infile_error, userdata); 122 | } 123 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | name: >- 6 | ${{ matrix.os }} ruby ${{ matrix.ruby }} ${{ matrix.db }} 7 | runs-on: ${{ matrix.os }} 8 | continue-on-error: ${{ matrix.allow-failure || false }} 9 | strategy: 10 | matrix: 11 | include: 12 | # Ruby 3.x on Ubuntu 24.04 LTS 13 | - {os: ubuntu-24.04, ruby: 'head', db: mysql84} 14 | - {os: ubuntu-24.04, ruby: '3.4', db: mysql84} 15 | 16 | # Ruby 3.x on Ubuntu 22.04 LTS 17 | - {os: ubuntu-22.04, ruby: '3.4', db: mysql80} 18 | - {os: ubuntu-22.04, ruby: '3.3', db: mysql80} 19 | - {os: ubuntu-22.04, ruby: '3.2', db: mysql80} 20 | - {os: ubuntu-22.04, ruby: '3.1', db: mysql80} 21 | - {os: ubuntu-22.04, ruby: '3.0', db: mysql80} 22 | 23 | # Ruby 2.x on Ubuntu 20.04 LTS 24 | - {os: ubuntu-20.04, ruby: '2.7', db: mysql80} 25 | - {os: ubuntu-20.04, ruby: '2.6', db: mysql80} 26 | - {os: ubuntu-20.04, ruby: '2.5', db: mysql80} 27 | - {os: ubuntu-20.04, ruby: '2.4', db: mysql80} 28 | - {os: ubuntu-20.04, ruby: '2.3', db: mysql80} 29 | - {os: ubuntu-20.04, ruby: '2.2', db: mysql80} 30 | - {os: ubuntu-20.04, ruby: '2.1', db: mysql80} 31 | - {os: ubuntu-20.04, ruby: '2.0', db: mysql80} 32 | 33 | # MySQL 5.7 packages stopped after Ubuntu 18.04 Bionic 34 | # - {os: ubuntu-18.04, ruby: '2.7', db: mysql57} 35 | 36 | # MariaDB LTS versions 37 | # db: on Linux, ci/setup.sh installs the specified packages 38 | # db: on MacOS, installs a Homebrew package use "name@X.Y" to specify a version 39 | 40 | - {os: ubuntu-24.04, ruby: '3.4', db: mariadb11.4} 41 | - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.11} 42 | - {os: ubuntu-22.04, ruby: '2.7', db: mariadb10.11} 43 | - {os: ubuntu-22.04, ruby: '3.0', db: mariadb10.6} 44 | - {os: ubuntu-20.04, ruby: '2.7', db: mariadb10.6} 45 | 46 | # TODO - Windows CI 47 | # - {os: windows-2022, ruby: '3.2', db: mysql80} 48 | # - {os: windows-2022, ruby: '2.7', db: mysql80} 49 | 50 | # Allow failure due to this issue: 51 | # https://github.com/brianmario/mysql2/issues/1194 52 | - {os: macos-latest, ruby: '3.4', db: mariadb@11.4, ssl: openssl@3, allow-failure: true} 53 | - {os: macos-latest, ruby: '3.4', db: mysql@8.4, ssl: openssl@3, allow-failure: true} 54 | - {os: macos-latest, ruby: '2.6', db: mysql@8.0, ssl: openssl@1.1, allow-failure: true} 55 | 56 | # On the fail-fast: true, it cancels all in-progress jobs 57 | # if any matrix job fails, which we don't want. 58 | fail-fast: false 59 | env: 60 | BUNDLE_WITHOUT: development 61 | # reduce MacOS CI time, don't need to clean a runtime that isn't saved 62 | HOMEBREW_NO_INSTALL_CLEANUP: 1 63 | HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 64 | steps: 65 | - uses: actions/checkout@v3 66 | - uses: ruby/setup-ruby@v1 67 | with: 68 | ruby-version: ${{ matrix.ruby }} 69 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 70 | - if: runner.os == 'Linux' || runner.os == 'macOS' 71 | run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts 72 | - if: runner.os == 'Windows' 73 | run: echo "127.0.0.1 mysql2gem.example.com" | tee -a C:/Windows/System32/drivers/etc/hosts 74 | - run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV 75 | - run: bash ci/setup.sh 76 | # Set the verbose option in the Makefile to print compiling command lines. 77 | - run: echo "MAKEFLAGS=V=1" >> $GITHUB_ENV 78 | - if: matrix.ssl 79 | run: echo "rake_spec_opts=--with-openssl-dir=$(brew --prefix ${{ matrix.ssl }})" >> $GITHUB_ENV 80 | - run: bundle exec rake spec -- $rake_spec_opts 81 | -------------------------------------------------------------------------------- /support/B7B3B788A8D3785C.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Comment: Hostname: 3 | Version: Hockeypuck 2.2 4 | 5 | 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 64 | 73BwBlSVqPeJ3dPGq4CW53iVYPM= 65 | =mheB 66 | -----END PGP PUBLIC KEY BLOCK----- 67 | -------------------------------------------------------------------------------- /lib/mysql2/error.rb: -------------------------------------------------------------------------------- 1 | module Mysql2 2 | class Error < StandardError 3 | ENCODE_OPTS = { 4 | undef: :replace, 5 | invalid: :replace, 6 | replace: '?'.freeze, 7 | }.freeze 8 | 9 | ConnectionError = Class.new(Error) 10 | TimeoutError = Class.new(Error) 11 | 12 | CODES = { 13 | 1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT 14 | 15 | 1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR 16 | 1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR 17 | 1152 => ConnectionError, # ER_ABORTING_CONNECTION 18 | 1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE 19 | 1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE 20 | 1155 => ConnectionError, # ER_NET_FCNTL_ERROR 21 | 1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER 22 | 1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR 23 | 1158 => ConnectionError, # ER_NET_READ_ERROR 24 | 1159 => ConnectionError, # ER_NET_READ_INTERRUPTED 25 | 1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE 26 | 1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED 27 | 1927 => ConnectionError, # ER_CONNECTION_KILLED 28 | 29 | 2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR 30 | 2002 => ConnectionError, # CR_CONNECTION_ERROR 31 | 2003 => ConnectionError, # CR_CONN_HOST_ERROR 32 | 2004 => ConnectionError, # CR_IPSOCK_ERROR 33 | 2005 => ConnectionError, # CR_UNKNOWN_HOST 34 | 2006 => ConnectionError, # CR_SERVER_GONE_ERROR 35 | 2007 => ConnectionError, # CR_VERSION_ERROR 36 | 2009 => ConnectionError, # CR_WRONG_HOST_INFO 37 | 2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR 38 | 2013 => ConnectionError, # CR_SERVER_LOST 39 | 2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE 40 | 2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR 41 | 2027 => ConnectionError, # CR_MALFORMED_PACKET 42 | 2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL 43 | 2048 => ConnectionError, # CR_INVALID_CONN_HANDLE 44 | 2049 => ConnectionError, # CR_UNUSED_1 45 | }.freeze 46 | 47 | attr_reader :error_number, :sql_state 48 | 49 | # Mysql gem compatibility 50 | alias errno error_number 51 | alias error message 52 | 53 | def initialize(msg, server_version = nil, error_number = nil, sql_state = nil) 54 | @server_version = server_version 55 | @error_number = error_number 56 | @sql_state = sql_state ? sql_state.encode(**ENCODE_OPTS) : nil 57 | 58 | super(clean_message(msg)) 59 | end 60 | 61 | def self.new_with_args(msg, server_version, error_number, sql_state) 62 | error_class = CODES.fetch(error_number, self) 63 | error_class.new(msg, server_version, error_number, sql_state) 64 | end 65 | 66 | private 67 | 68 | # In MySQL 5.5+ error messages are always constructed server-side as UTF-8 69 | # then returned in the encoding set by the `character_set_results` system 70 | # variable. 71 | # 72 | # See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for 73 | # more context. 74 | # 75 | # Before MySQL 5.5 error message template strings are in whatever encoding 76 | # is associated with the error message language. 77 | # See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html 78 | # for more information. 79 | # 80 | # The issue is that the user-data inserted in the message could potentially 81 | # be in any encoding MySQL supports and is insert into the latin1, euckr or 82 | # koi8r string raw. Meaning there's a high probability the string will be 83 | # corrupt encoding-wise. 84 | # 85 | # See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for 86 | # more information. 87 | # 88 | # So in an attempt to make sure the error message string is always in a valid 89 | # encoding, we'll assume UTF-8 and clean the string of anything that's not a 90 | # valid UTF-8 character. 91 | # 92 | # Returns a valid UTF-8 string. 93 | def clean_message(message) 94 | if @server_version && @server_version > 50500 95 | message.encode(**ENCODE_OPTS) 96 | else 97 | message.encode(Encoding::UTF_8, **ENCODE_OPTS) 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /tasks/compile.rake: -------------------------------------------------------------------------------- 1 | require "rake/extensiontask" 2 | 3 | load File.expand_path('../../mysql2.gemspec', __FILE__) unless defined? Mysql2::GEMSPEC 4 | 5 | Rake::ExtensionTask.new("mysql2", Mysql2::GEMSPEC) do |ext| 6 | # put binaries into lib/mysql2/ or lib/mysql2/x.y/ 7 | ext.lib_dir = File.join 'lib', 'mysql2' 8 | 9 | # clean compiled extension 10 | CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}" 11 | 12 | if RUBY_PLATFORM =~ /mswin|mingw/ && !defined?(RubyInstaller) 13 | # Expand the path because the build dir is 3-4 levels deep in tmp/platform/version/ 14 | connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) 15 | ext.config_options = ["--with-mysql-dir=#{connector_dir}"] 16 | else 17 | ext.cross_compile = true 18 | ext.cross_platform = ENV['CROSS_PLATFORMS'] ? ENV['CROSS_PLATFORMS'].split(':') : ['x86-mingw32', 'x86-mswin32-60', 'x64-mingw32'] 19 | ext.cross_config_options << { 20 | 'x86-mingw32' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__), 21 | 'x86-mswin32-60' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__), 22 | 'x64-mingw32' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x64')}", __FILE__), 23 | } 24 | 25 | ext.cross_compiling do |spec| 26 | Rake::Task['lib/mysql2/mysql2.rb'].invoke 27 | # vendor/libmysql.dll is invoked from extconf.rb 28 | Rake::Task['vendor/README'].invoke 29 | 30 | # only the source gem has a package dependency - the binary gem ships it's own DLL version 31 | spec.metadata.delete('msys2_mingw_dependencies') 32 | 33 | spec.files << 'lib/mysql2/mysql2.rb' 34 | spec.files << 'vendor/libmysql.dll' 35 | spec.files << 'vendor/README' 36 | spec.post_install_message = <<-POST_INSTALL_MESSAGE 37 | 38 | ====================================================================================================== 39 | 40 | You've installed the binary version of #{spec.name}. 41 | It was built using MySQL Connector/C version #{CONNECTOR_VERSION}. 42 | It's recommended to use the exact same version to avoid potential issues. 43 | 44 | At the time of building this gem, the necessary DLL files were retrieved from: 45 | #{vendor_mysql_url(spec.platform)} 46 | 47 | This gem *includes* vendor/libmysql.dll with redistribution notice in vendor/README. 48 | 49 | ====================================================================================================== 50 | 51 | POST_INSTALL_MESSAGE 52 | end 53 | end 54 | end 55 | Rake::Task[:spec].prerequisites << :compile 56 | 57 | file 'vendor/README' do 58 | connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) 59 | when_writing 'copying Connector/C README' do 60 | cp "#{connector_dir}/README", 'vendor/README' 61 | end 62 | end 63 | 64 | file 'lib/mysql2/mysql2.rb' do |t| 65 | name = Mysql2::GEMSPEC.name 66 | File.open(t.name, 'wb') do |f| 67 | f.write <<-END_OF_RUBY 68 | RUBY_VERSION =~ /(\\d+.\\d+)/ 69 | require "#{name}/\#{$1}/#{name}" 70 | END_OF_RUBY 71 | end 72 | end 73 | 74 | # DevKit task following the example of Luis Lavena's test-ruby-c-extension 75 | task :devkit do 76 | begin 77 | require "devkit" 78 | rescue LoadError 79 | abort "Failed to activate RubyInstaller's DevKit required for compilation." 80 | end 81 | end 82 | 83 | if RUBY_PLATFORM =~ /mingw|mswin/ 84 | Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' unless defined?(RubyInstaller) 85 | Rake::Task['compile'].prerequisites.unshift 'devkit' 86 | elsif Rake::Task.tasks.map(&:name).include? 'cross' 87 | Rake::Task['cross'].prerequisites.unshift 'vendor:mysql:cross' 88 | end 89 | 90 | desc "Build binary gems for Windows with rake-compiler-dock" 91 | task 'gem:windows' do 92 | require 'rake_compiler_dock' 93 | RakeCompilerDock.sh <<-EOT 94 | bundle install 95 | rake clean 96 | rm vendor/libmysql.dll 97 | rake cross native gem CROSS_PLATFORMS=x86-mingw32:x86-mswin32-60 98 | EOT 99 | RakeCompilerDock.sh <<-EOT 100 | bundle install 101 | rake clean 102 | rm vendor/libmysql.dll 103 | rake cross native gem CROSS_PLATFORMS=x64-mingw32 104 | EOT 105 | end 106 | -------------------------------------------------------------------------------- /ci/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | # Change the password to be empty. 6 | CHANGED_PASSWORD=false 7 | CHANGED_PASSWORD_SHA2=false 8 | # Change the password to be empty, recreating the root user on mariadb < 10.2 9 | # where ALTER USER is not available. 10 | # https://stackoverflow.com/questions/56052177/ 11 | CHANGED_PASSWORD_BY_RECREATE=false 12 | 13 | # Install the default used DB if DB is not set. 14 | if [[ -n ${GITHUB_ACTIONS-} && -z ${DB-} ]]; then 15 | if command -v lsb_release > /dev/null; then 16 | case "$(lsb_release -cs)" in 17 | xenial | bionic) 18 | sudo apt-get install -qq mysql-server-5.7 mysql-client-core-5.7 mysql-client-5.7 19 | CHANGED_PASSWORD=true 20 | ;; 21 | focal) 22 | sudo apt-get install -qq mysql-server-8.0 mysql-client-core-8.0 mysql-client-8.0 23 | CHANGED_PASSWORD=true 24 | ;; 25 | jammy) 26 | sudo apt-get install -qq mysql-server-8.0 mysql-client-core-8.0 mysql-client-8.0 27 | CHANGED_PASSWORD=true 28 | ;; 29 | *) 30 | ;; 31 | esac 32 | fi 33 | fi 34 | 35 | # Install MySQL 5.5 if DB=mysql55 36 | if [[ -n ${DB-} && x$DB =~ ^xmysql55 ]]; then 37 | sudo bash ci/mysql55.sh 38 | fi 39 | 40 | # Install MySQL 5.7 if DB=mysql57 41 | if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then 42 | sudo bash ci/mysql57.sh 43 | CHANGED_PASSWORD=true 44 | fi 45 | 46 | # Install MySQL 8.0 if DB=mysql80 47 | if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then 48 | sudo bash ci/mysql80.sh 49 | CHANGED_PASSWORD_SHA2=true 50 | fi 51 | 52 | # Install MySQL 8.4 if DB=mysql84 53 | if [[ -n ${DB-} && x$DB =~ ^xmysql84 ]]; then 54 | sudo bash ci/mysql84.sh 55 | CHANGED_PASSWORD_SHA2=true 56 | fi 57 | 58 | # Install MariaDB 10.6 if DB=mariadb10.6 59 | if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.6 ]]; then 60 | sudo bash ci/mariadb106.sh 61 | CHANGED_PASSWORD_BY_RECREATE=true 62 | fi 63 | 64 | # Install MariaDB 10.11 if DB=mariadb10.11 65 | if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb10.11 ]]; then 66 | sudo bash ci/mariadb1011.sh 67 | CHANGED_PASSWORD_BY_RECREATE=true 68 | fi 69 | 70 | # Install MariaDB 11.4 if DB=mariadb11.4 71 | if [[ -n ${GITHUB_ACTIONS-} && -n ${DB-} && x$DB =~ ^xmariadb11.4 ]]; then 72 | sudo bash ci/mariadb114.sh 73 | CHANGED_PASSWORD_BY_RECREATE=true 74 | fi 75 | 76 | # Install MySQL/MariaDB if OS=darwin 77 | if [[ x$OSTYPE =~ ^xdarwin ]]; then 78 | brew update > /dev/null 79 | 80 | # Check available packages. 81 | for KEYWORD in mysql mariadb; do 82 | brew search "${KEYWORD}" 83 | done 84 | 85 | brew info "$DB" 86 | brew install "$DB" zstd 87 | brew link "$DB" # explicitly activate in case of kegged LTS versions 88 | DB_PREFIX="$(brew --prefix "${DB}")" 89 | export PATH="${DB_PREFIX}/bin:${PATH}" 90 | export LDFLAGS="-L${DB_PREFIX}/lib" 91 | export CPPFLAGS="-I${DB_PREFIX}/include" 92 | 93 | mysql.server start 94 | CHANGED_PASSWORD_BY_RECREATE=true 95 | fi 96 | 97 | # TODO: get SSL working on OS X in Travis 98 | if ! [[ x$OSTYPE =~ ^xdarwin ]]; then 99 | sudo bash ci/ssl.sh 100 | sudo service mysql restart 101 | fi 102 | 103 | mysqld --version 104 | 105 | MYSQL_OPTS='' 106 | DB_SYS_USER=root 107 | if ! [[ x$OSTYPE =~ ^xdarwin ]]; then 108 | if [[ -n ${GITHUB_ACTIONS-} && -f /etc/mysql/debian.cnf ]]; then 109 | MYSQL_OPTS='--defaults-extra-file=/etc/mysql/debian.cnf' 110 | # Install from packages in OS official packages. 111 | if sudo grep -q debian-sys-maint /etc/mysql/debian.cnf; then 112 | # bionic, focal 113 | DB_SYS_USER=debian-sys-maint 114 | else 115 | # xenial 116 | DB_SYS_USER=root 117 | fi 118 | fi 119 | fi 120 | 121 | if [ "${CHANGED_PASSWORD}" = true ]; then 122 | # https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ 123 | sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" \ 124 | -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" 125 | elif [ "${CHANGED_PASSWORD_SHA2}" = true ]; then 126 | # In MySQL 5.7, the default authentication plugin is mysql_native_password. 127 | # As of MySQL 8.0, the default authentication plugin is changed to caching_sha2_password. 128 | sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" \ 129 | -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY ''" 130 | elif [ "${CHANGED_PASSWORD_BY_RECREATE}" = true ]; then 131 | sudo mysql ${MYSQL_OPTS} -u "${DB_SYS_USER}" <= "3.2" 13 | GC.verify_compaction_references(expand_heap: true, toward: :empty) 14 | else 15 | GC.verify_compaction_references(double_heap: true, toward: :empty) 16 | end 17 | end 18 | 19 | RSpec.configure do |config| 20 | config.disable_monkey_patching! 21 | 22 | config.expect_with :rspec do |expectations| 23 | expectations.max_formatted_output_length = 1200 24 | end 25 | 26 | def with_internal_encoding(encoding) 27 | old_enc = Encoding.default_internal 28 | old_verbose = $VERBOSE 29 | $VERBOSE = nil 30 | Encoding.default_internal = encoding 31 | $VERBOSE = old_verbose 32 | 33 | yield 34 | ensure 35 | $VERBOSE = nil 36 | Encoding.default_internal = old_enc 37 | $VERBOSE = old_verbose 38 | end 39 | 40 | def new_client(option_overrides = {}) 41 | client = Mysql2::Client.new(DatabaseCredentials['root'].merge(option_overrides)) 42 | @clients ||= [] 43 | @clients << client 44 | return client unless block_given? 45 | 46 | begin 47 | yield client 48 | ensure 49 | client.close 50 | @clients.delete(client) 51 | end 52 | end 53 | 54 | def num_classes 55 | # rubocop:disable Lint/UnifiedInteger 56 | 0.instance_of?(Integer) ? [Integer] : [Fixnum, Bignum] 57 | # rubocop:enable Lint/UnifiedInteger 58 | end 59 | 60 | # Use monotonic time if possible (ruby >= 2.1.0) 61 | if defined?(Process::CLOCK_MONOTONIC) 62 | def clock_time 63 | Process.clock_gettime Process::CLOCK_MONOTONIC 64 | end 65 | else 66 | def clock_time 67 | Time.now.to_f 68 | end 69 | end 70 | 71 | # A directory where SSL certificates pem files exist. 72 | def ssl_cert_dir 73 | return @ssl_cert_dir if @ssl_cert_dir 74 | 75 | dir = ENV['TEST_RUBY_MYSQL2_SSL_CERT_DIR'] 76 | @ssl_cert_dir = if dir && !dir.empty? 77 | dir 78 | else 79 | '/etc/mysql' 80 | end 81 | @ssl_cert_dir 82 | end 83 | 84 | # A host used to create the certificates pem files. 85 | def ssl_cert_host 86 | return @ssl_cert_host if @ssl_cert_host 87 | 88 | host = ENV['TEST_RUBY_MYSQL2_SSL_CERT_HOST'] 89 | @ssl_cert_host = if host && !host.empty? 90 | host 91 | else 92 | 'mysql2gem.example.com' 93 | end 94 | @ssl_cert_host 95 | end 96 | 97 | config.before(:suite) do 98 | begin 99 | new_client 100 | rescue Mysql2::Error => e 101 | username = DatabaseCredentials['root']['username'] 102 | database = DatabaseCredentials['root']['database'] 103 | message = %( 104 | An error occurred while connecting to the testing database server. 105 | Make sure that the database server is running. 106 | Make sure that `mysql -u #{username} [options] #{database}` succeeds by the root user config in spec/configuration.yml. 107 | Make sure that the testing database '#{database}' exists. If it does not exist, create it. 108 | ) 109 | warn message 110 | raise e 111 | end 112 | end 113 | 114 | config.before(:context) do 115 | new_client do |client| 116 | client.query %[ 117 | CREATE TABLE IF NOT EXISTS mysql2_test ( 118 | id MEDIUMINT NOT NULL AUTO_INCREMENT, 119 | null_test VARCHAR(10), 120 | bit_test BIT(64), 121 | single_bit_test BIT(1), 122 | tiny_int_test TINYINT, 123 | bool_cast_test TINYINT(1), 124 | small_int_test SMALLINT, 125 | medium_int_test MEDIUMINT, 126 | int_test INT, 127 | big_int_test BIGINT, 128 | float_test FLOAT(10,3), 129 | float_zero_test FLOAT(10,3), 130 | double_test DOUBLE(10,3), 131 | decimal_test DECIMAL(10,3), 132 | decimal_zero_test DECIMAL(10,3), 133 | date_test DATE, 134 | date_time_test DATETIME, 135 | timestamp_test TIMESTAMP, 136 | time_test TIME, 137 | year_test YEAR(4), 138 | char_test CHAR(10), 139 | varchar_test VARCHAR(10), 140 | binary_test BINARY(10), 141 | varbinary_test VARBINARY(10), 142 | tiny_blob_test TINYBLOB, 143 | tiny_text_test TINYTEXT, 144 | blob_test BLOB, 145 | text_test TEXT, 146 | medium_blob_test MEDIUMBLOB, 147 | medium_text_test MEDIUMTEXT, 148 | long_blob_test LONGBLOB, 149 | long_text_test LONGTEXT, 150 | enum_test ENUM('val1', 'val2'), 151 | set_test SET('val1', 'val2'), 152 | PRIMARY KEY (id) 153 | ) 154 | ] 155 | client.query "DELETE FROM mysql2_test;" 156 | client.query %[ 157 | INSERT INTO mysql2_test ( 158 | null_test, bit_test, single_bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test, 159 | float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test, 160 | year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test, 161 | tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test, 162 | long_blob_test, long_text_test, enum_test, set_test 163 | ) 164 | 165 | VALUES ( 166 | NULL, b'101', b'1', 1, 1, 10, 10, 10, 10, 167 | 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', 168 | 2009, "test", "test", "test", "test", "test", 169 | "test", "test", "test", "test", "test", 170 | "test", "test", 'val1', 'val1,val2' 171 | ) 172 | ] 173 | end 174 | end 175 | 176 | config.before(:example) do 177 | @client = new_client 178 | end 179 | 180 | config.after(:example) do 181 | @clients.each(&:close) 182 | end 183 | end 184 | -------------------------------------------------------------------------------- /ext/mysql2/mysql_enc_name_to_ruby.h: -------------------------------------------------------------------------------- 1 | /* C code produced by gperf version 3.0.4 */ 2 | /* Command-line: gperf */ 3 | /* Computed positions: -k'1,3,$' */ 4 | 5 | #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ 6 | && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ 7 | && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ 8 | && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ 9 | && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ 10 | && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ 11 | && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ 12 | && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ 13 | && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ 14 | && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ 15 | && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ 16 | && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ 17 | && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ 18 | && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ 19 | && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ 20 | && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ 21 | && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ 22 | && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ 23 | && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ 24 | && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ 25 | && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ 26 | && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ 27 | && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) 28 | /* The character set is not based on ISO-646. */ 29 | error "gperf generated tables don't work with this execution character set. Please report a bug to ." 30 | #endif 31 | 32 | struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; }; 33 | /* maximum key range = 71, duplicates = 0 */ 34 | 35 | #ifdef __GNUC__ 36 | __inline 37 | #else 38 | #ifdef __cplusplus 39 | inline 40 | #endif 41 | #endif 42 | static unsigned int 43 | mysql2_mysql_enc_name_to_rb_hash (str, len) 44 | register const char *str; 45 | register unsigned int len; 46 | { 47 | static const unsigned char asso_values[] = 48 | { 49 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 50 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 51 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 52 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 53 | 74, 74, 74, 74, 74, 74, 74, 74, 15, 5, 54 | 0, 30, 5, 25, 40, 10, 20, 50, 74, 74, 55 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 56 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 57 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 58 | 74, 74, 74, 74, 74, 74, 74, 40, 5, 0, 59 | 15, 10, 0, 0, 0, 5, 74, 0, 25, 5, 60 | 0, 5, 74, 74, 20, 5, 5, 0, 74, 45, 61 | 74, 0, 74, 74, 74, 74, 74, 74, 74, 74, 62 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 63 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 64 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 65 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 66 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 67 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 68 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 69 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 70 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 71 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 72 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 73 | 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74 | 74, 74, 74, 74, 74, 74 75 | }; 76 | return len + asso_values[(unsigned char)str[2]] + asso_values[(unsigned char)str[0]] + asso_values[(unsigned char)str[len - 1]]; 77 | } 78 | 79 | #ifdef __GNUC__ 80 | __inline 81 | #if defined __GNUC_STDC_INLINE__ || defined __GNUC_GNU_INLINE__ 82 | __attribute__ ((__gnu_inline__)) 83 | #endif 84 | #endif 85 | const struct mysql2_mysql_enc_name_to_rb_map * 86 | mysql2_mysql_enc_name_to_rb (str, len) 87 | register const char *str; 88 | register unsigned int len; 89 | { 90 | enum 91 | { 92 | TOTAL_KEYWORDS = 42, 93 | MIN_WORD_LENGTH = 3, 94 | MAX_WORD_LENGTH = 8, 95 | MIN_HASH_VALUE = 3, 96 | MAX_HASH_VALUE = 73 97 | }; 98 | 99 | static const struct mysql2_mysql_enc_name_to_rb_map wordlist[] = 100 | { 101 | {""}, {""}, {""}, 102 | {"gbk", "GBK"}, 103 | {""}, 104 | {"utf32", "UTF-32"}, 105 | {"gb2312", "GB2312"}, 106 | {"keybcs2", NULL}, 107 | {""}, 108 | {"ucs2", "UTF-16BE"}, 109 | {"koi8u", "KOI8-R"}, 110 | {"binary", "ASCII-8BIT"}, 111 | {"utf8mb4", "UTF-8"}, 112 | {"macroman", "macRoman"}, 113 | {"ujis", "eucJP-ms"}, 114 | {"greek", "ISO-8859-7"}, 115 | {"cp1251", "Windows-1251"}, 116 | {"utf16le", "UTF-16LE"}, 117 | {""}, 118 | {"sjis", "Shift_JIS"}, 119 | {"macce", "macCentEuro"}, 120 | {"cp1257", "Windows-1257"}, 121 | {"eucjpms", "eucJP-ms"}, 122 | {""}, 123 | {"utf8", "UTF-8"}, 124 | {"cp852", "CP852"}, 125 | {"cp1250", "Windows-1250"}, 126 | {"gb18030", "GB18030"}, 127 | {""}, 128 | {"swe7", NULL}, 129 | {"koi8r", "KOI8-R"}, 130 | {"tis620", "TIS-620"}, 131 | {"geostd8", NULL}, 132 | {""}, 133 | {"big5", "Big5"}, 134 | {"euckr", "EUC-KR"}, 135 | {"latin2", "ISO-8859-2"}, 136 | {"utf8mb3", "UTF-8"}, 137 | {""}, 138 | {"dec8", NULL}, 139 | {"cp850", "CP850"}, 140 | {"latin1", "ISO-8859-1"}, 141 | {""}, 142 | {"hp8", NULL}, 143 | {""}, 144 | {"utf16", "UTF-16"}, 145 | {"latin7", "ISO-8859-13"}, 146 | {""}, {""}, {""}, 147 | {"ascii", "US-ASCII"}, 148 | {"cp1256", "Windows-1256"}, 149 | {""}, {""}, {""}, 150 | {"cp932", "Windows-31J"}, 151 | {"hebrew", "ISO-8859-8"}, 152 | {""}, {""}, {""}, {""}, 153 | {"latin5", "ISO-8859-9"}, 154 | {""}, {""}, {""}, 155 | {"cp866", "IBM866"}, 156 | {""}, {""}, {""}, {""}, {""}, {""}, {""}, 157 | {"armscii8", NULL} 158 | }; 159 | 160 | if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) 161 | { 162 | register int key = mysql2_mysql_enc_name_to_rb_hash (str, len); 163 | 164 | if (key <= MAX_HASH_VALUE && key >= 0) 165 | { 166 | register const char *s = wordlist[key].name; 167 | 168 | if (*str == *s && !strcmp (str + 1, s + 1)) 169 | return &wordlist[key]; 170 | } 171 | } 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /support/C74CD1D8.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | 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 103 | =mCOI 104 | -----END PGP PUBLIC KEY BLOCK----- 105 | -------------------------------------------------------------------------------- /lib/mysql2/client.rb: -------------------------------------------------------------------------------- 1 | module Mysql2 2 | class Client 3 | attr_reader :query_options, :read_timeout 4 | 5 | def self.default_query_options 6 | @default_query_options ||= { 7 | as: :hash, # the type of object you want each row back as; also supports :array (an array of values) 8 | 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 9 | cast_booleans: false, # cast tinyint(1) fields as true/false in ruby 10 | symbolize_keys: false, # return field names as symbols instead of strings 11 | database_timezone: :local, # timezone Mysql2 will assume datetime objects are stored in 12 | application_timezone: nil, # timezone Mysql2 will convert to before handing the object back to the caller 13 | cache_rows: true, # tells Mysql2 to use its internal row cache for results 14 | connect_flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | CONNECT_ATTRS, 15 | cast: true, 16 | default_file: nil, 17 | default_group: nil, 18 | } 19 | end 20 | 21 | def initialize(opts = {}) 22 | raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash 23 | 24 | opts = Mysql2::Util.key_hash_as_symbols(opts) 25 | @read_timeout = nil 26 | @query_options = self.class.default_query_options.dup 27 | @query_options.merge! opts 28 | 29 | initialize_ext 30 | 31 | # Set default connect_timeout to avoid unlimited retries from signal interruption 32 | opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) 33 | 34 | # TODO: stricter validation rather than silent massaging 35 | %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| 36 | next unless opts.key?(key) 37 | 38 | case key 39 | when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin, :get_server_public_key 40 | send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation 41 | when :connect_timeout, :read_timeout, :write_timeout 42 | send(:"#{key}=", Integer(opts[key])) unless opts[key].nil? 43 | else 44 | send(:"#{key}=", opts[key]) 45 | end 46 | end 47 | 48 | # force the encoding to utf8mb4 49 | self.charset_name = opts[:encoding] || 'utf8mb4' 50 | 51 | mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode] 52 | if (mode == SSL_MODE_VERIFY_CA || mode == SSL_MODE_VERIFY_IDENTITY) && !opts[:sslca] 53 | opts[:sslca] = find_default_ca_path 54 | end 55 | 56 | ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher) 57 | ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify) 58 | self.ssl_mode = mode if mode 59 | 60 | flags = case opts[:flags] 61 | when Array 62 | parse_flags_array(opts[:flags], @query_options[:connect_flags]) 63 | when String 64 | parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags]) 65 | when Integer 66 | @query_options[:connect_flags] | opts[:flags] 67 | else 68 | @query_options[:connect_flags] 69 | end 70 | 71 | # SSL verify is a connection flag rather than a mysql_ssl_set option 72 | flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] 73 | 74 | check_and_clean_query_options 75 | 76 | user = opts[:username] || opts[:user] 77 | pass = opts[:password] || opts[:pass] 78 | host = opts[:host] || opts[:hostname] 79 | port = opts[:port] 80 | database = opts[:database] || opts[:dbname] || opts[:db] 81 | socket = opts[:socket] || opts[:sock] 82 | 83 | # Correct the data types before passing these values down to the C level 84 | user = user.to_s unless user.nil? 85 | pass = pass.to_s unless pass.nil? 86 | host = host.to_s unless host.nil? 87 | port = port.to_i unless port.nil? 88 | database = database.to_s unless database.nil? 89 | socket = socket.to_s unless socket.nil? 90 | conn_attrs = parse_connect_attrs(opts[:connect_attrs]) 91 | 92 | connect user, pass, host, port, database, socket, flags, conn_attrs 93 | end 94 | 95 | def parse_ssl_mode(mode) 96 | m = mode.to_s.upcase 97 | if m.start_with?('SSL_MODE_') 98 | return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m) 99 | else 100 | x = 'SSL_MODE_' + m 101 | return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x) 102 | end 103 | warn "Unknown MySQL ssl_mode flag: #{mode}" 104 | end 105 | 106 | def parse_flags_array(flags, initial = 0) 107 | flags.reduce(initial) do |memo, f| 108 | fneg = f.start_with?('-') ? f[1..-1] : nil 109 | if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg) 110 | memo & ~ Mysql2::Client.const_get(fneg) 111 | elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f) 112 | memo | Mysql2::Client.const_get(f) 113 | else 114 | warn "Unknown MySQL connection flag: '#{f}'" 115 | memo 116 | end 117 | end 118 | end 119 | 120 | # Find any default system CA paths to handle system roots 121 | # by default if stricter validation is requested and no 122 | # path is provide. 123 | def find_default_ca_path 124 | [ 125 | "/etc/ssl/certs/ca-certificates.crt", 126 | "/etc/pki/tls/certs/ca-bundle.crt", 127 | "/etc/ssl/ca-bundle.pem", 128 | "/etc/ssl/cert.pem", 129 | ].find { |f| File.exist?(f) } 130 | end 131 | 132 | # Set default program_name in performance_schema.session_connect_attrs 133 | # and performance_schema.session_account_connect_attrs 134 | def parse_connect_attrs(conn_attrs) 135 | return {} if Mysql2::Client::CONNECT_ATTRS.zero? 136 | 137 | conn_attrs ||= {} 138 | conn_attrs[:program_name] ||= $PROGRAM_NAME 139 | conn_attrs.each_with_object({}) do |(key, value), hash| 140 | hash[key.to_s] = value.to_s 141 | end 142 | end 143 | 144 | def query(sql, options = {}) 145 | Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do 146 | _query(sql, @query_options.merge(options)) 147 | end 148 | end 149 | 150 | def query_info 151 | info = query_info_string 152 | return {} unless info 153 | 154 | info_hash = {} 155 | info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i } 156 | info_hash 157 | end 158 | 159 | def info 160 | self.class.info 161 | end 162 | 163 | private 164 | 165 | def check_and_clean_query_options 166 | if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) } 167 | warn "============= WARNING FROM mysql2 =============" 168 | warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future." 169 | warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options." 170 | warn "============= END WARNING FROM mysql2 =========" 171 | end 172 | 173 | # avoid logging sensitive data via #inspect 174 | @query_options.delete(:password) 175 | @query_options.delete(:pass) 176 | end 177 | 178 | class << self 179 | private 180 | 181 | def local_offset 182 | ::Time.local(2010).utc_offset.to_r / 86400 183 | end 184 | end 185 | end 186 | end 187 | -------------------------------------------------------------------------------- /ext/mysql2/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | require 'English' 3 | 4 | ### Some helper functions 5 | 6 | def asplode(lib) 7 | if RUBY_PLATFORM =~ /mingw|mswin/ 8 | abort "-----\n#{lib} is missing. Check your installation of MySQL or Connector/C, and try again.\n-----" 9 | elsif RUBY_PLATFORM =~ /darwin/ 10 | abort "-----\n#{lib} is missing. You may need to 'brew install mysql' or 'port install mysql', and try again.\n-----" 11 | else 12 | 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-----" 13 | end 14 | end 15 | 16 | def add_ssl_defines(header) 17 | 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| 18 | m && have_const(ssl_mode, header) 19 | end 20 | if all_modes_found 21 | $CFLAGS << ' -DFULL_SSL_MODE_SUPPORT' 22 | else 23 | # if we only have ssl toggle (--ssl,--disable-ssl) from 5.7.3 to 5.7.10 24 | # and the verify server cert option. This is also the case for MariaDB. 25 | has_verify_support = have_const('MYSQL_OPT_SSL_VERIFY_SERVER_CERT', header) 26 | has_enforce_support = have_const('MYSQL_OPT_SSL_ENFORCE', header) 27 | $CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if !has_verify_support && !has_enforce_support 28 | end 29 | end 30 | 31 | ### Check for Ruby C extension interfaces 32 | 33 | # 2.1+ 34 | have_func('rb_absint_size') 35 | have_func('rb_absint_singlebit_p') 36 | 37 | # 2.7+ 38 | have_func('rb_gc_mark_movable') 39 | 40 | # Missing in RBX (https://github.com/rubinius/rubinius/issues/3771) 41 | have_func('rb_wait_for_single_fd') 42 | 43 | # 3.0+ 44 | have_func('rb_enc_interned_str', 'ruby.h') 45 | 46 | ### Find OpenSSL library 47 | 48 | # User-specified OpenSSL if explicitly specified 49 | if with_config('openssl-dir') 50 | _, lib = dir_config('openssl') 51 | if lib 52 | # Ruby versions below 2.0 on Unix and below 2.1 on Windows 53 | # do not properly search for lib directories, and must be corrected: 54 | # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 55 | unless lib && lib[-3, 3] == 'lib' 56 | @libdir_basename = 'lib' 57 | _, lib = dir_config('openssl') 58 | end 59 | abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } 60 | warn "-----\nUsing --with-openssl-dir=#{File.dirname lib}\n-----" 61 | $LDFLAGS << " -L#{lib}" 62 | end 63 | # Homebrew OpenSSL on MacOS 64 | elsif RUBY_PLATFORM =~ /darwin/ && system('command -v brew') 65 | openssl_location = `brew --prefix openssl`.strip 66 | $LIBPATH << "#{openssl_location}/lib" unless openssl_location.empty? 67 | end 68 | 69 | if RUBY_PLATFORM =~ /darwin/ && system('command -v brew') 70 | zstd_location = `brew --prefix zstd`.strip 71 | $LIBPATH << "#{zstd_location}/lib" unless zstd_location.empty? 72 | end 73 | 74 | ### Find MySQL client library 75 | 76 | # borrowed from mysqlplus 77 | # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb 78 | dirs = ENV.fetch('PATH').split(File::PATH_SEPARATOR) + %w[ 79 | /opt 80 | /opt/local 81 | /opt/local/mysql 82 | /opt/local/lib/mysql5* 83 | /opt/homebrew/opt/mysql* 84 | /usr 85 | /usr/mysql 86 | /usr/local 87 | /usr/local/mysql 88 | /usr/local/mysql-* 89 | /usr/local/lib/mysql5* 90 | /usr/local/opt/mysql5* 91 | /usr/local/opt/mysql@* 92 | /usr/local/opt/mysql-client 93 | /usr/local/opt/mysql-client@* 94 | ].map { |dir| "#{dir}/bin" } 95 | 96 | # For those without HOMEBREW_ROOT in PATH 97 | dirs << "#{ENV['HOMEBREW_ROOT']}/bin" if ENV['HOMEBREW_ROOT'] 98 | 99 | GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}".freeze 100 | 101 | # If the user has provided a --with-mysql-dir argument, we must respect it or fail. 102 | inc, lib = dir_config('mysql') 103 | if inc && lib 104 | # Ruby versions below 2.0 on Unix and below 2.1 on Windows 105 | # do not properly search for lib directories, and must be corrected: 106 | # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 107 | unless lib && lib[-3, 3] == 'lib' 108 | @libdir_basename = 'lib' 109 | inc, lib = dir_config('mysql') 110 | end 111 | abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } 112 | abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } 113 | warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" 114 | rpath_dir = lib 115 | have_library('mysqlclient') 116 | elsif (mc = with_config('mysql-config') || Dir[GLOB].first) 117 | # If the user has provided a --with-mysql-config argument, we must respect it or fail. 118 | # If the user gave --with-mysql-config with no argument means we should try to find it. 119 | mc = Dir[GLOB].first if mc == true 120 | abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exist?(mc) 121 | abort "-----\nCannot execute mysql_config at #{mc}\n-----" unless File.executable?(mc) 122 | warn "-----\nUsing mysql_config at #{mc}\n-----" 123 | ver = `#{mc} --version`.chomp.to_f 124 | includes = `#{mc} --include`.chomp 125 | abort unless $CHILD_STATUS.success? 126 | libs = `#{mc} --libs_r`.chomp 127 | # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r). 128 | libs = `#{mc} --libs`.chomp if ver >= 5.5 || libs.empty? 129 | abort unless $CHILD_STATUS.success? 130 | $INCFLAGS += ' ' + includes 131 | $libs = libs + " " + $libs 132 | rpath_dir = libs 133 | else 134 | _, usr_local_lib = dir_config('mysql', '/usr/local') 135 | 136 | asplode("mysql client") unless find_library('mysqlclient', nil, usr_local_lib, "#{usr_local_lib}/mysql") 137 | 138 | rpath_dir = usr_local_lib 139 | end 140 | 141 | if have_header('mysql.h') 142 | prefix = nil 143 | elsif have_header('mysql/mysql.h') 144 | prefix = 'mysql' 145 | else 146 | asplode 'mysql.h' 147 | end 148 | 149 | %w[errmsg.h].each do |h| 150 | header = [prefix, h].compact.join('/') 151 | asplode h unless have_header header 152 | end 153 | 154 | mysql_h = [prefix, 'mysql.h'].compact.join('/') 155 | add_ssl_defines(mysql_h) 156 | have_struct_member('MYSQL', 'net.vio', mysql_h) 157 | have_struct_member('MYSQL', 'net.pvio', mysql_h) 158 | 159 | # These constants are actually enums, so they cannot be detected by #ifdef in C code. 160 | have_const('MYSQL_DEFAULT_AUTH', mysql_h) 161 | have_const('MYSQL_ENABLE_CLEARTEXT_PLUGIN', mysql_h) 162 | have_const('SERVER_QUERY_NO_GOOD_INDEX_USED', mysql_h) 163 | have_const('SERVER_QUERY_NO_INDEX_USED', mysql_h) 164 | have_const('SERVER_QUERY_WAS_SLOW', mysql_h) 165 | have_const('MYSQL_OPTION_MULTI_STATEMENTS_ON', mysql_h) 166 | have_const('MYSQL_OPTION_MULTI_STATEMENTS_OFF', mysql_h) 167 | have_const('MYSQL_OPT_GET_SERVER_PUBLIC_KEY', mysql_h) 168 | 169 | # my_bool is replaced by C99 bool in MySQL 8.0, but we want 170 | # to retain compatibility with the typedef in earlier MySQLs. 171 | have_type('my_bool', mysql_h) 172 | 173 | # detect mysql functions 174 | have_func('mysql_ssl_set', mysql_h) 175 | 176 | ### Compiler flags to help catch errors 177 | 178 | # This is our wishlist. We use whichever flags work on the host. 179 | # -Wall and -Wextra are included by default. 180 | wishlist = [ 181 | '-Weverything', 182 | '-Wno-compound-token-split-by-macro', # Fixed in Ruby 2.7+ at https://bugs.ruby-lang.org/issues/17865 183 | '-Wno-bad-function-cast', # rb_thread_call_without_gvl returns void * that we cast to VALUE 184 | '-Wno-conditional-uninitialized', # false positive in client.c 185 | '-Wno-covered-switch-default', # result.c -- enum_field_types (when fully covered, e.g. mysql 5.5) 186 | '-Wno-declaration-after-statement', # GET_CLIENT followed by GET_STATEMENT in statement.c 187 | '-Wno-disabled-macro-expansion', # rubby :( 188 | '-Wno-documentation-unknown-command', # rubby :( 189 | '-Wno-missing-field-initializers', # gperf generates bad code 190 | '-Wno-missing-variable-declarations', # missing symbols due to ruby native ext initialization 191 | '-Wno-padded', # mysql :( 192 | '-Wno-reserved-id-macro', # rubby :( 193 | '-Wno-sign-conversion', # gperf generates bad code 194 | '-Wno-static-in-inline', # gperf generates bad code 195 | '-Wno-switch-enum', # result.c -- enum_field_types (when not fully covered, e.g. mysql 5.6+) 196 | '-Wno-undef', # rubinius :( 197 | '-Wno-unreachable-code', # rubby :( 198 | '-Wno-used-but-marked-unused', # rubby :( 199 | ] 200 | 201 | usable_flags = wishlist.select do |flag| 202 | try_link('int main() {return 0;}', "-Werror #{flag}") 203 | end 204 | 205 | $CFLAGS << ' ' << usable_flags.join(' ') 206 | 207 | ### Sanitizers to help with debugging -- many are available on both Clang/LLVM and GCC 208 | 209 | enabled_sanitizers = disabled_sanitizers = [] 210 | # Specify a comma-separated list of sanitizers, or try them all by default 211 | sanitizers = with_config('sanitize') 212 | case sanitizers 213 | when true 214 | # Try them all, turn on whatever we can 215 | enabled_sanitizers = %w[address cfi integer memory thread undefined].select do |s| 216 | try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") 217 | end 218 | abort "-----\nCould not enable any sanitizers!\n-----" if enabled_sanitizers.empty? 219 | when String 220 | # Figure out which sanitizers are supported 221 | enabled_sanitizers, disabled_sanitizers = sanitizers.split(',').partition do |s| 222 | try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") 223 | end 224 | end 225 | 226 | unless disabled_sanitizers.empty? # rubocop:disable Style/IfUnlessModifier 227 | abort "-----\nCould not enable requested sanitizers: #{disabled_sanitizers.join(',')}\n-----" 228 | end 229 | 230 | unless enabled_sanitizers.empty? 231 | warn "-----\nEnabling sanitizers: #{enabled_sanitizers.join(',')}\n-----" 232 | enabled_sanitizers.each do |s| 233 | # address sanitizer requires runtime support 234 | if s == 'address' # rubocop:disable Style/IfUnlessModifier 235 | have_library('asan') || $LDFLAGS << ' -fsanitize=address' 236 | end 237 | $CFLAGS << " -fsanitize=#{s}" 238 | end 239 | # Options for line numbers in backtraces 240 | $CFLAGS << ' -g -fno-omit-frame-pointer' 241 | end 242 | 243 | ### Find MySQL Client on Windows, set RPATH to find the library at runtime 244 | 245 | if RUBY_PLATFORM =~ /mswin|mingw/ && !defined?(RubyInstaller) 246 | # Build libmysql.a interface link library 247 | require 'rake' 248 | 249 | # Build libmysql.a interface link library 250 | # Use rake to rebuild only if these files change 251 | deffile = File.expand_path('../../../support/libmysql.def', __FILE__) 252 | libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) 253 | file 'libmysql.a' => [deffile, libfile] do 254 | when_writing 'building libmysql.a' do 255 | # Ruby kindly shows us where dllwrap is, but that tool does more than we want. 256 | # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. 257 | dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool') 258 | sh dlltool, '--kill-at', 259 | '--dllname', 'libmysql.dll', 260 | '--output-lib', 'libmysql.a', 261 | '--input-def', deffile, libfile 262 | end 263 | end 264 | 265 | Rake::Task['libmysql.a'].invoke 266 | $LOCAL_LIBS << ' ' << 'libmysql.a' 267 | 268 | # Make sure the generated interface library works (if cross-compiling, trust without verifying) 269 | unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ 270 | abort "-----\nCannot find libmysql.a\n-----" unless have_library('libmysql') 271 | abort "-----\nCannot link to libmysql.a (my_init)\n-----" unless have_func('my_init') 272 | end 273 | 274 | # Vendor libmysql.dll 275 | vendordir = File.expand_path('../../../vendor/', __FILE__) 276 | directory vendordir 277 | 278 | vendordll = File.join(vendordir, 'libmysql.dll') 279 | dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) 280 | file vendordll => [dllfile, vendordir] do 281 | when_writing 'copying libmysql.dll' do 282 | cp dllfile, vendordll 283 | end 284 | end 285 | 286 | # Copy libmysql.dll to the local vendor directory by default 287 | if arg_config('--no-vendor-libmysql') 288 | # Fine, don't. 289 | puts "--no-vendor-libmysql" 290 | else # Default: arg_config('--vendor-libmysql') 291 | # Let's do it! 292 | Rake::Task[vendordll].invoke 293 | end 294 | else 295 | case explicit_rpath = with_config('mysql-rpath') 296 | when true 297 | abort "-----\nOption --with-mysql-rpath must have an argument\n-----" 298 | when false 299 | warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" 300 | when String 301 | # The user gave us a value so use it 302 | rpath_flags = " -Wl,-rpath,#{explicit_rpath}" 303 | warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" 304 | $LDFLAGS << rpath_flags 305 | else 306 | if (libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2]) 307 | rpath_flags = " -Wl,-rpath,#{libdir}" 308 | if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) 309 | # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. 310 | warn "-----\nSetting rpath to #{libdir}\n-----" 311 | $LDFLAGS << rpath_flags 312 | else 313 | if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? 314 | # If we got here because try_link failed, warn the user 315 | warn "-----\nDon't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load\n-----" 316 | end 317 | # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. 318 | warn "-----\nSetting libpath to #{libdir}\n-----" 319 | $LIBPATH << libdir unless $LIBPATH.include?(libdir) 320 | end 321 | end 322 | end 323 | end 324 | 325 | create_makefile('mysql2/mysql2') 326 | -------------------------------------------------------------------------------- /spec/mysql2/result_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Mysql2::Result do 4 | before(:example) do 5 | @result = @client.query "SELECT 1" 6 | end 7 | 8 | it "should raise a TypeError exception when it doesn't wrap a result set" do 9 | expect { Mysql2::Result.new }.to raise_error(TypeError) 10 | expect { Mysql2::Result.allocate }.to raise_error(TypeError) 11 | end 12 | 13 | it "should have included Enumerable" do 14 | expect(Mysql2::Result.ancestors.include?(Enumerable)).to be true 15 | end 16 | 17 | it "should respond to #each" do 18 | expect(@result).to respond_to(:each) 19 | end 20 | 21 | it "should respond to #free" do 22 | expect(@result).to respond_to(:free) 23 | end 24 | 25 | it "should raise a Mysql2::Error exception upon a bad query" do 26 | expect do 27 | @client.query "bad sql" 28 | end.to raise_error(Mysql2::Error) 29 | 30 | expect do 31 | @client.query "SELECT 1" 32 | end.not_to raise_error 33 | end 34 | 35 | it "should respond to #count, which is aliased as #size" do 36 | r = @client.query "SELECT 1" 37 | expect(r).to respond_to :count 38 | expect(r).to respond_to :size 39 | end 40 | 41 | it "should be able to return the number of rows in the result set" do 42 | r = @client.query "SELECT 1" 43 | expect(r.count).to eql(1) 44 | expect(r.size).to eql(1) 45 | end 46 | 47 | context "metadata queries" do 48 | it "should show tables" do 49 | @result = @client.query "SHOW TABLES" 50 | end 51 | end 52 | 53 | context "#each" do 54 | it "should yield rows as hash's" do 55 | @result.each do |row| 56 | expect(row).to be_an_instance_of(Hash) 57 | end 58 | end 59 | 60 | it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do 61 | @result.each(symbolize_keys: true) do |row| 62 | expect(row.keys.first).to be_an_instance_of(Symbol) 63 | end 64 | end 65 | 66 | it "should be able to return results as an array" do 67 | @result.each(as: :array) do |row| 68 | expect(row).to be_an_instance_of(Array) 69 | end 70 | end 71 | 72 | it "should cache previously yielded results by default" do 73 | expect(@result.first.object_id).to eql(@result.first.object_id) 74 | end 75 | 76 | it "should not cache previously yielded results if cache_rows is disabled" do 77 | result = @client.query "SELECT 1", cache_rows: false 78 | expect(result.first.object_id).not_to eql(result.first.object_id) 79 | end 80 | 81 | it "should be able to iterate a second time even if cache_rows is disabled" do 82 | result = @client.query "SELECT 1 UNION SELECT 2", cache_rows: false 83 | expect(result.to_a).to eql(result.to_a) 84 | end 85 | 86 | it "should yield different value for #first if streaming" do 87 | result = @client.query "SELECT 1 UNION SELECT 2", stream: true, cache_rows: false 88 | expect(result.first).not_to eql(result.first) 89 | end 90 | 91 | it "should yield the same value for #first if streaming is disabled" do 92 | result = @client.query "SELECT 1 UNION SELECT 2", stream: false 93 | expect(result.first).to eql(result.first) 94 | end 95 | 96 | it "should throw an exception if we try to iterate twice when streaming is enabled" do 97 | result = @client.query "SELECT 1 UNION SELECT 2", stream: true, cache_rows: false 98 | 99 | expect do 100 | result.each.to_a 101 | result.each.to_a 102 | end.to raise_exception(Mysql2::Error) 103 | end 104 | end 105 | 106 | context "#fields" do 107 | let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") } 108 | 109 | it "method should exist" do 110 | expect(test_result).to respond_to(:fields) 111 | end 112 | 113 | it "should return an array of field names in proper order" do 114 | result = @client.query "SELECT 'a', 'b', 'c'" 115 | expect(result.fields).to eql(%w[a b c]) 116 | end 117 | 118 | it "should return an array of frozen strings" do 119 | result = @client.query "SELECT 'a', 'b', 'c'" 120 | result.fields.each do |f| 121 | expect(f).to be_frozen 122 | end 123 | end 124 | end 125 | 126 | context "#field_types" do 127 | let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") } 128 | 129 | it "method should exist" do 130 | expect(test_result).to respond_to(:field_types) 131 | end 132 | 133 | it "should return correct types" do 134 | expected_types = %w[ 135 | mediumint(9) 136 | varchar(13) 137 | bit(64) 138 | bit(1) 139 | tinyint(4) 140 | tinyint(1) 141 | smallint(6) 142 | mediumint(9) 143 | int(11) 144 | bigint(20) 145 | float(10,3) 146 | float(10,3) 147 | double(10,3) 148 | decimal(10,3) 149 | decimal(10,3) 150 | date 151 | datetime 152 | timestamp 153 | time 154 | year(4) 155 | char(13) 156 | varchar(13) 157 | binary(10) 158 | varbinary(10) 159 | tinyblob 160 | text(1020) 161 | blob 162 | text(262140) 163 | mediumblob 164 | text(67108860) 165 | longblob 166 | longtext 167 | enum 168 | set 169 | ] 170 | 171 | expect(test_result.field_types).to eql(expected_types) 172 | end 173 | 174 | it "should return an array of field types in proper order" do 175 | result = @client.query( 176 | "SELECT cast('a' as char), " \ 177 | "cast(1.2 as decimal(15, 2)), " \ 178 | "cast(1.2 as decimal(15, 5)), " \ 179 | "cast(1.2 as decimal(15, 4)), " \ 180 | "cast(1.2 as decimal(15, 10)), " \ 181 | "cast(1.2 as decimal(14, 0)), " \ 182 | "cast(1.2 as decimal(15, 0)), " \ 183 | "cast(1.2 as decimal(16, 0)), " \ 184 | "cast(1.0 as decimal(16, 1))", 185 | ) 186 | 187 | expected_types = %w[ 188 | varchar(1) 189 | decimal(15,2) 190 | decimal(15,5) 191 | decimal(15,4) 192 | decimal(15,10) 193 | decimal(14,0) 194 | decimal(15,0) 195 | decimal(16,0) 196 | decimal(16,1) 197 | ] 198 | 199 | expect(result.field_types).to eql(expected_types) 200 | end 201 | 202 | it "should return json type on mysql 8.0" do 203 | next unless /8.\d+.\d+/ =~ @client.server_info[:version] 204 | 205 | result = @client.query("SELECT JSON_OBJECT('key', 'value')") 206 | expect(result.field_types).to eql(['json']) 207 | end 208 | end 209 | 210 | context "streaming" do 211 | it "should maintain a count while streaming" do 212 | result = @client.query('SELECT 1', stream: true, cache_rows: false) 213 | expect(result.count).to eql(0) 214 | result.each.to_a 215 | expect(result.count).to eql(1) 216 | end 217 | 218 | it "should retain the count when mixing first and each" do 219 | result = @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false) 220 | expect(result.count).to eql(0) 221 | result.first 222 | expect(result.count).to eql(1) 223 | result.each.to_a 224 | expect(result.count).to eql(2) 225 | end 226 | 227 | it "should not yield nil at the end of streaming" do 228 | result = @client.query('SELECT * FROM mysql2_test', stream: true, cache_rows: false) 229 | result.each { |r| expect(r).not_to be_nil } 230 | end 231 | 232 | it "#count should be zero for rows after streaming when there were no results" do 233 | @client.query "USE test" 234 | result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", stream: true, cache_rows: false) 235 | expect(result.count).to eql(0) 236 | result.each.to_a 237 | expect(result.count).to eql(0) 238 | end 239 | 240 | it "should raise an exception if streaming ended due to a timeout" do 241 | @client.query "CREATE TEMPORARY TABLE streamingTest (val BINARY(255)) ENGINE=MEMORY" 242 | 243 | # Insert enough records to force the result set into multiple reads 244 | # (the BINARY type is used simply because it forces full width results) 245 | 10000.times do |i| 246 | @client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')" 247 | end 248 | 249 | @client.query "SET net_write_timeout = 1" 250 | res = @client.query "SELECT * FROM streamingTest", stream: true, cache_rows: false 251 | 252 | expect do 253 | res.each_with_index do |_, i| 254 | # Exhaust the first result packet then trigger a timeout 255 | sleep 4 if i > 0 && i % 1000 == 0 256 | end 257 | end.to raise_error(Mysql2::Error, /Lost connection/) 258 | end 259 | end 260 | 261 | context "row data type mapping" do 262 | let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first } 263 | 264 | it "should return nil values for NULL and strings for everything else when :cast is false" do 265 | 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 266 | expect(result["null_test"]).to be_nil 267 | expect(result["tiny_int_test"]).to eql("1") 268 | expect(result["bool_cast_test"]).to eql("1") 269 | expect(result["int_test"]).to eql("10") 270 | expect(result["date_test"]).to eql("2010-04-04") 271 | expect(result["enum_test"]).to eql("val1") 272 | end 273 | 274 | it "should return nil for a NULL value" do 275 | expect(test_result['null_test']).to be_an_instance_of(NilClass) 276 | expect(test_result['null_test']).to eql(nil) 277 | end 278 | 279 | it "should return String for a BIT(64) value" do 280 | expect(test_result['bit_test']).to be_an_instance_of(String) 281 | expect(test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") 282 | end 283 | 284 | it "should return String for a BIT(1) value" do 285 | expect(test_result['single_bit_test']).to be_an_instance_of(String) 286 | expect(test_result['single_bit_test']).to eql("\001") 287 | end 288 | 289 | it "should return Fixnum for a TINYINT value" do 290 | expect(num_classes).to include(test_result['tiny_int_test'].class) 291 | expect(test_result['tiny_int_test']).to eql(1) 292 | end 293 | 294 | context "cast booleans for TINYINT if :cast_booleans is enabled" do 295 | # rubocop:disable Style/Semicolon 296 | let(:id1) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; @client.last_id } 297 | let(:id2) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; @client.last_id } 298 | let(:id3) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; @client.last_id } 299 | # rubocop:enable Style/Semicolon 300 | 301 | after do 302 | @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" 303 | end 304 | 305 | it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do 306 | result1 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id1} LIMIT 1", cast_booleans: true 307 | result2 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id2} LIMIT 1", cast_booleans: true 308 | result3 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id3} LIMIT 1", cast_booleans: true 309 | expect(result1.first['bool_cast_test']).to be true 310 | expect(result2.first['bool_cast_test']).to be false 311 | expect(result3.first['bool_cast_test']).to be true 312 | end 313 | end 314 | 315 | context "cast booleans for BIT(1) if :cast_booleans is enabled" do 316 | # rubocop:disable Style/Semicolon 317 | let(:id1) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; @client.last_id } 318 | let(:id2) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; @client.last_id } 319 | # rubocop:enable Style/Semicolon 320 | 321 | after do 322 | @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" 323 | end 324 | 325 | it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do 326 | result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", cast_booleans: true 327 | result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", cast_booleans: true 328 | expect(result1.first['single_bit_test']).to be true 329 | expect(result2.first['single_bit_test']).to be false 330 | end 331 | end 332 | 333 | it "should return Fixnum for a SMALLINT value" do 334 | expect(num_classes).to include(test_result['small_int_test'].class) 335 | expect(test_result['small_int_test']).to eql(10) 336 | end 337 | 338 | it "should return Fixnum for a MEDIUMINT value" do 339 | expect(num_classes).to include(test_result['medium_int_test'].class) 340 | expect(test_result['medium_int_test']).to eql(10) 341 | end 342 | 343 | it "should return Fixnum for an INT value" do 344 | expect(num_classes).to include(test_result['int_test'].class) 345 | expect(test_result['int_test']).to eql(10) 346 | end 347 | 348 | it "should return Fixnum for a BIGINT value" do 349 | expect(num_classes).to include(test_result['big_int_test'].class) 350 | expect(test_result['big_int_test']).to eql(10) 351 | end 352 | 353 | it "should return Fixnum for a YEAR value" do 354 | expect(num_classes).to include(test_result['year_test'].class) 355 | expect(test_result['year_test']).to eql(2009) 356 | end 357 | 358 | it "should return BigDecimal for a DECIMAL value" do 359 | expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal) 360 | expect(test_result['decimal_test']).to eql(10.3) 361 | end 362 | 363 | it "should return Float for a FLOAT value" do 364 | expect(test_result['float_test']).to be_an_instance_of(Float) 365 | expect(test_result['float_test']).to eql(10.3) 366 | end 367 | 368 | it "should return Float for a DOUBLE value" do 369 | expect(test_result['double_test']).to be_an_instance_of(Float) 370 | expect(test_result['double_test']).to eql(10.3) 371 | end 372 | 373 | it "should return Time for a DATETIME value when within the supported range" do 374 | expect(test_result['date_time_test']).to be_an_instance_of(Time) 375 | expect(test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') 376 | end 377 | 378 | it "should return Time when timestamp is < 1901-12-13 20:45:52" do 379 | r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") 380 | expect(r.first['test']).to be_an_instance_of(Time) 381 | end 382 | 383 | it "should return Time when timestamp is > 2038-01-19T03:14:07" do 384 | r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") 385 | expect(r.first['test']).to be_an_instance_of(Time) 386 | end 387 | 388 | it "should return Time for a TIMESTAMP value when within the supported range" do 389 | expect(test_result['timestamp_test']).to be_an_instance_of(Time) 390 | expect(test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') 391 | end 392 | 393 | it "should return Time for a TIME value" do 394 | expect(test_result['time_test']).to be_an_instance_of(Time) 395 | expect(test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') 396 | end 397 | 398 | it "should return Date for a DATE value" do 399 | expect(test_result['date_test']).to be_an_instance_of(Date) 400 | expect(test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') 401 | end 402 | 403 | it "should return String for an ENUM value" do 404 | expect(test_result['enum_test']).to be_an_instance_of(String) 405 | expect(test_result['enum_test']).to eql('val1') 406 | end 407 | 408 | it "should raise an error given an invalid DATETIME" do 409 | if @client.info[:version] < "8.0" 410 | expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ 411 | raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") 412 | else 413 | expect(@client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").to_a.first).to \ 414 | eql("bad_datetime" => nil) 415 | end 416 | end 417 | 418 | context "string encoding for ENUM values" do 419 | it "should default to the connection's encoding if Encoding.default_internal is nil" do 420 | with_internal_encoding nil do 421 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 422 | expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) 423 | 424 | client2 = new_client(encoding: 'ascii') 425 | result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 426 | expect(result['enum_test'].encoding).to eql(Encoding::ASCII) 427 | end 428 | end 429 | 430 | it "should use Encoding.default_internal" do 431 | with_internal_encoding Encoding::UTF_8 do 432 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 433 | expect(result['enum_test'].encoding).to eql(Encoding.default_internal) 434 | end 435 | 436 | with_internal_encoding Encoding::ASCII do 437 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 438 | expect(result['enum_test'].encoding).to eql(Encoding.default_internal) 439 | end 440 | end 441 | end 442 | 443 | it "should return String for a SET value" do 444 | expect(test_result['set_test']).to be_an_instance_of(String) 445 | expect(test_result['set_test']).to eql('val1,val2') 446 | end 447 | 448 | context "string encoding for SET values" do 449 | it "should default to the connection's encoding if Encoding.default_internal is nil" do 450 | with_internal_encoding nil do 451 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 452 | expect(result['set_test'].encoding).to eql(Encoding::UTF_8) 453 | 454 | client2 = new_client(encoding: 'ascii') 455 | result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 456 | expect(result['set_test'].encoding).to eql(Encoding::ASCII) 457 | end 458 | end 459 | 460 | it "should use Encoding.default_internal" do 461 | with_internal_encoding Encoding::UTF_8 do 462 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 463 | expect(result['set_test'].encoding).to eql(Encoding.default_internal) 464 | end 465 | 466 | with_internal_encoding Encoding::ASCII do 467 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 468 | expect(result['set_test'].encoding).to eql(Encoding.default_internal) 469 | end 470 | end 471 | end 472 | 473 | it "should return String for a BINARY value" do 474 | expect(test_result['binary_test']).to be_an_instance_of(String) 475 | expect(test_result['binary_test']).to eql("test#{"\000" * 6}") 476 | end 477 | 478 | context "string encoding for BINARY values" do 479 | it "should default to binary if Encoding.default_internal is nil" do 480 | with_internal_encoding nil do 481 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 482 | expect(result['binary_test'].encoding).to eql(Encoding::BINARY) 483 | end 484 | end 485 | 486 | it "should not use Encoding.default_internal" do 487 | with_internal_encoding Encoding::UTF_8 do 488 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 489 | expect(result['binary_test'].encoding).to eql(Encoding::BINARY) 490 | end 491 | 492 | with_internal_encoding Encoding::ASCII do 493 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 494 | expect(result['binary_test'].encoding).to eql(Encoding::BINARY) 495 | end 496 | end 497 | end 498 | 499 | { 500 | 'char_test' => 'CHAR', 501 | 'varchar_test' => 'VARCHAR', 502 | 'varbinary_test' => 'VARBINARY', 503 | 'tiny_blob_test' => 'TINYBLOB', 504 | 'tiny_text_test' => 'TINYTEXT', 505 | 'blob_test' => 'BLOB', 506 | 'text_test' => 'TEXT', 507 | 'medium_blob_test' => 'MEDIUMBLOB', 508 | 'medium_text_test' => 'MEDIUMTEXT', 509 | 'long_blob_test' => 'LONGBLOB', 510 | 'long_text_test' => 'LONGTEXT', 511 | }.each do |field, type| 512 | it "should return a String for #{type}" do 513 | expect(test_result[field]).to be_an_instance_of(String) 514 | expect(test_result[field]).to eql("test") 515 | end 516 | 517 | context "string encoding for #{type} values" do 518 | if %w[VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB].include?(type) 519 | it "should default to binary if Encoding.default_internal is nil" do 520 | with_internal_encoding nil do 521 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 522 | expect(result['binary_test'].encoding).to eql(Encoding::BINARY) 523 | end 524 | end 525 | 526 | it "should not use Encoding.default_internal" do 527 | with_internal_encoding Encoding::UTF_8 do 528 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 529 | expect(result['binary_test'].encoding).to eql(Encoding::BINARY) 530 | end 531 | 532 | with_internal_encoding Encoding::ASCII do 533 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 534 | expect(result['binary_test'].encoding).to eql(Encoding::BINARY) 535 | end 536 | end 537 | else 538 | it "should default to utf-8 if Encoding.default_internal is nil" do 539 | with_internal_encoding nil do 540 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 541 | expect(result[field].encoding).to eql(Encoding::UTF_8) 542 | 543 | client2 = new_client(encoding: 'ascii') 544 | result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 545 | expect(result[field].encoding).to eql(Encoding::ASCII) 546 | end 547 | end 548 | 549 | it "should use Encoding.default_internal" do 550 | with_internal_encoding Encoding::UTF_8 do 551 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 552 | expect(result[field].encoding).to eql(Encoding.default_internal) 553 | end 554 | 555 | with_internal_encoding Encoding::ASCII do 556 | result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first 557 | expect(result[field].encoding).to eql(Encoding.default_internal) 558 | end 559 | end 560 | end 561 | end 562 | end 563 | end 564 | 565 | context "server flags" do 566 | let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY null_test DESC LIMIT 1") } 567 | 568 | it "should set a definitive value for query_was_slow" do 569 | expect(test_result.server_flags[:query_was_slow]).to eql(false) 570 | end 571 | it "should set a definitive value for no_index_used" do 572 | expect(test_result.server_flags[:no_index_used]).to eql(true) 573 | end 574 | it "should set a definitive value for no_good_index_used" do 575 | expect(test_result.server_flags[:no_good_index_used]).to eql(false) 576 | end 577 | end 578 | end 579 | -------------------------------------------------------------------------------- /ext/mysql2/statement.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern VALUE mMysql2, cMysql2Error; 4 | static VALUE cMysql2Statement, cBigDecimal, cDateTime, cDate; 5 | static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang; 6 | static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year, 7 | intern_query_options; 8 | 9 | #ifndef NEW_TYPEDDATA_WRAPPER 10 | #define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval) 11 | #endif 12 | 13 | #define RAW_GET_STATEMENT(self) \ 14 | mysql_stmt_wrapper *stmt_wrapper; \ 15 | TypedData_Get_Struct(self, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper); \ 16 | 17 | #define GET_STATEMENT(self) \ 18 | RAW_GET_STATEMENT(self) \ 19 | if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \ 20 | if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); } 21 | 22 | static void rb_mysql_stmt_mark(void * ptr) { 23 | mysql_stmt_wrapper *stmt_wrapper = ptr; 24 | if (!stmt_wrapper) return; 25 | 26 | rb_gc_mark_movable(stmt_wrapper->client); 27 | } 28 | 29 | static void rb_mysql_stmt_free(void *ptr) { 30 | mysql_stmt_wrapper *stmt_wrapper = ptr; 31 | decr_mysql2_stmt(stmt_wrapper); 32 | } 33 | 34 | static size_t rb_mysql_stmt_memsize(const void * ptr) { 35 | const mysql_stmt_wrapper *stmt_wrapper = ptr; 36 | return sizeof(*stmt_wrapper); 37 | } 38 | 39 | #ifdef HAVE_RB_GC_MARK_MOVABLE 40 | static void rb_mysql_stmt_compact(void * ptr) { 41 | mysql_stmt_wrapper *stmt_wrapper = ptr; 42 | if (!stmt_wrapper) return; 43 | 44 | rb_mysql2_gc_location(stmt_wrapper->client); 45 | } 46 | #endif 47 | 48 | static const rb_data_type_t rb_mysql_statement_type = { 49 | "rb_mysql_statement", 50 | { 51 | rb_mysql_stmt_mark, 52 | rb_mysql_stmt_free, 53 | rb_mysql_stmt_memsize, 54 | #ifdef HAVE_RB_GC_MARK_MOVABLE 55 | rb_mysql_stmt_compact, 56 | #endif 57 | }, 58 | 0, 59 | 0, 60 | #ifdef RUBY_TYPED_FREE_IMMEDIATELY 61 | RUBY_TYPED_FREE_IMMEDIATELY, 62 | #endif 63 | }; 64 | 65 | static void *nogvl_stmt_close(void *ptr) { 66 | mysql_stmt_wrapper *stmt_wrapper = ptr; 67 | if (stmt_wrapper->stmt) { 68 | mysql_stmt_close(stmt_wrapper->stmt); 69 | stmt_wrapper->stmt = NULL; 70 | } 71 | return NULL; 72 | } 73 | 74 | void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) { 75 | stmt_wrapper->refcount--; 76 | 77 | if (stmt_wrapper->refcount == 0) { 78 | nogvl_stmt_close(stmt_wrapper); 79 | xfree(stmt_wrapper); 80 | } 81 | } 82 | 83 | void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) { 84 | VALUE e; 85 | GET_CLIENT(stmt_wrapper->client); 86 | VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt)); 87 | VALUE rb_sql_state = rb_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt)); 88 | 89 | rb_encoding *conn_enc; 90 | conn_enc = rb_to_encoding(wrapper->encoding); 91 | 92 | rb_encoding *default_internal_enc = rb_default_internal_encoding(); 93 | 94 | rb_enc_associate(rb_error_msg, conn_enc); 95 | rb_enc_associate(rb_sql_state, conn_enc); 96 | if (default_internal_enc) { 97 | rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc); 98 | rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); 99 | } 100 | 101 | e = rb_funcall(cMysql2Error, intern_new_with_args, 4, 102 | rb_error_msg, 103 | LONG2FIX(wrapper->server_version), 104 | UINT2NUM(mysql_stmt_errno(stmt_wrapper->stmt)), 105 | rb_sql_state); 106 | rb_exc_raise(e); 107 | } 108 | 109 | /* 110 | * used to pass all arguments to mysql_stmt_prepare while inside 111 | * nogvl_prepare_statement_args 112 | */ 113 | struct nogvl_prepare_statement_args { 114 | MYSQL_STMT *stmt; 115 | VALUE sql; 116 | const char *sql_ptr; 117 | unsigned long sql_len; 118 | }; 119 | 120 | static void *nogvl_prepare_statement(void *ptr) { 121 | struct nogvl_prepare_statement_args *args = ptr; 122 | 123 | if (mysql_stmt_prepare(args->stmt, args->sql_ptr, args->sql_len)) { 124 | return (void*)Qfalse; 125 | } else { 126 | return (void*)Qtrue; 127 | } 128 | } 129 | 130 | VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { 131 | mysql_stmt_wrapper *stmt_wrapper; 132 | VALUE rb_stmt; 133 | rb_encoding *conn_enc; 134 | 135 | Check_Type(sql, T_STRING); 136 | 137 | #ifdef NEW_TYPEDDATA_WRAPPER 138 | rb_stmt = TypedData_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper); 139 | #else 140 | rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); 141 | #endif 142 | { 143 | stmt_wrapper->client = rb_client; 144 | stmt_wrapper->refcount = 1; 145 | stmt_wrapper->closed = 0; 146 | stmt_wrapper->stmt = NULL; 147 | } 148 | 149 | // instantiate stmt 150 | { 151 | GET_CLIENT(rb_client); 152 | stmt_wrapper->stmt = mysql_stmt_init(wrapper->client); 153 | conn_enc = rb_to_encoding(wrapper->encoding); 154 | } 155 | if (stmt_wrapper->stmt == NULL) { 156 | rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory"); 157 | } 158 | 159 | // set STMT_ATTR_UPDATE_MAX_LENGTH attr 160 | { 161 | my_bool truth = 1; 162 | if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) { 163 | rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH"); 164 | } 165 | } 166 | 167 | // call mysql_stmt_prepare w/o gvl 168 | { 169 | struct nogvl_prepare_statement_args args; 170 | args.stmt = stmt_wrapper->stmt; 171 | // ensure the string is in the encoding the connection is expecting 172 | args.sql = rb_str_export_to_enc(sql, conn_enc); 173 | args.sql_ptr = RSTRING_PTR(sql); 174 | args.sql_len = RSTRING_LEN(sql); 175 | 176 | if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { 177 | rb_raise_mysql2_stmt_error(stmt_wrapper); 178 | } 179 | } 180 | 181 | return rb_stmt; 182 | } 183 | 184 | /* call-seq: stmt.param_count # => Numeric 185 | * 186 | * Returns the number of parameters the prepared statement expects. 187 | */ 188 | static VALUE rb_mysql_stmt_param_count(VALUE self) { 189 | GET_STATEMENT(self); 190 | 191 | return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt)); 192 | } 193 | 194 | /* call-seq: stmt.field_count # => Numeric 195 | * 196 | * Returns the number of fields the prepared statement returns. 197 | */ 198 | static VALUE rb_mysql_stmt_field_count(VALUE self) { 199 | GET_STATEMENT(self); 200 | 201 | return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt)); 202 | } 203 | 204 | static void *nogvl_stmt_execute(void *ptr) { 205 | MYSQL_STMT *stmt = ptr; 206 | 207 | if (mysql_stmt_execute(stmt)) { 208 | return (void*)Qfalse; 209 | } else { 210 | return (void*)Qtrue; 211 | } 212 | } 213 | 214 | static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) { 215 | unsigned long length; 216 | 217 | bind_buffer->buffer = RSTRING_PTR(string); 218 | 219 | length = RSTRING_LEN(string); 220 | bind_buffer->buffer_length = length; 221 | *length_buffer = length; 222 | 223 | bind_buffer->length = length_buffer; 224 | } 225 | 226 | /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means 227 | * the buffer is a Ruby string pointer and not our memory to manage. 228 | */ 229 | #define FREE_BINDS \ 230 | for (i = 0; i < bind_count; i++) { \ 231 | if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \ 232 | xfree(bind_buffers[i].buffer); \ 233 | } \ 234 | } \ 235 | if (argc > 0) { \ 236 | xfree(bind_buffers); \ 237 | xfree(length_buffers); \ 238 | } 239 | 240 | /* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */ 241 | static int my_big2ll(VALUE bignum, LONG_LONG *ptr) 242 | { 243 | unsigned LONG_LONG num; 244 | size_t len; 245 | // rb_absint_size was added in 2.1.0. See: 246 | // https://github.com/ruby/ruby/commit/9fea875 247 | #ifdef HAVE_RB_ABSINT_SIZE 248 | int nlz_bits = 0; 249 | len = rb_absint_size(bignum, &nlz_bits); 250 | #else 251 | len = RBIGNUM_LEN(bignum) * SIZEOF_BDIGITS; 252 | #endif 253 | if (len > sizeof(LONG_LONG)) goto overflow; 254 | if (RBIGNUM_POSITIVE_P(bignum)) { 255 | num = rb_big2ull(bignum); 256 | if (num > LLONG_MAX) 257 | goto overflow; 258 | *ptr = num; 259 | } 260 | else { 261 | if (len == 8 && 262 | #ifdef HAVE_RB_ABSINT_SIZE 263 | nlz_bits == 0 && 264 | #endif 265 | // rb_absint_singlebit_p was added in 2.1.0. See: 266 | // https://github.com/ruby/ruby/commit/e5ff9d5 267 | #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P) 268 | /* Optimized to avoid object allocation for Ruby 2.1+ 269 | * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0` 270 | */ 271 | !rb_absint_singlebit_p(bignum) 272 | #else 273 | rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1) 274 | #endif 275 | ) { 276 | goto overflow; 277 | } 278 | *ptr = rb_big2ll(bignum); 279 | } 280 | return 0; 281 | overflow: 282 | return 1; 283 | } 284 | 285 | /* call-seq: stmt.execute 286 | * 287 | * Executes the current prepared statement, returns +result+. 288 | */ 289 | static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { 290 | MYSQL_BIND *bind_buffers = NULL; 291 | unsigned long *length_buffers = NULL; 292 | unsigned long bind_count; 293 | unsigned long i; 294 | MYSQL_STMT *stmt; 295 | MYSQL_RES *metadata; 296 | VALUE opts; 297 | VALUE current; 298 | VALUE resultObj; 299 | VALUE *params_enc = NULL; 300 | int is_streaming; 301 | rb_encoding *conn_enc; 302 | 303 | GET_STATEMENT(self); 304 | GET_CLIENT(stmt_wrapper->client); 305 | 306 | conn_enc = rb_to_encoding(wrapper->encoding); 307 | 308 | stmt = stmt_wrapper->stmt; 309 | bind_count = mysql_stmt_param_count(stmt); 310 | 311 | // Get count of ordinary arguments, and extract hash opts/keyword arguments 312 | // Use a local scope to avoid leaking the temporary count variable 313 | { 314 | int c = rb_scan_args(argc, argv, "*:", NULL, &opts); 315 | if (c != (long)bind_count) { 316 | rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c); 317 | } 318 | } 319 | 320 | // setup any bind variables in the query 321 | if (bind_count > 0) { 322 | // Scratch space for string encoding exports, allocate on the stack 323 | params_enc = alloca(sizeof(VALUE) * bind_count); 324 | bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND)); 325 | length_buffers = xcalloc(bind_count, sizeof(unsigned long)); 326 | 327 | for (i = 0; i < bind_count; i++) { 328 | bind_buffers[i].buffer = NULL; 329 | params_enc[i] = Qnil; 330 | 331 | switch (TYPE(argv[i])) { 332 | case T_NIL: 333 | bind_buffers[i].buffer_type = MYSQL_TYPE_NULL; 334 | break; 335 | case T_FIXNUM: 336 | #if SIZEOF_INT < SIZEOF_LONG 337 | bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; 338 | bind_buffers[i].buffer = xmalloc(sizeof(long long int)); 339 | *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]); 340 | #else 341 | bind_buffers[i].buffer_type = MYSQL_TYPE_LONG; 342 | bind_buffers[i].buffer = xmalloc(sizeof(int)); 343 | *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]); 344 | #endif 345 | break; 346 | case T_BIGNUM: 347 | { 348 | LONG_LONG num; 349 | if (my_big2ll(argv[i], &num) == 0) { 350 | bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; 351 | bind_buffers[i].buffer = xmalloc(sizeof(long long int)); 352 | *(LONG_LONG*)(bind_buffers[i].buffer) = num; 353 | } else { 354 | /* The bignum was larger than we can fit in LONG_LONG, send it as a string */ 355 | bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL; 356 | params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc); 357 | set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); 358 | } 359 | } 360 | break; 361 | case T_FLOAT: 362 | bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE; 363 | bind_buffers[i].buffer = xmalloc(sizeof(double)); 364 | *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]); 365 | break; 366 | case T_STRING: 367 | bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; 368 | 369 | params_enc[i] = argv[i]; 370 | params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); 371 | set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); 372 | break; 373 | case T_TRUE: 374 | bind_buffers[i].buffer_type = MYSQL_TYPE_TINY; 375 | bind_buffers[i].buffer = xmalloc(sizeof(signed char)); 376 | *(signed char*)(bind_buffers[i].buffer) = 1; 377 | break; 378 | case T_FALSE: 379 | bind_buffers[i].buffer_type = MYSQL_TYPE_TINY; 380 | bind_buffers[i].buffer = xmalloc(sizeof(signed char)); 381 | *(signed char*)(bind_buffers[i].buffer) = 0; 382 | break; 383 | default: 384 | // TODO: what Ruby type should support MYSQL_TYPE_TIME 385 | if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) { 386 | MYSQL_TIME t; 387 | VALUE rb_time = argv[i]; 388 | 389 | bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME; 390 | bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); 391 | 392 | memset(&t, 0, sizeof(MYSQL_TIME)); 393 | t.neg = 0; 394 | 395 | if (CLASS_OF(argv[i]) == rb_cTime) { 396 | t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0)); 397 | } else if (CLASS_OF(argv[i]) == cDateTime) { 398 | t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000; 399 | } 400 | 401 | t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0)); 402 | t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0)); 403 | t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0)); 404 | t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0)); 405 | t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0)); 406 | t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0)); 407 | 408 | *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; 409 | } else if (CLASS_OF(argv[i]) == cDate) { 410 | MYSQL_TIME t; 411 | VALUE rb_time = argv[i]; 412 | 413 | bind_buffers[i].buffer_type = MYSQL_TYPE_DATE; 414 | bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); 415 | 416 | memset(&t, 0, sizeof(MYSQL_TIME)); 417 | t.second_part = 0; 418 | t.neg = 0; 419 | t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0)); 420 | t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0)); 421 | t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0)); 422 | 423 | *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; 424 | } else if (CLASS_OF(argv[i]) == cBigDecimal) { 425 | bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL; 426 | 427 | // DECIMAL are represented with the "string representation of the 428 | // original server-side value", see 429 | // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html 430 | // This should be independent of the locale used both on the server 431 | // and the client side. 432 | VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0); 433 | 434 | params_enc[i] = rb_val_as_string; 435 | params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); 436 | set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); 437 | } 438 | break; 439 | } 440 | } 441 | 442 | // copies bind_buffers into internal storage 443 | if (mysql_stmt_bind_param(stmt, bind_buffers)) { 444 | FREE_BINDS; 445 | rb_raise_mysql2_stmt_error(stmt_wrapper); 446 | } 447 | } 448 | 449 | // Duplicate the options hash, merge! extra opts, put the copy into the Result object 450 | current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_query_options)); 451 | (void)RB_GC_GUARD(current); 452 | Check_Type(current, T_HASH); 453 | 454 | // Merge in hash opts/keyword arguments 455 | if (!NIL_P(opts)) { 456 | rb_funcall(current, intern_merge_bang, 1, opts); 457 | } 458 | 459 | is_streaming = (Qtrue == rb_hash_aref(current, sym_stream)); 460 | 461 | // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no 462 | // Ruby API calls are allowed so that GC is not invoked. If the connection is 463 | // in results-streaming-mode for Statement A, and in the middle Statement B 464 | // gets garbage collected, a message will be sent to the server notifying it 465 | // to release Statement B, resulting in the following error: 466 | // Commands out of sync; you can't run this command now 467 | // 468 | // In streaming mode, statement execute must return a cursor because we 469 | // cannot prevent other Statement objects from being garbage collected 470 | // between fetches of each row of the result set. The following error 471 | // occurs if cursor mode is not set: 472 | // Row retrieval was canceled by mysql_stmt_close 473 | 474 | if (is_streaming) { 475 | unsigned long type = CURSOR_TYPE_READ_ONLY; 476 | if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) { 477 | FREE_BINDS; 478 | rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY"); 479 | } 480 | } 481 | 482 | if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) { 483 | FREE_BINDS; 484 | rb_raise_mysql2_stmt_error(stmt_wrapper); 485 | } 486 | 487 | FREE_BINDS; 488 | 489 | metadata = mysql_stmt_result_metadata(stmt); 490 | if (metadata == NULL) { 491 | if (mysql_stmt_errno(stmt) != 0) { 492 | // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. 493 | wrapper->active_fiber = Qnil; 494 | rb_raise_mysql2_stmt_error(stmt_wrapper); 495 | } 496 | // no data and no error, so query was not a SELECT 497 | return Qnil; 498 | } 499 | 500 | if (!is_streaming) { 501 | // receive the whole result set from the server 502 | if (mysql_stmt_store_result(stmt)) { 503 | mysql_free_result(metadata); 504 | rb_raise_mysql2_stmt_error(stmt_wrapper); 505 | } 506 | wrapper->active_fiber = Qnil; 507 | } 508 | 509 | resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self); 510 | 511 | rb_mysql_set_server_query_flags(wrapper->client, resultObj); 512 | 513 | if (!is_streaming) { 514 | // cache all result 515 | rb_funcall(resultObj, intern_each, 0); 516 | } 517 | 518 | return resultObj; 519 | } 520 | 521 | /* call-seq: stmt.fields # => array 522 | * 523 | * Returns a list of fields that will be returned by this statement. 524 | */ 525 | static VALUE rb_mysql_stmt_fields(VALUE self) { 526 | MYSQL_FIELD *fields; 527 | MYSQL_RES *metadata; 528 | unsigned int field_count; 529 | unsigned int i; 530 | VALUE field_list; 531 | MYSQL_STMT* stmt; 532 | rb_encoding *default_internal_enc, *conn_enc; 533 | GET_STATEMENT(self); 534 | GET_CLIENT(stmt_wrapper->client); 535 | stmt = stmt_wrapper->stmt; 536 | 537 | default_internal_enc = rb_default_internal_encoding(); 538 | { 539 | GET_CLIENT(stmt_wrapper->client); 540 | conn_enc = rb_to_encoding(wrapper->encoding); 541 | } 542 | 543 | metadata = mysql_stmt_result_metadata(stmt); 544 | if (metadata == NULL) { 545 | if (mysql_stmt_errno(stmt) != 0) { 546 | // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. 547 | wrapper->active_fiber = Qnil; 548 | rb_raise_mysql2_stmt_error(stmt_wrapper); 549 | } 550 | // no data and no error, so query was not a SELECT 551 | return Qnil; 552 | } 553 | 554 | fields = mysql_fetch_fields(metadata); 555 | field_count = mysql_stmt_field_count(stmt); 556 | field_list = rb_ary_new2((long)field_count); 557 | 558 | for (i = 0; i < field_count; i++) { 559 | VALUE rb_field; 560 | 561 | rb_field = rb_str_new(fields[i].name, fields[i].name_length); 562 | rb_enc_associate(rb_field, conn_enc); 563 | if (default_internal_enc) { 564 | rb_field = rb_str_export_to_enc(rb_field, default_internal_enc); 565 | } 566 | 567 | rb_ary_store(field_list, (long)i, rb_field); 568 | } 569 | 570 | mysql_free_result(metadata); 571 | return field_list; 572 | } 573 | 574 | /* call-seq: 575 | * stmt.last_id 576 | * 577 | * Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE. 578 | */ 579 | static VALUE rb_mysql_stmt_last_id(VALUE self) { 580 | GET_STATEMENT(self); 581 | return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt)); 582 | } 583 | 584 | /* call-seq: 585 | * stmt.affected_rows 586 | * 587 | * Returns the number of rows changed, deleted, or inserted. 588 | */ 589 | static VALUE rb_mysql_stmt_affected_rows(VALUE self) { 590 | my_ulonglong affected; 591 | GET_STATEMENT(self); 592 | 593 | affected = mysql_stmt_affected_rows(stmt_wrapper->stmt); 594 | if (affected == (my_ulonglong)-1) { 595 | rb_raise_mysql2_stmt_error(stmt_wrapper); 596 | } 597 | 598 | return ULL2NUM(affected); 599 | } 600 | 601 | /* call-seq: 602 | * stmt.close 603 | * 604 | * Explicitly closing this will free up server resources immediately rather 605 | * than waiting for the garbage collector. Useful if you're managing your 606 | * own prepared statement cache. 607 | */ 608 | static VALUE rb_mysql_stmt_close(VALUE self) { 609 | RAW_GET_STATEMENT(self); 610 | 611 | if (!stmt_wrapper->closed) { 612 | stmt_wrapper->closed = 1; 613 | rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0); 614 | } 615 | 616 | return Qnil; 617 | } 618 | 619 | /* call-seq: 620 | * stmt.closed? 621 | * 622 | * Returns wheter or not the statement have been closed. 623 | */ 624 | static VALUE rb_mysql_stmt_closed_p(VALUE self) { 625 | RAW_GET_STATEMENT(self); 626 | 627 | return stmt_wrapper->closed ? Qtrue : Qfalse; 628 | } 629 | 630 | void init_mysql2_statement() { 631 | cDate = rb_const_get(rb_cObject, rb_intern("Date")); 632 | rb_global_variable(&cDate); 633 | 634 | cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); 635 | rb_global_variable(&cDateTime); 636 | 637 | cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal")); 638 | rb_global_variable(&cBigDecimal); 639 | 640 | cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); 641 | rb_undef_alloc_func(cMysql2Statement); 642 | rb_global_variable(&cMysql2Statement); 643 | 644 | rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0); 645 | rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0); 646 | rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1); 647 | rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0); 648 | rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0); 649 | rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0); 650 | rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0); 651 | rb_define_method(cMysql2Statement, "closed?", rb_mysql_stmt_closed_p, 0); 652 | 653 | sym_stream = ID2SYM(rb_intern("stream")); 654 | 655 | intern_new_with_args = rb_intern("new_with_args"); 656 | intern_each = rb_intern("each"); 657 | 658 | intern_sec_fraction = rb_intern("sec_fraction"); 659 | intern_usec = rb_intern("usec"); 660 | intern_sec = rb_intern("sec"); 661 | intern_min = rb_intern("min"); 662 | intern_hour = rb_intern("hour"); 663 | intern_day = rb_intern("day"); 664 | intern_month = rb_intern("month"); 665 | intern_year = rb_intern("year"); 666 | 667 | intern_to_s = rb_intern("to_s"); 668 | intern_merge_bang = rb_intern("merge!"); 669 | intern_query_options = rb_intern("@query_options"); 670 | } 671 | --------------------------------------------------------------------------------