mysql2 0.1.8 → 0.1.9

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.
@@ -1,3 +1,6 @@
1
+ #ifndef MYSQL2_EXT
2
+ #define MYSQL2_EXT
3
+
1
4
  #include <ruby.h>
2
5
  #include <fcntl.h>
3
6
 
@@ -15,7 +18,6 @@
15
18
 
16
19
  #ifdef HAVE_RUBY_ENCODING_H
17
20
  #include <ruby/encoding.h>
18
- static int utf8Encoding, binaryEncoding;
19
21
  #endif
20
22
 
21
23
  #if defined(__GNUC__) && (__GNUC__ >= 3)
@@ -24,56 +26,24 @@ static int utf8Encoding, binaryEncoding;
24
26
  #define RB_MYSQL_UNUSED
25
27
  #endif
26
28
 
27
- static VALUE cBigDecimal, cDate, cDateTime;
28
- static ID intern_new, intern_utc;
29
+ #include <result.h>
29
30
 
30
- /* Mysql2::Error */
31
- static VALUE cMysql2Error;
31
+ extern VALUE mMysql2;
32
32
 
33
- /* Mysql2::Client */
34
- typedef struct {
35
- MYSQL * client;
36
- } mysql2_client_wrapper;
37
- #define GetMysql2Client(obj, sval) (sval = ((mysql2_client_wrapper*)(DATA_PTR(obj)))->client);
38
- static ID sym_socket, sym_host, sym_port, sym_username, sym_password,
39
- sym_database, sym_reconnect, sym_connect_timeout, sym_id, sym_version,
40
- sym_sslkey, sym_sslcert, sym_sslca, sym_sslcapath, sym_sslcipher,
41
- sym_symbolize_keys, sym_async;
42
- static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass);
43
- static VALUE rb_mysql_client_init(int argc, VALUE * argv, VALUE self);
44
- static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self);
45
- static VALUE rb_mysql_client_escape(VALUE self, VALUE str);
46
- static VALUE rb_mysql_client_info(VALUE self);
47
- static VALUE rb_mysql_client_server_info(VALUE self);
48
- static VALUE rb_mysql_client_socket(VALUE self);
49
- static VALUE rb_mysql_client_async_result(VALUE self);
50
- static VALUE rb_mysql_client_last_id(VALUE self);
51
- static VALUE rb_mysql_client_affected_rows(VALUE self);
52
- static void rb_mysql_client_free(void * client);
33
+ /* Mysql2::Error */
34
+ extern VALUE cMysql2Error;
53
35
 
54
36
  /* Mysql2::Result */
55
37
  typedef struct {
56
38
  VALUE fields;
57
39
  VALUE rows;
58
- unsigned long numberOfFields;
40
+ unsigned int numberOfFields;
59
41
  unsigned long numberOfRows;
60
42
  unsigned long lastRowProcessed;
61
- int resultFreed;
43
+ short int resultFreed;
62
44
  MYSQL_RES *result;
63
45
  } mysql2_result_wrapper;
64
46
  #define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj));
65
- static VALUE cMysql2Result;
66
- static VALUE rb_mysql_result_to_obj(MYSQL_RES * res);
67
- static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self);
68
- static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self);
69
- static void rb_mysql_result_free(void * wrapper);
70
- static void rb_mysql_result_mark(void * wrapper);
71
- static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper);
72
-
73
- /* Mysql2::Error */
74
- static VALUE rb_raise_mysql2_error(MYSQL *client);
75
- static VALUE rb_mysql_error_error_number(VALUE obj);
76
- static VALUE rb_mysql_error_sql_state(VALUE obj);
77
47
 
78
48
  /*
79
49
  * used to pass all arguments to mysql_real_connect while inside
@@ -124,3 +94,5 @@ rb_thread_blocking_region(
124
94
  return rv;
125
95
  }
126
96
  #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
97
+
98
+ #endif
@@ -0,0 +1,340 @@
1
+ #include <mysql2_ext.h>
2
+
3
+ #ifdef HAVE_RUBY_ENCODING_H
4
+ rb_encoding *binaryEncoding;
5
+ #endif
6
+
7
+ ID sym_symbolize_keys;
8
+ ID intern_new, intern_utc, intern_encoding_from_charset_code;
9
+
10
+ VALUE cBigDecimal, cDate, cDateTime;
11
+ VALUE cMysql2Result;
12
+ extern VALUE cMysql2Client;
13
+
14
+ static void rb_mysql_result_mark(void * wrapper) {
15
+ mysql2_result_wrapper * w = wrapper;
16
+ if (w) {
17
+ rb_gc_mark(w->fields);
18
+ rb_gc_mark(w->rows);
19
+ }
20
+ }
21
+
22
+ /* this may be called manually or during GC */
23
+ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
24
+ if (wrapper && wrapper->resultFreed != 1) {
25
+ mysql_free_result(wrapper->result);
26
+ wrapper->resultFreed = 1;
27
+ }
28
+ }
29
+
30
+ /* this is called during GC */
31
+ static void rb_mysql_result_free(void * wrapper) {
32
+ mysql2_result_wrapper * w = wrapper;
33
+ /* FIXME: this may call flush_use_result, which can hit the socket */
34
+ rb_mysql_result_free_result(w);
35
+ xfree(wrapper);
36
+ }
37
+
38
+ /*
39
+ * for small results, this won't hit the network, but there's no
40
+ * reliable way for us to tell this so we'll always release the GVL
41
+ * to be safe
42
+ */
43
+ static VALUE nogvl_fetch_row(void *ptr) {
44
+ MYSQL_RES *result = ptr;
45
+
46
+ return (VALUE)mysql_fetch_row(result);
47
+ }
48
+
49
+ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
50
+ mysql2_result_wrapper * wrapper;
51
+
52
+ GetMysql2Result(self, wrapper);
53
+
54
+ if (wrapper->fields == Qnil) {
55
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
56
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
57
+ }
58
+
59
+ VALUE rb_field = rb_ary_entry(wrapper->fields, idx);
60
+ if (rb_field == Qnil) {
61
+ MYSQL_FIELD *field = NULL;
62
+ #ifdef HAVE_RUBY_ENCODING_H
63
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
64
+ rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
65
+ #endif
66
+
67
+ field = mysql_fetch_field_direct(wrapper->result, idx);
68
+ if (symbolize_keys) {
69
+ char buf[field->name_length+1];
70
+ memcpy(buf, field->name, field->name_length);
71
+ buf[field->name_length] = 0;
72
+ rb_field = ID2SYM(rb_intern(buf));
73
+ } else {
74
+ rb_field = rb_str_new(field->name, field->name_length);
75
+ #ifdef HAVE_RUBY_ENCODING_H
76
+ rb_enc_associate(rb_field, conn_enc);
77
+ if (default_internal_enc) {
78
+ rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
79
+ }
80
+ #endif
81
+ }
82
+ rb_ary_store(wrapper->fields, idx, rb_field);
83
+ }
84
+
85
+ return rb_field;
86
+ }
87
+
88
+ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
89
+ VALUE rowHash, opts, block;
90
+ mysql2_result_wrapper * wrapper;
91
+ MYSQL_ROW row;
92
+ MYSQL_FIELD * fields = NULL;
93
+ unsigned int i = 0, symbolizeKeys = 0;
94
+ unsigned long * fieldLengths;
95
+ void * ptr;
96
+ #ifdef HAVE_RUBY_ENCODING_H
97
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
98
+ rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
99
+ #endif
100
+
101
+ GetMysql2Result(self, wrapper);
102
+
103
+ if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
104
+ Check_Type(opts, T_HASH);
105
+ if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
106
+ symbolizeKeys = 1;
107
+ }
108
+ }
109
+
110
+ ptr = wrapper->result;
111
+ row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
112
+ if (row == NULL) {
113
+ return Qnil;
114
+ }
115
+
116
+ rowHash = rb_hash_new();
117
+ fields = mysql_fetch_fields(wrapper->result);
118
+ fieldLengths = mysql_fetch_lengths(wrapper->result);
119
+ if (wrapper->fields == Qnil) {
120
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
121
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
122
+ }
123
+
124
+ for (i = 0; i < wrapper->numberOfFields; i++) {
125
+ VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
126
+ if (row[i]) {
127
+ VALUE val;
128
+ switch(fields[i].type) {
129
+ case MYSQL_TYPE_NULL: // NULL-type field
130
+ val = Qnil;
131
+ break;
132
+ case MYSQL_TYPE_BIT: // BIT field (MySQL 5.0.3 and up)
133
+ val = rb_str_new(row[i], fieldLengths[i]);
134
+ break;
135
+ case MYSQL_TYPE_TINY: // TINYINT field
136
+ case MYSQL_TYPE_SHORT: // SMALLINT field
137
+ case MYSQL_TYPE_LONG: // INTEGER field
138
+ case MYSQL_TYPE_INT24: // MEDIUMINT field
139
+ case MYSQL_TYPE_LONGLONG: // BIGINT field
140
+ case MYSQL_TYPE_YEAR: // YEAR field
141
+ val = rb_cstr2inum(row[i], 10);
142
+ break;
143
+ case MYSQL_TYPE_DECIMAL: // DECIMAL or NUMERIC field
144
+ case MYSQL_TYPE_NEWDECIMAL: // Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up)
145
+ val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i]));
146
+ break;
147
+ case MYSQL_TYPE_FLOAT: // FLOAT field
148
+ case MYSQL_TYPE_DOUBLE: // DOUBLE or REAL field
149
+ val = rb_float_new(strtod(row[i], NULL));
150
+ break;
151
+ case MYSQL_TYPE_TIME: { // TIME field
152
+ int hour, min, sec, tokens;
153
+ tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec);
154
+ val = rb_funcall(rb_cTime, intern_utc, 6, INT2NUM(0), INT2NUM(1), INT2NUM(1), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
155
+ break;
156
+ }
157
+ case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
158
+ case MYSQL_TYPE_DATETIME: { // DATETIME field
159
+ int year, month, day, hour, min, sec, tokens;
160
+ tokens = sscanf(row[i], "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
161
+ if (year+month+day+hour+min+sec == 0) {
162
+ val = Qnil;
163
+ } else {
164
+ if (month < 1 || day < 1) {
165
+ rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
166
+ val = Qnil;
167
+ } else {
168
+ val = rb_funcall(rb_cTime, intern_utc, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
169
+ }
170
+ }
171
+ break;
172
+ }
173
+ case MYSQL_TYPE_DATE: // DATE field
174
+ case MYSQL_TYPE_NEWDATE: { // Newer const used > 5.0
175
+ int year, month, day, tokens;
176
+ tokens = sscanf(row[i], "%4d-%2d-%2d", &year, &month, &day);
177
+ if (year+month+day == 0) {
178
+ val = Qnil;
179
+ } else {
180
+ if (month < 1 || day < 1) {
181
+ rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
182
+ val = Qnil;
183
+ } else {
184
+ val = rb_funcall(cDate, intern_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
185
+ }
186
+ }
187
+ break;
188
+ }
189
+ case MYSQL_TYPE_TINY_BLOB:
190
+ case MYSQL_TYPE_MEDIUM_BLOB:
191
+ case MYSQL_TYPE_LONG_BLOB:
192
+ case MYSQL_TYPE_BLOB:
193
+ case MYSQL_TYPE_VAR_STRING:
194
+ case MYSQL_TYPE_VARCHAR:
195
+ case MYSQL_TYPE_STRING: // CHAR or BINARY field
196
+ case MYSQL_TYPE_SET: // SET field
197
+ case MYSQL_TYPE_ENUM: // ENUM field
198
+ case MYSQL_TYPE_GEOMETRY: // Spatial fielda
199
+ default:
200
+ val = rb_str_new(row[i], fieldLengths[i]);
201
+ #ifdef HAVE_RUBY_ENCODING_H
202
+ // if binary flag is set, respect it's wishes
203
+ if (fields[i].flags & BINARY_FLAG) {
204
+ rb_enc_associate(val, binaryEncoding);
205
+ } else {
206
+ // lookup the encoding configured on this field
207
+ VALUE new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset_code, 1, INT2NUM(fields[i].charsetnr));
208
+ if (new_encoding != Qnil) {
209
+ // use the field encoding we were able to match
210
+ rb_encoding *enc = rb_to_encoding(new_encoding);
211
+ rb_enc_associate(val, enc);
212
+ } else {
213
+ // otherwise fall-back to the connection's encoding
214
+ rb_enc_associate(val, conn_enc);
215
+ }
216
+ if (default_internal_enc) {
217
+ val = rb_str_export_to_enc(val, default_internal_enc);
218
+ }
219
+ }
220
+ #endif
221
+ break;
222
+ }
223
+ rb_hash_aset(rowHash, field, val);
224
+ } else {
225
+ rb_hash_aset(rowHash, field, Qnil);
226
+ }
227
+ }
228
+ return rowHash;
229
+ }
230
+
231
+ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
232
+ mysql2_result_wrapper * wrapper;
233
+ unsigned int i = 0;
234
+
235
+ GetMysql2Result(self, wrapper);
236
+
237
+ if (wrapper->fields == Qnil) {
238
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
239
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
240
+ }
241
+
242
+ if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
243
+ for (i=0; i<wrapper->numberOfFields; i++) {
244
+ rb_mysql_result_fetch_field(self, i, 0);
245
+ }
246
+ }
247
+
248
+ return wrapper->fields;
249
+ }
250
+
251
+ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
252
+ VALUE opts, block;
253
+ mysql2_result_wrapper * wrapper;
254
+ unsigned long i;
255
+
256
+ GetMysql2Result(self, wrapper);
257
+
258
+ rb_scan_args(argc, argv, "01&", &opts, &block);
259
+
260
+ if (wrapper->lastRowProcessed == 0) {
261
+ wrapper->numberOfRows = mysql_num_rows(wrapper->result);
262
+ if (wrapper->numberOfRows == 0) {
263
+ return Qnil;
264
+ }
265
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
266
+ }
267
+
268
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
269
+ // we've already read the entire dataset from the C result into our
270
+ // internal array. Lets hand that over to the user since it's ready to go
271
+ for (i = 0; i < wrapper->numberOfRows; i++) {
272
+ rb_yield(rb_ary_entry(wrapper->rows, i));
273
+ }
274
+ } else {
275
+ unsigned long rowsProcessed = 0;
276
+ rowsProcessed = RARRAY_LEN(wrapper->rows);
277
+ for (i = 0; i < wrapper->numberOfRows; i++) {
278
+ VALUE row;
279
+ if (i < rowsProcessed) {
280
+ row = rb_ary_entry(wrapper->rows, i);
281
+ } else {
282
+ row = rb_mysql_result_fetch_row(argc, argv, self);
283
+ rb_ary_store(wrapper->rows, i, row);
284
+ wrapper->lastRowProcessed++;
285
+ }
286
+
287
+ if (row == Qnil) {
288
+ // we don't need the mysql C dataset around anymore, peace it
289
+ rb_mysql_result_free_result(wrapper);
290
+ return Qnil;
291
+ }
292
+
293
+ if (block != Qnil) {
294
+ rb_yield(row);
295
+ }
296
+ }
297
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
298
+ // we don't need the mysql C dataset around anymore, peace it
299
+ rb_mysql_result_free_result(wrapper);
300
+ }
301
+ }
302
+
303
+ return wrapper->rows;
304
+ }
305
+
306
+ /* Mysql2::Result */
307
+ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
308
+ VALUE obj;
309
+ mysql2_result_wrapper * wrapper;
310
+ obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
311
+ wrapper->numberOfFields = 0;
312
+ wrapper->numberOfRows = 0;
313
+ wrapper->lastRowProcessed = 0;
314
+ wrapper->resultFreed = 0;
315
+ wrapper->result = r;
316
+ wrapper->fields = Qnil;
317
+ wrapper->rows = Qnil;
318
+ rb_obj_call_init(obj, 0, NULL);
319
+ return obj;
320
+ }
321
+
322
+ void init_mysql2_result()
323
+ {
324
+ cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
325
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
326
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
327
+
328
+ cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
329
+ rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
330
+ rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
331
+
332
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
333
+ intern_new = rb_intern("new");
334
+ intern_utc = rb_intern("utc");
335
+ intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code");
336
+
337
+ #ifdef HAVE_RUBY_ENCODING_H
338
+ binaryEncoding = rb_enc_find("binary");
339
+ #endif
340
+ }
@@ -0,0 +1,7 @@
1
+ #ifndef MYSQL2_RESULT_H
2
+ #define MYSQL2_RESULT_H
3
+
4
+ void init_mysql2_result();
5
+ VALUE rb_mysql_result_to_obj(MYSQL_RES * r);
6
+
7
+ #endif
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ # AR adapter for using a fibered mysql2 connection with EM
4
+ # This adapter should be used within Thin or Unicorn with the rack-fiber_pool middleware.
5
+ # Just update your database.yml's adapter to be 'em_mysql2'
6
+
7
+ module ActiveRecord
8
+ class Base
9
+ def self.em_mysql2_connection(config)
10
+ client = ::Mysql2::Fibered::Client.new(config.symbolize_keys)
11
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
12
+ ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
13
+ end
14
+ end
15
+ end
16
+
17
+ require 'fiber'
18
+ require 'eventmachine' unless defined? EventMachine
19
+ require 'mysql2' unless defined? Mysql2
20
+ require 'active_record/connection_adapters/mysql2_adapter'
21
+ require 'active_record/fiber_patches'
22
+
23
+ module Mysql2
24
+ module Fibered
25
+ class Client < ::Mysql2::Client
26
+ module Watcher
27
+ def initialize(client, deferable)
28
+ @client = client
29
+ @deferable = deferable
30
+ end
31
+
32
+ def notify_readable
33
+ begin
34
+ detach
35
+ results = @client.async_result
36
+ @deferable.succeed(results)
37
+ rescue Exception => e
38
+ puts e.backtrace.join("\n\t")
39
+ @deferable.fail(e)
40
+ end
41
+ end
42
+ end
43
+
44
+ def query(sql, opts={})
45
+ super(sql, opts.merge(:async => true))
46
+ deferable = ::EM::DefaultDeferrable.new
47
+ ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true
48
+ fiber = Fiber.current
49
+ deferable.callback do |result|
50
+ fiber.resume(result)
51
+ end
52
+ deferable.errback do |err|
53
+ fiber.resume(err)
54
+ end
55
+ Fiber.yield
56
+ end
57
+ end
58
+ end
59
+ end