rdp-mysql2 0.2.7.1

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