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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 998b06e77dcb78b2f30307834490a7c9c7d61422dea586f646cead7b74467058
4
- data.tar.gz: e5bf1ecf9e8b2b675db0d3c3ee7531db4ab022aa725c4ed0c16db088884c6075
3
+ metadata.gz: 9f4205f6f8754fd3b29bbf4d5ae3330e15b719565bbc9ab46039946cba683c1b
4
+ data.tar.gz: 65a8b6472442d8568b195fd45a61c0c337ef8507fa6528ebc1babf4e15ada1df
5
5
  SHA512:
6
- metadata.gz: bd2acff1c38bbed9bea64f3fbb9ad474e4d42198dccbcdca0aa1b8ba3b52e35711f1c9e2e1a6859c15a71c38ed08ba06b5adcb5fbb5054b1b4264fd51ae0933f
7
- data.tar.gz: e032a04cfb901d48a4e1e711cd576c380562f2b7f871724d06568dff8bd3b599fd3ea86360b3bcbe2f8f9471f986d6b1cc8f8d8e69309877778d1ce442dd65f3
6
+ metadata.gz: 3fd1f19a93dd462a532cf0d2afcea6ac9b0949da83bc51569c5de9f5eb31c681e4214c16caa8e5779a3a7d14996fd3f3bbdc363bde875899458584d84420fecc
7
+ data.tar.gz: 17a7c0129f4cf5acfcc7f2b85ca305506f87548660ad2419f973ca45f1bbd81b45f52df9255b628f4914ec7a749570329941b3d7fbb83546509780cca1843aba
data/README.md CHANGED
@@ -13,7 +13,7 @@ gem 'trilogy'
13
13
  And then execute:
14
14
 
15
15
  ```
16
- $ bundle
16
+ $ bundle install
17
17
  ```
18
18
 
19
19
  Or install it yourself as:
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
- task :test => :compile
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
@@ -1,13 +1,13 @@
1
1
  #include <ruby.h>
2
2
  #include <ruby/encoding.h>
3
-
4
- #include <trilogy.h>
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, id_localtime, id_utc;
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 int encoding_for_charset(TRILOGY_CHARSET_t charset)
57
+ static rb_encoding * encoding_for_charset(TRILOGY_CHARSET_t charset)
58
58
  {
59
- static int map[TRILOGY_CHARSET_MAX];
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
- return map[charset] = (encoding_name ? rb_enc_find_index(encoding_name) : -1);
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
- int year, month, day, hour, min, sec;
170
- char msec_char[7] = {0};
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
- char cstr[CAST_STACK_SIZE];
173
- cstr_from_value(cstr, value, "Invalid date: %.*s");
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
- int tokens = sscanf(cstr, "%4u-%2u-%2u %2u:%2u:%2u.%6s", &year, &month, &day, &hour, &min, &sec, msec_char);
282
+ hour = parse_2digits(p + 11);
283
+ if (hour < 0 || p[13] != ':')
284
+ return Qnil;
176
285
 
177
- // msec might not be present, so check for 6 tokens rather than 7
178
- if (tokens < 6) {
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
- // pad out msec_char with zeroes at the end as it could be at any
191
- // level of precision
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
- int year, month, day;
318
+ const char *p = (const char *)value->data;
319
+ size_t len = value->data_len;
202
320
 
203
- char cstr[CAST_STACK_SIZE];
204
- cstr_from_value(cstr, value, "Invalid date: %.*s");
321
+ if (len != 10)
322
+ return Qnil;
205
323
 
206
- int tokens = sscanf(cstr, "%4u-%2u-%2u", &year, &month, &day);
207
- VALUE Date = rb_const_get(rb_cObject, rb_intern("Date"));
324
+ int year = parse_4digits(p);
325
+ if (year < 0 || p[4] != '-')
326
+ return Qnil;
208
327
 
209
- if (tokens < 3) {
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
- int hour, min, sec;
225
- char msec_char[7] = {0};
349
+ const char *p = (const char *)value->data;
350
+ size_t len = value->data_len;
226
351
 
227
- char cstr[CAST_STACK_SIZE];
228
- cstr_from_value(cstr, value, "Invalid time: %.*s");
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 tokens = sscanf(cstr, "%2u:%2u:%2u.%6s", &hour, &min, &sec, msec_char);
356
+ int hour = parse_2digits(p);
357
+ if (hour < 0 || p[2] != ':')
358
+ return Qnil;
231
359
 
232
- if (tokens < 3) {
360
+ int min = parse_2digits(p + 3);
361
+ if (min < 0 || p[5] != ':')
233
362
  return Qnil;
234
- }
235
363
 
236
- // pad out msec_char with zeroes at the end as it could be at any
237
- // level of precision
238
- for (size_t i = strlen(msec_char); i < sizeof(msec_char) - 1; i++) {
239
- msec_char[i] = '0';
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 rb_funcall(rb_cTime, options->database_local_time ? id_local : id_utc, 7, INT2NUM(2000), INT2NUM(1),
243
- INT2NUM(1), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(atoi(msec_char)));
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
  }