mysql2 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
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
+ short int active;
37
+ MYSQL client;
38
+ } mysql_client_wrapper;
39
+
40
+ #endif
@@ -10,7 +10,7 @@ have_func('rb_thread_blocking_region')
10
10
 
11
11
  # borrowed from mysqlplus
12
12
  # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb
13
- dirs = ENV['PATH'].split(':') + %w[
13
+ dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[
14
14
  /opt
15
15
  /opt/local
16
16
  /opt/local/mysql
@@ -24,7 +24,7 @@ dirs = ENV['PATH'].split(':') + %w[
24
24
 
25
25
  GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}"
26
26
 
27
- if /mswin32/ =~ RUBY_PLATFORM
27
+ if RUBY_PLATFORM =~ /mswin|mingw/
28
28
  inc, lib = dir_config('mysql')
29
29
  exit 1 unless have_library("libmysql")
30
30
  elsif mc = (with_config('mysql-config') || Dir[GLOB].first) then
@@ -57,7 +57,9 @@ end
57
57
  asplode h unless have_header h
58
58
  end
59
59
 
60
- $CFLAGS << ' -Wall -Wextra -funroll-loops'
61
- # $CFLAGS << ' -O0 -ggdb3'
60
+ unless RUBY_PLATFORM =~ /mswin/
61
+ $CFLAGS << ' -Wall -funroll-loops'
62
+ end
63
+ # $CFLAGS << ' -O0 -ggdb3 -Wextra'
62
64
 
63
65
  create_makefile('mysql2/mysql2')
@@ -1,496 +1,12 @@
1
1
  #include <mysql2_ext.h>
2
2
 
3
- VALUE mMysql2, cMysql2Client;
4
- VALUE cMysql2Error, intern_encoding_from_charset;
5
- ID sym_id, sym_version, sym_async;
6
-
7
- #define REQUIRE_OPEN_DB(_ctxt) \
8
- if(!_ctxt->net.vio) { \
9
- rb_raise(cMysql2Error, "closed MySQL connection"); \
10
- return Qnil; \
11
- }
12
-
13
- /*
14
- * non-blocking mysql_*() functions that we won't be wrapping since
15
- * they do not appear to hit the network nor issue any interruptible
16
- * or blocking system calls.
17
- *
18
- * - mysql_affected_rows()
19
- * - mysql_error()
20
- * - mysql_fetch_fields()
21
- * - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths
22
- * - mysql_field_count()
23
- * - mysql_get_client_info()
24
- * - mysql_get_client_version()
25
- * - mysql_get_server_info()
26
- * - mysql_get_server_version()
27
- * - mysql_insert_id()
28
- * - mysql_num_fields()
29
- * - mysql_num_rows()
30
- * - mysql_options()
31
- * - mysql_real_escape_string()
32
- * - mysql_ssl_set()
33
- */
34
-
35
- static VALUE rb_raise_mysql2_error(MYSQL *client) {
36
- VALUE e = rb_exc_new2(cMysql2Error, mysql_error(client));
37
- rb_funcall(e, rb_intern("error_number="), 1, INT2NUM(mysql_errno(client)));
38
- rb_funcall(e, rb_intern("sql_state="), 1, rb_tainted_str_new2(mysql_sqlstate(client)));
39
- rb_exc_raise(e);
40
- return Qnil;
41
- }
42
-
43
- static VALUE nogvl_init(void *ptr) {
44
- MYSQL * client = (MYSQL *)ptr;
45
-
46
- /* may initialize embedded server and read /etc/services off disk */
47
- mysql_init(client);
48
-
49
- return client ? Qtrue : Qfalse;
50
- }
51
-
52
- static VALUE nogvl_connect(void *ptr) {
53
- struct nogvl_connect_args *args = ptr;
54
- MYSQL *client;
55
-
56
- client = mysql_real_connect(args->mysql, args->host,
57
- args->user, args->passwd,
58
- args->db, args->port, args->unix_socket,
59
- args->client_flag);
60
-
61
- return client ? Qtrue : Qfalse;
62
- }
63
-
64
- static void rb_mysql_client_free(void * ptr) {
65
- MYSQL * client = (MYSQL *)ptr;
66
-
67
- /*
68
- * we'll send a QUIT message to the server, but that message is more of a
69
- * formality than a hard requirement since the socket is getting shutdown
70
- * anyways, so ensure the socket write does not block our interpreter
71
- */
72
- int fd = client->net.fd;
73
- int flags;
74
-
75
- if (fd >= 0) {
76
- /*
77
- * if the socket is dead we have no chance of blocking,
78
- * so ignore any potential fcntl errors since they don't matter
79
- */
80
- flags = fcntl(fd, F_GETFL);
81
- if (flags > 0 && !(flags & O_NONBLOCK))
82
- fcntl(fd, F_SETFL, flags | O_NONBLOCK);
83
- }
84
-
85
- /* It's safe to call mysql_close() on an already closed connection. */
86
- mysql_close(client);
87
- xfree(ptr);
88
- }
89
-
90
- static VALUE nogvl_close(void * ptr) {
91
- mysql_close((MYSQL *)ptr);
92
- return Qnil;
93
- }
94
-
95
- static VALUE allocate(VALUE klass)
96
- {
97
- MYSQL * client;
98
-
99
- return Data_Make_Struct(
100
- klass,
101
- MYSQL,
102
- NULL,
103
- rb_mysql_client_free,
104
- client
105
- );
106
- }
107
-
108
- static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket)
109
- {
110
- MYSQL * client;
111
- struct nogvl_connect_args args;
112
-
113
- Data_Get_Struct(self, MYSQL, client);
114
-
115
- args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
116
- args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
117
- args.port = NIL_P(port) ? 3306 : NUM2INT(port);
118
- args.user = NIL_P(user) ? NULL : StringValuePtr(user);
119
- args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
120
- args.db = NIL_P(database) ? NULL : StringValuePtr(database);
121
- args.mysql = client;
122
- args.client_flag = 0;
123
-
124
- if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse)
125
- {
126
- // unable to connect
127
- return rb_raise_mysql2_error(client);
128
- }
129
-
130
- return self;
131
- }
132
-
133
- /*
134
- * Immediately disconnect from the server, normally the garbage collector
135
- * will disconnect automatically when a connection is no longer needed.
136
- * Explicitly closing this will free up server resources sooner than waiting
137
- * for the garbage collector.
138
- */
139
- static VALUE rb_mysql_client_close(VALUE self) {
140
- MYSQL *client;
141
-
142
- Data_Get_Struct(self, MYSQL, client);
143
-
144
- REQUIRE_OPEN_DB(client);
145
- rb_thread_blocking_region(nogvl_close, client, RUBY_UBF_IO, 0);
146
-
147
- return Qnil;
148
- }
149
-
150
- /*
151
- * mysql_send_query is unlikely to block since most queries are small
152
- * enough to fit in a socket buffer, but sometimes large UPDATE and
153
- * INSERTs will cause the process to block
154
- */
155
- static VALUE nogvl_send_query(void *ptr) {
156
- struct nogvl_send_query_args *args = ptr;
157
- int rv;
158
- const char *sql = StringValuePtr(args->sql);
159
- long sql_len = RSTRING_LEN(args->sql);
160
-
161
- rv = mysql_send_query(args->mysql, sql, sql_len);
162
-
163
- return rv == 0 ? Qtrue : Qfalse;
164
- }
165
-
166
- /*
167
- * even though we did rb_thread_select before calling this, a large
168
- * response can overflow the socket buffers and cause us to eventually
169
- * block while calling mysql_read_query_result
170
- */
171
- static VALUE nogvl_read_query_result(void *ptr) {
172
- MYSQL * client = ptr;
173
- my_bool res = mysql_read_query_result(client);
174
-
175
- return res == 0 ? Qtrue : Qfalse;
176
- }
177
-
178
- /* mysql_store_result may (unlikely) read rows off the socket */
179
- static VALUE nogvl_store_result(void *ptr) {
180
- MYSQL * client = ptr;
181
- return (VALUE)mysql_store_result(client);
182
- }
183
-
184
- static VALUE rb_mysql_client_async_result(VALUE self) {
185
- MYSQL * client;
186
- MYSQL_RES * result;
187
-
188
- Data_Get_Struct(self, MYSQL, client);
189
-
190
- REQUIRE_OPEN_DB(client);
191
- if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) {
192
- return rb_raise_mysql2_error(client);
193
- }
194
-
195
- result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, client, RUBY_UBF_IO, 0);
196
- if (result == NULL) {
197
- if (mysql_field_count(client) != 0) {
198
- rb_raise_mysql2_error(client);
199
- }
200
- return Qnil;
201
- }
202
-
203
- VALUE resultObj = rb_mysql_result_to_obj(result);
204
- rb_iv_set(resultObj, "@encoding", rb_iv_get(self, "@encoding"));
205
- return resultObj;
206
- }
207
-
208
- static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
209
- struct nogvl_send_query_args args;
210
- fd_set fdset;
211
- int fd, retval;
212
- int async = 0;
213
- VALUE opts;
214
- VALUE rb_async;
215
-
216
- MYSQL * client;
217
-
218
- if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
219
- if ((rb_async = rb_hash_aref(opts, sym_async)) != Qnil) {
220
- async = rb_async == Qtrue ? 1 : 0;
221
- }
222
- }
223
-
224
- Check_Type(args.sql, T_STRING);
225
- #ifdef HAVE_RUBY_ENCODING_H
226
- rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
227
- // ensure the string is in the encoding the connection is expecting
228
- args.sql = rb_str_export_to_enc(args.sql, conn_enc);
229
- #endif
230
-
231
- Data_Get_Struct(self, MYSQL, client);
232
-
233
- REQUIRE_OPEN_DB(client);
234
-
235
- args.mysql = client;
236
- if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
237
- return rb_raise_mysql2_error(client);
238
- }
239
-
240
- if (!async) {
241
- // the below code is largely from do_mysql
242
- // http://github.com/datamapper/do
243
- fd = client->net.fd;
244
- for(;;) {
245
- FD_ZERO(&fdset);
246
- FD_SET(fd, &fdset);
247
-
248
- retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL);
249
-
250
- if (retval < 0) {
251
- rb_sys_fail(0);
252
- }
253
-
254
- if (retval > 0) {
255
- break;
256
- }
257
- }
258
-
259
- return rb_mysql_client_async_result(self);
260
- } else {
261
- return Qnil;
262
- }
263
- }
264
-
265
- static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
266
- MYSQL * client;
267
- VALUE newStr;
268
- unsigned long newLen, oldLen;
269
-
270
- Check_Type(str, T_STRING);
271
- #ifdef HAVE_RUBY_ENCODING_H
272
- rb_encoding *default_internal_enc = rb_default_internal_encoding();
273
- rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
274
- // ensure the string is in the encoding the connection is expecting
275
- str = rb_str_export_to_enc(str, conn_enc);
276
- #endif
277
-
278
- oldLen = RSTRING_LEN(str);
279
- char escaped[(oldLen*2)+1];
280
-
281
- Data_Get_Struct(self, MYSQL, client);
282
-
283
- REQUIRE_OPEN_DB(client);
284
- newLen = mysql_real_escape_string(client, escaped, StringValuePtr(str), RSTRING_LEN(str));
285
- if (newLen == oldLen) {
286
- // no need to return a new ruby string if nothing changed
287
- return str;
288
- } else {
289
- newStr = rb_str_new(escaped, newLen);
290
- #ifdef HAVE_RUBY_ENCODING_H
291
- rb_enc_associate(newStr, conn_enc);
292
- if (default_internal_enc) {
293
- newStr = rb_str_export_to_enc(newStr, default_internal_enc);
294
- }
295
- #endif
296
- return newStr;
297
- }
298
- }
299
-
300
- static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE self) {
301
- VALUE version = rb_hash_new(), client_info;
302
- #ifdef HAVE_RUBY_ENCODING_H
303
- rb_encoding *default_internal_enc = rb_default_internal_encoding();
304
- rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
305
- #endif
306
-
307
- rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
308
- client_info = rb_str_new2(mysql_get_client_info());
309
- #ifdef HAVE_RUBY_ENCODING_H
310
- rb_enc_associate(client_info, conn_enc);
311
- if (default_internal_enc) {
312
- client_info = rb_str_export_to_enc(client_info, default_internal_enc);
313
- }
314
- #endif
315
- rb_hash_aset(version, sym_version, client_info);
316
- return version;
317
- }
318
-
319
- static VALUE rb_mysql_client_server_info(VALUE self) {
320
- MYSQL * client;
321
- VALUE version, server_info;
322
- #ifdef HAVE_RUBY_ENCODING_H
323
- rb_encoding *default_internal_enc = rb_default_internal_encoding();
324
- rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
325
- #endif
326
-
327
- Data_Get_Struct(self, MYSQL, client);
328
- REQUIRE_OPEN_DB(client);
329
-
330
- version = rb_hash_new();
331
- rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client)));
332
- server_info = rb_str_new2(mysql_get_server_info(client));
333
- #ifdef HAVE_RUBY_ENCODING_H
334
- rb_enc_associate(server_info, conn_enc);
335
- if (default_internal_enc) {
336
- server_info = rb_str_export_to_enc(server_info, default_internal_enc);
337
- }
338
- #endif
339
- rb_hash_aset(version, sym_version, server_info);
340
- return version;
341
- }
342
-
343
- static VALUE rb_mysql_client_socket(VALUE self) {
344
- MYSQL * client;
345
- Data_Get_Struct(self, MYSQL, client);
346
- REQUIRE_OPEN_DB(client);
347
- return INT2NUM(client->net.fd);
348
- }
349
-
350
- static VALUE rb_mysql_client_last_id(VALUE self) {
351
- MYSQL * client;
352
- Data_Get_Struct(self, MYSQL, client);
353
- REQUIRE_OPEN_DB(client);
354
- return ULL2NUM(mysql_insert_id(client));
355
- }
356
-
357
- static VALUE rb_mysql_client_affected_rows(VALUE self) {
358
- MYSQL * client;
359
- Data_Get_Struct(self, MYSQL, client);
360
- REQUIRE_OPEN_DB(client);
361
- return ULL2NUM(mysql_affected_rows(client));
362
- }
363
-
364
- static VALUE set_reconnect(VALUE self, VALUE value)
365
- {
366
- my_bool reconnect;
367
- MYSQL * client;
368
-
369
- Data_Get_Struct(self, MYSQL, client);
370
-
371
- if(!NIL_P(value)) {
372
- reconnect = value == Qfalse ? 0 : 1;
373
-
374
- /* set default reconnect behavior */
375
- if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect)) {
376
- /* TODO: warning - unable to set reconnect behavior */
377
- rb_warn("%s\n", mysql_error(client));
378
- }
379
- }
380
- return value;
381
- }
382
-
383
- static VALUE set_connect_timeout(VALUE self, VALUE value)
384
- {
385
- unsigned int connect_timeout = 0;
386
- MYSQL * client;
387
-
388
- Data_Get_Struct(self, MYSQL, client);
389
-
390
- if(!NIL_P(value)) {
391
- connect_timeout = NUM2INT(value);
392
- if(0 == connect_timeout) return value;
393
-
394
- /* set default connection timeout behavior */
395
- if (mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
396
- /* TODO: warning - unable to set connection timeout */
397
- rb_warn("%s\n", mysql_error(client));
398
- }
399
- }
400
- return value;
401
- }
402
-
403
- static VALUE set_charset_name(VALUE self, VALUE value)
404
- {
405
- char * charset_name;
406
- MYSQL * client;
407
-
408
- Data_Get_Struct(self, MYSQL, client);
409
-
410
- #ifdef HAVE_RUBY_ENCODING_H
411
- VALUE new_encoding, old_encoding;
412
- new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
413
- if (new_encoding == Qnil) {
414
- rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(value));
415
- } else {
416
- old_encoding = rb_iv_get(self, "@encoding");
417
- if (old_encoding == Qnil) {
418
- rb_iv_set(self, "@encoding", new_encoding);
419
- }
420
- }
421
- #endif
422
-
423
- charset_name = StringValuePtr(value);
424
-
425
- if (mysql_options(client, MYSQL_SET_CHARSET_NAME, charset_name)) {
426
- /* TODO: warning - unable to set charset */
427
- rb_warn("%s\n", mysql_error(client));
428
- }
429
-
430
- return value;
431
- }
432
-
433
- static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher)
434
- {
435
- MYSQL * client;
436
- Data_Get_Struct(self, MYSQL, client);
437
-
438
- if(!NIL_P(ca) || !NIL_P(key)) {
439
- mysql_ssl_set(client,
440
- NIL_P(key) ? NULL : StringValuePtr(key),
441
- NIL_P(cert) ? NULL : StringValuePtr(cert),
442
- NIL_P(ca) ? NULL : StringValuePtr(ca),
443
- NIL_P(capath) ? NULL : StringValuePtr(capath),
444
- NIL_P(cipher) ? NULL : StringValuePtr(cipher));
445
- }
446
-
447
- return self;
448
- }
449
-
450
- static VALUE init_connection(VALUE self)
451
- {
452
- MYSQL * client;
453
- Data_Get_Struct(self, MYSQL, client);
454
-
455
- if (rb_thread_blocking_region(nogvl_init, client, RUBY_UBF_IO, 0) == Qfalse) {
456
- /* TODO: warning - not enough memory? */
457
- return rb_raise_mysql2_error(client);
458
- }
459
-
460
- return self;
461
- }
3
+ VALUE mMysql2, cMysql2Error;
462
4
 
463
5
  /* Ruby Extension initializer */
464
6
  void Init_mysql2() {
465
- mMysql2 = rb_define_module("Mysql2");
466
- cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
467
-
468
- rb_define_alloc_func(cMysql2Client, allocate);
469
-
470
- rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
471
- rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
472
- rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
473
- rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
474
- rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
475
- rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
476
- rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
477
- rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
478
- rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
479
-
480
- rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
481
- rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
482
- rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
483
- rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
484
- rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0);
485
- rb_define_private_method(cMysql2Client, "connect", rb_connect, 6);
486
-
7
+ mMysql2 = rb_define_module("Mysql2");
487
8
  cMysql2Error = rb_const_get(mMysql2, rb_intern("Error"));
488
9
 
10
+ init_mysql2_client();
489
11
  init_mysql2_result();
490
-
491
- sym_id = ID2SYM(rb_intern("id"));
492
- sym_version = ID2SYM(rb_intern("version"));
493
- sym_async = ID2SYM(rb_intern("async"));
494
-
495
- intern_encoding_from_charset = rb_intern("encoding_from_charset");
496
12
  }