sbf-do_mysql 0.10.17
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|