Skip to content

Commit

Permalink
Merge branch 'master' into stmt
Browse files Browse the repository at this point in the history
  • Loading branch information
brianmario committed Jun 15, 2011
2 parents 247a9a6 + f078542 commit 542d25a
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 177 deletions.
16 changes: 15 additions & 1 deletion CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog

## 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

Expand All @@ -12,6 +19,13 @@
* 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.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
Expand Down Expand Up @@ -151,4 +165,4 @@
* updated extconf (thanks to the mysqlplus project) for easier gem building

## 0.1.0 (April 6th, 2010)
* initial release
* initial release
23 changes: 23 additions & 0 deletions README.md
Expand Up @@ -158,8 +158,31 @@ client = Mysql2::Client.new
result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true)
```

### Skipping casting

Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false.

``` ruby
client = Mysql2::Client.new
result = client.query("SELECT * FROM table", :cast => false)
```

Here are the results from the `query_without_mysql_casting.rb` script in the benchmarks folder:

``` sh
user system total real
Mysql2 (cast: true) 0.340000 0.000000 0.340000 ( 0.405018)
Mysql2 (cast: false) 0.160000 0.010000 0.170000 ( 0.209937)
Mysql 0.080000 0.000000 0.080000 ( 0.129355)
do_mysql 0.520000 0.010000 0.530000 ( 0.574619)
```

Although Mysql2 performs reasonably well at retrieving uncasted data, it (currently) is not as fast as the Mysql gem. In spite of this small disadvantage, Mysql2 still sports a friendlier interface and doesn't block the entire ruby process when querying.

### Async

NOTE: Not supported on Windows.

`Mysql2::Client` takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries.
But, in order to take full advantage of it in your Ruby code, you can do:

Expand Down
13 changes: 11 additions & 2 deletions benchmark/query_without_mysql_casting.rb
Expand Up @@ -14,9 +14,18 @@
Benchmark.bmbm do |x|
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
mysql2.query "USE #{database}"
x.report "Mysql2" do
x.report "Mysql2 (cast: true)" do
number_of.times do
mysql2_result = mysql2.query sql, :symbolize_keys => true
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
Expand Down
152 changes: 74 additions & 78 deletions ext/mysql2/client.c
Expand Up @@ -145,10 +145,6 @@ static VALUE nogvl_close(void *ptr) {
flags = fcntl(wrapper->client->net.fd, F_GETFL);
if (flags > 0 && !(flags & O_NONBLOCK))
fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
#else
u_long iMode;
iMode = 1;
ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
#endif

mysql_close(wrapper->client);
Expand Down Expand Up @@ -281,6 +277,10 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
#endif
GET_CLIENT(self);

// if we're not waiting on a result, do nothing
if (!wrapper->active)
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
Expand Down Expand Up @@ -311,19 +311,72 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
return resultObj;
}

struct async_query_args {
int fd;
VALUE self;
};

static VALUE do_query(void *args) {
struct async_query_args *async_args;
struct timeval tv;
struct timeval* tvp;
long int sec;
fd_set fdset;
int retval;
int fd_set_fd;
VALUE read_timeout;

async_args = (struct async_query_args *)args;
read_timeout = rb_iv_get(async_args->self, "@read_timeout");

tvp = NULL;
if (!NIL_P(read_timeout)) {
Check_Type(read_timeout, T_FIXNUM);
tvp = &tv;
sec = FIX2INT(read_timeout);
// TODO: support partial seconds?
// also, this check is here for sanity, we also check up in Ruby
if (sec >= 0) {
tvp->tv_sec = sec;
} else {
rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
}
tvp->tv_usec = 0;
}

fd_set_fd = async_args->fd;
for(;;) {
// the below code is largely from do_mysql
// http://github.com/datamapper/do
FD_ZERO(&fdset);
FD_SET(fd_set_fd, &fdset);

retval = rb_thread_select(fd_set_fd + 1, &fdset, NULL, NULL, tvp);

if (retval == 0) {
rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
}

if (retval < 0) {
rb_sys_fail(0);
}

if (retval > 0) {
break;
}
}

return rb_mysql_client_async_result(async_args->self);
}

static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
struct async_query_args async_args;
struct nogvl_send_query_args args;
fd_set fdset;
int fd, retval;
int async = 0;
VALUE opts, defaults, read_timeout;
VALUE opts, defaults;
#ifdef HAVE_RUBY_ENCODING_H
rb_encoding *conn_enc;
#endif
struct timeval tv;
struct timeval* tvp;
long int sec;
VALUE result;
GET_CLIENT(self);

REQUIRE_OPEN_DB(wrapper);
Expand Down Expand Up @@ -362,70 +415,19 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
return rb_raise_mysql2_error(wrapper);
}

read_timeout = rb_iv_get(self, "@read_timeout");

tvp = NULL;
if (!NIL_P(read_timeout)) {
Check_Type(read_timeout, T_FIXNUM);
tvp = &tv;
sec = FIX2INT(read_timeout);
// TODO: support partial seconds?
// also, this check is here for sanity, we also check up in Ruby
if (sec >= 0) {
tvp->tv_sec = sec;
} else {
rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
}
tvp->tv_usec = 0;
}

#ifndef _WIN32
if (!async) {
// the below code is largely from do_mysql
// http://github.com/datamapper/do
fd = wrapper->client->net.fd;
for(;;) {
int fd_set_fd = fd;

#ifdef _WIN32
WSAPROTOCOL_INFO wsa_pi;
// dupicate the SOCKET from libmysql
int r = WSADuplicateSocket(fd, GetCurrentProcessId(), &wsa_pi);
SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
// create the CRT fd so ruby can get back to the SOCKET
fd_set_fd = _open_osfhandle(s, O_RDWR|O_BINARY);
#endif

FD_ZERO(&fdset);
FD_SET(fd_set_fd, &fdset);

retval = rb_thread_select(fd_set_fd + 1, &fdset, NULL, NULL, tvp);

#ifdef _WIN32
// cleanup the CRT fd
_close(fd_set_fd);
// cleanup the duplicated SOCKET
closesocket(s);
#endif
async_args.fd = wrapper->client->net.fd;
async_args.self = self;

if (retval == 0) {
rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
}

if (retval < 0) {
rb_sys_fail(0);
}

if (retval > 0) {
break;
}
}

result = rb_mysql_client_async_result(self);

return result;
return rb_ensure(do_query, (VALUE)&async_args, rb_mysql_client_async_result, self);
} else {
return Qnil;
}
#else
// this will just block until the result is ready
return rb_mysql_client_async_result(self);
#endif
}

static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
Expand Down Expand Up @@ -523,18 +525,12 @@ static VALUE rb_mysql_client_server_info(VALUE self) {

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;
#ifdef _WIN32
WSAPROTOCOL_INFO wsa_pi;
// dupicate the SOCKET from libmysql
int r = WSADuplicateSocket(wrapper->client->net.fd, GetCurrentProcessId(), &wsa_pi);
SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
// create the CRT fd so ruby can get back to the SOCKET
fd_set_fd = _open_osfhandle(s, O_RDWR|O_BINARY);
return INT2NUM(fd_set_fd);
#else
return INT2NUM(fd_set_fd);
rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
#endif
}

Expand Down

0 comments on commit 542d25a

Please sign in to comment.