mysqlplus 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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