sbf-do_mysql 0.10.17

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.
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
+ }