Compare commits

..

69 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
Lourens Naude
79f742908d Use rb_thread_alone exclusively to determine if the query should be Thread scheduled 2008-11-01 17:12:03 +00: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
Lourens Naude
57712a64e6 Conditional schedule cleanup 2008-10-31 15:48:28 +00:00
Lourens Naude
4640893f27 Do not schedule in a single threaded environment 2008-10-31 15:09:33 +00:00
Lourens Naude
48a61dd627 Introduce Mysql#idle? && Mysql#busy? as Threaded connection pool helpers for Mysql#c_async_query 2008-10-20 01:46:03 +01:00
Lourens Naude
1af85383eb Applied Roger's async validation patch, with a minimal test case 2008-10-19 23:17:42 +01:00
Lourens Naude
9d66a3b71e Piggy back schedule loop on MYSQL_STATUS_READY 2008-10-19 23:04:33 +01:00
Lourens Naude
e81e145c15 Prefer Objects to GC as label 2008-10-09 02:13:40 +01:00
Lourens Naude
a9fea270f5 Better GC stats 2008-10-09 02:01:57 +01:00
Lourens Naude
35d2545c17 Selective enable || disable GC for result retrieval with Mysql#disable_gc = true|false 2008-10-08 22:11:14 +01:00
Lourens Naude
8e6f300f9d Introduce Mysql#reconnected? 2008-10-08 17:09:47 +01:00
Lourens Naude
f57c2a6576 Let #c_async_query support a block syntax that yields the result; ensure async in progress is negated by #get_result 2008-10-08 05:17:01 +01:00
Lourens Naude
4e5967bd24 Do not schedule && retrieve the result if #query_with_result is false 2008-10-06 23:32:43 +01: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
Lourens Naude
85141abf09 Validate async queries 2008-10-02 01:56:10 +01: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
21 changed files with 1333 additions and 718 deletions

17
README
View File

@ -2,6 +2,8 @@
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
@ -20,6 +22,18 @@ An enhanced MySQL database driver. With support for async operations and threade
--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
@ -30,3 +44,6 @@ 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

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,23 +1,75 @@
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")
#exit 1 unless have_library("libmysql")
have_library("libmysql") or die "can't find libmysql."
elsif mc = with_config('mysql-config') then
mc = 'mysql_config' if mc == true
cflags = `#{mc} --cflags`.chomp
exit 1 if $? != 0
libs = `#{mc} --libs`.chomp
exit 1 if $? != 0
#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
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?
have_library(libs.shift)
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..."
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
@ -29,12 +81,14 @@ 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
#exit 1
die "can't find 'mysql.h'."
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
@ -49,9 +103,10 @@ end
if /mswin32/ =~ RUBY_PLATFORM && !/-E/.match(cpp)
cpp << " -E"
end
unless system "#{cpp} > confout" then
exit 1
end
#unless system "#{cpp} > confout" then
# exit 1
#end
exec_command("#{cpp} > confout")
File.unlink "conftest.c"
error_syms = []
@ -73,4 +128,10 @@ 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,16 +6,49 @@
#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>
@ -60,9 +93,13 @@ 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;
@ -175,12 +212,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_tainted_str_new2(mysql_sqlstate(m)));
rb_iv_set(e, "sqlstate", rb_enc_tainted_str_new2(mysql_sqlstate(m)));
#endif
rb_exc_raise(e);
}
static VALUE mysqlres2obj(MYSQL_RES* res)
static VALUE mysqlres2obj(MYSQL_RES* res, VALUE gc_disabled)
{
VALUE obj;
struct mysql_res* resp;
@ -190,8 +227,10 @@ static VALUE mysqlres2obj(MYSQL_RES* res)
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;
}
@ -202,9 +241,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_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, "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, "type", INT2NUM(f->type));
rb_iv_set(obj, "length", INT2NUM(f->length));
rb_iv_set(obj, "max_length", INT2NUM(f->max_length));
@ -227,10 +266,135 @@ 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)
{
@ -258,27 +422,28 @@ 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);
if (mysql_real_connect(&myp->handler, h, u, p, d, pp, s, f) == NULL)
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
#elif MYSQL_VERSION_ID >= 32115
if (mysql_real_connect(&myp->handler, h, u, p, pp, s, f) == NULL)
#else
if (mysql_real_connect(&myp->handler, h, u, p, pp, s) == NULL)
#endif
mysql_raise(&myp->handler);
myp->handler.reconnect = 0;
myp->connection = Qtrue;
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 );
optimize_for_async(obj);
myp->query_with_result = Qtrue;
rb_obj_call_init(obj, argc, argv);
//schedule_connect(obj);
return obj;
}
@ -288,7 +453,7 @@ static VALUE escape_string(VALUE klass, VALUE str)
{
VALUE ret;
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)));
return ret;
}
@ -296,7 +461,7 @@ static VALUE escape_string(VALUE klass, VALUE str)
/* client_info() */
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
@ -341,6 +506,9 @@ static VALUE real_connect2(int argc, VALUE* argv, VALUE obj)
mysql_raise(m);
m->reconnect = 0;
GetMysqlStruct(obj)->connection = Qtrue;
optimize_for_async(obj);
//schedule_connect(obj);
return obj;
}
@ -422,7 +590,7 @@ static VALUE real_escape_string(VALUE obj, VALUE str)
MYSQL* m = GetHandler(obj);
VALUE ret;
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)));
return ret;
}
@ -461,7 +629,7 @@ static VALUE change_user(int argc, VALUE* argv, VALUE obj)
/* character_set_name() */
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
@ -526,7 +694,7 @@ static VALUE field_count(VALUE obj)
/* host_info() */
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() */
@ -538,14 +706,14 @@ static VALUE proto_info(VALUE obj)
/* server_info() */
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() */
static VALUE info(VALUE 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() */
@ -580,7 +748,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_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);
return ret;
}
@ -595,7 +763,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);
return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
}
/* list_processes() */
@ -605,7 +773,7 @@ static VALUE list_processes(VALUE obj)
MYSQL_RES* res = mysql_list_processes(m);
if (res == NULL)
mysql_raise(m);
return mysqlres2obj(res);
return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
}
/* list_tables(table=nil) */
@ -625,7 +793,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_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);
return ret;
}
@ -689,9 +857,10 @@ static VALUE my_stat(VALUE obj)
const char* s = mysql_stat(m);
if (s == NULL)
mysql_raise(m);
return rb_tainted_str_new2(s);
return rb_enc_tainted_str_new2(s);
}
// 1.9 friendly
typedef struct
{
MYSQL *mysql_instance;
@ -703,7 +872,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 runs a good long while for very large queries
*(settings->store_it_here) = mysql_store_result(settings->mysql_instance); // this one line runs a good long while for very large queries
return Qnil;
}
@ -718,13 +887,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); /* not sure if this should be RUBY_UBF_IO or RUBY_UBF_PROCESS here -- see Ruby 1.9 ChangeLog */
rb_thread_blocking_region(store_result_to_location, (void *) &linker, RUBY_UBF_IO, 0);
#endif
if (res == NULL)
mysql_raise(m);
return mysqlres2obj(res);
return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
}
/* thread_id() */
@ -740,29 +909,54 @@ static VALUE use_result(VALUE obj)
MYSQL_RES* res = mysql_use_result(m);
if (res == NULL)
mysql_raise(m);
return mysqlres2obj(res);
return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
}
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()) {
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);
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);
VALUE robj = mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
rb_ensure(rb_yield, robj, res_free, robj);
}
#if MYSQL_VERSION_ID >= 40101
@ -774,7 +968,16 @@ static VALUE query(VALUE obj, VALUE sql)
#endif
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);
if (GetMysqlStruct(obj)->query_with_result == Qfalse)
return obj;
@ -789,17 +992,46 @@ static VALUE socket(VALUE obj)
MYSQL* m = GetHandler(obj);
return INT2NUM(m->net.fd);
}
/* socket_type */
/* socket_type --currently returns true or false, needs some work */
static VALUE socket_type(VALUE obj)
{
MYSQL* m = GetHandler(obj);
VALUE description = vio_description( m->net.vio );
return NILorSTRING( description );
if(vio_description(m->net.vio))
return Qtrue; // TODO return a ruby string
else
return Qnil;
}
/* blocking */
static VALUE blocking(VALUE obj){
return ( GetMysqlStruct(obj)->blocking ? Qtrue : Qfalse );
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) */
@ -814,71 +1046,177 @@ 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) {
rb_raise(eMysql, "query: not connected");
if (GetMysqlStruct(obj)->connection == Qfalse && async_in_progress(obj) == Qtrue ) {
idle( obj );
rb_raise(eMysql, "query: not connected");
}
if (mysql_send_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0)
mysql_raise(m);
return Qnil;
validate_async_query(obj);
if (mysql_send_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0){
idle( obj );
mysql_raise(m);
}
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)
{
MYSQL* m = GetHandler(obj);
async_in_progress_set( obj, Qfalse );
if (GetMysqlStruct(obj)->connection == Qfalse) {
rb_raise(eMysql, "query: not connected");
idle( obj );
rb_raise(eMysql, "query: not connected");
}
if (mysql_read_query_result(m) != 0)
mysql_raise(m);
if (mysql_read_query_result(m) != 0){
idle( obj );
mysql_raise(m);
}
if (GetMysqlStruct(obj)->query_with_result == Qfalse)
return obj;
if (mysql_field_count(m) == 0)
return Qnil;
return Qnil;
return store_result(obj);
}
static VALUE schedule(VALUE obj, VALUE timeout)
static void schedule_query(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 };
FD_ZERO(&read);
FD_SET(m->net.fd, &read);
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 );
rb_raise(eMysql, "query: timeout");
}
if (ret == 0) {
continue;
}
if (rb_thread_select(m->net.fd + 1, &read, NULL, NULL, &tv) < 0) {
rb_raise(eMysql, "query: timeout");
if (m->status == MYSQL_STATUS_READY){
break;
}
}
}
/* async_query(sql,timeout=nil) */
static int should_schedule_query(){
return rb_thread_alone() != 1;
}
/* async_query(sql,timeout=nil)
optionally take a block
*/
static VALUE async_query(int argc, VALUE* argv, VALUE obj)
{
MYSQL* m = GetHandler(obj);
VALUE sql, timeout;
MYSQL* m = GetHandler(obj);
VALUE sql, timeout;
rb_scan_args(argc, argv, "11", &sql, &timeout);
rb_scan_args(argc, argv, "11", &sql, &timeout);
send_query(obj,sql);
async_in_progress_set( obj, Qfalse );
schedule(obj, timeout);
busy(obj);
return get_result(obj);
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
@ -979,7 +1317,7 @@ static VALUE set_server_option(VALUE obj, VALUE option)
static VALUE sqlstate(VALUE obj)
{
MYSQL *m = GetHandler(obj);
return rb_tainted_str_new2(mysql_sqlstate(m));
return rb_enc_tainted_str_new2(mysql_sqlstate(m));
}
#endif
@ -1144,16 +1482,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_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;
}
/* process_all_hashes (internal) */
/* process_all_hashes (internal helper) */
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;
VALUE ary = Qnil;
if(build_array)
ary = rb_ary_new();
MYSQL_ROW row = mysql_fetch_row(res); // grab one off the top, to determine the rows
@ -1174,7 +1512,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_tainted_str_new2(fields[i].name);
VALUE s = rb_enc_tainted_str_new2(fields[i].name);
rb_obj_freeze(s);
rb_ary_store(colname, i, s);
}
@ -1187,7 +1525,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_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);
rb_obj_freeze(s);
rb_ary_store(colname, i, s);
@ -1203,7 +1541,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_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)
rb_ary_push(ary, hash);
@ -1220,6 +1558,8 @@ 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) */
@ -1237,12 +1577,12 @@ static VALUE fetch_hash2(VALUE obj, VALUE with_table)
return Qnil;
hash = rb_hash_new();
if (with_table == Qfalse) {
if (with_table == Qnil || 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_tainted_str_new2(fields[i].name);
VALUE s = rb_enc_tainted_str_new2(fields[i].name);
rb_obj_freeze(s);
rb_ary_store(colname, i, s);
}
@ -1255,7 +1595,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_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);
rb_obj_freeze(s);
rb_ary_store(colname, i, s);
@ -1265,7 +1605,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_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;
}
@ -1397,7 +1737,7 @@ static VALUE field_hash(VALUE obj)
static VALUE field_inspect(VALUE obj)
{
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));
return s;
}
@ -1456,7 +1796,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_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);
}
@ -1603,12 +1943,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(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]);
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));
*(MYSQL_TIME*)&(s->param.buffer[i]) = t;
} else if (CLASS_OF(argv[i]) == cMysqlTime) {
MYSQL_TIME t;
@ -1770,7 +2110,7 @@ static VALUE stmt_fetch(VALUE obj)
case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_BIT:
#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;
default:
rb_raise(rb_eTypeError, "unknown buffer_type: %d", s->result.bind[i].buffer_type);
@ -1911,7 +2251,7 @@ static VALUE stmt_result_metadata(VALUE obj)
mysql_stmt_raise(s->stmt);
return Qnil;
}
return mysqlres2obj(res);
return mysqlres2obj(res, Qfalse);
}
/* row_seek(offset) */
@ -1959,7 +2299,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_tainted_str_new2(mysql_stmt_sqlstate(s->stmt));
return rb_enc_tainted_str_new2(mysql_stmt_sqlstate(s->stmt));
}
/*-------------------------------
@ -2169,12 +2509,22 @@ 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);
@ -2475,4 +2825,4 @@ void Init_mysql(void)
/* Mysql::Error constant */
#define rb_define_mysql_const(s) rb_define_const(eMysql, #s, INT2NUM(s))
#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
def async_query(sql, timeout = nil)
def ruby_async_query(sql, timeout = nil) # known to deadlock TODO
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 Executable file → Normal file
View File

@ -1,29 +1,30 @@
Gem::Specification.new do |s|
s.name = "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.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.description = "Enhanced Ruby MySQL driver"
s.has_rdoc = true
s.authors = ["Muhammad A. Ali"]
s.authors = ["Muhammad A. Ali", "Jeremy Suriel", "John Bintz"]
s.platform = Gem::Platform::RUBY
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.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.rdoc_options = ["--main", "README"]
s.extra_rdoc_files = ["README"]
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'

37
test/all_hashes_test.rb Normal file
View File

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

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

@ -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'

40
test/gc_benchmark_test.rb Normal file
View File

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

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

34
test/out_of_sync_test.rb Normal file
View File

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

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

18
test/reconnected_test.rb Normal file
View File

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

View File

@ -1,43 +0,0 @@
# 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,4 +1,3 @@
require 'rubygems'
require 'mysqlplus'
class MysqlTest
@ -196,4 +195,4 @@ class ThreadedMysqlTest < MysqlTest
end
end
end
end

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
@ -21,4 +21,4 @@ start = Time.now
end
end.map{|t| t.join }
p (Time.now - start)
p (Time.now - start)