mysql2 0.3.21 → 0.4.1

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/README.md +28 -6
  4. data/examples/eventmachine.rb +1 -1
  5. data/examples/threaded.rb +4 -6
  6. data/ext/mysql2/client.c +93 -84
  7. data/ext/mysql2/client.h +21 -1
  8. data/ext/mysql2/extconf.rb +60 -41
  9. data/ext/mysql2/infile.c +2 -2
  10. data/ext/mysql2/mysql2_ext.c +1 -0
  11. data/ext/mysql2/mysql2_ext.h +5 -6
  12. data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
  13. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  14. data/ext/mysql2/result.c +464 -97
  15. data/ext/mysql2/result.h +11 -4
  16. data/ext/mysql2/statement.c +491 -0
  17. data/ext/mysql2/statement.h +18 -0
  18. data/lib/mysql2/client.rb +32 -24
  19. data/lib/mysql2/console.rb +1 -1
  20. data/lib/mysql2/em.rb +5 -6
  21. data/lib/mysql2/error.rb +17 -26
  22. data/lib/mysql2/field.rb +3 -0
  23. data/lib/mysql2/statement.rb +17 -0
  24. data/lib/mysql2/version.rb +1 -1
  25. data/lib/mysql2.rb +37 -35
  26. data/spec/em/em_spec.rb +21 -21
  27. data/spec/mysql2/client_spec.rb +322 -290
  28. data/spec/mysql2/error_spec.rb +37 -36
  29. data/spec/mysql2/result_spec.rb +200 -209
  30. data/spec/mysql2/statement_spec.rb +684 -0
  31. data/spec/spec_helper.rb +7 -0
  32. data/spec/ssl/ca-cert.pem +17 -0
  33. data/spec/ssl/ca-key.pem +27 -0
  34. data/spec/ssl/ca.cnf +22 -0
  35. data/spec/ssl/cert.cnf +22 -0
  36. data/spec/ssl/client-cert.pem +17 -0
  37. data/spec/ssl/client-key.pem +27 -0
  38. data/spec/ssl/client-req.pem +15 -0
  39. data/spec/ssl/gen_certs.sh +48 -0
  40. data/spec/ssl/pkcs8-client-key.pem +28 -0
  41. data/spec/ssl/pkcs8-server-key.pem +28 -0
  42. data/spec/ssl/server-cert.pem +17 -0
  43. data/spec/ssl/server-key.pem +27 -0
  44. data/spec/ssl/server-req.pem +15 -0
  45. data/support/mysql_enc_to_ruby.rb +7 -8
  46. data/support/ruby_enc_to_mysql.rb +1 -1
  47. metadata +41 -47
data/ext/mysql2/client.h CHANGED
@@ -50,7 +50,27 @@ typedef struct {
50
50
  MYSQL *client;
51
51
  } mysql_client_wrapper;
52
52
 
53
- void init_mysql2_client();
53
+ #define REQUIRE_CONNECTED(wrapper) \
54
+ REQUIRE_INITIALIZED(wrapper) \
55
+ if (!wrapper->connected && !wrapper->reconnect_enabled) { \
56
+ rb_raise(cMysql2Error, "closed MySQL connection"); \
57
+ }
58
+
59
+ void rb_mysql_client_set_active_thread(VALUE self);
60
+
61
+ #define MARK_CONN_INACTIVE(conn) do {\
62
+ wrapper->active_thread = Qnil; \
63
+ } while(0)
64
+
65
+ #define GET_CLIENT(self) \
66
+ mysql_client_wrapper *wrapper; \
67
+ Data_Get_Struct(self, mysql_client_wrapper, wrapper);
68
+
69
+ void init_mysql2_client(void);
54
70
  void decr_mysql2_client(mysql_client_wrapper *wrapper);
55
71
 
56
72
  #endif
73
+
74
+ #ifndef HAVE_RB_HASH_DUP
75
+ VALUE rb_hash_dup(VALUE other);
76
+ #endif
@@ -1,7 +1,8 @@
1
1
  # encoding: UTF-8
2
2
  require 'mkmf'
3
+ require 'English'
3
4
 
4
- def asplode lib
5
+ def asplode(lib)
5
6
  if RUBY_PLATFORM =~ /mingw|mswin/
6
7
  abort "-----\n#{lib} is missing. Check your installation of MySQL or Connector/C, and try again.\n-----"
7
8
  elsif RUBY_PLATFORM =~ /darwin/
@@ -22,7 +23,7 @@ have_func('rb_intern3')
22
23
 
23
24
  # borrowed from mysqlplus
24
25
  # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb
25
- dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[
26
+ dirs = ENV.fetch('PATH').split(File::PATH_SEPARATOR) + %w(
26
27
  /opt
27
28
  /opt/local
28
29
  /opt/local/mysql
@@ -33,13 +34,15 @@ dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[
33
34
  /usr/local/mysql
34
35
  /usr/local/mysql-*
35
36
  /usr/local/lib/mysql5*
36
- ].map{|dir| "#{dir}/bin" }
37
+ /usr/local/opt/mysql5*
38
+ ).map { |dir| dir << '/bin' }
37
39
 
38
40
  GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}"
39
41
 
40
42
  # If the user has provided a --with-mysql-dir argument, we must respect it or fail.
41
43
  inc, lib = dir_config('mysql')
42
44
  if inc && lib
45
+ # TODO: Remove when 2.0.0 is the minimum supported version
43
46
  # Ruby versions not incorporating the mkmf fix at
44
47
  # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717
45
48
  # do not properly search for lib directories, and must be corrected
@@ -47,48 +50,33 @@ if inc && lib
47
50
  @libdir_basename = 'lib'
48
51
  inc, lib = dir_config('mysql')
49
52
  end
50
- abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any?{|dir| File.directory?(dir)}
51
- abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any?{|dir| File.directory?(dir)}
52
- warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----"
53
+ abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) }
54
+ abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) }
55
+ warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----"
53
56
  rpath_dir = lib
54
- elsif mc = (with_config('mysql-config') || Dir[GLOB].first)
57
+ elsif (mc = (with_config('mysql-config') || Dir[GLOB].first))
55
58
  # If the user has provided a --with-mysql-config argument, we must respect it or fail.
56
59
  # If the user gave --with-mysql-config with no argument means we should try to find it.
57
60
  mc = Dir[GLOB].first if mc == true
58
- abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exists?(mc)
61
+ abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exist?(mc)
59
62
  abort "-----\nCannot execute mysql_config at #{mc}\n-----" unless File.executable?(mc)
60
- warn "-----\nUsing mysql_config at #{mc}\n-----"
63
+ warn "-----\nUsing mysql_config at #{mc}\n-----"
61
64
  ver = `#{mc} --version`.chomp.to_f
62
65
  includes = `#{mc} --include`.chomp
63
- exit 1 if $? != 0
66
+ abort unless $CHILD_STATUS.success?
64
67
  libs = `#{mc} --libs_r`.chomp
65
68
  # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r).
66
- if ver >= 5.5 || libs.empty?
67
- libs = `#{mc} --libs`.chomp
68
- end
69
- exit 1 if $? != 0
69
+ libs = `#{mc} --libs`.chomp if ver >= 5.5 || libs.empty?
70
+ abort unless $CHILD_STATUS.success?
70
71
  $INCFLAGS += ' ' + includes
71
72
  $libs = libs + " " + $libs
72
73
  rpath_dir = libs
73
74
  else
74
- inc, lib = dir_config('mysql', '/usr/local')
75
- unless find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql")
76
- found = false
77
- # For some systems and some versions of libmysqlclient, there were extra
78
- # libraries needed to link. Try each typical extra library, add it to the
79
- # global compile flags, and see if that allows us to link libmysqlclient.
80
- warn "-----\nlibmysqlclient is missing. Trying again with extra runtime libraries...\n-----"
81
-
82
- %w{ m z socket nsl mygcc }.each do |extra_lib|
83
- if have_library(extra_lib) && find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql")
84
- found = true
85
- break
86
- end
87
- end
88
- asplode('libmysqlclient') unless found
89
- end
75
+ _, usr_local_lib = dir_config('mysql', '/usr/local')
90
76
 
91
- rpath_dir = lib
77
+ asplode("mysql client") unless find_library('mysqlclient', 'mysql_query', usr_local_lib, "#{usr_local_lib}/mysql")
78
+
79
+ rpath_dir = usr_local_lib
92
80
  end
93
81
 
94
82
  if have_header('mysql.h')
@@ -99,18 +87,49 @@ else
99
87
  asplode 'mysql.h'
100
88
  end
101
89
 
102
- %w{ errmsg.h mysqld_error.h }.each do |h|
90
+ %w(errmsg.h mysqld_error.h).each do |h|
103
91
  header = [prefix, h].compact.join '/'
104
- asplode h unless have_header h
92
+ asplode h unless have_header header
93
+ end
94
+
95
+ # This is our wishlist. We use whichever flags work on the host.
96
+ # -Wall and -Wextra are included by default.
97
+ wishlist = [
98
+ '-Weverything',
99
+ '-Wno-bad-function-cast', # rb_thread_call_without_gvl returns void * that we cast to VALUE
100
+ '-Wno-conditional-uninitialized', # false positive in client.c
101
+ '-Wno-covered-switch-default', # result.c -- enum_field_types (when fully covered, e.g. mysql 5.5)
102
+ '-Wno-declaration-after-statement', # GET_CLIENT followed by GET_STATEMENT in statement.c
103
+ '-Wno-disabled-macro-expansion', # rubby :(
104
+ '-Wno-documentation-unknown-command', # rubby :(
105
+ '-Wno-missing-field-initializers', # gperf generates bad code
106
+ '-Wno-missing-variable-declarations', # missing symbols due to ruby native ext initialization
107
+ '-Wno-padded', # mysql :(
108
+ '-Wno-sign-conversion', # gperf generates bad code
109
+ '-Wno-static-in-inline', # gperf generates bad code
110
+ '-Wno-switch-enum', # result.c -- enum_field_types (when not fully covered, e.g. mysql 5.6+)
111
+ '-Wno-undef', # rubinius :(
112
+ '-Wno-used-but-marked-unused', # rubby :(
113
+ ]
114
+
115
+ if ENV['CI']
116
+ wishlist += [
117
+ '-Werror',
118
+ '-fsanitize=address',
119
+ '-fsanitize=cfi',
120
+ '-fsanitize=integer',
121
+ '-fsanitize=memory',
122
+ '-fsanitize=thread',
123
+ '-fsanitize=undefined',
124
+ ]
105
125
  end
106
126
 
107
- # These gcc style flags are also supported by clang and xcode compilers,
108
- # so we'll use a does-it-work test instead of an is-it-gcc test.
109
- gcc_flags = ' -Wall -funroll-loops'
110
- if try_link('int main() {return 0;}', gcc_flags)
111
- $CFLAGS << gcc_flags
127
+ usable_flags = wishlist.select do |flag|
128
+ try_link('int main() {return 0;}', flag)
112
129
  end
113
130
 
131
+ $CFLAGS << ' ' << usable_flags.join(' ')
132
+
114
133
  if RUBY_PLATFORM =~ /mswin|mingw/
115
134
  # Build libmysql.a interface link library
116
135
  require 'rake'
@@ -119,7 +138,7 @@ if RUBY_PLATFORM =~ /mswin|mingw/
119
138
  # Use rake to rebuild only if these files change
120
139
  deffile = File.expand_path('../../../support/libmysql.def', __FILE__)
121
140
  libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib'))
122
- file 'libmysql.a' => [deffile, libfile] do |t|
141
+ file 'libmysql.a' => [deffile, libfile] do
123
142
  when_writing 'building libmysql.a' do
124
143
  # Ruby kindly shows us where dllwrap is, but that tool does more than we want.
125
144
  # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly.
@@ -146,7 +165,7 @@ if RUBY_PLATFORM =~ /mswin|mingw/
146
165
 
147
166
  vendordll = File.join(vendordir, 'libmysql.dll')
148
167
  dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll'))
149
- file vendordll => [dllfile, vendordir] do |t|
168
+ file vendordll => [dllfile, vendordir] do
150
169
  when_writing 'copying libmysql.dll' do
151
170
  cp dllfile, vendordll
152
171
  end
@@ -172,7 +191,7 @@ else
172
191
  warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----"
173
192
  $LDFLAGS << rpath_flags
174
193
  else
175
- if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2]
194
+ if (libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2])
176
195
  rpath_flags = " -Wl,-rpath,#{libdir}"
177
196
  if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags)
178
197
  # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X.
data/ext/mysql2/infile.c CHANGED
@@ -56,7 +56,7 @@ mysql2_local_infile_init(void **ptr, const char *filename, void *userdata)
56
56
  * < 0 error
57
57
  */
58
58
  static int
59
- mysql2_local_infile_read(void *ptr, char *buf, uint buf_len)
59
+ mysql2_local_infile_read(void *ptr, char *buf, unsigned int buf_len)
60
60
  {
61
61
  int count;
62
62
  mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr;
@@ -95,7 +95,7 @@ mysql2_local_infile_end(void *ptr)
95
95
  * Error message number (see http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html)
96
96
  */
97
97
  static int
98
- mysql2_local_infile_error(void *ptr, char *error_msg, uint error_msg_len)
98
+ mysql2_local_infile_error(void *ptr, char *error_msg, unsigned int error_msg_len)
99
99
  {
100
100
  mysql2_local_infile_data *data = (mysql2_local_infile_data *) ptr;
101
101
 
@@ -9,4 +9,5 @@ void Init_mysql2() {
9
9
 
10
10
  init_mysql2_client();
11
11
  init_mysql2_result();
12
+ init_mysql2_statement();
12
13
  }
@@ -1,18 +1,14 @@
1
1
  #ifndef MYSQL2_EXT
2
2
  #define MYSQL2_EXT
3
3
 
4
+ void Init_mysql2(void);
5
+
4
6
  /* tell rbx not to use it's caching compat layer
5
7
  by doing this we're making a promise to RBX that
6
8
  we'll never modify the pointers we get back from RSTRING_PTR */
7
9
  #define RSTRING_NOT_MODIFIED
8
10
  #include <ruby.h>
9
11
 
10
- #ifndef HAVE_UINT
11
- #define HAVE_UINT
12
- typedef unsigned short ushort;
13
- typedef unsigned int uint;
14
- #endif
15
-
16
12
  #ifdef HAVE_MYSQL_H
17
13
  #include <mysql.h>
18
14
  #include <mysql_com.h>
@@ -33,12 +29,15 @@ typedef unsigned int uint;
33
29
  #endif
34
30
 
35
31
  #if defined(__GNUC__) && (__GNUC__ >= 3)
32
+ #define RB_MYSQL_NORETURN __attribute__ ((noreturn))
36
33
  #define RB_MYSQL_UNUSED __attribute__ ((unused))
37
34
  #else
35
+ #define RB_MYSQL_NORETURN
38
36
  #define RB_MYSQL_UNUSED
39
37
  #endif
40
38
 
41
39
  #include <client.h>
40
+ #include <statement.h>
42
41
  #include <result.h>
43
42
  #include <infile.h>
44
43
 
@@ -1,4 +1,4 @@
1
- /* C code produced by gperf version 3.0.3 */
1
+ /* C code produced by gperf version 3.0.4 */
2
2
  /* Command-line: gperf */
3
3
  /* Computed positions: -k'1,3,$' */
4
4
 
@@ -78,7 +78,7 @@ mysql2_mysql_enc_name_to_rb_hash (str, len)
78
78
 
79
79
  #ifdef __GNUC__
80
80
  __inline
81
- #ifdef __GNUC_STDC_INLINE__
81
+ #if defined __GNUC_STDC_INLINE__ || defined __GNUC_GNU_INLINE__
82
82
  __attribute__ ((__gnu_inline__))
83
83
  #endif
84
84
  #endif
@@ -1,4 +1,4 @@
1
- const char *mysql2_mysql_enc_to_rb[] = {
1
+ static const char *mysql2_mysql_enc_to_rb[] = {
2
2
  "Big5",
3
3
  "ISO-8859-2",
4
4
  NULL,
@@ -54,13 +54,13 @@ const char *mysql2_mysql_enc_to_rb[] = {
54
54
  "macRoman",
55
55
  "UTF-16",
56
56
  "UTF-16",
57
- NULL,
57
+ "",
58
58
  "Windows-1256",
59
59
  "Windows-1257",
60
60
  "Windows-1257",
61
61
  "UTF-32",
62
62
  "UTF-32",
63
- NULL,
63
+ "",
64
64
  "ASCII-8BIT",
65
65
  NULL,
66
66
  "US-ASCII",
@@ -119,10 +119,10 @@ const char *mysql2_mysql_enc_to_rb[] = {
119
119
  "UTF-16",
120
120
  "UTF-16",
121
121
  "UTF-16",
122
- NULL,
123
- NULL,
124
- NULL,
125
- NULL,
122
+ "UTF-16",
123
+ "UTF-16",
124
+ "UTF-16",
125
+ "UTF-16",
126
126
  NULL,
127
127
  NULL,
128
128
  NULL,
@@ -146,6 +146,10 @@ const char *mysql2_mysql_enc_to_rb[] = {
146
146
  "UTF-16BE",
147
147
  "UTF-16BE",
148
148
  "UTF-16BE",
149
+ "UTF-16BE",
150
+ "UTF-16BE",
151
+ "UTF-16BE",
152
+ "UTF-16BE",
149
153
  NULL,
150
154
  NULL,
151
155
  NULL,
@@ -153,11 +157,11 @@ const char *mysql2_mysql_enc_to_rb[] = {
153
157
  NULL,
154
158
  NULL,
155
159
  NULL,
156
- NULL,
157
- NULL,
158
- NULL,
159
- NULL,
160
- NULL,
160
+ "UTF-16BE",
161
+ "UTF-32",
162
+ "UTF-32",
163
+ "UTF-32",
164
+ "UTF-32",
161
165
  "UTF-32",
162
166
  "UTF-32",
163
167
  "UTF-32",
@@ -178,10 +182,6 @@ const char *mysql2_mysql_enc_to_rb[] = {
178
182
  "UTF-32",
179
183
  "UTF-32",
180
184
  "UTF-32",
181
- NULL,
182
- NULL,
183
- NULL,
184
- NULL,
185
185
  NULL,
186
186
  NULL,
187
187
  NULL,
@@ -210,6 +210,10 @@ const char *mysql2_mysql_enc_to_rb[] = {
210
210
  "UTF-8",
211
211
  "UTF-8",
212
212
  "UTF-8",
213
+ "UTF-8",
214
+ "UTF-8",
215
+ "UTF-8",
216
+ "UTF-8",
213
217
  NULL,
214
218
  NULL,
215
219
  NULL,
@@ -217,11 +221,11 @@ const char *mysql2_mysql_enc_to_rb[] = {
217
221
  NULL,
218
222
  NULL,
219
223
  NULL,
220
- NULL,
221
- NULL,
222
- NULL,
223
- NULL,
224
- NULL,
224
+ "UTF-8",
225
+ "UTF-8",
226
+ "UTF-8",
227
+ "UTF-8",
228
+ "UTF-8",
225
229
  "UTF-8",
226
230
  "UTF-8",
227
231
  "UTF-8",
@@ -243,4 +247,3 @@ const char *mysql2_mysql_enc_to_rb[] = {
243
247
  "UTF-8",
244
248
  "UTF-8"
245
249
  };
246
-
data/ext/mysql2/result.c CHANGED
@@ -1,7 +1,5 @@
1
1
  #include <mysql2_ext.h>
2
2
 
3
- #include <stdint.h>
4
-
5
3
  #include "mysql_enc_to_ruby.h"
6
4
 
7
5
  #ifdef HAVE_RUBY_ENCODING_H
@@ -54,8 +52,20 @@ static rb_encoding *binaryEncoding;
54
52
  mysql2_result_wrapper *wrapper; \
55
53
  Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
56
54
 
55
+ typedef struct {
56
+ int symbolizeKeys;
57
+ int asArray;
58
+ int castBool;
59
+ int cacheRows;
60
+ int cast;
61
+ int streaming;
62
+ ID db_timezone;
63
+ ID app_timezone;
64
+ VALUE block_given;
65
+ } result_each_args;
66
+
67
+ VALUE cBigDecimal, cDateTime, cDate;
57
68
  static VALUE cMysql2Result;
58
- static VALUE cBigDecimal, cDate, cDateTime;
59
69
  static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
60
70
  extern VALUE mMysql2, cMysql2Client, cMysql2Error;
61
71
  static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
@@ -63,6 +73,7 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_a
63
73
  sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
64
74
  static ID intern_merge;
65
75
 
76
+ /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
66
77
  static void rb_mysql_result_mark(void * wrapper) {
67
78
  mysql2_result_wrapper * w = wrapper;
68
79
  if (w) {
@@ -70,13 +81,44 @@ static void rb_mysql_result_mark(void * wrapper) {
70
81
  rb_gc_mark(w->rows);
71
82
  rb_gc_mark(w->encoding);
72
83
  rb_gc_mark(w->client);
84
+ rb_gc_mark(w->statement);
73
85
  }
74
86
  }
75
87
 
76
88
  /* this may be called manually or during GC */
77
89
  static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
78
- if (wrapper && wrapper->resultFreed != 1) {
90
+ if (!wrapper) return;
91
+
92
+ if (wrapper->resultFreed != 1) {
93
+ if (wrapper->stmt_wrapper) {
94
+ mysql_stmt_free_result(wrapper->stmt_wrapper->stmt);
95
+
96
+ /* MySQL BUG? If the statement handle was previously used, and so
97
+ * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed,
98
+ * MySQL still thinks the result set buffer is available and will prefetch the
99
+ * first result in mysql_stmt_execute. This will corrupt or crash the program.
100
+ * By setting bind_result_done back to 0, we make MySQL think that a result set
101
+ * has never been bound to this statement handle before to prevent the prefetch.
102
+ */
103
+ wrapper->stmt_wrapper->stmt->bind_result_done = 0;
104
+
105
+ if (wrapper->result_buffers) {
106
+ unsigned int i;
107
+ for (i = 0; i < wrapper->numberOfFields; i++) {
108
+ if (wrapper->result_buffers[i].buffer) {
109
+ xfree(wrapper->result_buffers[i].buffer);
110
+ }
111
+ }
112
+ xfree(wrapper->result_buffers);
113
+ xfree(wrapper->is_null);
114
+ xfree(wrapper->error);
115
+ xfree(wrapper->length);
116
+ }
117
+ /* Clue that the next statement execute will need to allocate a new result buffer. */
118
+ wrapper->result_buffers = NULL;
119
+ }
79
120
  /* FIXME: this may call flush_use_result, which can hit the socket */
121
+ /* For prepared statements, wrapper->result is the result metadata */
80
122
  mysql_free_result(wrapper->result);
81
123
  wrapper->resultFreed = 1;
82
124
  }
@@ -84,7 +126,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
84
126
 
85
127
  /* this is called during GC */
86
128
  static void rb_mysql_result_free(void *ptr) {
87
- mysql2_result_wrapper * wrapper = ptr;
129
+ mysql2_result_wrapper *wrapper = ptr;
88
130
  rb_mysql_result_free_result(wrapper);
89
131
 
90
132
  // If the GC gets to client first it will be nil
@@ -92,6 +134,10 @@ static void rb_mysql_result_free(void *ptr) {
92
134
  decr_mysql2_client(wrapper->client_wrapper);
93
135
  }
94
136
 
137
+ if (wrapper->statement != Qnil) {
138
+ decr_mysql2_stmt(wrapper->stmt_wrapper);
139
+ }
140
+
95
141
  xfree(wrapper);
96
142
  }
97
143
 
@@ -106,7 +152,14 @@ static void *nogvl_fetch_row(void *ptr) {
106
152
  return mysql_fetch_row(result);
107
153
  }
108
154
 
109
- static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
155
+ static void *nogvl_stmt_fetch(void *ptr) {
156
+ MYSQL_STMT *stmt = ptr;
157
+ uintptr_t r = mysql_stmt_fetch(stmt);
158
+
159
+ return (void *)r;
160
+ }
161
+
162
+ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) {
110
163
  VALUE rb_field;
111
164
  GET_RESULT(self);
112
165
 
@@ -185,7 +238,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
185
238
  */
186
239
  static unsigned int msec_char_to_uint(char *msec_char, size_t len)
187
240
  {
188
- int i;
241
+ size_t i;
189
242
  for (i = 0; i < (len - 1); i++) {
190
243
  if (msec_char[i] == '\0') {
191
244
  msec_char[i] = '0';
@@ -194,7 +247,274 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
194
247
  return (unsigned int)strtoul(msec_char, NULL, 10);
195
248
  }
196
249
 
197
- static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) {
250
+ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) {
251
+ unsigned int i;
252
+ GET_RESULT(self);
253
+
254
+ if (wrapper->result_buffers != NULL) return;
255
+
256
+ wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND));
257
+ wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
258
+ wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
259
+ wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long));
260
+
261
+ for (i = 0; i < wrapper->numberOfFields; i++) {
262
+ wrapper->result_buffers[i].buffer_type = fields[i].type;
263
+
264
+ // mysql type | C type
265
+ switch(fields[i].type) {
266
+ case MYSQL_TYPE_NULL: // NULL
267
+ break;
268
+ case MYSQL_TYPE_TINY: // signed char
269
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(signed char));
270
+ wrapper->result_buffers[i].buffer_length = sizeof(signed char);
271
+ break;
272
+ case MYSQL_TYPE_SHORT: // short int
273
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(short int));
274
+ wrapper->result_buffers[i].buffer_length = sizeof(short int);
275
+ break;
276
+ case MYSQL_TYPE_INT24: // int
277
+ case MYSQL_TYPE_LONG: // int
278
+ case MYSQL_TYPE_YEAR: // int
279
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(int));
280
+ wrapper->result_buffers[i].buffer_length = sizeof(int);
281
+ break;
282
+ case MYSQL_TYPE_LONGLONG: // long long int
283
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(long long int));
284
+ wrapper->result_buffers[i].buffer_length = sizeof(long long int);
285
+ break;
286
+ case MYSQL_TYPE_FLOAT: // float
287
+ case MYSQL_TYPE_DOUBLE: // double
288
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(double));
289
+ wrapper->result_buffers[i].buffer_length = sizeof(double);
290
+ break;
291
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
292
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
293
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
294
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
295
+ case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME
296
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME));
297
+ wrapper->result_buffers[i].buffer_length = sizeof(MYSQL_TIME);
298
+ break;
299
+ case MYSQL_TYPE_DECIMAL: // char[]
300
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
301
+ case MYSQL_TYPE_STRING: // char[]
302
+ case MYSQL_TYPE_VAR_STRING: // char[]
303
+ case MYSQL_TYPE_VARCHAR: // char[]
304
+ case MYSQL_TYPE_TINY_BLOB: // char[]
305
+ case MYSQL_TYPE_BLOB: // char[]
306
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
307
+ case MYSQL_TYPE_LONG_BLOB: // char[]
308
+ case MYSQL_TYPE_BIT: // char[]
309
+ case MYSQL_TYPE_SET: // char[]
310
+ case MYSQL_TYPE_ENUM: // char[]
311
+ case MYSQL_TYPE_GEOMETRY: // char[]
312
+ wrapper->result_buffers[i].buffer = xmalloc(fields[i].max_length);
313
+ wrapper->result_buffers[i].buffer_length = fields[i].max_length;
314
+ break;
315
+ default:
316
+ rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type);
317
+ }
318
+
319
+ wrapper->result_buffers[i].is_null = &wrapper->is_null[i];
320
+ wrapper->result_buffers[i].length = &wrapper->length[i];
321
+ wrapper->result_buffers[i].error = &wrapper->error[i];
322
+ wrapper->result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0);
323
+ }
324
+ }
325
+
326
+ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
327
+ {
328
+ VALUE rowVal;
329
+ unsigned int i = 0;
330
+
331
+ #ifdef HAVE_RUBY_ENCODING_H
332
+ rb_encoding *default_internal_enc;
333
+ rb_encoding *conn_enc;
334
+ #endif
335
+ GET_RESULT(self);
336
+
337
+ #ifdef HAVE_RUBY_ENCODING_H
338
+ default_internal_enc = rb_default_internal_encoding();
339
+ conn_enc = rb_to_encoding(wrapper->encoding);
340
+ #endif
341
+
342
+ if (args->asArray) {
343
+ rowVal = rb_ary_new2(wrapper->numberOfFields);
344
+ } else {
345
+ rowVal = rb_hash_new();
346
+ }
347
+ if (wrapper->fields == Qnil) {
348
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
349
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
350
+ }
351
+
352
+ if (wrapper->result_buffers == NULL) {
353
+ rb_mysql_result_alloc_result_buffers(self, fields);
354
+ }
355
+
356
+ if (mysql_stmt_bind_result(wrapper->stmt_wrapper->stmt, wrapper->result_buffers)) {
357
+ rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper);
358
+ }
359
+
360
+ {
361
+ switch((uintptr_t)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt_wrapper->stmt, RUBY_UBF_IO, 0)) {
362
+ case 0:
363
+ /* success */
364
+ break;
365
+
366
+ case 1:
367
+ /* error */
368
+ rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper);
369
+
370
+ case MYSQL_NO_DATA:
371
+ /* no more row */
372
+ return Qnil;
373
+
374
+ case MYSQL_DATA_TRUNCATED:
375
+ rb_raise(cMysql2Error, "IMPLBUG: caught MYSQL_DATA_TRUNCATED. should not come here as buffer_length is set to fields[i].max_length.");
376
+ }
377
+ }
378
+
379
+ for (i = 0; i < wrapper->numberOfFields; i++) {
380
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
381
+ VALUE val = Qnil;
382
+ MYSQL_TIME *ts;
383
+
384
+ if (wrapper->is_null[i]) {
385
+ val = Qnil;
386
+ } else {
387
+ const MYSQL_BIND* const result_buffer = &wrapper->result_buffers[i];
388
+
389
+ switch(result_buffer->buffer_type) {
390
+ case MYSQL_TYPE_TINY: // signed char
391
+ if (args->castBool && fields[i].length == 1) {
392
+ val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse;
393
+ break;
394
+ }
395
+ if (result_buffer->is_unsigned) {
396
+ val = UINT2NUM(*((unsigned char*)result_buffer->buffer));
397
+ } else {
398
+ val = INT2NUM(*((signed char*)result_buffer->buffer));
399
+ }
400
+ break;
401
+ case MYSQL_TYPE_SHORT: // short int
402
+ if (result_buffer->is_unsigned) {
403
+ val = UINT2NUM(*((unsigned short int*)result_buffer->buffer));
404
+ } else {
405
+ val = INT2NUM(*((short int*)result_buffer->buffer));
406
+ }
407
+ break;
408
+ case MYSQL_TYPE_INT24: // int
409
+ case MYSQL_TYPE_LONG: // int
410
+ case MYSQL_TYPE_YEAR: // int
411
+ if (result_buffer->is_unsigned) {
412
+ val = UINT2NUM(*((unsigned int*)result_buffer->buffer));
413
+ } else {
414
+ val = INT2NUM(*((int*)result_buffer->buffer));
415
+ }
416
+ break;
417
+ case MYSQL_TYPE_LONGLONG: // long long int
418
+ if (result_buffer->is_unsigned) {
419
+ val = ULL2NUM(*((unsigned long long int*)result_buffer->buffer));
420
+ } else {
421
+ val = LL2NUM(*((long long int*)result_buffer->buffer));
422
+ }
423
+ break;
424
+ case MYSQL_TYPE_FLOAT: // float
425
+ val = rb_float_new((double)(*((float*)result_buffer->buffer)));
426
+ break;
427
+ case MYSQL_TYPE_DOUBLE: // double
428
+ val = rb_float_new((double)(*((double*)result_buffer->buffer)));
429
+ break;
430
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
431
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
432
+ ts = (MYSQL_TIME*)result_buffer->buffer;
433
+ val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day));
434
+ break;
435
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
436
+ ts = (MYSQL_TIME*)result_buffer->buffer;
437
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part));
438
+ if (!NIL_P(args->app_timezone)) {
439
+ if (args->app_timezone == intern_local) {
440
+ val = rb_funcall(val, intern_localtime, 0);
441
+ } else { // utc
442
+ val = rb_funcall(val, intern_utc, 0);
443
+ }
444
+ }
445
+ break;
446
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
447
+ case MYSQL_TYPE_TIMESTAMP: { // MYSQL_TIME
448
+ uint64_t seconds;
449
+
450
+ ts = (MYSQL_TIME*)result_buffer->buffer;
451
+ seconds = (ts->year*31557600ULL) + (ts->month*2592000ULL) + (ts->day*86400ULL) + (ts->hour*3600ULL) + (ts->minute*60ULL) + ts->second;
452
+
453
+ if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
454
+ VALUE offset = INT2NUM(0);
455
+ if (args->db_timezone == intern_local) {
456
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
457
+ }
458
+ val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), offset);
459
+ if (!NIL_P(args->app_timezone)) {
460
+ if (args->app_timezone == intern_local) {
461
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
462
+ val = rb_funcall(val, intern_new_offset, 1, offset);
463
+ } else { // utc
464
+ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
465
+ }
466
+ }
467
+ } else {
468
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part));
469
+ if (!NIL_P(args->app_timezone)) {
470
+ if (args->app_timezone == intern_local) {
471
+ val = rb_funcall(val, intern_localtime, 0);
472
+ } else { // utc
473
+ val = rb_funcall(val, intern_utc, 0);
474
+ }
475
+ }
476
+ }
477
+ break;
478
+ }
479
+ case MYSQL_TYPE_DECIMAL: // char[]
480
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
481
+ val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length)));
482
+ break;
483
+ case MYSQL_TYPE_STRING: // char[]
484
+ case MYSQL_TYPE_VAR_STRING: // char[]
485
+ case MYSQL_TYPE_VARCHAR: // char[]
486
+ case MYSQL_TYPE_TINY_BLOB: // char[]
487
+ case MYSQL_TYPE_BLOB: // char[]
488
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
489
+ case MYSQL_TYPE_LONG_BLOB: // char[]
490
+ case MYSQL_TYPE_BIT: // char[]
491
+ case MYSQL_TYPE_SET: // char[]
492
+ case MYSQL_TYPE_ENUM: // char[]
493
+ case MYSQL_TYPE_GEOMETRY: // char[]
494
+ val = rb_str_new(result_buffer->buffer, *(result_buffer->length));
495
+ #ifdef HAVE_RUBY_ENCODING_H
496
+ val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
497
+ #endif
498
+ break;
499
+ default:
500
+ rb_raise(cMysql2Error, "unhandled buffer type: %d",
501
+ result_buffer->buffer_type);
502
+ }
503
+ }
504
+
505
+ if (args->asArray) {
506
+ rb_ary_push(rowVal, val);
507
+ } else {
508
+ rb_hash_aset(rowVal, field, val);
509
+ }
510
+ }
511
+
512
+ return rowVal;
513
+ }
514
+
515
+
516
+ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
517
+ {
198
518
  VALUE rowVal;
199
519
  MYSQL_ROW row;
200
520
  unsigned int i = 0;
@@ -217,7 +537,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
217
537
  return Qnil;
218
538
  }
219
539
 
220
- if (asArray) {
540
+ if (args->asArray) {
221
541
  rowVal = rb_ary_new2(wrapper->numberOfFields);
222
542
  } else {
223
543
  rowVal = rb_hash_new();
@@ -229,12 +549,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
229
549
  }
230
550
 
231
551
  for (i = 0; i < wrapper->numberOfFields; i++) {
232
- VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
552
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
233
553
  if (row[i]) {
234
554
  VALUE val = Qnil;
235
555
  enum enum_field_types type = fields[i].type;
236
556
 
237
- if (!cast) {
557
+ if (!args->cast) {
238
558
  if (type == MYSQL_TYPE_NULL) {
239
559
  val = Qnil;
240
560
  } else {
@@ -249,14 +569,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
249
569
  val = Qnil;
250
570
  break;
251
571
  case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
252
- if (castBool && fields[i].length == 1) {
572
+ if (args->castBool && fields[i].length == 1) {
253
573
  val = *row[i] == 1 ? Qtrue : Qfalse;
254
574
  }else{
255
575
  val = rb_str_new(row[i], fieldLengths[i]);
256
576
  }
257
577
  break;
258
578
  case MYSQL_TYPE_TINY: /* TINYINT field */
259
- if (castBool && fields[i].length == 1) {
579
+ if (args->castBool && fields[i].length == 1) {
260
580
  val = *row[i] != '0' ? Qtrue : Qfalse;
261
581
  break;
262
582
  }
@@ -299,9 +619,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
299
619
  break;
300
620
  }
301
621
  msec = msec_char_to_uint(msec_char, sizeof(msec_char));
302
- val = rb_funcall(rb_cTime, db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
303
- if (!NIL_P(app_timezone)) {
304
- if (app_timezone == intern_local) {
622
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
623
+ if (!NIL_P(args->app_timezone)) {
624
+ if (args->app_timezone == intern_local) {
305
625
  val = rb_funcall(val, intern_localtime, 0);
306
626
  } else { /* utc */
307
627
  val = rb_funcall(val, intern_utc, 0);
@@ -332,12 +652,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
332
652
  } else {
333
653
  if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
334
654
  VALUE offset = INT2NUM(0);
335
- if (db_timezone == intern_local) {
655
+ if (args->db_timezone == intern_local) {
336
656
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
337
657
  }
338
658
  val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset);
339
- if (!NIL_P(app_timezone)) {
340
- if (app_timezone == intern_local) {
659
+ if (!NIL_P(args->app_timezone)) {
660
+ if (args->app_timezone == intern_local) {
341
661
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
342
662
  val = rb_funcall(val, intern_new_offset, 1, offset);
343
663
  } else { /* utc */
@@ -346,9 +666,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
346
666
  }
347
667
  } else {
348
668
  msec = msec_char_to_uint(msec_char, sizeof(msec_char));
349
- val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
350
- if (!NIL_P(app_timezone)) {
351
- if (app_timezone == intern_local) {
669
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
670
+ if (!NIL_P(args->app_timezone)) {
671
+ if (args->app_timezone == intern_local) {
352
672
  val = rb_funcall(val, intern_localtime, 0);
353
673
  } else { /* utc */
354
674
  val = rb_funcall(val, intern_utc, 0);
@@ -398,13 +718,13 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
398
718
  break;
399
719
  }
400
720
  }
401
- if (asArray) {
721
+ if (args->asArray) {
402
722
  rb_ary_push(rowVal, val);
403
723
  } else {
404
724
  rb_hash_aset(rowVal, field, val);
405
725
  }
406
726
  } else {
407
- if (asArray) {
727
+ if (args->asArray) {
408
728
  rb_ary_push(rowVal, Qnil);
409
729
  } else {
410
730
  rb_hash_aset(rowVal, field, Qnil);
@@ -432,7 +752,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
432
752
  wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
433
753
  }
434
754
 
435
- if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
755
+ if ((my_ulonglong)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
436
756
  for (i=0; i<wrapper->numberOfFields; i++) {
437
757
  rb_mysql_result_fetch_field(self, i, symbolizeKeys);
438
758
  }
@@ -441,55 +761,16 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
441
761
  return wrapper->fields;
442
762
  }
443
763
 
444
- static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
445
- VALUE defaults, opts, block;
446
- ID db_timezone, app_timezone, dbTz, appTz;
764
+ static VALUE rb_mysql_result_each_(VALUE self,
765
+ VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args),
766
+ const result_each_args *args)
767
+ {
447
768
  unsigned long i;
448
- const char * errstr;
449
- int symbolizeKeys, asArray, castBool, cacheRows, cast;
450
- MYSQL_FIELD * fields = NULL;
769
+ const char *errstr;
770
+ MYSQL_FIELD *fields = NULL;
451
771
 
452
772
  GET_RESULT(self);
453
773
 
454
- defaults = rb_iv_get(self, "@query_options");
455
- Check_Type(defaults, T_HASH);
456
- if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
457
- opts = rb_funcall(defaults, intern_merge, 1, opts);
458
- } else {
459
- opts = defaults;
460
- }
461
-
462
- symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
463
- asArray = rb_hash_aref(opts, sym_as) == sym_array;
464
- castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
465
- cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
466
- cast = RTEST(rb_hash_aref(opts, sym_cast));
467
-
468
- if (wrapper->is_streaming && cacheRows) {
469
- rb_warn(":cache_rows is ignored if :stream is true");
470
- }
471
-
472
- dbTz = rb_hash_aref(opts, sym_database_timezone);
473
- if (dbTz == sym_local) {
474
- db_timezone = intern_local;
475
- } else if (dbTz == sym_utc) {
476
- db_timezone = intern_utc;
477
- } else {
478
- if (!NIL_P(dbTz)) {
479
- rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
480
- }
481
- db_timezone = intern_local;
482
- }
483
-
484
- appTz = rb_hash_aref(opts, sym_application_timezone);
485
- if (appTz == sym_local) {
486
- app_timezone = intern_local;
487
- } else if (appTz == sym_utc) {
488
- app_timezone = intern_utc;
489
- } else {
490
- app_timezone = Qnil;
491
- }
492
-
493
774
  if (wrapper->is_streaming) {
494
775
  /* When streaming, we will only yield rows, not return them. */
495
776
  if (wrapper->rows == Qnil) {
@@ -502,10 +783,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
502
783
  fields = mysql_fetch_fields(wrapper->result);
503
784
 
504
785
  do {
505
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
786
+ row = fetch_row_func(self, fields, args);
506
787
  if (row != Qnil) {
507
788
  wrapper->numberOfRows++;
508
- if (block != Qnil) {
789
+ if (args->block_given != Qnil) {
509
790
  rb_yield(row);
510
791
  }
511
792
  }
@@ -524,20 +805,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
524
805
  rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
525
806
  }
526
807
  } else {
527
- if (wrapper->lastRowProcessed == 0) {
528
- wrapper->numberOfRows = mysql_num_rows(wrapper->result);
529
- if (wrapper->numberOfRows == 0) {
530
- wrapper->rows = rb_ary_new();
531
- return wrapper->rows;
532
- }
533
- wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
534
- } else if (!cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
535
- mysql_data_seek(wrapper->result, 0);
536
- wrapper->lastRowProcessed = 0;
537
- wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
538
- }
539
-
540
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
808
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
541
809
  /* we've already read the entire dataset from the C result into our */
542
810
  /* internal array. Lets hand that over to the user since it's ready to go */
543
811
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -550,11 +818,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
550
818
 
551
819
  for (i = 0; i < wrapper->numberOfRows; i++) {
552
820
  VALUE row;
553
- if (cacheRows && i < rowsProcessed) {
821
+ if (args->cacheRows && i < rowsProcessed) {
554
822
  row = rb_ary_entry(wrapper->rows, i);
555
823
  } else {
556
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
557
- if (cacheRows) {
824
+ row = fetch_row_func(self, fields, args);
825
+ if (args->cacheRows) {
558
826
  rb_ary_store(wrapper->rows, i, row);
559
827
  }
560
828
  wrapper->lastRowProcessed++;
@@ -562,26 +830,109 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
562
830
 
563
831
  if (row == Qnil) {
564
832
  /* we don't need the mysql C dataset around anymore, peace it */
565
- if (cacheRows) {
566
- rb_mysql_result_free_result(wrapper);
567
- }
833
+ rb_mysql_result_free_result(wrapper);
568
834
  return Qnil;
569
835
  }
570
836
 
571
- if (block != Qnil) {
837
+ if (args->block_given != Qnil) {
572
838
  rb_yield(row);
573
839
  }
574
840
  }
575
- if (wrapper->lastRowProcessed == wrapper->numberOfRows && cacheRows) {
841
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
576
842
  /* we don't need the mysql C dataset around anymore, peace it */
577
843
  rb_mysql_result_free_result(wrapper);
578
844
  }
579
845
  }
580
846
  }
581
847
 
848
+ // FIXME return Enumerator instead?
849
+ // return rb_ary_each(wrapper->rows);
582
850
  return wrapper->rows;
583
851
  }
584
852
 
853
+ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
854
+ result_each_args args;
855
+ VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
856
+ ID db_timezone, app_timezone, dbTz, appTz;
857
+ int symbolizeKeys, asArray, castBool, cacheRows, cast;
858
+
859
+ GET_RESULT(self);
860
+
861
+ defaults = rb_iv_get(self, "@query_options");
862
+ Check_Type(defaults, T_HASH);
863
+ if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
864
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
865
+ } else {
866
+ opts = defaults;
867
+ }
868
+
869
+ symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
870
+ asArray = rb_hash_aref(opts, sym_as) == sym_array;
871
+ castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
872
+ cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
873
+ cast = RTEST(rb_hash_aref(opts, sym_cast));
874
+
875
+ if (wrapper->is_streaming && cacheRows) {
876
+ rb_warn(":cache_rows is ignored if :stream is true");
877
+ }
878
+
879
+ if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) {
880
+ rb_warn(":cache_rows is forced for prepared statements (if not streaming)");
881
+ }
882
+
883
+ if (wrapper->stmt_wrapper && !cast) {
884
+ rb_warn(":cast is forced for prepared statements");
885
+ }
886
+
887
+ dbTz = rb_hash_aref(opts, sym_database_timezone);
888
+ if (dbTz == sym_local) {
889
+ db_timezone = intern_local;
890
+ } else if (dbTz == sym_utc) {
891
+ db_timezone = intern_utc;
892
+ } else {
893
+ if (!NIL_P(dbTz)) {
894
+ rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
895
+ }
896
+ db_timezone = intern_local;
897
+ }
898
+
899
+ appTz = rb_hash_aref(opts, sym_application_timezone);
900
+ if (appTz == sym_local) {
901
+ app_timezone = intern_local;
902
+ } else if (appTz == sym_utc) {
903
+ app_timezone = intern_utc;
904
+ } else {
905
+ app_timezone = Qnil;
906
+ }
907
+
908
+ if (wrapper->lastRowProcessed == 0 && !wrapper->is_streaming) {
909
+ wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result);
910
+ if (wrapper->numberOfRows == 0) {
911
+ wrapper->rows = rb_ary_new();
912
+ return wrapper->rows;
913
+ }
914
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
915
+ }
916
+
917
+ // Backward compat
918
+ args.symbolizeKeys = symbolizeKeys;
919
+ args.asArray = asArray;
920
+ args.castBool = castBool;
921
+ args.cacheRows = cacheRows;
922
+ args.cast = cast;
923
+ args.db_timezone = db_timezone;
924
+ args.app_timezone = app_timezone;
925
+ args.block_given = block;
926
+
927
+ if (wrapper->stmt_wrapper) {
928
+ fetch_row_func = rb_mysql_result_fetch_row_stmt;
929
+ } else {
930
+ fetch_row_func = rb_mysql_result_fetch_row;
931
+ }
932
+
933
+ return rb_mysql_result_each_(self, fetch_row_func, &args);
934
+ }
935
+
585
936
  static VALUE rb_mysql_result_count(VALUE self) {
586
937
  GET_RESULT(self);
587
938
 
@@ -595,12 +946,16 @@ static VALUE rb_mysql_result_count(VALUE self) {
595
946
  return LONG2NUM(RARRAY_LEN(wrapper->rows));
596
947
  } else {
597
948
  /* MySQL returns an unsigned 64-bit long here */
598
- return ULL2NUM(mysql_num_rows(wrapper->result));
949
+ if (wrapper->stmt_wrapper) {
950
+ return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt));
951
+ } else {
952
+ return ULL2NUM(mysql_num_rows(wrapper->result));
953
+ }
599
954
  }
600
955
  }
601
956
 
602
957
  /* Mysql2::Result */
603
- VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) {
958
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) {
604
959
  VALUE obj;
605
960
  mysql2_result_wrapper * wrapper;
606
961
 
@@ -617,9 +972,21 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
617
972
  wrapper->client = client;
618
973
  wrapper->client_wrapper = DATA_PTR(client);
619
974
  wrapper->client_wrapper->refcount++;
975
+ wrapper->result_buffers = NULL;
976
+ wrapper->is_null = NULL;
977
+ wrapper->error = NULL;
978
+ wrapper->length = NULL;
979
+
980
+ /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */
981
+ wrapper->statement = statement;
982
+ if (statement != Qnil) {
983
+ wrapper->stmt_wrapper = DATA_PTR(statement);
984
+ wrapper->stmt_wrapper->refcount++;
985
+ } else {
986
+ wrapper->stmt_wrapper = NULL;
987
+ }
620
988
 
621
989
  rb_obj_call_init(obj, 0, NULL);
622
-
623
990
  rb_iv_set(obj, "@query_options", options);
624
991
 
625
992
  /* Options that cannot be changed in results.each(...) { |row| }