Compare commits

..

No commits in common. "master" and "19_full_thread_no_gil" have entirely different histories.

21 changed files with 718 additions and 1333 deletions

17
README
View File

@ -2,8 +2,6 @@
An enhanced MySQL database driver. With support for async operations and threaded database access.
Added Encoding awareness for Ruby 1.9. Convert all data to default external encoding ( merged from http://github.com/lsegal/mysql-ruby )
== Building
gem build mysqlplus.gemspec
@ -22,18 +20,6 @@ Added Encoding awareness for Ruby 1.9. Convert all data to default external enc
--with-mysql-dir=/usr/local/mysql \
--with-mysql-lib=/usr/local/mysql/lib \
--with-mysql-include=/usr/local/mysql/include
== Using
to use within rails
add require 'mysqlplus' to the top of environment.rb
this instantiates the Mysql class that you can use.
and it will automagically use async_query instead of query, so it's a drop in replacement.
Same with other scripts that want to use it--just require 'mysqlplus' BEFORE you require 'mysql' and it will
load the asynchronous version, then ignore the sequent require 'mysql' call.
== Other helpful mysql utilities:
slim attributes http://slim-attributes.rubyforge.org/ boosts mysql speed by using arrays instead of hashed lookup.
Hash extension gem also results in speedups when used: http://blog.chak.org/2008/02/09/speeding-up-activerecord-with-hashes-take-2/
=== Credits
@ -44,6 +30,3 @@ Lourens Naude for 1.9 integration help.
=== License
Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
== Mailing list
http://groups.google.com/group/never-block?hl=en

View File

@ -1,12 +0,0 @@
TODO list:
Is there a quick, cheap, easy way to test for writability so we don't have to use select itself? Does select take any time that it's worth looking into this?
Some of the tests currently might "think" they are using the ruby select but in reality be using the C select.
gc_disabled is unused
if they call get_result twice consecutively it should blow (and maybe already does).
mingw support
add slim attributes right in there :)

File diff suppressed because it is too large Load Diff

View File

@ -1,76 +1,24 @@
require 'mkmf'
def exec_command(command, flag_raise=false)
output = `#{command}`
return output.chomp if $? == 0
msg = "failed: #{command}"
raise msg if flag_raise
die msg
end
def die(message)
$stderr.puts "*** ERROR: #{message}"
exit 1
end
if /mswin32/ =~ RUBY_PLATFORM
inc, lib = dir_config('mysql')
#exit 1 unless have_library("libmysql")
have_library("libmysql") or die "can't find libmysql."
exit 1 unless have_library("libmysql")
elsif mc = with_config('mysql-config') then
mc = 'mysql_config' if mc == true
#cflags = `#{mc} --cflags`.chomp
#exit 1 if $? != 0
cflags = exec_command("#{mc} --cflags")
#libs = `#{mc} --libs`.chomp
#exit 1 if $? != 0
libs = exec_command("#{mc} --libs")
$CPPFLAGS += ' ' + cflags
$libs = libs + " " + $libs
else
puts "Trying to detect MySQL configuration with mysql_config command..."
begin
cflags = libs = nil
dirs = ENV['PATH'].split(':') + %w[
/opt
/opt/local
/opt/local/mysql
/opt/local/lib/mysql5
/usr
/usr/local
/usr/local/mysql
/usr/local/mysql-*
/usr/local/lib/mysql5
].map{|dir| "#{dir}/bin" }
GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}"
if /mswin32/ =~ RUBY_PLATFORM
inc, lib = dir_config('mysql')
exit 1 unless have_library("libmysql")
elsif mc = (with_config('mysql-config') || Dir[GLOB].first) then
mc = Dir[GLOB].first if mc == true
puts "Succeeded to detect MySQL configuration: #{mc}"
cflags = `#{mc} --cflags`.chomp
exit 1 if $? != 0
libs = `#{mc} --libs`.chomp
exit 1 if $? != 0
$CPPFLAGS += ' ' + cflags
$libs = libs + " " + $libs
else
puts "Failed to detect MySQL configuration with mysql_config command."
puts "Trying to detect MySQL client library..."
else
inc, lib = dir_config('mysql', '/usr/local')
libs = ['m', 'z', 'socket', 'nsl', 'mygcc']
while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do
#exit 1 if libs.empty?
!libs.empty? or die "can't find mysql client library."
exit 1 if libs.empty?
have_library(libs.shift)
end
end
end
end
have_func('mysql_ssl_set')
@ -81,14 +29,12 @@ if have_header('mysql.h') then
elsif have_header('mysql/mysql.h') then
src = "#include <mysql/errmsg.h>\n#include <mysql/mysqld_error.h>\n"
else
#exit 1
die "can't find 'mysql.h'."
exit 1
end
# check for 1.9
if have_func('rb_thread_blocking_region') and have_macro('RUBY_UBF_IO', 'ruby.h')
$CFLAGS += " -DHAVE_TBR "
$CPPFLAGS << " -DHAVE_TBR "
end
# make mysql constant
@ -103,10 +49,9 @@ end
if /mswin32/ =~ RUBY_PLATFORM && !/-E/.match(cpp)
cpp << " -E"
end
#unless system "#{cpp} > confout" then
# exit 1
#end
exec_command("#{cpp} > confout")
unless system "#{cpp} > confout" then
exit 1
end
File.unlink "conftest.c"
error_syms = []
@ -128,10 +73,4 @@ File.open('error_const.h', 'w') do |f|
end
end
$CPPFLAGS += " -DRUBY19" if RUBY_VERSION =~ /1.9/
if hard_mysql_path = $libs[%r{-L(/[^ ]+)}, 1]
$LDFLAGS << " -Wl,-rpath,#{hard_mysql_path}"
end
create_makefile("mysql")

View File

@ -6,49 +6,16 @@
#include <ruby.h>
#include <errno.h>
#include <stdarg.h>
#ifndef RSTRING_PTR
#define RSTRING_PTR(str) RSTRING(str)->ptr
#endif
#ifndef RSTRING_LEN
#define RSTRING_LEN(str) RSTRING(str)->len
#endif
#ifndef RARRAY_PTR
#define RARRAY_PTR(ary) RARRAY(ary)->ptr
#endif
#ifndef HAVE_RB_STR_SET_LEN
#define rb_str_set_len(str, length) (RSTRING_LEN(str) = (length))
#endif
#ifdef RUBY19
#include <ruby/encoding.h>
#define DEFAULT_ENCODING (rb_enc_get(rb_enc_default_external()))
#else
#define DEFAULT_ENCODING NULL
#define rb_enc_str_new(ptr, len, enc) rb_str_new(ptr, len)
#endif
VALUE
rb_enc_tainted_str_new(const char *ptr, long len)
{
VALUE str = rb_enc_str_new(ptr, len, DEFAULT_ENCODING);
OBJ_TAINT(str);
return str;
}
VALUE
rb_enc_tainted_str_new2(const char *ptr)
{
VALUE str = rb_enc_str_new(ptr, strlen(ptr), DEFAULT_ENCODING);
OBJ_TAINT(str);
return str;
}
#ifdef HAVE_MYSQL_H
#include <mysql.h>
#include <mysql_com.h>
@ -93,13 +60,9 @@ struct mysql {
MYSQL handler;
char connection;
char query_with_result;
char gc_disabled;
char blocking;
int async_in_progress;
char busy;
};
// a wrapper for mysql_res's so we can detect double frees
struct mysql_res {
MYSQL_RES* res;
char freed;
@ -212,12 +175,12 @@ static void mysql_raise(MYSQL* m)
VALUE e = rb_exc_new2(eMysql, mysql_error(m));
rb_iv_set(e, "errno", INT2FIX(mysql_errno(m)));
#if MYSQL_VERSION_ID >= 40101
rb_iv_set(e, "sqlstate", rb_enc_tainted_str_new2(mysql_sqlstate(m)));
rb_iv_set(e, "sqlstate", rb_tainted_str_new2(mysql_sqlstate(m)));
#endif
rb_exc_raise(e);
}
static VALUE mysqlres2obj(MYSQL_RES* res, VALUE gc_disabled)
static VALUE mysqlres2obj(MYSQL_RES* res)
{
VALUE obj;
struct mysql_res* resp;
@ -227,10 +190,8 @@ static VALUE mysqlres2obj(MYSQL_RES* res, VALUE gc_disabled)
resp->res = res;
resp->freed = Qfalse;
rb_obj_call_init(obj, 0, NULL);
/* disabled until it can be reviewed further--rely on the normal GC for now.
if (++store_result_count > GC_STORE_RESULT_LIMIT)
rb_gc();
*/
return obj;
}
@ -241,9 +202,9 @@ static VALUE make_field_obj(MYSQL_FIELD* f)
if (f == NULL)
return Qnil;
obj = rb_obj_alloc(cMysqlField);
rb_iv_set(obj, "name", f->name? rb_str_freeze(rb_enc_tainted_str_new2(f->name)): Qnil);
rb_iv_set(obj, "table", f->table? rb_str_freeze(rb_enc_tainted_str_new2(f->table)): Qnil);
rb_iv_set(obj, "def", f->def? rb_str_freeze(rb_enc_tainted_str_new2(f->def)): Qnil);
rb_iv_set(obj, "name", f->name? rb_str_freeze(rb_tainted_str_new2(f->name)): Qnil);
rb_iv_set(obj, "table", f->table? rb_str_freeze(rb_tainted_str_new2(f->table)): Qnil);
rb_iv_set(obj, "def", f->def? rb_str_freeze(rb_tainted_str_new2(f->def)): Qnil);
rb_iv_set(obj, "type", INT2NUM(f->type));
rb_iv_set(obj, "length", INT2NUM(f->length));
rb_iv_set(obj, "max_length", INT2NUM(f->max_length));
@ -266,135 +227,10 @@ static VALUE init(VALUE klass)
mysql_init(&myp->handler);
myp->connection = Qfalse;
myp->query_with_result = Qtrue;
myp->gc_disabled = Qtrue;
rb_obj_call_init(obj, 0, NULL);
return obj;
}
// =========== a 1.9 rb_thread_blocking_region simplifier attempt
#ifdef HAVE_TBR
typedef struct
{
void *func_pointer;
int param_count;
void *args[10];
} arg_holder, *arg_holder2;
// here's how to make rb_thread_blocking_region much cleaner and easier
// syntax: param_count+2, func_pointer to call, [RUBY_UBF_IO or RUBY_UBF_PROCESS], param1, param2...
// the third parameter is the interuptor--possible values appear to be RUBY_UBF_IO or RUBY_UBF_PROCESS http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/ad8c1326b2a8e404/00447b9aa15979be?lnk=raot
// ex: (int) returned_this = rb_thread_blocking_region_variable_params(10, &method_name, RUBY_UBF_IO, param1, param2, param3, param4, param5, param6, param7, param8)
static void *call_single_function_rb_thread_blocking_region(void *arg_holder_in);
void *rb_thread_blocking_region_variable_params(int number, ...)
{
va_list param_pt;
va_start(param_pt, number);
int index;
arg_holder param_storer;
void *func_pointer = va_arg(param_pt, void *);
void *interrupter = va_arg(param_pt, void *);
param_storer.func_pointer = func_pointer;
int real_param_count = number - 2;
param_storer.param_count = real_param_count;
for(index = 0 ; index < real_param_count ; index++)
{
void *arg = va_arg(param_pt, void *);
param_storer.args[index] = arg;
}
va_end(param_pt);
return (void *) rb_thread_blocking_region((rb_blocking_function_t *)call_single_function_rb_thread_blocking_region, (void *) &param_storer, interrupter, 0);
}
// used internally
static void * call_single_function_rb_thread_blocking_region(void *arg_holder_in)
{
arg_holder *params_and_func = (arg_holder *) arg_holder_in;
int param_count = params_and_func->param_count;
void *result;
switch(param_count)
{
case 3:;
void * (*pt3Func)(void *, void *, void *) = params_and_func->func_pointer;
result = (*pt3Func)(params_and_func->args[0], params_and_func->args[1], params_and_func->args[2]);
break;
case 6:;
void * (*pt6Func)(void *, void *, void *, void *, void *, void *) = params_and_func->func_pointer;
result = (*pt6Func)(params_and_func->args[0], params_and_func->args[1], params_and_func->args[2], params_and_func->args[3], params_and_func->args[4], params_and_func->args[5]);
break;
case 8:;
void * (*pt8Func)(void *, void *, void *, void *, void *, void *, void *, void *) = params_and_func->func_pointer;
result = (*pt8Func)(params_and_func->args[0], params_and_func->args[1], params_and_func->args[2], params_and_func->args[3], params_and_func->args[4], params_and_func->args[5], params_and_func->args[6], params_and_func->args[7]);
break;
default:;
printf("UNknown param count--please add it! %d\n", param_count);
result = (void *) Qnil;
}
return result;
}
#endif
static VALUE connection_identifier( VALUE obj )
{
MYSQL* m = GetHandler(obj);
return mysql_thread_id( m );
}
static VALUE async_in_progress( VALUE obj )
{
struct mysql* m = GetMysqlStruct(obj);
return ( m->async_in_progress == connection_identifier(obj) ) ? Qtrue : Qfalse;
}
static VALUE async_in_progress_set( VALUE obj, VALUE flag )
{
struct mysql* m = GetMysqlStruct(obj);
m->async_in_progress = (flag == Qnil || flag == Qfalse) ? 0 : connection_identifier(obj);
return flag;
}
// does this actually really do anything helpful? Not sure.
static void optimize_for_async( VALUE obj )
{
struct mysql* m = GetMysqlStruct(obj);
my_bool was_blocking;
vio_blocking(m->handler.net.vio, 0, &was_blocking);
m->blocking = vio_is_blocking( m->handler.net.vio );
vio_fastsend( m->handler.net.vio );
async_in_progress_set( obj, Qfalse );
}
// TODO does nothing currently
static void schedule_connect(VALUE obj )
{
/* TODO is this old?
MYSQL* m = GetHandler(obj);
fd_set read;
struct timeval tv = { tv_sec: m->options.connect_timeout, tv_usec: 0 };
if (rb_thread_select(0, NULL, NULL, NULL, &tv) < 0) {
rb_raise(eMysql, "connect: timeout");
}
*/
/*
FD_ZERO(&read);
FD_SET(m->net.fd, &read);
if (rb_thread_select(m->net.fd + 1, &read, NULL, NULL, &tv) < 0) {
rb_raise(eMysql, "connect: timeout");
}
*/
}
/* real_connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, sock=nil, flag=nil) */
static VALUE real_connect(int argc, VALUE* argv, VALUE klass)
{
@ -422,12 +258,8 @@ static VALUE real_connect(int argc, VALUE* argv, VALUE klass)
obj = Data_Make_Struct(klass, struct mysql, 0, free_mysql, myp);
#if MYSQL_VERSION_ID >= 32200
mysql_init(&myp->handler); /* we get here */
# ifdef HAVE_TBR
if( (MYSQL *) rb_thread_blocking_region_variable_params(10, &mysql_real_connect, RUBY_UBF_IO, &myp->handler, h, u, p, d, pp, s, f) == NULL)
# else
if(mysql_real_connect(&myp->handler, h, u, p, d, pp, s, f) == NULL)
# endif
mysql_init(&myp->handler);
if (mysql_real_connect(&myp->handler, h, u, p, d, pp, s, f) == NULL)
#elif MYSQL_VERSION_ID >= 32115
if (mysql_real_connect(&myp->handler, h, u, p, pp, s, f) == NULL)
#else
@ -438,13 +270,16 @@ static VALUE real_connect(int argc, VALUE* argv, VALUE klass)
myp->handler.reconnect = 0;
myp->connection = Qtrue;
optimize_for_async(obj);
my_bool was_blocking;
vio_blocking(myp->handler.net.vio, 0, &was_blocking);
myp->blocking = vio_is_blocking( myp->handler.net.vio );
vio_fastsend( myp->handler.net.vio );
myp->query_with_result = Qtrue;
rb_obj_call_init(obj, argc, argv);
//schedule_connect(obj);
return obj;
}
@ -453,7 +288,7 @@ static VALUE escape_string(VALUE klass, VALUE str)
{
VALUE ret;
Check_Type(str, T_STRING);
ret = rb_enc_str_new(0, (RSTRING_LEN(str))*2+1, DEFAULT_ENCODING);
ret = rb_str_new(0, (RSTRING_LEN(str))*2+1);
rb_str_set_len(ret, mysql_escape_string(RSTRING_PTR(ret), RSTRING_PTR(str), RSTRING_LEN(str)));
return ret;
}
@ -461,7 +296,7 @@ static VALUE escape_string(VALUE klass, VALUE str)
/* client_info() */
static VALUE client_info(VALUE klass)
{
return rb_enc_tainted_str_new2(mysql_get_client_info());
return rb_tainted_str_new2(mysql_get_client_info());
}
#if MYSQL_VERSION_ID >= 32332
@ -507,9 +342,6 @@ static VALUE real_connect2(int argc, VALUE* argv, VALUE obj)
m->reconnect = 0;
GetMysqlStruct(obj)->connection = Qtrue;
optimize_for_async(obj);
//schedule_connect(obj);
return obj;
}
@ -590,7 +422,7 @@ static VALUE real_escape_string(VALUE obj, VALUE str)
MYSQL* m = GetHandler(obj);
VALUE ret;
Check_Type(str, T_STRING);
ret = rb_enc_str_new(0, (RSTRING_LEN(str))*2+1, DEFAULT_ENCODING);
ret = rb_str_new(0, (RSTRING_LEN(str))*2+1);
rb_str_set_len(ret, mysql_real_escape_string(m, RSTRING_PTR(ret), RSTRING_PTR(str), RSTRING_LEN(str)));
return ret;
}
@ -629,7 +461,7 @@ static VALUE change_user(int argc, VALUE* argv, VALUE obj)
/* character_set_name() */
static VALUE character_set_name(VALUE obj)
{
return rb_enc_tainted_str_new2(mysql_character_set_name(GetHandler(obj)));
return rb_tainted_str_new2(mysql_character_set_name(GetHandler(obj)));
}
#endif
@ -694,7 +526,7 @@ static VALUE field_count(VALUE obj)
/* host_info() */
static VALUE host_info(VALUE obj)
{
return rb_enc_tainted_str_new2(mysql_get_host_info(GetHandler(obj)));
return rb_tainted_str_new2(mysql_get_host_info(GetHandler(obj)));
}
/* proto_info() */
@ -706,14 +538,14 @@ static VALUE proto_info(VALUE obj)
/* server_info() */
static VALUE server_info(VALUE obj)
{
return rb_enc_tainted_str_new2(mysql_get_server_info(GetHandler(obj)));
return rb_tainted_str_new2(mysql_get_server_info(GetHandler(obj)));
}
/* info() */
static VALUE info(VALUE obj)
{
const char* p = mysql_info(GetHandler(obj));
return p? rb_enc_tainted_str_new2(p): Qnil;
return p? rb_tainted_str_new2(p): Qnil;
}
/* insert_id() */
@ -748,7 +580,7 @@ static VALUE list_dbs(int argc, VALUE* argv, VALUE obj)
n = mysql_num_rows(res);
ret = rb_ary_new2(n);
for (i=0; i<n; i++)
rb_ary_store(ret, i, rb_enc_tainted_str_new2(mysql_fetch_row(res)[0]));
rb_ary_store(ret, i, rb_tainted_str_new2(mysql_fetch_row(res)[0]));
mysql_free_result(res);
return ret;
}
@ -763,7 +595,7 @@ static VALUE list_fields(int argc, VALUE* argv, VALUE obj)
res = mysql_list_fields(m, StringValuePtr(table), NILorSTRING(field));
if (res == NULL)
mysql_raise(m);
return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
return mysqlres2obj(res);
}
/* list_processes() */
@ -773,7 +605,7 @@ static VALUE list_processes(VALUE obj)
MYSQL_RES* res = mysql_list_processes(m);
if (res == NULL)
mysql_raise(m);
return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
return mysqlres2obj(res);
}
/* list_tables(table=nil) */
@ -793,7 +625,7 @@ static VALUE list_tables(int argc, VALUE* argv, VALUE obj)
n = mysql_num_rows(res);
ret = rb_ary_new2(n);
for (i=0; i<n; i++)
rb_ary_store(ret, i, rb_enc_tainted_str_new2(mysql_fetch_row(res)[0]));
rb_ary_store(ret, i, rb_tainted_str_new2(mysql_fetch_row(res)[0]));
mysql_free_result(res);
return ret;
}
@ -857,10 +689,9 @@ static VALUE my_stat(VALUE obj)
const char* s = mysql_stat(m);
if (s == NULL)
mysql_raise(m);
return rb_enc_tainted_str_new2(s);
return rb_tainted_str_new2(s);
}
// 1.9 friendly
typedef struct
{
MYSQL *mysql_instance;
@ -872,7 +703,7 @@ typedef struct
static VALUE store_result_to_location(void *settings_in)
{
mysql_result_to_here_t *settings = (mysql_result_to_here_t *) settings_in;
*(settings->store_it_here) = mysql_store_result(settings->mysql_instance); // this one line runs a good long while for very large queries
*(settings->store_it_here) = mysql_store_result(settings->mysql_instance); // this one runs a good long while for very large queries
return Qnil;
}
@ -887,13 +718,13 @@ static VALUE store_result(VALUE obj)
mysql_result_to_here_t linker;
linker.mysql_instance = m;
linker.store_it_here = &res;
rb_thread_blocking_region(store_result_to_location, (void *) &linker, RUBY_UBF_IO, 0);
rb_thread_blocking_region(store_result_to_location, (void *) &linker, RUBY_UBF_IO, 0); /* not sure if this should be RUBY_UBF_IO or RUBY_UBF_PROCESS here -- see Ruby 1.9 ChangeLog */
#endif
if (res == NULL)
mysql_raise(m);
return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
return mysqlres2obj(res);
}
/* thread_id() */
@ -909,54 +740,29 @@ static VALUE use_result(VALUE obj)
MYSQL_RES* res = mysql_use_result(m);
if (res == NULL)
mysql_raise(m);
return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
return mysqlres2obj(res);
}
static VALUE res_free(VALUE);
typedef struct {
MYSQL *m;
const char *data;
unsigned long len;
} QueryArgs;
static VALUE blocking_query(void *data)
{
QueryArgs *args = (QueryArgs *) data;
return (VALUE) mysql_real_query(args->m, args->data, args->len);
}
/* query(sql) */
static VALUE query(VALUE obj, VALUE sql)
{
int loop = 0;
MYSQL* m = GetHandler(obj);
QueryArgs args;
int result;
Check_Type(sql, T_STRING);
if (GetMysqlStruct(obj)->connection == Qfalse) {
rb_raise(eMysql, "query: not connected");
}
if (rb_block_given_p()) {
#ifdef RUBY_VM
args.m = m;
args.data = RSTRING_PTR(sql);
args.len = RSTRING_LEN(sql);
result = (int) rb_thread_blocking_region(blocking_query, &args, RUBY_UBF_PROCESS, 0);
#else
result = mysql_real_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql));
#endif
if (result != 0)
if (mysql_real_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0)
mysql_raise(m);
do {
MYSQL_RES* res = mysql_store_result(m);
if (res == NULL) {
if (mysql_field_count(m) != 0)
mysql_raise(m);
} else {
VALUE robj = mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
VALUE robj = mysqlres2obj(res);
rb_ensure(rb_yield, robj, res_free, robj);
}
#if MYSQL_VERSION_ID >= 40101
@ -968,16 +774,7 @@ static VALUE query(VALUE obj, VALUE sql)
#endif
return obj;
}
#ifdef RUBY_VM
args.m = m;
args.data = RSTRING_PTR(sql);
args.len = RSTRING_LEN(sql);
result = (int) rb_thread_blocking_region(blocking_query, &args, RUBY_UBF_PROCESS, 0);
#else
result = mysql_real_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql));
#endif
if (result != 0)
if (mysql_real_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0)
mysql_raise(m);
if (GetMysqlStruct(obj)->query_with_result == Qfalse)
return obj;
@ -992,15 +789,12 @@ static VALUE socket(VALUE obj)
MYSQL* m = GetHandler(obj);
return INT2NUM(m->net.fd);
}
/* socket_type --currently returns true or false, needs some work */
/* socket_type */
static VALUE socket_type(VALUE obj)
{
MYSQL* m = GetHandler(obj);
if(vio_description(m->net.vio))
return Qtrue; // TODO return a ruby string
else
return Qnil;
VALUE description = vio_description( m->net.vio );
return NILorSTRING( description );
}
/* blocking */
@ -1008,32 +802,6 @@ static VALUE blocking(VALUE obj){
return ( GetMysqlStruct(obj)->blocking ? Qtrue : Qfalse );
}
/* is_busy */
static VALUE is_busy(VALUE obj){
return ( GetMysqlStruct(obj)->busy ? Qtrue : Qfalse );
}
static VALUE is_idle(VALUE obj){
return ( is_busy(obj) == Qtrue ) ? Qfalse : Qtrue;
}
/* busy(true|false) */
static VALUE busy_set(VALUE obj, VALUE flag)
{
if (TYPE(flag) != T_TRUE && TYPE(flag) != T_FALSE)
rb_raise(rb_eTypeError, "invalid type, required true or false.");
GetMysqlStruct(obj)->busy = flag;
return flag;
}
static void busy( VALUE obj ){
busy_set( obj, Qtrue );
}
static void idle( VALUE obj ){
busy_set( obj, Qfalse );
}
/* readable(timeout=nil) */
static VALUE readable( int argc, VALUE* argv, VALUE obj )
{
@ -1046,152 +814,59 @@ static VALUE readable( int argc, VALUE* argv, VALUE obj )
if ( NIL_P( timeout ) ){
timeout = m->net.read_timeout;
}
// todo could do a rb_blocking_region here
return ( vio_poll_read( m->net.vio, INT2NUM(timeout) ) == 0 ? Qtrue : Qfalse );
}
/* retry */
static VALUE retry( VALUE obj )
{
MYSQL* m = GetHandler(obj);
return ( vio_should_retry( m->net.vio ) == 1 ? Qtrue : Qfalse );
}
/* interrupted */
static VALUE interrupted( VALUE obj )
{
MYSQL* m = GetHandler(obj);
return ( vio_was_interrupted( m->net.vio ) == 1 ? Qtrue : Qfalse );
}
/* reconnected */
static VALUE reconnected( VALUE obj ){
MYSQL* m = GetHandler(obj);
int current_connection_id = mysql_thread_id( m );
mysql_ping(m);
return ( current_connection_id == mysql_thread_id( m ) ) ? Qfalse : Qtrue;
}
/* disable_gc(true|false) */
static VALUE disable_gc_set(VALUE obj, VALUE flag)
{
if (TYPE(flag) != T_TRUE && TYPE(flag) != T_FALSE)
rb_raise(rb_eTypeError, "invalid type, required true or false.");
GetMysqlStruct(obj)->gc_disabled = flag;
return flag;
}
/* gc_disabled */
static VALUE gc_disabled( VALUE obj ){
return GetMysqlStruct(obj)->gc_disabled ? Qtrue: Qfalse;
}
static void validate_async_query( VALUE obj )
{
if( async_in_progress(obj) == Qtrue ){
async_in_progress_set(obj, Qfalse);
rb_raise(eMysql, "Query out of sequence: Each call to Mysql#send_query requires a successive Mysql#get_result.");
}
}
/* for testing */
static VALUE simulate_disconnect( VALUE obj )
{
MYSQL* m = GetHandler(obj);
mysql_library_end();
return Qnil;
}
/* send_query(sql) */
static VALUE send_query(VALUE obj, VALUE sql)
{
MYSQL* m = GetHandler(obj);
Check_Type(sql, T_STRING);
if (GetMysqlStruct(obj)->connection == Qfalse && async_in_progress(obj) == Qtrue ) {
idle( obj );
if (GetMysqlStruct(obj)->connection == Qfalse) {
rb_raise(eMysql, "query: not connected");
}
validate_async_query(obj);
if (mysql_send_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0){
idle( obj );
if (mysql_send_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0)
mysql_raise(m);
}
async_in_progress_set( obj, Qtrue );
return Qnil;
}
/*
get_result
returns the mysql_result set (default) [i.e. all rows in said said]
or nil if query_with_result == false
*/
/* get_result */
static VALUE get_result(VALUE obj)
{
MYSQL* m = GetHandler(obj);
async_in_progress_set( obj, Qfalse );
if (GetMysqlStruct(obj)->connection == Qfalse) {
idle( obj );
rb_raise(eMysql, "query: not connected");
}
if (mysql_read_query_result(m) != 0){
idle( obj );
if (mysql_read_query_result(m) != 0)
mysql_raise(m);
}
if (GetMysqlStruct(obj)->query_with_result == Qfalse)
return obj;
if (mysql_field_count(m) == 0)
return Qnil;
return store_result(obj);
}
static void schedule_query(VALUE obj, VALUE timeout)
static VALUE schedule(VALUE obj, VALUE timeout)
{
MYSQL* m = GetHandler(obj);
fd_set read;
int ret;
timeout = ( NIL_P(timeout) ? m->net.read_timeout : INT2NUM(timeout) );
struct timeval tv = { tv_sec: timeout, tv_usec: 0 };
for(;;){
FD_ZERO(&read);
FD_SET(m->net.fd, &read);
ret = rb_thread_select(m->net.fd + 1, &read, NULL, NULL, &tv);
if (ret < 0) {
idle( obj );
if (rb_thread_select(m->net.fd + 1, &read, NULL, NULL, &tv) < 0) {
rb_raise(eMysql, "query: timeout");
}
if (ret == 0) {
continue;
}
if (m->status == MYSQL_STATUS_READY){
break;
}
}
}
static int should_schedule_query(){
return rb_thread_alone() != 1;
}
/* async_query(sql,timeout=nil)
optionally take a block
*/
/* async_query(sql,timeout=nil) */
static VALUE async_query(int argc, VALUE* argv, VALUE obj)
{
MYSQL* m = GetHandler(obj);
@ -1199,24 +874,11 @@ static VALUE async_query(int argc, VALUE* argv, VALUE obj)
rb_scan_args(argc, argv, "11", &sql, &timeout);
async_in_progress_set( obj, Qfalse );
send_query(obj,sql);
busy(obj);
schedule(obj, timeout);
send_query( obj, sql );
if ( should_schedule_query() ){
schedule_query(obj, timeout);
}
if (rb_block_given_p()) {
rb_yield( get_result(obj) );
idle( obj );
return obj;
}else{
idle( obj );
return get_result(obj);
}
}
#if MYSQL_VERSION_ID >= 40100
@ -1317,7 +979,7 @@ static VALUE set_server_option(VALUE obj, VALUE option)
static VALUE sqlstate(VALUE obj)
{
MYSQL *m = GetHandler(obj);
return rb_enc_tainted_str_new2(mysql_sqlstate(m));
return rb_tainted_str_new2(mysql_sqlstate(m));
}
#endif
@ -1482,16 +1144,16 @@ static VALUE fetch_row(VALUE obj)
return Qnil;
ary = rb_ary_new2(n);
for (i=0; i<n; i++)
rb_ary_store(ary, i, row[i]? rb_enc_tainted_str_new(row[i], lengths[i]): Qnil);
rb_ary_store(ary, i, row[i]? rb_tainted_str_new(row[i], lengths[i]): Qnil);
return ary;
}
/* process_all_hashes (internal helper) */
/* process_all_hashes (internal) */
static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, int yield)
{
MYSQL_RES* res = GetMysqlRes(obj);
unsigned int n = mysql_num_fields(res);
VALUE ary = Qnil;
VALUE ary;
if(build_array)
ary = rb_ary_new();
MYSQL_ROW row = mysql_fetch_row(res); // grab one off the top, to determine the rows
@ -1512,7 +1174,7 @@ static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, in
if (colname == Qnil) {
colname = rb_ary_new2(n);
for (i=0; i<n; i++) {
VALUE s = rb_enc_tainted_str_new2(fields[i].name);
VALUE s = rb_tainted_str_new2(fields[i].name);
rb_obj_freeze(s);
rb_ary_store(colname, i, s);
}
@ -1525,7 +1187,7 @@ static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, in
colname = rb_ary_new2(n);
for (i=0; i<n; i++) {
int len = strlen(fields[i].table)+strlen(fields[i].name)+1;
VALUE s = rb_enc_tainted_str_new(NULL, len);
VALUE s = rb_tainted_str_new(NULL, len);
snprintf(RSTRING_PTR(s), len+1, "%s.%s", fields[i].table, fields[i].name);
rb_obj_freeze(s);
rb_ary_store(colname, i, s);
@ -1541,7 +1203,7 @@ static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, in
hash = rb_hash_new();
lengths = mysql_fetch_lengths(res);
for (i=0; i<n; i++) {
rb_hash_aset(hash, rb_ary_entry(colname, i), row[i]? rb_enc_tainted_str_new(row[i], lengths[i]): Qnil);
rb_hash_aset(hash, rb_ary_entry(colname, i), row[i]? rb_tainted_str_new(row[i], lengths[i]): Qnil);
}
if(build_array)
rb_ary_push(ary, hash);
@ -1558,8 +1220,6 @@ static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, in
if(yield)
return obj;
return Qnil; /* we should never get here -- this takes out a compiler warning */
}
/* fetch_hash2 (internal) */
@ -1577,12 +1237,12 @@ static VALUE fetch_hash2(VALUE obj, VALUE with_table)
return Qnil;
hash = rb_hash_new();
if (with_table == Qnil || with_table == Qfalse) {
if (with_table == Qfalse) {
colname = rb_iv_get(obj, "colname");
if (colname == Qnil) {
colname = rb_ary_new2(n);
for (i=0; i<n; i++) {
VALUE s = rb_enc_tainted_str_new2(fields[i].name);
VALUE s = rb_tainted_str_new2(fields[i].name);
rb_obj_freeze(s);
rb_ary_store(colname, i, s);
}
@ -1595,7 +1255,7 @@ static VALUE fetch_hash2(VALUE obj, VALUE with_table)
colname = rb_ary_new2(n);
for (i=0; i<n; i++) {
int len = strlen(fields[i].table)+strlen(fields[i].name)+1;
VALUE s = rb_enc_tainted_str_new(NULL, len);
VALUE s = rb_tainted_str_new(NULL, len);
snprintf(RSTRING_PTR(s), len+1, "%s.%s", fields[i].table, fields[i].name);
rb_obj_freeze(s);
rb_ary_store(colname, i, s);
@ -1605,7 +1265,7 @@ static VALUE fetch_hash2(VALUE obj, VALUE with_table)
}
}
for (i=0; i<n; i++) {
rb_hash_aset(hash, rb_ary_entry(colname, i), row[i]? rb_enc_tainted_str_new(row[i], lengths[i]): Qnil);
rb_hash_aset(hash, rb_ary_entry(colname, i), row[i]? rb_tainted_str_new(row[i], lengths[i]): Qnil);
}
return hash;
}
@ -1737,7 +1397,7 @@ static VALUE field_hash(VALUE obj)
static VALUE field_inspect(VALUE obj)
{
VALUE n = rb_iv_get(obj, "name");
VALUE s = rb_enc_str_new(0, RSTRING_LEN(n) + 16, DEFAULT_ENCODING);
VALUE s = rb_str_new(0, RSTRING_LEN(n) + 16);
sprintf(RSTRING_PTR(s), "#<Mysql::Field:%s>", RSTRING_PTR(n));
return s;
}
@ -1796,7 +1456,7 @@ static void mysql_stmt_raise(MYSQL_STMT* s)
{
VALUE e = rb_exc_new2(eMysql, mysql_stmt_error(s));
rb_iv_set(e, "errno", INT2FIX(mysql_stmt_errno(s)));
rb_iv_set(e, "sqlstate", rb_enc_tainted_str_new2(mysql_stmt_sqlstate(s)));
rb_iv_set(e, "sqlstate", rb_tainted_str_new2(mysql_stmt_sqlstate(s)));
rb_exc_raise(e);
}
@ -1943,12 +1603,12 @@ static VALUE stmt_execute(int argc, VALUE *argv, VALUE obj)
s->param.bind[i].buffer = &(s->param.buffer[i]);
t.second_part = 0;
t.neg = 0;
t.second = FIX2INT(rb_ary_entry(a, 0));
t.minute = FIX2INT(rb_ary_entry(a, 1));
t.hour = FIX2INT(rb_ary_entry(a, 2));
t.day = FIX2INT(rb_ary_entry(a, 3));
t.month = FIX2INT(rb_ary_entry(a, 4));
t.year = FIX2INT(rb_ary_entry(a, 5));
t.second = FIX2INT(RARRAY(a)->ptr[0]);
t.minute = FIX2INT(RARRAY(a)->ptr[1]);
t.hour = FIX2INT(RARRAY(a)->ptr[2]);
t.day = FIX2INT(RARRAY(a)->ptr[3]);
t.month = FIX2INT(RARRAY(a)->ptr[4]);
t.year = FIX2INT(RARRAY(a)->ptr[5]);
*(MYSQL_TIME*)&(s->param.buffer[i]) = t;
} else if (CLASS_OF(argv[i]) == cMysqlTime) {
MYSQL_TIME t;
@ -2110,7 +1770,7 @@ static VALUE stmt_fetch(VALUE obj)
case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_BIT:
#endif
v = rb_enc_tainted_str_new(s->result.bind[i].buffer, s->result.length[i]);
v = rb_tainted_str_new(s->result.bind[i].buffer, s->result.length[i]);
break;
default:
rb_raise(rb_eTypeError, "unknown buffer_type: %d", s->result.bind[i].buffer_type);
@ -2251,7 +1911,7 @@ static VALUE stmt_result_metadata(VALUE obj)
mysql_stmt_raise(s->stmt);
return Qnil;
}
return mysqlres2obj(res, Qfalse);
return mysqlres2obj(res);
}
/* row_seek(offset) */
@ -2299,7 +1959,7 @@ static VALUE stmt_send_long_data(VALUE obj, VALUE col, VALUE data)
static VALUE stmt_sqlstate(VALUE obj)
{
struct mysql_stmt* s = DATA_PTR(obj);
return rb_enc_tainted_str_new2(mysql_stmt_sqlstate(s->stmt));
return rb_tainted_str_new2(mysql_stmt_sqlstate(s->stmt));
}
/*-------------------------------
@ -2509,22 +2169,12 @@ void Init_mysql(void)
rb_define_method(cMysql, "query", query, 1);
rb_define_method(cMysql, "real_query", query, 1);
rb_define_method(cMysql, "c_async_query", async_query, -1);
rb_define_method(cMysql, "async_in_progress?", async_in_progress, 0);
rb_define_method(cMysql, "async_in_progress=", async_in_progress_set, 1);
rb_define_method(cMysql, "send_query", send_query, 1);
rb_define_method(cMysql, "simulate_disconnect", simulate_disconnect, 0);
rb_define_method(cMysql, "reconnected?", reconnected, 0);
rb_define_method(cMysql, "get_result", get_result, 0);
rb_define_method(cMysql, "readable?", readable, -1);
rb_define_method(cMysql, "retry?", retry, 0);
rb_define_method(cMysql, "interrupted?", interrupted, 0);
rb_define_method(cMysql, "blocking?", blocking, 0);
rb_define_method(cMysql, "gc_disabled?", gc_disabled, 0);
rb_define_method(cMysql, "disable_gc=", disable_gc_set, 1);
rb_define_method(cMysql, "busy?", is_busy, 0);
rb_define_method(cMysql, "idle?", is_idle, 0);
rb_define_method(cMysql, "busy=", busy_set, 1);
rb_define_method(cMysql, "socket", socket, 0);
rb_define_method(cMysql, "socket_type", socket_type, 0);
rb_define_method(cMysql, "refresh", refresh, 1);
rb_define_method(cMysql, "reload", reload, 0);
rb_define_method(cMysql, "select_db", select_db, 1);

View File

@ -1,23 +1,19 @@
require File.dirname(__FILE__) + '/mysql' # load our version of mysql--note
# if someone does a require 'mysql' after a require 'mysqlplus' then their screen will be littered with warnings
# and the "old" mysql will override the "new" mysqlplus, so be careful.
require 'mysql'
#
# The mysqlplus library is a [slightly updated] fork of the Mysql class, with asynchronous capability added
# See http://www.kitebird.com/articles/ruby-mysql.html for details, as well as the test directory within the gem
#
class Mysql
def ruby_async_query(sql, timeout = nil) # known to deadlock TODO
def async_query(sql, timeout = nil)
send_query(sql)
select [ (@sockets ||= {})[socket] ||= IO.new(socket) ], nil, nil, nil
get_result
end
begin
alias_method :async_query, :c_async_query
rescue NameError => e
raise LoadError.new("error loading mysqlplus--this may mean you ran a require 'mysql' before a require 'mysqplus', which must come first -- possibly also run gem uninstall mysql")
end
end
class Mysql::Result
def all_hashes
rows = []
each_hash { |row| rows << row }
rows
end
end

41
mysqlplus.gemspec Normal file → Executable file
View File

@ -1,30 +1,29 @@
Gem::Specification.new do |s|
s.name = "mysqlplus"
s.version = "0.1.4"
s.date = "2010-07-04"
s.summary = "Enhanced Ruby MySQL driver with Ruby 1.9 encoding awareness"
s.email = "jeremysuriel@gmail.com"
s.homepage = "http://github.com/jrmey/mysqlplus"
s.version = "0.1.0"
s.date = "2008-08-13"
s.summary = "Enhanced Ruby MySQL driver"
s.email = "oldmoe@gmail.com"
s.homepage = "http://github.com/oldmoe/mysqlplus"
s.description = "Enhanced Ruby MySQL driver"
s.has_rdoc = true
s.authors = ["Muhammad A. Ali", "Jeremy Suriel", "John Bintz"]
s.authors = ["Muhammad A. Ali"]
s.platform = Gem::Platform::RUBY
s.files = %w[
README
Rakefile
TODO_LIST
ext/error_const.h
ext/extconf.rb
ext/mysql.c
lib/mysqlplus.rb
mysqlplus.gemspec
] + Dir.glob('test/*')
s.files = [
"mysqlplus.gemspec",
"README",
"Rakefile",
"lib/mysqlplus.rb",
"test/test_helper.rb",
"test/native_threaded_test.rb",
"test/c_threaded_test.rb",
"test/evented_test.rb",
"ext/error_const.h",
"ext/extconf.rb",
"ext/mysql.c"
]
s.rdoc_options = ["--main", "README"]
s.extra_rdoc_files = ["README"]
s.extensions << "ext/extconf.rb"
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
end
end

View File

@ -1,9 +0,0 @@
# I suppose if all the tests don't blow up, that probably means pass
require 'mysqlplus'
for file in Dir.glob('*_test.rb') do
puts 'testing ' + file
# fork so we don't run out of connections to the mysql db, as few tests ever clean up their old processes
pid = Process.fork { load file }
Process.wait(pid)
end
puts 'successful'

View File

@ -1,37 +0,0 @@
require 'create_test_db'
use_the_all_hashes_method = true
$count = 5
$start = Time.now
$connections = []
$count.times do
$connections << Mysql.real_connect('localhost','root', '', 'local_test_db')
end
$threads = []
$count.times do |i|
$threads << Thread.new do
query = "select * from test_table"
puts "sending query on connection #{i}"
conn = $connections[i]
result = conn.async_query(query)
if use_the_all_hashes_method
saved = result.all_hashes
else
saved = []
result.each_hash {|h| saved << h }
end
result.free
end
end
puts 'waiting on threads'
$threads.each{|t| t.join }
puts Time.now - $start

View File

@ -1,7 +0,0 @@
require File.dirname(__FILE__) + '/test_helper'
m = Mysql.real_connect('localhost','root','','mysql')
m.c_async_query( 'SELECT * FROM user' ) do |result|
puts result.inspect
end

View File

@ -1,22 +0,0 @@
# If this script returns without the word pass
# you may have compiled mysqlplus using ruby and
# run it using a different version of ruby
if RUBY_VERSION >= "1.9.1"
require 'mysqlplus'
require 'socket'
require 'timeout'
TCPServer.new '0.0.0.0', 8002
Thread.new {
sleep 2
print "pass"
system("kill -9 #{Process.pid}")
}
Timeout::timeout(1) {
# uncomment this line to do the 'real' test
# which hangs otherwise (blows up if code is bad, otherwise hangs)
Mysql.real_connect '127.0.0.1', 'root', 'pass', 'db', 8002
}
raise 'should never get here'
end

View File

@ -1,17 +0,0 @@
require 'mysqlplus'
begin
Mysql.real_connect('fakehost','root', '', 'local_leadgen_dev')
rescue Mysql::Error
end
begin
Mysql.real_connect('localhost','root', '', 'faketable')
rescue Mysql::Error
end
begin
Mysql.real_connect('localhost', 'root', 'pass', 'db', 3307)# bad port
rescue Mysql::Error
end
print "pass"

View File

@ -1,22 +0,0 @@
# To run first execute:
=begin
create database local_test_db;
use local_test_db;
CREATE TABLE test_table (
c1 INT,
c2 VARCHAR(20)
);
=end
# This script shows the effect of using .all_hashes instead of looping on each hash
# run it by substiting in a 'long' [many row] query for the query variable and toggling use_all_hashes here at the top
# note that we load all the rows first, then run .all_hashes on the result [to see more easily the effect of all hashes]
# on my machine and a 200_000 row table, it took 3.38s versus 3.65s for the old .each_hash way [note also that .each_hash is
# almost as fast, now, as .all_hashes--they've both been optimized]
require 'mysqlplus'
puts 'initing db'
# init the DB
conn = Mysql.real_connect('localhost', 'root', '', 'local_test_db')
conn.query("delete from test_table")
200_000.times {conn.query(" insert into test_table (c1, c2) values (3, 'ABCDEFG')")}
puts 'connection pool ready'

View File

@ -1,40 +0,0 @@
require 'mysqlplus'
require 'benchmark'
with_gc = Mysql.real_connect('localhost','root','','mysql')
without_gc = Mysql.real_connect('localhost','root','','mysql')
without_gc.disable_gc = true
$gc_stats = []
def countable_gc?
GC.respond_to? :count
end
def gc_counts( label, scope )
$gc_stats << "Objects #{scope} ( #{label} ) #{GC.count}"
end
def with_gc_counts( label )
gc_counts( label, 'before' ) if countable_gc?
yield
gc_counts( label, 'after' ) if countable_gc?
end
n = 1000
Benchmark.bmbm do |x|
x.report( 'With GC' ) do
with_gc_counts( 'With GC' ) do
n.times{ with_gc.c_async_query( 'SELECT * FROM user' ) }
end
end
GC.start
x.report( 'Without GC' ) do
with_gc_counts( 'Without GC' ) do
n.times{ without_gc.c_async_query( 'SELECT * FROM user' ) }
end
end
end
puts $gc_stats.join( ' | ' )

View File

@ -1,6 +0,0 @@
require 'mysqlplus'
a = Mysql.real_connect('localhost','root')
100.times { a.query("select sleep(0)") }
print "pass"

View File

@ -1,34 +0,0 @@
require File.dirname(__FILE__) + '/test_helper'
m = Mysql.real_connect('localhost','root')
m.reconnect = true
$count = 0
class << m
def safe_query( query )
begin
send_query( query )
rescue => e
$count += 1
puts e.message
end
end
end
m.safe_query( 'select sleep(1)' )
m.safe_query( 'select sleep(1)' )#raises
m.simulate_disconnect #fires mysql_library_end
m.safe_query( 'select sleep(1)' )
m.safe_query( 'select sleep(1)' )#raises
m.close
m.connect('localhost','root')
m.safe_query( 'select sleep(1)' )
m.safe_query( 'select sleep(1)' )#raises
m.simulate_disconnect
raise unless $count == 3
m.safe_query( 'BEGIN' )
m.safe_query( 'select sleep(1)' ) # raises
m.get_result()
m.safe_query( 'COMMIT' )
m.get_result
raise unless $count == 4

View File

@ -1,18 +0,0 @@
require File.dirname(__FILE__) + '/test_helper'
$m = Mysql.real_connect('localhost','root')
#$m.reconnect = true
def assert_reconnected
puts $m.reconnected?().inspect
sleep 1
yield
puts $m.reconnected?().inspect
end
assert_reconnected do
$m.simulate_disconnect
end
assert_reconnected do
$m.close
end

43
test/test_all_hashes.rb Normal file
View File

@ -0,0 +1,43 @@
# shows the effect of using .all_hashes instead of looping on each hash
# run it by substiting in a 'long' [many row] query for the query variable and toggling use_all_hashes here at the top
# note that we load all the rows first, then run .all_hashes on the result [to see more easily the effect of all hashes]
# on my machine and a 200_000 row table, it took 3.38s versus 3.65s for the old .each_hash way [note also that .each_hash is
# almost as fast, now, as .all_hashes--they've both been optimized]
require 'mysqlplus'
use_the_all_hashes_method = true
$count = 5
$start = Time.now
$connections = []
$count.times do
$connections << Mysql.real_connect('localhost','root', '', 'local_leadgen_dev')
end
puts 'connection pool ready'
$threads = []
$count.times do |i|
$threads << Thread.new do
query = "select * from campus_zips"
puts "sending query on connection #{i}"
conn = $connections[i]
result = conn.async_query(query)
if use_the_all_hashes_method
saved = result.all_hashes
else
saved = []
result.each_hash {|h| saved << h }
end
result.free
end
end
puts 'waiting on threads'
$threads.each{|t| t.join }
puts Time.now - $start

View File

@ -1,3 +1,4 @@
require 'rubygems'
require 'mysqlplus'
class MysqlTest

View File

@ -5,7 +5,8 @@
# from .82s to .62s
# you can experiment with it by changing the query here to be a long one, and toggling the do_the_use_query_optimization variable
# this also has the interesting property of 'freeing' Ruby to do thread changes mid-query.
require 'create_test_db'
require 'mysqlplus'
do_the_use_query_optimization = true
@ -15,7 +16,7 @@ $start = Time.now
$connections = []
$count.times do
$connections << Mysql.real_connect('localhost','root', '', 'local_test_db')
$connections << Mysql.real_connect('localhost','root', '', 'local_leadgen_dev')
end
puts 'connection pool ready'
@ -27,7 +28,7 @@ $count.times do |i|
puts "sending query on connection #{i}"
conn = $connections[i]
saved = []
query = "select * from test_table"
query = "select * from campus_zips"
if do_the_use_query_optimization
conn.query_with_result=false
result = conn.async_query(query)

View File

@ -1,7 +1,7 @@
require 'mysqlplus'
require 'rubygems'
require 'sequel'
require 'mysqlplus'
class Mysql
unless method_defined? :sync_query
alias :sync_query :query