[DRE-commits] [ruby-mysql2] 01/05: Imported Upstream version 0.3.14
Cédric Boutillier
boutil at moszumanska.debian.org
Tue Nov 26 17:41:17 UTC 2013
This is an automated email from the git hooks/post-receive script.
boutil pushed a commit to branch master
in repository ruby-mysql2.
commit 14aa9e18c5c117c016c99ab36e39de47fbb93861
Author: Cédric Boutillier <boutil at debian.org>
Date: Tue Nov 26 15:30:38 2013 +0100
Imported Upstream version 0.3.14
---
.gitignore | 12 -
.rspec | 3 -
.rvmrc | 1 -
.travis.yml | 7 -
CHANGELOG.md | 244 ----------
Gemfile | 3 -
README.md | 162 ++++++-
Rakefile | 5 -
benchmark/active_record.rb | 51 --
benchmark/active_record_threaded.rb | 42 --
benchmark/allocations.rb | 33 --
benchmark/escape.rb | 36 --
benchmark/query_with_mysql_casting.rb | 80 ----
benchmark/query_without_mysql_casting.rb | 56 ---
benchmark/sequel.rb | 37 --
benchmark/setup_db.rb | 119 -----
benchmark/threaded.rb | 44 --
checksums.yaml.gz | Bin 0 -> 271 bytes
ext/mysql2/client.c | 776 +++++++++++++++++++++++--------
ext/mysql2/client.h | 31 +-
ext/mysql2/extconf.rb | 85 +++-
ext/mysql2/mysql2_ext.h | 9 +-
ext/mysql2/mysql_enc_name_to_ruby.h | 168 +++++++
ext/mysql2/mysql_enc_to_ruby.h | 246 ++++++++++
ext/mysql2/result.c | 312 ++++++++-----
ext/mysql2/result.h | 5 +-
lib/mysql2.rb | 26 +-
lib/mysql2/client.rb | 245 ++--------
lib/mysql2/console.rb | 5 +
lib/mysql2/em.rb | 16 +-
lib/mysql2/version.rb | 2 +-
metadata.yml | 271 ++++-------
mysql2.gemspec | 29 --
spec/configuration.yml.example | 17 +
spec/em/em_spec.rb | 74 ++-
spec/my.cnf.example | 9 +
spec/mysql2/client_spec.rb | 366 +++++++++++++--
spec/mysql2/error_spec.rb | 26 +-
spec/mysql2/result_spec.rb | 98 +++-
spec/spec_helper.rb | 17 +-
support/mysql_enc_to_ruby.rb | 82 ++++
support/ruby_enc_to_mysql.rb | 61 +++
tasks/benchmarks.rake | 20 -
tasks/compile.rake | 71 ---
tasks/rspec.rake | 16 -
tasks/vendor_mysql.rake | 40 --
46 files changed, 2310 insertions(+), 1748 deletions(-)
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 1bc4656..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,12 +0,0 @@
-Makefile
-*.dSYM
-*.o
-*.bundle
-*.so
-*.a
-*.rbc
-mkmf.log
-pkg/
-tmp
-vendor
-lib/mysql2/mysql2.rb
diff --git a/.rspec b/.rspec
deleted file mode 100644
index 61354bc..0000000
--- a/.rspec
+++ /dev/null
@@ -1,3 +0,0 @@
---format documentation
---colour
---fail-fast
diff --git a/.rvmrc b/.rvmrc
deleted file mode 100644
index ed6705c..0000000
--- a/.rvmrc
+++ /dev/null
@@ -1 +0,0 @@
-rvm use 1.9.3 at mysql2 --create
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index db8a449..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-rvm:
- - 1.8.7
- - 1.9.2
- - 1.9.3
- - ree
-before_script:
- - "mysql -e 'create database test;' >/dev/null"
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 2ba50a4..0000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# Changelog
-
-## 0.3.11 (December 6th, 2011)
-* change mysql error detection strategy from using mysql_field_count to the more explicit mysql_errno
-* bugfix to avoid race condition with active connections that error out
-* revert back to using xmalloc/xfree for allocations
-* avoid potentially unsafe Ruby C API usage w/o GVL
-* reacquire GVL before retrying on EINTR on connect
-
-## 0.3.10 (November 9th, 2011)
-
-## 0.3.9 (November 9th, 2011)
-
-## 0.3.8 (November 9th, 2011)
-* remove fiber support from mysql2, the code has moved to the
- em-synchrony gem.
-* use rb_wait_for_single_fd() if available
-* fixed a bug with inheriting query options
-* remove ext/ from the default loadpath
-* fix build issues on OSX with Xcode 4.2 (gcc-llvm compiler)
-
-## 0.3.7 (August 16th, 2011)
-* ensure symbolized column names support encodings in 1.9
-
-## 0.3.6 (June 17th, 2011)
-* fix bug in Time/DateTime range detection
-* (win32) fix bug where the Mysql2::Client object wasn't cleaned up properly if interrupted during a query
-* add Mysql2::Result#count (aliased as size) to get the row count for the dataset
- this can be especially helpful if you want to get the number of rows without having to inflate
- the entire dataset into ruby (since this happens lazily)
-
-## 0.3.5 (June 15th, 2011)
-* bug fix for Time/DateTime usage depending on 32/64bit Ruby
-
-## 0.3.4 (June 15th, 2011)
-* fix a long standing bug where a signal would interrupt rb_thread_select and put the connection in a permanently broken state
-* turn on casting in the ActiveRecord again, users can disable it if they need to for performance reasons
-
-## 0.3.3 (June 14th, 2011)
-* disable async support, and access to the underlying file descriptor under Windows. It's never worked reliably and ruby-core has a lot of work to do in order to make it possible.
-* added support for turning eager-casting off. This is especially useful in ORMs that will lazily cast values upon access.
-* added a warning if a 0.2.x release is being used with ActiveRecord 3.1 since both the 0.2.x releases and AR 3.1 have mysql2 adapters, we want you to use the one in AR 3.1
-* added Mysql2::Client.escape (class-level method)
-* disabled eager-casting in the bundled ActiveRecord adapter (for Rails 3.0 or less)
-
-## 0.3.2 (April 26th, 2011)
-* Fix typo in initialization for older ActiveRecord versions
-
-## 0.3.1 (April 26th, 2011)
-* Fix typo in initialization for older ActiveRecord versions
-
-## 0.3.0 (April 26th, 2011)
-* switch to MySQL Connector/C for win32 builds
-* win32 bugfixes
-* BREAKING CHANGE: the ActiveRecord adapter has been pulled into Rails 3.1 and is no longer part of the gem
-* added Mysql2::Client.escape (class-level) for raw one-off non-encoding-aware escaping
-
-## 0.2.18 (December 6th, 2011)
-* change mysql error detection strategy from using mysql_field_count to the more explicit mysql_errno
-* bugfix to avoid race condition with active connections that error out
-* revert back to using xmalloc/xfree for allocations
-* avoid potentially unsafe Ruby C API usage w/o GVL
-* reacquire GVL before retrying on EINTR on connect
-
-## 0.2.17 (November 9th, 2011)
-
-## 0.2.16 (November 9th, 2011)
-
-## 0.2.15 (November 9th, 2011)
-
-## 0.2.14 (November 9th, 2011)
-* use rb_wait_for_single_fd() if available
-* fixed a bug with inheriting query options
-* remove ext/ from the default loadpath
-* fix build issues on OSX with Xcode 4.2 (gcc-llvm compiler)
-
-## 0.2.13 (August 16th, 2011)
-* fix stupid bug around symbol encoding support (thanks coderrr!)
-
-## 0.2.12 (August 16th, 2011)
-* ensure symbolized column names support encodings in 1.9
-* plugging sql vulnerability in mysql2 adapter
-
-## 0.2.11 (June 17th, 2011)
-* fix bug in Time/DateTime range detection
-* (win32) fix bug where the Mysql2::Client object wasn't cleaned up properly if interrupted during a query
-* add Mysql2::Result#count (aliased as size) to get the row count for the dataset
- this can be especially helpful if you want to get the number of rows without having to inflate
- the entire dataset into ruby (since this happens lazily)
-
-## 0.2.10 (June 15th, 2011)
-* bug fix for Time/DateTime usage depending on 32/64bit Ruby
-
-## 0.2.9 (June 15th, 2011)
-* fix a long standing bug where a signal would interrupt rb_thread_select and put the connection in a permanently broken state
-* turn on casting in the ActiveRecord again, users can disable it if they need to for performance reasons
-
-## 0.2.8 (June 14th, 2011)
-* disable async support, and access to the underlying file descriptor under Windows. It's never worked reliably and ruby-core has a lot of work to do in order to make it possible.
-* added support for turning eager-casting off. This is especially useful in ORMs that will lazily cast values upon access.
-* added a warning if a 0.2.x release is being used with ActiveRecord 3.1 since both the 0.2.x releases and AR 3.1 have mysql2 adapters, we want you to use the one in AR 3.1
-* added Mysql2::Client.escape (class-level method)
-* disabled eager-casting in the bundled ActiveRecord adapter (for Rails 3.0 or less)
-
-## 0.2.7 (March 28th, 2011)
-* various fixes for em_mysql2 and fiber usage
-* use our own Mysql2IndexDefinition class for better compatibility across ActiveRecord versions
-* ensure the query is a string earlier in the Mysql2::Client#query codepath for 1.9
-* only set binary ruby encoding on fields that have a binary flag *and* encoding set
-* a few various optimizations
-* add support for :read_timeout to be set on a connection
-* Fix to install with MariDB on Windows
-* add fibered em connection without activerecord
-* fix some 1.9.3 compilation warnings
-* add LD_RUN_PATH when using hard coded mysql paths - this should help users with MySQL installed in non-standard locations
-* for windows support, duplicate the socket from libmysql and create a temporary CRT fd
-* fix for handling years before 1970 on Windows
-* fixes to the Fiber adapter
-* set wait_timeout maximum on Windows to 2147483
-* update supported range for Time objects
-* upon being required, make sure the libmysql we're using is the one we were built against
-* add Mysql2::Client#thread_id
-* add Mysql2::Client#ping
-* switch connection check in AR adapter to use Mysql2::Client#ping for efficiency
-* prefer linking against thread-safe version of libmysqlclient
-* define RSTRING_NOT_MODIFIED for an awesome rbx speed boost
-* expose Mysql2::Client#encoding in 1.9, make sure we set the error message and sqlstate encodings accordingly
-* do not segfault when raising for invalid charset (found in 1.9.3dev)
-
-## 0.2.6 (October 19th, 2010)
-* version bump since the 0.2.5 win32 binary gems were broken
-
-## 0.2.5 (October 19th, 2010)
-* fixes for easier Win32 binary gem deployment for targeting 1.8 and 1.9 in the same gem
-* refactor of connection checks and management to avoid race conditions with the GC/threading to prevent the unexpected loss of connections
-* update the default flags during connection
-* add support for setting wait_timeout on AR adapter
-* upgrade to rspec2
-* bugfix for an edge case where the GC would clean up a Mysql2::Client object before the underlying MYSQL pointer had been initialized
-* fix to CFLAGS to allow compilation on SPARC with sunstudio compiler - Anko painting <anko.com+github at gmail.com>
-
-## 0.2.4 (September 17th, 2010)
-* a few patches for win32 support from Luis Lavena - thanks man!
-* bugfix from Eric Wong to avoid a potential stack overflow during Mysql2::Client#escape
-* added the ability to turn internal row caching on/off via the :cache_rows => true/false option
-* a couple of small patches for rbx compatibility
-* set IndexDefinition#length in AR adapter - Kouhei Yanagita <yanagi at shakenbu.org>
-* fix a long-standing data corruption bug - thank you thank you thank you to @joedamato (http://github.com/ice799)
-* bugfix from calling mysql_close on a closed/freed connection surfaced by the above fix
-
-## 0.2.3 (August 20th, 2010)
-* connection flags can now be passed to the constructor via the :flags key
-* switch AR adapter connection over to use FOUND_ROWS option
-* patch to ensure we use DateTime objects in place of Time for timestamps that are out of the supported range on 32bit platforms < 1.9.2
-
-## 0.2.2 (August 19th, 2010)
-* Change how AR adapter would send initial commands upon connecting
-** we can make multiple session variable assignments in a single query
-* fix signal handling when waiting on queries
-* retry connect if interrupted by signals
-
-## 0.2.1 (August 16th, 2010)
-* bring mysql2 ActiveRecord adapter back into gem
-
-## 0.2.0 (August 16th, 2010)
-* switch back to letting libmysql manage all allocation/thread-state/freeing for the connection
-* cache various numeric type conversions in hot-spots of the code for a little speed boost
-* ActiveRecord adapter moved into Rails 3 core
-** Don't worry 2.3.x users! We'll either release the adapter as a separate gem, or try to get it into 2.3.9
-* Fix for the "closed MySQL connection" error (GH #31)
-* Fix for the "can't modify frozen object" error in 1.9.2 (GH #37)
-* Introduce cascading query and result options (more info in README)
-* Sequel adapter pulled into core (will be in the next release - 3.15.0 at the time of writing)
-* add a safety check when attempting to send a query before a result has been fetched
-
-## 0.1.9 (July 17th, 2010)
-* Support async ActiveRecord access with fibers and EventMachine (mperham)
-* string encoding support for 1.9, respecting Encoding.default_internal
-* added support for rake-compiler (tenderlove)
-* bugfixes for ActiveRecord driver
-** one minor bugfix for TimeZone support
-** fix the select_rows method to return what it should according to the docs (r-stu31)
-* Mysql2::Client#fields method added - returns the array of field names from a resultset, as strings
-* Sequel adapter
-** bugfix regarding sybolized field names (Eric Wong)
-** fix query logging in Sequel adapter
-* Lots of nice code cleanup (tenderlove)
-** Mysql2::Error definition moved to pure-Ruby
-** Mysql2::client#initialize definition moved to pure-Ruby
-** Mysql2::Result partially moved to pure-Ruby
-
-## 0.1.8 (June 2nd, 2010)
-* fixes for AR adapter for timezone juggling
-* fixes to be able to run benchmarks and specs under 1.9.2
-
-## 0.1.7 (May 22nd, 2010)
-* fix a bug when using the disconnect! method on a closed connection in the AR driver
-
-## 0.1.6 (May 14th, 2010)
-* more fixes to the AR adapter related to casting
-* add missing index creation override method to AR adapter
-* added sql_state and error_number methods to the Mysql2::Error exception class
-
-## 0.1.5 (May 12th, 2010)
-* quite a few patches from Eric Wong related to thread-safety, non-blocking I/O and general cleanup
-** wrap mysql_real_connect with rb_thread_blocking_region
-** release GVL for possibly blocking mysql_* library calls
-** [cleanup] quiet down warnings
-** [cleanup] make all C symbols static
-** add Mysql2::Client#close method
-** correctly free the wrapped result in case of EOF
-** Fix memory leak from the result wrapper struct itself
-** make Mysql2::Client destructor safely non-blocking
-* bug fixes for ActiveRecord adapter
-** added casting for default values since they all come back from Mysql as strings (!?!)
-** missing constant was added
-** fixed a typo in the show_variable method
-* switched over sscanf for date/time parsing in C
-* made some specs a little finer-grained
-* initial Sequel adapter added
-* updated query benchmarks to reflect the difference between casting in C and in Ruby
-
-## 0.1.4 (April 23rd, 2010)
-* optimization: implemented a local cache for rows that are lazily created in ruby during iteration. The MySQL C result is freed as soon as all the results have been cached
-* optimization: implemented a local cache for field names so every row reuses the same objects as field names/keys
-* refactor the Mysql2 connection adapter for ActiveRecord to not extend the Mysql adapter - now being a free-standing connection adapter
-
-## 0.1.3 (April 15th, 2010)
-* added an EventMachine Deferrable API
-* added an ActiveRecord connection adapter
-** should be compatible with 2.3.5 and 3.0 (including Arel)
-
-## 0.1.2 (April 9th, 2010)
-* fix a bug (copy/paste fail) around checking for empty TIME values and returning nil (thanks @marius)
-
-## 0.1.1 (April 6th, 2010)
-* added affected_rows method (mysql_affected_rows)
-* added last_id method (last_insert_id)
-* enable reconnect option by default
-* added initial async query support
-* updated extconf (thanks to the mysqlplus project) for easier gem building
-
-## 0.1.0 (April 6th, 2010)
-* initial release
diff --git a/Gemfile b/Gemfile
deleted file mode 100644
index f6a560f..0000000
--- a/Gemfile
+++ /dev/null
@@ -1,3 +0,0 @@
-source :rubygems
-
-gemspec
\ No newline at end of file
diff --git a/README.md b/README.md
index 111e2d1..4847460 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# Mysql2 - A modern, simple and very fast Mysql library for Ruby - binding to libmysql
+[](https://travis-ci.org/brianmario/mysql2)
+
The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results.
Some database libraries out there serve as direct 1:1 mappings of the already complex C API's available.
This one is not.
@@ -13,12 +15,61 @@ Mysql2::Client - your connection to the database
Mysql2::Result - returned from issuing a #query on the connection. It includes Enumerable.
## Installing
-
+### OSX / Linux
``` sh
gem install mysql2
```
-You may have to specify --with-mysql-config=/some/random/path/bin/mysql_config
+This gem links against MySQL's `libmysqlclient` C shared library. You may need to install a package such as `libmysqlclient-dev`, `mysql-devel`, or other appropriate package for your system.
+
+By default, the mysql2 gem will try to find a copy of MySQL in this order:
+
+* Option `--with-mysql-dir`, if provided (see below).
+* Option `--with-mysql-config`, if provided (see below).
+* Several typical paths for `msyql_config` (default for the majority of users).
+* The directory `/usr/local`.
+
+### Configuration options
+
+Use these options by `gem install mysql2 -- [--optionA] [--optionB=argument]`.
+
+* `--with-mysql-dir[=/path/to/mysqldir]` -
+Specify the directory where MySQL is installed. The mysql2 gem will not use
+`mysql_config`, but will instead look at `mysqldir/lib` and `mysqldir/include`
+for the library and header files.
+This option is mutually exclusive with `--with-mysql-config`.
+
+* `--with-mysql-config[=/path/to/mysql_config]` -
+Specify a path to the `mysql_config` binary provided by your copy of MySQL. The
+mysql2 gem will ask this `mysql_config` binary about the compiler and linker
+arguments needed.
+This option is mutually exclusive with `--with-mysql-dir`.
+
+* `--with-mysql-rpath=/path/to/mysql/lib` / `--without-mysql-rpath` -
+Override the runtime path used to find the MySQL libraries.
+This may be needed if you deploy to a system where these libraries
+are located somewhere different than on your build system.
+This overrides any rpath calculated by default or by the options above.
+
+### Windows
+First, make sure you have the DevKit installed (http://rubyinstaller.org/downloads/) and its variables
+are loaded by running devkit\devktvars.bat .
+
+Next, you need a MySQL library to link against. If you have MySQL loaded on your development machine,
+you can use that. If not, you will need to either copy the MySQL directory from your server, or else
+obtain a copy of the MySQL C connector: http://dev.mysql.com/downloads/connector/c/
+
+If you're using the connector, I recommend just getting the .zip file and unzipping it someplace convenient.
+
+Now you can install mysql2. You must use the `--with-mysql-dir` option to tell gem where your MySQL library
+files are. For example, if you unzipped the connector to c:\mysql-connector-c-6.1.1-win32 you would install
+the gem like this:
+
+ gem install mysql2 -- --with-mysql-dir=c:\mysql-connector-c-6.1.1-win32
+
+Finally, you must copy libmysql.dll from the lib subdirectory of your MySQL or MySQL connector directory into
+your ruby\bin directory. In the above example, libmysql.dll would be located at
+c:\mysql-connector-c-6.1.1-win32\lib .
## Usage
@@ -85,6 +136,77 @@ results.each(:as => :array) do |row|
end
```
+## 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,
+ :reconnect = true/false,
+ :local_infile = true/false,
+ :secure_auth = true/false,
+ :default_file = '/path/to/my.cfg',
+ :default_group = 'my.cfg section'
+ )
+```
+### Multiple result sets
+
+You can also retrieve multiple result sets. For this to work you need to connect with
+flags `Mysql2::Client::MULTI_STATEMENTS`. Using multiple result sets is normally used
+when calling stored procedures that return more than one result set
+
+``` 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
+```
+
+See https://gist.github.com/1367987 for using MULTI_STATEMENTS with Active Record.
+
+### Secure auth
+
+Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this).
+When secure_auth is enabled, the server will refuse a connection if the account password is stored in old pre-MySQL 4.1 format.
+The MySQL 5.6.5 client library may also refuse to attempt a connection if provided an older format password.
+To bypass this restriction in the client, pass the option :secure_auth => false to Mysql2::Client.new().
+If using ActiveRecord, your database.yml might look something like this:
+
+```
+development:
+ adapter: mysql2
+ encoding: utf8
+ database: my_db_name
+ username: root
+ password: my_password
+ host: 127.0.0.1
+ port: 3306
+ secure_auth: false
+```
+
+### Reading a MySQL config file
+You may read configuration options from a MySQL configuration file by passing
+the `:default_file` and `:default_group` paramters. For example:
+
+```
+ client = Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client')
+```
+
+
## Cascading config
The default config hash is at:
@@ -212,15 +334,30 @@ This is especially helpful since it saves the cost of creating the row in Ruby i
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.
-## ActiveRecord
+### 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:
-To use the ActiveRecord driver (with or without rails), all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
+* `: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.
+
+## Active Record
+
+To use the Active Record driver (with or without rails), all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
That was easy right? :)
-NOTE: as of 0.3.0, and ActiveRecord 3.1 - the ActiveRecord adapter has been pulled out of this gem and into ActiveRecord itself. If you need to use mysql2 with
+NOTE: as of 0.3.0, and Active Record 3.1 - the Active Record adapter has been pulled out of this gem and into Active Record itself. If you need to use mysql2 with
Rails versions < 3.1 make sure and specify `gem "mysql2", "~> 0.2.7"` in your Gemfile
-## Asynchronous ActiveRecord
+## Asynchronous Active Record
Please see the [em-synchrony](https://github.com/igrigorik/em-synchrony) project for details about using EventMachine with mysql2 and Rails.
@@ -276,7 +413,7 @@ The specs pass on my system (SL 10.6.3, x86_64) in these rubies:
* ruby-trunk
* rbx-head - broken at the moment, working with the rbx team for a solution
-The ActiveRecord driver should work on 2.3.5 and 3.0
+The Active Record driver should work on 2.3.5 and 3.0
## Yeah... but why?
@@ -326,9 +463,16 @@ CREATE USER '<user>'@'localhost' IDENTIFIED BY '';
GRANT ALL PRIVILEGES ON test.* TO '<user>'@'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 ActiveRecord adapter
+* 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 ActiveRecord adapter (uses Fibers and EventMachine)
+* 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.
diff --git a/Rakefile b/Rakefile
deleted file mode 100644
index 7bb616b..0000000
--- a/Rakefile
+++ /dev/null
@@ -1,5 +0,0 @@
-# encoding: UTF-8
-require 'rake'
-
-# Load custom tasks
-Dir['tasks/*.rake'].sort.each { |f| load f }
diff --git a/benchmark/active_record.rb b/benchmark/active_record.rb
deleted file mode 100644
index 5120ac7..0000000
--- a/benchmark/active_record.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# encoding: UTF-8
-$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
-require 'rubygems'
-require 'benchmark'
-require 'active_record'
-
-ActiveRecord::Base.default_timezone = :local
-ActiveRecord::Base.time_zone_aware_attributes = true
-
-number_of = 10
-mysql2_opts = {
- :adapter => 'mysql2',
- :database => 'test'
-}
-mysql_opts = {
- :adapter => 'mysql',
- :database => 'test'
-}
-
-class Mysql2Model < ActiveRecord::Base
- set_table_name :mysql2_test
-end
-
-class MysqlModel < ActiveRecord::Base
- set_table_name :mysql2_test
-end
-
-Benchmark.bmbm do |x|
- x.report "Mysql2" do
- Mysql2Model.establish_connection(mysql2_opts)
- number_of.times do
- Mysql2Model.all(:limit => 1000).each{ |r|
- r.attributes.keys.each{ |k|
- r.send(k.to_sym)
- }
- }
- end
- end
-
- x.report "Mysql" do
- MysqlModel.establish_connection(mysql_opts)
- number_of.times do
- MysqlModel.all(:limit => 1000).each{ |r|
- r.attributes.keys.each{ |k|
- r.send(k.to_sym)
- }
- }
- end
- end
-end
\ No newline at end of file
diff --git a/benchmark/active_record_threaded.rb b/benchmark/active_record_threaded.rb
deleted file mode 100644
index 8c828bc..0000000
--- a/benchmark/active_record_threaded.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# encoding: UTF-8
-$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
-require 'rubygems'
-require 'benchmark'
-require 'active_record'
-
-times = 25
-
-
-# mysql2
-mysql2_opts = {
- :adapter => 'mysql2',
- :database => 'test',
- :pool => times
-}
-ActiveRecord::Base.establish_connection(mysql2_opts)
-x = Benchmark.realtime do
- threads = []
- times.times do
- threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
- end
- threads.each {|t| t.join }
-end
-puts "mysql2: #{x} seconds"
-
-
-# mysql
-mysql2_opts = {
- :adapter => 'mysql',
- :database => 'test',
- :pool => times
-}
-ActiveRecord::Base.establish_connection(mysql2_opts)
-x = Benchmark.realtime do
- threads = []
- times.times do
- threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
- end
- threads.each {|t| t.join }
-end
-puts "mysql: #{x} seconds"
diff --git a/benchmark/allocations.rb b/benchmark/allocations.rb
deleted file mode 100644
index cf0c931..0000000
--- a/benchmark/allocations.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# encoding: UTF-8
-$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
-raise Mysql2::Mysql2Error.new("GC allocation benchmarks only supported on Ruby 1.9!") unless RUBY_VERSION =~ /1\.9/
-
-require 'rubygems'
-require 'benchmark'
-require 'active_record'
-
-ActiveRecord::Base.default_timezone = :local
-ActiveRecord::Base.time_zone_aware_attributes = true
-
-class Mysql2Model < ActiveRecord::Base
- set_table_name :mysql2_test
-end
-
-def bench_allocations(feature, iterations = 10, &blk)
- puts "GC overhead for #{feature}"
- Mysql2Model.establish_connection(:adapter => 'mysql2', :database => 'test')
- GC::Profiler.clear
- GC::Profiler.enable
- iterations.times{ blk.call }
- GC::Profiler.report(STDOUT)
- GC::Profiler.disable
-end
-
-bench_allocations('coercion') do
- Mysql2Model.all(:limit => 1000).each{ |r|
- r.attributes.keys.each{ |k|
- r.send(k.to_sym)
- }
- }
-end
\ No newline at end of file
diff --git a/benchmark/escape.rb b/benchmark/escape.rb
deleted file mode 100644
index 852d52a..0000000
--- a/benchmark/escape.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# encoding: UTF-8
-$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
-require 'rubygems'
-require 'benchmark'
-require 'mysql'
-require 'mysql2'
-require 'do_mysql'
-
-def run_escape_benchmarks(str, number_of = 1000)
- Benchmark.bmbm do |x|
- mysql = Mysql.new("localhost", "root")
- x.report "Mysql #{str.inspect}" do
- number_of.times do
- mysql.quote str
- end
- end
-
- mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
- x.report "Mysql2 #{str.inspect}" do
- number_of.times do
- mysql2.escape str
- end
- end
-
- do_mysql = DataObjects::Connection.new("mysql://localhost/test")
- x.report "do_mysql #{str.inspect}" do
- number_of.times do
- do_mysql.quote_string str
- end
- end
- end
-end
-
-run_escape_benchmarks "abc'def\"ghi\0jkl%mno"
-run_escape_benchmarks "clean string"
\ No newline at end of file
diff --git a/benchmark/query_with_mysql_casting.rb b/benchmark/query_with_mysql_casting.rb
deleted file mode 100644
index 5b460dd..0000000
--- a/benchmark/query_with_mysql_casting.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# encoding: UTF-8
-$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
-require 'rubygems'
-require 'benchmark'
-require 'mysql'
-require 'mysql2'
-require 'do_mysql'
-
-number_of = 100
-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.new(value)
- when Mysql::Field::TYPE_DOUBLE, Mysql::Field::TYPE_FLOAT
- value.to_f
- when Mysql::Field::TYPE_DATE
- Date.parse(value)
- when Mysql::Field::TYPE_TIME, Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP
- Time.parse(value)
- when Mysql::Field::TYPE_BLOB, Mysql::Field::TYPE_BIT, Mysql::Field::TYPE_STRING,
- Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET
- Mysql::Field::TYPE_ENUM
- value
- else
- value
- end
-end
-
-Benchmark.bmbm do |x|
- mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
- mysql2.query "USE #{database}"
- x.report "Mysql2" do
- number_of.times do
- mysql2_result = mysql2.query sql, :symbolize_keys => true
- mysql2_result.each do |res|
- # puts res.inspect
- end
- end
- end
-
- mysql = Mysql.new("localhost", "root")
- mysql.query "USE #{database}"
- x.report "Mysql" do
- number_of.times do
- mysql_result = mysql.query sql
- fields = mysql_result.fetch_fields
- mysql_result.each do |row|
- row_hash = {}
- row.each_with_index do |f, j|
- row_hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, row[j])
- end
- # puts row_hash.inspect
- end
- end
- end
-
- do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
- command = do_mysql.create_command sql
- x.report "do_mysql" do
- number_of.times do
- do_result = command.execute_reader
- do_result.each do |res|
- # puts res.inspect
- end
- end
- end
-end
\ No newline at end of file
diff --git a/benchmark/query_without_mysql_casting.rb b/benchmark/query_without_mysql_casting.rb
deleted file mode 100644
index 3929d15..0000000
--- a/benchmark/query_without_mysql_casting.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# encoding: UTF-8
-$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
-require 'rubygems'
-require 'benchmark'
-require 'mysql'
-require 'mysql2'
-require 'do_mysql'
-
-number_of = 100
-database = 'test'
-sql = "SELECT * FROM mysql2_test LIMIT 100"
-
-Benchmark.bmbm do |x|
- mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
- mysql2.query "USE #{database}"
- x.report "Mysql2 (cast: true)" do
- number_of.times do
- mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => true
- mysql2_result.each do |res|
- # puts res.inspect
- end
- end
- end
-
- x.report "Mysql2 (cast: false)" do
- number_of.times do
- mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => false
- mysql2_result.each do |res|
- # puts res.inspect
- end
- end
- end
-
- mysql = Mysql.new("localhost", "root")
- mysql.query "USE #{database}"
- x.report "Mysql" do
- number_of.times do
- mysql_result = mysql.query sql
- mysql_result.each_hash do |res|
- # puts res.inspect
- end
- end
- end
-
- do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
- command = DataObjects::Mysql::Command.new do_mysql, sql
- x.report "do_mysql" do
- number_of.times do
- do_result = command.execute_reader
- do_result.each do |res|
- # puts res.inspect
- end
- end
- end
-end
\ No newline at end of file
diff --git a/benchmark/sequel.rb b/benchmark/sequel.rb
deleted file mode 100644
index 0dac47f..0000000
--- a/benchmark/sequel.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# encoding: UTF-8
-$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
-require 'rubygems'
-require 'benchmark'
-require 'mysql2'
-require 'sequel'
-require 'sequel/adapters/do'
-
-number_of = 10
-mysql2_opts = "mysql2://localhost/test"
-mysql_opts = "mysql://localhost/test"
-do_mysql_opts = "do:mysql://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.bmbm do |x|
- x.report "Mysql2" do
- number_of.times do
- Mysql2Model.limit(1000).all
- end
- end
-
- x.report "do:mysql" do
- number_of.times do
- DOMysqlModel.limit(1000).all
- end
- end
-
- x.report "Mysql" do
- number_of.times do
- MysqlModel.limit(1000).all
- end
- end
-end
\ No newline at end of file
diff --git a/benchmark/setup_db.rb b/benchmark/setup_db.rb
deleted file mode 100644
index a5395b3..0000000
--- a/benchmark/setup_db.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-# encoding: UTF-8
-$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
-# This script is for generating psudo-random data into a single table consisting of nearly every
-# 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
- at client = Mysql2::Client.new :host => "localhost", :username => "root", :database => "test"
-
- at client.query create_table_sql
-
-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,
- :varchar_test => five_words,
- :binary_test => five_words,
- :varbinary_test => five_words,
- :tiny_blob_test => five_words,
- :tiny_text_test => Faker::Lorem.paragraph(rand(5)),
- :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 => ['val1', 'val2'].rand,
- :set_test => ['val1', 'val2', 'val1,val2'].rand
- )
- if n % 100 == 0
- $stdout.putc '.'
- $stdout.flush
- end
-end
-puts
-puts "Done"
\ No newline at end of file
diff --git a/benchmark/threaded.rb b/benchmark/threaded.rb
deleted file mode 100644
index 7b989a2..0000000
--- a/benchmark/threaded.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# encoding: UTF-8
-$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
-require 'rubygems'
-require 'benchmark'
-require 'active_record'
-
-mysql2_opts = {
- :adapter => 'mysql2',
- :database => 'test',
- :pool => 25
-}
-ActiveRecord::Base.establish_connection(mysql2_opts)
-x = Benchmark.realtime do
- threads = []
- 25.times do
- threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
- end
- threads.each {|t| t.join }
-end
-puts x
-
-mysql2_opts = {
- :adapter => 'mysql',
- :database => 'test',
- :pool => 25
-}
-ActiveRecord::Base.establish_connection(mysql2_opts)
-x = Benchmark.realtime do
- threads = []
- 25.times do
- threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
- end
- threads.each {|t| t.join }
-end
-puts x
-
-# these results are similar on 1.8.7, 1.9.2 and rbx-head
-#
-# $ bundle exec ruby benchmarks/threaded.rb
-# 1.0774750709533691
-#
-# and using the mysql gem
-# 25.099437952041626
\ No newline at end of file
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
new file mode 100644
index 0000000..5d580ac
Binary files /dev/null and b/checksums.yaml.gz differ
diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c
index 28f9ced..8db616d 100644
--- a/ext/mysql2/client.c
+++ b/ext/mysql2/client.c
@@ -4,29 +4,60 @@
#ifndef _WIN32
#include <sys/socket.h>
#endif
+#include <unistd.h>
#include "wait_for_single_fd.h"
+#include "mysql_enc_name_to_ruby.h"
+
VALUE cMysql2Client;
extern VALUE mMysql2, cMysql2Error;
-static VALUE intern_encoding_from_charset;
-static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
-static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
+static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
+static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql;
+
+#ifndef HAVE_RB_HASH_DUP
+static VALUE rb_hash_dup(VALUE other) {
+ return rb_funcall(rb_cHash, rb_intern("[]"), 1, other);
+}
+#endif
-#define REQUIRE_OPEN_DB(wrapper) \
- if(!wrapper->reconnect_enabled && wrapper->closed) { \
+#define REQUIRE_INITIALIZED(wrapper) \
+ if (!wrapper->initialized) { \
+ rb_raise(cMysql2Error, "MySQL client is not initialized"); \
+ }
+
+#define REQUIRE_CONNECTED(wrapper) \
+ REQUIRE_INITIALIZED(wrapper) \
+ if (!wrapper->connected && !wrapper->reconnect_enabled) { \
rb_raise(cMysql2Error, "closed MySQL connection"); \
}
+#define REQUIRE_NOT_CONNECTED(wrapper) \
+ REQUIRE_INITIALIZED(wrapper) \
+ if (wrapper->connected) { \
+ rb_raise(cMysql2Error, "MySQL connection is already open"); \
+ }
+
#define MARK_CONN_INACTIVE(conn) \
- wrapper->active = 0
+ wrapper->active_thread = Qnil;
#define GET_CLIENT(self) \
mysql_client_wrapper *wrapper; \
Data_Get_Struct(self, mysql_client_wrapper, wrapper)
/*
+ * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
+ * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
+ * linking against the server itself
+ */
+#ifdef LIBMYSQL_VERSION
+ #define MYSQL_LINK_VERSION LIBMYSQL_VERSION
+#else
+ #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION
+#endif
+
+/*
* used to pass all arguments to mysql_real_connect while inside
- * rb_thread_blocking_region
+ * rb_thread_call_without_gvl
*/
struct nogvl_connect_args {
MYSQL *mysql;
@@ -41,7 +72,7 @@ struct nogvl_connect_args {
/*
* used to pass all arguments to mysql_send_query while inside
- * rb_thread_blocking_region
+ * rb_thread_call_without_gvl
*/
struct nogvl_send_query_args {
MYSQL *mysql;
@@ -52,6 +83,15 @@ struct nogvl_send_query_args {
};
/*
+ * 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;
+};
+
+/*
* 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.
@@ -77,12 +117,14 @@ 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;
#ifdef HAVE_RUBY_ENCODING_H
rb_encoding *default_internal_enc = rb_default_internal_encoding();
rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
@@ -95,22 +137,22 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
}
#endif
- VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg);
+ e = rb_exc_new3(cMysql2Error, rb_error_msg);
rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
rb_exc_raise(e);
return Qnil;
}
-static VALUE nogvl_init(void *ptr) {
+static void *nogvl_init(void *ptr) {
MYSQL *client;
/* may initialize embedded server and read /etc/services off disk */
client = mysql_init((MYSQL *)ptr);
- return client ? Qtrue : Qfalse;
+ return (void*)(client ? Qtrue : Qfalse);
}
-static VALUE nogvl_connect(void *ptr) {
+static void *nogvl_connect(void *ptr) {
struct nogvl_connect_args *args = ptr;
MYSQL *client;
@@ -119,18 +161,18 @@ static VALUE nogvl_connect(void *ptr) {
args->db, args->port, args->unix_socket,
args->client_flag);
- return client ? Qtrue : Qfalse;
+ return (void *)(client ? Qtrue : Qfalse);
}
-static VALUE nogvl_close(void *ptr) {
+static void *nogvl_close(void *ptr) {
mysql_client_wrapper *wrapper;
#ifndef _WIN32
int flags;
#endif
wrapper = ptr;
- if (!wrapper->closed) {
- wrapper->closed = 1;
- wrapper->active = 0;
+ if (wrapper->connected) {
+ wrapper->active_thread = Qnil;
+ wrapper->connected = 0;
/*
* we'll send a QUIT message to the server, but that message is more of a
* formality than a hard requirement since the socket is getting shutdown
@@ -147,18 +189,20 @@ static VALUE nogvl_close(void *ptr) {
#endif
mysql_close(wrapper->client);
- xfree(wrapper->client);
}
- return Qnil;
+ return NULL;
}
-static void rb_mysql_client_free(void * ptr) {
+static void rb_mysql_client_free(void *ptr) {
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
- nogvl_close(wrapper);
-
- xfree(ptr);
+ wrapper->refcount--;
+ if (wrapper->refcount == 0) {
+ nogvl_close(wrapper);
+ xfree(wrapper->client);
+ xfree(wrapper);
+ }
}
static VALUE allocate(VALUE klass) {
@@ -166,13 +210,22 @@ static VALUE allocate(VALUE klass) {
mysql_client_wrapper * wrapper;
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
wrapper->encoding = Qnil;
- wrapper->active = 0;
+ wrapper->active_thread = Qnil;
wrapper->reconnect_enabled = 0;
- wrapper->closed = 1;
+ wrapper->connected = 0; /* means that a database connection is open */
+ wrapper->initialized = 0; /* means that that the wrapper is initialized */
+ wrapper->refcount = 1;
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
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;
@@ -185,7 +238,7 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen);
if (newLen == oldLen) {
- // no need to return a new ruby string if nothing changed
+ /* no need to return a new ruby string if nothing changed */
xfree(newStr);
return str;
} else {
@@ -198,30 +251,59 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE 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);
+#ifdef HAVE_RUBY_ENCODING_H
+ rb_enc_associate(rb_str, rb_utf8_encoding());
+#endif
+
+ return rb_str;
+}
+
static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
struct nogvl_connect_args args;
VALUE rv;
GET_CLIENT(self);
- args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
+ args.host = NIL_P(host) ? NULL : StringValuePtr(host);
args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
- args.port = NIL_P(port) ? 3306 : NUM2INT(port);
+ args.port = NIL_P(port) ? 0 : NUM2INT(port);
args.user = NIL_P(user) ? NULL : StringValuePtr(user);
args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
args.db = NIL_P(database) ? NULL : StringValuePtr(database);
args.mysql = wrapper->client;
args.client_flag = NUM2ULONG(flags);
- rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0);
+ rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
if (rv == Qfalse) {
- while (rv == Qfalse && errno == EINTR) {
+ while (rv == Qfalse && errno == EINTR && !mysql_errno(wrapper->client)) {
errno = 0;
- rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0);
+ rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
}
if (rv == Qfalse)
return rb_raise_mysql2_error(wrapper);
}
+ wrapper->connected = 1;
return self;
}
@@ -234,8 +316,8 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
static VALUE rb_mysql_client_close(VALUE self) {
GET_CLIENT(self);
- if (!wrapper->closed) {
- rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
+ if (wrapper->connected) {
+ rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
}
return Qnil;
@@ -246,20 +328,20 @@ static VALUE rb_mysql_client_close(VALUE self) {
* enough to fit in a socket buffer, but sometimes large UPDATE and
* INSERTs will cause the process to block
*/
-static VALUE nogvl_send_query(void *ptr) {
+static void *nogvl_send_query(void *ptr) {
struct nogvl_send_query_args *args = ptr;
int rv;
rv = mysql_send_query(args->mysql, args->sql_ptr, args->sql_len);
- return rv == 0 ? Qtrue : Qfalse;
+ return (void*)(rv == 0 ? Qtrue : Qfalse);
}
static VALUE do_send_query(void *args) {
struct nogvl_send_query_args *query_args = args;
mysql_client_wrapper *wrapper = query_args->wrapper;
- if (rb_thread_blocking_region(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
- // an error occurred, we're not active anymore
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
+ /* an error occurred, we're not active anymore */
MARK_CONN_INACTIVE(self);
return rb_raise_mysql2_error(wrapper);
}
@@ -271,66 +353,83 @@ static VALUE do_send_query(void *args) {
* response can overflow the socket buffers and cause us to eventually
* block while calling mysql_read_query_result
*/
-static VALUE nogvl_read_query_result(void *ptr) {
+static void *nogvl_read_query_result(void *ptr) {
MYSQL * client = ptr;
my_bool res = mysql_read_query_result(client);
- return res == 0 ? Qtrue : Qfalse;
+ return (void *)(res == 0 ? Qtrue : Qfalse);
}
-/* mysql_store_result may (unlikely) read rows off the socket */
-static VALUE nogvl_store_result(void *ptr) {
+static void *nogvl_do_result(void *ptr, char use_result) {
mysql_client_wrapper *wrapper;
MYSQL_RES *result;
wrapper = (mysql_client_wrapper *)ptr;
- result = mysql_store_result(wrapper->client);
+ 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 = 0;
+ /* once our result is stored off, this connection is
+ ready for another command to be issued */
+ wrapper->active_thread = Qnil;
- return (VALUE)result;
+ return result;
}
+/* mysql_store_result may (unlikely) read rows off the socket */
+static 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;
-#ifdef HAVE_RUBY_ENCODING_H
- mysql2_result_wrapper * result_wrapper;
-#endif
+ VALUE current, is_streaming;
GET_CLIENT(self);
- // if we're not waiting on a result, do nothing
- if (!wrapper->active)
+ /* if we're not waiting on a result, do nothing */
+ if (NIL_P(wrapper->active_thread))
return Qnil;
- REQUIRE_OPEN_DB(wrapper);
- if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
- // an error occurred, mark this connection inactive
+ REQUIRE_CONNECTED(wrapper);
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
+ /* an error occurred, mark this connection inactive */
MARK_CONN_INACTIVE(self);
return rb_raise_mysql2_error(wrapper);
}
- result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
+ is_streaming = rb_hash_aref(rb_iv_get(self, "@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) {
MARK_CONN_INACTIVE(self);
rb_raise_mysql2_error(wrapper);
}
- // no data and no error, so query was not a SELECT
+ /* no data and no error, so query was not a SELECT */
return Qnil;
}
- resultObj = rb_mysql_result_to_obj(result);
- // pass-through query options for result construction later
- rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
+ current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
+ RB_GC_GUARD(current);
+ Check_Type(current, T_HASH);
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
-#ifdef HAVE_RUBY_ENCODING_H
- GetMysql2Result(resultObj, result_wrapper);
- result_wrapper->encoding = wrapper->encoding;
-#endif
return resultObj;
}
@@ -343,12 +442,13 @@ struct async_query_args {
static VALUE disconnect_and_raise(VALUE self, VALUE error) {
GET_CLIENT(self);
- wrapper->closed = 1;
- wrapper->active = 0;
+ wrapper->active_thread = Qnil;
+ wrapper->connected = 0;
- // manually close the socket for read/write
- // this feels dirty, but is there another way?
- shutdown(wrapper->client->net.fd, 2);
+ /* manually close the socket for read/write
+ this feels dirty, but is there another way? */
+ close(wrapper->client->net.fd);
+ wrapper->client->net.fd = -1;
rb_exc_raise(error);
@@ -371,8 +471,8 @@ static VALUE do_query(void *args) {
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
+ /* 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 {
@@ -408,63 +508,106 @@ static VALUE finish_and_mark_inactive(void *args) {
GET_CLIENT(self);
- if (wrapper->active) {
- // if we got here, the result hasn't been read off the wire yet
- // so lets do that and then throw it away because we have no way
- // of getting it back up to the caller from here
- result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
+ if (!NIL_P(wrapper->active_thread)) {
+ /* if we got here, the result hasn't been read off the wire yet
+ so lets do that and then throw it away because we have no way
+ of getting it back up to the caller from here */
+ result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
mysql_free_result(result);
- wrapper->active = 0;
+ wrapper->active_thread = Qnil;
}
return Qnil;
}
#endif
+/* 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_client_query(int argc, VALUE * argv, VALUE self) {
#ifndef _WIN32
struct async_query_args async_args;
#endif
struct nogvl_send_query_args args;
int async = 0;
- VALUE opts, defaults;
+ VALUE opts, current;
+ VALUE thread_current = rb_thread_current();
#ifdef HAVE_RUBY_ENCODING_H
rb_encoding *conn_enc;
#endif
GET_CLIENT(self);
- REQUIRE_OPEN_DB(wrapper);
+ REQUIRE_CONNECTED(wrapper);
args.mysql = wrapper->client;
+ current = rb_hash_dup(rb_iv_get(self, "@query_options"));
+ RB_GC_GUARD(current);
+ Check_Type(current, T_HASH);
+ rb_iv_set(self, "@current_query_options", current);
- defaults = rb_iv_get(self, "@query_options");
if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
- opts = rb_funcall(defaults, intern_merge, 1, opts);
- rb_iv_set(self, "@query_options", opts);
+ rb_funcall(current, intern_merge_bang, 1, opts);
- if (rb_hash_aref(opts, sym_async) == Qtrue) {
+ if (rb_hash_aref(current, sym_async) == Qtrue) {
async = 1;
}
- } else {
- opts = defaults;
}
Check_Type(args.sql, T_STRING);
#ifdef HAVE_RUBY_ENCODING_H
conn_enc = rb_to_encoding(wrapper->encoding);
- // ensure the string is in the encoding the connection is expecting
+ /* ensure the string is in the encoding the connection is expecting */
args.sql = rb_str_export_to_enc(args.sql, conn_enc);
#endif
args.sql_ptr = StringValuePtr(args.sql);
args.sql_len = RSTRING_LEN(args.sql);
- // see if this connection is still waiting on a result from a previous query
- if (wrapper->active == 0) {
- // mark this connection active
- wrapper->active = 1;
- } else {
+ /* see if this connection is still waiting on a result from a previous query */
+ if (NIL_P(wrapper->active_thread)) {
+ /* mark this connection active */
+ wrapper->active_thread = thread_current;
+ } else if (wrapper->active_thread == thread_current) {
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
+ } else {
+ VALUE inspect = rb_inspect(wrapper->active_thread);
+ const char *thr = StringValueCStr(inspect);
+
+ rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
+ RB_GC_GUARD(inspect);
}
args.wrapper = wrapper;
@@ -485,11 +628,16 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
#else
do_send_query(&args);
- // this will just block until the result is ready
+ /* this will just block until the result is ready */
return rb_ensure(rb_mysql_client_async_result, self, finish_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;
@@ -500,12 +648,12 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
#endif
GET_CLIENT(self);
- REQUIRE_OPEN_DB(wrapper);
+ REQUIRE_CONNECTED(wrapper);
Check_Type(str, T_STRING);
#ifdef HAVE_RUBY_ENCODING_H
default_internal_enc = rb_default_internal_encoding();
conn_enc = rb_to_encoding(wrapper->encoding);
- // ensure the string is in the encoding the connection is expecting
+ /* ensure the string is in the encoding the connection is expecting */
str = rb_str_export_to_enc(str, conn_enc);
#endif
@@ -514,7 +662,7 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
if (newLen == oldLen) {
- // no need to return a new ruby string if nothing changed
+ /* no need to return a new ruby string if nothing changed */
xfree(newStr);
return str;
} else {
@@ -530,13 +678,91 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE 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 = NUM2INT(value);
+ retval = &intval;
+ break;
+
+ case MYSQL_OPT_READ_TIMEOUT:
+ intval = NUM2INT(value);
+ retval = &intval;
+ break;
+
+ case MYSQL_OPT_WRITE_TIMEOUT:
+ intval = NUM2INT(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;
+
+ case MYSQL_SECURE_AUTH:
+ boolval = (value == Qfalse ? 0 : 1);
+ retval = &boolval;
+ break;
+
+ case MYSQL_READ_DEFAULT_FILE:
+ charval = (const char *)StringValuePtr(value);
+ retval = charval;
+ break;
+
+ case MYSQL_READ_DEFAULT_GROUP:
+ charval = (const char *)StringValuePtr(value);
+ retval = charval;
+ break;
+
+ default:
+ return Qfalse;
+ }
+
+ 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 reconnect, this option is also stored in the wrapper struct */
+ if (opt == MYSQL_OPT_RECONNECT)
+ wrapper->reconnect_enabled = boolval;
+ }
+
+ return (result == 0) ? Qtrue : Qfalse;
+}
+
+/* call-seq:
+ * client.info
+ *
+ * Returns a string that represents the client library version.
+ */
static VALUE rb_mysql_client_info(VALUE self) {
VALUE version, client_info;
#ifdef HAVE_RUBY_ENCODING_H
rb_encoding *default_internal_enc;
rb_encoding *conn_enc;
-#endif
GET_CLIENT(self);
+#endif
version = rb_hash_new();
#ifdef HAVE_RUBY_ENCODING_H
@@ -556,6 +782,11 @@ static VALUE rb_mysql_client_info(VALUE self) {
return version;
}
+/* 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;
#ifdef HAVE_RUBY_ENCODING_H
@@ -564,7 +795,7 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
#endif
GET_CLIENT(self);
- REQUIRE_OPEN_DB(wrapper);
+ REQUIRE_CONNECTED(wrapper);
#ifdef HAVE_RUBY_ENCODING_H
default_internal_enc = rb_default_internal_encoding();
conn_enc = rb_to_encoding(wrapper->encoding);
@@ -583,28 +814,48 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
return version;
}
+/* call-seq:
+ * client.socket
+ *
+ * Return the file descriptor number for this client.
+ */
static VALUE rb_mysql_client_socket(VALUE self) {
GET_CLIENT(self);
#ifndef _WIN32
- REQUIRE_OPEN_DB(wrapper);
- int fd_set_fd = wrapper->client->net.fd;
- return INT2NUM(fd_set_fd);
+ {
+ int fd_set_fd;
+ REQUIRE_CONNECTED(wrapper);
+ fd_set_fd = wrapper->client->net.fd;
+ return INT2NUM(fd_set_fd);
+ }
#else
rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
#endif
}
+/* 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_OPEN_DB(wrapper);
+ 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_OPEN_DB(wrapper);
+ REQUIRE_CONNECTED(wrapper);
retVal = mysql_affected_rows(wrapper->client);
if (retVal == (my_ulonglong)-1) {
rb_raise_mysql2_error(wrapper);
@@ -612,93 +863,227 @@ static VALUE rb_mysql_client_affected_rows(VALUE self) {
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_OPEN_DB(wrapper);
+ REQUIRE_CONNECTED(wrapper);
retVal = mysql_thread_id(wrapper->client);
return ULL2NUM(retVal);
}
-static VALUE nogvl_ping(void *ptr) {
+static void *nogvl_select_db(void *ptr) {
+ struct nogvl_select_db_args *args = ptr;
+
+ if (mysql_select_db(args->mysql, args->db) == 0)
+ return (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 = StringValuePtr(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 mysql_ping(client) == 0 ? Qtrue : Qfalse;
+ 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 (wrapper->closed) {
+ if (!wrapper->connected) {
return Qfalse;
} else {
- return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
+ return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
+ }
+}
+
+/* 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;
}
+
+ current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
+ RB_GC_GUARD(current);
+ Check_Type(current, T_HASH);
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
+
+ return resultObj;
}
#ifdef HAVE_RUBY_ENCODING_H
+/* 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;
}
#endif
+/* 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) {
- my_bool reconnect;
- GET_CLIENT(self);
-
- if(!NIL_P(value)) {
- reconnect = value == Qfalse ? 0 : 1;
+ return _mysql_client_options(self, MYSQL_OPT_RECONNECT, value);
+}
- wrapper->reconnect_enabled = reconnect;
- /* set default reconnect behavior */
- if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
- /* TODO: warning - unable to set reconnect behavior */
- rb_warn("%s\n", mysql_error(wrapper->client));
- }
- }
- return 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) {
- unsigned int connect_timeout = 0;
- GET_CLIENT(self);
+ 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);
+}
- if(!NIL_P(value)) {
- connect_timeout = NUM2INT(value);
- if(0 == connect_timeout) return 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_iv_set(self, "@read_timeout", value);
+ return _mysql_client_options(self, MYSQL_OPT_READ_TIMEOUT, value);
+}
- /* set default connection timeout behavior */
- if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
- /* TODO: warning - unable to set connection timeout */
- rb_warn("%s\n", mysql_error(wrapper->client));
- }
+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 value;
+ return _mysql_client_options(self, MYSQL_OPT_WRITE_TIMEOUT, value);
}
static VALUE set_charset_name(VALUE self, VALUE value) {
- char * charset_name;
+ char *charset_name;
#ifdef HAVE_RUBY_ENCODING_H
- VALUE new_encoding;
+ size_t charset_name_len;
+ const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
+ rb_encoding *enc;
+ VALUE rb_enc;
#endif
GET_CLIENT(self);
+ charset_name = RSTRING_PTR(value);
+
#ifdef HAVE_RUBY_ENCODING_H
- new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
- if (new_encoding == Qnil) {
+ charset_name_len = RSTRING_LEN(value);
+ mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, charset_name_len);
+ if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
VALUE inspect = rb_inspect(value);
rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
} else {
- if (wrapper->encoding == Qnil) {
- wrapper->encoding = new_encoding;
- }
+ enc = rb_enc_find(mysql2rb->rb_name);
+ rb_enc = rb_enc_from_encoding(enc);
+ wrapper->encoding = rb_enc;
}
#endif
- charset_name = StringValuePtr(value);
-
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));
@@ -710,48 +1095,62 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
GET_CLIENT(self);
- if(!NIL_P(ca) || !NIL_P(key)) {
- mysql_ssl_set(wrapper->client,
- NIL_P(key) ? NULL : StringValuePtr(key),
- NIL_P(cert) ? NULL : StringValuePtr(cert),
- NIL_P(ca) ? NULL : StringValuePtr(ca),
- NIL_P(capath) ? NULL : StringValuePtr(capath),
- NIL_P(cipher) ? NULL : StringValuePtr(cipher));
- }
+ mysql_ssl_set(wrapper->client,
+ NIL_P(key) ? NULL : StringValuePtr(key),
+ NIL_P(cert) ? NULL : StringValuePtr(cert),
+ NIL_P(ca) ? NULL : StringValuePtr(ca),
+ NIL_P(capath) ? NULL : StringValuePtr(capath),
+ NIL_P(cipher) ? NULL : StringValuePtr(cipher));
return self;
}
-static VALUE init_connection(VALUE self) {
+static VALUE set_secure_auth(VALUE self, VALUE value) {
+ return _mysql_client_options(self, MYSQL_SECURE_AUTH, value);
+}
+
+static VALUE set_read_default_file(VALUE self, VALUE value) {
+ return _mysql_client_options(self, MYSQL_READ_DEFAULT_FILE, value);
+}
+
+static VALUE set_read_default_group(VALUE self, VALUE value) {
+ return _mysql_client_options(self, MYSQL_READ_DEFAULT_GROUP, value);
+}
+
+static VALUE initialize_ext(VALUE self) {
GET_CLIENT(self);
- if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
/* TODO: warning - not enough memory? */
return rb_raise_mysql2_error(wrapper);
}
- wrapper->closed = 0;
+ wrapper->initialized = 1;
return self;
}
void init_mysql2_client() {
- // verify the libmysql we're about to use was the version we were built against
- // https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99
+ /* 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_SERVER_VERSION[i] != 0; i++) {
+
+ for (i = 0; lib[i] != 0 && MYSQL_LINK_VERSION[i] != 0; i++) {
if (lib[i] == '.') {
dots++;
- // we only compare MAJOR and MINOR
+ /* we only compare MAJOR and MINOR */
if (dots == 2) break;
}
- if (lib[i] != MYSQL_SERVER_VERSION[i]) {
- rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_SERVER_VERSION, lib);
+ if (lib[i] != MYSQL_LINK_VERSION[i]) {
+ rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib);
return;
}
}
+#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);
@@ -760,6 +1159,7 @@ void init_mysql2_client() {
rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
+ rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
@@ -769,133 +1169,145 @@ void init_mysql2_client() {
rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
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, "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, "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);
#ifdef HAVE_RUBY_ENCODING_H
rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
#endif
- rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
+ rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1);
+ rb_define_private_method(cMysql2Client, "write_timeout=", set_write_timeout, 1);
+ rb_define_private_method(cMysql2Client, "local_infile=", set_local_infile, 1);
rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
+ rb_define_private_method(cMysql2Client, "secure_auth=", set_secure_auth, 1);
+ rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1);
+ rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1);
rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
- rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0);
+ rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
- intern_encoding_from_charset = rb_intern("encoding_from_charset");
-
sym_id = ID2SYM(rb_intern("id"));
sym_version = ID2SYM(rb_intern("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"));
intern_merge = rb_intern("merge");
+ intern_merge_bang = rb_intern("merge!");
intern_error_number_eql = rb_intern("error_number=");
intern_sql_state_eql = rb_intern("sql_state=");
#ifdef CLIENT_LONG_PASSWORD
rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
- INT2NUM(CLIENT_LONG_PASSWORD));
+ LONG2NUM(CLIENT_LONG_PASSWORD));
#endif
#ifdef CLIENT_FOUND_ROWS
rb_const_set(cMysql2Client, rb_intern("FOUND_ROWS"),
- INT2NUM(CLIENT_FOUND_ROWS));
+ LONG2NUM(CLIENT_FOUND_ROWS));
#endif
#ifdef CLIENT_LONG_FLAG
rb_const_set(cMysql2Client, rb_intern("LONG_FLAG"),
- INT2NUM(CLIENT_LONG_FLAG));
+ LONG2NUM(CLIENT_LONG_FLAG));
#endif
#ifdef CLIENT_CONNECT_WITH_DB
rb_const_set(cMysql2Client, rb_intern("CONNECT_WITH_DB"),
- INT2NUM(CLIENT_CONNECT_WITH_DB));
+ LONG2NUM(CLIENT_CONNECT_WITH_DB));
#endif
#ifdef CLIENT_NO_SCHEMA
rb_const_set(cMysql2Client, rb_intern("NO_SCHEMA"),
- INT2NUM(CLIENT_NO_SCHEMA));
+ LONG2NUM(CLIENT_NO_SCHEMA));
#endif
#ifdef CLIENT_COMPRESS
- rb_const_set(cMysql2Client, rb_intern("COMPRESS"), INT2NUM(CLIENT_COMPRESS));
+ rb_const_set(cMysql2Client, rb_intern("COMPRESS"), LONG2NUM(CLIENT_COMPRESS));
#endif
#ifdef CLIENT_ODBC
- rb_const_set(cMysql2Client, rb_intern("ODBC"), INT2NUM(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"),
- INT2NUM(CLIENT_LOCAL_FILES));
+ LONG2NUM(CLIENT_LOCAL_FILES));
#endif
#ifdef CLIENT_IGNORE_SPACE
rb_const_set(cMysql2Client, rb_intern("IGNORE_SPACE"),
- INT2NUM(CLIENT_IGNORE_SPACE));
+ LONG2NUM(CLIENT_IGNORE_SPACE));
#endif
#ifdef CLIENT_PROTOCOL_41
rb_const_set(cMysql2Client, rb_intern("PROTOCOL_41"),
- INT2NUM(CLIENT_PROTOCOL_41));
+ LONG2NUM(CLIENT_PROTOCOL_41));
#endif
#ifdef CLIENT_INTERACTIVE
rb_const_set(cMysql2Client, rb_intern("INTERACTIVE"),
- INT2NUM(CLIENT_INTERACTIVE));
+ LONG2NUM(CLIENT_INTERACTIVE));
#endif
#ifdef CLIENT_SSL
- rb_const_set(cMysql2Client, rb_intern("SSL"), INT2NUM(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"),
- INT2NUM(CLIENT_IGNORE_SIGPIPE));
+ LONG2NUM(CLIENT_IGNORE_SIGPIPE));
#endif
#ifdef CLIENT_TRANSACTIONS
rb_const_set(cMysql2Client, rb_intern("TRANSACTIONS"),
- INT2NUM(CLIENT_TRANSACTIONS));
+ LONG2NUM(CLIENT_TRANSACTIONS));
#endif
#ifdef CLIENT_RESERVED
- rb_const_set(cMysql2Client, rb_intern("RESERVED"), INT2NUM(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"),
- INT2NUM(CLIENT_SECURE_CONNECTION));
+ LONG2NUM(CLIENT_SECURE_CONNECTION));
#endif
#ifdef CLIENT_MULTI_STATEMENTS
rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"),
- INT2NUM(CLIENT_MULTI_STATEMENTS));
+ LONG2NUM(CLIENT_MULTI_STATEMENTS));
#endif
#ifdef CLIENT_PS_MULTI_RESULTS
rb_const_set(cMysql2Client, rb_intern("PS_MULTI_RESULTS"),
- INT2NUM(CLIENT_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"),
- INT2NUM(CLIENT_SSL_VERIFY_SERVER_CERT));
+ LONG2NUM(CLIENT_SSL_VERIFY_SERVER_CERT));
#endif
#ifdef CLIENT_REMEMBER_OPTIONS
rb_const_set(cMysql2Client, rb_intern("REMEMBER_OPTIONS"),
- INT2NUM(CLIENT_REMEMBER_OPTIONS));
+ LONG2NUM(CLIENT_REMEMBER_OPTIONS));
#endif
#ifdef CLIENT_ALL_FLAGS
rb_const_set(cMysql2Client, rb_intern("ALL_FLAGS"),
- INT2NUM(CLIENT_ALL_FLAGS));
+ LONG2NUM(CLIENT_ALL_FLAGS));
#endif
#ifdef CLIENT_BASIC_FLAGS
rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
- INT2NUM(CLIENT_BASIC_FLAGS));
+ LONG2NUM(CLIENT_BASIC_FLAGS));
#endif
}
diff --git a/ext/mysql2/client.h b/ext/mysql2/client.h
index af4f763..fedf9a3 100644
--- a/ext/mysql2/client.h
+++ b/ext/mysql2/client.h
@@ -1,24 +1,30 @@
#ifndef MYSQL2_CLIENT_H
#define MYSQL2_CLIENT_H
+#ifndef HAVE_RB_THREAD_CALL_WITHOUT_GVL
+#ifdef HAVE_RB_THREAD_BLOCKING_REGION
+
+/* emulate rb_thread_call_without_gvl with rb_thread_blocking_region */
+#define rb_thread_call_without_gvl(func, data1, ubf, data2) \
+ rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2)
+
+#else /* ! HAVE_RB_THREAD_BLOCKING_REGION */
/*
- * partial emulation of the 1.9 rb_thread_blocking_region under 1.8,
+ * partial emulation of the 2.0 rb_thread_call_without_gvl under 1.8,
* this is enough for dealing with blocking I/O functions in the
* presence of threads.
*/
-#ifndef HAVE_RB_THREAD_BLOCKING_REGION
#include <rubysig.h>
#define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
typedef void rb_unblock_function_t(void *);
-typedef VALUE rb_blocking_function_t(void *);
-static VALUE
-rb_thread_blocking_region(
- rb_blocking_function_t *func, void *data1,
+static void *
+rb_thread_call_without_gvl(
+ void *(*func)(void *), void *data1,
RB_MYSQL_UNUSED rb_unblock_function_t *ubf,
RB_MYSQL_UNUSED void *data2)
{
- VALUE rv;
+ void *rv;
TRAP_BEG;
rv = func(data1);
@@ -28,15 +34,20 @@ rb_thread_blocking_region(
}
#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
+#endif /* ! HAVE_RB_THREAD_CALL_WITHOUT_GVL */
void init_mysql2_client();
typedef struct {
VALUE encoding;
- int active;
+ VALUE active_thread; /* rb_thread_current() or Qnil */
int reconnect_enabled;
- int closed;
+ int active;
+ int connected;
+ int initialized;
+ int refcount;
+ int freed;
MYSQL *client;
} mysql_client_wrapper;
-#endif
\ No newline at end of file
+#endif
diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb
index 388520b..71a7a57 100644
--- a/ext/mysql2/extconf.rb
+++ b/ext/mysql2/extconf.rb
@@ -5,9 +5,14 @@ def asplode lib
abort "-----\n#{lib} is missing. please check your installation of mysql and try again.\n-----"
end
+# 2.0-only
+have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
+
# 1.9-only
have_func('rb_thread_blocking_region')
have_func('rb_wait_for_single_fd')
+have_func('rb_hash_dup')
+have_func('rb_intern3')
# borrowed from mysqlplus
# http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb
@@ -17,6 +22,7 @@ dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[
/opt/local/mysql
/opt/local/lib/mysql5
/usr
+ /usr/mysql
/usr/local
/usr/local/mysql
/usr/local/mysql-*
@@ -25,20 +31,39 @@ dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[
GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}"
-if RUBY_PLATFORM =~ /mswin|mingw/
- inc, lib = dir_config('mysql')
- exit 1 unless have_library("libmysql")
-elsif mc = (with_config('mysql-config') || Dir[GLOB].first) then
+# If the user has provided a --with-mysql-dir argument, we must respect it or fail.
+inc, lib = dir_config('mysql')
+if inc && lib
+ # Ruby versions not incorporating the mkmf fix at
+ # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717
+ # do not properly search for lib directories, and must be corrected
+ unless lib && lib[-3, 3] == 'lib'
+ @libdir_basename = 'lib'
+ inc, lib = dir_config('mysql')
+ end
+ abort "-----\nCannot find include dir at #{inc}\n-----" unless inc && File.directory?(inc)
+ abort "-----\nCannot find library dir at #{lib}\n-----" unless lib && File.directory?(lib)
+ warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----"
+ rpath_dir = lib
+elsif mc = (with_config('mysql-config') || Dir[GLOB].first)
+ # If the user has provided a --with-mysql-config argument, we must respect it or fail.
+ # If the user gave --with-mysql-config with no argument means we should try to find it.
mc = Dir[GLOB].first if mc == true
- cflags = `#{mc} --cflags`.chomp
+ abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exists?(mc)
+ abort "-----\nCannot execute mysql_config at #{mc}\n-----" unless File.executable?(mc)
+ warn "-----\nUsing mysql_config at #{mc}\n-----"
+ ver = `#{mc} --version`.chomp.to_f
+ includes = `#{mc} --include`.chomp
exit 1 if $? != 0
libs = `#{mc} --libs_r`.chomp
- if libs.empty?
+ # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r).
+ if ver >= 5.5 || libs.empty?
libs = `#{mc} --libs`.chomp
end
exit 1 if $? != 0
- $CPPFLAGS += ' ' + cflags
+ $INCFLAGS += ' ' + includes
$libs = libs + " " + $libs
+ rpath_dir = libs
else
inc, lib = dir_config('mysql', '/usr/local')
libs = ['m', 'z', 'socket', 'nsl', 'mygcc']
@@ -46,11 +71,16 @@ else
exit 1 if libs.empty?
have_library(libs.shift)
end
+ rpath_dir = lib
+end
+
+if RUBY_PLATFORM =~ /mswin|mingw/
+ exit 1 unless have_library('libmysql')
end
-if have_header('mysql.h') then
+if have_header('mysql.h')
prefix = nil
-elsif have_header('mysql/mysql.h') then
+elsif have_header('mysql/mysql.h')
prefix = 'mysql'
else
asplode 'mysql.h'
@@ -61,13 +91,40 @@ end
asplode h unless have_header h
end
-unless RUBY_PLATFORM =~ /mswin/ or RUBY_PLATFORM =~ /sparc/
- $CFLAGS << ' -Wall -funroll-loops'
+# These gcc style flags are also supported by clang and xcode compilers,
+# so we'll use a does-it-work test instead of an is-it-gcc test.
+gcc_flags = ' -Wall -funroll-loops'
+if try_link('int main() {return 0;}', gcc_flags)
+ $CFLAGS << gcc_flags
end
-# $CFLAGS << ' -O0 -ggdb3 -Wextra'
-if hard_mysql_path = $libs[%r{-L(/[^ ]+)}, 1]
- $LDFLAGS << " -Wl,-rpath,#{hard_mysql_path}"
+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
create_makefile('mysql2/mysql2')
diff --git a/ext/mysql2/mysql2_ext.h b/ext/mysql2/mysql2_ext.h
index 8bb08c4..36a4cdf 100644
--- a/ext/mysql2/mysql2_ext.h
+++ b/ext/mysql2/mysql2_ext.h
@@ -1,9 +1,9 @@
#ifndef MYSQL2_EXT
#define MYSQL2_EXT
-// tell rbx not to use it's caching compat layer
-// by doing this we're making a promize to RBX that
-// we'll never modify the pointers we get back from RSTRING_PTR
+/* 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 <ruby.h>
#include <fcntl.h>
@@ -29,6 +29,9 @@ typedef unsigned int uint;
#ifdef HAVE_RUBY_ENCODING_H
#include <ruby/encoding.h>
#endif
+#ifdef HAVE_RUBY_THREAD_H
+#include <ruby/thread.h>
+#endif
#if defined(__GNUC__) && (__GNUC__ >= 3)
#define RB_MYSQL_UNUSED __attribute__ ((unused))
diff --git a/ext/mysql2/mysql_enc_name_to_ruby.h b/ext/mysql2/mysql_enc_name_to_ruby.h
new file mode 100644
index 0000000..dfabeef
--- /dev/null
+++ b/ext/mysql2/mysql_enc_name_to_ruby.h
@@ -0,0 +1,168 @@
+/* C code produced by gperf version 3.0.3 */
+/* 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 <bug-gnu-gperf at gnu.org>."
+#endif
+
+struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; };
+/* maximum key range = 66, 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[] =
+ {
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 40, 5,
+ 0, 69, 0, 40, 25, 20, 10, 55, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 35, 5, 0,
+ 10, 0, 20, 0, 5, 5, 69, 0, 10, 15,
+ 0, 0, 69, 69, 25, 5, 5, 0, 69, 30,
+ 69, 0, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69
+ };
+ return len + asso_values[(unsigned char)str[2]] + asso_values[(unsigned char)str[0]] + asso_values[(unsigned char)str[len - 1]];
+}
+
+#ifdef __GNUC__
+__inline
+#ifdef __GNUC_STDC_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 = 39,
+ MIN_WORD_LENGTH = 3,
+ MAX_WORD_LENGTH = 8,
+ MIN_HASH_VALUE = 3,
+ MAX_HASH_VALUE = 68
+ };
+
+ static const struct mysql2_mysql_enc_name_to_rb_map wordlist[] =
+ {
+ {""}, {""}, {""},
+ {"gbk", "GBK"},
+ {""},
+ {"greek", "ISO-8859-7"},
+ {"gb2312", "GB2312"},
+ {"keybcs2", NULL},
+ {""},
+ {"ucs2", "UTF-16BE"},
+ {"koi8u", "KOI8-R"},
+ {"binary", "ASCII-8BIT"},
+ {"eucjpms", "eucJP-ms"},
+ {""},
+ {"ujis", "eucJP-ms"},
+ {"cp852", "CP852"},
+ {"cp1251", "Windows-1251"},
+ {"geostd8", NULL},
+ {""},
+ {"sjis", "Shift_JIS"},
+ {"macce", "macCentEuro"},
+ {"latin2", "ISO-8859-2"},
+ {""},
+ {"macroman", "macRoman"},
+ {"dec8", NULL},
+ {"utf32", "UTF-32"},
+ {"latin1", "ISO-8859-1"},
+ {"utf8mb4", "UTF-8"},
+ {"hp8", NULL},
+ {"swe7", NULL},
+ {"euckr", "EUC-KR"},
+ {"cp1257", "Windows-1257"},
+ {""}, {""},
+ {"utf8", "UTF-8"},
+ {"koi8r", "KOI8-R"},
+ {"cp1256", "Windows-1256"},
+ {""}, {""}, {""},
+ {"cp866", "IBM866"},
+ {"latin7", "ISO-8859-13"},
+ {""}, {""}, {""},
+ {"ascii", "US-ASCII"},
+ {"hebrew", "ISO-8859-8"},
+ {""}, {""},
+ {"big5", "Big5"},
+ {"utf16", "UTF-16"},
+ {"cp1250", "Windows-1250"},
+ {""}, {""}, {""},
+ {"cp850", "CP850"},
+ {"tis620", "TIS-620"},
+ {""}, {""}, {""},
+ {"cp932", "Windows-31J"},
+ {"latin5", "ISO-8859-9"},
+ {""}, {""}, {""}, {""}, {""}, {""},
+ {"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;
+}
diff --git a/ext/mysql2/mysql_enc_to_ruby.h b/ext/mysql2/mysql_enc_to_ruby.h
new file mode 100644
index 0000000..37dbf6f
--- /dev/null
+++ b/ext/mysql2/mysql_enc_to_ruby.h
@@ -0,0 +1,246 @@
+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",
+ NULL,
+ "Windows-1256",
+ "Windows-1257",
+ "Windows-1257",
+ "UTF-32",
+ "UTF-32",
+ NULL,
+ "ASCII-8BIT",
+ NULL,
+ "US-ASCII",
+ "Windows-1250",
+ "Windows-1256",
+ "IBM866",
+ NULL,
+ "ISO-8859-7",
+ "ISO-8859-8",
+ NULL,
+ NULL,
+ "KOI8-R",
+ "KOI8-R",
+ NULL,
+ "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",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 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",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "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,
+ 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",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 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"
+};
+
diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c
index 8a2834b..bcdc2c8 100644
--- a/ext/mysql2/result.c
+++ b/ext/mysql2/result.c
@@ -1,6 +1,8 @@
#include <mysql2_ext.h>
#include <stdint.h>
+#include "mysql_enc_to_ruby.h"
+
#ifdef HAVE_RUBY_ENCODING_H
static rb_encoding *binaryEncoding;
#endif
@@ -27,7 +29,7 @@ static rb_encoding *binaryEncoding;
* (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
*/
#define MYSQL2_MIN_TIME 2678400ULL
-#elif SIZEOF_INT < SIZEOF_LONG // 64bit Ruby 1.8
+#elif SIZEOF_INT < SIZEOF_LONG /* 64bit Ruby 1.8 */
/* 0139-1-1 00:00:00 UTC
*
* (139*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
@@ -51,11 +53,9 @@ static VALUE cMysql2Result;
static VALUE cBigDecimal, cDate, cDateTime;
static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
extern VALUE mMysql2, cMysql2Client, cMysql2Error;
-static VALUE intern_encoding_from_charset;
-static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code,
- intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
+static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
- sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast;
+ sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
static ID intern_merge;
static void rb_mysql_result_mark(void * wrapper) {
@@ -64,22 +64,33 @@ static void rb_mysql_result_mark(void * wrapper) {
rb_gc_mark(w->fields);
rb_gc_mark(w->rows);
rb_gc_mark(w->encoding);
+ rb_gc_mark(w->client);
}
}
/* this may be called manually or during GC */
static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
if (wrapper && wrapper->resultFreed != 1) {
+ /* FIXME: this may call flush_use_result, which can hit the socket */
mysql_free_result(wrapper->result);
wrapper->resultFreed = 1;
}
}
/* this is called during GC */
-static void rb_mysql_result_free(void * wrapper) {
- mysql2_result_wrapper * w = wrapper;
- /* FIXME: this may call flush_use_result, which can hit the socket */
- rb_mysql_result_free_result(w);
+static void rb_mysql_result_free(void *ptr) {
+ mysql2_result_wrapper * wrapper = ptr;
+ rb_mysql_result_free_result(wrapper);
+
+ // If the GC gets to client first it will be nil
+ if (wrapper->client != Qnil) {
+ wrapper->client_wrapper->refcount--;
+ if (wrapper->client_wrapper->refcount == 0) {
+ xfree(wrapper->client_wrapper->client);
+ xfree(wrapper->client_wrapper);
+ }
+ }
+
xfree(wrapper);
}
@@ -88,10 +99,10 @@ static void rb_mysql_result_free(void * wrapper) {
* reliable way for us to tell this so we'll always release the GVL
* to be safe
*/
-static VALUE nogvl_fetch_row(void *ptr) {
+static void *nogvl_fetch_row(void *ptr) {
MYSQL_RES *result = ptr;
- return (VALUE)mysql_fetch_row(result);
+ return mysql_fetch_row(result);
}
static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
@@ -114,15 +125,18 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
field = mysql_fetch_field_direct(wrapper->result, idx);
if (symbolize_keys) {
- VALUE colStr;
char buf[field->name_length+1];
memcpy(buf, field->name, field->name_length);
buf[field->name_length] = 0;
+
+#ifdef HAVE_RB_INTERN3
+ rb_field = rb_intern3(buf, field->name_length, rb_utf8_encoding());
+ rb_field = ID2SYM(rb_field);
+#else
+ VALUE colStr;
colStr = rb_str_new2(buf);
-#ifdef HAVE_RUBY_ENCODING_H
- rb_enc_associate(colStr, rb_utf8_encoding());
-#endif
rb_field = ID2SYM(rb_to_id(colStr));
+#endif
} else {
rb_field = rb_str_new(field->name, field->name_length);
#ifdef HAVE_RUBY_ENCODING_H
@@ -140,20 +154,27 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
#ifdef HAVE_RUBY_ENCODING_H
static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) {
- // if binary flag is set, respect it's wishes
+ /* if binary flag is set, respect it's 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
- VALUE new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset_code, 1, INT2NUM(field.charsetnr));
- if (new_encoding != Qnil) {
- // use the field encoding we were able to match
- rb_encoding *enc = rb_to_encoding(new_encoding);
- rb_enc_associate(val, enc);
+ /* lookup the encoding configured on this field */
+ const char *enc_name;
+ int enc_index;
+
+ enc_name = mysql2_mysql_enc_to_rb[field.charsetnr-1];
+ 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
+ /* 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);
}
@@ -163,11 +184,10 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
#endif
-static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast) {
+static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) {
VALUE rowVal;
mysql2_result_wrapper * wrapper;
MYSQL_ROW row;
- MYSQL_FIELD * fields = NULL;
unsigned int i = 0;
unsigned long * fieldLengths;
void * ptr;
@@ -183,7 +203,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
#endif
ptr = wrapper->result;
- row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
+ row = (MYSQL_ROW)rb_thread_call_without_gvl(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
if (row == NULL) {
return Qnil;
}
@@ -193,7 +213,6 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
} else {
rowVal = rb_hash_new();
}
- fields = mysql_fetch_fields(wrapper->result);
fieldLengths = mysql_fetch_lengths(wrapper->result);
if (wrapper->fields == Qnil) {
wrapper->numberOfFields = mysql_num_fields(wrapper->result);
@@ -217,34 +236,40 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
}
} else {
switch(type) {
- case MYSQL_TYPE_NULL: // NULL-type field
+ case MYSQL_TYPE_NULL: /* NULL-type field */
val = Qnil;
break;
- case MYSQL_TYPE_BIT: // BIT field (MySQL 5.0.3 and up)
- val = rb_str_new(row[i], fieldLengths[i]);
+ case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
+ if (castBool && fields[i].length == 1) {
+ val = *row[i] == 1 ? Qtrue : Qfalse;
+ }else{
+ val = rb_str_new(row[i], fieldLengths[i]);
+ }
break;
- case MYSQL_TYPE_TINY: // TINYINT field
+ case MYSQL_TYPE_TINY: /* TINYINT field */
if (castBool && fields[i].length == 1) {
- val = *row[i] == '1' ? Qtrue : Qfalse;
+ 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
+ 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 (strtod(row[i], NULL) == 0.000000){
+ 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(cBigDecimal, intern_new, 1, opt_decimal_zero);
}else{
val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i]));
}
break;
- case MYSQL_TYPE_FLOAT: // FLOAT field
- case MYSQL_TYPE_DOUBLE: { // DOUBLE or REAL field
+ 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){
@@ -254,25 +279,35 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
}
break;
}
- case MYSQL_TYPE_TIME: { // TIME field
- int hour, min, sec, tokens;
- tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec);
- val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
+ case MYSQL_TYPE_TIME: { /* TIME field */
+ int tokens;
+ unsigned int hour=0, min=0, sec=0;
+ tokens = sscanf(row[i], "%2u:%2u:%2u", &hour, &min, &sec);
+ if (tokens < 3) {
+ val = Qnil;
+ break;
+ }
+ val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec));
if (!NIL_P(app_timezone)) {
if (app_timezone == intern_local) {
val = rb_funcall(val, intern_localtime, 0);
- } else { // utc
+ } else { /* utc */
val = rb_funcall(val, intern_utc, 0);
}
}
break;
}
- case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
- case MYSQL_TYPE_DATETIME: { // DATETIME field
- unsigned int year, month, day, hour, min, sec, tokens;
+ 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;
uint64_t seconds;
- tokens = sscanf(row[i], "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
+ tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6u", &year, &month, &day, &hour, &min, &sec, &msec);
+ if (tokens < 6) { /* msec might be empty */
+ val = Qnil;
+ break;
+ }
seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec;
if (seconds == 0) {
@@ -282,26 +317,26 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
val = Qnil;
} else {
- if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
+ if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
VALUE offset = INT2NUM(0);
if (db_timezone == intern_local) {
offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
}
- val = rb_funcall(cDateTime, intern_civil, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), offset);
+ val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset);
if (!NIL_P(app_timezone)) {
if (app_timezone == intern_local) {
offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
val = rb_funcall(val, intern_new_offset, 1, offset);
- } else { // utc
+ } else { /* utc */
val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
}
}
- } else {
- val = rb_funcall(rb_cTime, db_timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
+ } else { /* use Time, supports microseconds */
+ val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
if (!NIL_P(app_timezone)) {
if (app_timezone == intern_local) {
val = rb_funcall(val, intern_localtime, 0);
- } else { // utc
+ } else { /* utc */
val = rb_funcall(val, intern_utc, 0);
}
}
@@ -310,10 +345,15 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
}
break;
}
- case MYSQL_TYPE_DATE: // DATE field
- case MYSQL_TYPE_NEWDATE: { // Newer const used > 5.0
- int year, month, day, tokens;
- tokens = sscanf(row[i], "%4d-%2d-%2d", &year, &month, &day);
+ 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 {
@@ -321,7 +361,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
val = Qnil;
} else {
- val = rb_funcall(cDate, intern_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
+ val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day));
}
}
break;
@@ -332,10 +372,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
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
+ 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]);
#ifdef HAVE_RUBY_ENCODING_H
@@ -369,6 +409,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
GetMysql2Result(self, wrapper);
defaults = rb_iv_get(self, "@query_options");
+ Check_Type(defaults, T_HASH);
if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
symbolizeKeys = 1;
}
@@ -392,11 +433,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
ID db_timezone, app_timezone, dbTz, appTz;
mysql2_result_wrapper * wrapper;
unsigned long i;
- int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1;
+ int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1, streaming = 0;
+ MYSQL_FIELD * fields = NULL;
GetMysql2Result(self, wrapper);
defaults = rb_iv_get(self, "@query_options");
+ Check_Type(defaults, T_HASH);
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
opts = rb_funcall(defaults, intern_merge, 1, opts);
} else {
@@ -423,6 +466,14 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
cast = 0;
}
+ if(rb_hash_aref(opts, sym_stream) == Qtrue) {
+ streaming = 1;
+ }
+
+ if(streaming && cacheRows) {
+ rb_warn("cacheRows is ignored if streaming is true");
+ }
+
dbTz = rb_hash_aref(opts, sym_database_timezone);
if (dbTz == sym_local) {
db_timezone = intern_local;
@@ -445,48 +496,81 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
}
if (wrapper->lastRowProcessed == 0) {
- wrapper->numberOfRows = mysql_num_rows(wrapper->result);
- if (wrapper->numberOfRows == 0) {
+ if(streaming) {
+ /* We can't get number of rows if we're streaming, */
+ /* until we've finished fetching all rows */
+ wrapper->numberOfRows = 0;
wrapper->rows = rb_ary_new();
- return wrapper->rows;
+ } else {
+ wrapper->numberOfRows = mysql_num_rows(wrapper->result);
+ if (wrapper->numberOfRows == 0) {
+ wrapper->rows = rb_ary_new();
+ return wrapper->rows;
+ }
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
}
- wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
}
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
- // we've already read the entire dataset from the C result into our
- // internal array. Lets hand that over to the user since it's ready to go
- 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);
- for (i = 0; i < wrapper->numberOfRows; i++) {
+ if (streaming) {
+ if(!wrapper->streamingComplete) {
VALUE row;
- if (cacheRows && i < rowsProcessed) {
- row = rb_ary_entry(wrapper->rows, i);
- } else {
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast);
- if (cacheRows) {
- rb_ary_store(wrapper->rows, i, row);
+
+ fields = mysql_fetch_fields(wrapper->result);
+
+ do {
+ row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
+
+ if (block != Qnil && row != Qnil) {
+ rb_yield(row);
+ wrapper->lastRowProcessed++;
}
- wrapper->lastRowProcessed++;
- }
+ } while(row != Qnil);
- if (row == Qnil) {
- // we don't need the mysql C dataset around anymore, peace it
- rb_mysql_result_free_result(wrapper);
- return Qnil;
+ rb_mysql_result_free_result(wrapper);
+
+ wrapper->numberOfRows = wrapper->lastRowProcessed;
+ wrapper->streamingComplete = 1;
+ } 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 (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 (cacheRows && i < rowsProcessed) {
+ row = rb_ary_entry(wrapper->rows, i);
+ } else {
+ row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
+ if (cacheRows) {
+ rb_ary_store(wrapper->rows, i, row);
+ }
+ wrapper->lastRowProcessed++;
+ }
+
+ if (row == Qnil) {
+ /* we don't need the mysql C dataset around anymore, peace it */
+ rb_mysql_result_free_result(wrapper);
+ return Qnil;
+ }
- if (block != Qnil) {
- rb_yield(row);
+ if (block != Qnil) {
+ rb_yield(row);
+ }
+ }
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
+ /* we don't need the mysql C dataset around anymore, peace it */
+ rb_mysql_result_free_result(wrapper);
}
- }
- if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
- // we don't need the mysql C dataset around anymore, peace it
- rb_mysql_result_free_result(wrapper);
}
}
@@ -497,12 +581,19 @@ static VALUE rb_mysql_result_count(VALUE self) {
mysql2_result_wrapper *wrapper;
GetMysql2Result(self, wrapper);
-
- return INT2FIX(mysql_num_rows(wrapper->result));
+ if(wrapper->resultFreed) {
+ if (wrapper->streamingComplete){
+ return LONG2NUM(wrapper->numberOfRows);
+ } else {
+ return LONG2NUM(RARRAY_LEN(wrapper->rows));
+ }
+ } else {
+ return INT2FIX(mysql_num_rows(wrapper->result));
+ }
}
/* Mysql2::Result */
-VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
+VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) {
VALUE obj;
mysql2_result_wrapper * wrapper;
obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
@@ -513,8 +604,16 @@ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
wrapper->result = r;
wrapper->fields = Qnil;
wrapper->rows = Qnil;
- wrapper->encoding = Qnil;
+ wrapper->encoding = encoding;
+ wrapper->streamingComplete = 0;
+ wrapper->client = client;
+ wrapper->client_wrapper = DATA_PTR(client);
+ wrapper->client_wrapper->refcount++;
+
rb_obj_call_init(obj, 0, NULL);
+
+ rb_iv_set(obj, "@query_options", options);
+
return obj;
}
@@ -529,9 +628,6 @@ void init_mysql2_result() {
rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
rb_define_alias(cMysql2Result, "size", "count");
- intern_encoding_from_charset = rb_intern("encoding_from_charset");
- intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code");
-
intern_new = rb_intern("new");
intern_utc = rb_intern("utc");
intern_local = rb_intern("local");
@@ -551,9 +647,11 @@ void init_mysql2_result() {
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
+ 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);
diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h
index efc3376..2bb6207 100644
--- a/ext/mysql2/result.h
+++ b/ext/mysql2/result.h
@@ -2,17 +2,20 @@
#define MYSQL2_RESULT_H
void init_mysql2_result();
-VALUE rb_mysql_result_to_obj(MYSQL_RES * r);
+VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r);
typedef struct {
VALUE fields;
VALUE rows;
+ VALUE client;
VALUE encoding;
unsigned int numberOfFields;
unsigned long numberOfRows;
unsigned long lastRowProcessed;
+ char streamingComplete;
char resultFreed;
MYSQL_RES *result;
+ mysql_client_wrapper *client_wrapper;
} mysql2_result_wrapper;
#define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj));
diff --git a/lib/mysql2.rb b/lib/mysql2.rb
index 2db3c47..45d0a54 100644
--- a/lib/mysql2.rb
+++ b/lib/mysql2.rb
@@ -5,8 +5,8 @@ require 'rational' unless RUBY_VERSION >= '1.9.2'
require 'mysql2/version' unless defined? Mysql2::VERSION
require 'mysql2/error'
-require 'mysql2/result'
require 'mysql2/mysql2'
+require 'mysql2/result'
require 'mysql2/client'
# = Mysql2
@@ -16,6 +16,26 @@ module Mysql2
end
if defined?(ActiveRecord::VERSION::STRING) && ActiveRecord::VERSION::STRING < "3.1"
- puts "WARNING: This version of mysql2 (#{Mysql2::VERSION}) doesn't ship with the ActiveRecord adapter bundled anymore as it's now part of Rails 3.1"
- puts "WARNING: Please use the 0.2.x releases if you plan on using it in Rails <= 3.0.x"
+ 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::Util
+
+ #
+ # Rekey a string-keyed hash with equivalent symbols.
+ #
+ def self.key_hash_as_symbols(hash)
+ return nil unless hash
+ Hash[hash.map { |k,v| [k.to_sym, v] }]
+ end
+
end
diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb
index 20ed442..6290abc 100644
--- a/lib/mysql2/client.rb
+++ b/lib/mysql2/client.rb
@@ -1,6 +1,6 @@
module Mysql2
class Client
- attr_reader :query_options
+ attr_reader :query_options, :read_timeout
@@default_query_options = {
:as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
:async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
@@ -10,37 +10,60 @@ module Mysql2
:application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
:cache_rows => true, # tells Mysql2 to use it's internal row cache for results
:connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
- :cast => true
+ :cast => true,
+ :default_file => nil,
+ :default_group => nil
}
def initialize(opts = {})
+ opts = Mysql2::Util.key_hash_as_symbols( opts )
+ @read_timeout = nil
@query_options = @@default_query_options.dup
@query_options.merge! opts
- init_connection
+ initialize_ext
- [:reconnect, :connect_timeout].each do |key|
+ [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth].each do |key|
next unless opts.key?(key)
- send(:"#{key}=", opts[key])
+ case key
+ when :reconnect, :local_infile, :secure_auth
+ send(:"#{key}=", !!opts[key])
+ when :connect_timeout, :read_timeout, :write_timeout
+ send(:"#{key}=", opts[key].to_i)
+ else
+ send(:"#{key}=", opts[key])
+ end
end
+
# force the encoding to utf8
self.charset_name = opts[:encoding] || 'utf8'
- @read_timeout = opts[:read_timeout]
- if @read_timeout and @read_timeout < 0
- raise Mysql2::Error, "read_timeout must be a positive integer, you passed #{@read_timeout}"
- end
+ ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
+ ssl_set(*ssl_options) if ssl_options.any?
- ssl_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher))
+ if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) }
+ warn "============= WARNING FROM mysql2 ============="
+ warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future."
+ warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
+ warn "============= END WARNING FROM mysql2 ========="
+ end
- user = opts[:username]
- pass = opts[:password]
- host = opts[:host] || 'localhost'
- port = opts[:port] || 3306
- database = opts[:database]
- socket = opts[:socket]
+ 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]
flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags]
+ # Correct the data types before passing these values down to the C level
+ user = user.to_s unless user.nil?
+ pass = pass.to_s unless pass.nil?
+ host = host.to_s unless host.nil?
+ port = port.to_i unless port.nil?
+ database = database.to_s unless database.nil?
+ socket = socket.to_s unless socket.nil?
+
connect user, pass, host, port, database, socket, flags
end
@@ -48,190 +71,12 @@ module Mysql2
@@default_query_options
end
- # NOTE: from ruby-mysql
- if defined? Encoding
- CHARSET_MAP = {
- "armscii8" => nil,
- "ascii" => Encoding::US_ASCII,
- "big5" => Encoding::Big5,
- "binary" => Encoding::ASCII_8BIT,
- "cp1250" => Encoding::Windows_1250,
- "cp1251" => Encoding::Windows_1251,
- "cp1256" => Encoding::Windows_1256,
- "cp1257" => Encoding::Windows_1257,
- "cp850" => Encoding::CP850,
- "cp852" => Encoding::CP852,
- "cp866" => Encoding::IBM866,
- "cp932" => Encoding::Windows_31J,
- "dec8" => nil,
- "eucjpms" => Encoding::EucJP_ms,
- "euckr" => Encoding::EUC_KR,
- "gb2312" => Encoding::EUC_CN,
- "gbk" => Encoding::GBK,
- "geostd8" => nil,
- "greek" => Encoding::ISO_8859_7,
- "hebrew" => Encoding::ISO_8859_8,
- "hp8" => nil,
- "keybcs2" => nil,
- "koi8r" => Encoding::KOI8_R,
- "koi8u" => Encoding::KOI8_U,
- "latin1" => Encoding::ISO_8859_1,
- "latin2" => Encoding::ISO_8859_2,
- "latin5" => Encoding::ISO_8859_9,
- "latin7" => Encoding::ISO_8859_13,
- "macce" => Encoding::MacCentEuro,
- "macroman" => Encoding::MacRoman,
- "sjis" => Encoding::SHIFT_JIS,
- "swe7" => nil,
- "tis620" => Encoding::TIS_620,
- "ucs2" => Encoding::UTF_16BE,
- "ujis" => Encoding::EucJP_ms,
- "utf8" => Encoding::UTF_8,
- }
-
- MYSQL_CHARSET_MAP = {
- 1 => {:name => "big5", :collation => "big5_chinese_ci"},
- 2 => {:name => "latin2", :collation => "latin2_czech_cs"},
- 3 => {:name => "dec8", :collation => "dec8_swedish_ci"},
- 4 => {:name => "cp850", :collation => "cp850_general_ci"},
- 5 => {:name => "latin1", :collation => "latin1_german1_ci"},
- 6 => {:name => "hp8", :collation => "hp8_english_ci"},
- 7 => {:name => "koi8r", :collation => "koi8r_general_ci"},
- 8 => {:name => "latin1", :collation => "latin1_swedish_ci"},
- 9 => {:name => "latin2", :collation => "latin2_general_ci"},
- 10 => {:name => "swe7", :collation => "swe7_swedish_ci"},
- 11 => {:name => "ascii", :collation => "ascii_general_ci"},
- 12 => {:name => "ujis", :collation => "ujis_japanese_ci"},
- 13 => {:name => "sjis", :collation => "sjis_japanese_ci"},
- 14 => {:name => "cp1251", :collation => "cp1251_bulgarian_ci"},
- 15 => {:name => "latin1", :collation => "latin1_danish_ci"},
- 16 => {:name => "hebrew", :collation => "hebrew_general_ci"},
- 17 => {:name => "filename", :collation => "filename"},
- 18 => {:name => "tis620", :collation => "tis620_thai_ci"},
- 19 => {:name => "euckr", :collation => "euckr_korean_ci"},
- 20 => {:name => "latin7", :collation => "latin7_estonian_cs"},
- 21 => {:name => "latin2", :collation => "latin2_hungarian_ci"},
- 22 => {:name => "koi8u", :collation => "koi8u_general_ci"},
- 23 => {:name => "cp1251", :collation => "cp1251_ukrainian_ci"},
- 24 => {:name => "gb2312", :collation => "gb2312_chinese_ci"},
- 25 => {:name => "greek", :collation => "greek_general_ci"},
- 26 => {:name => "cp1250", :collation => "cp1250_general_ci"},
- 27 => {:name => "latin2", :collation => "latin2_croatian_ci"},
- 28 => {:name => "gbk", :collation => "gbk_chinese_ci"},
- 29 => {:name => "cp1257", :collation => "cp1257_lithuanian_ci"},
- 30 => {:name => "latin5", :collation => "latin5_turkish_ci"},
- 31 => {:name => "latin1", :collation => "latin1_german2_ci"},
- 32 => {:name => "armscii8", :collation => "armscii8_general_ci"},
- 33 => {:name => "utf8", :collation => "utf8_general_ci"},
- 34 => {:name => "cp1250", :collation => "cp1250_czech_cs"},
- 35 => {:name => "ucs2", :collation => "ucs2_general_ci"},
- 36 => {:name => "cp866", :collation => "cp866_general_ci"},
- 37 => {:name => "keybcs2", :collation => "keybcs2_general_ci"},
- 38 => {:name => "macce", :collation => "macce_general_ci"},
- 39 => {:name => "macroman", :collation => "macroman_general_ci"},
- 40 => {:name => "cp852", :collation => "cp852_general_ci"},
- 41 => {:name => "latin7", :collation => "latin7_general_ci"},
- 42 => {:name => "latin7", :collation => "latin7_general_cs"},
- 43 => {:name => "macce", :collation => "macce_bin"},
- 44 => {:name => "cp1250", :collation => "cp1250_croatian_ci"},
- 47 => {:name => "latin1", :collation => "latin1_bin"},
- 48 => {:name => "latin1", :collation => "latin1_general_ci"},
- 49 => {:name => "latin1", :collation => "latin1_general_cs"},
- 50 => {:name => "cp1251", :collation => "cp1251_bin"},
- 51 => {:name => "cp1251", :collation => "cp1251_general_ci"},
- 52 => {:name => "cp1251", :collation => "cp1251_general_cs"},
- 53 => {:name => "macroman", :collation => "macroman_bin"},
- 57 => {:name => "cp1256", :collation => "cp1256_general_ci"},
- 58 => {:name => "cp1257", :collation => "cp1257_bin"},
- 59 => {:name => "cp1257", :collation => "cp1257_general_ci"},
- 63 => {:name => "binary", :collation => "binary"},
- 64 => {:name => "armscii8", :collation => "armscii8_bin"},
- 65 => {:name => "ascii", :collation => "ascii_bin"},
- 66 => {:name => "cp1250", :collation => "cp1250_bin"},
- 67 => {:name => "cp1256", :collation => "cp1256_bin"},
- 68 => {:name => "cp866", :collation => "cp866_bin"},
- 69 => {:name => "dec8", :collation => "dec8_bin"},
- 70 => {:name => "greek", :collation => "greek_bin"},
- 71 => {:name => "hebrew", :collation => "hebrew_bin"},
- 72 => {:name => "hp8", :collation => "hp8_bin"},
- 73 => {:name => "keybcs2", :collation => "keybcs2_bin"},
- 74 => {:name => "koi8r", :collation => "koi8r_bin"},
- 75 => {:name => "koi8u", :collation => "koi8u_bin"},
- 77 => {:name => "latin2", :collation => "latin2_bin"},
- 78 => {:name => "latin5", :collation => "latin5_bin"},
- 79 => {:name => "latin7", :collation => "latin7_bin"},
- 80 => {:name => "cp850", :collation => "cp850_bin"},
- 81 => {:name => "cp852", :collation => "cp852_bin"},
- 82 => {:name => "swe7", :collation => "swe7_bin"},
- 83 => {:name => "utf8", :collation => "utf8_bin"},
- 84 => {:name => "big5", :collation => "big5_bin"},
- 85 => {:name => "euckr", :collation => "euckr_bin"},
- 86 => {:name => "gb2312", :collation => "gb2312_bin"},
- 87 => {:name => "gbk", :collation => "gbk_bin"},
- 88 => {:name => "sjis", :collation => "sjis_bin"},
- 89 => {:name => "tis620", :collation => "tis620_bin"},
- 90 => {:name => "ucs2", :collation => "ucs2_bin"},
- 91 => {:name => "ujis", :collation => "ujis_bin"},
- 92 => {:name => "geostd8", :collation => "geostd8_general_ci"},
- 93 => {:name => "geostd8", :collation => "geostd8_bin"},
- 94 => {:name => "latin1", :collation => "latin1_spanish_ci"},
- 95 => {:name => "cp932", :collation => "cp932_japanese_ci"},
- 96 => {:name => "cp932", :collation => "cp932_bin"},
- 97 => {:name => "eucjpms", :collation => "eucjpms_japanese_ci"},
- 98 => {:name => "eucjpms", :collation => "eucjpms_bin"},
- 99 => {:name => "cp1250", :collation => "cp1250_polish_ci"},
- 128 => {:name => "ucs2", :collation => "ucs2_unicode_ci"},
- 129 => {:name => "ucs2", :collation => "ucs2_icelandic_ci"},
- 130 => {:name => "ucs2", :collation => "ucs2_latvian_ci"},
- 131 => {:name => "ucs2", :collation => "ucs2_romanian_ci"},
- 132 => {:name => "ucs2", :collation => "ucs2_slovenian_ci"},
- 133 => {:name => "ucs2", :collation => "ucs2_polish_ci"},
- 134 => {:name => "ucs2", :collation => "ucs2_estonian_ci"},
- 135 => {:name => "ucs2", :collation => "ucs2_spanish_ci"},
- 136 => {:name => "ucs2", :collation => "ucs2_swedish_ci"},
- 137 => {:name => "ucs2", :collation => "ucs2_turkish_ci"},
- 138 => {:name => "ucs2", :collation => "ucs2_czech_ci"},
- 139 => {:name => "ucs2", :collation => "ucs2_danish_ci"},
- 140 => {:name => "ucs2", :collation => "ucs2_lithuanian_ci"},
- 141 => {:name => "ucs2", :collation => "ucs2_slovak_ci"},
- 142 => {:name => "ucs2", :collation => "ucs2_spanish2_ci"},
- 143 => {:name => "ucs2", :collation => "ucs2_roman_ci"},
- 144 => {:name => "ucs2", :collation => "ucs2_persian_ci"},
- 145 => {:name => "ucs2", :collation => "ucs2_esperanto_ci"},
- 146 => {:name => "ucs2", :collation => "ucs2_hungarian_ci"},
- 192 => {:name => "utf8", :collation => "utf8_unicode_ci"},
- 193 => {:name => "utf8", :collation => "utf8_icelandic_ci"},
- 194 => {:name => "utf8", :collation => "utf8_latvian_ci"},
- 195 => {:name => "utf8", :collation => "utf8_romanian_ci"},
- 196 => {:name => "utf8", :collation => "utf8_slovenian_ci"},
- 197 => {:name => "utf8", :collation => "utf8_polish_ci"},
- 198 => {:name => "utf8", :collation => "utf8_estonian_ci"},
- 199 => {:name => "utf8", :collation => "utf8_spanish_ci"},
- 200 => {:name => "utf8", :collation => "utf8_swedish_ci"},
- 201 => {:name => "utf8", :collation => "utf8_turkish_ci"},
- 202 => {:name => "utf8", :collation => "utf8_czech_ci"},
- 203 => {:name => "utf8", :collation => "utf8_danish_ci"},
- 204 => {:name => "utf8", :collation => "utf8_lithuanian_ci"},
- 205 => {:name => "utf8", :collation => "utf8_slovak_ci"},
- 206 => {:name => "utf8", :collation => "utf8_spanish2_ci"},
- 207 => {:name => "utf8", :collation => "utf8_roman_ci"},
- 208 => {:name => "utf8", :collation => "utf8_persian_ci"},
- 209 => {:name => "utf8", :collation => "utf8_esperanto_ci"},
- 210 => {:name => "utf8", :collation => "utf8_hungarian_ci"},
- 254 => {:name => "utf8", :collation => "utf8_general_cs"}
- }
-
- def self.encoding_from_charset(charset)
- CHARSET_MAP[charset.to_s.downcase]
- end
-
- def self.encoding_from_charset_code(code)
- if mapping = MYSQL_CHARSET_MAP[code]
- encoding_from_charset(mapping[:name])
- else
- nil
- 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
private
diff --git a/lib/mysql2/console.rb b/lib/mysql2/console.rb
new file mode 100644
index 0000000..cad8243
--- /dev/null
+++ b/lib/mysql2/console.rb
@@ -0,0 +1,5 @@
+# Loaded by script/console. Land helpers here.
+
+Pry.config.prompt = lambda do |context, nesting, pry|
+ "[mysql2] #{context}> "
+end
diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb
index e8c984b..5a64499 100644
--- a/lib/mysql2/em.rb
+++ b/lib/mysql2/em.rb
@@ -15,18 +15,28 @@ module Mysql2
def notify_readable
detach
begin
- @deferable.succeed(@client.async_result)
+ result = @client.async_result
rescue Exception => e
@deferable.fail(e)
+ else
+ @deferable.succeed(result)
end
end
end
+ def close(*args)
+ if @watch
+ @watch.detach
+ end
+ super(*args)
+ end
+
def query(sql, opts={})
if ::EM.reactor_running?
super(sql, opts.merge(:async => true))
deferable = ::EM::DefaultDeferrable.new
- ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true
+ @watch = ::EM.watch(self.socket, Watcher, self, deferable)
+ @watch.notify_readable = true
deferable
else
super(sql, opts)
@@ -34,4 +44,4 @@ module Mysql2
end
end
end
-end
\ No newline at end of file
+end
diff --git a/lib/mysql2/version.rb b/lib/mysql2/version.rb
index b623df1..4318859 100644
--- a/lib/mysql2/version.rb
+++ b/lib/mysql2/version.rb
@@ -1,3 +1,3 @@
module Mysql2
- VERSION = "0.3.11"
+ VERSION = "0.3.14"
end
diff --git a/metadata.yml b/metadata.yml
index 52d4ad7..ccc24c1 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,245 +1,140 @@
---- !ruby/object:Gem::Specification
+--- !ruby/object:Gem::Specification
name: mysql2
-version: !ruby/object:Gem::Version
- hash: 5
- prerelease:
- segments:
- - 0
- - 3
- - 11
- version: 0.3.11
+version: !ruby/object:Gem::Version
+ version: 0.3.14
platform: ruby
-authors:
+authors:
- Brian Lopez
autorequire:
bindir: bin
cert_chain: []
-
-date: 2011-12-06 00:00:00 -08:00
-default_executable:
-dependencies:
-- !ruby/object:Gem::Dependency
+date: 2013-11-07 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
name: eventmachine
- prerelease: false
- requirement: &id001 !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
type: :development
- version_requirements: *id001
-- !ruby/object:Gem::Dependency
- name: rake-compiler
prerelease: false
- requirement: &id002 !ruby/object:Gem::Requirement
- none: false
- requirements:
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+- !ruby/object:Gem::Dependency
+ name: rake-compiler
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
- - ~>
- - !ruby/object:Gem::Version
- hash: 13
- segments:
- - 0
- - 7
- - 7
- version: 0.7.7
- type: :development
- version_requirements: *id002
-- !ruby/object:Gem::Dependency
- name: rake
- prerelease: false
- requirement: &id003 !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - "="
- - !ruby/object:Gem::Version
- hash: 49
- segments:
- - 0
- - 8
- - 7
- version: 0.8.7
- type: :development
- version_requirements: *id003
-- !ruby/object:Gem::Dependency
- name: rspec
- prerelease: false
- requirement: &id004 !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
- type: :development
- version_requirements: *id004
-- !ruby/object:Gem::Dependency
- name: activerecord
- prerelease: false
- requirement: &id005 !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
- type: :development
- version_requirements: *id005
-- !ruby/object:Gem::Dependency
- name: mysql
- prerelease: false
- requirement: &id006 !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
+ - !ruby/object:Gem::Version
+ version: 0.8.1
type: :development
- version_requirements: *id006
-- !ruby/object:Gem::Dependency
- name: do_mysql
prerelease: false
- requirement: &id007 !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: 0.8.1
+- !ruby/object:Gem::Dependency
+ name: rake
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: 0.9.3
type: :development
- version_requirements: *id007
-- !ruby/object:Gem::Dependency
- name: sequel
prerelease: false
- requirement: &id008 !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: 0.9.3
+- !ruby/object:Gem::Dependency
+ name: rspec
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: 2.8.0
type: :development
- version_requirements: *id008
-- !ruby/object:Gem::Dependency
- name: faker
prerelease: false
- requirement: &id009 !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
- type: :development
- version_requirements: *id009
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: 2.8.0
description:
email: seniorlopez at gmail.com
executables: []
-
-extensions:
+extensions:
- ext/mysql2/extconf.rb
extra_rdoc_files: []
-
-files:
-- .gitignore
-- .rspec
-- .rvmrc
-- .travis.yml
-- CHANGELOG.md
-- Gemfile
+files:
- MIT-LICENSE
- README.md
-- Rakefile
-- benchmark/active_record.rb
-- benchmark/active_record_threaded.rb
-- benchmark/allocations.rb
-- benchmark/escape.rb
-- benchmark/query_with_mysql_casting.rb
-- benchmark/query_without_mysql_casting.rb
-- benchmark/sequel.rb
-- benchmark/setup_db.rb
-- benchmark/threaded.rb
-- examples/eventmachine.rb
-- examples/threaded.rb
- ext/mysql2/client.c
- ext/mysql2/client.h
- ext/mysql2/extconf.rb
- ext/mysql2/mysql2_ext.c
- ext/mysql2/mysql2_ext.h
+- ext/mysql2/mysql_enc_name_to_ruby.h
+- ext/mysql2/mysql_enc_to_ruby.h
- ext/mysql2/result.c
- ext/mysql2/result.h
- ext/mysql2/wait_for_single_fd.h
- lib/mysql2.rb
- lib/mysql2/client.rb
+- lib/mysql2/console.rb
- lib/mysql2/em.rb
- lib/mysql2/error.rb
- lib/mysql2/result.rb
- lib/mysql2/version.rb
-- mysql2.gemspec
+- support/mysql_enc_to_ruby.rb
+- support/ruby_enc_to_mysql.rb
+- examples/eventmachine.rb
+- examples/threaded.rb
+- spec/configuration.yml.example
- spec/em/em_spec.rb
+- spec/my.cnf.example
- spec/mysql2/client_spec.rb
- spec/mysql2/error_spec.rb
- spec/mysql2/result_spec.rb
- spec/rcov.opts
- spec/spec_helper.rb
-- tasks/benchmarks.rake
-- tasks/compile.rake
-- tasks/rspec.rake
-- tasks/vendor_mysql.rake
-has_rdoc: true
homepage: http://github.com/brianmario/mysql2
-licenses: []
-
+licenses:
+- MIT
+metadata: {}
post_install_message:
-rdoc_options:
+rdoc_options:
- --charset=UTF-8
-require_paths:
+require_paths:
- lib
-required_ruby_version: !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
-required_rubygems_version: !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
requirements: []
-
rubyforge_project:
-rubygems_version: 1.3.9.3
+rubygems_version: 2.0.3
signing_key:
-specification_version: 3
+specification_version: 4
summary: A simple, fast Mysql library for Ruby, binding to libmysql
-test_files:
+test_files:
- examples/eventmachine.rb
- examples/threaded.rb
+- spec/configuration.yml.example
- spec/em/em_spec.rb
+- spec/my.cnf.example
- spec/mysql2/client_spec.rb
- spec/mysql2/error_spec.rb
- spec/mysql2/result_spec.rb
diff --git a/mysql2.gemspec b/mysql2.gemspec
deleted file mode 100644
index 2181f0f..0000000
--- a/mysql2.gemspec
+++ /dev/null
@@ -1,29 +0,0 @@
-require File.expand_path('../lib/mysql2/version', __FILE__)
-
-Gem::Specification.new do |s|
- s.name = %q{mysql2}
- s.version = Mysql2::VERSION
- s.authors = ["Brian Lopez"]
- s.date = Time.now.utc.strftime("%Y-%m-%d")
- s.email = %q{seniorlopez at gmail.com}
- s.extensions = ["ext/mysql2/extconf.rb"]
- s.files = `git ls-files`.split("\n")
- s.homepage = %q{http://github.com/brianmario/mysql2}
- s.rdoc_options = ["--charset=UTF-8"]
- s.require_paths = ["lib"]
- s.rubygems_version = %q{1.4.2}
- s.summary = %q{A simple, fast Mysql library for Ruby, binding to libmysql}
- s.test_files = `git ls-files spec examples`.split("\n")
-
- # tests
- s.add_development_dependency 'eventmachine'
- s.add_development_dependency 'rake-compiler', "~> 0.7.7"
- s.add_development_dependency 'rake', '0.8.7' # NB: 0.8.7 required by rake-compiler 0.7.9
- s.add_development_dependency 'rspec'
- # benchmarks
- s.add_development_dependency 'activerecord'
- s.add_development_dependency 'mysql'
- s.add_development_dependency 'do_mysql'
- s.add_development_dependency 'sequel'
- s.add_development_dependency 'faker'
-end
diff --git a/spec/configuration.yml.example b/spec/configuration.yml.example
new file mode 100644
index 0000000..5a4406f
--- /dev/null
+++ b/spec/configuration.yml.example
@@ -0,0 +1,17 @@
+root:
+ host: localhost
+ username: root
+ password:
+ database: test
+
+user:
+ host: localhost
+ username: LOCALUSERNAME
+ password:
+ database: mysql2_test
+
+numericuser:
+ host: localhost
+ username: LOCALUSERNAME
+ password:
+ database: 12345
diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb
index b8b53a9..7bead7c 100644
--- a/spec/em/em_spec.rb
+++ b/spec/em/em_spec.rb
@@ -8,17 +8,19 @@ begin
it "should support async queries" do
results = []
EM.run do
- client1 = Mysql2::EM::Client.new
+ 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
+ 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
@@ -29,13 +31,14 @@ begin
it "should support queries in callbacks" do
results = []
EM.run do
- client = Mysql2::EM::Client.new
+ 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 |result|
- results << result.first
+ defer2.callback do |r|
+ results << r.first
+ client.close
EM.stop_event_loop
end
end
@@ -44,6 +47,67 @@ begin
results[0].keys.should include("first_query")
results[1].keys.should include("second_query")
end
+
+ it "should not swallow exceptions raised in callbacks" do
+ lambda {
+ EM.run do
+ client = Mysql2::EM::Client.new DatabaseCredentials['root']
+ defer = client.query "SELECT sleep(0.1) as first_query"
+ defer.callback do |result|
+ client.close
+ raise 'some error'
+ end
+ defer.errback do |err|
+ # This _shouldn't_ be run, but it needed to prevent the specs from
+ # freezing if this test fails.
+ EM.stop_event_loop
+ end
+ end
+ }.should raise_error
+ end
+
+ context 'when an exception is raised by the client' do
+ let(:client) { Mysql2::EM::Client.new DatabaseCredentials['root'] }
+ let(:error) { StandardError.new('some error') }
+ before { client.stub(:async_result).and_raise(error) }
+
+ 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 |result|
+ # 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
+ errors.should == [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 |result|
+ callbacks_run << :callback
+ # This _shouldn't_ be run, but it is needed to prevent the specs from
+ # freezing if this test fails.
+ EM.stop_event_loop
+ end
+ defer.errback do |err|
+ callbacks_run << :errback
+ EM.stop_event_loop
+ end
+ end
+ end
+ callbacks_run.should == [:errback]
+ end
+ end
end
rescue LoadError
puts "EventMachine not installed, skipping the specs that use it"
diff --git a/spec/my.cnf.example b/spec/my.cnf.example
new file mode 100644
index 0000000..5e7792d
--- /dev/null
+++ b/spec/my.cnf.example
@@ -0,0 +1,9 @@
+[root]
+host=localhost
+user=LOCALUSERNAME
+password=
+
+[client]
+host=localhost
+user=LOCALUSERNAME
+password=
diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb
index d9b6015..350a43f 100644
--- a/spec/mysql2/client_spec.rb
+++ b/spec/mysql2/client_spec.rb
@@ -2,16 +2,46 @@
require 'spec_helper'
describe Mysql2::Client do
- before(:each) do
- @client = Mysql2::Client.new
+ context "using defaults file" do
+ let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) }
+
+ it "should not raise an exception for valid defaults group" do
+ lambda {
+ @client = Mysql2::Client.new(:default_file => cnf_file, :default_group => "test")
+ }.should_not raise_error(Mysql2::Error)
+ end
+
+ it "should not raise an exception without default group" do
+ lambda {
+ @client = Mysql2::Client.new(:default_file => cnf_file)
+ }.should_not raise_error(Mysql2::Error)
+ end
+ end
+
+ it "should raise an exception upon connection failure" do
+ lambda {
+ # The odd local host IP address forces the mysql client library to
+ # use a TCP socket rather than a domain socket.
+ Mysql2::Client.new DatabaseCredentials['root'].merge('host' => '127.0.0.2', 'port' => 999999)
+ }.should raise_error(Mysql2::Error)
end
if defined? Encoding
it "should raise an exception on create for invalid encodings" do
lambda {
- c = Mysql2::Client.new(:encoding => "fake")
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake"))
}.should raise_error(Mysql2::Error)
end
+
+ it "should not raise an exception on create for a valid encoding" do
+ lambda {
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8"))
+ }.should_not raise_error(Mysql2::Error)
+
+ lambda {
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5"))
+ }.should_not raise_error(Mysql2::Error)
+ end
end
it "should accept connect flags and pass them to #connect" do
@@ -63,11 +93,25 @@ describe Mysql2::Client do
results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
results[0]['Variable_name'].should eql('Ssl_cipher')
results[0]['Value'].should_not be_nil
- results[0]['Value'].class.should eql(String)
+ results[0]['Value'].should be_kind_of(String)
+ results[0]['Value'].should_not be_empty
results[1]['Variable_name'].should eql('Ssl_version')
results[1]['Value'].should_not be_nil
- results[1]['Value'].class.should eql(String)
+ results[1]['Value'].should be_kind_of(String)
+ results[1]['Value'].should_not be_empty
+
+ ssl_client.close
+ end
+
+ it "should be able to connect to database with numeric-only name" do
+ lambda {
+ creds = DatabaseCredentials['numericuser']
+ @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`"
+ @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`"
+ client = Mysql2::Client.new creds
+ @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`"
+ }.should_not raise_error
end
it "should respond to #close" do
@@ -85,22 +129,117 @@ describe Mysql2::Client do
@client.should respond_to(:query)
end
+ it "should respond to #warning_count" do
+ @client.should respond_to(:warning_count)
+ end
+
+ context "#warning_count" do
+ context "when no warnings" do
+ it "should 0" do
+ @client.query('select 1')
+ @client.warning_count.should == 0
+ 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"
+ # http://dev.mysql.com/doc/refman/5.0/en/explain-extended.html
+ @client.query("explain extended select 1")
+ @client.warning_count.should > 0
+ end
+ end
+ end
+
+ it "should respond to #query_info" do
+ @client.should respond_to(:query_info)
+ end
+
+ context "#query_info" do
+ context "when no info present" do
+ it "should 0" do
+ @client.query('select 1')
+ @client.query_info.should be_empty
+ @client.query_info_string.should be_nil
+ end
+ end
+ context "when has some info" do
+ 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)")
+
+ @client.query_info.should eql({:records => 2, :duplicates => 0, :warnings => 0})
+ @client.query_info_string.should eq('Records: 2 Duplicates: 0 Warnings: 0')
+
+ @client.query "DROP TABLE infoTest"
+ end
+ end
+ end
+
+ it "should expect connect_timeout to be a positive integer" do
+ lambda {
+ Mysql2::Client.new(:connect_timeout => -1)
+ }.should raise_error(Mysql2::Error)
+ end
+
it "should expect read_timeout to be a positive integer" do
lambda {
Mysql2::Client.new(:read_timeout => -1)
}.should raise_error(Mysql2::Error)
end
+ it "should expect write_timeout to be a positive integer" do
+ lambda {
+ Mysql2::Client.new(:write_timeout => -1)
+ }.should raise_error(Mysql2::Error)
+ 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 {
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false)
+ }.to_not raise_exception(Mysql2::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 {
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false)
+ }.to raise_exception(Mysql2::Error)
+ end
+
it "should only accept strings as the query parameter" do
lambda {
@client.query ["SELECT 'not right'"]
}.should raise_error(TypeError)
end
- it "should accept an options hash that inherits from Mysql2::Client.default_query_options" do
- @client.query "SELECT 1", :something => :else
- @client.query_options.should eql(@client.query_options.merge(:something => :else))
+ it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do
+ result = @client.query "SELECT 1", :something => :else
+ @client.query_options[:something].should be_nil
+ result.instance_variable_get('@query_options').should eql(@client.query_options.merge(:something => :else))
+ @client.instance_variable_get('@current_query_options').should eql(@client.query_options.merge(:something => :else))
+
+ result = @client.query "SELECT 1"
+ result.instance_variable_get('@query_options').should eql(@client.query_options)
+ @client.instance_variable_get('@current_query_options').should eql(@client.query_options)
+ end
+
+ it "should allow changing query options for subsequent queries" do
+ @client.query_options.merge!(:something => :else)
+ result = @client.query "SELECT 1"
+ @client.query_options[:something].should eql(:else)
+ result.instance_variable_get('@query_options')[:something].should eql(:else)
+
+ # Clean up after this test
+ @client.query_options.delete(:something)
+ @client.query_options[:something].should be_nil
end
it "should return results as a hash by default" do
@@ -131,37 +270,52 @@ describe Mysql2::Client do
}.should 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
+ begin
+ @client.query("SELECT 1")
+ rescue Mysql2::Error => e
+ message = e.message
+ end
+ re = Regexp.escape(thr.inspect)
+ message.should match(Regexp.new(re))
+ end
+
it "should timeout if we wait longer than :read_timeout" do
- client = Mysql2::Client.new(:read_timeout => 1)
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 1))
lambda {
client.query("SELECT sleep(2)")
}.should raise_error(Mysql2::Error)
end
- # XXX this test is not deterministic (because Unix signal handling is not)
- # and may fail on a loaded system
- it "should run signal handlers while waiting for a response" do
- mark = {}
- trap(:USR1) { mark[:USR1] = Time.now }
- begin
- mark[:START] = Time.now
- pid = fork do
- sleep 1 # wait for client "SELECT sleep(2)" query to start
- Process.kill(:USR1, Process.ppid)
- sleep # wait for explicit kill to prevent GC disconnect
+ if !defined? Rubinius
+ # XXX this test is not deterministic (because Unix signal handling is not)
+ # and may fail on a loaded system
+ it "should run signal handlers while waiting for a response" do
+ mark = {}
+ trap(:USR1) { mark[:USR1] = Time.now }
+ begin
+ mark[:START] = Time.now
+ pid = fork do
+ sleep 1 # wait for client "SELECT sleep(2)" query to start
+ Process.kill(:USR1, Process.ppid)
+ sleep # wait for explicit kill to prevent GC disconnect
+ end
+ @client.query("SELECT sleep(2)")
+ mark[:END] = Time.now
+ mark.include?(:USR1).should be_true
+ (mark[:USR1] - mark[:START]).should >= 1
+ (mark[:USR1] - mark[:START]).should < 1.3
+ (mark[:END] - mark[:USR1]).should > 0.9
+ (mark[:END] - mark[:START]).should >= 2
+ (mark[:END] - mark[:START]).should < 2.3
+ Process.kill(:TERM, pid)
+ Process.waitpid2(pid)
+ ensure
+ trap(:USR1, 'DEFAULT')
end
- @client.query("SELECT sleep(2)")
- mark[:END] = Time.now
- mark.include?(:USR1).should be_true
- (mark[:USR1] - mark[:START]).should >= 1
- (mark[:USR1] - mark[:START]).should < 1.1
- (mark[:END] - mark[:USR1]).should > 0.9
- (mark[:END] - mark[:START]).should >= 2
- (mark[:END] - mark[:START]).should < 2.1
- Process.kill(:TERM, pid)
- Process.waitpid2(pid)
- ensure
- trap(:USR1, 'DEFAULT')
end
end
@@ -184,14 +338,14 @@ describe Mysql2::Client do
end
rescue Timeout::Error
end
-
+
lambda {
@client.query("SELECT 1")
}.should raise_error(Mysql2::Error, 'closed MySQL connection')
end
it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
- client = Mysql2::Client.new(:reconnect => true)
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true))
begin
Timeout.timeout(1) do
client.query("SELECT sleep(2)")
@@ -204,13 +358,47 @@ describe Mysql2::Client do
}.should_not raise_error(Mysql2::Error)
end
+ it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction true" do
+ client = Mysql2::Client.new(DatabaseCredentials['root'])
+ begin
+ Timeout.timeout(1) do
+ client.query("SELECT sleep(2)")
+ end
+ rescue Timeout::Error
+ end
+
+ lambda {
+ client.query("SELECT 1")
+ }.should raise_error(Mysql2::Error)
+
+ client.reconnect = true
+
+ begin
+ Timeout.timeout(1) do
+ client.query("SELECT sleep(2)")
+ end
+ rescue Timeout::Error
+ end
+
+ lambda {
+ client.query("SELECT 1")
+ }.should_not raise_error(Mysql2::Error)
+
+ end
+
it "threaded queries should be supported" do
threads, results = [], {}
- connect = lambda{ Mysql2::Client.new(:host => "localhost", :username => "root") }
+ lock = Mutex.new
+ connect = lambda{
+ Mysql2::Client.new(DatabaseCredentials['root'])
+ }
Timeout.timeout(0.7) do
5.times {
threads << Thread.new do
- results[Thread.current.object_id] = connect.call.query("SELECT sleep(0.5) as result")
+ result = connect.call.query("SELECT sleep(0.5) as result")
+ lock.synchronize do
+ results[Thread.current.object_id] = result
+ end
end
}
end
@@ -239,6 +427,55 @@ describe Mysql2::Client do
result.class.should eql(Mysql2::Result)
end
end
+
+ context "Multiple results sets" do
+ before(:each) do
+ @multi_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:flags => Mysql2::Client::MULTI_STATEMENTS))
+ end
+
+ it "returns multiple result sets" do
+ @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'").first.should eql({ 'set_1' => 1 })
+
+ @multi_client.next_result.should be_true
+ @multi_client.store_result.first.should eql({ 'set_2' => 2 })
+
+ @multi_client.next_result.should be_false
+ end
+
+ it "does not interfere with other statements" do
+ @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'")
+ while( @multi_client.next_result )
+ @multi_client.store_result
+ end
+
+ @multi_client.query( "select 3 as 'next'").first.should == { 'next' => 3 }
+ end
+
+ it "will raise on query if there are outstanding results to read" do
+ @multi_client.query("SELECT 1; SELECT 2; SELECT 3")
+ lambda {
+ @multi_client.query("SELECT 4")
+ }.should raise_error(Mysql2::Error)
+ end
+
+ it "#abandon_results! should work" do
+ @multi_client.query("SELECT 1; SELECT 2; SELECT 3")
+ @multi_client.abandon_results!
+ lambda {
+ @multi_client.query("SELECT 4")
+ }.should_not raise_error(Mysql2::Error)
+ end
+
+ it "#more_results? should work" do
+ @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'")
+ @multi_client.more_results?.should be_true
+
+ @multi_client.next_result
+ @multi_client.store_result
+
+ @multi_client.more_results?.should be_false
+ end
+ end
end
it "should respond to #socket" do
@@ -279,7 +516,7 @@ describe Mysql2::Client do
}.should_not raise_error(SystemStackError)
end
- if RUBY_VERSION =~ /1.9/
+ unless RUBY_VERSION =~ /1.8/
it "should carry over the original string's encoding" do
str = "abc'def\"ghi\0jkl%mno"
escaped = Mysql2::Client.escape(str)
@@ -345,7 +582,7 @@ describe Mysql2::Client do
Encoding.default_internal = nil
@client.info[:version].encoding.should eql(Encoding.find('utf-8'))
- client2 = Mysql2::Client.new :encoding => 'ascii'
+ client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
client2.info[:version].encoding.should eql(Encoding.find('us-ascii'))
end
@@ -384,7 +621,7 @@ describe Mysql2::Client do
Encoding.default_internal = nil
@client.server_info[:version].encoding.should eql(Encoding.find('utf-8'))
- client2 = Mysql2::Client.new :encoding => 'ascii'
+ client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii'))
end
@@ -399,11 +636,11 @@ describe Mysql2::Client do
it "should raise a Mysql2::Error exception upon connection failure" do
lambda {
- bad_client = Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
+ Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
}.should raise_error(Mysql2::Error)
lambda {
- good_client = Mysql2::Client.new
+ Mysql2::Client.new DatabaseCredentials['root']
}.should_not raise_error(Mysql2::Error)
end
@@ -451,15 +688,54 @@ describe Mysql2::Client do
@client.should 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
+ @client.should respond_to(:select_db)
+ end
+
+ it "should switch databases" do
+ @client.select_db("test_selectdb_0")
+ @client.query("SHOW TABLES").first.values.first.should eql("test0")
+ @client.select_db("test_selectdb_1")
+ @client.query("SHOW TABLES").first.values.first.should eql("test1")
+ @client.select_db("test_selectdb_0")
+ @client.query("SHOW TABLES").first.values.first.should eql("test0")
+ end
+
+ it "should raise a Mysql2::Error when the database doesn't exist" do
+ lambda {
+ @client.select_db("nopenothere")
+ }.should raise_error(Mysql2::Error)
+ end
+
+ it "should return the database switched to" do
+ @client.select_db("test_selectdb_1").should eq("test_selectdb_1")
+ end
+ end
+
it "#thread_id should return a boolean" do
@client.ping.should eql(true)
@client.close
@client.ping.should eql(false)
end
-if RUBY_VERSION =~ /1.9/
- it "should respond to #encoding" do
- @client.should respond_to(:encoding)
+ unless RUBY_VERSION =~ /1.8/
+ it "should respond to #encoding" do
+ @client.should respond_to(:encoding)
+ end
end
end
-end
diff --git a/spec/mysql2/error_spec.rb b/spec/mysql2/error_spec.rb
index 882a5f8..b308bc8 100644
--- a/spec/mysql2/error_spec.rb
+++ b/spec/mysql2/error_spec.rb
@@ -3,18 +3,22 @@ require 'spec_helper'
describe Mysql2::Error do
before(:each) do
- @client = Mysql2::Client.new :encoding => "utf8"
begin
- @client.query("HAHAHA")
+ @err_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8"))
+ @err_client.query("HAHAHA")
rescue Mysql2::Error => e
@error = e
+ ensure
+ @err_client.close
end
- @client2 = Mysql2::Client.new :encoding => "big5"
begin
- @client2.query("HAHAHA")
+ @err_client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5"))
+ @err_client2.query("HAHAHA")
rescue Mysql2::Error => e
@error2 = e
+ ensure
+ @err_client2.close
end
end
@@ -35,11 +39,11 @@ describe Mysql2::Error do
@error.should respond_to(:error)
end
- if RUBY_VERSION =~ /1.9/
+ unless RUBY_VERSION =~ /1.8/
it "#message encoding should match the connection's encoding, or Encoding.default_internal if set" do
if Encoding.default_internal.nil?
- @error.message.encoding.should eql(@client.encoding)
- @error2.message.encoding.should eql(@client2.encoding)
+ @error.message.encoding.should eql(@err_client.encoding)
+ @error2.message.encoding.should eql(@err_client2.encoding)
else
@error.message.encoding.should eql(Encoding.default_internal)
@error2.message.encoding.should eql(Encoding.default_internal)
@@ -48,8 +52,8 @@ describe Mysql2::Error do
it "#error encoding should match the connection's encoding, or Encoding.default_internal if set" do
if Encoding.default_internal.nil?
- @error.error.encoding.should eql(@client.encoding)
- @error2.error.encoding.should eql(@client2.encoding)
+ @error.error.encoding.should eql(@err_client.encoding)
+ @error2.error.encoding.should eql(@err_client2.encoding)
else
@error.error.encoding.should eql(Encoding.default_internal)
@error2.error.encoding.should eql(Encoding.default_internal)
@@ -58,8 +62,8 @@ describe Mysql2::Error do
it "#sql_state encoding should match the connection's encoding, or Encoding.default_internal if set" do
if Encoding.default_internal.nil?
- @error.sql_state.encoding.should eql(@client.encoding)
- @error2.sql_state.encoding.should eql(@client2.encoding)
+ @error.sql_state.encoding.should eql(@err_client.encoding)
+ @error2.sql_state.encoding.should eql(@err_client2.encoding)
else
@error.sql_state.encoding.should eql(Encoding.default_internal)
@error2.sql_state.encoding.should eql(Encoding.default_internal)
diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb
index f907714..0d9cca9 100644
--- a/spec/mysql2/result_spec.rb
+++ b/spec/mysql2/result_spec.rb
@@ -3,11 +3,36 @@ require 'spec_helper'
describe Mysql2::Result do
before(:each) do
- @client = Mysql2::Client.new :host => "localhost", :username => "root", :database => 'test'
+ @result = @client.query "SELECT 1"
end
- before(:each) do
- @result = @client.query "SELECT 1"
+ it "should maintain a count while streaming" do
+ result = @client.query('SELECT 1')
+
+ result.count.should eql(1)
+ result.each.to_a
+ result.count.should eql(1)
+ end
+
+ it "should set the actual count of rows after streaming" do
+ @client.query "USE test"
+ result = @client.query("SELECT * FROM mysql2_test", :stream => true, :cache_rows => false)
+ result.count.should eql(0)
+ result.each {|r| }
+ result.count.should eql(1)
+ end
+
+ it "should not yield nil at the end of streaming" do
+ result = @client.query('SELECT * FROM mysql2_test', :stream => true)
+ result.each { |r| r.should_not be_nil}
+ end
+
+ it "#count should be zero for rows after streaming when there were no results " do
+ @client.query "USE test"
+ result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false)
+ result.count.should eql(0)
+ result.each.to_a
+ result.count.should eql(0)
end
it "should have included Enumerable" do
@@ -73,6 +98,25 @@ describe Mysql2::Result do
result = @client.query "SELECT 1", :cache_rows => false
result.first.object_id.should_not eql(result.first.object_id)
end
+
+ it "should yield different value for #first if streaming" do
+ result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false
+ result.first.should_not eql(result.first)
+ end
+
+ it "should yield the same value for #first if streaming is disabled" do
+ result = @client.query "SELECT 1 UNION SELECT 2", :stream => false
+ result.first.should eql(result.first)
+ 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 {
+ result.each.to_a
+ result.each.to_a
+ }.to raise_exception(Mysql2::Error)
+ end
end
context "#fields" do
@@ -100,11 +144,11 @@ describe Mysql2::Result do
it "should return nil values for NULL and strings for everything else when :cast is false" do
result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast => false).first
result["null_test"].should be_nil
- result["tiny_int_test"].should == "1"
- result["bool_cast_test"].should == "1"
- result["int_test"].should == "10"
- result["date_test"].should == "2010-04-04"
- result["enum_test"].should == "val1"
+ result["tiny_int_test"].should eql("1")
+ result["bool_cast_test"].should eql("1")
+ result["int_test"].should eql("10")
+ result["date_test"].should eql("2010-04-04")
+ result["enum_test"].should eql("val1")
end
it "should return nil for a NULL value" do
@@ -112,11 +156,16 @@ describe Mysql2::Result do
@test_result['null_test'].should eql(nil)
end
- it "should return Fixnum for a BIT value" do
+ it "should return String for a BIT(64) value" do
@test_result['bit_test'].class.should eql(String)
@test_result['bit_test'].should eql("\000\000\000\000\000\000\000\005")
end
+ it "should return String for a BIT(1) value" do
+ @test_result['single_bit_test'].class.should eql(String)
+ @test_result['single_bit_test'].should eql("\001")
+ end
+
it "should return Fixnum for a TINYINT value" do
[Fixnum, Bignum].should include(@test_result['tiny_int_test'].class)
@test_result['tiny_int_test'].should eql(1)
@@ -127,11 +176,29 @@ describe Mysql2::Result do
id1 = @client.last_id
@client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (0)'
id2 = @client.last_id
+ @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'
+ id3 = @client.last_id
result1 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast_booleans => true
result2 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 0 LIMIT 1', :cast_booleans => true
+ result3 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = -1 LIMIT 1', :cast_booleans => true
result1.first['bool_cast_test'].should be_true
result2.first['bool_cast_test'].should be_false
+ result3.first['bool_cast_test'].should be_true
+
+ @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})"
+ end
+
+ it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do
+ @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'
+ id1 = @client.last_id
+ @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'
+ id2 = @client.last_id
+
+ result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", :cast_booleans => true
+ result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", :cast_booleans => true
+ result1.first['single_bit_test'].should be_true
+ result2.first['single_bit_test'].should be_false
@client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})"
end
@@ -182,7 +249,7 @@ describe Mysql2::Result do
end
if 1.size == 4 # 32bit
- if RUBY_VERSION =~ /1.9/
+ unless RUBY_VERSION =~ /1.8/
klass = Time
else
klass = DateTime
@@ -200,7 +267,7 @@ describe Mysql2::Result do
r.first['test'].class.should eql(klass)
end
elsif 1.size == 8 # 64bit
- if RUBY_VERSION =~ /1.9/
+ unless RUBY_VERSION =~ /1.8/
it "should return Time when timestamp is < 1901-12-13 20:45:52" do
r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test")
r.first['test'].class.should eql(Time)
@@ -255,10 +322,11 @@ describe Mysql2::Result do
result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
result['enum_test'].encoding.should eql(Encoding.find('utf-8'))
- client2 = Mysql2::Client.new :encoding => 'ascii'
+ client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
client2.query "USE test"
result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
result['enum_test'].encoding.should eql(Encoding.find('us-ascii'))
+ client2.close
end
it "should use Encoding.default_internal" do
@@ -284,10 +352,11 @@ describe Mysql2::Result do
result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
result['set_test'].encoding.should eql(Encoding.find('utf-8'))
- client2 = Mysql2::Client.new :encoding => 'ascii'
+ client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
client2.query "USE test"
result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
result['set_test'].encoding.should eql(Encoding.find('us-ascii'))
+ client2.close
end
it "should use Encoding.default_internal" do
@@ -366,10 +435,11 @@ describe Mysql2::Result do
result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
result[field].encoding.should eql(Encoding.find('utf-8'))
- client2 = Mysql2::Client.new :encoding => 'ascii'
+ client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
client2.query "USE test"
result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
result[field].encoding.should eql(Encoding.find('us-ascii'))
+ client2.close
end
it "should use Encoding.default_internal" do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index d543877..eb26edb 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -3,15 +3,26 @@
require 'rspec'
require 'mysql2'
require 'timeout'
+require 'yaml'
+DatabaseCredentials = YAML.load_file('spec/configuration.yml')
RSpec.configure do |config|
+ config.before :each do
+ @client = Mysql2::Client.new DatabaseCredentials['root']
+ end
+
+ config.after :each do
+ @client.close
+ end
+
config.before(:all) do
- client = Mysql2::Client.new :host => "localhost", :username => "root", :database => 'test'
+ client = Mysql2::Client.new DatabaseCredentials['root']
client.query %[
CREATE TABLE IF NOT EXISTS mysql2_test (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
null_test VARCHAR(10),
bit_test BIT(64),
+ single_bit_test BIT(1),
tiny_int_test TINYINT,
bool_cast_test TINYINT(1),
small_int_test SMALLINT,
@@ -48,7 +59,7 @@ RSpec.configure do |config|
client.query "DELETE FROM mysql2_test;"
client.query %[
INSERT INTO mysql2_test (
- null_test, bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test,
+ null_test, bit_test, single_bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test,
float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test,
year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test,
tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test,
@@ -56,7 +67,7 @@ RSpec.configure do |config|
)
VALUES (
- NULL, b'101', 1, 1, 10, 10, 10, 10,
+ NULL, b'101', b'1', 1, 1, 10, 10, 10, 10,
10.3, 0, 10.3, 10.3, 0, '2010-4-4', '2010-4-4 11:44:00', '2010-4-4 11:44:00', '11:44:00',
2009, "test", "test", "test", "test", "test",
"test", "test", "test", "test", "test",
diff --git a/support/mysql_enc_to_ruby.rb b/support/mysql_enc_to_ruby.rb
new file mode 100644
index 0000000..4a3ef70
--- /dev/null
+++ b/support/mysql_enc_to_ruby.rb
@@ -0,0 +1,82 @@
+$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"
+}
+
+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[collation[1]]
+ 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 = "NULL"
+
+ if !encoding.nil? && encoding[1] != "NULL"
+ name = "\"#{encoding[1]}\""
+ end
+
+ " #{name}"
+end
+
+# start printing output
+
+puts "const char *mysql2_mysql_enc_to_rb[] = {"
+puts encodings_with_nil.join(",\n")
+puts "};"
+puts
diff --git a/support/ruby_enc_to_mysql.rb b/support/ruby_enc_to_mysql.rb
new file mode 100644
index 0000000..112016c
--- /dev/null
+++ b/support/ruby_enc_to_mysql.rb
@@ -0,0 +1,61 @@
+mysql_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"
+}
+
+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|
+ if ruby.nil?
+ name = "NULL"
+ else
+ name = "\"#{ruby}\""
+ end
+
+ puts "#{mysql}, #{name}"
+end
diff --git a/tasks/benchmarks.rake b/tasks/benchmarks.rake
deleted file mode 100644
index 33c8568..0000000
--- a/tasks/benchmarks.rake
+++ /dev/null
@@ -1,20 +0,0 @@
-BENCHMARKS = Dir["#{File.dirname(__FILE__)}/../benchmark/*.rb"].map do |path|
- File.basename(path, '.rb')
-end.select { |x| x != '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
\ No newline at end of file
diff --git a/tasks/compile.rake b/tasks/compile.rake
deleted file mode 100644
index e42b5f1..0000000
--- a/tasks/compile.rake
+++ /dev/null
@@ -1,71 +0,0 @@
-require "rake/extensiontask"
-
-CONNECTOR_VERSION = "6.0.2" #"mysql-connector-c-noinstall-6.0.2-win32.zip"
-CONNECTOR_MIRROR = ENV['CONNECTOR_MIRROR'] || ENV['MYSQL_MIRROR'] || "http://mysql.he.net/"
-
-def gemspec
- @clean_gemspec ||= eval(File.read(File.expand_path('../../mysql2.gemspec', __FILE__)))
-end
-
-Rake::ExtensionTask.new("mysql2", gemspec) do |ext|
- # reference where the vendored MySQL got extracted
- connector_lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', "mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32"))
-
- # DRY options feed into compile or cross-compile process
- windows_options = [
- "--with-mysql-include=#{connector_lib}/include",
- "--with-mysql-lib=#{connector_lib}/lib"
- ]
-
- # automatically add build options to avoid need of manual input
- if RUBY_PLATFORM =~ /mswin|mingw/ then
- ext.config_options = windows_options
- else
- ext.cross_compile = true
- ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60']
- ext.cross_config_options = windows_options
-
- # inject 1.8/1.9 pure-ruby entry point when cross compiling only
- ext.cross_compiling do |spec|
- spec.files << 'lib/mysql2/mysql2.rb'
- 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 where available
- in the following download:
-
- http://dev.mysql.com/get/Downloads/Connector-C/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32.zip/from/pick
-
- And put lib\\libmysql.dll file in your Ruby bin directory, for example C:\\Ruby\\bin
-
-======================================================================================================
-
- POST_INSTALL_MESSAGE
- end
- end
-
- ext.lib_dir = File.join 'lib', 'mysql2'
-
- # clean compiled extension
- CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}"
-end
-Rake::Task[:spec].prerequisites << :compile
-
-file 'lib/mysql2/mysql2.rb' do |t|
- name = gemspec.name
- File.open(t.name, 'wb') do |f|
- f.write <<-eoruby
-RUBY_VERSION =~ /(\\d+.\\d+)/
-require "#{name}/\#{$1}/#{name}"
- eoruby
- end
-end
-
-if Rake::Task.task_defined?(:cross)
- Rake::Task[:cross].prerequisites << "lib/mysql2/mysql2.rb"
-end
diff --git a/tasks/rspec.rake b/tasks/rspec.rake
deleted file mode 100644
index a6628e8..0000000
--- a/tasks/rspec.rake
+++ /dev/null
@@ -1,16 +0,0 @@
-begin
- require 'rspec'
- require 'rspec/core/rake_task'
-
- 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
-
- task :default => :spec
-rescue LoadError
- puts "rspec, or one of its dependencies, is not available. Install it with: sudo gem install rspec"
-end
\ No newline at end of file
diff --git a/tasks/vendor_mysql.rake b/tasks/vendor_mysql.rake
deleted file mode 100644
index ce76840..0000000
--- a/tasks/vendor_mysql.rake
+++ /dev/null
@@ -1,40 +0,0 @@
-require 'rake/clean'
-require 'rake/extensioncompiler'
-
-# download mysql library and headers
-directory "vendor"
-
-file "vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32.zip" => ["vendor"] do |t|
- url = "http://dev.mysql.com/get/Downloads/Connector-C/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32.zip/from/#{CONNECTOR_MIRROR}/"
- when_writing "downloading #{t.name}" do
- cd File.dirname(t.name) do
- sh "wget -c #{url} || curl -C - -O #{url}"
- end
- end
-end
-
-file "vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/include/mysql.h" => ["vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32.zip"] do |t|
- full_file = File.expand_path(t.prerequisites.last)
- when_writing "creating #{t.name}" do
- cd "vendor" do
- sh "unzip #{full_file} mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/bin/** mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/include/** mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/lib/**"
- end
- # update file timestamp to avoid Rake perform this extraction again.
- touch t.name
- end
-end
-
-# clobber expanded packages
-CLOBBER.include("vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32")
-
-# vendor:mysql
-task 'vendor:mysql' => ["vendor/mysql-connector-c-noinstall-#{CONNECTOR_VERSION}-win32/include/mysql.h"]
-
-# hook into cross compilation vendored mysql dependency
-if RUBY_PLATFORM =~ /mingw|mswin/ then
- Rake::Task['compile'].prerequisites.unshift 'vendor:mysql'
-else
- if Rake::Task.tasks.map {|t| t.name }.include? 'cross'
- Rake::Task['cross'].prerequisites.unshift 'vendor:mysql'
- end
-end
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-mysql2.git
More information about the Pkg-ruby-extras-commits
mailing list