Compare commits

..

61 Commits

Author SHA1 Message Date
John Bintz
be4fffe220 changing gem name back to make it work with bundler 2010-11-17 11:24:53 -05:00
John Bintz
2eb503a89d when a mysql directory is specified in the lib list, tell the linker to add that directory to the compiled library's LD_RUN_PATH 2010-11-17 11:18:42 -05:00
Jeremy Suriel
a6bdcfb673 version bump 2010-07-06 15:21:27 -04:00
Jeremy Suriel
dd5b61c9a9 reference ruby 1.9 encoding mysql-ruby project in readme 2010-07-06 15:20:14 -04:00
Jeremy Suriel
0d334c5077 update gemspec for gemcutter 2010-07-06 15:19:05 -04:00
Jeremy Suriel
bfd0e9b5f0 update gemspec for gemcutter 2010-07-06 15:17:52 -04:00
Jeremy Suriel
8920f77c69 2010-07-06 11:43:13 -07:00
Jeremy Suriel
6651972045 Determine mysql config (from original branch) 2010-07-03 23:01:44 -04:00
Jeremy Suriel
13bec8bd19 Added Encoding awareness for Ruby 1.9. Convert all data to default external encoding 2010-07-03 22:28:24 -04:00
Filip Tepper
2daef86c48 gemspec now compatible with bundler 2010-05-18 15:47:53 +02:00
Roger Pack
e283a6d89f add note of other helpful utilities 2009-05-22 20:29:04 +00:00
Roger Pack
482fd82d83 allow the gem to compile, also ensure it requires our version of mysql.so 2009-05-13 00:23:47 +00:00
Roger Pack
b44d124700 add some notes to the tests 2009-04-21 15:08:40 +00:00
Roger Pack
c5042772b5 add some comments to the C code. take out come compiler warnings, add some notes. 2009-04-21 02:54:29 +00:00
Roger Pack
d40d8ff323 create an automated test, somewhat automated it seems impossible to actually interrupt mysql real_connect 2009-04-21 02:17:44 +00:00
Roger Pack
d1433a549e attempt to not blow up on ctrl+c during connect--that might have been a problem 2009-04-21 02:13:24 +00:00
Roger Pack
52d95ff3e8 better exception message on collision with mysql gem 2009-04-21 01:41:06 +00:00
oldmoe
de93dd90a9 fixed typo in error message 2009-04-20 15:35:31 -07:00
Roger Pack
731baec31e update TODO 2009-04-18 23:10:14 +00:00
Roger Pack
49726a5d80 minor cleanup 2009-04-18 23:07:30 +00:00
Roger Pack
021cd36670 add test runner for all 2009-04-18 23:06:12 +00:00
Roger Pack
e14c8b9876 share test create db code, add a comment for the rb_thread_blocking_region helper in the C code 2009-04-18 22:55:17 +00:00
Roger Pack
f9c62edae3 add instructions to the test_all_hashes test 2009-04-18 22:50:45 +00:00
Roger Pack
0f14fb920c add an assertion for the out_of_sync_test 2009-04-18 22:42:12 +00:00
Roger Pack
0fa6f9f30f remove the require rubygems from test, so that we can have more control over which version is being tested 2009-04-18 22:39:12 +00:00
Roger Pack
aab6964387 overcome a ruby warning message 2009-04-18 22:36:55 +00:00
Roger Pack
f910919ffc take out begins_with_insensitive--never liked that thing, anyway, and turned out to not be the real bug 2009-04-18 22:36:16 +00:00
Roger Pack
c235dcdf46 overcome merge conflicts 2009-04-18 22:35:02 +00:00
Roger Pack
e4bb045695 overcome a merge conflict which wasn't one 2009-04-18 22:24:31 +00:00
Roger Pack
497be9ca26 Merge branch '19_non_gvl_connect_original' 2009-04-18 22:23:17 +00:00
Aman Gupta
6768cc73dc Update gemspec 2009-03-22 19:17:07 -07:00
Roger Pack
3198fc25e7 add docu on how to use within rails--hopefully this is right 2009-01-31 20:15:37 +00:00
Roger Pack
91179231f0 add small documentation, also raise with a useful message if they require mysqlplus after mysql--don't know if that works with 1.9 but it does with 1.8.x 2009-01-29 23:53:48 +00:00
Aman Gupta
bb63b9d78c remove misleading comments from tests 2009-01-25 13:00:34 -08:00
Aman Gupta
e259f9507e improve extconf glob to find osx stock installs (/usr/local/mysql-5.0.51b-osx10.5-x86/bin/mysql_config) 2009-01-25 13:00:28 -08:00
Aman Gupta
8320b50e64 update extconf to look in common mysql installation paths 2009-01-23 22:14:38 -08:00
Roger Pack
f44f9c6d13 update some tests 2009-01-12 18:42:46 +00:00
unknown
9471ee3629 use c_async_query by default 2009-01-12 11:18:58 -07:00
unknown
edff42ab2c use c_async_query by default 2009-01-12 11:18:33 -07:00
unknown
b80dcb437e add todo list 2009-01-12 11:16:10 -07:00
Roger Pack
98373d7b15 take out the rb gc for now, until I can merge in Lourens' stuff, and also add a test for multiple queries 2009-01-08 06:30:24 +00:00
Roger Pack
d2549f3907 remove test output 2008-12-20 23:07:21 +00:00
Roger Pack
9f677c3047 resolve merge conflicts 2008-12-20 23:03:56 +00:00
Roger Pack
9d88487022 add test for when a db isn't there 2008-12-20 22:59:35 +00:00
Hongli Lai (Phusion)
dcb1e10c16 Fix various compiler warnings. 2008-11-18 19:23:31 +01:00
Hongli Lai (Phusion)
8a758f77e3 When in Ruby 1.9, release the global interpreter lock while performing a query, so that other threads can still run. 2008-11-18 19:18:51 +01:00
Hongli Lai (Phusion)
1af96063be Fix compilation on Ruby 1.9. 2008-11-18 18:56:40 +01:00
Roger Pack
8099da577d allow the 19_non_gvl branch to work with 1.8, also fix some warnings 2008-10-31 12:37:00 -06:00
Roger Pack
1404c2bc44 code cleanup, since rb_thread_blocking_region actually returns the value of the function passed to it 2008-10-03 14:11:11 -06:00
Roger Pack
a1d10c140b take out some compiler warnings 2008-10-01 16:30:45 -06:00
Roger Pack
4ff863b4f9 code cleanup to avoid warnings 2008-10-01 08:09:55 -06:00
Roger Pack
ce962ebc7d add in a function to make real_connect non blocking 2008-09-30 22:35:59 -06:00
Roger Pack
ee9cf7c47e overcome another merge conflict 2008-09-30 17:04:06 -06:00
Roger Pack
63114d674c overcome extconf conflict 2008-09-30 17:02:58 -06:00
Roger Pack
cf86732d4a switch to c based all_hashes 2008-09-30 16:59:51 -06:00
Roger Pack
ab757bdf9d rename RB_UBF_DFL to RUBY_UBF_IO 2008-09-24 11:01:57 -06:00
Roger Pack
f73ba931ef overcome conflict 2008-09-24 11:01:14 -06:00
humanzz
c17d4b6a7a fixing cflags 2008-09-14 15:00:29 +02:00
Roger Pack
02e265072c update the RB_UBF_DL for more recent versions of Ruby 1.9, which doesn't have it 2008-09-08 12:39:00 -06:00
Roger Pack
16d9c043c2 Merge branch 'master' into 19_full_thread_no_gil 2008-09-08 12:17:20 -06:00
Roger Pack
fcdc6337cf add in ability to make queries on 1.9 absolutely simultaneously--currently broken, however--feel free to help out--currently the master branch is faster 2008-08-28 17:10:04 -06:00
18 changed files with 1028 additions and 702 deletions

17
README
View File

@ -2,6 +2,8 @@
An enhanced MySQL database driver. With support for async operations and threaded database access. 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 == Building
gem build mysqlplus.gemspec gem build mysqlplus.gemspec
@ -20,6 +22,18 @@ An enhanced MySQL database driver. With support for async operations and threade
--with-mysql-dir=/usr/local/mysql \ --with-mysql-dir=/usr/local/mysql \
--with-mysql-lib=/usr/local/mysql/lib \ --with-mysql-lib=/usr/local/mysql/lib \
--with-mysql-include=/usr/local/mysql/include --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 === Credits
@ -30,3 +44,6 @@ Lourens Naude for 1.9 integration help.
=== License === License
Ruby License, http://www.ruby-lang.org/en/LICENSE.txt. Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
== Mailing list
http://groups.google.com/group/never-block?hl=en

12
TODO_LIST Normal file
View File

@ -0,0 +1,12 @@
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,22 +1,75 @@
require 'mkmf' 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 if /mswin32/ =~ RUBY_PLATFORM
inc, lib = dir_config('mysql') inc, lib = dir_config('mysql')
exit 1 unless have_library("libmysql") #exit 1 unless have_library("libmysql")
have_library("libmysql") or die "can't find libmysql."
elsif mc = with_config('mysql-config') then elsif mc = with_config('mysql-config') then
mc = 'mysql_config' if mc == true mc = 'mysql_config' if mc == true
cflags = `#{mc} --cflags`.chomp #cflags = `#{mc} --cflags`.chomp
exit 1 if $? != 0 #exit 1 if $? != 0
libs = `#{mc} --libs`.chomp cflags = exec_command("#{mc} --cflags")
exit 1 if $? != 0 #libs = `#{mc} --libs`.chomp
#exit 1 if $? != 0
libs = exec_command("#{mc} --libs")
$CPPFLAGS += ' ' + cflags $CPPFLAGS += ' ' + cflags
$libs = libs + " " + $libs $libs = libs + " " + $libs
else else
inc, lib = dir_config('mysql', '/usr/local') puts "Trying to detect MySQL configuration with mysql_config command..."
libs = ['m', 'z', 'socket', 'nsl', 'mygcc'] begin
while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do cflags = libs = nil
exit 1 if libs.empty?
have_library(libs.shift) 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..."
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."
have_library(libs.shift)
end
end
end end
end end
@ -28,11 +81,14 @@ if have_header('mysql.h') then
elsif have_header('mysql/mysql.h') then elsif have_header('mysql/mysql.h') then
src = "#include <mysql/errmsg.h>\n#include <mysql/mysqld_error.h>\n" src = "#include <mysql/errmsg.h>\n#include <mysql/mysqld_error.h>\n"
else else
exit 1 #exit 1
die "can't find 'mysql.h'."
end end
if have_func('rb_thread_blocking_region') and have_macro('RB_UBF_DFL', 'ruby.h') # check for 1.9
cflags << "-DHAVE_TBR" if have_func('rb_thread_blocking_region') and have_macro('RUBY_UBF_IO', 'ruby.h')
$CFLAGS += " -DHAVE_TBR "
$CPPFLAGS << " -DHAVE_TBR "
end end
# make mysql constant # make mysql constant
@ -47,9 +103,10 @@ end
if /mswin32/ =~ RUBY_PLATFORM && !/-E/.match(cpp) if /mswin32/ =~ RUBY_PLATFORM && !/-E/.match(cpp)
cpp << " -E" cpp << " -E"
end end
unless system "#{cpp} > confout" then #unless system "#{cpp} > confout" then
exit 1 # exit 1
end #end
exec_command("#{cpp} > confout")
File.unlink "conftest.c" File.unlink "conftest.c"
error_syms = [] error_syms = []
@ -71,4 +128,10 @@ File.open('error_const.h', 'w') do |f|
end end
end end
create_makefile("mysql") $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,16 +6,49 @@
#include <ruby.h> #include <ruby.h>
#include <errno.h> #include <errno.h>
#include <stdarg.h>
#ifndef RSTRING_PTR #ifndef RSTRING_PTR
#define RSTRING_PTR(str) RSTRING(str)->ptr #define RSTRING_PTR(str) RSTRING(str)->ptr
#endif #endif
#ifndef RSTRING_LEN #ifndef RSTRING_LEN
#define RSTRING_LEN(str) RSTRING(str)->len #define RSTRING_LEN(str) RSTRING(str)->len
#endif #endif
#ifndef RARRAY_PTR
#define RARRAY_PTR(ary) RARRAY(ary)->ptr
#endif
#ifndef HAVE_RB_STR_SET_LEN #ifndef HAVE_RB_STR_SET_LEN
#define rb_str_set_len(str, length) (RSTRING_LEN(str) = (length)) #define rb_str_set_len(str, length) (RSTRING_LEN(str) = (length))
#endif #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 #ifdef HAVE_MYSQL_H
#include <mysql.h> #include <mysql.h>
#include <mysql_com.h> #include <mysql_com.h>
@ -66,6 +99,7 @@ struct mysql {
char busy; char busy;
}; };
// a wrapper for mysql_res's so we can detect double frees
struct mysql_res { struct mysql_res {
MYSQL_RES* res; MYSQL_RES* res;
char freed; char freed;
@ -178,7 +212,7 @@ static void mysql_raise(MYSQL* m)
VALUE e = rb_exc_new2(eMysql, mysql_error(m)); VALUE e = rb_exc_new2(eMysql, mysql_error(m));
rb_iv_set(e, "errno", INT2FIX(mysql_errno(m))); rb_iv_set(e, "errno", INT2FIX(mysql_errno(m)));
#if MYSQL_VERSION_ID >= 40101 #if MYSQL_VERSION_ID >= 40101
rb_iv_set(e, "sqlstate", rb_tainted_str_new2(mysql_sqlstate(m))); rb_iv_set(e, "sqlstate", rb_enc_tainted_str_new2(mysql_sqlstate(m)));
#endif #endif
rb_exc_raise(e); rb_exc_raise(e);
} }
@ -193,9 +227,10 @@ static VALUE mysqlres2obj(MYSQL_RES* res, VALUE gc_disabled)
resp->res = res; resp->res = res;
resp->freed = Qfalse; resp->freed = Qfalse;
rb_obj_call_init(obj, 0, NULL); rb_obj_call_init(obj, 0, NULL);
if (++store_result_count > GC_STORE_RESULT_LIMIT && gc_disabled == Qfalse){ /* disabled until it can be reviewed further--rely on the normal GC for now.
rb_gc(); if (++store_result_count > GC_STORE_RESULT_LIMIT)
} rb_gc();
*/
return obj; return obj;
} }
@ -206,9 +241,9 @@ static VALUE make_field_obj(MYSQL_FIELD* f)
if (f == NULL) if (f == NULL)
return Qnil; return Qnil;
obj = rb_obj_alloc(cMysqlField); obj = rb_obj_alloc(cMysqlField);
rb_iv_set(obj, "name", f->name? rb_str_freeze(rb_tainted_str_new2(f->name)): Qnil); 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_tainted_str_new2(f->table)): 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_tainted_str_new2(f->def)): Qnil); rb_iv_set(obj, "def", f->def? rb_str_freeze(rb_enc_tainted_str_new2(f->def)): Qnil);
rb_iv_set(obj, "type", INT2NUM(f->type)); rb_iv_set(obj, "type", INT2NUM(f->type));
rb_iv_set(obj, "length", INT2NUM(f->length)); rb_iv_set(obj, "length", INT2NUM(f->length));
rb_iv_set(obj, "max_length", INT2NUM(f->max_length)); rb_iv_set(obj, "max_length", INT2NUM(f->max_length));
@ -231,11 +266,81 @@ static VALUE init(VALUE klass)
mysql_init(&myp->handler); mysql_init(&myp->handler);
myp->connection = Qfalse; myp->connection = Qfalse;
myp->query_with_result = Qtrue; myp->query_with_result = Qtrue;
myp->gc_disabled = Qfalse; myp->gc_disabled = Qtrue;
rb_obj_call_init(obj, 0, NULL); rb_obj_call_init(obj, 0, NULL);
return obj; 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 ) static VALUE connection_identifier( VALUE obj )
{ {
MYSQL* m = GetHandler(obj); MYSQL* m = GetHandler(obj);
@ -255,6 +360,7 @@ static VALUE async_in_progress_set( VALUE obj, VALUE flag )
return flag; return flag;
} }
// does this actually really do anything helpful? Not sure.
static void optimize_for_async( VALUE obj ) static void optimize_for_async( VALUE obj )
{ {
struct mysql* m = GetMysqlStruct(obj); struct mysql* m = GetMysqlStruct(obj);
@ -266,16 +372,19 @@ static void optimize_for_async( VALUE obj )
async_in_progress_set( obj, Qfalse ); async_in_progress_set( obj, Qfalse );
} }
// TODO does nothing currently
static void schedule_connect(VALUE obj ) static void schedule_connect(VALUE obj )
{ {
/* TODO is this old?
MYSQL* m = GetHandler(obj); MYSQL* m = GetHandler(obj);
fd_set read; fd_set read;
struct timeval tv = { tv_sec: m->options.connect_timeout, tv_usec: 0 }; struct timeval tv = { tv_sec: m->options.connect_timeout, tv_usec: 0 };
if (rb_thread_select(0, NULL, NULL, NULL, &tv) < 0) { if (rb_thread_select(0, NULL, NULL, NULL, &tv) < 0) {
rb_raise(eMysql, "connect: timeout"); rb_raise(eMysql, "connect: timeout");
} }
*/
/* /*
FD_ZERO(&read); FD_ZERO(&read);
FD_SET(m->net.fd, &read); FD_SET(m->net.fd, &read);
@ -313,8 +422,12 @@ static VALUE real_connect(int argc, VALUE* argv, VALUE klass)
obj = Data_Make_Struct(klass, struct mysql, 0, free_mysql, myp); obj = Data_Make_Struct(klass, struct mysql, 0, free_mysql, myp);
#if MYSQL_VERSION_ID >= 32200 #if MYSQL_VERSION_ID >= 32200
mysql_init(&myp->handler); mysql_init(&myp->handler); /* we get here */
if (mysql_real_connect(&myp->handler, h, u, p, d, pp, s, f) == NULL) # 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
#elif MYSQL_VERSION_ID >= 32115 #elif MYSQL_VERSION_ID >= 32115
if (mysql_real_connect(&myp->handler, h, u, p, pp, s, f) == NULL) if (mysql_real_connect(&myp->handler, h, u, p, pp, s, f) == NULL)
#else #else
@ -330,7 +443,7 @@ static VALUE real_connect(int argc, VALUE* argv, VALUE klass)
myp->query_with_result = Qtrue; myp->query_with_result = Qtrue;
rb_obj_call_init(obj, argc, argv); rb_obj_call_init(obj, argc, argv);
schedule_connect(obj); //schedule_connect(obj);
return obj; return obj;
} }
@ -340,7 +453,7 @@ static VALUE escape_string(VALUE klass, VALUE str)
{ {
VALUE ret; VALUE ret;
Check_Type(str, T_STRING); Check_Type(str, T_STRING);
ret = rb_str_new(0, (RSTRING_LEN(str))*2+1); ret = rb_enc_str_new(0, (RSTRING_LEN(str))*2+1, DEFAULT_ENCODING);
rb_str_set_len(ret, mysql_escape_string(RSTRING_PTR(ret), RSTRING_PTR(str), RSTRING_LEN(str))); rb_str_set_len(ret, mysql_escape_string(RSTRING_PTR(ret), RSTRING_PTR(str), RSTRING_LEN(str)));
return ret; return ret;
} }
@ -348,7 +461,7 @@ static VALUE escape_string(VALUE klass, VALUE str)
/* client_info() */ /* client_info() */
static VALUE client_info(VALUE klass) static VALUE client_info(VALUE klass)
{ {
return rb_tainted_str_new2(mysql_get_client_info()); return rb_enc_tainted_str_new2(mysql_get_client_info());
} }
#if MYSQL_VERSION_ID >= 32332 #if MYSQL_VERSION_ID >= 32332
@ -395,7 +508,7 @@ static VALUE real_connect2(int argc, VALUE* argv, VALUE obj)
GetMysqlStruct(obj)->connection = Qtrue; GetMysqlStruct(obj)->connection = Qtrue;
optimize_for_async(obj); optimize_for_async(obj);
schedule_connect(obj); //schedule_connect(obj);
return obj; return obj;
} }
@ -477,7 +590,7 @@ static VALUE real_escape_string(VALUE obj, VALUE str)
MYSQL* m = GetHandler(obj); MYSQL* m = GetHandler(obj);
VALUE ret; VALUE ret;
Check_Type(str, T_STRING); Check_Type(str, T_STRING);
ret = rb_str_new(0, (RSTRING_LEN(str))*2+1); ret = rb_enc_str_new(0, (RSTRING_LEN(str))*2+1, DEFAULT_ENCODING);
rb_str_set_len(ret, mysql_real_escape_string(m, RSTRING_PTR(ret), RSTRING_PTR(str), RSTRING_LEN(str))); rb_str_set_len(ret, mysql_real_escape_string(m, RSTRING_PTR(ret), RSTRING_PTR(str), RSTRING_LEN(str)));
return ret; return ret;
} }
@ -516,7 +629,7 @@ static VALUE change_user(int argc, VALUE* argv, VALUE obj)
/* character_set_name() */ /* character_set_name() */
static VALUE character_set_name(VALUE obj) static VALUE character_set_name(VALUE obj)
{ {
return rb_tainted_str_new2(mysql_character_set_name(GetHandler(obj))); return rb_enc_tainted_str_new2(mysql_character_set_name(GetHandler(obj)));
} }
#endif #endif
@ -581,7 +694,7 @@ static VALUE field_count(VALUE obj)
/* host_info() */ /* host_info() */
static VALUE host_info(VALUE obj) static VALUE host_info(VALUE obj)
{ {
return rb_tainted_str_new2(mysql_get_host_info(GetHandler(obj))); return rb_enc_tainted_str_new2(mysql_get_host_info(GetHandler(obj)));
} }
/* proto_info() */ /* proto_info() */
@ -593,14 +706,14 @@ static VALUE proto_info(VALUE obj)
/* server_info() */ /* server_info() */
static VALUE server_info(VALUE obj) static VALUE server_info(VALUE obj)
{ {
return rb_tainted_str_new2(mysql_get_server_info(GetHandler(obj))); return rb_enc_tainted_str_new2(mysql_get_server_info(GetHandler(obj)));
} }
/* info() */ /* info() */
static VALUE info(VALUE obj) static VALUE info(VALUE obj)
{ {
const char* p = mysql_info(GetHandler(obj)); const char* p = mysql_info(GetHandler(obj));
return p? rb_tainted_str_new2(p): Qnil; return p? rb_enc_tainted_str_new2(p): Qnil;
} }
/* insert_id() */ /* insert_id() */
@ -635,7 +748,7 @@ static VALUE list_dbs(int argc, VALUE* argv, VALUE obj)
n = mysql_num_rows(res); n = mysql_num_rows(res);
ret = rb_ary_new2(n); ret = rb_ary_new2(n);
for (i=0; i<n; i++) for (i=0; i<n; i++)
rb_ary_store(ret, i, rb_tainted_str_new2(mysql_fetch_row(res)[0])); rb_ary_store(ret, i, rb_enc_tainted_str_new2(mysql_fetch_row(res)[0]));
mysql_free_result(res); mysql_free_result(res);
return ret; return ret;
} }
@ -680,7 +793,7 @@ static VALUE list_tables(int argc, VALUE* argv, VALUE obj)
n = mysql_num_rows(res); n = mysql_num_rows(res);
ret = rb_ary_new2(n); ret = rb_ary_new2(n);
for (i=0; i<n; i++) for (i=0; i<n; i++)
rb_ary_store(ret, i, rb_tainted_str_new2(mysql_fetch_row(res)[0])); rb_ary_store(ret, i, rb_enc_tainted_str_new2(mysql_fetch_row(res)[0]));
mysql_free_result(res); mysql_free_result(res);
return ret; return ret;
} }
@ -744,16 +857,42 @@ static VALUE my_stat(VALUE obj)
const char* s = mysql_stat(m); const char* s = mysql_stat(m);
if (s == NULL) if (s == NULL)
mysql_raise(m); mysql_raise(m);
return rb_tainted_str_new2(s); return rb_enc_tainted_str_new2(s);
}
// 1.9 friendly
typedef struct
{
MYSQL *mysql_instance;
MYSQL_RES **store_it_here;
} mysql_result_to_here_t,
*shared_stuff_p;
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
return Qnil;
} }
/* store_result() */ /* store_result() */
static VALUE store_result(VALUE obj) static VALUE store_result(VALUE obj)
{ {
MYSQL* m = GetHandler(obj); MYSQL* m = GetHandler(obj);
MYSQL_RES* res = mysql_store_result(m); MYSQL_RES* res = NULL;
#ifndef HAVE_TBR
res = mysql_store_result(m);
#else
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);
#endif
if (res == NULL) if (res == NULL)
mysql_raise(m); mysql_raise(m);
return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled); return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
} }
@ -774,18 +913,43 @@ static VALUE use_result(VALUE obj)
} }
static VALUE res_free(VALUE); 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) */ /* query(sql) */
static VALUE query(VALUE obj, VALUE sql) static VALUE query(VALUE obj, VALUE sql)
{ {
int loop = 0; int loop = 0;
MYSQL* m = GetHandler(obj); MYSQL* m = GetHandler(obj);
QueryArgs args;
int result;
Check_Type(sql, T_STRING); Check_Type(sql, T_STRING);
if (GetMysqlStruct(obj)->connection == Qfalse) { if (GetMysqlStruct(obj)->connection == Qfalse) {
rb_raise(eMysql, "query: not connected"); rb_raise(eMysql, "query: not connected");
} }
if (rb_block_given_p()) { if (rb_block_given_p()) {
if (mysql_real_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0) #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)
mysql_raise(m); mysql_raise(m);
do { do {
MYSQL_RES* res = mysql_store_result(m); MYSQL_RES* res = mysql_store_result(m);
if (res == NULL) { if (res == NULL) {
@ -804,7 +968,16 @@ static VALUE query(VALUE obj, VALUE sql)
#endif #endif
return obj; return obj;
} }
if (mysql_real_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0)
#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)
mysql_raise(m); mysql_raise(m);
if (GetMysqlStruct(obj)->query_with_result == Qfalse) if (GetMysqlStruct(obj)->query_with_result == Qfalse)
return obj; return obj;
@ -820,6 +993,16 @@ static VALUE socket(VALUE obj)
return INT2NUM(m->net.fd); return INT2NUM(m->net.fd);
} }
/* socket_type --currently returns true or false, needs some work */
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;
}
/* blocking */ /* blocking */
static VALUE blocking(VALUE obj){ static VALUE blocking(VALUE obj){
return ( GetMysqlStruct(obj)->blocking ? Qtrue : Qfalse ); return ( GetMysqlStruct(obj)->blocking ? Qtrue : Qfalse );
@ -863,7 +1046,7 @@ static VALUE readable( int argc, VALUE* argv, VALUE obj )
if ( NIL_P( timeout ) ){ if ( NIL_P( timeout ) ){
timeout = m->net.read_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 ); return ( vio_poll_read( m->net.vio, INT2NUM(timeout) ) == 0 ? Qtrue : Qfalse );
} }
@ -905,42 +1088,20 @@ static VALUE gc_disabled( VALUE obj ){
static void validate_async_query( VALUE obj ) static void validate_async_query( VALUE obj )
{ {
MYSQL* m = GetHandler(obj);
if( async_in_progress(obj) == Qtrue ){ if( async_in_progress(obj) == Qtrue ){
async_in_progress_set(obj, Qfalse); 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."); rb_raise(eMysql, "Query out of sequence: Each call to Mysql#send_query requires a successive Mysql#get_result.");
} }
} }
static void simulate_disconnect( VALUE obj ) /* for testing */
static VALUE simulate_disconnect( VALUE obj )
{ {
MYSQL* m = GetHandler(obj); MYSQL* m = GetHandler(obj);
mysql_library_end(); mysql_library_end();
return Qnil;
} }
static int begins_with_insensitive(char *candidate, char *check_for_in_upper_case)
{
/* skip opening whitespace --tab is 11, newline is 12, cr is 15, space 32 */
char *where_at = candidate;
while( ((*where_at >= 11 && *where_at <= 15) || (*where_at == 32)) && (where_at != 0))
where_at++;
char *where_at_in_test = check_for_in_upper_case;
while(*where_at_in_test)
{
int candidate_char = *where_at;
if(candidate_char == 0)
return 0; /* end of line */
if(candidate_char >= 97 && candidate_char < 122) /* then it's upper case --lower case ify it */
candidate_char -= 32;
if(candidate_char != *where_at_in_test)
return 0;
where_at_in_test++;
where_at++;
}
return 1;
}
/* send_query(sql) */ /* send_query(sql) */
static VALUE send_query(VALUE obj, VALUE sql) static VALUE send_query(VALUE obj, VALUE sql)
@ -959,29 +1120,17 @@ static VALUE send_query(VALUE obj, VALUE sql)
if (mysql_send_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0){ if (mysql_send_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0){
idle( obj ); idle( obj );
mysql_raise(m); mysql_raise(m);
} }
async_in_progress_set( obj, Qtrue );
/* what about http://dev.mysql.com/doc/refman/5.0/en/implicit-commit.html and more? */ return Qnil;
if(
begins_with_insensitive(RSTRING_PTR(sql), "SET ") ||
begins_with_insensitive(RSTRING_PTR(sql), "BEGIN") ||
begins_with_insensitive(RSTRING_PTR(sql), "START TRANSACTION") ||
begins_with_insensitive(RSTRING_PTR(sql), "ROLLBACK") ||
begins_with_insensitive(RSTRING_PTR(sql), "LOCK ") ||
begins_with_insensitive(RSTRING_PTR(sql), "UNLOCK ") ||
begins_with_insensitive(RSTRING_PTR(sql), "USE ") ||
begins_with_insensitive(RSTRING_PTR(sql), "COMMIT") )
{
/* do not mark an async in progress --they used send_query for something that doesn't necessarily have a result--is this allowable? */
async_in_progress_set( obj, Qfalse );
} else {
async_in_progress_set( obj, Qtrue );
}
return Qnil;
} }
/* get_result */ /*
get_result
returns the mysql_result set (default) [i.e. all rows in said said]
or nil if query_with_result == false
*/
static VALUE get_result(VALUE obj) static VALUE get_result(VALUE obj)
{ {
MYSQL* m = GetHandler(obj); MYSQL* m = GetHandler(obj);
@ -999,9 +1148,11 @@ static VALUE get_result(VALUE obj)
if (GetMysqlStruct(obj)->query_with_result == Qfalse) if (GetMysqlStruct(obj)->query_with_result == Qfalse)
return obj; return obj;
if (mysql_field_count(m) == 0) if (mysql_field_count(m) == 0)
return Qnil; return Qnil;
return store_result(obj);
return store_result(obj);
} }
static void schedule_query(VALUE obj, VALUE timeout) static void schedule_query(VALUE obj, VALUE timeout)
@ -1038,7 +1189,9 @@ static int should_schedule_query(){
return rb_thread_alone() != 1; return rb_thread_alone() != 1;
} }
/* async_query(sql,timeout=nil) */ /* async_query(sql,timeout=nil)
optionally take a block
*/
static VALUE async_query(int argc, VALUE* argv, VALUE obj) static VALUE async_query(int argc, VALUE* argv, VALUE obj)
{ {
MYSQL* m = GetHandler(obj); MYSQL* m = GetHandler(obj);
@ -1164,7 +1317,7 @@ static VALUE set_server_option(VALUE obj, VALUE option)
static VALUE sqlstate(VALUE obj) static VALUE sqlstate(VALUE obj)
{ {
MYSQL *m = GetHandler(obj); MYSQL *m = GetHandler(obj);
return rb_tainted_str_new2(mysql_sqlstate(m)); return rb_enc_tainted_str_new2(mysql_sqlstate(m));
} }
#endif #endif
@ -1329,16 +1482,16 @@ static VALUE fetch_row(VALUE obj)
return Qnil; return Qnil;
ary = rb_ary_new2(n); ary = rb_ary_new2(n);
for (i=0; i<n; i++) for (i=0; i<n; i++)
rb_ary_store(ary, i, row[i]? rb_tainted_str_new(row[i], lengths[i]): Qnil); rb_ary_store(ary, i, row[i]? rb_enc_tainted_str_new(row[i], lengths[i]): Qnil);
return ary; return ary;
} }
/* process_all_hashes (internal) */ /* process_all_hashes (internal helper) */
static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, int yield) static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, int yield)
{ {
MYSQL_RES* res = GetMysqlRes(obj); MYSQL_RES* res = GetMysqlRes(obj);
unsigned int n = mysql_num_fields(res); unsigned int n = mysql_num_fields(res);
VALUE ary; VALUE ary = Qnil;
if(build_array) if(build_array)
ary = rb_ary_new(); ary = rb_ary_new();
MYSQL_ROW row = mysql_fetch_row(res); // grab one off the top, to determine the rows MYSQL_ROW row = mysql_fetch_row(res); // grab one off the top, to determine the rows
@ -1359,7 +1512,7 @@ static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, in
if (colname == Qnil) { if (colname == Qnil) {
colname = rb_ary_new2(n); colname = rb_ary_new2(n);
for (i=0; i<n; i++) { for (i=0; i<n; i++) {
VALUE s = rb_tainted_str_new2(fields[i].name); VALUE s = rb_enc_tainted_str_new2(fields[i].name);
rb_obj_freeze(s); rb_obj_freeze(s);
rb_ary_store(colname, i, s); rb_ary_store(colname, i, s);
} }
@ -1372,7 +1525,7 @@ static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, in
colname = rb_ary_new2(n); colname = rb_ary_new2(n);
for (i=0; i<n; i++) { for (i=0; i<n; i++) {
int len = strlen(fields[i].table)+strlen(fields[i].name)+1; int len = strlen(fields[i].table)+strlen(fields[i].name)+1;
VALUE s = rb_tainted_str_new(NULL, len); VALUE s = rb_enc_tainted_str_new(NULL, len);
snprintf(RSTRING_PTR(s), len+1, "%s.%s", fields[i].table, fields[i].name); snprintf(RSTRING_PTR(s), len+1, "%s.%s", fields[i].table, fields[i].name);
rb_obj_freeze(s); rb_obj_freeze(s);
rb_ary_store(colname, i, s); rb_ary_store(colname, i, s);
@ -1388,7 +1541,7 @@ static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, in
hash = rb_hash_new(); hash = rb_hash_new();
lengths = mysql_fetch_lengths(res); lengths = mysql_fetch_lengths(res);
for (i=0; i<n; i++) { for (i=0; i<n; i++) {
rb_hash_aset(hash, rb_ary_entry(colname, i), row[i]? rb_tainted_str_new(row[i], lengths[i]): Qnil); rb_hash_aset(hash, rb_ary_entry(colname, i), row[i]? rb_enc_tainted_str_new(row[i], lengths[i]): Qnil);
} }
if(build_array) if(build_array)
rb_ary_push(ary, hash); rb_ary_push(ary, hash);
@ -1405,6 +1558,8 @@ static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, in
if(yield) if(yield)
return obj; return obj;
return Qnil; /* we should never get here -- this takes out a compiler warning */
} }
/* fetch_hash2 (internal) */ /* fetch_hash2 (internal) */
@ -1422,12 +1577,12 @@ static VALUE fetch_hash2(VALUE obj, VALUE with_table)
return Qnil; return Qnil;
hash = rb_hash_new(); hash = rb_hash_new();
if (with_table == Qfalse) { if (with_table == Qnil || with_table == Qfalse) {
colname = rb_iv_get(obj, "colname"); colname = rb_iv_get(obj, "colname");
if (colname == Qnil) { if (colname == Qnil) {
colname = rb_ary_new2(n); colname = rb_ary_new2(n);
for (i=0; i<n; i++) { for (i=0; i<n; i++) {
VALUE s = rb_tainted_str_new2(fields[i].name); VALUE s = rb_enc_tainted_str_new2(fields[i].name);
rb_obj_freeze(s); rb_obj_freeze(s);
rb_ary_store(colname, i, s); rb_ary_store(colname, i, s);
} }
@ -1440,7 +1595,7 @@ static VALUE fetch_hash2(VALUE obj, VALUE with_table)
colname = rb_ary_new2(n); colname = rb_ary_new2(n);
for (i=0; i<n; i++) { for (i=0; i<n; i++) {
int len = strlen(fields[i].table)+strlen(fields[i].name)+1; int len = strlen(fields[i].table)+strlen(fields[i].name)+1;
VALUE s = rb_tainted_str_new(NULL, len); VALUE s = rb_enc_tainted_str_new(NULL, len);
snprintf(RSTRING_PTR(s), len+1, "%s.%s", fields[i].table, fields[i].name); snprintf(RSTRING_PTR(s), len+1, "%s.%s", fields[i].table, fields[i].name);
rb_obj_freeze(s); rb_obj_freeze(s);
rb_ary_store(colname, i, s); rb_ary_store(colname, i, s);
@ -1450,7 +1605,7 @@ static VALUE fetch_hash2(VALUE obj, VALUE with_table)
} }
} }
for (i=0; i<n; i++) { for (i=0; i<n; i++) {
rb_hash_aset(hash, rb_ary_entry(colname, i), row[i]? rb_tainted_str_new(row[i], lengths[i]): Qnil); rb_hash_aset(hash, rb_ary_entry(colname, i), row[i]? rb_enc_tainted_str_new(row[i], lengths[i]): Qnil);
} }
return hash; return hash;
} }
@ -1582,7 +1737,7 @@ static VALUE field_hash(VALUE obj)
static VALUE field_inspect(VALUE obj) static VALUE field_inspect(VALUE obj)
{ {
VALUE n = rb_iv_get(obj, "name"); VALUE n = rb_iv_get(obj, "name");
VALUE s = rb_str_new(0, RSTRING_LEN(n) + 16); VALUE s = rb_enc_str_new(0, RSTRING_LEN(n) + 16, DEFAULT_ENCODING);
sprintf(RSTRING_PTR(s), "#<Mysql::Field:%s>", RSTRING_PTR(n)); sprintf(RSTRING_PTR(s), "#<Mysql::Field:%s>", RSTRING_PTR(n));
return s; return s;
} }
@ -1641,7 +1796,7 @@ static void mysql_stmt_raise(MYSQL_STMT* s)
{ {
VALUE e = rb_exc_new2(eMysql, mysql_stmt_error(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, "errno", INT2FIX(mysql_stmt_errno(s)));
rb_iv_set(e, "sqlstate", rb_tainted_str_new2(mysql_stmt_sqlstate(s))); rb_iv_set(e, "sqlstate", rb_enc_tainted_str_new2(mysql_stmt_sqlstate(s)));
rb_exc_raise(e); rb_exc_raise(e);
} }
@ -1788,12 +1943,12 @@ static VALUE stmt_execute(int argc, VALUE *argv, VALUE obj)
s->param.bind[i].buffer = &(s->param.buffer[i]); s->param.bind[i].buffer = &(s->param.buffer[i]);
t.second_part = 0; t.second_part = 0;
t.neg = 0; t.neg = 0;
t.second = FIX2INT(RARRAY(a)->ptr[0]); t.second = FIX2INT(rb_ary_entry(a, 0));
t.minute = FIX2INT(RARRAY(a)->ptr[1]); t.minute = FIX2INT(rb_ary_entry(a, 1));
t.hour = FIX2INT(RARRAY(a)->ptr[2]); t.hour = FIX2INT(rb_ary_entry(a, 2));
t.day = FIX2INT(RARRAY(a)->ptr[3]); t.day = FIX2INT(rb_ary_entry(a, 3));
t.month = FIX2INT(RARRAY(a)->ptr[4]); t.month = FIX2INT(rb_ary_entry(a, 4));
t.year = FIX2INT(RARRAY(a)->ptr[5]); t.year = FIX2INT(rb_ary_entry(a, 5));
*(MYSQL_TIME*)&(s->param.buffer[i]) = t; *(MYSQL_TIME*)&(s->param.buffer[i]) = t;
} else if (CLASS_OF(argv[i]) == cMysqlTime) { } else if (CLASS_OF(argv[i]) == cMysqlTime) {
MYSQL_TIME t; MYSQL_TIME t;
@ -1955,7 +2110,7 @@ static VALUE stmt_fetch(VALUE obj)
case MYSQL_TYPE_NEWDECIMAL: case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_BIT: case MYSQL_TYPE_BIT:
#endif #endif
v = rb_tainted_str_new(s->result.bind[i].buffer, s->result.length[i]); v = rb_enc_tainted_str_new(s->result.bind[i].buffer, s->result.length[i]);
break; break;
default: default:
rb_raise(rb_eTypeError, "unknown buffer_type: %d", s->result.bind[i].buffer_type); rb_raise(rb_eTypeError, "unknown buffer_type: %d", s->result.bind[i].buffer_type);
@ -2144,7 +2299,7 @@ static VALUE stmt_send_long_data(VALUE obj, VALUE col, VALUE data)
static VALUE stmt_sqlstate(VALUE obj) static VALUE stmt_sqlstate(VALUE obj)
{ {
struct mysql_stmt* s = DATA_PTR(obj); struct mysql_stmt* s = DATA_PTR(obj);
return rb_tainted_str_new2(mysql_stmt_sqlstate(s->stmt)); return rb_enc_tainted_str_new2(mysql_stmt_sqlstate(s->stmt));
} }
/*------------------------------- /*-------------------------------
@ -2670,4 +2825,4 @@ void Init_mysql(void)
/* Mysql::Error constant */ /* Mysql::Error constant */
#define rb_define_mysql_const(s) rb_define_const(eMysql, #s, INT2NUM(s)) #define rb_define_mysql_const(s) rb_define_const(eMysql, #s, INT2NUM(s))
#include "error_const.h" #include "error_const.h"
} }

View File

@ -1,19 +1,23 @@
require 'mysql' 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.
#
# 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 class Mysql
def async_query(sql, timeout = nil) def ruby_async_query(sql, timeout = nil) # known to deadlock TODO
send_query(sql) send_query(sql)
select [ (@sockets ||= {})[socket] ||= IO.new(socket) ], nil, nil, nil select [ (@sockets ||= {})[socket] ||= IO.new(socket) ], nil, nil, nil
get_result get_result
end 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 end
class Mysql::Result
def all_hashes
rows = []
each_hash { |row| rows << row }
rows
end
end

41
mysqlplus.gemspec Executable file → Normal file
View File

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

9
test/RUN_ALL_TESTS.RB Normal file
View File

@ -0,0 +1,9 @@
# 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,8 +1,4 @@
# shows the effect of using .all_hashes instead of looping on each hash require 'create_test_db'
# 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
require 'mysqlplus'
use_the_all_hashes_method = true use_the_all_hashes_method = true
@ -12,16 +8,15 @@ $start = Time.now
$connections = [] $connections = []
$count.times do $count.times do
$connections << Mysql.real_connect('localhost','root', '', 'local_leadgen_dev') $connections << Mysql.real_connect('localhost','root', '', 'local_test_db')
end end
puts 'connection pool ready'
$threads = [] $threads = []
$count.times do |i| $count.times do |i|
$threads << Thread.new do $threads << Thread.new do
query = "select * from campus_zips" query = "select * from test_table"
puts "sending query on connection #{i}" puts "sending query on connection #{i}"
conn = $connections[i] conn = $connections[i]
result = conn.async_query(query) result = conn.async_query(query)

View File

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

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

22
test/create_test_db.rb Normal file
View File

@ -0,0 +1,22 @@
# 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,4 +1,3 @@
require 'rubygems'
require 'mysqlplus' require 'mysqlplus'
require 'benchmark' require 'benchmark'
@ -38,4 +37,4 @@ Benchmark.bmbm do |x|
end end
end end
puts $gc_stats.join( ' | ' ) puts $gc_stats.join( ' | ' )

View File

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

View File

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

View File

@ -5,8 +5,7 @@
# from .82s to .62s # 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 # 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. # 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 do_the_use_query_optimization = true
@ -16,7 +15,7 @@ $start = Time.now
$connections = [] $connections = []
$count.times do $count.times do
$connections << Mysql.real_connect('localhost','root', '', 'local_leadgen_dev') $connections << Mysql.real_connect('localhost','root', '', 'local_test_db')
end end
puts 'connection pool ready' puts 'connection pool ready'
@ -28,7 +27,7 @@ $count.times do |i|
puts "sending query on connection #{i}" puts "sending query on connection #{i}"
conn = $connections[i] conn = $connections[i]
saved = [] saved = []
query = "select * from campus_zips" query = "select * from test_table"
if do_the_use_query_optimization if do_the_use_query_optimization
conn.query_with_result=false conn.query_with_result=false
result = conn.async_query(query) result = conn.async_query(query)

View File

@ -1,4 +1,3 @@
require 'rubygems'
require 'mysqlplus' require 'mysqlplus'
class MysqlTest class MysqlTest
@ -196,4 +195,4 @@ class ThreadedMysqlTest < MysqlTest
end end
end end
end end

View File

@ -1,7 +1,7 @@
require 'mysqlplus'
require 'rubygems' require 'rubygems'
require 'sequel' require 'sequel'
require 'mysqlplus'
class Mysql class Mysql
unless method_defined? :sync_query unless method_defined? :sync_query
alias :sync_query :query alias :sync_query :query
@ -21,4 +21,4 @@ start = Time.now
end end
end.map{|t| t.join } end.map{|t| t.join }
p (Time.now - start) p (Time.now - start)