mysql2-sp 0.3.10

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