mysql2 0.3.11-x86-mswin32-60 → 0.3.18-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +280 -75
  3. data/ext/mysql2/client.c +721 -206
  4. data/ext/mysql2/client.h +26 -12
  5. data/ext/mysql2/extconf.rb +120 -16
  6. data/ext/mysql2/infile.c +122 -0
  7. data/ext/mysql2/infile.h +1 -0
  8. data/ext/mysql2/mysql2_ext.h +7 -4
  9. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  10. data/ext/mysql2/mysql_enc_to_ruby.h +246 -0
  11. data/ext/mysql2/result.c +230 -112
  12. data/ext/mysql2/result.h +4 -1
  13. data/lib/mysql2.rb +46 -3
  14. data/lib/mysql2/1.8/mysql2.so +0 -0
  15. data/lib/mysql2/1.9/mysql2.so +0 -0
  16. data/lib/mysql2/2.0/mysql2.so +0 -0
  17. data/lib/mysql2/2.1/mysql2.so +0 -0
  18. data/lib/mysql2/client.rb +48 -200
  19. data/lib/mysql2/console.rb +5 -0
  20. data/lib/mysql2/em.rb +22 -3
  21. data/lib/mysql2/error.rb +71 -6
  22. data/lib/mysql2/mysql2.rb +2 -0
  23. data/lib/mysql2/version.rb +1 -1
  24. data/spec/configuration.yml.example +17 -0
  25. data/spec/em/em_spec.rb +90 -5
  26. data/spec/my.cnf.example +9 -0
  27. data/spec/mysql2/client_spec.rb +501 -69
  28. data/spec/mysql2/error_spec.rb +58 -44
  29. data/spec/mysql2/result_spec.rb +191 -74
  30. data/spec/spec_helper.rb +23 -3
  31. data/spec/test_data +1 -0
  32. data/support/libmysql.def +219 -0
  33. data/support/mysql_enc_to_ruby.rb +82 -0
  34. data/support/ruby_enc_to_mysql.rb +61 -0
  35. data/vendor/README +654 -0
  36. data/vendor/libmysql.dll +0 -0
  37. metadata +86 -221
  38. data/.gitignore +0 -12
  39. data/.rspec +0 -3
  40. data/.rvmrc +0 -1
  41. data/.travis.yml +0 -7
  42. data/CHANGELOG.md +0 -244
  43. data/Gemfile +0 -3
  44. data/MIT-LICENSE +0 -20
  45. data/Rakefile +0 -5
  46. data/benchmark/active_record.rb +0 -51
  47. data/benchmark/active_record_threaded.rb +0 -42
  48. data/benchmark/allocations.rb +0 -33
  49. data/benchmark/escape.rb +0 -36
  50. data/benchmark/query_with_mysql_casting.rb +0 -80
  51. data/benchmark/query_without_mysql_casting.rb +0 -56
  52. data/benchmark/sequel.rb +0 -37
  53. data/benchmark/setup_db.rb +0 -119
  54. data/benchmark/threaded.rb +0 -44
  55. data/mysql2.gemspec +0 -29
  56. data/tasks/benchmarks.rake +0 -20
  57. data/tasks/compile.rake +0 -71
  58. data/tasks/rspec.rake +0 -16
  59. data/tasks/vendor_mysql.rake +0 -40
@@ -0,0 +1,246 @@
1
+ const char *mysql2_mysql_enc_to_rb[] = {
2
+ "Big5",
3
+ "ISO-8859-2",
4
+ NULL,
5
+ "CP850",
6
+ "ISO-8859-1",
7
+ NULL,
8
+ "KOI8-R",
9
+ "ISO-8859-1",
10
+ "ISO-8859-2",
11
+ NULL,
12
+ "US-ASCII",
13
+ "eucJP-ms",
14
+ "Shift_JIS",
15
+ "Windows-1251",
16
+ "ISO-8859-1",
17
+ "ISO-8859-8",
18
+ NULL,
19
+ "TIS-620",
20
+ "EUC-KR",
21
+ "ISO-8859-13",
22
+ "ISO-8859-2",
23
+ "KOI8-R",
24
+ "Windows-1251",
25
+ "GB2312",
26
+ "ISO-8859-7",
27
+ "Windows-1250",
28
+ "ISO-8859-2",
29
+ "GBK",
30
+ "Windows-1257",
31
+ "ISO-8859-9",
32
+ "ISO-8859-1",
33
+ NULL,
34
+ "UTF-8",
35
+ "Windows-1250",
36
+ "UTF-16BE",
37
+ "IBM866",
38
+ NULL,
39
+ "macCentEuro",
40
+ "macRoman",
41
+ "CP852",
42
+ "ISO-8859-13",
43
+ "ISO-8859-13",
44
+ "macCentEuro",
45
+ "Windows-1250",
46
+ "UTF-8",
47
+ "UTF-8",
48
+ "ISO-8859-1",
49
+ "ISO-8859-1",
50
+ "ISO-8859-1",
51
+ "Windows-1251",
52
+ "Windows-1251",
53
+ "Windows-1251",
54
+ "macRoman",
55
+ "UTF-16",
56
+ "UTF-16",
57
+ NULL,
58
+ "Windows-1256",
59
+ "Windows-1257",
60
+ "Windows-1257",
61
+ "UTF-32",
62
+ "UTF-32",
63
+ NULL,
64
+ "ASCII-8BIT",
65
+ NULL,
66
+ "US-ASCII",
67
+ "Windows-1250",
68
+ "Windows-1256",
69
+ "IBM866",
70
+ NULL,
71
+ "ISO-8859-7",
72
+ "ISO-8859-8",
73
+ NULL,
74
+ NULL,
75
+ "KOI8-R",
76
+ "KOI8-R",
77
+ NULL,
78
+ "ISO-8859-2",
79
+ "ISO-8859-9",
80
+ "ISO-8859-13",
81
+ "CP850",
82
+ "CP852",
83
+ NULL,
84
+ "UTF-8",
85
+ "Big5",
86
+ "EUC-KR",
87
+ "GB2312",
88
+ "GBK",
89
+ "Shift_JIS",
90
+ "TIS-620",
91
+ "UTF-16BE",
92
+ "eucJP-ms",
93
+ NULL,
94
+ NULL,
95
+ "ISO-8859-1",
96
+ "Windows-31J",
97
+ "Windows-31J",
98
+ "eucJP-ms",
99
+ "eucJP-ms",
100
+ "Windows-1250",
101
+ NULL,
102
+ "UTF-16",
103
+ "UTF-16",
104
+ "UTF-16",
105
+ "UTF-16",
106
+ "UTF-16",
107
+ "UTF-16",
108
+ "UTF-16",
109
+ "UTF-16",
110
+ "UTF-16",
111
+ "UTF-16",
112
+ "UTF-16",
113
+ "UTF-16",
114
+ "UTF-16",
115
+ "UTF-16",
116
+ "UTF-16",
117
+ "UTF-16",
118
+ "UTF-16",
119
+ "UTF-16",
120
+ "UTF-16",
121
+ "UTF-16",
122
+ NULL,
123
+ NULL,
124
+ NULL,
125
+ NULL,
126
+ NULL,
127
+ NULL,
128
+ NULL,
129
+ "UTF-16BE",
130
+ "UTF-16BE",
131
+ "UTF-16BE",
132
+ "UTF-16BE",
133
+ "UTF-16BE",
134
+ "UTF-16BE",
135
+ "UTF-16BE",
136
+ "UTF-16BE",
137
+ "UTF-16BE",
138
+ "UTF-16BE",
139
+ "UTF-16BE",
140
+ "UTF-16BE",
141
+ "UTF-16BE",
142
+ "UTF-16BE",
143
+ "UTF-16BE",
144
+ "UTF-16BE",
145
+ "UTF-16BE",
146
+ "UTF-16BE",
147
+ "UTF-16BE",
148
+ "UTF-16BE",
149
+ NULL,
150
+ NULL,
151
+ NULL,
152
+ NULL,
153
+ NULL,
154
+ NULL,
155
+ NULL,
156
+ NULL,
157
+ NULL,
158
+ NULL,
159
+ NULL,
160
+ NULL,
161
+ "UTF-32",
162
+ "UTF-32",
163
+ "UTF-32",
164
+ "UTF-32",
165
+ "UTF-32",
166
+ "UTF-32",
167
+ "UTF-32",
168
+ "UTF-32",
169
+ "UTF-32",
170
+ "UTF-32",
171
+ "UTF-32",
172
+ "UTF-32",
173
+ "UTF-32",
174
+ "UTF-32",
175
+ "UTF-32",
176
+ "UTF-32",
177
+ "UTF-32",
178
+ "UTF-32",
179
+ "UTF-32",
180
+ "UTF-32",
181
+ NULL,
182
+ NULL,
183
+ NULL,
184
+ NULL,
185
+ NULL,
186
+ NULL,
187
+ NULL,
188
+ NULL,
189
+ NULL,
190
+ NULL,
191
+ NULL,
192
+ NULL,
193
+ "UTF-8",
194
+ "UTF-8",
195
+ "UTF-8",
196
+ "UTF-8",
197
+ "UTF-8",
198
+ "UTF-8",
199
+ "UTF-8",
200
+ "UTF-8",
201
+ "UTF-8",
202
+ "UTF-8",
203
+ "UTF-8",
204
+ "UTF-8",
205
+ "UTF-8",
206
+ "UTF-8",
207
+ "UTF-8",
208
+ "UTF-8",
209
+ "UTF-8",
210
+ "UTF-8",
211
+ "UTF-8",
212
+ "UTF-8",
213
+ NULL,
214
+ NULL,
215
+ NULL,
216
+ NULL,
217
+ NULL,
218
+ NULL,
219
+ NULL,
220
+ NULL,
221
+ NULL,
222
+ NULL,
223
+ NULL,
224
+ NULL,
225
+ "UTF-8",
226
+ "UTF-8",
227
+ "UTF-8",
228
+ "UTF-8",
229
+ "UTF-8",
230
+ "UTF-8",
231
+ "UTF-8",
232
+ "UTF-8",
233
+ "UTF-8",
234
+ "UTF-8",
235
+ "UTF-8",
236
+ "UTF-8",
237
+ "UTF-8",
238
+ "UTF-8",
239
+ "UTF-8",
240
+ "UTF-8",
241
+ "UTF-8",
242
+ "UTF-8",
243
+ "UTF-8",
244
+ "UTF-8"
245
+ };
246
+
@@ -1,6 +1,9 @@
1
1
  #include <mysql2_ext.h>
2
+
2
3
  #include <stdint.h>
3
4
 
5
+ #include "mysql_enc_to_ruby.h"
6
+
4
7
  #ifdef HAVE_RUBY_ENCODING_H
5
8
  static rb_encoding *binaryEncoding;
6
9
  #endif
@@ -27,7 +30,7 @@ static rb_encoding *binaryEncoding;
27
30
  * (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
28
31
  */
29
32
  #define MYSQL2_MIN_TIME 2678400ULL
30
- #elif SIZEOF_INT < SIZEOF_LONG // 64bit Ruby 1.8
33
+ #elif SIZEOF_INT < SIZEOF_LONG /* 64bit Ruby 1.8 */
31
34
  /* 0139-1-1 00:00:00 UTC
32
35
  *
33
36
  * (139*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
@@ -51,11 +54,9 @@ static VALUE cMysql2Result;
51
54
  static VALUE cBigDecimal, cDate, cDateTime;
52
55
  static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
53
56
  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 ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
57
58
  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
+ sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
59
60
  static ID intern_merge;
60
61
 
61
62
  static void rb_mysql_result_mark(void * wrapper) {
@@ -64,22 +65,29 @@ static void rb_mysql_result_mark(void * wrapper) {
64
65
  rb_gc_mark(w->fields);
65
66
  rb_gc_mark(w->rows);
66
67
  rb_gc_mark(w->encoding);
68
+ rb_gc_mark(w->client);
67
69
  }
68
70
  }
69
71
 
70
72
  /* this may be called manually or during GC */
71
73
  static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
72
74
  if (wrapper && wrapper->resultFreed != 1) {
75
+ /* FIXME: this may call flush_use_result, which can hit the socket */
73
76
  mysql_free_result(wrapper->result);
74
77
  wrapper->resultFreed = 1;
75
78
  }
76
79
  }
77
80
 
78
81
  /* 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);
82
+ static void rb_mysql_result_free(void *ptr) {
83
+ mysql2_result_wrapper * wrapper = ptr;
84
+ rb_mysql_result_free_result(wrapper);
85
+
86
+ // If the GC gets to client first it will be nil
87
+ if (wrapper->client != Qnil) {
88
+ decr_mysql2_client(wrapper->client_wrapper);
89
+ }
90
+
83
91
  xfree(wrapper);
84
92
  }
85
93
 
@@ -88,10 +96,10 @@ static void rb_mysql_result_free(void * wrapper) {
88
96
  * reliable way for us to tell this so we'll always release the GVL
89
97
  * to be safe
90
98
  */
91
- static VALUE nogvl_fetch_row(void *ptr) {
99
+ static void *nogvl_fetch_row(void *ptr) {
92
100
  MYSQL_RES *result = ptr;
93
101
 
94
- return (VALUE)mysql_fetch_row(result);
102
+ return mysql_fetch_row(result);
95
103
  }
96
104
 
97
105
  static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
@@ -114,15 +122,14 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
114
122
 
115
123
  field = mysql_fetch_field_direct(wrapper->result, idx);
116
124
  if (symbolize_keys) {
125
+ #ifdef HAVE_RB_INTERN3
126
+ rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding());
127
+ rb_field = ID2SYM(rb_field);
128
+ #else
117
129
  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
130
+ colStr = rb_str_new(field->name, field->name_length);
125
131
  rb_field = ID2SYM(rb_to_id(colStr));
132
+ #endif
126
133
  } else {
127
134
  rb_field = rb_str_new(field->name, field->name_length);
128
135
  #ifdef HAVE_RUBY_ENCODING_H
@@ -140,20 +147,27 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
140
147
 
141
148
  #ifdef HAVE_RUBY_ENCODING_H
142
149
  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
150
+ /* if binary flag is set, respect it's wishes */
144
151
  if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
145
152
  rb_enc_associate(val, binaryEncoding);
153
+ } else if (!field.charsetnr) {
154
+ /* MySQL 4.x may not provide an encoding, binary will get the bytes through */
155
+ rb_enc_associate(val, binaryEncoding);
146
156
  } 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);
157
+ /* lookup the encoding configured on this field */
158
+ const char *enc_name;
159
+ int enc_index;
160
+
161
+ enc_name = mysql2_mysql_enc_to_rb[field.charsetnr-1];
162
+ if (enc_name != NULL) {
163
+ /* use the field encoding we were able to match */
164
+ enc_index = rb_enc_find_index(enc_name);
165
+ rb_enc_set_index(val, enc_index);
153
166
  } else {
154
- // otherwise fall-back to the connection's encoding
167
+ /* otherwise fall-back to the connection's encoding */
155
168
  rb_enc_associate(val, conn_enc);
156
169
  }
170
+
157
171
  if (default_internal_enc) {
158
172
  val = rb_str_export_to_enc(val, default_internal_enc);
159
173
  }
@@ -162,12 +176,25 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
162
176
  }
163
177
  #endif
164
178
 
179
+ /* Interpret microseconds digits left-aligned in fixed-width field.
180
+ * e.g. 10.123 seconds means 10 seconds and 123000 microseconds,
181
+ * because the microseconds are to the right of the decimal point.
182
+ */
183
+ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
184
+ {
185
+ int i;
186
+ for (i = 0; i < (len - 1); i++) {
187
+ if (msec_char[i] == '\0') {
188
+ msec_char[i] = '0';
189
+ }
190
+ }
191
+ return (unsigned int)strtoul(msec_char, NULL, 10);
192
+ }
165
193
 
166
- static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast) {
194
+ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) {
167
195
  VALUE rowVal;
168
196
  mysql2_result_wrapper * wrapper;
169
197
  MYSQL_ROW row;
170
- MYSQL_FIELD * fields = NULL;
171
198
  unsigned int i = 0;
172
199
  unsigned long * fieldLengths;
173
200
  void * ptr;
@@ -183,7 +210,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
183
210
  #endif
184
211
 
185
212
  ptr = wrapper->result;
186
- row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
213
+ row = (MYSQL_ROW)rb_thread_call_without_gvl(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
187
214
  if (row == NULL) {
188
215
  return Qnil;
189
216
  }
@@ -193,7 +220,6 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
193
220
  } else {
194
221
  rowVal = rb_hash_new();
195
222
  }
196
- fields = mysql_fetch_fields(wrapper->result);
197
223
  fieldLengths = mysql_fetch_lengths(wrapper->result);
198
224
  if (wrapper->fields == Qnil) {
199
225
  wrapper->numberOfFields = mysql_num_fields(wrapper->result);
@@ -206,7 +232,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
206
232
  VALUE val = Qnil;
207
233
  enum enum_field_types type = fields[i].type;
208
234
 
209
- if(!cast) {
235
+ if (!cast) {
210
236
  if (type == MYSQL_TYPE_NULL) {
211
237
  val = Qnil;
212
238
  } else {
@@ -217,34 +243,40 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
217
243
  }
218
244
  } else {
219
245
  switch(type) {
220
- case MYSQL_TYPE_NULL: // NULL-type field
246
+ case MYSQL_TYPE_NULL: /* NULL-type field */
221
247
  val = Qnil;
222
248
  break;
223
- case MYSQL_TYPE_BIT: // BIT field (MySQL 5.0.3 and up)
224
- val = rb_str_new(row[i], fieldLengths[i]);
249
+ case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
250
+ if (castBool && fields[i].length == 1) {
251
+ val = *row[i] == 1 ? Qtrue : Qfalse;
252
+ }else{
253
+ val = rb_str_new(row[i], fieldLengths[i]);
254
+ }
225
255
  break;
226
- case MYSQL_TYPE_TINY: // TINYINT field
256
+ case MYSQL_TYPE_TINY: /* TINYINT field */
227
257
  if (castBool && fields[i].length == 1) {
228
- val = *row[i] == '1' ? Qtrue : Qfalse;
258
+ val = *row[i] != '0' ? Qtrue : Qfalse;
229
259
  break;
230
260
  }
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
261
+ case MYSQL_TYPE_SHORT: /* SMALLINT field */
262
+ case MYSQL_TYPE_LONG: /* INTEGER field */
263
+ case MYSQL_TYPE_INT24: /* MEDIUMINT field */
264
+ case MYSQL_TYPE_LONGLONG: /* BIGINT field */
265
+ case MYSQL_TYPE_YEAR: /* YEAR field */
236
266
  val = rb_cstr2inum(row[i], 10);
237
267
  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){
268
+ case MYSQL_TYPE_DECIMAL: /* DECIMAL or NUMERIC field */
269
+ case MYSQL_TYPE_NEWDECIMAL: /* Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up) */
270
+ if (fields[i].decimals == 0) {
271
+ val = rb_cstr2inum(row[i], 10);
272
+ } else if (strtod(row[i], NULL) == 0.000000){
241
273
  val = rb_funcall(cBigDecimal, intern_new, 1, opt_decimal_zero);
242
274
  }else{
243
275
  val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i]));
244
276
  }
245
277
  break;
246
- case MYSQL_TYPE_FLOAT: // FLOAT field
247
- case MYSQL_TYPE_DOUBLE: { // DOUBLE or REAL field
278
+ case MYSQL_TYPE_FLOAT: /* FLOAT field */
279
+ case MYSQL_TYPE_DOUBLE: { /* DOUBLE or REAL field */
248
280
  double column_to_double;
249
281
  column_to_double = strtod(row[i], NULL);
250
282
  if (column_to_double == 0.000000){
@@ -254,54 +286,69 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
254
286
  }
255
287
  break;
256
288
  }
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));
289
+ case MYSQL_TYPE_TIME: { /* TIME field */
290
+ int tokens;
291
+ unsigned int hour=0, min=0, sec=0, msec=0;
292
+ char msec_char[7] = {'0','0','0','0','0','0','\0'};
293
+
294
+ tokens = sscanf(row[i], "%2u:%2u:%2u.%6s", &hour, &min, &sec, msec_char);
295
+ if (tokens < 3) {
296
+ val = Qnil;
297
+ break;
298
+ }
299
+ msec = msec_char_to_uint(msec_char, sizeof(msec_char));
300
+ val = rb_funcall(rb_cTime, db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
261
301
  if (!NIL_P(app_timezone)) {
262
302
  if (app_timezone == intern_local) {
263
303
  val = rb_funcall(val, intern_localtime, 0);
264
- } else { // utc
304
+ } else { /* utc */
265
305
  val = rb_funcall(val, intern_utc, 0);
266
306
  }
267
307
  }
268
308
  break;
269
309
  }
270
- case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
271
- case MYSQL_TYPE_DATETIME: { // DATETIME field
272
- unsigned int year, month, day, hour, min, sec, tokens;
310
+ case MYSQL_TYPE_TIMESTAMP: /* TIMESTAMP field */
311
+ case MYSQL_TYPE_DATETIME: { /* DATETIME field */
312
+ int tokens;
313
+ unsigned int year=0, month=0, day=0, hour=0, min=0, sec=0, msec=0;
314
+ char msec_char[7] = {'0','0','0','0','0','0','\0'};
273
315
  uint64_t seconds;
274
316
 
275
- tokens = sscanf(row[i], "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
317
+ tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6s", &year, &month, &day, &hour, &min, &sec, msec_char);
318
+ if (tokens < 6) { /* msec might be empty */
319
+ val = Qnil;
320
+ break;
321
+ }
276
322
  seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec;
277
323
 
278
324
  if (seconds == 0) {
279
325
  val = Qnil;
280
326
  } else {
281
327
  if (month < 1 || day < 1) {
282
- rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
328
+ rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]);
283
329
  val = Qnil;
284
330
  } else {
285
- if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
331
+ if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
286
332
  VALUE offset = INT2NUM(0);
287
333
  if (db_timezone == intern_local) {
288
334
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
289
335
  }
290
- val = rb_funcall(cDateTime, intern_civil, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), offset);
336
+ val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset);
291
337
  if (!NIL_P(app_timezone)) {
292
338
  if (app_timezone == intern_local) {
293
339
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
294
340
  val = rb_funcall(val, intern_new_offset, 1, offset);
295
- } else { // utc
341
+ } else { /* utc */
296
342
  val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
297
343
  }
298
344
  }
299
345
  } else {
300
- val = rb_funcall(rb_cTime, db_timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
346
+ msec = msec_char_to_uint(msec_char, sizeof(msec_char));
347
+ val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
301
348
  if (!NIL_P(app_timezone)) {
302
349
  if (app_timezone == intern_local) {
303
350
  val = rb_funcall(val, intern_localtime, 0);
304
- } else { // utc
351
+ } else { /* utc */
305
352
  val = rb_funcall(val, intern_utc, 0);
306
353
  }
307
354
  }
@@ -310,18 +357,23 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
310
357
  }
311
358
  break;
312
359
  }
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);
360
+ case MYSQL_TYPE_DATE: /* DATE field */
361
+ case MYSQL_TYPE_NEWDATE: { /* Newer const used > 5.0 */
362
+ int tokens;
363
+ unsigned int year=0, month=0, day=0;
364
+ tokens = sscanf(row[i], "%4u-%2u-%2u", &year, &month, &day);
365
+ if (tokens < 3) {
366
+ val = Qnil;
367
+ break;
368
+ }
317
369
  if (year+month+day == 0) {
318
370
  val = Qnil;
319
371
  } else {
320
372
  if (month < 1 || day < 1) {
321
- rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
373
+ rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]);
322
374
  val = Qnil;
323
375
  } else {
324
- val = rb_funcall(cDate, intern_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
376
+ val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day));
325
377
  }
326
378
  }
327
379
  break;
@@ -332,10 +384,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
332
384
  case MYSQL_TYPE_BLOB:
333
385
  case MYSQL_TYPE_VAR_STRING:
334
386
  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
387
+ case MYSQL_TYPE_STRING: /* CHAR or BINARY field */
388
+ case MYSQL_TYPE_SET: /* SET field */
389
+ case MYSQL_TYPE_ENUM: /* ENUM field */
390
+ case MYSQL_TYPE_GEOMETRY: /* Spatial fielda */
339
391
  default:
340
392
  val = rb_str_new(row[i], fieldLengths[i]);
341
393
  #ifdef HAVE_RUBY_ENCODING_H
@@ -369,6 +421,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
369
421
  GetMysql2Result(self, wrapper);
370
422
 
371
423
  defaults = rb_iv_get(self, "@query_options");
424
+ Check_Type(defaults, T_HASH);
372
425
  if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
373
426
  symbolizeKeys = 1;
374
427
  }
@@ -392,11 +445,14 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
392
445
  ID db_timezone, app_timezone, dbTz, appTz;
393
446
  mysql2_result_wrapper * wrapper;
394
447
  unsigned long i;
395
- int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1;
448
+ const char * errstr;
449
+ int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1, streaming = 0;
450
+ MYSQL_FIELD * fields = NULL;
396
451
 
397
452
  GetMysql2Result(self, wrapper);
398
453
 
399
454
  defaults = rb_iv_get(self, "@query_options");
455
+ Check_Type(defaults, T_HASH);
400
456
  if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
401
457
  opts = rb_funcall(defaults, intern_merge, 1, opts);
402
458
  } else {
@@ -423,6 +479,14 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
423
479
  cast = 0;
424
480
  }
425
481
 
482
+ if (rb_hash_aref(opts, sym_stream) == Qtrue) {
483
+ streaming = 1;
484
+ }
485
+
486
+ if (streaming && cacheRows) {
487
+ rb_warn("cacheRows is ignored if streaming is true");
488
+ }
489
+
426
490
  dbTz = rb_hash_aref(opts, sym_database_timezone);
427
491
  if (dbTz == sym_local) {
428
492
  db_timezone = intern_local;
@@ -445,48 +509,88 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
445
509
  }
446
510
 
447
511
  if (wrapper->lastRowProcessed == 0) {
448
- wrapper->numberOfRows = mysql_num_rows(wrapper->result);
449
- if (wrapper->numberOfRows == 0) {
512
+ if (streaming) {
513
+ /* We can't get number of rows if we're streaming, */
514
+ /* until we've finished fetching all rows */
515
+ wrapper->numberOfRows = 0;
450
516
  wrapper->rows = rb_ary_new();
451
- return wrapper->rows;
517
+ } else {
518
+ wrapper->numberOfRows = mysql_num_rows(wrapper->result);
519
+ if (wrapper->numberOfRows == 0) {
520
+ wrapper->rows = rb_ary_new();
521
+ return wrapper->rows;
522
+ }
523
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
452
524
  }
453
- wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
454
525
  }
455
526
 
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++) {
527
+ if (streaming) {
528
+ if (!wrapper->streamingComplete) {
466
529
  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);
530
+
531
+ fields = mysql_fetch_fields(wrapper->result);
532
+
533
+ do {
534
+ row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
535
+
536
+ if (block != Qnil && row != Qnil) {
537
+ rb_yield(row);
538
+ wrapper->lastRowProcessed++;
473
539
  }
474
- wrapper->lastRowProcessed++;
475
- }
540
+ } while(row != Qnil);
476
541
 
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
- }
542
+ rb_mysql_result_free_result(wrapper);
543
+
544
+ wrapper->numberOfRows = wrapper->lastRowProcessed;
545
+ wrapper->streamingComplete = 1;
482
546
 
483
- if (block != Qnil) {
484
- rb_yield(row);
547
+ // Check for errors, the connection might have gone out from under us
548
+ // mysql_error returns an empty string if there is no error
549
+ errstr = mysql_error(wrapper->client_wrapper->client);
550
+ if (errstr[0]) {
551
+ rb_raise(cMysql2Error, "%s", errstr);
485
552
  }
553
+ } else {
554
+ rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
486
555
  }
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);
556
+ } else {
557
+ if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
558
+ /* we've already read the entire dataset from the C result into our */
559
+ /* internal array. Lets hand that over to the user since it's ready to go */
560
+ for (i = 0; i < wrapper->numberOfRows; i++) {
561
+ rb_yield(rb_ary_entry(wrapper->rows, i));
562
+ }
563
+ } else {
564
+ unsigned long rowsProcessed = 0;
565
+ rowsProcessed = RARRAY_LEN(wrapper->rows);
566
+ fields = mysql_fetch_fields(wrapper->result);
567
+
568
+ for (i = 0; i < wrapper->numberOfRows; i++) {
569
+ VALUE row;
570
+ if (cacheRows && i < rowsProcessed) {
571
+ row = rb_ary_entry(wrapper->rows, i);
572
+ } else {
573
+ row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
574
+ if (cacheRows) {
575
+ rb_ary_store(wrapper->rows, i, row);
576
+ }
577
+ wrapper->lastRowProcessed++;
578
+ }
579
+
580
+ if (row == Qnil) {
581
+ /* we don't need the mysql C dataset around anymore, peace it */
582
+ rb_mysql_result_free_result(wrapper);
583
+ return Qnil;
584
+ }
585
+
586
+ if (block != Qnil) {
587
+ rb_yield(row);
588
+ }
589
+ }
590
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
591
+ /* we don't need the mysql C dataset around anymore, peace it */
592
+ rb_mysql_result_free_result(wrapper);
593
+ }
490
594
  }
491
595
  }
492
596
 
@@ -497,12 +601,19 @@ static VALUE rb_mysql_result_count(VALUE self) {
497
601
  mysql2_result_wrapper *wrapper;
498
602
 
499
603
  GetMysql2Result(self, wrapper);
500
-
501
- return INT2FIX(mysql_num_rows(wrapper->result));
604
+ if (wrapper->resultFreed) {
605
+ if (wrapper->streamingComplete){
606
+ return LONG2NUM(wrapper->numberOfRows);
607
+ } else {
608
+ return LONG2NUM(RARRAY_LEN(wrapper->rows));
609
+ }
610
+ } else {
611
+ return INT2FIX(mysql_num_rows(wrapper->result));
612
+ }
502
613
  }
503
614
 
504
615
  /* Mysql2::Result */
505
- VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
616
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) {
506
617
  VALUE obj;
507
618
  mysql2_result_wrapper * wrapper;
508
619
  obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
@@ -513,8 +624,16 @@ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
513
624
  wrapper->result = r;
514
625
  wrapper->fields = Qnil;
515
626
  wrapper->rows = Qnil;
516
- wrapper->encoding = Qnil;
627
+ wrapper->encoding = encoding;
628
+ wrapper->streamingComplete = 0;
629
+ wrapper->client = client;
630
+ wrapper->client_wrapper = DATA_PTR(client);
631
+ wrapper->client_wrapper->refcount++;
632
+
517
633
  rb_obj_call_init(obj, 0, NULL);
634
+
635
+ rb_iv_set(obj, "@query_options", options);
636
+
518
637
  return obj;
519
638
  }
520
639
 
@@ -529,9 +648,6 @@ void init_mysql2_result() {
529
648
  rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
530
649
  rb_define_alias(cMysql2Result, "size", "count");
531
650
 
532
- intern_encoding_from_charset = rb_intern("encoding_from_charset");
533
- intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code");
534
-
535
651
  intern_new = rb_intern("new");
536
652
  intern_utc = rb_intern("utc");
537
653
  intern_local = rb_intern("local");
@@ -551,9 +667,11 @@ void init_mysql2_result() {
551
667
  sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
552
668
  sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
553
669
  sym_cast = ID2SYM(rb_intern("cast"));
670
+ sym_stream = ID2SYM(rb_intern("stream"));
671
+ sym_name = ID2SYM(rb_intern("name"));
554
672
 
555
673
  opt_decimal_zero = rb_str_new2("0.0");
556
- rb_global_variable(&opt_decimal_zero); //never GC
674
+ rb_global_variable(&opt_decimal_zero); /*never GC */
557
675
  opt_float_zero = rb_float_new((double)0);
558
676
  rb_global_variable(&opt_float_zero);
559
677
  opt_time_year = INT2NUM(2000);