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,995 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* clickhouse-client.h -- TCP packet loop over chc_io.
|
|
3
|
+
*
|
|
4
|
+
* Exactly one TU must `#define CHC_IMPLEMENTATION` before including;
|
|
5
|
+
* the implementation uses internal varint / io / block helpers from
|
|
6
|
+
* clickhouse.h.
|
|
7
|
+
*
|
|
8
|
+
* Caller owns the chc_io (socket setup, TLS, timeouts, cancel polling).
|
|
9
|
+
* One chc_client wraps one connection. No reconnect / endpoint failover
|
|
10
|
+
* / DNS — caller-side concerns.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
#ifndef CLICKHOUSE_CLIENT_H
|
|
14
|
+
#define CLICKHOUSE_CLIENT_H
|
|
15
|
+
|
|
16
|
+
#include <stdbool.h>
|
|
17
|
+
#include <stdint.h>
|
|
18
|
+
|
|
19
|
+
#include "clickhouse.h"
|
|
20
|
+
#include "clickhouse-compression.h" /* chc_compression enum, chc_codec struct */
|
|
21
|
+
|
|
22
|
+
#ifdef __cplusplus
|
|
23
|
+
extern "C" {
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
/* Default protocol revision the client speaks. Matches clickhouse-cpp's
|
|
27
|
+
* DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS pin. */
|
|
28
|
+
#define CHC_CLIENT_DEFAULT_REVISION 54459u
|
|
29
|
+
|
|
30
|
+
typedef struct chc_client_opts {
|
|
31
|
+
/* Identity. Defaults applied when fields are zero/NULL. */
|
|
32
|
+
const char *client_name; /* default "clickhouse-c" */
|
|
33
|
+
uint64_t client_version_major; /* default 0 */
|
|
34
|
+
uint64_t client_version_minor; /* default 0 */
|
|
35
|
+
uint64_t client_version_patch; /* default 0 */
|
|
36
|
+
uint64_t client_revision; /* default CHC_CLIENT_DEFAULT_REVISION */
|
|
37
|
+
|
|
38
|
+
/* Hello body. */
|
|
39
|
+
const char *database; /* default "default" */
|
|
40
|
+
const char *user; /* default "default" */
|
|
41
|
+
const char *password; /* default "" */
|
|
42
|
+
|
|
43
|
+
/* Compression. CHC_COMP_NONE if codec is NULL. */
|
|
44
|
+
chc_compression compression;
|
|
45
|
+
const chc_codec *codec;
|
|
46
|
+
|
|
47
|
+
/* Internal read buffer size. 0 = use CHC_READ_BUFFER (8 KiB). */
|
|
48
|
+
size_t read_buffer_bytes;
|
|
49
|
+
} chc_client_opts;
|
|
50
|
+
|
|
51
|
+
typedef struct chc_server_info {
|
|
52
|
+
char name[64];
|
|
53
|
+
char timezone[64];
|
|
54
|
+
char display_name[128];
|
|
55
|
+
uint64_t version_major;
|
|
56
|
+
uint64_t version_minor;
|
|
57
|
+
uint64_t version_patch;
|
|
58
|
+
uint64_t revision; /* min(client_revision, server_revision) */
|
|
59
|
+
} chc_server_info;
|
|
60
|
+
|
|
61
|
+
typedef struct chc_client chc_client;
|
|
62
|
+
|
|
63
|
+
/* Performs Hello / HelloAck handshake immediately. On failure caller may
|
|
64
|
+
* call chc_client_close to free any partially-allocated state. */
|
|
65
|
+
int chc_client_init(chc_client **out, const chc_client_opts *opts,
|
|
66
|
+
const chc_alloc *al, chc_io *io, chc_err *err);
|
|
67
|
+
|
|
68
|
+
void chc_client_close(chc_client *c);
|
|
69
|
+
|
|
70
|
+
const chc_server_info *chc_client_server_info(const chc_client *c);
|
|
71
|
+
|
|
72
|
+
int chc_client_send_query(chc_client *c,
|
|
73
|
+
const char *sql, size_t sql_len,
|
|
74
|
+
const char *query_id, size_t query_id_len,
|
|
75
|
+
chc_err *err);
|
|
76
|
+
|
|
77
|
+
/* Per-query setting. name / value are NUL-terminated. Matches
|
|
78
|
+
* clickhouse-cpp's QuerySettingsField.flags low two bits. */
|
|
79
|
+
typedef struct chc_query_setting {
|
|
80
|
+
const char *name;
|
|
81
|
+
const char *value;
|
|
82
|
+
bool important; /* flag bit 0 */
|
|
83
|
+
bool custom; /* flag bit 1 (user-defined "SET custom_*=...") */
|
|
84
|
+
} chc_query_setting;
|
|
85
|
+
|
|
86
|
+
/* Per-query parameter (substituted into `{name:Type}` placeholders in the
|
|
87
|
+
* SQL text). name / value are NUL-terminated. The wire-level flags byte is
|
|
88
|
+
* always CUSTOM. The server parses value via Field::restoreFromDump, so
|
|
89
|
+
* callers must format value as a typed Field literal: e.g. `'hello'` for a
|
|
90
|
+
* String, `42` for an integer, `[1,2,3]` for an array. NULL is `'\\N'`.
|
|
91
|
+
* (Unlike clickhouse-cpp's higher-level Client::SetParam, which auto-quotes
|
|
92
|
+
* raw strings, this library passes the value through verbatim so callers
|
|
93
|
+
* keep full control of typed and non-string values.) */
|
|
94
|
+
typedef struct chc_query_param {
|
|
95
|
+
const char *name;
|
|
96
|
+
const char *value;
|
|
97
|
+
} chc_query_param;
|
|
98
|
+
|
|
99
|
+
typedef struct chc_query_opts {
|
|
100
|
+
const char *query_id;
|
|
101
|
+
size_t query_id_len;
|
|
102
|
+
const chc_query_setting *settings;
|
|
103
|
+
size_t n_settings;
|
|
104
|
+
const chc_query_param *params;
|
|
105
|
+
size_t n_params;
|
|
106
|
+
} chc_query_opts;
|
|
107
|
+
|
|
108
|
+
int chc_client_send_query_ex(chc_client *c,
|
|
109
|
+
const char *sql, size_t sql_len,
|
|
110
|
+
const chc_query_opts *opts, chc_err *err);
|
|
111
|
+
|
|
112
|
+
typedef enum chc_packet_kind {
|
|
113
|
+
CHC_PKT_HELLO = 0,
|
|
114
|
+
CHC_PKT_DATA = 1,
|
|
115
|
+
CHC_PKT_EXCEPTION = 2,
|
|
116
|
+
CHC_PKT_PROGRESS = 3,
|
|
117
|
+
CHC_PKT_PONG = 4,
|
|
118
|
+
CHC_PKT_END_OF_STREAM = 5,
|
|
119
|
+
CHC_PKT_PROFILE_INFO = 6,
|
|
120
|
+
CHC_PKT_TOTALS = 7,
|
|
121
|
+
CHC_PKT_EXTREMES = 8,
|
|
122
|
+
CHC_PKT_LOG = 10,
|
|
123
|
+
CHC_PKT_TABLE_COLUMNS = 11,
|
|
124
|
+
CHC_PKT_PROFILE_EVENTS = 14,
|
|
125
|
+
} chc_packet_kind;
|
|
126
|
+
|
|
127
|
+
/* CHC_PKT_EXCEPTION payload. Caller frees with chc_exception_free
|
|
128
|
+
* if produced. */
|
|
129
|
+
typedef struct chc_exception chc_exception;
|
|
130
|
+
struct chc_exception {
|
|
131
|
+
int32_t code;
|
|
132
|
+
char *name; /* allocated in chc_alloc */
|
|
133
|
+
size_t name_len;
|
|
134
|
+
char *display_text;
|
|
135
|
+
size_t display_text_len;
|
|
136
|
+
char *stack_trace;
|
|
137
|
+
size_t stack_trace_len;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
void chc_exception_free(chc_exception *e, const chc_alloc *al);
|
|
141
|
+
|
|
142
|
+
typedef struct chc_packet {
|
|
143
|
+
chc_packet_kind kind;
|
|
144
|
+
|
|
145
|
+
/* Payload selected by kind; exactly one member is live, none for
|
|
146
|
+
* PONG / END_OF_STREAM / TABLE_COLUMNS. */
|
|
147
|
+
union {
|
|
148
|
+
/* CHC_PKT_DATA / TOTALS / EXTREMES / LOG / PROFILE_EVENTS:
|
|
149
|
+
* caller-owned chc_block, freed with chc_block_destroy. */
|
|
150
|
+
chc_block *block;
|
|
151
|
+
|
|
152
|
+
/* CHC_PKT_EXCEPTION: caller-owned, freed with chc_exception_free. */
|
|
153
|
+
chc_exception *exception;
|
|
154
|
+
|
|
155
|
+
/* CHC_PKT_PROGRESS. */
|
|
156
|
+
struct {
|
|
157
|
+
uint64_t rows, bytes, total_rows;
|
|
158
|
+
uint64_t written_rows, written_bytes; /* >= rev 54420 */
|
|
159
|
+
} progress;
|
|
160
|
+
|
|
161
|
+
/* CHC_PKT_PROFILE_INFO. */
|
|
162
|
+
struct {
|
|
163
|
+
uint64_t rows, blocks, bytes, rows_before_limit;
|
|
164
|
+
uint8_t applied_limit, calculated_rows_before_limit;
|
|
165
|
+
} profile;
|
|
166
|
+
};
|
|
167
|
+
} chc_packet;
|
|
168
|
+
|
|
169
|
+
/* Read the next packet. On exception packets the caller MUST inspect
|
|
170
|
+
* out->kind == CHC_PKT_EXCEPTION; the function returns CHC_OK with the
|
|
171
|
+
* exception attached, not CHC_ERR_SERVER. */
|
|
172
|
+
int chc_client_recv_packet(chc_client *c, chc_packet *out, chc_err *err);
|
|
173
|
+
|
|
174
|
+
/* Free anything chc_client_recv_packet allocated for this packet:
|
|
175
|
+
* the block (if any) and the exception chain (if any). Safe to call
|
|
176
|
+
* with packet->{block,exception} already NULLed by the caller (after
|
|
177
|
+
* the caller takes ownership). */
|
|
178
|
+
void chc_packet_clear(chc_client *c, chc_packet *p);
|
|
179
|
+
|
|
180
|
+
/* Send a Data block. bb == NULL emits an empty block which the server
|
|
181
|
+
* interprets as "no more INSERT rows" or as the query-text terminator
|
|
182
|
+
* sent at the end of every SendQuery. */
|
|
183
|
+
int chc_client_send_data(chc_client *c, const chc_block_builder *bb,
|
|
184
|
+
chc_err *err);
|
|
185
|
+
|
|
186
|
+
int chc_client_send_cancel(chc_client *c, chc_err *err);
|
|
187
|
+
int chc_client_send_ping(chc_client *c, chc_err *err);
|
|
188
|
+
|
|
189
|
+
#ifdef CHC_IMPLEMENTATION
|
|
190
|
+
|
|
191
|
+
#include <stdlib.h>
|
|
192
|
+
#include <string.h>
|
|
193
|
+
|
|
194
|
+
/* ----- protocol revision constants (mirror clickhouse-cpp client.cpp) ----- */
|
|
195
|
+
#define CHC__REV_TEMPORARY_TABLES 50264u
|
|
196
|
+
#define CHC__REV_TOTAL_ROWS_IN_PROGRESS 51554u
|
|
197
|
+
#define CHC__REV_BLOCK_INFO 51903u
|
|
198
|
+
#define CHC__REV_CLIENT_INFO 54032u
|
|
199
|
+
#define CHC__REV_SERVER_TIMEZONE 54058u
|
|
200
|
+
#define CHC__REV_QUOTA_KEY_IN_CLIENT 54060u
|
|
201
|
+
#define CHC__REV_SERVER_DISPLAY_NAME 54372u
|
|
202
|
+
#define CHC__REV_VERSION_PATCH 54401u
|
|
203
|
+
#define CHC__REV_CLIENT_WRITE_INFO 54420u
|
|
204
|
+
#define CHC__REV_SETTINGS_AS_STRINGS 54429u
|
|
205
|
+
#define CHC__REV_INTERSERVER_SECRET 54441u
|
|
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
|
|
213
|
+
|
|
214
|
+
/* Client → server packet kinds. */
|
|
215
|
+
#define CHC__CLIENT_HELLO 0u
|
|
216
|
+
#define CHC__CLIENT_QUERY 1u
|
|
217
|
+
#define CHC__CLIENT_DATA 2u
|
|
218
|
+
#define CHC__CLIENT_CANCEL 3u
|
|
219
|
+
#define CHC__CLIENT_PING 4u
|
|
220
|
+
|
|
221
|
+
struct chc_client {
|
|
222
|
+
const chc_alloc *al;
|
|
223
|
+
chc_io *io;
|
|
224
|
+
chc_in in; /* persistent buffered input */
|
|
225
|
+
|
|
226
|
+
chc_server_info server;
|
|
227
|
+
uint64_t client_version_major;
|
|
228
|
+
uint64_t client_version_minor;
|
|
229
|
+
uint64_t client_version_patch;
|
|
230
|
+
uint64_t client_revision;
|
|
231
|
+
chc_compression compression;
|
|
232
|
+
const chc_codec *codec;
|
|
233
|
+
|
|
234
|
+
/* Meaningful only for an ioless `chc_in`.
|
|
235
|
+
* io-backed refill blocks inside reader, so packet always completes,
|
|
236
|
+
* leaving these at reset values across calls. */
|
|
237
|
+
int recv_in_block; /* 0 = at packet boundary; 1 = mid block-bearing packet */
|
|
238
|
+
chc_packet_kind recv_kind; /* committed kind for the in-progress block */
|
|
239
|
+
chc_block *recv_partial; /* retained partial block */
|
|
240
|
+
size_t recv_next_col; /* resume column index */
|
|
241
|
+
|
|
242
|
+
/* Compressed-block frame-granularity resume; lifecycle = one compressed block.
|
|
243
|
+
* recv_dec_in accumulates decompressed bytes the column reader has not yet consumed;
|
|
244
|
+
* recv_decomp decompresses one frame at a time from `in`.
|
|
245
|
+
* recv_partial / recv_next_col carry per-column progress over recv_dec_in. */
|
|
246
|
+
bool recv_dec_active;
|
|
247
|
+
chc__decomp_src recv_decomp;
|
|
248
|
+
chc_in recv_dec_in;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/* Free the persisted compressed-resume decompressor + decompressed buffer.
|
|
252
|
+
* Idempotent; no-op when no compressed block is in flight. */
|
|
253
|
+
static void
|
|
254
|
+
chc__client_recv_comp_free(chc_client *c)
|
|
255
|
+
{
|
|
256
|
+
if (!c->recv_dec_active) return;
|
|
257
|
+
chc__decomp_src_free(&c->recv_decomp);
|
|
258
|
+
chc_in_free(&c->recv_dec_in);
|
|
259
|
+
c->recv_dec_active = false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* Free retained recv-resume state. Safe at any packet boundary or teardown. */
|
|
263
|
+
static void
|
|
264
|
+
chc__client_recv_state_free(chc_client *c)
|
|
265
|
+
{
|
|
266
|
+
if (c->recv_partial) { chc_block_destroy(c->recv_partial, c->al); c->recv_partial = NULL; }
|
|
267
|
+
chc__client_recv_comp_free(c);
|
|
268
|
+
c->recv_in_block = 0;
|
|
269
|
+
c->recv_next_col = 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
void
|
|
273
|
+
chc_exception_free(chc_exception *e, const chc_alloc *al)
|
|
274
|
+
{
|
|
275
|
+
if (!e) return;
|
|
276
|
+
al->free(al->ud, e->name, e->name_len + 1);
|
|
277
|
+
al->free(al->ud, e->display_text, e->display_text_len + 1);
|
|
278
|
+
al->free(al->ud, e->stack_trace, e->stack_trace_len + 1);
|
|
279
|
+
al->free(al->ud, e, sizeof *e);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
static int
|
|
283
|
+
chc__read_i32_le(chc_in *in, int32_t *out, chc_err *err)
|
|
284
|
+
{
|
|
285
|
+
uint32_t u;
|
|
286
|
+
int rc = chc__read_u32_le(in, &u, err);
|
|
287
|
+
if (rc != CHC_OK) return rc;
|
|
288
|
+
*out = (int32_t) u;
|
|
289
|
+
return CHC_OK;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
static int
|
|
293
|
+
chc__client_send_hello(chc_client *c, const chc_client_opts *opts, chc_err *err)
|
|
294
|
+
{
|
|
295
|
+
int rc;
|
|
296
|
+
const char *name = opts->client_name ? opts->client_name : "clickhouse-c";
|
|
297
|
+
size_t name_len = strlen(name);
|
|
298
|
+
const char *db = opts->database ? opts->database : "default";
|
|
299
|
+
const char *us = opts->user ? opts->user : "default";
|
|
300
|
+
const char *pw = opts->password ? opts->password : "";
|
|
301
|
+
|
|
302
|
+
if ((rc = chc__write_varuint(c->io, CHC__CLIENT_HELLO, err))) return rc;
|
|
303
|
+
if ((rc = chc__write_string (c->io, name, name_len, err))) return rc;
|
|
304
|
+
if ((rc = chc__write_varuint(c->io, opts->client_version_major, err))) return rc;
|
|
305
|
+
if ((rc = chc__write_varuint(c->io, opts->client_version_minor, err))) return rc;
|
|
306
|
+
if ((rc = chc__write_varuint(c->io, c->client_revision, err))) return rc;
|
|
307
|
+
if ((rc = chc__write_string (c->io, db, strlen(db), err))) return rc;
|
|
308
|
+
if ((rc = chc__write_string (c->io, us, strlen(us), err))) return rc;
|
|
309
|
+
if ((rc = chc__write_string (c->io, pw, strlen(pw), err))) return rc;
|
|
310
|
+
return CHC_OK;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
static void
|
|
314
|
+
chc__copy_short(char *dst, size_t cap, const char *src, size_t len)
|
|
315
|
+
{
|
|
316
|
+
size_t n = len < cap - 1 ? len : cap - 1;
|
|
317
|
+
if (n) memcpy(dst, src, n);
|
|
318
|
+
dst[n] = '\0';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* Reads chained exception. Caller frees via chc_exception_free. */
|
|
322
|
+
static int
|
|
323
|
+
chc__read_exception(chc_client *c, chc_exception **out, chc_err *err)
|
|
324
|
+
{
|
|
325
|
+
chc_exception *e = chc__calloc(c->al, sizeof *e, err);
|
|
326
|
+
if (!e) return CHC_ERR_OOM;
|
|
327
|
+
uint8_t has_nested;
|
|
328
|
+
int rc;
|
|
329
|
+
if ((rc = chc__read_i32_le (&c->in, &e->code, err)) ||
|
|
330
|
+
(rc = chc__read_string (&c->in, &e->name, &e->name_len, err)) ||
|
|
331
|
+
(rc = chc__read_string (&c->in, &e->display_text, &e->display_text_len, err)) ||
|
|
332
|
+
(rc = chc__read_string (&c->in, &e->stack_trace, &e->stack_trace_len, err)) ||
|
|
333
|
+
(rc = chc__read_byte (&c->in, &has_nested, err))) {
|
|
334
|
+
chc_exception_free(e, c->al);
|
|
335
|
+
return rc;
|
|
336
|
+
}
|
|
337
|
+
*out = e;
|
|
338
|
+
return CHC_OK;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
static int
|
|
342
|
+
chc__client_recv_hello(chc_client *c, chc_err *err)
|
|
343
|
+
{
|
|
344
|
+
uint64_t kind;
|
|
345
|
+
int rc = chc__read_varuint(&c->in, &kind, err);
|
|
346
|
+
if (rc != CHC_OK) return rc;
|
|
347
|
+
if (kind == CHC_PKT_EXCEPTION) {
|
|
348
|
+
chc_exception *e = NULL;
|
|
349
|
+
rc = chc__read_exception(c, &e, err);
|
|
350
|
+
if (rc != CHC_OK) return rc;
|
|
351
|
+
chc__err_set(err, CHC_ERR_SERVER, "%s",
|
|
352
|
+
e->display_text ? e->display_text : (e->name ? e->name : ""));
|
|
353
|
+
err->server_code = e->code;
|
|
354
|
+
chc__copy_short(err->server_name, sizeof err->server_name,
|
|
355
|
+
e->name, e->name_len);
|
|
356
|
+
chc_exception_free(e, c->al);
|
|
357
|
+
return CHC_ERR_SERVER;
|
|
358
|
+
}
|
|
359
|
+
if (kind != CHC_PKT_HELLO)
|
|
360
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
361
|
+
"expected Hello, got %llu",
|
|
362
|
+
(unsigned long long) kind);
|
|
363
|
+
|
|
364
|
+
char *s; size_t slen;
|
|
365
|
+
if ((rc = chc__read_string(&c->in, &s, &slen, err))) return rc;
|
|
366
|
+
chc__copy_short(c->server.name, sizeof c->server.name, s, slen);
|
|
367
|
+
c->al->free(c->al->ud, s, slen + 1);
|
|
368
|
+
|
|
369
|
+
if ((rc = chc__read_varuint(&c->in, &c->server.version_major, err))) return rc;
|
|
370
|
+
if ((rc = chc__read_varuint(&c->in, &c->server.version_minor, err))) return rc;
|
|
371
|
+
if ((rc = chc__read_varuint(&c->in, &c->server.revision, err))) return rc;
|
|
372
|
+
|
|
373
|
+
if (c->server.revision >= CHC__REV_SERVER_TIMEZONE) {
|
|
374
|
+
if ((rc = chc__read_string(&c->in, &s, &slen, err))) return rc;
|
|
375
|
+
chc__copy_short(c->server.timezone, sizeof c->server.timezone, s, slen);
|
|
376
|
+
c->al->free(c->al->ud, s, slen + 1);
|
|
377
|
+
}
|
|
378
|
+
if (c->server.revision >= CHC__REV_SERVER_DISPLAY_NAME) {
|
|
379
|
+
if ((rc = chc__read_string(&c->in, &s, &slen, err))) return rc;
|
|
380
|
+
chc__copy_short(c->server.display_name, sizeof c->server.display_name, s, slen);
|
|
381
|
+
c->al->free(c->al->ud, s, slen + 1);
|
|
382
|
+
}
|
|
383
|
+
if (c->server.revision >= CHC__REV_VERSION_PATCH) {
|
|
384
|
+
if ((rc = chc__read_varuint(&c->in, &c->server.version_patch, err))) return rc;
|
|
385
|
+
}
|
|
386
|
+
return CHC_OK;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
static int
|
|
390
|
+
chc__recv_pong(chc_client *c, chc_err *err)
|
|
391
|
+
{
|
|
392
|
+
bool ioless = c->in.io == NULL;
|
|
393
|
+
if (ioless) chc__in_checkpoint(&c->in);
|
|
394
|
+
uint64_t kind;
|
|
395
|
+
int rc = chc__read_varuint(&c->in, &kind, err);
|
|
396
|
+
if (rc != CHC_OK) goto maybe_rewind;
|
|
397
|
+
if (kind == CHC_PKT_EXCEPTION) {
|
|
398
|
+
chc_exception *e = NULL;
|
|
399
|
+
rc = chc__read_exception(c, &e, err); /* frees its partial on non-OK */
|
|
400
|
+
if (rc != CHC_OK) goto maybe_rewind;
|
|
401
|
+
chc__err_set(err, CHC_ERR_SERVER, "%s",
|
|
402
|
+
e->display_text ? e->display_text : (e->name ? e->name : ""));
|
|
403
|
+
err->server_code = e->code;
|
|
404
|
+
chc__copy_short(err->server_name, sizeof err->server_name,
|
|
405
|
+
e->name, e->name_len);
|
|
406
|
+
chc_exception_free(e, c->al);
|
|
407
|
+
return CHC_ERR_SERVER;
|
|
408
|
+
}
|
|
409
|
+
if (kind != CHC_PKT_PONG)
|
|
410
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL, "expected Pong, got %llu",
|
|
411
|
+
(unsigned long long) kind);
|
|
412
|
+
return CHC_OK;
|
|
413
|
+
maybe_rewind:
|
|
414
|
+
if (ioless && rc == CHC_WOULD_BLOCK) chc__in_rewind(&c->in);
|
|
415
|
+
return rc;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
int
|
|
419
|
+
chc_client_init(chc_client **out, const chc_client_opts *opts,
|
|
420
|
+
const chc_alloc *al, chc_io *io, chc_err *err)
|
|
421
|
+
{
|
|
422
|
+
chc_client_opts def_opts = {};
|
|
423
|
+
if (!opts) opts = &def_opts;
|
|
424
|
+
|
|
425
|
+
chc_client *c = chc__calloc(al, sizeof *c, err);
|
|
426
|
+
if (!c) return CHC_ERR_OOM;
|
|
427
|
+
c->al = al;
|
|
428
|
+
c->io = io;
|
|
429
|
+
c->client_version_major = opts->client_version_major;
|
|
430
|
+
c->client_version_minor = opts->client_version_minor;
|
|
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;
|
|
436
|
+
|
|
437
|
+
int rc = chc_in_init(&c->in, io, al, opts->read_buffer_bytes, err);
|
|
438
|
+
if (rc != CHC_OK) { al->free(al->ud, c, sizeof *c); return rc; }
|
|
439
|
+
|
|
440
|
+
rc = chc__client_send_hello(c, opts, err);
|
|
441
|
+
if (rc != CHC_OK) goto fail;
|
|
442
|
+
rc = chc__client_recv_hello(c, err);
|
|
443
|
+
if (rc != CHC_OK) goto fail;
|
|
444
|
+
|
|
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
|
+
/* Addendum: send empty quota_key. */
|
|
452
|
+
if (c->server.revision >= CHC__REV_ADDENDUM) {
|
|
453
|
+
rc = chc__write_string(c->io, "", 0, err);
|
|
454
|
+
if (rc != CHC_OK) goto fail;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* Probe Ping. Server-side late-stage rejections (eg invalid
|
|
458
|
+
* default_database in 24.x) only surface after the Addendum is read,
|
|
459
|
+
* not in the Hello reply. Without a probe, the rejection races the
|
|
460
|
+
* caller's first query: caller's writes may hit ECONNRESET before the
|
|
461
|
+
* exception packet is read. The Ping forces a round-trip here so the
|
|
462
|
+
* exception is delivered at init time instead. Matches clickhouse-cpp's
|
|
463
|
+
* SetPingBeforeQuery posture for the connection-establishment case. */
|
|
464
|
+
rc = chc_client_send_ping(c, err);
|
|
465
|
+
if (rc != CHC_OK) goto fail;
|
|
466
|
+
rc = chc__recv_pong(c, err);
|
|
467
|
+
if (rc != CHC_OK) goto fail;
|
|
468
|
+
|
|
469
|
+
*out = c;
|
|
470
|
+
return CHC_OK;
|
|
471
|
+
|
|
472
|
+
fail:
|
|
473
|
+
chc_in_free(&c->in);
|
|
474
|
+
al->free(al->ud, c, sizeof *c);
|
|
475
|
+
*out = NULL;
|
|
476
|
+
return rc;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
void
|
|
480
|
+
chc_client_close(chc_client *c)
|
|
481
|
+
{
|
|
482
|
+
if (!c) return;
|
|
483
|
+
chc__client_recv_state_free(c);
|
|
484
|
+
chc_in_free(&c->in);
|
|
485
|
+
c->al->free(c->al->ud, c, sizeof *c);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const chc_server_info *
|
|
489
|
+
chc_client_server_info(const chc_client *c)
|
|
490
|
+
{
|
|
491
|
+
return c ? &c->server : NULL;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
int
|
|
495
|
+
chc_client_send_ping(chc_client *c, chc_err *err)
|
|
496
|
+
{
|
|
497
|
+
return chc__write_varuint(c->io, CHC__CLIENT_PING, err);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
int
|
|
501
|
+
chc_client_send_cancel(chc_client *c, chc_err *err)
|
|
502
|
+
{
|
|
503
|
+
return chc__write_varuint(c->io, CHC__CLIENT_CANCEL, err);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* Write a block body (BlockInfo + cols + rows) to a chc_io. Used for
|
|
507
|
+
* both the uncompressed direct path and the compressed buffer-then-emit
|
|
508
|
+
* path. */
|
|
509
|
+
static int
|
|
510
|
+
chc__client_write_block_body(chc_client *c, chc_io *sink,
|
|
511
|
+
const chc_block_builder *bb, chc_err *err)
|
|
512
|
+
{
|
|
513
|
+
int rc;
|
|
514
|
+
chc_block_opts opts = {
|
|
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);
|
|
519
|
+
|
|
520
|
+
if (opts.has_block_info) {
|
|
521
|
+
rc = chc__write_block_info(sink, err);
|
|
522
|
+
if (rc != CHC_OK) return rc;
|
|
523
|
+
}
|
|
524
|
+
if ((rc = chc__write_varuint(sink, 0, err))) return rc; /* n_cols */
|
|
525
|
+
if ((rc = chc__write_varuint(sink, 0, err))) return rc; /* n_rows */
|
|
526
|
+
return CHC_OK;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* Write a Data packet. bb may be NULL for an empty (0 columns, 0 rows)
|
|
530
|
+
* block — the terminator the server uses to detect end-of-INSERT and
|
|
531
|
+
* end-of-query-text. */
|
|
532
|
+
static int
|
|
533
|
+
chc__client_write_data(chc_client *c, const chc_block_builder *bb, chc_err *err)
|
|
534
|
+
{
|
|
535
|
+
int rc;
|
|
536
|
+
if ((rc = chc__write_varuint(c->io, CHC__CLIENT_DATA, err))) return rc;
|
|
537
|
+
/* Temporary table name (always empty from us). */
|
|
538
|
+
if (c->server.revision >= CHC__REV_TEMPORARY_TABLES) {
|
|
539
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (c->compression == CHC_COMP_NONE) {
|
|
543
|
+
return chc__client_write_block_body(c, c->io, bb, err);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (!c->codec)
|
|
547
|
+
return chc__err_set(err, CHC_ERR_USAGE,
|
|
548
|
+
"compression enabled but codec is NULL");
|
|
549
|
+
|
|
550
|
+
chc__mem_sink ms;
|
|
551
|
+
chc_io sink_io;
|
|
552
|
+
chc__mem_sink_init(&ms, &sink_io, c->al);
|
|
553
|
+
rc = chc__client_write_block_body(c, &sink_io, bb, err);
|
|
554
|
+
if (rc != CHC_OK) { chc__mem_sink_free(&ms); return rc; }
|
|
555
|
+
rc = chc__comp_emit_chunks(c->io, c->codec, c->compression,
|
|
556
|
+
ms.buf, ms.len, c->al, err);
|
|
557
|
+
chc__mem_sink_free(&ms);
|
|
558
|
+
return rc;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
int
|
|
562
|
+
chc_client_send_data(chc_client *c, const chc_block_builder *bb, chc_err *err)
|
|
563
|
+
{
|
|
564
|
+
return chc__client_write_data(c, bb, err);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
int
|
|
568
|
+
chc_client_send_query_ex(chc_client *c, const char *sql, size_t sql_len,
|
|
569
|
+
const chc_query_opts *opts, chc_err *err)
|
|
570
|
+
{
|
|
571
|
+
chc_query_opts def = {};
|
|
572
|
+
if (!opts) opts = &def;
|
|
573
|
+
|
|
574
|
+
int rc;
|
|
575
|
+
if ((rc = chc__write_varuint(c->io, CHC__CLIENT_QUERY, err))) return rc;
|
|
576
|
+
if ((rc = chc__write_string (c->io, opts->query_id, opts->query_id_len, err))) return rc;
|
|
577
|
+
|
|
578
|
+
/* ClientInfo. clickhouse-cpp sends a fully-populated struct; we send
|
|
579
|
+
* the minimum the server tolerates (initial fields blank, iface=TCP). */
|
|
580
|
+
if (c->server.revision >= CHC__REV_CLIENT_INFO) {
|
|
581
|
+
uint8_t query_kind = 1; /* INITIAL_QUERY */
|
|
582
|
+
if ((rc = chc__write_bytes (c->io, &query_kind, 1, err))) return rc;
|
|
583
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* initial_user */
|
|
584
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* initial_query_id */
|
|
585
|
+
if ((rc = chc__write_string(c->io, "[::ffff:127.0.0.1]:0", 20, err))) return rc;
|
|
586
|
+
if (c->server.revision >= CHC__REV_INITIAL_QUERY_START) {
|
|
587
|
+
uint8_t z8[8] = {};
|
|
588
|
+
if ((rc = chc__write_bytes(c->io, z8, 8, err))) return rc; /* int64 */
|
|
589
|
+
}
|
|
590
|
+
uint8_t iface_type = 1; /* TCP */
|
|
591
|
+
if ((rc = chc__write_bytes (c->io, &iface_type, 1, err))) return rc;
|
|
592
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* os_user */
|
|
593
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc; /* client_hostname */
|
|
594
|
+
if ((rc = chc__write_string(c->io, "clickhouse-c client", 19, err))) return rc;
|
|
595
|
+
if ((rc = chc__write_varuint(c->io, c->client_version_major, err))) return rc;
|
|
596
|
+
if ((rc = chc__write_varuint(c->io, c->client_version_minor, err))) return rc;
|
|
597
|
+
if ((rc = chc__write_varuint(c->io, c->client_revision, err))) return rc;
|
|
598
|
+
|
|
599
|
+
if (c->server.revision >= CHC__REV_QUOTA_KEY_IN_CLIENT)
|
|
600
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
601
|
+
if (c->server.revision >= CHC__REV_DISTRIBUTED_DEPTH)
|
|
602
|
+
if ((rc = chc__write_varuint(c->io, 0, err))) return rc;
|
|
603
|
+
if (c->server.revision >= CHC__REV_VERSION_PATCH)
|
|
604
|
+
if ((rc = chc__write_varuint(c->io, c->client_version_patch, err))) return rc;
|
|
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
|
+
}
|
|
615
|
+
|
|
616
|
+
/* Per-query settings: name + varuint(flags) + value, repeated, then
|
|
617
|
+
* empty-string terminator. Pre-54429 binary serialization isn't
|
|
618
|
+
* implemented; the empty-list path still works because the terminator
|
|
619
|
+
* is shape-compatible. */
|
|
620
|
+
if (c->server.revision >= CHC__REV_SETTINGS_AS_STRINGS) {
|
|
621
|
+
for (size_t i = 0; i < opts->n_settings; i++) {
|
|
622
|
+
const chc_query_setting *s = &opts->settings[i];
|
|
623
|
+
size_t nlen = s->name ? strlen(s->name) : 0;
|
|
624
|
+
size_t vlen = s->value ? strlen(s->value) : 0;
|
|
625
|
+
uint64_t flags = (s->important ? 1u : 0u) | (s->custom ? 2u : 0u);
|
|
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;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (c->server.revision >= CHC__REV_INTERSERVER_SECRET) {
|
|
641
|
+
if ((rc = chc__write_string(c->io, "", 0, err))) return rc;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/* Stages::Complete = 2. */
|
|
645
|
+
if ((rc = chc__write_varuint(c->io, 2, err))) return rc;
|
|
646
|
+
/* Compression state: 1 if enabled, 0 otherwise. */
|
|
647
|
+
if ((rc = chc__write_varuint(c->io,
|
|
648
|
+
c->compression != CHC_COMP_NONE ? 1u : 0u, err))) return rc;
|
|
649
|
+
/* Query text. */
|
|
650
|
+
if ((rc = chc__write_string(c->io, sql, sql_len, err))) return rc;
|
|
651
|
+
|
|
652
|
+
/* Parameters: same shape as settings; flags always CUSTOM (bit 1). */
|
|
653
|
+
if (c->server.revision >= CHC__REV_PARAMETERS) {
|
|
654
|
+
for (size_t i = 0; i < opts->n_params; i++) {
|
|
655
|
+
const chc_query_param *p = &opts->params[i];
|
|
656
|
+
size_t nlen = p->name ? strlen(p->name) : 0;
|
|
657
|
+
size_t vlen = p->value ? strlen(p->value) : 0;
|
|
658
|
+
if ((rc = chc__write_string (c->io, p->name, nlen, err))) return rc;
|
|
659
|
+
if ((rc = chc__write_varuint(c->io, 2u, err))) return rc;
|
|
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);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/* Finalize: send an empty Data block as the query-text terminator. */
|
|
670
|
+
return chc__client_write_data(c, NULL, err);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
int
|
|
674
|
+
chc_client_send_query(chc_client *c, const char *sql, size_t sql_len,
|
|
675
|
+
const char *query_id, size_t query_id_len, chc_err *err)
|
|
676
|
+
{
|
|
677
|
+
chc_query_opts opts = {
|
|
678
|
+
.query_id = query_id,
|
|
679
|
+
.query_id_len = query_id_len,
|
|
680
|
+
};
|
|
681
|
+
return chc_client_send_query_ex(c, sql, sql_len, &opts, err);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
void
|
|
685
|
+
chc_packet_clear(chc_client *c, chc_packet *p)
|
|
686
|
+
{
|
|
687
|
+
if (!p) return;
|
|
688
|
+
switch (p->kind) {
|
|
689
|
+
case CHC_PKT_DATA: case CHC_PKT_TOTALS: case CHC_PKT_EXTREMES:
|
|
690
|
+
case CHC_PKT_LOG: case CHC_PKT_PROFILE_EVENTS:
|
|
691
|
+
if (p->block) { chc_block_destroy(p->block, c->al); p->block = NULL; }
|
|
692
|
+
break;
|
|
693
|
+
case CHC_PKT_EXCEPTION:
|
|
694
|
+
if (p->exception) { chc_exception_free(p->exception, c->al); p->exception = NULL; }
|
|
695
|
+
break;
|
|
696
|
+
default:
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* Read the leading string a block-bearing packet carries before block body.
|
|
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). */
|
|
704
|
+
static int
|
|
705
|
+
chc__recv_skip_lead_string(chc_client *c, int gated, chc_err *err)
|
|
706
|
+
{
|
|
707
|
+
if (gated && c->server.revision < CHC__REV_TEMPORARY_TABLES)
|
|
708
|
+
return CHC_OK;
|
|
709
|
+
char *s; size_t slen;
|
|
710
|
+
int rc = chc__read_string(&c->in, &s, &slen, err);
|
|
711
|
+
if (rc != CHC_OK) return rc;
|
|
712
|
+
c->al->free(c->al->ud, s, slen + 1);
|
|
713
|
+
return CHC_OK;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/* Read one compressed block body from c->in into *out, io-backed
|
|
717
|
+
* path only. Build a per-call decompressor + io-backed dec_in over the raw in
|
|
718
|
+
* and run the block reader; io-backed refill blocks, so the block always
|
|
719
|
+
* completes in one call. The ioless path is handled by
|
|
720
|
+
* chc__recv_block_compressed_resume, which keeps decomp state alive across
|
|
721
|
+
* CHC_WOULD_BLOCK for frame-granularity resumption.
|
|
722
|
+
*
|
|
723
|
+
* The ioless checkpoint/rewind below is dead for the blocking caller; it stays
|
|
724
|
+
* to keep this function correct if ever driven over an ioless raw in (baseline
|
|
725
|
+
* rewind-to-block-start). */
|
|
726
|
+
static int
|
|
727
|
+
chc__recv_block_compressed(chc_client *c, const chc_block_opts *opts,
|
|
728
|
+
chc_block **out, chc_err *err)
|
|
729
|
+
{
|
|
730
|
+
if (!c->codec)
|
|
731
|
+
return chc__err_set(err, CHC_ERR_USAGE,
|
|
732
|
+
"compression enabled but codec is NULL");
|
|
733
|
+
bool ioless = c->in.io == NULL;
|
|
734
|
+
if (ioless) chc__in_checkpoint(&c->in); /* raw in, at compressed block start */
|
|
735
|
+
chc__decomp_src src;
|
|
736
|
+
chc_io decomp_io;
|
|
737
|
+
chc__decomp_src_init(&src, &c->in, c->codec, c->al, &decomp_io);
|
|
738
|
+
chc_in dec_in;
|
|
739
|
+
int rc = chc_in_init(&dec_in, &decomp_io, c->al, 0, err);
|
|
740
|
+
if (rc != CHC_OK) { chc__decomp_src_free(&src); return rc; }
|
|
741
|
+
rc = chc_block_read(&dec_in, c->al, opts, out, err);
|
|
742
|
+
chc_in_free(&dec_in);
|
|
743
|
+
chc__decomp_src_free(&src);
|
|
744
|
+
if (ioless && rc == CHC_WOULD_BLOCK) chc__in_rewind(&c->in);
|
|
745
|
+
return rc;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/* Frame-granularity compressed resume for an ioless `in`. Keeps a
|
|
749
|
+
* persisted decompressor + ioless decompressed buffer (c->recv_decomp /
|
|
750
|
+
* c->recv_dec_in) alive across CHC_WOULD_BLOCK so a compressed Data block
|
|
751
|
+
* streamed over many reads re-decompresses at most one frame and re-parses at
|
|
752
|
+
* most one column on retry, instead of re-decompressing from frame 0.
|
|
753
|
+
*
|
|
754
|
+
* Demand-driven: pumps exactly one frame whenever the column reader underruns
|
|
755
|
+
* the decompressed buffer. A block's frames sum to exactly its serialized size
|
|
756
|
+
* and the next packet starts with an uncompressed tag, so chc__block_resume_in
|
|
757
|
+
* reports CHC_OK before it would ever demand a frame from the next packet --
|
|
758
|
+
* eager pumping would mis-read that tag as a frame and fail on a hash mismatch.
|
|
759
|
+
*
|
|
760
|
+
* Contract mirrors the uncompressed branch's chc__block_resume_in use:
|
|
761
|
+
* CHC_OK -- block complete in c->recv_partial (caller takes it);
|
|
762
|
+
* decomp state torn down.
|
|
763
|
+
* CHC_WOULD_BLOCK -- raw `in` drained mid-frame; partial block + decomp state
|
|
764
|
+
* retained, raw rewound to the incomplete frame's start.
|
|
765
|
+
* other -- error; recv_partial freed/NULL, decomp state torn down. */
|
|
766
|
+
static int
|
|
767
|
+
chc__recv_block_compressed_resume(chc_client *c, const chc_block_opts *opts,
|
|
768
|
+
chc_err *err)
|
|
769
|
+
{
|
|
770
|
+
if (!c->codec)
|
|
771
|
+
return chc__err_set(err, CHC_ERR_USAGE,
|
|
772
|
+
"compression enabled but codec is NULL");
|
|
773
|
+
|
|
774
|
+
if (!c->recv_dec_active) {
|
|
775
|
+
int rc = chc_in_init_ioless(&c->recv_dec_in, c->al);
|
|
776
|
+
if (rc != CHC_OK) return rc;
|
|
777
|
+
chc_io scratch_io; /* push mode: the decomp io adapter goes unused */
|
|
778
|
+
chc__decomp_src_init(&c->recv_decomp, &c->in, c->codec, c->al, &scratch_io);
|
|
779
|
+
c->recv_dec_active = true;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
for (;;) {
|
|
783
|
+
/* Parse as far as the decompressed bytes allow (per-column retain is
|
|
784
|
+
* active: recv_dec_in is ioless). */
|
|
785
|
+
int rc = chc__block_resume_in(&c->recv_dec_in, c->al, opts,
|
|
786
|
+
&c->recv_partial, &c->recv_next_col, err);
|
|
787
|
+
if (rc == CHC_OK) {
|
|
788
|
+
chc__client_recv_comp_free(c);
|
|
789
|
+
return CHC_OK;
|
|
790
|
+
}
|
|
791
|
+
if (rc != CHC_WOULD_BLOCK) { /* real error: resume freed the block */
|
|
792
|
+
chc__client_recv_comp_free(c);
|
|
793
|
+
c->recv_partial = NULL;
|
|
794
|
+
c->recv_next_col = 0;
|
|
795
|
+
return rc;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/* recv_dec_in drained mid-block: decompress exactly ONE more frame from
|
|
799
|
+
* the raw compressed `in` and push it into recv_dec_in. */
|
|
800
|
+
chc__in_checkpoint(&c->in); /* frame start on raw */
|
|
801
|
+
int frc = chc__decomp_read_frame(&c->recv_decomp, err);
|
|
802
|
+
if (frc == CHC_WOULD_BLOCK) { /* frame incomplete: need socket bytes */
|
|
803
|
+
chc__in_rewind(&c->in); /* keep partial frame buffered */
|
|
804
|
+
return CHC_WOULD_BLOCK;
|
|
805
|
+
}
|
|
806
|
+
if (frc != CHC_OK) { /* hash mismatch / codec / oom */
|
|
807
|
+
chc__client_recv_state_free(c);
|
|
808
|
+
return frc;
|
|
809
|
+
}
|
|
810
|
+
/* Complete frame committed (no rewind); push its decompressed bytes.
|
|
811
|
+
* chc_in_submit copies, so the next frame may reuse frame_buf. */
|
|
812
|
+
rc = chc_in_submit(&c->recv_dec_in, c->recv_decomp.frame_buf,
|
|
813
|
+
c->recv_decomp.frame_fill, err);
|
|
814
|
+
if (rc != CHC_OK) {
|
|
815
|
+
chc__client_recv_state_free(c);
|
|
816
|
+
return rc;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/* Resumable packet decoder shared by chc_client_recv_packet and
|
|
822
|
+
* chc_async_recv_packet. For io-backed `in` refill blocks inside the reader.
|
|
823
|
+
* Ioless-gated checkpoints/resets are skipped, and a packet always completes
|
|
824
|
+
* in one call. For an ioless `in` it checkpoints at packet start and at each
|
|
825
|
+
* resumable boundary so a mid-parse drain returns CHC_WOULD_BLOCK with
|
|
826
|
+
* c->recv_* state retained for the next call.
|
|
827
|
+
*
|
|
828
|
+
* recv keeps parse state alive across CHC_WOULD_BLOCK: a Data block streamed
|
|
829
|
+
* over many reads resumes at the in-progress column (uncompressed, via
|
|
830
|
+
* chc__block_resume_in). Compressed Data resumes at frame granularity over an
|
|
831
|
+
* ioless `in` (chc__recv_block_compressed_resume); the blocking io-backed path
|
|
832
|
+
* rebuilds the decompressor per call (chc__recv_block_compressed). */
|
|
833
|
+
static int
|
|
834
|
+
chc__recv_packet_resumable(chc_client *c, chc_packet *out, chc_err *err)
|
|
835
|
+
{
|
|
836
|
+
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
|
+
};
|
|
841
|
+
int rc;
|
|
842
|
+
int is_log = 0; /* LOG/PROFILE_EVENTS: never compressed */
|
|
843
|
+
|
|
844
|
+
memset(out, 0, sizeof *out);
|
|
845
|
+
|
|
846
|
+
if (!c->recv_in_block) {
|
|
847
|
+
if (ioless) chc__in_checkpoint(&c->in); /* packet start */
|
|
848
|
+
uint64_t kind;
|
|
849
|
+
rc = chc__read_varuint(&c->in, &kind, err);
|
|
850
|
+
if (rc != CHC_OK) goto maybe_rewind;
|
|
851
|
+
|
|
852
|
+
switch (kind) {
|
|
853
|
+
/* ---- control / non-block packets: parse inline ---- */
|
|
854
|
+
case CHC_PKT_EXCEPTION:
|
|
855
|
+
out->kind = CHC_PKT_EXCEPTION;
|
|
856
|
+
rc = chc__read_exception(c, &out->exception, err); /* frees its partial on non-OK */
|
|
857
|
+
if (rc != CHC_OK) goto maybe_rewind;
|
|
858
|
+
goto control_done;
|
|
859
|
+
|
|
860
|
+
case CHC_PKT_PROGRESS:
|
|
861
|
+
out->kind = CHC_PKT_PROGRESS;
|
|
862
|
+
if ((rc = chc__read_varuint(&c->in, &out->progress.rows, err)) ||
|
|
863
|
+
(rc = chc__read_varuint(&c->in, &out->progress.bytes, err)))
|
|
864
|
+
goto maybe_rewind;
|
|
865
|
+
if (c->server.revision >= CHC__REV_TOTAL_ROWS_IN_PROGRESS)
|
|
866
|
+
if ((rc = chc__read_varuint(&c->in, &out->progress.total_rows, err)))
|
|
867
|
+
goto maybe_rewind;
|
|
868
|
+
if (c->server.revision >= CHC__REV_CLIENT_WRITE_INFO) {
|
|
869
|
+
if ((rc = chc__read_varuint(&c->in, &out->progress.written_rows, err)) ||
|
|
870
|
+
(rc = chc__read_varuint(&c->in, &out->progress.written_bytes, err)))
|
|
871
|
+
goto maybe_rewind;
|
|
872
|
+
}
|
|
873
|
+
goto control_done;
|
|
874
|
+
|
|
875
|
+
case CHC_PKT_PONG:
|
|
876
|
+
out->kind = CHC_PKT_PONG;
|
|
877
|
+
goto control_done;
|
|
878
|
+
|
|
879
|
+
case CHC_PKT_END_OF_STREAM:
|
|
880
|
+
out->kind = CHC_PKT_END_OF_STREAM;
|
|
881
|
+
goto control_done;
|
|
882
|
+
|
|
883
|
+
case CHC_PKT_PROFILE_INFO:
|
|
884
|
+
out->kind = CHC_PKT_PROFILE_INFO;
|
|
885
|
+
if ((rc = chc__read_varuint(&c->in, &out->profile.rows, err)) ||
|
|
886
|
+
(rc = chc__read_varuint(&c->in, &out->profile.blocks, err)) ||
|
|
887
|
+
(rc = chc__read_varuint(&c->in, &out->profile.bytes, err)) ||
|
|
888
|
+
(rc = chc__read_byte (&c->in, &out->profile.applied_limit, err)) ||
|
|
889
|
+
(rc = chc__read_varuint(&c->in, &out->profile.rows_before_limit, err)) ||
|
|
890
|
+
(rc = chc__read_byte (&c->in, &out->profile.calculated_rows_before_limit, err)))
|
|
891
|
+
goto maybe_rewind;
|
|
892
|
+
goto control_done;
|
|
893
|
+
|
|
894
|
+
case CHC_PKT_TABLE_COLUMNS: {
|
|
895
|
+
out->kind = CHC_PKT_TABLE_COLUMNS;
|
|
896
|
+
/* table name + columns metadata; both varstrings, both ignored. */
|
|
897
|
+
char *s; size_t slen;
|
|
898
|
+
if ((rc = chc__read_string(&c->in, &s, &slen, err))) goto maybe_rewind;
|
|
899
|
+
c->al->free(c->al->ud, s, slen + 1);
|
|
900
|
+
if ((rc = chc__read_string(&c->in, &s, &slen, err))) goto maybe_rewind;
|
|
901
|
+
c->al->free(c->al->ud, s, slen + 1);
|
|
902
|
+
goto control_done;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/* ---- block-bearing: commit kind + leading string, then resume ---- */
|
|
906
|
+
case CHC_PKT_DATA: out->kind = c->recv_kind = CHC_PKT_DATA; break;
|
|
907
|
+
case CHC_PKT_TOTALS: out->kind = c->recv_kind = CHC_PKT_TOTALS; break;
|
|
908
|
+
case CHC_PKT_EXTREMES:out->kind = c->recv_kind = CHC_PKT_EXTREMES; break;
|
|
909
|
+
case CHC_PKT_LOG: out->kind = c->recv_kind = CHC_PKT_LOG; is_log = 1; break;
|
|
910
|
+
case CHC_PKT_PROFILE_EVENTS:
|
|
911
|
+
out->kind = c->recv_kind = CHC_PKT_PROFILE_EVENTS; is_log = 1; break;
|
|
912
|
+
|
|
913
|
+
default:
|
|
914
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
915
|
+
"unknown server packet %llu",
|
|
916
|
+
(unsigned long long) kind);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
rc = chc__recv_skip_lead_string(c, !is_log, err);
|
|
920
|
+
if (rc != CHC_OK) goto maybe_rewind;
|
|
921
|
+
|
|
922
|
+
/* Kind + leading string committed; resume owns the cursor from here.
|
|
923
|
+
* Do NOT rewind/reset on a mid-block would-block: resume re-parses only
|
|
924
|
+
* from the in-progress column. */
|
|
925
|
+
c->recv_in_block = 1;
|
|
926
|
+
c->recv_partial = NULL;
|
|
927
|
+
c->recv_next_col = 0;
|
|
928
|
+
} else {
|
|
929
|
+
out->kind = c->recv_kind;
|
|
930
|
+
is_log = (c->recv_kind == CHC_PKT_LOG ||
|
|
931
|
+
c->recv_kind == CHC_PKT_PROFILE_EVENTS);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/* LOG/PROFILE_EVENTS blocks are never compressed; route through the
|
|
935
|
+
* uncompressed resume path regardless of c->compression. */
|
|
936
|
+
if (c->compression == CHC_COMP_NONE || is_log) {
|
|
937
|
+
rc = chc__block_resume_in(&c->in, c->al, &opts,
|
|
938
|
+
&c->recv_partial, &c->recv_next_col, err);
|
|
939
|
+
if (rc == CHC_WOULD_BLOCK) return rc; /* resume owns rewind; partial retained */
|
|
940
|
+
c->recv_in_block = 0;
|
|
941
|
+
if (rc != CHC_OK) {
|
|
942
|
+
c->recv_partial = NULL; /* resume already freed it */
|
|
943
|
+
c->recv_next_col = 0;
|
|
944
|
+
return rc;
|
|
945
|
+
}
|
|
946
|
+
out->block = c->recv_partial;
|
|
947
|
+
c->recv_partial = NULL;
|
|
948
|
+
c->recv_next_col = 0;
|
|
949
|
+
if (ioless) chc_in_reset(&c->in);
|
|
950
|
+
return CHC_OK;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/* Compressed Data. ioless: frame-granularity resume over a persisted
|
|
954
|
+
* decompressor + ioless decompressed buffer. io-backed:
|
|
955
|
+
* baseline rebuild-per-call (chc__recv_block_compressed). */
|
|
956
|
+
if (ioless) {
|
|
957
|
+
rc = chc__recv_block_compressed_resume(c, &opts, err);
|
|
958
|
+
if (rc == CHC_WOULD_BLOCK) return rc; /* partial block + decomp state retained */
|
|
959
|
+
c->recv_in_block = 0;
|
|
960
|
+
if (rc != CHC_OK) return rc; /* resume tore down its state + recv_partial */
|
|
961
|
+
out->block = c->recv_partial;
|
|
962
|
+
c->recv_partial = NULL;
|
|
963
|
+
c->recv_next_col = 0;
|
|
964
|
+
chc_in_reset(&c->in);
|
|
965
|
+
return CHC_OK;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
rc = chc__recv_block_compressed(c, &opts, &out->block, err);
|
|
969
|
+
if (rc == CHC_WOULD_BLOCK) return rc;
|
|
970
|
+
c->recv_in_block = 0;
|
|
971
|
+
if (rc != CHC_OK) return rc;
|
|
972
|
+
return CHC_OK;
|
|
973
|
+
|
|
974
|
+
control_done:
|
|
975
|
+
if (ioless) chc_in_reset(&c->in);
|
|
976
|
+
return CHC_OK;
|
|
977
|
+
|
|
978
|
+
maybe_rewind:
|
|
979
|
+
if (ioless && rc == CHC_WOULD_BLOCK) chc__in_rewind(&c->in);
|
|
980
|
+
return rc;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
int
|
|
984
|
+
chc_client_recv_packet(chc_client *c, chc_packet *out, chc_err *err)
|
|
985
|
+
{
|
|
986
|
+
return chc__recv_packet_resumable(c, out, err);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
#endif /* CHC_IMPLEMENTATION */
|
|
990
|
+
|
|
991
|
+
#ifdef __cplusplus
|
|
992
|
+
}
|
|
993
|
+
#endif
|
|
994
|
+
|
|
995
|
+
#endif /* CLICKHOUSE_CLIENT_H */
|