mysql2 0.3.8 → 0.4.10
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.md +1 -220
- data/LICENSE +21 -0
- data/README.md +370 -79
- data/examples/eventmachine.rb +1 -1
- data/examples/threaded.rb +4 -6
- data/ext/mysql2/client.c +1017 -305
- data/ext/mysql2/client.h +35 -11
- data/ext/mysql2/extconf.rb +222 -34
- data/ext/mysql2/infile.c +122 -0
- data/ext/mysql2/infile.h +1 -0
- data/ext/mysql2/mysql2_ext.c +1 -0
- data/ext/mysql2/mysql2_ext.h +12 -14
- data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
- data/ext/mysql2/mysql_enc_to_ruby.h +249 -0
- data/ext/mysql2/result.c +664 -166
- data/ext/mysql2/result.h +16 -6
- data/ext/mysql2/statement.c +595 -0
- data/ext/mysql2/statement.h +19 -0
- data/lib/mysql2/client.rb +118 -211
- data/lib/mysql2/console.rb +5 -0
- data/lib/mysql2/em.rb +23 -5
- data/lib/mysql2/error.rb +62 -6
- data/lib/mysql2/field.rb +3 -0
- data/lib/mysql2/statement.rb +17 -0
- data/lib/mysql2/version.rb +1 -1
- data/lib/mysql2.rb +66 -3
- data/spec/configuration.yml.example +11 -0
- data/spec/em/em_spec.rb +96 -10
- data/spec/my.cnf.example +9 -0
- data/spec/mysql2/client_spec.rb +779 -205
- data/spec/mysql2/error_spec.rb +58 -45
- data/spec/mysql2/result_spec.rb +316 -159
- data/spec/mysql2/statement_spec.rb +776 -0
- data/spec/spec_helper.rb +97 -56
- data/spec/ssl/ca-cert.pem +17 -0
- data/spec/ssl/ca-key.pem +27 -0
- data/spec/ssl/ca.cnf +22 -0
- data/spec/ssl/cert.cnf +22 -0
- data/spec/ssl/client-cert.pem +17 -0
- data/spec/ssl/client-key.pem +27 -0
- data/spec/ssl/client-req.pem +15 -0
- data/spec/ssl/gen_certs.sh +48 -0
- data/spec/ssl/pkcs8-client-key.pem +28 -0
- data/spec/ssl/pkcs8-server-key.pem +28 -0
- data/spec/ssl/server-cert.pem +17 -0
- data/spec/ssl/server-key.pem +27 -0
- data/spec/ssl/server-req.pem +15 -0
- data/spec/test_data +1 -0
- data/support/5072E1F5.asc +432 -0
- data/support/libmysql.def +219 -0
- data/support/mysql_enc_to_ruby.rb +81 -0
- data/support/ruby_enc_to_mysql.rb +61 -0
- metadata +77 -196
- data/.gitignore +0 -12
- data/.rspec +0 -3
- data/.rvmrc +0 -1
- data/.travis.yml +0 -7
- data/Gemfile +0 -3
- data/MIT-LICENSE +0 -20
- data/Rakefile +0 -5
- data/benchmark/active_record.rb +0 -51
- data/benchmark/active_record_threaded.rb +0 -42
- data/benchmark/allocations.rb +0 -33
- data/benchmark/escape.rb +0 -36
- data/benchmark/query_with_mysql_casting.rb +0 -80
- data/benchmark/query_without_mysql_casting.rb +0 -56
- data/benchmark/sequel.rb +0 -37
- data/benchmark/setup_db.rb +0 -119
- data/benchmark/threaded.rb +0 -44
- data/mysql2.gemspec +0 -29
- data/tasks/benchmarks.rake +0 -20
- data/tasks/compile.rake +0 -71
- data/tasks/rspec.rake +0 -16
- data/tasks/vendor_mysql.rake +0 -40
data/ext/mysql2/client.c
CHANGED
@@ -1,32 +1,79 @@
|
|
1
1
|
#include <mysql2_ext.h>
|
2
|
-
|
2
|
+
|
3
|
+
#include <time.h>
|
3
4
|
#include <errno.h>
|
4
5
|
#ifndef _WIN32
|
6
|
+
#include <sys/types.h>
|
5
7
|
#include <sys/socket.h>
|
6
8
|
#endif
|
9
|
+
#ifndef _MSC_VER
|
10
|
+
#include <unistd.h>
|
11
|
+
#endif
|
12
|
+
#include <fcntl.h>
|
7
13
|
#include "wait_for_single_fd.h"
|
8
14
|
|
15
|
+
#include "mysql_enc_name_to_ruby.h"
|
16
|
+
|
9
17
|
VALUE cMysql2Client;
|
10
18
|
extern VALUE mMysql2, cMysql2Error;
|
11
|
-
static VALUE
|
12
|
-
static
|
13
|
-
static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
|
19
|
+
static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
|
20
|
+
static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;
|
14
21
|
|
15
|
-
#
|
16
|
-
|
17
|
-
|
22
|
+
#ifndef HAVE_RB_HASH_DUP
|
23
|
+
VALUE rb_hash_dup(VALUE other) {
|
24
|
+
return rb_funcall(rb_cHash, intern_brackets, 1, other);
|
25
|
+
}
|
26
|
+
#endif
|
27
|
+
|
28
|
+
#define REQUIRE_INITIALIZED(wrapper) \
|
29
|
+
if (!wrapper->initialized) { \
|
30
|
+
rb_raise(cMysql2Error, "MySQL client is not initialized"); \
|
18
31
|
}
|
19
32
|
|
20
|
-
#
|
21
|
-
wrapper->
|
33
|
+
#if defined(HAVE_MYSQL_NET_VIO) || defined(HAVE_ST_NET_VIO)
|
34
|
+
#define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1)
|
35
|
+
#elif defined(HAVE_MYSQL_NET_PVIO) || defined(HAVE_ST_NET_PVIO)
|
36
|
+
#define CONNECTED(wrapper) (wrapper->client->net.pvio != NULL && wrapper->client->net.fd != -1)
|
37
|
+
#endif
|
22
38
|
|
23
|
-
#define
|
24
|
-
|
25
|
-
|
39
|
+
#define REQUIRE_CONNECTED(wrapper) \
|
40
|
+
REQUIRE_INITIALIZED(wrapper) \
|
41
|
+
if (!CONNECTED(wrapper) && !wrapper->reconnect_enabled) { \
|
42
|
+
rb_raise(cMysql2Error, "MySQL client is not connected"); \
|
43
|
+
}
|
44
|
+
|
45
|
+
#define REQUIRE_NOT_CONNECTED(wrapper) \
|
46
|
+
REQUIRE_INITIALIZED(wrapper) \
|
47
|
+
if (CONNECTED(wrapper)) { \
|
48
|
+
rb_raise(cMysql2Error, "MySQL connection is already open"); \
|
49
|
+
}
|
50
|
+
|
51
|
+
/*
|
52
|
+
* compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
|
53
|
+
* variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
|
54
|
+
* linking against the server itself
|
55
|
+
*/
|
56
|
+
#if defined(MARIADB_CLIENT_VERSION_STR)
|
57
|
+
#define MYSQL_LINK_VERSION MARIADB_CLIENT_VERSION_STR
|
58
|
+
#elif defined(LIBMYSQL_VERSION)
|
59
|
+
#define MYSQL_LINK_VERSION LIBMYSQL_VERSION
|
60
|
+
#else
|
61
|
+
#define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION
|
62
|
+
#endif
|
63
|
+
|
64
|
+
/*
|
65
|
+
* compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10.
|
66
|
+
*/
|
67
|
+
#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
|
68
|
+
#define SSL_MODE_DISABLED 1
|
69
|
+
#define SSL_MODE_REQUIRED 3
|
70
|
+
#define HAVE_CONST_SSL_MODE_DISABLED
|
71
|
+
#define HAVE_CONST_SSL_MODE_REQUIRED
|
72
|
+
#endif
|
26
73
|
|
27
74
|
/*
|
28
75
|
* used to pass all arguments to mysql_real_connect while inside
|
29
|
-
*
|
76
|
+
* rb_thread_call_without_gvl
|
30
77
|
*/
|
31
78
|
struct nogvl_connect_args {
|
32
79
|
MYSQL *mysql;
|
@@ -41,14 +88,61 @@ struct nogvl_connect_args {
|
|
41
88
|
|
42
89
|
/*
|
43
90
|
* used to pass all arguments to mysql_send_query while inside
|
44
|
-
*
|
91
|
+
* rb_thread_call_without_gvl
|
45
92
|
*/
|
46
93
|
struct nogvl_send_query_args {
|
47
94
|
MYSQL *mysql;
|
48
95
|
VALUE sql;
|
96
|
+
const char *sql_ptr;
|
97
|
+
long sql_len;
|
49
98
|
mysql_client_wrapper *wrapper;
|
50
99
|
};
|
51
100
|
|
101
|
+
/*
|
102
|
+
* used to pass all arguments to mysql_select_db while inside
|
103
|
+
* rb_thread_call_without_gvl
|
104
|
+
*/
|
105
|
+
struct nogvl_select_db_args {
|
106
|
+
MYSQL *mysql;
|
107
|
+
char *db;
|
108
|
+
};
|
109
|
+
|
110
|
+
static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
|
111
|
+
unsigned long version = mysql_get_client_version();
|
112
|
+
|
113
|
+
if (version < 50703) {
|
114
|
+
rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." );
|
115
|
+
return Qnil;
|
116
|
+
}
|
117
|
+
#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
|
118
|
+
GET_CLIENT(self);
|
119
|
+
int val = NUM2INT( setting );
|
120
|
+
if (version >= 50703 && version < 50711) {
|
121
|
+
if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
|
122
|
+
bool b = ( val == SSL_MODE_REQUIRED );
|
123
|
+
int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b );
|
124
|
+
return INT2NUM(result);
|
125
|
+
} else {
|
126
|
+
rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" );
|
127
|
+
return Qnil;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
#endif
|
131
|
+
#ifdef FULL_SSL_MODE_SUPPORT
|
132
|
+
GET_CLIENT(self);
|
133
|
+
int val = NUM2INT( setting );
|
134
|
+
|
135
|
+
if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) {
|
136
|
+
rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val );
|
137
|
+
}
|
138
|
+
int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val );
|
139
|
+
|
140
|
+
return INT2NUM(result);
|
141
|
+
#endif
|
142
|
+
#ifdef NO_SSL_MODE_SUPPORT
|
143
|
+
return Qnil;
|
144
|
+
#endif
|
145
|
+
}
|
52
146
|
/*
|
53
147
|
* non-blocking mysql_*() functions that we won't be wrapping since
|
54
148
|
* they do not appear to hit the network nor issue any interruptible
|
@@ -75,90 +169,150 @@ static void rb_mysql_client_mark(void * wrapper) {
|
|
75
169
|
mysql_client_wrapper * w = wrapper;
|
76
170
|
if (w) {
|
77
171
|
rb_gc_mark(w->encoding);
|
172
|
+
rb_gc_mark(w->active_thread);
|
78
173
|
}
|
79
174
|
}
|
80
175
|
|
81
176
|
static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
|
82
177
|
VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
|
83
178
|
VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
|
84
|
-
|
85
|
-
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
86
|
-
rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
|
179
|
+
VALUE e;
|
87
180
|
|
88
|
-
|
89
|
-
rb_enc_associate(
|
90
|
-
|
91
|
-
rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
|
92
|
-
rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
|
93
|
-
}
|
181
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
182
|
+
rb_enc_associate(rb_error_msg, rb_utf8_encoding());
|
183
|
+
rb_enc_associate(rb_sql_state, rb_usascii_encoding());
|
94
184
|
#endif
|
95
185
|
|
96
|
-
|
97
|
-
|
98
|
-
|
186
|
+
e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
|
187
|
+
rb_error_msg,
|
188
|
+
LONG2FIX(wrapper->server_version),
|
189
|
+
UINT2NUM(mysql_errno(wrapper->client)),
|
190
|
+
rb_sql_state);
|
99
191
|
rb_exc_raise(e);
|
100
|
-
return Qnil;
|
101
192
|
}
|
102
193
|
|
103
|
-
static
|
194
|
+
static void *nogvl_init(void *ptr) {
|
104
195
|
MYSQL *client;
|
196
|
+
mysql_client_wrapper *wrapper = ptr;
|
105
197
|
|
106
198
|
/* may initialize embedded server and read /etc/services off disk */
|
107
|
-
client = mysql_init(
|
108
|
-
|
199
|
+
client = mysql_init(wrapper->client);
|
200
|
+
|
201
|
+
if (client) mysql2_set_local_infile(client, wrapper);
|
202
|
+
|
203
|
+
return (void*)(client ? Qtrue : Qfalse);
|
109
204
|
}
|
110
205
|
|
111
|
-
static
|
206
|
+
static void *nogvl_connect(void *ptr) {
|
112
207
|
struct nogvl_connect_args *args = ptr;
|
113
208
|
MYSQL *client;
|
114
209
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
args->client_flag);
|
120
|
-
} while (! client && errno == EINTR && (errno = 0) == 0);
|
210
|
+
client = mysql_real_connect(args->mysql, args->host,
|
211
|
+
args->user, args->passwd,
|
212
|
+
args->db, args->port, args->unix_socket,
|
213
|
+
args->client_flag);
|
121
214
|
|
122
|
-
return client ? Qtrue : Qfalse;
|
215
|
+
return (void *)(client ? Qtrue : Qfalse);
|
123
216
|
}
|
124
217
|
|
125
|
-
static VALUE nogvl_close(void *ptr) {
|
126
|
-
mysql_client_wrapper *wrapper;
|
127
218
|
#ifndef _WIN32
|
128
|
-
|
219
|
+
/*
|
220
|
+
* Redirect clientfd to /dev/null for mysql_close and SSL_close to write,
|
221
|
+
* shutdown, and close. The hack is needed to prevent shutdown() from breaking
|
222
|
+
* a socket that may be in use by the parent or other processes after fork.
|
223
|
+
*
|
224
|
+
* /dev/null is used to absorb writes; previously a dummy socket was used, but
|
225
|
+
* it could not abosrb writes and caused openssl to go into an infinite loop.
|
226
|
+
*
|
227
|
+
* Returns Qtrue or Qfalse (success or failure)
|
228
|
+
*
|
229
|
+
* Note: if this function is needed on Windows, use "nul" instead of "/dev/null"
|
230
|
+
*/
|
231
|
+
static VALUE invalidate_fd(int clientfd)
|
232
|
+
{
|
233
|
+
#ifdef O_CLOEXEC
|
234
|
+
/* Atomically set CLOEXEC on the new FD in case another thread forks */
|
235
|
+
int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC);
|
236
|
+
#else
|
237
|
+
/* Well we don't have O_CLOEXEC, trigger the fallback code below */
|
238
|
+
int sockfd = -1;
|
129
239
|
#endif
|
130
|
-
|
131
|
-
if (
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
* we'll send a QUIT message to the server, but that message is more of a
|
136
|
-
* formality than a hard requirement since the socket is getting shutdown
|
137
|
-
* anyways, so ensure the socket write does not block our interpreter
|
138
|
-
*
|
139
|
-
*
|
140
|
-
* if the socket is dead we have no chance of blocking,
|
141
|
-
* so ignore any potential fcntl errors since they don't matter
|
240
|
+
|
241
|
+
if (sockfd < 0) {
|
242
|
+
/* Either O_CLOEXEC wasn't defined at compile time, or it was defined at
|
243
|
+
* compile time, but isn't available at run-time. So we'll just be quick
|
244
|
+
* about setting FD_CLOEXEC now.
|
142
245
|
*/
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
246
|
+
int flags;
|
247
|
+
sockfd = open("/dev/null", O_RDWR);
|
248
|
+
flags = fcntl(sockfd, F_GETFD);
|
249
|
+
/* Do the flags dance in case there are more defined flags in the future */
|
250
|
+
if (flags != -1) {
|
251
|
+
flags |= FD_CLOEXEC;
|
252
|
+
fcntl(sockfd, F_SETFD, flags);
|
253
|
+
}
|
254
|
+
}
|
148
255
|
|
256
|
+
if (sockfd < 0) {
|
257
|
+
/* Cannot raise here, because one or both of the following may be true:
|
258
|
+
* a) we have no GVL (in C Ruby)
|
259
|
+
* b) are running as a GC finalizer
|
260
|
+
*/
|
261
|
+
return Qfalse;
|
262
|
+
}
|
263
|
+
|
264
|
+
dup2(sockfd, clientfd);
|
265
|
+
close(sockfd);
|
266
|
+
|
267
|
+
return Qtrue;
|
268
|
+
}
|
269
|
+
#endif /* _WIN32 */
|
270
|
+
|
271
|
+
static void *nogvl_close(void *ptr) {
|
272
|
+
mysql_client_wrapper *wrapper = ptr;
|
273
|
+
|
274
|
+
if (!wrapper->closed) {
|
149
275
|
mysql_close(wrapper->client);
|
150
|
-
|
276
|
+
wrapper->closed = 1;
|
277
|
+
wrapper->reconnect_enabled = 0;
|
278
|
+
wrapper->active_thread = Qnil;
|
151
279
|
}
|
152
280
|
|
153
|
-
return
|
281
|
+
return NULL;
|
154
282
|
}
|
155
283
|
|
156
|
-
|
157
|
-
|
284
|
+
/* this is called during GC */
|
285
|
+
static void rb_mysql_client_free(void *ptr) {
|
286
|
+
mysql_client_wrapper *wrapper = ptr;
|
287
|
+
decr_mysql2_client(wrapper);
|
288
|
+
}
|
158
289
|
|
159
|
-
|
290
|
+
void decr_mysql2_client(mysql_client_wrapper *wrapper)
|
291
|
+
{
|
292
|
+
wrapper->refcount--;
|
160
293
|
|
161
|
-
|
294
|
+
if (wrapper->refcount == 0) {
|
295
|
+
#ifndef _WIN32
|
296
|
+
if (CONNECTED(wrapper) && !wrapper->automatic_close) {
|
297
|
+
/* The client is being garbage collected while connected. Prevent
|
298
|
+
* mysql_close() from sending a mysql-QUIT or from calling shutdown() on
|
299
|
+
* the socket by invalidating it. invalidate_fd() will drop this
|
300
|
+
* process's reference to the socket only, while a QUIT or shutdown()
|
301
|
+
* would render the underlying connection unusable, interrupting other
|
302
|
+
* processes which share this object across a fork().
|
303
|
+
*/
|
304
|
+
if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
|
305
|
+
fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
|
306
|
+
close(wrapper->client->net.fd);
|
307
|
+
}
|
308
|
+
wrapper->client->net.fd = -1;
|
309
|
+
}
|
310
|
+
#endif
|
311
|
+
|
312
|
+
nogvl_close(wrapper);
|
313
|
+
xfree(wrapper->client);
|
314
|
+
xfree(wrapper);
|
315
|
+
}
|
162
316
|
}
|
163
317
|
|
164
318
|
static VALUE allocate(VALUE klass) {
|
@@ -166,13 +320,26 @@ static VALUE allocate(VALUE klass) {
|
|
166
320
|
mysql_client_wrapper * wrapper;
|
167
321
|
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
|
168
322
|
wrapper->encoding = Qnil;
|
169
|
-
wrapper->
|
323
|
+
wrapper->active_thread = Qnil;
|
324
|
+
wrapper->automatic_close = 1;
|
325
|
+
wrapper->server_version = 0;
|
170
326
|
wrapper->reconnect_enabled = 0;
|
171
|
-
wrapper->
|
172
|
-
wrapper->
|
327
|
+
wrapper->connect_timeout = 0;
|
328
|
+
wrapper->initialized = 0; /* means that that the wrapper is initialized */
|
329
|
+
wrapper->refcount = 1;
|
330
|
+
wrapper->closed = 0;
|
331
|
+
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
|
332
|
+
|
173
333
|
return obj;
|
174
334
|
}
|
175
335
|
|
336
|
+
/* call-seq:
|
337
|
+
* Mysql2::Client.escape(string)
|
338
|
+
*
|
339
|
+
* Escape +string+ so that it may be used in a SQL statement.
|
340
|
+
* Note that this escape method is not connection encoding aware.
|
341
|
+
* If you need encoding support use Mysql2::Client#escape instead.
|
342
|
+
*/
|
176
343
|
static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
|
177
344
|
unsigned char *newStr;
|
178
345
|
VALUE rb_str;
|
@@ -181,83 +348,168 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
|
|
181
348
|
Check_Type(str, T_STRING);
|
182
349
|
|
183
350
|
oldLen = RSTRING_LEN(str);
|
184
|
-
newStr =
|
351
|
+
newStr = xmalloc(oldLen*2+1);
|
185
352
|
|
186
|
-
newLen = mysql_escape_string((char *)newStr,
|
353
|
+
newLen = mysql_escape_string((char *)newStr, RSTRING_PTR(str), oldLen);
|
187
354
|
if (newLen == oldLen) {
|
188
|
-
|
189
|
-
|
355
|
+
/* no need to return a new ruby string if nothing changed */
|
356
|
+
xfree(newStr);
|
190
357
|
return str;
|
191
358
|
} else {
|
192
359
|
rb_str = rb_str_new((const char*)newStr, newLen);
|
193
360
|
#ifdef HAVE_RUBY_ENCODING_H
|
194
361
|
rb_enc_copy(rb_str, str);
|
195
362
|
#endif
|
196
|
-
|
363
|
+
xfree(newStr);
|
197
364
|
return rb_str;
|
198
365
|
}
|
199
366
|
}
|
200
367
|
|
368
|
+
static VALUE rb_mysql_client_warning_count(VALUE self) {
|
369
|
+
unsigned int warning_count;
|
370
|
+
GET_CLIENT(self);
|
371
|
+
|
372
|
+
warning_count = mysql_warning_count(wrapper->client);
|
373
|
+
|
374
|
+
return UINT2NUM(warning_count);
|
375
|
+
}
|
376
|
+
|
377
|
+
static VALUE rb_mysql_info(VALUE self) {
|
378
|
+
const char *info;
|
379
|
+
VALUE rb_str;
|
380
|
+
GET_CLIENT(self);
|
381
|
+
|
382
|
+
info = mysql_info(wrapper->client);
|
383
|
+
|
384
|
+
if (info == NULL) {
|
385
|
+
return Qnil;
|
386
|
+
}
|
387
|
+
|
388
|
+
rb_str = rb_str_new2(info);
|
389
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
390
|
+
rb_enc_associate(rb_str, rb_utf8_encoding());
|
391
|
+
#endif
|
392
|
+
|
393
|
+
return rb_str;
|
394
|
+
}
|
395
|
+
|
396
|
+
static VALUE rb_mysql_get_ssl_cipher(VALUE self)
|
397
|
+
{
|
398
|
+
const char *cipher;
|
399
|
+
VALUE rb_str;
|
400
|
+
GET_CLIENT(self);
|
401
|
+
|
402
|
+
cipher = mysql_get_ssl_cipher(wrapper->client);
|
403
|
+
|
404
|
+
if (cipher == NULL) {
|
405
|
+
return Qnil;
|
406
|
+
}
|
407
|
+
|
408
|
+
rb_str = rb_str_new2(cipher);
|
409
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
410
|
+
rb_enc_associate(rb_str, rb_utf8_encoding());
|
411
|
+
#endif
|
412
|
+
|
413
|
+
return rb_str;
|
414
|
+
}
|
415
|
+
|
201
416
|
static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
|
202
417
|
struct nogvl_connect_args args;
|
418
|
+
time_t start_time, end_time, elapsed_time, connect_timeout;
|
419
|
+
VALUE rv;
|
203
420
|
GET_CLIENT(self);
|
204
421
|
|
205
|
-
args.host
|
206
|
-
args.unix_socket = NIL_P(socket)
|
207
|
-
args.port
|
208
|
-
args.user
|
209
|
-
args.passwd
|
210
|
-
args.db
|
211
|
-
args.mysql
|
422
|
+
args.host = NIL_P(host) ? NULL : StringValueCStr(host);
|
423
|
+
args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket);
|
424
|
+
args.port = NIL_P(port) ? 0 : NUM2INT(port);
|
425
|
+
args.user = NIL_P(user) ? NULL : StringValueCStr(user);
|
426
|
+
args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass);
|
427
|
+
args.db = NIL_P(database) ? NULL : StringValueCStr(database);
|
428
|
+
args.mysql = wrapper->client;
|
212
429
|
args.client_flag = NUM2ULONG(flags);
|
213
430
|
|
214
|
-
if (
|
215
|
-
|
216
|
-
|
431
|
+
if (wrapper->connect_timeout)
|
432
|
+
time(&start_time);
|
433
|
+
rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
|
434
|
+
if (rv == Qfalse) {
|
435
|
+
while (rv == Qfalse && errno == EINTR) {
|
436
|
+
if (wrapper->connect_timeout) {
|
437
|
+
time(&end_time);
|
438
|
+
/* avoid long connect timeout from system time changes */
|
439
|
+
if (end_time < start_time)
|
440
|
+
start_time = end_time;
|
441
|
+
elapsed_time = end_time - start_time;
|
442
|
+
/* avoid an early timeout due to time truncating milliseconds off the start time */
|
443
|
+
if (elapsed_time > 0)
|
444
|
+
elapsed_time--;
|
445
|
+
if (elapsed_time >= (time_t)wrapper->connect_timeout)
|
446
|
+
break;
|
447
|
+
connect_timeout = wrapper->connect_timeout - elapsed_time;
|
448
|
+
mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
|
449
|
+
}
|
450
|
+
errno = 0;
|
451
|
+
rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
|
452
|
+
}
|
453
|
+
/* restore the connect timeout for reconnecting */
|
454
|
+
if (wrapper->connect_timeout)
|
455
|
+
mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
|
456
|
+
if (rv == Qfalse)
|
457
|
+
rb_raise_mysql2_error(wrapper);
|
217
458
|
}
|
218
459
|
|
460
|
+
wrapper->server_version = mysql_get_server_version(wrapper->client);
|
219
461
|
return self;
|
220
462
|
}
|
221
463
|
|
222
464
|
/*
|
223
|
-
* Immediately disconnect from the server
|
465
|
+
* Immediately disconnect from the server; normally the garbage collector
|
224
466
|
* will disconnect automatically when a connection is no longer needed.
|
225
467
|
* Explicitly closing this will free up server resources sooner than waiting
|
226
468
|
* for the garbage collector.
|
469
|
+
*
|
470
|
+
* @return [nil]
|
227
471
|
*/
|
228
472
|
static VALUE rb_mysql_client_close(VALUE self) {
|
229
473
|
GET_CLIENT(self);
|
230
474
|
|
231
|
-
if (
|
232
|
-
|
475
|
+
if (wrapper->client) {
|
476
|
+
rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
|
233
477
|
}
|
234
478
|
|
235
479
|
return Qnil;
|
236
480
|
}
|
237
481
|
|
482
|
+
/* call-seq:
|
483
|
+
* client.closed?
|
484
|
+
*
|
485
|
+
* @return [Boolean]
|
486
|
+
*/
|
487
|
+
static VALUE rb_mysql_client_closed(VALUE self) {
|
488
|
+
GET_CLIENT(self);
|
489
|
+
return CONNECTED(wrapper) ? Qfalse : Qtrue;
|
490
|
+
}
|
491
|
+
|
238
492
|
/*
|
239
493
|
* mysql_send_query is unlikely to block since most queries are small
|
240
494
|
* enough to fit in a socket buffer, but sometimes large UPDATE and
|
241
495
|
* INSERTs will cause the process to block
|
242
496
|
*/
|
243
|
-
static
|
497
|
+
static void *nogvl_send_query(void *ptr) {
|
244
498
|
struct nogvl_send_query_args *args = ptr;
|
245
499
|
int rv;
|
246
|
-
const char *sql = StringValuePtr(args->sql);
|
247
|
-
long sql_len = RSTRING_LEN(args->sql);
|
248
500
|
|
249
|
-
rv = mysql_send_query(args->mysql,
|
501
|
+
rv = mysql_send_query(args->mysql, args->sql_ptr, args->sql_len);
|
250
502
|
|
251
|
-
return rv == 0 ? Qtrue : Qfalse;
|
503
|
+
return (void*)(rv == 0 ? Qtrue : Qfalse);
|
252
504
|
}
|
253
505
|
|
254
506
|
static VALUE do_send_query(void *args) {
|
255
507
|
struct nogvl_send_query_args *query_args = args;
|
256
508
|
mysql_client_wrapper *wrapper = query_args->wrapper;
|
257
|
-
if (
|
258
|
-
|
259
|
-
|
260
|
-
|
509
|
+
if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
|
510
|
+
/* an error occurred, we're not active anymore */
|
511
|
+
wrapper->active_thread = Qnil;
|
512
|
+
rb_raise_mysql2_error(wrapper);
|
261
513
|
}
|
262
514
|
return Qnil;
|
263
515
|
}
|
@@ -267,64 +519,82 @@ static VALUE do_send_query(void *args) {
|
|
267
519
|
* response can overflow the socket buffers and cause us to eventually
|
268
520
|
* block while calling mysql_read_query_result
|
269
521
|
*/
|
270
|
-
static
|
522
|
+
static void *nogvl_read_query_result(void *ptr) {
|
271
523
|
MYSQL * client = ptr;
|
272
|
-
|
524
|
+
bool res = mysql_read_query_result(client);
|
273
525
|
|
274
|
-
return res == 0 ? Qtrue : Qfalse;
|
526
|
+
return (void *)(res == 0 ? Qtrue : Qfalse);
|
275
527
|
}
|
276
528
|
|
277
|
-
|
278
|
-
|
279
|
-
mysql_client_wrapper *wrapper;
|
529
|
+
static void *nogvl_do_result(void *ptr, char use_result) {
|
530
|
+
mysql_client_wrapper *wrapper = ptr;
|
280
531
|
MYSQL_RES *result;
|
281
532
|
|
282
|
-
|
283
|
-
|
533
|
+
if (use_result) {
|
534
|
+
result = mysql_use_result(wrapper->client);
|
535
|
+
} else {
|
536
|
+
result = mysql_store_result(wrapper->client);
|
537
|
+
}
|
538
|
+
|
539
|
+
/* once our result is stored off, this connection is
|
540
|
+
ready for another command to be issued */
|
541
|
+
wrapper->active_thread = Qnil;
|
284
542
|
|
285
|
-
|
286
|
-
|
287
|
-
|
543
|
+
return result;
|
544
|
+
}
|
545
|
+
|
546
|
+
/* mysql_store_result may (unlikely) read rows off the socket */
|
547
|
+
static void *nogvl_store_result(void *ptr) {
|
548
|
+
return nogvl_do_result(ptr, 0);
|
549
|
+
}
|
288
550
|
|
289
|
-
|
551
|
+
static void *nogvl_use_result(void *ptr) {
|
552
|
+
return nogvl_do_result(ptr, 1);
|
290
553
|
}
|
291
554
|
|
555
|
+
/* call-seq:
|
556
|
+
* client.async_result
|
557
|
+
*
|
558
|
+
* Returns the result for the last async issued query.
|
559
|
+
*/
|
292
560
|
static VALUE rb_mysql_client_async_result(VALUE self) {
|
293
561
|
MYSQL_RES * result;
|
294
562
|
VALUE resultObj;
|
295
|
-
|
296
|
-
mysql2_result_wrapper * result_wrapper;
|
297
|
-
#endif
|
563
|
+
VALUE current, is_streaming;
|
298
564
|
GET_CLIENT(self);
|
299
565
|
|
300
|
-
|
301
|
-
if (
|
566
|
+
/* if we're not waiting on a result, do nothing */
|
567
|
+
if (NIL_P(wrapper->active_thread))
|
302
568
|
return Qnil;
|
303
569
|
|
304
|
-
|
305
|
-
if (
|
306
|
-
|
307
|
-
|
308
|
-
|
570
|
+
REQUIRE_CONNECTED(wrapper);
|
571
|
+
if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
|
572
|
+
/* an error occurred, mark this connection inactive */
|
573
|
+
wrapper->active_thread = Qnil;
|
574
|
+
rb_raise_mysql2_error(wrapper);
|
309
575
|
}
|
310
576
|
|
311
|
-
|
577
|
+
is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
|
578
|
+
if (is_streaming == Qtrue) {
|
579
|
+
result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0);
|
580
|
+
} else {
|
581
|
+
result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
|
582
|
+
}
|
312
583
|
|
313
584
|
if (result == NULL) {
|
314
|
-
if (
|
585
|
+
if (mysql_errno(wrapper->client) != 0) {
|
586
|
+
wrapper->active_thread = Qnil;
|
315
587
|
rb_raise_mysql2_error(wrapper);
|
316
588
|
}
|
589
|
+
/* no data and no error, so query was not a SELECT */
|
317
590
|
return Qnil;
|
318
591
|
}
|
319
592
|
|
320
|
-
|
321
|
-
|
322
|
-
|
593
|
+
current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
|
594
|
+
(void)RB_GC_GUARD(current);
|
595
|
+
Check_Type(current, T_HASH);
|
596
|
+
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
|
323
597
|
|
324
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
325
|
-
GetMysql2Result(resultObj, result_wrapper);
|
326
|
-
result_wrapper->encoding = wrapper->encoding;
|
327
|
-
#endif
|
328
598
|
return resultObj;
|
329
599
|
}
|
330
600
|
|
@@ -337,27 +607,30 @@ struct async_query_args {
|
|
337
607
|
static VALUE disconnect_and_raise(VALUE self, VALUE error) {
|
338
608
|
GET_CLIENT(self);
|
339
609
|
|
340
|
-
wrapper->
|
341
|
-
wrapper->active = 0;
|
610
|
+
wrapper->active_thread = Qnil;
|
342
611
|
|
343
|
-
|
344
|
-
|
345
|
-
|
612
|
+
/* Invalidate the MySQL socket to prevent further communication.
|
613
|
+
* The GC will come along later and call mysql_close to free it.
|
614
|
+
*/
|
615
|
+
if (CONNECTED(wrapper)) {
|
616
|
+
if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
|
617
|
+
fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n");
|
618
|
+
close(wrapper->client->net.fd);
|
619
|
+
}
|
620
|
+
wrapper->client->net.fd = -1;
|
621
|
+
}
|
346
622
|
|
347
623
|
rb_exc_raise(error);
|
348
|
-
|
349
|
-
return Qnil;
|
350
624
|
}
|
351
625
|
|
352
626
|
static VALUE do_query(void *args) {
|
353
|
-
struct async_query_args *async_args;
|
627
|
+
struct async_query_args *async_args = args;
|
354
628
|
struct timeval tv;
|
355
|
-
struct timeval*
|
629
|
+
struct timeval *tvp;
|
356
630
|
long int sec;
|
357
631
|
int retval;
|
358
632
|
VALUE read_timeout;
|
359
633
|
|
360
|
-
async_args = (struct async_query_args *)args;
|
361
634
|
read_timeout = rb_iv_get(async_args->self, "@read_timeout");
|
362
635
|
|
363
636
|
tvp = NULL;
|
@@ -365,8 +638,8 @@ static VALUE do_query(void *args) {
|
|
365
638
|
Check_Type(read_timeout, T_FIXNUM);
|
366
639
|
tvp = &tv;
|
367
640
|
sec = FIX2INT(read_timeout);
|
368
|
-
|
369
|
-
|
641
|
+
/* TODO: support partial seconds?
|
642
|
+
also, this check is here for sanity, we also check up in Ruby */
|
370
643
|
if (sec >= 0) {
|
371
644
|
tvp->tv_sec = sec;
|
372
645
|
} else {
|
@@ -393,92 +666,140 @@ static VALUE do_query(void *args) {
|
|
393
666
|
|
394
667
|
return Qnil;
|
395
668
|
}
|
669
|
+
#endif
|
670
|
+
|
671
|
+
static VALUE disconnect_and_mark_inactive(VALUE self) {
|
672
|
+
GET_CLIENT(self);
|
673
|
+
|
674
|
+
/* Check if execution terminated while result was still being read. */
|
675
|
+
if (!NIL_P(wrapper->active_thread)) {
|
676
|
+
if (CONNECTED(wrapper)) {
|
677
|
+
/* Invalidate the MySQL socket to prevent further communication. */
|
678
|
+
#ifndef _WIN32
|
679
|
+
if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
|
680
|
+
rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n");
|
681
|
+
close(wrapper->client->net.fd);
|
682
|
+
}
|
396
683
|
#else
|
397
|
-
|
398
|
-
|
399
|
-
|
684
|
+
close(wrapper->client->net.fd);
|
685
|
+
#endif
|
686
|
+
wrapper->client->net.fd = -1;
|
687
|
+
}
|
688
|
+
/* Skip mysql client check performed before command execution. */
|
689
|
+
wrapper->client->status = MYSQL_STATUS_READY;
|
690
|
+
wrapper->active_thread = Qnil;
|
691
|
+
}
|
400
692
|
|
401
|
-
|
693
|
+
return Qnil;
|
694
|
+
}
|
402
695
|
|
696
|
+
void rb_mysql_client_set_active_thread(VALUE self) {
|
697
|
+
VALUE thread_current = rb_thread_current();
|
403
698
|
GET_CLIENT(self);
|
404
699
|
|
405
|
-
if
|
406
|
-
|
407
|
-
//
|
408
|
-
|
409
|
-
|
410
|
-
|
700
|
+
// see if this connection is still waiting on a result from a previous query
|
701
|
+
if (NIL_P(wrapper->active_thread)) {
|
702
|
+
// mark this connection active
|
703
|
+
wrapper->active_thread = thread_current;
|
704
|
+
} else if (wrapper->active_thread == thread_current) {
|
705
|
+
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
|
706
|
+
} else {
|
707
|
+
VALUE inspect = rb_inspect(wrapper->active_thread);
|
708
|
+
const char *thr = StringValueCStr(inspect);
|
411
709
|
|
412
|
-
|
710
|
+
rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
|
711
|
+
}
|
712
|
+
}
|
713
|
+
|
714
|
+
/* call-seq:
|
715
|
+
* client.abandon_results!
|
716
|
+
*
|
717
|
+
* When using MULTI_STATEMENTS support, calling this will throw
|
718
|
+
* away any unprocessed results as fast as it can in order to
|
719
|
+
* put the connection back into a state where queries can be issued
|
720
|
+
* again.
|
721
|
+
*/
|
722
|
+
static VALUE rb_mysql_client_abandon_results(VALUE self) {
|
723
|
+
MYSQL_RES *result;
|
724
|
+
int ret;
|
725
|
+
|
726
|
+
GET_CLIENT(self);
|
727
|
+
|
728
|
+
while (mysql_more_results(wrapper->client) == 1) {
|
729
|
+
ret = mysql_next_result(wrapper->client);
|
730
|
+
if (ret > 0) {
|
731
|
+
rb_raise_mysql2_error(wrapper);
|
732
|
+
}
|
733
|
+
|
734
|
+
result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
|
735
|
+
|
736
|
+
if (result != NULL) {
|
737
|
+
mysql_free_result(result);
|
738
|
+
}
|
413
739
|
}
|
414
740
|
|
415
741
|
return Qnil;
|
416
742
|
}
|
417
|
-
#endif
|
418
743
|
|
419
|
-
|
744
|
+
/* call-seq:
|
745
|
+
* client.query(sql, options = {})
|
746
|
+
*
|
747
|
+
* Query the database with +sql+, with optional +options+. For the possible
|
748
|
+
* options, see default_query_options on the Mysql2::Client class.
|
749
|
+
*/
|
750
|
+
static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
|
420
751
|
#ifndef _WIN32
|
421
752
|
struct async_query_args async_args;
|
422
753
|
#endif
|
423
754
|
struct nogvl_send_query_args args;
|
424
|
-
int async = 0;
|
425
|
-
VALUE opts, defaults;
|
426
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
427
|
-
rb_encoding *conn_enc;
|
428
|
-
#endif
|
429
755
|
GET_CLIENT(self);
|
430
756
|
|
431
|
-
|
757
|
+
REQUIRE_CONNECTED(wrapper);
|
432
758
|
args.mysql = wrapper->client;
|
433
759
|
|
760
|
+
(void)RB_GC_GUARD(current);
|
761
|
+
Check_Type(current, T_HASH);
|
762
|
+
rb_iv_set(self, "@current_query_options", current);
|
434
763
|
|
435
|
-
|
436
|
-
if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
|
437
|
-
opts = rb_funcall(defaults, intern_merge, 1, opts);
|
438
|
-
rb_iv_set(self, "@query_options", opts);
|
439
|
-
|
440
|
-
if (rb_hash_aref(opts, sym_async) == Qtrue) {
|
441
|
-
async = 1;
|
442
|
-
}
|
443
|
-
} else {
|
444
|
-
opts = defaults;
|
445
|
-
}
|
446
|
-
|
447
|
-
Check_Type(args.sql, T_STRING);
|
764
|
+
Check_Type(sql, T_STRING);
|
448
765
|
#ifdef HAVE_RUBY_ENCODING_H
|
449
|
-
|
450
|
-
|
451
|
-
|
766
|
+
/* ensure the string is in the encoding the connection is expecting */
|
767
|
+
args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
|
768
|
+
#else
|
769
|
+
args.sql = sql;
|
452
770
|
#endif
|
771
|
+
args.sql_ptr = RSTRING_PTR(args.sql);
|
772
|
+
args.sql_len = RSTRING_LEN(args.sql);
|
773
|
+
args.wrapper = wrapper;
|
453
774
|
|
454
|
-
|
455
|
-
if (wrapper->active == 0) {
|
456
|
-
// mark this connection active
|
457
|
-
wrapper->active = 1;
|
458
|
-
} else {
|
459
|
-
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
|
460
|
-
}
|
775
|
+
rb_mysql_client_set_active_thread(self);
|
461
776
|
|
462
|
-
|
777
|
+
#ifndef _WIN32
|
463
778
|
rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
|
464
779
|
|
465
|
-
|
466
|
-
|
780
|
+
if (rb_hash_aref(current, sym_async) == Qtrue) {
|
781
|
+
return Qnil;
|
782
|
+
} else {
|
467
783
|
async_args.fd = wrapper->client->net.fd;
|
468
784
|
async_args.self = self;
|
469
785
|
|
470
786
|
rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
|
471
787
|
|
472
|
-
return rb_mysql_client_async_result
|
473
|
-
} else {
|
474
|
-
return Qnil;
|
788
|
+
return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
|
475
789
|
}
|
476
790
|
#else
|
477
|
-
|
478
|
-
|
791
|
+
do_send_query(&args);
|
792
|
+
|
793
|
+
/* this will just block until the result is ready */
|
794
|
+
return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
|
479
795
|
#endif
|
480
796
|
}
|
481
797
|
|
798
|
+
/* call-seq:
|
799
|
+
* client.escape(string)
|
800
|
+
*
|
801
|
+
* Escape +string+ so that it may be used in a SQL statement.
|
802
|
+
*/
|
482
803
|
static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
|
483
804
|
unsigned char *newStr;
|
484
805
|
VALUE rb_str;
|
@@ -489,22 +810,27 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
|
|
489
810
|
#endif
|
490
811
|
GET_CLIENT(self);
|
491
812
|
|
492
|
-
|
813
|
+
REQUIRE_CONNECTED(wrapper);
|
493
814
|
Check_Type(str, T_STRING);
|
494
815
|
#ifdef HAVE_RUBY_ENCODING_H
|
495
816
|
default_internal_enc = rb_default_internal_encoding();
|
496
817
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
497
|
-
|
818
|
+
/* ensure the string is in the encoding the connection is expecting */
|
498
819
|
str = rb_str_export_to_enc(str, conn_enc);
|
499
820
|
#endif
|
500
821
|
|
501
822
|
oldLen = RSTRING_LEN(str);
|
502
|
-
newStr =
|
823
|
+
newStr = xmalloc(oldLen*2+1);
|
503
824
|
|
504
|
-
newLen = mysql_real_escape_string(wrapper->client, (char *)newStr,
|
825
|
+
newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen);
|
505
826
|
if (newLen == oldLen) {
|
506
|
-
|
507
|
-
|
827
|
+
/* no need to return a new ruby string if nothing changed */
|
828
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
829
|
+
if (default_internal_enc) {
|
830
|
+
str = rb_str_export_to_enc(str, default_internal_enc);
|
831
|
+
}
|
832
|
+
#endif
|
833
|
+
xfree(newStr);
|
508
834
|
return str;
|
509
835
|
} else {
|
510
836
|
rb_str = rb_str_new((const char*)newStr, newLen);
|
@@ -514,37 +840,133 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
|
|
514
840
|
rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
|
515
841
|
}
|
516
842
|
#endif
|
517
|
-
|
843
|
+
xfree(newStr);
|
518
844
|
return rb_str;
|
519
845
|
}
|
520
846
|
}
|
521
847
|
|
522
|
-
static VALUE
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
848
|
+
static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
|
849
|
+
int result;
|
850
|
+
const void *retval = NULL;
|
851
|
+
unsigned int intval = 0;
|
852
|
+
const char * charval = NULL;
|
853
|
+
bool boolval;
|
854
|
+
|
528
855
|
GET_CLIENT(self);
|
529
|
-
version = rb_hash_new();
|
530
856
|
|
531
|
-
|
532
|
-
|
533
|
-
|
857
|
+
REQUIRE_NOT_CONNECTED(wrapper);
|
858
|
+
|
859
|
+
if (NIL_P(value))
|
860
|
+
return Qfalse;
|
861
|
+
|
862
|
+
switch(opt) {
|
863
|
+
case MYSQL_OPT_CONNECT_TIMEOUT:
|
864
|
+
intval = NUM2UINT(value);
|
865
|
+
retval = &intval;
|
866
|
+
break;
|
867
|
+
|
868
|
+
case MYSQL_OPT_READ_TIMEOUT:
|
869
|
+
intval = NUM2UINT(value);
|
870
|
+
retval = &intval;
|
871
|
+
break;
|
872
|
+
|
873
|
+
case MYSQL_OPT_WRITE_TIMEOUT:
|
874
|
+
intval = NUM2UINT(value);
|
875
|
+
retval = &intval;
|
876
|
+
break;
|
877
|
+
|
878
|
+
case MYSQL_OPT_LOCAL_INFILE:
|
879
|
+
intval = (value == Qfalse ? 0 : 1);
|
880
|
+
retval = &intval;
|
881
|
+
break;
|
882
|
+
|
883
|
+
case MYSQL_OPT_RECONNECT:
|
884
|
+
boolval = (value == Qfalse ? 0 : 1);
|
885
|
+
retval = &boolval;
|
886
|
+
break;
|
887
|
+
|
888
|
+
#ifdef MYSQL_SECURE_AUTH
|
889
|
+
case MYSQL_SECURE_AUTH:
|
890
|
+
boolval = (value == Qfalse ? 0 : 1);
|
891
|
+
retval = &boolval;
|
892
|
+
break;
|
534
893
|
#endif
|
535
894
|
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
895
|
+
case MYSQL_READ_DEFAULT_FILE:
|
896
|
+
charval = (const char *)StringValueCStr(value);
|
897
|
+
retval = charval;
|
898
|
+
break;
|
899
|
+
|
900
|
+
case MYSQL_READ_DEFAULT_GROUP:
|
901
|
+
charval = (const char *)StringValueCStr(value);
|
902
|
+
retval = charval;
|
903
|
+
break;
|
904
|
+
|
905
|
+
case MYSQL_INIT_COMMAND:
|
906
|
+
charval = (const char *)StringValueCStr(value);
|
907
|
+
retval = charval;
|
908
|
+
break;
|
909
|
+
|
910
|
+
#ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
|
911
|
+
case MYSQL_ENABLE_CLEARTEXT_PLUGIN:
|
912
|
+
boolval = (value == Qfalse ? 0 : 1);
|
913
|
+
retval = &boolval;
|
914
|
+
break;
|
915
|
+
#endif
|
916
|
+
|
917
|
+
default:
|
918
|
+
return Qfalse;
|
919
|
+
}
|
920
|
+
|
921
|
+
result = mysql_options(wrapper->client, opt, retval);
|
922
|
+
|
923
|
+
/* Zero means success */
|
924
|
+
if (result != 0) {
|
925
|
+
rb_warn("%s\n", mysql_error(wrapper->client));
|
926
|
+
} else {
|
927
|
+
/* Special case for options that are stored in the wrapper struct */
|
928
|
+
switch (opt) {
|
929
|
+
case MYSQL_OPT_RECONNECT:
|
930
|
+
wrapper->reconnect_enabled = boolval;
|
931
|
+
break;
|
932
|
+
case MYSQL_OPT_CONNECT_TIMEOUT:
|
933
|
+
wrapper->connect_timeout = intval;
|
934
|
+
break;
|
935
|
+
}
|
542
936
|
}
|
937
|
+
|
938
|
+
return (result == 0) ? Qtrue : Qfalse;
|
939
|
+
}
|
940
|
+
|
941
|
+
/* call-seq:
|
942
|
+
* client.info
|
943
|
+
*
|
944
|
+
* Returns a string that represents the client library version.
|
945
|
+
*/
|
946
|
+
static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
|
947
|
+
VALUE version_info, version, header_version;
|
948
|
+
version_info = rb_hash_new();
|
949
|
+
|
950
|
+
version = rb_str_new2(mysql_get_client_info());
|
951
|
+
header_version = rb_str_new2(MYSQL_LINK_VERSION);
|
952
|
+
|
953
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
954
|
+
rb_enc_associate(version, rb_usascii_encoding());
|
955
|
+
rb_enc_associate(header_version, rb_usascii_encoding());
|
543
956
|
#endif
|
544
|
-
|
545
|
-
|
957
|
+
|
958
|
+
rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version()));
|
959
|
+
rb_hash_aset(version_info, sym_version, version);
|
960
|
+
rb_hash_aset(version_info, sym_header_version, header_version);
|
961
|
+
|
962
|
+
return version_info;
|
546
963
|
}
|
547
964
|
|
965
|
+
/* call-seq:
|
966
|
+
* client.server_info
|
967
|
+
*
|
968
|
+
* Returns a string that represents the server version number
|
969
|
+
*/
|
548
970
|
static VALUE rb_mysql_client_server_info(VALUE self) {
|
549
971
|
VALUE version, server_info;
|
550
972
|
#ifdef HAVE_RUBY_ENCODING_H
|
@@ -553,7 +975,7 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
|
|
553
975
|
#endif
|
554
976
|
GET_CLIENT(self);
|
555
977
|
|
556
|
-
|
978
|
+
REQUIRE_CONNECTED(wrapper);
|
557
979
|
#ifdef HAVE_RUBY_ENCODING_H
|
558
980
|
default_internal_enc = rb_default_internal_encoding();
|
559
981
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
@@ -572,28 +994,46 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
|
|
572
994
|
return version;
|
573
995
|
}
|
574
996
|
|
997
|
+
/* call-seq:
|
998
|
+
* client.socket
|
999
|
+
*
|
1000
|
+
* Return the file descriptor number for this client.
|
1001
|
+
*/
|
1002
|
+
#ifndef _WIN32
|
575
1003
|
static VALUE rb_mysql_client_socket(VALUE self) {
|
576
1004
|
GET_CLIENT(self);
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
return INT2NUM(fd_set_fd);
|
1005
|
+
REQUIRE_CONNECTED(wrapper);
|
1006
|
+
return INT2NUM(wrapper->client->net.fd);
|
1007
|
+
}
|
581
1008
|
#else
|
1009
|
+
static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
|
582
1010
|
rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
|
583
|
-
#endif
|
584
1011
|
}
|
1012
|
+
#endif
|
585
1013
|
|
1014
|
+
/* call-seq:
|
1015
|
+
* client.last_id
|
1016
|
+
*
|
1017
|
+
* Returns the value generated for an AUTO_INCREMENT column by the previous INSERT or UPDATE
|
1018
|
+
* statement.
|
1019
|
+
*/
|
586
1020
|
static VALUE rb_mysql_client_last_id(VALUE self) {
|
587
1021
|
GET_CLIENT(self);
|
588
|
-
|
1022
|
+
REQUIRE_CONNECTED(wrapper);
|
589
1023
|
return ULL2NUM(mysql_insert_id(wrapper->client));
|
590
1024
|
}
|
591
1025
|
|
1026
|
+
/* call-seq:
|
1027
|
+
* client.affected_rows
|
1028
|
+
*
|
1029
|
+
* returns the number of rows changed, deleted, or inserted by the last statement
|
1030
|
+
* if it was an UPDATE, DELETE, or INSERT.
|
1031
|
+
*/
|
592
1032
|
static VALUE rb_mysql_client_affected_rows(VALUE self) {
|
593
1033
|
my_ulonglong retVal;
|
594
1034
|
GET_CLIENT(self);
|
595
1035
|
|
596
|
-
|
1036
|
+
REQUIRE_CONNECTED(wrapper);
|
597
1037
|
retVal = mysql_affected_rows(wrapper->client);
|
598
1038
|
if (retVal == (my_ulonglong)-1) {
|
599
1039
|
rb_raise_mysql2_error(wrapper);
|
@@ -601,93 +1041,259 @@ static VALUE rb_mysql_client_affected_rows(VALUE self) {
|
|
601
1041
|
return ULL2NUM(retVal);
|
602
1042
|
}
|
603
1043
|
|
1044
|
+
/* call-seq:
|
1045
|
+
* client.thread_id
|
1046
|
+
*
|
1047
|
+
* Returns the thread ID of the current connection.
|
1048
|
+
*/
|
604
1049
|
static VALUE rb_mysql_client_thread_id(VALUE self) {
|
605
1050
|
unsigned long retVal;
|
606
1051
|
GET_CLIENT(self);
|
607
1052
|
|
608
|
-
|
1053
|
+
REQUIRE_CONNECTED(wrapper);
|
609
1054
|
retVal = mysql_thread_id(wrapper->client);
|
610
1055
|
return ULL2NUM(retVal);
|
611
1056
|
}
|
612
1057
|
|
613
|
-
static
|
1058
|
+
static void *nogvl_select_db(void *ptr) {
|
1059
|
+
struct nogvl_select_db_args *args = ptr;
|
1060
|
+
|
1061
|
+
if (mysql_select_db(args->mysql, args->db) == 0)
|
1062
|
+
return (void *)Qtrue;
|
1063
|
+
else
|
1064
|
+
return (void *)Qfalse;
|
1065
|
+
}
|
1066
|
+
|
1067
|
+
/* call-seq:
|
1068
|
+
* client.select_db(name)
|
1069
|
+
*
|
1070
|
+
* Causes the database specified by +name+ to become the default (current)
|
1071
|
+
* database on the connection specified by mysql.
|
1072
|
+
*/
|
1073
|
+
static VALUE rb_mysql_client_select_db(VALUE self, VALUE db)
|
1074
|
+
{
|
1075
|
+
struct nogvl_select_db_args args;
|
1076
|
+
|
1077
|
+
GET_CLIENT(self);
|
1078
|
+
REQUIRE_CONNECTED(wrapper);
|
1079
|
+
|
1080
|
+
args.mysql = wrapper->client;
|
1081
|
+
args.db = StringValueCStr(db);
|
1082
|
+
|
1083
|
+
if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse)
|
1084
|
+
rb_raise_mysql2_error(wrapper);
|
1085
|
+
|
1086
|
+
return db;
|
1087
|
+
}
|
1088
|
+
|
1089
|
+
static void *nogvl_ping(void *ptr) {
|
614
1090
|
MYSQL *client = ptr;
|
615
1091
|
|
616
|
-
return mysql_ping(client) == 0 ? Qtrue : Qfalse;
|
1092
|
+
return (void *)(mysql_ping(client) == 0 ? Qtrue : Qfalse);
|
617
1093
|
}
|
618
1094
|
|
1095
|
+
/* call-seq:
|
1096
|
+
* client.ping
|
1097
|
+
*
|
1098
|
+
* Checks whether the connection to the server is working. If the connection
|
1099
|
+
* has gone down and auto-reconnect is enabled an attempt to reconnect is made.
|
1100
|
+
* If the connection is down and auto-reconnect is disabled, ping returns an
|
1101
|
+
* error.
|
1102
|
+
*/
|
619
1103
|
static VALUE rb_mysql_client_ping(VALUE self) {
|
620
1104
|
GET_CLIENT(self);
|
621
1105
|
|
622
|
-
if (wrapper
|
1106
|
+
if (!CONNECTED(wrapper)) {
|
623
1107
|
return Qfalse;
|
624
1108
|
} else {
|
625
|
-
return
|
1109
|
+
return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
|
1110
|
+
}
|
1111
|
+
}
|
1112
|
+
|
1113
|
+
/* call-seq:
|
1114
|
+
* client.more_results?
|
1115
|
+
*
|
1116
|
+
* Returns true or false if there are more results to process.
|
1117
|
+
*/
|
1118
|
+
static VALUE rb_mysql_client_more_results(VALUE self)
|
1119
|
+
{
|
1120
|
+
GET_CLIENT(self);
|
1121
|
+
if (mysql_more_results(wrapper->client) == 0)
|
1122
|
+
return Qfalse;
|
1123
|
+
else
|
1124
|
+
return Qtrue;
|
1125
|
+
}
|
1126
|
+
|
1127
|
+
/* call-seq:
|
1128
|
+
* client.next_result
|
1129
|
+
*
|
1130
|
+
* Fetch the next result set from the server.
|
1131
|
+
* Returns nothing.
|
1132
|
+
*/
|
1133
|
+
static VALUE rb_mysql_client_next_result(VALUE self)
|
1134
|
+
{
|
1135
|
+
int ret;
|
1136
|
+
GET_CLIENT(self);
|
1137
|
+
ret = mysql_next_result(wrapper->client);
|
1138
|
+
if (ret > 0) {
|
1139
|
+
rb_raise_mysql2_error(wrapper);
|
1140
|
+
return Qfalse;
|
1141
|
+
} else if (ret == 0) {
|
1142
|
+
return Qtrue;
|
1143
|
+
} else {
|
1144
|
+
return Qfalse;
|
1145
|
+
}
|
1146
|
+
}
|
1147
|
+
|
1148
|
+
/* call-seq:
|
1149
|
+
* client.store_result
|
1150
|
+
*
|
1151
|
+
* Return the next result object from a query which
|
1152
|
+
* yielded multiple result sets.
|
1153
|
+
*/
|
1154
|
+
static VALUE rb_mysql_client_store_result(VALUE self)
|
1155
|
+
{
|
1156
|
+
MYSQL_RES * result;
|
1157
|
+
VALUE resultObj;
|
1158
|
+
VALUE current;
|
1159
|
+
GET_CLIENT(self);
|
1160
|
+
|
1161
|
+
result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
|
1162
|
+
|
1163
|
+
if (result == NULL) {
|
1164
|
+
if (mysql_errno(wrapper->client) != 0) {
|
1165
|
+
rb_raise_mysql2_error(wrapper);
|
1166
|
+
}
|
1167
|
+
/* no data and no error, so query was not a SELECT */
|
1168
|
+
return Qnil;
|
626
1169
|
}
|
1170
|
+
|
1171
|
+
current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
|
1172
|
+
(void)RB_GC_GUARD(current);
|
1173
|
+
Check_Type(current, T_HASH);
|
1174
|
+
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
|
1175
|
+
|
1176
|
+
return resultObj;
|
627
1177
|
}
|
628
1178
|
|
629
1179
|
#ifdef HAVE_RUBY_ENCODING_H
|
1180
|
+
/* call-seq:
|
1181
|
+
* client.encoding
|
1182
|
+
*
|
1183
|
+
* Returns the encoding set on the client.
|
1184
|
+
*/
|
630
1185
|
static VALUE rb_mysql_client_encoding(VALUE self) {
|
631
1186
|
GET_CLIENT(self);
|
632
1187
|
return wrapper->encoding;
|
633
1188
|
}
|
634
1189
|
#endif
|
635
1190
|
|
636
|
-
|
637
|
-
|
1191
|
+
/* call-seq:
|
1192
|
+
* client.automatic_close?
|
1193
|
+
*
|
1194
|
+
* @return [Boolean]
|
1195
|
+
*/
|
1196
|
+
static VALUE get_automatic_close(VALUE self) {
|
638
1197
|
GET_CLIENT(self);
|
1198
|
+
return wrapper->automatic_close ? Qtrue : Qfalse;
|
1199
|
+
}
|
639
1200
|
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
1201
|
+
/* call-seq:
|
1202
|
+
* client.automatic_close = false
|
1203
|
+
*
|
1204
|
+
* Set this to +false+ to leave the connection open after it is garbage
|
1205
|
+
* collected. To avoid "Aborted connection" errors on the server, explicitly
|
1206
|
+
* call +close+ when the connection is no longer needed.
|
1207
|
+
*
|
1208
|
+
* @see http://dev.mysql.com/doc/en/communication-errors.html
|
1209
|
+
*/
|
1210
|
+
static VALUE set_automatic_close(VALUE self, VALUE value) {
|
1211
|
+
GET_CLIENT(self);
|
1212
|
+
if (RTEST(value)) {
|
1213
|
+
wrapper->automatic_close = 1;
|
1214
|
+
} else {
|
1215
|
+
#ifndef _WIN32
|
1216
|
+
wrapper->automatic_close = 0;
|
1217
|
+
#else
|
1218
|
+
rb_warn("Connections are always closed by garbage collector on Windows");
|
1219
|
+
#endif
|
649
1220
|
}
|
650
1221
|
return value;
|
651
1222
|
}
|
652
1223
|
|
1224
|
+
/* call-seq:
|
1225
|
+
* client.reconnect = true
|
1226
|
+
*
|
1227
|
+
* Enable or disable the automatic reconnect behavior of libmysql.
|
1228
|
+
* Read http://dev.mysql.com/doc/refman/5.5/en/auto-reconnect.html
|
1229
|
+
* for more information.
|
1230
|
+
*/
|
1231
|
+
static VALUE set_reconnect(VALUE self, VALUE value) {
|
1232
|
+
return _mysql_client_options(self, MYSQL_OPT_RECONNECT, value);
|
1233
|
+
}
|
1234
|
+
|
1235
|
+
static VALUE set_local_infile(VALUE self, VALUE value) {
|
1236
|
+
return _mysql_client_options(self, MYSQL_OPT_LOCAL_INFILE, value);
|
1237
|
+
}
|
1238
|
+
|
653
1239
|
static VALUE set_connect_timeout(VALUE self, VALUE value) {
|
654
|
-
|
655
|
-
|
1240
|
+
long int sec;
|
1241
|
+
Check_Type(value, T_FIXNUM);
|
1242
|
+
sec = FIX2INT(value);
|
1243
|
+
if (sec < 0) {
|
1244
|
+
rb_raise(cMysql2Error, "connect_timeout must be a positive integer, you passed %ld", sec);
|
1245
|
+
}
|
1246
|
+
return _mysql_client_options(self, MYSQL_OPT_CONNECT_TIMEOUT, value);
|
1247
|
+
}
|
656
1248
|
|
657
|
-
|
658
|
-
|
659
|
-
|
1249
|
+
static VALUE set_read_timeout(VALUE self, VALUE value) {
|
1250
|
+
long int sec;
|
1251
|
+
Check_Type(value, T_FIXNUM);
|
1252
|
+
sec = FIX2INT(value);
|
1253
|
+
if (sec < 0) {
|
1254
|
+
rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
|
1255
|
+
}
|
1256
|
+
/* Set the instance variable here even though _mysql_client_options
|
1257
|
+
might not succeed, because the timeout is used in other ways
|
1258
|
+
elsewhere */
|
1259
|
+
rb_iv_set(self, "@read_timeout", value);
|
1260
|
+
return _mysql_client_options(self, MYSQL_OPT_READ_TIMEOUT, value);
|
1261
|
+
}
|
660
1262
|
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
1263
|
+
static VALUE set_write_timeout(VALUE self, VALUE value) {
|
1264
|
+
long int sec;
|
1265
|
+
Check_Type(value, T_FIXNUM);
|
1266
|
+
sec = FIX2INT(value);
|
1267
|
+
if (sec < 0) {
|
1268
|
+
rb_raise(cMysql2Error, "write_timeout must be a positive integer, you passed %ld", sec);
|
666
1269
|
}
|
667
|
-
return value;
|
1270
|
+
return _mysql_client_options(self, MYSQL_OPT_WRITE_TIMEOUT, value);
|
668
1271
|
}
|
669
1272
|
|
670
1273
|
static VALUE set_charset_name(VALUE self, VALUE value) {
|
671
|
-
char *
|
1274
|
+
char *charset_name;
|
672
1275
|
#ifdef HAVE_RUBY_ENCODING_H
|
673
|
-
|
1276
|
+
const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
|
1277
|
+
rb_encoding *enc;
|
1278
|
+
VALUE rb_enc;
|
674
1279
|
#endif
|
675
1280
|
GET_CLIENT(self);
|
676
1281
|
|
1282
|
+
Check_Type(value, T_STRING);
|
1283
|
+
charset_name = RSTRING_PTR(value);
|
1284
|
+
|
677
1285
|
#ifdef HAVE_RUBY_ENCODING_H
|
678
|
-
|
679
|
-
if (
|
1286
|
+
mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
|
1287
|
+
if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
|
680
1288
|
VALUE inspect = rb_inspect(value);
|
681
1289
|
rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
|
682
1290
|
} else {
|
683
|
-
|
684
|
-
|
685
|
-
|
1291
|
+
enc = rb_enc_find(mysql2rb->rb_name);
|
1292
|
+
rb_enc = rb_enc_from_encoding(enc);
|
1293
|
+
wrapper->encoding = rb_enc;
|
686
1294
|
}
|
687
1295
|
#endif
|
688
1296
|
|
689
|
-
charset_name = StringValuePtr(value);
|
690
|
-
|
691
1297
|
if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
|
692
1298
|
/* TODO: warning - unable to set charset */
|
693
1299
|
rb_warn("%s\n", mysql_error(wrapper->client));
|
@@ -699,192 +1305,298 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
|
|
699
1305
|
static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
|
700
1306
|
GET_CLIENT(self);
|
701
1307
|
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
NIL_P(cipher) ? NULL : StringValuePtr(cipher));
|
709
|
-
}
|
1308
|
+
mysql_ssl_set(wrapper->client,
|
1309
|
+
NIL_P(key) ? NULL : StringValueCStr(key),
|
1310
|
+
NIL_P(cert) ? NULL : StringValueCStr(cert),
|
1311
|
+
NIL_P(ca) ? NULL : StringValueCStr(ca),
|
1312
|
+
NIL_P(capath) ? NULL : StringValueCStr(capath),
|
1313
|
+
NIL_P(cipher) ? NULL : StringValueCStr(cipher));
|
710
1314
|
|
711
1315
|
return self;
|
712
1316
|
}
|
713
1317
|
|
714
|
-
static VALUE
|
1318
|
+
static VALUE set_secure_auth(VALUE self, VALUE value) {
|
1319
|
+
/* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */
|
1320
|
+
#ifdef MYSQL_SECURE_AUTH
|
1321
|
+
return _mysql_client_options(self, MYSQL_SECURE_AUTH, value);
|
1322
|
+
#else
|
1323
|
+
return Qfalse;
|
1324
|
+
#endif
|
1325
|
+
}
|
1326
|
+
|
1327
|
+
static VALUE set_read_default_file(VALUE self, VALUE value) {
|
1328
|
+
return _mysql_client_options(self, MYSQL_READ_DEFAULT_FILE, value);
|
1329
|
+
}
|
1330
|
+
|
1331
|
+
static VALUE set_read_default_group(VALUE self, VALUE value) {
|
1332
|
+
return _mysql_client_options(self, MYSQL_READ_DEFAULT_GROUP, value);
|
1333
|
+
}
|
1334
|
+
|
1335
|
+
static VALUE set_init_command(VALUE self, VALUE value) {
|
1336
|
+
return _mysql_client_options(self, MYSQL_INIT_COMMAND, value);
|
1337
|
+
}
|
1338
|
+
|
1339
|
+
static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) {
|
1340
|
+
#ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
|
1341
|
+
return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value);
|
1342
|
+
#else
|
1343
|
+
rb_raise(cMysql2Error, "enable-cleartext-plugin is not available, you may need a newer MySQL client library");
|
1344
|
+
#endif
|
1345
|
+
}
|
1346
|
+
|
1347
|
+
static VALUE initialize_ext(VALUE self) {
|
715
1348
|
GET_CLIENT(self);
|
716
1349
|
|
717
|
-
if (
|
1350
|
+
if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
|
718
1351
|
/* TODO: warning - not enough memory? */
|
719
|
-
|
1352
|
+
rb_raise_mysql2_error(wrapper);
|
720
1353
|
}
|
721
1354
|
|
722
|
-
wrapper->
|
1355
|
+
wrapper->initialized = 1;
|
723
1356
|
return self;
|
724
1357
|
}
|
725
1358
|
|
1359
|
+
/* call-seq: client.prepare # => Mysql2::Statement
|
1360
|
+
*
|
1361
|
+
* Create a new prepared statement.
|
1362
|
+
*/
|
1363
|
+
static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
|
1364
|
+
GET_CLIENT(self);
|
1365
|
+
REQUIRE_CONNECTED(wrapper);
|
1366
|
+
|
1367
|
+
return rb_mysql_stmt_new(self, sql);
|
1368
|
+
}
|
1369
|
+
|
726
1370
|
void init_mysql2_client() {
|
727
|
-
|
728
|
-
|
1371
|
+
#ifdef _WIN32
|
1372
|
+
/* verify the libmysql we're about to use was the version we were built against
|
1373
|
+
https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */
|
729
1374
|
int i;
|
730
1375
|
int dots = 0;
|
731
1376
|
const char *lib = mysql_get_client_info();
|
732
|
-
|
1377
|
+
|
1378
|
+
for (i = 0; lib[i] != 0 && MYSQL_LINK_VERSION[i] != 0; i++) {
|
733
1379
|
if (lib[i] == '.') {
|
734
1380
|
dots++;
|
735
|
-
|
1381
|
+
/* we only compare MAJOR and MINOR */
|
736
1382
|
if (dots == 2) break;
|
737
1383
|
}
|
738
|
-
if (lib[i] !=
|
739
|
-
rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.",
|
740
|
-
return;
|
1384
|
+
if (lib[i] != MYSQL_LINK_VERSION[i]) {
|
1385
|
+
rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib);
|
741
1386
|
}
|
742
1387
|
}
|
1388
|
+
#endif
|
1389
|
+
|
1390
|
+
/* Initializing mysql library, so different threads could call Client.new */
|
1391
|
+
/* without race condition in the library */
|
1392
|
+
if (mysql_library_init(0, NULL, NULL) != 0) {
|
1393
|
+
rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library");
|
1394
|
+
}
|
743
1395
|
|
1396
|
+
#if 0
|
1397
|
+
mMysql2 = rb_define_module("Mysql2"); Teach RDoc about Mysql2 constant.
|
1398
|
+
#endif
|
744
1399
|
cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
|
745
1400
|
|
746
1401
|
rb_define_alloc_func(cMysql2Client, allocate);
|
747
1402
|
|
748
1403
|
rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
|
1404
|
+
rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
|
749
1405
|
|
750
1406
|
rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
|
751
|
-
rb_define_method(cMysql2Client, "
|
1407
|
+
rb_define_method(cMysql2Client, "closed?", rb_mysql_client_closed, 0);
|
1408
|
+
rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
|
752
1409
|
rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
|
753
|
-
rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
|
754
1410
|
rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
|
755
1411
|
rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
|
756
1412
|
rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
|
757
1413
|
rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
|
758
1414
|
rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
|
1415
|
+
rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1);
|
759
1416
|
rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
|
760
1417
|
rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
|
1418
|
+
rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
|
1419
|
+
rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0);
|
1420
|
+
rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
|
1421
|
+
rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
|
1422
|
+
rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0);
|
1423
|
+
rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1);
|
1424
|
+
rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1);
|
1425
|
+
rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0);
|
1426
|
+
rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0);
|
1427
|
+
rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0);
|
761
1428
|
#ifdef HAVE_RUBY_ENCODING_H
|
762
1429
|
rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
|
763
1430
|
#endif
|
764
1431
|
|
765
|
-
rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
|
766
1432
|
rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
|
1433
|
+
rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1);
|
1434
|
+
rb_define_private_method(cMysql2Client, "write_timeout=", set_write_timeout, 1);
|
1435
|
+
rb_define_private_method(cMysql2Client, "local_infile=", set_local_infile, 1);
|
767
1436
|
rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
|
1437
|
+
rb_define_private_method(cMysql2Client, "secure_auth=", set_secure_auth, 1);
|
1438
|
+
rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1);
|
1439
|
+
rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1);
|
1440
|
+
rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1);
|
768
1441
|
rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
|
769
|
-
rb_define_private_method(cMysql2Client, "
|
1442
|
+
rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1);
|
1443
|
+
rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1);
|
1444
|
+
rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
|
770
1445
|
rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
|
771
|
-
|
772
|
-
intern_encoding_from_charset = rb_intern("encoding_from_charset");
|
1446
|
+
rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
|
773
1447
|
|
774
1448
|
sym_id = ID2SYM(rb_intern("id"));
|
775
1449
|
sym_version = ID2SYM(rb_intern("version"));
|
1450
|
+
sym_header_version = ID2SYM(rb_intern("header_version"));
|
776
1451
|
sym_async = ID2SYM(rb_intern("async"));
|
777
1452
|
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
|
778
1453
|
sym_as = ID2SYM(rb_intern("as"));
|
779
1454
|
sym_array = ID2SYM(rb_intern("array"));
|
1455
|
+
sym_stream = ID2SYM(rb_intern("stream"));
|
780
1456
|
|
1457
|
+
intern_brackets = rb_intern("[]");
|
781
1458
|
intern_merge = rb_intern("merge");
|
782
|
-
|
783
|
-
|
1459
|
+
intern_merge_bang = rb_intern("merge!");
|
1460
|
+
intern_new_with_args = rb_intern("new_with_args");
|
784
1461
|
|
785
1462
|
#ifdef CLIENT_LONG_PASSWORD
|
786
1463
|
rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
|
787
|
-
|
1464
|
+
LONG2NUM(CLIENT_LONG_PASSWORD));
|
1465
|
+
#else
|
1466
|
+
/* HACK because MariaDB 10.2 no longer defines this constant,
|
1467
|
+
* but we're using it in our default connection flags. */
|
1468
|
+
rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), INT2NUM(0));
|
788
1469
|
#endif
|
789
1470
|
|
790
1471
|
#ifdef CLIENT_FOUND_ROWS
|
791
1472
|
rb_const_set(cMysql2Client, rb_intern("FOUND_ROWS"),
|
792
|
-
|
1473
|
+
LONG2NUM(CLIENT_FOUND_ROWS));
|
793
1474
|
#endif
|
794
1475
|
|
795
1476
|
#ifdef CLIENT_LONG_FLAG
|
796
1477
|
rb_const_set(cMysql2Client, rb_intern("LONG_FLAG"),
|
797
|
-
|
1478
|
+
LONG2NUM(CLIENT_LONG_FLAG));
|
798
1479
|
#endif
|
799
1480
|
|
800
1481
|
#ifdef CLIENT_CONNECT_WITH_DB
|
801
1482
|
rb_const_set(cMysql2Client, rb_intern("CONNECT_WITH_DB"),
|
802
|
-
|
1483
|
+
LONG2NUM(CLIENT_CONNECT_WITH_DB));
|
803
1484
|
#endif
|
804
1485
|
|
805
1486
|
#ifdef CLIENT_NO_SCHEMA
|
806
1487
|
rb_const_set(cMysql2Client, rb_intern("NO_SCHEMA"),
|
807
|
-
|
1488
|
+
LONG2NUM(CLIENT_NO_SCHEMA));
|
808
1489
|
#endif
|
809
1490
|
|
810
1491
|
#ifdef CLIENT_COMPRESS
|
811
|
-
rb_const_set(cMysql2Client, rb_intern("COMPRESS"),
|
1492
|
+
rb_const_set(cMysql2Client, rb_intern("COMPRESS"), LONG2NUM(CLIENT_COMPRESS));
|
812
1493
|
#endif
|
813
1494
|
|
814
1495
|
#ifdef CLIENT_ODBC
|
815
|
-
rb_const_set(cMysql2Client, rb_intern("ODBC"),
|
1496
|
+
rb_const_set(cMysql2Client, rb_intern("ODBC"), LONG2NUM(CLIENT_ODBC));
|
816
1497
|
#endif
|
817
1498
|
|
818
1499
|
#ifdef CLIENT_LOCAL_FILES
|
819
1500
|
rb_const_set(cMysql2Client, rb_intern("LOCAL_FILES"),
|
820
|
-
|
1501
|
+
LONG2NUM(CLIENT_LOCAL_FILES));
|
821
1502
|
#endif
|
822
1503
|
|
823
1504
|
#ifdef CLIENT_IGNORE_SPACE
|
824
1505
|
rb_const_set(cMysql2Client, rb_intern("IGNORE_SPACE"),
|
825
|
-
|
1506
|
+
LONG2NUM(CLIENT_IGNORE_SPACE));
|
826
1507
|
#endif
|
827
1508
|
|
828
1509
|
#ifdef CLIENT_PROTOCOL_41
|
829
1510
|
rb_const_set(cMysql2Client, rb_intern("PROTOCOL_41"),
|
830
|
-
|
1511
|
+
LONG2NUM(CLIENT_PROTOCOL_41));
|
831
1512
|
#endif
|
832
1513
|
|
833
1514
|
#ifdef CLIENT_INTERACTIVE
|
834
1515
|
rb_const_set(cMysql2Client, rb_intern("INTERACTIVE"),
|
835
|
-
|
1516
|
+
LONG2NUM(CLIENT_INTERACTIVE));
|
836
1517
|
#endif
|
837
1518
|
|
838
1519
|
#ifdef CLIENT_SSL
|
839
|
-
rb_const_set(cMysql2Client, rb_intern("SSL"),
|
1520
|
+
rb_const_set(cMysql2Client, rb_intern("SSL"), LONG2NUM(CLIENT_SSL));
|
840
1521
|
#endif
|
841
1522
|
|
842
1523
|
#ifdef CLIENT_IGNORE_SIGPIPE
|
843
1524
|
rb_const_set(cMysql2Client, rb_intern("IGNORE_SIGPIPE"),
|
844
|
-
|
1525
|
+
LONG2NUM(CLIENT_IGNORE_SIGPIPE));
|
845
1526
|
#endif
|
846
1527
|
|
847
1528
|
#ifdef CLIENT_TRANSACTIONS
|
848
1529
|
rb_const_set(cMysql2Client, rb_intern("TRANSACTIONS"),
|
849
|
-
|
1530
|
+
LONG2NUM(CLIENT_TRANSACTIONS));
|
850
1531
|
#endif
|
851
1532
|
|
852
1533
|
#ifdef CLIENT_RESERVED
|
853
|
-
rb_const_set(cMysql2Client, rb_intern("RESERVED"),
|
1534
|
+
rb_const_set(cMysql2Client, rb_intern("RESERVED"), LONG2NUM(CLIENT_RESERVED));
|
854
1535
|
#endif
|
855
1536
|
|
856
1537
|
#ifdef CLIENT_SECURE_CONNECTION
|
857
1538
|
rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"),
|
858
|
-
|
1539
|
+
LONG2NUM(CLIENT_SECURE_CONNECTION));
|
1540
|
+
#else
|
1541
|
+
/* HACK because MySQL5.7 no longer defines this constant,
|
1542
|
+
* but we're using it in our default connection flags. */
|
1543
|
+
rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0));
|
859
1544
|
#endif
|
860
1545
|
|
861
1546
|
#ifdef CLIENT_MULTI_STATEMENTS
|
862
1547
|
rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"),
|
863
|
-
|
1548
|
+
LONG2NUM(CLIENT_MULTI_STATEMENTS));
|
864
1549
|
#endif
|
865
1550
|
|
866
1551
|
#ifdef CLIENT_PS_MULTI_RESULTS
|
867
1552
|
rb_const_set(cMysql2Client, rb_intern("PS_MULTI_RESULTS"),
|
868
|
-
|
1553
|
+
LONG2NUM(CLIENT_PS_MULTI_RESULTS));
|
869
1554
|
#endif
|
870
1555
|
|
871
1556
|
#ifdef CLIENT_SSL_VERIFY_SERVER_CERT
|
872
1557
|
rb_const_set(cMysql2Client, rb_intern("SSL_VERIFY_SERVER_CERT"),
|
873
|
-
|
1558
|
+
LONG2NUM(CLIENT_SSL_VERIFY_SERVER_CERT));
|
874
1559
|
#endif
|
875
1560
|
|
876
1561
|
#ifdef CLIENT_REMEMBER_OPTIONS
|
877
1562
|
rb_const_set(cMysql2Client, rb_intern("REMEMBER_OPTIONS"),
|
878
|
-
|
1563
|
+
LONG2NUM(CLIENT_REMEMBER_OPTIONS));
|
879
1564
|
#endif
|
880
1565
|
|
881
1566
|
#ifdef CLIENT_ALL_FLAGS
|
882
1567
|
rb_const_set(cMysql2Client, rb_intern("ALL_FLAGS"),
|
883
|
-
|
1568
|
+
LONG2NUM(CLIENT_ALL_FLAGS));
|
884
1569
|
#endif
|
885
1570
|
|
886
1571
|
#ifdef CLIENT_BASIC_FLAGS
|
887
1572
|
rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
|
888
|
-
|
1573
|
+
LONG2NUM(CLIENT_BASIC_FLAGS));
|
1574
|
+
#endif
|
1575
|
+
|
1576
|
+
#if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above
|
1577
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
|
1578
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED));
|
1579
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
|
1580
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA));
|
1581
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY));
|
1582
|
+
#elif defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) // MySQL 5.7.3 - 5.7.10
|
1583
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
|
1584
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
|
1585
|
+
#endif
|
1586
|
+
|
1587
|
+
#ifndef HAVE_CONST_SSL_MODE_DISABLED
|
1588
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0));
|
1589
|
+
#endif
|
1590
|
+
#ifndef HAVE_CONST_SSL_MODE_PREFERRED
|
1591
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(0));
|
1592
|
+
#endif
|
1593
|
+
#ifndef HAVE_CONST_SSL_MODE_REQUIRED
|
1594
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(0));
|
1595
|
+
#endif
|
1596
|
+
#ifndef HAVE_CONST_SSL_MODE_VERIFY_CA
|
1597
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(0));
|
1598
|
+
#endif
|
1599
|
+
#ifndef HAVE_CONST_SSL_MODE_VERIFY_IDENTITY
|
1600
|
+
rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0));
|
889
1601
|
#endif
|
890
1602
|
}
|