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/
tmp
vendor
lib/mysql2/mysql2.rb

View File

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

View File

@ -89,18 +89,18 @@ or
=== 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
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...
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 ;)
== Timezones
=== Timezones
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
== 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.
client = Mysql2::Client.new
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.
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.
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
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.
Once the entire dataset has been converted into ruby objects, Mysql2::Result will free the Mysql C result object as it's no longer needed.
This caching behavior can be disabled by setting the :cache_rows option to false.
As for field values themselves, I'm workin on it - but expect that soon.
== Compatibility

View File

@ -1 +1 @@
0.2.3
0.2.4

View File

@ -70,7 +70,7 @@ Benchmark.bmbm do |x|
end
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
puts "do_mysql"
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 intern_merge, intern_error_number_eql, intern_sql_state_eql;
#define REQUIRE_OPEN_DB(_ctxt) \
if(!_ctxt->net.vio) { \
#define REQUIRE_OPEN_DB(wrapper) \
if(wrapper->closed || !wrapper->client->net.vio) { \
rb_raise(cMysql2Error, "closed MySQL connection"); \
return Qnil; \
}
#define MARK_CONN_INACTIVE(conn) \
wrapper->active = 0;
wrapper->active = 0
#define GET_CLIENT(self) \
mysql_client_wrapper *wrapper; \
MYSQL *client; \
Data_Get_Struct(self, mysql_client_wrapper, wrapper); \
client = &wrapper->client;
Data_Get_Struct(self, mysql_client_wrapper, wrapper)
/*
* 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) {
MYSQL * client = (MYSQL *)ptr;
MYSQL **client = (MYSQL **)ptr;
/* may initialize embedded server and read /etc/services off disk */
client = mysql_init(NULL);
return client ? Qtrue : Qfalse;
*client = mysql_init(NULL);
return *client ? Qtrue : Qfalse;
}
static VALUE nogvl_connect(void *ptr) {
@ -108,36 +105,43 @@ static VALUE nogvl_connect(void *ptr) {
}
static void rb_mysql_client_free(void * ptr) {
mysql_client_wrapper * wrapper = (mysql_client_wrapper *)ptr;
MYSQL * client = &wrapper->client;
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
/*
* 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
* anyways, so ensure the socket write does not block our interpreter
*/
int fd = client->net.fd;
int flags;
int fd = wrapper->client->net.fd;
if (fd >= 0) {
/*
* if the socket is dead we have no chance of blocking,
* 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))
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. */
mysql_close(client);
if (!wrapper->closed) {
mysql_close(wrapper->client);
}
xfree(ptr);
}
static VALUE nogvl_close(void * ptr) {
MYSQL *client = (MYSQL *)ptr;
mysql_close(client);
client->net.fd = -1;
mysql_client_wrapper *wrapper = ptr;
if (!wrapper->closed) {
mysql_close(wrapper->client);
wrapper->closed = 1;
}
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);
wrapper->encoding = Qnil;
wrapper->active = 0;
wrapper->closed = 0;
return obj;
}
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;
GET_CLIENT(self)
GET_CLIENT(self);
args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
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.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
args.db = NIL_P(database) ? NULL : StringValuePtr(database);
args.mysql = client;
args.mysql = wrapper->client;
args.client_flag = NUM2INT(flags);
if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
// unable to connect
return rb_raise_mysql2_error(client);
return rb_raise_mysql2_error(wrapper->client);
}
return self;
@ -178,9 +183,9 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
* for the garbage collector.
*/
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;
}
@ -221,30 +226,30 @@ static VALUE nogvl_store_result(void *ptr) {
static VALUE rb_mysql_client_async_result(VALUE self) {
MYSQL_RES * result;
GET_CLIENT(self)
GET_CLIENT(self);
REQUIRE_OPEN_DB(client);
if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) {
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
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
MARK_CONN_INACTIVE(self);
if (result == NULL) {
if (mysql_field_count(client) != 0) {
rb_raise_mysql2_error(client);
if (mysql_field_count(wrapper->client) != 0) {
rb_raise_mysql2_error(wrapper->client);
}
return Qnil;
}
VALUE resultObj = rb_mysql_result_to_obj(result);
// 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
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 async = 0;
VALUE opts, defaults;
GET_CLIENT(self)
GET_CLIENT(self);
REQUIRE_OPEN_DB(client);
args.mysql = client;
REQUIRE_OPEN_DB(wrapper);
args.mysql = wrapper->client;
// see if this connection is still waiting on a result from a previous query
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) {
// an error occurred, we're not active anymore
MARK_CONN_INACTIVE(self);
return rb_raise_mysql2_error(client);
return rb_raise_mysql2_error(wrapper->client);
}
if (!async) {
// the below code is largely from do_mysql
// http://github.com/datamapper/do
fd = client->net.fd;
fd = wrapper->client->net.fd;
for(;;) {
FD_ZERO(&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) {
VALUE newStr;
unsigned long newLen, oldLen;
GET_CLIENT(self)
GET_CLIENT(self);
Check_Type(str, T_STRING);
#ifdef HAVE_RUBY_ENCODING_H
@ -338,15 +343,15 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
#endif
oldLen = RSTRING_LEN(str);
char escaped[(oldLen*2)+1];
newStr = rb_str_new(0, oldLen*2+1);
REQUIRE_OPEN_DB(client);
newLen = mysql_real_escape_string(client, escaped, StringValuePtr(str), oldLen);
REQUIRE_OPEN_DB(wrapper);
newLen = mysql_real_escape_string(wrapper->client, RSTRING_PTR(newStr), StringValuePtr(str), oldLen);
if (newLen == oldLen) {
// no need to return a new ruby string if nothing changed
return str;
} else {
newStr = rb_str_new(escaped, newLen);
rb_str_resize(newStr, newLen);
#ifdef HAVE_RUBY_ENCODING_H
rb_enc_associate(newStr, conn_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) {
VALUE version, server_info;
GET_CLIENT(self)
GET_CLIENT(self);
#ifdef HAVE_RUBY_ENCODING_H
rb_encoding *default_internal_enc = rb_default_internal_encoding();
rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
#endif
REQUIRE_OPEN_DB(client);
REQUIRE_OPEN_DB(wrapper);
version = rb_hash_new();
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client)));
server_info = rb_str_new2(mysql_get_server_info(client));
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
#ifdef HAVE_RUBY_ENCODING_H
rb_enc_associate(server_info, conn_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) {
GET_CLIENT(self)
REQUIRE_OPEN_DB(client);
return INT2NUM(client->net.fd);
GET_CLIENT(self);
REQUIRE_OPEN_DB(wrapper);
return INT2NUM(wrapper->client->net.fd);
}
static VALUE rb_mysql_client_last_id(VALUE self) {
GET_CLIENT(self)
REQUIRE_OPEN_DB(client);
return ULL2NUM(mysql_insert_id(client));
GET_CLIENT(self);
REQUIRE_OPEN_DB(wrapper);
return ULL2NUM(mysql_insert_id(wrapper->client));
}
static VALUE rb_mysql_client_affected_rows(VALUE self) {
GET_CLIENT(self)
REQUIRE_OPEN_DB(client);
return ULL2NUM(mysql_affected_rows(client));
GET_CLIENT(self);
REQUIRE_OPEN_DB(wrapper);
return ULL2NUM(mysql_affected_rows(wrapper->client));
}
static VALUE set_reconnect(VALUE self, VALUE value) {
my_bool reconnect;
GET_CLIENT(self)
GET_CLIENT(self);
if(!NIL_P(value)) {
reconnect = value == Qfalse ? 0 : 1;
/* 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 */
rb_warn("%s\n", mysql_error(client));
rb_warn("%s\n", mysql_error(wrapper->client));
}
}
return value;
@ -436,16 +441,16 @@ static VALUE set_reconnect(VALUE self, VALUE value) {
static VALUE set_connect_timeout(VALUE self, VALUE value) {
unsigned int connect_timeout = 0;
GET_CLIENT(self)
GET_CLIENT(self);
if(!NIL_P(value)) {
connect_timeout = NUM2INT(value);
if(0 == connect_timeout) return value;
/* 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 */
rb_warn("%s\n", mysql_error(client));
rb_warn("%s\n", mysql_error(wrapper->client));
}
}
return value;
@ -453,7 +458,7 @@ static VALUE set_connect_timeout(VALUE self, VALUE value) {
static VALUE set_charset_name(VALUE self, VALUE value) {
char * charset_name;
GET_CLIENT(self)
GET_CLIENT(self);
#ifdef HAVE_RUBY_ENCODING_H
VALUE new_encoding;
@ -469,19 +474,19 @@ static VALUE set_charset_name(VALUE self, VALUE 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 */
rb_warn("%s\n", mysql_error(client));
rb_warn("%s\n", mysql_error(wrapper->client));
}
return value;
}
static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
GET_CLIENT(self)
GET_CLIENT(self);
if(!NIL_P(ca) || !NIL_P(key)) {
mysql_ssl_set(client,
mysql_ssl_set(wrapper->client,
NIL_P(key) ? NULL : StringValuePtr(key),
NIL_P(cert) ? NULL : StringValuePtr(cert),
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) {
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? */
return rb_raise_mysql2_error(client);
return rb_raise_mysql2_error(wrapper->client);
}
return self;

View File

@ -34,7 +34,8 @@ void init_mysql2_client();
typedef struct {
VALUE encoding;
short int active;
MYSQL client;
short int closed;
MYSQL *client;
} mysql_client_wrapper;
#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,
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,
sym_local, sym_utc, sym_cast_booleans;
sym_local, sym_utc, sym_cast_booleans, sym_cache_rows;
static ID intern_merge;
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;
mysql2_result_wrapper * wrapper;
unsigned long i;
int symbolizeKeys = 0, asArray = 0, castBool = 0;
int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1;
GetMysql2Result(self, wrapper);
@ -339,6 +339,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
castBool = 1;
}
if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
cacheRows = 0;
}
dbTz = rb_hash_aref(opts, sym_database_timezone);
if (dbTz == sym_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);
}
if (wrapper->lastRowProcessed == 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++) {
@ -380,11 +384,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
rowsProcessed = RARRAY_LEN(wrapper->rows);
for (i = 0; i < wrapper->numberOfRows; i++) {
VALUE row;
if (i < rowsProcessed) {
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);
rb_ary_store(wrapper->rows, i, row);
if (cacheRows) {
rb_ary_store(wrapper->rows, i, row);
}
wrapper->lastRowProcessed++;
}
@ -453,11 +459,12 @@ void init_mysql2_result() {
sym_cast_booleans = ID2SYM(rb_intern("cast_booleans"));
sym_database_timezone = ID2SYM(rb_intern("database_timezone"));
sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
rb_global_variable(&opt_decimal_zero); //never GC
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);
rb_global_variable(&opt_float_zero);
opt_time_year = INT2NUM(2000);
opt_time_month = INT2NUM(1);
opt_utc_offset = INT2NUM(0);

View File

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

View File

@ -447,10 +447,11 @@ module ActiveRecord
if current_index != row[:Key_name]
next if row[:Key_name] == PRIMARY # skip the primary key
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
indexes.last.columns << row[:Column_name]
indexes.last.lengths << row[:Sub_part]
end
indexes
end

View File

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

View File

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

View File

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

View File

@ -5,11 +5,11 @@
Gem::Specification.new do |s|
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.authors = ["Brian Lopez"]
s.date = %q{2010-08-20}
s.date = %q{2010-09-17}
s.email = %q{seniorlopez@gmail.com}
s.extensions = ["ext/mysql2/extconf.rb"]
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)
# 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 RUBY_PLATFORM !~ /mingw|mswin/
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.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
@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 if RUBY_PLATFORM !~ /mingw|mswin/
end
end
it "should respond to #escape" do
@ -144,6 +146,18 @@ describe Mysql2::Client do
@client.escape(str).object_id.should eql(str.object_id)
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
@client.should respond_to(:info)
end

View File

@ -13,4 +13,13 @@ describe Mysql2::Error do
it "should respond to #sql_state" do
@error.should respond_to(:sql_state)
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

View File

@ -47,8 +47,13 @@ describe Mysql2::Result do
end
end
it "should cache previously yielded results" do
@result.first.should eql(@result.first)
it "should cache previously yielded results by default" do
@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

View File

@ -1,10 +1,15 @@
gem 'rake-compiler', '~> 0.7.1'
require "rake/extensiontask"
MYSQL_VERSION = "5.1.49"
MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.localhost.net.ar"
MYSQL_VERSION = "5.1.50"
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
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
ext.config_options << "--with-mysql-include=#{mysql_lib}/include"
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
ext.lib_dir = File.join 'lib', 'mysql2'
# clean compiled extension
CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}"
end
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