trilogy 2.9.0 → 2.12.4
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 +4 -4
- data/README.md +1 -1
- data/Rakefile +16 -1
- data/ext/trilogy-ruby/cast.c +181 -55
- data/ext/trilogy-ruby/cext.c +337 -125
- data/ext/trilogy-ruby/extconf.rb +15 -3
- data/ext/trilogy-ruby/inc/trilogy/allocator.h +61 -0
- data/ext/trilogy-ruby/inc/trilogy/buffer.h +13 -0
- data/ext/trilogy-ruby/inc/trilogy/client.h +11 -0
- data/ext/trilogy-ruby/inc/trilogy/error.h +2 -1
- data/ext/trilogy-ruby/inc/trilogy/socket.h +2 -0
- data/ext/trilogy-ruby/inc/trilogy.h +1 -0
- data/ext/trilogy-ruby/src/buffer.c +25 -5
- data/ext/trilogy-ruby/src/client.c +425 -99
- data/ext/trilogy-ruby/src/packet_parser.c +1 -1
- data/ext/trilogy-ruby/src/socket.c +39 -29
- data/ext/trilogy-ruby/trilogy-ruby.h +4 -2
- data/ext/trilogy-ruby/trilogy_xallocator.h +1 -0
- data/lib/trilogy/encoding.rb +2 -0
- data/lib/trilogy/error.rb +7 -0
- data/lib/trilogy/result.rb +18 -0
- data/lib/trilogy/version.rb +1 -1
- data/lib/trilogy.rb +81 -1
- data/trilogy.gemspec +3 -2
- metadata +12 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f4205f6f8754fd3b29bbf4d5ae3330e15b719565bbc9ab46039946cba683c1b
|
|
4
|
+
data.tar.gz: 65a8b6472442d8568b195fd45a61c0c337ef8507fa6528ebc1babf4e15ada1df
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3fd1f19a93dd462a532cf0d2afcea6ac9b0949da83bc51569c5de9f5eb31c681e4214c16caa8e5779a3a7d14996fd3f3bbdc363bde875899458584d84420fecc
|
|
7
|
+
data.tar.gz: 17a7c0129f4cf5acfcc7f2b85ca305506f87548660ad2419f973ca45f1bbd81b45f52df9255b628f4914ec7a749570329941b3d7fbb83546509780cca1843aba
|
data/README.md
CHANGED
data/Rakefile
CHANGED
|
@@ -19,10 +19,25 @@ Rake::TestTask.new do |t|
|
|
|
19
19
|
t.test_files = FileList['test/*_test.rb']
|
|
20
20
|
t.verbose = true
|
|
21
21
|
end
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
task :test => [:compile, "db:clean"]
|
|
23
24
|
|
|
24
25
|
task :default => :test
|
|
25
26
|
|
|
26
27
|
task :console => :compile do
|
|
27
28
|
sh "ruby -I lib -r trilogy -S irb"
|
|
28
29
|
end
|
|
30
|
+
|
|
31
|
+
namespace :db do
|
|
32
|
+
task :create do
|
|
33
|
+
mysql_command = "mysql -uroot -e"
|
|
34
|
+
%x( #{mysql_command} "create DATABASE IF NOT EXISTS test DEFAULT CHARACTER SET utf8mb4" )
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
task :drop do
|
|
38
|
+
mysql_command = "mysql -uroot -e"
|
|
39
|
+
%x( #{mysql_command} "drop DATABASE IF EXISTS test" )
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
task :clean => ["db:drop", "db:create"]
|
|
43
|
+
end
|
data/ext/trilogy-ruby/cast.c
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#include <ruby.h>
|
|
2
2
|
#include <ruby/encoding.h>
|
|
3
|
-
|
|
4
|
-
#include <
|
|
3
|
+
#include <time.h>
|
|
4
|
+
#include <limits.h>
|
|
5
5
|
|
|
6
6
|
#include "trilogy-ruby.h"
|
|
7
7
|
|
|
8
8
|
#define CAST_STACK_SIZE 64
|
|
9
9
|
|
|
10
|
-
static ID id_BigDecimal, id_Integer, id_new, id_local
|
|
10
|
+
static ID id_BigDecimal, id_Integer, id_new, id_local;
|
|
11
11
|
|
|
12
12
|
static const char *ruby_encoding_name_map[] = {
|
|
13
13
|
[TRILOGY_ENCODING_ARMSCII8] = NULL,
|
|
@@ -54,17 +54,20 @@ static const char *ruby_encoding_name_map[] = {
|
|
|
54
54
|
[TRILOGY_ENCODING_MAX] = NULL,
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
static
|
|
57
|
+
static rb_encoding * encoding_for_charset(TRILOGY_CHARSET_t charset)
|
|
58
58
|
{
|
|
59
|
-
static
|
|
59
|
+
static rb_encoding * map[TRILOGY_CHARSET_MAX];
|
|
60
60
|
|
|
61
|
-
if (map[charset]) {
|
|
61
|
+
if (RB_LIKELY(map[charset])) {
|
|
62
62
|
return map[charset];
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const char *encoding_name = ruby_encoding_name_map[trilogy_encoding_from_charset(charset)];
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
map[charset] = rb_enc_find(encoding_name);
|
|
67
|
+
if (!map[charset]) {
|
|
68
|
+
map[charset] = rb_ascii8bit_encoding();
|
|
69
|
+
}
|
|
70
|
+
return map[charset];
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
static void cstr_from_value(char *buf, const trilogy_value_t *value, const char *errmsg)
|
|
@@ -78,6 +81,89 @@ static void cstr_from_value(char *buf, const trilogy_value_t *value, const char
|
|
|
78
81
|
buf[value->data_len] = 0;
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
// For UTC: uses the Hinnant civil_to_days algorithm (C++20 std::chrono
|
|
85
|
+
// foundation, handles the full MySQL 1000-9999 year range without timegm).
|
|
86
|
+
// For local: uses mktime (standard C) which consults the system timezone.
|
|
87
|
+
// http://howardhinnant.github.io/date_algorithms.html
|
|
88
|
+
static time_t civil_to_epoch_utc(int year, int month, int day, int hour, int min, int sec)
|
|
89
|
+
{
|
|
90
|
+
year -= (month <= 2);
|
|
91
|
+
int era = (year >= 0 ? year : year - 399) / 400;
|
|
92
|
+
unsigned yoe = (unsigned)(year - era * 400);
|
|
93
|
+
unsigned doy = (153 * (month > 2 ? month - 3 : month + 9) + 2) / 5 + (unsigned)day - 1;
|
|
94
|
+
unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
|
95
|
+
long days = (long)era * 146097 + (long)doe - 719468;
|
|
96
|
+
return (time_t)(days * 86400 + hour * 3600 + min * 60 + sec);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static VALUE trilogy_make_time(int year, int month, int day, int hour, int min, int sec,
|
|
100
|
+
int usec, int local)
|
|
101
|
+
{
|
|
102
|
+
if (local) {
|
|
103
|
+
return rb_funcall(
|
|
104
|
+
rb_cTime, id_local, 7,
|
|
105
|
+
INT2NUM(year), INT2NUM(month), INT2NUM(day),
|
|
106
|
+
INT2NUM(hour), INT2NUM(min), INT2NUM(sec),
|
|
107
|
+
INT2NUM(usec)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
struct timespec ts = {
|
|
112
|
+
.tv_sec = civil_to_epoch_utc(year, month, day, hour, min, sec),
|
|
113
|
+
.tv_nsec = (long)usec * 1000,
|
|
114
|
+
};
|
|
115
|
+
return rb_time_timespec_new(&ts, INT_MAX - 1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Byte-arithmetic datetime parsing helpers (inspired by Go's go-sql-driver/mysql)
|
|
119
|
+
// These avoid sscanf overhead by parsing ASCII digits directly.
|
|
120
|
+
|
|
121
|
+
static inline int byte_to_digit(const char b)
|
|
122
|
+
{
|
|
123
|
+
if (b < '0' || b > '9')
|
|
124
|
+
return -1;
|
|
125
|
+
return b - '0';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static inline int parse_2digits(const char *p)
|
|
129
|
+
{
|
|
130
|
+
int d1 = byte_to_digit(p[0]);
|
|
131
|
+
int d2 = byte_to_digit(p[1]);
|
|
132
|
+
if (d1 < 0 || d2 < 0)
|
|
133
|
+
return -1;
|
|
134
|
+
return d1 * 10 + d2;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static inline int parse_4digits(const char *p)
|
|
138
|
+
{
|
|
139
|
+
int d0 = byte_to_digit(p[0]);
|
|
140
|
+
int d1 = byte_to_digit(p[1]);
|
|
141
|
+
int d2 = byte_to_digit(p[2]);
|
|
142
|
+
int d3 = byte_to_digit(p[3]);
|
|
143
|
+
if (d0 < 0 || d1 < 0 || d2 < 0 || d3 < 0)
|
|
144
|
+
return -1;
|
|
145
|
+
return d0 * 1000 + d1 * 100 + d2 * 10 + d3;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Parse 1-6 fractional digits into microseconds (6-digit value).
|
|
149
|
+
// "1" => 100000
|
|
150
|
+
// "12" => 120000
|
|
151
|
+
// "123" => 123000
|
|
152
|
+
// "123456" => 123456
|
|
153
|
+
static inline int parse_microseconds(const char *p, size_t len)
|
|
154
|
+
{
|
|
155
|
+
int usec = 0;
|
|
156
|
+
int multiplier = 100000;
|
|
157
|
+
for (size_t i = 0; i < len && i < 6; i++) {
|
|
158
|
+
int d = byte_to_digit(p[i]);
|
|
159
|
+
if (d < 0)
|
|
160
|
+
return -1;
|
|
161
|
+
usec += d * multiplier;
|
|
162
|
+
multiplier /= 10;
|
|
163
|
+
}
|
|
164
|
+
return usec;
|
|
165
|
+
}
|
|
166
|
+
|
|
81
167
|
static unsigned long long ull_from_buf(const char *digits, size_t len)
|
|
82
168
|
{
|
|
83
169
|
if (!len)
|
|
@@ -166,16 +252,54 @@ rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *co
|
|
|
166
252
|
}
|
|
167
253
|
case TRILOGY_TYPE_TIMESTAMP:
|
|
168
254
|
case TRILOGY_TYPE_DATETIME: {
|
|
169
|
-
|
|
170
|
-
|
|
255
|
+
const char *p = (const char *)value->data;
|
|
256
|
+
size_t len = value->data_len;
|
|
257
|
+
int year, month, day, hour = 0, min = 0, sec = 0, usec = 0;
|
|
258
|
+
|
|
259
|
+
// Length-based dispatch like Go's parseDateTime:
|
|
260
|
+
// 10 = "YYYY-MM-DD"
|
|
261
|
+
// 19 = "YYYY-MM-DD HH:MM:SS"
|
|
262
|
+
// 21-26 = "YYYY-MM-DD HH:MM:SS.F" through "YYYY-MM-DD HH:MM:SS.FFFFFF"
|
|
263
|
+
if (len < 10)
|
|
264
|
+
return Qnil;
|
|
171
265
|
|
|
172
|
-
|
|
173
|
-
|
|
266
|
+
year = parse_4digits(p);
|
|
267
|
+
if (year < 0 || p[4] != '-')
|
|
268
|
+
return Qnil;
|
|
269
|
+
|
|
270
|
+
month = parse_2digits(p + 5);
|
|
271
|
+
if (month < 0 || p[7] != '-')
|
|
272
|
+
return Qnil;
|
|
273
|
+
|
|
274
|
+
day = parse_2digits(p + 8);
|
|
275
|
+
if (day < 0)
|
|
276
|
+
return Qnil;
|
|
277
|
+
|
|
278
|
+
if (len >= 19) {
|
|
279
|
+
if (p[10] != ' ')
|
|
280
|
+
return Qnil;
|
|
174
281
|
|
|
175
|
-
|
|
282
|
+
hour = parse_2digits(p + 11);
|
|
283
|
+
if (hour < 0 || p[13] != ':')
|
|
284
|
+
return Qnil;
|
|
176
285
|
|
|
177
|
-
|
|
178
|
-
|
|
286
|
+
min = parse_2digits(p + 14);
|
|
287
|
+
if (min < 0 || p[16] != ':')
|
|
288
|
+
return Qnil;
|
|
289
|
+
|
|
290
|
+
sec = parse_2digits(p + 17);
|
|
291
|
+
if (sec < 0)
|
|
292
|
+
return Qnil;
|
|
293
|
+
|
|
294
|
+
if (len > 19) {
|
|
295
|
+
if (p[19] != '.' || len < 21 || len > 26)
|
|
296
|
+
return Qnil;
|
|
297
|
+
|
|
298
|
+
usec = parse_microseconds(p + 20, len - 20);
|
|
299
|
+
if (usec < 0)
|
|
300
|
+
return Qnil;
|
|
301
|
+
}
|
|
302
|
+
} else if (len != 10) {
|
|
179
303
|
return Qnil;
|
|
180
304
|
}
|
|
181
305
|
|
|
@@ -187,28 +311,29 @@ rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *co
|
|
|
187
311
|
rb_raise(Trilogy_CastError, "Invalid date: %.*s", (int)value->data_len, (char *)value->data);
|
|
188
312
|
}
|
|
189
313
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
for (size_t i = strlen(msec_char); i < sizeof(msec_char) - 1; i++) {
|
|
193
|
-
msec_char[i] = '0';
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return rb_funcall(rb_cTime, options->database_local_time ? id_local : id_utc, 7, INT2NUM(year),
|
|
197
|
-
INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec),
|
|
198
|
-
INT2NUM(atoi(msec_char)));
|
|
314
|
+
return trilogy_make_time(year, month, day, hour, min, sec, usec,
|
|
315
|
+
options->database_local_time);
|
|
199
316
|
}
|
|
200
317
|
case TRILOGY_TYPE_DATE: {
|
|
201
|
-
|
|
318
|
+
const char *p = (const char *)value->data;
|
|
319
|
+
size_t len = value->data_len;
|
|
202
320
|
|
|
203
|
-
|
|
204
|
-
|
|
321
|
+
if (len != 10)
|
|
322
|
+
return Qnil;
|
|
205
323
|
|
|
206
|
-
int
|
|
207
|
-
|
|
324
|
+
int year = parse_4digits(p);
|
|
325
|
+
if (year < 0 || p[4] != '-')
|
|
326
|
+
return Qnil;
|
|
208
327
|
|
|
209
|
-
|
|
328
|
+
int month = parse_2digits(p + 5);
|
|
329
|
+
if (month < 0 || p[7] != '-')
|
|
210
330
|
return Qnil;
|
|
211
|
-
|
|
331
|
+
|
|
332
|
+
int day = parse_2digits(p + 8);
|
|
333
|
+
if (day < 0)
|
|
334
|
+
return Qnil;
|
|
335
|
+
|
|
336
|
+
VALUE Date = rb_const_get(rb_cObject, rb_intern("Date"));
|
|
212
337
|
|
|
213
338
|
if (year == 0 && month == 0 && day == 0) {
|
|
214
339
|
return Qnil;
|
|
@@ -221,26 +346,37 @@ rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *co
|
|
|
221
346
|
return rb_funcall(Date, id_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
|
|
222
347
|
}
|
|
223
348
|
case TRILOGY_TYPE_TIME: {
|
|
224
|
-
|
|
225
|
-
|
|
349
|
+
const char *p = (const char *)value->data;
|
|
350
|
+
size_t len = value->data_len;
|
|
226
351
|
|
|
227
|
-
|
|
228
|
-
|
|
352
|
+
// Expected: "HH:MM:SS" (8) or "HH:MM:SS.F" through "HH:MM:SS.FFFFFF" (10-15)
|
|
353
|
+
if (len < 8)
|
|
354
|
+
return Qnil;
|
|
229
355
|
|
|
230
|
-
int
|
|
356
|
+
int hour = parse_2digits(p);
|
|
357
|
+
if (hour < 0 || p[2] != ':')
|
|
358
|
+
return Qnil;
|
|
231
359
|
|
|
232
|
-
|
|
360
|
+
int min = parse_2digits(p + 3);
|
|
361
|
+
if (min < 0 || p[5] != ':')
|
|
233
362
|
return Qnil;
|
|
234
|
-
}
|
|
235
363
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
364
|
+
int sec = parse_2digits(p + 6);
|
|
365
|
+
if (sec < 0)
|
|
366
|
+
return Qnil;
|
|
367
|
+
|
|
368
|
+
int usec = 0;
|
|
369
|
+
if (len > 8) {
|
|
370
|
+
if (p[8] != '.' || len < 10 || len > 15)
|
|
371
|
+
return Qnil;
|
|
372
|
+
|
|
373
|
+
usec = parse_microseconds(p + 9, len - 9);
|
|
374
|
+
if (usec < 0)
|
|
375
|
+
return Qnil;
|
|
240
376
|
}
|
|
241
377
|
|
|
242
|
-
return
|
|
243
|
-
|
|
378
|
+
return trilogy_make_time(2000, 1, 1, hour, min, sec, usec,
|
|
379
|
+
options->database_local_time);
|
|
244
380
|
}
|
|
245
381
|
default:
|
|
246
382
|
break;
|
|
@@ -248,15 +384,7 @@ rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *co
|
|
|
248
384
|
}
|
|
249
385
|
|
|
250
386
|
// for all other types, just return a string
|
|
251
|
-
|
|
252
|
-
VALUE str = rb_str_new(value->data, value->data_len);
|
|
253
|
-
|
|
254
|
-
int encoding_index = encoding_for_charset(column->charset);
|
|
255
|
-
if (encoding_index != -1) {
|
|
256
|
-
rb_enc_associate_index(str, encoding_index);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return str;
|
|
387
|
+
return rb_enc_str_new(value->data, value->data_len, encoding_for_charset(column->charset));
|
|
260
388
|
}
|
|
261
389
|
|
|
262
390
|
void rb_trilogy_cast_init(void)
|
|
@@ -268,6 +396,4 @@ void rb_trilogy_cast_init(void)
|
|
|
268
396
|
id_Integer = rb_intern("Integer");
|
|
269
397
|
id_new = rb_intern("new");
|
|
270
398
|
id_local = rb_intern("local");
|
|
271
|
-
id_localtime = rb_intern("localtime");
|
|
272
|
-
id_utc = rb_intern("utc");
|
|
273
399
|
}
|