pax_global_header00006660000000000000000000000064135674335630014531gustar00rootroot0000000000000052 comment=db6733aa8f130866c30dbeccd18e81113556bb4b mysql2-0.5.3/000077500000000000000000000000001356743356300127655ustar00rootroot00000000000000mysql2-0.5.3/.gitignore000066400000000000000000000002441356743356300147550ustar00rootroot00000000000000bin/ Makefile *.dSYM *.o *.bundle *.so *.a *.rbc mkmf.log pkg/ tmp vendor lib/mysql2/mysql2.rb spec/configuration.yml spec/my.cnf Gemfile.lock .ruby-version .rvmrc mysql2-0.5.3/.rspec000066400000000000000000000000701356743356300140770ustar00rootroot00000000000000--colour --format documentation --order rand --warnings mysql2-0.5.3/.rubocop.yml000066400000000000000000000007771356743356300152520ustar00rootroot00000000000000inherit_from: .rubocop_todo.yml AllCops: TargetRubyVersion: 2.0 DisplayCopNames: true Exclude: - 'pkg/**/*' - 'tmp/**/*' - 'vendor/**/*' Layout/CaseIndentation: EnforcedStyle: end Layout/IndentHash: EnforcedStyle: consistent Lint/EndAlignment: EnforcedStyleAlignWith: variable Style/TrailingCommaInArguments: EnforcedStyleForMultiline: consistent_comma Style/TrailingCommaInLiteral: EnforcedStyleForMultiline: consistent_comma Style/TrivialAccessors: AllowPredicates: true mysql2-0.5.3/.rubocop_todo.yml000066400000000000000000000046041356743356300162700ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config` # on 2017-11-25 19:54:28 -0500 using RuboCop version 0.50.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent Layout/IndentHeredoc: Exclude: - 'support/ruby_enc_to_mysql.rb' - 'tasks/compile.rake' # Offense count: 2 Metrics/AbcSize: Max: 90 # Offense count: 31 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: Max: 860 # Offense count: 1 # Configuration parameters: CountBlocks. Metrics/BlockNesting: Max: 5 # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: Max: 125 # Offense count: 3 Metrics/CyclomaticComplexity: Max: 30 # Offense count: 313 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 232 # Offense count: 6 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 57 # Offense count: 2 Metrics/PerceivedComplexity: Max: 27 # Offense count: 3 # Configuration parameters: Blacklist. # Blacklist: END, (?-mix:EO[A-Z]{1}) Naming/HeredocDelimiterNaming: Exclude: - 'tasks/compile.rake' # Offense count: 10 Style/Documentation: Exclude: - 'spec/**/*' - 'test/**/*' - 'benchmark/active_record.rb' - 'benchmark/allocations.rb' - 'benchmark/query_with_mysql_casting.rb' - 'lib/mysql2.rb' - 'lib/mysql2/client.rb' - 'lib/mysql2/em.rb' - 'lib/mysql2/error.rb' - 'lib/mysql2/result.rb' - 'lib/mysql2/statement.rb' # Offense count: 14 # Configuration parameters: AllowedVariables. Style/GlobalVars: Exclude: - 'ext/mysql2/extconf.rb' # Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: Strict. Style/NumericLiterals: MinDigits: 20 # Offense count: 726 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: Enabled: false mysql2-0.5.3/.travis.yml000066400000000000000000000035321356743356300151010ustar00rootroot00000000000000sudo: required dist: trusty services: docker language: ruby bundler_args: --without benchmarks development # Pin Rubygems to a working version. Sometimes it breaks upstream. Update now and then. before_install: - gem --version - gem update --system --quiet || gem update --system 2.7.10 --quiet - gem update bundler - gem --version - bash .travis_setup.sh addons: hosts: - mysql2gem.example.com apt: packages: - mysql-server-5.6 - mysql-client-core-5.6 - mysql-client-5.6 rvm: - 2.6 - 2.5 - 2.4 - 2.3 - 2.2 - 2.1 - 2.0.0 - ruby-head matrix: include: - rvm: 2.4 env: DB=mariadb10.0 addons: mariadb: 10.0 hosts: - mysql2gem.example.com - rvm: 2.4 env: DB=mariadb10.1 addons: mariadb: 10.1 hosts: - mysql2gem.example.com - rvm: 2.4 env: DB=mariadb10.2 addons: mariadb: 10.2 hosts: - mysql2gem.example.com - rvm: 2.4 env: DB=mariadb10.3 addons: mariadb: 10.3 hosts: - mysql2gem.example.com - rvm: 2.4 env: DB=mysql55 addons: hosts: - mysql2gem.example.com - rvm: 2.4 env: DB=mysql57 dist: xenial addons: hosts: - mysql2gem.example.com - rvm: 2.4 env: DB=mysql80 dist: xenial addons: hosts: - mysql2gem.example.com - os: osx rvm: 2.4 env: DB=mysql56 addons: hosts: - mysql2gem.example.com - rvm: 2.4 env: DOCKER=centos before_install: true install: docker build -t mysql2 -f .travis_Dockerfile_centos . script: docker run --add-host=mysql2gem.example.com:127.0.0.1 -t mysql2 fast_finish: true allow_failures: - rvm: ruby-head - os: osx rvm: 2.4 env: DB=mysql56 mysql2-0.5.3/.travis_Dockerfile_centos000066400000000000000000000007261356743356300200050ustar00rootroot00000000000000FROM centos:7 WORKDIR /build COPY . . RUN yum -y update RUN yum -y install epel-release # The options are to install faster. RUN yum -y install \ --setopt=deltarpm=0 \ --setopt=install_weak_deps=false \ --setopt=tsflags=nodocs \ mariadb-server \ mariadb-devel \ ruby-devel \ git \ gcc \ gcc-c++ \ make RUN gem install --no-document "rubygems-update:~>2.7" && update_rubygems RUN gem install --no-document "bundler:~>1.17" CMD sh .travis_centos.sh mysql2-0.5.3/.travis_centos.sh000066400000000000000000000004131356743356300162600ustar00rootroot00000000000000#!/usr/bin/env bash set -eux # Start mysqld service. sh .travis_setup_centos.sh bundle install --path vendor/bundle --without benchmarks development # USER environment value is not set as a default in the container environment. export USER=root bundle exec rake mysql2-0.5.3/.travis_mysql55.sh000066400000000000000000000003421356743356300163050ustar00rootroot00000000000000#!/usr/bin/env bash set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql apt-get update -qq apt-get install -qq mysql-server-5.5 mysql-client-core-5.5 mysql-client-5.5 libmysqlclient-dev mysql2-0.5.3/.travis_mysql57.sh000066400000000000000000000007331356743356300163130ustar00rootroot00000000000000#!/usr/bin/env bash set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/5072E1F5.asc add-apt-repository 'http://repo.mysql.com/apt/ubuntu mysql-5.7' apt-get update -qq apt-get install -qq mysql-server libmysqlclient-dev # https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" mysql2-0.5.3/.travis_mysql80.sh000066400000000000000000000007331356743356300163070ustar00rootroot00000000000000#!/usr/bin/env bash set -eux apt-get purge -qq '^mysql*' '^libmysql*' rm -fr /etc/mysql rm -fr /var/lib/mysql apt-key add support/5072E1F5.asc add-apt-repository 'http://repo.mysql.com/apt/ubuntu mysql-8.0' apt-get update -qq apt-get install -qq mysql-server libmysqlclient-dev # https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/ mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" mysql2-0.5.3/.travis_setup.sh000066400000000000000000000034511356743356300161320ustar00rootroot00000000000000#!/usr/bin/env bash set -eux # Install MySQL 5.5 if DB=mysql55 if [[ -n ${DB-} && x$DB =~ ^xmysql55 ]]; then sudo bash .travis_mysql55.sh fi # Install MySQL 5.7 if DB=mysql57 if [[ -n ${DB-} && x$DB =~ ^xmysql57 ]]; then sudo bash .travis_mysql57.sh fi # Install MySQL 8.0 if DB=mysql80 if [[ -n ${DB-} && x$DB =~ ^xmysql80 ]]; then sudo bash .travis_mysql80.sh fi # Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x if [[ -n ${DB-} && x$DB =~ ^xmariadb10.0 ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev fi # Install MariaDB client headers after Travis CI fix for MariaDB 10.2 broke earlier 10.x if [[ -n ${DB-} && x$DB =~ ^xmariadb10.1 ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' libmariadbclient-dev fi # Install MariaDB 10.2 if DB=mariadb10.2 # NOTE this is a workaround until Travis CI merges a fix to its mariadb addon. if [[ -n ${DB-} && x$DB =~ ^xmariadb10.2 ]]; then sudo apt-get install -y -o Dpkg::Options::='--force-confnew' mariadb-server mariadb-server-10.2 libmariadbclient18 fi # Install MySQL if OS=darwin if [[ x$OSTYPE =~ ^xdarwin ]]; then brew update brew install "$DB" mariadb-connector-c $(brew --prefix "$DB")/bin/mysql.server start fi # TODO: get SSL working on OS X in Travis if ! [[ x$OSTYPE =~ ^xdarwin ]]; then sudo bash .travis_ssl.sh sudo service mysql restart fi # Print the MySQL version and create the test DB if [[ x$OSTYPE =~ ^xdarwin ]]; then $(brew --prefix "$DB")/bin/mysqld --version $(brew --prefix "$DB")/bin/mysql -u root -e 'CREATE DATABASE IF NOT EXISTS test' else mysqld --version # IF NOT EXISTS is mariadb-10+ only - https://mariadb.com/kb/en/mariadb/comment-syntax/ mysql -u root -e 'CREATE DATABASE /*M!50701 IF NOT EXISTS */ test' fi mysql2-0.5.3/.travis_setup_centos.sh000066400000000000000000000007651356743356300175120ustar00rootroot00000000000000#!/usr/bin/env bash set -eux MYSQL_TEST_LOG="$(pwd)/mysql.log" # mysql_install_db uses wrong path for resolveip # https://jira.mariadb.org/browse/MDEV-18563 # https://travis-ci.org/brianmario/mysql2/jobs/615263124#L2840 ln -s "$(command -v resolveip)" /usr/libexec/resolveip mysql_install_db \ --log-error="${MYSQL_TEST_LOG}" /usr/libexec/mysqld \ --user=root \ --log-error="${MYSQL_TEST_LOG}" \ --ssl & sleep 3 cat ${MYSQL_TEST_LOG} mysql -u root -e 'CREATE DATABASE IF NOT EXISTS test' mysql2-0.5.3/.travis_ssl.sh000066400000000000000000000007301356743356300155700ustar00rootroot00000000000000#!/usr/bin/env bash set -eux # Make sure there is an /etc/mysql mkdir -p /etc/mysql # Copy the local certs to /etc/mysql cp spec/ssl/*pem /etc/mysql/ # Wherever MySQL configs live, go there (this is for cross-platform) cd $(my_print_defaults --help | grep my.cnf | xargs find 2>/dev/null | xargs dirname) # Put the configs into the server echo " [mysqld] ssl-ca=/etc/mysql/ca-cert.pem ssl-cert=/etc/mysql/server-cert.pem ssl-key=/etc/mysql/server-key.pem " >> my.cnf mysql2-0.5.3/CHANGELOG.md000066400000000000000000000001271356743356300145760ustar00rootroot00000000000000Changes are maintained under [Releases](https://github.com/brianmario/mysql2/releases) mysql2-0.5.3/Gemfile000066400000000000000000000011041356743356300142540ustar00rootroot00000000000000source 'https://rubygems.org' gemspec gem 'rake', '~> 10.4.2' gem 'rake-compiler', '~> 1.0' group :test do gem 'eventmachine' unless RUBY_PLATFORM =~ /mswin|mingw/ gem 'rspec', '~> 3.2' # https://github.com/bbatsov/rubocop/pull/4789 gem 'rubocop', '~> 0.50.0' end group :benchmarks do gem 'activerecord', '>= 3.0' gem 'benchmark-ips' gem 'do_mysql' gem 'faker' gem 'mysql' gem 'sequel' end group :development do gem 'pry' gem 'rake-compiler-dock', '~> 0.7.0' end platforms :rbx do gem 'rubysl-bigdecimal' gem 'rubysl-drb' gem 'rubysl-rake' end mysql2-0.5.3/LICENSE000066400000000000000000000020661356743356300137760ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Brian Lopez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mysql2-0.5.3/README.md000066400000000000000000000610711356743356300142510ustar00rootroot00000000000000# Mysql2 - A modern, simple and very fast MySQL library for Ruby - binding to libmysql Travis CI [![Travis CI Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2) Appveyor CI [![Appveyor CI Status](https://ci.appveyor.com/api/projects/status/github/sodabrew/mysql2)](https://ci.appveyor.com/project/sodabrew/mysql2) The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results. Some database libraries out there serve as direct 1:1 mappings of the already complex C APIs available. This one is not. It also forces the use of UTF-8 [or binary] for the connection and uses encoding-aware MySQL API calls where it can. The API consists of three classes: `Mysql2::Client` - your connection to the database. `Mysql2::Result` - returned from issuing a #query on the connection. It includes Enumerable. `Mysql2::Statement` - returned from issuing a #prepare on the connection. Execute the statement to get a Result. ## Installing ### General Instructions ``` sh gem install mysql2 ``` This gem links against MySQL's `libmysqlclient` library or `Connector/C` library, and compatible alternatives such as MariaDB. You may need to install a package such as `libmariadb-dev`, `libmysqlclient-dev`, `mysql-devel`, or other appropriate package for your system. See below for system-specific instructions. By default, the mysql2 gem will try to find a copy of MySQL in this order: * Option `--with-mysql-dir`, if provided (see below). * Option `--with-mysql-config`, if provided (see below). * Several typical paths for `mysql_config` (default for the majority of users). * The directory `/usr/local`. ### Configuration options Use these options by `gem install mysql2 -- [--optionA] [--optionB=argument]`. * `--with-mysql-dir[=/path/to/mysqldir]` - Specify the directory where MySQL is installed. The mysql2 gem will not use `mysql_config`, but will instead look at `mysqldir/lib` and `mysqldir/include` for the library and header files. This option is mutually exclusive with `--with-mysql-config`. * `--with-mysql-config[=/path/to/mysql_config]` - Specify a path to the `mysql_config` binary provided by your copy of MySQL. The mysql2 gem will ask this `mysql_config` binary about the compiler and linker arguments needed. This option is mutually exclusive with `--with-mysql-dir`. * `--with-mysql-rpath=/path/to/mysql/lib` / `--without-mysql-rpath` - Override the runtime path used to find the MySQL libraries. This may be needed if you deploy to a system where these libraries are located somewhere different than on your build system. This overrides any rpath calculated by default or by the options above. * `--with-sanitize[=address,cfi,integer,memory,thread,undefined]` - Enable sanitizers for Clang / GCC. If no argument is given, try to enable all sanitizers or fail if none are available. If a command-separated list of specific sanitizers is given, configure will fail unless they all are available. Note that the some sanitizers may incur a performance penalty, and the Address Sanitizer may require a runtime library. To see line numbers in backtraces, declare these environment variables (adjust the llvm-symbolizer path as needed for your system): ``` sh export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.4 export ASAN_OPTIONS=symbolize=1 ``` ### Linux and other Unixes You may need to install a package such as `libmariadb-dev`, `libmysqlclient-dev`, `mysql-devel`, or `default-libmysqlclient-dev`; refer to your distribution's package guide to find the particular package. The most common issue we see is a user who has the library file `libmysqlclient.so` but is missing the header file `mysql.h` -- double check that you have the _-dev_ packages installed. ### Mac OS X You may use MacPorts, Homebrew, or a native MySQL installer package. The most common paths will be automatically searched. If you want to select a specific MySQL directory, use the `--with-mysql-dir` or `--with-mysql-config` options above. If you have not done so already, you will need to install the XCode select tools by running `xcode-select --install`. ### Windows Make sure that you have Ruby and the DevKit compilers installed. We recommend the [Ruby Installer](http://rubyinstaller.org) distribution. By default, the mysql2 gem will download and use MySQL Connector/C from mysql.com. If you prefer to use a local installation of Connector/C, add the flag `--with-mysql-dir=c:/mysql-connector-c-x-y-z` (_this path may use forward slashes_). By default, the `libmysql.dll` library will be copied into the mysql2 gem directory. To prevent this, add the flag `--no-vendor-libmysql`. The mysql2 gem will search for `libmysql.dll` in the following paths, in order: * Environment variable `RUBY_MYSQL2_LIBMYSQL_DLL=C:\path\to\libmysql.dll` (_note the Windows-style backslashes_). * In the mysql2 gem's own directory `vendor/libmysql.dll` * In the system's default library search paths. ## Usage Connect to a database: ``` ruby # this takes a hash of options, almost all of which map directly # to the familiar database.yml in rails # See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html client = Mysql2::Client.new(:host => "localhost", :username => "root") ``` Then query it: ``` ruby results = client.query("SELECT * FROM users WHERE group='githubbers'") ``` Need to escape something first? ``` ruby escaped = client.escape("gi'thu\"bbe\0r's") results = client.query("SELECT * FROM users WHERE group='#{escaped}'") ``` You can get a count of your results with `results.count`. Finally, iterate over the results: ``` ruby results.each do |row| # conveniently, row is a hash # the keys are the fields, as you'd expect # the values are pre-built ruby primitives mapped from their corresponding field types in MySQL puts row["id"] # row["id"].is_a? Integer if row["dne"] # non-existant hash entry is nil puts row["dne"] end end ``` Or, you might just keep it simple: ``` ruby client.query("SELECT * FROM users WHERE group='githubbers'").each do |row| # do something with row, it's ready to rock end ``` How about with symbolized keys? ``` ruby client.query("SELECT * FROM users WHERE group='githubbers'", :symbolize_keys => true).each do |row| # do something with row, it's ready to rock end ``` You can get the headers and the columns in the order that they were returned by the query like this: ``` ruby headers = results.fields # <= that's an array of field names, in order results.each(:as => :array) do |row| # Each row is an array, ordered the same as the query results # An otter's den is called a "holt" or "couch" end ``` Prepared statements are supported, as well. In a prepared statement, use a `?` in place of each value and then execute the statement to retrieve a result set. Pass your arguments to the execute method in the same number and order as the question marks in the statement. Query options can be passed as keyword arguments to the execute method. Be sure to read about the known limitations of prepared statements at [https://dev.mysql.com/doc/refman/5.6/en/c-api-prepared-statement-problems.html](https://dev.mysql.com/doc/refman/5.6/en/c-api-prepared-statement-problems.html) ``` ruby statement = @client.prepare("SELECT * FROM users WHERE login_count = ?") result1 = statement.execute(1) result2 = statement.execute(2) statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND location LIKE ?") result = statement.execute(1, "CA") statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND location LIKE ?") result = statement.execute(1, "CA", :as => :array) ``` ## Connection options You may set the following connection options in Mysql2::Client.new(...): ``` ruby Mysql2::Client.new( :host, :username, :password, :port, :database, :socket = '/path/to/mysql.sock', :flags = REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | MULTI_STATEMENTS, :encoding = 'utf8', :read_timeout = seconds, :write_timeout = seconds, :connect_timeout = seconds, :connect_attrs = {:program_name => $PROGRAM_NAME, ...}, :reconnect = true/false, :local_infile = true/false, :secure_auth = true/false, :ssl_mode = :disabled / :preferred / :required / :verify_ca / :verify_identity, :default_file = '/path/to/my.cfg', :default_group = 'my.cfg section', :default_auth = 'authentication_windows_client' :init_command => sql ) ``` ### Connecting to MySQL on localhost and elsewhere The underlying MySQL client library uses the `:host` parameter to determine the type of connection to make, with special interpretation you should be aware of: * An empty value or `"localhost"` will attempt a local connection: * On Unix, connect to the default local socket path. (To set a custom socket path, use the `:socket` parameter). * On Windows, connect using a shared-memory connection, if enabled, or TCP. * A value of `"."` on Windows specifies a named-pipe connection. * An IPv4 or IPv6 address will result in a TCP connection. * Any other value will be looked up as a hostname for a TCP connection. ### SSL options Setting any of the following options will enable an SSL connection, but only if your MySQL client library and server have been compiled with SSL support. MySQL client library defaults will be used for any parameters that are left out or set to nil. Relative paths are allowed, and may be required by managed hosting providers such as Heroku. Set `:sslverify => true` to require that the server presents a valid certificate. ``` ruby Mysql2::Client.new( # ...options as above..., :sslkey => '/path/to/client-key.pem', :sslcert => '/path/to/client-cert.pem', :sslca => '/path/to/ca-cert.pem', :sslcapath => '/path/to/cacerts', :sslcipher => 'DHE-RSA-AES256-SHA', :sslverify => true, ) ``` ### Secure auth Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). When secure_auth is enabled, the server will refuse a connection if the account password is stored in old pre-MySQL 4.1 format. The MySQL 5.6.5 client library may also refuse to attempt a connection if provided an older format password. To bypass this restriction in the client, pass the option `:secure_auth => false` to Mysql2::Client.new(). ### Flags option parsing The `:flags` parameter accepts an integer, a string, or an array. The integer form allows the client to assemble flags from constants defined under `Mysql2::Client` such as `Mysql2::Client::FOUND_ROWS`. Use a bitwise `|` (OR) to specify several flags. The string form will be split on whitespace and parsed as with the array form: Plain flags are added to the default flags, while flags prefixed with `-` (minus) are removed from the default flags. ### Using Active Record's database.yml Active Record typically reads its configuration from a file named `database.yml` or an environment variable `DATABASE_URL`. Use the value `mysql2` as the adapter name. For example: ``` yaml development: adapter: mysql2 encoding: utf8 database: my_db_name username: root password: my_password host: 127.0.0.1 port: 3306 flags: - -COMPRESS - FOUND_ROWS - MULTI_STATEMENTS secure_auth: false ``` ### Using Active Record's DATABASE_URL Active Record typically reads its configuration from a file named `database.yml` or an environment variable `DATABASE_URL`. Use the value `mysql2` as the protocol name. For example: ``` shell DATABASE_URL=mysql2://sql_user:sql_pass@sql_host_name:port/sql_db_name?option1=value1&option2=value2 ``` ### Reading a MySQL config file You may read configuration options from a MySQL configuration file by passing the `:default_file` and `:default_group` parameters. For example: ``` ruby Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') ``` ### Initial command on connect and reconnect If you specify the `:init_command` option, the SQL string you provide will be executed after the connection is established. If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect. It is useful if you want to provide session options which survive reconnection. ``` ruby Mysql2::Client.new(:init_command => "SET @@SESSION.sql_mode = 'STRICT_ALL_TABLES'") ``` ### Multiple result sets You can also retrieve multiple result sets. For this to work you need to connect with flags `Mysql2::Client::MULTI_STATEMENTS`. Multiple result sets can be used with stored procedures that return more than one result set, and for bundling several SQL statements into a single call to `client.query`. ``` ruby client = Mysql2::Client.new(:host => "localhost", :username => "root", :flags => Mysql2::Client::MULTI_STATEMENTS) result = client.query('CALL sp_customer_list( 25, 10 )') # result now contains the first result set while client.next_result result = client.store_result # result now contains the next result set end ``` Repeated calls to `client.next_result` will return true, false, or raise an exception if the respective query erred. When `client.next_result` returns true, call `client.store_result` to retrieve a result object. Exceptions are not raised until `client.next_result` is called to find the status of the respective query. Subsequent queries are not executed if an earlier query raised an exception. Subsequent calls to `client.next_result` will return false. ``` ruby result = client.query('SELECT 1; SELECT 2; SELECT A; SELECT 3') p result.first while client.next_result result = client.store_result p result.first end ``` Yields: ```ruby {"1"=>1} {"2"=>2} next_result: Unknown column 'A' in 'field list' (Mysql2::Error) ``` ## Cascading config The default config hash is at: ``` ruby Mysql2::Client.default_query_options ``` which defaults to: ``` ruby {:async => false, :as => :hash, :symbolize_keys => false} ``` that can be used as so: ``` ruby # these are the defaults all Mysql2::Client instances inherit Mysql2::Client.default_query_options.merge!(:as => :array) ``` or ``` ruby # this will change the defaults for all future results returned by the #query method _for this connection only_ c = Mysql2::Client.new c.query_options.merge!(:symbolize_keys => true) ``` or ``` ruby # this will set the options for the Mysql2::Result instance returned from the #query method c = Mysql2::Client.new c.query(sql, :symbolize_keys => true) ``` or ``` ruby # this will set the options for the Mysql2::Result instance returned from the #execute method c = Mysql2::Client.new s = c.prepare(sql) s.execute(arg1, args2, :symbolize_keys => true) ``` ## Result types ### Array of Arrays Pass the `:as => :array` option to any of the above methods of configuration ### Array of Hashes The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash ### Timezones Mysql2 now supports two timezone options: ``` ruby :database_timezone # this is the timezone Mysql2 will assume fields are already stored as, and will use this when creating the initial Time objects in ruby :application_timezone # this is the timezone Mysql2 will convert to before finally handing back to the caller ``` In other words, if `:database_timezone` is set to `:utc` - Mysql2 will create the Time objects using `Time.utc(...)` from the raw value libmysql hands over initially. Then, if `:application_timezone` is set to say - `:local` - Mysql2 will then convert the just-created UTC Time object to local time. Both options only allow two values - `:local` or `:utc` - with the exception that `:application_timezone` can be [and defaults to] nil ### Casting "boolean" columns You can now tell Mysql2 to cast `tinyint(1)` fields to boolean values in Ruby with the `:cast_booleans` option. ``` ruby client = Mysql2::Client.new result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true) ``` Keep in mind that this works only with fields and not with computed values, e.g. this result will contain `1`, not `true`: ``` ruby client = Mysql2::Client.new result = client.query("SELECT true", :cast_booleans => true) ``` CAST function wouldn't help here as there's no way to cast to TINYINT(1). Apparently the only way to solve this is to use a stored procedure with return type set to TINYINT(1). ### Skipping casting Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false. (Note that :cast => false overrides :cast_booleans => true.) ``` ruby client = Mysql2::Client.new result = client.query("SELECT * FROM table", :cast => false) ``` Here are the results from the `query_without_mysql_casting.rb` script in the benchmarks folder: ``` sh user system total real Mysql2 (cast: true) 0.340000 0.000000 0.340000 ( 0.405018) Mysql2 (cast: false) 0.160000 0.010000 0.170000 ( 0.209937) Mysql 0.080000 0.000000 0.080000 ( 0.129355) do_mysql 0.520000 0.010000 0.530000 ( 0.574619) ``` Although Mysql2 performs reasonably well at retrieving uncasted data, it (currently) is not as fast as the Mysql gem. In spite of this small disadvantage, Mysql2 still sports a friendlier interface and doesn't block the entire ruby process when querying. ### Async NOTE: Not supported on Windows. `Mysql2::Client` takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries. But, in order to take full advantage of it in your Ruby code, you can do: ``` ruby client.query("SELECT sleep(5)", :async => true) ``` Which will return nil immediately. At this point you'll probably want to use some socket monitoring mechanism like EventMachine or even IO.select. Once the socket becomes readable, you can do: ``` ruby # result will be a Mysql2::Result instance result = client.async_result ``` NOTE: Because of the way MySQL's query API works, this method will block until the result is ready. So if you really need things to stay async, it's best to just monitor the socket with something like EventMachine. If you need multiple query concurrency take a look at using a connection pool. ### Row Caching By default, Mysql2 will cache rows that have been created in Ruby (since this happens lazily). This is especially helpful since it saves the cost of creating the row in Ruby if you were to iterate over the collection again. If you only plan on using each row once, then it's much more efficient to disable this behavior by setting the `:cache_rows` option to false. This would be helpful if you wanted to iterate over the results in a streaming manner. Meaning the GC would cleanup rows you don't need anymore as you're iterating over the result set. ### Streaming `Mysql2::Client` can optionally only fetch rows from the server on demand by setting `:stream => true`. This is handy when handling very large result sets which might not fit in memory on the client. ``` ruby result = client.query("SELECT * FROM really_big_Table", :stream => true) ``` There are a few things that need to be kept in mind while using streaming: * `:cache_rows` is ignored currently. (if you want to use `:cache_rows` you probably don't want to be using `:stream`) * You must fetch all rows in the result set of your query before you can make new queries. (i.e. with `Mysql2::Result#each`) Read more about the consequences of using `mysql_use_result` (what streaming is implemented with) here: [http://dev.mysql.com/doc/refman/5.0/en/mysql-use-result.html](http://dev.mysql.com/doc/refman/5.0/en/mysql-use-result.html). ### Lazy Everything Well... almost ;) Field name strings/symbols are shared across all the rows so only one object is ever created to represent the field name for an entire dataset. Rows themselves are lazily created in ruby-land when an attempt to yield it is made via #each. For example, if you were to yield 4 rows from a 100 row dataset, only 4 hashes will be created. The rest will sit and wait in C-land until you want them (or when the GC goes to cleanup your `Mysql2::Result` instance). Now say you were to iterate over that same collection again, this time yielding 15 rows - the 4 previous rows that had already been turned into ruby hashes would be pulled from an internal cache, then 11 more would be created and stored in that cache. Once the entire dataset has been converted into ruby objects, Mysql2::Result will free the Mysql C result object as it's no longer needed. This caching behavior can be disabled by setting the `:cache_rows` option to false. As for field values themselves, I'm workin on it - but expect that soon. ## Compatibility This gem is tested with the following Ruby versions on Linux and Mac OS X: * Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x, 2.5.x, 2.6.x * Rubinius 2.x and 3.x do work but may fail under some workloads This gem is tested with the following MySQL and MariaDB versions: * MySQL 5.5, 5.6, 5.7, 8.0 * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) * MariaDB 5.5, 10.0, 10.1, 10.2, 10.3 ### Ruby on Rails / Active Record * mysql2 0.5.x works with Rails / Active Record 5.0.7, 5.1.6, and higher. * mysql2 0.4.x works with Rails / Active Record 4.2.5 - 5.0 and higher. * mysql2 0.3.x works with Rails / Active Record 3.1, 3.2, 4.x, 5.0. * mysql2 0.2.x works with Rails / Active Record 2.3 - 3.0. ### Asynchronous Active Record Please see the [em-synchrony](https://github.com/igrigorik/em-synchrony) project for details about using EventMachine with mysql2 and Rails. ### Sequel Sequel includes a mysql2 adapter in all releases since 3.15 (2010-09-01). Use the prefix "mysql2://" in your connection specification. ### EventMachine The mysql2 EventMachine deferrable api allows you to make async queries using EventMachine, while specifying callbacks for success for failure. Here's a simple example: ``` ruby require 'mysql2/em' EM.run do client1 = Mysql2::EM::Client.new defer1 = client1.query "SELECT sleep(3) as first_query" defer1.callback do |result| puts "Result: #{result.to_a.inspect}" end client2 = Mysql2::EM::Client.new defer2 = client2.query "SELECT sleep(1) second_query" defer2.callback do |result| puts "Result: #{result.to_a.inspect}" end end ``` ## Benchmarks and Comparison The mysql2 gem converts MySQL field types to Ruby data types in C code, providing a serious speed benefit. The do_mysql gem also converts MySQL fields types, but has a considerably more complex API and is still ~2x slower than mysql2. The mysql gem returns only nil or string data types, leaving you to convert field values to Ruby types in Ruby-land, which is much slower than mysql2's C code. For a comparative benchmark, the script below performs a basic "SELECT * FROM" query on a table with 30k rows and fields of nearly every Ruby-representable data type, then iterating over every row using an #each like method yielding a block: ``` sh user system total real Mysql2 0.750000 0.180000 0.930000 (1.821655) do_mysql 1.650000 0.200000 1.850000 (2.811357) Mysql 7.500000 0.210000 7.710000 (8.065871) ``` These results are from the `query_with_mysql_casting.rb` script in the benchmarks folder. ## Development Use 'bundle install' to install the necessary development and testing gems: ``` sh bundle install rake ``` The tests require the "test" database to exist, and expect to connect both as root and the running user, both with a blank password: ``` sql CREATE DATABASE test; CREATE USER ''@'localhost' IDENTIFIED BY ''; GRANT ALL PRIVILEGES ON test.* TO ''@'localhost'; ``` You can change these defaults in the spec/configuration.yml which is generated automatically when you run rake (or explicitly `rake spec/configuration.yml`). For a normal installation on a Mac, you most likely do not need to do anything, though. ## Special Thanks * Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude * [Yury Korolev](http://github.com/yury) - for TONS of help testing the Active Record adapter * [Aaron Patterson](http://github.com/tenderlove) - tons of contributions, suggestions and general badassness * [Mike Perham](http://github.com/mperham) - Async Active Record adapter (uses Fibers and EventMachine) * [Aaron Stone](http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support * [Kouhei Ueno](https://github.com/nyaxt) - for the original work on Prepared Statements way back in 2012 * [John Cant](http://github.com/johncant) - polishing and updating Prepared Statements support * [Justin Case](http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged * [Tamir Duberstein](http://github.com/tamird) - for help with timeouts and all around updates and cleanups mysql2-0.5.3/Rakefile000066400000000000000000000006211356743356300144310ustar00rootroot00000000000000require 'rake' # Load custom tasks (careful attention to define tasks before prerequisites) load 'tasks/vendor_mysql.rake' load 'tasks/rspec.rake' load 'tasks/compile.rake' load 'tasks/generate.rake' load 'tasks/benchmarks.rake' begin require 'rubocop/rake_task' RuboCop::RakeTask.new task default: %i[spec rubocop] rescue LoadError warn 'RuboCop is not available' task default: :spec end mysql2-0.5.3/appveyor.yml000066400000000000000000000026251356743356300153620ustar00rootroot00000000000000--- version: "{build}" clone_depth: 10 install: - SET PATH=C:\MinGW\msys\1.0\bin;%PATH% - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - ruby --version - gem --version - bundler --version - bundle install --without benchmarks --path vendor/bundle - IF DEFINED MINGW_PACKAGE_PREFIX (ridk exec pacman -S --noconfirm --needed %MINGW_PACKAGE_PREFIX%-libmariadbclient) build_script: - bundle exec rake compile test_script: - '"C:\Program Files\MySQL\MySQL Server 5.7\bin\mysql" --version' - > "C:\Program Files\MySQL\MySQL Server 5.7\bin\mysql" -u root -p"Password12!" -e " CREATE DATABASE IF NOT EXISTS test; CREATE USER '%USERNAME%'@'localhost'; SET PASSWORD = PASSWORD(''); FLUSH PRIVILEGES; " - bundle exec rake spec on_failure: - find tmp -name "*.log" | xargs cat environment: matrix: - ruby_version: "26-x64" MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" - ruby_version: "25-x64" MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" - ruby_version: "24-x64" MINGW_PACKAGE_PREFIX: "mingw-w64-x86_64" - ruby_version: "24" MINGW_PACKAGE_PREFIX: "mingw-w64-i686" - ruby_version: "23-x64" - ruby_version: "23" - ruby_version: "22-x64" - ruby_version: "22" - ruby_version: "21-x64" - ruby_version: "21" - ruby_version: "200-x64" - ruby_version: "200" cache: - vendor services: - mysql hosts: mysql2gem.example.com: 127.0.0.1 mysql2-0.5.3/benchmark/000077500000000000000000000000001356743356300147175ustar00rootroot00000000000000mysql2-0.5.3/benchmark/active_record.rb000066400000000000000000000012521356743356300200550ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' require 'benchmark/ips' require 'active_record' ActiveRecord::Base.default_timezone = :local ActiveRecord::Base.time_zone_aware_attributes = true opts = { database: 'test' } class TestModel < ActiveRecord::Base self.table_name = 'mysql2_test' end batch_size = 1000 Benchmark.ips do |x| %w[mysql mysql2].each do |adapter| TestModel.establish_connection(opts.merge(adapter: adapter)) x.report(adapter) do TestModel.limit(batch_size).to_a.each do |r| r.attributes.each_key do |k| r.send(k.to_sym) end end end end x.compare! end mysql2-0.5.3/benchmark/active_record_threaded.rb000066400000000000000000000010451356743356300217150ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' require 'benchmark/ips' require 'active_record' number_of_threads = 25 opts = { database: 'test', pool: number_of_threads } Benchmark.ips do |x| %w[mysql mysql2].each do |adapter| ActiveRecord::Base.establish_connection(opts.merge(adapter: adapter)) x.report(adapter) do Array.new(number_of_threads) do Thread.new { ActiveRecord::Base.connection.execute('SELECT SLEEP(1)') } end.each(&:join) end end x.compare! end mysql2-0.5.3/benchmark/allocations.rb000066400000000000000000000014051356743356300175540ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' require 'active_record' ActiveRecord::Base.default_timezone = :local ActiveRecord::Base.time_zone_aware_attributes = true class TestModel < ActiveRecord::Base self.table_name = 'mysql2_test' end def bench_allocations(feature, iterations = 10, batch_size = 1000) puts "GC overhead for #{feature}" TestModel.establish_connection(adapter: 'mysql2', database: 'test') GC::Profiler.clear GC::Profiler.enable iterations.times { yield batch_size } GC::Profiler.report(STDOUT) GC::Profiler.disable end bench_allocations('coercion') do |batch_size| TestModel.limit(batch_size).to_a.each do |r| r.attributes.each_key do |k| r.send(k.to_sym) end end end mysql2-0.5.3/benchmark/escape.rb000066400000000000000000000013441356743356300165060ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' require 'benchmark/ips' require 'mysql' require 'mysql2' require 'do_mysql' def run_escape_benchmarks(str) Benchmark.ips do |x| mysql = Mysql.new("localhost", "root") x.report "Mysql #{str.inspect}" do mysql.quote str end mysql2 = Mysql2::Client.new(host: "localhost", username: "root") x.report "Mysql2 #{str.inspect}" do mysql2.escape str end do_mysql = DataObjects::Connection.new("mysql://localhost/test") x.report "do_mysql #{str.inspect}" do do_mysql.quote_string str end x.compare! end end run_escape_benchmarks "abc'def\"ghi\0jkl%mno" run_escape_benchmarks "clean string" mysql2-0.5.3/benchmark/query_with_mysql_casting.rb000066400000000000000000000040011356743356300223740ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' require 'benchmark/ips' require 'mysql' require 'mysql2' require 'do_mysql' database = 'test' sql = "SELECT * FROM mysql2_test LIMIT 100" class Mysql include Enumerable end def mysql_cast(type, value) case type when Mysql::Field::TYPE_NULL nil when Mysql::Field::TYPE_TINY, Mysql::Field::TYPE_SHORT, Mysql::Field::TYPE_LONG, Mysql::Field::TYPE_INT24, Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_YEAR value.to_i when Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_NEWDECIMAL BigDecimal(value) when Mysql::Field::TYPE_DOUBLE, Mysql::Field::TYPE_FLOAT value.to_f when Mysql::Field::TYPE_DATE Date.parse(value) when Mysql::Field::TYPE_TIME, Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP Time.parse(value) when Mysql::Field::TYPE_BLOB, Mysql::Field::TYPE_BIT, Mysql::Field::TYPE_STRING, Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET, Mysql::Field::TYPE_ENUM value else value end end debug = ENV['DEBUG'] Benchmark.ips do |x| mysql2 = Mysql2::Client.new(host: "localhost", username: "root") mysql2.query "USE #{database}" x.report "Mysql2" do mysql2_result = mysql2.query sql, symbolize_keys: true mysql2_result.each { |res| puts res.inspect if debug } end mysql = Mysql.new("localhost", "root") mysql.query "USE #{database}" x.report "Mysql" do mysql_result = mysql.query sql fields = mysql_result.fetch_fields mysql_result.each do |row| row_hash = row.each_with_index.each_with_object({}) do |(f, j), hash| hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, f) end puts row_hash.inspect if debug end end do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") command = do_mysql.create_command sql x.report "do_mysql" do do_result = command.execute_reader do_result.each { |res| puts res.inspect if debug } end x.compare! end mysql2-0.5.3/benchmark/query_without_mysql_casting.rb000066400000000000000000000022561356743356300231360ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' require 'benchmark/ips' require 'mysql' require 'mysql2' require 'do_mysql' database = 'test' sql = "SELECT * FROM mysql2_test LIMIT 100" debug = ENV['DEBUG'] Benchmark.ips do |x| mysql2 = Mysql2::Client.new(host: "localhost", username: "root") mysql2.query "USE #{database}" x.report "Mysql2 (cast: true)" do mysql2_result = mysql2.query sql, symbolize_keys: true, cast: true mysql2_result.each { |res| puts res.inspect if debug } end x.report "Mysql2 (cast: false)" do mysql2_result = mysql2.query sql, symbolize_keys: true, cast: false mysql2_result.each { |res| puts res.inspect if debug } end mysql = Mysql.new("localhost", "root") mysql.query "USE #{database}" x.report "Mysql" do mysql_result = mysql.query sql mysql_result.each_hash { |res| puts res.inspect if debug } end do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") command = DataObjects::Mysql::Command.new do_mysql, sql x.report "do_mysql" do do_result = command.execute_reader do_result.each { |res| puts res.inspect if debug } end x.compare! end mysql2-0.5.3/benchmark/sequel.rb000066400000000000000000000014271356743356300165460ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' require 'benchmark/ips' require 'mysql2' require 'sequel' require 'sequel/adapters/do' mysql2_opts = "mysql2://root@localhost/test" mysql_opts = "mysql://root@localhost/test" do_mysql_opts = "do:mysql://root@localhost/test" class Mysql2Model < Sequel::Model(Sequel.connect(mysql2_opts)[:mysql2_test]); end class MysqlModel < Sequel::Model(Sequel.connect(mysql_opts)[:mysql2_test]); end class DOMysqlModel < Sequel::Model(Sequel.connect(do_mysql_opts)[:mysql2_test]); end Benchmark.ips do |x| x.report "Mysql2" do Mysql2Model.limit(1000).all end x.report "do:mysql" do DOMysqlModel.limit(1000).all end x.report "Mysql" do MysqlModel.limit(1000).all end x.compare! end mysql2-0.5.3/benchmark/setup_db.rb000066400000000000000000000105271356743356300170560ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') # This script is for generating psudo-random data into a single table consisting of nearly every # data type MySQL 5.1 supports. # # It's meant to be used with the query.rb benchmark script (or others in the future) require 'mysql2' require 'rubygems' require 'faker' num = ENV['NUM'] && ENV['NUM'].to_i || 10_000 create_table_sql = %[ CREATE TABLE IF NOT EXISTS mysql2_test ( null_test VARCHAR(10), bit_test BIT, tiny_int_test TINYINT, small_int_test SMALLINT, medium_int_test MEDIUMINT, int_test INT, big_int_test BIGINT, float_test FLOAT(10,3), float_zero_test FLOAT(10,3), double_test DOUBLE(10,3), decimal_test DECIMAL(10,3), decimal_zero_test DECIMAL(10,3), date_test DATE, date_time_test DATETIME, timestamp_test TIMESTAMP, time_test TIME, year_test YEAR(4), char_test CHAR(10), varchar_test VARCHAR(10), binary_test BINARY(10), varbinary_test VARBINARY(10), tiny_blob_test TINYBLOB, tiny_text_test TINYTEXT, blob_test BLOB, text_test TEXT, medium_blob_test MEDIUMBLOB, medium_text_test MEDIUMTEXT, long_blob_test LONGBLOB, long_text_test LONGTEXT, enum_test ENUM('val1', 'val2'), set_test SET('val1', 'val2') ) DEFAULT CHARSET=utf8 ] # connect to localhost by default, pass options as needed @client = Mysql2::Client.new host: "localhost", username: "root", database: "test" @client.query create_table_sql @client.query 'TRUNCATE mysql2_test' def insert_record(args) insert_sql = " INSERT INTO mysql2_test ( null_test, bit_test, tiny_int_test, small_int_test, medium_int_test, int_test, big_int_test, float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test, year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test, tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test, long_blob_test, long_text_test, enum_test, set_test ) VALUES ( NULL, #{args[:bit_test]}, #{args[:tiny_int_test]}, #{args[:small_int_test]}, #{args[:medium_int_test]}, #{args[:int_test]}, #{args[:big_int_test]}, #{args[:float_test]}, #{args[:float_zero_test]}, #{args[:double_test]}, #{args[:decimal_test]}, #{args[:decimal_zero_test]}, '#{args[:date_test]}', '#{args[:date_time_test]}', '#{args[:timestamp_test]}', '#{args[:time_test]}', #{args[:year_test]}, '#{args[:char_test]}', '#{args[:varchar_test]}', '#{args[:binary_test]}', '#{args[:varbinary_test]}', '#{args[:tiny_blob_test]}', '#{args[:tiny_text_test]}', '#{args[:blob_test]}', '#{args[:text_test]}', '#{args[:medium_blob_test]}', '#{args[:medium_text_test]}', '#{args[:long_blob_test]}', '#{args[:long_text_test]}', '#{args[:enum_test]}', '#{args[:set_test]}' ) " @client.query insert_sql end puts "Creating #{num} records" num.times do |n| five_words = Faker::Lorem.words(rand(5)) twenty5_paragraphs = Faker::Lorem.paragraphs(rand(25)) insert_record( bit_test: 1, tiny_int_test: rand(128), small_int_test: rand(32767), medium_int_test: rand(8388607), int_test: rand(2147483647), big_int_test: rand(9223372036854775807), float_test: rand(32767) / 1.87, float_zero_test: 0.0, double_test: rand(8388607) / 1.87, decimal_test: rand(8388607) / 1.87, decimal_zero_test: 0, date_test: '2010-4-4', date_time_test: '2010-4-4 11:44:00', timestamp_test: '2010-4-4 11:44:00', time_test: '11:44:00', year_test: Time.now.year, char_test: five_words.join.slice(0, 10), # CHAR(10) varchar_test: five_words.join.slice(0, 10), # VARCHAR(10) binary_test: five_words.join.byteslice(0, 10), # BINARY(10) varbinary_test: five_words.join.byteslice(0, 10), # VARBINARY(10) tiny_blob_test: five_words.join.byteslice(0, 255), # TINYBLOB tiny_text_test: Faker::Lorem.paragraph(rand(5)).byteslice(0, 255), # TINYTEXT blob_test: twenty5_paragraphs, text_test: twenty5_paragraphs, medium_blob_test: twenty5_paragraphs, medium_text_test: twenty5_paragraphs, long_blob_test: twenty5_paragraphs, long_text_test: twenty5_paragraphs, enum_test: %w[val1 val2].sample, set_test: %w[val1 val2 val1,val2].sample, ) if (n % 100).zero? $stdout.putc '.' $stdout.flush end end puts puts "Done" mysql2-0.5.3/examples/000077500000000000000000000000001356743356300146035ustar00rootroot00000000000000mysql2-0.5.3/examples/eventmachine.rb000066400000000000000000000006741356743356300176050ustar00rootroot00000000000000$LOAD_PATH.unshift 'lib' require 'rubygems' require 'eventmachine' require 'mysql2/em' EM.run do client1 = Mysql2::EM::Client.new defer1 = client1.query "SELECT sleep(3) as first_query" defer1.callback do |result| puts "Result: #{result.to_a.inspect}" end client2 = Mysql2::EM::Client.new defer2 = client2.query "SELECT sleep(1) second_query" defer2.callback do |result| puts "Result: #{result.to_a.inspect}" end end mysql2-0.5.3/examples/threaded.rb000066400000000000000000000010501356743356300167040ustar00rootroot00000000000000$LOAD_PATH.unshift 'lib' require 'mysql2' require 'timeout' # Should never exceed worst case 3.5 secs across all 20 threads Timeout.timeout(3.5) do Array.new(20) do Thread.new do overhead = rand(3) puts ">> thread #{Thread.current.object_id} query, #{overhead} sec overhead" # 3 second overhead per query Mysql2::Client.new(host: "localhost", username: "root").query("SELECT sleep(#{overhead}) as result") puts "<< thread #{Thread.current.object_id} result, #{overhead} sec overhead" end end.each(&:join) end mysql2-0.5.3/ext/000077500000000000000000000000001356743356300135655ustar00rootroot00000000000000mysql2-0.5.3/ext/mysql2/000077500000000000000000000000001356743356300150145ustar00rootroot00000000000000mysql2-0.5.3/ext/mysql2/client.c000066400000000000000000001431741356743356300164500ustar00rootroot00000000000000#include #include #include #ifndef _WIN32 #include #include #endif #ifndef _MSC_VER #include #endif #include #include "wait_for_single_fd.h" #include "mysql_enc_name_to_ruby.h" VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError; static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; static VALUE sym_no_good_index_used, sym_no_index_used, sym_query_was_slow; static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args, intern_current_query_options, intern_read_timeout; #define REQUIRE_INITIALIZED(wrapper) \ if (!wrapper->initialized) { \ rb_raise(cMysql2Error, "MySQL client is not initialized"); \ } #if defined(HAVE_MYSQL_NET_VIO) || defined(HAVE_ST_NET_VIO) #define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1) #elif defined(HAVE_MYSQL_NET_PVIO) || defined(HAVE_ST_NET_PVIO) #define CONNECTED(wrapper) (wrapper->client->net.pvio != NULL && wrapper->client->net.fd != -1) #endif #define REQUIRE_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ if (!CONNECTED(wrapper) && !wrapper->reconnect_enabled) { \ rb_raise(cMysql2Error, "MySQL client is not connected"); \ } #define REQUIRE_NOT_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ if (CONNECTED(wrapper)) { \ rb_raise(cMysql2Error, "MySQL connection is already open"); \ } /* * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when * linking against the server itself */ #if defined(MARIADB_CLIENT_VERSION_STR) #define MYSQL_LINK_VERSION MARIADB_CLIENT_VERSION_STR #elif defined(LIBMYSQL_VERSION) #define MYSQL_LINK_VERSION LIBMYSQL_VERSION #else #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION #endif /* * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10. */ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE #define SSL_MODE_DISABLED 1 #define SSL_MODE_REQUIRED 3 #define HAVE_CONST_SSL_MODE_DISABLED #define HAVE_CONST_SSL_MODE_REQUIRED #endif /* * used to pass all arguments to mysql_real_connect while inside * rb_thread_call_without_gvl */ struct nogvl_connect_args { MYSQL *mysql; const char *host; const char *user; const char *passwd; const char *db; unsigned int port; const char *unix_socket; unsigned long client_flag; }; /* * used to pass all arguments to mysql_send_query while inside * rb_thread_call_without_gvl */ struct nogvl_send_query_args { MYSQL *mysql; VALUE sql; const char *sql_ptr; long sql_len; mysql_client_wrapper *wrapper; }; /* * used to pass all arguments to mysql_select_db while inside * rb_thread_call_without_gvl */ struct nogvl_select_db_args { MYSQL *mysql; char *db; }; static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) { unsigned long version = mysql_get_client_version(); if (version < 50703) { rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." ); return Qnil; } #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE GET_CLIENT(self); int val = NUM2INT( setting ); // Either MySQL 5.7.3 - 5.7.10, or Connector/C 6.1.3 - 6.1.x if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200)) { if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) { my_bool b = ( val == SSL_MODE_REQUIRED ); int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b ); return INT2NUM(result); } else { rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" ); return Qnil; } } else { rb_warn( "Your mysql client library does not support ssl_mode as expected." ); return Qnil; } #endif #ifdef FULL_SSL_MODE_SUPPORT GET_CLIENT(self); int val = NUM2INT( setting ); if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) { rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val ); } int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val ); return INT2NUM(result); #endif #ifdef NO_SSL_MODE_SUPPORT return Qnil; #endif } /* * non-blocking mysql_*() functions that we won't be wrapping since * they do not appear to hit the network nor issue any interruptible * or blocking system calls. * * - mysql_affected_rows() * - mysql_error() * - mysql_fetch_fields() * - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths * - mysql_field_count() * - mysql_get_client_info() * - mysql_get_client_version() * - mysql_get_server_info() * - mysql_get_server_version() * - mysql_insert_id() * - mysql_num_fields() * - mysql_num_rows() * - mysql_options() * - mysql_real_escape_string() * - mysql_ssl_set() */ static void rb_mysql_client_mark(void * wrapper) { mysql_client_wrapper * w = wrapper; if (w) { rb_gc_mark(w->encoding); rb_gc_mark(w->active_thread); } } static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client)); VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client)); VALUE e; rb_enc_associate(rb_error_msg, rb_utf8_encoding()); rb_enc_associate(rb_sql_state, rb_usascii_encoding()); e = rb_funcall(cMysql2Error, intern_new_with_args, 4, rb_error_msg, LONG2FIX(wrapper->server_version), UINT2NUM(mysql_errno(wrapper->client)), rb_sql_state); rb_exc_raise(e); } static void *nogvl_init(void *ptr) { MYSQL *client; mysql_client_wrapper *wrapper = ptr; /* may initialize embedded server and read /etc/services off disk */ client = mysql_init(wrapper->client); if (client) mysql2_set_local_infile(client, wrapper); return (void*)(client ? Qtrue : Qfalse); } static void *nogvl_connect(void *ptr) { struct nogvl_connect_args *args = ptr; MYSQL *client; client = mysql_real_connect(args->mysql, args->host, args->user, args->passwd, args->db, args->port, args->unix_socket, args->client_flag); return (void *)(client ? Qtrue : Qfalse); } #ifndef _WIN32 /* * Redirect clientfd to /dev/null for mysql_close and SSL_close to write, * shutdown, and close. The hack is needed to prevent shutdown() from breaking * a socket that may be in use by the parent or other processes after fork. * * /dev/null is used to absorb writes; previously a dummy socket was used, but * it could not abosrb writes and caused openssl to go into an infinite loop. * * Returns Qtrue or Qfalse (success or failure) * * Note: if this function is needed on Windows, use "nul" instead of "/dev/null" */ static VALUE invalidate_fd(int clientfd) { #ifdef O_CLOEXEC /* Atomically set CLOEXEC on the new FD in case another thread forks */ int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC); #else /* Well we don't have O_CLOEXEC, trigger the fallback code below */ int sockfd = -1; #endif if (sockfd < 0) { /* Either O_CLOEXEC wasn't defined at compile time, or it was defined at * compile time, but isn't available at run-time. So we'll just be quick * about setting FD_CLOEXEC now. */ int flags; sockfd = open("/dev/null", O_RDWR); flags = fcntl(sockfd, F_GETFD); /* Do the flags dance in case there are more defined flags in the future */ if (flags != -1) { flags |= FD_CLOEXEC; fcntl(sockfd, F_SETFD, flags); } } if (sockfd < 0) { /* Cannot raise here, because one or both of the following may be true: * a) we have no GVL (in C Ruby) * b) are running as a GC finalizer */ return Qfalse; } dup2(sockfd, clientfd); close(sockfd); return Qtrue; } #endif /* _WIN32 */ static void *nogvl_close(void *ptr) { mysql_client_wrapper *wrapper = ptr; if (!wrapper->closed) { mysql_close(wrapper->client); wrapper->closed = 1; wrapper->reconnect_enabled = 0; wrapper->active_thread = Qnil; } return NULL; } /* this is called during GC */ static void rb_mysql_client_free(void *ptr) { mysql_client_wrapper *wrapper = ptr; decr_mysql2_client(wrapper); } void decr_mysql2_client(mysql_client_wrapper *wrapper) { wrapper->refcount--; if (wrapper->refcount == 0) { #ifndef _WIN32 if (CONNECTED(wrapper) && !wrapper->automatic_close) { /* The client is being garbage collected while connected. Prevent * mysql_close() from sending a mysql-QUIT or from calling shutdown() on * the socket by invalidating it. invalidate_fd() will drop this * process's reference to the socket only, while a QUIT or shutdown() * would render the underlying connection unusable, interrupting other * processes which share this object across a fork(). */ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n"); close(wrapper->client->net.fd); } wrapper->client->net.fd = -1; } #endif nogvl_close(wrapper); xfree(wrapper->client); xfree(wrapper); } } static VALUE allocate(VALUE klass) { VALUE obj; mysql_client_wrapper * wrapper; obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; wrapper->active_thread = Qnil; wrapper->automatic_close = 1; wrapper->server_version = 0; wrapper->reconnect_enabled = 0; wrapper->connect_timeout = 0; wrapper->initialized = 0; /* means that that the wrapper is initialized */ wrapper->refcount = 1; wrapper->closed = 0; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); return obj; } /* call-seq: * Mysql2::Client.escape(string) * * Escape +string+ so that it may be used in a SQL statement. * Note that this escape method is not connection encoding aware. * If you need encoding support use Mysql2::Client#escape instead. */ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) { unsigned char *newStr; VALUE rb_str; unsigned long newLen, oldLen; Check_Type(str, T_STRING); oldLen = RSTRING_LEN(str); newStr = xmalloc(oldLen*2+1); newLen = mysql_escape_string((char *)newStr, RSTRING_PTR(str), oldLen); if (newLen == oldLen) { /* no need to return a new ruby string if nothing changed */ xfree(newStr); return str; } else { rb_str = rb_str_new((const char*)newStr, newLen); rb_enc_copy(rb_str, str); xfree(newStr); return rb_str; } } static VALUE rb_mysql_client_warning_count(VALUE self) { unsigned int warning_count; GET_CLIENT(self); warning_count = mysql_warning_count(wrapper->client); return UINT2NUM(warning_count); } static VALUE rb_mysql_info(VALUE self) { const char *info; VALUE rb_str; GET_CLIENT(self); info = mysql_info(wrapper->client); if (info == NULL) { return Qnil; } rb_str = rb_str_new2(info); rb_enc_associate(rb_str, rb_utf8_encoding()); return rb_str; } static VALUE rb_mysql_get_ssl_cipher(VALUE self) { const char *cipher; VALUE rb_str; GET_CLIENT(self); cipher = mysql_get_ssl_cipher(wrapper->client); if (cipher == NULL) { return Qnil; } rb_str = rb_str_new2(cipher); rb_enc_associate(rb_str, rb_utf8_encoding()); return rb_str; } #ifdef CLIENT_CONNECT_ATTRS static int opt_connect_attr_add_i(VALUE key, VALUE value, VALUE arg) { mysql_client_wrapper *wrapper = (mysql_client_wrapper *)arg; rb_encoding *enc = rb_to_encoding(wrapper->encoding); key = rb_str_export_to_enc(key, enc); value = rb_str_export_to_enc(value, enc); mysql_options4(wrapper->client, MYSQL_OPT_CONNECT_ATTR_ADD, StringValueCStr(key), StringValueCStr(value)); return ST_CONTINUE; } #endif static VALUE rb_mysql_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags, VALUE conn_attrs) { struct nogvl_connect_args args; time_t start_time, end_time, elapsed_time, connect_timeout; VALUE rv; GET_CLIENT(self); args.host = NIL_P(host) ? NULL : StringValueCStr(host); args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket); args.port = NIL_P(port) ? 0 : NUM2INT(port); args.user = NIL_P(user) ? NULL : StringValueCStr(user); args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass); args.db = NIL_P(database) ? NULL : StringValueCStr(database); args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); #ifdef CLIENT_CONNECT_ATTRS mysql_options(wrapper->client, MYSQL_OPT_CONNECT_ATTR_RESET, 0); rb_hash_foreach(conn_attrs, opt_connect_attr_add_i, (VALUE)wrapper); #endif if (wrapper->connect_timeout) time(&start_time); rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); if (rv == Qfalse) { while (rv == Qfalse && errno == EINTR) { if (wrapper->connect_timeout) { time(&end_time); /* avoid long connect timeout from system time changes */ if (end_time < start_time) start_time = end_time; elapsed_time = end_time - start_time; /* avoid an early timeout due to time truncating milliseconds off the start time */ if (elapsed_time > 0) elapsed_time--; if (elapsed_time >= (time_t)wrapper->connect_timeout) break; connect_timeout = wrapper->connect_timeout - elapsed_time; mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout); } errno = 0; rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); } /* restore the connect timeout for reconnecting */ if (wrapper->connect_timeout) mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout); if (rv == Qfalse) rb_raise_mysql2_error(wrapper); } wrapper->server_version = mysql_get_server_version(wrapper->client); return self; } /* * Immediately disconnect from the server; normally the garbage collector * will disconnect automatically when a connection is no longer needed. * Explicitly closing this will free up server resources sooner than waiting * for the garbage collector. * * @return [nil] */ static VALUE rb_mysql_client_close(VALUE self) { GET_CLIENT(self); if (wrapper->client) { rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0); } return Qnil; } /* call-seq: * client.closed? * * @return [Boolean] */ static VALUE rb_mysql_client_closed(VALUE self) { GET_CLIENT(self); return CONNECTED(wrapper) ? Qfalse : Qtrue; } /* * mysql_send_query is unlikely to block since most queries are small * enough to fit in a socket buffer, but sometimes large UPDATE and * INSERTs will cause the process to block */ static void *nogvl_send_query(void *ptr) { struct nogvl_send_query_args *args = ptr; int rv; rv = mysql_send_query(args->mysql, args->sql_ptr, args->sql_len); return (void*)(rv == 0 ? Qtrue : Qfalse); } static VALUE do_send_query(void *args) { struct nogvl_send_query_args *query_args = args; mysql_client_wrapper *wrapper = query_args->wrapper; if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ wrapper->active_thread = Qnil; rb_raise_mysql2_error(wrapper); } return Qnil; } /* * even though we did rb_thread_select before calling this, a large * response can overflow the socket buffers and cause us to eventually * block while calling mysql_read_query_result */ static void *nogvl_read_query_result(void *ptr) { MYSQL * client = ptr; my_bool res = mysql_read_query_result(client); return (void *)(res == 0 ? Qtrue : Qfalse); } static void *nogvl_do_result(void *ptr, char use_result) { mysql_client_wrapper *wrapper = ptr; MYSQL_RES *result; if (use_result) { result = mysql_use_result(wrapper->client); } else { result = mysql_store_result(wrapper->client); } /* once our result is stored off, this connection is ready for another command to be issued */ wrapper->active_thread = Qnil; return result; } /* mysql_store_result may (unlikely) read rows off the socket */ static void *nogvl_store_result(void *ptr) { return nogvl_do_result(ptr, 0); } static void *nogvl_use_result(void *ptr) { return nogvl_do_result(ptr, 1); } /* call-seq: * client.async_result * * Returns the result for the last async issued query. */ static VALUE rb_mysql_client_async_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; VALUE current, is_streaming; GET_CLIENT(self); /* if we're not waiting on a result, do nothing */ if (NIL_P(wrapper->active_thread)) return Qnil; REQUIRE_CONNECTED(wrapper); if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, mark this connection inactive */ wrapper->active_thread = Qnil; rb_raise_mysql2_error(wrapper); } is_streaming = rb_hash_aref(rb_ivar_get(self, intern_current_query_options), sym_stream); if (is_streaming == Qtrue) { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0); } else { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); } if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { wrapper->active_thread = Qnil; rb_raise_mysql2_error(wrapper); } /* no data and no error, so query was not a SELECT */ return Qnil; } // Duplicate the options hash and put the copy in the Result object current = rb_hash_dup(rb_ivar_get(self, intern_current_query_options)); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); rb_mysql_set_server_query_flags(wrapper->client, resultObj); return resultObj; } #ifndef _WIN32 struct async_query_args { int fd; VALUE self; }; static VALUE disconnect_and_raise(VALUE self, VALUE error) { GET_CLIENT(self); wrapper->active_thread = Qnil; /* Invalidate the MySQL socket to prevent further communication. * The GC will come along later and call mysql_close to free it. */ if (CONNECTED(wrapper)) { if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n"); close(wrapper->client->net.fd); } wrapper->client->net.fd = -1; } rb_exc_raise(error); } static VALUE do_query(void *args) { struct async_query_args *async_args = args; struct timeval tv; struct timeval *tvp; long int sec; int retval; VALUE read_timeout; read_timeout = rb_ivar_get(async_args->self, intern_read_timeout); tvp = NULL; if (!NIL_P(read_timeout)) { Check_Type(read_timeout, T_FIXNUM); tvp = &tv; sec = FIX2INT(read_timeout); /* TODO: support partial seconds? also, this check is here for sanity, we also check up in Ruby */ if (sec >= 0) { tvp->tv_sec = sec; } else { rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec); } tvp->tv_usec = 0; } for(;;) { retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp); if (retval == 0) { rb_raise(cMysql2TimeoutError, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout)); } if (retval < 0) { rb_sys_fail(0); } if (retval > 0) { break; } } return Qnil; } #endif static VALUE disconnect_and_mark_inactive(VALUE self) { GET_CLIENT(self); /* Check if execution terminated while result was still being read. */ if (!NIL_P(wrapper->active_thread)) { if (CONNECTED(wrapper)) { /* Invalidate the MySQL socket to prevent further communication. */ #ifndef _WIN32 if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n"); close(wrapper->client->net.fd); } #else close(wrapper->client->net.fd); #endif wrapper->client->net.fd = -1; } /* Skip mysql client check performed before command execution. */ wrapper->client->status = MYSQL_STATUS_READY; wrapper->active_thread = Qnil; } return Qnil; } void rb_mysql_client_set_active_thread(VALUE self) { VALUE thread_current = rb_thread_current(); GET_CLIENT(self); // see if this connection is still waiting on a result from a previous query if (NIL_P(wrapper->active_thread)) { // mark this connection active wrapper->active_thread = thread_current; } else if (wrapper->active_thread == thread_current) { rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result"); } else { VALUE inspect = rb_inspect(wrapper->active_thread); const char *thr = StringValueCStr(inspect); rb_raise(cMysql2Error, "This connection is in use by: %s", thr); } } /* call-seq: * client.abandon_results! * * When using MULTI_STATEMENTS support, calling this will throw * away any unprocessed results as fast as it can in order to * put the connection back into a state where queries can be issued * again. */ static VALUE rb_mysql_client_abandon_results(VALUE self) { MYSQL_RES *result; int ret; GET_CLIENT(self); while (mysql_more_results(wrapper->client) == 1) { ret = mysql_next_result(wrapper->client); if (ret > 0) { rb_raise_mysql2_error(wrapper); } result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); if (result != NULL) { mysql_free_result(result); } } return Qnil; } /* call-seq: * client.query(sql, options = {}) * * Query the database with +sql+, with optional +options+. For the possible * options, see default_query_options on the Mysql2::Client class. */ static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) { #ifndef _WIN32 struct async_query_args async_args; #endif struct nogvl_send_query_args args; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); args.mysql = wrapper->client; (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); rb_ivar_set(self, intern_current_query_options, current); Check_Type(sql, T_STRING); /* ensure the string is in the encoding the connection is expecting */ args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding)); args.sql_ptr = RSTRING_PTR(args.sql); args.sql_len = RSTRING_LEN(args.sql); args.wrapper = wrapper; rb_mysql_client_set_active_thread(self); #ifndef _WIN32 rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0); if (rb_hash_aref(current, sym_async) == Qtrue) { return Qnil; } else { async_args.fd = wrapper->client->net.fd; async_args.self = self; rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0); return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self); } #else do_send_query(&args); /* this will just block until the result is ready */ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self); #endif } /* call-seq: * client.escape(string) * * Escape +string+ so that it may be used in a SQL statement. */ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) { unsigned char *newStr; VALUE rb_str; unsigned long newLen, oldLen; rb_encoding *default_internal_enc; rb_encoding *conn_enc; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); Check_Type(str, T_STRING); default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); /* ensure the string is in the encoding the connection is expecting */ str = rb_str_export_to_enc(str, conn_enc); oldLen = RSTRING_LEN(str); newStr = xmalloc(oldLen*2+1); newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen); if (newLen == oldLen) { /* no need to return a new ruby string if nothing changed */ if (default_internal_enc) { str = rb_str_export_to_enc(str, default_internal_enc); } xfree(newStr); return str; } else { rb_str = rb_str_new((const char*)newStr, newLen); rb_enc_associate(rb_str, conn_enc); if (default_internal_enc) { rb_str = rb_str_export_to_enc(rb_str, default_internal_enc); } xfree(newStr); return rb_str; } } static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { int result; const void *retval = NULL; unsigned int intval = 0; const char * charval = NULL; my_bool boolval; GET_CLIENT(self); REQUIRE_NOT_CONNECTED(wrapper); if (NIL_P(value)) return Qfalse; switch(opt) { case MYSQL_OPT_CONNECT_TIMEOUT: intval = NUM2UINT(value); retval = &intval; break; case MYSQL_OPT_READ_TIMEOUT: intval = NUM2UINT(value); retval = &intval; break; case MYSQL_OPT_WRITE_TIMEOUT: intval = NUM2UINT(value); retval = &intval; break; case MYSQL_OPT_LOCAL_INFILE: intval = (value == Qfalse ? 0 : 1); retval = &intval; break; case MYSQL_OPT_RECONNECT: boolval = (value == Qfalse ? 0 : 1); retval = &boolval; break; #ifdef MYSQL_SECURE_AUTH case MYSQL_SECURE_AUTH: boolval = (value == Qfalse ? 0 : 1); retval = &boolval; break; #endif case MYSQL_READ_DEFAULT_FILE: charval = (const char *)StringValueCStr(value); retval = charval; break; case MYSQL_READ_DEFAULT_GROUP: charval = (const char *)StringValueCStr(value); retval = charval; break; case MYSQL_INIT_COMMAND: charval = (const char *)StringValueCStr(value); retval = charval; break; case MYSQL_DEFAULT_AUTH: charval = (const char *)StringValueCStr(value); retval = charval; break; #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN case MYSQL_ENABLE_CLEARTEXT_PLUGIN: boolval = (value == Qfalse ? 0 : 1); retval = &boolval; break; #endif default: return Qfalse; } result = mysql_options(wrapper->client, opt, retval); /* Zero means success */ if (result != 0) { rb_warn("%s\n", mysql_error(wrapper->client)); } else { /* Special case for options that are stored in the wrapper struct */ switch (opt) { case MYSQL_OPT_RECONNECT: wrapper->reconnect_enabled = boolval; break; case MYSQL_OPT_CONNECT_TIMEOUT: wrapper->connect_timeout = intval; break; } } return (result == 0) ? Qtrue : Qfalse; } /* call-seq: * client.info * * Returns a string that represents the client library version. */ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) { VALUE version_info, version, header_version; version_info = rb_hash_new(); version = rb_str_new2(mysql_get_client_info()); header_version = rb_str_new2(MYSQL_LINK_VERSION); rb_enc_associate(version, rb_usascii_encoding()); rb_enc_associate(header_version, rb_usascii_encoding()); rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version())); rb_hash_aset(version_info, sym_version, version); rb_hash_aset(version_info, sym_header_version, header_version); return version_info; } /* call-seq: * client.server_info * * Returns a string that represents the server version number */ static VALUE rb_mysql_client_server_info(VALUE self) { VALUE version, server_info; rb_encoding *default_internal_enc; rb_encoding *conn_enc; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); version = rb_hash_new(); rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client))); server_info = rb_str_new2(mysql_get_server_info(wrapper->client)); rb_enc_associate(server_info, conn_enc); if (default_internal_enc) { server_info = rb_str_export_to_enc(server_info, default_internal_enc); } rb_hash_aset(version, sym_version, server_info); return version; } /* call-seq: * client.socket * * Return the file descriptor number for this client. */ #ifndef _WIN32 static VALUE rb_mysql_client_socket(VALUE self) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); return INT2NUM(wrapper->client->net.fd); } #else static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) { rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows"); } #endif /* call-seq: * client.last_id * * Returns the value generated for an AUTO_INCREMENT column by the previous INSERT or UPDATE * statement. */ static VALUE rb_mysql_client_last_id(VALUE self) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); return ULL2NUM(mysql_insert_id(wrapper->client)); } /* call-seq: * client.affected_rows * * returns the number of rows changed, deleted, or inserted by the last statement * if it was an UPDATE, DELETE, or INSERT. */ static VALUE rb_mysql_client_affected_rows(VALUE self) { my_ulonglong retVal; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); retVal = mysql_affected_rows(wrapper->client); if (retVal == (my_ulonglong)-1) { rb_raise_mysql2_error(wrapper); } return ULL2NUM(retVal); } /* call-seq: * client.thread_id * * Returns the thread ID of the current connection. */ static VALUE rb_mysql_client_thread_id(VALUE self) { unsigned long retVal; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); retVal = mysql_thread_id(wrapper->client); return ULL2NUM(retVal); } static void *nogvl_select_db(void *ptr) { struct nogvl_select_db_args *args = ptr; if (mysql_select_db(args->mysql, args->db) == 0) return (void *)Qtrue; else return (void *)Qfalse; } /* call-seq: * client.select_db(name) * * Causes the database specified by +name+ to become the default (current) * database on the connection specified by mysql. */ static VALUE rb_mysql_client_select_db(VALUE self, VALUE db) { struct nogvl_select_db_args args; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); args.mysql = wrapper->client; args.db = StringValueCStr(db); if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse) rb_raise_mysql2_error(wrapper); return db; } static void *nogvl_ping(void *ptr) { MYSQL *client = ptr; return (void *)(mysql_ping(client) == 0 ? Qtrue : Qfalse); } /* call-seq: * client.ping * * Checks whether the connection to the server is working. If the connection * has gone down and auto-reconnect is enabled an attempt to reconnect is made. * If the connection is down and auto-reconnect is disabled, ping returns an * error. */ static VALUE rb_mysql_client_ping(VALUE self) { GET_CLIENT(self); if (!CONNECTED(wrapper)) { return Qfalse; } else { return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0); } } /* call-seq: * client.set_server_option(value) * * Enables or disables an option for the connection. * Read https://dev.mysql.com/doc/refman/5.7/en/mysql-set-server-option.html * for more information. */ static VALUE rb_mysql_client_set_server_option(VALUE self, VALUE value) { GET_CLIENT(self); if (mysql_set_server_option(wrapper->client, NUM2INT(value)) == 0) { return Qtrue; } else { return Qfalse; } } /* call-seq: * client.more_results? * * Returns true or false if there are more results to process. */ static VALUE rb_mysql_client_more_results(VALUE self) { GET_CLIENT(self); if (mysql_more_results(wrapper->client) == 0) return Qfalse; else return Qtrue; } /* call-seq: * client.next_result * * Fetch the next result set from the server. * Returns nothing. */ static VALUE rb_mysql_client_next_result(VALUE self) { int ret; GET_CLIENT(self); ret = mysql_next_result(wrapper->client); if (ret > 0) { rb_raise_mysql2_error(wrapper); return Qfalse; } else if (ret == 0) { return Qtrue; } else { return Qfalse; } } /* call-seq: * client.store_result * * Return the next result object from a query which * yielded multiple result sets. */ static VALUE rb_mysql_client_store_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; VALUE current; GET_CLIENT(self); result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { rb_raise_mysql2_error(wrapper); } /* no data and no error, so query was not a SELECT */ return Qnil; } // Duplicate the options hash and put the copy in the Result object current = rb_hash_dup(rb_ivar_get(self, intern_current_query_options)); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); return resultObj; } /* call-seq: * client.encoding * * Returns the encoding set on the client. */ static VALUE rb_mysql_client_encoding(VALUE self) { GET_CLIENT(self); return wrapper->encoding; } /* call-seq: * client.automatic_close? * * @return [Boolean] */ static VALUE get_automatic_close(VALUE self) { GET_CLIENT(self); return wrapper->automatic_close ? Qtrue : Qfalse; } /* call-seq: * client.automatic_close = false * * Set this to +false+ to leave the connection open after it is garbage * collected. To avoid "Aborted connection" errors on the server, explicitly * call +close+ when the connection is no longer needed. * * @see http://dev.mysql.com/doc/en/communication-errors.html */ static VALUE set_automatic_close(VALUE self, VALUE value) { GET_CLIENT(self); if (RTEST(value)) { wrapper->automatic_close = 1; } else { #ifndef _WIN32 wrapper->automatic_close = 0; #else rb_warn("Connections are always closed by garbage collector on Windows"); #endif } return value; } /* call-seq: * client.reconnect = true * * Enable or disable the automatic reconnect behavior of libmysql. * Read http://dev.mysql.com/doc/refman/5.5/en/auto-reconnect.html * for more information. */ static VALUE set_reconnect(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_OPT_RECONNECT, value); } static VALUE set_local_infile(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_OPT_LOCAL_INFILE, value); } static VALUE set_connect_timeout(VALUE self, VALUE value) { long int sec; Check_Type(value, T_FIXNUM); sec = FIX2INT(value); if (sec < 0) { rb_raise(cMysql2Error, "connect_timeout must be a positive integer, you passed %ld", sec); } return _mysql_client_options(self, MYSQL_OPT_CONNECT_TIMEOUT, value); } static VALUE set_read_timeout(VALUE self, VALUE value) { long int sec; Check_Type(value, T_FIXNUM); sec = FIX2INT(value); if (sec < 0) { rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec); } /* Set the instance variable here even though _mysql_client_options might not succeed, because the timeout is used in other ways elsewhere */ rb_ivar_set(self, intern_read_timeout, value); return _mysql_client_options(self, MYSQL_OPT_READ_TIMEOUT, value); } static VALUE set_write_timeout(VALUE self, VALUE value) { long int sec; Check_Type(value, T_FIXNUM); sec = FIX2INT(value); if (sec < 0) { rb_raise(cMysql2Error, "write_timeout must be a positive integer, you passed %ld", sec); } return _mysql_client_options(self, MYSQL_OPT_WRITE_TIMEOUT, value); } static VALUE set_charset_name(VALUE self, VALUE value) { char *charset_name; const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb; rb_encoding *enc; VALUE rb_enc; GET_CLIENT(self); Check_Type(value, T_STRING); charset_name = RSTRING_PTR(value); mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value)); if (mysql2rb == NULL || mysql2rb->rb_name == NULL) { VALUE inspect = rb_inspect(value); rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect)); } else { enc = rb_enc_find(mysql2rb->rb_name); rb_enc = rb_enc_from_encoding(enc); wrapper->encoding = rb_enc; } if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) { /* TODO: warning - unable to set charset */ rb_warn("%s\n", mysql_error(wrapper->client)); } return value; } static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) { GET_CLIENT(self); mysql_ssl_set(wrapper->client, NIL_P(key) ? NULL : StringValueCStr(key), NIL_P(cert) ? NULL : StringValueCStr(cert), NIL_P(ca) ? NULL : StringValueCStr(ca), NIL_P(capath) ? NULL : StringValueCStr(capath), NIL_P(cipher) ? NULL : StringValueCStr(cipher)); return self; } static VALUE set_secure_auth(VALUE self, VALUE value) { /* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */ #ifdef MYSQL_SECURE_AUTH return _mysql_client_options(self, MYSQL_SECURE_AUTH, value); #else return Qfalse; #endif } static VALUE set_read_default_file(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_READ_DEFAULT_FILE, value); } static VALUE set_read_default_group(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_READ_DEFAULT_GROUP, value); } static VALUE set_init_command(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_INIT_COMMAND, value); } static VALUE set_default_auth(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_DEFAULT_AUTH, value); } static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) { #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value); #else rb_raise(cMysql2Error, "enable-cleartext-plugin is not available, you may need a newer MySQL client library"); #endif } static VALUE initialize_ext(VALUE self) { GET_CLIENT(self); if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) { /* TODO: warning - not enough memory? */ rb_raise_mysql2_error(wrapper); } wrapper->initialized = 1; return self; } /* call-seq: client.prepare # => Mysql2::Statement * * Create a new prepared statement. */ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); return rb_mysql_stmt_new(self, sql); } void init_mysql2_client() { #ifdef _WIN32 /* verify the libmysql we're about to use was the version we were built against https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */ int i; int dots = 0; const char *lib = mysql_get_client_info(); for (i = 0; lib[i] != 0 && MYSQL_LINK_VERSION[i] != 0; i++) { if (lib[i] == '.') { dots++; /* we only compare MAJOR and MINOR */ if (dots == 2) break; } if (lib[i] != MYSQL_LINK_VERSION[i]) { rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib); } } #endif /* Initializing mysql library, so different threads could call Client.new */ /* without race condition in the library */ if (mysql_library_init(0, NULL, NULL) != 0) { rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library"); } #if 0 mMysql2 = rb_define_module("Mysql2"); Teach RDoc about Mysql2 constant. #endif cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject); rb_define_alloc_func(cMysql2Client, allocate); rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1); rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0); rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0); rb_define_method(cMysql2Client, "closed?", rb_mysql_client_closed, 0); rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0); rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1); rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0); rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0); rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0); rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0); rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0); rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1); rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0); rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0); rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1); rb_define_method(cMysql2Client, "set_server_option", rb_mysql_client_set_server_option, 1); rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0); rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0); rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0); rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0); rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1); rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1); rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0); rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0); rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0); rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1); rb_define_private_method(cMysql2Client, "write_timeout=", set_write_timeout, 1); rb_define_private_method(cMysql2Client, "local_infile=", set_local_infile, 1); rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1); rb_define_private_method(cMysql2Client, "secure_auth=", set_secure_auth, 1); rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1); rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1); rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1); rb_define_private_method(cMysql2Client, "default_auth=", set_default_auth, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1); rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); rb_define_private_method(cMysql2Client, "connect", rb_mysql_connect, 8); rb_define_private_method(cMysql2Client, "_query", rb_mysql_query, 2); sym_id = ID2SYM(rb_intern("id")); sym_version = ID2SYM(rb_intern("version")); sym_header_version = ID2SYM(rb_intern("header_version")); sym_async = ID2SYM(rb_intern("async")); sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); sym_array = ID2SYM(rb_intern("array")); sym_stream = ID2SYM(rb_intern("stream")); sym_no_good_index_used = ID2SYM(rb_intern("no_good_index_used")); sym_no_index_used = ID2SYM(rb_intern("no_index_used")); sym_query_was_slow = ID2SYM(rb_intern("query_was_slow")); intern_brackets = rb_intern("[]"); intern_merge = rb_intern("merge"); intern_merge_bang = rb_intern("merge!"); intern_new_with_args = rb_intern("new_with_args"); intern_current_query_options = rb_intern("@current_query_options"); intern_read_timeout = rb_intern("@read_timeout"); #ifdef CLIENT_LONG_PASSWORD rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), LONG2NUM(CLIENT_LONG_PASSWORD)); #else /* HACK because MariaDB 10.2 no longer defines this constant, * but we're using it in our default connection flags. */ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), INT2NUM(0)); #endif #ifdef CLIENT_FOUND_ROWS rb_const_set(cMysql2Client, rb_intern("FOUND_ROWS"), LONG2NUM(CLIENT_FOUND_ROWS)); #endif #ifdef CLIENT_LONG_FLAG rb_const_set(cMysql2Client, rb_intern("LONG_FLAG"), LONG2NUM(CLIENT_LONG_FLAG)); #endif #ifdef CLIENT_CONNECT_WITH_DB rb_const_set(cMysql2Client, rb_intern("CONNECT_WITH_DB"), LONG2NUM(CLIENT_CONNECT_WITH_DB)); #endif #ifdef CLIENT_NO_SCHEMA rb_const_set(cMysql2Client, rb_intern("NO_SCHEMA"), LONG2NUM(CLIENT_NO_SCHEMA)); #endif #ifdef CLIENT_COMPRESS rb_const_set(cMysql2Client, rb_intern("COMPRESS"), LONG2NUM(CLIENT_COMPRESS)); #endif #ifdef CLIENT_ODBC rb_const_set(cMysql2Client, rb_intern("ODBC"), LONG2NUM(CLIENT_ODBC)); #endif #ifdef CLIENT_LOCAL_FILES rb_const_set(cMysql2Client, rb_intern("LOCAL_FILES"), LONG2NUM(CLIENT_LOCAL_FILES)); #endif #ifdef CLIENT_IGNORE_SPACE rb_const_set(cMysql2Client, rb_intern("IGNORE_SPACE"), LONG2NUM(CLIENT_IGNORE_SPACE)); #endif #ifdef CLIENT_PROTOCOL_41 rb_const_set(cMysql2Client, rb_intern("PROTOCOL_41"), LONG2NUM(CLIENT_PROTOCOL_41)); #endif #ifdef CLIENT_INTERACTIVE rb_const_set(cMysql2Client, rb_intern("INTERACTIVE"), LONG2NUM(CLIENT_INTERACTIVE)); #endif #ifdef CLIENT_SSL rb_const_set(cMysql2Client, rb_intern("SSL"), LONG2NUM(CLIENT_SSL)); #endif #ifdef CLIENT_IGNORE_SIGPIPE rb_const_set(cMysql2Client, rb_intern("IGNORE_SIGPIPE"), LONG2NUM(CLIENT_IGNORE_SIGPIPE)); #endif #ifdef CLIENT_TRANSACTIONS rb_const_set(cMysql2Client, rb_intern("TRANSACTIONS"), LONG2NUM(CLIENT_TRANSACTIONS)); #endif #ifdef CLIENT_RESERVED rb_const_set(cMysql2Client, rb_intern("RESERVED"), LONG2NUM(CLIENT_RESERVED)); #endif #ifdef CLIENT_SECURE_CONNECTION rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(CLIENT_SECURE_CONNECTION)); #else /* HACK because MySQL5.7 no longer defines this constant, * but we're using it in our default connection flags. */ rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0)); #endif #ifdef HAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_ON rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_ON"), LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_ON)); #endif #ifdef HAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_OFF rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_OFF"), LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_OFF)); #endif #ifdef CLIENT_MULTI_STATEMENTS rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"), LONG2NUM(CLIENT_MULTI_STATEMENTS)); #endif #ifdef CLIENT_PS_MULTI_RESULTS rb_const_set(cMysql2Client, rb_intern("PS_MULTI_RESULTS"), LONG2NUM(CLIENT_PS_MULTI_RESULTS)); #endif #ifdef CLIENT_SSL_VERIFY_SERVER_CERT rb_const_set(cMysql2Client, rb_intern("SSL_VERIFY_SERVER_CERT"), LONG2NUM(CLIENT_SSL_VERIFY_SERVER_CERT)); #endif #ifdef CLIENT_REMEMBER_OPTIONS rb_const_set(cMysql2Client, rb_intern("REMEMBER_OPTIONS"), LONG2NUM(CLIENT_REMEMBER_OPTIONS)); #endif #ifdef CLIENT_ALL_FLAGS rb_const_set(cMysql2Client, rb_intern("ALL_FLAGS"), LONG2NUM(CLIENT_ALL_FLAGS)); #endif #ifdef CLIENT_BASIC_FLAGS rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"), LONG2NUM(CLIENT_BASIC_FLAGS)); #endif #ifdef CLIENT_CONNECT_ATTRS rb_const_set(cMysql2Client, rb_intern("CONNECT_ATTRS"), LONG2NUM(CLIENT_CONNECT_ATTRS)); #else /* HACK because MySQL 5.5 and earlier don't define this constant, * but we're using it in our default connection flags. */ rb_const_set(cMysql2Client, rb_intern("CONNECT_ATTRS"), INT2NUM(0)); #endif #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY)); #elif defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) // MySQL 5.7.3 - 5.7.10 rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED)); #endif #ifndef HAVE_CONST_SSL_MODE_DISABLED rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0)); #endif #ifndef HAVE_CONST_SSL_MODE_PREFERRED rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(0)); #endif #ifndef HAVE_CONST_SSL_MODE_REQUIRED rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(0)); #endif #ifndef HAVE_CONST_SSL_MODE_VERIFY_CA rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(0)); #endif #ifndef HAVE_CONST_SSL_MODE_VERIFY_IDENTITY rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0)); #endif } #define flag_to_bool(f) ((client->server_status & f) ? Qtrue : Qfalse) void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result) { VALUE server_flags = rb_hash_new(); #ifdef HAVE_CONST_SERVER_QUERY_NO_GOOD_INDEX_USED rb_hash_aset(server_flags, sym_no_good_index_used, flag_to_bool(SERVER_QUERY_NO_GOOD_INDEX_USED)); #else rb_hash_aset(server_flags, sym_no_good_index_used, Qnil); #endif #ifdef HAVE_CONST_SERVER_QUERY_NO_INDEX_USED rb_hash_aset(server_flags, sym_no_index_used, flag_to_bool(SERVER_QUERY_NO_INDEX_USED)); #else rb_hash_aset(server_flags, sym_no_index_used, Qnil); #endif #ifdef HAVE_CONST_SERVER_QUERY_WAS_SLOW rb_hash_aset(server_flags, sym_query_was_slow, flag_to_bool(SERVER_QUERY_WAS_SLOW)); #else rb_hash_aset(server_flags, sym_query_was_slow, Qnil); #endif rb_iv_set(result, "@server_flags", server_flags); } mysql2-0.5.3/ext/mysql2/client.h000066400000000000000000000012531356743356300164440ustar00rootroot00000000000000#ifndef MYSQL2_CLIENT_H #define MYSQL2_CLIENT_H typedef struct { VALUE encoding; VALUE active_thread; /* rb_thread_current() or Qnil */ long server_version; int reconnect_enabled; unsigned int connect_timeout; int active; int automatic_close; int initialized; int refcount; int closed; MYSQL *client; } mysql_client_wrapper; void rb_mysql_client_set_active_thread(VALUE self); void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result); #define GET_CLIENT(self) \ mysql_client_wrapper *wrapper; \ Data_Get_Struct(self, mysql_client_wrapper, wrapper); void init_mysql2_client(void); void decr_mysql2_client(mysql_client_wrapper *wrapper); #endif mysql2-0.5.3/ext/mysql2/extconf.rb000066400000000000000000000243651356743356300170210ustar00rootroot00000000000000require 'mkmf' require 'English' def asplode(lib) if RUBY_PLATFORM =~ /mingw|mswin/ abort "-----\n#{lib} is missing. Check your installation of MySQL or Connector/C, and try again.\n-----" elsif RUBY_PLATFORM =~ /darwin/ abort "-----\n#{lib} is missing. You may need to 'brew install mysql' or 'port install mysql', and try again.\n-----" else abort "-----\n#{lib} is missing. You may need to 'sudo apt-get install libmariadb-dev', 'sudo apt-get install libmysqlclient-dev' or 'sudo yum install mysql-devel', and try again.\n-----" end end def add_ssl_defines(header) all_modes_found = %w[SSL_MODE_DISABLED SSL_MODE_PREFERRED SSL_MODE_REQUIRED SSL_MODE_VERIFY_CA SSL_MODE_VERIFY_IDENTITY].inject(true) do |m, ssl_mode| m && have_const(ssl_mode, header) end $CFLAGS << ' -DFULL_SSL_MODE_SUPPORT' if all_modes_found # if we only have ssl toggle (--ssl,--disable-ssl) from 5.7.3 to 5.7.10 has_no_support = all_modes_found ? false : !have_const('MYSQL_OPT_SSL_ENFORCE', header) $CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if has_no_support end # 2.1+ have_func('rb_absint_size') have_func('rb_absint_singlebit_p') # Missing in RBX (https://github.com/rubinius/rubinius/issues/3771) have_func('rb_wait_for_single_fd') # borrowed from mysqlplus # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb dirs = ENV.fetch('PATH').split(File::PATH_SEPARATOR) + %w[ /opt /opt/local /opt/local/mysql /opt/local/lib/mysql5* /usr /usr/mysql /usr/local /usr/local/mysql /usr/local/mysql-* /usr/local/lib/mysql5* /usr/local/opt/mysql5* ].map { |dir| dir << '/bin' } # For those without HOMEBREW_ROOT in PATH dirs << "#{ENV['HOMEBREW_ROOT']}/bin" if ENV['HOMEBREW_ROOT'] GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}".freeze # If the user has provided a --with-mysql-dir argument, we must respect it or fail. inc, lib = dir_config('mysql') if inc && lib # Ruby versions below 2.0 on Unix and below 2.1 on Windows # do not properly search for lib directories, and must be corrected: # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 unless lib && lib[-3, 3] == 'lib' @libdir_basename = 'lib' inc, lib = dir_config('mysql') end abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" rpath_dir = lib have_library('mysqlclient') elsif (mc = (with_config('mysql-config') || Dir[GLOB].first)) # If the user has provided a --with-mysql-config argument, we must respect it or fail. # If the user gave --with-mysql-config with no argument means we should try to find it. mc = Dir[GLOB].first if mc == true abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exist?(mc) abort "-----\nCannot execute mysql_config at #{mc}\n-----" unless File.executable?(mc) warn "-----\nUsing mysql_config at #{mc}\n-----" ver = `#{mc} --version`.chomp.to_f includes = `#{mc} --include`.chomp abort unless $CHILD_STATUS.success? libs = `#{mc} --libs_r`.chomp # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r). libs = `#{mc} --libs`.chomp if ver >= 5.5 || libs.empty? abort unless $CHILD_STATUS.success? $INCFLAGS += ' ' + includes $libs = libs + " " + $libs rpath_dir = libs else _, usr_local_lib = dir_config('mysql', '/usr/local') asplode("mysql client") unless find_library('mysqlclient', nil, usr_local_lib, "#{usr_local_lib}/mysql") rpath_dir = usr_local_lib end if have_header('mysql.h') prefix = nil elsif have_header('mysql/mysql.h') prefix = 'mysql' else asplode 'mysql.h' end %w[errmsg.h].each do |h| header = [prefix, h].compact.join('/') asplode h unless have_header header end mysql_h = [prefix, 'mysql.h'].compact.join('/') add_ssl_defines(mysql_h) have_struct_member('MYSQL', 'net.vio', mysql_h) have_struct_member('MYSQL', 'net.pvio', mysql_h) # These constants are actually enums, so they cannot be detected by #ifdef in C code. have_const('MYSQL_ENABLE_CLEARTEXT_PLUGIN', mysql_h) have_const('SERVER_QUERY_NO_GOOD_INDEX_USED', mysql_h) have_const('SERVER_QUERY_NO_INDEX_USED', mysql_h) have_const('SERVER_QUERY_WAS_SLOW', mysql_h) have_const('MYSQL_OPTION_MULTI_STATEMENTS_ON', mysql_h) have_const('MYSQL_OPTION_MULTI_STATEMENTS_OFF', mysql_h) # my_bool is replaced by C99 bool in MySQL 8.0, but we want # to retain compatibility with the typedef in earlier MySQLs. have_type('my_bool', mysql_h) # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. wishlist = [ '-Weverything', '-Wno-bad-function-cast', # rb_thread_call_without_gvl returns void * that we cast to VALUE '-Wno-conditional-uninitialized', # false positive in client.c '-Wno-covered-switch-default', # result.c -- enum_field_types (when fully covered, e.g. mysql 5.5) '-Wno-declaration-after-statement', # GET_CLIENT followed by GET_STATEMENT in statement.c '-Wno-disabled-macro-expansion', # rubby :( '-Wno-documentation-unknown-command', # rubby :( '-Wno-missing-field-initializers', # gperf generates bad code '-Wno-missing-variable-declarations', # missing symbols due to ruby native ext initialization '-Wno-padded', # mysql :( '-Wno-reserved-id-macro', # rubby :( '-Wno-sign-conversion', # gperf generates bad code '-Wno-static-in-inline', # gperf generates bad code '-Wno-switch-enum', # result.c -- enum_field_types (when not fully covered, e.g. mysql 5.6+) '-Wno-undef', # rubinius :( '-Wno-unreachable-code', # rubby :( '-Wno-used-but-marked-unused', # rubby :( ] usable_flags = wishlist.select do |flag| try_link('int main() {return 0;}', "-Werror #{flag}") end $CFLAGS << ' ' << usable_flags.join(' ') enabled_sanitizers = disabled_sanitizers = [] # Specify a commna-separated list of sanitizers, or try them all by default sanitizers = with_config('sanitize') case sanitizers when true # Try them all, turn on whatever we can enabled_sanitizers = %w[address cfi integer memory thread undefined].select do |s| try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") end abort "-----\nCould not enable any sanitizers!\n-----" if enabled_sanitizers.empty? when String # Figure out which sanitizers are supported enabled_sanitizers, disabled_sanitizers = sanitizers.split(',').partition do |s| try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") end end unless disabled_sanitizers.empty? abort "-----\nCould not enable requested sanitizers: #{disabled_sanitizers.join(',')}\n-----" end unless enabled_sanitizers.empty? warn "-----\nEnabling sanitizers: #{enabled_sanitizers.join(',')}\n-----" enabled_sanitizers.each do |s| # address sanitizer requires runtime support if s == 'address' # rubocop:disable Style/IfUnlessModifier have_library('asan') || $LDFLAGS << ' -fsanitize=address' end $CFLAGS << " -fsanitize=#{s}" end # Options for line numbers in backtraces $CFLAGS << ' -g -fno-omit-frame-pointer' end if RUBY_PLATFORM =~ /mswin|mingw/ && !defined?(RubyInstaller) # Build libmysql.a interface link library require 'rake' # Build libmysql.a interface link library # Use rake to rebuild only if these files change deffile = File.expand_path('../../../support/libmysql.def', __FILE__) libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) file 'libmysql.a' => [deffile, libfile] do when_writing 'building libmysql.a' do # Ruby kindly shows us where dllwrap is, but that tool does more than we want. # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool') sh dlltool, '--kill-at', '--dllname', 'libmysql.dll', '--output-lib', 'libmysql.a', '--input-def', deffile, libfile end end Rake::Task['libmysql.a'].invoke $LOCAL_LIBS << ' ' << 'libmysql.a' # Make sure the generated interface library works (if cross-compiling, trust without verifying) unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ abort "-----\nCannot find libmysql.a\n-----" unless have_library('libmysql') abort "-----\nCannot link to libmysql.a (my_init)\n-----" unless have_func('my_init') end # Vendor libmysql.dll vendordir = File.expand_path('../../../vendor/', __FILE__) directory vendordir vendordll = File.join(vendordir, 'libmysql.dll') dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) file vendordll => [dllfile, vendordir] do when_writing 'copying libmysql.dll' do cp dllfile, vendordll end end # Copy libmysql.dll to the local vendor directory by default if arg_config('--no-vendor-libmysql') # Fine, don't. puts "--no-vendor-libmysql" else # Default: arg_config('--vendor-libmysql') # Let's do it! Rake::Task[vendordll].invoke end else case explicit_rpath = with_config('mysql-rpath') when true abort "-----\nOption --with-mysql-rpath must have an argument\n-----" when false warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" when String # The user gave us a value so use it rpath_flags = " -Wl,-rpath,#{explicit_rpath}" warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" $LDFLAGS << rpath_flags else if (libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2]) rpath_flags = " -Wl,-rpath,#{libdir}" if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. warn "-----\nSetting rpath to #{libdir}\n-----" $LDFLAGS << rpath_flags else if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? # If we got here because try_link failed, warn the user warn "-----\nDon't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load\n-----" end # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. warn "-----\nSetting libpath to #{libdir}\n-----" $LIBPATH << libdir unless $LIBPATH.include?(libdir) end end end end create_makefile('mysql2/mysql2') mysql2-0.5.3/ext/mysql2/infile.c000066400000000000000000000060701356743356300164310ustar00rootroot00000000000000#include #include #ifndef _MSC_VER #include #endif #include #define ERROR_LEN 1024 typedef struct { int fd; char *filename; char error[ERROR_LEN]; mysql_client_wrapper *wrapper; } mysql2_local_infile_data; /* MySQL calls this function when a user begins a LOAD DATA LOCAL INFILE query. * * Allocate a data struct and pass it back through the data pointer. * * Returns: * 0 on success * 1 on error */ static int mysql2_local_infile_init(void **ptr, const char *filename, void *userdata) { mysql2_local_infile_data *data = malloc(sizeof(mysql2_local_infile_data)); if (!data) return 1; *ptr = data; data->error[0] = 0; data->wrapper = userdata; data->filename = strdup(filename); if (!data->filename) { snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename); return 1; } data->fd = open(filename, O_RDONLY); if (data->fd < 0) { snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename); return 1; } return 0; } /* MySQL calls this function to read data from the local file. * * Returns: * > 0 number of bytes read * == 0 end of file * < 0 error */ static int mysql2_local_infile_read(void *ptr, char *buf, unsigned int buf_len) { int count; mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr; count = (int)read(data->fd, buf, buf_len); if (count < 0) { snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), data->filename); } return count; } /* MySQL calls this function when we're done with the LOCAL INFILE query. * * ptr will be null if the init function failed. */ static void mysql2_local_infile_end(void *ptr) { mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr; if (data) { if (data->fd >= 0) close(data->fd); if (data->filename) free(data->filename); free(data); } } /* MySQL calls this function if any of the functions above returned an error. * * This function is called even if init failed, with whatever ptr value * init has set, regardless of the return value of the init function. * * Returns: * Error message number (see http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html) */ static int mysql2_local_infile_error(void *ptr, char *error_msg, unsigned int error_msg_len) { mysql2_local_infile_data *data = (mysql2_local_infile_data *) ptr; if (data) { snprintf(error_msg, error_msg_len, "%s", data->error); return CR_UNKNOWN_ERROR; } snprintf(error_msg, error_msg_len, "Out of memory"); return CR_OUT_OF_MEMORY; } /* Tell MySQL Client to use our own local_infile functions. * This is both due to bugginess in the default handlers, * and to improve the Rubyness of the handlers here. */ void mysql2_set_local_infile(MYSQL *mysql, void *userdata) { mysql_set_local_infile_handler(mysql, mysql2_local_infile_init, mysql2_local_infile_read, mysql2_local_infile_end, mysql2_local_infile_error, userdata); } mysql2-0.5.3/ext/mysql2/infile.h000066400000000000000000000000741356743356300164340ustar00rootroot00000000000000void mysql2_set_local_infile(MYSQL *mysql, void *userdata); mysql2-0.5.3/ext/mysql2/mysql2_ext.c000066400000000000000000000006101356743356300172640ustar00rootroot00000000000000#include VALUE mMysql2, cMysql2Error, cMysql2TimeoutError; /* Ruby Extension initializer */ void Init_mysql2() { mMysql2 = rb_define_module("Mysql2"); cMysql2Error = rb_const_get(mMysql2, rb_intern("Error")); cMysql2TimeoutError = rb_const_get(cMysql2Error, rb_intern("TimeoutError")); init_mysql2_client(); init_mysql2_result(); init_mysql2_statement(); } mysql2-0.5.3/ext/mysql2/mysql2_ext.h000066400000000000000000000020411356743356300172710ustar00rootroot00000000000000#ifndef MYSQL2_EXT #define MYSQL2_EXT void Init_mysql2(void); /* tell rbx not to use it's caching compat layer by doing this we're making a promise to RBX that we'll never modify the pointers we get back from RSTRING_PTR */ #define RSTRING_NOT_MODIFIED #include #ifdef HAVE_MYSQL_H #include #include #else #include #include #endif #include #include #if defined(__GNUC__) && (__GNUC__ >= 3) #define RB_MYSQL_NORETURN __attribute__ ((noreturn)) #define RB_MYSQL_UNUSED __attribute__ ((unused)) #else #define RB_MYSQL_NORETURN #define RB_MYSQL_UNUSED #endif /* MySQL 8.0 replaces my_bool with C99 bool. Earlier versions of MySQL had * a typedef to char. Gem users reported failures on big endian systems when * using C99 bool types with older MySQLs due to mismatched behavior. */ #ifndef HAVE_TYPE_MY_BOOL #include typedef bool my_bool; #endif #include #include #include #include #endif mysql2-0.5.3/ext/mysql2/mysql_enc_name_to_ruby.h000066400000000000000000000134461356743356300217320ustar00rootroot00000000000000/* C code produced by gperf version 3.0.4 */ /* Command-line: gperf */ /* Computed positions: -k'1,3,$' */ #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) /* The character set is not based on ISO-646. */ error "gperf generated tables don't work with this execution character set. Please report a bug to ." #endif struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; }; /* maximum key range = 71, duplicates = 0 */ #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static unsigned int mysql2_mysql_enc_name_to_rb_hash (str, len) register const char *str; register unsigned int len; { static const unsigned char asso_values[] = { 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 15, 5, 0, 74, 5, 25, 40, 10, 20, 50, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 40, 5, 0, 15, 10, 0, 0, 0, 5, 74, 0, 25, 5, 0, 5, 74, 74, 20, 5, 5, 0, 74, 45, 74, 0, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74 }; return len + asso_values[(unsigned char)str[2]] + asso_values[(unsigned char)str[0]] + asso_values[(unsigned char)str[len - 1]]; } #ifdef __GNUC__ __inline #if defined __GNUC_STDC_INLINE__ || defined __GNUC_GNU_INLINE__ __attribute__ ((__gnu_inline__)) #endif #endif const struct mysql2_mysql_enc_name_to_rb_map * mysql2_mysql_enc_name_to_rb (str, len) register const char *str; register unsigned int len; { enum { TOTAL_KEYWORDS = 41, MIN_WORD_LENGTH = 3, MAX_WORD_LENGTH = 8, MIN_HASH_VALUE = 3, MAX_HASH_VALUE = 73 }; static const struct mysql2_mysql_enc_name_to_rb_map wordlist[] = { {""}, {""}, {""}, {"gbk", "GBK"}, {""}, {"utf32", "UTF-32"}, {"gb2312", "GB2312"}, {"keybcs2", NULL}, {""}, {"ucs2", "UTF-16BE"}, {"koi8u", "KOI8-R"}, {"binary", "ASCII-8BIT"}, {"utf8mb4", "UTF-8"}, {"macroman", "macRoman"}, {"ujis", "eucJP-ms"}, {"greek", "ISO-8859-7"}, {"cp1251", "Windows-1251"}, {"utf16le", "UTF-16LE"}, {""}, {"sjis", "Shift_JIS"}, {"macce", "macCentEuro"}, {"cp1257", "Windows-1257"}, {"eucjpms", "eucJP-ms"}, {""}, {"utf8", "UTF-8"}, {"cp852", "CP852"}, {"cp1250", "Windows-1250"}, {"gb18030", "GB18030"}, {""}, {"swe7", NULL}, {"koi8r", "KOI8-R"}, {"tis620", "TIS-620"}, {"geostd8", NULL}, {""}, {"big5", "Big5"}, {"euckr", "EUC-KR"}, {"latin2", "ISO-8859-2"}, {""}, {""}, {"dec8", NULL}, {"cp850", "CP850"}, {"latin1", "ISO-8859-1"}, {""}, {"hp8", NULL}, {""}, {"utf16", "UTF-16"}, {"latin7", "ISO-8859-13"}, {""}, {""}, {""}, {"ascii", "US-ASCII"}, {"cp1256", "Windows-1256"}, {""}, {""}, {""}, {"cp932", "Windows-31J"}, {"hebrew", "ISO-8859-8"}, {""}, {""}, {""}, {""}, {"latin5", "ISO-8859-9"}, {""}, {""}, {""}, {"cp866", "IBM866"}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {"armscii8", NULL} }; if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { register int key = mysql2_mysql_enc_name_to_rb_hash (str, len); if (key <= MAX_HASH_VALUE && key >= 0) { register const char *s = wordlist[key].name; if (*str == *s && !strcmp (str + 1, s + 1)) return &wordlist[key]; } } return 0; } mysql2-0.5.3/ext/mysql2/mysql_enc_to_ruby.h000066400000000000000000000072211356743356300207240ustar00rootroot00000000000000static const char *mysql2_mysql_enc_to_rb[] = { "Big5", "ISO-8859-2", NULL, "CP850", "ISO-8859-1", NULL, "KOI8-R", "ISO-8859-1", "ISO-8859-2", NULL, "US-ASCII", "eucJP-ms", "Shift_JIS", "Windows-1251", "ISO-8859-1", "ISO-8859-8", NULL, "TIS-620", "EUC-KR", "ISO-8859-13", "ISO-8859-2", "KOI8-R", "Windows-1251", "GB2312", "ISO-8859-7", "Windows-1250", "ISO-8859-2", "GBK", "Windows-1257", "ISO-8859-9", "ISO-8859-1", NULL, "UTF-8", "Windows-1250", "UTF-16BE", "IBM866", NULL, "macCentEuro", "macRoman", "CP852", "ISO-8859-13", "ISO-8859-13", "macCentEuro", "Windows-1250", "UTF-8", "UTF-8", "ISO-8859-1", "ISO-8859-1", "ISO-8859-1", "Windows-1251", "Windows-1251", "Windows-1251", "macRoman", "UTF-16", "UTF-16", "UTF-16LE", "Windows-1256", "Windows-1257", "Windows-1257", "UTF-32", "UTF-32", "UTF-16LE", "ASCII-8BIT", NULL, "US-ASCII", "Windows-1250", "Windows-1256", "IBM866", NULL, "ISO-8859-7", "ISO-8859-8", NULL, NULL, "KOI8-R", "KOI8-R", "UTF-8", "ISO-8859-2", "ISO-8859-9", "ISO-8859-13", "CP850", "CP852", NULL, "UTF-8", "Big5", "EUC-KR", "GB2312", "GBK", "Shift_JIS", "TIS-620", "UTF-16BE", "eucJP-ms", NULL, NULL, "ISO-8859-1", "Windows-31J", "Windows-31J", "eucJP-ms", "eucJP-ms", "Windows-1250", NULL, "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", NULL, NULL, NULL, "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", NULL, NULL, NULL, NULL, NULL, NULL, NULL, "UTF-16BE", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", NULL, NULL, NULL, NULL, NULL, NULL, NULL, "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "GB18030", "GB18030", "GB18030", NULL, NULL, NULL, NULL, "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", NULL, "UTF-8", "UTF-8", "UTF-8", NULL, "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", NULL, "UTF-8", "UTF-8", "UTF-8", NULL, "UTF-8", NULL, NULL, "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8" }; mysql2-0.5.3/ext/mysql2/result.c000066400000000000000000001076421356743356300165100ustar00rootroot00000000000000#include #include "mysql_enc_to_ruby.h" #define MYSQL2_CHARSETNR_SIZE (sizeof(mysql2_mysql_enc_to_rb)/sizeof(mysql2_mysql_enc_to_rb[0])) static rb_encoding *binaryEncoding; /* on 64bit platforms we can handle dates way outside 2038-01-19T03:14:07 * * (9999*31557600) + (12*2592000) + (31*86400) + (11*3600) + (59*60) + 59 */ #define MYSQL2_MAX_TIME 315578267999ULL /* 0000-1-1 00:00:00 UTC * * (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0 */ #define MYSQL2_MIN_TIME 2678400ULL #define GET_RESULT(self) \ mysql2_result_wrapper *wrapper; \ Data_Get_Struct(self, mysql2_result_wrapper, wrapper); typedef struct { int symbolizeKeys; int asArray; int castBool; int cacheRows; int cast; int streaming; ID db_timezone; ID app_timezone; int block_given; /* boolean */ } result_each_args; extern VALUE mMysql2, cMysql2Client, cMysql2Error; static VALUE cMysql2Result, cDateTime, cDate; static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset; static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset, intern_merge, intern_BigDecimal, intern_query_options; static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name; /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */ static void rb_mysql_result_mark(void * wrapper) { mysql2_result_wrapper * w = wrapper; if (w) { rb_gc_mark(w->fields); rb_gc_mark(w->rows); rb_gc_mark(w->encoding); rb_gc_mark(w->client); rb_gc_mark(w->statement); } } /* this may be called manually or during GC */ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { if (!wrapper) return; if (wrapper->resultFreed != 1) { if (wrapper->stmt_wrapper) { if (!wrapper->stmt_wrapper->closed) { mysql_stmt_free_result(wrapper->stmt_wrapper->stmt); /* MySQL BUG? If the statement handle was previously used, and so * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed, * MySQL still thinks the result set buffer is available and will prefetch the * first result in mysql_stmt_execute. This will corrupt or crash the program. * By setting bind_result_done back to 0, we make MySQL think that a result set * has never been bound to this statement handle before to prevent the prefetch. */ wrapper->stmt_wrapper->stmt->bind_result_done = 0; } if (wrapper->statement != Qnil) { decr_mysql2_stmt(wrapper->stmt_wrapper); } if (wrapper->result_buffers) { unsigned int i; for (i = 0; i < wrapper->numberOfFields; i++) { if (wrapper->result_buffers[i].buffer) { xfree(wrapper->result_buffers[i].buffer); } } xfree(wrapper->result_buffers); xfree(wrapper->is_null); xfree(wrapper->error); xfree(wrapper->length); } /* Clue that the next statement execute will need to allocate a new result buffer. */ wrapper->result_buffers = NULL; } /* FIXME: this may call flush_use_result, which can hit the socket */ /* For prepared statements, wrapper->result is the result metadata */ mysql_free_result(wrapper->result); wrapper->resultFreed = 1; } } /* this is called during GC */ static void rb_mysql_result_free(void *ptr) { mysql2_result_wrapper *wrapper = ptr; rb_mysql_result_free_result(wrapper); // If the GC gets to client first it will be nil if (wrapper->client != Qnil) { decr_mysql2_client(wrapper->client_wrapper); } xfree(wrapper); } static VALUE rb_mysql_result_free_(VALUE self) { GET_RESULT(self); rb_mysql_result_free_result(wrapper); return Qnil; } /* * for small results, this won't hit the network, but there's no * reliable way for us to tell this so we'll always release the GVL * to be safe */ static void *nogvl_fetch_row(void *ptr) { MYSQL_RES *result = ptr; return mysql_fetch_row(result); } static void *nogvl_stmt_fetch(void *ptr) { MYSQL_STMT *stmt = ptr; uintptr_t r = mysql_stmt_fetch(stmt); return (void *)r; } static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) { VALUE rb_field; GET_RESULT(self); if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } rb_field = rb_ary_entry(wrapper->fields, idx); if (rb_field == Qnil) { MYSQL_FIELD *field = NULL; rb_encoding *default_internal_enc = rb_default_internal_encoding(); rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); field = mysql_fetch_field_direct(wrapper->result, idx); if (symbolize_keys) { rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding()); rb_field = ID2SYM(rb_field); } else { rb_field = rb_str_new(field->name, field->name_length); rb_enc_associate(rb_field, conn_enc); if (default_internal_enc) { rb_field = rb_str_export_to_enc(rb_field, default_internal_enc); } } rb_ary_store(wrapper->fields, idx, rb_field); } return rb_field; } static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) { /* if binary flag is set, respect its wishes */ if (field.flags & BINARY_FLAG && field.charsetnr == 63) { rb_enc_associate(val, binaryEncoding); } else if (!field.charsetnr) { /* MySQL 4.x may not provide an encoding, binary will get the bytes through */ rb_enc_associate(val, binaryEncoding); } else { /* lookup the encoding configured on this field */ const char *enc_name; int enc_index; enc_name = (field.charsetnr-1 < MYSQL2_CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL; if (enc_name != NULL) { /* use the field encoding we were able to match */ enc_index = rb_enc_find_index(enc_name); rb_enc_set_index(val, enc_index); } else { /* otherwise fall-back to the connection's encoding */ rb_enc_associate(val, conn_enc); } if (default_internal_enc) { val = rb_str_export_to_enc(val, default_internal_enc); } } return val; } /* Interpret microseconds digits left-aligned in fixed-width field. * e.g. 10.123 seconds means 10 seconds and 123000 microseconds, * because the microseconds are to the right of the decimal point. */ static unsigned int msec_char_to_uint(char *msec_char, size_t len) { size_t i; for (i = 0; i < (len - 1); i++) { if (msec_char[i] == '\0') { msec_char[i] = '0'; } } return (unsigned int)strtoul(msec_char, NULL, 10); } static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) { unsigned int i; GET_RESULT(self); if (wrapper->result_buffers != NULL) return; wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND)); wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long)); for (i = 0; i < wrapper->numberOfFields; i++) { wrapper->result_buffers[i].buffer_type = fields[i].type; // mysql type | C type switch(fields[i].type) { case MYSQL_TYPE_NULL: // NULL break; case MYSQL_TYPE_TINY: // signed char wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(signed char)); wrapper->result_buffers[i].buffer_length = sizeof(signed char); break; case MYSQL_TYPE_SHORT: // short int case MYSQL_TYPE_YEAR: // short int wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(short int)); wrapper->result_buffers[i].buffer_length = sizeof(short int); break; case MYSQL_TYPE_INT24: // int case MYSQL_TYPE_LONG: // int wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(int)); wrapper->result_buffers[i].buffer_length = sizeof(int); break; case MYSQL_TYPE_LONGLONG: // long long int wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(long long int)); wrapper->result_buffers[i].buffer_length = sizeof(long long int); break; case MYSQL_TYPE_FLOAT: // float case MYSQL_TYPE_DOUBLE: // double wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(double)); wrapper->result_buffers[i].buffer_length = sizeof(double); break; case MYSQL_TYPE_TIME: // MYSQL_TIME case MYSQL_TYPE_DATE: // MYSQL_TIME case MYSQL_TYPE_NEWDATE: // MYSQL_TIME case MYSQL_TYPE_DATETIME: // MYSQL_TIME case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME)); wrapper->result_buffers[i].buffer_length = sizeof(MYSQL_TIME); break; case MYSQL_TYPE_DECIMAL: // char[] case MYSQL_TYPE_NEWDECIMAL: // char[] case MYSQL_TYPE_STRING: // char[] case MYSQL_TYPE_VAR_STRING: // char[] case MYSQL_TYPE_VARCHAR: // char[] case MYSQL_TYPE_TINY_BLOB: // char[] case MYSQL_TYPE_BLOB: // char[] case MYSQL_TYPE_MEDIUM_BLOB: // char[] case MYSQL_TYPE_LONG_BLOB: // char[] case MYSQL_TYPE_BIT: // char[] case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] default: wrapper->result_buffers[i].buffer = xmalloc(fields[i].max_length); wrapper->result_buffers[i].buffer_length = fields[i].max_length; break; } wrapper->result_buffers[i].is_null = &wrapper->is_null[i]; wrapper->result_buffers[i].length = &wrapper->length[i]; wrapper->result_buffers[i].error = &wrapper->error[i]; wrapper->result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0); } } static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) { VALUE rowVal; unsigned int i = 0; rb_encoding *default_internal_enc; rb_encoding *conn_enc; GET_RESULT(self); default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } if (args->asArray) { rowVal = rb_ary_new2(wrapper->numberOfFields); } else { rowVal = rb_hash_new(); } if (wrapper->result_buffers == NULL) { rb_mysql_result_alloc_result_buffers(self, fields); } if (mysql_stmt_bind_result(wrapper->stmt_wrapper->stmt, wrapper->result_buffers)) { rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper); } { switch((uintptr_t)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt_wrapper->stmt, RUBY_UBF_IO, 0)) { case 0: /* success */ break; case 1: /* error */ rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper); case MYSQL_NO_DATA: /* no more row */ return Qnil; case MYSQL_DATA_TRUNCATED: rb_raise(cMysql2Error, "IMPLBUG: caught MYSQL_DATA_TRUNCATED. should not come here as buffer_length is set to fields[i].max_length."); } } for (i = 0; i < wrapper->numberOfFields; i++) { VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys); VALUE val = Qnil; MYSQL_TIME *ts; if (wrapper->is_null[i]) { val = Qnil; } else { const MYSQL_BIND* const result_buffer = &wrapper->result_buffers[i]; switch(result_buffer->buffer_type) { case MYSQL_TYPE_TINY: // signed char if (args->castBool && fields[i].length == 1) { val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse; break; } if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned char*)result_buffer->buffer)); } else { val = INT2NUM(*((signed char*)result_buffer->buffer)); } break; case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ if (args->castBool && fields[i].length == 1) { val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse; }else{ val = rb_str_new(result_buffer->buffer, *(result_buffer->length)); } break; case MYSQL_TYPE_SHORT: // short int case MYSQL_TYPE_YEAR: // short int if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned short int*)result_buffer->buffer)); } else { val = INT2NUM(*((short int*)result_buffer->buffer)); } break; case MYSQL_TYPE_INT24: // int case MYSQL_TYPE_LONG: // int if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned int*)result_buffer->buffer)); } else { val = INT2NUM(*((int*)result_buffer->buffer)); } break; case MYSQL_TYPE_LONGLONG: // long long int if (result_buffer->is_unsigned) { val = ULL2NUM(*((unsigned long long int*)result_buffer->buffer)); } else { val = LL2NUM(*((long long int*)result_buffer->buffer)); } break; case MYSQL_TYPE_FLOAT: // float val = rb_float_new((double)(*((float*)result_buffer->buffer))); break; case MYSQL_TYPE_DOUBLE: // double val = rb_float_new((double)(*((double*)result_buffer->buffer))); break; case MYSQL_TYPE_DATE: // MYSQL_TIME case MYSQL_TYPE_NEWDATE: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); break; case MYSQL_TYPE_TIME: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { // utc val = rb_funcall(val, intern_utc, 0); } } break; case MYSQL_TYPE_DATETIME: // MYSQL_TIME case MYSQL_TYPE_TIMESTAMP: { // MYSQL_TIME uint64_t seconds; ts = (MYSQL_TIME*)result_buffer->buffer; seconds = (ts->year*31557600ULL) + (ts->month*2592000ULL) + (ts->day*86400ULL) + (ts->hour*3600ULL) + (ts->minute*60ULL) + ts->second; if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead VALUE offset = INT2NUM(0); if (args->db_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); } val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), offset); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); val = rb_funcall(val, intern_new_offset, 1, offset); } else { // utc val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset); } } } else { val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { // utc val = rb_funcall(val, intern_utc, 0); } } } break; } case MYSQL_TYPE_DECIMAL: // char[] case MYSQL_TYPE_NEWDECIMAL: // char[] val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length))); break; case MYSQL_TYPE_STRING: // char[] case MYSQL_TYPE_VAR_STRING: // char[] case MYSQL_TYPE_VARCHAR: // char[] case MYSQL_TYPE_TINY_BLOB: // char[] case MYSQL_TYPE_BLOB: // char[] case MYSQL_TYPE_MEDIUM_BLOB: // char[] case MYSQL_TYPE_LONG_BLOB: // char[] case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] default: val = rb_str_new(result_buffer->buffer, *(result_buffer->length)); val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); break; } } if (args->asArray) { rb_ary_push(rowVal, val); } else { rb_hash_aset(rowVal, field, val); } } return rowVal; } static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) { VALUE rowVal; MYSQL_ROW row; unsigned int i = 0; unsigned long * fieldLengths; void * ptr; rb_encoding *default_internal_enc; rb_encoding *conn_enc; GET_RESULT(self); default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); ptr = wrapper->result; row = (MYSQL_ROW)rb_thread_call_without_gvl(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0); if (row == NULL) { return Qnil; } if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } if (args->asArray) { rowVal = rb_ary_new2(wrapper->numberOfFields); } else { rowVal = rb_hash_new(); } fieldLengths = mysql_fetch_lengths(wrapper->result); for (i = 0; i < wrapper->numberOfFields; i++) { VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys); if (row[i]) { VALUE val = Qnil; enum enum_field_types type = fields[i].type; if (!args->cast) { if (type == MYSQL_TYPE_NULL) { val = Qnil; } else { val = rb_str_new(row[i], fieldLengths[i]); val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); } } else { switch(type) { case MYSQL_TYPE_NULL: /* NULL-type field */ val = Qnil; break; case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ if (args->castBool && fields[i].length == 1) { val = *row[i] == 1 ? Qtrue : Qfalse; }else{ val = rb_str_new(row[i], fieldLengths[i]); } break; case MYSQL_TYPE_TINY: /* TINYINT field */ if (args->castBool && fields[i].length == 1) { val = *row[i] != '0' ? Qtrue : Qfalse; break; } case MYSQL_TYPE_SHORT: /* SMALLINT field */ case MYSQL_TYPE_LONG: /* INTEGER field */ case MYSQL_TYPE_INT24: /* MEDIUMINT field */ case MYSQL_TYPE_LONGLONG: /* BIGINT field */ case MYSQL_TYPE_YEAR: /* YEAR field */ val = rb_cstr2inum(row[i], 10); break; case MYSQL_TYPE_DECIMAL: /* DECIMAL or NUMERIC field */ case MYSQL_TYPE_NEWDECIMAL: /* Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up) */ if (fields[i].decimals == 0) { val = rb_cstr2inum(row[i], 10); } else if (strtod(row[i], NULL) == 0.000000){ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero); }else{ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(row[i], fieldLengths[i])); } break; case MYSQL_TYPE_FLOAT: /* FLOAT field */ case MYSQL_TYPE_DOUBLE: { /* DOUBLE or REAL field */ double column_to_double; column_to_double = strtod(row[i], NULL); if (column_to_double == 0.000000){ val = opt_float_zero; }else{ val = rb_float_new(column_to_double); } break; } case MYSQL_TYPE_TIME: { /* TIME field */ int tokens; unsigned int hour=0, min=0, sec=0, msec=0; char msec_char[7] = {'0','0','0','0','0','0','\0'}; tokens = sscanf(row[i], "%2u:%2u:%2u.%6s", &hour, &min, &sec, msec_char); if (tokens < 3) { val = Qnil; break; } msec = msec_char_to_uint(msec_char, sizeof(msec_char)); val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { /* utc */ val = rb_funcall(val, intern_utc, 0); } } break; } case MYSQL_TYPE_TIMESTAMP: /* TIMESTAMP field */ case MYSQL_TYPE_DATETIME: { /* DATETIME field */ int tokens; unsigned int year=0, month=0, day=0, hour=0, min=0, sec=0, msec=0; char msec_char[7] = {'0','0','0','0','0','0','\0'}; uint64_t seconds; tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6s", &year, &month, &day, &hour, &min, &sec, msec_char); if (tokens < 6) { /* msec might be empty */ val = Qnil; break; } seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec; if (seconds == 0) { val = Qnil; } else { if (month < 1 || day < 1) { rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]); val = Qnil; } else { if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */ VALUE offset = INT2NUM(0); if (args->db_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); } val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); val = rb_funcall(val, intern_new_offset, 1, offset); } else { /* utc */ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset); } } } else { msec = msec_char_to_uint(msec_char, sizeof(msec_char)); val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { /* utc */ val = rb_funcall(val, intern_utc, 0); } } } } } break; } case MYSQL_TYPE_DATE: /* DATE field */ case MYSQL_TYPE_NEWDATE: { /* Newer const used > 5.0 */ int tokens; unsigned int year=0, month=0, day=0; tokens = sscanf(row[i], "%4u-%2u-%2u", &year, &month, &day); if (tokens < 3) { val = Qnil; break; } if (year+month+day == 0) { val = Qnil; } else { if (month < 1 || day < 1) { rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]); val = Qnil; } else { val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day)); } } break; } case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_VARCHAR: case MYSQL_TYPE_STRING: /* CHAR or BINARY field */ case MYSQL_TYPE_SET: /* SET field */ case MYSQL_TYPE_ENUM: /* ENUM field */ case MYSQL_TYPE_GEOMETRY: /* Spatial fielda */ default: val = rb_str_new(row[i], fieldLengths[i]); val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); break; } } if (args->asArray) { rb_ary_push(rowVal, val); } else { rb_hash_aset(rowVal, field, val); } } else { if (args->asArray) { rb_ary_push(rowVal, Qnil); } else { rb_hash_aset(rowVal, field, Qnil); } } } return rowVal; } static VALUE rb_mysql_result_fetch_fields(VALUE self) { unsigned int i = 0; short int symbolizeKeys = 0; VALUE defaults; GET_RESULT(self); defaults = rb_ivar_get(self, intern_query_options); Check_Type(defaults, T_HASH); if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) { symbolizeKeys = 1; } if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } if ((my_ulonglong)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) { for (i=0; inumberOfFields; i++) { rb_mysql_result_fetch_field(self, i, symbolizeKeys); } } return wrapper->fields; } static VALUE rb_mysql_result_each_(VALUE self, VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args), const result_each_args *args) { unsigned long i; const char *errstr; MYSQL_FIELD *fields = NULL; GET_RESULT(self); if (wrapper->is_streaming) { /* When streaming, we will only yield rows, not return them. */ if (wrapper->rows == Qnil) { wrapper->rows = rb_ary_new(); } if (!wrapper->streamingComplete) { VALUE row; fields = mysql_fetch_fields(wrapper->result); do { row = fetch_row_func(self, fields, args); if (row != Qnil) { wrapper->numberOfRows++; if (args->block_given) { rb_yield(row); } } } while(row != Qnil); rb_mysql_result_free_result(wrapper); wrapper->streamingComplete = 1; // Check for errors, the connection might have gone out from under us // mysql_error returns an empty string if there is no error errstr = mysql_error(wrapper->client_wrapper->client); if (errstr[0]) { rb_raise(cMysql2Error, "%s", errstr); } } else { rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery)."); } } else { if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) { /* we've already read the entire dataset from the C result into our */ /* internal array. Lets hand that over to the user since it's ready to go */ for (i = 0; i < wrapper->numberOfRows; i++) { rb_yield(rb_ary_entry(wrapper->rows, i)); } } else { unsigned long rowsProcessed = 0; rowsProcessed = RARRAY_LEN(wrapper->rows); fields = mysql_fetch_fields(wrapper->result); for (i = 0; i < wrapper->numberOfRows; i++) { VALUE row; if (args->cacheRows && i < rowsProcessed) { row = rb_ary_entry(wrapper->rows, i); } else { row = fetch_row_func(self, fields, args); if (args->cacheRows) { rb_ary_store(wrapper->rows, i, row); } wrapper->lastRowProcessed++; } if (row == Qnil) { /* we don't need the mysql C dataset around anymore, peace it */ if (args->cacheRows) { rb_mysql_result_free_result(wrapper); } return Qnil; } if (args->block_given) { rb_yield(row); } } if (wrapper->lastRowProcessed == wrapper->numberOfRows && args->cacheRows) { /* we don't need the mysql C dataset around anymore, peace it */ rb_mysql_result_free_result(wrapper); } } } // FIXME return Enumerator instead? // return rb_ary_each(wrapper->rows); return wrapper->rows; } static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; VALUE defaults, opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); ID db_timezone, app_timezone, dbTz, appTz; int symbolizeKeys, asArray, castBool, cacheRows, cast; GET_RESULT(self); if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); } defaults = rb_ivar_get(self, intern_query_options); Check_Type(defaults, T_HASH); // A block can be passed to this method, but since we don't call the block directly from C, // we don't need to capture it into a variable here with the "&" scan arg. if (rb_scan_args(argc, argv, "01", &opts) == 1) { opts = rb_funcall(defaults, intern_merge, 1, opts); } else { opts = defaults; } symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys)); asArray = rb_hash_aref(opts, sym_as) == sym_array; castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans)); cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows)); cast = RTEST(rb_hash_aref(opts, sym_cast)); if (wrapper->is_streaming && cacheRows) { rb_warn(":cache_rows is ignored if :stream is true"); } if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) { rb_warn(":cache_rows is forced for prepared statements (if not streaming)"); cacheRows = 1; } if (wrapper->stmt_wrapper && !cast) { rb_warn(":cast is forced for prepared statements"); } dbTz = rb_hash_aref(opts, sym_database_timezone); if (dbTz == sym_local) { db_timezone = intern_local; } else if (dbTz == sym_utc) { db_timezone = intern_utc; } else { if (!NIL_P(dbTz)) { rb_warn(":database_timezone option must be :utc or :local - defaulting to :local"); } db_timezone = intern_local; } appTz = rb_hash_aref(opts, sym_application_timezone); if (appTz == sym_local) { app_timezone = intern_local; } else if (appTz == sym_utc) { app_timezone = intern_utc; } else { app_timezone = Qnil; } if (wrapper->rows == Qnil && !wrapper->is_streaming) { wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result); wrapper->rows = rb_ary_new2(wrapper->numberOfRows); } else if (wrapper->rows && !cacheRows) { if (wrapper->resultFreed) { rb_raise(cMysql2Error, "Result set has already been freed"); } mysql_data_seek(wrapper->result, 0); wrapper->lastRowProcessed = 0; wrapper->rows = rb_ary_new2(wrapper->numberOfRows); } // Backward compat args.symbolizeKeys = symbolizeKeys; args.asArray = asArray; args.castBool = castBool; args.cacheRows = cacheRows; args.cast = cast; args.db_timezone = db_timezone; args.app_timezone = app_timezone; args.block_given = rb_block_given_p(); if (wrapper->stmt_wrapper) { fetch_row_func = rb_mysql_result_fetch_row_stmt; } else { fetch_row_func = rb_mysql_result_fetch_row; } return rb_mysql_result_each_(self, fetch_row_func, &args); } static VALUE rb_mysql_result_count(VALUE self) { GET_RESULT(self); if (wrapper->is_streaming) { /* This is an unsigned long per result.h */ return ULONG2NUM(wrapper->numberOfRows); } if (wrapper->resultFreed) { /* Ruby arrays have platform signed long length */ return LONG2NUM(RARRAY_LEN(wrapper->rows)); } else { /* MySQL returns an unsigned 64-bit long here */ if (wrapper->stmt_wrapper) { return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt)); } else { return ULL2NUM(mysql_num_rows(wrapper->result)); } } } /* Mysql2::Result */ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) { VALUE obj; mysql2_result_wrapper * wrapper; obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper); wrapper->numberOfFields = 0; wrapper->numberOfRows = 0; wrapper->lastRowProcessed = 0; wrapper->resultFreed = 0; wrapper->result = r; wrapper->fields = Qnil; wrapper->rows = Qnil; wrapper->encoding = encoding; wrapper->streamingComplete = 0; wrapper->client = client; wrapper->client_wrapper = DATA_PTR(client); wrapper->client_wrapper->refcount++; wrapper->result_buffers = NULL; wrapper->is_null = NULL; wrapper->error = NULL; wrapper->length = NULL; /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */ wrapper->statement = statement; if (statement != Qnil) { wrapper->stmt_wrapper = DATA_PTR(statement); wrapper->stmt_wrapper->refcount++; } else { wrapper->stmt_wrapper = NULL; } rb_obj_call_init(obj, 0, NULL); rb_ivar_set(obj, intern_query_options, options); /* Options that cannot be changed in results.each(...) { |row| } * should be processed here. */ wrapper->is_streaming = (rb_hash_aref(options, sym_stream) == Qtrue ? 1 : 0); return obj; } void init_mysql2_result() { cDate = rb_const_get(rb_cObject, rb_intern("Date")); cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject); rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0); rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0); rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0); rb_define_alias(cMysql2Result, "size", "count"); intern_new = rb_intern("new"); intern_utc = rb_intern("utc"); intern_local = rb_intern("local"); intern_merge = rb_intern("merge"); intern_localtime = rb_intern("localtime"); intern_local_offset = rb_intern("local_offset"); intern_civil = rb_intern("civil"); intern_new_offset = rb_intern("new_offset"); intern_BigDecimal = rb_intern("BigDecimal"); intern_query_options = rb_intern("@query_options"); sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); sym_array = ID2SYM(rb_intern("array")); sym_local = ID2SYM(rb_intern("local")); sym_utc = ID2SYM(rb_intern("utc")); sym_cast_booleans = ID2SYM(rb_intern("cast_booleans")); sym_database_timezone = ID2SYM(rb_intern("database_timezone")); sym_application_timezone = ID2SYM(rb_intern("application_timezone")); sym_cache_rows = ID2SYM(rb_intern("cache_rows")); sym_cast = ID2SYM(rb_intern("cast")); sym_stream = ID2SYM(rb_intern("stream")); sym_name = ID2SYM(rb_intern("name")); opt_decimal_zero = rb_str_new2("0.0"); rb_global_variable(&opt_decimal_zero); /*never GC */ opt_float_zero = rb_float_new((double)0); rb_global_variable(&opt_float_zero); opt_time_year = INT2NUM(2000); opt_time_month = INT2NUM(1); opt_utc_offset = INT2NUM(0); binaryEncoding = rb_enc_find("binary"); } mysql2-0.5.3/ext/mysql2/result.h000066400000000000000000000013051356743356300165020ustar00rootroot00000000000000#ifndef MYSQL2_RESULT_H #define MYSQL2_RESULT_H void init_mysql2_result(void); VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement); typedef struct { VALUE fields; VALUE rows; VALUE client; VALUE encoding; VALUE statement; my_ulonglong numberOfFields; my_ulonglong numberOfRows; unsigned long lastRowProcessed; char is_streaming; char streamingComplete; char resultFreed; MYSQL_RES *result; mysql_stmt_wrapper *stmt_wrapper; mysql_client_wrapper *client_wrapper; /* statement result bind buffers */ MYSQL_BIND *result_buffers; my_bool *is_null; my_bool *error; unsigned long *length; } mysql2_result_wrapper; #endif mysql2-0.5.3/ext/mysql2/statement.c000066400000000000000000000470711356743356300171750ustar00rootroot00000000000000#include extern VALUE mMysql2, cMysql2Error; static VALUE cMysql2Statement, cBigDecimal, cDateTime, cDate; static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang; static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year, intern_query_options; #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \ if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \ if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); } static void rb_mysql_stmt_mark(void * ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; if (!stmt_wrapper) return; rb_gc_mark(stmt_wrapper->client); } static void *nogvl_stmt_close(void *ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; if (stmt_wrapper->stmt) { mysql_stmt_close(stmt_wrapper->stmt); stmt_wrapper->stmt = NULL; } return NULL; } static void rb_mysql_stmt_free(void *ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; decr_mysql2_stmt(stmt_wrapper); } void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) { stmt_wrapper->refcount--; if (stmt_wrapper->refcount == 0) { nogvl_stmt_close(stmt_wrapper); xfree(stmt_wrapper); } } void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) { VALUE e; GET_CLIENT(stmt_wrapper->client); VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt)); VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt)); rb_encoding *conn_enc; conn_enc = rb_to_encoding(wrapper->encoding); rb_encoding *default_internal_enc = rb_default_internal_encoding(); rb_enc_associate(rb_error_msg, conn_enc); rb_enc_associate(rb_sql_state, conn_enc); if (default_internal_enc) { rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc); rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); } e = rb_funcall(cMysql2Error, intern_new_with_args, 4, rb_error_msg, LONG2FIX(wrapper->server_version), UINT2NUM(mysql_stmt_errno(stmt_wrapper->stmt)), rb_sql_state); rb_exc_raise(e); } /* * used to pass all arguments to mysql_stmt_prepare while inside * nogvl_prepare_statement_args */ struct nogvl_prepare_statement_args { MYSQL_STMT *stmt; VALUE sql; const char *sql_ptr; unsigned long sql_len; }; static void *nogvl_prepare_statement(void *ptr) { struct nogvl_prepare_statement_args *args = ptr; if (mysql_stmt_prepare(args->stmt, args->sql_ptr, args->sql_len)) { return (void*)Qfalse; } else { return (void*)Qtrue; } } VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { mysql_stmt_wrapper *stmt_wrapper; VALUE rb_stmt; rb_encoding *conn_enc; Check_Type(sql, T_STRING); rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); { stmt_wrapper->client = rb_client; stmt_wrapper->refcount = 1; stmt_wrapper->closed = 0; stmt_wrapper->stmt = NULL; } // instantiate stmt { GET_CLIENT(rb_client); stmt_wrapper->stmt = mysql_stmt_init(wrapper->client); conn_enc = rb_to_encoding(wrapper->encoding); } if (stmt_wrapper->stmt == NULL) { rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory"); } // set STMT_ATTR_UPDATE_MAX_LENGTH attr { my_bool truth = 1; if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) { rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH"); } } // call mysql_stmt_prepare w/o gvl { struct nogvl_prepare_statement_args args; args.stmt = stmt_wrapper->stmt; // ensure the string is in the encoding the connection is expecting args.sql = rb_str_export_to_enc(sql, conn_enc); args.sql_ptr = RSTRING_PTR(sql); args.sql_len = RSTRING_LEN(sql); if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { rb_raise_mysql2_stmt_error(stmt_wrapper); } } return rb_stmt; } /* call-seq: stmt.param_count # => Numeric * * Returns the number of parameters the prepared statement expects. */ static VALUE rb_mysql_stmt_param_count(VALUE self) { GET_STATEMENT(self); return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt)); } /* call-seq: stmt.field_count # => Numeric * * Returns the number of fields the prepared statement returns. */ static VALUE rb_mysql_stmt_field_count(VALUE self) { GET_STATEMENT(self); return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt)); } static void *nogvl_stmt_execute(void *ptr) { MYSQL_STMT *stmt = ptr; if (mysql_stmt_execute(stmt)) { return (void*)Qfalse; } else { return (void*)Qtrue; } } static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) { unsigned long length; bind_buffer->buffer = RSTRING_PTR(string); length = RSTRING_LEN(string); bind_buffer->buffer_length = length; *length_buffer = length; bind_buffer->length = length_buffer; } /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means * the buffer is a Ruby string pointer and not our memory to manage. */ #define FREE_BINDS \ for (i = 0; i < bind_count; i++) { \ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \ xfree(bind_buffers[i].buffer); \ } \ } \ if (argc > 0) { \ xfree(bind_buffers); \ xfree(length_buffers); \ } /* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */ static int my_big2ll(VALUE bignum, LONG_LONG *ptr) { unsigned LONG_LONG num; size_t len; // rb_absint_size was added in 2.1.0. See: // https://github.com/ruby/ruby/commit/9fea875 #ifdef HAVE_RB_ABSINT_SIZE int nlz_bits = 0; len = rb_absint_size(bignum, &nlz_bits); #else len = RBIGNUM_LEN(bignum) * SIZEOF_BDIGITS; #endif if (len > sizeof(LONG_LONG)) goto overflow; if (RBIGNUM_POSITIVE_P(bignum)) { num = rb_big2ull(bignum); if (num > LLONG_MAX) goto overflow; *ptr = num; } else { if (len == 8 && #ifdef HAVE_RB_ABSINT_SIZE nlz_bits == 0 && #endif // rb_absint_singlebit_p was added in 2.1.0. See: // https://github.com/ruby/ruby/commit/e5ff9d5 #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P) /* Optimized to avoid object allocation for Ruby 2.1+ * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0` */ !rb_absint_singlebit_p(bignum) #else rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1) #endif ) { goto overflow; } *ptr = rb_big2ll(bignum); } return 0; overflow: return 1; } /* call-seq: stmt.execute * * Executes the current prepared statement, returns +result+. */ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) { MYSQL_BIND *bind_buffers = NULL; unsigned long *length_buffers = NULL; unsigned long bind_count; unsigned long i; MYSQL_STMT *stmt; MYSQL_RES *metadata; VALUE opts; VALUE current; VALUE resultObj; VALUE *params_enc = NULL; int is_streaming; rb_encoding *conn_enc; GET_STATEMENT(self); GET_CLIENT(stmt_wrapper->client); conn_enc = rb_to_encoding(wrapper->encoding); stmt = stmt_wrapper->stmt; bind_count = mysql_stmt_param_count(stmt); // Get count of ordinary arguments, and extract hash opts/keyword arguments // Use a local scope to avoid leaking the temporary count variable { int c = rb_scan_args(argc, argv, "*:", NULL, &opts); if (c != (long)bind_count) { rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c); } } // setup any bind variables in the query if (bind_count > 0) { // Scratch space for string encoding exports, allocate on the stack params_enc = alloca(sizeof(VALUE) * bind_count); bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND)); length_buffers = xcalloc(bind_count, sizeof(unsigned long)); for (i = 0; i < bind_count; i++) { bind_buffers[i].buffer = NULL; params_enc[i] = Qnil; switch (TYPE(argv[i])) { case T_NIL: bind_buffers[i].buffer_type = MYSQL_TYPE_NULL; break; case T_FIXNUM: #if SIZEOF_INT < SIZEOF_LONG bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; bind_buffers[i].buffer = xmalloc(sizeof(long long int)); *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]); #else bind_buffers[i].buffer_type = MYSQL_TYPE_LONG; bind_buffers[i].buffer = xmalloc(sizeof(int)); *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]); #endif break; case T_BIGNUM: { LONG_LONG num; if (my_big2ll(argv[i], &num) == 0) { bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; bind_buffers[i].buffer = xmalloc(sizeof(long long int)); *(LONG_LONG*)(bind_buffers[i].buffer) = num; } else { /* The bignum was larger than we can fit in LONG_LONG, send it as a string */ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL; params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc); set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); } } break; case T_FLOAT: bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE; bind_buffers[i].buffer = xmalloc(sizeof(double)); *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]); break; case T_STRING: bind_buffers[i].buffer_type = MYSQL_TYPE_STRING; params_enc[i] = argv[i]; params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); break; case T_TRUE: bind_buffers[i].buffer_type = MYSQL_TYPE_TINY; bind_buffers[i].buffer = xmalloc(sizeof(signed char)); *(signed char*)(bind_buffers[i].buffer) = 1; break; case T_FALSE: bind_buffers[i].buffer_type = MYSQL_TYPE_TINY; bind_buffers[i].buffer = xmalloc(sizeof(signed char)); *(signed char*)(bind_buffers[i].buffer) = 0; break; default: // TODO: what Ruby type should support MYSQL_TYPE_TIME if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) { MYSQL_TIME t; VALUE rb_time = argv[i]; bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME; bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); memset(&t, 0, sizeof(MYSQL_TIME)); t.neg = 0; if (CLASS_OF(argv[i]) == rb_cTime) { t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0)); } else if (CLASS_OF(argv[i]) == cDateTime) { t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000; } t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0)); t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0)); t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0)); t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0)); t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0)); t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0)); *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cDate) { MYSQL_TIME t; VALUE rb_time = argv[i]; bind_buffers[i].buffer_type = MYSQL_TYPE_DATE; bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); memset(&t, 0, sizeof(MYSQL_TIME)); t.second_part = 0; t.neg = 0; t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0)); t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0)); t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0)); *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cBigDecimal) { bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL; // DECIMAL are represented with the "string representation of the // original server-side value", see // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html // This should be independent of the locale used both on the server // and the client side. VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0); params_enc[i] = rb_val_as_string; params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); } break; } } // copies bind_buffers into internal storage if (mysql_stmt_bind_param(stmt, bind_buffers)) { FREE_BINDS; rb_raise_mysql2_stmt_error(stmt_wrapper); } } // Duplicate the options hash, merge! extra opts, put the copy into the Result object current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_query_options)); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); // Merge in hash opts/keyword arguments if (!NIL_P(opts)) { rb_funcall(current, intern_merge_bang, 1, opts); } is_streaming = (Qtrue == rb_hash_aref(current, sym_stream)); // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no // Ruby API calls are allowed so that GC is not invoked. If the connection is // in results-streaming-mode for Statement A, and in the middle Statement B // gets garbage collected, a message will be sent to the server notifying it // to release Statement B, resulting in the following error: // Commands out of sync; you can't run this command now // // In streaming mode, statement execute must return a cursor because we // cannot prevent other Statement objects from being garbage collected // between fetches of each row of the result set. The following error // occurs if cursor mode is not set: // Row retrieval was canceled by mysql_stmt_close if (is_streaming) { unsigned long type = CURSOR_TYPE_READ_ONLY; if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) { FREE_BINDS; rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY"); } } if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) { FREE_BINDS; rb_raise_mysql2_stmt_error(stmt_wrapper); } FREE_BINDS; metadata = mysql_stmt_result_metadata(stmt); if (metadata == NULL) { if (mysql_stmt_errno(stmt) != 0) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. wrapper->active_thread = Qnil; rb_raise_mysql2_stmt_error(stmt_wrapper); } // no data and no error, so query was not a SELECT return Qnil; } if (!is_streaming) { // recieve the whole result set from the server if (mysql_stmt_store_result(stmt)) { mysql_free_result(metadata); rb_raise_mysql2_stmt_error(stmt_wrapper); } wrapper->active_thread = Qnil; } resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self); rb_mysql_set_server_query_flags(wrapper->client, resultObj); if (!is_streaming) { // cache all result rb_funcall(resultObj, intern_each, 0); } return resultObj; } /* call-seq: stmt.fields # => array * * Returns a list of fields that will be returned by this statement. */ static VALUE rb_mysql_stmt_fields(VALUE self) { MYSQL_FIELD *fields; MYSQL_RES *metadata; unsigned int field_count; unsigned int i; VALUE field_list; MYSQL_STMT* stmt; rb_encoding *default_internal_enc, *conn_enc; GET_STATEMENT(self); GET_CLIENT(stmt_wrapper->client); stmt = stmt_wrapper->stmt; default_internal_enc = rb_default_internal_encoding(); { GET_CLIENT(stmt_wrapper->client); conn_enc = rb_to_encoding(wrapper->encoding); } metadata = mysql_stmt_result_metadata(stmt); if (metadata == NULL) { if (mysql_stmt_errno(stmt) != 0) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. wrapper->active_thread = Qnil; rb_raise_mysql2_stmt_error(stmt_wrapper); } // no data and no error, so query was not a SELECT return Qnil; } fields = mysql_fetch_fields(metadata); field_count = mysql_stmt_field_count(stmt); field_list = rb_ary_new2((long)field_count); for (i = 0; i < field_count; i++) { VALUE rb_field; rb_field = rb_str_new(fields[i].name, fields[i].name_length); rb_enc_associate(rb_field, conn_enc); if (default_internal_enc) { rb_field = rb_str_export_to_enc(rb_field, default_internal_enc); } rb_ary_store(field_list, (long)i, rb_field); } mysql_free_result(metadata); return field_list; } /* call-seq: * stmt.last_id * * Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE. */ static VALUE rb_mysql_stmt_last_id(VALUE self) { GET_STATEMENT(self); return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt)); } /* call-seq: * stmt.affected_rows * * Returns the number of rows changed, deleted, or inserted. */ static VALUE rb_mysql_stmt_affected_rows(VALUE self) { my_ulonglong affected; GET_STATEMENT(self); affected = mysql_stmt_affected_rows(stmt_wrapper->stmt); if (affected == (my_ulonglong)-1) { rb_raise_mysql2_stmt_error(stmt_wrapper); } return ULL2NUM(affected); } /* call-seq: * stmt.close * * Explicitly closing this will free up server resources immediately rather * than waiting for the garbage collector. Useful if you're managing your * own prepared statement cache. */ static VALUE rb_mysql_stmt_close(VALUE self) { GET_STATEMENT(self); stmt_wrapper->closed = 1; rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0); return Qnil; } void init_mysql2_statement() { cDate = rb_const_get(rb_cObject, rb_intern("Date")); cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal")); cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0); rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0); rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1); rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0); rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0); rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0); rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0); sym_stream = ID2SYM(rb_intern("stream")); intern_new_with_args = rb_intern("new_with_args"); intern_each = rb_intern("each"); intern_sec_fraction = rb_intern("sec_fraction"); intern_usec = rb_intern("usec"); intern_sec = rb_intern("sec"); intern_min = rb_intern("min"); intern_hour = rb_intern("hour"); intern_day = rb_intern("day"); intern_month = rb_intern("month"); intern_year = rb_intern("year"); intern_to_s = rb_intern("to_s"); intern_merge_bang = rb_intern("merge!"); intern_query_options = rb_intern("@query_options"); } mysql2-0.5.3/ext/mysql2/statement.h000066400000000000000000000006171356743356300171750ustar00rootroot00000000000000#ifndef MYSQL2_STATEMENT_H #define MYSQL2_STATEMENT_H typedef struct { VALUE client; MYSQL_STMT *stmt; int refcount; int closed; } mysql_stmt_wrapper; void init_mysql2_statement(void); void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper); VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) RB_MYSQL_NORETURN; #endif mysql2-0.5.3/ext/mysql2/wait_for_single_fd.h000066400000000000000000000016301356743356300210110ustar00rootroot00000000000000/* * backwards compatibility for Rubinius. See * https://github.com/rubinius/rubinius/issues/3771. * * Ruby 1.9.3 provides this API which allows the use of ppoll() on Linux * to minimize select() and malloc() overhead on high-numbered FDs. */ #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD # include #else # define RB_WAITFD_IN 0x001 # define RB_WAITFD_PRI 0x002 # define RB_WAITFD_OUT 0x004 static int my_wait_for_single_fd(int fd, int events, struct timeval *tvp) { fd_set fdset; fd_set *rfds = NULL; fd_set *wfds = NULL; fd_set *efds = NULL; FD_ZERO(&fdset); FD_SET(fd, &fdset); if (events & RB_WAITFD_IN) rfds = &fdset; if (events & RB_WAITFD_OUT) wfds = &fdset; if (events & RB_WAITFD_PRI) efds = &fdset; return rb_thread_select(fd + 1, rfds, wfds, efds, tvp); } #define rb_wait_for_single_fd(fd,events,tvp) \ my_wait_for_single_fd((fd),(events),(tvp)) #endif mysql2-0.5.3/lib/000077500000000000000000000000001356743356300135335ustar00rootroot00000000000000mysql2-0.5.3/lib/mysql2.rb000066400000000000000000000055341356743356300153160ustar00rootroot00000000000000require 'date' require 'bigdecimal' # Load libmysql.dll before requiring mysql2/mysql2.so # This gives a chance to be flexible about the load path # Or to bomb out with a clear error message instead of a linker crash if RUBY_PLATFORM =~ /mswin|mingw/ dll_path = if ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] # If this environment variable is set, it overrides any other paths # The user is advised to use backslashes not forward slashes ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))) # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)) elsif defined?(RubyInstaller) # RubyInstaller-2.4+ native build doesn't need DLL preloading else # This will use default / system library paths 'libmysql.dll' end if dll_path require 'fiddle' kernel32 = Fiddle.dlopen 'kernel32' load_library = Fiddle::Function.new( kernel32['LoadLibraryW'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT, ) if load_library.call(dll_path.encode('utf-16le')).zero? abort "Failed to load libmysql.dll from #{dll_path}" end end end require 'mysql2/version' unless defined? Mysql2::VERSION require 'mysql2/error' require 'mysql2/mysql2' require 'mysql2/result' require 'mysql2/client' require 'mysql2/field' require 'mysql2/statement' # = Mysql2 # # A modern, simple and very fast Mysql library for Ruby - binding to libmysql module Mysql2 end if defined?(ActiveRecord::VERSION::STRING) && ActiveRecord::VERSION::STRING < "3.1" begin require 'active_record/connection_adapters/mysql2_adapter' rescue LoadError warn "============= WARNING FROM mysql2 =============" warn "This version of mysql2 (#{Mysql2::VERSION}) doesn't ship with the ActiveRecord adapter." warn "In Rails version 3.1.0 and up, the mysql2 ActiveRecord adapter is included with rails." 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." warn "============= END WARNING FROM mysql2 =============" end end # For holding utility methods module Mysql2 module Util # # Rekey a string-keyed hash with equivalent symbols. # def self.key_hash_as_symbols(hash) return nil unless hash Hash[hash.map { |k, v| [k.to_sym, v] }] end # # In Mysql2::Client#query and Mysql2::Statement#execute, # Thread#handle_interrupt is used to prevent Timeout#timeout # from interrupting query execution. # # Timeout::ExitException was removed in Ruby 2.3.0, 2.2.3, and 2.1.8, # but is present in earlier 2.1.x and 2.2.x, so we provide a shim. # require 'timeout' TIMEOUT_ERROR_CLASS = if defined?(::Timeout::ExitException) ::Timeout::ExitException else ::Timeout::Error end end end mysql2-0.5.3/lib/mysql2/000077500000000000000000000000001356743356300147625ustar00rootroot00000000000000mysql2-0.5.3/lib/mysql2/client.rb000066400000000000000000000141101356743356300165620ustar00rootroot00000000000000module Mysql2 class Client attr_reader :query_options, :read_timeout def self.default_query_options @default_query_options ||= { as: :hash, # the type of object you want each row back as; also supports :array (an array of values) async: false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result cast_booleans: false, # cast tinyint(1) fields as true/false in ruby symbolize_keys: false, # return field names as symbols instead of strings database_timezone: :local, # timezone Mysql2 will assume datetime objects are stored in application_timezone: nil, # timezone Mysql2 will convert to before handing the object back to the caller cache_rows: true, # tells Mysql2 to use its internal row cache for results connect_flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | CONNECT_ATTRS, cast: true, default_file: nil, default_group: nil, } end def initialize(opts = {}) raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash opts = Mysql2::Util.key_hash_as_symbols(opts) @read_timeout = nil @query_options = self.class.default_query_options.dup @query_options.merge! opts initialize_ext # Set default connect_timeout to avoid unlimited retries from signal interruption opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) # TODO: stricter validation rather than silent massaging %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth].each do |key| next unless opts.key?(key) case key when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation when :connect_timeout, :read_timeout, :write_timeout send(:"#{key}=", Integer(opts[key])) unless opts[key].nil? else send(:"#{key}=", opts[key]) end end # force the encoding to utf8 self.charset_name = opts[:encoding] || 'utf8' ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher) ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify) self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode] flags = case opts[:flags] when Array parse_flags_array(opts[:flags], @query_options[:connect_flags]) when String parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags]) when Integer @query_options[:connect_flags] | opts[:flags] else @query_options[:connect_flags] end # SSL verify is a connection flag rather than a mysql_ssl_set option flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) } warn "============= WARNING FROM mysql2 =============" warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future." warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options." warn "============= END WARNING FROM mysql2 =========" end user = opts[:username] || opts[:user] pass = opts[:password] || opts[:pass] host = opts[:host] || opts[:hostname] port = opts[:port] database = opts[:database] || opts[:dbname] || opts[:db] socket = opts[:socket] || opts[:sock] # Correct the data types before passing these values down to the C level user = user.to_s unless user.nil? pass = pass.to_s unless pass.nil? host = host.to_s unless host.nil? port = port.to_i unless port.nil? database = database.to_s unless database.nil? socket = socket.to_s unless socket.nil? conn_attrs = parse_connect_attrs(opts[:connect_attrs]) connect user, pass, host, port, database, socket, flags, conn_attrs end def parse_ssl_mode(mode) m = mode.to_s.upcase if m.start_with?('SSL_MODE_') return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m) else x = 'SSL_MODE_' + m return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x) end warn "Unknown MySQL ssl_mode flag: #{mode}" end def parse_flags_array(flags, initial = 0) flags.reduce(initial) do |memo, f| fneg = f.start_with?('-') ? f[1..-1] : nil if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg) memo & ~ Mysql2::Client.const_get(fneg) elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f) memo | Mysql2::Client.const_get(f) else warn "Unknown MySQL connection flag: '#{f}'" memo end end end # Set default program_name in performance_schema.session_connect_attrs # and performance_schema.session_account_connect_attrs def parse_connect_attrs(conn_attrs) return {} if Mysql2::Client::CONNECT_ATTRS.zero? conn_attrs ||= {} conn_attrs[:program_name] ||= $PROGRAM_NAME conn_attrs.each_with_object({}) do |(key, value), hash| hash[key.to_s] = value.to_s end end def query(sql, options = {}) Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do _query(sql, @query_options.merge(options)) end end def query_info info = query_info_string return {} unless info info_hash = {} info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i } info_hash end def info self.class.info end class << self private def local_offset ::Time.local(2010).utc_offset.to_r / 86400 end end end end mysql2-0.5.3/lib/mysql2/console.rb000066400000000000000000000001711356743356300167500ustar00rootroot00000000000000# Loaded by script/console. Land helpers here. Pry.config.prompt = lambda do |context, *| "[mysql2] #{context}> " end mysql2-0.5.3/lib/mysql2/em.rb000066400000000000000000000021441356743356300157110ustar00rootroot00000000000000require 'eventmachine' require 'mysql2' module Mysql2 module EM class Client < ::Mysql2::Client module Watcher def initialize(client, deferable) @client = client @deferable = deferable @is_watching = true end def notify_readable detach begin result = @client.async_result rescue StandardError => e @deferable.fail(e) else @deferable.succeed(result) end end def watching? @is_watching end def unbind @is_watching = false end end def close(*args) @watch.detach if @watch && @watch.watching? super(*args) end def query(sql, opts = {}) if ::EM.reactor_running? super(sql, opts.merge(async: true)) deferable = ::EM::DefaultDeferrable.new @watch = ::EM.watch(socket, Watcher, self, deferable) @watch.notify_readable = true deferable else super(sql, opts) end end end end end mysql2-0.5.3/lib/mysql2/error.rb000066400000000000000000000073161356743356300164470ustar00rootroot00000000000000module Mysql2 class Error < StandardError ENCODE_OPTS = { undef: :replace, invalid: :replace, replace: '?'.freeze, }.freeze ConnectionError = Class.new(Error) TimeoutError = Class.new(Error) CODES = { 1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT 1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR 1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR 1152 => ConnectionError, # ER_ABORTING_CONNECTION 1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE 1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE 1155 => ConnectionError, # ER_NET_FCNTL_ERROR 1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER 1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR 1158 => ConnectionError, # ER_NET_READ_ERROR 1159 => ConnectionError, # ER_NET_READ_INTERRUPTED 1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE 1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED 2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR 2002 => ConnectionError, # CR_CONNECTION_ERROR 2003 => ConnectionError, # CR_CONN_HOST_ERROR 2004 => ConnectionError, # CR_IPSOCK_ERROR 2005 => ConnectionError, # CR_UNKNOWN_HOST 2006 => ConnectionError, # CR_SERVER_GONE_ERROR 2007 => ConnectionError, # CR_VERSION_ERROR 2009 => ConnectionError, # CR_WRONG_HOST_INFO 2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR 2013 => ConnectionError, # CR_SERVER_LOST 2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE 2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR 2027 => ConnectionError, # CR_MALFORMED_PACKET 2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL 2048 => ConnectionError, # CR_INVALID_CONN_HANDLE 2049 => ConnectionError, # CR_UNUSED_1 }.freeze attr_reader :error_number, :sql_state # Mysql gem compatibility alias errno error_number alias error message def initialize(msg, server_version = nil, error_number = nil, sql_state = nil) @server_version = server_version @error_number = error_number @sql_state = sql_state ? sql_state.encode(**ENCODE_OPTS) : nil super(clean_message(msg)) end def self.new_with_args(msg, server_version, error_number, sql_state) error_class = CODES.fetch(error_number, self) error_class.new(msg, server_version, error_number, sql_state) end private # In MySQL 5.5+ error messages are always constructed server-side as UTF-8 # then returned in the encoding set by the `character_set_results` system # variable. # # See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for # more context. # # Before MySQL 5.5 error message template strings are in whatever encoding # is associated with the error message language. # See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html # for more information. # # The issue is that the user-data inserted in the message could potentially # be in any encoding MySQL supports and is insert into the latin1, euckr or # koi8r string raw. Meaning there's a high probability the string will be # corrupt encoding-wise. # # See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for # more information. # # So in an attempt to make sure the error message string is always in a valid # encoding, we'll assume UTF-8 and clean the string of anything that's not a # valid UTF-8 character. # # Returns a valid UTF-8 string. def clean_message(message) if @server_version && @server_version > 50500 message.encode(**ENCODE_OPTS) else message.encode(Encoding::UTF_8, **ENCODE_OPTS) end end end end mysql2-0.5.3/lib/mysql2/field.rb000066400000000000000000000000651356743356300163730ustar00rootroot00000000000000module Mysql2 Field = Struct.new(:name, :type) end mysql2-0.5.3/lib/mysql2/result.rb000066400000000000000000000001351356743356300166240ustar00rootroot00000000000000module Mysql2 class Result attr_reader :server_flags include Enumerable end end mysql2-0.5.3/lib/mysql2/statement.rb000066400000000000000000000003471356743356300173170ustar00rootroot00000000000000module Mysql2 class Statement include Enumerable def execute(*args, **kwargs) Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do _execute(*args, **kwargs) end end end end mysql2-0.5.3/lib/mysql2/version.rb000066400000000000000000000000551356743356300167740ustar00rootroot00000000000000module Mysql2 VERSION = "0.5.3".freeze end mysql2-0.5.3/mysql2.gemspec000066400000000000000000000012541356743356300155630ustar00rootroot00000000000000require File.expand_path('../lib/mysql2/version', __FILE__) Mysql2::GEMSPEC = Gem::Specification.new do |s| s.name = 'mysql2' s.version = Mysql2::VERSION s.authors = ['Brian Lopez', 'Aaron Stone'] s.license = "MIT" s.email = ['seniorlopez@gmail.com', 'aaron@serendipity.cx'] s.extensions = ["ext/mysql2/extconf.rb"] s.homepage = 'https://github.com/brianmario/mysql2' s.rdoc_options = ["--charset=UTF-8"] s.summary = 'A simple, fast Mysql library for Ruby, binding to libmysql' s.required_ruby_version = '>= 2.0.0' s.files = `git ls-files README.md CHANGELOG.md LICENSE ext lib support`.split s.metadata['msys2_mingw_dependencies'] = 'libmariadbclient' end mysql2-0.5.3/script/000077500000000000000000000000001356743356300142715ustar00rootroot00000000000000mysql2-0.5.3/script/bootstrap000077500000000000000000000001411356743356300162300ustar00rootroot00000000000000#!/bin/sh set -e cd "$(dirname "$0")/.." exec bundle install --binstubs --path vendor/gems "$@" mysql2-0.5.3/script/console000077500000000000000000000001631356743356300156610ustar00rootroot00000000000000#!/bin/sh # Run a Ruby REPL. set -e cd $(dirname "$0")/.. exec ruby -S bin/pry -Ilib -r mysql2 -r mysql2/console mysql2-0.5.3/spec/000077500000000000000000000000001356743356300137175ustar00rootroot00000000000000mysql2-0.5.3/spec/configuration.yml.example000066400000000000000000000002351356743356300207430ustar00rootroot00000000000000root: host: localhost username: root password: database: test user: host: localhost username: LOCALUSERNAME password: database: mysql2_test mysql2-0.5.3/spec/em/000077500000000000000000000000001356743356300143205ustar00rootroot00000000000000mysql2-0.5.3/spec/em/em_spec.rb000066400000000000000000000101761356743356300162650ustar00rootroot00000000000000require 'spec_helper' begin require 'eventmachine' require 'mysql2/em' RSpec.describe Mysql2::EM::Client do it "should support async queries" do results = [] EM.run do client1 = Mysql2::EM::Client.new DatabaseCredentials['root'] defer1 = client1.query "SELECT sleep(0.1) as first_query" defer1.callback do |result| results << result.first client1.close EM.stop_event_loop end client2 = Mysql2::EM::Client.new DatabaseCredentials['root'] defer2 = client2.query "SELECT sleep(0.025) second_query" defer2.callback do |result| results << result.first client2.close end end expect(results[0].keys).to include("second_query") expect(results[1].keys).to include("first_query") end it "should support queries in callbacks" do results = [] EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer1 = client.query "SELECT sleep(0.025) as first_query" defer1.callback do |result| results << result.first defer2 = client.query "SELECT sleep(0.025) as second_query" defer2.callback do |r| results << r.first client.close EM.stop_event_loop end end end expect(results[0].keys).to include("first_query") expect(results[1].keys).to include("second_query") end it "should not swallow exceptions raised in callbacks" do expect do EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query "SELECT sleep(0.1) as first_query" defer.callback do client.close raise 'some error' end defer.errback do # This _shouldn't_ be run, but it needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop end end end.to raise_error('some error') end context 'when an exception is raised by the client' do let(:client) { Mysql2::EM::Client.new DatabaseCredentials['root'] } let(:error) { StandardError.new('some error') } before { allow(client).to receive(:async_result).and_raise(error) } after { client.close } it "should swallow exceptions raised in by the client" do errors = [] EM.run do defer = client.query "SELECT sleep(0.1) as first_query" defer.callback do # This _shouldn't_ be run, but it is needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop end defer.errback do |err| errors << err EM.stop_event_loop end end expect(errors).to eq([error]) end it "should fail the deferrable" do callbacks_run = [] EM.run do defer = client.query "SELECT sleep(0.025) as first_query" EM.add_timer(0.1) do defer.callback do callbacks_run << :callback # This _shouldn't_ be run, but it is needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop end defer.errback do callbacks_run << :errback EM.stop_event_loop end end end expect(callbacks_run).to eq([:errback]) end end it "should not raise error when closing client with no query running" do callbacks_run = [] EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query("select sleep(0.025)") defer.callback do callbacks_run << :callback end defer.errback do callbacks_run << :errback end EM.add_timer(0.1) do expect(callbacks_run).to eq([:callback]) expect do client.close end.not_to raise_error EM.stop_event_loop end end end end rescue LoadError puts "EventMachine not installed, skipping the specs that use it" end mysql2-0.5.3/spec/my.cnf.example000066400000000000000000000001511356743356300164630ustar00rootroot00000000000000[root] host=localhost user=LOCALUSERNAME password= [client] host=localhost user=LOCALUSERNAME password= mysql2-0.5.3/spec/mysql2/000077500000000000000000000000001356743356300151465ustar00rootroot00000000000000mysql2-0.5.3/spec/mysql2/client_spec.rb000066400000000000000000001117121356743356300177660ustar00rootroot00000000000000require 'spec_helper' RSpec.describe Mysql2::Client do context "using defaults file" do let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) } it "should not raise an exception for valid defaults group" do expect do new_client(default_file: cnf_file, default_group: "test") end.not_to raise_error end it "should not raise an exception without default group" do expect do new_client(default_file: cnf_file) end.not_to raise_error end end it "should raise a Mysql::Error::ConnectionError upon connection failure" do expect do # The odd local host IP address forces the mysql client library to # use a TCP socket rather than a domain socket. new_client('host' => '127.0.0.2', 'port' => 999999) end.to raise_error(Mysql2::Error::ConnectionError) end it "should raise an exception on create for invalid encodings" do expect do new_client(encoding: "fake") end.to raise_error(Mysql2::Error) end it "should raise an exception on non-string encodings" do expect do new_client(encoding: :fake) end.to raise_error(TypeError) end it "should not raise an exception on create for a valid encoding" do expect do new_client(encoding: "utf8") end.not_to raise_error expect do new_client(DatabaseCredentials['root'].merge(encoding: "big5")) end.not_to raise_error end Klient = Class.new(Mysql2::Client) do attr_reader :connect_args def connect(*args) @connect_args ||= [] @connect_args << args end end it "should accept connect flags and pass them to #connect" do client = Klient.new flags: Mysql2::Client::FOUND_ROWS expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to be > 0 end it "should parse flags array" do client = Klient.new flags: %w[FOUND_ROWS -PROTOCOL_41] expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end it "should parse flags string" do client = Klient.new flags: "FOUND_ROWS -PROTOCOL_41" expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do client = Klient.new client_flags = Mysql2::Client::REMEMBER_OPTIONS | Mysql2::Client::LONG_PASSWORD | Mysql2::Client::LONG_FLAG | Mysql2::Client::TRANSACTIONS | Mysql2::Client::PROTOCOL_41 | Mysql2::Client::SECURE_CONNECTION | Mysql2::Client::CONNECT_ATTRS expect(client.connect_args.last[6]).to eql(client_flags) end it "should execute init command" do options = DatabaseCredentials['root'].dup options[:init_command] = "SET @something = 'setting_value';" client = new_client(options) result = client.query("SELECT @something;") expect(result.first['@something']).to eq('setting_value') end it "should send init_command after reconnect" do options = DatabaseCredentials['root'].dup options[:init_command] = "SET @something = 'setting_value';" options[:reconnect] = true client = new_client(options) result = client.query("SELECT @something;") expect(result.first['@something']).to eq('setting_value') # get the current connection id result = client.query("SELECT CONNECTION_ID()") first_conn_id = result.first['CONNECTION_ID()'] # break the current connection expect { client.query("KILL #{first_conn_id}") }.to raise_error(Mysql2::Error) client.ping # reconnect now # get the new connection id result = client.query("SELECT CONNECTION_ID()") second_conn_id = result.first['CONNECTION_ID()'] # confirm reconnect by checking the new connection id expect(first_conn_id).not_to eq(second_conn_id) # At last, check that the init command executed result = client.query("SELECT @something;") expect(result.first['@something']).to eq('setting_value') end it "should have a global default_query_options hash" do expect(Mysql2::Client).to respond_to(:default_query_options) end it "should be able to connect via SSL options" do ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'" ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' } pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' } pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled # You may need to adjust the lines below to match your SSL certificate paths ssl_client = nil option_overrides = { 'host' => 'mysql2gem.example.com', # must match the certificates :sslkey => '/etc/mysql/client-key.pem', :sslcert => '/etc/mysql/client-cert.pem', :sslca => '/etc/mysql/ca-cert.pem', :sslcipher => 'DHE-RSA-AES256-SHA', :sslverify => true, } %i[sslkey sslcert sslca].each do |item| unless File.exist?(option_overrides[item]) pending("DON'T WORRY, THIS TEST PASSES - but #{option_overrides[item]} does not exist.") break end end expect do ssl_client = new_client(option_overrides) end.not_to raise_error results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }] expect(results['Ssl_cipher']).not_to be_empty expect(results['Ssl_version']).not_to be_empty expect(ssl_client.ssl_cipher).not_to be_empty expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher) end def run_gc if defined?(Rubinius) GC.run(true) else GC.start end sleep(0.5) end it "should terminate connections when calling close" do # rubocop:disable Lint/AmbiguousBlockAssociation expect do client = Mysql2::Client.new(DatabaseCredentials['root']) connection_id = client.thread_id client.close # mysql_close sends a quit command without waiting for a response # so give the server some time to handle the detect the closed connection closed = false 10.times do closed = @client.query("SHOW PROCESSLIST").none? { |row| row['Id'] == connection_id } break if closed sleep(0.1) end expect(closed).to eq(true) end.to_not change { @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a } # rubocop:enable Lint/AmbiguousBlockAssociation end it "should not leave dangling connections after garbage collection" do run_gc # rubocop:disable Lint/AmbiguousBlockAssociation expect do expect do 10.times do Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1') end end.to change { @client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i }.by(10) run_gc end.to_not change { @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a } # rubocop:enable Lint/AmbiguousBlockAssociation end context "#set_server_option" do let(:client) do new_client.tap do |client| client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON) end end it 'returns true when multi_statements is enable' do expect(client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)).to be true end it 'returns true when multi_statements is disable' do expect(client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)).to be true end it 'returns false when multi_statements is neither OPTION_MULTI_STATEMENTS_OFF or OPTION_MULTI_STATEMENTS_ON' do expect(client.set_server_option(344)).to be false end it 'enables multiple-statement' do client.query("SELECT 1;SELECT 2;") expect(client.next_result).to be true expect(client.store_result.first).to eql('2' => 2) expect(client.next_result).to be false end it 'disables multiple-statement' do client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF) expect { client.query("SELECT 1;SELECT 2;") }.to raise_error(Mysql2::Error) end end context "#automatic_close" do it "is enabled by default" do expect(new_client.automatic_close?).to be(true) end if RUBY_PLATFORM =~ /mingw|mswin/ it "cannot be disabled" do expect do client = new_client(automatic_close: false) expect(client.automatic_close?).to be(true) end.to output(/always closed by garbage collector/).to_stderr expect do client = new_client(automatic_close: true) expect(client.automatic_close?).to be(true) end.to_not output(/always closed by garbage collector/).to_stderr expect do client = new_client(automatic_close: true) client.automatic_close = false expect(client.automatic_close?).to be(true) end.to output(/always closed by garbage collector/).to_stderr end else it "can be configured" do client = new_client(automatic_close: false) expect(client.automatic_close?).to be(false) end it "can be assigned" do client = new_client client.automatic_close = false expect(client.automatic_close?).to be(false) client.automatic_close = true expect(client.automatic_close?).to be(true) client.automatic_close = nil expect(client.automatic_close?).to be(false) client.automatic_close = 9 expect(client.automatic_close?).to be(true) end it "should not close connections when running in a child process" do run_gc client = Mysql2::Client.new(DatabaseCredentials['root']) client.automatic_close = false child = fork do client.query('SELECT 1') client = nil run_gc end Process.wait(child) # this will throw an error if the underlying socket was shutdown by the # child's GC expect { client.query('SELECT 1') }.to_not raise_exception client.close end end end it "should be able to connect to database with numeric-only name" do database = 1235 @client.query "CREATE DATABASE IF NOT EXISTS `#{database}`" expect do new_client('database' => database) end.not_to raise_error @client.query "DROP DATABASE IF EXISTS `#{database}`" end it "should respond to #close" do expect(@client).to respond_to(:close) end it "should be able to close properly" do expect(@client.close).to be_nil expect do @client.query "SELECT 1" end.to raise_error(Mysql2::Error) end context "#closed?" do it "should return false when connected" do expect(@client.closed?).to eql(false) end it "should return true after close" do @client.close expect(@client.closed?).to eql(true) end end it "should not try to query closed mysql connection" do client = new_client(reconnect: true) expect(client.close).to be_nil expect do client.query "SELECT 1" end.to raise_error(Mysql2::Error) end it "should respond to #query" do expect(@client).to respond_to(:query) end it "should respond to #warning_count" do expect(@client).to respond_to(:warning_count) end context "#warning_count" do context "when no warnings" do it "should 0" do @client.query('select 1') expect(@client.warning_count).to eq(0) end end context "when has a warnings" do it "should > 0" do # "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS" # https://dev.mysql.com/doc/refman/5.7/en/show-warnings.html @client.query('DROP TABLE IF EXISTS test.no_such_table') expect(@client.warning_count).to be > 0 end end end it "should respond to #query_info" do expect(@client).to respond_to(:query_info) end context "#query_info" do context "when no info present" do it "should 0" do @client.query('select 1') expect(@client.query_info).to be_empty expect(@client.query_info_string).to be_nil end end context "when has some info" do it "should retrieve it" do @client.query "USE test" @client.query "CREATE TABLE IF NOT EXISTS infoTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" # http://dev.mysql.com/doc/refman/5.0/en/mysql-info.html says # # Note that mysql_info() returns a non-NULL value for INSERT ... VALUES only for the multiple-row form of the statement (that is, only if multiple value lists are specified). @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)") expect(@client.query_info).to eql(records: 2, duplicates: 0, warnings: 0) expect(@client.query_info_string).to eq('Records: 2 Duplicates: 0 Warnings: 0') @client.query "DROP TABLE infoTest" end end end context ":local_infile" do before(:all) do new_client(local_infile: true) do |client| local = client.query "SHOW VARIABLES LIKE 'local_infile'" local_enabled = local.any? { |x| x['Value'] == 'ON' } skip("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled client.query %[ CREATE TABLE IF NOT EXISTS infileTest ( id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY, foo VARCHAR(10), bar MEDIUMTEXT ) ] end end after(:all) do new_client do |client| client.query "DROP TABLE IF EXISTS infileTest" end end it "should raise an error when local_infile is disabled" do client = new_client(local_infile: false) expect do client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" end.to raise_error(Mysql2::Error, /command is not allowed/) end it "should raise an error when a non-existent file is loaded" do client = new_client(local_infile: true) expect do client.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest" end.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here') end it "should LOAD DATA LOCAL INFILE" do client = new_client(local_infile: true) client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" info = client.query_info expect(info).to eql(records: 1, deleted: 0, skipped: 0, warnings: 0) result = client.query "SELECT * FROM infileTest" expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World') end end it "should expect connect_timeout to be a positive integer" do expect do new_client(connect_timeout: -1) end.to raise_error(Mysql2::Error) end it "should expect read_timeout to be a positive integer" do expect do new_client(read_timeout: -1) end.to raise_error(Mysql2::Error) end it "should expect write_timeout to be a positive integer" do expect do new_client(write_timeout: -1) end.to raise_error(Mysql2::Error) end it "should allow nil read_timeout" do client = new_client(read_timeout: nil) expect(client.read_timeout).to be_nil end it "should set default program_name in connect_attrs" do client = new_client if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/) pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.') end result = client.query("SELECT attr_value FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id() AND attr_name = 'program_name'") expect(result.first['attr_value']).to eq($PROGRAM_NAME) end it "should set custom connect_attrs" do client = new_client(connect_attrs: { program_name: 'my_program_name', foo: 'fooval', bar: 'barval' }) if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/) pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.') end results = Hash[client.query("SELECT * FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id()").map { |x| x.values_at('ATTR_NAME', 'ATTR_VALUE') }] expect(results['program_name']).to eq('my_program_name') expect(results['foo']).to eq('fooval') expect(results['bar']).to eq('barval') end context "#query" do it "should let you query again if iterating is finished when streaming" do @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false).each.to_a expect do @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false) end.to_not raise_error end it "should not let you query again if iterating is not finished when streaming" do @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false).first expect do @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false) end.to raise_exception(Mysql2::Error) end it "should only accept strings as the query parameter" do expect do @client.query ["SELECT 'not right'"] end.to raise_error(TypeError) end it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do result = @client.query "SELECT 1", something: :else expect(@client.query_options[:something]).to be_nil expect(result.instance_variable_get('@query_options')).to eql(@client.query_options.merge(something: :else)) expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options.merge(something: :else)) result = @client.query "SELECT 1" expect(result.instance_variable_get('@query_options')).to eql(@client.query_options) expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options) end it "should allow changing query options for subsequent queries" do @client.query_options[:something] = :else result = @client.query "SELECT 1" expect(@client.query_options[:something]).to eql(:else) expect(result.instance_variable_get('@query_options')[:something]).to eql(:else) # Clean up after this test @client.query_options.delete(:something) expect(@client.query_options[:something]).to be_nil end it "should return results as a hash by default" do expect(@client.query("SELECT 1").first).to be_an_instance_of(Hash) end it "should be able to return results as an array" do expect(@client.query("SELECT 1", as: :array).first).to be_an_instance_of(Array) @client.query("SELECT 1").each(as: :array) end it "should be able to return results with symbolized keys" do expect(@client.query("SELECT 1", symbolize_keys: true).first.keys[0]).to be_an_instance_of(Symbol) end it "should require an open connection" do @client.close expect do @client.query "SELECT 1" end.to raise_error(Mysql2::Error) end it "should detect closed connection on query read error" do connection_id = @client.thread_id Thread.new do sleep(0.1) Mysql2::Client.new(DatabaseCredentials['root']).tap do |supervisor| supervisor.query("KILL #{connection_id}") end.close end expect do @client.query("SELECT SLEEP(1)") end.to raise_error(Mysql2::Error, /Lost connection to MySQL server/) if RUBY_PLATFORM !~ /mingw|mswin/ expect do @client.socket end.to raise_error(Mysql2::Error, 'MySQL client is not connected') end end if RUBY_PLATFORM !~ /mingw|mswin/ it "should not allow another query to be sent without fetching a result first" do @client.query("SELECT 1", async: true) expect do @client.query("SELECT 1") end.to raise_error(Mysql2::Error) end it "should describe the thread holding the active query" do thr = Thread.new { @client.query("SELECT 1", async: true) } thr.join expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(thr.inspect))) end it "should timeout if we wait longer than :read_timeout" do client = new_client(read_timeout: 0) expect do client.query('SELECT SLEEP(0.1)') end.to raise_error(Mysql2::Error::TimeoutError) end # XXX this test is not deterministic (because Unix signal handling is not) # and may fail on a loaded system it "should run signal handlers while waiting for a response" do kill_time = 0.25 query_time = 4 * kill_time mark = {} begin trap(:USR1) { mark.store(:USR1, clock_time) } pid = fork do sleep kill_time # wait for client query to start Process.kill(:USR1, Process.ppid) sleep # wait for explicit kill to prevent GC disconnect end mark.store(:QUERY_START, clock_time) @client.query("SELECT SLEEP(#{query_time})") mark.store(:QUERY_END, clock_time) ensure Process.kill(:TERM, pid) Process.waitpid2(pid) trap(:USR1, 'DEFAULT') end # the query ran uninterrupted expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.1).of(query_time) # signals fired while the query was running expect(mark.fetch(:USR1)).to be_between(mark.fetch(:QUERY_START), mark.fetch(:QUERY_END)) end it "#socket should return a Fixnum (file descriptor from C)" do expect(@client.socket).to be_an_instance_of(0.class) expect(@client.socket).not_to eql(0) end it "#socket should require an open connection" do @client.close expect do @client.socket end.to raise_error(Mysql2::Error) end it 'should be impervious to connection-corrupting timeouts in #execute' do # attempt to break the connection stmt = @client.prepare('SELECT SLEEP(?)') expect { Timeout.timeout(0.1) { stmt.execute(0.2) } }.to raise_error(Timeout::Error) stmt.close # expect the connection to not be broken expect { @client.query('SELECT 1') }.to_not raise_error end context 'when a non-standard exception class is raised' do it "should close the connection when an exception is raised" do expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'MySQL client is not connected') end it "should handle Timeouts without leaving the connection hanging if reconnect is true" do if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5') pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end client = new_client(reconnect: true) expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { client.query('SELECT 1') }.to_not raise_error end it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction" do if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5') pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end client = new_client expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { client.query('SELECT 1') }.to raise_error(Mysql2::Error) client.reconnect = true expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { client.query('SELECT 1') }.to_not raise_error end end it "threaded queries should be supported" do sleep_time = 0.5 # Note that each thread opens its own database connection start = clock_time threads = Array.new(5) do Thread.new do new_client do |client| client.query("SELECT SLEEP(#{sleep_time})") end Thread.current.object_id end end values = threads.map(&:value) stop = clock_time # This check demonstrates that the threads are sleeping concurrently: # In the serial case, the difference would be a multiple of sleep time expect(stop - start).to be_within(0.1).of(sleep_time) expect(values).to match_array(threads.map(&:object_id)) end it "evented async queries should be supported" do # should immediately return nil expect(@client.query("SELECT sleep(0.1)", async: true)).to eql(nil) io_wrapper = IO.for_fd(@client.socket, autoclose: false) loops = 0 loops += 1 until IO.select([io_wrapper], nil, nil, 0.05) # make sure we waited some period of time expect(loops >= 1).to be true result = @client.async_result expect(result).to be_an_instance_of(Mysql2::Result) end end context "Multiple results sets" do before(:each) do @multi_client = new_client(flags: Mysql2::Client::MULTI_STATEMENTS) end it "should raise an exception when one of multiple statements fails" do result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';") expect(result.first['set_1']).to be(1) expect do @multi_client.next_result end.to raise_error(Mysql2::Error) expect(@multi_client.next_result).to be false end it "returns multiple result sets" do expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql('set_1' => 1) expect(@multi_client.next_result).to be true expect(@multi_client.store_result.first).to eql('set_2' => 2) expect(@multi_client.next_result).to be false end it "does not interfere with other statements" do @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'") @multi_client.store_result while @multi_client.next_result expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq('next' => 3) end it "will raise on query if there are outstanding results to read" do @multi_client.query("SELECT 1; SELECT 2; SELECT 3") expect do @multi_client.query("SELECT 4") end.to raise_error(Mysql2::Error) end it "#abandon_results! should work" do @multi_client.query("SELECT 1; SELECT 2; SELECT 3") @multi_client.abandon_results! expect do @multi_client.query("SELECT 4") end.not_to raise_error end it "#more_results? should work" do @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'") expect(@multi_client.more_results?).to be true @multi_client.next_result @multi_client.store_result expect(@multi_client.more_results?).to be false end it "#more_results? should work with stored procedures" do @multi_client.query("DROP PROCEDURE IF EXISTS test_proc") @multi_client.query("CREATE PROCEDURE test_proc() BEGIN SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'; END") expect(@multi_client.query("CALL test_proc()").first).to eql('set_1' => 1) expect(@multi_client.more_results?).to be true @multi_client.next_result expect(@multi_client.store_result.first).to eql('set_2' => 2) @multi_client.next_result expect(@multi_client.store_result).to be_nil # this is the result from CALL itself expect(@multi_client.more_results?).to be false end end end it "should respond to #socket" do expect(@client).to respond_to(:socket) end if RUBY_PLATFORM =~ /mingw|mswin/ it "#socket should raise as it's not supported" do expect do @client.socket end.to raise_error(Mysql2::Error, /Raw access to the mysql file descriptor isn't supported on Windows/) end end it "should respond to escape" do expect(Mysql2::Client).to respond_to(:escape) end context "escape" do it "should return a new SQL-escape version of the passed string" do expect(Mysql2::Client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno") end it "should return the passed string if nothing was escaped" do str = "plain" expect(Mysql2::Client.escape(str).object_id).to eql(str.object_id) end it "should not overflow the thread stack" do expect do Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join end.not_to raise_error end it "should not overflow the process stack" do expect do Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join end.not_to raise_error end it "should carry over the original string's encoding" do str = "abc'def\"ghi\0jkl%mno" escaped = Mysql2::Client.escape(str) expect(escaped.encoding).to eql(str.encoding) str.encode!('us-ascii') escaped = Mysql2::Client.escape(str) expect(escaped.encoding).to eql(str.encoding) end end it "should respond to #escape" do expect(@client).to respond_to(:escape) end context "#escape" do it "should return a new SQL-escape version of the passed string" do expect(@client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno") end it "should return the passed string if nothing was escaped" do str = "plain" expect(@client.escape(str).object_id).to eql(str.object_id) end it "should not overflow the thread stack" do expect do Thread.new { @client.escape("'" * 256 * 1024) }.join end.not_to raise_error end it "should not overflow the process stack" do expect do Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join end.not_to raise_error end it "should require an open connection" do @client.close expect do @client.escape "" end.to raise_error(Mysql2::Error) end context 'when mysql encoding is not utf8' do let(:client) { new_client(encoding: "ujis") } it 'should return a internal encoding string if Encoding.default_internal is set' do with_internal_encoding Encoding::UTF_8 do expect(client.escape("\u{30C6}\u{30B9}\u{30C8}")).to eq "\u{30C6}\u{30B9}\u{30C8}" expect(client.escape("\u{30C6}'\u{30B9}\"\u{30C8}")).to eq "\u{30C6}\\'\u{30B9}\\\"\u{30C8}" end end end end it "should respond to #info" do expect(@client).to respond_to(:info) end it "#info should return a hash containing the client version ID and String" do info = @client.info expect(info).to be_an_instance_of(Hash) expect(info).to have_key(:id) expect(info[:id]).to be_an_instance_of(0.class) expect(info).to have_key(:version) expect(info[:version]).to be_an_instance_of(String) end context "strings returned by #info" do it "should be tagged as ascii" do expect(@client.info[:version].encoding).to eql(Encoding::US_ASCII) expect(@client.info[:header_version].encoding).to eql(Encoding::US_ASCII) end end context "strings returned by .info" do it "should be tagged as ascii" do expect(Mysql2::Client.info[:version].encoding).to eql(Encoding::US_ASCII) expect(Mysql2::Client.info[:header_version].encoding).to eql(Encoding::US_ASCII) end end it "should respond to #server_info" do expect(@client).to respond_to(:server_info) end it "#server_info should return a hash containing the client version ID and String" do server_info = @client.server_info expect(server_info).to be_an_instance_of(Hash) expect(server_info).to have_key(:id) expect(server_info[:id]).to be_an_instance_of(0.class) expect(server_info).to have_key(:version) expect(server_info[:version]).to be_an_instance_of(String) end it "#server_info should require an open connection" do @client.close expect do @client.server_info end.to raise_error(Mysql2::Error) end context "strings returned by #server_info" do it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8) client2 = new_client(encoding: 'ascii') expect(client2.server_info[:version].encoding).to eql(Encoding::ASCII) end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) end end end it "should raise a Mysql2::Error::ConnectionError exception upon connection failure due to invalid credentials" do expect do new_client(host: 'localhost', username: 'asdfasdf8d2h', password: 'asdfasdfw42') end.to raise_error(Mysql2::Error::ConnectionError) expect do new_client(DatabaseCredentials['root']) end.not_to raise_error end context 'write operations api' do before(:each) do @client.query "USE test" @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" end after(:each) do @client.query "DROP TABLE lastIdTest" end it "should respond to #last_id" do expect(@client).to respond_to(:last_id) end it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do expect(@client.last_id).to eql(0) @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" expect(@client.last_id).to eql(1) end it "should respond to #last_id" do expect(@client).to respond_to(:last_id) end it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" expect(@client.affected_rows).to eql(1) @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1" expect(@client.affected_rows).to eql(1) end it "#last_id should handle BIGINT auto-increment ids above 32 bits" do # The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x. # Insert a row with a given ID, this should raise the auto-increment state @client.query "INSERT INTO lastIdTest (id, blah) VALUES (5000000000, 5000)" expect(@client.last_id).to eql(5000000000) @client.query "INSERT INTO lastIdTest (blah) VALUES (5001)" expect(@client.last_id).to eql(5000000001) end end it "should respond to #thread_id" do expect(@client).to respond_to(:thread_id) end it "#thread_id should be a Fixnum" do expect(@client.thread_id).to be_an_instance_of(0.class) end it "should respond to #ping" do expect(@client).to respond_to(:ping) end context "select_db" do before(:each) do 2.times do |i| @client.query("CREATE DATABASE test_selectdb_#{i}") @client.query("USE test_selectdb_#{i}") @client.query("CREATE TABLE test#{i} (`id` int NOT NULL PRIMARY KEY)") end end after(:each) do 2.times do |i| @client.query("DROP DATABASE test_selectdb_#{i}") end end it "should respond to #select_db" do expect(@client).to respond_to(:select_db) end it "should switch databases" do @client.select_db("test_selectdb_0") expect(@client.query("SHOW TABLES").first.values.first).to eql("test0") @client.select_db("test_selectdb_1") expect(@client.query("SHOW TABLES").first.values.first).to eql("test1") @client.select_db("test_selectdb_0") expect(@client.query("SHOW TABLES").first.values.first).to eql("test0") end it "should raise a Mysql2::Error when the database doesn't exist" do expect do @client.select_db("nopenothere") end.to raise_error(Mysql2::Error) end it "should return the database switched to" do expect(@client.select_db("test_selectdb_1")).to eq("test_selectdb_1") end end it "#thread_id should return a boolean" do expect(@client.ping).to eql(true) @client.close expect(@client.ping).to eql(false) end it "should be able to connect using plaintext password" do client = new_client(enable_cleartext_plugin: true) client.query('SELECT 1') end it "should respond to #encoding" do expect(@client).to respond_to(:encoding) end end mysql2-0.5.3/spec/mysql2/error_spec.rb000066400000000000000000000040601356743356300176360ustar00rootroot00000000000000require 'spec_helper' RSpec.describe Mysql2::Error do let(:error) do begin @client.query("HAHAHA") rescue Mysql2::Error => e error = e end error end it "responds to error_number and sql_state, with aliases" do expect(error).to respond_to(:error_number) expect(error).to respond_to(:sql_state) # Mysql gem compatibility expect(error).to respond_to(:errno) expect(error).to respond_to(:error) end context 'encoding' do let(:valid_utf8) { '造字' } let(:error) do begin @client.query(valid_utf8) rescue Mysql2::Error => e e end end let(:invalid_utf8) { ["e5c67d1f"].pack('H*').force_encoding(Encoding::UTF_8) } let(:bad_err) do begin @client.query(invalid_utf8) rescue Mysql2::Error => e e end end before do # sanity check expect(valid_utf8.encoding).to eql(Encoding::UTF_8) expect(valid_utf8).to be_valid_encoding expect(invalid_utf8.encoding).to eql(Encoding::UTF_8) expect(invalid_utf8).to_not be_valid_encoding end it "returns error messages as UTF-8 by default" do with_internal_encoding nil do expect(error.message.encoding).to eql(Encoding::UTF_8) expect(error.message).to be_valid_encoding expect(bad_err.message.encoding).to eql(Encoding::UTF_8) expect(bad_err.message).to be_valid_encoding expect(bad_err.message).to include("??}\u001F") end end it "returns sql state as ASCII" do expect(error.sql_state.encoding).to eql(Encoding::US_ASCII) expect(error.sql_state).to be_valid_encoding end it "returns error messages and sql state in Encoding.default_internal if set" do with_internal_encoding Encoding::UTF_16LE do expect(error.message.encoding).to eql(Encoding.default_internal) expect(error.message).to be_valid_encoding expect(bad_err.message.encoding).to eql(Encoding.default_internal) expect(bad_err.message).to be_valid_encoding end end end end mysql2-0.5.3/spec/mysql2/result_spec.rb000066400000000000000000000457341356743356300200400ustar00rootroot00000000000000require 'spec_helper' RSpec.describe Mysql2::Result do before(:each) do @result = @client.query "SELECT 1" end it "should raise a TypeError exception when it doesn't wrap a result set" do r = Mysql2::Result.new expect { r.count }.to raise_error(TypeError) expect { r.fields }.to raise_error(TypeError) expect { r.size }.to raise_error(TypeError) expect { r.each }.to raise_error(TypeError) end it "should have included Enumerable" do expect(Mysql2::Result.ancestors.include?(Enumerable)).to be true end it "should respond to #each" do expect(@result).to respond_to(:each) end it "should respond to #free" do expect(@result).to respond_to(:free) end it "should raise a Mysql2::Error exception upon a bad query" do expect do @client.query "bad sql" end.to raise_error(Mysql2::Error) expect do @client.query "SELECT 1" end.not_to raise_error end it "should respond to #count, which is aliased as #size" do r = @client.query "SELECT 1" expect(r).to respond_to :count expect(r).to respond_to :size end it "should be able to return the number of rows in the result set" do r = @client.query "SELECT 1" expect(r.count).to eql(1) expect(r.size).to eql(1) end context "metadata queries" do it "should show tables" do @result = @client.query "SHOW TABLES" end end context "#each" do it "should yield rows as hash's" do @result.each do |row| expect(row).to be_an_instance_of(Hash) end end it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do @result.each(symbolize_keys: true) do |row| expect(row.keys.first).to be_an_instance_of(Symbol) end end it "should be able to return results as an array" do @result.each(as: :array) do |row| expect(row).to be_an_instance_of(Array) end end it "should cache previously yielded results by default" do expect(@result.first.object_id).to eql(@result.first.object_id) end it "should not cache previously yielded results if cache_rows is disabled" do result = @client.query "SELECT 1", cache_rows: false expect(result.first.object_id).not_to eql(result.first.object_id) end it "should be able to iterate a second time even if cache_rows is disabled" do result = @client.query "SELECT 1 UNION SELECT 2", cache_rows: false expect(result.to_a).to eql(result.to_a) end it "should yield different value for #first if streaming" do result = @client.query "SELECT 1 UNION SELECT 2", stream: true, cache_rows: false expect(result.first).not_to eql(result.first) end it "should yield the same value for #first if streaming is disabled" do result = @client.query "SELECT 1 UNION SELECT 2", stream: false expect(result.first).to eql(result.first) end it "should throw an exception if we try to iterate twice when streaming is enabled" do result = @client.query "SELECT 1 UNION SELECT 2", stream: true, cache_rows: false expect do result.each.to_a result.each.to_a end.to raise_exception(Mysql2::Error) end end context "#fields" do let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") } it "method should exist" do expect(test_result).to respond_to(:fields) end it "should return an array of field names in proper order" do result = @client.query "SELECT 'a', 'b', 'c'" expect(result.fields).to eql(%w[a b c]) end end context "streaming" do it "should maintain a count while streaming" do result = @client.query('SELECT 1', stream: true, cache_rows: false) expect(result.count).to eql(0) result.each.to_a expect(result.count).to eql(1) end it "should retain the count when mixing first and each" do result = @client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false) expect(result.count).to eql(0) result.first expect(result.count).to eql(1) result.each.to_a expect(result.count).to eql(2) end it "should not yield nil at the end of streaming" do result = @client.query('SELECT * FROM mysql2_test', stream: true, cache_rows: false) result.each { |r| expect(r).not_to be_nil } end it "#count should be zero for rows after streaming when there were no results" do @client.query "USE test" result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", stream: true, cache_rows: false) expect(result.count).to eql(0) result.each.to_a expect(result.count).to eql(0) end it "should raise an exception if streaming ended due to a timeout" do @client.query "CREATE TEMPORARY TABLE streamingTest (val BINARY(255)) ENGINE=MEMORY" # Insert enough records to force the result set into multiple reads # (the BINARY type is used simply because it forces full width results) 10000.times do |i| @client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')" end @client.query "SET net_write_timeout = 1" res = @client.query "SELECT * FROM streamingTest", stream: true, cache_rows: false expect do res.each_with_index do |_, i| # Exhaust the first result packet then trigger a timeout sleep 4 if i > 0 && i % 1000 == 0 end end.to raise_error(Mysql2::Error, /Lost connection/) end end context "row data type mapping" do let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first } it "should return nil values for NULL and strings for everything else when :cast is false" do result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', cast: false).first expect(result["null_test"]).to be_nil expect(result["tiny_int_test"]).to eql("1") expect(result["bool_cast_test"]).to eql("1") expect(result["int_test"]).to eql("10") expect(result["date_test"]).to eql("2010-04-04") expect(result["enum_test"]).to eql("val1") end it "should return nil for a NULL value" do expect(test_result['null_test']).to be_an_instance_of(NilClass) expect(test_result['null_test']).to eql(nil) end it "should return String for a BIT(64) value" do expect(test_result['bit_test']).to be_an_instance_of(String) expect(test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do expect(test_result['single_bit_test']).to be_an_instance_of(String) expect(test_result['single_bit_test']).to eql("\001") end it "should return Fixnum for a TINYINT value" do expect(num_classes).to include(test_result['tiny_int_test'].class) expect(test_result['tiny_int_test']).to eql(1) end context "cast booleans for TINYINT if :cast_booleans is enabled" do # rubocop:disable Style/Semicolon let(:id1) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; @client.last_id } let(:id2) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; @client.last_id } let(:id3) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; @client.last_id } # rubocop:enable Style/Semicolon after do @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" end it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do result1 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id1} LIMIT 1", cast_booleans: true result2 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id2} LIMIT 1", cast_booleans: true result3 = @client.query "SELECT bool_cast_test FROM mysql2_test WHERE id = #{id3} LIMIT 1", cast_booleans: true expect(result1.first['bool_cast_test']).to be true expect(result2.first['bool_cast_test']).to be false expect(result3.first['bool_cast_test']).to be true end end context "cast booleans for BIT(1) if :cast_booleans is enabled" do # rubocop:disable Style/Semicolon let(:id1) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; @client.last_id } let(:id2) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; @client.last_id } # rubocop:enable Style/Semicolon after do @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" end it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", cast_booleans: true result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", cast_booleans: true expect(result1.first['single_bit_test']).to be true expect(result2.first['single_bit_test']).to be false end end it "should return Fixnum for a SMALLINT value" do expect(num_classes).to include(test_result['small_int_test'].class) expect(test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do expect(num_classes).to include(test_result['medium_int_test'].class) expect(test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do expect(num_classes).to include(test_result['int_test'].class) expect(test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do expect(num_classes).to include(test_result['big_int_test'].class) expect(test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do expect(num_classes).to include(test_result['year_test'].class) expect(test_result['year_test']).to eql(2009) end it "should return BigDecimal for a DECIMAL value" do expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal) expect(test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do expect(test_result['float_test']).to be_an_instance_of(Float) expect(test_result['float_test']).to eql(10.3) end it "should return Float for a DOUBLE value" do expect(test_result['double_test']).to be_an_instance_of(Float) expect(test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do expect(test_result['date_time_test']).to be_an_instance_of(Time) expect(test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time when timestamp is < 1901-12-13 20:45:52" do r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") expect(r.first['test']).to be_an_instance_of(Time) end it "should return Time for a TIMESTAMP value when within the supported range" do expect(test_result['timestamp_test']).to be_an_instance_of(Time) expect(test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do expect(test_result['time_test']).to be_an_instance_of(Time) expect(test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do expect(test_result['date_test']).to be_an_instance_of(Date) expect(test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do expect(test_result['enum_test']).to be_an_instance_of(String) expect(test_result['enum_test']).to eql('val1') end it "should raise an error given an invalid DATETIME" do expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") end context "string encoding for ENUM values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::ASCII) end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a SET value" do expect(test_result['set_test']).to be_an_instance_of(String) expect(test_result['set_test']).to eql('val1,val2') end context "string encoding for SET values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::UTF_8) client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::ASCII) end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a BINARY value" do expect(test_result['binary_test']).to be_an_instance_of(String) expect(test_result['binary_test']).to eql("test#{"\000" * 6}") end context "string encoding for BINARY values" do it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end end { 'char_test' => 'CHAR', 'varchar_test' => 'VARCHAR', 'varbinary_test' => 'VARBINARY', 'tiny_blob_test' => 'TINYBLOB', 'tiny_text_test' => 'TINYTEXT', 'blob_test' => 'BLOB', 'text_test' => 'TEXT', 'medium_blob_test' => 'MEDIUMBLOB', 'medium_text_test' => 'MEDIUMTEXT', 'long_blob_test' => 'LONGBLOB', 'long_text_test' => 'LONGTEXT', }.each do |field, type| it "should return a String for #{type}" do expect(test_result[field]).to be_an_instance_of(String) expect(test_result[field]).to eql("test") end context "string encoding for #{type} values" do if %w[VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB].include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end else it "should default to utf-8 if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::UTF_8) client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::ASCII) end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end end end end end end context "server flags" do let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY null_test DESC LIMIT 1") } it "should set a definitive value for query_was_slow" do expect(test_result.server_flags[:query_was_slow]).to eql(false) end it "should set a definitive value for no_index_used" do expect(test_result.server_flags[:no_index_used]).to eql(true) end it "should set a definitive value for no_good_index_used" do expect(test_result.server_flags[:no_good_index_used]).to eql(false) end end end mysql2-0.5.3/spec/mysql2/statement_spec.rb000066400000000000000000000647731356743356300205320ustar00rootroot00000000000000require './spec/spec_helper.rb' RSpec.describe Mysql2::Statement do before :each do @client = new_client(encoding: "utf8") end def stmt_count # Use the performance schema in MySQL 5.7 and above @client.query("SELECT COUNT(1) AS count FROM performance_schema.prepared_statements_instances").first['count'].to_i rescue Mysql2::Error # Fall back to the global prepapred statement counter @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i end it "should create a statement" do statement = nil expect { statement = @client.prepare 'SELECT 1' }.to change(&method(:stmt_count)).by(1) expect(statement).to be_an_instance_of(Mysql2::Statement) end it "should raise an exception when server disconnects" do @client.close expect { @client.prepare 'SELECT 1' }.to raise_error(Mysql2::Error) end it "should tell us the param count" do statement = @client.prepare 'SELECT ?, ?' expect(statement.param_count).to eq(2) statement2 = @client.prepare 'SELECT 1' expect(statement2.param_count).to eq(0) end it "should tell us the field count" do statement = @client.prepare 'SELECT ?, ?' expect(statement.field_count).to eq(2) statement2 = @client.prepare 'SELECT 1' expect(statement2.field_count).to eq(1) end it "should let us execute our statement" do statement = @client.prepare 'SELECT 1' expect(statement.execute).not_to eq(nil) end it "should raise an exception without a block" do statement = @client.prepare 'SELECT 1' expect { statement.execute.each }.to raise_error(LocalJumpError) end it "should tell us the result count" do statement = @client.prepare 'SELECT 1' result = statement.execute expect(result.count).to eq(1) end it "should let us iterate over results" do statement = @client.prepare 'SELECT 1' result = statement.execute rows = [] result.each { |r| rows << r } expect(rows).to eq([{ "1" => 1 }]) end it "should handle booleans" do stmt = @client.prepare('SELECT ? AS `true`, ? AS `false`') result = stmt.execute(true, false) expect(result.to_a).to eq(['true' => 1, 'false' => 0]) end it "should handle bignum but in int64_t" do stmt = @client.prepare('SELECT ? AS max, ? AS min') int64_max = (1 << 63) - 1 int64_min = -(1 << 63) result = stmt.execute(int64_max, int64_min) expect(result.to_a).to eq(['max' => int64_max, 'min' => int64_min]) end it "should handle bignum but beyond int64_t" do stmt = @client.prepare('SELECT ? AS max1, ? AS max2, ? AS max3, ? AS min1, ? AS min2, ? AS min3') int64_max1 = (1 << 63) int64_max2 = (1 << 64) - 1 int64_max3 = 1 << 64 int64_min1 = -(1 << 63) - 1 int64_min2 = -(1 << 64) + 1 int64_min3 = -0xC000000000000000 result = stmt.execute(int64_max1, int64_max2, int64_max3, int64_min1, int64_min2, int64_min3) expect(result.to_a).to eq(['max1' => int64_max1, 'max2' => int64_max2, 'max3' => int64_max3, 'min1' => int64_min1, 'min2' => int64_min2, 'min3' => int64_min3]) end it "should accept keyword arguments on statement execute" do stmt = @client.prepare 'SELECT 1 AS a' expect(stmt.execute(as: :hash).first).to eq("a" => 1) expect(stmt.execute(as: :array).first).to eq([1]) end it "should accept bind arguments and keyword arguments on statement execute" do stmt = @client.prepare 'SELECT ? AS a' expect(stmt.execute(1, as: :hash).first).to eq("a" => 1) expect(stmt.execute(1, as: :array).first).to eq([1]) end it "should keep its result after other query" do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int)' @client.query 'INSERT INTO mysql2_stmt_q (a) VALUES (1), (2)' stmt = @client.prepare('SELECT a FROM mysql2_stmt_q WHERE a = ?') result1 = stmt.execute(1) result2 = stmt.execute(2) expect(result2.first).to eq("a" => 2) expect(result1.first).to eq("a" => 1) @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end it "should be reusable 1000 times" do statement = @client.prepare 'SELECT 1' 1000.times do result = statement.execute expect(result.to_a.length).to eq(1) end end it "should be reusable 10000 times" do statement = @client.prepare 'SELECT 1' 10000.times do result = statement.execute expect(result.to_a.length).to eq(1) end end it "should handle comparisons and likes" do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int, b varchar(10))' @client.query 'INSERT INTO mysql2_stmt_q (a, b) VALUES (1, "Hello"), (2, "World")' statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE a < ?' results = statement.execute(2) expect(results.first).to eq("a" => 1, "b" => "Hello") statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE b LIKE ?' results = statement.execute('%orld') expect(results.first).to eq("a" => 2, "b" => "World") @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end it "should select dates" do statement = @client.prepare 'SELECT NOW()' result = statement.execute expect(result.first.first[1]).to be_an_instance_of(Time) end it "should prepare Date values" do now = Date.today statement = @client.prepare('SELECT ? AS a') result = statement.execute(now) expect(result.first['a'].to_s).to eql(now.strftime('%F')) end it "should prepare Time values with microseconds" do now = Time.now statement = @client.prepare('SELECT ? AS a') result = statement.execute(now) # microseconds is six digits after the decimal, but only test on 5 significant figures expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) end it "should prepare DateTime values with microseconds" do now = DateTime.now statement = @client.prepare('SELECT ? AS a') result = statement.execute(now) # microseconds is six digits after the decimal, but only test on 5 significant figures expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z')) end it "should tell us about the fields" do statement = @client.prepare 'SELECT 1 as foo, 2' statement.execute list = statement.fields expect(list.length).to eq(2) expect(list.first).to eq('foo') expect(list[1]).to eq('2') end it "should handle as a decimal binding a BigDecimal" do stmt = @client.prepare('SELECT ? AS decimal_test') test_result = stmt.execute(BigDecimal("123.45")).first expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal) expect(test_result['decimal_test']).to eql(123.45) end it "should update a DECIMAL value passing a BigDecimal" do @client.query 'USE test' @client.query 'DROP TABLE IF EXISTS mysql2_stmt_decimal_test' @client.query 'CREATE TABLE mysql2_stmt_decimal_test (decimal_test DECIMAL(10,3))' @client.prepare("INSERT INTO mysql2_stmt_decimal_test VALUES (?)").execute(BigDecimal("123.45")) test_result = @client.query("SELECT * FROM mysql2_stmt_decimal_test").first expect(test_result['decimal_test']).to eql(123.45) end it "should warn but still work if cache_rows is set to false" do statement = @client.prepare 'SELECT 1' result = nil expect { result = statement.execute(cache_rows: false).to_a }.to output(/:cache_rows is forced for prepared statements/).to_stderr expect(result.length).to eq(1) end context "utf8_db" do before(:each) do @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") @client.query("CREATE DATABASE test_mysql2_stmt_utf8") @client.query("USE test_mysql2_stmt_utf8") @client.query("CREATE TABLE テーブル (整数 int, 文字列 varchar(32)) charset=utf8") @client.query("INSERT INTO テーブル (整数, 文字列) VALUES (1, 'イチ'), (2, '弐'), (3, 'さん')") end after(:each) do @client.query("DROP DATABASE test_mysql2_stmt_utf8") end it "should be able to retrieve utf8 field names correctly" do stmt = @client.prepare 'SELECT * FROM `テーブル`' expect(stmt.fields).to eq(%w[整数 文字列]) result = stmt.execute expect(result.to_a).to eq([{ "整数" => 1, "文字列" => "イチ" }, { "整数" => 2, "文字列" => "弐" }, { "整数" => 3, "文字列" => "さん" }]) end it "should be able to retrieve utf8 param query correctly" do stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' expect(stmt.param_count).to eq(1) result = stmt.execute 'イチ' expect(result.to_a).to eq([{ "整数" => 1 }]) end it "should be able to retrieve query with param in different encoding correctly" do stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' expect(stmt.param_count).to eq(1) param = 'イチ'.encode("EUC-JP") result = stmt.execute param expect(result.to_a).to eq([{ "整数" => 1 }]) end end context "streaming result" do it "should be able to stream query result" do n = 1 stmt = @client.prepare("SELECT 1 UNION SELECT 2") stmt.execute(stream: true, cache_rows: false, as: :array).each do |r| case n when 1 expect(r).to eq([1]) when 2 expect(r).to eq([2]) else violated "returned more than two rows" end n += 1 end end end context "#each" do # note: The current impl. of prepared statement requires results to be cached on #execute except for streaming queries # The drawback of this is that args of Result#each is ignored... it "should yield rows as hash's" do @result = @client.prepare("SELECT 1").execute @result.each do |row| expect(row).to be_an_instance_of(Hash) end end it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do @result = @client.prepare("SELECT 1").execute(symbolize_keys: true) @result.each do |row| expect(row.keys.first).to be_an_instance_of(Symbol) end end it "should be able to return results as an array" do @result = @client.prepare("SELECT 1").execute(as: :array) @result.each do |row| expect(row).to be_an_instance_of(Array) end end it "should cache previously yielded results by default" do @result = @client.prepare("SELECT 1").execute expect(@result.first.object_id).to eql(@result.first.object_id) end it "should yield different value for #first if streaming" do result = @client.prepare("SELECT 1 UNION SELECT 2").execute(stream: true, cache_rows: true) expect(result.first).not_to eql(result.first) end it "should yield the same value for #first if streaming is disabled" do result = @client.prepare("SELECT 1 UNION SELECT 2").execute(stream: false) expect(result.first).to eql(result.first) end it "should throw an exception if we try to iterate twice when streaming is enabled" do result = @client.prepare("SELECT 1 UNION SELECT 2").execute(stream: true, cache_rows: false) expect do result.each {} result.each {} end.to raise_exception(Mysql2::Error) end end context "#fields" do it "method should exist" do stmt = @client.prepare("SELECT 1") expect(stmt).to respond_to(:fields) end it "should return an array of field names in proper order" do stmt = @client.prepare("SELECT 'a', 'b', 'c'") expect(stmt.fields).to eql(%w[a b c]) end it "should return nil for statement with no result fields" do stmt = @client.prepare("INSERT INTO mysql2_test () VALUES ()") expect(stmt.fields).to eql(nil) end end context "row data type mapping" do let(:test_result) { @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute.first } it "should return nil for a NULL value" do expect(test_result['null_test']).to be_an_instance_of(NilClass) expect(test_result['null_test']).to eql(nil) end it "should return String for a BIT(64) value" do expect(test_result['bit_test']).to be_an_instance_of(String) expect(test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do expect(test_result['single_bit_test']).to be_an_instance_of(String) expect(test_result['single_bit_test']).to eql("\001") end it "should return Fixnum for a TINYINT value" do expect(num_classes).to include(test_result['tiny_int_test'].class) expect(test_result['tiny_int_test']).to eql(1) end context "cast booleans for TINYINT if :cast_booleans is enabled" do # rubocop:disable Style/Semicolon let(:id1) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; @client.last_id } let(:id2) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; @client.last_id } let(:id3) { @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; @client.last_id } # rubocop:enable Style/Semicolon after do @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" end it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do query = @client.prepare 'SELECT bool_cast_test FROM mysql2_test WHERE id = ?' result1 = query.execute id1, cast_booleans: true result2 = query.execute id2, cast_booleans: true result3 = query.execute id3, cast_booleans: true expect(result1.first['bool_cast_test']).to be true expect(result2.first['bool_cast_test']).to be false expect(result3.first['bool_cast_test']).to be true end end context "cast booleans for BIT(1) if :cast_booleans is enabled" do # rubocop:disable Style/Semicolon let(:id1) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; @client.last_id } let(:id2) { @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; @client.last_id } # rubocop:enable Style/Semicolon after do @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" end it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do query = @client.prepare 'SELECT single_bit_test FROM mysql2_test WHERE id = ?' result1 = query.execute id1, cast_booleans: true result2 = query.execute id2, cast_booleans: true expect(result1.first['single_bit_test']).to be true expect(result2.first['single_bit_test']).to be false end end it "should return Fixnum for a SMALLINT value" do expect(num_classes).to include(test_result['small_int_test'].class) expect(test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do expect(num_classes).to include(test_result['medium_int_test'].class) expect(test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do expect(num_classes).to include(test_result['int_test'].class) expect(test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do expect(num_classes).to include(test_result['big_int_test'].class) expect(test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do expect(num_classes).to include(test_result['year_test'].class) expect(test_result['year_test']).to eql(2009) end it "should return BigDecimal for a DECIMAL value" do expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal) expect(test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do expect(test_result['float_test']).to be_an_instance_of(Float) expect(test_result['float_test']).to be_within(1e-5).of(10.3) end it "should return Float for a DOUBLE value" do expect(test_result['double_test']).to be_an_instance_of(Float) expect(test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do expect(test_result['date_time_test']).to be_an_instance_of(Time) expect(test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time when timestamp is < 1901-12-13 20:45:52" do r = @client.prepare("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test").execute expect(r.first['test']).to be_an_instance_of(Time) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute expect(r.first['test']).to be_an_instance_of(Time) end it "should return Time for a TIMESTAMP value when within the supported range" do expect(test_result['timestamp_test']).to be_an_instance_of(Time) expect(test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do expect(test_result['time_test']).to be_an_instance_of(Time) expect(test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do expect(test_result['date_test']).to be_an_instance_of(Date) expect(test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do expect(test_result['enum_test']).to be_an_instance_of(String) expect(test_result['enum_test']).to eql('val1') end it "should raise an error given an invalid DATETIME" do expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") end context "string encoding for ENUM values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::US_ASCII) end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a SET value" do expect(test_result['set_test']).to be_an_instance_of(String) expect(test_result['set_test']).to eql('val1,val2') end context "string encoding for SET values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::UTF_8) client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::US_ASCII) end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a BINARY value" do expect(test_result['binary_test']).to be_an_instance_of(String) expect(test_result['binary_test']).to eql("test#{"\000" * 6}") end context "string encoding for BINARY values" do it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end end { 'char_test' => 'CHAR', 'varchar_test' => 'VARCHAR', 'varbinary_test' => 'VARBINARY', 'tiny_blob_test' => 'TINYBLOB', 'tiny_text_test' => 'TINYTEXT', 'blob_test' => 'BLOB', 'text_test' => 'TEXT', 'medium_blob_test' => 'MEDIUMBLOB', 'medium_text_test' => 'MEDIUMTEXT', 'long_blob_test' => 'LONGBLOB', 'long_text_test' => 'LONGTEXT', }.each do |field, type| it "should return a String for #{type}" do expect(test_result[field]).to be_an_instance_of(String) expect(test_result[field]).to eql("test") end context "string encoding for #{type} values" do if %w[VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB].include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end else it "should default to utf-8 if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::UTF_8) client2 = new_client(encoding: 'ascii') result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::US_ASCII) end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end end end end end end context 'last_id' do before(:each) do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))' end after(:each) do @client.query 'DROP TABLE lastIdTest' end it 'should return last insert id' do stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' expect(stmt.last_id).to eq 0 stmt.execute 1 expect(stmt.last_id).to eq 1 end it 'should handle bigint ids' do stmt = @client.prepare 'INSERT INTO lastIdTest (id, blah) VALUES (?, ?)' stmt.execute 5000000000, 5000 expect(stmt.last_id).to eql(5000000000) stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' stmt.execute 5001 expect(stmt.last_id).to eql(5000000001) end end context 'affected_rows' do before :each do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))' end after :each do @client.query 'DROP TABLE lastIdTest' end it 'should return number of rows affected by an insert' do stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' stmt.execute 1 expect(stmt.affected_rows).to eq 1 end it 'should return number of rows affected by an update' do stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' stmt.execute 1 expect(stmt.affected_rows).to eq 1 stmt.execute 2 expect(stmt.affected_rows).to eq 1 stmt = @client.prepare 'UPDATE lastIdTest SET blah=? WHERE blah=?' stmt.execute 0, 1 expect(stmt.affected_rows).to eq 1 end it 'should return number of rows affected by a delete' do stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' stmt.execute 1 expect(stmt.affected_rows).to eq 1 stmt.execute 2 expect(stmt.affected_rows).to eq 1 stmt = @client.prepare 'DELETE FROM lastIdTest WHERE blah=?' stmt.execute 1 expect(stmt.affected_rows).to eq 1 end end context 'close' do it 'should free server resources' do stmt = @client.prepare 'SELECT 1' GC.disable expect { stmt.close }.to change(&method(:stmt_count)).by(-1) GC.enable end it 'should raise an error on subsequent execution' do stmt = @client.prepare 'SELECT 1' stmt.close expect { stmt.execute }.to raise_error(Mysql2::Error, /Invalid statement handle/) end end end mysql2-0.5.3/spec/rcov.opts000066400000000000000000000001011356743356300155670ustar00rootroot00000000000000--exclude spec,gem --text-summary --sort coverage --sort-reverse mysql2-0.5.3/spec/spec_helper.rb000066400000000000000000000070721356743356300165430ustar00rootroot00000000000000require 'rspec' require 'mysql2' require 'timeout' require 'yaml' DatabaseCredentials = YAML.load_file('spec/configuration.yml') RSpec.configure do |config| config.disable_monkey_patching! def with_internal_encoding(encoding) old_enc = Encoding.default_internal old_verbose = $VERBOSE $VERBOSE = nil Encoding.default_internal = encoding $VERBOSE = old_verbose yield ensure $VERBOSE = nil Encoding.default_internal = old_enc $VERBOSE = old_verbose end def new_client(option_overrides = {}) client = Mysql2::Client.new(DatabaseCredentials['root'].merge(option_overrides)) @clients ||= [] @clients << client return client unless block_given? begin yield client ensure client.close @clients.delete(client) end end def num_classes # rubocop:disable Lint/UnifiedInteger 0.class == Integer ? [Integer] : [Fixnum, Bignum] # rubocop:enable Lint/UnifiedInteger end # Use monotonic time if possible (ruby >= 2.1.0) if defined?(Process::CLOCK_MONOTONIC) def clock_time Process.clock_gettime Process::CLOCK_MONOTONIC end else def clock_time Time.now.to_f end end config.before :each do @client = new_client end config.after :each do @clients.each(&:close) end config.before(:all) do new_client do |client| client.query %[ CREATE TABLE IF NOT EXISTS mysql2_test ( id MEDIUMINT NOT NULL AUTO_INCREMENT, null_test VARCHAR(10), bit_test BIT(64), single_bit_test BIT(1), tiny_int_test TINYINT, bool_cast_test TINYINT(1), small_int_test SMALLINT, medium_int_test MEDIUMINT, int_test INT, big_int_test BIGINT, float_test FLOAT(10,3), float_zero_test FLOAT(10,3), double_test DOUBLE(10,3), decimal_test DECIMAL(10,3), decimal_zero_test DECIMAL(10,3), date_test DATE, date_time_test DATETIME, timestamp_test TIMESTAMP, time_test TIME, year_test YEAR(4), char_test CHAR(10), varchar_test VARCHAR(10), binary_test BINARY(10), varbinary_test VARBINARY(10), tiny_blob_test TINYBLOB, tiny_text_test TINYTEXT, blob_test BLOB, text_test TEXT, medium_blob_test MEDIUMBLOB, medium_text_test MEDIUMTEXT, long_blob_test LONGBLOB, long_text_test LONGTEXT, enum_test ENUM('val1', 'val2'), set_test SET('val1', 'val2'), PRIMARY KEY (id) ) ] client.query "DELETE FROM mysql2_test;" client.query %[ INSERT INTO mysql2_test ( null_test, bit_test, single_bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test, float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test, year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test, tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test, long_blob_test, long_text_test, enum_test, set_test ) VALUES ( NULL, b'101', b'1', 1, 1, 10, 10, 10, 10, 10.3, 0, 10.3, 10.3, 0, '2010-4-4', '2010-4-4 11:44:00', '2010-4-4 11:44:00', '11:44:00', 2009, "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", 'val1', 'val1,val2' ) ] end end end mysql2-0.5.3/spec/ssl/000077500000000000000000000000001356743356300145205ustar00rootroot00000000000000mysql2-0.5.3/spec/ssl/ca-cert.pem000066400000000000000000000017311356743356300165430ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICqjCCAZICCQDbDS+Z2mpWkDANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxj YV9teXNxbDJnZW0wHhcNMTUwOTA5MDQ1NzIxWhcNMjUwNzE4MDQ1NzIxWjAXMRUw EwYDVQQDDAxjYV9teXNxbDJnZW0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQDFnpc22lPFtdPELsIffsDt8cD2Hkt47nGMcKQ9n4U98yAg+fodipyP1Bn0 2OeaONqpttJIET7HxlGrtugPtV/O8XZHlhfAHrRUDMFZJhgnnqK+c/7fRGeB0Eqw ljBlRD/dDL3bFq5hVBC9QsGi5k03r+xLPKm5ccAr4WtofcoKXqEbSO6koTSrsGG5 7inlldM2AVzrY2kXbe0jAyNvYmDL2ycN8G2wObogPWDfITQRhOxfkzKIQiEhQF2Y /DlhT7IbIarBIm6abf6JxZ6/Sm5XyVNEWOnryXM6rKyVeGktCxLHNmxx5eKYs440 8hNgURa8pB+aZaiokkwhM1+jmE83AgMBAAEwDQYJKoZIhvcNAQELBQADggEBACrQ umqygXkkbff5Jqf6AYi30U3c+byX+IButRKXN9Ete2LPcT76o/snS9Lexf3KQsIy a2Tcc9adak7pBf7FgHdiZkWiQp3MDgx2gJu6Uu6TNzfT8jy2JrHyBWw4ydEvhyA8 cgelTHSaudafKeQgU4KYc8bqafYFILkWxPzgtwitENIDfx/SHt65BWaQZjYJlFou zPZXeoT3lAwKGYqIvwPvBTC23cXg/Swt/mcKe3/Xxjx85Dw/9vi6a9+VQwlOojgd w2o07xkIcJcI0Oxyp3mD0U5wAmBQGI76Yi9ZDROHF65KEXfQ3tYKl2vR7CXpcJ4+ 7+fVsE8+dADJdZIiuaA= -----END CERTIFICATE----- mysql2-0.5.3/spec/ssl/ca-key.pem000066400000000000000000000032171356743356300163770ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAxZ6XNtpTxbXTxC7CH37A7fHA9h5LeO5xjHCkPZ+FPfMgIPn6 HYqcj9QZ9NjnmjjaqbbSSBE+x8ZRq7boD7VfzvF2R5YXwB60VAzBWSYYJ56ivnP+ 30RngdBKsJYwZUQ/3Qy92xauYVQQvULBouZNN6/sSzypuXHAK+FraH3KCl6hG0ju pKE0q7Bhue4p5ZXTNgFc62NpF23tIwMjb2Jgy9snDfBtsDm6ID1g3yE0EYTsX5My iEIhIUBdmPw5YU+yGyGqwSJumm3+icWev0puV8lTRFjp68lzOqyslXhpLQsSxzZs ceXimLOONPITYFEWvKQfmmWoqJJMITNfo5hPNwIDAQABAoIBAQClkmtFRQVdKCum Ojrg4nVIpv2x983qI3U1YobpLocXUWVA29BIAgOMqfuZXkYlu67Q9OEYCoLcJHf2 88dYqfD81OfxsHpzuAYESa+RPs6MG2hlQ5BuhcRnShnZ++vOXLFZRjynnEg8OY/Q 0makUmqt1pKWstvNCNYmrbYtFP87UXQCF06zkhM0cZJvVt0gPZGUituWI0uAoE51 U8+WSwD/T761BHM6BuMn56mfZkP5jeVIFl0iFha9rGR0Z6K8mVQAYQAUtUx9tN/3 a8fEOcYulq/9R5tMRWtsF8LD8DGQBNkY3e/WKDuZtLw2Dl3L09gxVH9DXCLiYU5d OG3JmqDpAoGBAP08yq143H4n6yGT9DC8YjaLgN0VoenK21CEqhwtGWipc/kbGooe /jaHl6bo9v1GOGlJieqSUqsXNltS7FOLhGFAQFwMYZ3V/h15Vx23Z+xkCCHIB6HH YJZqkQY7Jt86wXcaLU5j9fxM+BY+8Ets4bVhZN9Ai6AnlTz0+d8UJG+bAoGBAMfG efYrdjTKI5eK9aiVJyoh57BEPOsTsave2U+R8Q+fErQ0QD0UmbWgwYGgkPuDrFYT owg09EEz88KONv18VZ+mB1qfyQUoOL6rWIGxXC08upy2i9100PaBFiYlkLNoK7yJ bze0rFSiFclJJXZGzEaVvcEdKnXxfhttaJwQGK6VAoGBAOQEUvJzuwWU5/CqCdvA JCa84eEv00RxtZwAeDM6oIBO4+/O6cyoL3nmCTTu20YebjjPUHF4IxuOoREFz2lC XIY8ljbLpzG5N0BOu5Q0SkzdnTzdoZGXtm55se+MX2nsu7qERXsqIpl0rIVLUo53 kZwCABPNSGuCeKwUYNDukAg1AoGBALiHHSqEVKhIOn4FDgqM0uM49CA9t6NPyqI9 sq6r2GWcgpNPXDLPL3e0KGlK3gBkTLApbULsXt1HVpZT9HlJ+nD/0/UieHS6BUgh Txxkrgbe/GQ6vZBuEYJQFBxiQHlm9Fcu/zsOOMvn94W4edD5bkCYmfChtxHAYcKF 2cWlnJbNAoGAWMV4GIY2DYlztXdyMVuPwsjPcSPMmL8Nc2ATWYRfcoG0Zl0yvwPh 2VOu7Q/7bNF2LOe6lPe1hoeB6rT44IYZaWMo3ikY8xW9RztOLSv8E9uE1K9yq8OA P8QzXmr1Lga+hoEmMHc2biEJNeF6iAcAFfrHj9Sr7w5PC8g4A3PlCvU= -----END RSA PRIVATE KEY----- mysql2-0.5.3/spec/ssl/ca.cnf000066400000000000000000000011101356743356300155640ustar00rootroot00000000000000 [ ca ] # January 1, 2015 default_startdate = 2015010360000Z [ req ] distinguished_name = req_distinguished_name [ req_distinguished_name ] # If this isn't set, the error is error, no objects specified in config file commonName = Common Name (hostname, IP, or your name) countryName_default = US stateOrProvinceName_default = CA localityName_default = San Francisco 0.organizationName_default = mysql2_gem organizationalUnitName_default = Mysql2Gem emailAddress_default = mysql2gem@example.com commonName_default = ca_mysql2gem mysql2-0.5.3/spec/ssl/cert.cnf000066400000000000000000000011211356743356300161400ustar00rootroot00000000000000 [ ca ] # January 1, 2015 default_startdate = 2015010360000Z [ req ] distinguished_name = req_distinguished_name [ req_distinguished_name ] # If this isn't set, the error is error, no objects specified in config file commonName = Common Name (hostname, IP, or your name) countryName_default = US stateOrProvinceName_default = CA localityName_default = San Francisco 0.organizationName_default = mysql2_gem organizationalUnitName_default = Mysql2Gem emailAddress_default = mysql2gem@example.com commonName_default = mysql2gem.example.com mysql2-0.5.3/spec/ssl/client-cert.pem000066400000000000000000000017311356743356300174360ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy Z2VtMB4XDTE1MDkwOTA0NTcyMVoXDTI1MDcxODA0NTcyMVowIDEeMBwGA1UEAwwV bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEA1ZuBf1FVJqil7/LvnXqPd43ujo0xqbFy7QrqmM5U/UM3ggMCf2Gr2/Wo ZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMUuH+Uiv9UwmOFl9Em3FXQQ8SG7fV7651u AUskNgfEqoy+f+uvi1P155rHNDx7Yw6i+wwfpLGTU0boMnLL6cO/KcIbZlx4/2Lq r5sYbpIqhz46bbG+fIhvepruH9h7WVWqAibTqymYrA3T03O/HWTOqfq03gM7Oe3t JvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2Ajh7Yx9DQ1gqClTglbPFHNiWPcGACg+W 2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA2 qTbfgDm0IG8x1qP61ztT9F2WRwG7cp6qHT5oB5wDcOUFes9QJjeB8RoIkB+hRlqG J6/Tbxs49d7oKhOQ0UaTnfIKC5m0UFYFGc3lUcwxQyggOWx9XV5ZmGb48+RLFnDV Gfcs/hvfem6Xfpgzr8bGs2ZM9x1j9YnXNJVePmKwktjCPnXPOeHyxNZPA+CWHed/ dNg1IWuQnnp20LgNRARCTgR/ONAJNUfh2GqRLq2JOf0cyhNlsKQ3epkeUyc72knI oWVxPluQYvFHN+xif0FMGVLM8lz0b+6uPJDA2Km70B/iorMRVb0vbMeFrMmQ5UgM 4tplX52P2vb6JNnektfR -----END CERTIFICATE----- mysql2-0.5.3/spec/ssl/client-key.pem000066400000000000000000000032131356743356300172660ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA1ZuBf1FVJqil7/LvnXqPd43ujo0xqbFy7QrqmM5U/UM3ggMC f2Gr2/WoZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMUuH+Uiv9UwmOFl9Em3FXQQ8SG 7fV7651uAUskNgfEqoy+f+uvi1P155rHNDx7Yw6i+wwfpLGTU0boMnLL6cO/KcIb Zlx4/2Lqr5sYbpIqhz46bbG+fIhvepruH9h7WVWqAibTqymYrA3T03O/HWTOqfq0 3gM7Oe3tJvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2Ajh7Yx9DQ1gqClTglbPFHNiW PcGACg+W2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQIDAQABAoIBAEWhGjZZWctvQCAW bbtEv012a6P2LJEnMdJJM6253IRuC8MKnh7NxMq/qjOWK0OX+R+tQ0qt1Udk9H6U 92SAAHAkHaYCmHYywvtWm66gU+2Q34Gnp2AcHFfyinBLgTNHlvkNRe/G8QMWzFrB 3luNt57Tn5b8Hbh+1gpYW8pOF2BMgIsLRK+8b26TKUWrSCc/ZxOSY4wmrNybxkgr HGt27lwIN0cvJZbmQvHevNzzCn+bYoo2K1MQj34xHbZ2NLqKqFVlSJtr9+BHffAc fkcf+V+D+FkitUVkha9qXa02wtLzYSF+Q5Ef3kQQs6hs/HOdN16g17l9QC6Mk1vm a9yV5CECgYEA/9FglQmFimwBCOWEvjkZzoXFusuvRWRgAPU/1c9DAYRS2GfOkjlH RPAltczdXh4EQ0NkCqHH7JWgrdXGonKg4fcITumdwcYKV5QfmKBO4onAboEM0Wq7 wjifuga7npQhPnGvkXFDamVz5McQPObvV42VAUwk1N00gOYw/46ryLkCgYEA1cJv jHAq0DKlUGXKyZ+ixsogRpwTQvND/qUquSLgD/KgfeT+70AnsEF6DbVLKoaJ35CF ju83VYLfeBljq+E/lgmAyaChplORRXcu+xPQE4rbp0MbsoBOYGNWLFAw3twGsQf9 iuAtCVxij/hhj4FWRebYHMnV6Min2VPbZdASNnUCgYBIiX8gY3XJPTzB4ArWwWwu 4kGh6NWHEKIkQ2ZZYw615GZ1VGH/llw+EPYwaamvYUWGKRq55QvCat8Hy6EqOOSj jh99+MIxyszt7mNTLMmRdMvqyY7v5prcxJ+N6RDUM16FzUiiLgKWrbPCACv7iOP+ 6HeCyat77ElR73OfUz4kiQKBgH+2r9cEnU/PMp4ac1KLokGLOkV1srxpg9J89E2w 3JYqrGELlJV1i0DvnfDaxJIf1/hO7L09h537l3C2Gqry5X7LJrtQ0cQCYeVTFCrG 56cFa78/hSjdJ/bG4xGOx+QfKZBT6dQzpDTXkbva9s86w0T4a16n6LowSLi8NXVb H8aRAoGBAKzlt6deB+ASIrGH6mM0eLxF1OcNTB+rE4AJxoUyO1oAmCv9UeK3IzwP ohhmo/kEOSCVG6WE+6+r9mojcoHu3ZrobVKl59R7KMdzunMXqxZcXeTqjvqdTtV7 rWuEz/TKIe7o0Tx19XVGuNftyx2pLuspSAAbZ+YAQJtzmLzsGkss -----END RSA PRIVATE KEY----- mysql2-0.5.3/spec/ssl/client-req.pem000066400000000000000000000016131356743356300172670ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ZuBf1FVJqil7/LvnXqPd43u jo0xqbFy7QrqmM5U/UM3ggMCf2Gr2/WoZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMU uH+Uiv9UwmOFl9Em3FXQQ8SG7fV7651uAUskNgfEqoy+f+uvi1P155rHNDx7Yw6i +wwfpLGTU0boMnLL6cO/KcIbZlx4/2Lqr5sYbpIqhz46bbG+fIhvepruH9h7WVWq AibTqymYrA3T03O/HWTOqfq03gM7Oe3tJvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2 Ajh7Yx9DQ1gqClTglbPFHNiWPcGACg+W2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQID AQABoAAwDQYJKoZIhvcNAQELBQADggEBAB05YaBSyAKCgHfBWhpZ1+OOVp1anr2v TkStnqmNrNM2qBJXjfrythPTX4EJAt7+eNdH/6IVA93qKC/EUQVuMjgfMmMUaM+m 5pqfAo95w7vUY147U9nbC+EIo2u1KOVTNTgl45H372/1vCwTHZYu2atCk4tN3ueO 0O2XW89Kq94/7PDAExN2PhZdeATVX9dPNT+7ZUDNe8cuq9v0YCHy+2JN2WkplxcG kMyCE3YYLnd96YtWiS9DOUib3+b7FwyGe0dXeLVw1br3NZGCZrybyfmnAQfiouAF 9nMxKIpWFSx00ubGrUefOQqp6nuk27n+scgr4+d6dBXz9efEEvTbLKA= -----END CERTIFICATE REQUEST----- mysql2-0.5.3/spec/ssl/gen_certs.sh000066400000000000000000000032451356743356300170310ustar00rootroot00000000000000#!/usr/bin/env bash set -eux echo " [ ca ] # January 1, 2015 default_startdate = 2015010360000Z [ req ] distinguished_name = req_distinguished_name [ req_distinguished_name ] # If this isn't set, the error is "error, no objects specified in config file" commonName = Common Name (hostname, IP, or your name) countryName_default = US stateOrProvinceName_default = CA localityName_default = San Francisco 0.organizationName_default = mysql2_gem organizationalUnitName_default = Mysql2Gem emailAddress_default = mysql2gem@example.com " | tee ca.cnf cert.cnf # The client and server certs must have a diferent common name than the CA # to avoid "SSL connection error: error:00000001:lib(0):func(0):reason(1)" echo " commonName_default = ca_mysql2gem " >> ca.cnf echo " commonName_default = mysql2gem.example.com " >> cert.cnf # Generate a set of certificates openssl genrsa -out ca-key.pem 2048 openssl req -new -x509 -nodes -days 3600 -key ca-key.pem -out ca-cert.pem -batch -config ca.cnf openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-server-key.pem -out server-req.pem -batch -config cert.cnf openssl x509 -req -in server-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-client-key.pem -out client-req.pem -batch -config cert.cnf openssl x509 -req -in client-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem # Convert format from PKCS#8 to PKCS#1 openssl rsa -in pkcs8-server-key.pem -out server-key.pem openssl rsa -in pkcs8-client-key.pem -out client-key.pem echo "done" mysql2-0.5.3/spec/ssl/pkcs8-client-key.pem000066400000000000000000000032501356743356300203150ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVm4F/UVUmqKXv 8u+deo93je6OjTGpsXLtCuqYzlT9QzeCAwJ/Yavb9ahkkY9OTU0VqJTIzmaFKWCL T9IY8Sn0kxS4f5SK/1TCY4WX0SbcVdBDxIbt9XvrnW4BSyQ2B8SqjL5/66+LU/Xn msc0PHtjDqL7DB+ksZNTRugycsvpw78pwhtmXHj/YuqvmxhukiqHPjptsb58iG96 mu4f2HtZVaoCJtOrKZisDdPTc78dZM6p+rTeAzs57e0m8lupfYt5xC+LZJtChfxz YytDpfvEN7YCOHtjH0NDWCoKVOCVs8Uc2JY9wYAKD5bam0Jj9DZJ0/mHV22PlLDl XAvd28KNAgMBAAECggEARaEaNllZy29AIBZtu0S/TXZro/YskScx0kkzrbnchG4L wwqeHs3Eyr+qM5YrQ5f5H61DSq3VR2T0fpT3ZIAAcCQdpgKYdjLC+1abrqBT7ZDf gaenYBwcV/KKcEuBM0eW+Q1F78bxAxbMWsHeW423ntOflvwduH7WClhbyk4XYEyA iwtEr7xvbpMpRatIJz9nE5JjjCas3JvGSCsca3buXAg3Ry8lluZC8d683PMKf5ti ijYrUxCPfjEdtnY0uoqoVWVIm2v34Ed98Bx+Rx/5X4P4WSK1RWSFr2pdrTbC0vNh IX5DkR/eRBCzqGz8c503XqDXuX1ALoyTW+Zr3JXkIQKBgQD/0WCVCYWKbAEI5YS+ ORnOhcW6y69FZGAA9T/Vz0MBhFLYZ86SOUdE8CW1zN1eHgRDQ2QKocfslaCt1cai cqDh9whO6Z3BxgpXlB+YoE7iicBugQzRarvCOJ+6BruelCE+ca+RcUNqZXPkxxA8 5u9XjZUBTCTU3TSA5jD/jqvIuQKBgQDVwm+McCrQMqVQZcrJn6LGyiBGnBNC80P+ pSq5IuAP8qB95P7vQCewQXoNtUsqhonfkIWO7zdVgt94GWOr4T+WCYDJoKGmU5FF dy77E9ATitunQxuygE5gY1YsUDDe3AaxB/2K4C0JXGKP+GGPgVZF5tgcydXoyKfZ U9tl0BI2dQKBgEiJfyBjdck9PMHgCtbBbC7iQaHo1YcQoiRDZlljDrXkZnVUYf+W XD4Q9jBpqa9hRYYpGrnlC8Jq3wfLoSo45KOOH334wjHKzO3uY1MsyZF0y+rJju/m mtzEn43pENQzXoXNSKIuApats8IAK/uI4/7od4LJq3vsSVHvc59TPiSJAoGAf7av 1wSdT88ynhpzUouiQYs6RXWyvGmD0nz0TbDcliqsYQuUlXWLQO+d8NrEkh/X+E7s vT2HnfuXcLYaqvLlfssmu1DRxAJh5VMUKsbnpwVrvz+FKN0n9sbjEY7H5B8pkFPp 1DOkNNeRu9r2zzrDRPhrXqfoujBIuLw1dVsfxpECgYEArOW3p14H4BIisYfqYzR4 vEXU5w1MH6sTgAnGhTI7WgCYK/1R4rcjPA+iGGaj+QQ5IJUbpYT7r6v2aiNyge7d muhtUqXn1Hsox3O6cxerFlxd5OqO+p1O1Xuta4TP9Moh7ujRPHX1dUa41+3LHaku 6ylIABtn5gBAm3OYvOwaSyw= -----END PRIVATE KEY----- mysql2-0.5.3/spec/ssl/pkcs8-server-key.pem000066400000000000000000000032541356743356300203510ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCtyxWYD01zMQby RwzqzrGypf/x/rhNUT70HChE9TpojPmCe9e38eSvz1+0kGWb4VGKB705mPsY8yry IO/T2StZI11Ddh7qGbiXJFFeU3s4lrcis3qZTZNvui8hPVmGRjn6TRe6FkX0B/lF Ip0TV3X2aSAIU7oZpn6qmn4WZFFKgy/EVuDzeaf/RvTrfhnOhbBwhX+93WXt50fK 73YFBEUJ4hOO0VoB34O/555OHY4/FRjTWTusL2LgxcTcE/GRGk9BPBN4cfhmlJHB hTSiXkGOyLGv8kbrYffockDhkiPJbMSdWIRV+ZpQX3YXf2y3lGOGbWptgMi3ttzh 6RaKmoZJAgMBAAECggEAFhAyLZvDuVwABcH/Yc/bv1JTq+UqgKZP1627bwWy5JMB Gg+e0ztiTO+GtuWeAKwaLevNmgJR3lkAmryTtdFcL3TN4kKcqhuZ05ZIvjDa89Qu a7ldVxkCHq0ETrP7KZDAy4X9/SHWv6RDgQNj7ZCs6RtvdZ8rgRYh/oaeezlBGLRZ OIeWWHTYk8CMhu+tzR+BHZdXUEHo/sZHwmpdhQQ0HtXs87Eo51waVwHL0BgXT+ak v7pbhe6sGgNgoix59Lu9WbazxXIyRYDMkZfg0fCauRMG05Wvaeuonk/zlC88Eg3M yQIW8+Boe7M2yI+egRYeM2TCBnr2B7n8fTG/xQEKAQKBgQDe+hgYWboh6ejCarAB UOd90D//aLtbyu0HBQ6YNiPPUvVmEMfDVscxw6BXTtRWpHYSxsPV+IpUM73St/cu RmrP1DOTT4sJJf9lvNg35+OceGHRe0hVUU9mVqBMbLz5NOPwxWlq4eSgK+cAKq3g 5lp47IFxX0R+g+dvk7YhSy4+rwKBgQDHiEBLjZahY100PffLygIwvwbTyoK9UMjS On1sGqOf9aF79zQIIRhEmPN/je9jqnAcf8+ivS1dLYoUboosm7215349Uw6S9C43 RIn1iLZRGO7Beq3KOrdp4NFnh3QsgoH4jp1gp4w6QyBC2uYBdQXA9GpUR2+Y45BI KVSHQV4IhwKBgQDQLig48+04pK9QdVOGpwa7DKfzytDCzx+mIi6SJlogw4+ij6Ay 3N51s/QMD+loS3yB41oMeFSOcRCVoHUDm3M2PyU4MFfbXsKpNjuZVsPH3w1VDAlo vtWm8tIPCKcW9S6sKWRXCjju4o52NWLKS8fEhuwD8bJ9fKGkJwEw7IRsuQKBgQCU D4feSI+I7InB9WXGI/1iHK49RJ2lS6fpUBu3t0DJtuSAb5x9l8lBRdoSQclsxJFy pGj4Erbx2JQIu0nu9hZdQA1OBi7fXzBYNJTGzQ60uPKaQaVqVg26FGhvEXVkfedi ALnJeiq1JRBwa6yXUjXVy8iHB4dJBTwQQBMIVronSwKBgQDOPYklqdzRv+u4XukW WsvK7GLj9huwdH0NI5gouGMb5OTgfVo66+urq4qYcHRN4vcdxbP9KoJPBvKhdx+D A36I2ERs//92gIz6lcNsqmdAzHAX1yzp4IyFGsjzaKibnQrft7hb9Kfg9VypBb/G 31x7mm+gZm4RP3jbcBPDn0xU/w== -----END PRIVATE KEY----- mysql2-0.5.3/spec/ssl/server-cert.pem000066400000000000000000000017311356743356300174660ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy Z2VtMB4XDTE1MDkwOTA0NTcyMVoXDTI1MDcxODA0NTcyMVowIDEeMBwGA1UEAwwV bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEArcsVmA9NczEG8kcM6s6xsqX/8f64TVE+9BwoRPU6aIz5gnvXt/Hkr89f tJBlm+FRige9OZj7GPMq8iDv09krWSNdQ3Ye6hm4lyRRXlN7OJa3IrN6mU2Tb7ov IT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkgCFO6GaZ+qpp+FmRRSoMvxFbg83mn/0b0 634ZzoWwcIV/vd1l7edHyu92BQRFCeITjtFaAd+Dv+eeTh2OPxUY01k7rC9i4MXE 3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsixr/JG62H36HJA4ZIjyWzEnViEVfmaUF92 F39st5Rjhm1qbYDIt7bc4ekWipqGSQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBB PtRuVmOHiRPH3PmsZPtkgVagznojO+r0GDTxys5bxof8+HcokL6gbb4bRqzUQTdC 98RTsuFPnd/I8FbbaL+UyeXeKjjOEBPFyllOdykmpd67mHCA89574y7Ib7lkvtr1 nQFMbeKmcz4uLm1vAi/aOdAIA2de4yJU2XnOkVLDgYnQxpWR451WKqt/FtiuCzpQ E3peEemM2XVxvCMmfBAaroAyLYFrWOhNA7UoWVsubp7Ypf7SYuOh+sU6JLsYSadQ XVqgvIKG4deUpdl7oUBRz79spAi1OpHWiNmW3b+8nKJoHTfYkKzCebeLdI++xSjX jXNryv5xK88LFIPKL/7e -----END CERTIFICATE----- mysql2-0.5.3/spec/ssl/server-key.pem000066400000000000000000000032171356743356300173220ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEArcsVmA9NczEG8kcM6s6xsqX/8f64TVE+9BwoRPU6aIz5gnvX t/Hkr89ftJBlm+FRige9OZj7GPMq8iDv09krWSNdQ3Ye6hm4lyRRXlN7OJa3IrN6 mU2Tb7ovIT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkgCFO6GaZ+qpp+FmRRSoMvxFbg 83mn/0b0634ZzoWwcIV/vd1l7edHyu92BQRFCeITjtFaAd+Dv+eeTh2OPxUY01k7 rC9i4MXE3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsixr/JG62H36HJA4ZIjyWzEnViE VfmaUF92F39st5Rjhm1qbYDIt7bc4ekWipqGSQIDAQABAoIBABYQMi2bw7lcAAXB /2HP279SU6vlKoCmT9etu28FsuSTARoPntM7YkzvhrblngCsGi3rzZoCUd5ZAJq8 k7XRXC90zeJCnKobmdOWSL4w2vPULmu5XVcZAh6tBE6z+ymQwMuF/f0h1r+kQ4ED Y+2QrOkbb3WfK4EWIf6Gnns5QRi0WTiHllh02JPAjIbvrc0fgR2XV1BB6P7GR8Jq XYUENB7V7POxKOdcGlcBy9AYF0/mpL+6W4XurBoDYKIsefS7vVm2s8VyMkWAzJGX 4NHwmrkTBtOVr2nrqJ5P85QvPBINzMkCFvPgaHuzNsiPnoEWHjNkwgZ69ge5/H0x v8UBCgECgYEA3voYGFm6IenowmqwAVDnfdA//2i7W8rtBwUOmDYjz1L1ZhDHw1bH McOgV07UVqR2EsbD1fiKVDO90rf3LkZqz9Qzk0+LCSX/ZbzYN+fjnHhh0XtIVVFP ZlagTGy8+TTj8MVpauHkoCvnACqt4OZaeOyBcV9EfoPnb5O2IUsuPq8CgYEAx4hA S42WoWNdND33y8oCML8G08qCvVDI0jp9bBqjn/Whe/c0CCEYRJjzf43vY6pwHH/P or0tXS2KFG6KLJu9ted+PVMOkvQuN0SJ9Yi2URjuwXqtyjq3aeDRZ4d0LIKB+I6d YKeMOkMgQtrmAXUFwPRqVEdvmOOQSClUh0FeCIcCgYEA0C4oOPPtOKSvUHVThqcG uwyn88rQws8fpiIukiZaIMOPoo+gMtzedbP0DA/paEt8geNaDHhUjnEQlaB1A5tz Nj8lODBX217CqTY7mVbDx98NVQwJaL7VpvLSDwinFvUurClkVwo47uKOdjViykvH xIbsA/GyfXyhpCcBMOyEbLkCgYEAlA+H3kiPiOyJwfVlxiP9YhyuPUSdpUun6VAb t7dAybbkgG+cfZfJQUXaEkHJbMSRcqRo+BK28diUCLtJ7vYWXUANTgYu318wWDSU xs0OtLjymkGlalYNuhRobxF1ZH3nYgC5yXoqtSUQcGusl1I11cvIhweHSQU8EEAT CFa6J0sCgYEAzj2JJanc0b/ruF7pFlrLyuxi4/YbsHR9DSOYKLhjG+Tk4H1aOuvr q6uKmHB0TeL3HcWz/SqCTwbyoXcfgwN+iNhEbP//doCM+pXDbKpnQMxwF9cs6eCM hRrI82iom50K37e4W/Sn4PVcqQW/xt9ce5pvoGZuET9423ATw59MVP8= -----END RSA PRIVATE KEY----- mysql2-0.5.3/spec/ssl/server-req.pem000066400000000000000000000016131356743356300173170ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArcsVmA9NczEG8kcM6s6xsqX/ 8f64TVE+9BwoRPU6aIz5gnvXt/Hkr89ftJBlm+FRige9OZj7GPMq8iDv09krWSNd Q3Ye6hm4lyRRXlN7OJa3IrN6mU2Tb7ovIT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkg CFO6GaZ+qpp+FmRRSoMvxFbg83mn/0b0634ZzoWwcIV/vd1l7edHyu92BQRFCeIT jtFaAd+Dv+eeTh2OPxUY01k7rC9i4MXE3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsix r/JG62H36HJA4ZIjyWzEnViEVfmaUF92F39st5Rjhm1qbYDIt7bc4ekWipqGSQID AQABoAAwDQYJKoZIhvcNAQELBQADggEBAInWIFsq14b8PhDroMMvi1ma30xyQGjg KObIxakwXkliSxmCdVqV4+MV6w8hK3z0q7H+NzRFByjo1PnU8BCIa058m5uvbjmM wGQvpcxmpm1p8VKKoeTqvE8OelbrqHrmyNIq7E/S3UZelVt+HOIPJOOs/aqEzaEg VL1u+4kCMbHM2rG8dii060oZ5i/gUtMn2TQWcNjSQBvaVztW5FOL74bYkoq0zIwd MFl2BoYyAnERJEcBmh1f+D7MuxPaqTUKjUmfDbHCMAAyTS1FHr9AnIN0/C2Mxywl H/zL9/DkfR53KZjITkf2gTH5D/N5oDUwmgCg6UZ0MeTOP27jvgcvb/k= -----END CERTIFICATE REQUEST----- mysql2-0.5.3/spec/test_data000066400000000000000000000000171356743356300156100ustar00rootroot00000000000000\N Hello World mysql2-0.5.3/support/000077500000000000000000000000001356743356300145015ustar00rootroot00000000000000mysql2-0.5.3/support/5072E1F5.asc000066400000000000000000000662601356743356300161610ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 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 roUTAKClYAhZuX2nUNwH4vlEJQHDqYa5yQ== =ghXk -----END PGP PUBLIC KEY BLOCK----- mysql2-0.5.3/support/libmysql.def000066400000000000000000000107221356743356300170170ustar00rootroot00000000000000; MySQL's Connector/C ships with a libmysql.dll main library and libmysql.lib ; interface library. However, the interface library is not linkable by MinGW. ; ; At compile time, we generate a libmysql.a interface library with dlltool.exe. ; ; This def file can be re-generated using the reimp.exe or gendef.exe tools. ; LIBRARY libmysql.dll EXPORTS mysql_affected_rows mysql_affected_rows@4 mysql_change_user mysql_change_user@16 mysql_character_set_name mysql_character_set_name@4 mysql_close mysql_close@4 mysql_data_seek mysql_data_seek@12 mysql_debug mysql_debug@4 mysql_dump_debug_info mysql_dump_debug_info@4 mysql_eof mysql_eof@4 mysql_errno mysql_errno@4 mysql_error mysql_error@4 mysql_escape_string mysql_escape_string@12 mysql_fetch_field mysql_fetch_field@4 mysql_fetch_field_direct mysql_fetch_field_direct@8 mysql_fetch_fields mysql_fetch_fields@4 mysql_fetch_lengths mysql_fetch_lengths@4 mysql_fetch_row mysql_fetch_row@4 mysql_field_count mysql_field_count@4 mysql_field_seek mysql_field_seek@8 mysql_field_tell mysql_field_tell@4 mysql_free_result mysql_free_result@4 mysql_get_client_info mysql_get_client_info@0 mysql_get_client_version mysql_get_client_version@0 mysql_get_host_info mysql_get_host_info@4 mysql_get_option mysql_get_option@12 mysql_get_proto_info mysql_get_proto_info@4 mysql_get_server_info mysql_get_server_info@4 mysql_get_server_version mysql_get_server_version@4 mysql_get_ssl_cipher mysql_get_ssl_cipher@4 mysql_hex_string mysql_hex_string@12 mysql_info mysql_info@4 mysql_init mysql_init@4 mysql_insert_id mysql_insert_id@4 mysql_kill mysql_kill@8 mysql_library_end mysql_library_end@0 mysql_library_init mysql_library_init@12 mysql_list_dbs mysql_list_dbs@8 mysql_list_fields mysql_list_fields@12 mysql_list_processes mysql_list_processes@4 mysql_list_tables mysql_list_tables@8 mysql_more_results mysql_more_results@4 mysql_next_result mysql_next_result@4 mysql_num_fields mysql_num_fields@4 mysql_num_rows mysql_num_rows@4 mysql_options mysql_options@12 mysql_options4 mysql_options4@16 mysql_ping mysql_ping@4 mysql_query mysql_query@8 mysql_read_query_result mysql_read_query_result@4 mysql_real_connect mysql_real_connect@32 mysql_real_escape_string mysql_real_escape_string@16 mysql_real_query mysql_real_query@12 mysql_refresh mysql_refresh@8 mysql_reset_connection mysql_reset_connection@4 mysql_rollback mysql_rollback@4 mysql_row_seek mysql_row_seek@8 mysql_row_tell mysql_row_tell@4 mysql_select_db mysql_select_db@8 mysql_send_query mysql_send_query@12 mysql_server_end mysql_server_end@0 mysql_server_init mysql_server_init@12 mysql_session_track_get_first mysql_session_track_get_first@16 mysql_session_track_get_next mysql_session_track_get_next@16 mysql_set_character_set mysql_set_character_set@8 mysql_set_local_infile_default mysql_set_local_infile_default@4 mysql_set_local_infile_handler mysql_set_local_infile_handler@24 mysql_set_server_option mysql_set_server_option@8 mysql_shutdown mysql_shutdown@8 mysql_sqlstate mysql_sqlstate@4 mysql_ssl_set mysql_ssl_set@24 mysql_stat mysql_stat@4 mysql_stmt_affected_rows mysql_stmt_affected_rows@4 mysql_stmt_attr_get mysql_stmt_attr_get@12 mysql_stmt_attr_set mysql_stmt_attr_set@12 mysql_stmt_bind_param mysql_stmt_bind_param@8 mysql_stmt_bind_result mysql_stmt_bind_result@8 mysql_stmt_close mysql_stmt_close@4 mysql_stmt_data_seek mysql_stmt_data_seek@12 mysql_stmt_errno mysql_stmt_errno@4 mysql_stmt_error mysql_stmt_error@4 mysql_stmt_execute mysql_stmt_execute@4 mysql_stmt_fetch mysql_stmt_fetch@4 mysql_stmt_fetch_column mysql_stmt_fetch_column@16 mysql_stmt_field_count mysql_stmt_field_count@4 mysql_stmt_free_result mysql_stmt_free_result@4 mysql_stmt_init mysql_stmt_init@4 mysql_stmt_insert_id mysql_stmt_insert_id@4 mysql_stmt_next_result mysql_stmt_next_result@4 mysql_stmt_num_rows mysql_stmt_num_rows@4 mysql_stmt_param_count mysql_stmt_param_count@4 mysql_stmt_param_metadata mysql_stmt_param_metadata@4 mysql_stmt_prepare mysql_stmt_prepare@12 mysql_stmt_reset mysql_stmt_reset@4 mysql_stmt_result_metadata mysql_stmt_result_metadata@4 mysql_stmt_row_seek mysql_stmt_row_seek@8 mysql_stmt_row_tell mysql_stmt_row_tell@4 mysql_stmt_send_long_data mysql_stmt_send_long_data@16 mysql_stmt_sqlstate mysql_stmt_sqlstate@4 mysql_stmt_store_result mysql_stmt_store_result@4 mysql_store_result mysql_store_result@4 mysql_thread_end mysql_thread_end@0 mysql_thread_id mysql_thread_id@4 mysql_thread_init mysql_thread_init@0 mysql_thread_safe mysql_thread_safe@0 mysql_use_result mysql_use_result@4 mysql_warning_count mysql_warning_count@4 mysql2-0.5.3/support/mysql_enc_to_ruby.rb000066400000000000000000000044701356743356300205700ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'mysql2' user, pass, host, port = ENV.values_at('user', 'pass', 'host', 'port') mysql_to_rb = { "big5" => "Big5", "dec8" => "NULL", "cp850" => "CP850", "hp8" => "NULL", "koi8r" => "KOI8-R", "latin1" => "ISO-8859-1", "latin2" => "ISO-8859-2", "swe7" => "NULL", "ascii" => "US-ASCII", "ujis" => "eucJP-ms", "sjis" => "Shift_JIS", "hebrew" => "ISO-8859-8", "tis620" => "TIS-620", "euckr" => "EUC-KR", "koi8u" => "KOI8-R", "gb2312" => "GB2312", "greek" => "ISO-8859-7", "cp1250" => "Windows-1250", "gbk" => "GBK", "latin5" => "ISO-8859-9", "armscii8" => "NULL", "utf8" => "UTF-8", "ucs2" => "UTF-16BE", "cp866" => "IBM866", "keybcs2" => "NULL", "macce" => "macCentEuro", "macroman" => "macRoman", "cp852" => "CP852", "latin7" => "ISO-8859-13", "utf8mb4" => "UTF-8", "cp1251" => "Windows-1251", "utf16" => "UTF-16", "cp1256" => "Windows-1256", "cp1257" => "Windows-1257", "utf32" => "UTF-32", "binary" => "ASCII-8BIT", "geostd8" => "NULL", "cp932" => "Windows-31J", "eucjpms" => "eucJP-ms", "utf16le" => "UTF-16LE", "gb18030" => "GB18030", } client = Mysql2::Client.new(username: user, password: pass, host: host, port: port.to_i) collations = client.query "SHOW COLLATION", as: :array encodings = Array.new(collations.to_a.last[2].to_i) encodings_with_nil = Array.new(encodings.size) collations.each do |collation| mysql_col_idx = collation[2].to_i rb_enc = mysql_to_rb.fetch(collation[1]) do |mysql_enc| $stderr.puts "WARNING: Missing mapping for collation \"#{collation[0]}\" with encoding \"#{mysql_enc}\" and id #{mysql_col_idx}, assuming NULL" "NULL" end encodings[mysql_col_idx - 1] = [mysql_col_idx, rb_enc] end encodings.each_with_index do |encoding, idx| encodings_with_nil[idx] = (encoding || [idx, "NULL"]) end encodings_with_nil.sort! do |a, b| a[0] <=> b[0] end encodings_with_nil = encodings_with_nil.map do |encoding| name = if encoding.nil? || encoding[1] == 'NULL' 'NULL' else "\"#{encoding[1]}\"" end " #{name}" end # start printing output puts "static const char *mysql2_mysql_enc_to_rb[] = {" puts encodings_with_nil.join(",\n") puts "};" mysql2-0.5.3/support/ruby_enc_to_mysql.rb000066400000000000000000000027551356743356300205740ustar00rootroot00000000000000mysql_to_rb = { "big5" => "Big5", "dec8" => nil, "cp850" => "CP850", "hp8" => nil, "koi8r" => "KOI8-R", "latin1" => "ISO-8859-1", "latin2" => "ISO-8859-2", "swe7" => nil, "ascii" => "US-ASCII", "ujis" => "eucJP-ms", "sjis" => "Shift_JIS", "hebrew" => "ISO-8859-8", "tis620" => "TIS-620", "euckr" => "EUC-KR", "koi8u" => "KOI8-R", "gb2312" => "GB2312", "greek" => "ISO-8859-7", "cp1250" => "Windows-1250", "gbk" => "GBK", "latin5" => "ISO-8859-9", "armscii8" => nil, "utf8" => "UTF-8", "ucs2" => "UTF-16BE", "cp866" => "IBM866", "keybcs2" => nil, "macce" => "macCentEuro", "macroman" => "macRoman", "cp852" => "CP852", "latin7" => "ISO-8859-13", "utf8mb4" => "UTF-8", "cp1251" => "Windows-1251", "utf16" => "UTF-16", "cp1256" => "Windows-1256", "cp1257" => "Windows-1257", "utf32" => "UTF-32", "binary" => "ASCII-8BIT", "geostd8" => nil, "cp932" => "Windows-31J", "eucjpms" => "eucJP-ms", "utf16le" => "UTF-16LE", "gb18030" => "GB18030", } puts <<-HEADER %readonly-tables %enum %define lookup-function-name mysql2_mysql_enc_name_to_rb %define hash-function-name mysql2_mysql_enc_name_to_rb_hash %struct-type struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; } %% HEADER mysql_to_rb.each do |mysql, ruby| name = if ruby.nil? "NULL" else "\"#{ruby}\"" end puts "#{mysql}, #{name}" end mysql2-0.5.3/tasks/000077500000000000000000000000001356743356300141125ustar00rootroot00000000000000mysql2-0.5.3/tasks/benchmarks.rake000066400000000000000000000006601356743356300170750ustar00rootroot00000000000000BENCHMARKS = Dir["#{File.dirname(__FILE__)}/../benchmark/*.rb"].map do |path| File.basename(path, '.rb') end - ['setup_db'] namespace :bench do BENCHMARKS.each do |feature| desc "Run #{feature} benchmarks" task(feature) { ruby "benchmark/#{feature}.rb" } end task :all do BENCHMARKS.each do |feature| ruby "benchmark/#{feature}.rb" end end task :setup do ruby 'benchmark/setup_db' end end mysql2-0.5.3/tasks/compile.rake000066400000000000000000000074101356743356300164100ustar00rootroot00000000000000require "rake/extensiontask" load File.expand_path('../../mysql2.gemspec', __FILE__) unless defined? Mysql2::GEMSPEC Rake::ExtensionTask.new("mysql2", Mysql2::GEMSPEC) do |ext| # put binaries into lib/mysql2/ or lib/mysql2/x.y/ ext.lib_dir = File.join 'lib', 'mysql2' # clean compiled extension CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}" if RUBY_PLATFORM =~ /mswin|mingw/ && !defined?(RubyInstaller) # Expand the path because the build dir is 3-4 levels deep in tmp/platform/version/ connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) ext.config_options = ["--with-mysql-dir=#{connector_dir}"] else ext.cross_compile = true ext.cross_platform = ENV['CROSS_PLATFORMS'] ? ENV['CROSS_PLATFORMS'].split(':') : ['x86-mingw32', 'x86-mswin32-60', 'x64-mingw32'] ext.cross_config_options << { 'x86-mingw32' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__), 'x86-mswin32-60' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x86')}", __FILE__), 'x64-mingw32' => "--with-mysql-dir=" + File.expand_path("../../vendor/#{vendor_mysql_dir('x64')}", __FILE__), } ext.cross_compiling do |spec| Rake::Task['lib/mysql2/mysql2.rb'].invoke # vendor/libmysql.dll is invoked from extconf.rb Rake::Task['vendor/README'].invoke # only the source gem has a package dependency - the binary gem ships it's own DLL version spec.metadata.delete('msys2_mingw_dependencies') spec.files << 'lib/mysql2/mysql2.rb' spec.files << 'vendor/libmysql.dll' spec.files << 'vendor/README' spec.post_install_message = <<-POST_INSTALL_MESSAGE ====================================================================================================== You've installed the binary version of #{spec.name}. It was built using MySQL Connector/C version #{CONNECTOR_VERSION}. It's recommended to use the exact same version to avoid potential issues. At the time of building this gem, the necessary DLL files were retrieved from: #{vendor_mysql_url(spec.platform)} This gem *includes* vendor/libmysql.dll with redistribution notice in vendor/README. ====================================================================================================== POST_INSTALL_MESSAGE end end end Rake::Task[:spec].prerequisites << :compile file 'vendor/README' do connector_dir = File.expand_path("../../vendor/#{vendor_mysql_dir}", __FILE__) when_writing 'copying Connector/C README' do cp "#{connector_dir}/README", 'vendor/README' end end file 'lib/mysql2/mysql2.rb' do |t| name = Mysql2::GEMSPEC.name File.open(t.name, 'wb') do |f| f.write <<-END_OF_RUBY RUBY_VERSION =~ /(\\d+.\\d+)/ require "#{name}/\#{$1}/#{name}" END_OF_RUBY end end # DevKit task following the example of Luis Lavena's test-ruby-c-extension task :devkit do begin require "devkit" rescue LoadError abort "Failed to activate RubyInstaller's DevKit required for compilation." end end if RUBY_PLATFORM =~ /mingw|mswin/ Rake::Task['compile'].prerequisites.unshift 'vendor:mysql' unless defined?(RubyInstaller) Rake::Task['compile'].prerequisites.unshift 'devkit' elsif Rake::Task.tasks.map(&:name).include? 'cross' Rake::Task['cross'].prerequisites.unshift 'vendor:mysql:cross' end desc "Build binary gems for Windows with rake-compiler-dock" task 'gem:windows' do require 'rake_compiler_dock' RakeCompilerDock.sh <<-EOT bundle install rake clean rm vendor/libmysql.dll rake cross native gem CROSS_PLATFORMS=x86-mingw32:x86-mswin32-60 EOT RakeCompilerDock.sh <<-EOT bundle install rake clean rm vendor/libmysql.dll rake cross native gem CROSS_PLATFORMS=x64-mingw32 EOT end mysql2-0.5.3/tasks/generate.rake000066400000000000000000000002741356743356300165530ustar00rootroot00000000000000task :encodings do sh "ruby support/mysql_enc_to_ruby.rb > ./ext/mysql2/mysql_enc_to_ruby.h" sh "ruby support/ruby_enc_to_mysql.rb | gperf > ./ext/mysql2/mysql_enc_name_to_ruby.h" end mysql2-0.5.3/tasks/rspec.rake000066400000000000000000000031761356743356300161010ustar00rootroot00000000000000begin require 'rspec' require 'rspec/core/rake_task' desc " Run all examples with Valgrind" namespace :spec do task :valgrind do VALGRIND_OPTS = %w[ --num-callers=50 --error-limit=no --partial-loads-ok=yes --undef-value-errors=no --trace-children=yes ].freeze cmdline = "valgrind #{VALGRIND_OPTS.join(' ')} bundle exec rake spec" puts cmdline system cmdline end end desc "Run all examples with RCov" RSpec::Core::RakeTask.new('spec:rcov') do |t| t.rcov = true end RSpec::Core::RakeTask.new('spec') do |t| t.verbose = true end rescue LoadError puts "rspec, or one of its dependencies, is not available. Install it with: sudo gem install rspec" end file 'spec/configuration.yml' => 'spec/configuration.yml.example' do |task| CLEAN.exclude task.name src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) dst_path = File.expand_path("../../#{task.name}", __FILE__) File.open(dst_path, 'w') do |dst_file| File.open(src_path).each_line do |line| dst_file.write line.gsub(/LOCALUSERNAME/, ENV['USER']) end end end file 'spec/my.cnf' => 'spec/my.cnf.example' do |task| CLEAN.exclude task.name src_path = File.expand_path("../../#{task.prerequisites.first}", __FILE__) dst_path = File.expand_path("../../#{task.name}", __FILE__) File.open(dst_path, 'w') do |dst_file| File.open(src_path).each_line do |line| dst_file.write line.gsub(/LOCALUSERNAME/, ENV['USER']) end end end Rake::Task[:spec].prerequisites << :'spec/configuration.yml' Rake::Task[:spec].prerequisites << :'spec/my.cnf' mysql2-0.5.3/tasks/vendor_mysql.rake000066400000000000000000000041671356743356300175100ustar00rootroot00000000000000require 'rake/clean' require 'rake/extensioncompiler' CONNECTOR_VERSION = "6.1.11".freeze # NOTE: Track the upstream version from time to time def vendor_mysql_platform(platform = nil) platform ||= RUBY_PLATFORM platform =~ /x64/ ? "winx64" : "win32" end def vendor_mysql_dir(*args) "mysql-connector-c-#{CONNECTOR_VERSION}-#{vendor_mysql_platform(*args)}" end def vendor_mysql_zip(*args) "#{vendor_mysql_dir(*args)}.zip" end def vendor_mysql_url(*args) "http://cdn.mysql.com/Downloads/Connector-C/#{vendor_mysql_zip(*args)}" end # vendor:mysql task "vendor:mysql:cross" do # When cross-compiling, grab both 32 and 64 bit connectors Rake::Task['vendor:mysql'].invoke('x86') Rake::Task['vendor:mysql'].invoke('x64') end task "vendor:mysql", [:platform] do |_t, args| puts "vendor:mysql for #{vendor_mysql_dir(args[:platform])}" # download mysql library and headers directory "vendor" file "vendor/#{vendor_mysql_zip(args[:platform])}" => ["vendor"] do |t| url = vendor_mysql_url(args[:platform]) when_writing "downloading #{t.name}" do cd "vendor" do sh "curl", "-C", "-", "-O", url do |ok| sh "wget", "-c", url unless ok end end end end file "vendor/#{vendor_mysql_dir(args[:platform])}/include/mysql.h" => ["vendor/#{vendor_mysql_zip(args[:platform])}"] do |t| full_file = File.expand_path(t.prerequisites.last) when_writing "creating #{t.name}" do cd "vendor" do sh "unzip", "-uq", full_file, "#{vendor_mysql_dir(args[:platform])}/bin/**", "#{vendor_mysql_dir(args[:platform])}/include/**", "#{vendor_mysql_dir(args[:platform])}/lib/**", "#{vendor_mysql_dir(args[:platform])}/README" # contains the license info end # update file timestamp to avoid Rake performing this extraction again. touch t.name end end # clobber expanded packages CLOBBER.include("vendor/#{vendor_mysql_dir(args[:platform])}") Rake::Task["vendor/#{vendor_mysql_dir(args[:platform])}/include/mysql.h"].invoke Rake::Task["vendor:mysql"].reenable # allow task to be invoked again (with another platform) end