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.
- checksums.yaml +7 -0
- data/ChangeLog.markdown +116 -0
- data/LICENSE +20 -0
- data/README.markdown +100 -0
- data/Rakefile +25 -0
- data/ext/do_mysql/compat.h +55 -0
- data/ext/do_mysql/do_common.c +510 -0
- data/ext/do_mysql/do_common.h +132 -0
- data/ext/do_mysql/do_mysql.c +691 -0
- data/ext/do_mysql/error.h +403 -0
- data/ext/do_mysql/extconf.rb +87 -0
- data/ext/do_mysql/mysql_compat.h +25 -0
- data/lib/do_mysql/encoding.rb +39 -0
- data/lib/do_mysql/transaction.rb +31 -0
- data/lib/do_mysql/version.rb +5 -0
- data/lib/do_mysql.rb +24 -0
- data/spec/command_spec.rb +7 -0
- data/spec/connection_spec.rb +55 -0
- data/spec/encoding_spec.rb +46 -0
- data/spec/error/sql_error_spec.rb +6 -0
- data/spec/reader_spec.rb +29 -0
- data/spec/result_spec.rb +38 -0
- data/spec/spec_helper.rb +242 -0
- data/spec/typecast/array_spec.rb +6 -0
- data/spec/typecast/bigdecimal_spec.rb +7 -0
- data/spec/typecast/boolean_spec.rb +7 -0
- data/spec/typecast/byte_array_spec.rb +6 -0
- data/spec/typecast/class_spec.rb +6 -0
- data/spec/typecast/date_spec.rb +30 -0
- data/spec/typecast/datetime_spec.rb +30 -0
- data/spec/typecast/float_spec.rb +7 -0
- data/spec/typecast/integer_spec.rb +6 -0
- data/spec/typecast/nil_spec.rb +8 -0
- data/spec/typecast/other_spec.rb +6 -0
- data/spec/typecast/range_spec.rb +6 -0
- data/spec/typecast/string_spec.rb +6 -0
- data/spec/typecast/time_spec.rb +6 -0
- data/tasks/compile.rake +16 -0
- data/tasks/release.rake +14 -0
- data/tasks/retrieve.rake +20 -0
- data/tasks/spec.rake +10 -0
- data/tasks/ssl.rake +26 -0
- 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
|
+
}
|