mysql2-sp 0.3.10

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