sbf-do_mysql 0.10.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/ChangeLog.markdown +116 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +100 -0
  5. data/Rakefile +25 -0
  6. data/ext/do_mysql/compat.h +55 -0
  7. data/ext/do_mysql/do_common.c +510 -0
  8. data/ext/do_mysql/do_common.h +132 -0
  9. data/ext/do_mysql/do_mysql.c +691 -0
  10. data/ext/do_mysql/error.h +403 -0
  11. data/ext/do_mysql/extconf.rb +87 -0
  12. data/ext/do_mysql/mysql_compat.h +25 -0
  13. data/lib/do_mysql/encoding.rb +39 -0
  14. data/lib/do_mysql/transaction.rb +31 -0
  15. data/lib/do_mysql/version.rb +5 -0
  16. data/lib/do_mysql.rb +24 -0
  17. data/spec/command_spec.rb +7 -0
  18. data/spec/connection_spec.rb +55 -0
  19. data/spec/encoding_spec.rb +46 -0
  20. data/spec/error/sql_error_spec.rb +6 -0
  21. data/spec/reader_spec.rb +29 -0
  22. data/spec/result_spec.rb +38 -0
  23. data/spec/spec_helper.rb +242 -0
  24. data/spec/typecast/array_spec.rb +6 -0
  25. data/spec/typecast/bigdecimal_spec.rb +7 -0
  26. data/spec/typecast/boolean_spec.rb +7 -0
  27. data/spec/typecast/byte_array_spec.rb +6 -0
  28. data/spec/typecast/class_spec.rb +6 -0
  29. data/spec/typecast/date_spec.rb +30 -0
  30. data/spec/typecast/datetime_spec.rb +30 -0
  31. data/spec/typecast/float_spec.rb +7 -0
  32. data/spec/typecast/integer_spec.rb +6 -0
  33. data/spec/typecast/nil_spec.rb +8 -0
  34. data/spec/typecast/other_spec.rb +6 -0
  35. data/spec/typecast/range_spec.rb +6 -0
  36. data/spec/typecast/string_spec.rb +6 -0
  37. data/spec/typecast/time_spec.rb +6 -0
  38. data/tasks/compile.rake +16 -0
  39. data/tasks/release.rake +14 -0
  40. data/tasks/retrieve.rake +20 -0
  41. data/tasks/spec.rake +10 -0
  42. data/tasks/ssl.rake +26 -0
  43. metadata +101 -0
@@ -0,0 +1,691 @@
1
+ #include <ruby.h>
2
+ #include <time.h>
3
+ #ifndef _WIN32
4
+ #include <sys/time.h>
5
+ #endif
6
+ #include <string.h>
7
+
8
+ #include <mysql.h>
9
+ #include <errmsg.h>
10
+ #include <mysqld_error.h>
11
+
12
+ #include "mysql_compat.h"
13
+ #include "compat.h"
14
+
15
+ #include "error.h"
16
+ #include "do_common.h"
17
+
18
+ #ifndef HAVE_CONST_MYSQL_TYPE_STRING
19
+ #define HAVE_OLD_MYSQL_VERSION
20
+ #endif
21
+
22
+ #ifdef _WIN32
23
+ #define do_mysql_cCommand_execute do_mysql_cCommand_execute_sync
24
+ #else
25
+ #define do_mysql_cCommand_execute do_mysql_cCommand_execute_async
26
+ #endif
27
+
28
+ #ifndef HAVE_RB_THREAD_FD_SELECT
29
+ #define rb_fdset_t fd_set
30
+ #define rb_fd_isset(n, f) FD_ISSET(n, f)
31
+ #define rb_fd_init(f) FD_ZERO(f)
32
+ #define rb_fd_zero(f) FD_ZERO(f)
33
+ #define rb_fd_set(n, f) FD_SET(n, f)
34
+ #define rb_fd_clr(n, f) FD_CLR(n, f)
35
+ #define rb_fd_term(f)
36
+ #define rb_thread_fd_select rb_thread_select
37
+ #endif
38
+
39
+ #define CHECK_AND_RAISE(mysql_result_value, query) if (0 != mysql_result_value) { do_mysql_raise_error(self, db, query); }
40
+
41
+ void do_mysql_full_connect(VALUE self, MYSQL *db);
42
+
43
+ // Classes that we'll build in Init
44
+ VALUE mDO_Mysql;
45
+ VALUE mDO_MysqlEncoding;
46
+ VALUE cDO_MysqlConnection;
47
+ VALUE cDO_MysqlCommand;
48
+ VALUE cDO_MysqlResult;
49
+ VALUE cDO_MysqlReader;
50
+
51
+ // Figures out what we should cast a given mysql field type to
52
+ VALUE do_mysql_infer_ruby_type(const MYSQL_FIELD *field) {
53
+ switch (field->type) {
54
+ case MYSQL_TYPE_NULL:
55
+ return Qnil;
56
+ case MYSQL_TYPE_TINY:
57
+ return rb_cTrueClass;
58
+ #ifdef HAVE_CONST_MYSQL_TYPE_BIT
59
+ case MYSQL_TYPE_BIT:
60
+ #endif
61
+ case MYSQL_TYPE_SHORT:
62
+ case MYSQL_TYPE_LONG:
63
+ case MYSQL_TYPE_INT24:
64
+ case MYSQL_TYPE_LONGLONG:
65
+ case MYSQL_TYPE_YEAR:
66
+ return rb_cInteger;
67
+ #ifdef HAVE_CONST_MYSQL_TYPE_NEWDECIMAL
68
+ case MYSQL_TYPE_NEWDECIMAL:
69
+ #endif
70
+ case MYSQL_TYPE_DECIMAL:
71
+ return rb_cBigDecimal;
72
+ case MYSQL_TYPE_FLOAT:
73
+ case MYSQL_TYPE_DOUBLE:
74
+ return rb_cFloat;
75
+ case MYSQL_TYPE_TIMESTAMP:
76
+ case MYSQL_TYPE_DATETIME:
77
+ return rb_cDateTime;
78
+ case MYSQL_TYPE_DATE:
79
+ case MYSQL_TYPE_NEWDATE:
80
+ return rb_cDate;
81
+ case MYSQL_TYPE_STRING:
82
+ case MYSQL_TYPE_VAR_STRING:
83
+ case MYSQL_TYPE_TINY_BLOB:
84
+ case MYSQL_TYPE_MEDIUM_BLOB:
85
+ case MYSQL_TYPE_LONG_BLOB:
86
+ case MYSQL_TYPE_BLOB:
87
+ #ifdef HAVE_ST_CHARSETNR
88
+ if (field->charsetnr == 63) {
89
+ return rb_cByteArray;
90
+ }
91
+ else {
92
+ return rb_cString;
93
+ }
94
+ #else
95
+ // We assume a string here if we don't have a specific charset
96
+ return rb_cString;
97
+ #endif
98
+ default:
99
+ return rb_cString;
100
+ }
101
+ }
102
+
103
+ // Convert C-string to a Ruby instance of Ruby type "type"
104
+ VALUE do_mysql_typecast(const char *value, long length, const VALUE type, int encoding) {
105
+ if (!value) {
106
+ return Qnil;
107
+ }
108
+
109
+ if (type == rb_cTrueClass) {
110
+ return (value == 0 || strcmp("0", value) == 0) ? Qfalse : Qtrue;
111
+ }
112
+ else if (type == rb_cByteArray) {
113
+ return rb_funcall(rb_cByteArray, DO_ID_NEW, 1, rb_str_new(value, length));
114
+ }
115
+ else {
116
+ return data_objects_typecast(value, length, type, encoding);
117
+ }
118
+ }
119
+
120
+ void do_mysql_raise_error(VALUE self, MYSQL *db, VALUE query) {
121
+ int errnum = mysql_errno(db);
122
+ VALUE message = rb_str_new2(mysql_error(db));
123
+ VALUE sql_state = Qnil;
124
+
125
+ #ifdef HAVE_MYSQL_SQLSTATE
126
+ sql_state = rb_str_new2(mysql_sqlstate(db));
127
+ #endif
128
+
129
+ data_objects_raise_error(self, do_mysql_errors, errnum, message, query, sql_state);
130
+ }
131
+
132
+ #ifdef _WIN32
133
+ MYSQL_RES *do_mysql_cCommand_execute_sync(VALUE self, VALUE connection, MYSQL *db, VALUE query) {
134
+ int retval;
135
+ struct timeval start;
136
+ const char *str = rb_str_ptr_readonly(query);
137
+ long len = rb_str_len(query);
138
+
139
+ if (mysql_ping(db) && mysql_errno(db) == CR_SERVER_GONE_ERROR) {
140
+ // Ok, we do one more try here by doing a full connect
141
+ VALUE connection = rb_iv_get(self, "@connection");
142
+ do_mysql_full_connect(connection, db);
143
+ }
144
+
145
+ gettimeofday(&start, NULL);
146
+ retval = mysql_real_query(db, str, len);
147
+ data_objects_debug(connection, query, &start);
148
+
149
+ CHECK_AND_RAISE(retval, query);
150
+
151
+ return mysql_store_result(db);
152
+ }
153
+ #else
154
+ MYSQL_RES *do_mysql_cCommand_execute_async(VALUE self, VALUE connection, MYSQL *db, VALUE query) {
155
+ int retval;
156
+
157
+ if ((retval = mysql_ping(db)) && mysql_errno(db) == CR_SERVER_GONE_ERROR) {
158
+ do_mysql_full_connect(connection, db);
159
+ }
160
+
161
+ struct timeval start;
162
+ const char *str = rb_str_ptr_readonly(query);
163
+ long len = rb_str_len(query);
164
+
165
+ gettimeofday(&start, NULL);
166
+ retval = mysql_send_query(db, str, len);
167
+
168
+ CHECK_AND_RAISE(retval, query);
169
+
170
+ int socket_fd = db->net.fd;
171
+ rb_fdset_t rset;
172
+ rb_fd_init(&rset);
173
+ rb_fd_set(socket_fd, &rset);
174
+
175
+ while (1) {
176
+
177
+ retval = rb_thread_fd_select(socket_fd + 1, &rset, NULL, NULL, NULL);
178
+
179
+ if (retval < 0) {
180
+ rb_fd_term(&rset);
181
+ rb_sys_fail(0);
182
+ }
183
+
184
+ if (retval == 0) {
185
+ continue;
186
+ }
187
+
188
+ if (db->status == MYSQL_STATUS_READY) {
189
+ break;
190
+ }
191
+ }
192
+ rb_fd_term(&rset);
193
+
194
+ retval = mysql_read_query_result(db);
195
+ CHECK_AND_RAISE(retval, query);
196
+ data_objects_debug(connection, query, &start);
197
+
198
+ MYSQL_RES *result = mysql_store_result(db);
199
+
200
+ if (!result) {
201
+ CHECK_AND_RAISE(mysql_errno(db), query);
202
+ }
203
+
204
+ return result;
205
+ }
206
+ #endif
207
+
208
+ void do_mysql_full_connect(VALUE self, MYSQL *db) {
209
+ VALUE r_host = rb_iv_get(self, "@host");
210
+ const char *host = "localhost";
211
+
212
+ if (r_host != Qnil) {
213
+ host = StringValuePtr(r_host);
214
+ }
215
+
216
+ VALUE r_user = rb_iv_get(self, "@user");
217
+ const char *user = "root";
218
+
219
+ if (r_user != Qnil) {
220
+ user = StringValuePtr(r_user);
221
+ }
222
+
223
+ VALUE r_password = rb_iv_get(self, "@password");
224
+ char *password = NULL;
225
+
226
+ if (r_password != Qnil) {
227
+ password = StringValuePtr(r_password);
228
+ }
229
+
230
+ VALUE r_port = rb_iv_get(self, "@port");
231
+ int port = 3306;
232
+
233
+ if (r_port != Qnil) {
234
+ port = NUM2INT(r_port);
235
+ }
236
+
237
+ VALUE r_path = rb_iv_get(self, "@path");
238
+ char *path = NULL;
239
+ char *database = NULL;
240
+
241
+ if (r_path != Qnil) {
242
+ path = StringValuePtr(r_path);
243
+ database = strtok(path, "/"); // not threadsafe
244
+ }
245
+
246
+ if (!database || !*database) {
247
+ database = NULL;
248
+ }
249
+
250
+ VALUE r_query = rb_iv_get(self, "@query");
251
+ char *socket = NULL;
252
+
253
+ // Check to see if we're on the db machine. If so, try to use the socket
254
+ if (strcasecmp(host, "localhost") == 0) {
255
+ socket = data_objects_get_uri_option(r_query, "socket");
256
+
257
+ if (socket) {
258
+ rb_iv_set(self, "@using_socket", Qtrue);
259
+ }
260
+ }
261
+
262
+ #ifdef HAVE_MYSQL_SSL_SET
263
+ char *ssl_client_key, *ssl_client_cert, *ssl_ca_cert, *ssl_ca_path, *ssl_cipher;
264
+ VALUE r_ssl;
265
+
266
+ if (rb_obj_is_kind_of(r_query, rb_cHash)) {
267
+ r_ssl = rb_hash_aref(r_query, rb_str_new2("ssl"));
268
+
269
+ if (rb_obj_is_kind_of(r_ssl, rb_cHash)) {
270
+ ssl_client_key = data_objects_get_uri_option(r_ssl, "client_key");
271
+ ssl_client_cert = data_objects_get_uri_option(r_ssl, "client_cert");
272
+ ssl_ca_cert = data_objects_get_uri_option(r_ssl, "ca_cert");
273
+ ssl_ca_path = data_objects_get_uri_option(r_ssl, "ca_path");
274
+ ssl_cipher = data_objects_get_uri_option(r_ssl, "cipher");
275
+
276
+ data_objects_assert_file_exists(ssl_client_key, "client_key doesn't exist");
277
+ data_objects_assert_file_exists(ssl_client_cert, "client_cert doesn't exist");
278
+ data_objects_assert_file_exists(ssl_ca_cert, "ca_cert doesn't exist");
279
+
280
+ mysql_ssl_set(db, ssl_client_key, ssl_client_cert, ssl_ca_cert, ssl_ca_path, ssl_cipher);
281
+ }
282
+ else if (r_ssl != Qnil) {
283
+ rb_raise(rb_eArgError, "ssl must be passed a hash");
284
+ }
285
+ }
286
+ #endif
287
+
288
+ unsigned long client_flags = 0;
289
+
290
+ MYSQL *result = mysql_real_connect(
291
+ db,
292
+ host,
293
+ user,
294
+ password,
295
+ database,
296
+ port,
297
+ socket,
298
+ client_flags
299
+ );
300
+
301
+ if (!result) {
302
+ do_mysql_raise_error(self, db, Qnil);
303
+ }
304
+
305
+ #ifdef HAVE_MYSQL_GET_SSL_CIPHER
306
+ const char *ssl_cipher_used = mysql_get_ssl_cipher(db);
307
+
308
+ if (ssl_cipher_used) {
309
+ rb_iv_set(self, "@ssl_cipher", rb_str_new2(ssl_cipher_used));
310
+ }
311
+ #endif
312
+
313
+ #ifdef MYSQL_OPT_RECONNECT
314
+ my_bool reconnect = 1;
315
+ mysql_options(db, MYSQL_OPT_RECONNECT, &reconnect);
316
+ #endif
317
+
318
+ // We only support encoding for MySQL versions providing mysql_set_character_set.
319
+ // Without this function there are potential issues with mysql_real_escape_string
320
+ // since that doesn't take the character set into consideration when setting it
321
+ // using a SET CHARACTER SET query. Since we don't want to stimulate these possible
322
+ // issues we simply ignore it and assume the user has configured this correctly.
323
+
324
+ #ifdef HAVE_MYSQL_SET_CHARACTER_SET
325
+ // Set the connections character set
326
+ VALUE encoding = rb_iv_get(self, "@encoding");
327
+ VALUE my_encoding = rb_hash_aref(data_objects_const_get(mDO_MysqlEncoding, "MAP"), encoding);
328
+
329
+ if (my_encoding != Qnil) {
330
+ int encoding_error = mysql_set_character_set(db, rb_str_ptr_readonly(my_encoding));
331
+
332
+ if (encoding_error != 0) {
333
+ do_mysql_raise_error(self, db, Qnil);
334
+ }
335
+ else {
336
+ #ifdef HAVE_RUBY_ENCODING_H
337
+ const char* ruby_encoding = rb_str_ptr_readonly(encoding);
338
+ if (strcasecmp("UTF-8-MB4", ruby_encoding) == 0) {
339
+ ruby_encoding = "UTF-8";
340
+ }
341
+ rb_iv_set(self, "@encoding_id", INT2FIX(rb_enc_find_index(ruby_encoding)));
342
+ #endif
343
+
344
+ rb_iv_set(self, "@my_encoding", my_encoding);
345
+ }
346
+ }
347
+ else {
348
+ rb_warn("Encoding %s is not a known Ruby encoding for MySQL\n", rb_str_ptr_readonly(encoding));
349
+ rb_iv_set(self, "@encoding", rb_str_new2("UTF-8"));
350
+ #ifdef HAVE_RUBY_ENCODING_H
351
+ rb_iv_set(self, "@encoding_id", INT2FIX(rb_enc_find_index("UTF-8")));
352
+ #endif
353
+ rb_iv_set(self, "@my_encoding", rb_str_new2("utf8"));
354
+ }
355
+ #endif
356
+
357
+ // Disable sql_auto_is_null
358
+ do_mysql_cCommand_execute(Qnil, self, db, rb_str_new2("SET sql_auto_is_null = 0"));
359
+ // removed NO_AUTO_VALUE_ON_ZERO because of MySQL bug http://bugs.mysql.com/bug.php?id=42270
360
+ // added NO_BACKSLASH_ESCAPES so that backslashes should not be escaped as in other databases
361
+
362
+ // For really anscient MySQL versions we don't attempt any strictness
363
+ #ifdef HAVE_MYSQL_GET_SERVER_VERSION
364
+ //4.0 does not support sql_mode at all, while later 4.x versions do not support certain session parameters
365
+ if (mysql_get_server_version(db) >= 50000) {
366
+ do_mysql_cCommand_execute(Qnil, self, db, rb_str_new2("SET SESSION sql_mode = 'ANSI,NO_BACKSLASH_ESCAPES,NO_DIR_IN_CREATE,NO_ENGINE_SUBSTITUTION,NO_UNSIGNED_SUBTRACTION,TRADITIONAL'"));
367
+ }
368
+ else if (mysql_get_server_version(db) >= 40100) {
369
+ do_mysql_cCommand_execute(Qnil, self, db, rb_str_new2("SET SESSION sql_mode = 'ANSI,NO_DIR_IN_CREATE,NO_UNSIGNED_SUBTRACTION'"));
370
+ }
371
+ #endif
372
+
373
+ rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
374
+ }
375
+
376
+ VALUE do_mysql_cConnection_initialize(VALUE self, VALUE uri) {
377
+ rb_iv_set(self, "@using_socket", Qfalse);
378
+ rb_iv_set(self, "@ssl_cipher", Qnil);
379
+
380
+ VALUE r_host = rb_funcall(uri, rb_intern("host"), 0);
381
+
382
+ if (r_host != Qnil) {
383
+ rb_iv_set(self, "@host", r_host);
384
+ }
385
+
386
+ VALUE r_user = rb_funcall(uri, rb_intern("user"), 0);
387
+
388
+ if (r_user != Qnil) {
389
+ rb_iv_set(self, "@user", r_user);
390
+ }
391
+
392
+ VALUE r_password = rb_funcall(uri, rb_intern("password"), 0);
393
+
394
+ if (r_password != Qnil) {
395
+ rb_iv_set(self, "@password", r_password);
396
+ }
397
+
398
+ VALUE r_path = rb_funcall(uri, rb_intern("path"), 0);
399
+
400
+ if (r_path != Qnil) {
401
+ rb_iv_set(self, "@path", r_path);
402
+ }
403
+
404
+ VALUE r_port = rb_funcall(uri, rb_intern("port"), 0);
405
+
406
+ if (r_port != Qnil) {
407
+ rb_iv_set(self, "@port", r_port);
408
+ }
409
+
410
+ // Pull the querystring off the URI
411
+ VALUE r_query = rb_funcall(uri, rb_intern("query"), 0);
412
+
413
+ rb_iv_set(self, "@query", r_query);
414
+
415
+ const char *encoding = data_objects_get_uri_option(r_query, "encoding");
416
+
417
+ if (!encoding) {
418
+ encoding = data_objects_get_uri_option(r_query, "charset");
419
+
420
+ if (!encoding) { encoding = "UTF-8"; }
421
+ }
422
+
423
+ rb_iv_set(self, "@encoding", rb_str_new2(encoding));
424
+
425
+ MYSQL *db = mysql_init(NULL);
426
+
427
+ do_mysql_full_connect(self, db);
428
+ rb_iv_set(self, "@uri", uri);
429
+ return Qtrue;
430
+ }
431
+
432
+ VALUE do_mysql_cConnection_dispose(VALUE self) {
433
+ VALUE connection_container = rb_iv_get(self, "@connection");
434
+
435
+ MYSQL *db;
436
+
437
+ if (connection_container == Qnil) {
438
+ return Qfalse;
439
+ }
440
+
441
+ db = DATA_PTR(connection_container);
442
+
443
+ if (!db) {
444
+ return Qfalse;
445
+ }
446
+
447
+ mysql_close(db);
448
+ rb_iv_set(self, "@connection", Qnil);
449
+ return Qtrue;
450
+ }
451
+
452
+ VALUE do_mysql_cConnection_quote_string(VALUE self, VALUE string) {
453
+
454
+ MYSQL *db = DATA_PTR(rb_iv_get(self, "@connection"));
455
+ const char *source = rb_str_ptr_readonly(string);
456
+ long source_len = rb_str_len(string);
457
+ long buffer_len = source_len * 2 + 3;
458
+
459
+ // Overflow check
460
+ if(buffer_len <= source_len) {
461
+ rb_raise(rb_eArgError, "Input string is too large to be safely quoted");
462
+ }
463
+
464
+ // Allocate space for the escaped version of 'string'. Use + 3 allocate space for null term.
465
+ // and the leading and trailing single-quotes.
466
+ // Thanks to http://www.browardphp.com/mysql_manual_en/manual_MySQL_APIs.html#mysql_real_escape_string
467
+ char *escaped = calloc(buffer_len, sizeof(char));
468
+
469
+ if (!escaped) {
470
+ rb_memerror();
471
+ }
472
+
473
+ unsigned long quoted_length;
474
+ VALUE result;
475
+
476
+ // Escape 'source' using the current encoding in use on the conection 'db'
477
+ #ifdef HAVE_MYSQL_REAL_ESCAPE_STRING_QUOTE
478
+ quoted_length = mysql_real_escape_string_quote(db, escaped + 1, source, source_len, '\'');
479
+ #else
480
+ quoted_length = mysql_real_escape_string(db, escaped + 1, source, source_len);
481
+ #endif
482
+
483
+ if (quoted_length == (unsigned long)-1) {
484
+ free(escaped);
485
+ rb_raise(rb_eArgError, "Failed to quote string. Make sure to (re)compile do_mysql against the correct libmysqlclient");
486
+ }
487
+
488
+ // Wrap the escaped string in single-quotes, this is DO's convention
489
+ escaped[0] = escaped[quoted_length + 1] = '\'';
490
+ // We don't want to use the internal encoding, because this needs
491
+ // to go into the database in the connection encoding
492
+
493
+ result = DATA_OBJECTS_STR_NEW(escaped, quoted_length + 2, FIX2INT(rb_iv_get(self, "@encoding_id")), NULL);
494
+
495
+ free(escaped);
496
+
497
+ return result;
498
+ }
499
+
500
+ VALUE do_mysql_cCommand_execute_non_query(int argc, VALUE *argv, VALUE self) {
501
+ VALUE connection = rb_iv_get(self, "@connection");
502
+ VALUE mysql_connection = rb_iv_get(connection, "@connection");
503
+
504
+ if (mysql_connection == Qnil) {
505
+ rb_raise(eDO_ConnectionError, "This connection has already been closed.");
506
+ }
507
+
508
+ MYSQL *db = DATA_PTR(mysql_connection);
509
+ VALUE query = data_objects_build_query_from_args(self, argc, argv);
510
+ MYSQL_RES *response = do_mysql_cCommand_execute(self, connection, db, query);
511
+
512
+ my_ulonglong affected_rows = mysql_affected_rows(db);
513
+ my_ulonglong insert_id = mysql_insert_id(db);
514
+
515
+ mysql_free_result(response);
516
+
517
+ if (((my_ulonglong)-1) == affected_rows) {
518
+ return Qnil;
519
+ }
520
+
521
+ return rb_funcall(cDO_MysqlResult, DO_ID_NEW, 3, self, INT2NUM(affected_rows), insert_id == 0 ? Qnil : ULL2NUM(insert_id));
522
+ }
523
+
524
+ VALUE do_mysql_cCommand_execute_reader(int argc, VALUE *argv, VALUE self) {
525
+ VALUE connection = rb_iv_get(self, "@connection");
526
+ VALUE mysql_connection = rb_iv_get(connection, "@connection");
527
+
528
+ if (mysql_connection == Qnil) {
529
+ rb_raise(eDO_ConnectionError, "This result set has already been closed.");
530
+ }
531
+
532
+ VALUE query = data_objects_build_query_from_args(self, argc, argv);
533
+ MYSQL *db = DATA_PTR(mysql_connection);
534
+ MYSQL_RES *response = do_mysql_cCommand_execute(self, connection, db, query);
535
+
536
+ unsigned int field_count = mysql_field_count(db);
537
+ VALUE reader = rb_funcall(cDO_MysqlReader, DO_ID_NEW, 0);
538
+
539
+ rb_iv_set(reader, "@connection", connection);
540
+ rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, response));
541
+ rb_iv_set(reader, "@opened", Qfalse);
542
+ rb_iv_set(reader, "@field_count", INT2NUM(field_count));
543
+
544
+ VALUE field_names = rb_ary_new();
545
+ VALUE field_types = rb_iv_get(self, "@field_types");
546
+
547
+ char guess_default_field_types = 0;
548
+
549
+ if (field_types == Qnil || RARRAY_LEN(field_types) == 0) {
550
+ field_types = rb_ary_new();
551
+ guess_default_field_types = 1;
552
+ }
553
+ else if (RARRAY_LEN(field_types) != field_count) {
554
+ // Whoops... wrong number of types passed to set_types. Close the reader and raise
555
+ // and error
556
+ rb_funcall(reader, rb_intern("close"), 0);
557
+ rb_raise(rb_eArgError, "Field-count mismatch. Expected %ld fields, but the query yielded %d", RARRAY_LEN(field_types), field_count);
558
+ }
559
+
560
+ MYSQL_FIELD *field;
561
+ unsigned int i;
562
+
563
+ for(i = 0; i < field_count; i++) {
564
+ field = mysql_fetch_field_direct(response, i);
565
+ rb_ary_push(field_names, rb_str_new2(field->name));
566
+
567
+ if (guess_default_field_types == 1) {
568
+ rb_ary_push(field_types, do_mysql_infer_ruby_type(field));
569
+ }
570
+ }
571
+
572
+ rb_iv_set(reader, "@fields", field_names);
573
+ rb_iv_set(reader, "@field_types", field_types);
574
+
575
+ if (rb_block_given_p()) {
576
+ rb_yield(reader);
577
+ rb_funcall(reader, rb_intern("close"), 0);
578
+ }
579
+
580
+ return reader;
581
+ }
582
+
583
+ // This should be called to ensure that the internal result reader is freed
584
+ VALUE do_mysql_cReader_close(VALUE self) {
585
+ // Get the reader from the instance variable, maybe refactor this?
586
+ VALUE reader_container = rb_iv_get(self, "@reader");
587
+
588
+ if (reader_container == Qnil) {
589
+ return Qfalse;
590
+ }
591
+
592
+ MYSQL_RES *reader = DATA_PTR(reader_container);
593
+
594
+ // The Meat
595
+ if (!reader) {
596
+ return Qfalse;
597
+ }
598
+
599
+ mysql_free_result(reader);
600
+ rb_iv_set(self, "@reader", Qnil);
601
+ rb_iv_set(self, "@opened", Qfalse);
602
+ return Qtrue;
603
+ }
604
+
605
+ // Retrieve a single row
606
+ VALUE do_mysql_cReader_next(VALUE self) {
607
+ // Get the reader from the instance variable, maybe refactor this?
608
+ VALUE reader_container = rb_iv_get(self, "@reader");
609
+
610
+ if (reader_container == Qnil) {
611
+ return Qfalse;
612
+ }
613
+
614
+ MYSQL_RES *reader = DATA_PTR(reader_container);
615
+ if(!reader) {
616
+ return Qfalse;
617
+ }
618
+ MYSQL_ROW result = mysql_fetch_row(reader);
619
+
620
+ // The Meat
621
+ VALUE field_types = rb_iv_get(self, "@field_types");
622
+ VALUE row = rb_ary_new();
623
+ unsigned long *lengths = mysql_fetch_lengths(reader);
624
+
625
+ rb_iv_set(self, "@opened", result ? Qtrue : Qfalse);
626
+
627
+ if (!result) {
628
+ return Qfalse;
629
+ }
630
+
631
+ int enc = -1;
632
+ #ifdef HAVE_RUBY_ENCODING_H
633
+ VALUE encoding_id = rb_iv_get(rb_iv_get(self, "@connection"), "@encoding_id");
634
+
635
+ if (encoding_id != Qnil) {
636
+ enc = FIX2INT(encoding_id);
637
+ }
638
+ #endif
639
+
640
+ VALUE field_type;
641
+ unsigned int i;
642
+
643
+ for (i = 0; i < reader->field_count; i++) {
644
+ // The field_type data could be cached in a c-array
645
+ field_type = rb_ary_entry(field_types, i);
646
+ rb_ary_push(row, do_mysql_typecast(result[i], lengths[i], field_type, enc));
647
+ }
648
+
649
+ rb_iv_set(self, "@values", row);
650
+ return Qtrue;
651
+ }
652
+
653
+ void Init_do_mysql(void) {
654
+ data_objects_common_init();
655
+
656
+ // Top Level Module that all the classes live under
657
+ mDO_Mysql = rb_define_module_under(mDO, "Mysql");
658
+ mDO_MysqlEncoding = rb_define_module_under(mDO_Mysql, "Encoding");
659
+
660
+ cDO_MysqlConnection = rb_define_class_under(mDO_Mysql, "Connection", cDO_Connection);
661
+ rb_define_method(cDO_MysqlConnection, "initialize", do_mysql_cConnection_initialize, 1);
662
+ rb_define_method(cDO_MysqlConnection, "using_socket?", data_objects_cConnection_is_using_socket, 0);
663
+ rb_define_method(cDO_MysqlConnection, "ssl_cipher", data_objects_cConnection_ssl_cipher, 0);
664
+ rb_define_method(cDO_MysqlConnection, "character_set", data_objects_cConnection_character_set , 0);
665
+ rb_define_method(cDO_MysqlConnection, "dispose", do_mysql_cConnection_dispose, 0);
666
+ rb_define_method(cDO_MysqlConnection, "quote_string", do_mysql_cConnection_quote_string, 1);
667
+ rb_define_method(cDO_MysqlConnection, "quote_date", data_objects_cConnection_quote_date, 1);
668
+ rb_define_method(cDO_MysqlConnection, "quote_time", data_objects_cConnection_quote_time, 1);
669
+ rb_define_method(cDO_MysqlConnection, "quote_datetime", data_objects_cConnection_quote_date_time, 1);
670
+
671
+ cDO_MysqlCommand = rb_define_class_under(mDO_Mysql, "Command", cDO_Command);
672
+ rb_define_method(cDO_MysqlCommand, "set_types", data_objects_cCommand_set_types, -1);
673
+ rb_define_method(cDO_MysqlCommand, "execute_non_query", do_mysql_cCommand_execute_non_query, -1);
674
+ rb_define_method(cDO_MysqlCommand, "execute_reader", do_mysql_cCommand_execute_reader, -1);
675
+
676
+ // Non-Query result
677
+ cDO_MysqlResult = rb_define_class_under(mDO_Mysql, "Result", cDO_Result);
678
+
679
+ // Query result
680
+ cDO_MysqlReader = rb_define_class_under(mDO_Mysql, "Reader", cDO_Reader);
681
+ rb_define_method(cDO_MysqlReader, "close", do_mysql_cReader_close, 0);
682
+ rb_define_method(cDO_MysqlReader, "next!", do_mysql_cReader_next, 0);
683
+ rb_define_method(cDO_MysqlReader, "values", data_objects_cReader_values, 0);
684
+ rb_define_method(cDO_MysqlReader, "fields", data_objects_cReader_fields, 0);
685
+ rb_define_method(cDO_MysqlReader, "field_count", data_objects_cReader_field_count, 0);
686
+
687
+ rb_global_variable(&cDO_MysqlResult);
688
+ rb_global_variable(&cDO_MysqlReader);
689
+
690
+ data_objects_define_errors(mDO_Mysql, do_mysql_errors);
691
+ }