mysqlplus 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README CHANGED
@@ -29,6 +29,10 @@ An enhanced MySQL database driver. With support for async operations and threade
29
29
  Same with other scripts that want to use it--just require 'mysqlplus' BEFORE you require 'mysql' and it will
30
30
  load the asynchronous version, then ignore the sequent require 'mysql' call.
31
31
 
32
+ == Other helpful mysql utilities:
33
+ slim attributes http://slim-attributes.rubyforge.org/ boosts mysql speed by using arrays instead of hashed lookup.
34
+ Hash extension gem also results in speedups when used: http://blog.chak.org/2008/02/09/speeding-up-activerecord-with-hashes-take-2/
35
+
32
36
  === Credits
33
37
 
34
38
  Aman Gupta, for help in threading support and improved tests
@@ -38,3 +42,6 @@ Lourens Naude for 1.9 integration help.
38
42
 
39
43
  === License
40
44
  Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
45
+
46
+ == Mailing list
47
+ http://groups.google.com/group/never-block?hl=en
data/TODO_LIST CHANGED
@@ -1,13 +1,12 @@
1
1
  TODO list:
2
2
 
3
- Is there a quick, cheap, easy way to test for writability so we don't have to use select on the way in? Does it take long anyway?
3
+ 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?
4
4
 
5
- Docs for how to use with rails, etc. [there is mysqlplus_adapter, so maybe we're good there].
5
+ Some of the tests currently might "think" they are using the ruby select but in reality be using the C select.
6
6
 
7
- Merge in lourens' branch -- I like the double check for not using send_query twice in a row! [rdp]
7
+ gc_disabled is unused
8
8
 
9
- look into http://coderrr.wordpress.com/2009/01/11/ruby-and-mysqlplus-select-deadlock/
9
+ if they call get_result twice consecutively it should blow (and maybe already does).
10
10
 
11
- Some of the test/* anticipate a certain database existing--todo create it first, then drop it [?]
12
-
13
- critical todo list:
11
+ mingw support
12
+ add slim attributes right in there :)
@@ -533,4 +533,7 @@
533
533
  rb_define_mysql_const(ER_ADMIN_WRONG_MRG_TABLE);
534
534
  rb_define_mysql_const(ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT);
535
535
  rb_define_mysql_const(ER_NAME_BECOMES_EMPTY);
536
+ rb_define_mysql_const(ER_AMBIGUOUS_FIELD_TERM);
537
+ rb_define_mysql_const(ER_LOAD_DATA_INVALID_COLUMN);
538
+ rb_define_mysql_const(ER_LOG_PURGE_NO_FILE);
536
539
  rb_define_mysql_const(ER_ERROR_LAST);
@@ -45,8 +45,10 @@ else
45
45
  exit 1
46
46
  end
47
47
 
48
+ # check for 1.9
48
49
  if have_func('rb_thread_blocking_region') and have_macro('RUBY_UBF_IO', 'ruby.h')
49
- $CPPFLAGS << " -DHAVE_TBR"
50
+ $CFLAGS += " -DHAVE_TBR "
51
+ $CPPFLAGS << " -DHAVE_TBR "
50
52
  end
51
53
 
52
54
  # make mysql constant
@@ -85,4 +87,4 @@ File.open('error_const.h', 'w') do |f|
85
87
  end
86
88
  end
87
89
 
88
- create_makefile("mysql")
90
+ create_makefile("mysql")
@@ -6,6 +6,7 @@
6
6
 
7
7
  #include <ruby.h>
8
8
  #include <errno.h>
9
+ #include <stdarg.h>
9
10
  #ifndef RSTRING_PTR
10
11
  #define RSTRING_PTR(str) RSTRING(str)->ptr
11
12
  #endif
@@ -60,9 +61,13 @@ struct mysql {
60
61
  MYSQL handler;
61
62
  char connection;
62
63
  char query_with_result;
64
+ char gc_disabled;
63
65
  char blocking;
66
+ int async_in_progress;
67
+ char busy;
64
68
  };
65
69
 
70
+ // a wrapper for mysql_res's so we can detect double frees
66
71
  struct mysql_res {
67
72
  MYSQL_RES* res;
68
73
  char freed;
@@ -180,7 +185,7 @@ static void mysql_raise(MYSQL* m)
180
185
  rb_exc_raise(e);
181
186
  }
182
187
 
183
- static VALUE mysqlres2obj(MYSQL_RES* res)
188
+ static VALUE mysqlres2obj(MYSQL_RES* res, VALUE gc_disabled)
184
189
  {
185
190
  VALUE obj;
186
191
  struct mysql_res* resp;
@@ -229,10 +234,135 @@ static VALUE init(VALUE klass)
229
234
  mysql_init(&myp->handler);
230
235
  myp->connection = Qfalse;
231
236
  myp->query_with_result = Qtrue;
237
+ myp->gc_disabled = Qtrue;
232
238
  rb_obj_call_init(obj, 0, NULL);
233
239
  return obj;
234
240
  }
235
241
 
242
+ // =========== a 1.9 rb_thread_blocking_region simplifier attempt
243
+ #ifdef HAVE_TBR
244
+
245
+ typedef struct
246
+ {
247
+ void *func_pointer;
248
+ int param_count;
249
+ void *args[10];
250
+ } arg_holder, *arg_holder2;
251
+
252
+ // here's how to make rb_thread_blocking_region much cleaner and easier
253
+ // syntax: param_count+2, func_pointer to call, [RUBY_UBF_IO or RUBY_UBF_PROCESS], param1, param2...
254
+ // 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
255
+ // ex: (int) returned_this = rb_thread_blocking_region_variable_params(10, &method_name, RUBY_UBF_IO, param1, param2, param3, param4, param5, param6, param7, param8)
256
+
257
+ static void *call_single_function_rb_thread_blocking_region(void *arg_holder_in);
258
+
259
+ void *rb_thread_blocking_region_variable_params(int number, ...)
260
+ {
261
+ va_list param_pt;
262
+ va_start(param_pt, number);
263
+ int index;
264
+ arg_holder param_storer;
265
+ void *func_pointer = va_arg(param_pt, void *);
266
+ void *interrupter = va_arg(param_pt, void *);
267
+ param_storer.func_pointer = func_pointer;
268
+ int real_param_count = number - 2;
269
+ param_storer.param_count = real_param_count;
270
+ for(index = 0 ; index < real_param_count ; index++)
271
+ {
272
+ void *arg = va_arg(param_pt, void *);
273
+ param_storer.args[index] = arg;
274
+
275
+ }
276
+ va_end(param_pt);
277
+
278
+ return (void *) rb_thread_blocking_region((rb_blocking_function_t *)call_single_function_rb_thread_blocking_region, (void *) &param_storer, interrupter, 0);
279
+
280
+ }
281
+
282
+ // used internally
283
+ static void * call_single_function_rb_thread_blocking_region(void *arg_holder_in)
284
+ {
285
+ arg_holder *params_and_func = (arg_holder *) arg_holder_in;
286
+ int param_count = params_and_func->param_count;
287
+ void *result;
288
+ switch(param_count)
289
+ {
290
+ case 3:;
291
+ void * (*pt3Func)(void *, void *, void *) = params_and_func->func_pointer;
292
+ result = (*pt3Func)(params_and_func->args[0], params_and_func->args[1], params_and_func->args[2]);
293
+ break;
294
+ case 6:;
295
+ void * (*pt6Func)(void *, void *, void *, void *, void *, void *) = params_and_func->func_pointer;
296
+ 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]);
297
+ break;
298
+ case 8:;
299
+ void * (*pt8Func)(void *, void *, void *, void *, void *, void *, void *, void *) = params_and_func->func_pointer;
300
+ 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]);
301
+ break;
302
+ default:;
303
+ printf("UNknown param count--please add it! %d\n", param_count);
304
+ result = (void *) Qnil;
305
+ }
306
+
307
+ return result;
308
+ }
309
+
310
+ #endif
311
+
312
+ static VALUE connection_identifier( VALUE obj )
313
+ {
314
+ MYSQL* m = GetHandler(obj);
315
+ return mysql_thread_id( m );
316
+ }
317
+
318
+ static VALUE async_in_progress( VALUE obj )
319
+ {
320
+ struct mysql* m = GetMysqlStruct(obj);
321
+ return ( m->async_in_progress == connection_identifier(obj) ) ? Qtrue : Qfalse;
322
+ }
323
+
324
+ static VALUE async_in_progress_set( VALUE obj, VALUE flag )
325
+ {
326
+ struct mysql* m = GetMysqlStruct(obj);
327
+ m->async_in_progress = (flag == Qnil || flag == Qfalse) ? 0 : connection_identifier(obj);
328
+ return flag;
329
+ }
330
+
331
+ // does this actually really do anything helpful? Not sure.
332
+ static void optimize_for_async( VALUE obj )
333
+ {
334
+ struct mysql* m = GetMysqlStruct(obj);
335
+ my_bool was_blocking;
336
+ vio_blocking(m->handler.net.vio, 0, &was_blocking);
337
+ m->blocking = vio_is_blocking( m->handler.net.vio );
338
+
339
+ vio_fastsend( m->handler.net.vio );
340
+ async_in_progress_set( obj, Qfalse );
341
+ }
342
+
343
+ // TODO does nothing currently
344
+ static void schedule_connect(VALUE obj )
345
+ {
346
+ /* TODO is this old?
347
+ MYSQL* m = GetHandler(obj);
348
+ fd_set read;
349
+
350
+ struct timeval tv = { tv_sec: m->options.connect_timeout, tv_usec: 0 };
351
+ if (rb_thread_select(0, NULL, NULL, NULL, &tv) < 0) {
352
+ rb_raise(eMysql, "connect: timeout");
353
+ }
354
+ */
355
+
356
+ /*
357
+ FD_ZERO(&read);
358
+ FD_SET(m->net.fd, &read);
359
+
360
+ if (rb_thread_select(m->net.fd + 1, &read, NULL, NULL, &tv) < 0) {
361
+ rb_raise(eMysql, "connect: timeout");
362
+ }
363
+ */
364
+ }
365
+
236
366
  /* real_connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, sock=nil, flag=nil) */
237
367
  static VALUE real_connect(int argc, VALUE* argv, VALUE klass)
238
368
  {
@@ -260,27 +390,28 @@ static VALUE real_connect(int argc, VALUE* argv, VALUE klass)
260
390
 
261
391
  obj = Data_Make_Struct(klass, struct mysql, 0, free_mysql, myp);
262
392
  #if MYSQL_VERSION_ID >= 32200
263
- mysql_init(&myp->handler);
264
- if (mysql_real_connect(&myp->handler, h, u, p, d, pp, s, f) == NULL)
393
+ mysql_init(&myp->handler); /* we get here */
394
+ # ifdef HAVE_TBR
395
+ 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)
396
+ # else
397
+ if(mysql_real_connect(&myp->handler, h, u, p, d, pp, s, f) == NULL)
398
+ # endif
265
399
  #elif MYSQL_VERSION_ID >= 32115
266
400
  if (mysql_real_connect(&myp->handler, h, u, p, pp, s, f) == NULL)
267
401
  #else
268
402
  if (mysql_real_connect(&myp->handler, h, u, p, pp, s) == NULL)
269
403
  #endif
270
404
  mysql_raise(&myp->handler);
271
-
405
+
272
406
  myp->handler.reconnect = 0;
273
407
  myp->connection = Qtrue;
274
408
 
275
- my_bool was_blocking;
276
-
277
- vio_blocking(myp->handler.net.vio, 0, &was_blocking);
278
- myp->blocking = vio_is_blocking( myp->handler.net.vio );
279
-
280
- vio_fastsend( myp->handler.net.vio );
409
+ optimize_for_async(obj);
281
410
 
282
411
  myp->query_with_result = Qtrue;
283
412
  rb_obj_call_init(obj, argc, argv);
413
+
414
+ //schedule_connect(obj);
284
415
 
285
416
  return obj;
286
417
  }
@@ -343,6 +474,9 @@ static VALUE real_connect2(int argc, VALUE* argv, VALUE obj)
343
474
  mysql_raise(m);
344
475
  m->reconnect = 0;
345
476
  GetMysqlStruct(obj)->connection = Qtrue;
477
+
478
+ optimize_for_async(obj);
479
+ //schedule_connect(obj);
346
480
 
347
481
  return obj;
348
482
  }
@@ -597,7 +731,7 @@ static VALUE list_fields(int argc, VALUE* argv, VALUE obj)
597
731
  res = mysql_list_fields(m, StringValuePtr(table), NILorSTRING(field));
598
732
  if (res == NULL)
599
733
  mysql_raise(m);
600
- return mysqlres2obj(res);
734
+ return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
601
735
  }
602
736
 
603
737
  /* list_processes() */
@@ -607,7 +741,7 @@ static VALUE list_processes(VALUE obj)
607
741
  MYSQL_RES* res = mysql_list_processes(m);
608
742
  if (res == NULL)
609
743
  mysql_raise(m);
610
- return mysqlres2obj(res);
744
+ return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
611
745
  }
612
746
 
613
747
  /* list_tables(table=nil) */
@@ -694,14 +828,40 @@ static VALUE my_stat(VALUE obj)
694
828
  return rb_tainted_str_new2(s);
695
829
  }
696
830
 
831
+ // 1.9 friendly
832
+ typedef struct
833
+ {
834
+ MYSQL *mysql_instance;
835
+ MYSQL_RES **store_it_here;
836
+
837
+ } mysql_result_to_here_t,
838
+ *shared_stuff_p;
839
+
840
+ static VALUE store_result_to_location(void *settings_in)
841
+ {
842
+ mysql_result_to_here_t *settings = (mysql_result_to_here_t *) settings_in;
843
+ *(settings->store_it_here) = mysql_store_result(settings->mysql_instance); // this one line runs a good long while for very large queries
844
+ return Qnil;
845
+ }
846
+
697
847
  /* store_result() */
698
848
  static VALUE store_result(VALUE obj)
699
849
  {
700
850
  MYSQL* m = GetHandler(obj);
701
- MYSQL_RES* res = mysql_store_result(m);
851
+ MYSQL_RES* res = NULL;
852
+ #ifndef HAVE_TBR
853
+ res = mysql_store_result(m);
854
+ #else
855
+ mysql_result_to_here_t linker;
856
+ linker.mysql_instance = m;
857
+ linker.store_it_here = &res;
858
+ rb_thread_blocking_region(store_result_to_location, (void *) &linker, RUBY_UBF_IO, 0);
859
+ #endif
860
+
702
861
  if (res == NULL)
703
862
  mysql_raise(m);
704
- return mysqlres2obj(res);
863
+
864
+ return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
705
865
  }
706
866
 
707
867
  /* thread_id() */
@@ -717,7 +877,7 @@ static VALUE use_result(VALUE obj)
717
877
  MYSQL_RES* res = mysql_use_result(m);
718
878
  if (res == NULL)
719
879
  mysql_raise(m);
720
- return mysqlres2obj(res);
880
+ return mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
721
881
  }
722
882
 
723
883
  static VALUE res_free(VALUE);
@@ -764,7 +924,7 @@ static VALUE query(VALUE obj, VALUE sql)
764
924
  if (mysql_field_count(m) != 0)
765
925
  mysql_raise(m);
766
926
  } else {
767
- VALUE robj = mysqlres2obj(res);
927
+ VALUE robj = mysqlres2obj(res, GetMysqlStruct(obj)->gc_disabled);
768
928
  rb_ensure(rb_yield, robj, res_free, robj);
769
929
  }
770
930
  #if MYSQL_VERSION_ID >= 40101
@@ -800,17 +960,46 @@ static VALUE socket(VALUE obj)
800
960
  MYSQL* m = GetHandler(obj);
801
961
  return INT2NUM(m->net.fd);
802
962
  }
803
- /* socket_type */
963
+
964
+ /* socket_type --currently returns true or false, needs some work */
804
965
  static VALUE socket_type(VALUE obj)
805
966
  {
806
967
  MYSQL* m = GetHandler(obj);
807
- VALUE description = vio_description( m->net.vio );
808
- return (VALUE) NILorSTRING( description );
968
+ if(vio_description(m->net.vio))
969
+ return Qtrue; // TODO return a ruby string
970
+ else
971
+ return Qnil;
809
972
  }
810
973
 
811
974
  /* blocking */
812
975
  static VALUE blocking(VALUE obj){
813
- return ( GetMysqlStruct(obj)->blocking ? Qtrue : Qfalse );
976
+ return ( GetMysqlStruct(obj)->blocking ? Qtrue : Qfalse );
977
+ }
978
+
979
+ /* is_busy */
980
+ static VALUE is_busy(VALUE obj){
981
+ return ( GetMysqlStruct(obj)->busy ? Qtrue : Qfalse );
982
+ }
983
+
984
+ static VALUE is_idle(VALUE obj){
985
+ return ( is_busy(obj) == Qtrue ) ? Qfalse : Qtrue;
986
+ }
987
+
988
+ /* busy(true|false) */
989
+ static VALUE busy_set(VALUE obj, VALUE flag)
990
+ {
991
+ if (TYPE(flag) != T_TRUE && TYPE(flag) != T_FALSE)
992
+ rb_raise(rb_eTypeError, "invalid type, required true or false.");
993
+ GetMysqlStruct(obj)->busy = flag;
994
+ return flag;
995
+ }
996
+
997
+ static void busy( VALUE obj ){
998
+ busy_set( obj, Qtrue );
999
+ }
1000
+
1001
+ static void idle( VALUE obj ){
1002
+ busy_set( obj, Qfalse );
814
1003
  }
815
1004
 
816
1005
  /* readable(timeout=nil) */
@@ -825,69 +1014,177 @@ static VALUE readable( int argc, VALUE* argv, VALUE obj )
825
1014
  if ( NIL_P( timeout ) ){
826
1015
  timeout = m->net.read_timeout;
827
1016
  }
828
-
1017
+ // todo could do a rb_blocking_region here
829
1018
  return ( vio_poll_read( m->net.vio, INT2NUM(timeout) ) == 0 ? Qtrue : Qfalse );
830
1019
  }
831
1020
 
1021
+ /* retry */
1022
+ static VALUE retry( VALUE obj )
1023
+ {
1024
+ MYSQL* m = GetHandler(obj);
1025
+ return ( vio_should_retry( m->net.vio ) == 1 ? Qtrue : Qfalse );
1026
+ }
1027
+
1028
+ /* interrupted */
1029
+ static VALUE interrupted( VALUE obj )
1030
+ {
1031
+ MYSQL* m = GetHandler(obj);
1032
+ return ( vio_was_interrupted( m->net.vio ) == 1 ? Qtrue : Qfalse );
1033
+ }
1034
+
1035
+ /* reconnected */
1036
+ static VALUE reconnected( VALUE obj ){
1037
+ MYSQL* m = GetHandler(obj);
1038
+ int current_connection_id = mysql_thread_id( m );
1039
+ mysql_ping(m);
1040
+ return ( current_connection_id == mysql_thread_id( m ) ) ? Qfalse : Qtrue;
1041
+ }
1042
+
1043
+ /* disable_gc(true|false) */
1044
+ static VALUE disable_gc_set(VALUE obj, VALUE flag)
1045
+ {
1046
+ if (TYPE(flag) != T_TRUE && TYPE(flag) != T_FALSE)
1047
+ rb_raise(rb_eTypeError, "invalid type, required true or false.");
1048
+ GetMysqlStruct(obj)->gc_disabled = flag;
1049
+ return flag;
1050
+ }
1051
+
1052
+ /* gc_disabled */
1053
+ static VALUE gc_disabled( VALUE obj ){
1054
+ return GetMysqlStruct(obj)->gc_disabled ? Qtrue: Qfalse;
1055
+ }
1056
+
1057
+ static void validate_async_query( VALUE obj )
1058
+ {
1059
+ if( async_in_progress(obj) == Qtrue ){
1060
+ async_in_progress_set(obj, Qfalse);
1061
+ rb_raise(eMysql, "Query out of sequence: Each call to Mysql#send_query requires a successive Mysql#get_result.");
1062
+ }
1063
+ }
1064
+
1065
+ /* for testing */
1066
+ static VALUE simulate_disconnect( VALUE obj )
1067
+ {
1068
+ MYSQL* m = GetHandler(obj);
1069
+ mysql_library_end();
1070
+ return Qnil;
1071
+ }
1072
+
1073
+
832
1074
  /* send_query(sql) */
833
1075
  static VALUE send_query(VALUE obj, VALUE sql)
834
1076
  {
835
1077
  MYSQL* m = GetHandler(obj);
836
-
1078
+
837
1079
  Check_Type(sql, T_STRING);
838
- if (GetMysqlStruct(obj)->connection == Qfalse) {
839
- rb_raise(eMysql, "query: not connected");
1080
+
1081
+ if (GetMysqlStruct(obj)->connection == Qfalse && async_in_progress(obj) == Qtrue ) {
1082
+ idle( obj );
1083
+ rb_raise(eMysql, "query: not connected");
840
1084
  }
841
- if (mysql_send_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0)
842
- mysql_raise(m);
843
- return Qnil;
1085
+
1086
+ validate_async_query(obj);
1087
+
1088
+ if (mysql_send_query(m, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0){
1089
+ idle( obj );
1090
+ mysql_raise(m);
1091
+ }
1092
+ async_in_progress_set( obj, Qtrue );
1093
+
1094
+ return Qnil;
844
1095
  }
845
1096
 
846
- /* get_result */
1097
+ /*
1098
+ get_result
1099
+ returns the mysql_result set (default) [i.e. all rows in said said]
1100
+ or nil if query_with_result == false
1101
+ */
847
1102
  static VALUE get_result(VALUE obj)
848
1103
  {
849
1104
  MYSQL* m = GetHandler(obj);
1105
+
1106
+ async_in_progress_set( obj, Qfalse );
1107
+
850
1108
  if (GetMysqlStruct(obj)->connection == Qfalse) {
851
- rb_raise(eMysql, "query: not connected");
1109
+ idle( obj );
1110
+ rb_raise(eMysql, "query: not connected");
852
1111
  }
853
- if (mysql_read_query_result(m) != 0)
854
- mysql_raise(m);
1112
+ if (mysql_read_query_result(m) != 0){
1113
+ idle( obj );
1114
+ mysql_raise(m);
1115
+ }
1116
+
855
1117
  if (GetMysqlStruct(obj)->query_with_result == Qfalse)
856
1118
  return obj;
1119
+
857
1120
  if (mysql_field_count(m) == 0)
858
- return Qnil;
1121
+ return Qnil;
1122
+
859
1123
  return store_result(obj);
860
1124
  }
861
1125
 
862
- static void schedule(VALUE obj, VALUE timeout)
1126
+ static void schedule_query(VALUE obj, VALUE timeout)
863
1127
  {
864
1128
  MYSQL* m = GetHandler(obj);
865
1129
  fd_set read;
866
-
1130
+ int ret;
1131
+
867
1132
  timeout = ( NIL_P(timeout) ? m->net.read_timeout : INT2NUM(timeout) );
868
1133
 
869
1134
  struct timeval tv = { tv_sec: timeout, tv_usec: 0 };
870
1135
 
871
- FD_ZERO(&read);
872
- FD_SET(m->net.fd, &read);
1136
+ for(;;){
1137
+ FD_ZERO(&read);
1138
+ FD_SET(m->net.fd, &read);
1139
+
1140
+ ret = rb_thread_select(m->net.fd + 1, &read, NULL, NULL, &tv);
1141
+ if (ret < 0) {
1142
+ idle( obj );
1143
+ rb_raise(eMysql, "query: timeout");
1144
+ }
1145
+
1146
+ if (ret == 0) {
1147
+ continue;
1148
+ }
873
1149
 
874
- if (rb_thread_select(m->net.fd + 1, &read, NULL, NULL, &tv) < 0) {
875
- rb_raise(eMysql, "query: timeout");
1150
+ if (m->status == MYSQL_STATUS_READY){
1151
+ break;
1152
+ }
876
1153
  }
877
1154
  }
878
1155
 
879
- /* async_query(sql,timeout=nil) */
1156
+ static int should_schedule_query(){
1157
+ return rb_thread_alone() != 1;
1158
+ }
1159
+
1160
+ /* async_query(sql,timeout=nil)
1161
+ optionally take a block
1162
+ */
880
1163
  static VALUE async_query(int argc, VALUE* argv, VALUE obj)
881
1164
  {
882
- VALUE sql, timeout;
1165
+ MYSQL* m = GetHandler(obj);
1166
+ VALUE sql, timeout;
1167
+
1168
+ rb_scan_args(argc, argv, "11", &sql, &timeout);
883
1169
 
884
- rb_scan_args(argc, argv, "11", &sql, &timeout);
1170
+ async_in_progress_set( obj, Qfalse );
885
1171
 
886
- send_query(obj,sql);
1172
+ busy(obj);
887
1173
 
888
- schedule(obj, timeout);
1174
+ send_query( obj, sql );
889
1175
 
890
- return get_result(obj);
1176
+ if ( should_schedule_query() ){
1177
+ schedule_query(obj, timeout);
1178
+ }
1179
+
1180
+ if (rb_block_given_p()) {
1181
+ rb_yield( get_result(obj) );
1182
+ idle( obj );
1183
+ return obj;
1184
+ }else{
1185
+ idle( obj );
1186
+ return get_result(obj);
1187
+ }
891
1188
  }
892
1189
 
893
1190
  #if MYSQL_VERSION_ID >= 40100
@@ -1157,7 +1454,7 @@ static VALUE fetch_row(VALUE obj)
1157
1454
  return ary;
1158
1455
  }
1159
1456
 
1160
- /* process_all_hashes (internal) */
1457
+ /* process_all_hashes (internal helper) */
1161
1458
  static VALUE process_all_hashes(VALUE obj, VALUE with_table, int build_array, int yield)
1162
1459
  {
1163
1460
  MYSQL_RES* res = GetMysqlRes(obj);
@@ -1922,7 +2219,7 @@ static VALUE stmt_result_metadata(VALUE obj)
1922
2219
  mysql_stmt_raise(s->stmt);
1923
2220
  return Qnil;
1924
2221
  }
1925
- return mysqlres2obj(res);
2222
+ return mysqlres2obj(res, Qfalse);
1926
2223
  }
1927
2224
 
1928
2225
  /* row_seek(offset) */
@@ -2180,12 +2477,22 @@ void Init_mysql(void)
2180
2477
  rb_define_method(cMysql, "query", query, 1);
2181
2478
  rb_define_method(cMysql, "real_query", query, 1);
2182
2479
  rb_define_method(cMysql, "c_async_query", async_query, -1);
2480
+ rb_define_method(cMysql, "async_in_progress?", async_in_progress, 0);
2481
+ rb_define_method(cMysql, "async_in_progress=", async_in_progress_set, 1);
2183
2482
  rb_define_method(cMysql, "send_query", send_query, 1);
2483
+ rb_define_method(cMysql, "simulate_disconnect", simulate_disconnect, 0);
2484
+ rb_define_method(cMysql, "reconnected?", reconnected, 0);
2184
2485
  rb_define_method(cMysql, "get_result", get_result, 0);
2185
2486
  rb_define_method(cMysql, "readable?", readable, -1);
2487
+ rb_define_method(cMysql, "retry?", retry, 0);
2488
+ rb_define_method(cMysql, "interrupted?", interrupted, 0);
2186
2489
  rb_define_method(cMysql, "blocking?", blocking, 0);
2490
+ rb_define_method(cMysql, "gc_disabled?", gc_disabled, 0);
2491
+ rb_define_method(cMysql, "disable_gc=", disable_gc_set, 1);
2492
+ rb_define_method(cMysql, "busy?", is_busy, 0);
2493
+ rb_define_method(cMysql, "idle?", is_idle, 0);
2494
+ rb_define_method(cMysql, "busy=", busy_set, 1);
2187
2495
  rb_define_method(cMysql, "socket", socket, 0);
2188
- rb_define_method(cMysql, "socket_type", socket_type, 0);
2189
2496
  rb_define_method(cMysql, "refresh", refresh, 1);
2190
2497
  rb_define_method(cMysql, "reload", reload, 0);
2191
2498
  rb_define_method(cMysql, "select_db", select_db, 1);
@@ -1,8 +1,10 @@
1
- require 'mysql' # this should load the mysqlplus version of mysql.so, as we assume the user has installed mysql as a gem and have not done any previous "require 'mysql'" to have loaded the other
1
+ require File.dirname(__FILE__) + '/mysql' # load our version of mysql--note
2
+ # if someone does a require 'mysql' after a require 'mysqlplus' then their screen will be littered with warnings
3
+ # and the "old" mysql will override the "new" mysqlplus, so be careful.
2
4
 
3
5
  #
4
- # Mysqlplus library gives you a [slightly modified] version of the Mysql class
5
- # See http://www.kitebird.com/articles/ruby-mysql.html for details, as well as the test directory within the library
6
+ # The mysqlplus library is a [slightly updated] fork of the Mysql class, with asynchronous capability added
7
+ # See http://www.kitebird.com/articles/ruby-mysql.html for details, as well as the test directory within the gem
6
8
  #
7
9
  class Mysql
8
10
 
@@ -15,7 +17,7 @@ class Mysql
15
17
  begin
16
18
  alias_method :async_query, :c_async_query
17
19
  rescue NameError => e
18
- raise LoadError.new "error loading mysqlplus--this may mean you ran a require 'mysql' before a require 'mysqplus', which much come first"
20
+ 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")
19
21
  end
20
22
 
21
23
  end
@@ -1,13 +1,13 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "mysqlplus"
3
- s.version = "0.1.1"
3
+ s.version = "0.1.2"
4
4
  s.date = "2009-03-22"
5
5
  s.summary = "Enhanced Ruby MySQL driver"
6
6
  s.email = "oldmoe@gmail.com"
7
7
  s.homepage = "http://github.com/oldmoe/mysqlplus"
8
8
  s.description = "Enhanced Ruby MySQL driver"
9
9
  s.has_rdoc = true
10
- s.authors = ["Muhammad A. Ali"]
10
+ s.authors = ["Muhammad A. Ali et al"]
11
11
  s.platform = Gem::Platform::RUBY
12
12
  s.files = %w[
13
13
  README
@@ -18,18 +18,13 @@ Gem::Specification.new do |s|
18
18
  ext/mysql.c
19
19
  lib/mysqlplus.rb
20
20
  mysqlplus.gemspec
21
- test/c_threaded_test.rb
22
- test/evented_test.rb
23
- test/native_threaded_test.rb
24
- test/test_all_hashes.rb
25
- test/test_failure.rb
26
- test/test_helper.rb
27
- test/test_many_requests.rb
28
- test/test_parsing_while_response_is_being_read.rb
29
- test/test_threaded_sequel.rb
30
- ]
21
+ ] + Dir.glob('test/*')
31
22
  s.rdoc_options = ["--main", "README"]
32
23
  s.extra_rdoc_files = ["README"]
33
24
  s.extensions << "ext/extconf.rb"
34
- end
35
25
 
26
+ if s.respond_to? :specification_version then
27
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
28
+ s.specification_version = 3
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ # I suppose if all the tests don't blow up, that probably means pass
2
+ require 'mysqlplus'
3
+ for file in Dir.glob('*_test.rb') do
4
+ puts 'testing ' + file
5
+ # fork so we don't run out of connections to the mysql db, as few tests ever clean up their old processes
6
+ pid = Process.fork { load file }
7
+ Process.wait(pid)
8
+ end
9
+ puts 'successful'
@@ -1,9 +1,4 @@
1
- # shows the effect of using .all_hashes instead of looping on each hash
2
- # run it by substiting in a 'long' [many row] query for the query variable and toggling use_all_hashes here at the top
3
- # note that we load all the rows first, then run .all_hashes on the result [to see more easily the effect of all hashes]
4
- # on my machine and a 200_000 row table, it took 3.38s versus 3.65s
5
- require 'rubygems'
6
- require 'mysqlplus'
1
+ require 'create_test_db'
7
2
 
8
3
  use_the_all_hashes_method = true
9
4
 
@@ -13,16 +8,15 @@ $start = Time.now
13
8
 
14
9
  $connections = []
15
10
  $count.times do
16
- $connections << Mysql.real_connect('localhost','root', '', 'local_leadgen_dev')
11
+ $connections << Mysql.real_connect('localhost','root', '', 'local_test_db')
17
12
  end
18
13
 
19
- puts 'connection pool ready'
20
14
 
21
15
  $threads = []
22
16
  $count.times do |i|
23
17
  $threads << Thread.new do
24
18
 
25
- query = "select * from campus_zips"
19
+ query = "select * from test_table"
26
20
  puts "sending query on connection #{i}"
27
21
  conn = $connections[i]
28
22
  result = conn.async_query(query)
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ m = Mysql.real_connect('localhost','root','','mysql')
4
+
5
+ m.c_async_query( 'SELECT * FROM user' ) do |result|
6
+ puts result.inspect
7
+ end
@@ -0,0 +1,22 @@
1
+ # If this script returns without the word pass
2
+ # you may have compiled mysqlplus using ruby and
3
+ # run it using a different version of ruby
4
+
5
+ if RUBY_VERSION >= "1.9.1"
6
+ require 'mysqlplus'
7
+ require 'socket'
8
+ require 'timeout'
9
+ TCPServer.new '0.0.0.0', 8002
10
+ Thread.new {
11
+ sleep 2
12
+ print "pass"
13
+ system("kill -9 #{Process.pid}")
14
+ }
15
+ Timeout::timeout(1) {
16
+ # uncomment this line to do the 'real' test
17
+ # which hangs otherwise (blows up if code is bad, otherwise hangs)
18
+ Mysql.real_connect '127.0.0.1', 'root', 'pass', 'db', 8002
19
+ }
20
+ raise 'should never get here'
21
+ end
22
+
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'mysqlplus'
3
2
  begin
4
3
  Mysql.real_connect('fakehost','root', '', 'local_leadgen_dev')
@@ -12,6 +11,7 @@ begin
12
11
  Mysql.real_connect('localhost', 'root', 'pass', 'db', 3307)# bad port
13
12
  rescue Mysql::Error
14
13
  end
14
+
15
15
  print "pass"
16
16
 
17
17
 
@@ -0,0 +1,22 @@
1
+ # To run first execute:
2
+ =begin
3
+ create database local_test_db;
4
+ use local_test_db;
5
+ CREATE TABLE test_table (
6
+ c1 INT,
7
+ c2 VARCHAR(20)
8
+ );
9
+ =end
10
+ # This script shows the effect of using .all_hashes instead of looping on each hash
11
+ # run it by substiting in a 'long' [many row] query for the query variable and toggling use_all_hashes here at the top
12
+ # note that we load all the rows first, then run .all_hashes on the result [to see more easily the effect of all hashes]
13
+ # 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
14
+ # almost as fast, now, as .all_hashes--they've both been optimized]
15
+ require 'mysqlplus'
16
+
17
+ puts 'initing db'
18
+ # init the DB
19
+ conn = Mysql.real_connect('localhost', 'root', '', 'local_test_db')
20
+ conn.query("delete from test_table")
21
+ 200_000.times {conn.query(" insert into test_table (c1, c2) values (3, 'ABCDEFG')")}
22
+ puts 'connection pool ready'
@@ -0,0 +1,40 @@
1
+ require 'mysqlplus'
2
+ require 'benchmark'
3
+
4
+ with_gc = Mysql.real_connect('localhost','root','','mysql')
5
+ without_gc = Mysql.real_connect('localhost','root','','mysql')
6
+ without_gc.disable_gc = true
7
+
8
+ $gc_stats = []
9
+
10
+ def countable_gc?
11
+ GC.respond_to? :count
12
+ end
13
+
14
+ def gc_counts( label, scope )
15
+ $gc_stats << "Objects #{scope} ( #{label} ) #{GC.count}"
16
+ end
17
+
18
+ def with_gc_counts( label )
19
+ gc_counts( label, 'before' ) if countable_gc?
20
+ yield
21
+ gc_counts( label, 'after' ) if countable_gc?
22
+ end
23
+
24
+ n = 1000
25
+
26
+ Benchmark.bmbm do |x|
27
+ x.report( 'With GC' ) do
28
+ with_gc_counts( 'With GC' ) do
29
+ n.times{ with_gc.c_async_query( 'SELECT * FROM user' ) }
30
+ end
31
+ end
32
+ GC.start
33
+ x.report( 'Without GC' ) do
34
+ with_gc_counts( 'Without GC' ) do
35
+ n.times{ without_gc.c_async_query( 'SELECT * FROM user' ) }
36
+ end
37
+ end
38
+ end
39
+
40
+ puts $gc_stats.join( ' | ' )
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'mysqlplus'
3
2
  a = Mysql.real_connect('localhost','root')
4
3
  100.times { a.query("select sleep(0)") }
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ m = Mysql.real_connect('localhost','root')
4
+ m.reconnect = true
5
+ $count = 0
6
+ class << m
7
+ def safe_query( query )
8
+ begin
9
+ send_query( query )
10
+ rescue => e
11
+ $count += 1
12
+ puts e.message
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ m.safe_query( 'select sleep(1)' )
19
+ m.safe_query( 'select sleep(1)' )#raises
20
+ m.simulate_disconnect #fires mysql_library_end
21
+ m.safe_query( 'select sleep(1)' )
22
+ m.safe_query( 'select sleep(1)' )#raises
23
+ m.close
24
+ m.connect('localhost','root')
25
+ m.safe_query( 'select sleep(1)' )
26
+ m.safe_query( 'select sleep(1)' )#raises
27
+ m.simulate_disconnect
28
+ raise unless $count == 3
29
+ m.safe_query( 'BEGIN' )
30
+ m.safe_query( 'select sleep(1)' ) # raises
31
+ m.get_result()
32
+ m.safe_query( 'COMMIT' )
33
+ m.get_result
34
+ raise unless $count == 4
@@ -5,8 +5,7 @@
5
5
  # from .82s to .62s
6
6
  # you can experiment with it by changing the query here to be a long one, and toggling the do_the_use_query_optimization variable
7
7
  # this also has the interesting property of 'freeing' Ruby to do thread changes mid-query.
8
- require 'rubygems'
9
- require 'mysqlplus'
8
+ require 'create_test_db'
10
9
 
11
10
  do_the_use_query_optimization = true
12
11
 
@@ -16,7 +15,7 @@ $start = Time.now
16
15
 
17
16
  $connections = []
18
17
  $count.times do
19
- $connections << Mysql.real_connect('localhost','root', '', 'local_leadgen_dev')
18
+ $connections << Mysql.real_connect('localhost','root', '', 'local_test_db')
20
19
  end
21
20
 
22
21
  puts 'connection pool ready'
@@ -28,7 +27,7 @@ $count.times do |i|
28
27
  puts "sending query on connection #{i}"
29
28
  conn = $connections[i]
30
29
  saved = []
31
- query = "select * from campus_zips"
30
+ query = "select * from test_table"
32
31
  if do_the_use_query_optimization
33
32
  conn.query_with_result=false
34
33
  result = conn.async_query(query)
@@ -0,0 +1,18 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ $m = Mysql.real_connect('localhost','root')
4
+ #$m.reconnect = true
5
+
6
+ def assert_reconnected
7
+ puts $m.reconnected?().inspect
8
+ sleep 1
9
+ yield
10
+ puts $m.reconnected?().inspect
11
+ end
12
+
13
+ assert_reconnected do
14
+ $m.simulate_disconnect
15
+ end
16
+ assert_reconnected do
17
+ $m.close
18
+ end
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'mysqlplus'
3
2
 
4
3
  class MysqlTest
@@ -196,4 +195,4 @@ class ThreadedMysqlTest < MysqlTest
196
195
  end
197
196
  end
198
197
 
199
- end
198
+ end
@@ -1,7 +1,7 @@
1
+ require 'mysqlplus'
1
2
  require 'rubygems'
2
3
  require 'sequel'
3
4
 
4
- require 'mysqlplus'
5
5
  class Mysql
6
6
  unless method_defined? :sync_query
7
7
  alias :sync_query :query
@@ -21,4 +21,4 @@ start = Time.now
21
21
  end
22
22
  end.map{|t| t.join }
23
23
 
24
- p (Time.now - start)
24
+ p (Time.now - start)
metadata CHANGED
@@ -1,15 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysqlplus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 2
9
+ version: 0.1.2
5
10
  platform: ruby
6
11
  authors:
7
- - Muhammad A. Ali
12
+ - Muhammad A. Ali et al
8
13
  autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2009-03-22 00:00:00 -07:00
17
+ date: 2009-03-22 00:00:00 -06:00
13
18
  default_executable:
14
19
  dependencies: []
15
20
 
@@ -30,17 +35,26 @@ files:
30
35
  - ext/mysql.c
31
36
  - lib/mysqlplus.rb
32
37
  - mysqlplus.gemspec
38
+ - test/all_hashes_test.rb
39
+ - test/async_query_with_block_test.rb
40
+ - test/connect_failure2_test.rb
41
+ - test/connect_failure_test.rb
42
+ - test/create_test_db.rb
33
43
  - test/c_threaded_test.rb
34
44
  - test/evented_test.rb
45
+ - test/gc_benchmark_test.rb
46
+ - test/many_requests_test.rb
35
47
  - test/native_threaded_test.rb
36
- - test/test_all_hashes.rb
37
- - test/test_failure.rb
48
+ - test/out_of_sync_test.rb
49
+ - test/query_with_result_false_test.rb
50
+ - test/reconnected_test.rb
51
+ - test/RUN_ALL_TESTS.RB
38
52
  - test/test_helper.rb
39
- - test/test_many_requests.rb
40
- - test/test_parsing_while_response_is_being_read.rb
41
- - test/test_threaded_sequel.rb
53
+ - test/threaded_sequel_test.rb
42
54
  has_rdoc: true
43
55
  homepage: http://github.com/oldmoe/mysqlplus
56
+ licenses: []
57
+
44
58
  post_install_message:
45
59
  rdoc_options:
46
60
  - --main
@@ -51,20 +65,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
68
+ segments:
69
+ - 0
54
70
  version: "0"
55
- version:
56
71
  required_rubygems_version: !ruby/object:Gem::Requirement
57
72
  requirements:
58
73
  - - ">="
59
74
  - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
60
77
  version: "0"
61
- version:
62
78
  requirements: []
63
79
 
64
80
  rubyforge_project:
65
- rubygems_version: 1.3.1
81
+ rubygems_version: 1.3.6
66
82
  signing_key:
67
- specification_version: 2
83
+ specification_version: 3
68
84
  summary: Enhanced Ruby MySQL driver
69
85
  test_files: []
70
86