rdp-mysql2 0.2.7.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 (50) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/CHANGELOG.md +142 -0
  5. data/Gemfile +3 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.rdoc +261 -0
  8. data/Rakefile +5 -0
  9. data/benchmark/active_record.rb +51 -0
  10. data/benchmark/active_record_threaded.rb +42 -0
  11. data/benchmark/allocations.rb +33 -0
  12. data/benchmark/escape.rb +36 -0
  13. data/benchmark/query_with_mysql_casting.rb +80 -0
  14. data/benchmark/query_without_mysql_casting.rb +47 -0
  15. data/benchmark/sequel.rb +37 -0
  16. data/benchmark/setup_db.rb +119 -0
  17. data/benchmark/threaded.rb +44 -0
  18. data/examples/eventmachine.rb +21 -0
  19. data/examples/threaded.rb +20 -0
  20. data/ext/mysql2/client.c +839 -0
  21. data/ext/mysql2/client.h +41 -0
  22. data/ext/mysql2/extconf.rb +72 -0
  23. data/ext/mysql2/mysql2_ext.c +12 -0
  24. data/ext/mysql2/mysql2_ext.h +42 -0
  25. data/ext/mysql2/result.c +488 -0
  26. data/ext/mysql2/result.h +20 -0
  27. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +64 -0
  28. data/lib/active_record/connection_adapters/mysql2_adapter.rb +654 -0
  29. data/lib/active_record/fiber_patches.rb +104 -0
  30. data/lib/arel/engines/sql/compilers/mysql2_compiler.rb +11 -0
  31. data/lib/mysql2.rb +16 -0
  32. data/lib/mysql2/client.rb +240 -0
  33. data/lib/mysql2/em.rb +37 -0
  34. data/lib/mysql2/em_fiber.rb +31 -0
  35. data/lib/mysql2/error.rb +15 -0
  36. data/lib/mysql2/result.rb +5 -0
  37. data/lib/mysql2/version.rb +3 -0
  38. data/mysql2.gemspec +32 -0
  39. data/spec/em/em_fiber_spec.rb +22 -0
  40. data/spec/em/em_spec.rb +49 -0
  41. data/spec/mysql2/client_spec.rb +430 -0
  42. data/spec/mysql2/error_spec.rb +69 -0
  43. data/spec/mysql2/result_spec.rb +333 -0
  44. data/spec/rcov.opts +3 -0
  45. data/spec/spec_helper.rb +66 -0
  46. data/tasks/benchmarks.rake +20 -0
  47. data/tasks/compile.rake +71 -0
  48. data/tasks/rspec.rake +16 -0
  49. data/tasks/vendor_mysql.rake +40 -0
  50. metadata +236 -0
@@ -0,0 +1,41 @@
1
+ #ifndef MYSQL2_CLIENT_H
2
+ #define MYSQL2_CLIENT_H
3
+
4
+ /*
5
+ * partial emulation of the 1.9 rb_thread_blocking_region under 1.8,
6
+ * this is enough for dealing with blocking I/O functions in the
7
+ * presence of threads.
8
+ */
9
+ #ifndef HAVE_RB_THREAD_BLOCKING_REGION
10
+
11
+ #include <rubysig.h>
12
+ #define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
13
+ typedef void rb_unblock_function_t(void *);
14
+ typedef VALUE rb_blocking_function_t(void *);
15
+ static VALUE
16
+ rb_thread_blocking_region(
17
+ rb_blocking_function_t *func, void *data1,
18
+ RB_MYSQL_UNUSED rb_unblock_function_t *ubf,
19
+ RB_MYSQL_UNUSED void *data2)
20
+ {
21
+ VALUE rv;
22
+
23
+ TRAP_BEG;
24
+ rv = func(data1);
25
+ TRAP_END;
26
+
27
+ return rv;
28
+ }
29
+
30
+ #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
31
+
32
+ void init_mysql2_client();
33
+
34
+ typedef struct {
35
+ VALUE encoding;
36
+ char active;
37
+ char closed;
38
+ MYSQL *client;
39
+ } mysql_client_wrapper;
40
+
41
+ #endif
@@ -0,0 +1,72 @@
1
+ # encoding: UTF-8
2
+ require 'mkmf'
3
+
4
+ def asplode lib
5
+ abort "-----\n#{lib} is missing. please check your installation of mysql and try again.\n-----"
6
+ end
7
+
8
+ # 1.9-only
9
+ have_func('rb_thread_blocking_region')
10
+
11
+ # borrowed from mysqlplus
12
+ # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb
13
+ dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[
14
+ /opt
15
+ /opt/local
16
+ /opt/local/mysql
17
+ /opt/local/lib/mysql5
18
+ /usr
19
+ /usr/local
20
+ /usr/local/mysql
21
+ /usr/local/mysql-*
22
+ /usr/local/lib/mysql5
23
+ ].map{|dir| "#{dir}/bin" }
24
+
25
+ GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}"
26
+
27
+ if RUBY_PLATFORM =~ /mswin|mingw/
28
+ inc, lib = dir_config('mysql')
29
+ exit 1 unless have_library("libmysql")
30
+ elsif mc = (with_config('mysql-config') || Dir[GLOB].first) then
31
+ mc = Dir[GLOB].first if mc == true
32
+ cflags = `#{mc} --cflags`.chomp
33
+ exit 1 if $? != 0
34
+ libs = `#{mc} --libs_r`.chomp
35
+ if libs.empty?
36
+ libs = `#{mc} --libs`.chomp
37
+ end
38
+ exit 1 if $? != 0
39
+ $CPPFLAGS += ' ' + cflags
40
+ $libs = libs + " " + $libs
41
+ else
42
+ inc, lib = dir_config('mysql', '/usr/local')
43
+ libs = ['m', 'z', 'socket', 'nsl', 'mygcc']
44
+ while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do
45
+ exit 1 if libs.empty?
46
+ have_library(libs.shift)
47
+ end
48
+ end
49
+
50
+ if have_header('mysql.h') then
51
+ prefix = nil
52
+ elsif have_header('mysql/mysql.h') then
53
+ prefix = 'mysql'
54
+ else
55
+ asplode 'mysql.h'
56
+ end
57
+
58
+ %w{ errmsg.h mysqld_error.h }.each do |h|
59
+ header = [prefix, h].compact.join '/'
60
+ asplode h unless have_header h
61
+ end
62
+
63
+ unless RUBY_PLATFORM =~ /mswin/ or RUBY_PLATFORM =~ /sparc/
64
+ $CFLAGS << ' -Wall -funroll-loops'
65
+ end
66
+ # $CFLAGS << ' -O0 -ggdb3 -Wextra'
67
+
68
+ if hard_mysql_path = $libs[%r{-L(/[^ ]+)}, 1]
69
+ $LDFLAGS << " -Wl,-rpath,#{hard_mysql_path}"
70
+ end
71
+
72
+ create_makefile('mysql2/mysql2')
@@ -0,0 +1,12 @@
1
+ #include <mysql2_ext.h>
2
+
3
+ VALUE mMysql2, cMysql2Error;
4
+
5
+ /* Ruby Extension initializer */
6
+ void Init_mysql2() {
7
+ mMysql2 = rb_define_module("Mysql2");
8
+ cMysql2Error = rb_const_get(mMysql2, rb_intern("Error"));
9
+
10
+ init_mysql2_client();
11
+ init_mysql2_result();
12
+ }
@@ -0,0 +1,42 @@
1
+ #ifndef MYSQL2_EXT
2
+ #define MYSQL2_EXT
3
+
4
+ // tell rbx not to use it's caching compat layer
5
+ // by doing this we're making a promize to RBX that
6
+ // we'll never modify the pointers we get back from RSTRING_PTR
7
+ #define RSTRING_NOT_MODIFIED
8
+ #include <ruby.h>
9
+ #include <fcntl.h>
10
+
11
+ #ifndef HAVE_UINT
12
+ #define HAVE_UINT
13
+ typedef unsigned short ushort;
14
+ typedef unsigned int uint;
15
+ #endif
16
+
17
+ #ifdef HAVE_MYSQL_H
18
+ #include <mysql.h>
19
+ #include <mysql_com.h>
20
+ #include <errmsg.h>
21
+ #include <mysqld_error.h>
22
+ #else
23
+ #include <mysql/mysql.h>
24
+ #include <mysql/mysql_com.h>
25
+ #include <mysql/errmsg.h>
26
+ #include <mysql/mysqld_error.h>
27
+ #endif
28
+
29
+ #ifdef HAVE_RUBY_ENCODING_H
30
+ #include <ruby/encoding.h>
31
+ #endif
32
+
33
+ #if defined(__GNUC__) && (__GNUC__ >= 3)
34
+ #define RB_MYSQL_UNUSED __attribute__ ((unused))
35
+ #else
36
+ #define RB_MYSQL_UNUSED
37
+ #endif
38
+
39
+ #include <client.h>
40
+ #include <result.h>
41
+
42
+ #endif
@@ -0,0 +1,488 @@
1
+ #include <mysql2_ext.h>
2
+
3
+ #ifdef HAVE_RUBY_ENCODING_H
4
+ static rb_encoding *binaryEncoding;
5
+ #endif
6
+
7
+ #define MYSQL2_MAX_YEAR 2058
8
+
9
+ #ifdef NEGATIVE_TIME_T
10
+ /* 1901-12-13 20:45:52 UTC : The oldest time in 32-bit signed time_t. */
11
+ #define MYSQL2_MIN_YEAR 1902
12
+ #else
13
+ /* 1970-01-01 00:00:00 UTC : The Unix epoch - the oldest time in portable time_t. */
14
+ #define MYSQL2_MIN_YEAR 1970
15
+ #endif
16
+
17
+ static VALUE cMysql2Result;
18
+ static VALUE cBigDecimal, cDate, cDateTime;
19
+ static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
20
+ extern VALUE mMysql2, cMysql2Client, cMysql2Error;
21
+ static VALUE intern_encoding_from_charset;
22
+ static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code,
23
+ intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
24
+ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
25
+ sym_local, sym_utc, sym_cast_booleans, sym_cache_rows;
26
+ static ID intern_merge;
27
+
28
+ static void rb_mysql_result_mark(void * wrapper) {
29
+ mysql2_result_wrapper * w = wrapper;
30
+ if (w) {
31
+ rb_gc_mark(w->fields);
32
+ rb_gc_mark(w->rows);
33
+ rb_gc_mark(w->encoding);
34
+ }
35
+ }
36
+
37
+ /* this may be called manually or during GC */
38
+ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
39
+ if (wrapper && wrapper->resultFreed != 1) {
40
+ mysql_free_result(wrapper->result);
41
+ wrapper->resultFreed = 1;
42
+ }
43
+ }
44
+
45
+ /* this is called during GC */
46
+ static void rb_mysql_result_free(void * wrapper) {
47
+ mysql2_result_wrapper * w = wrapper;
48
+ /* FIXME: this may call flush_use_result, which can hit the socket */
49
+ rb_mysql_result_free_result(w);
50
+ xfree(wrapper);
51
+ }
52
+
53
+ /*
54
+ * for small results, this won't hit the network, but there's no
55
+ * reliable way for us to tell this so we'll always release the GVL
56
+ * to be safe
57
+ */
58
+ static VALUE nogvl_fetch_row(void *ptr) {
59
+ MYSQL_RES *result = ptr;
60
+
61
+ return (VALUE)mysql_fetch_row(result);
62
+ }
63
+
64
+ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
65
+ mysql2_result_wrapper * wrapper;
66
+ VALUE rb_field;
67
+ GetMysql2Result(self, wrapper);
68
+
69
+ if (wrapper->fields == Qnil) {
70
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
71
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
72
+ }
73
+
74
+ rb_field = rb_ary_entry(wrapper->fields, idx);
75
+ if (rb_field == Qnil) {
76
+ MYSQL_FIELD *field = NULL;
77
+ #ifdef HAVE_RUBY_ENCODING_H
78
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
79
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
80
+ #endif
81
+
82
+ field = mysql_fetch_field_direct(wrapper->result, idx);
83
+ if (symbolize_keys) {
84
+ char buf[field->name_length+1];
85
+ memcpy(buf, field->name, field->name_length);
86
+ buf[field->name_length] = 0;
87
+ rb_field = ID2SYM(rb_intern(buf));
88
+ } else {
89
+ rb_field = rb_str_new(field->name, field->name_length);
90
+ #ifdef HAVE_RUBY_ENCODING_H
91
+ rb_enc_associate(rb_field, conn_enc);
92
+ if (default_internal_enc) {
93
+ rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
94
+ }
95
+ #endif
96
+ }
97
+ rb_ary_store(wrapper->fields, idx, rb_field);
98
+ }
99
+
100
+ return rb_field;
101
+ }
102
+
103
+ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool) {
104
+ VALUE rowVal;
105
+ mysql2_result_wrapper * wrapper;
106
+ MYSQL_ROW row;
107
+ MYSQL_FIELD * fields = NULL;
108
+ unsigned int i = 0;
109
+ unsigned long * fieldLengths;
110
+ void * ptr;
111
+ #ifdef HAVE_RUBY_ENCODING_H
112
+ rb_encoding *default_internal_enc;
113
+ rb_encoding *conn_enc;
114
+ #endif
115
+ GetMysql2Result(self, wrapper);
116
+
117
+ #ifdef HAVE_RUBY_ENCODING_H
118
+ default_internal_enc = rb_default_internal_encoding();
119
+ conn_enc = rb_to_encoding(wrapper->encoding);
120
+ #endif
121
+
122
+ ptr = wrapper->result;
123
+ row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
124
+ if (row == NULL) {
125
+ return Qnil;
126
+ }
127
+
128
+ if (asArray) {
129
+ rowVal = rb_ary_new2(wrapper->numberOfFields);
130
+ } else {
131
+ rowVal = rb_hash_new();
132
+ }
133
+ fields = mysql_fetch_fields(wrapper->result);
134
+ fieldLengths = mysql_fetch_lengths(wrapper->result);
135
+ if (wrapper->fields == Qnil) {
136
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
137
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
138
+ }
139
+
140
+ for (i = 0; i < wrapper->numberOfFields; i++) {
141
+ VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
142
+ if (row[i]) {
143
+ VALUE val = Qnil;
144
+ switch(fields[i].type) {
145
+ case MYSQL_TYPE_NULL: // NULL-type field
146
+ val = Qnil;
147
+ break;
148
+ case MYSQL_TYPE_BIT: // BIT field (MySQL 5.0.3 and up)
149
+ val = rb_str_new(row[i], fieldLengths[i]);
150
+ break;
151
+ case MYSQL_TYPE_TINY: // TINYINT field
152
+ if (castBool && fields[i].length == 1) {
153
+ val = *row[i] == '1' ? Qtrue : Qfalse;
154
+ break;
155
+ }
156
+ case MYSQL_TYPE_SHORT: // SMALLINT field
157
+ case MYSQL_TYPE_LONG: // INTEGER field
158
+ case MYSQL_TYPE_INT24: // MEDIUMINT field
159
+ case MYSQL_TYPE_LONGLONG: // BIGINT field
160
+ case MYSQL_TYPE_YEAR: // YEAR field
161
+ val = rb_cstr2inum(row[i], 10);
162
+ break;
163
+ case MYSQL_TYPE_DECIMAL: // DECIMAL or NUMERIC field
164
+ case MYSQL_TYPE_NEWDECIMAL: // Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up)
165
+ if (strtod(row[i], NULL) == 0.000000){
166
+ val = rb_funcall(cBigDecimal, intern_new, 1, opt_decimal_zero);
167
+ }else{
168
+ val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i]));
169
+ }
170
+ break;
171
+ case MYSQL_TYPE_FLOAT: // FLOAT field
172
+ case MYSQL_TYPE_DOUBLE: { // DOUBLE or REAL field
173
+ double column_to_double;
174
+ column_to_double = strtod(row[i], NULL);
175
+ if (column_to_double == 0.000000){
176
+ val = opt_float_zero;
177
+ }else{
178
+ val = rb_float_new(column_to_double);
179
+ }
180
+ break;
181
+ }
182
+ case MYSQL_TYPE_TIME: { // TIME field
183
+ int hour, min, sec, tokens;
184
+ tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec);
185
+ val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
186
+ if (!NIL_P(app_timezone)) {
187
+ if (app_timezone == intern_local) {
188
+ val = rb_funcall(val, intern_localtime, 0);
189
+ } else { // utc
190
+ val = rb_funcall(val, intern_utc, 0);
191
+ }
192
+ }
193
+ break;
194
+ }
195
+ case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
196
+ case MYSQL_TYPE_DATETIME: { // DATETIME field
197
+ int year, month, day, hour, min, sec, tokens;
198
+ tokens = sscanf(row[i], "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
199
+ if (year+month+day+hour+min+sec == 0) {
200
+ val = Qnil;
201
+ } else {
202
+ if (month < 1 || day < 1) {
203
+ rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
204
+ val = Qnil;
205
+ } else {
206
+ if (year < MYSQL2_MIN_YEAR || year+month+day > MYSQL2_MAX_YEAR) { // use DateTime instead
207
+ VALUE offset = INT2NUM(0);
208
+ if (db_timezone == intern_local) {
209
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
210
+ }
211
+ val = rb_funcall(cDateTime, intern_civil, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), offset);
212
+ if (!NIL_P(app_timezone)) {
213
+ if (app_timezone == intern_local) {
214
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
215
+ val = rb_funcall(val, intern_new_offset, 1, offset);
216
+ } else { // utc
217
+ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
218
+ }
219
+ }
220
+ } else {
221
+ val = rb_funcall(rb_cTime, db_timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
222
+ if (!NIL_P(app_timezone)) {
223
+ if (app_timezone == intern_local) {
224
+ val = rb_funcall(val, intern_localtime, 0);
225
+ } else { // utc
226
+ val = rb_funcall(val, intern_utc, 0);
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ break;
233
+ }
234
+ case MYSQL_TYPE_DATE: // DATE field
235
+ case MYSQL_TYPE_NEWDATE: { // Newer const used > 5.0
236
+ int year, month, day, tokens;
237
+ tokens = sscanf(row[i], "%4d-%2d-%2d", &year, &month, &day);
238
+ if (year+month+day == 0) {
239
+ val = Qnil;
240
+ } else {
241
+ if (month < 1 || day < 1) {
242
+ rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
243
+ val = Qnil;
244
+ } else {
245
+ val = rb_funcall(cDate, intern_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
246
+ }
247
+ }
248
+ break;
249
+ }
250
+ case MYSQL_TYPE_TINY_BLOB:
251
+ case MYSQL_TYPE_MEDIUM_BLOB:
252
+ case MYSQL_TYPE_LONG_BLOB:
253
+ case MYSQL_TYPE_BLOB:
254
+ case MYSQL_TYPE_VAR_STRING:
255
+ case MYSQL_TYPE_VARCHAR:
256
+ case MYSQL_TYPE_STRING: // CHAR or BINARY field
257
+ case MYSQL_TYPE_SET: // SET field
258
+ case MYSQL_TYPE_ENUM: // ENUM field
259
+ case MYSQL_TYPE_GEOMETRY: // Spatial fielda
260
+ default:
261
+ val = rb_str_new(row[i], fieldLengths[i]);
262
+ #ifdef HAVE_RUBY_ENCODING_H
263
+ // if binary flag is set, respect it's wishes
264
+ if (fields[i].flags & BINARY_FLAG && fields[i].charsetnr == 63) {
265
+ rb_enc_associate(val, binaryEncoding);
266
+ } else {
267
+ // lookup the encoding configured on this field
268
+ VALUE new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset_code, 1, INT2NUM(fields[i].charsetnr));
269
+ if (new_encoding != Qnil) {
270
+ // use the field encoding we were able to match
271
+ rb_encoding *enc = rb_to_encoding(new_encoding);
272
+ rb_enc_associate(val, enc);
273
+ } else {
274
+ // otherwise fall-back to the connection's encoding
275
+ rb_enc_associate(val, conn_enc);
276
+ }
277
+ if (default_internal_enc) {
278
+ val = rb_str_export_to_enc(val, default_internal_enc);
279
+ }
280
+ }
281
+ #endif
282
+ break;
283
+ }
284
+ if (asArray) {
285
+ rb_ary_push(rowVal, val);
286
+ } else {
287
+ rb_hash_aset(rowVal, field, val);
288
+ }
289
+ } else {
290
+ if (asArray) {
291
+ rb_ary_push(rowVal, Qnil);
292
+ } else {
293
+ rb_hash_aset(rowVal, field, Qnil);
294
+ }
295
+ }
296
+ }
297
+ return rowVal;
298
+ }
299
+
300
+ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
301
+ mysql2_result_wrapper * wrapper;
302
+ unsigned int i = 0;
303
+ short int symbolizeKeys = 0;
304
+ VALUE defaults;
305
+
306
+ GetMysql2Result(self, wrapper);
307
+
308
+ defaults = rb_iv_get(self, "@query_options");
309
+ if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
310
+ symbolizeKeys = 1;
311
+ }
312
+
313
+ if (wrapper->fields == Qnil) {
314
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
315
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
316
+ }
317
+
318
+ if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
319
+ for (i=0; i<wrapper->numberOfFields; i++) {
320
+ rb_mysql_result_fetch_field(self, i, symbolizeKeys);
321
+ }
322
+ }
323
+
324
+ return wrapper->fields;
325
+ }
326
+
327
+ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
328
+ VALUE defaults, opts, block;
329
+ ID db_timezone, app_timezone, dbTz, appTz;
330
+ mysql2_result_wrapper * wrapper;
331
+ unsigned long i;
332
+ int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1;
333
+
334
+ GetMysql2Result(self, wrapper);
335
+
336
+ defaults = rb_iv_get(self, "@query_options");
337
+ if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
338
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
339
+ } else {
340
+ opts = defaults;
341
+ }
342
+
343
+ if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
344
+ symbolizeKeys = 1;
345
+ }
346
+
347
+ if (rb_hash_aref(opts, sym_as) == sym_array) {
348
+ asArray = 1;
349
+ }
350
+
351
+ if (rb_hash_aref(opts, sym_cast_booleans) == Qtrue) {
352
+ castBool = 1;
353
+ }
354
+
355
+ if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
356
+ cacheRows = 0;
357
+ }
358
+
359
+ dbTz = rb_hash_aref(opts, sym_database_timezone);
360
+ if (dbTz == sym_local) {
361
+ db_timezone = intern_local;
362
+ } else if (dbTz == sym_utc) {
363
+ db_timezone = intern_utc;
364
+ } else {
365
+ if (!NIL_P(dbTz)) {
366
+ rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
367
+ }
368
+ db_timezone = intern_local;
369
+ }
370
+
371
+ appTz = rb_hash_aref(opts, sym_application_timezone);
372
+ if (appTz == sym_local) {
373
+ app_timezone = intern_local;
374
+ } else if (appTz == sym_utc) {
375
+ app_timezone = intern_utc;
376
+ } else {
377
+ app_timezone = Qnil;
378
+ }
379
+
380
+ if (wrapper->lastRowProcessed == 0) {
381
+ wrapper->numberOfRows = mysql_num_rows(wrapper->result);
382
+ if (wrapper->numberOfRows == 0) {
383
+ wrapper->rows = rb_ary_new();
384
+ return wrapper->rows;
385
+ }
386
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
387
+ }
388
+
389
+ if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
390
+ // we've already read the entire dataset from the C result into our
391
+ // internal array. Lets hand that over to the user since it's ready to go
392
+ for (i = 0; i < wrapper->numberOfRows; i++) {
393
+ rb_yield(rb_ary_entry(wrapper->rows, i));
394
+ }
395
+ } else {
396
+ unsigned long rowsProcessed = 0;
397
+ rowsProcessed = RARRAY_LEN(wrapper->rows);
398
+ for (i = 0; i < wrapper->numberOfRows; i++) {
399
+ VALUE row;
400
+ if (cacheRows && i < rowsProcessed) {
401
+ row = rb_ary_entry(wrapper->rows, i);
402
+ } else {
403
+ row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool);
404
+ if (cacheRows) {
405
+ rb_ary_store(wrapper->rows, i, row);
406
+ }
407
+ wrapper->lastRowProcessed++;
408
+ }
409
+
410
+ if (row == Qnil) {
411
+ // we don't need the mysql C dataset around anymore, peace it
412
+ rb_mysql_result_free_result(wrapper);
413
+ return Qnil;
414
+ }
415
+
416
+ if (block != Qnil) {
417
+ rb_yield(row);
418
+ }
419
+ }
420
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
421
+ // we don't need the mysql C dataset around anymore, peace it
422
+ rb_mysql_result_free_result(wrapper);
423
+ }
424
+ }
425
+
426
+ return wrapper->rows;
427
+ }
428
+
429
+ /* Mysql2::Result */
430
+ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
431
+ VALUE obj;
432
+ mysql2_result_wrapper * wrapper;
433
+ obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
434
+ wrapper->numberOfFields = 0;
435
+ wrapper->numberOfRows = 0;
436
+ wrapper->lastRowProcessed = 0;
437
+ wrapper->resultFreed = 0;
438
+ wrapper->result = r;
439
+ wrapper->fields = Qnil;
440
+ wrapper->rows = Qnil;
441
+ wrapper->encoding = Qnil;
442
+ rb_obj_call_init(obj, 0, NULL);
443
+ return obj;
444
+ }
445
+
446
+ void init_mysql2_result() {
447
+ cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
448
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
449
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
450
+
451
+ cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
452
+ rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
453
+ rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
454
+
455
+ intern_encoding_from_charset = rb_intern("encoding_from_charset");
456
+ intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code");
457
+
458
+ intern_new = rb_intern("new");
459
+ intern_utc = rb_intern("utc");
460
+ intern_local = rb_intern("local");
461
+ intern_merge = rb_intern("merge");
462
+ intern_localtime = rb_intern("localtime");
463
+ intern_local_offset = rb_intern("local_offset");
464
+ intern_civil = rb_intern("civil");
465
+ intern_new_offset = rb_intern("new_offset");
466
+
467
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
468
+ sym_as = ID2SYM(rb_intern("as"));
469
+ sym_array = ID2SYM(rb_intern("array"));
470
+ sym_local = ID2SYM(rb_intern("local"));
471
+ sym_utc = ID2SYM(rb_intern("utc"));
472
+ sym_cast_booleans = ID2SYM(rb_intern("cast_booleans"));
473
+ sym_database_timezone = ID2SYM(rb_intern("database_timezone"));
474
+ sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
475
+ sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
476
+
477
+ opt_decimal_zero = rb_str_new2("0.0");
478
+ rb_global_variable(&opt_decimal_zero); //never GC
479
+ opt_float_zero = rb_float_new((double)0);
480
+ rb_global_variable(&opt_float_zero);
481
+ opt_time_year = INT2NUM(2000);
482
+ opt_time_month = INT2NUM(1);
483
+ opt_utc_offset = INT2NUM(0);
484
+
485
+ #ifdef HAVE_RUBY_ENCODING_H
486
+ binaryEncoding = rb_enc_find("binary");
487
+ #endif
488
+ }