do_mysql 0.9.12-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,939 @@
1
+ #include <ruby.h>
2
+ #include <string.h>
3
+ #include <math.h>
4
+ #include <ctype.h>
5
+ #include <time.h>
6
+ #include <mysql.h>
7
+ #include <errmsg.h>
8
+ #include <mysqld_error.h>
9
+
10
+ #define RUBY_CLASS(name) rb_const_get(rb_cObject, rb_intern(name))
11
+ #define RUBY_STRING(char_ptr) rb_str_new2(char_ptr)
12
+ #define TAINTED_STRING(name, length) rb_tainted_str_new(name, length)
13
+ #define DRIVER_CLASS(klass, parent) (rb_define_class_under(mDOMysql, klass, parent))
14
+ #define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
15
+ #define CHECK_AND_RAISE(mysql_result_value, str) if (0 != mysql_result_value) { raise_mysql_error(connection, db, mysql_result_value, str); }
16
+ #define PUTS(string) rb_funcall(rb_mKernel, rb_intern("puts"), 1, RUBY_STRING(string))
17
+
18
+ #ifndef RSTRING_PTR
19
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
20
+ #endif
21
+
22
+ #ifndef RSTRING_LEN
23
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
24
+ #endif
25
+
26
+ #ifndef RARRAY_LEN
27
+ #define RARRAY_LEN(a) RARRAY(a)->len
28
+ #endif
29
+
30
+ #ifdef _WIN32
31
+ #define cCommand_execute cCommand_execute_sync
32
+ #define do_int64 signed __int64
33
+ #else
34
+ #define cCommand_execute cCommand_execute_async
35
+ #define do_int64 signed long long int
36
+ #endif
37
+
38
+ // To store rb_intern values
39
+ static ID ID_TO_I;
40
+ static ID ID_TO_F;
41
+ static ID ID_TO_S;
42
+ static ID ID_TO_TIME;
43
+ static ID ID_NEW;
44
+ static ID ID_NEW_DATE;
45
+ static ID ID_CONST_GET;
46
+ static ID ID_RATIONAL;
47
+ static ID ID_UTC;
48
+ static ID ID_ESCAPE_SQL;
49
+ static ID ID_STRFTIME;
50
+ static ID ID_LOGGER;
51
+ static ID ID_DEBUG;
52
+ static ID ID_LEVEL;
53
+
54
+ // Reference to Extlib module
55
+ static VALUE mExtlib;
56
+
57
+ // References to DataObjects base classes
58
+ static VALUE mDO;
59
+ static VALUE cDO_Quoting;
60
+ static VALUE cDO_Connection;
61
+ static VALUE cDO_Command;
62
+ static VALUE cDO_Result;
63
+ static VALUE cDO_Reader;
64
+
65
+ // References to Ruby classes that we'll need
66
+ static VALUE rb_cDate;
67
+ static VALUE rb_cDateTime;
68
+ static VALUE rb_cBigDecimal;
69
+ static VALUE rb_cByteArray;
70
+
71
+ // Classes that we'll build in Init
72
+ static VALUE mDOMysql;
73
+ static VALUE cConnection;
74
+ static VALUE cCommand;
75
+ static VALUE cResult;
76
+ static VALUE cReader;
77
+ static VALUE eMysqlError;
78
+ static VALUE eArgumentError;
79
+
80
+ // Figures out what we should cast a given mysql field type to
81
+ static VALUE infer_ruby_type(MYSQL_FIELD *field) {
82
+ switch(field->type) {
83
+ case MYSQL_TYPE_NULL:
84
+ return Qnil;
85
+ case MYSQL_TYPE_TINY:
86
+ return rb_cTrueClass;
87
+ case MYSQL_TYPE_BIT:
88
+ case MYSQL_TYPE_SHORT:
89
+ case MYSQL_TYPE_LONG:
90
+ case MYSQL_TYPE_INT24:
91
+ case MYSQL_TYPE_LONGLONG:
92
+ case MYSQL_TYPE_YEAR:
93
+ return rb_cInteger;
94
+ case MYSQL_TYPE_NEWDECIMAL:
95
+ case MYSQL_TYPE_DECIMAL:
96
+ return rb_cBigDecimal;
97
+ case MYSQL_TYPE_FLOAT:
98
+ case MYSQL_TYPE_DOUBLE:
99
+ return rb_cFloat;
100
+ case MYSQL_TYPE_TIMESTAMP:
101
+ case MYSQL_TYPE_DATETIME:
102
+ return rb_cDateTime;
103
+ case MYSQL_TYPE_TIME:
104
+ return rb_cDateTime;
105
+ case MYSQL_TYPE_DATE:
106
+ case MYSQL_TYPE_NEWDATE:
107
+ return rb_cDate;
108
+ case MYSQL_TYPE_TINY_BLOB:
109
+ case MYSQL_TYPE_MEDIUM_BLOB:
110
+ case MYSQL_TYPE_LONG_BLOB:
111
+ case MYSQL_TYPE_BLOB:
112
+ return rb_cByteArray;
113
+ default:
114
+ return rb_cString;
115
+ }
116
+ }
117
+
118
+ // Find the greatest common denominator and reduce the provided numerator and denominator.
119
+ // This replaces calles to Rational.reduce! which does the same thing, but really slowly.
120
+ static void reduce( do_int64 *numerator, do_int64 *denominator ) {
121
+ do_int64 a, b, c;
122
+ a = *numerator;
123
+ b = *denominator;
124
+ while ( a != 0 ) {
125
+ c = a; a = b % a; b = c;
126
+ }
127
+ *numerator = *numerator / b;
128
+ *denominator = *denominator / b;
129
+ }
130
+
131
+ // Generate the date integer which Date.civil_to_jd returns
132
+ static int jd_from_date(int year, int month, int day) {
133
+ int a, b;
134
+ if ( month <= 2 ) {
135
+ year -= 1;
136
+ month += 12;
137
+ }
138
+ a = year / 100;
139
+ b = 2 - a + (a / 4);
140
+ return floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524;
141
+ }
142
+
143
+ static VALUE seconds_to_offset(long seconds_offset) {
144
+ do_int64 num = seconds_offset, den = 86400;
145
+ reduce(&num, &den);
146
+ return rb_funcall(rb_mKernel, ID_RATIONAL, 2, rb_ll2inum(num), rb_ll2inum(den));
147
+ }
148
+
149
+ static VALUE timezone_to_offset(int hour_offset, int minute_offset) {
150
+ do_int64 seconds = 0;
151
+
152
+ seconds += hour_offset * 3600;
153
+ seconds += minute_offset * 60;
154
+
155
+ return seconds_to_offset(seconds);
156
+ }
157
+
158
+ static VALUE parse_date(const char *date) {
159
+ int year, month, day;
160
+ int jd, ajd;
161
+ VALUE rational;
162
+
163
+ sscanf(date, "%4d-%2d-%2d", &year, &month, &day);
164
+
165
+ jd = jd_from_date(year, month, day);
166
+
167
+ // Math from Date.jd_to_ajd
168
+ ajd = jd * 2 - 1;
169
+ rational = rb_funcall(rb_mKernel, ID_RATIONAL, 2, INT2NUM(ajd), INT2NUM(2));
170
+ return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
171
+ }
172
+
173
+ static VALUE parse_time(const char *date) {
174
+
175
+ int year, month, day, hour, min, sec, usec, tokens;
176
+ char subsec[7];
177
+
178
+ if (0 != strchr(date, '.')) {
179
+ // right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
180
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
181
+ sscanf(subsec, "%d", &usec);
182
+ } else {
183
+ tokens = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
184
+ if (tokens == 3) {
185
+ hour = 0;
186
+ min = 0;
187
+ sec = 0;
188
+ }
189
+ usec = 0;
190
+ }
191
+
192
+ if ( year + month + day + hour + min + sec + usec == 0 ) { // Mysql TIMESTAMPS can default to 0
193
+ return Qnil;
194
+ }
195
+
196
+ return rb_funcall(rb_cTime, rb_intern("local"), 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec));
197
+ }
198
+
199
+ static VALUE parse_date_time(const char *date) {
200
+ VALUE ajd, offset;
201
+
202
+ int year, month, day, hour, min, sec, usec, hour_offset, minute_offset;
203
+ int jd;
204
+ do_int64 num, den;
205
+
206
+
207
+ long int gmt_offset;
208
+ int is_dst;
209
+
210
+ time_t rawtime;
211
+ struct tm * timeinfo;
212
+
213
+ int tokens_read, max_tokens;
214
+
215
+ if ( strcmp(date, "") == 0 ) {
216
+ return Qnil;
217
+ }
218
+
219
+ if (0 != strchr(date, '.')) {
220
+ // This is a datetime with sub-second precision
221
+ tokens_read = sscanf(date, "%4d-%2d-%2d%*c%2d:%2d:%2d.%d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &usec, &hour_offset, &minute_offset);
222
+ max_tokens = 9;
223
+ } else {
224
+ // This is a datetime second precision
225
+ tokens_read = sscanf(date, "%4d-%2d-%2d%*c%2d:%2d:%2d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &hour_offset, &minute_offset);
226
+ max_tokens = 8;
227
+ }
228
+
229
+ if (max_tokens == tokens_read) {
230
+ // We read the Date, Time, and Timezone info
231
+ minute_offset *= hour_offset < 0 ? -1 : 1;
232
+ } else if ((max_tokens - 1) == tokens_read) {
233
+ // We read the Date and Time, but no Minute Offset
234
+ minute_offset = 0;
235
+ } else if (tokens_read == 3 || tokens_read >= (max_tokens - 3)) {
236
+ if (tokens_read == 3) {
237
+ hour = 0;
238
+ min = 0;
239
+ hour_offset = 0;
240
+ minute_offset = 0;
241
+ sec = 0;
242
+ }
243
+ // We read the Date and Time, default to the current locale's offset
244
+
245
+ // Get localtime
246
+ time(&rawtime);
247
+ timeinfo = localtime(&rawtime);
248
+
249
+ is_dst = timeinfo->tm_isdst * 3600;
250
+
251
+ // Reset to GM Time
252
+ timeinfo = gmtime(&rawtime);
253
+
254
+ gmt_offset = mktime(timeinfo) - rawtime;
255
+
256
+ if ( is_dst > 0 )
257
+ gmt_offset -= is_dst;
258
+
259
+ hour_offset = -(gmt_offset / 3600);
260
+ minute_offset = -(gmt_offset % 3600 / 60);
261
+
262
+ } else {
263
+ // Something went terribly wrong
264
+ rb_raise(eMysqlError, "Couldn't parse date: %s", date);
265
+ }
266
+
267
+ jd = jd_from_date(year, month, day);
268
+
269
+ // Generate ajd with fractional days for the time
270
+ // Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
271
+ num = (hour * 1440) + (min * 24);
272
+
273
+ // Modify the numerator so when we apply the timezone everything works out
274
+ num -= (hour_offset * 1440) + (minute_offset * 24);
275
+
276
+ den = (24 * 1440);
277
+ reduce(&num, &den);
278
+
279
+ num = (num * 86400) + (sec * den);
280
+ den = den * 86400;
281
+ reduce(&num, &den);
282
+
283
+ num = (jd * den) + num;
284
+
285
+ num = num * 2;
286
+ num = num - den;
287
+ den = den * 2;
288
+
289
+ reduce(&num, &den);
290
+
291
+ ajd = rb_funcall(rb_mKernel, ID_RATIONAL, 2, rb_ull2inum(num), rb_ull2inum(den));
292
+ offset = timezone_to_offset(hour_offset, minute_offset);
293
+
294
+ return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
295
+ }
296
+
297
+ // Convert C-string to a Ruby instance of Ruby type "type"
298
+ static VALUE typecast(const char *value, long length, const VALUE type) {
299
+
300
+ if(NULL == value) {
301
+ return Qnil;
302
+ }
303
+
304
+ if (type == rb_cInteger) {
305
+ return rb_cstr2inum(value, 10);
306
+ } else if (type == rb_cString) {
307
+ return TAINTED_STRING(value, length);
308
+ } else if (type == rb_cFloat) {
309
+ return rb_float_new(rb_cstr_to_dbl(value, Qfalse));
310
+ } else if (type == rb_cBigDecimal) {
311
+ return rb_funcall(rb_cBigDecimal, ID_NEW, 1, TAINTED_STRING(value, length));
312
+ } else if (type == rb_cDate) {
313
+ return parse_date(value);
314
+ } else if (type == rb_cDateTime) {
315
+ return parse_date_time(value);
316
+ } else if (type == rb_cTime) {
317
+ return parse_time(value);
318
+ } else if (type == rb_cTrueClass) {
319
+ return (0 == value || 0 == strcmp("0", value)) ? Qfalse : Qtrue;
320
+ } else if (type == rb_cByteArray) {
321
+ return rb_funcall(rb_cByteArray, ID_NEW, 1, TAINTED_STRING(value, length));
322
+ } else if (type == rb_cClass) {
323
+ return rb_funcall(rb_cObject, rb_intern("full_const_get"), 1, TAINTED_STRING(value, length));
324
+ } else if (type == rb_cObject) {
325
+ return rb_marshal_load(rb_str_new(value, length));
326
+ } else if (type == rb_cNilClass) {
327
+ return Qnil;
328
+ } else {
329
+ return TAINTED_STRING(value, length);
330
+ }
331
+
332
+ }
333
+
334
+ static void data_objects_debug(VALUE string, struct timeval* start) {
335
+ struct timeval stop;
336
+ char *message;
337
+
338
+ char *query = RSTRING_PTR(string);
339
+ int length = RSTRING_LEN(string);
340
+ char total_time[32];
341
+ do_int64 duration = 0;
342
+
343
+ VALUE logger = rb_funcall(mDOMysql, ID_LOGGER, 0);
344
+ int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
345
+
346
+ if (0 == log_level) {
347
+ gettimeofday(&stop, NULL);
348
+
349
+ duration = (stop.tv_sec - start->tv_sec) * 1000000 + stop.tv_usec - start->tv_usec;
350
+
351
+ snprintf(total_time, 32, "%.6f", duration / 1000000.0);
352
+ message = (char *)calloc(length + strlen(total_time) + 4, sizeof(char));
353
+ snprintf(message, length + strlen(total_time) + 4, "(%s) %s", total_time, query);
354
+ rb_funcall(logger, ID_DEBUG, 1, rb_str_new(message, length + strlen(total_time) + 3));
355
+ }
356
+ }
357
+ static void raise_mysql_error(VALUE connection, MYSQL *db, int mysql_error_code, char* str) {
358
+ char *mysql_error_message = (char *)mysql_error(db);
359
+
360
+ if(mysql_error_code == 1) {
361
+ mysql_error_code = mysql_errno(db);
362
+ }
363
+ if(str) {
364
+ rb_raise(eMysqlError, "(mysql_errno=%04d, sql_state=%s) %s\nQuery: %s", mysql_error_code, mysql_sqlstate(db), mysql_error_message, str);
365
+ } else {
366
+ rb_raise(eMysqlError, "(mysql_errno=%04d, sql_state=%s) %s", mysql_error_code, mysql_sqlstate(db), mysql_error_message);
367
+ }
368
+ }
369
+
370
+ static char * get_uri_option(VALUE query_hash, char * key) {
371
+ VALUE query_value;
372
+ char * value = NULL;
373
+
374
+ if(!rb_obj_is_kind_of(query_hash, rb_cHash)) { return NULL; }
375
+
376
+ query_value = rb_hash_aref(query_hash, RUBY_STRING(key));
377
+
378
+ if (Qnil != query_value) {
379
+ value = StringValuePtr(query_value);
380
+ }
381
+
382
+ return value;
383
+ }
384
+
385
+ #ifdef _WIN32
386
+ static MYSQL_RES* cCommand_execute_sync(VALUE self, MYSQL* db, VALUE query) {
387
+ int retval;
388
+ struct timeval start;
389
+ char* str = RSTRING_PTR(query);
390
+ int len = RSTRING_LEN(query);
391
+
392
+ VALUE connection = rb_iv_get(self, "@connection");
393
+
394
+ if(mysql_ping(db) && mysql_errno(db) == CR_SERVER_GONE_ERROR) {
395
+ CHECK_AND_RAISE(mysql_errno(db), "Mysql server has gone away. \
396
+ Please report this issue to the Datamapper project. \
397
+ Specify your at least your MySQL version when filing a ticket");
398
+ }
399
+ gettimeofday(&start, NULL);
400
+ retval = mysql_real_query(db, str, len);
401
+ CHECK_AND_RAISE(retval, str);
402
+
403
+ data_objects_debug(query, &start);
404
+
405
+ return mysql_store_result(db);
406
+ }
407
+ #else
408
+ static MYSQL_RES* cCommand_execute_async(VALUE self, MYSQL* db, VALUE query) {
409
+ int socket_fd;
410
+ int retval;
411
+ fd_set rset;
412
+ struct timeval start;
413
+ char* str = RSTRING_PTR(query);
414
+ int len = RSTRING_LEN(query);
415
+
416
+ VALUE connection = rb_iv_get(self, "@connection");
417
+
418
+ if(mysql_ping(db) && mysql_errno(db) == CR_SERVER_GONE_ERROR) {
419
+ CHECK_AND_RAISE(mysql_errno(db), "Mysql server has gone away. \
420
+ Please report this issue to the Datamapper project. \
421
+ Specify your at least your MySQL version when filing a ticket");
422
+ }
423
+ retval = mysql_send_query(db, str, len);
424
+
425
+ CHECK_AND_RAISE(retval, str);
426
+ gettimeofday(&start, NULL);
427
+
428
+ socket_fd = db->net.fd;
429
+
430
+ for(;;) {
431
+ FD_ZERO(&rset);
432
+ FD_SET(socket_fd, &rset);
433
+
434
+ retval = rb_thread_select(socket_fd + 1, &rset, NULL, NULL, NULL);
435
+
436
+ if (retval < 0) {
437
+ rb_sys_fail(0);
438
+ }
439
+
440
+ if (retval == 0) {
441
+ continue;
442
+ }
443
+
444
+ if (db->status == MYSQL_STATUS_READY) {
445
+ break;
446
+ }
447
+ }
448
+
449
+ retval = mysql_read_query_result(db);
450
+ CHECK_AND_RAISE(retval, str);
451
+
452
+ data_objects_debug(query, &start);
453
+
454
+ return mysql_store_result(db);
455
+ }
456
+ #endif
457
+
458
+ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
459
+ VALUE r_host, r_user, r_password, r_path, r_query, r_port;
460
+
461
+ char *host = "localhost", *user = "root", *password = NULL, *path;
462
+ char *database = "", *socket = NULL;
463
+ char *encoding = NULL;
464
+
465
+ int port = 3306;
466
+ unsigned long client_flags = 0;
467
+ int encoding_error;
468
+
469
+ MYSQL *db = 0, *result;
470
+ db = (MYSQL *)mysql_init(NULL);
471
+
472
+ rb_iv_set(self, "@using_socket", Qfalse);
473
+
474
+ r_host = rb_funcall(uri, rb_intern("host"), 0);
475
+ if (Qnil != r_host) {
476
+ host = StringValuePtr(r_host);
477
+ }
478
+
479
+ r_user = rb_funcall(uri, rb_intern("user"), 0);
480
+ if (Qnil != r_user) {
481
+ user = StringValuePtr(r_user);
482
+ }
483
+
484
+ r_password = rb_funcall(uri, rb_intern("password"), 0);
485
+ if (Qnil != r_password) {
486
+ password = StringValuePtr(r_password);
487
+ }
488
+
489
+ r_path = rb_funcall(uri, rb_intern("path"), 0);
490
+ path = StringValuePtr(r_path);
491
+ if (Qnil != r_path) {
492
+ database = strtok(path, "/");
493
+ }
494
+
495
+ if (NULL == database || 0 == strlen(database)) {
496
+ rb_raise(eMysqlError, "Database must be specified");
497
+ }
498
+
499
+ // Pull the querystring off the URI
500
+ r_query = rb_funcall(uri, rb_intern("query"), 0);
501
+
502
+ // Check to see if we're on the db machine. If so, try to use the socket
503
+ if (0 == strcasecmp(host, "localhost")) {
504
+ socket = get_uri_option(r_query, "socket");
505
+ if (NULL != socket) {
506
+ rb_iv_set(self, "@using_socket", Qtrue);
507
+ }
508
+ }
509
+
510
+ r_port = rb_funcall(uri, rb_intern("port"), 0);
511
+ if (Qnil != r_port) {
512
+ port = NUM2INT(r_port);
513
+ }
514
+
515
+ encoding = get_uri_option(r_query, "encoding");
516
+ if (!encoding) { encoding = get_uri_option(r_query, "charset"); }
517
+ if (!encoding) { encoding = "utf8"; }
518
+
519
+ // If ssl? {
520
+ // mysql_ssl_set(db, key, cert, ca, capath, cipher)
521
+ // }
522
+
523
+ result = (MYSQL *)mysql_real_connect(
524
+ db,
525
+ host,
526
+ user,
527
+ password,
528
+ database,
529
+ port,
530
+ socket,
531
+ client_flags
532
+ );
533
+
534
+ if (NULL == result) {
535
+ raise_mysql_error(Qnil, db, -1, NULL);
536
+ }
537
+
538
+ #ifdef MYSQL_OPT_RECONNECT
539
+ my_bool reconnect = 1;
540
+ mysql_options(db, MYSQL_OPT_RECONNECT, &reconnect);
541
+ #endif
542
+
543
+ // Set the connections character set
544
+ encoding_error = mysql_set_character_set(db, encoding);
545
+ if (0 != encoding_error) {
546
+ raise_mysql_error(Qnil, db, encoding_error, NULL);
547
+ }
548
+
549
+ // Disable sql_auto_is_null
550
+ cCommand_execute(self, db, rb_str_new2("SET sql_auto_is_null = 0"));
551
+ cCommand_execute(self, db, rb_str_new2("SET SESSION sql_mode = 'ANSI,NO_AUTO_VALUE_ON_ZERO,NO_DIR_IN_CREATE,NO_ENGINE_SUBSTITUTION,NO_UNSIGNED_SUBTRACTION,TRADITIONAL'"));
552
+
553
+ rb_iv_set(self, "@uri", uri);
554
+ rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
555
+
556
+ return Qtrue;
557
+ }
558
+
559
+ static VALUE cConnection_character_set(VALUE self) {
560
+ VALUE connection_container = rb_iv_get(self, "@connection");
561
+ MYSQL *db;
562
+
563
+ const char *encoding;
564
+
565
+ if (Qnil == connection_container)
566
+ return Qfalse;
567
+
568
+ db = DATA_PTR(connection_container);
569
+
570
+ encoding = mysql_character_set_name(db);
571
+
572
+ return RUBY_STRING(encoding);
573
+ }
574
+
575
+ static VALUE cConnection_is_using_socket(VALUE self) {
576
+ return rb_iv_get(self, "@using_socket");
577
+ }
578
+
579
+ static VALUE cConnection_dispose(VALUE self) {
580
+ VALUE connection_container = rb_iv_get(self, "@connection");
581
+
582
+ MYSQL *db;
583
+
584
+ if (Qnil == connection_container)
585
+ return Qfalse;
586
+
587
+ db = DATA_PTR(connection_container);
588
+
589
+ if (NULL == db)
590
+ return Qfalse;
591
+
592
+ mysql_close(db);
593
+ rb_iv_set(self, "@connection", Qnil);
594
+
595
+ return Qtrue;
596
+ }
597
+
598
+ /*
599
+ Accepts an array of Ruby types (Fixnum, Float, String, etc...) and turns them
600
+ into Ruby-strings so we can easily typecast later
601
+ */
602
+ static VALUE cCommand_set_types(int argc, VALUE *argv, VALUE self) {
603
+ VALUE type_strings = rb_ary_new();
604
+ VALUE array = rb_ary_new();
605
+
606
+ int i, j;
607
+
608
+ for ( i = 0; i < argc; i++) {
609
+ rb_ary_push(array, argv[i]);
610
+ }
611
+
612
+ for (i = 0; i < RARRAY_LEN(array); i++) {
613
+ VALUE entry = rb_ary_entry(array, i);
614
+ if(TYPE(entry) == T_CLASS) {
615
+ rb_ary_push(type_strings, entry);
616
+ } else if (TYPE(entry) == T_ARRAY) {
617
+ for (j = 0; j < RARRAY_LEN(entry); j++) {
618
+ VALUE sub_entry = rb_ary_entry(entry, j);
619
+ if(TYPE(sub_entry) == T_CLASS) {
620
+ rb_ary_push(type_strings, sub_entry);
621
+ } else {
622
+ rb_raise(eArgumentError, "Invalid type given");
623
+ }
624
+ }
625
+ } else {
626
+ rb_raise(eArgumentError, "Invalid type given");
627
+ }
628
+ }
629
+
630
+ rb_iv_set(self, "@field_types", type_strings);
631
+
632
+ return array;
633
+ }
634
+
635
+ VALUE cConnection_quote_time(VALUE self, VALUE value) {
636
+ return rb_funcall(value, ID_STRFTIME, 1, RUBY_STRING("'%Y-%m-%d %H:%M:%S'"));
637
+ }
638
+
639
+
640
+ VALUE cConnection_quote_date_time(VALUE self, VALUE value) {
641
+ // TODO: Support non-local dates. we need to call #new_offset on the date to be
642
+ // quoted and pass in the current locale's date offset (self.new_offset((hours * 3600).to_r / 86400)
643
+ return rb_funcall(value, ID_STRFTIME, 1, RUBY_STRING("'%Y-%m-%d %H:%M:%S'"));
644
+ }
645
+
646
+ VALUE cConnection_quote_date(VALUE self, VALUE value) {
647
+ return rb_funcall(value, ID_STRFTIME, 1, RUBY_STRING("'%Y-%m-%d'"));
648
+ }
649
+
650
+ static VALUE cConnection_quote_string(VALUE self, VALUE string) {
651
+ MYSQL *db = DATA_PTR(rb_iv_get(self, "@connection"));
652
+ const char *source = RSTRING_PTR(string);
653
+ int source_len = RSTRING_LEN(string);
654
+ char *escaped;
655
+ VALUE result;
656
+
657
+ int quoted_length = 0;
658
+
659
+ // Allocate space for the escaped version of 'string'. Use + 3 allocate space for null term.
660
+ // and the leading and trailing single-quotes.
661
+ // Thanks to http://www.browardphp.com/mysql_manual_en/manual_MySQL_APIs.html#mysql_real_escape_string
662
+ escaped = (char *)calloc(source_len * 2 + 3, sizeof(char));
663
+
664
+ // Escape 'source' using the current encoding in use on the conection 'db'
665
+ quoted_length = mysql_real_escape_string(db, escaped + 1, source, source_len);
666
+
667
+ // Wrap the escaped string in single-quotes, this is DO's convention
668
+ escaped[0] = escaped[quoted_length + 1] = '\'';
669
+ result = rb_str_new(escaped, quoted_length + 2);
670
+ free(escaped);
671
+ return result;
672
+ }
673
+
674
+ static VALUE build_query_from_args(VALUE klass, int count, VALUE *args) {
675
+ VALUE query = rb_iv_get(klass, "@text");
676
+
677
+ int i;
678
+ VALUE array = rb_ary_new();
679
+ for ( i = 0; i < count; i++) {
680
+ rb_ary_push(array, (VALUE)args[i]);
681
+ }
682
+ query = rb_funcall(klass, ID_ESCAPE_SQL, 1, array);
683
+
684
+ return query;
685
+ }
686
+
687
+ static VALUE cCommand_execute_non_query(int argc, VALUE *argv, VALUE self) {
688
+ VALUE query;
689
+
690
+ MYSQL_RES *response = 0;
691
+
692
+ my_ulonglong affected_rows;
693
+ VALUE connection = rb_iv_get(self, "@connection");
694
+ VALUE mysql_connection = rb_iv_get(connection, "@connection");
695
+ if (Qnil == mysql_connection) {
696
+ rb_raise(eMysqlError, "This connection has already been closed.");
697
+ }
698
+
699
+ MYSQL *db = DATA_PTR(mysql_connection);
700
+ query = build_query_from_args(self, argc, argv);
701
+
702
+ response = cCommand_execute(self, db, query);
703
+
704
+ affected_rows = mysql_affected_rows(db);
705
+ mysql_free_result(response);
706
+
707
+ if (-1 == affected_rows)
708
+ return Qnil;
709
+
710
+ return rb_funcall(cResult, ID_NEW, 3, self, INT2NUM(affected_rows), INT2NUM(mysql_insert_id(db)));
711
+ }
712
+
713
+ static VALUE cCommand_execute_reader(int argc, VALUE *argv, VALUE self) {
714
+ VALUE query, reader;
715
+ VALUE field_names, field_types;
716
+
717
+ unsigned int field_count;
718
+ int i;
719
+
720
+ char guess_default_field_types = 0;
721
+ VALUE connection = rb_iv_get(self, "@connection");
722
+ VALUE mysql_connection = rb_iv_get(connection, "@connection");
723
+ if (Qnil == mysql_connection) {
724
+ rb_raise(eMysqlError, "This connection has already been closed.");
725
+ }
726
+
727
+ MYSQL *db = DATA_PTR(mysql_connection);
728
+
729
+ MYSQL_RES *response = 0;
730
+ MYSQL_FIELD *field;
731
+
732
+ query = build_query_from_args(self, argc, argv);
733
+
734
+ response = cCommand_execute(self, db, query);
735
+
736
+ if (!response) {
737
+ return Qnil;
738
+ }
739
+
740
+ field_count = mysql_field_count(db);
741
+
742
+ reader = rb_funcall(cReader, ID_NEW, 0);
743
+ rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, response));
744
+ rb_iv_set(reader, "@opened", Qtrue);
745
+ rb_iv_set(reader, "@field_count", INT2NUM(field_count));
746
+
747
+ field_names = rb_ary_new();
748
+ field_types = rb_iv_get(self, "@field_types");
749
+
750
+ if ( field_types == Qnil || 0 == RARRAY_LEN(field_types) ) {
751
+ field_types = rb_ary_new();
752
+ guess_default_field_types = 1;
753
+ } else if (RARRAY_LEN(field_types) != field_count) {
754
+ // Whoops... wrong number of types passed to set_types. Close the reader and raise
755
+ // and error
756
+ rb_funcall(reader, rb_intern("close"), 0);
757
+ rb_raise(eArgumentError, "Field-count mismatch. Expected %ld fields, but the query yielded %d", RARRAY_LEN(field_types), field_count);
758
+ }
759
+
760
+ for(i = 0; i < field_count; i++) {
761
+ field = mysql_fetch_field_direct(response, i);
762
+ rb_ary_push(field_names, RUBY_STRING(field->name));
763
+
764
+ if (1 == guess_default_field_types) {
765
+ rb_ary_push(field_types, infer_ruby_type(field));
766
+ }
767
+ }
768
+
769
+ rb_iv_set(reader, "@fields", field_names);
770
+ rb_iv_set(reader, "@field_types", field_types);
771
+
772
+ if (rb_block_given_p()) {
773
+ rb_yield(reader);
774
+ rb_funcall(reader, rb_intern("close"), 0);
775
+ }
776
+
777
+ return reader;
778
+ }
779
+
780
+ // This should be called to ensure that the internal result reader is freed
781
+ static VALUE cReader_close(VALUE self) {
782
+ // Get the reader from the instance variable, maybe refactor this?
783
+ VALUE reader_container = rb_iv_get(self, "@reader");
784
+
785
+ MYSQL_RES *reader;
786
+
787
+ if (Qnil == reader_container)
788
+ return Qfalse;
789
+
790
+ reader = DATA_PTR(reader_container);
791
+
792
+ // The Meat
793
+ if (NULL == reader)
794
+ return Qfalse;
795
+
796
+ mysql_free_result(reader);
797
+ rb_iv_set(self, "@reader", Qnil);
798
+
799
+ return Qtrue;
800
+ }
801
+
802
+ // Retrieve a single row
803
+ static VALUE cReader_next(VALUE self) {
804
+ // Get the reader from the instance variable, maybe refactor this?
805
+ VALUE reader_container = rb_iv_get(self, "@reader");
806
+ VALUE field_types, field_type, row;
807
+
808
+ MYSQL_RES *reader;
809
+ MYSQL_ROW result;
810
+ unsigned long *lengths;
811
+
812
+ int i;
813
+
814
+ if (Qnil == reader_container) {
815
+ return Qfalse;
816
+ }
817
+
818
+ reader = DATA_PTR(reader_container);
819
+
820
+ // The Meat
821
+ field_types = rb_iv_get(self, "@field_types");
822
+ row = rb_ary_new();
823
+ result = mysql_fetch_row(reader);
824
+ lengths = mysql_fetch_lengths(reader);
825
+
826
+ rb_iv_set(self, "@state", result ? Qtrue : Qfalse);
827
+
828
+ if (!result) {
829
+ return Qfalse;
830
+ }
831
+
832
+ for (i = 0; i < reader->field_count; i++) {
833
+ // The field_type data could be cached in a c-array
834
+ field_type = rb_ary_entry(field_types, i);
835
+ rb_ary_push(row, typecast(result[i], lengths[i], field_type));
836
+ }
837
+
838
+ rb_iv_set(self, "@values", row);
839
+
840
+ return Qtrue;
841
+ }
842
+
843
+ static VALUE cReader_values(VALUE self) {
844
+ VALUE state = rb_iv_get(self, "@state");
845
+ if ( state == Qnil || state == Qfalse ) {
846
+ rb_raise(eMysqlError, "Reader is not initialized");
847
+ }
848
+ else {
849
+ return rb_iv_get(self, "@values");
850
+ }
851
+ }
852
+
853
+ static VALUE cReader_fields(VALUE self) {
854
+ return rb_iv_get(self, "@fields");
855
+ }
856
+
857
+ static VALUE cReader_field_count(VALUE self) {
858
+ return rb_iv_get(self, "@field_count");
859
+ }
860
+
861
+ static VALUE cReader_row_count(VALUE self) {
862
+ return rb_iv_get(self, "@row_count");
863
+ }
864
+
865
+ void Init_do_mysql_ext() {
866
+ rb_require("bigdecimal");
867
+ rb_require("date");
868
+
869
+ rb_funcall(rb_mKernel, rb_intern("require"), 1, RUBY_STRING("data_objects"));
870
+
871
+ ID_TO_I = rb_intern("to_i");
872
+ ID_TO_F = rb_intern("to_f");
873
+ ID_TO_S = rb_intern("to_s");
874
+ ID_TO_TIME = rb_intern("to_time");
875
+ ID_NEW = rb_intern("new");
876
+ #ifdef RUBY_LESS_THAN_186
877
+ ID_NEW_DATE = rb_intern("new0");
878
+ #else
879
+ ID_NEW_DATE = rb_intern("new!");
880
+ #endif
881
+ ID_CONST_GET = rb_intern("const_get");
882
+ ID_RATIONAL = rb_intern("Rational");
883
+ ID_UTC = rb_intern("utc");
884
+ ID_ESCAPE_SQL = rb_intern("escape_sql");
885
+ ID_STRFTIME = rb_intern("strftime");
886
+ ID_LOGGER = rb_intern("logger");
887
+ ID_DEBUG = rb_intern("debug");
888
+ ID_LEVEL = rb_intern("level");
889
+
890
+ // Store references to a few helpful clases that aren't in Ruby Core
891
+ rb_cDate = RUBY_CLASS("Date");
892
+ rb_cDateTime = RUBY_CLASS("DateTime");
893
+ rb_cBigDecimal = RUBY_CLASS("BigDecimal");
894
+
895
+ // Get references to the Extlib module
896
+ mExtlib = CONST_GET(rb_mKernel, "Extlib");
897
+ rb_cByteArray = CONST_GET(mExtlib, "ByteArray");
898
+
899
+ // Get references to the DataObjects module and its classes
900
+ mDO = CONST_GET(rb_mKernel, "DataObjects");
901
+ cDO_Quoting = CONST_GET(mDO, "Quoting");
902
+ cDO_Connection = CONST_GET(mDO, "Connection");
903
+ cDO_Command = CONST_GET(mDO, "Command");
904
+ cDO_Result = CONST_GET(mDO, "Result");
905
+ cDO_Reader = CONST_GET(mDO, "Reader");
906
+
907
+ // Top Level Module that all the classes live under
908
+ mDOMysql = rb_define_module_under(mDO, "Mysql");
909
+
910
+ eArgumentError = CONST_GET(rb_mKernel, "ArgumentError");
911
+ eMysqlError = rb_define_class("MysqlError", rb_eStandardError);
912
+
913
+ cConnection = DRIVER_CLASS("Connection", cDO_Connection);
914
+ rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
915
+ rb_define_method(cConnection, "using_socket?", cConnection_is_using_socket, 0);
916
+ rb_define_method(cConnection, "character_set", cConnection_character_set , 0);
917
+ rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
918
+ rb_define_method(cConnection, "quote_string", cConnection_quote_string, 1);
919
+ rb_define_method(cConnection, "quote_date", cConnection_quote_date, 1);
920
+ rb_define_method(cConnection, "quote_time", cConnection_quote_time, 1);
921
+ rb_define_method(cConnection, "quote_datetime", cConnection_quote_date_time, 1);
922
+
923
+ cCommand = DRIVER_CLASS("Command", cDO_Command);
924
+ rb_define_method(cCommand, "set_types", cCommand_set_types, -1);
925
+ rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
926
+ rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
927
+
928
+ // Non-Query result
929
+ cResult = DRIVER_CLASS("Result", cDO_Result);
930
+
931
+ // Query result
932
+ cReader = DRIVER_CLASS("Reader", cDO_Reader);
933
+ rb_define_method(cReader, "close", cReader_close, 0);
934
+ rb_define_method(cReader, "next!", cReader_next, 0);
935
+ rb_define_method(cReader, "values", cReader_values, 0);
936
+ rb_define_method(cReader, "fields", cReader_fields, 0);
937
+ rb_define_method(cReader, "field_count", cReader_field_count, 0);
938
+ rb_define_method(cReader, "row_count", cReader_row_count, 0);
939
+ }