ch_connect 0.3.1 → 0.4.1
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/CHANGELOG.md +14 -0
- data/README.md +8 -8
- data/ext/ch_connect_native/ch_connect_native.c +196 -49
- data/lib/ch_connect/connection.rb +19 -1
- data/lib/ch_connect/response.rb +12 -1
- data/lib/ch_connect/version.rb +1 -1
- data/vendor/clickhouse-c/VENDOR.md +5 -2
- data/vendor/clickhouse-c/clickhouse-async.h +15 -22
- data/vendor/clickhouse-c/clickhouse-client.h +187 -212
- data/vendor/clickhouse-c/clickhouse-compression.h +8 -5
- data/vendor/clickhouse-c/clickhouse.h +1033 -982
- metadata +1 -1
|
@@ -23,9 +23,13 @@
|
|
|
23
23
|
extern "C" {
|
|
24
24
|
#endif
|
|
25
25
|
|
|
26
|
-
/*
|
|
27
|
-
*
|
|
28
|
-
|
|
26
|
+
/* Protocol revisions & feature gates are documented in the Native Protocol
|
|
27
|
+
* spec: clickhouse.com/docs/reference/interfaces/specs/NativeProtocol
|
|
28
|
+
*
|
|
29
|
+
* Oldest supported server is ClickHouse 23.3. Client advertises
|
|
30
|
+
* SPARSE_SERIALIZATION (54465). */
|
|
31
|
+
#define CHC_SERVER_MIN_REVISION 54462u
|
|
32
|
+
#define CHC_CLIENT_REVISION 54465u
|
|
29
33
|
|
|
30
34
|
typedef struct chc_client_opts {
|
|
31
35
|
/* Identity. Defaults applied when fields are zero/NULL. */
|
|
@@ -33,7 +37,6 @@ typedef struct chc_client_opts {
|
|
|
33
37
|
uint64_t client_version_major; /* default 0 */
|
|
34
38
|
uint64_t client_version_minor; /* default 0 */
|
|
35
39
|
uint64_t client_version_patch; /* default 0 */
|
|
36
|
-
uint64_t client_revision; /* default CHC_CLIENT_DEFAULT_REVISION */
|
|
37
40
|
|
|
38
41
|
/* Hello body. */
|
|
39
42
|
const char *database; /* default "default" */
|
|
@@ -50,20 +53,27 @@ typedef struct chc_client_opts {
|
|
|
50
53
|
|
|
51
54
|
typedef struct chc_server_info {
|
|
52
55
|
char name[64];
|
|
53
|
-
char timezone[64];
|
|
56
|
+
char timezone[64]; /* updated by CHC_PKT_TIMEZONE_UPDATE */
|
|
54
57
|
char display_name[128];
|
|
55
58
|
uint64_t version_major;
|
|
56
59
|
uint64_t version_minor;
|
|
57
60
|
uint64_t version_patch;
|
|
58
|
-
uint64_t revision; /* min(
|
|
61
|
+
uint64_t revision; /* min(CHC_CLIENT_REVISION, server revision) */
|
|
59
62
|
} chc_server_info;
|
|
60
63
|
|
|
61
64
|
typedef struct chc_client chc_client;
|
|
65
|
+
typedef struct chc_exception chc_exception;
|
|
62
66
|
|
|
63
67
|
/* Performs Hello / HelloAck handshake immediately. On failure caller may
|
|
64
|
-
* call chc_client_close to free any partially-allocated state.
|
|
68
|
+
* call chc_client_close to free any partially-allocated state.
|
|
69
|
+
*
|
|
70
|
+
* Handshake rejection returns CHC_ERR_SERVER and leaves err->msg empty. When
|
|
71
|
+
* exc is not NULL, caller owns *exc and frees it with chc_exception_free.
|
|
72
|
+
* Other results leave *exc unchanged. Query exceptions still arrive as
|
|
73
|
+
* packets, but handshake rejection produces no client */
|
|
65
74
|
int chc_client_init(chc_client **out, const chc_client_opts *opts,
|
|
66
|
-
const chc_alloc *al, chc_io *io,
|
|
75
|
+
const chc_alloc *al, chc_io *io, chc_exception **exc,
|
|
76
|
+
chc_err *err);
|
|
67
77
|
|
|
68
78
|
void chc_client_close(chc_client *c);
|
|
69
79
|
|
|
@@ -74,8 +84,9 @@ int chc_client_send_query(chc_client *c,
|
|
|
74
84
|
const char *query_id, size_t query_id_len,
|
|
75
85
|
chc_err *err);
|
|
76
86
|
|
|
77
|
-
/* Per-query setting. name / value are NUL-terminated.
|
|
78
|
-
*
|
|
87
|
+
/* Per-query setting. name / value are NUL-terminated. Flags layout per the
|
|
88
|
+
* Native Protocol spec (Query packet, settings entry); only the low two
|
|
89
|
+
* bits are assigned. */
|
|
79
90
|
typedef struct chc_query_setting {
|
|
80
91
|
const char *name;
|
|
81
92
|
const char *value;
|
|
@@ -88,8 +99,7 @@ typedef struct chc_query_setting {
|
|
|
88
99
|
* always CUSTOM. The server parses value via Field::restoreFromDump, so
|
|
89
100
|
* callers must format value as a typed Field literal: e.g. `'hello'` for a
|
|
90
101
|
* String, `42` for an integer, `[1,2,3]` for an array. NULL is `'\\N'`.
|
|
91
|
-
*
|
|
92
|
-
* raw strings, this library passes the value through verbatim so callers
|
|
102
|
+
* The value goes on the wire verbatim -- no auto-quoting -- so callers
|
|
93
103
|
* keep full control of typed and non-string values.) */
|
|
94
104
|
typedef struct chc_query_param {
|
|
95
105
|
const char *name;
|
|
@@ -122,11 +132,11 @@ typedef enum chc_packet_kind {
|
|
|
122
132
|
CHC_PKT_LOG = 10,
|
|
123
133
|
CHC_PKT_TABLE_COLUMNS = 11,
|
|
124
134
|
CHC_PKT_PROFILE_EVENTS = 14,
|
|
135
|
+
CHC_PKT_TIMEZONE_UPDATE = 17, /* no payload, see chc_server_info.timezone */
|
|
125
136
|
} chc_packet_kind;
|
|
126
137
|
|
|
127
138
|
/* CHC_PKT_EXCEPTION payload. Caller frees with chc_exception_free
|
|
128
139
|
* if produced. */
|
|
129
|
-
typedef struct chc_exception chc_exception;
|
|
130
140
|
struct chc_exception {
|
|
131
141
|
int32_t code;
|
|
132
142
|
char *name; /* allocated in chc_alloc */
|
|
@@ -143,7 +153,7 @@ typedef struct chc_packet {
|
|
|
143
153
|
chc_packet_kind kind;
|
|
144
154
|
|
|
145
155
|
/* Payload selected by kind; exactly one member is live, none for
|
|
146
|
-
* PONG / END_OF_STREAM / TABLE_COLUMNS. */
|
|
156
|
+
* PONG / END_OF_STREAM / TABLE_COLUMNS / TIMEZONE_UPDATE. */
|
|
147
157
|
union {
|
|
148
158
|
/* CHC_PKT_DATA / TOTALS / EXTREMES / LOG / PROFILE_EVENTS:
|
|
149
159
|
* caller-owned chc_block, freed with chc_block_destroy. */
|
|
@@ -155,7 +165,9 @@ typedef struct chc_packet {
|
|
|
155
165
|
/* CHC_PKT_PROGRESS. */
|
|
156
166
|
struct {
|
|
157
167
|
uint64_t rows, bytes, total_rows;
|
|
158
|
-
uint64_t
|
|
168
|
+
uint64_t total_bytes; /* 0 below rev 54463 */
|
|
169
|
+
uint64_t written_rows, written_bytes;
|
|
170
|
+
uint64_t elapsed_ns;
|
|
159
171
|
} progress;
|
|
160
172
|
|
|
161
173
|
/* CHC_PKT_PROFILE_INFO. */
|
|
@@ -191,25 +203,18 @@ int chc_client_send_ping(chc_client *c, chc_err *err);
|
|
|
191
203
|
#include <stdlib.h>
|
|
192
204
|
#include <string.h>
|
|
193
205
|
|
|
194
|
-
/*
|
|
195
|
-
|
|
196
|
-
#define
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
#define
|
|
200
|
-
#define
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
#define CHC__REV_OPENTELEMETRY 54442u
|
|
207
|
-
#define CHC__REV_DISTRIBUTED_DEPTH 54448u
|
|
208
|
-
#define CHC__REV_INITIAL_QUERY_START 54449u
|
|
209
|
-
#define CHC__REV_PARALLEL_REPLICAS 54453u
|
|
210
|
-
#define CHC__REV_CUSTOM_SERIALIZATION 54454u
|
|
211
|
-
#define CHC__REV_ADDENDUM 54458u
|
|
212
|
-
#define CHC__REV_PARAMETERS 54459u
|
|
206
|
+
/* Only gate above CHC_SERVER_MIN_REVISION, Progress omits total_bytes below
|
|
207
|
+
* it. TimezoneUpdate (54464) & sparse (54465) decode at any revision */
|
|
208
|
+
#define CHC__REV_TOTAL_BYTES_IN_PROGRESS 54463u
|
|
209
|
+
|
|
210
|
+
/* Mirror ClickHouse DBMS_MAX_HELLO_STRING_SIZE & DBMS_MAX_PASSWORD_COMPLEXITY_RULES */
|
|
211
|
+
#define CHC__MAX_HELLO_STRING 4096u
|
|
212
|
+
#define CHC__MAX_PASSWORD_RULES 256u
|
|
213
|
+
|
|
214
|
+
static const chc_block_opts chc__tcp_block_opts = {
|
|
215
|
+
.has_block_info = true,
|
|
216
|
+
.has_custom_serialization = true,
|
|
217
|
+
};
|
|
213
218
|
|
|
214
219
|
/* Client → server packet kinds. */
|
|
215
220
|
#define CHC__CLIENT_HELLO 0u
|
|
@@ -227,7 +232,6 @@ struct chc_client {
|
|
|
227
232
|
uint64_t client_version_major;
|
|
228
233
|
uint64_t client_version_minor;
|
|
229
234
|
uint64_t client_version_patch;
|
|
230
|
-
uint64_t client_revision;
|
|
231
235
|
chc_compression compression;
|
|
232
236
|
const chc_codec *codec;
|
|
233
237
|
|
|
@@ -303,19 +307,38 @@ chc__client_send_hello(chc_client *c, const chc_client_opts *opts, chc_err *err)
|
|
|
303
307
|
if ((rc = chc__write_string (c->io, name, name_len, err))) return rc;
|
|
304
308
|
if ((rc = chc__write_varuint(c->io, opts->client_version_major, err))) return rc;
|
|
305
309
|
if ((rc = chc__write_varuint(c->io, opts->client_version_minor, err))) return rc;
|
|
306
|
-
if ((rc = chc__write_varuint(c->io,
|
|
310
|
+
if ((rc = chc__write_varuint(c->io, CHC_CLIENT_REVISION, err))) return rc;
|
|
307
311
|
if ((rc = chc__write_string (c->io, db, strlen(db), err))) return rc;
|
|
308
312
|
if ((rc = chc__write_string (c->io, us, strlen(us), err))) return rc;
|
|
309
313
|
if ((rc = chc__write_string (c->io, pw, strlen(pw), err))) return rc;
|
|
310
314
|
return CHC_OK;
|
|
311
315
|
}
|
|
312
316
|
|
|
313
|
-
|
|
314
|
-
|
|
317
|
+
/* Read Hello-bounded server string into dst, truncating to cap - 1 bytes */
|
|
318
|
+
static int
|
|
319
|
+
chc__read_server_string(chc_in *in, char *dst, size_t cap, chc_err *err)
|
|
315
320
|
{
|
|
316
|
-
|
|
317
|
-
|
|
321
|
+
uint64_t len;
|
|
322
|
+
int rc = chc__read_varuint(in, &len, err);
|
|
323
|
+
if (rc != CHC_OK) return rc;
|
|
324
|
+
if (len > CHC__MAX_HELLO_STRING)
|
|
325
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL, "server string too long: %llu",
|
|
326
|
+
(unsigned long long) len);
|
|
327
|
+
size_t n = len < cap ? (size_t) len : cap - 1;
|
|
328
|
+
if ((rc = chc__read_bytes(in, dst, n, err))) return rc;
|
|
318
329
|
dst[n] = '\0';
|
|
330
|
+
return chc__skip_bytes(in, (size_t) len - n, err);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
static void
|
|
334
|
+
chc__client_setup(chc_client *c, const chc_client_opts *opts)
|
|
335
|
+
{
|
|
336
|
+
c->client_version_major = opts->client_version_major;
|
|
337
|
+
c->client_version_minor = opts->client_version_minor;
|
|
338
|
+
c->client_version_patch = opts->client_version_patch;
|
|
339
|
+
c->server.revision = CHC_CLIENT_REVISION;
|
|
340
|
+
c->compression = opts->codec ? opts->compression : CHC_COMP_NONE;
|
|
341
|
+
c->codec = opts->codec;
|
|
319
342
|
}
|
|
320
343
|
|
|
321
344
|
/* Reads chained exception. Caller frees via chc_exception_free. */
|
|
@@ -339,7 +362,7 @@ chc__read_exception(chc_client *c, chc_exception **out, chc_err *err)
|
|
|
339
362
|
}
|
|
340
363
|
|
|
341
364
|
static int
|
|
342
|
-
chc__client_recv_hello(chc_client *c, chc_err *err)
|
|
365
|
+
chc__client_recv_hello(chc_client *c, chc_exception **exc, chc_err *err)
|
|
343
366
|
{
|
|
344
367
|
uint64_t kind;
|
|
345
368
|
int rc = chc__read_varuint(&c->in, &kind, err);
|
|
@@ -348,12 +371,9 @@ chc__client_recv_hello(chc_client *c, chc_err *err)
|
|
|
348
371
|
chc_exception *e = NULL;
|
|
349
372
|
rc = chc__read_exception(c, &e, err);
|
|
350
373
|
if (rc != CHC_OK) return rc;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
err
|
|
354
|
-
chc__copy_short(err->server_name, sizeof err->server_name,
|
|
355
|
-
e->name, e->name_len);
|
|
356
|
-
chc_exception_free(e, c->al);
|
|
374
|
+
if (exc) *exc = e;
|
|
375
|
+
else chc_exception_free(e, c->al);
|
|
376
|
+
chc_err_reset(err);
|
|
357
377
|
return CHC_ERR_SERVER;
|
|
358
378
|
}
|
|
359
379
|
if (kind != CHC_PKT_HELLO)
|
|
@@ -361,33 +381,40 @@ chc__client_recv_hello(chc_client *c, chc_err *err)
|
|
|
361
381
|
"expected Hello, got %llu",
|
|
362
382
|
(unsigned long long) kind);
|
|
363
383
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
384
|
+
/* Commit only complete Hello, async handshake retries on WOULD_BLOCK */
|
|
385
|
+
chc_server_info s = {};
|
|
386
|
+
uint64_t n_rules, nonce;
|
|
387
|
+
char rule[1];
|
|
388
|
+
if ((rc = chc__read_server_string(&c->in, s.name, sizeof s.name, err)) ||
|
|
389
|
+
(rc = chc__read_varuint(&c->in, &s.version_major, err)) ||
|
|
390
|
+
(rc = chc__read_varuint(&c->in, &s.version_minor, err)) ||
|
|
391
|
+
(rc = chc__read_varuint(&c->in, &s.revision, err)))
|
|
392
|
+
return rc;
|
|
393
|
+
if (s.revision < CHC_SERVER_MIN_REVISION)
|
|
394
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
395
|
+
"server revision %llu older than %u",
|
|
396
|
+
(unsigned long long) s.revision, CHC_SERVER_MIN_REVISION);
|
|
397
|
+
if (s.revision > CHC_CLIENT_REVISION) s.revision = CHC_CLIENT_REVISION;
|
|
398
|
+
|
|
399
|
+
if ((rc = chc__read_server_string(&c->in, s.timezone, sizeof s.timezone, err)) ||
|
|
400
|
+
(rc = chc__read_server_string(&c->in, s.display_name, sizeof s.display_name, err)) ||
|
|
401
|
+
(rc = chc__read_varuint(&c->in, &s.version_patch, err)) ||
|
|
402
|
+
(rc = chc__read_varuint(&c->in, &n_rules, err)))
|
|
403
|
+
return rc;
|
|
404
|
+
if (n_rules > CHC__MAX_PASSWORD_RULES)
|
|
405
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL, "too many password rules: %llu",
|
|
406
|
+
(unsigned long long) n_rules);
|
|
407
|
+
/* Pattern & message pairs, discarded */
|
|
408
|
+
for (uint64_t i = 0; i < 2 * n_rules; i++)
|
|
409
|
+
if ((rc = chc__read_server_string(&c->in, rule, sizeof rule, err))) return rc;
|
|
410
|
+
/* Nonce only serves interserver secret */
|
|
411
|
+
if ((rc = chc__read_u64_le(&c->in, &nonce, err))) return rc;
|
|
412
|
+
c->server = s;
|
|
386
413
|
return CHC_OK;
|
|
387
414
|
}
|
|
388
415
|
|
|
389
416
|
static int
|
|
390
|
-
chc__recv_pong(chc_client *c, chc_err *err)
|
|
417
|
+
chc__recv_pong(chc_client *c, chc_exception **exc, chc_err *err)
|
|
391
418
|
{
|
|
392
419
|
bool ioless = c->in.io == NULL;
|
|
393
420
|
if (ioless) chc__in_checkpoint(&c->in);
|
|
@@ -398,12 +425,9 @@ chc__recv_pong(chc_client *c, chc_err *err)
|
|
|
398
425
|
chc_exception *e = NULL;
|
|
399
426
|
rc = chc__read_exception(c, &e, err); /* frees its partial on non-OK */
|
|
400
427
|
if (rc != CHC_OK) goto maybe_rewind;
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
err
|
|
404
|
-
chc__copy_short(err->server_name, sizeof err->server_name,
|
|
405
|
-
e->name, e->name_len);
|
|
406
|
-
chc_exception_free(e, c->al);
|
|
428
|
+
if (exc) *exc = e;
|
|
429
|
+
else chc_exception_free(e, c->al);
|
|
430
|
+
chc_err_reset(err);
|
|
407
431
|
return CHC_ERR_SERVER;
|
|
408
432
|
}
|
|
409
433
|
if (kind != CHC_PKT_PONG)
|
|
@@ -417,7 +441,8 @@ maybe_rewind:
|
|
|
417
441
|
|
|
418
442
|
int
|
|
419
443
|
chc_client_init(chc_client **out, const chc_client_opts *opts,
|
|
420
|
-
const chc_alloc *al, chc_io *io,
|
|
444
|
+
const chc_alloc *al, chc_io *io, chc_exception **exc,
|
|
445
|
+
chc_err *err)
|
|
421
446
|
{
|
|
422
447
|
chc_client_opts def_opts = {};
|
|
423
448
|
if (!opts) opts = &def_opts;
|
|
@@ -426,44 +451,30 @@ chc_client_init(chc_client **out, const chc_client_opts *opts,
|
|
|
426
451
|
if (!c) return CHC_ERR_OOM;
|
|
427
452
|
c->al = al;
|
|
428
453
|
c->io = io;
|
|
429
|
-
|
|
430
|
-
c
|
|
431
|
-
c->client_version_patch = opts->client_version_patch;
|
|
432
|
-
c->client_revision = opts->client_revision ? opts->client_revision
|
|
433
|
-
: CHC_CLIENT_DEFAULT_REVISION;
|
|
434
|
-
c->compression = opts->codec ? opts->compression : CHC_COMP_NONE;
|
|
435
|
-
c->codec = opts->codec;
|
|
454
|
+
|
|
455
|
+
chc__client_setup(c, opts);
|
|
436
456
|
|
|
437
457
|
int rc = chc_in_init(&c->in, io, al, opts->read_buffer_bytes, err);
|
|
438
458
|
if (rc != CHC_OK) { al->free(al->ud, c, sizeof *c); return rc; }
|
|
439
459
|
|
|
440
460
|
rc = chc__client_send_hello(c, opts, err);
|
|
441
461
|
if (rc != CHC_OK) goto fail;
|
|
442
|
-
rc = chc__client_recv_hello(c, err);
|
|
462
|
+
rc = chc__client_recv_hello(c, exc, err);
|
|
443
463
|
if (rc != CHC_OK) goto fail;
|
|
444
464
|
|
|
445
|
-
/* Server's effective revision is min(ours, server). After the
|
|
446
|
-
* handshake we use this to gate optional fields on subsequent
|
|
447
|
-
* packets. */
|
|
448
|
-
if (c->server.revision > c->client_revision)
|
|
449
|
-
c->server.revision = c->client_revision;
|
|
450
|
-
|
|
451
465
|
/* Addendum: send empty quota_key. */
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if (rc != CHC_OK) goto fail;
|
|
455
|
-
}
|
|
466
|
+
rc = chc__write_string(c->io, "", 0, err);
|
|
467
|
+
if (rc != CHC_OK) goto fail;
|
|
456
468
|
|
|
457
469
|
/* Probe Ping. Server-side late-stage rejections (eg invalid
|
|
458
470
|
* default_database in 24.x) only surface after the Addendum is read,
|
|
459
471
|
* not in the Hello reply. Without a probe, the rejection races the
|
|
460
472
|
* caller's first query: caller's writes may hit ECONNRESET before the
|
|
461
473
|
* exception packet is read. The Ping forces a round-trip here so the
|
|
462
|
-
* exception is delivered at init time instead.
|
|
463
|
-
* SetPingBeforeQuery posture for the connection-establishment case. */
|
|
474
|
+
* exception is delivered at init time instead. */
|
|
464
475
|
rc = chc_client_send_ping(c, err);
|
|
465
476
|
if (rc != CHC_OK) goto fail;
|
|
466
|
-
rc = chc__recv_pong(c, err);
|
|
477
|
+
rc = chc__recv_pong(c, exc, err);
|
|
467
478
|
if (rc != CHC_OK) goto fail;
|
|
468
479
|
|
|
469
480
|
*out = c;
|
|
@@ -507,20 +518,13 @@ chc_client_send_cancel(chc_client *c, chc_err *err)
|
|
|
507
518
|
* both the uncompressed direct path and the compressed buffer-then-emit
|
|
508
519
|
* path. */
|
|
509
520
|
static int
|
|
510
|
-
chc__client_write_block_body(
|
|
511
|
-
|
|
521
|
+
chc__client_write_block_body(chc_io *sink, const chc_block_builder *bb,
|
|
522
|
+
chc_err *err)
|
|
512
523
|
{
|
|
513
524
|
int rc;
|
|
514
|
-
|
|
515
|
-
.has_block_info = c->server.revision >= CHC__REV_BLOCK_INFO,
|
|
516
|
-
.has_custom_serialization = c->server.revision >= CHC__REV_CUSTOM_SERIALIZATION,
|
|
517
|
-
};
|
|
518
|
-
if (bb) return chc_block_write(sink, bb, &opts, err);
|
|
525
|
+
if (bb) return chc_block_write(sink, bb, &chc__tcp_block_opts, err);
|
|
519
526
|
|
|
520
|
-
if (
|
|
521
|
-
rc = chc__write_block_info(sink, err);
|
|
522
|
-
if (rc != CHC_OK) return rc;
|
|
523
|
-
}
|
|
527
|
+
if ((rc = chc__write_block_info(sink, err))) return rc;
|
|
524
528
|
if ((rc = chc__write_varuint(sink, 0, err))) return rc; /* n_cols */
|
|
525
529
|
if ((rc = chc__write_varuint(sink, 0, err))) return rc; /* n_rows */
|
|
526
530
|
return CHC_OK;
|
|
@@ -535,12 +539,10 @@ chc__client_write_data(chc_client *c, const chc_block_builder *bb, chc_err *err)
|
|
|
535
539
|
int rc;
|
|
536
540
|
if ((rc = chc__write_varuint(c->io, CHC__CLIENT_DATA, err))) return rc;
|
|
537
541
|
/* Temporary table name (always empty from us). */
|
|
538
|
-
if (c->
|
|
539
|
-
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
540
|
-
}
|
|
542
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
541
543
|
|
|
542
544
|
if (c->compression == CHC_COMP_NONE) {
|
|
543
|
-
return chc__client_write_block_body(c
|
|
545
|
+
return chc__client_write_block_body(c->io, bb, err);
|
|
544
546
|
}
|
|
545
547
|
|
|
546
548
|
if (!c->codec)
|
|
@@ -550,7 +552,7 @@ chc__client_write_data(chc_client *c, const chc_block_builder *bb, chc_err *err)
|
|
|
550
552
|
chc__mem_sink ms;
|
|
551
553
|
chc_io sink_io;
|
|
552
554
|
chc__mem_sink_init(&ms, &sink_io, c->al);
|
|
553
|
-
rc = chc__client_write_block_body(
|
|
555
|
+
rc = chc__client_write_block_body(&sink_io, bb, err);
|
|
554
556
|
if (rc != CHC_OK) { chc__mem_sink_free(&ms); return rc; }
|
|
555
557
|
rc = chc__comp_emit_chunks(c->io, c->codec, c->compression,
|
|
556
558
|
ms.buf, ms.len, c->al, err);
|
|
@@ -575,71 +577,48 @@ chc_client_send_query_ex(chc_client *c, const char *sql, size_t sql_len,
|
|
|
575
577
|
if ((rc = chc__write_varuint(c->io, CHC__CLIENT_QUERY, err))) return rc;
|
|
576
578
|
if ((rc = chc__write_string (c->io, opts->query_id, opts->query_id_len, err))) return rc;
|
|
577
579
|
|
|
578
|
-
/* ClientInfo.
|
|
579
|
-
* the minimum the server tolerates (initial fields blank,
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (c->server.revision >= CHC__REV_OPENTELEMETRY) {
|
|
606
|
-
uint8_t no_otel = 0;
|
|
607
|
-
if ((rc = chc__write_bytes(c->io, &no_otel, 1, err))) return rc;
|
|
608
|
-
}
|
|
609
|
-
if (c->server.revision >= CHC__REV_PARALLEL_REPLICAS) {
|
|
610
|
-
if ((rc = chc__write_varuint(c->io, 0, err))) return rc;
|
|
611
|
-
if ((rc = chc__write_varuint(c->io, 0, err))) return rc;
|
|
612
|
-
if ((rc = chc__write_varuint(c->io, 0, err))) return rc;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
580
|
+
/* ClientInfo. Body layout per the Native Protocol spec (Query packet).
|
|
581
|
+
* We send the minimum the server tolerates (initial fields blank,
|
|
582
|
+
* iface=TCP); only client_name identifies us. */
|
|
583
|
+
uint8_t query_kind = 1; /* INITIAL_QUERY */
|
|
584
|
+
uint8_t z8[8] = {};
|
|
585
|
+
uint8_t iface_type = 1; /* TCP */
|
|
586
|
+
uint8_t no_otel = 0;
|
|
587
|
+
if ((rc = chc__write_bytes (c->io, &query_kind, 1, err))) return rc;
|
|
588
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* initial_user */
|
|
589
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* initial_query_id */
|
|
590
|
+
if ((rc = chc__write_string(c->io, "[::ffff:127.0.0.1]:0", 20, err))) return rc;
|
|
591
|
+
if ((rc = chc__write_bytes (c->io, z8, 8, err))) return rc; /* initial_query_start_time */
|
|
592
|
+
if ((rc = chc__write_bytes (c->io, &iface_type, 1, err))) return rc;
|
|
593
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* os_user */
|
|
594
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* client_hostname */
|
|
595
|
+
if ((rc = chc__write_string(c->io, "clickhouse-c client", 19, err))) return rc;
|
|
596
|
+
if ((rc = chc__write_varuint(c->io, c->client_version_major, err))) return rc;
|
|
597
|
+
if ((rc = chc__write_varuint(c->io, c->client_version_minor, err))) return rc;
|
|
598
|
+
if ((rc = chc__write_varuint(c->io, CHC_CLIENT_REVISION, err))) return rc;
|
|
599
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* quota_key */
|
|
600
|
+
if ((rc = chc__write_varuint(c->io, 0, err))) return rc; /* distributed_depth */
|
|
601
|
+
if ((rc = chc__write_varuint(c->io, c->client_version_patch, err))) return rc;
|
|
602
|
+
if ((rc = chc__write_bytes (c->io, &no_otel, 1, err))) return rc;
|
|
603
|
+
/* Parallel replicas: collaborate_with_initiator, count, number */
|
|
604
|
+
if ((rc = chc__write_varuint(c->io, 0, err))) return rc;
|
|
605
|
+
if ((rc = chc__write_varuint(c->io, 0, err))) return rc;
|
|
606
|
+
if ((rc = chc__write_varuint(c->io, 0, err))) return rc;
|
|
615
607
|
|
|
616
608
|
/* Per-query settings: name + varuint(flags) + value, repeated, then
|
|
617
|
-
* empty-string terminator.
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if ((rc = chc__write_string (c->io, s->name, nlen, err))) return rc;
|
|
627
|
-
if ((rc = chc__write_varuint(c->io, flags, err))) return rc;
|
|
628
|
-
if ((rc = chc__write_string (c->io, s->value, vlen, err))) return rc;
|
|
629
|
-
}
|
|
630
|
-
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
631
|
-
} else {
|
|
632
|
-
if (opts->n_settings)
|
|
633
|
-
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
634
|
-
"server revision %llu < %u: query settings unsupported",
|
|
635
|
-
(unsigned long long) c->server.revision,
|
|
636
|
-
CHC__REV_SETTINGS_AS_STRINGS);
|
|
637
|
-
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
609
|
+
* empty-string terminator. */
|
|
610
|
+
for (size_t i = 0; i < opts->n_settings; i++) {
|
|
611
|
+
const chc_query_setting *s = &opts->settings[i];
|
|
612
|
+
size_t nlen = s->name ? strlen(s->name) : 0;
|
|
613
|
+
size_t vlen = s->value ? strlen(s->value) : 0;
|
|
614
|
+
uint64_t flags = (s->important ? 1u : 0u) | (s->custom ? 2u : 0u);
|
|
615
|
+
if ((rc = chc__write_string (c->io, s->name, nlen, err))) return rc;
|
|
616
|
+
if ((rc = chc__write_varuint(c->io, flags, err))) return rc;
|
|
617
|
+
if ((rc = chc__write_string (c->io, s->value, vlen, err))) return rc;
|
|
638
618
|
}
|
|
619
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
639
620
|
|
|
640
|
-
if (c->
|
|
641
|
-
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
642
|
-
}
|
|
621
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* interserver secret */
|
|
643
622
|
|
|
644
623
|
/* Stages::Complete = 2. */
|
|
645
624
|
if ((rc = chc__write_varuint(c->io, 2, err))) return rc;
|
|
@@ -650,21 +629,15 @@ chc_client_send_query_ex(chc_client *c, const char *sql, size_t sql_len,
|
|
|
650
629
|
if ((rc = chc__write_string(c->io, sql, sql_len, err))) return rc;
|
|
651
630
|
|
|
652
631
|
/* Parameters: same shape as settings; flags always CUSTOM (bit 1). */
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if ((rc = chc__write_string (c->io, p->value, vlen, err))) return rc;
|
|
661
|
-
}
|
|
662
|
-
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
663
|
-
} else if (opts->n_params) {
|
|
664
|
-
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
665
|
-
"server revision %llu < %u: query parameters unsupported",
|
|
666
|
-
(unsigned long long) c->server.revision, CHC__REV_PARAMETERS);
|
|
632
|
+
for (size_t i = 0; i < opts->n_params; i++) {
|
|
633
|
+
const chc_query_param *p = &opts->params[i];
|
|
634
|
+
size_t nlen = p->name ? strlen(p->name) : 0;
|
|
635
|
+
size_t vlen = p->value ? strlen(p->value) : 0;
|
|
636
|
+
if ((rc = chc__write_string (c->io, p->name, nlen, err))) return rc;
|
|
637
|
+
if ((rc = chc__write_varuint(c->io, 2u, err))) return rc;
|
|
638
|
+
if ((rc = chc__write_string (c->io, p->value, vlen, err))) return rc;
|
|
667
639
|
}
|
|
640
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
668
641
|
|
|
669
642
|
/* Finalize: send an empty Data block as the query-text terminator. */
|
|
670
643
|
return chc__client_write_data(c, NULL, err);
|
|
@@ -698,14 +671,10 @@ chc_packet_clear(chc_client *c, chc_packet *p)
|
|
|
698
671
|
}
|
|
699
672
|
}
|
|
700
673
|
|
|
701
|
-
/*
|
|
702
|
-
* DATA/TOTALS/EXTREMES gate a temp-table name on REV_TEMPORARY_TABLES (gated=1);
|
|
703
|
-
* LOG/PROFILE_EVENTS always prepend a tag (gated=0). */
|
|
674
|
+
/* Skip temp-table name or log tag block-bearing packets carry before block */
|
|
704
675
|
static int
|
|
705
|
-
chc__recv_skip_lead_string(chc_client *c,
|
|
676
|
+
chc__recv_skip_lead_string(chc_client *c, chc_err *err)
|
|
706
677
|
{
|
|
707
|
-
if (gated && c->server.revision < CHC__REV_TEMPORARY_TABLES)
|
|
708
|
-
return CHC_OK;
|
|
709
678
|
char *s; size_t slen;
|
|
710
679
|
int rc = chc__read_string(&c->in, &s, &slen, err);
|
|
711
680
|
if (rc != CHC_OK) return rc;
|
|
@@ -834,10 +803,7 @@ static int
|
|
|
834
803
|
chc__recv_packet_resumable(chc_client *c, chc_packet *out, chc_err *err)
|
|
835
804
|
{
|
|
836
805
|
bool ioless = c->in.io == NULL;
|
|
837
|
-
chc_block_opts opts =
|
|
838
|
-
.has_block_info = c->server.revision >= CHC__REV_BLOCK_INFO,
|
|
839
|
-
.has_custom_serialization = c->server.revision >= CHC__REV_CUSTOM_SERIALIZATION,
|
|
840
|
-
};
|
|
806
|
+
const chc_block_opts *opts = &chc__tcp_block_opts;
|
|
841
807
|
int rc;
|
|
842
808
|
int is_log = 0; /* LOG/PROFILE_EVENTS: never compressed */
|
|
843
809
|
|
|
@@ -859,17 +825,17 @@ chc__recv_packet_resumable(chc_client *c, chc_packet *out, chc_err *err)
|
|
|
859
825
|
|
|
860
826
|
case CHC_PKT_PROGRESS:
|
|
861
827
|
out->kind = CHC_PKT_PROGRESS;
|
|
862
|
-
if ((rc = chc__read_varuint(&c->in, &out->progress.rows,
|
|
863
|
-
(rc = chc__read_varuint(&c->in, &out->progress.bytes,
|
|
828
|
+
if ((rc = chc__read_varuint(&c->in, &out->progress.rows, err)) ||
|
|
829
|
+
(rc = chc__read_varuint(&c->in, &out->progress.bytes, err)) ||
|
|
830
|
+
(rc = chc__read_varuint(&c->in, &out->progress.total_rows, err)))
|
|
864
831
|
goto maybe_rewind;
|
|
865
|
-
if (c->server.revision >=
|
|
866
|
-
if ((rc = chc__read_varuint(&c->in, &out->progress.
|
|
832
|
+
if (c->server.revision >= CHC__REV_TOTAL_BYTES_IN_PROGRESS)
|
|
833
|
+
if ((rc = chc__read_varuint(&c->in, &out->progress.total_bytes, err)))
|
|
867
834
|
goto maybe_rewind;
|
|
868
|
-
if (c->
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
}
|
|
835
|
+
if ((rc = chc__read_varuint(&c->in, &out->progress.written_rows, err)) ||
|
|
836
|
+
(rc = chc__read_varuint(&c->in, &out->progress.written_bytes, err)) ||
|
|
837
|
+
(rc = chc__read_varuint(&c->in, &out->progress.elapsed_ns, err)))
|
|
838
|
+
goto maybe_rewind;
|
|
873
839
|
goto control_done;
|
|
874
840
|
|
|
875
841
|
case CHC_PKT_PONG:
|
|
@@ -902,6 +868,15 @@ chc__recv_packet_resumable(chc_client *c, chc_packet *out, chc_err *err)
|
|
|
902
868
|
goto control_done;
|
|
903
869
|
}
|
|
904
870
|
|
|
871
|
+
/* Empty timezone means server default, as clickhouse-client treats it */
|
|
872
|
+
case CHC_PKT_TIMEZONE_UPDATE: {
|
|
873
|
+
out->kind = CHC_PKT_TIMEZONE_UPDATE;
|
|
874
|
+
char tz[sizeof c->server.timezone];
|
|
875
|
+
if ((rc = chc__read_server_string(&c->in, tz, sizeof tz, err))) goto maybe_rewind;
|
|
876
|
+
memcpy(c->server.timezone, tz, sizeof tz);
|
|
877
|
+
goto control_done;
|
|
878
|
+
}
|
|
879
|
+
|
|
905
880
|
/* ---- block-bearing: commit kind + leading string, then resume ---- */
|
|
906
881
|
case CHC_PKT_DATA: out->kind = c->recv_kind = CHC_PKT_DATA; break;
|
|
907
882
|
case CHC_PKT_TOTALS: out->kind = c->recv_kind = CHC_PKT_TOTALS; break;
|
|
@@ -916,7 +891,7 @@ chc__recv_packet_resumable(chc_client *c, chc_packet *out, chc_err *err)
|
|
|
916
891
|
(unsigned long long) kind);
|
|
917
892
|
}
|
|
918
893
|
|
|
919
|
-
rc = chc__recv_skip_lead_string(c,
|
|
894
|
+
rc = chc__recv_skip_lead_string(c, err);
|
|
920
895
|
if (rc != CHC_OK) goto maybe_rewind;
|
|
921
896
|
|
|
922
897
|
/* Kind + leading string committed; resume owns the cursor from here.
|
|
@@ -934,7 +909,7 @@ chc__recv_packet_resumable(chc_client *c, chc_packet *out, chc_err *err)
|
|
|
934
909
|
/* LOG/PROFILE_EVENTS blocks are never compressed; route through the
|
|
935
910
|
* uncompressed resume path regardless of c->compression. */
|
|
936
911
|
if (c->compression == CHC_COMP_NONE || is_log) {
|
|
937
|
-
rc = chc__block_resume_in(&c->in, c->al,
|
|
912
|
+
rc = chc__block_resume_in(&c->in, c->al, opts,
|
|
938
913
|
&c->recv_partial, &c->recv_next_col, err);
|
|
939
914
|
if (rc == CHC_WOULD_BLOCK) return rc; /* resume owns rewind; partial retained */
|
|
940
915
|
c->recv_in_block = 0;
|
|
@@ -954,7 +929,7 @@ chc__recv_packet_resumable(chc_client *c, chc_packet *out, chc_err *err)
|
|
|
954
929
|
* decompressor + ioless decompressed buffer. io-backed:
|
|
955
930
|
* baseline rebuild-per-call (chc__recv_block_compressed). */
|
|
956
931
|
if (ioless) {
|
|
957
|
-
rc = chc__recv_block_compressed_resume(c,
|
|
932
|
+
rc = chc__recv_block_compressed_resume(c, opts, err);
|
|
958
933
|
if (rc == CHC_WOULD_BLOCK) return rc; /* partial block + decomp state retained */
|
|
959
934
|
c->recv_in_block = 0;
|
|
960
935
|
if (rc != CHC_OK) return rc; /* resume tore down its state + recv_partial */
|
|
@@ -965,7 +940,7 @@ chc__recv_packet_resumable(chc_client *c, chc_packet *out, chc_err *err)
|
|
|
965
940
|
return CHC_OK;
|
|
966
941
|
}
|
|
967
942
|
|
|
968
|
-
rc = chc__recv_block_compressed(c,
|
|
943
|
+
rc = chc__recv_block_compressed(c, opts, &out->block, err);
|
|
969
944
|
if (rc == CHC_WOULD_BLOCK) return rc;
|
|
970
945
|
c->recv_in_block = 0;
|
|
971
946
|
if (rc != CHC_OK) return rc;
|