Merge branch 'master' into stmt

This commit is contained in:
Brian Lopez 2010-09-24 14:39:21 -07:00
commit 39a28c4a91
18 changed files with 226 additions and 123 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ mkmf.log
pkg/ pkg/
tmp tmp
vendor vendor
lib/mysql2/mysql2.rb

View File

@ -1,5 +1,14 @@
# Changelog # Changelog
## 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@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) ## 0.2.3 (August 20th, 2010)
* connection flags can now be passed to the constructor via the :flags key * connection flags can now be passed to the constructor via the :flags key
* switch AR adapter connection over to use FOUND_ROWS option * switch AR adapter connection over to use FOUND_ROWS option

View File

@ -89,18 +89,18 @@ or
=== Array of Arrays === Array of Arrays
Pass the {:as => :array} option to any of the above methods of configuration Pass the :as => :array option to any of the above methods of configuration
=== Array of Hashes === Array of Hashes
The default result type is set to :hash, but you can override a previous setting to something else with {:as => :hash} The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash
=== Others... === Others...
I may add support for {:as => :csv} or even {:as => :json} to allow for *much* more efficient generation of those data types from result sets. I may add support for :as => :csv or even :as => :json to allow for *much* more efficient generation of those data types from result sets.
If you'd like to see either of these (or others), open an issue and start bugging me about it ;) If you'd like to see either of these (or others), open an issue and start bugging me about it ;)
== Timezones === Timezones
Mysql2 now supports two timezone options: Mysql2 now supports two timezone options:
@ -112,14 +112,14 @@ Then, if :application_timezone is set to say - :local - Mysql2 will then convert
Both options only allow two values - :local or :utc - with the exception that :application_timezone can be [and defaults to] nil Both options only allow two values - :local or :utc - with the exception that :application_timezone can be [and defaults to] nil
== Casting "boolean" columns === Casting "boolean" columns
You can now tell Mysql2 to cast tinyint(1) fields to boolean values in Ruby with the :cast_booleans option. You can now tell Mysql2 to cast tinyint(1) fields to boolean values in Ruby with the :cast_booleans option.
client = Mysql2::Client.new client = Mysql2::Client.new
result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true) result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true)
== Async === Async
Mysql2::Client takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries. 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: But, in order to take full advantage of it in your Ruby code, you can do:
@ -136,6 +136,14 @@ NOTE: Because of the way MySQL's query API works, this method will block until t
So if you really need things to stay async, it's best to just monitor the socket with something like EventMachine. So if you really need things to stay async, it's best to just monitor the socket with something like EventMachine.
If you need multiple query concurrency take a look at using a connection pool. If you need multiple query concurrency take a look at using a connection pool.
=== Row Caching
By default, Mysql2 will cache rows that have been created in Ruby (since this happens lazily).
This is especially helpful since it saves the cost of creating the row in Ruby if you were to iterate over the collection again.
If you only plan on using each row once, then it's much more efficient to disable this behavior by setting the :cache_rows option to false.
This would be helpful if you wanted to iterate over the results in a streaming manner. Meaning the GC would cleanup rows you don't need anymore as you're iterating over the result set.
== ActiveRecord == ActiveRecord
To use the ActiveRecord driver, all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2". To use the ActiveRecord driver, all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
@ -182,6 +190,8 @@ For example, if you were to yield 4 rows from a 100 row dataset, only 4 hashes w
Now say you were to iterate over that same collection again, this time yielding 15 rows - the 4 previous rows that had already been turned into ruby hashes would be pulled from an internal cache, then 11 more would be created and stored in that cache. Now say you were to iterate over that same collection again, this time yielding 15 rows - the 4 previous rows that had already been turned into ruby hashes would be pulled from an internal cache, then 11 more would be created and stored in that cache.
Once the entire dataset has been converted into ruby objects, Mysql2::Result will free the Mysql C result object as it's no longer needed. Once the entire dataset has been converted into ruby objects, Mysql2::Result will free the Mysql C result object as it's no longer needed.
This caching behavior can be disabled by setting the :cache_rows option to false.
As for field values themselves, I'm workin on it - but expect that soon. As for field values themselves, I'm workin on it - but expect that soon.
== Compatibility == Compatibility

View File

@ -1 +1 @@
0.2.3 0.2.4

View File

@ -70,7 +70,7 @@ Benchmark.bmbm do |x|
end end
do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}") do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
command = DataObjects::Mysql::Command.new do_mysql, sql command = do_mysql.create_command sql
x.report do x.report do
puts "do_mysql" puts "do_mysql"
number_of.times do number_of.times do

View File

@ -8,20 +8,18 @@ static VALUE intern_encoding_from_charset;
static ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array; static ID 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 ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
#define REQUIRE_OPEN_DB(_ctxt) \ #define REQUIRE_OPEN_DB(wrapper) \
if(!_ctxt->net.vio) { \ if(wrapper->closed || !wrapper->client->net.vio) { \
rb_raise(cMysql2Error, "closed MySQL connection"); \ rb_raise(cMysql2Error, "closed MySQL connection"); \
return Qnil; \ return Qnil; \
} }
#define MARK_CONN_INACTIVE(conn) \ #define MARK_CONN_INACTIVE(conn) \
wrapper->active = 0; wrapper->active = 0
#define GET_CLIENT(self) \ #define GET_CLIENT(self) \
mysql_client_wrapper *wrapper; \ mysql_client_wrapper *wrapper; \
MYSQL *client; \ Data_Get_Struct(self, mysql_client_wrapper, wrapper)
Data_Get_Struct(self, mysql_client_wrapper, wrapper); \
client = &wrapper->client;
/* /*
* used to pass all arguments to mysql_real_connect while inside * used to pass all arguments to mysql_real_connect while inside
@ -85,12 +83,11 @@ static VALUE rb_raise_mysql2_error(MYSQL *client) {
} }
static VALUE nogvl_init(void *ptr) { static VALUE nogvl_init(void *ptr) {
MYSQL * client = (MYSQL *)ptr; MYSQL **client = (MYSQL **)ptr;
/* may initialize embedded server and read /etc/services off disk */ /* may initialize embedded server and read /etc/services off disk */
client = mysql_init(NULL); *client = mysql_init(NULL);
return *client ? Qtrue : Qfalse;
return client ? Qtrue : Qfalse;
} }
static VALUE nogvl_connect(void *ptr) { static VALUE nogvl_connect(void *ptr) {
@ -108,36 +105,43 @@ static VALUE nogvl_connect(void *ptr) {
} }
static void rb_mysql_client_free(void * ptr) { static void rb_mysql_client_free(void * ptr) {
mysql_client_wrapper * wrapper = (mysql_client_wrapper *)ptr; mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
MYSQL * client = &wrapper->client;
/* /*
* we'll send a QUIT message to the server, but that message is more of a * 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 * formality than a hard requirement since the socket is getting shutdown
* anyways, so ensure the socket write does not block our interpreter * anyways, so ensure the socket write does not block our interpreter
*/ */
int fd = client->net.fd; int fd = wrapper->client->net.fd;
int flags;
if (fd >= 0) { if (fd >= 0) {
/* /*
* if the socket is dead we have no chance of blocking, * if the socket is dead we have no chance of blocking,
* so ignore any potential fcntl errors since they don't matter * so ignore any potential fcntl errors since they don't matter
*/ */
flags = fcntl(fd, F_GETFL); #ifndef _WIN32
int flags = fcntl(fd, F_GETFL);
if (flags > 0 && !(flags & O_NONBLOCK)) if (flags > 0 && !(flags & O_NONBLOCK))
fcntl(fd, F_SETFL, flags | O_NONBLOCK); fcntl(fd, F_SETFL, flags | O_NONBLOCK);
#else
u_long iMode = 1;
ioctlsocket(fd, FIONBIO, &iMode);
#endif
} }
/* It's safe to call mysql_close() on an already closed connection. */ /* It's safe to call mysql_close() on an already closed connection. */
mysql_close(client); if (!wrapper->closed) {
mysql_close(wrapper->client);
}
xfree(ptr); xfree(ptr);
} }
static VALUE nogvl_close(void * ptr) { static VALUE nogvl_close(void * ptr) {
MYSQL *client = (MYSQL *)ptr; mysql_client_wrapper *wrapper = ptr;
mysql_close(client); if (!wrapper->closed) {
client->net.fd = -1; mysql_close(wrapper->client);
wrapper->closed = 1;
}
return Qnil; return Qnil;
} }
@ -147,12 +151,13 @@ static VALUE allocate(VALUE klass) {
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
wrapper->encoding = Qnil; wrapper->encoding = Qnil;
wrapper->active = 0; wrapper->active = 0;
wrapper->closed = 0;
return obj; return obj;
} }
static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) { static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
struct nogvl_connect_args args; struct nogvl_connect_args args;
GET_CLIENT(self) GET_CLIENT(self);
args.host = NIL_P(host) ? "localhost" : StringValuePtr(host); args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket); args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
@ -160,12 +165,12 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
args.user = NIL_P(user) ? NULL : StringValuePtr(user); args.user = NIL_P(user) ? NULL : StringValuePtr(user);
args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass); args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
args.db = NIL_P(database) ? NULL : StringValuePtr(database); args.db = NIL_P(database) ? NULL : StringValuePtr(database);
args.mysql = client; args.mysql = wrapper->client;
args.client_flag = NUM2INT(flags); args.client_flag = NUM2INT(flags);
if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) { if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
// unable to connect // unable to connect
return rb_raise_mysql2_error(client); return rb_raise_mysql2_error(wrapper->client);
} }
return self; return self;
@ -178,9 +183,9 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
* for the garbage collector. * for the garbage collector.
*/ */
static VALUE rb_mysql_client_close(VALUE self) { static VALUE rb_mysql_client_close(VALUE self) {
GET_CLIENT(self) GET_CLIENT(self);
rb_thread_blocking_region(nogvl_close, client, RUBY_UBF_IO, 0); rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
return Qnil; return Qnil;
} }
@ -221,30 +226,30 @@ static VALUE nogvl_store_result(void *ptr) {
static VALUE rb_mysql_client_async_result(VALUE self) { static VALUE rb_mysql_client_async_result(VALUE self) {
MYSQL_RES * result; MYSQL_RES * result;
GET_CLIENT(self) GET_CLIENT(self);
REQUIRE_OPEN_DB(client); REQUIRE_OPEN_DB(wrapper);
if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) { if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
// an error occurred, mark this connection inactive // an error occurred, mark this connection inactive
MARK_CONN_INACTIVE(self); MARK_CONN_INACTIVE(self);
return rb_raise_mysql2_error(client); return rb_raise_mysql2_error(wrapper->client);
} }
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, client, RUBY_UBF_IO, 0); result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper->client, RUBY_UBF_IO, 0);
// we have our result, mark this connection inactive // we have our result, mark this connection inactive
MARK_CONN_INACTIVE(self); MARK_CONN_INACTIVE(self);
if (result == NULL) { if (result == NULL) {
if (mysql_field_count(client) != 0) { if (mysql_field_count(wrapper->client) != 0) {
rb_raise_mysql2_error(client); rb_raise_mysql2_error(wrapper->client);
} }
return Qnil; return Qnil;
} }
VALUE resultObj = rb_mysql_result_to_obj(result); VALUE resultObj = rb_mysql_result_to_obj(result);
// pass-through query options for result construction later // pass-through query options for result construction later
rb_iv_set(resultObj, "@query_options", rb_obj_dup(rb_iv_get(self, "@query_options"))); rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
mysql2_result_wrapper * result_wrapper; mysql2_result_wrapper * result_wrapper;
@ -260,10 +265,10 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
int fd, retval; int fd, retval;
int async = 0; int async = 0;
VALUE opts, defaults; VALUE opts, defaults;
GET_CLIENT(self) GET_CLIENT(self);
REQUIRE_OPEN_DB(client); REQUIRE_OPEN_DB(wrapper);
args.mysql = client; args.mysql = wrapper->client;
// see if this connection is still waiting on a result from a previous query // see if this connection is still waiting on a result from a previous query
if (wrapper->active == 0) { if (wrapper->active == 0) {
@ -294,13 +299,13 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) { if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
// an error occurred, we're not active anymore // an error occurred, we're not active anymore
MARK_CONN_INACTIVE(self); MARK_CONN_INACTIVE(self);
return rb_raise_mysql2_error(client); return rb_raise_mysql2_error(wrapper->client);
} }
if (!async) { if (!async) {
// the below code is largely from do_mysql // the below code is largely from do_mysql
// http://github.com/datamapper/do // http://github.com/datamapper/do
fd = client->net.fd; fd = wrapper->client->net.fd;
for(;;) { for(;;) {
FD_ZERO(&fdset); FD_ZERO(&fdset);
FD_SET(fd, &fdset); FD_SET(fd, &fdset);
@ -327,7 +332,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
static VALUE rb_mysql_client_escape(VALUE self, VALUE str) { static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
VALUE newStr; VALUE newStr;
unsigned long newLen, oldLen; unsigned long newLen, oldLen;
GET_CLIENT(self) GET_CLIENT(self);
Check_Type(str, T_STRING); Check_Type(str, T_STRING);
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
@ -338,15 +343,15 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
#endif #endif
oldLen = RSTRING_LEN(str); oldLen = RSTRING_LEN(str);
char escaped[(oldLen*2)+1]; newStr = rb_str_new(0, oldLen*2+1);
REQUIRE_OPEN_DB(client); REQUIRE_OPEN_DB(wrapper);
newLen = mysql_real_escape_string(client, escaped, StringValuePtr(str), oldLen); newLen = mysql_real_escape_string(wrapper->client, RSTRING_PTR(newStr), StringValuePtr(str), oldLen);
if (newLen == 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
return str; return str;
} else { } else {
newStr = rb_str_new(escaped, newLen); rb_str_resize(newStr, newLen);
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
rb_enc_associate(newStr, conn_enc); rb_enc_associate(newStr, conn_enc);
if (default_internal_enc) { if (default_internal_enc) {
@ -379,17 +384,17 @@ static VALUE rb_mysql_client_info(VALUE self) {
static VALUE rb_mysql_client_server_info(VALUE self) { static VALUE rb_mysql_client_server_info(VALUE self) {
VALUE version, server_info; VALUE version, server_info;
GET_CLIENT(self) GET_CLIENT(self);
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
rb_encoding *default_internal_enc = rb_default_internal_encoding(); rb_encoding *default_internal_enc = rb_default_internal_encoding();
rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
#endif #endif
REQUIRE_OPEN_DB(client); REQUIRE_OPEN_DB(wrapper);
version = rb_hash_new(); version = rb_hash_new();
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client))); rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
server_info = rb_str_new2(mysql_get_server_info(client)); server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
rb_enc_associate(server_info, conn_enc); rb_enc_associate(server_info, conn_enc);
if (default_internal_enc) { if (default_internal_enc) {
@ -401,34 +406,34 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
} }
static VALUE rb_mysql_client_socket(VALUE self) { static VALUE rb_mysql_client_socket(VALUE self) {
GET_CLIENT(self) GET_CLIENT(self);
REQUIRE_OPEN_DB(client); REQUIRE_OPEN_DB(wrapper);
return INT2NUM(client->net.fd); return INT2NUM(wrapper->client->net.fd);
} }
static VALUE rb_mysql_client_last_id(VALUE self) { static VALUE rb_mysql_client_last_id(VALUE self) {
GET_CLIENT(self) GET_CLIENT(self);
REQUIRE_OPEN_DB(client); REQUIRE_OPEN_DB(wrapper);
return ULL2NUM(mysql_insert_id(client)); return ULL2NUM(mysql_insert_id(wrapper->client));
} }
static VALUE rb_mysql_client_affected_rows(VALUE self) { static VALUE rb_mysql_client_affected_rows(VALUE self) {
GET_CLIENT(self) GET_CLIENT(self);
REQUIRE_OPEN_DB(client); REQUIRE_OPEN_DB(wrapper);
return ULL2NUM(mysql_affected_rows(client)); return ULL2NUM(mysql_affected_rows(wrapper->client));
} }
static VALUE set_reconnect(VALUE self, VALUE value) { static VALUE set_reconnect(VALUE self, VALUE value) {
my_bool reconnect; my_bool reconnect;
GET_CLIENT(self) GET_CLIENT(self);
if(!NIL_P(value)) { if(!NIL_P(value)) {
reconnect = value == Qfalse ? 0 : 1; reconnect = value == Qfalse ? 0 : 1;
/* set default reconnect behavior */ /* set default reconnect behavior */
if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect)) { if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
/* TODO: warning - unable to set reconnect behavior */ /* TODO: warning - unable to set reconnect behavior */
rb_warn("%s\n", mysql_error(client)); rb_warn("%s\n", mysql_error(wrapper->client));
} }
} }
return value; return value;
@ -436,16 +441,16 @@ static VALUE set_reconnect(VALUE self, VALUE value) {
static VALUE set_connect_timeout(VALUE self, VALUE value) { static VALUE set_connect_timeout(VALUE self, VALUE value) {
unsigned int connect_timeout = 0; unsigned int connect_timeout = 0;
GET_CLIENT(self) GET_CLIENT(self);
if(!NIL_P(value)) { if(!NIL_P(value)) {
connect_timeout = NUM2INT(value); connect_timeout = NUM2INT(value);
if(0 == connect_timeout) return value; if(0 == connect_timeout) return value;
/* set default connection timeout behavior */ /* set default connection timeout behavior */
if (mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) { if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
/* TODO: warning - unable to set connection timeout */ /* TODO: warning - unable to set connection timeout */
rb_warn("%s\n", mysql_error(client)); rb_warn("%s\n", mysql_error(wrapper->client));
} }
} }
return value; return value;
@ -453,7 +458,7 @@ static VALUE set_connect_timeout(VALUE self, VALUE value) {
static VALUE set_charset_name(VALUE self, VALUE value) { static VALUE set_charset_name(VALUE self, VALUE value) {
char * charset_name; char * charset_name;
GET_CLIENT(self) GET_CLIENT(self);
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
VALUE new_encoding; VALUE new_encoding;
@ -469,19 +474,19 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
charset_name = StringValuePtr(value); charset_name = StringValuePtr(value);
if (mysql_options(client, MYSQL_SET_CHARSET_NAME, charset_name)) { if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
/* TODO: warning - unable to set charset */ /* TODO: warning - unable to set charset */
rb_warn("%s\n", mysql_error(client)); rb_warn("%s\n", mysql_error(wrapper->client));
} }
return value; return value;
} }
static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) { static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
GET_CLIENT(self) GET_CLIENT(self);
if(!NIL_P(ca) || !NIL_P(key)) { if(!NIL_P(ca) || !NIL_P(key)) {
mysql_ssl_set(client, mysql_ssl_set(wrapper->client,
NIL_P(key) ? NULL : StringValuePtr(key), NIL_P(key) ? NULL : StringValuePtr(key),
NIL_P(cert) ? NULL : StringValuePtr(cert), NIL_P(cert) ? NULL : StringValuePtr(cert),
NIL_P(ca) ? NULL : StringValuePtr(ca), NIL_P(ca) ? NULL : StringValuePtr(ca),
@ -493,11 +498,11 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
} }
static VALUE init_connection(VALUE self) { static VALUE init_connection(VALUE self) {
GET_CLIENT(self) GET_CLIENT(self);
if (rb_thread_blocking_region(nogvl_init, client, RUBY_UBF_IO, 0) == Qfalse) { if (rb_thread_blocking_region(nogvl_init, ((void *) &wrapper->client), RUBY_UBF_IO, 0) == Qfalse) {
/* TODO: warning - not enough memory? */ /* TODO: warning - not enough memory? */
return rb_raise_mysql2_error(client); return rb_raise_mysql2_error(wrapper->client);
} }
return self; return self;

View File

@ -34,7 +34,8 @@ void init_mysql2_client();
typedef struct { typedef struct {
VALUE encoding; VALUE encoding;
short int active; short int active;
MYSQL client; short int closed;
MYSQL *client;
} mysql_client_wrapper; } mysql_client_wrapper;
#endif #endif

View File

@ -12,7 +12,7 @@ static VALUE intern_encoding_from_charset;
static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code, static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code,
intern_localtime, intern_local_offset, intern_civil, intern_new_offset; intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
static ID sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone, static ID sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
sym_local, sym_utc, sym_cast_booleans; sym_local, sym_utc, sym_cast_booleans, sym_cache_rows;
static ID intern_merge; static ID intern_merge;
static void rb_mysql_result_mark(void * wrapper) { static void rb_mysql_result_mark(void * wrapper) {
@ -316,7 +316,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
ID db_timezone, app_timezone, dbTz, appTz; ID db_timezone, app_timezone, dbTz, appTz;
mysql2_result_wrapper * wrapper; mysql2_result_wrapper * wrapper;
unsigned long i; unsigned long i;
int symbolizeKeys = 0, asArray = 0, castBool = 0; int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1;
GetMysql2Result(self, wrapper); GetMysql2Result(self, wrapper);
@ -339,6 +339,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
castBool = 1; castBool = 1;
} }
if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
cacheRows = 0;
}
dbTz = rb_hash_aref(opts, sym_database_timezone); dbTz = rb_hash_aref(opts, sym_database_timezone);
if (dbTz == sym_local) { if (dbTz == sym_local) {
db_timezone = intern_local; db_timezone = intern_local;
@ -369,7 +373,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
wrapper->rows = rb_ary_new2(wrapper->numberOfRows); wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
} }
if (wrapper->lastRowProcessed == wrapper->numberOfRows) { if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
// we've already read the entire dataset from the C result into our // 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 // internal array. Lets hand that over to the user since it's ready to go
for (i = 0; i < wrapper->numberOfRows; i++) { for (i = 0; i < wrapper->numberOfRows; i++) {
@ -380,11 +384,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
rowsProcessed = RARRAY_LEN(wrapper->rows); rowsProcessed = RARRAY_LEN(wrapper->rows);
for (i = 0; i < wrapper->numberOfRows; i++) { for (i = 0; i < wrapper->numberOfRows; i++) {
VALUE row; VALUE row;
if (i < rowsProcessed) { if (cacheRows && i < rowsProcessed) {
row = rb_ary_entry(wrapper->rows, i); row = rb_ary_entry(wrapper->rows, i);
} else { } else {
row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool); row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool);
rb_ary_store(wrapper->rows, i, row); if (cacheRows) {
rb_ary_store(wrapper->rows, i, row);
}
wrapper->lastRowProcessed++; wrapper->lastRowProcessed++;
} }
@ -453,11 +459,12 @@ void init_mysql2_result() {
sym_cast_booleans = ID2SYM(rb_intern("cast_booleans")); sym_cast_booleans = ID2SYM(rb_intern("cast_booleans"));
sym_database_timezone = ID2SYM(rb_intern("database_timezone")); sym_database_timezone = ID2SYM(rb_intern("database_timezone"));
sym_application_timezone = ID2SYM(rb_intern("application_timezone")); sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
rb_global_variable(&opt_decimal_zero); //never GC
opt_decimal_zero = rb_str_new2("0.0"); opt_decimal_zero = rb_str_new2("0.0");
rb_global_variable(&opt_float_zero); rb_global_variable(&opt_decimal_zero); //never GC
opt_float_zero = rb_float_new((double)0); opt_float_zero = rb_float_new((double)0);
rb_global_variable(&opt_float_zero);
opt_time_year = INT2NUM(2000); opt_time_year = INT2NUM(2000);
opt_time_month = INT2NUM(1); opt_time_month = INT2NUM(1);
opt_utc_offset = INT2NUM(0); opt_utc_offset = INT2NUM(0);

View File

@ -8,7 +8,7 @@ typedef struct {
VALUE fields; VALUE fields;
VALUE rows; VALUE rows;
VALUE encoding; VALUE encoding;
unsigned int numberOfFields; long numberOfFields;
unsigned long numberOfRows; unsigned long numberOfRows;
unsigned long lastRowProcessed; unsigned long lastRowProcessed;
short int resultFreed; short int resultFreed;

View File

@ -447,10 +447,11 @@ module ActiveRecord
if current_index != row[:Key_name] if current_index != row[:Key_name]
next if row[:Key_name] == PRIMARY # skip the primary key next if row[:Key_name] == PRIMARY # skip the primary key
current_index = row[:Key_name] current_index = row[:Key_name]
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, []) indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
end end
indexes.last.columns << row[:Column_name] indexes.last.columns << row[:Column_name]
indexes.last.lengths << row[:Sub_part]
end end
indexes indexes
end end

View File

@ -13,5 +13,5 @@ require 'mysql2/field'
# #
# A modern, simple and very fast Mysql library for Ruby - binding to libmysql # A modern, simple and very fast Mysql library for Ruby - binding to libmysql
module Mysql2 module Mysql2
VERSION = "0.2.3" VERSION = "0.2.4"
end end

View File

@ -2,12 +2,13 @@ module Mysql2
class Client class Client
attr_reader :query_options attr_reader :query_options
@@default_query_options = { @@default_query_options = {
:as => :hash, :as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
:async => false, :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
:cast_booleans => false, :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
:symbolize_keys => false, :symbolize_keys => false, # return field names as symbols instead of strings
:database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
:application_timezone => nil # timezone Mysql2 will convert to before handing the object back to the caller :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
} }
def initialize(opts = {}) def initialize(opts = {})

View File

@ -7,5 +7,9 @@ module Mysql2
@error_number = nil @error_number = nil
@sql_state = nil @sql_state = nil
end end
# Mysql gem compatibility
alias_method :errno, :error_number
alias_method :error, :message
end end
end end

View File

@ -5,11 +5,11 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = %q{mysql2} s.name = %q{mysql2}
s.version = "0.2.3" s.version = "0.2.4"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Brian Lopez"] s.authors = ["Brian Lopez"]
s.date = %q{2010-08-20} s.date = %q{2010-09-17}
s.email = %q{seniorlopez@gmail.com} s.email = %q{seniorlopez@gmail.com}
s.extensions = ["ext/mysql2/extconf.rb"] s.extensions = ["ext/mysql2/extconf.rb"]
s.extra_rdoc_files = [ s.extra_rdoc_files = [

View File

@ -105,30 +105,32 @@ describe Mysql2::Client do
# XXX this test is not deterministic (because Unix signal handling is not) # XXX this test is not deterministic (because Unix signal handling is not)
# and may fail on a loaded system # and may fail on a loaded system
it "should run signal handlers while waiting for a response" do if RUBY_PLATFORM !~ /mingw|mswin/
mark = {} it "should run signal handlers while waiting for a response" do
trap(:USR1) { mark[:USR1] = Time.now } mark = {}
begin trap(:USR1) { mark[:USR1] = Time.now }
mark[:START] = Time.now begin
pid = fork do mark[:START] = Time.now
sleep 1 # wait for client "SELECT sleep(2)" query to start pid = fork do
Process.kill(:USR1, Process.ppid) sleep 1 # wait for client "SELECT sleep(2)" query to start
sleep # wait for explicit kill to prevent GC disconnect 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.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
@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
end if RUBY_PLATFORM !~ /mingw|mswin/ end
end end
it "should respond to #escape" do it "should respond to #escape" do
@ -144,6 +146,18 @@ describe Mysql2::Client do
@client.escape(str).object_id.should eql(str.object_id) @client.escape(str).object_id.should eql(str.object_id)
end end
it "#escape should not overflow the thread stack" do
lambda {
Thread.new { @client.escape("'" * 256 * 1024) }.join
}.should_not raise_error(SystemStackError)
end
it "#escape should not overflow the process stack" do
lambda {
Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
}.should_not raise_error(SystemStackError)
end
it "should respond to #info" do it "should respond to #info" do
@client.should respond_to(:info) @client.should respond_to(:info)
end end

View File

@ -13,4 +13,13 @@ describe Mysql2::Error do
it "should respond to #sql_state" do it "should respond to #sql_state" do
@error.should respond_to(:sql_state) @error.should respond_to(:sql_state)
end end
# Mysql gem compatibility
it "should alias #error_number to #errno" do
@error.should respond_to(:errno)
end
it "should alias #message to #error" do
@error.should respond_to(:error)
end
end end

View File

@ -47,8 +47,13 @@ describe Mysql2::Result do
end end
end end
it "should cache previously yielded results" do it "should cache previously yielded results by default" do
@result.first.should eql(@result.first) @result.first.object_id.should eql(@result.first.object_id)
end
it "should not cache previously yielded results if cache_rows is disabled" do
result = @client.query "SELECT 1", :cache_rows => false
result.first.object_id.should_not eql(result.first.object_id)
end end
end end

View File

@ -1,10 +1,15 @@
gem 'rake-compiler', '~> 0.7.1' gem 'rake-compiler', '~> 0.7.1'
require "rake/extensiontask" require "rake/extensiontask"
MYSQL_VERSION = "5.1.49" MYSQL_VERSION = "5.1.50"
MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.localhost.net.ar" MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.mirrors.pair.com"
Rake::ExtensionTask.new("mysql2", JEWELER.gemspec) do |ext|
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 # reference where the vendored MySQL got extracted
mysql_lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', "mysql-#{MYSQL_VERSION}-win32")) mysql_lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', "mysql-#{MYSQL_VERSION}-win32"))
@ -12,8 +17,39 @@ Rake::ExtensionTask.new("mysql2", JEWELER.gemspec) do |ext|
if RUBY_PLATFORM =~ /mswin|mingw/ then if RUBY_PLATFORM =~ /mswin|mingw/ then
ext.config_options << "--with-mysql-include=#{mysql_lib}/include" ext.config_options << "--with-mysql-include=#{mysql_lib}/include"
ext.config_options << "--with-mysql-lib=#{mysql_lib}/lib/opt" ext.config_options << "--with-mysql-lib=#{mysql_lib}/lib/opt"
else
ext.cross_compile = true
ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60']
ext.cross_config_options << "--with-mysql-include=#{mysql_lib}/include"
ext.cross_config_options << "--with-mysql-lib=#{mysql_lib}/lib/opt"
end end
ext.lib_dir = File.join 'lib', 'mysql2' ext.lib_dir = File.join 'lib', 'mysql2'
# clean compiled extension
CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}"
end end
Rake::Task[:spec].prerequisites << :compile Rake::Task[:spec].prerequisites << :compile
namespace :cross do
task :file_list do
gemspec.extensions = []
gemspec.files += Dir["lib/#{gemspec.name}/#{gemspec.name}.rb"]
gemspec.files += Dir["lib/#{gemspec.name}/1.{8,9}/#{gemspec.name}.so"]
# gemspec.files += Dir["ext/mysql2/*.dll"]
end
end
file 'lib/mysql2/mysql2.rb' do
name = gemspec.name
File.open("lib/#{name}/#{name}.rb", 'wb') do |f|
f.write <<-eoruby
require "#{name}/\#{RUBY_VERSION.sub(/\\.\\d+$/, '')}/#{name}"
eoruby
end
end
if Rake::Task.task_defined?(:cross)
Rake::Task[:cross].prerequisites << "lib/mysql2/mysql2.rb"
Rake::Task[:cross].prerequisites << "cross:file_list"
end