Compare commits

...

50 Commits

Author SHA1 Message Date
John Bintz b99e0a057a add LD_RUN_PATH when using hard coded mysql paths 2010-12-07 10:24:30 -05:00
Brian Lopez 52f62c8b44 compiler warnings are annoying 2010-11-27 14:56:05 -08:00
Brian Lopez 66f595a406 Merge branch 'em_fiber' of https://github.com/dj2/mysql2 into dj2-em_fiber 2010-11-27 14:46:34 -08:00
dan sinclair 5f6ff0d4a8 forgot to remove debug 2010-11-27 15:32:54 -05:00
Brian Lopez 80697d11b2 we don't need to print the error here as the caller should be handling that on their own the way they want to 2010-11-27 11:10:18 -08:00
dan sinclair dac2f02c9b if no reactor use synch query 2010-11-26 22:42:34 -05:00
dan sinclair 9d49728c30 add em_fiber spec 2010-11-26 22:42:09 -05:00
dj2 c2e2f0c46c add fibered em connection without activerecord 2010-11-26 11:01:20 -05:00
Youhei Kondou 550c68cade Fix to install with MariDB on Windows 2010-11-13 17:47:58 +09:00
Brian Lopez d48846f13b add support for :read_timeout to be set on a connection 2010-11-09 12:27:04 -08:00
Brian Lopez 99766a2303 mysql_num_fields returns an unsigned int 2010-10-31 12:37:53 -07:00
Brian Lopez badc5e04e3 resultFreed flag only needs to be one byte 2010-10-31 12:34:18 -07:00
Brian Lopez 974a5658d6 active and closed flags only need to be one byte 2010-10-31 12:31:54 -07:00
Brian Lopez 60b8d061ac mysql_errno returns an unsigned int 2010-10-31 12:24:27 -07:00
Brian Lopez 2b37ace3dc only set binary ruby encoding on fields that have a binary flag *and* encoding set 2010-10-31 12:11:20 -07:00
Brian Lopez 1057f976d0 ensure the query is a string earlier in the Mysql2::Client#query codepath for 1.9 2010-10-31 10:11:59 -07:00
Brian Lopez 90ddb63e52 use our own index def class for better compatibility across ActiveRecord versions 2010-10-27 15:36:25 -07:00
Brian Lopez 426cff8adc bump to 0.2.6 to push fixed win32 gems 2010-10-19 17:24:55 -07:00
Brian Lopez f79bf6261e Version bump to 0.2.6 2010-10-19 17:23:43 -07:00
Brian Lopez 05df9e312d update files for 0.2.5 release 2010-10-19 16:33:07 -07:00
Brian Lopez 60fd02ebe6 Version bump to 0.2.5 2010-10-19 16:32:42 -07:00
Brian Lopez 7e75f5ed4c make sure we always set wait_timeout even if a bad value was given 2010-10-19 08:21:41 -07:00
Anko painting e7dcf37bd4 fix to CFLAGS to allow compilation on SPARC with sunstudio compiler 2010-10-18 18:33:34 -07:00
Brian Lopez 0b3b63305e only use wait_timeout if it's a Fixnum 2010-10-18 12:15:16 -07:00
Brian Lopez 47405ae1f0 update gemspec from file updates 2010-10-18 06:59:19 -07:00
Brian Lopez a8041fed21 no need to invalidate the FD now that we're only modifying it once, in one place 2010-10-17 18:13:04 -07:00
Brian Lopez e823b9ec0d no need to check if the FD is >= 0 now that we centralized the close/free logic. Once closed, none of that code will run again 2010-10-17 18:12:02 -07:00
Brian Lopez c8020f29ac wording fix in readme 2010-10-17 17:58:00 -07:00
Brian Lopez 3c6959a8bb prevent the GC from freeing a connection that hasn't been allocated yet 2010-10-17 17:35:41 -07:00
Brian Lopez 410e914411 only run the EM specs if EM is installed 2010-10-17 17:34:34 -07:00
Brian Lopez 3b6229771a remove benchmark that tested code that has since been removed 2010-10-17 17:34:21 -07:00
Brian Lopez 0ae583fe64 since we free the connection on close, there's no reason to have both flags 2010-10-17 16:59:50 -07:00
Brian Lopez 22c9ff48a8 some more rspec2 changes, organize rake tasks 2010-10-17 16:51:14 -07:00
Brian Lopez 0a7e7ee475 centralize closing and freeing logic for connections 2010-10-17 16:34:49 -07:00
Brian Lopez ba4f3612b6 move to rspec2 2010-10-17 16:34:09 -07:00
Brian Lopez 841ee2bba4 make sure we invalidate the closed FD in case it's value is reused on a new connection 2010-10-15 22:37:51 -07:00
Brian Lopez 6e5cda6a52 avoid potential race-condition with closing a connection 2010-10-15 12:27:52 -07:00
Brian Lopez 832eb2d247 add option for setting the wait_timeout in the AR adapter (this can be done in database.yml) 2010-10-15 07:45:07 -07:00
Brian Lopez c394122fd9 add some more defaults to the connect flags 2010-10-14 23:38:25 -07:00
Brian Lopez a6b5e9c28c add connect_flags to default options and add REMEMBER_OPTIONS to that list. fix NUM2INT to be NUM2ULONG as it should be for flags 2010-10-14 08:12:52 -07:00
Brian Lopez 7169649857 free the client after close if we can 2010-10-13 15:25:37 -07:00
Brian Lopez 225ddadaf7 forgot to remove this 2010-10-13 15:18:12 -07:00
Brian Lopez 0d1e9916bf get rid of double-pointer casting 2010-10-13 15:17:35 -07:00
Brian Lopez a17ba07c75 a couple of minor updates to connection management with some specs 2010-10-06 10:33:52 -07:00
Brian Lopez f9d30e8f85 check for error from mysql_affected_rows call 2010-10-05 23:36:37 -07:00
Brian Lopez 40f0cd012c change connection check symantecs 2010-10-05 23:36:05 -07:00
Anton Mironov 687487d5a5 Detach before executing callbacks.
This allows to make queries in callbacks.
2010-09-28 13:47:19 +08:00
Brian Lopez 2ae908c512 make sure we don't hit a race condition if this EM spec is taking longer to run than normal 2010-09-27 13:25:10 -07:00
Brian Lopez 4383885634 was in a hurry earlier 2010-09-27 13:24:37 -07:00
Brian Lopez 307b92b966 whoops, lost this line in a previous patch 2010-09-27 11:17:47 -07:00
26 changed files with 343 additions and 162 deletions

2
.rspec Normal file
View File

@ -0,0 +1,2 @@
--format documentation
--colour

View File

@ -1,5 +1,17 @@
# Changelog # Changelog
## 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@gmail.com>
## 0.2.4 (September 17th, 2010) ## 0.2.4 (September 17th, 2010)
* a few patches for win32 support from Luis Lavena - thanks man! * 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 * bugfix from Eric Wong to avoid a potential stack overflow during Mysql2::Client#escape

View File

@ -234,7 +234,7 @@ then iterating over every row using an #each like method yielding a block:
== Special Thanks == Special Thanks
* Eric Wong - for the contribution (and informative explanations of) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude * 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 ActiveRecord adapter
* Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness * 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 ActiveRecord adapter (uses Fibers and EventMachine)

View File

@ -1,42 +1,5 @@
# encoding: UTF-8 # encoding: UTF-8
begin
require 'jeweler'
JEWELER = Jeweler::Tasks.new do |gem|
gem.name = "mysql2"
gem.summary = "A simple, fast Mysql library for Ruby, binding to libmysql"
gem.email = "seniorlopez@gmail.com"
gem.homepage = "http://github.com/brianmario/mysql2"
gem.authors = ["Brian Lopez"]
gem.require_paths = ["lib", "ext"]
gem.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
gem.files = `git ls-files`.split("\n")
gem.extensions = ["ext/mysql2/extconf.rb"]
gem.files.include %w(lib/jeweler/templates/.document lib/jeweler/templates/.gitignore)
# gem.rubyforge_project = "mysql2"
end
rescue LoadError
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler -s http://gems.github.com"
end
require 'rake' require 'rake'
require 'spec/rake/spectask'
desc "Run all examples with RCov"
Spec::Rake::SpecTask.new('spec:rcov') do |t|
t.spec_files = FileList['spec/']
t.rcov = true
t.rcov_opts = lambda do
IO.readlines("spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
end
end
Spec::Rake::SpecTask.new('spec') do |t|
t.spec_files = FileList['spec/']
t.spec_opts << '--options' << 'spec/spec.opts'
t.verbose = true
t.warning = true
end
task :default => :spec
# Load custom tasks # Load custom tasks
Dir['tasks/*.rake'].sort.each { |f| load f } Dir['tasks/*.rake'].sort.each { |f| load f }

View File

@ -1 +1 @@
0.2.4 0.2.6

View File

@ -1,20 +0,0 @@
# encoding: UTF-8
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
require 'rubygems'
require 'benchmark'
require 'mysql2'
iterations = 1000
client = Mysql2::Client.new(:host => "localhost", :username => "root", :database => "test")
query = lambda{ iterations.times{ client.query("SELECT mysql2_test.* FROM mysql2_test") } }
Benchmark.bmbm do |x|
x.report('select') do
query.call
end
x.report('rb_thread_select') do
thread = Thread.new{ sleep(10) }
query.call
thread.kill
end
end

View File

@ -9,7 +9,7 @@ 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(wrapper) \ #define REQUIRE_OPEN_DB(wrapper) \
if(wrapper->closed || !wrapper->client->net.vio) { \ if(wrapper->closed) { \
rb_raise(cMysql2Error, "closed MySQL connection"); \ rb_raise(cMysql2Error, "closed MySQL connection"); \
return Qnil; \ return Qnil; \
} }
@ -76,18 +76,18 @@ static void rb_mysql_client_mark(void * wrapper) {
static VALUE rb_raise_mysql2_error(MYSQL *client) { static VALUE rb_raise_mysql2_error(MYSQL *client) {
VALUE e = rb_exc_new2(cMysql2Error, mysql_error(client)); VALUE e = rb_exc_new2(cMysql2Error, mysql_error(client));
rb_funcall(e, intern_error_number_eql, 1, INT2NUM(mysql_errno(client))); rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(client)));
rb_funcall(e, intern_sql_state_eql, 1, rb_tainted_str_new2(mysql_sqlstate(client))); rb_funcall(e, intern_sql_state_eql, 1, rb_tainted_str_new2(mysql_sqlstate(client)));
rb_exc_raise(e); rb_exc_raise(e);
return Qnil; return Qnil;
} }
static VALUE nogvl_init(void *ptr) { static VALUE nogvl_init(void *ptr) {
MYSQL **client = (MYSQL **)ptr; MYSQL *client;
/* 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((MYSQL *)ptr);
return *client ? Qtrue : Qfalse; return client ? Qtrue : Qfalse;
} }
static VALUE nogvl_connect(void *ptr) { static VALUE nogvl_connect(void *ptr) {
@ -104,45 +104,42 @@ static VALUE nogvl_connect(void *ptr) {
return client ? Qtrue : Qfalse; return client ? Qtrue : Qfalse;
} }
static void rb_mysql_client_free(void * ptr) { static VALUE nogvl_close(void *ptr) {
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; mysql_client_wrapper *wrapper = ptr;
if (!wrapper->closed) {
wrapper->closed = 1;
/* /*
* 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 = wrapper->client->net.fd; *
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
*/ */
#ifndef _WIN32 #ifndef _WIN32
int flags = fcntl(fd, F_GETFL); int flags = fcntl(wrapper->client->net.fd, F_GETFL);
if (flags > 0 && !(flags & O_NONBLOCK)) if (flags > 0 && !(flags & O_NONBLOCK))
fcntl(fd, F_SETFL, flags | O_NONBLOCK); fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
#else #else
u_long iMode = 1; u_long iMode = 1;
ioctlsocket(fd, FIONBIO, &iMode); ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
#endif #endif
mysql_close(wrapper->client);
free(wrapper->client);
} }
/* It's safe to call mysql_close() on an already closed connection. */ return Qnil;
if (!wrapper->closed) {
mysql_close(wrapper->client);
}
xfree(ptr);
} }
static VALUE nogvl_close(void * ptr) { static void rb_mysql_client_free(void * ptr) {
mysql_client_wrapper *wrapper = ptr; mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
if (!wrapper->closed) {
mysql_close(wrapper->client); nogvl_close(wrapper);
wrapper->closed = 1;
} xfree(ptr);
return Qnil;
} }
static VALUE allocate(VALUE klass) { static VALUE allocate(VALUE klass) {
@ -151,7 +148,8 @@ 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; wrapper->closed = 1;
wrapper->client = (MYSQL*)malloc(sizeof(MYSQL));
return obj; return obj;
} }
@ -166,7 +164,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
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 = wrapper->client; args.mysql = wrapper->client;
args.client_flag = NUM2INT(flags); args.client_flag = NUM2ULONG(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
@ -185,7 +183,9 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
static VALUE rb_mysql_client_close(VALUE self) { static VALUE rb_mysql_client_close(VALUE self) {
GET_CLIENT(self); GET_CLIENT(self);
if (!wrapper->closed) {
rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0); rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
}
return Qnil; return Qnil;
} }
@ -264,7 +264,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
fd_set fdset; fd_set fdset;
int fd, retval; int fd, retval;
int async = 0; int async = 0;
VALUE opts, defaults; VALUE opts, defaults, read_timeout;
GET_CLIENT(self); GET_CLIENT(self);
REQUIRE_OPEN_DB(wrapper); REQUIRE_OPEN_DB(wrapper);
@ -290,6 +290,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
opts = defaults; opts = defaults;
} }
Check_Type(args.sql, T_STRING);
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); rb_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
@ -302,6 +303,23 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
return rb_raise_mysql2_error(wrapper->client); return rb_raise_mysql2_error(wrapper->client);
} }
read_timeout = rb_iv_get(self, "@read_timeout");
struct timeval tv;
struct timeval* tvp = NULL;
if (!NIL_P(read_timeout)) {
Check_Type(read_timeout, T_FIXNUM);
tvp = &tv;
long int 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;
}
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
@ -310,7 +328,11 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
FD_ZERO(&fdset); FD_ZERO(&fdset);
FD_SET(fd, &fdset); FD_SET(fd, &fdset);
retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL); retval = rb_thread_select(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) { if (retval < 0) {
rb_sys_fail(0); rb_sys_fail(0);
@ -334,6 +356,7 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
unsigned long newLen, oldLen; unsigned long newLen, oldLen;
GET_CLIENT(self); GET_CLIENT(self);
REQUIRE_OPEN_DB(wrapper);
Check_Type(str, T_STRING); Check_Type(str, T_STRING);
#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();
@ -345,7 +368,6 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
oldLen = RSTRING_LEN(str); oldLen = RSTRING_LEN(str);
newStr = rb_str_new(0, oldLen*2+1); newStr = rb_str_new(0, oldLen*2+1);
REQUIRE_OPEN_DB(wrapper);
newLen = mysql_real_escape_string(wrapper->client, RSTRING_PTR(newStr), 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
@ -365,6 +387,7 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
static VALUE rb_mysql_client_info(VALUE self) { static VALUE rb_mysql_client_info(VALUE self) {
VALUE version = rb_hash_new(), client_info; VALUE version = rb_hash_new(), client_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);
@ -385,13 +408,13 @@ 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);
REQUIRE_OPEN_DB(wrapper);
#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(wrapper);
version = rb_hash_new(); version = rb_hash_new();
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->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)); server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
@ -419,8 +442,14 @@ static VALUE rb_mysql_client_last_id(VALUE self) {
static VALUE rb_mysql_client_affected_rows(VALUE self) { static VALUE rb_mysql_client_affected_rows(VALUE self) {
GET_CLIENT(self); GET_CLIENT(self);
my_ulonglong retVal;
REQUIRE_OPEN_DB(wrapper); REQUIRE_OPEN_DB(wrapper);
return ULL2NUM(mysql_affected_rows(wrapper->client)); retVal = mysql_affected_rows(wrapper->client);
if (retVal == (my_ulonglong)-1) {
rb_raise_mysql2_error(wrapper->client);
}
return ULL2NUM(retVal);
} }
static VALUE set_reconnect(VALUE self, VALUE value) { static VALUE set_reconnect(VALUE self, VALUE value) {
@ -500,11 +529,12 @@ 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, ((void *) &wrapper->client), RUBY_UBF_IO, 0) == Qfalse) { if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
/* TODO: warning - not enough memory? */ /* TODO: warning - not enough memory? */
return rb_raise_mysql2_error(wrapper->client); return rb_raise_mysql2_error(wrapper->client);
} }
wrapper->closed = 0;
return self; return self;
} }

View File

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

View File

@ -57,9 +57,13 @@ end
asplode h unless have_header h asplode h unless have_header h
end end
unless RUBY_PLATFORM =~ /mswin/ unless RUBY_PLATFORM =~ /mswin/ or RUBY_PLATFORM =~ /sparc/
$CFLAGS << ' -Wall -funroll-loops' $CFLAGS << ' -Wall -funroll-loops'
end end
# $CFLAGS << ' -O0 -ggdb3 -Wextra' # $CFLAGS << ' -O0 -ggdb3 -Wextra'
if hard_mysql_path = $libs[%r{-L(/[^ ]+)}, 1]
$LDFLAGS << " -Wl,-rpath,#{hard_mysql_path}"
end
create_makefile('mysql2/mysql2') create_makefile('mysql2/mysql2')

View File

@ -4,6 +4,12 @@
#include <ruby.h> #include <ruby.h>
#include <fcntl.h> #include <fcntl.h>
#ifndef HAVE_UINT
#define HAVE_UINT
typedef unsigned short ushort;
typedef unsigned int uint;
#endif
#ifdef HAVE_MYSQL_H #ifdef HAVE_MYSQL_H
#include <mysql.h> #include <mysql.h>
#include <mysql_com.h> #include <mysql_com.h>

View File

@ -248,7 +248,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
val = rb_str_new(row[i], fieldLengths[i]); val = rb_str_new(row[i], fieldLengths[i]);
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
// if binary flag is set, respect it's wishes // if binary flag is set, respect it's wishes
if (fields[i].flags & BINARY_FLAG) { if (fields[i].flags & BINARY_FLAG && fields[i].charsetnr == 63) {
rb_enc_associate(val, binaryEncoding); rb_enc_associate(val, binaryEncoding);
} else { } else {
// lookup the encoding configured on this field // lookup the encoding configured on this field

View File

@ -8,10 +8,10 @@ typedef struct {
VALUE fields; VALUE fields;
VALUE rows; VALUE rows;
VALUE encoding; VALUE encoding;
long numberOfFields; unsigned int numberOfFields;
unsigned long numberOfRows; unsigned long numberOfRows;
unsigned long lastRowProcessed; unsigned long lastRowProcessed;
short int resultFreed; char resultFreed;
MYSQL_RES *result; MYSQL_RES *result;
} mysql2_result_wrapper; } mysql2_result_wrapper;

View File

@ -35,7 +35,6 @@ module Mysql2
results = @client.async_result results = @client.async_result
@deferable.succeed(results) @deferable.succeed(results)
rescue Exception => e rescue Exception => e
puts e.backtrace.join("\n\t")
@deferable.fail(e) @deferable.fail(e)
end end
end end

View File

@ -18,6 +18,9 @@ module ActiveRecord
end end
module ConnectionAdapters module ConnectionAdapters
class Mysql2IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
end
class Mysql2Column < Column class Mysql2Column < Column
BOOL = "tinyint(1)" BOOL = "tinyint(1)"
def extract_default(default) def extract_default(default)
@ -447,7 +450,7 @@ 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 << Mysql2IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
end end
indexes.last.columns << row[:Column_name] indexes.last.columns << row[:Column_name]
@ -617,8 +620,15 @@ module ActiveRecord
# Turn this off. http://dev.rubyonrails.org/ticket/6778 # Turn this off. http://dev.rubyonrails.org/ticket/6778
variable_assignments = ['SQL_AUTO_IS_NULL=0'] variable_assignments = ['SQL_AUTO_IS_NULL=0']
encoding = @config[:encoding] encoding = @config[:encoding]
# make sure we set the encoding
variable_assignments << "NAMES '#{encoding}'" if encoding variable_assignments << "NAMES '#{encoding}'" if encoding
# increase timeout so mysql server doesn't disconnect us
wait_timeout = @config[:wait_timeout]
wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
variable_assignments << "@@wait_timeout = #{wait_timeout}"
execute("SET #{variable_assignments.join(', ')}", :skip_logging) execute("SET #{variable_assignments.join(', ')}", :skip_logging)
end end

View File

@ -12,5 +12,5 @@ require 'mysql2/result'
# #
# 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.4" VERSION = "0.2.6"
end end

View File

@ -8,7 +8,8 @@ module Mysql2
:symbolize_keys => false, # return field names as symbols instead of strings :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 :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
} }
def initialize(opts = {}) def initialize(opts = {})
@ -23,6 +24,11 @@ module Mysql2
# force the encoding to utf8 # force the encoding to utf8
self.charset_name = opts[:encoding] || '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_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslciper)) ssl_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslciper))
user = opts[:username] user = opts[:username]
@ -31,7 +37,7 @@ module Mysql2
port = opts[:port] || 3306 port = opts[:port] || 3306
database = opts[:database] database = opts[:database]
socket = opts[:socket] socket = opts[:socket]
flags = opts[:flags] || 0 flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags]
connect user, pass, host, port, database, socket, flags connect user, pass, host, port, database, socket, flags
end end

View File

@ -13,20 +13,24 @@ module Mysql2
end end
def notify_readable def notify_readable
detach
begin begin
@deferable.succeed(@client.async_result) @deferable.succeed(@client.async_result)
rescue Exception => e rescue Exception => e
@deferable.fail(e) @deferable.fail(e)
end end
detach
end end
end end
def query(sql, opts={}) def query(sql, opts={})
if ::EM.reactor_running?
super(sql, opts.merge(:async => true)) super(sql, opts.merge(:async => true))
deferable = ::EM::DefaultDeferrable.new deferable = ::EM::DefaultDeferrable.new
::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true
deferable deferable
else
super(sql, opts)
end
end end
end end
end end

29
lib/mysql2/em_fiber.rb Normal file
View File

@ -0,0 +1,29 @@
# encoding: utf-8
require 'mysql2/em'
require 'fiber'
module Mysql2
module EM
module Fiber
class Client < ::Mysql2::EM::Client
def query(sql, opts={})
if ::EM.reactor_running?
deferable = super(sql, opts)
fiber = ::Fiber.current
deferable.callback do |result|
fiber.resume(result)
end
deferable.errback do |err|
fiber.resume(err)
end
::Fiber.yield
else
super(sql, opts)
end
end
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.4" s.version = "0.2.6"
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-09-17} s.date = %q{2010-10-19}
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 = [
@ -17,6 +17,7 @@ Gem::Specification.new do |s|
] ]
s.files = [ s.files = [
".gitignore", ".gitignore",
".rspec",
"CHANGELOG.md", "CHANGELOG.md",
"MIT-LICENSE", "MIT-LICENSE",
"README.rdoc", "README.rdoc",
@ -29,7 +30,6 @@ Gem::Specification.new do |s|
"benchmark/query_without_mysql_casting.rb", "benchmark/query_without_mysql_casting.rb",
"benchmark/sequel.rb", "benchmark/sequel.rb",
"benchmark/setup_db.rb", "benchmark/setup_db.rb",
"benchmark/thread_alone.rb",
"examples/eventmachine.rb", "examples/eventmachine.rb",
"examples/threaded.rb", "examples/threaded.rb",
"ext/mysql2/client.c", "ext/mysql2/client.c",
@ -46,6 +46,7 @@ Gem::Specification.new do |s|
"lib/mysql2.rb", "lib/mysql2.rb",
"lib/mysql2/client.rb", "lib/mysql2/client.rb",
"lib/mysql2/em.rb", "lib/mysql2/em.rb",
"lib/mysql2/em_fiber.rb",
"lib/mysql2/error.rb", "lib/mysql2/error.rb",
"lib/mysql2/result.rb", "lib/mysql2/result.rb",
"mysql2.gemspec", "mysql2.gemspec",
@ -54,10 +55,11 @@ Gem::Specification.new do |s|
"spec/mysql2/error_spec.rb", "spec/mysql2/error_spec.rb",
"spec/mysql2/result_spec.rb", "spec/mysql2/result_spec.rb",
"spec/rcov.opts", "spec/rcov.opts",
"spec/spec.opts",
"spec/spec_helper.rb", "spec/spec_helper.rb",
"tasks/benchmarks.rake", "tasks/benchmarks.rake",
"tasks/compile.rake", "tasks/compile.rake",
"tasks/jeweler.rake",
"tasks/rspec.rake",
"tasks/vendor_mysql.rake" "tasks/vendor_mysql.rake"
] ]
s.homepage = %q{http://github.com/brianmario/mysql2} s.homepage = %q{http://github.com/brianmario/mysql2}

22
spec/em/em_fiber_spec.rb Normal file
View File

@ -0,0 +1,22 @@
# encoding: UTF-8
if defined? EventMachine && defined? Fiber
require 'spec_helper'
require 'mysql2/em_fiber'
describe Mysql2::EM::Fiber::Client do
it 'should support queries' do
results = []
EM.run do
Fiber.new {
client1 = Mysql2::EM::Fiber::Client.new
results = client1.query "SELECT sleep(0.1) as first_query"
EM.stop_event_loop
}.resume
end
results.first.keys.should include("first_query")
end
end
else
puts "Either EventMachine or Fibers not available. Skipping tests that use them."
end

View File

@ -1,13 +1,14 @@
# encoding: UTF-8 # encoding: UTF-8
require 'spec_helper' if defined? EventMachine
require 'mysql2/em' require 'spec_helper'
require 'mysql2/em'
describe Mysql2::EM::Client do describe Mysql2::EM::Client do
it "should support async queries" do it "should support async queries" do
results = [] results = []
EM.run do EM.run do
client1 = Mysql2::EM::Client.new client1 = Mysql2::EM::Client.new
defer1 = client1.query "SELECT sleep(0.05) as first_query" defer1 = client1.query "SELECT sleep(0.1) as first_query"
defer1.callback do |result| defer1.callback do |result|
results << result.first results << result.first
EM.stop_event_loop EM.stop_event_loop
@ -23,4 +24,26 @@ describe Mysql2::EM::Client do
results[0].keys.should include("second_query") results[0].keys.should include("second_query")
results[1].keys.should include("first_query") results[1].keys.should include("first_query")
end end
it "should support queries in callbacks" do
results = []
EM.run do
client = Mysql2::EM::Client.new
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
EM.stop_event_loop
end
end
end
results[0].keys.should include("first_query")
results[1].keys.should include("second_query")
end
end
else
puts "EventMachine not installed, skipping the specs that use it"
end end

View File

@ -23,10 +23,10 @@ describe Mysql2::Client do
end end
end end
client = klient.new :flags => Mysql2::Client::FOUND_ROWS client = klient.new :flags => Mysql2::Client::FOUND_ROWS
client.connect_args.last.last.should == Mysql2::Client::FOUND_ROWS (client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true
end end
it "should default flags to 0" do it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
klient = Class.new(Mysql2::Client) do klient = Class.new(Mysql2::Client) do
attr_reader :connect_args attr_reader :connect_args
def connect *args def connect *args
@ -35,7 +35,12 @@ describe Mysql2::Client do
end end
end end
client = klient.new client = klient.new
client.connect_args.last.last.should == 0 (client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS |
Mysql2::Client::LONG_PASSWORD |
Mysql2::Client::LONG_FLAG |
Mysql2::Client::TRANSACTIONS |
Mysql2::Client::PROTOCOL_41 |
Mysql2::Client::SECURE_CONNECTION)).should be_true
end end
it "should have a global default_query_options hash" do it "should have a global default_query_options hash" do
@ -71,13 +76,28 @@ describe Mysql2::Client do
it "should be able to close properly" do it "should be able to close properly" do
@client.close.should be_nil @client.close.should be_nil
lambda {
@client.query "SELECT 1"
}.should raise_error(Mysql2::Error)
end end
it "should respond to #query" do it "should respond to #query" do
@client.should respond_to(:query) @client.should respond_to(:query)
end 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
context "#query" do context "#query" do
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 it "should accept an options hash that inherits from Mysql2::Client.default_query_options" do
@client.query "SELECT 1", :something => :else @client.query "SELECT 1", :something => :else
@client.query_options.should eql(@client.query_options.merge(:something => :else)) @client.query_options.should eql(@client.query_options.merge(:something => :else))
@ -103,6 +123,20 @@ describe Mysql2::Client do
}.should raise_error(Mysql2::Error) }.should raise_error(Mysql2::Error)
end end
it "should require an open connection" do
@client.close
lambda {
@client.query "SELECT 1"
}.should raise_error(Mysql2::Error)
end
it "should timeout if we wait longer than :read_timeout" do
client = Mysql2::Client.new(: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) # 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
if RUBY_PLATFORM !~ /mingw|mswin/ if RUBY_PLATFORM !~ /mingw|mswin/
@ -137,27 +171,36 @@ describe Mysql2::Client do
@client.should respond_to(:escape) @client.should respond_to(:escape)
end end
it "#escape should return a new SQL-escape version of the passed string" do context "#escape" do
it "should return a new SQL-escape version of the passed string" do
@client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno") @client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
end end
it "#escape should return the passed string if nothing was escaped" do it "should return the passed string if nothing was escaped" do
str = "plain" str = "plain"
@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 it "should not overflow the thread stack" do
lambda { lambda {
Thread.new { @client.escape("'" * 256 * 1024) }.join Thread.new { @client.escape("'" * 256 * 1024) }.join
}.should_not raise_error(SystemStackError) }.should_not raise_error(SystemStackError)
end end
it "#escape should not overflow the process stack" do it "should not overflow the process stack" do
lambda { lambda {
Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
}.should_not raise_error(SystemStackError) }.should_not raise_error(SystemStackError)
end end
it "should require an open connection" do
@client.close
lambda {
@client.escape ""
}.should raise_error(Mysql2::Error)
end
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
@ -203,6 +246,13 @@ describe Mysql2::Client do
server_info[:version].class.should eql(String) server_info[:version].class.should eql(String)
end end
it "#server_info should require an open connection" do
@client.close
lambda {
@client.server_info
}.should raise_error(Mysql2::Error)
end
if defined? Encoding if defined? Encoding
context "strings returned by #server_info" do context "strings returned by #server_info" do
it "should default to the connection's encoding if Encoding.default_internal is nil" do it "should default to the connection's encoding if Encoding.default_internal is nil" do
@ -231,6 +281,13 @@ describe Mysql2::Client do
@client.socket.should_not eql(0) @client.socket.should_not eql(0)
end end
it "#socket should require an open connection" do
@client.close
lambda {
@client.socket
}.should raise_error(Mysql2::Error)
end
it "should raise a Mysql2::Error exception upon connection failure" do it "should raise a Mysql2::Error exception upon connection failure" do
lambda { lambda {
bad_client = Mysql2::Client.new :host => "dfjhdi9wrhw", :username => 'asdfasdf8d2h' bad_client = Mysql2::Client.new :host => "dfjhdi9wrhw", :username => 'asdfasdf8d2h'

View File

@ -1,2 +0,0 @@
--format specdoc
--colour

View File

@ -1,10 +1,11 @@
# encoding: UTF-8 # encoding: UTF-8
require 'rubygems' $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require 'rspec'
require 'mysql2' require 'mysql2'
require 'timeout' require 'timeout'
Spec::Runner.configure do |config| RSpec.configure do |config|
config.before(:all) do config.before(:all) do
client = Mysql2::Client.new :database => 'test' client = Mysql2::Client.new :database => 'test'
client.query %[ client.query %[

17
tasks/jeweler.rake Normal file
View File

@ -0,0 +1,17 @@
begin
require 'jeweler'
JEWELER = Jeweler::Tasks.new do |gem|
gem.name = "mysql2"
gem.summary = "A simple, fast Mysql library for Ruby, binding to libmysql"
gem.email = "seniorlopez@gmail.com"
gem.homepage = "http://github.com/brianmario/mysql2"
gem.authors = ["Brian Lopez"]
gem.require_paths = ["lib", "ext"]
gem.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
gem.files = `git ls-files`.split("\n")
gem.extensions = ["ext/mysql2/extconf.rb"]
gem.files.include %w(lib/jeweler/templates/.document lib/jeweler/templates/.gitignore)
end
rescue LoadError
puts "jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
end

16
tasks/rspec.rake Normal file
View File

@ -0,0 +1,16 @@
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