solaris-mysql2 0.3.11

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 +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
+ }