mysql2 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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