@@ -75,26 +75,37 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_a
75
75
sym_local , sym_utc , sym_cast_booleans , sym_cache_rows , sym_cast , sym_stream , sym_name ;
76
76
static ID intern_merge ;
77
77
78
+ /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
78
79
static void rb_mysql_result_mark (void * wrapper ) {
79
80
mysql2_result_wrapper * w = wrapper ;
80
81
if (w ) {
81
82
rb_gc_mark (w -> fields );
82
83
rb_gc_mark (w -> rows );
83
84
rb_gc_mark (w -> encoding );
84
85
rb_gc_mark (w -> client );
86
+ rb_gc_mark (w -> statement );
85
87
}
86
88
}
87
89
88
90
/* this may be called manually or during GC */
89
91
static void rb_mysql_result_free_result (mysql2_result_wrapper * wrapper ) {
90
- unsigned int i ;
91
92
if (!wrapper ) return ;
92
93
93
94
if (wrapper -> resultFreed != 1 ) {
94
- if (wrapper -> stmt ) {
95
- mysql_stmt_free_result (wrapper -> stmt );
95
+ if (wrapper -> stmt_wrapper ) {
96
+ mysql_stmt_free_result (wrapper -> stmt_wrapper -> stmt );
97
+
98
+ /* MySQL BUG? If the statement handle was previously used, and so
99
+ * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed,
100
+ * MySQL still thinks the result set buffer is available and will prefetch the
101
+ * first result in mysql_stmt_execute. This will corrupt or crash the program.
102
+ * By setting bind_result_done back to 0, we make MySQL think that a result set
103
+ * has never been bound to this statement handle before to prevent the prefetch.
104
+ */
105
+ wrapper -> stmt_wrapper -> stmt -> bind_result_done = 0 ;
96
106
97
107
if (wrapper -> result_buffers ) {
108
+ unsigned int i ;
98
109
for (i = 0 ; i < wrapper -> numberOfFields ; i ++ ) {
99
110
if (wrapper -> result_buffers [i ].buffer ) {
100
111
xfree (wrapper -> result_buffers [i ].buffer );
@@ -105,8 +116,11 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
105
116
xfree (wrapper -> error );
106
117
xfree (wrapper -> length );
107
118
}
119
+ /* Clue that the next statement execute will need to allocate a new result buffer. */
120
+ wrapper -> result_buffers = NULL ;
108
121
}
109
122
/* FIXME: this may call flush_use_result, which can hit the socket */
123
+ /* For prepared statements, wrapper->result is the result metadata */
110
124
mysql_free_result (wrapper -> result );
111
125
wrapper -> resultFreed = 1 ;
112
126
}
@@ -122,6 +136,10 @@ static void rb_mysql_result_free(void *ptr) {
122
136
decr_mysql2_client (wrapper -> client_wrapper );
123
137
}
124
138
139
+ if (wrapper -> statement != Qnil ) {
140
+ decr_mysql2_stmt (wrapper -> stmt_wrapper );
141
+ }
142
+
125
143
xfree (wrapper );
126
144
}
127
145
@@ -338,23 +356,23 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co
338
356
rb_mysql_result_alloc_result_buffers (self , fields );
339
357
}
340
358
341
- if (mysql_stmt_bind_result (wrapper -> stmt , wrapper -> result_buffers )) {
342
- rb_raise_mysql2_stmt_error2 (wrapper -> stmt
359
+ if (mysql_stmt_bind_result (wrapper -> stmt_wrapper -> stmt , wrapper -> result_buffers )) {
360
+ rb_raise_mysql2_stmt_error2 (wrapper -> stmt_wrapper -> stmt
343
361
#ifdef HAVE_RUBY_ENCODING_H
344
362
, conn_enc
345
363
#endif
346
364
);
347
365
}
348
366
349
367
{
350
- switch ((uintptr_t )rb_thread_call_without_gvl (nogvl_stmt_fetch , wrapper -> stmt , RUBY_UBF_IO , 0 )) {
368
+ switch ((uintptr_t )rb_thread_call_without_gvl (nogvl_stmt_fetch , wrapper -> stmt_wrapper -> stmt , RUBY_UBF_IO , 0 )) {
351
369
case 0 :
352
370
/* success */
353
371
break ;
354
372
355
373
case 1 :
356
374
/* error */
357
- rb_raise_mysql2_stmt_error2 (wrapper -> stmt
375
+ rb_raise_mysql2_stmt_error2 (wrapper -> stmt_wrapper -> stmt
358
376
#ifdef HAVE_RUBY_ENCODING_H
359
377
, conn_enc
360
378
#endif
@@ -870,11 +888,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
870
888
rb_warn (":cache_rows is ignored if :stream is true" );
871
889
}
872
890
873
- if (wrapper -> stmt && !cacheRows && !wrapper -> is_streaming ) {
891
+ if (wrapper -> stmt_wrapper && !cacheRows && !wrapper -> is_streaming ) {
874
892
rb_warn (":cache_rows is forced for prepared statements (if not streaming)" );
875
893
}
876
894
877
- if (wrapper -> stmt && !cast ) {
895
+ if (wrapper -> stmt_wrapper && !cast ) {
878
896
rb_warn (":cast is forced for prepared statements" );
879
897
}
880
898
@@ -900,7 +918,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
900
918
}
901
919
902
920
if (wrapper -> lastRowProcessed == 0 && !wrapper -> is_streaming ) {
903
- wrapper -> numberOfRows = wrapper -> stmt ? mysql_stmt_num_rows (wrapper -> stmt ) : mysql_num_rows (wrapper -> result );
921
+ wrapper -> numberOfRows = wrapper -> stmt_wrapper ? mysql_stmt_num_rows (wrapper -> stmt_wrapper -> stmt ) : mysql_num_rows (wrapper -> result );
904
922
if (wrapper -> numberOfRows == 0 ) {
905
923
wrapper -> rows = rb_ary_new ();
906
924
return wrapper -> rows ;
@@ -918,7 +936,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
918
936
args .app_timezone = app_timezone ;
919
937
args .block_given = block ;
920
938
921
- if (wrapper -> stmt ) {
939
+ if (wrapper -> stmt_wrapper ) {
922
940
fetch_row_func = rb_mysql_result_fetch_row_stmt ;
923
941
} else {
924
942
fetch_row_func = rb_mysql_result_fetch_row ;
@@ -940,16 +958,16 @@ static VALUE rb_mysql_result_count(VALUE self) {
940
958
return LONG2NUM (RARRAY_LEN (wrapper -> rows ));
941
959
} else {
942
960
/* MySQL returns an unsigned 64-bit long here */
943
- if (wrapper -> stmt ) {
944
- return ULL2NUM (mysql_stmt_num_rows (wrapper -> stmt ));
961
+ if (wrapper -> stmt_wrapper ) {
962
+ return ULL2NUM (mysql_stmt_num_rows (wrapper -> stmt_wrapper -> stmt ));
945
963
} else {
946
964
return ULL2NUM (mysql_num_rows (wrapper -> result ));
947
965
}
948
966
}
949
967
}
950
968
951
969
/* Mysql2::Result */
952
- VALUE rb_mysql_result_to_obj (VALUE client , VALUE encoding , VALUE options , MYSQL_RES * r , MYSQL_STMT * s ) {
970
+ VALUE rb_mysql_result_to_obj (VALUE client , VALUE encoding , VALUE options , MYSQL_RES * r , VALUE statement ) {
953
971
VALUE obj ;
954
972
mysql2_result_wrapper * wrapper ;
955
973
@@ -966,12 +984,20 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
966
984
wrapper -> client = client ;
967
985
wrapper -> client_wrapper = DATA_PTR (client );
968
986
wrapper -> client_wrapper -> refcount ++ ;
969
- wrapper -> stmt = s ;
970
987
wrapper -> result_buffers = NULL ;
971
988
wrapper -> is_null = NULL ;
972
989
wrapper -> error = NULL ;
973
990
wrapper -> length = NULL ;
974
991
992
+ /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */
993
+ wrapper -> statement = statement ;
994
+ if (statement != Qnil ) {
995
+ wrapper -> stmt_wrapper = DATA_PTR (statement );
996
+ wrapper -> stmt_wrapper -> refcount ++ ;
997
+ } else {
998
+ wrapper -> stmt_wrapper = NULL ;
999
+ }
1000
+
975
1001
rb_obj_call_init (obj , 0 , NULL );
976
1002
rb_iv_set (obj , "@query_options" , options );
977
1003
0 commit comments