ch_connect 0.2.2 → 0.3.0
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/.standard.yml +5 -0
- data/CHANGELOG.md +10 -1
- data/README.md +156 -87
- data/ext/ch_connect_native/ch_connect_native.c +1001 -0
- data/ext/ch_connect_native/extconf.rb +32 -0
- data/lib/ch_connect/config.rb +60 -19
- data/lib/ch_connect/connection.rb +382 -13
- data/lib/ch_connect/response.rb +0 -8
- data/lib/ch_connect/version.rb +1 -1
- data/lib/ch_connect.rb +14 -11
- data/vendor/clickhouse-c/LICENSE +203 -0
- data/vendor/clickhouse-c/VENDOR.md +24 -0
- data/vendor/clickhouse-c/clickhouse-async.h +301 -0
- data/vendor/clickhouse-c/clickhouse-client.h +995 -0
- data/vendor/clickhouse-c/clickhouse-compression.h +634 -0
- data/vendor/clickhouse-c/clickhouse.h +3394 -0
- metadata +19 -14
- data/lib/ch_connect/body_reader.rb +0 -79
- data/lib/ch_connect/http_transport.rb +0 -59
- data/lib/ch_connect/native_format_parser.rb +0 -405
- data/lib/ch_connect/transport_result.rb +0 -12
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ChConnect::NativeClient — ioless ClickHouse native-protocol state machine
|
|
3
|
+
* backed by the vendored clickhouse-c header-only library.
|
|
4
|
+
*
|
|
5
|
+
* This extension performs NO I/O: Ruby owns the socket (and TLS) and pumps
|
|
6
|
+
* bytes through the state machine:
|
|
7
|
+
* take_output -> bytes to write to the socket
|
|
8
|
+
* feed(bytes) -> submit bytes read from the socket
|
|
9
|
+
* handshake_step / recv_step -> :done | :want_read
|
|
10
|
+
* All calls are short and non-blocking, so the GVL is never released and
|
|
11
|
+
* interrupts can only fire between calls. Result blocks are decoded in C
|
|
12
|
+
* into Ruby objects used by ChConnect::Response.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
#include <ruby.h>
|
|
16
|
+
#include <ruby/encoding.h>
|
|
17
|
+
#include <ruby/intern.h>
|
|
18
|
+
|
|
19
|
+
#include <limits.h>
|
|
20
|
+
#include <string.h>
|
|
21
|
+
#include <time.h>
|
|
22
|
+
|
|
23
|
+
/* CHC_NO_LZ4 / CHC_NO_ZSTD / CHC_EXT_HAVE_* are set by extconf.rb based on
|
|
24
|
+
* which libraries are available at build time. */
|
|
25
|
+
#define CHC_IMPLEMENTATION
|
|
26
|
+
#define CHC_PROVIDE_STDLIB_ALLOC
|
|
27
|
+
#include "clickhouse.h"
|
|
28
|
+
#include "clickhouse-compression.h"
|
|
29
|
+
#include "clickhouse-client.h"
|
|
30
|
+
#include "clickhouse-async.h"
|
|
31
|
+
|
|
32
|
+
static VALUE cNativeClient;
|
|
33
|
+
static VALUE eQueryError;
|
|
34
|
+
static VALUE eConnectionError;
|
|
35
|
+
static VALUE eUnsupportedTypeError;
|
|
36
|
+
static VALUE cIPAddr;
|
|
37
|
+
static VALUE cDate;
|
|
38
|
+
static VALUE vAF_INET;
|
|
39
|
+
static VALUE vAF_INET6;
|
|
40
|
+
|
|
41
|
+
static ID id_jd;
|
|
42
|
+
static ID id_new;
|
|
43
|
+
static ID id_pow;
|
|
44
|
+
static ID id_div;
|
|
45
|
+
static ID id_BigDecimal;
|
|
46
|
+
|
|
47
|
+
static VALUE sym_read_rows;
|
|
48
|
+
static VALUE sym_read_bytes;
|
|
49
|
+
static VALUE sym_written_rows;
|
|
50
|
+
static VALUE sym_written_bytes;
|
|
51
|
+
static VALUE sym_total_rows_to_read;
|
|
52
|
+
static VALUE sym_result_rows;
|
|
53
|
+
static VALUE sym_result_bytes;
|
|
54
|
+
static VALUE sym_done;
|
|
55
|
+
static VALUE sym_want_read;
|
|
56
|
+
static VALUE sym_lz4;
|
|
57
|
+
static VALUE sym_zstd;
|
|
58
|
+
|
|
59
|
+
/* Julian day number of 1970-01-01 (Date.jd(2440588) == Date.new(1970, 1, 1)) */
|
|
60
|
+
#define UNIX_EPOCH_JD 2440588
|
|
61
|
+
|
|
62
|
+
/* One immutable codec shared by all connections and threads: the built-in
|
|
63
|
+
* adapters are stateless wrappers over the one-shot lz4/zstd functions. Both
|
|
64
|
+
* slot pairs are filled (when available) so decode survives a server-side
|
|
65
|
+
* `SET network_compression_method` different from ours. */
|
|
66
|
+
static chc_codec g_codec;
|
|
67
|
+
|
|
68
|
+
typedef enum {
|
|
69
|
+
NATIVE_READY = 0,
|
|
70
|
+
NATIVE_ACTIVE,
|
|
71
|
+
NATIVE_BROKEN,
|
|
72
|
+
} native_state;
|
|
73
|
+
|
|
74
|
+
/* --- column decoding ---------------------------------------------------- */
|
|
75
|
+
|
|
76
|
+
static VALUE decode_column(const chc_column *col, const chc_type *t, long n_rows, native_state *state);
|
|
77
|
+
|
|
78
|
+
NORETURN(static void raise_unsupported_type(const chc_type *t, native_state *state));
|
|
79
|
+
|
|
80
|
+
static void
|
|
81
|
+
raise_unsupported_type(const chc_type *t, native_state *state)
|
|
82
|
+
{
|
|
83
|
+
size_t len = 0;
|
|
84
|
+
const char *name = chc_type_name(t, &len);
|
|
85
|
+
*state = NATIVE_BROKEN;
|
|
86
|
+
rb_raise(eUnsupportedTypeError, "Unsupported column type: %.*s", (int)len, name);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Native-format fixed-width values are always little-endian. Decode them
|
|
90
|
+
* explicitly instead of relying on the host's byte order. */
|
|
91
|
+
static inline uint16_t
|
|
92
|
+
load_u16le(const uint8_t *p)
|
|
93
|
+
{
|
|
94
|
+
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static inline uint32_t
|
|
98
|
+
load_u32le(const uint8_t *p)
|
|
99
|
+
{
|
|
100
|
+
return (uint32_t)p[0]
|
|
101
|
+
| ((uint32_t)p[1] << 8)
|
|
102
|
+
| ((uint32_t)p[2] << 16)
|
|
103
|
+
| ((uint32_t)p[3] << 24);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static inline uint64_t
|
|
107
|
+
load_u64le(const uint8_t *p)
|
|
108
|
+
{
|
|
109
|
+
return (uint64_t)load_u32le(p) | ((uint64_t)load_u32le(p + 4) << 32);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static inline int16_t
|
|
113
|
+
load_i16le(const uint8_t *p)
|
|
114
|
+
{
|
|
115
|
+
uint16_t u = load_u16le(p);
|
|
116
|
+
int16_t v;
|
|
117
|
+
memcpy(&v, &u, sizeof v);
|
|
118
|
+
return v;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static inline int32_t
|
|
122
|
+
load_i32le(const uint8_t *p)
|
|
123
|
+
{
|
|
124
|
+
uint32_t u = load_u32le(p);
|
|
125
|
+
int32_t v;
|
|
126
|
+
memcpy(&v, &u, sizeof v);
|
|
127
|
+
return v;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static inline int64_t
|
|
131
|
+
load_i64le(const uint8_t *p)
|
|
132
|
+
{
|
|
133
|
+
uint64_t u = load_u64le(p);
|
|
134
|
+
int64_t v;
|
|
135
|
+
memcpy(&v, &u, sizeof v);
|
|
136
|
+
return v;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static inline float
|
|
140
|
+
load_f32le(const uint8_t *p)
|
|
141
|
+
{
|
|
142
|
+
uint32_t bits = load_u32le(p);
|
|
143
|
+
float v;
|
|
144
|
+
memcpy(&v, &bits, sizeof v);
|
|
145
|
+
return v;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static inline double
|
|
149
|
+
load_f64le(const uint8_t *p)
|
|
150
|
+
{
|
|
151
|
+
uint64_t bits = load_u64le(p);
|
|
152
|
+
double v;
|
|
153
|
+
memcpy(&v, &bits, sizeof v);
|
|
154
|
+
return v;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* FIXED-layout leaf decoding, dispatched on logical kind */
|
|
158
|
+
static VALUE
|
|
159
|
+
decode_fixed(const chc_column *col, const chc_type *t, long n_rows, native_state *state)
|
|
160
|
+
{
|
|
161
|
+
size_t es = 0;
|
|
162
|
+
const uint8_t *data = n_rows > 0
|
|
163
|
+
? (const uint8_t *)chc_column_fixed_data(col, &es)
|
|
164
|
+
: NULL;
|
|
165
|
+
VALUE ary = rb_ary_new_capa(n_rows);
|
|
166
|
+
chc_kind kind = chc_type_kind(t);
|
|
167
|
+
|
|
168
|
+
switch (kind) {
|
|
169
|
+
case CHC_UINT8: {
|
|
170
|
+
for (long i = 0; i < n_rows; i++) rb_ary_push(ary, INT2FIX(data[i]));
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case CHC_BOOL: {
|
|
174
|
+
for (long i = 0; i < n_rows; i++) rb_ary_push(ary, data[i] == 1 ? Qtrue : Qfalse);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case CHC_INT8:
|
|
178
|
+
case CHC_ENUM8: {
|
|
179
|
+
for (long i = 0; i < n_rows; i++) rb_ary_push(ary, INT2FIX((int8_t)data[i]));
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case CHC_UINT16: {
|
|
183
|
+
for (long i = 0; i < n_rows; i++) {
|
|
184
|
+
uint16_t v = load_u16le(data + i * 2);
|
|
185
|
+
rb_ary_push(ary, INT2FIX(v));
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case CHC_INT16:
|
|
190
|
+
case CHC_ENUM16: {
|
|
191
|
+
for (long i = 0; i < n_rows; i++) {
|
|
192
|
+
int16_t v = load_i16le(data + i * 2);
|
|
193
|
+
rb_ary_push(ary, INT2FIX(v));
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case CHC_UINT32: {
|
|
198
|
+
for (long i = 0; i < n_rows; i++) {
|
|
199
|
+
uint32_t v = load_u32le(data + i * 4);
|
|
200
|
+
rb_ary_push(ary, UINT2NUM(v));
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case CHC_INT32: {
|
|
205
|
+
for (long i = 0; i < n_rows; i++) {
|
|
206
|
+
int32_t v = load_i32le(data + i * 4);
|
|
207
|
+
rb_ary_push(ary, INT2NUM(v));
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case CHC_UINT64: {
|
|
212
|
+
for (long i = 0; i < n_rows; i++) rb_ary_push(ary, ULL2NUM(load_u64le(data + i * 8)));
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case CHC_INT64:
|
|
216
|
+
{
|
|
217
|
+
for (long i = 0; i < n_rows; i++) {
|
|
218
|
+
int64_t v = load_i64le(data + i * 8);
|
|
219
|
+
rb_ary_push(ary, LL2NUM(v));
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case CHC_UINT128:
|
|
224
|
+
case CHC_UINT256: {
|
|
225
|
+
size_t nbytes = (kind == CHC_UINT128) ? 16 : 32;
|
|
226
|
+
for (long i = 0; i < n_rows; i++) {
|
|
227
|
+
rb_ary_push(ary, rb_integer_unpack(data + i * nbytes, nbytes, 1, 0,
|
|
228
|
+
INTEGER_PACK_LITTLE_ENDIAN));
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case CHC_INT128:
|
|
233
|
+
case CHC_INT256: {
|
|
234
|
+
size_t nbytes = (kind == CHC_INT128) ? 16 : 32;
|
|
235
|
+
for (long i = 0; i < n_rows; i++) {
|
|
236
|
+
rb_ary_push(ary, rb_integer_unpack(data + i * nbytes, nbytes, 1, 0,
|
|
237
|
+
INTEGER_PACK_LITTLE_ENDIAN |
|
|
238
|
+
INTEGER_PACK_2COMP));
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
case CHC_FLOAT32: {
|
|
243
|
+
for (long i = 0; i < n_rows; i++) {
|
|
244
|
+
float v = load_f32le(data + i * 4);
|
|
245
|
+
rb_ary_push(ary, DBL2NUM((double)v));
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case CHC_FLOAT64: {
|
|
250
|
+
for (long i = 0; i < n_rows; i++) {
|
|
251
|
+
double v = load_f64le(data + i * 8);
|
|
252
|
+
rb_ary_push(ary, DBL2NUM(v));
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case CHC_FIXED_STRING: {
|
|
257
|
+
for (long i = 0; i < n_rows; i++) {
|
|
258
|
+
rb_ary_push(ary, rb_utf8_str_new((const char *)data + i * es, (long)es));
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
case CHC_DATE: {
|
|
263
|
+
for (long i = 0; i < n_rows; i++) {
|
|
264
|
+
uint16_t days = load_u16le(data + i * 2);
|
|
265
|
+
rb_ary_push(ary, rb_funcall(cDate, id_jd, 1, LONG2NUM(UNIX_EPOCH_JD + (long)days)));
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
case CHC_DATE32: {
|
|
270
|
+
for (long i = 0; i < n_rows; i++) {
|
|
271
|
+
int32_t days = load_i32le(data + i * 4);
|
|
272
|
+
rb_ary_push(ary, rb_funcall(cDate, id_jd, 1, LONG2NUM(UNIX_EPOCH_JD + (long)days)));
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case CHC_DATETIME: {
|
|
277
|
+
for (long i = 0; i < n_rows; i++) {
|
|
278
|
+
uint32_t ts = load_u32le(data + i * 4);
|
|
279
|
+
struct timespec tsp = { .tv_sec = (time_t)ts, .tv_nsec = 0 };
|
|
280
|
+
rb_ary_push(ary, rb_time_timespec_new(&tsp, INT_MAX - 1)); /* UTC */
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case CHC_DATETIME64: {
|
|
285
|
+
int scale = chc_type_datetime64_scale(t);
|
|
286
|
+
int64_t ticks_per_second = 1;
|
|
287
|
+
int64_t nanoseconds_per_tick = 1;
|
|
288
|
+
for (int s = 0; s < scale; s++) ticks_per_second *= 10;
|
|
289
|
+
for (int s = scale; s < 9; s++) nanoseconds_per_tick *= 10;
|
|
290
|
+
for (long i = 0; i < n_rows; i++) {
|
|
291
|
+
int64_t ticks = load_i64le(data + i * 8);
|
|
292
|
+
int64_t sec = ticks / ticks_per_second;
|
|
293
|
+
int64_t remainder = ticks % ticks_per_second;
|
|
294
|
+
if (remainder < 0) { remainder += ticks_per_second; sec -= 1; }
|
|
295
|
+
int64_t nsec = remainder * nanoseconds_per_tick;
|
|
296
|
+
struct timespec tsp = { .tv_sec = (time_t)sec, .tv_nsec = (long)nsec };
|
|
297
|
+
rb_ary_push(ary, rb_time_timespec_new(&tsp, INT_MAX - 1)); /* UTC */
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case CHC_UUID: {
|
|
302
|
+
char buf[37];
|
|
303
|
+
for (long i = 0; i < n_rows; i++) {
|
|
304
|
+
const uint8_t *p = data + i * 16;
|
|
305
|
+
uint64_t hi = load_u64le(p);
|
|
306
|
+
uint64_t lo = load_u64le(p + 8);
|
|
307
|
+
snprintf(buf, sizeof(buf), "%08llx-%04llx-%04llx-%04llx-%012llx",
|
|
308
|
+
(unsigned long long)(hi >> 32),
|
|
309
|
+
(unsigned long long)((hi >> 16) & 0xFFFF),
|
|
310
|
+
(unsigned long long)(hi & 0xFFFF),
|
|
311
|
+
(unsigned long long)(lo >> 48),
|
|
312
|
+
(unsigned long long)(lo & 0xFFFFFFFFFFFFULL));
|
|
313
|
+
rb_ary_push(ary, rb_utf8_str_new(buf, 36));
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case CHC_IPV4: {
|
|
318
|
+
for (long i = 0; i < n_rows; i++) {
|
|
319
|
+
uint32_t v = load_u32le(data + i * 4);
|
|
320
|
+
rb_ary_push(ary, rb_funcall(cIPAddr, id_new, 2, UINT2NUM(v), vAF_INET));
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case CHC_IPV6: {
|
|
325
|
+
for (long i = 0; i < n_rows; i++) {
|
|
326
|
+
const uint8_t *p = data + i * 16;
|
|
327
|
+
VALUE value = rb_integer_unpack(p, 16, 1, 0, INTEGER_PACK_BIG_ENDIAN);
|
|
328
|
+
rb_ary_push(ary, rb_funcall(cIPAddr, id_new, 2, value, vAF_INET6));
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
case CHC_DECIMAL32:
|
|
333
|
+
case CHC_DECIMAL64:
|
|
334
|
+
case CHC_DECIMAL128:
|
|
335
|
+
case CHC_DECIMAL256: {
|
|
336
|
+
int scale = chc_type_decimal_scale(t);
|
|
337
|
+
VALUE divisor = rb_funcall(INT2FIX(10), id_pow, 1, INT2FIX(scale));
|
|
338
|
+
for (long i = 0; i < n_rows; i++) {
|
|
339
|
+
VALUE unscaled;
|
|
340
|
+
if (kind == CHC_DECIMAL32) {
|
|
341
|
+
int32_t v = load_i32le(data + i * 4);
|
|
342
|
+
unscaled = INT2NUM(v);
|
|
343
|
+
} else if (kind == CHC_DECIMAL64) {
|
|
344
|
+
int64_t v = load_i64le(data + i * 8);
|
|
345
|
+
unscaled = LL2NUM(v);
|
|
346
|
+
} else {
|
|
347
|
+
size_t nbytes = (kind == CHC_DECIMAL128) ? 16 : 32;
|
|
348
|
+
unscaled = rb_integer_unpack(data + i * nbytes, nbytes, 1, 0,
|
|
349
|
+
INTEGER_PACK_LITTLE_ENDIAN |
|
|
350
|
+
INTEGER_PACK_2COMP);
|
|
351
|
+
}
|
|
352
|
+
VALUE bd = rb_funcall(rb_mKernel, id_BigDecimal, 1, unscaled);
|
|
353
|
+
rb_ary_push(ary, rb_funcall(bd, id_div, 1, divisor));
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
default:
|
|
358
|
+
raise_unsupported_type(t, state);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return ary;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
static VALUE
|
|
365
|
+
decode_string_column(const chc_column *col, long n_rows)
|
|
366
|
+
{
|
|
367
|
+
const uint8_t *data = chc_column_string_data(col);
|
|
368
|
+
const uint64_t *offsets = chc_column_string_offsets(col);
|
|
369
|
+
VALUE ary = rb_ary_new_capa(n_rows);
|
|
370
|
+
|
|
371
|
+
uint64_t start = 0;
|
|
372
|
+
for (long i = 0; i < n_rows; i++) {
|
|
373
|
+
uint64_t end = offsets[i];
|
|
374
|
+
rb_ary_push(ary, rb_utf8_str_new((const char *)data + start, (long)(end - start)));
|
|
375
|
+
start = end;
|
|
376
|
+
}
|
|
377
|
+
return ary;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
static VALUE
|
|
381
|
+
decode_column(const chc_column *col, const chc_type *t, long n_rows, native_state *state)
|
|
382
|
+
{
|
|
383
|
+
if (n_rows == 0) {
|
|
384
|
+
chc_kind kind = chc_type_kind(t);
|
|
385
|
+
if (kind == CHC_STRING) return rb_ary_new();
|
|
386
|
+
if (kind == CHC_NULLABLE || kind == CHC_ARRAY ||
|
|
387
|
+
kind == CHC_TUPLE || kind == CHC_MAP ||
|
|
388
|
+
kind == CHC_LOW_CARDINALITY) {
|
|
389
|
+
for (size_t i = 0; i < chc_type_n_children(t); i++)
|
|
390
|
+
decode_column(NULL, chc_type_child(t, i), 0, state);
|
|
391
|
+
return rb_ary_new();
|
|
392
|
+
}
|
|
393
|
+
return decode_fixed(NULL, t, 0, state);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
switch (chc_column_layout(col)) {
|
|
397
|
+
case CHC_COL_FIXED:
|
|
398
|
+
return decode_fixed(col, t, n_rows, state);
|
|
399
|
+
|
|
400
|
+
case CHC_COL_STRING:
|
|
401
|
+
return decode_string_column(col, n_rows);
|
|
402
|
+
|
|
403
|
+
case CHC_COL_NULLABLE: {
|
|
404
|
+
const uint8_t *null_map = chc_column_null_map(col);
|
|
405
|
+
const chc_column *inner = chc_column_nullable_inner(col);
|
|
406
|
+
const chc_type *inner_t = chc_type_child(t, 0);
|
|
407
|
+
VALUE vals = decode_column(inner, inner_t, n_rows, state);
|
|
408
|
+
for (long i = 0; i < n_rows; i++) {
|
|
409
|
+
if (null_map[i] == 1) rb_ary_store(vals, i, Qnil);
|
|
410
|
+
}
|
|
411
|
+
return vals;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
case CHC_COL_ARRAY: {
|
|
415
|
+
const uint64_t *offsets = chc_column_array_offsets(col);
|
|
416
|
+
const chc_column *values_col = chc_column_array_values(col);
|
|
417
|
+
|
|
418
|
+
if (chc_type_kind(t) == CHC_MAP) {
|
|
419
|
+
/* Map is an Array of Tuple(K, V) on the wire */
|
|
420
|
+
const chc_type *kt = chc_type_child(t, 0);
|
|
421
|
+
const chc_type *vt = chc_type_child(t, 1);
|
|
422
|
+
long total = (long)chc_column_n_rows(values_col);
|
|
423
|
+
const chc_column *keys_col = chc_column_tuple_child(values_col, 0);
|
|
424
|
+
const chc_column *vals_col = chc_column_tuple_child(values_col, 1);
|
|
425
|
+
VALUE keys = decode_column(keys_col, kt, total, state);
|
|
426
|
+
VALUE vals = decode_column(vals_col, vt, total, state);
|
|
427
|
+
|
|
428
|
+
VALUE ary = rb_ary_new_capa(n_rows);
|
|
429
|
+
uint64_t start = 0;
|
|
430
|
+
for (long i = 0; i < n_rows; i++) {
|
|
431
|
+
uint64_t end = offsets[i];
|
|
432
|
+
if (end > (uint64_t)RARRAY_LEN(keys) || end > (uint64_t)RARRAY_LEN(vals)) {
|
|
433
|
+
*state = NATIVE_BROKEN;
|
|
434
|
+
rb_raise(eConnectionError,
|
|
435
|
+
"Map offset out of bounds at row %ld: %llu", i,
|
|
436
|
+
(unsigned long long)end);
|
|
437
|
+
}
|
|
438
|
+
VALUE hash = rb_hash_new();
|
|
439
|
+
for (uint64_t j = start; j < end; j++) {
|
|
440
|
+
rb_hash_aset(hash, RARRAY_AREF(keys, (long)j), RARRAY_AREF(vals, (long)j));
|
|
441
|
+
}
|
|
442
|
+
rb_ary_push(ary, hash);
|
|
443
|
+
start = end;
|
|
444
|
+
}
|
|
445
|
+
return ary;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const chc_type *elem_t = chc_type_child(t, 0);
|
|
449
|
+
long total = (long)chc_column_n_rows(values_col);
|
|
450
|
+
VALUE elements = decode_column(values_col, elem_t, total, state);
|
|
451
|
+
|
|
452
|
+
VALUE ary = rb_ary_new_capa(n_rows);
|
|
453
|
+
uint64_t start = 0;
|
|
454
|
+
for (long i = 0; i < n_rows; i++) {
|
|
455
|
+
uint64_t end = offsets[i];
|
|
456
|
+
rb_ary_push(ary, rb_ary_subseq(elements, (long)start, (long)(end - start)));
|
|
457
|
+
start = end;
|
|
458
|
+
}
|
|
459
|
+
return ary;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
case CHC_COL_TUPLE: {
|
|
463
|
+
size_t arity = chc_column_tuple_arity(col);
|
|
464
|
+
VALUE children = rb_ary_new_capa((long)arity);
|
|
465
|
+
for (size_t k = 0; k < arity; k++) {
|
|
466
|
+
rb_ary_push(children, decode_column(chc_column_tuple_child(col, k),
|
|
467
|
+
chc_type_child(t, k), n_rows, state));
|
|
468
|
+
}
|
|
469
|
+
VALUE ary = rb_ary_new_capa(n_rows);
|
|
470
|
+
for (long i = 0; i < n_rows; i++) {
|
|
471
|
+
VALUE row = rb_ary_new_capa((long)arity);
|
|
472
|
+
for (size_t k = 0; k < arity; k++) {
|
|
473
|
+
rb_ary_push(row, RARRAY_AREF(RARRAY_AREF(children, (long)k), i));
|
|
474
|
+
}
|
|
475
|
+
rb_ary_push(ary, row);
|
|
476
|
+
}
|
|
477
|
+
return ary;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
case CHC_COL_LOW_CARDINALITY: {
|
|
481
|
+
const chc_column *dict = chc_column_lc_dict(col);
|
|
482
|
+
const chc_type *inner_t = chc_type_child(t, 0);
|
|
483
|
+
long dict_size = (long)chc_column_n_rows(dict);
|
|
484
|
+
VALUE dict_vals = decode_column(dict, inner_t, dict_size, state);
|
|
485
|
+
|
|
486
|
+
int key_size = chc_column_lc_key_size(col);
|
|
487
|
+
const void *keys = chc_column_lc_keys(col);
|
|
488
|
+
VALUE ary = rb_ary_new_capa(n_rows);
|
|
489
|
+
for (long i = 0; i < n_rows; i++) {
|
|
490
|
+
uint64_t idx;
|
|
491
|
+
switch (key_size) {
|
|
492
|
+
case 1: idx = ((const uint8_t *)keys)[i]; break;
|
|
493
|
+
case 2: idx = ((const uint16_t *)keys)[i]; break;
|
|
494
|
+
case 4: idx = ((const uint32_t *)keys)[i]; break;
|
|
495
|
+
default: idx = ((const uint64_t *)keys)[i]; break;
|
|
496
|
+
}
|
|
497
|
+
rb_ary_push(ary, RARRAY_AREF(dict_vals, (long)idx));
|
|
498
|
+
}
|
|
499
|
+
return ary;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
default:
|
|
503
|
+
raise_unsupported_type(t, state);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/* --- ioless client ------------------------------------------------------ */
|
|
508
|
+
|
|
509
|
+
typedef struct {
|
|
510
|
+
chc_async_client *ac;
|
|
511
|
+
chc_alloc alloc;
|
|
512
|
+
/* ACTIVE at checkout means the pump was abandoned mid-stream (for
|
|
513
|
+
* example by Thread#kill), so the slot discards the connection. */
|
|
514
|
+
native_state state;
|
|
515
|
+
int have_header;
|
|
516
|
+
/* parked across calls so a raise mid-decode can't leak the block */
|
|
517
|
+
chc_packet pending_pkt;
|
|
518
|
+
uint64_t read_rows, read_bytes, total_rows, written_rows, written_bytes;
|
|
519
|
+
uint64_t result_bytes;
|
|
520
|
+
} native_client_t;
|
|
521
|
+
|
|
522
|
+
static void
|
|
523
|
+
clear_pending(native_client_t *nc)
|
|
524
|
+
{
|
|
525
|
+
if (nc->ac) chc_async_packet_clear(nc->ac, &nc->pending_pkt);
|
|
526
|
+
memset(&nc->pending_pkt, 0, sizeof(nc->pending_pkt));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
static void
|
|
530
|
+
native_client_dfree(void *ptr)
|
|
531
|
+
{
|
|
532
|
+
native_client_t *nc = (native_client_t *)ptr;
|
|
533
|
+
clear_pending(nc);
|
|
534
|
+
if (nc->ac) chc_async_client_free(nc->ac);
|
|
535
|
+
xfree(nc);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
static const rb_data_type_t native_client_type = {
|
|
539
|
+
.wrap_struct_name = "ChConnect::NativeClient",
|
|
540
|
+
.function = { .dfree = native_client_dfree },
|
|
541
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
static native_client_t *
|
|
545
|
+
get_client(VALUE self)
|
|
546
|
+
{
|
|
547
|
+
native_client_t *nc;
|
|
548
|
+
TypedData_Get_Struct(self, native_client_t, &native_client_type, nc);
|
|
549
|
+
return nc;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
static VALUE
|
|
553
|
+
native_client_alloc(VALUE klass)
|
|
554
|
+
{
|
|
555
|
+
native_client_t *nc = ZALLOC(native_client_t);
|
|
556
|
+
return TypedData_Wrap_Struct(klass, &native_client_type, nc);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
static native_client_t *
|
|
560
|
+
get_live_client(VALUE self)
|
|
561
|
+
{
|
|
562
|
+
native_client_t *nc = get_client(self);
|
|
563
|
+
if (!nc->ac || nc->state == NATIVE_BROKEN) {
|
|
564
|
+
rb_raise(eConnectionError, "connection is closed or in a broken state");
|
|
565
|
+
}
|
|
566
|
+
return nc;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
static VALUE
|
|
570
|
+
native_client_initialize(VALUE self, VALUE database, VALUE username, VALUE password, VALUE compression)
|
|
571
|
+
{
|
|
572
|
+
native_client_t *nc = get_client(self);
|
|
573
|
+
|
|
574
|
+
Check_Type(database, T_STRING);
|
|
575
|
+
if (!NIL_P(username)) Check_Type(username, T_STRING);
|
|
576
|
+
if (!NIL_P(password)) Check_Type(password, T_STRING);
|
|
577
|
+
|
|
578
|
+
chc_compression compression_code;
|
|
579
|
+
if (NIL_P(compression)) compression_code = CHC_COMP_NONE;
|
|
580
|
+
else if (compression == sym_lz4) compression_code = CHC_COMP_LZ4;
|
|
581
|
+
else if (compression == sym_zstd) compression_code = CHC_COMP_ZSTD;
|
|
582
|
+
else rb_raise(rb_eArgError, "unknown native compression");
|
|
583
|
+
|
|
584
|
+
nc->alloc = chc_alloc_stdlib();
|
|
585
|
+
/* init copies the strings and performs no I/O */
|
|
586
|
+
chc_client_opts opts = {
|
|
587
|
+
.client_name = "ch_connect",
|
|
588
|
+
.database = StringValueCStr(database),
|
|
589
|
+
.user = NIL_P(username) ? NULL : StringValueCStr(username),
|
|
590
|
+
.password = NIL_P(password) ? NULL : StringValueCStr(password),
|
|
591
|
+
.codec = &g_codec,
|
|
592
|
+
.compression = compression_code,
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
chc_err err = {0};
|
|
596
|
+
if (chc_async_client_init(&nc->ac, &opts, &nc->alloc, &err) != CHC_OK) {
|
|
597
|
+
rb_raise(eConnectionError, "%s", err.msg);
|
|
598
|
+
}
|
|
599
|
+
nc->state = NATIVE_ACTIVE; /* handshake has not completed yet */
|
|
600
|
+
return self;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/* Returns buffered protocol bytes to write to the socket, or nil. */
|
|
604
|
+
static VALUE
|
|
605
|
+
native_client_take_output(VALUE self)
|
|
606
|
+
{
|
|
607
|
+
native_client_t *nc = get_live_client(self);
|
|
608
|
+
|
|
609
|
+
const uint8_t *buf = NULL;
|
|
610
|
+
size_t len = 0;
|
|
611
|
+
chc_async_pending_out(nc->ac, &buf, &len);
|
|
612
|
+
if (len == 0) return Qnil;
|
|
613
|
+
|
|
614
|
+
VALUE out = rb_str_new((const char *)buf, (long)len);
|
|
615
|
+
chc_async_consume_out(nc->ac, len);
|
|
616
|
+
return out;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/* Feeds bytes read from the socket into the state machine (copied). */
|
|
620
|
+
static VALUE
|
|
621
|
+
native_client_feed(VALUE self, VALUE bytes)
|
|
622
|
+
{
|
|
623
|
+
native_client_t *nc = get_live_client(self);
|
|
624
|
+
Check_Type(bytes, T_STRING);
|
|
625
|
+
|
|
626
|
+
chc_err err = {0};
|
|
627
|
+
if (chc_async_submit(nc->ac, RSTRING_PTR(bytes), (size_t)RSTRING_LEN(bytes), &err) != CHC_OK) {
|
|
628
|
+
nc->state = NATIVE_BROKEN;
|
|
629
|
+
rb_raise(eConnectionError, "%s", err.msg);
|
|
630
|
+
}
|
|
631
|
+
return Qnil;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
static VALUE
|
|
635
|
+
native_client_handshake_step(VALUE self)
|
|
636
|
+
{
|
|
637
|
+
native_client_t *nc = get_live_client(self);
|
|
638
|
+
|
|
639
|
+
nc->state = NATIVE_ACTIVE;
|
|
640
|
+
chc_err err = {0};
|
|
641
|
+
int rc = chc_async_handshake(nc->ac, &err);
|
|
642
|
+
if (rc == CHC_OK) {
|
|
643
|
+
nc->state = NATIVE_READY;
|
|
644
|
+
return sym_done;
|
|
645
|
+
}
|
|
646
|
+
if (rc == CHC_WOULD_BLOCK) return sym_want_read;
|
|
647
|
+
|
|
648
|
+
nc->state = NATIVE_BROKEN;
|
|
649
|
+
rb_raise(eConnectionError, "%s", err.msg);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/* Normalize Ruby pairs while allocation is still safe. The caller builds C
|
|
653
|
+
* pointers only after both settings and parameters have passed through here. */
|
|
654
|
+
static void
|
|
655
|
+
append_string_pairs(VALUE pairs, long count, VALUE flat)
|
|
656
|
+
{
|
|
657
|
+
for (long i = 0; i < count; i++) {
|
|
658
|
+
VALUE pair = RARRAY_AREF(pairs, i);
|
|
659
|
+
Check_Type(pair, T_ARRAY);
|
|
660
|
+
VALUE name = rb_ary_entry(pair, 0);
|
|
661
|
+
VALUE value = rb_ary_entry(pair, 1);
|
|
662
|
+
StringValue(name);
|
|
663
|
+
StringValue(value);
|
|
664
|
+
(void)StringValueCStr(name); /* reject embedded NUL */
|
|
665
|
+
(void)StringValueCStr(value);
|
|
666
|
+
rb_ary_push(flat, name);
|
|
667
|
+
rb_ary_push(flat, value);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
static VALUE
|
|
672
|
+
native_client_send_query(VALUE self, VALUE sql, VALUE params, VALUE settings)
|
|
673
|
+
{
|
|
674
|
+
native_client_t *nc = get_live_client(self);
|
|
675
|
+
if (nc->state != NATIVE_READY)
|
|
676
|
+
rb_raise(eConnectionError, "native connection is not ready for a query");
|
|
677
|
+
Check_Type(sql, T_STRING);
|
|
678
|
+
|
|
679
|
+
/* reset per-query state */
|
|
680
|
+
clear_pending(nc);
|
|
681
|
+
nc->state = NATIVE_ACTIVE;
|
|
682
|
+
nc->have_header = 0;
|
|
683
|
+
nc->read_rows = nc->read_bytes = nc->total_rows = 0;
|
|
684
|
+
nc->written_rows = nc->written_bytes = 0;
|
|
685
|
+
nc->result_bytes = 0;
|
|
686
|
+
VALUE result = rb_ary_new_capa(3);
|
|
687
|
+
rb_ary_push(result, rb_ary_new());
|
|
688
|
+
rb_ary_push(result, rb_ary_new());
|
|
689
|
+
rb_ary_push(result, rb_ary_new());
|
|
690
|
+
rb_iv_set(self, "@result", result);
|
|
691
|
+
|
|
692
|
+
if (!NIL_P(settings)) Check_Type(settings, T_ARRAY);
|
|
693
|
+
long n_settings = NIL_P(settings) ? 0 : RARRAY_LEN(settings);
|
|
694
|
+
if (!NIL_P(params)) Check_Type(params, T_ARRAY);
|
|
695
|
+
long n_params = NIL_P(params) ? 0 : RARRAY_LEN(params);
|
|
696
|
+
|
|
697
|
+
/* Coerce and validate every Ruby string before retaining any RSTRING_PTR.
|
|
698
|
+
* String coercion and rb_ary_push can allocate and trigger compacting GC;
|
|
699
|
+
* once this pass finishes, the pointer-building pass performs no Ruby
|
|
700
|
+
* allocation before chc_client_send_query_ex consumes the bytes. */
|
|
701
|
+
VALUE flat = rb_ary_new_capa(2 * (n_settings + n_params));
|
|
702
|
+
append_string_pairs(settings, n_settings, flat);
|
|
703
|
+
append_string_pairs(params, n_params, flat);
|
|
704
|
+
|
|
705
|
+
/* These counts come from public hashes and can be large. ALLOCV_N uses a
|
|
706
|
+
* small stack buffer or Ruby's heap without risking the native stack. Both
|
|
707
|
+
* buffers are allocated before retaining any movable Ruby string pointer. */
|
|
708
|
+
VALUE csettings_buf = 0, cparams_buf = 0;
|
|
709
|
+
chc_query_setting *csettings = ALLOCV_N(chc_query_setting, csettings_buf,
|
|
710
|
+
n_settings + 1);
|
|
711
|
+
chc_query_param *cparams = n_params > 0
|
|
712
|
+
? ALLOCV_N(chc_query_param, cparams_buf, n_params)
|
|
713
|
+
: NULL;
|
|
714
|
+
|
|
715
|
+
long n_total = 0;
|
|
716
|
+
for (long i = 0; i < n_settings; i++) {
|
|
717
|
+
VALUE sname = RARRAY_AREF(flat, 2 * i);
|
|
718
|
+
VALUE sval = RARRAY_AREF(flat, 2 * i + 1);
|
|
719
|
+
csettings[n_total++] = (chc_query_setting){
|
|
720
|
+
.name = RSTRING_PTR(sname), .value = RSTRING_PTR(sval)
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
/* Keep the decoder invariant last so a duplicate supplied through the
|
|
724
|
+
* low-level NativeClient API cannot override it. */
|
|
725
|
+
csettings[n_total++] = (chc_query_setting){
|
|
726
|
+
.name = "output_format_native_encode_types_in_binary_format", .value = "0"
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
chc_query_opts qopts = { .settings = csettings, .n_settings = (size_t)n_total };
|
|
730
|
+
|
|
731
|
+
if (n_params > 0) {
|
|
732
|
+
long base = 2 * n_settings;
|
|
733
|
+
for (long i = 0; i < n_params; i++) {
|
|
734
|
+
VALUE pname = RARRAY_AREF(flat, base + 2 * i);
|
|
735
|
+
VALUE pval = RARRAY_AREF(flat, base + 2 * i + 1);
|
|
736
|
+
cparams[i].name = RSTRING_PTR(pname);
|
|
737
|
+
cparams[i].value = RSTRING_PTR(pval);
|
|
738
|
+
}
|
|
739
|
+
qopts.params = cparams;
|
|
740
|
+
qopts.n_params = (size_t)n_params;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
chc_err err = {0};
|
|
744
|
+
/* the async wrapper has no send_query_ex; call it on the embedded client
|
|
745
|
+
* (same out-sink io) so settings and params are included */
|
|
746
|
+
int rc = chc_client_send_query_ex(&nc->ac->cli, RSTRING_PTR(sql), (size_t)RSTRING_LEN(sql),
|
|
747
|
+
&qopts, &err);
|
|
748
|
+
ALLOCV_END(cparams_buf);
|
|
749
|
+
ALLOCV_END(csettings_buf);
|
|
750
|
+
RB_GC_GUARD(flat);
|
|
751
|
+
RB_GC_GUARD(sql);
|
|
752
|
+
if (rc != CHC_OK) {
|
|
753
|
+
nc->state = NATIVE_BROKEN;
|
|
754
|
+
rb_raise(eConnectionError, "%s", err.msg);
|
|
755
|
+
}
|
|
756
|
+
return Qnil;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
static void
|
|
760
|
+
native_client_decode_block(VALUE self, native_client_t *nc, chc_block *block)
|
|
761
|
+
{
|
|
762
|
+
size_t n_rows = chc_block_n_rows(block);
|
|
763
|
+
size_t n_cols = chc_block_n_columns(block);
|
|
764
|
+
VALUE result = rb_iv_get(self, "@result");
|
|
765
|
+
if (NIL_P(result)) {
|
|
766
|
+
clear_pending(nc);
|
|
767
|
+
nc->state = NATIVE_BROKEN;
|
|
768
|
+
rb_raise(eConnectionError, "received native data without an active result");
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
for (size_t i = 0; i < n_cols; i++) {
|
|
772
|
+
chc_err err = {0};
|
|
773
|
+
if (chc_column_validate(chc_block_column(block, i), &err) != CHC_OK) {
|
|
774
|
+
VALUE msg = rb_sprintf("Invalid native column: %s",
|
|
775
|
+
err.msg[0] ? err.msg : "column validation failed");
|
|
776
|
+
clear_pending(nc);
|
|
777
|
+
nc->state = NATIVE_BROKEN;
|
|
778
|
+
rb_exc_raise(rb_exc_new_str(eConnectionError, msg));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (!nc->have_header && n_cols > 0) {
|
|
783
|
+
nc->have_header = 1;
|
|
784
|
+
VALUE columns = RARRAY_AREF(result, 0);
|
|
785
|
+
VALUE types = RARRAY_AREF(result, 1);
|
|
786
|
+
for (size_t i = 0; i < n_cols; i++) {
|
|
787
|
+
size_t len = 0;
|
|
788
|
+
const char *name = chc_block_column_name(block, i, &len);
|
|
789
|
+
rb_ary_push(columns, rb_str_intern(rb_utf8_str_new(name, (long)len)));
|
|
790
|
+
|
|
791
|
+
const chc_type *t = chc_block_column_type(block, i);
|
|
792
|
+
size_t tlen = 0;
|
|
793
|
+
const char *tname = chc_type_name(t, &tlen);
|
|
794
|
+
rb_ary_push(types, rb_str_intern(rb_utf8_str_new(tname, (long)tlen)));
|
|
795
|
+
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
VALUE rows = RARRAY_AREF(result, 2);
|
|
800
|
+
/* Decode all columns of this block, then append transposed rows. col_vals
|
|
801
|
+
* anchors the decoded arrays for GC; cols makes the transpose O(1). */
|
|
802
|
+
VALUE col_vals = rb_ary_new_capa((long)n_cols);
|
|
803
|
+
VALUE cols_buf;
|
|
804
|
+
VALUE *cols = ALLOCV_N(VALUE, cols_buf, n_cols);
|
|
805
|
+
for (size_t i = 0; i < n_cols; i++) {
|
|
806
|
+
cols[i] = decode_column(chc_block_column(block, i),
|
|
807
|
+
chc_block_column_type(block, i),
|
|
808
|
+
(long)n_rows, &nc->state);
|
|
809
|
+
rb_ary_push(col_vals, cols[i]);
|
|
810
|
+
}
|
|
811
|
+
for (size_t r = 0; r < n_rows; r++) {
|
|
812
|
+
VALUE row = rb_ary_new_capa((long)n_cols);
|
|
813
|
+
for (size_t c = 0; c < n_cols; c++) {
|
|
814
|
+
rb_ary_push(row, RARRAY_AREF(cols[c], (long)r));
|
|
815
|
+
}
|
|
816
|
+
rb_ary_push(rows, row);
|
|
817
|
+
}
|
|
818
|
+
ALLOCV_END(cols_buf);
|
|
819
|
+
RB_GC_GUARD(col_vals);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/* Drives the receive state machine over already-fed bytes.
|
|
823
|
+
* Returns :want_read when more socket data is needed, :done at end of
|
|
824
|
+
* stream. Raises QueryError / UnsupportedTypeError / ConnectionError. */
|
|
825
|
+
static VALUE
|
|
826
|
+
native_client_recv_step(VALUE self)
|
|
827
|
+
{
|
|
828
|
+
native_client_t *nc = get_live_client(self);
|
|
829
|
+
|
|
830
|
+
for (;;) {
|
|
831
|
+
clear_pending(nc);
|
|
832
|
+
|
|
833
|
+
chc_err err = {0};
|
|
834
|
+
int rc = chc_async_recv_packet(nc->ac, &nc->pending_pkt, &err);
|
|
835
|
+
if (rc == CHC_WOULD_BLOCK) return sym_want_read;
|
|
836
|
+
if (rc == CHC_ERR_TYPE) {
|
|
837
|
+
nc->state = NATIVE_BROKEN;
|
|
838
|
+
rb_raise(eUnsupportedTypeError, "Unsupported column type: %s", err.msg);
|
|
839
|
+
}
|
|
840
|
+
if (rc != CHC_OK) {
|
|
841
|
+
nc->state = NATIVE_BROKEN;
|
|
842
|
+
rb_raise(eConnectionError, "%s",
|
|
843
|
+
err.msg[0] ? err.msg : "connection lost while reading response");
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
chc_packet pkt = nc->pending_pkt; /* value alias; freed via clear */
|
|
847
|
+
|
|
848
|
+
if (pkt.kind == CHC_PKT_EXCEPTION) {
|
|
849
|
+
VALUE msg = rb_utf8_str_new(pkt.exception->display_text,
|
|
850
|
+
(long)pkt.exception->display_text_len);
|
|
851
|
+
clear_pending(nc);
|
|
852
|
+
rb_iv_set(self, "@result", Qnil);
|
|
853
|
+
/* A complete server exception terminates this query but leaves the
|
|
854
|
+
* native protocol synchronized and ready for the next query. */
|
|
855
|
+
nc->state = NATIVE_READY;
|
|
856
|
+
rb_exc_raise(rb_exc_new_str(eQueryError, msg));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (pkt.kind == CHC_PKT_PROGRESS) {
|
|
860
|
+
nc->read_rows += pkt.progress.rows;
|
|
861
|
+
nc->read_bytes += pkt.progress.bytes;
|
|
862
|
+
if (pkt.progress.total_rows > nc->total_rows) nc->total_rows = pkt.progress.total_rows;
|
|
863
|
+
nc->written_rows += pkt.progress.written_rows;
|
|
864
|
+
nc->written_bytes += pkt.progress.written_bytes;
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (pkt.kind == CHC_PKT_PROFILE_INFO) {
|
|
869
|
+
nc->result_bytes = pkt.profile.bytes;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (pkt.kind == CHC_PKT_DATA) {
|
|
874
|
+
native_client_decode_block(self, nc, pkt.block);
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (pkt.kind == CHC_PKT_END_OF_STREAM) {
|
|
879
|
+
clear_pending(nc);
|
|
880
|
+
nc->state = NATIVE_READY;
|
|
881
|
+
return sym_done;
|
|
882
|
+
}
|
|
883
|
+
/* PONG / TOTALS / EXTREMES / LOG / PROFILE_EVENTS — skip. */
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
static VALUE
|
|
888
|
+
native_client_take_result(VALUE self)
|
|
889
|
+
{
|
|
890
|
+
native_client_t *nc = get_client(self);
|
|
891
|
+
|
|
892
|
+
VALUE result = rb_iv_get(self, "@result");
|
|
893
|
+
if (NIL_P(result))
|
|
894
|
+
rb_raise(eConnectionError, "no completed native result is available");
|
|
895
|
+
VALUE rows = RARRAY_AREF(result, 2);
|
|
896
|
+
|
|
897
|
+
VALUE summary = rb_hash_new();
|
|
898
|
+
rb_hash_aset(summary, sym_read_rows, ULL2NUM(nc->read_rows));
|
|
899
|
+
rb_hash_aset(summary, sym_read_bytes, ULL2NUM(nc->read_bytes));
|
|
900
|
+
rb_hash_aset(summary, sym_written_rows, ULL2NUM(nc->written_rows));
|
|
901
|
+
rb_hash_aset(summary, sym_written_bytes, ULL2NUM(nc->written_bytes));
|
|
902
|
+
rb_hash_aset(summary, sym_total_rows_to_read, ULL2NUM(nc->total_rows));
|
|
903
|
+
rb_hash_aset(summary, sym_result_rows, LONG2NUM(RARRAY_LEN(rows)));
|
|
904
|
+
rb_hash_aset(summary, sym_result_bytes, ULL2NUM(nc->result_bytes));
|
|
905
|
+
|
|
906
|
+
rb_ary_push(result, summary);
|
|
907
|
+
/* drop the references so a pooled idle connection doesn't pin the last
|
|
908
|
+
* result set until its next query */
|
|
909
|
+
rb_iv_set(self, "@result", Qnil);
|
|
910
|
+
return result;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
static VALUE
|
|
914
|
+
native_client_broken_p(VALUE self)
|
|
915
|
+
{
|
|
916
|
+
native_client_t *nc = get_client(self);
|
|
917
|
+
return (!nc->ac || nc->state != NATIVE_READY) ? Qtrue : Qfalse;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
static VALUE
|
|
921
|
+
native_client_close(VALUE self)
|
|
922
|
+
{
|
|
923
|
+
native_client_t *nc = get_client(self);
|
|
924
|
+
clear_pending(nc);
|
|
925
|
+
if (nc->ac) {
|
|
926
|
+
chc_async_client_free(nc->ac);
|
|
927
|
+
nc->ac = NULL;
|
|
928
|
+
}
|
|
929
|
+
return Qnil;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
void
|
|
933
|
+
Init_ch_connect_native(void)
|
|
934
|
+
{
|
|
935
|
+
rb_require("date");
|
|
936
|
+
rb_require("ipaddr");
|
|
937
|
+
rb_require("bigdecimal");
|
|
938
|
+
rb_require("socket");
|
|
939
|
+
|
|
940
|
+
VALUE mChConnect = rb_define_module("ChConnect");
|
|
941
|
+
cNativeClient = rb_define_class_under(mChConnect, "NativeClient", rb_cObject);
|
|
942
|
+
rb_define_alloc_func(cNativeClient, native_client_alloc);
|
|
943
|
+
rb_define_method(cNativeClient, "initialize", native_client_initialize, 4);
|
|
944
|
+
rb_define_method(cNativeClient, "take_output", native_client_take_output, 0);
|
|
945
|
+
rb_define_method(cNativeClient, "feed", native_client_feed, 1);
|
|
946
|
+
rb_define_method(cNativeClient, "handshake_step", native_client_handshake_step, 0);
|
|
947
|
+
rb_define_method(cNativeClient, "send_query", native_client_send_query, 3);
|
|
948
|
+
rb_define_method(cNativeClient, "recv_step", native_client_recv_step, 0);
|
|
949
|
+
rb_define_method(cNativeClient, "take_result", native_client_take_result, 0);
|
|
950
|
+
rb_define_method(cNativeClient, "broken?", native_client_broken_p, 0);
|
|
951
|
+
rb_define_method(cNativeClient, "close", native_client_close, 0);
|
|
952
|
+
|
|
953
|
+
eQueryError = rb_path2class("ChConnect::QueryError");
|
|
954
|
+
eConnectionError = rb_path2class("ChConnect::ConnectionError");
|
|
955
|
+
eUnsupportedTypeError = rb_path2class("ChConnect::UnsupportedTypeError");
|
|
956
|
+
cIPAddr = rb_path2class("IPAddr");
|
|
957
|
+
cDate = rb_path2class("Date");
|
|
958
|
+
VALUE cSocket = rb_path2class("Socket");
|
|
959
|
+
vAF_INET = rb_const_get(cSocket, rb_intern("AF_INET"));
|
|
960
|
+
vAF_INET6 = rb_const_get(cSocket, rb_intern("AF_INET6"));
|
|
961
|
+
|
|
962
|
+
rb_gc_register_address(&eQueryError);
|
|
963
|
+
rb_gc_register_address(&eConnectionError);
|
|
964
|
+
rb_gc_register_address(&eUnsupportedTypeError);
|
|
965
|
+
rb_gc_register_address(&cIPAddr);
|
|
966
|
+
rb_gc_register_address(&cDate);
|
|
967
|
+
rb_gc_register_address(&vAF_INET);
|
|
968
|
+
rb_gc_register_address(&vAF_INET6);
|
|
969
|
+
|
|
970
|
+
id_jd = rb_intern("jd");
|
|
971
|
+
id_new = rb_intern("new");
|
|
972
|
+
id_pow = rb_intern("**");
|
|
973
|
+
id_div = rb_intern("/");
|
|
974
|
+
id_BigDecimal = rb_intern("BigDecimal");
|
|
975
|
+
|
|
976
|
+
sym_read_rows = ID2SYM(rb_intern("read_rows"));
|
|
977
|
+
sym_read_bytes = ID2SYM(rb_intern("read_bytes"));
|
|
978
|
+
sym_written_rows = ID2SYM(rb_intern("written_rows"));
|
|
979
|
+
sym_written_bytes = ID2SYM(rb_intern("written_bytes"));
|
|
980
|
+
sym_total_rows_to_read = ID2SYM(rb_intern("total_rows_to_read"));
|
|
981
|
+
sym_result_rows = ID2SYM(rb_intern("result_rows"));
|
|
982
|
+
sym_result_bytes = ID2SYM(rb_intern("result_bytes"));
|
|
983
|
+
sym_done = ID2SYM(rb_intern("done"));
|
|
984
|
+
sym_want_read = ID2SYM(rb_intern("want_read"));
|
|
985
|
+
sym_lz4 = ID2SYM(rb_intern("lz4"));
|
|
986
|
+
sym_zstd = ID2SYM(rb_intern("zstd"));
|
|
987
|
+
|
|
988
|
+
memset(&g_codec, 0, sizeof(g_codec));
|
|
989
|
+
#ifdef CHC_EXT_HAVE_LZ4
|
|
990
|
+
chc_lz4_codec_init(&g_codec);
|
|
991
|
+
rb_define_const(cNativeClient, "LZ4_AVAILABLE", Qtrue);
|
|
992
|
+
#else
|
|
993
|
+
rb_define_const(cNativeClient, "LZ4_AVAILABLE", Qfalse);
|
|
994
|
+
#endif
|
|
995
|
+
#ifdef CHC_EXT_HAVE_ZSTD
|
|
996
|
+
chc_zstd_codec_init(&g_codec);
|
|
997
|
+
rb_define_const(cNativeClient, "ZSTD_AVAILABLE", Qtrue);
|
|
998
|
+
#else
|
|
999
|
+
rb_define_const(cNativeClient, "ZSTD_AVAILABLE", Qfalse);
|
|
1000
|
+
#endif
|
|
1001
|
+
}
|