solaris-mysql2 0.3.11

Sign up to get free protection for your applications and to get access to all the features.
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 +244 -0
  6. data/Gemfile +3 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +334 -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 +901 -0
  22. data/ext/mysql2/client.h +42 -0
  23. data/ext/mysql2/extconf.rb +74 -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 +566 -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 +264 -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/solaris-mysql2.gemspec +29 -0
  36. data/spec/em/em_spec.rb +50 -0
  37. data/spec/mysql2/client_spec.rb +465 -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,74 @@
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/mysql
21
+ /usr/local
22
+ /usr/local/mysql
23
+ /usr/local/mysql-*
24
+ /usr/local/lib/mysql5
25
+ ].map{|dir| "#{dir}/bin" }
26
+
27
+ GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}"
28
+
29
+ if RUBY_PLATFORM =~ /mswin|mingw/
30
+ inc, lib = dir_config('mysql')
31
+ exit 1 unless have_library("libmysql")
32
+ elsif mc = (with_config('mysql-config') || Dir[GLOB].first) then
33
+ mc = Dir[GLOB].first if mc == true
34
+ cflags = `#{mc} --cflags`.chomp
35
+ exit 1 if $? != 0
36
+ libs = `#{mc} --libs_r`.chomp
37
+ if libs.empty?
38
+ libs = `#{mc} --libs`.chomp
39
+ end
40
+ exit 1 if $? != 0
41
+ $CPPFLAGS += ' ' + cflags
42
+ $libs = libs + " " + $libs
43
+ else
44
+ inc, lib = dir_config('mysql', '/usr/local')
45
+ libs = ['m', 'z', 'socket', 'nsl', 'mygcc']
46
+ while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do
47
+ exit 1 if libs.empty?
48
+ have_library(libs.shift)
49
+ end
50
+ end
51
+
52
+ if have_header('mysql.h') then
53
+ prefix = nil
54
+ elsif have_header('mysql/mysql.h') then
55
+ prefix = 'mysql'
56
+ else
57
+ asplode 'mysql.h'
58
+ end
59
+
60
+ %w{ errmsg.h mysqld_error.h }.each do |h|
61
+ header = [prefix, h].compact.join '/'
62
+ asplode h unless have_header h
63
+ end
64
+
65
+ unless RUBY_PLATFORM =~ /mswin|sparc|solaris/
66
+ $CFLAGS << ' -Wall -funroll-loops'
67
+ end
68
+ # $CFLAGS << ' -O0 -ggdb3 -Wextra'
69
+
70
+ if hard_mysql_path = $libs[%r{-L(/[^ ]+)}, 1] && RUBY_PLATFORM !~ /solaris/
71
+ $LDFLAGS << " -Wl,-rpath,#{hard_mysql_path}"
72
+ end
73
+
74
+ 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,566 @@
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
+ xfree(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 (strtod(row[i], NULL) == 0.000000){
241
+ val = rb_funcall(cBigDecimal, intern_new, 1, opt_decimal_zero);
242
+ }else{
243
+ val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i]));
244
+ }
245
+ break;
246
+ case MYSQL_TYPE_FLOAT: // FLOAT field
247
+ case MYSQL_TYPE_DOUBLE: { // DOUBLE or REAL field
248
+ double column_to_double;
249
+ column_to_double = strtod(row[i], NULL);
250
+ if (column_to_double == 0.000000){
251
+ val = opt_float_zero;
252
+ }else{
253
+ val = rb_float_new(column_to_double);
254
+ }
255
+ break;
256
+ }
257
+ case MYSQL_TYPE_TIME: { // TIME field
258
+ int hour, min, sec, tokens;
259
+ tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec);
260
+ val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
261
+ if (!NIL_P(app_timezone)) {
262
+ if (app_timezone == intern_local) {
263
+ val = rb_funcall(val, intern_localtime, 0);
264
+ } else { // utc
265
+ val = rb_funcall(val, intern_utc, 0);
266
+ }
267
+ }
268
+ break;
269
+ }
270
+ case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
271
+ case MYSQL_TYPE_DATETIME: { // DATETIME field
272
+ unsigned int year, month, day, hour, min, sec, tokens;
273
+ uint64_t seconds;
274
+
275
+ tokens = sscanf(row[i], "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
276
+ seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec;
277
+
278
+ if (seconds == 0) {
279
+ val = Qnil;
280
+ } else {
281
+ if (month < 1 || day < 1) {
282
+ rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
283
+ val = Qnil;
284
+ } else {
285
+ if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
286
+ VALUE offset = INT2NUM(0);
287
+ if (db_timezone == intern_local) {
288
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
289
+ }
290
+ val = rb_funcall(cDateTime, intern_civil, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), offset);
291
+ if (!NIL_P(app_timezone)) {
292
+ if (app_timezone == intern_local) {
293
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
294
+ val = rb_funcall(val, intern_new_offset, 1, offset);
295
+ } else { // utc
296
+ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
297
+ }
298
+ }
299
+ } else {
300
+ val = rb_funcall(rb_cTime, db_timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
301
+ if (!NIL_P(app_timezone)) {
302
+ if (app_timezone == intern_local) {
303
+ val = rb_funcall(val, intern_localtime, 0);
304
+ } else { // utc
305
+ val = rb_funcall(val, intern_utc, 0);
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+ break;
312
+ }
313
+ case MYSQL_TYPE_DATE: // DATE field
314
+ case MYSQL_TYPE_NEWDATE: { // Newer const used > 5.0
315
+ int year, month, day, tokens;
316
+ tokens = sscanf(row[i], "%4d-%2d-%2d", &year, &month, &day);
317
+ if (year+month+day == 0) {
318
+ val = Qnil;
319
+ } else {
320
+ if (month < 1 || day < 1) {
321
+ rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
322
+ val = Qnil;
323
+ } else {
324
+ val = rb_funcall(cDate, intern_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
325
+ }
326
+ }
327
+ break;
328
+ }
329
+ case MYSQL_TYPE_TINY_BLOB:
330
+ case MYSQL_TYPE_MEDIUM_BLOB:
331
+ case MYSQL_TYPE_LONG_BLOB:
332
+ case MYSQL_TYPE_BLOB:
333
+ case MYSQL_TYPE_VAR_STRING:
334
+ case MYSQL_TYPE_VARCHAR:
335
+ case MYSQL_TYPE_STRING: // CHAR or BINARY field
336
+ case MYSQL_TYPE_SET: // SET field
337
+ case MYSQL_TYPE_ENUM: // ENUM field
338
+ case MYSQL_TYPE_GEOMETRY: // Spatial fielda
339
+ default:
340
+ val = rb_str_new(row[i], fieldLengths[i]);
341
+ #ifdef HAVE_RUBY_ENCODING_H
342
+ val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
343
+ #endif
344
+ break;
345
+ }
346
+ }
347
+ if (asArray) {
348
+ rb_ary_push(rowVal, val);
349
+ } else {
350
+ rb_hash_aset(rowVal, field, val);
351
+ }
352
+ } else {
353
+ if (asArray) {
354
+ rb_ary_push(rowVal, Qnil);
355
+ } else {
356
+ rb_hash_aset(rowVal, field, Qnil);
357
+ }
358
+ }
359
+ }
360
+ return rowVal;
361
+ }
362
+
363
+ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
364
+ mysql2_result_wrapper * wrapper;
365
+ unsigned int i = 0;
366
+ short int symbolizeKeys = 0;
367
+ VALUE defaults;
368
+
369
+ GetMysql2Result(self, wrapper);
370
+
371
+ defaults = rb_iv_get(self, "@query_options");
372
+ if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
373
+ symbolizeKeys = 1;
374
+ }
375
+
376
+ if (wrapper->fields == Qnil) {
377
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
378
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
379
+ }
380
+
381
+ if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
382
+ for (i=0; i<wrapper->numberOfFields; i++) {
383
+ rb_mysql_result_fetch_field(self, i, symbolizeKeys);
384
+ }
385
+ }
386
+
387
+ return wrapper->fields;
388
+ }
389
+
390
+ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
391
+ VALUE defaults, opts, block;
392
+ ID db_timezone, app_timezone, dbTz, appTz;
393
+ mysql2_result_wrapper * wrapper;
394
+ unsigned long i;
395
+ int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1;
396
+
397
+ GetMysql2Result(self, wrapper);
398
+
399
+ defaults = rb_iv_get(self, "@query_options");
400
+ if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
401
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
402
+ } else {
403
+ opts = defaults;
404
+ }
405
+
406
+ if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
407
+ symbolizeKeys = 1;
408
+ }
409
+
410
+ if (rb_hash_aref(opts, sym_as) == sym_array) {
411
+ asArray = 1;
412
+ }
413
+
414
+ if (rb_hash_aref(opts, sym_cast_booleans) == Qtrue) {
415
+ castBool = 1;
416
+ }
417
+
418
+ if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
419
+ cacheRows = 0;
420
+ }
421
+
422
+ if (rb_hash_aref(opts, sym_cast) == Qfalse) {
423
+ cast = 0;
424
+ }
425
+
426
+ dbTz = rb_hash_aref(opts, sym_database_timezone);
427
+ if (dbTz == sym_local) {
428
+ db_timezone = intern_local;
429
+ } else if (dbTz == sym_utc) {
430
+ db_timezone = intern_utc;
431
+ } else {
432
+ if (!NIL_P(dbTz)) {
433
+ rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
434
+ }
435
+ db_timezone = intern_local;
436
+ }
437
+
438
+ appTz = rb_hash_aref(opts, sym_application_timezone);
439
+ if (appTz == sym_local) {
440
+ app_timezone = intern_local;
441
+ } else if (appTz == sym_utc) {
442
+ app_timezone = intern_utc;
443
+ } else {
444
+ app_timezone = Qnil;
445
+ }
446
+
447
+ if (wrapper->lastRowProcessed == 0) {
448
+ wrapper->numberOfRows = mysql_num_rows(wrapper->result);
449
+ if (wrapper->numberOfRows == 0) {
450
+ wrapper->rows = rb_ary_new();
451
+ return wrapper->rows;
452
+ }
453
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
454
+ }
455
+
456
+ if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
457
+ // we've already read the entire dataset from the C result into our
458
+ // internal array. Lets hand that over to the user since it's ready to go
459
+ for (i = 0; i < wrapper->numberOfRows; i++) {
460
+ rb_yield(rb_ary_entry(wrapper->rows, i));
461
+ }
462
+ } else {
463
+ unsigned long rowsProcessed = 0;
464
+ rowsProcessed = RARRAY_LEN(wrapper->rows);
465
+ for (i = 0; i < wrapper->numberOfRows; i++) {
466
+ VALUE row;
467
+ if (cacheRows && i < rowsProcessed) {
468
+ row = rb_ary_entry(wrapper->rows, i);
469
+ } else {
470
+ row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast);
471
+ if (cacheRows) {
472
+ rb_ary_store(wrapper->rows, i, row);
473
+ }
474
+ wrapper->lastRowProcessed++;
475
+ }
476
+
477
+ if (row == Qnil) {
478
+ // we don't need the mysql C dataset around anymore, peace it
479
+ rb_mysql_result_free_result(wrapper);
480
+ return Qnil;
481
+ }
482
+
483
+ if (block != Qnil) {
484
+ rb_yield(row);
485
+ }
486
+ }
487
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
488
+ // we don't need the mysql C dataset around anymore, peace it
489
+ rb_mysql_result_free_result(wrapper);
490
+ }
491
+ }
492
+
493
+ return wrapper->rows;
494
+ }
495
+
496
+ static VALUE rb_mysql_result_count(VALUE self) {
497
+ mysql2_result_wrapper *wrapper;
498
+
499
+ GetMysql2Result(self, wrapper);
500
+
501
+ return INT2FIX(mysql_num_rows(wrapper->result));
502
+ }
503
+
504
+ /* Mysql2::Result */
505
+ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
506
+ VALUE obj;
507
+ mysql2_result_wrapper * wrapper;
508
+ obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
509
+ wrapper->numberOfFields = 0;
510
+ wrapper->numberOfRows = 0;
511
+ wrapper->lastRowProcessed = 0;
512
+ wrapper->resultFreed = 0;
513
+ wrapper->result = r;
514
+ wrapper->fields = Qnil;
515
+ wrapper->rows = Qnil;
516
+ wrapper->encoding = Qnil;
517
+ rb_obj_call_init(obj, 0, NULL);
518
+ return obj;
519
+ }
520
+
521
+ void init_mysql2_result() {
522
+ cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
523
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
524
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
525
+
526
+ cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
527
+ rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
528
+ rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
529
+ rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
530
+ rb_define_alias(cMysql2Result, "size", "count");
531
+
532
+ intern_encoding_from_charset = rb_intern("encoding_from_charset");
533
+ intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code");
534
+
535
+ intern_new = rb_intern("new");
536
+ intern_utc = rb_intern("utc");
537
+ intern_local = rb_intern("local");
538
+ intern_merge = rb_intern("merge");
539
+ intern_localtime = rb_intern("localtime");
540
+ intern_local_offset = rb_intern("local_offset");
541
+ intern_civil = rb_intern("civil");
542
+ intern_new_offset = rb_intern("new_offset");
543
+
544
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
545
+ sym_as = ID2SYM(rb_intern("as"));
546
+ sym_array = ID2SYM(rb_intern("array"));
547
+ sym_local = ID2SYM(rb_intern("local"));
548
+ sym_utc = ID2SYM(rb_intern("utc"));
549
+ sym_cast_booleans = ID2SYM(rb_intern("cast_booleans"));
550
+ sym_database_timezone = ID2SYM(rb_intern("database_timezone"));
551
+ sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
552
+ sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
553
+ sym_cast = ID2SYM(rb_intern("cast"));
554
+
555
+ opt_decimal_zero = rb_str_new2("0.0");
556
+ rb_global_variable(&opt_decimal_zero); //never GC
557
+ opt_float_zero = rb_float_new((double)0);
558
+ rb_global_variable(&opt_float_zero);
559
+ opt_time_year = INT2NUM(2000);
560
+ opt_time_month = INT2NUM(1);
561
+ opt_utc_offset = INT2NUM(0);
562
+
563
+ #ifdef HAVE_RUBY_ENCODING_H
564
+ binaryEncoding = rb_enc_find("binary");
565
+ #endif
566
+ }