trilogy 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of trilogy might be problematic. Click here for more details.

Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +74 -0
  4. data/Rakefile +18 -0
  5. data/ext/trilogy-ruby/cast.c +272 -0
  6. data/ext/trilogy-ruby/cext.c +933 -0
  7. data/ext/trilogy-ruby/extconf.rb +16 -0
  8. data/ext/trilogy-ruby/inc/trilogy/blocking.h +163 -0
  9. data/ext/trilogy-ruby/inc/trilogy/buffer.h +64 -0
  10. data/ext/trilogy-ruby/inc/trilogy/builder.h +161 -0
  11. data/ext/trilogy-ruby/inc/trilogy/charset.h +277 -0
  12. data/ext/trilogy-ruby/inc/trilogy/client.h +546 -0
  13. data/ext/trilogy-ruby/inc/trilogy/error.h +43 -0
  14. data/ext/trilogy-ruby/inc/trilogy/packet_parser.h +34 -0
  15. data/ext/trilogy-ruby/inc/trilogy/protocol.h +756 -0
  16. data/ext/trilogy-ruby/inc/trilogy/reader.h +212 -0
  17. data/ext/trilogy-ruby/inc/trilogy/socket.h +111 -0
  18. data/ext/trilogy-ruby/inc/trilogy/vendor/curl_hostcheck.h +29 -0
  19. data/ext/trilogy-ruby/inc/trilogy/vendor/openssl_hostname_validation.h +51 -0
  20. data/ext/trilogy-ruby/inc/trilogy.h +8 -0
  21. data/ext/trilogy-ruby/src/blocking.c +241 -0
  22. data/ext/trilogy-ruby/src/buffer.c +60 -0
  23. data/ext/trilogy-ruby/src/builder.c +198 -0
  24. data/ext/trilogy-ruby/src/charset.c +212 -0
  25. data/ext/trilogy-ruby/src/client.c +728 -0
  26. data/ext/trilogy-ruby/src/error.c +17 -0
  27. data/ext/trilogy-ruby/src/packet_parser.c +140 -0
  28. data/ext/trilogy-ruby/src/protocol.c +676 -0
  29. data/ext/trilogy-ruby/src/reader.c +244 -0
  30. data/ext/trilogy-ruby/src/socket.c +623 -0
  31. data/ext/trilogy-ruby/src/vendor/curl_hostcheck.c +206 -0
  32. data/ext/trilogy-ruby/src/vendor/openssl_hostname_validation.c +175 -0
  33. data/ext/trilogy-ruby/trilogy-ruby.h +36 -0
  34. data/lib/trilogy/version.rb +3 -0
  35. data/lib/trilogy.rb +61 -0
  36. data/trilogy.gemspec +27 -0
  37. metadata +106 -0
@@ -0,0 +1,933 @@
1
+ #include <arpa/inet.h>
2
+ #include <errno.h>
3
+ #include <ruby.h>
4
+ #include <ruby/encoding.h>
5
+ #include <ruby/io.h>
6
+ #include <ruby/thread.h>
7
+ #include <sys/socket.h>
8
+ #include <sys/time.h>
9
+ #include <sys/un.h>
10
+
11
+ #include <trilogy.h>
12
+
13
+ #include "trilogy-ruby.h"
14
+
15
+ #define TRILOGY_RB_TIMEOUT 1
16
+
17
+ VALUE
18
+ rb_cTrilogyError;
19
+
20
+ static VALUE Trilogy_DatabaseError, Trilogy_Result;
21
+
22
+ static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows, id_connect_timeout, id_read_timeout,
23
+ id_write_timeout, id_keepalive_enabled, id_keepalive_idle, id_keepalive_interval, id_keepalive_count,
24
+ id_ivar_fields, id_ivar_rows, id_ivar_query_time, id_password, id_database, id_ssl_ca, id_ssl_capath, id_ssl_cert,
25
+ id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key, id_ssl_mode, id_tls_ciphersuites, id_tls_min_version,
26
+ id_tls_max_version;
27
+
28
+ struct trilogy_ctx {
29
+ trilogy_conn_t conn;
30
+ char server_version[TRILOGY_SERVER_VERSION_SIZE + 1];
31
+ unsigned int query_flags;
32
+ };
33
+
34
+ static struct trilogy_ctx *get_ctx(VALUE obj)
35
+ {
36
+ struct trilogy_ctx *ctx;
37
+ Data_Get_Struct(obj, struct trilogy_ctx, ctx);
38
+ return ctx;
39
+ }
40
+
41
+ static struct trilogy_ctx *get_open_ctx(VALUE obj)
42
+ {
43
+ struct trilogy_ctx *ctx = get_ctx(obj);
44
+
45
+ if (ctx->conn.socket == NULL) {
46
+ rb_raise(rb_eIOError, "connection closed");
47
+ }
48
+
49
+ return ctx;
50
+ }
51
+
52
+ NORETURN(static void handle_trilogy_error(struct trilogy_ctx *, int, const char *, ...));
53
+ static void handle_trilogy_error(struct trilogy_ctx *ctx, int rc, const char *msg, ...)
54
+ {
55
+ va_list args;
56
+ va_start(args, msg);
57
+ VALUE rbmsg = rb_vsprintf(msg, args);
58
+ va_end(args);
59
+
60
+ switch (rc) {
61
+ case TRILOGY_SYSERR:
62
+ rb_syserr_fail_str(errno, rbmsg);
63
+
64
+ case TRILOGY_ERR: {
65
+ VALUE message = rb_str_new(ctx->conn.error_message, ctx->conn.error_message_len);
66
+ VALUE exc = rb_exc_new3(Trilogy_DatabaseError,
67
+ rb_sprintf("%" PRIsVALUE ": %d %" PRIsVALUE, rbmsg, ctx->conn.error_code, message));
68
+
69
+ rb_ivar_set(exc, rb_intern("@error_code"), INT2FIX(ctx->conn.error_code));
70
+ rb_ivar_set(exc, rb_intern("@error_message"), message);
71
+
72
+ rb_exc_raise(exc);
73
+ }
74
+
75
+ case TRILOGY_OPENSSL_ERR: {
76
+ unsigned long ossl_error = ERR_get_error();
77
+ ERR_clear_error();
78
+ if (ERR_GET_LIB(ossl_error) == ERR_LIB_SYS) {
79
+ rb_syserr_fail_str(ERR_GET_REASON(ossl_error), rbmsg);
80
+ }
81
+ // We can't recover from OpenSSL level errors if there's
82
+ // an active connection.
83
+ if (ctx->conn.socket != NULL) {
84
+ trilogy_sock_shutdown(ctx->conn.socket);
85
+ }
86
+ rb_raise(rb_cTrilogyError, "%" PRIsVALUE ": SSL Error: %s", rbmsg, ERR_reason_error_string(ossl_error));
87
+ }
88
+
89
+ default:
90
+ rb_raise(rb_cTrilogyError, "%" PRIsVALUE ": %s", rbmsg, trilogy_error(rc));
91
+ }
92
+ }
93
+
94
+ static void free_trilogy(struct trilogy_ctx *ctx)
95
+ {
96
+ if (ctx->conn.socket != NULL) {
97
+ trilogy_free(&ctx->conn);
98
+ }
99
+ }
100
+
101
+ static VALUE allocate_trilogy(VALUE klass)
102
+ {
103
+ struct trilogy_ctx *ctx;
104
+
105
+ VALUE obj = Data_Make_Struct(klass, struct trilogy_ctx, NULL, free_trilogy, ctx);
106
+
107
+ memset(ctx->server_version, 0, sizeof(ctx->server_version));
108
+
109
+ ctx->query_flags = TRILOGY_FLAGS_DEFAULT;
110
+
111
+ if (trilogy_init(&ctx->conn) < 0) {
112
+ rb_syserr_fail(errno, "trilogy_init");
113
+ }
114
+
115
+ return obj;
116
+ }
117
+
118
+ static int flush_writes(struct trilogy_ctx *ctx)
119
+ {
120
+ while (1) {
121
+ int rc = trilogy_flush_writes(&ctx->conn);
122
+
123
+ if (rc != TRILOGY_AGAIN) {
124
+ return rc;
125
+ }
126
+
127
+ if (trilogy_sock_wait_write(ctx->conn.socket) < 0) {
128
+ rb_syserr_fail(ETIMEDOUT, "trilogy_flush_writes");
129
+ }
130
+ }
131
+ }
132
+
133
+ static struct timeval double_to_timeval(double secs)
134
+ {
135
+ double whole_secs = floor(secs);
136
+
137
+ return (struct timeval){
138
+ .tv_sec = whole_secs,
139
+ .tv_usec = floor((secs - whole_secs) * 1000000),
140
+ };
141
+ }
142
+
143
+ static int _cb_ruby_wait(trilogy_sock_t *sock, trilogy_wait_t wait)
144
+ {
145
+ struct timeval *timeout = NULL;
146
+ int wait_flag = 0;
147
+
148
+ switch (wait) {
149
+ case TRILOGY_WAIT_READ:
150
+ timeout = &sock->opts.read_timeout;
151
+ wait_flag = RB_WAITFD_IN;
152
+ break;
153
+
154
+ case TRILOGY_WAIT_WRITE:
155
+ timeout = &sock->opts.write_timeout;
156
+ wait_flag = RB_WAITFD_OUT;
157
+ break;
158
+
159
+ case TRILOGY_WAIT_HANDSHAKE:
160
+ timeout = &sock->opts.connect_timeout;
161
+ wait_flag = RB_WAITFD_IN;
162
+ break;
163
+
164
+ default:
165
+ return TRILOGY_ERR;
166
+ }
167
+
168
+ if (timeout->tv_sec == 0 && timeout->tv_usec == 0) {
169
+ timeout = NULL;
170
+ }
171
+
172
+ int fd = trilogy_sock_fd(sock);
173
+ if (rb_wait_for_single_fd(fd, wait_flag, timeout) <= 0)
174
+ return TRILOGY_SYSERR;
175
+
176
+ return 0;
177
+ }
178
+
179
+ struct nogvl_sock_args {
180
+ int rc;
181
+ trilogy_sock_t *sock;
182
+ };
183
+
184
+ static void *no_gvl_resolve(void *data)
185
+ {
186
+ struct nogvl_sock_args *args = data;
187
+ args->rc = trilogy_sock_resolve(args->sock);
188
+ return NULL;
189
+ }
190
+
191
+ static int try_connect(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake, const trilogy_sockopt_t *opts)
192
+ {
193
+ trilogy_sock_t *sock = trilogy_sock_new(opts);
194
+ if (sock == NULL) {
195
+ return TRILOGY_ERR;
196
+ }
197
+
198
+ struct nogvl_sock_args args = {.rc = 0, .sock = sock};
199
+
200
+ // Do the DNS resolving with the GVL unlocked. At this point all
201
+ // configuration data is copied and available to the trilogy socket.
202
+ rb_thread_call_without_gvl(no_gvl_resolve, (void *)&args, RUBY_UBF_IO, NULL);
203
+
204
+ int rc = args.rc;
205
+
206
+ if (rc != TRILOGY_OK) {
207
+ return rc;
208
+ }
209
+
210
+ /* replace the default wait callback with our GVL-aware callback so we can
211
+ escape the GVL on each wait operation without going through call_without_gvl */
212
+ sock->wait_cb = _cb_ruby_wait;
213
+ rc = trilogy_connect_send_socket(&ctx->conn, sock);
214
+ if (rc < 0)
215
+ return rc;
216
+
217
+ while (1) {
218
+ rc = trilogy_connect_recv(&ctx->conn, handshake);
219
+
220
+ if (rc == TRILOGY_OK) {
221
+ return rc;
222
+ }
223
+
224
+ if (rc != TRILOGY_AGAIN) {
225
+ return rc;
226
+ }
227
+
228
+ if (trilogy_sock_wait(ctx->conn.socket, TRILOGY_WAIT_HANDSHAKE) < 0)
229
+ return TRILOGY_RB_TIMEOUT;
230
+ }
231
+ }
232
+
233
+ static void auth_switch(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake)
234
+ {
235
+ int rc = trilogy_auth_switch_send(&ctx->conn, handshake);
236
+
237
+ if (rc == TRILOGY_AGAIN) {
238
+ rc = flush_writes(ctx);
239
+ }
240
+
241
+ if (rc != TRILOGY_OK) {
242
+ handle_trilogy_error(ctx, rc, "trilogy_auth_switch_send");
243
+ }
244
+
245
+ while (1) {
246
+ rc = trilogy_auth_recv(&ctx->conn, handshake);
247
+
248
+ if (rc == TRILOGY_OK) {
249
+ return;
250
+ }
251
+
252
+ if (rc != TRILOGY_AGAIN) {
253
+ handle_trilogy_error(ctx, rc, "trilogy_auth_recv");
254
+ }
255
+
256
+ if (trilogy_sock_wait_read(ctx->conn.socket) < 0) {
257
+ rb_syserr_fail(ETIMEDOUT, "trilogy_auth_recv");
258
+ }
259
+ }
260
+ }
261
+
262
+ static void authenticate(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake, trilogy_ssl_mode_t ssl_mode)
263
+ {
264
+ int rc;
265
+
266
+ if (ssl_mode != TRILOGY_SSL_DISABLED) {
267
+ if (handshake->capabilities & TRILOGY_CAPABILITIES_SSL) {
268
+ rc = trilogy_ssl_request_send(&ctx->conn);
269
+ if (rc == TRILOGY_AGAIN) {
270
+ rc = flush_writes(ctx);
271
+ }
272
+
273
+ if (rc != TRILOGY_OK) {
274
+ handle_trilogy_error(ctx, rc, "trilogy_ssl_request_send");
275
+ }
276
+
277
+ rc = trilogy_sock_upgrade_ssl(ctx->conn.socket);
278
+ if (rc != TRILOGY_OK) {
279
+ handle_trilogy_error(ctx, rc, "trilogy_ssl_upgrade");
280
+ }
281
+ } else {
282
+ if (ssl_mode != TRILOGY_SSL_PREFERRED_NOVERIFY) {
283
+ rb_raise(rb_cTrilogyError, "SSL required, not supported by server");
284
+ }
285
+ }
286
+ }
287
+
288
+ rc = trilogy_auth_send(&ctx->conn, handshake);
289
+
290
+ if (rc == TRILOGY_AGAIN) {
291
+ rc = flush_writes(ctx);
292
+ }
293
+
294
+ if (rc != TRILOGY_OK) {
295
+ handle_trilogy_error(ctx, rc, "trilogy_auth_send");
296
+ }
297
+
298
+ while (1) {
299
+ rc = trilogy_auth_recv(&ctx->conn, handshake);
300
+
301
+ if (rc == TRILOGY_OK || rc == TRILOGY_AUTH_SWITCH) {
302
+ break;
303
+ }
304
+
305
+ if (rc != TRILOGY_AGAIN) {
306
+ handle_trilogy_error(ctx, rc, "trilogy_auth_recv");
307
+ }
308
+
309
+ if (trilogy_sock_wait_read(ctx->conn.socket) < 0) {
310
+ rb_syserr_fail(ETIMEDOUT, "trilogy_auth_recv");
311
+ }
312
+ }
313
+
314
+ if (rc == TRILOGY_AUTH_SWITCH) {
315
+ auth_switch(ctx, handshake);
316
+ }
317
+ }
318
+
319
+ static VALUE rb_trilogy_initialize(VALUE self, VALUE opts)
320
+ {
321
+ struct trilogy_ctx *ctx = get_ctx(self);
322
+ trilogy_sockopt_t connopt = {0};
323
+ trilogy_handshake_t handshake;
324
+ VALUE val;
325
+
326
+ Check_Type(opts, T_HASH);
327
+
328
+ if ((val = rb_hash_lookup(opts, ID2SYM(id_ssl_mode))) != Qnil) {
329
+ Check_Type(val, T_FIXNUM);
330
+ connopt.ssl_mode = (trilogy_ssl_mode_t)NUM2INT(val);
331
+ }
332
+
333
+ if ((val = rb_hash_aref(opts, ID2SYM(id_connect_timeout))) != Qnil) {
334
+ connopt.connect_timeout = double_to_timeval(NUM2DBL(val));
335
+ }
336
+
337
+ if ((val = rb_hash_aref(opts, ID2SYM(id_read_timeout))) != Qnil) {
338
+ connopt.read_timeout = double_to_timeval(NUM2DBL(val));
339
+ }
340
+
341
+ if ((val = rb_hash_aref(opts, ID2SYM(id_write_timeout))) != Qnil) {
342
+ connopt.write_timeout = double_to_timeval(NUM2DBL(val));
343
+ }
344
+
345
+ if (RTEST(rb_hash_aref(opts, ID2SYM(id_keepalive_enabled)))) {
346
+ connopt.keepalive_enabled = true;
347
+ }
348
+
349
+ if ((val = rb_hash_lookup(opts, ID2SYM(id_keepalive_idle))) != Qnil) {
350
+ Check_Type(val, T_FIXNUM);
351
+ connopt.keepalive_idle = NUM2USHORT(val);
352
+ }
353
+
354
+ if ((val = rb_hash_lookup(opts, ID2SYM(id_keepalive_count))) != Qnil) {
355
+ Check_Type(val, T_FIXNUM);
356
+ connopt.keepalive_count = NUM2USHORT(val);
357
+ }
358
+
359
+ if ((val = rb_hash_lookup(opts, ID2SYM(id_keepalive_interval))) != Qnil) {
360
+ Check_Type(val, T_FIXNUM);
361
+ connopt.keepalive_interval = NUM2USHORT(val);
362
+ }
363
+
364
+ if ((val = rb_hash_lookup(opts, ID2SYM(id_host))) != Qnil) {
365
+ Check_Type(val, T_STRING);
366
+
367
+ connopt.hostname = StringValueCStr(val);
368
+ connopt.port = 3306;
369
+
370
+ if ((val = rb_hash_lookup(opts, ID2SYM(id_port))) != Qnil) {
371
+ Check_Type(val, T_FIXNUM);
372
+ connopt.port = NUM2USHORT(val);
373
+ }
374
+ } else {
375
+ connopt.path = (char *)"/tmp/mysql.sock";
376
+
377
+ if ((val = rb_hash_lookup(opts, ID2SYM(id_socket))) != Qnil) {
378
+ Check_Type(val, T_STRING);
379
+ connopt.path = StringValueCStr(val);
380
+ }
381
+ }
382
+
383
+ if ((val = rb_hash_aref(opts, ID2SYM(id_username))) != Qnil) {
384
+ Check_Type(val, T_STRING);
385
+ connopt.username = StringValueCStr(val);
386
+ }
387
+
388
+ if ((val = rb_hash_aref(opts, ID2SYM(id_password))) != Qnil) {
389
+ Check_Type(val, T_STRING);
390
+ connopt.password = RSTRING_PTR(val);
391
+ connopt.password_len = RSTRING_LEN(val);
392
+ }
393
+
394
+ if ((val = rb_hash_aref(opts, ID2SYM(id_database))) != Qnil) {
395
+ Check_Type(val, T_STRING);
396
+ connopt.database = StringValueCStr(val);
397
+ connopt.flags |= TRILOGY_CAPABILITIES_CONNECT_WITH_DB;
398
+ }
399
+
400
+ if (RTEST(rb_hash_aref(opts, ID2SYM(id_found_rows)))) {
401
+ connopt.flags |= TRILOGY_CAPABILITIES_FOUND_ROWS;
402
+ }
403
+
404
+ if ((val = rb_hash_aref(opts, ID2SYM(id_ssl_ca))) != Qnil) {
405
+ Check_Type(val, T_STRING);
406
+ connopt.ssl_ca = StringValueCStr(val);
407
+ }
408
+
409
+ if ((val = rb_hash_aref(opts, ID2SYM(id_ssl_capath))) != Qnil) {
410
+ Check_Type(val, T_STRING);
411
+ connopt.ssl_capath = StringValueCStr(val);
412
+ }
413
+
414
+ if ((val = rb_hash_aref(opts, ID2SYM(id_ssl_cert))) != Qnil) {
415
+ Check_Type(val, T_STRING);
416
+ connopt.ssl_cert = StringValueCStr(val);
417
+ }
418
+
419
+ if ((val = rb_hash_aref(opts, ID2SYM(id_ssl_cipher))) != Qnil) {
420
+ Check_Type(val, T_STRING);
421
+ connopt.ssl_cipher = StringValueCStr(val);
422
+ }
423
+
424
+ if ((val = rb_hash_aref(opts, ID2SYM(id_ssl_crl))) != Qnil) {
425
+ Check_Type(val, T_STRING);
426
+ connopt.ssl_crl = StringValueCStr(val);
427
+ }
428
+
429
+ if ((val = rb_hash_aref(opts, ID2SYM(id_ssl_crlpath))) != Qnil) {
430
+ Check_Type(val, T_STRING);
431
+ connopt.ssl_crlpath = StringValueCStr(val);
432
+ }
433
+
434
+ if ((val = rb_hash_aref(opts, ID2SYM(id_ssl_key))) != Qnil) {
435
+ Check_Type(val, T_STRING);
436
+ connopt.ssl_key = StringValueCStr(val);
437
+ }
438
+
439
+ if ((val = rb_hash_aref(opts, ID2SYM(id_tls_ciphersuites))) != Qnil) {
440
+ Check_Type(val, T_STRING);
441
+ connopt.tls_ciphersuites = StringValueCStr(val);
442
+ }
443
+
444
+ if ((val = rb_hash_aref(opts, ID2SYM(id_tls_min_version))) != Qnil) {
445
+ Check_Type(val, T_FIXNUM);
446
+ connopt.tls_min_version = NUM2INT(val);
447
+ }
448
+
449
+ if ((val = rb_hash_aref(opts, ID2SYM(id_tls_max_version))) != Qnil) {
450
+ Check_Type(val, T_FIXNUM);
451
+ connopt.tls_max_version = NUM2INT(val);
452
+ }
453
+
454
+ int rc = try_connect(ctx, &handshake, &connopt);
455
+ if (rc == TRILOGY_RB_TIMEOUT) {
456
+ rb_syserr_fail(ETIMEDOUT, "trilogy_connect_recv");
457
+ }
458
+ if (rc != TRILOGY_OK) {
459
+ if (connopt.path) {
460
+ handle_trilogy_error(ctx, rc, "trilogy_connect - unable to connect to %s", connopt.path);
461
+ } else {
462
+ handle_trilogy_error(ctx, rc, "trilogy_connect - unable to connect to %s:%hu", connopt.hostname,
463
+ connopt.port);
464
+ }
465
+ }
466
+
467
+ memcpy(ctx->server_version, handshake.server_version, TRILOGY_SERVER_VERSION_SIZE);
468
+ ctx->server_version[TRILOGY_SERVER_VERSION_SIZE] = 0;
469
+
470
+ authenticate(ctx, &handshake, connopt.ssl_mode);
471
+
472
+ return Qnil;
473
+ }
474
+
475
+ static VALUE rb_trilogy_change_db(VALUE self, VALUE database)
476
+ {
477
+ struct trilogy_ctx *ctx = get_open_ctx(self);
478
+
479
+ StringValue(database);
480
+
481
+ int rc = trilogy_change_db_send(&ctx->conn, RSTRING_PTR(database), RSTRING_LEN(database));
482
+
483
+ if (rc == TRILOGY_AGAIN) {
484
+ rc = flush_writes(ctx);
485
+ }
486
+
487
+ if (rc != TRILOGY_OK) {
488
+ handle_trilogy_error(ctx, rc, "trilogy_change_db_send");
489
+ }
490
+
491
+ while (1) {
492
+ rc = trilogy_change_db_recv(&ctx->conn);
493
+
494
+ if (rc == TRILOGY_OK) {
495
+ break;
496
+ }
497
+
498
+ if (rc != TRILOGY_AGAIN) {
499
+ handle_trilogy_error(ctx, rc, "trilogy_change_db_recv");
500
+ }
501
+
502
+ if (trilogy_sock_wait_read(ctx->conn.socket) < 0) {
503
+ rb_syserr_fail(ETIMEDOUT, "trilogy_change_db_recv");
504
+ }
505
+ }
506
+
507
+ return Qtrue;
508
+ }
509
+
510
+ static void load_query_options(unsigned int query_flags, struct rb_trilogy_cast_options *cast_options)
511
+ {
512
+ cast_options->cast = (query_flags & TRILOGY_FLAGS_CAST) != 0;
513
+ cast_options->cast_booleans = (query_flags & TRILOGY_FLAGS_CAST_BOOLEANS) != 0;
514
+ cast_options->database_local_time = (query_flags & TRILOGY_FLAGS_LOCAL_TIMEZONE) != 0;
515
+ cast_options->flatten_rows = (query_flags & TRILOGY_FLAGS_FLATTEN_ROWS) != 0;
516
+ }
517
+
518
+ struct read_query_state {
519
+ struct rb_trilogy_cast_options *cast_options;
520
+ struct trilogy_ctx *ctx;
521
+ VALUE query;
522
+
523
+ // to free by caller:
524
+ struct column_info *column_info;
525
+ trilogy_value_t *row_values;
526
+
527
+ // Error state for tracking
528
+ const char *msg;
529
+ int rc;
530
+ };
531
+
532
+ static void get_timespec_monotonic(struct timespec *ts)
533
+ {
534
+ #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
535
+ if (clock_gettime(CLOCK_MONOTONIC, ts) == -1) {
536
+ rb_sys_fail("clock_gettime");
537
+ }
538
+ #else
539
+ struct timeval tv;
540
+ if (gettimeofday(&tv, 0) < 0) {
541
+ rb_sys_fail("gettimeofday");
542
+ }
543
+ ts->tv_sec = tv.tv_sec;
544
+ ts->tv_nsec = tv.tv_usec * 1000;
545
+ #endif
546
+ }
547
+
548
+ static VALUE read_query_error(struct read_query_state *args, int rc, const char *msg)
549
+ {
550
+ args->rc = rc;
551
+ args->msg = msg;
552
+ return Qundef;
553
+ }
554
+
555
+ static VALUE execute_read_query(VALUE vargs)
556
+ {
557
+ struct read_query_state *args = (void *)vargs;
558
+ struct trilogy_ctx *ctx = args->ctx;
559
+ VALUE query = args->query;
560
+
561
+ struct timespec start;
562
+ get_timespec_monotonic(&start);
563
+
564
+ int rc = trilogy_query_send(&ctx->conn, RSTRING_PTR(query), RSTRING_LEN(query));
565
+
566
+ if (rc == TRILOGY_AGAIN) {
567
+ rc = flush_writes(ctx);
568
+ }
569
+
570
+ if (rc < 0) {
571
+ return read_query_error(args, rc, "trilogy_query_send");
572
+ }
573
+
574
+ uint64_t column_count = 0;
575
+
576
+ while (1) {
577
+ rc = trilogy_query_recv(&ctx->conn, &column_count);
578
+
579
+ if (rc == TRILOGY_OK || rc == TRILOGY_HAVE_RESULTS) {
580
+ break;
581
+ }
582
+
583
+ if (rc != TRILOGY_AGAIN) {
584
+ return read_query_error(args, rc, "trilogy_query_recv");
585
+ }
586
+
587
+ if (trilogy_sock_wait_read(ctx->conn.socket) < 0) {
588
+ rb_syserr_fail(ETIMEDOUT, "trilogy_query_recv");
589
+ }
590
+ }
591
+
592
+ struct timespec finish;
593
+ get_timespec_monotonic(&finish);
594
+
595
+ double query_time = finish.tv_sec - start.tv_sec;
596
+ query_time += (double)(finish.tv_nsec - start.tv_nsec) / 1000000000.0;
597
+
598
+ VALUE result = rb_obj_alloc(Trilogy_Result);
599
+
600
+ VALUE column_names = rb_ary_new2(column_count);
601
+ rb_ivar_set(result, id_ivar_fields, column_names);
602
+
603
+ VALUE rows = rb_ary_new();
604
+ rb_ivar_set(result, id_ivar_rows, rows);
605
+
606
+ rb_ivar_set(result, id_ivar_query_time, DBL2NUM(query_time));
607
+
608
+ if (rc == TRILOGY_OK) {
609
+ return result;
610
+ }
611
+
612
+ struct column_info *column_info = ALLOC_N(struct column_info, column_count);
613
+ args->column_info = column_info;
614
+
615
+ for (uint64_t i = 0; i < column_count; i++) {
616
+ trilogy_column_t column;
617
+
618
+ while (1) {
619
+ rc = trilogy_read_column(&ctx->conn, &column);
620
+
621
+ if (rc == TRILOGY_OK) {
622
+ break;
623
+ }
624
+
625
+ if (rc != TRILOGY_AGAIN) {
626
+ return read_query_error(args, rc, "trilogy_read_column");
627
+ }
628
+
629
+ if (trilogy_sock_wait_read(ctx->conn.socket) < 0) {
630
+ rb_syserr_fail(ETIMEDOUT, "trilogy_read_column");
631
+ }
632
+ }
633
+
634
+ VALUE column_name = rb_str_new(column.name, column.name_len);
635
+ OBJ_FREEZE(column_name);
636
+
637
+ rb_ary_push(column_names, column_name);
638
+
639
+ column_info[i].type = column.type;
640
+ column_info[i].flags = column.flags;
641
+ column_info[i].len = column.len;
642
+ column_info[i].charset = column.charset;
643
+ }
644
+
645
+ trilogy_value_t *row_values = ALLOC_N(trilogy_value_t, column_count);
646
+ args->row_values = row_values;
647
+
648
+ while (1) {
649
+ int rc = trilogy_read_row(&ctx->conn, row_values);
650
+
651
+ if (rc == TRILOGY_AGAIN) {
652
+ if (trilogy_sock_wait_read(ctx->conn.socket) < 0) {
653
+ rb_syserr_fail(ETIMEDOUT, "trilogy_read_row");
654
+ }
655
+ continue;
656
+ }
657
+
658
+ if (rc == TRILOGY_EOF) {
659
+ break;
660
+ }
661
+
662
+ if (rc != TRILOGY_OK) {
663
+ return read_query_error(args, rc, "trilogy_read_row");
664
+ }
665
+
666
+ if (args->cast_options->flatten_rows) {
667
+ for (uint64_t i = 0; i < column_count; i++) {
668
+ rb_ary_push(rows, rb_trilogy_cast_value(row_values + i, column_info + i, args->cast_options));
669
+ }
670
+ } else {
671
+ VALUE row = rb_ary_new2(column_count);
672
+ for (uint64_t i = 0; i < column_count; i++) {
673
+ rb_ary_push(row, rb_trilogy_cast_value(row_values + i, column_info + i, args->cast_options));
674
+ }
675
+ rb_ary_push(rows, row);
676
+ }
677
+ }
678
+
679
+ if (ctx->conn.server_status & TRILOGY_SERVER_STATUS_MORE_RESULTS_EXISTS) {
680
+ rb_raise(rb_cTrilogyError, "MORE_RESULTS_EXIST");
681
+ }
682
+
683
+ return result;
684
+ }
685
+
686
+ static VALUE rb_trilogy_query(VALUE self, VALUE query)
687
+ {
688
+ struct trilogy_ctx *ctx = get_open_ctx(self);
689
+
690
+ StringValue(query);
691
+
692
+ struct rb_trilogy_cast_options cast_options;
693
+ load_query_options(ctx->query_flags, &cast_options);
694
+
695
+ struct read_query_state args = {
696
+ .cast_options = &cast_options,
697
+ .column_info = NULL,
698
+ .ctx = ctx,
699
+ .query = query,
700
+ .row_values = NULL,
701
+ .rc = TRILOGY_OK,
702
+ .msg = NULL,
703
+ };
704
+
705
+ int state = 0;
706
+ VALUE result = rb_protect(execute_read_query, (VALUE)&args, &state);
707
+
708
+ xfree(args.column_info);
709
+ xfree(args.row_values);
710
+
711
+ // If we have seen an unexpected exception, jump to it so it gets raised.
712
+ if (state) {
713
+ trilogy_sock_shutdown(ctx->conn.socket);
714
+ rb_jump_tag(state);
715
+ }
716
+
717
+ // Handle errors we can gracefully recover from here that were due to
718
+ // errors signaled at the protocol level, not unexpected exceptions.
719
+ if (result == Qundef) {
720
+ handle_trilogy_error(ctx, args.rc, args.msg);
721
+ }
722
+
723
+ return result;
724
+ }
725
+
726
+ static VALUE rb_trilogy_ping(VALUE self)
727
+ {
728
+ struct trilogy_ctx *ctx = get_open_ctx(self);
729
+
730
+ int rc = trilogy_ping_send(&ctx->conn);
731
+
732
+ if (rc == TRILOGY_AGAIN) {
733
+ rc = flush_writes(ctx);
734
+ }
735
+
736
+ if (rc < 0) {
737
+ handle_trilogy_error(ctx, rc, "trilogy_ping_send");
738
+ }
739
+
740
+ while (1) {
741
+ rc = trilogy_ping_recv(&ctx->conn);
742
+
743
+ if (rc == TRILOGY_OK) {
744
+ break;
745
+ }
746
+
747
+ if (rc != TRILOGY_AGAIN) {
748
+ handle_trilogy_error(ctx, rc, "trilogy_ping_recv");
749
+ }
750
+
751
+ if (trilogy_sock_wait_read(ctx->conn.socket) < 0) {
752
+ rb_syserr_fail(ETIMEDOUT, "trilogy_ping_recv");
753
+ }
754
+ }
755
+
756
+ return Qtrue;
757
+ }
758
+
759
+ static VALUE rb_trilogy_escape(VALUE self, VALUE str)
760
+ {
761
+ struct trilogy_ctx *ctx = get_open_ctx(self);
762
+ rb_encoding *str_enc = rb_enc_get(str);
763
+
764
+ StringValue(str);
765
+
766
+ if (!rb_enc_asciicompat(str_enc)) {
767
+ rb_raise(rb_eEncCompatError, "input string must be ASCII-compatible");
768
+ }
769
+
770
+ const char *escaped_str;
771
+ size_t escaped_len;
772
+
773
+ int rc = trilogy_escape(&ctx->conn, RSTRING_PTR(str), RSTRING_LEN(str), &escaped_str, &escaped_len);
774
+
775
+ if (rc < 0) {
776
+ handle_trilogy_error(ctx, rc, "trilogy_escape");
777
+ }
778
+
779
+ return rb_enc_str_new(escaped_str, escaped_len, str_enc);
780
+ }
781
+
782
+ static VALUE rb_trilogy_close(VALUE self)
783
+ {
784
+ struct trilogy_ctx *ctx = get_ctx(self);
785
+
786
+ if (ctx->conn.socket == NULL) {
787
+ return Qnil;
788
+ }
789
+
790
+ int rc = trilogy_close_send(&ctx->conn);
791
+
792
+ if (rc == TRILOGY_AGAIN) {
793
+ rc = flush_writes(ctx);
794
+ }
795
+
796
+ if (rc == TRILOGY_OK) {
797
+ while (1) {
798
+ rc = trilogy_close_recv(&ctx->conn);
799
+
800
+ if (rc != TRILOGY_AGAIN) {
801
+ break;
802
+ }
803
+
804
+ if (trilogy_sock_wait_read(ctx->conn.socket) < 0) {
805
+ // timed out
806
+ break;
807
+ }
808
+ }
809
+ }
810
+
811
+ trilogy_free(&ctx->conn);
812
+
813
+ return Qnil;
814
+ }
815
+
816
+ static VALUE rb_trilogy_last_insert_id(VALUE self) { return ULL2NUM(get_open_ctx(self)->conn.last_insert_id); }
817
+
818
+ static VALUE rb_trilogy_affected_rows(VALUE self) { return ULL2NUM(get_open_ctx(self)->conn.affected_rows); }
819
+
820
+ static VALUE rb_trilogy_warning_count(VALUE self) { return UINT2NUM(get_open_ctx(self)->conn.warning_count); }
821
+
822
+ static VALUE rb_trilogy_last_gtid(VALUE self)
823
+ {
824
+ struct trilogy_ctx *ctx = get_open_ctx(self);
825
+ if (ctx->conn.last_gtid_len > 0) {
826
+ return rb_str_new(ctx->conn.last_gtid, ctx->conn.last_gtid_len);
827
+ } else {
828
+ return Qnil;
829
+ }
830
+ }
831
+
832
+ static VALUE rb_trilogy_query_flags(VALUE self) { return UINT2NUM(get_ctx(self)->query_flags); }
833
+
834
+ static VALUE rb_trilogy_query_flags_set(VALUE self, VALUE query_flags)
835
+ {
836
+ return get_ctx(self)->query_flags = NUM2UINT(query_flags);
837
+ }
838
+
839
+ static VALUE rb_trilogy_server_status(VALUE self) { return LONG2FIX(get_open_ctx(self)->conn.server_status); }
840
+
841
+ static VALUE rb_trilogy_server_version(VALUE self) { return rb_str_new_cstr(get_open_ctx(self)->server_version); }
842
+
843
+ void Init_cext()
844
+ {
845
+ VALUE Trilogy = rb_define_class("Trilogy", rb_cObject);
846
+
847
+ rb_define_alloc_func(Trilogy, allocate_trilogy);
848
+
849
+ rb_define_method(Trilogy, "initialize", rb_trilogy_initialize, 1);
850
+ rb_define_method(Trilogy, "change_db", rb_trilogy_change_db, 1);
851
+ rb_define_method(Trilogy, "query", rb_trilogy_query, 1);
852
+ rb_define_method(Trilogy, "ping", rb_trilogy_ping, 0);
853
+ rb_define_method(Trilogy, "escape", rb_trilogy_escape, 1);
854
+ rb_define_method(Trilogy, "close", rb_trilogy_close, 0);
855
+ rb_define_method(Trilogy, "last_insert_id", rb_trilogy_last_insert_id, 0);
856
+ rb_define_method(Trilogy, "affected_rows", rb_trilogy_affected_rows, 0);
857
+ rb_define_method(Trilogy, "warning_count", rb_trilogy_warning_count, 0);
858
+ rb_define_method(Trilogy, "last_gtid", rb_trilogy_last_gtid, 0);
859
+ rb_define_method(Trilogy, "query_flags", rb_trilogy_query_flags, 0);
860
+ rb_define_method(Trilogy, "query_flags=", rb_trilogy_query_flags_set, 1);
861
+ rb_define_method(Trilogy, "server_status", rb_trilogy_server_status, 0);
862
+ rb_define_method(Trilogy, "server_version", rb_trilogy_server_version, 0);
863
+ rb_define_const(Trilogy, "TLS_VERSION_10", INT2NUM(TRILOGY_TLS_VERSION_10));
864
+ rb_define_const(Trilogy, "TLS_VERSION_11", INT2NUM(TRILOGY_TLS_VERSION_11));
865
+ rb_define_const(Trilogy, "TLS_VERSION_12", INT2NUM(TRILOGY_TLS_VERSION_12));
866
+ rb_define_const(Trilogy, "TLS_VERSION_13", INT2NUM(TRILOGY_TLS_VERSION_13));
867
+
868
+ rb_define_const(Trilogy, "SSL_DISABLED", INT2NUM(TRILOGY_SSL_DISABLED));
869
+ rb_define_const(Trilogy, "SSL_VERIFY_IDENTITY", INT2NUM(TRILOGY_SSL_VERIFY_IDENTITY));
870
+ rb_define_const(Trilogy, "SSL_VERIFY_CA", INT2NUM(TRILOGY_SSL_VERIFY_CA));
871
+ rb_define_const(Trilogy, "SSL_REQUIRED_NOVERIFY", INT2NUM(TRILOGY_SSL_REQUIRED_NOVERIFY));
872
+ rb_define_const(Trilogy, "SSL_PREFERRED_NOVERIFY", INT2NUM(TRILOGY_SSL_PREFERRED_NOVERIFY));
873
+
874
+ rb_define_const(Trilogy, "QUERY_FLAGS_NONE", INT2NUM(0));
875
+ rb_define_const(Trilogy, "QUERY_FLAGS_CAST", INT2NUM(TRILOGY_FLAGS_CAST));
876
+ rb_define_const(Trilogy, "QUERY_FLAGS_CAST_BOOLEANS", INT2NUM(TRILOGY_FLAGS_CAST_BOOLEANS));
877
+ rb_define_const(Trilogy, "QUERY_FLAGS_LOCAL_TIMEZONE", INT2NUM(TRILOGY_FLAGS_LOCAL_TIMEZONE));
878
+ rb_define_const(Trilogy, "QUERY_FLAGS_FLATTEN_ROWS", INT2NUM(TRILOGY_FLAGS_FLATTEN_ROWS));
879
+ rb_define_const(Trilogy, "QUERY_FLAGS_DEFAULT", INT2NUM(TRILOGY_FLAGS_DEFAULT));
880
+
881
+ rb_cTrilogyError = rb_define_class_under(Trilogy, "Error", rb_eStandardError);
882
+ rb_global_variable(&rb_cTrilogyError);
883
+
884
+ Trilogy_DatabaseError = rb_define_class_under(Trilogy, "DatabaseError", rb_cTrilogyError);
885
+ rb_global_variable(&Trilogy_DatabaseError);
886
+
887
+ rb_define_attr(Trilogy_DatabaseError, "error_code", 1, 0);
888
+ rb_define_attr(Trilogy_DatabaseError, "error_message", 1, 0);
889
+
890
+ Trilogy_Result = rb_define_class_under(Trilogy, "Result", rb_cObject);
891
+ rb_global_variable(&Trilogy_Result);
892
+
893
+ rb_define_attr(Trilogy_Result, "fields", 1, 0);
894
+ rb_define_attr(Trilogy_Result, "rows", 1, 0);
895
+ rb_define_attr(Trilogy_Result, "query_time", 1, 0);
896
+
897
+ id_socket = rb_intern("socket");
898
+ id_host = rb_intern("host");
899
+ id_port = rb_intern("port");
900
+ id_username = rb_intern("username");
901
+ id_password = rb_intern("password");
902
+ id_found_rows = rb_intern("found_rows");
903
+ id_connect_timeout = rb_intern("connect_timeout");
904
+ id_read_timeout = rb_intern("read_timeout");
905
+ id_write_timeout = rb_intern("write_timeout");
906
+ id_keepalive_enabled = rb_intern("keepalive_enabled");
907
+ id_keepalive_idle = rb_intern("keepalive_idle");
908
+ id_keepalive_count = rb_intern("keepalive_count");
909
+ id_keepalive_interval = rb_intern("keepalive_interval");
910
+ id_database = rb_intern("database");
911
+ id_ssl_ca = rb_intern("ssl_ca");
912
+ id_ssl_capath = rb_intern("ssl_capath");
913
+ id_ssl_cert = rb_intern("ssl_cert");
914
+ id_ssl_cipher = rb_intern("ssl_cipher");
915
+ id_ssl_crl = rb_intern("ssl_crl");
916
+ id_ssl_crlpath = rb_intern("ssl_crlpath");
917
+ id_ssl_key = rb_intern("ssl_key");
918
+ id_ssl_mode = rb_intern("ssl_mode");
919
+ id_tls_ciphersuites = rb_intern("tls_ciphersuites");
920
+ id_tls_min_version = rb_intern("tls_min_version");
921
+ id_tls_max_version = rb_intern("tls_max_version");
922
+
923
+ id_ivar_fields = rb_intern("@fields");
924
+ id_ivar_rows = rb_intern("@rows");
925
+ id_ivar_query_time = rb_intern("@query_time");
926
+
927
+ rb_trilogy_cast_init();
928
+
929
+ // server_status flags
930
+ #define XX(name, code) rb_const_set(Trilogy, rb_intern(#name + strlen("TRILOGY_")), LONG2NUM(name));
931
+ TRILOGY_SERVER_STATUS(XX)
932
+ #undef XX
933
+ }