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.
@@ -0,0 +1,3394 @@
1
+ /*
2
+ * clickhouse.h -- core C client for ClickHouse Native wire format.
3
+ *
4
+ * Single-header library, stb style: exactly one translation unit in
5
+ * the consumer's build must `#define CHC_IMPLEMENTATION` before including
6
+ * this header. Other TUs include for declarations only.
7
+ *
8
+ * Scope here: type-name parser + AST, varint codec, block reader,
9
+ * column accessors, block writer. No I/O backend, no TCP loop, no
10
+ * compression -- those live in sibling headers (clickhouse-posix-io.h,
11
+ * clickhouse-client.h, clickhouse-compression.h, ...).
12
+ *
13
+ * License: Apache-2.0. See LICENSE.
14
+ */
15
+
16
+ #ifndef CLICKHOUSE_H
17
+ #define CLICKHOUSE_H
18
+
19
+ #include <stdbool.h>
20
+ #include <stddef.h>
21
+ #include <stdint.h>
22
+
23
+ /* -------------------------------------------------------------------------- */
24
+ /* C23 portability shim */
25
+ /* -------------------------------------------------------------------------- */
26
+ /* Macros use C23 features when available, degrading to GNU/builtin equivalent,
27
+ * then to harmless nop, so consumers may build under any -std >= c11 */
28
+
29
+ #if defined(__has_c_attribute)
30
+ # define CHC__HAS_ATTR(x) __has_c_attribute(x)
31
+ #else
32
+ # define CHC__HAS_ATTR(x) 0
33
+ #endif
34
+
35
+ #if CHC__HAS_ATTR(nodiscard)
36
+ # define CHC_NODISCARD [[nodiscard]]
37
+ #elif defined(__GNUC__) || defined(__clang__)
38
+ # define CHC_NODISCARD __attribute__((warn_unused_result))
39
+ #else
40
+ # define CHC_NODISCARD
41
+ #endif
42
+
43
+ #if CHC__HAS_ATTR(maybe_unused)
44
+ # define CHC_MAYBE_UNUSED [[maybe_unused]]
45
+ #elif defined(__GNUC__) || defined(__clang__)
46
+ # define CHC_MAYBE_UNUSED __attribute__((unused))
47
+ #else
48
+ # define CHC_MAYBE_UNUSED
49
+ #endif
50
+
51
+ #if CHC__HAS_ATTR(deprecated)
52
+ # define CHC_DEPRECATED(msg) [[deprecated(msg)]]
53
+ #elif defined(__GNUC__) || defined(__clang__)
54
+ # define CHC_DEPRECATED(msg) __attribute__((deprecated(msg)))
55
+ #else
56
+ # define CHC_DEPRECATED(msg)
57
+ #endif
58
+
59
+ #if CHC__HAS_ATTR(unsequenced)
60
+ # define CHC_UNSEQUENCED [[unsequenced]]
61
+ #else
62
+ # define CHC_UNSEQUENCED
63
+ #endif
64
+
65
+ #if CHC__HAS_ATTR(reproducible)
66
+ # define CHC_REPRODUCIBLE [[reproducible]]
67
+ #else
68
+ # define CHC_REPRODUCIBLE
69
+ #endif
70
+
71
+ /* ckd_mul (C23 <stdckdint.h>) backs chc__mul_size; see CHC__HAVE_CKD_MUL. */
72
+ #if defined(__has_include)
73
+ # if __has_include(<stdckdint.h>)
74
+ # include <stdckdint.h>
75
+ # define CHC__HAVE_CKD_MUL 1
76
+ # endif
77
+ #endif
78
+
79
+ #ifdef __cplusplus
80
+ extern "C" {
81
+ #endif
82
+
83
+ /* -------------------------------------------------------------------------- */
84
+ /* Errors */
85
+ /* -------------------------------------------------------------------------- */
86
+
87
+ enum {
88
+ CHC_OK,
89
+ CHC_ERR_IO,
90
+ CHC_ERR_EOF,
91
+ CHC_ERR_PROTOCOL,
92
+ CHC_ERR_TYPE,
93
+ CHC_ERR_OOM,
94
+ CHC_ERR_CANCELLED,
95
+ CHC_ERR_SERVER,
96
+ CHC_ERR_USAGE,
97
+ CHC_WOULD_BLOCK
98
+ };
99
+
100
+ #ifndef CHC_ERR_MSG_LEN
101
+ #define CHC_ERR_MSG_LEN 256
102
+ #endif
103
+
104
+ typedef struct chc_err {
105
+ int server_code;
106
+ char msg[CHC_ERR_MSG_LEN];
107
+ char server_name[64];
108
+ } chc_err;
109
+
110
+ static inline void chc_err_reset(chc_err *e) {
111
+ if (!e) return;
112
+ e->server_code = 0;
113
+ e->msg[0] = '\0';
114
+ e->server_name[0] = '\0';
115
+ }
116
+
117
+ /* -------------------------------------------------------------------------- */
118
+ /* Allocator */
119
+ /* -------------------------------------------------------------------------- */
120
+
121
+ typedef struct chc_alloc {
122
+ void *ud;
123
+ void *(*alloc)(void *ud, size_t bytes);
124
+ void *(*realloc)(void *ud, void *p, size_t old_bytes, size_t new_bytes);
125
+ void (*free)(void *ud, void *p, size_t bytes);
126
+ } chc_alloc;
127
+
128
+ #ifdef CHC_PROVIDE_STDLIB_ALLOC
129
+ chc_alloc chc_alloc_stdlib(void);
130
+ #endif
131
+
132
+ /* -------------------------------------------------------------------------- */
133
+ /* I/O */
134
+ /* -------------------------------------------------------------------------- */
135
+
136
+ typedef struct chc_io {
137
+ void *ud;
138
+ int (*read)(void *ud, void *buf, size_t len, size_t *out_n, chc_err *err);
139
+ int (*write)(void *ud, const void *buf, size_t len, chc_err *err);
140
+ int (*check_cancel)(void *ud);
141
+ } chc_io;
142
+
143
+ /* -------------------------------------------------------------------------- */
144
+ /* Buffered reader */
145
+ /* -------------------------------------------------------------------------- */
146
+
147
+ /* Buffered input parser pulls from. Two modes:
148
+ * - io-backed (chc_in_init): refills from a chc_io, for blocking io.
149
+ * - ioless (chc_in_init_ioless): bytes received via chc_in_submit;
150
+ * reading past submitted bytes returns CHC_WOULD_BLOCK, leaving
151
+ * in-progress parse to be retried once more bytes arrive. */
152
+ typedef struct chc_in chc_in;
153
+
154
+ CHC_NODISCARD int chc_in_init(chc_in *in, chc_io *io, const chc_alloc *al,
155
+ size_t cap, chc_err *err);
156
+ CHC_NODISCARD int chc_in_init_ioless(chc_in *in, const chc_alloc *al);
157
+ CHC_NODISCARD int chc_in_submit(chc_in *in, const void *buf, size_t len,
158
+ chc_err *err);
159
+ size_t chc_in_available(const chc_in *in); /* unconsumed bytes */
160
+ void chc_in_reset(chc_in *in); /* drop consumed, compact */
161
+ void chc_in_free(chc_in *in);
162
+
163
+ /* -------------------------------------------------------------------------- */
164
+ /* Type AST */
165
+ /* -------------------------------------------------------------------------- */
166
+
167
+ typedef enum chc_kind {
168
+ CHC_VOID = 0,
169
+ CHC_INT8, CHC_INT16, CHC_INT32, CHC_INT64, CHC_INT128, CHC_INT256,
170
+ CHC_UINT8, CHC_UINT16, CHC_UINT32, CHC_UINT64, CHC_UINT128, CHC_UINT256,
171
+ CHC_FLOAT32, CHC_FLOAT64, CHC_BFLOAT16,
172
+ CHC_BOOL,
173
+ CHC_DATE, CHC_DATE32,
174
+ CHC_DATETIME, CHC_DATETIME64,
175
+ CHC_TIME, CHC_TIME64,
176
+ CHC_STRING, CHC_FIXED_STRING,
177
+ CHC_DECIMAL32, CHC_DECIMAL64, CHC_DECIMAL128, CHC_DECIMAL256,
178
+ CHC_UUID, CHC_IPV4, CHC_IPV6,
179
+ CHC_ENUM8, CHC_ENUM16,
180
+ CHC_NULLABLE, CHC_ARRAY, CHC_TUPLE, CHC_MAP, CHC_NESTED,
181
+ CHC_LOW_CARDINALITY,
182
+ CHC_INTERVAL,
183
+ CHC_POINT, CHC_RING, CHC_POLYGON, CHC_MULTI_POLYGON,
184
+ CHC_VARIANT, CHC_DYNAMIC, CHC_JSON, CHC_OBJECT,
185
+ CHC_AGGREGATE_FUNCTION, CHC_SIMPLE_AGGREGATE_FUNCTION,
186
+ CHC_QBIT,
187
+ CHC_NOTHING,
188
+ CHC_KIND_COUNT
189
+ } chc_kind;
190
+
191
+ typedef struct chc_type chc_type;
192
+
193
+ CHC_NODISCARD int chc_type_parse(const char *name, size_t name_len,
194
+ const chc_alloc *al, chc_type **out, chc_err *err);
195
+ void chc_type_destroy(chc_type *t, const chc_alloc *al);
196
+
197
+ chc_kind chc_type_kind(const chc_type *t);
198
+ size_t chc_type_n_children(const chc_type *t);
199
+ const chc_type *chc_type_child(const chc_type *t, size_t i);
200
+
201
+ int chc_type_fixed_size(const chc_type *t);
202
+ size_t chc_type_elem_size(const chc_type *t);
203
+ int chc_type_decimal_precision(const chc_type *t);
204
+ int chc_type_decimal_scale(const chc_type *t);
205
+ int chc_type_datetime64_scale(const chc_type *t);
206
+
207
+ /* QBit(T, N): N (vector dimension). 0 on non-QBit types. The element type
208
+ * (BFloat16/Float32/Float64) is children[0], reached via chc_type_child(t, 0). */
209
+ size_t chc_type_qbit_dimension(const chc_type *t);
210
+ /* QBit element width in bits: 16/32/64. 0 on non-QBit types. Equals the
211
+ * number of FixedString bit-plane columns the column decodes into. */
212
+ size_t chc_type_qbit_element_size(const chc_type *t);
213
+ const char *chc_type_timezone(const chc_type *t, size_t *out_len);
214
+ const char *chc_type_name(const chc_type *t, size_t *out_len);
215
+
216
+ size_t chc_type_enum_count(const chc_type *t);
217
+ void chc_type_enum_at(const chc_type *t, size_t i,
218
+ const char **name, size_t *name_len,
219
+ int64_t *value);
220
+
221
+ /* For Tuple types: returns the ith child's field name or NULL when the
222
+ * tuple is anonymous (or i is out of range). NULL on non-Tuple types. */
223
+ const char *chc_type_tuple_field_name(const chc_type *t, size_t i,
224
+ size_t *out_len);
225
+
226
+ /* Reproduce the printable type name into buf. Returns the number of bytes
227
+ * that would have been written (snprintf-style); use to size buf on a
228
+ * second pass when the return value >= buf_len. buf may be NULL when
229
+ * buf_len == 0 (length query). */
230
+ size_t chc_type_format(const chc_type *t, char *buf, size_t buf_len);
231
+
232
+ /* -------------------------------------------------------------------------- */
233
+ /* Columns */
234
+ /* -------------------------------------------------------------------------- */
235
+
236
+ typedef enum chc_col_kind {
237
+ CHC_COL_FIXED = 1,
238
+ CHC_COL_STRING,
239
+ CHC_COL_NULLABLE,
240
+ CHC_COL_ARRAY,
241
+ CHC_COL_TUPLE,
242
+ CHC_COL_LOW_CARDINALITY,
243
+ CHC_COL_NOTHING
244
+ } chc_col_kind;
245
+
246
+ typedef struct chc_column chc_column;
247
+
248
+ chc_col_kind chc_column_layout(const chc_column *c);
249
+ size_t chc_column_n_rows(const chc_column *c);
250
+
251
+ /* FIXED. Contiguous n_rows * (*elem_size) bytes, little-endian on the wire.
252
+ * Caller responsible to fix endianness if necessary. */
253
+ const void *chc_column_fixed_data(const chc_column *c, size_t *elem_size);
254
+
255
+ /* STRING. Row i's bytes are at data + (i == 0 ? 0 : offsets[i-1]) ..
256
+ * data + offsets[i].
257
+ * Offsets are exclusive ends, host-byte-order. */
258
+ const uint8_t *chc_column_string_data(const chc_column *c);
259
+ const uint64_t *chc_column_string_offsets(const chc_column *c);
260
+
261
+ /* NULLABLE. null_map[i] == 1 means row i is NULL; inner column always has
262
+ * a value at row i regardless (placeholder zero/empty for nulls). */
263
+ const uint8_t *chc_column_null_map(const chc_column *c);
264
+ const chc_column *chc_column_nullable_inner(const chc_column *c);
265
+
266
+ /* ARRAY. offsets[i] is the cumulative end of row i in the values column.
267
+ * Map decodes as ARRAY whose values column is TUPLE(K, V). Offsets in
268
+ * host byte order. */
269
+ const uint64_t *chc_column_array_offsets(const chc_column *c);
270
+ const chc_column *chc_column_array_values(const chc_column *c);
271
+
272
+ /* TUPLE. All children share the same row count as the tuple itself. */
273
+ size_t chc_column_tuple_arity(const chc_column *c);
274
+ const chc_column *chc_column_tuple_child(const chc_column *c, size_t i);
275
+
276
+ /* LOW_CARDINALITY. key_size is 1/2/4/8. keys is n_rows * key_size, host
277
+ * byte order (swapped at decode time on BE). Dict is a column of the inner
278
+ * type; dict slot 0 is the default value, NULLs in LC(Nullable(T)) ride at
279
+ * dict slot 0 of the inner Nullable. */
280
+ int chc_column_lc_key_size(const chc_column *c);
281
+ const void *chc_column_lc_keys(const chc_column *c);
282
+ const chc_column *chc_column_lc_dict(const chc_column *c);
283
+
284
+ /* Walk a column tree & enforce cross-field invariants the server itself
285
+ * enforces on its native deserialization path:
286
+ * - Array offsets non-decreasing (SerializationArray.cpp:444, throws
287
+ * "Arrays offsets are not monotonically increasing")
288
+ * - LowCardinality keys < dict size (ColumnLowCardinality.cpp:255, throws
289
+ * "Index for LowCardinality is out of range")
290
+ * chc_block_read does NOT call this automatically — a peer that forges
291
+ * offsets or LC keys can cause callers to read past inner-column bounds.
292
+ * Consumers ingesting from untrusted senders should call this on each
293
+ * block column before traversing it. Returns CHC_OK on success, or
294
+ * CHC_ERR_PROTOCOL with a reason in err on the first violation. NULL c
295
+ * is treated as OK. */
296
+ CHC_NODISCARD int chc_column_validate(const chc_column *c, chc_err *err);
297
+
298
+ /* -------------------------------------------------------------------------- */
299
+ /* Block reader */
300
+ /* -------------------------------------------------------------------------- */
301
+
302
+ typedef struct chc_block chc_block;
303
+
304
+ typedef struct chc_block_opts {
305
+ /* TCP path (server_revision >= 51903): an 8-byte BlockInfo prefix is on
306
+ * the wire before num_columns. clickhouse-local does not emit it. */
307
+ bool has_block_info;
308
+
309
+ /* TCP path (server_revision >= 54454): a 1-byte has_custom_serialization
310
+ * flag follows each column's type name. clickhouse-local does not emit
311
+ * it. */
312
+ bool has_custom_serialization;
313
+
314
+ /* Internal read-buffer size. 0 = default (8 KiB). */
315
+ size_t read_buffer_bytes;
316
+ } chc_block_opts;
317
+
318
+ /* Read one block from a caller-owned chc_in. Reuse one chc_in (chc_in_init /
319
+ * chc_in_free) across calls to stream successive blocks: bytes read past the
320
+ * block boundary stay buffered for the next call. NULL opts means empty opts.
321
+ * Return 0 with *out = NULL at clean EOF, or CHC_ERR_* on error. */
322
+ CHC_NODISCARD int chc_block_read(chc_in *in, const chc_alloc *al,
323
+ const chc_block_opts *opts,
324
+ chc_block **out, chc_err *err);
325
+
326
+ void chc_block_destroy(chc_block *b, const chc_alloc *al);
327
+
328
+ size_t chc_block_n_rows(const chc_block *b);
329
+ size_t chc_block_n_columns(const chc_block *b);
330
+ const char *chc_block_column_name(const chc_block *b, size_t i, size_t *out_len);
331
+ const chc_type *chc_block_column_type(const chc_block *b, size_t i);
332
+ const chc_column *chc_block_column(const chc_block *b, size_t i);
333
+
334
+ /* BlockInfo accessors. Defined-but-zero when opts.has_block_info == false. */
335
+ bool chc_block_is_overflows(const chc_block *b);
336
+ int32_t chc_block_bucket_num(const chc_block *b);
337
+
338
+ /* -------------------------------------------------------------------------- */
339
+ /* Block writer */
340
+ /* -------------------------------------------------------------------------- */
341
+
342
+ typedef struct chc_block_builder chc_block_builder;
343
+
344
+ CHC_NODISCARD int chc_block_builder_init(chc_block_builder **out, const chc_alloc *al,
345
+ chc_err *err);
346
+ void chc_block_builder_destroy(chc_block_builder *bb);
347
+
348
+ /* For variable-length columns, offsets[i] is the cumulative end of row i
349
+ * (exclusive ends, host byte order). For fixed columns, data is n_rows *
350
+ * elem_size little-endian bytes. None of the slabs are copied; they must
351
+ * outlive chc_block_write. */
352
+ CHC_NODISCARD int chc_block_builder_append_fixed(chc_block_builder *bb,
353
+ const char *name, size_t name_len,
354
+ const chc_type *t,
355
+ const void *data, size_t n_rows,
356
+ chc_err *err);
357
+
358
+ CHC_NODISCARD int chc_block_builder_append_string(chc_block_builder *bb,
359
+ const char *name, size_t name_len,
360
+ const uint64_t *offsets,
361
+ const uint8_t *data, size_t n_rows,
362
+ chc_err *err);
363
+
364
+ /* Composite append helpers. Slabs stay caller-owned; the builder never
365
+ * copies. Offsets / keys are host byte order; the writer byte-swaps to
366
+ * little-endian on BE hosts. `t` carries the column's full CH type and
367
+ * must match the helper variant (e.g. _nullable_fixed expects
368
+ * Nullable(<fixed>), _array_string expects Array(String), and
369
+ * _low_cardinality_string expects LowCardinality(String) or
370
+ * LowCardinality(Nullable(String))).
371
+ *
372
+ * Nested arrays (Array(Array(T))) and Tuple columns are not exposed yet —
373
+ * add when a consumer asks. */
374
+ CHC_NODISCARD int chc_block_builder_append_nullable_fixed(
375
+ chc_block_builder *bb,
376
+ const char *name, size_t name_len,
377
+ const chc_type *t,
378
+ const uint8_t *null_map,
379
+ const void *inner_data,
380
+ size_t n_rows, chc_err *err);
381
+
382
+ CHC_NODISCARD int chc_block_builder_append_nullable_string(
383
+ chc_block_builder *bb,
384
+ const char *name, size_t name_len,
385
+ const chc_type *t,
386
+ const uint8_t *null_map,
387
+ const uint64_t *inner_offsets,
388
+ const uint8_t *inner_data,
389
+ size_t n_rows, chc_err *err);
390
+
391
+ CHC_NODISCARD int chc_block_builder_append_array_fixed(
392
+ chc_block_builder *bb,
393
+ const char *name, size_t name_len,
394
+ const chc_type *t,
395
+ const uint64_t *offsets,
396
+ const void *values,
397
+ size_t n_rows, chc_err *err);
398
+
399
+ CHC_NODISCARD int chc_block_builder_append_array_string(
400
+ chc_block_builder *bb,
401
+ const char *name, size_t name_len,
402
+ const chc_type *t,
403
+ const uint64_t *offsets,
404
+ const uint64_t *values_offsets,
405
+ const uint8_t *values_data,
406
+ size_t n_rows, chc_err *err);
407
+
408
+ /* Nested Array(Array(...(<fixed/string>))) variants. `t` is top-level
409
+ * Array type, `ndim` is nesting depth (must match `t`). level_offsets
410
+ * is ndim cumulative-end arrays ordered outer-to-inner, level_offsets_len
411
+ * gives count at each level. n_rows is top-level row count, must equal
412
+ * level_offsets_len[0] */
413
+ CHC_NODISCARD int chc_block_builder_append_array_nested_fixed(
414
+ chc_block_builder *bb,
415
+ const char *name, size_t name_len,
416
+ const chc_type *t,
417
+ int ndim,
418
+ const uint64_t * const *level_offsets,
419
+ const size_t *level_offsets_len,
420
+ const void *values,
421
+ size_t n_rows, chc_err *err);
422
+
423
+ CHC_NODISCARD int chc_block_builder_append_array_nested_string(
424
+ chc_block_builder *bb,
425
+ const char *name, size_t name_len,
426
+ const chc_type *t,
427
+ int ndim,
428
+ const uint64_t * const *level_offsets,
429
+ const size_t *level_offsets_len,
430
+ const uint64_t *values_offsets,
431
+ const uint8_t *values_data,
432
+ size_t n_rows, chc_err *err);
433
+
434
+ /* LowCardinality(String) or LowCardinality(Nullable(String)). For the
435
+ * Nullable variant the caller must place a null-sentinel entry at dict
436
+ * index 0 (CH convention) and use key 0 for null rows. */
437
+ /* JSON column, STRING serialization. `t` must be CHC_JSON. Rows are JSON
438
+ * document text, one per offset; builder emits an 8-byte LE serialization-
439
+ * version prefix (value 1) once before the same wire format as
440
+ * chc_block_builder_append_string. Caller is responsible for the input
441
+ * being valid JSON; server rejects malformed documents at INSERT time. */
442
+ CHC_NODISCARD int chc_block_builder_append_json_string(
443
+ chc_block_builder *bb,
444
+ const char *name, size_t name_len,
445
+ const chc_type *t, /* CHC_JSON */
446
+ const uint64_t *offsets,
447
+ const uint8_t *data,
448
+ size_t n_rows, chc_err *err);
449
+
450
+ CHC_NODISCARD int chc_block_builder_append_low_cardinality_string(
451
+ chc_block_builder *bb,
452
+ const char *name, size_t name_len,
453
+ const chc_type *t,
454
+ int key_size,
455
+ const void *keys,
456
+ const uint64_t *dict_offsets,
457
+ const uint8_t *dict_data,
458
+ size_t dict_n,
459
+ size_t n_rows, chc_err *err);
460
+
461
+ CHC_NODISCARD int chc_block_write(chc_io *io, const chc_block_builder *bb,
462
+ const chc_block_opts *opts, chc_err *err);
463
+
464
+ /* ========================================================================== */
465
+ /* Implementation */
466
+ /* ========================================================================== */
467
+
468
+ #ifdef CHC_IMPLEMENTATION
469
+
470
+ #include <ctype.h>
471
+ #include <stdarg.h>
472
+ #include <stdio.h>
473
+ #include <string.h>
474
+
475
+ /* Endianness detection. CH wire format is little-endian. On BE hosts the
476
+ * library byte-swaps the offsets/keys arrays it exposes through host-typed
477
+ * pointers; FIXED slabs stay LE. */
478
+ #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
479
+ # define CHC_BIG_ENDIAN 1
480
+ #else
481
+ # define CHC_BIG_ENDIAN 0
482
+ #endif
483
+
484
+ #if CHC_BIG_ENDIAN
485
+ static inline uint16_t chc__bswap16(uint16_t v) {
486
+ return (uint16_t) ((v >> 8) | (v << 8));
487
+ }
488
+ static inline uint32_t chc__bswap32(uint32_t v) {
489
+ return ((v & 0xff000000u) >> 24) | ((v & 0x00ff0000u) >> 8)
490
+ | ((v & 0x0000ff00u) << 8) | ((v & 0x000000ffu) << 24);
491
+ }
492
+ static inline uint64_t chc__bswap64(uint64_t v) {
493
+ return ((v & 0xff00000000000000ull) >> 56) | ((v & 0x00ff000000000000ull) >> 40)
494
+ | ((v & 0x0000ff0000000000ull) >> 24) | ((v & 0x000000ff00000000ull) >> 8)
495
+ | ((v & 0x00000000ff000000ull) << 8) | ((v & 0x0000000000ff0000ull) << 24)
496
+ | ((v & 0x000000000000ff00ull) << 40) | ((v & 0x00000000000000ffull) << 56);
497
+ }
498
+ #else
499
+ # define chc__bswap16(v) (v)
500
+ # define chc__bswap32(v) (v)
501
+ # define chc__bswap64(v) (v)
502
+ #endif
503
+
504
+ /* -------- CityHash short-string helpers ---------- */
505
+
506
+ /* Frozen v1.0.3 variant of CityHash, ported from city.cc.
507
+ * Original: Copyright (c) 2011 Google, Inc. (MIT licence).
508
+ * Short-string path lives here so chc__name_to_kind can reuse it; the
509
+ * 128-bit driver used by compressed-frame checksums sits in
510
+ * clickhouse-compression.h and builds on these helpers. */
511
+
512
+ static uint64_t chc__city_fetch64(const char *p) CHC_REPRODUCIBLE
513
+ {
514
+ uint64_t v;
515
+ memcpy(&v, p, 8);
516
+ #if CHC_BIG_ENDIAN
517
+ v = chc__bswap64(v);
518
+ #endif
519
+ return v;
520
+ }
521
+
522
+ static uint32_t chc__city_fetch32(const char *p) CHC_REPRODUCIBLE
523
+ {
524
+ uint32_t v;
525
+ memcpy(&v, p, 4);
526
+ #if CHC_BIG_ENDIAN
527
+ v = chc__bswap32(v);
528
+ #endif
529
+ return v;
530
+ }
531
+
532
+ static const uint64_t chc__city_k0 = 0xc3a5c85c97cb3127ULL;
533
+ static const uint64_t chc__city_k1 = 0xb492b66fbe98f273ULL;
534
+ static const uint64_t chc__city_k2 = 0x9ae16a3b2f90404fULL;
535
+ static const uint64_t chc__city_k3 = 0xc949d7c7509e6557ULL;
536
+
537
+ static uint64_t chc__city_rotate_at_least_1(uint64_t v, int s) CHC_UNSEQUENCED
538
+ {
539
+ return (v >> s) | (v << (64 - s));
540
+ }
541
+
542
+ static uint64_t chc__city_shift_mix(uint64_t v) CHC_UNSEQUENCED { return v ^ (v >> 47); }
543
+
544
+ static uint64_t chc__city_hash128_to_64(uint64_t lo, uint64_t hi) CHC_UNSEQUENCED
545
+ {
546
+ const uint64_t kMul = 0x9ddfea08eb382d69ULL;
547
+ uint64_t a = (lo ^ hi) * kMul;
548
+ a ^= (a >> 47);
549
+ uint64_t b = (hi ^ a) * kMul;
550
+ b ^= (b >> 47);
551
+ b *= kMul;
552
+ return b;
553
+ }
554
+
555
+ static uint64_t chc__city_hash_len_16(uint64_t u, uint64_t v) CHC_UNSEQUENCED
556
+ {
557
+ return chc__city_hash128_to_64(u, v);
558
+ }
559
+
560
+ static uint64_t chc__city_hash_len_0_to_16(const char *s, size_t len) CHC_REPRODUCIBLE
561
+ {
562
+ if (len > 8) {
563
+ uint64_t a = chc__city_fetch64(s);
564
+ uint64_t b = chc__city_fetch64(s + len - 8);
565
+ return chc__city_hash_len_16(a,
566
+ chc__city_rotate_at_least_1(b + len, (int) len)) ^ b;
567
+ }
568
+ if (len >= 4) {
569
+ uint64_t a = chc__city_fetch32(s);
570
+ return chc__city_hash_len_16(len + (a << 3),
571
+ chc__city_fetch32(s + len - 4));
572
+ }
573
+ if (len > 0) {
574
+ uint8_t a = (uint8_t) s[0];
575
+ uint8_t b = (uint8_t) s[len >> 1];
576
+ uint8_t c = (uint8_t) s[len - 1];
577
+ uint32_t y = (uint32_t) a + ((uint32_t) b << 8);
578
+ uint32_t z = (uint32_t) len + ((uint32_t) c << 2);
579
+ return chc__city_shift_mix(
580
+ (uint64_t) y * chc__city_k2 ^
581
+ (uint64_t) z * chc__city_k3) * chc__city_k2;
582
+ }
583
+ return chc__city_k2;
584
+ }
585
+
586
+ /* -------- error helpers ---------- */
587
+
588
+ #if defined(__GNUC__) || defined(__clang__)
589
+ # define CHC__PRINTF_FMT(fmt_idx, va_idx) \
590
+ __attribute__((format(printf, fmt_idx, va_idx)))
591
+ #else
592
+ # define CHC__PRINTF_FMT(fmt_idx, va_idx)
593
+ #endif
594
+
595
+ static int CHC__PRINTF_FMT(3, 4)
596
+ chc__err_set(chc_err *e, int code, const char *fmt, ...)
597
+ {
598
+ if (!e) return code;
599
+ if (fmt) {
600
+ va_list ap;
601
+ __builtin_va_start(ap, fmt);
602
+ vsnprintf(e->msg, sizeof e->msg, fmt, ap);
603
+ __builtin_va_end(ap);
604
+ } else {
605
+ e->msg[0] = '\0';
606
+ }
607
+ return code;
608
+ }
609
+
610
+ /* -------- alloc helpers ---------- */
611
+
612
+ static void *
613
+ chc__alloc(const chc_alloc *al, size_t n, chc_err *err)
614
+ {
615
+ void *p = al->alloc(al->ud, n);
616
+ if (!p) {
617
+ chc__err_set(err, CHC_ERR_OOM, "alloc(%zu) failed", n);
618
+ return NULL;
619
+ }
620
+ return p;
621
+ }
622
+
623
+ static void *
624
+ chc__calloc(const chc_alloc *al, size_t n, chc_err *err)
625
+ {
626
+ void *p = chc__alloc(al, n, err);
627
+ if (p) memset(p, 0, n);
628
+ return p;
629
+ }
630
+
631
+ static void *
632
+ chc__realloc(const chc_alloc *al, void *p, size_t old_n, size_t new_n,
633
+ chc_err *err)
634
+ {
635
+ void *q = al->realloc(al->ud, p, old_n, new_n);
636
+ if (!q && new_n) {
637
+ chc__err_set(err, CHC_ERR_OOM, "realloc(%zu->%zu) failed", old_n, new_n);
638
+ return NULL;
639
+ }
640
+ return q;
641
+ }
642
+
643
+ /* Overflow-checked size multiply for count*elem allocation sizing. Matches ckd_mul. */
644
+ #if defined(__has_builtin)
645
+ # if __has_builtin(__builtin_mul_overflow)
646
+ # define CHC__HAVE_MUL_OVERFLOW 1
647
+ # endif
648
+ #elif defined(__GNUC__) && (__GNUC__ >= 5)
649
+ # define CHC__HAVE_MUL_OVERFLOW 1
650
+ #endif
651
+
652
+ static bool
653
+ chc__mul_size(size_t a, size_t b, size_t *out)
654
+ {
655
+ #if defined(CHC__HAVE_CKD_MUL)
656
+ return ckd_mul(out, a, b);
657
+ #elif defined(CHC__HAVE_MUL_OVERFLOW)
658
+ return __builtin_mul_overflow(a, b, out);
659
+ #else
660
+ *out = a * b;
661
+ return a != 0 && b > (size_t) SIZE_MAX / a;
662
+ #endif
663
+ }
664
+
665
+ static char *
666
+ chc__strdup(const chc_alloc *al, const char *s, size_t n, chc_err *err)
667
+ {
668
+ char *p = chc__alloc(al, n + 1, err);
669
+ if (!p) return NULL;
670
+ if (n) memcpy(p, s, n);
671
+ p[n] = '\0';
672
+ return p;
673
+ }
674
+
675
+ /* Copy a quoted identifier body (between the outer quote chars), resolving
676
+ * the two escape forms ClickHouse's lexer accepts: a doubled quote stands
677
+ * for one literal quote (`` `` `` -> `` ` ``, `""` -> `"`), & `\X` keeps X
678
+ * verbatim. n is an upper bound; the resolved length is returned via
679
+ * *out_len. */
680
+ static char *
681
+ chc__strdup_unquote(const chc_alloc *al, const char *s, size_t n, char quote,
682
+ size_t *out_len, chc_err *err)
683
+ {
684
+ /* n is an upper bound; escapes shrink it */
685
+ size_t o = 0;
686
+ for (size_t i = 0; i < n; i++) {
687
+ if (s[i] == '\\' && i + 1 < n) { i++; o++; continue; }
688
+ if (s[i] == quote && i + 1 < n && s[i + 1] == quote) { i++; o++; continue; }
689
+ o++;
690
+ }
691
+ char *p = chc__alloc(al, o + 1, err);
692
+ if (!p) return NULL;
693
+ size_t j = 0;
694
+ for (size_t i = 0; i < n; i++) {
695
+ char c = s[i];
696
+ if (c == '\\' && i + 1 < n) { p[j++] = s[++i]; continue; }
697
+ if (c == quote && i + 1 < n && s[i + 1] == quote) { p[j++] = quote; i++; continue; }
698
+ p[j++] = c;
699
+ }
700
+ p[o] = '\0';
701
+ *out_len = o;
702
+ return p;
703
+ }
704
+
705
+ #ifdef CHC_PROVIDE_STDLIB_ALLOC
706
+ #include <stdlib.h>
707
+ static void *chc__std_alloc(CHC_MAYBE_UNUSED void *ud, size_t n)
708
+ { return malloc(n); }
709
+ static void *chc__std_realloc(CHC_MAYBE_UNUSED void *ud, void *p,
710
+ CHC_MAYBE_UNUSED size_t o, size_t n)
711
+ { return realloc(p, n); }
712
+ static void chc__std_free(CHC_MAYBE_UNUSED void *ud, void *p,
713
+ CHC_MAYBE_UNUSED size_t b)
714
+ {
715
+ #if defined(__STDC_VERSION_STDLIB_H__) \
716
+ && __STDC_VERSION_STDLIB_H__ >= 202311L
717
+ free_sized(p, b);
718
+ #else
719
+ free(p);
720
+ #endif
721
+ }
722
+ chc_alloc chc_alloc_stdlib(void) {
723
+ chc_alloc a = { NULL, chc__std_alloc, chc__std_realloc, chc__std_free };
724
+ return a;
725
+ }
726
+ #endif
727
+
728
+ /* -------- buffered reader ---------- */
729
+
730
+ #ifndef CHC_READ_BUFFER
731
+ #define CHC_READ_BUFFER 8192
732
+ #endif
733
+
734
+ /* Mirror ClickHouse's limits:
735
+ * https://github.com/ClickHouse/ClickHouse/blob/ef11941cf5a/src/IO/ReadHelpers.h#L38
736
+ * https://github.com/ClickHouse/ClickHouse/blob/ef11941cf5a/src/Core/Defines.h#L156-L158
737
+ * https://github.com/ClickHouse/ClickHouse/blob/ef11941cf5a/src/DataTypes/DataTypeFixedString.h#L5
738
+ * https://github.com/ClickHouse/ClickHouse/blob/ef11941cf5a/src/DataTypes/DataTypeFactory.cpp#L122-L127 */
739
+ #ifndef CHC_MAX_STRING_SIZE
740
+ #define CHC_MAX_STRING_SIZE (1ULL << 30)
741
+ #endif
742
+ #ifndef CHC_MAX_NUM_COLUMNS
743
+ #define CHC_MAX_NUM_COLUMNS 1000000ULL
744
+ #endif
745
+ #ifndef CHC_MAX_NUM_ROWS
746
+ #define CHC_MAX_NUM_ROWS 1000000000000ULL
747
+ #endif
748
+ #ifndef CHC_MAX_FIXEDSTRING_SIZE
749
+ #define CHC_MAX_FIXEDSTRING_SIZE 0xFFFFFFULL
750
+ #endif
751
+ #ifndef CHC_MAX_TYPE_DEPTH
752
+ #define CHC_MAX_TYPE_DEPTH 300
753
+ #endif
754
+
755
+ /* Reader mode is normally chosen at runtime per `in` (io-backed vs ioless).
756
+ * Two opt-in macros fix it at compile time to optimize out other branches: */
757
+ #if defined(CHC_NO_ASYNC) && defined(CHC_NO_SYNC)
758
+ # error "CHC_NO_ASYNC and CHC_NO_SYNC are mutually exclusive"
759
+ #elif defined(CHC_NO_ASYNC)
760
+ # define CHC__IOLESS(in) 0
761
+ #elif defined(CHC_NO_SYNC)
762
+ # define CHC__IOLESS(in) 1
763
+ #else
764
+ # define CHC__IOLESS(in) ((in)->io == NULL)
765
+ #endif
766
+
767
+ /* Public typedef in the declarations section; struct body is internal. */
768
+ struct chc_in {
769
+ chc_io *io; /* NULL => ioless */
770
+ const chc_alloc *al;
771
+ uint8_t *buf;
772
+ size_t cap;
773
+ size_t pos; /* read cursor */
774
+ size_t fill; /* bytes valid in buf */
775
+ bool eof;
776
+ uint64_t consumed; /* total bytes returned to caller */
777
+ size_t mark; /* rewind target; SIZE_MAX when unset */
778
+ };
779
+
780
+ int
781
+ chc_in_init(chc_in *in, chc_io *io, const chc_alloc *al,
782
+ size_t cap, chc_err *err)
783
+ {
784
+ if (cap == 0) cap = CHC_READ_BUFFER;
785
+ *in = (chc_in) { .io = io, .al = al, .cap = cap, .mark = SIZE_MAX };
786
+ in->buf = chc__alloc(al, cap, err);
787
+ if (!in->buf) return CHC_ERR_OOM;
788
+ return CHC_OK;
789
+ }
790
+
791
+ int
792
+ chc_in_init_ioless(chc_in *in, const chc_alloc *al)
793
+ {
794
+ *in = (chc_in) { .al = al, .mark = SIZE_MAX };
795
+ return CHC_OK;
796
+ }
797
+
798
+ /* Drop prefix [0, keep): keep = mark when a checkpoint is live, else pos.
799
+ * consumed counts returned bytes, not offsets, so compaction leaves it be;
800
+ * mark and pos shift together so (pos - mark) survives for rewind. */
801
+ static void
802
+ chc__in_compact(chc_in *in)
803
+ {
804
+ size_t keep = in->mark == SIZE_MAX ? in->pos : in->mark;
805
+ if (keep == 0) return;
806
+ size_t live = in->fill - keep;
807
+ if (live) memmove(in->buf, in->buf + keep, live);
808
+ in->fill -= keep;
809
+ in->pos -= keep;
810
+ if (in->mark != SIZE_MAX) in->mark -= keep;
811
+ }
812
+
813
+ int
814
+ chc_in_submit(chc_in *in, const void *buf, size_t len, chc_err *err)
815
+ {
816
+ if (in->io)
817
+ return chc__err_set(err, CHC_ERR_USAGE, "chc_in_submit on io-backed reader");
818
+ if (len == 0) return CHC_OK;
819
+ chc__in_compact(in);
820
+ if (in->fill + len > in->cap) {
821
+ size_t ncap = in->cap ? in->cap : CHC_READ_BUFFER;
822
+ while (ncap < in->fill + len) {
823
+ if (ncap > SIZE_MAX / 2) { ncap = in->fill + len; break; }
824
+ ncap *= 2;
825
+ }
826
+ uint8_t *nb = chc__realloc(in->al, in->buf, in->cap, ncap, err);
827
+ if (!nb) return CHC_ERR_OOM;
828
+ in->buf = nb;
829
+ in->cap = ncap;
830
+ }
831
+ memcpy(in->buf + in->fill, buf, len);
832
+ in->fill += len;
833
+ return CHC_OK;
834
+ }
835
+
836
+ size_t
837
+ chc_in_available(const chc_in *in)
838
+ {
839
+ return in->fill - in->pos;
840
+ }
841
+
842
+ void
843
+ chc_in_reset(chc_in *in)
844
+ {
845
+ in->mark = SIZE_MAX;
846
+ chc__in_compact(in);
847
+ }
848
+
849
+ void
850
+ chc_in_free(chc_in *in)
851
+ {
852
+ in->al->free(in->al->ud, in->buf, in->cap);
853
+ in->buf = NULL;
854
+ }
855
+
856
+ /* Mark read cursor as rewind target. Ioless checkpoints at a packet
857
+ * boundary so a mid-parse CHC_WOULD_BLOCK can rewind and re-parse once more bytes arrive. */
858
+ CHC_MAYBE_UNUSED static void
859
+ chc__in_checkpoint(chc_in *in)
860
+ {
861
+ in->mark = in->pos;
862
+ }
863
+
864
+ /* Restore the cursor to the last checkpoint, un-counting bytes consumed
865
+ * since it (so consumed stays equal to an io-backed read of the same
866
+ * stream). Clears the mark. */
867
+ CHC_MAYBE_UNUSED static int
868
+ chc__in_rewind(chc_in *in)
869
+ {
870
+ if (in->mark == SIZE_MAX)
871
+ return CHC_ERR_USAGE;
872
+ in->consumed -= in->pos - in->mark;
873
+ in->pos = in->mark;
874
+ in->mark = SIZE_MAX;
875
+ return CHC_OK;
876
+ }
877
+
878
+ /* Refill buf with at least one byte. Returns 0 on success, CHC_ERR_EOF on
879
+ * clean EOF, CHC_ERR_IO/CANCELLED on failure.
880
+ * Returns CHC_WOULD_BLOCK when not CHC_OK in ioless. */
881
+ static int
882
+ chc__in_refill(chc_in *in, chc_err *err)
883
+ {
884
+ if (in->pos < in->fill) return CHC_OK;
885
+ if (CHC__IOLESS(in))
886
+ return chc__err_set(err, CHC_WOULD_BLOCK, "ioless buffer drained");
887
+ if (in->eof) return chc__err_set(err, CHC_ERR_EOF, "unexpected eof");
888
+
889
+ if (in->io->check_cancel && in->io->check_cancel(in->io->ud))
890
+ return chc__err_set(err, CHC_ERR_CANCELLED, "cancelled");
891
+
892
+ in->pos = 0;
893
+ in->fill = 0;
894
+ size_t got = 0;
895
+ int rc = in->io->read(in->io->ud, in->buf, in->cap, &got, err);
896
+ if (rc != CHC_OK) {
897
+ if (err && err->msg[0] == '\0') chc__err_set(err, rc, "read failed");
898
+ return rc;
899
+ }
900
+ if (got == 0) { in->eof = true; return chc__err_set(err, CHC_ERR_EOF, "unexpected eof"); }
901
+ in->fill = got;
902
+ return CHC_OK;
903
+ }
904
+
905
+ static int
906
+ chc__read_byte(chc_in *in, uint8_t *out, chc_err *err)
907
+ {
908
+ if (in->pos >= in->fill) {
909
+ int rc = chc__in_refill(in, err);
910
+ if (rc != CHC_OK) return rc;
911
+ }
912
+ *out = in->buf[in->pos++];
913
+ in->consumed++;
914
+ return CHC_OK;
915
+ }
916
+
917
+ static int
918
+ chc__read_bytes(chc_in *in, void *dst, size_t n, chc_err *err)
919
+ {
920
+ uint8_t *p = dst;
921
+
922
+ if (in->pos < in->fill) {
923
+ size_t avail = in->fill - in->pos;
924
+ size_t take = n < avail ? n : avail;
925
+ memcpy(p, in->buf + in->pos, take);
926
+ in->pos += take;
927
+ in->consumed += take;
928
+ p += take;
929
+ n -= take;
930
+ }
931
+
932
+ /* Bypass staging buf when request spans more than one refill, read
933
+ * straight into caller's dst to skip the staging memcpy. Only fires
934
+ * after the staging buf is drained, so buffered-reader invariants
935
+ * (pos, fill, consumed) stay consistent. Disabled in ioless: bypassed
936
+ * bytes land outside in->buf and can't be rewound, so ioless routes
937
+ * everything through the (growable) staging buf. */
938
+ while (!CHC__IOLESS(in) && n > in->cap) {
939
+ if (in->eof)
940
+ return chc__err_set(err, CHC_ERR_EOF, "short read");
941
+ if (in->io->check_cancel && in->io->check_cancel(in->io->ud))
942
+ return chc__err_set(err, CHC_ERR_CANCELLED, "cancelled");
943
+ size_t got = 0;
944
+ int rc = in->io->read(in->io->ud, p, n, &got, err);
945
+ if (rc != CHC_OK) {
946
+ if (err && err->msg[0] == '\0') chc__err_set(err, rc, "read failed");
947
+ return rc;
948
+ }
949
+ if (got == 0) {
950
+ in->eof = true;
951
+ return chc__err_set(err, CHC_ERR_EOF, "short read");
952
+ }
953
+ p += got;
954
+ in->consumed += got;
955
+ n -= got;
956
+ }
957
+
958
+ while (n) {
959
+ if (in->pos >= in->fill) {
960
+ int rc = chc__in_refill(in, err);
961
+ if (rc == CHC_ERR_EOF)
962
+ return chc__err_set(err, CHC_ERR_EOF, "short read");
963
+ if (rc != CHC_OK) return rc;
964
+ }
965
+ size_t avail = in->fill - in->pos;
966
+ size_t take = n < avail ? n : avail;
967
+ memcpy(p, in->buf + in->pos, take);
968
+ in->pos += take;
969
+ in->consumed += take;
970
+ p += take;
971
+ n -= take;
972
+ }
973
+ return CHC_OK;
974
+ }
975
+
976
+ static int
977
+ chc__read_varuint(chc_in *in, uint64_t *out, chc_err *err)
978
+ {
979
+ uint64_t v = 0;
980
+ for (int i = 0; i < 10; i++) {
981
+ uint8_t b;
982
+ int rc = chc__read_byte(in, &b, err);
983
+ if (rc != CHC_OK) return rc;
984
+ v |= ((uint64_t)(b & 0x7f)) << (7 * i);
985
+ if (!(b & 0x80)) { *out = v; return CHC_OK; }
986
+ }
987
+ return chc__err_set(err, CHC_ERR_PROTOCOL, "varint too long");
988
+ }
989
+
990
+ static int
991
+ chc__read_u32_le(chc_in *in, uint32_t *out, chc_err *err)
992
+ {
993
+ uint32_t v;
994
+ int rc = chc__read_bytes(in, &v, sizeof v, err);
995
+ if (rc != CHC_OK) return rc;
996
+ *out = chc__bswap32(v);
997
+ return CHC_OK;
998
+ }
999
+
1000
+ static int
1001
+ chc__read_u64_le(chc_in *in, uint64_t *out, chc_err *err)
1002
+ {
1003
+ uint64_t v;
1004
+ int rc = chc__read_bytes(in, &v, sizeof v, err);
1005
+ if (rc != CHC_OK) return rc;
1006
+ *out = chc__bswap64(v);
1007
+ return CHC_OK;
1008
+ }
1009
+
1010
+ static int
1011
+ chc__read_string(chc_in *in, char **out, size_t *out_len, chc_err *err)
1012
+ {
1013
+ const chc_alloc *al = in->al;
1014
+ uint64_t len;
1015
+ int rc = chc__read_varuint(in, &len, err);
1016
+ if (rc != CHC_OK) return rc;
1017
+ if (len > CHC_MAX_STRING_SIZE)
1018
+ return chc__err_set(err, CHC_ERR_PROTOCOL, "string too long: %llu",
1019
+ (unsigned long long) len);
1020
+ char *buf = chc__alloc(al, len + 1, err);
1021
+ if (!buf) return CHC_ERR_OOM;
1022
+ if (len) {
1023
+ rc = chc__read_bytes(in, buf, (size_t) len, err);
1024
+ if (rc != CHC_OK) { al->free(al->ud, buf, len + 1); return rc; }
1025
+ }
1026
+ buf[len] = '\0';
1027
+ *out = buf;
1028
+ *out_len = (size_t) len;
1029
+ return CHC_OK;
1030
+ }
1031
+
1032
+ /* -------- type AST internals ---------- */
1033
+
1034
+ struct chc_type {
1035
+ chc_kind kind;
1036
+ char *name;
1037
+ size_t name_len;
1038
+ size_t n_children;
1039
+ chc_type **children;
1040
+ /* For Tuple: parallel to children[]. NULL when tuple has no field names.
1041
+ * Individual slots may be NULL for mixed-anonymity tuples (rare; CH allows it). */
1042
+ char **field_names;
1043
+ size_t *field_name_lens;
1044
+ union {
1045
+ struct { int n; } fixed_string; /* FixedString(N) */
1046
+ struct { int precision, scale; } decimal; /* Decimal(P, S) */
1047
+ struct { int scale; char *tz; size_t tz_len; } temporal; /* DateTime / DateTime64 / Time64 */
1048
+ struct { size_t dimension; } qbit; /* QBit(T, N): N; element type in children[0] */
1049
+ struct {
1050
+ size_t n;
1051
+ struct { char *name; uint32_t name_len; int16_t value; } *items;
1052
+ } enum_;
1053
+ };
1054
+ };
1055
+
1056
+ static bool chc__kind_is_decimal(chc_kind k)
1057
+ { return k == CHC_DECIMAL32 || k == CHC_DECIMAL64 || k == CHC_DECIMAL128 || k == CHC_DECIMAL256; }
1058
+ static bool chc__kind_is_enum(chc_kind k)
1059
+ { return k == CHC_ENUM8 || k == CHC_ENUM16; }
1060
+ static bool chc__kind_has_tz(chc_kind k)
1061
+ { return k == CHC_DATETIME || k == CHC_DATETIME64 || k == CHC_TIME64; }
1062
+
1063
+ void
1064
+ chc_type_destroy(chc_type *t, const chc_alloc *al)
1065
+ {
1066
+ if (!t) return;
1067
+ if (t->field_names) {
1068
+ for (size_t i = 0; i < t->n_children; i++)
1069
+ al->free(al->ud, t->field_names[i], t->field_name_lens[i] + 1);
1070
+ al->free(al->ud, t->field_names, t->n_children * sizeof *t->field_names);
1071
+ }
1072
+ al->free(al->ud, t->field_name_lens, t->n_children * sizeof *t->field_name_lens);
1073
+ for (size_t i = 0; i < t->n_children; i++)
1074
+ chc_type_destroy(t->children[i], al);
1075
+ al->free(al->ud, t->children, t->n_children * sizeof *t->children);
1076
+ if (chc__kind_is_enum(t->kind) && t->enum_.items) {
1077
+ for (size_t i = 0; i < t->enum_.n; i++)
1078
+ al->free(al->ud, t->enum_.items[i].name,
1079
+ t->enum_.items[i].name_len + 1);
1080
+ al->free(al->ud, t->enum_.items,
1081
+ t->enum_.n * sizeof *t->enum_.items);
1082
+ } else if (chc__kind_has_tz(t->kind))
1083
+ al->free(al->ud, t->temporal.tz, t->temporal.tz_len + 1);
1084
+ al->free(al->ud, t->name, t->name_len + 1);
1085
+ al->free(al->ud, t, sizeof *t);
1086
+ }
1087
+
1088
+ chc_kind chc_type_kind(const chc_type *t) { return t ? t->kind : CHC_VOID; }
1089
+ size_t chc_type_n_children(const chc_type *t) { return t ? t->n_children : 0; }
1090
+ const chc_type *chc_type_child(const chc_type *t, size_t i) { return (t && i < t->n_children) ? t->children[i] : NULL; }
1091
+ int chc_type_fixed_size(const chc_type *t) { return t && t->kind == CHC_FIXED_STRING ? t->fixed_string.n : 0; }
1092
+ int chc_type_decimal_scale(const chc_type *t) { return (t && chc__kind_is_decimal(t->kind)) ? t->decimal.scale : 0; }
1093
+ int chc_type_datetime64_scale(const chc_type *t) { return (t && (t->kind == CHC_DATETIME64 || t->kind == CHC_TIME64)) ? t->temporal.scale : 0; }
1094
+ size_t chc_type_qbit_dimension(const chc_type *t) { return (t && t->kind == CHC_QBIT) ? t->qbit.dimension : 0; }
1095
+ size_t chc_type_qbit_element_size(const chc_type *t) { return (t && t->kind == CHC_QBIT && t->n_children == 1) ? chc_type_elem_size(t->children[0]) * 8 : 0; }
1096
+ const char *chc_type_name(const chc_type *t, size_t *out_len) {
1097
+ if (out_len) *out_len = t ? t->name_len : 0;
1098
+ return t ? t->name : NULL;
1099
+ }
1100
+ const char *chc_type_timezone(const chc_type *t, size_t *out_len) {
1101
+ bool has = t && chc__kind_has_tz(t->kind);
1102
+ if (out_len) *out_len = has ? t->temporal.tz_len : 0;
1103
+ return has ? t->temporal.tz : NULL;
1104
+ }
1105
+ size_t chc_type_enum_count(const chc_type *t) { return (t && chc__kind_is_enum(t->kind)) ? t->enum_.n : 0; }
1106
+ void chc_type_enum_at(const chc_type *t, size_t i,
1107
+ const char **name, size_t *name_len,
1108
+ int64_t *value) {
1109
+ if (!t || !chc__kind_is_enum(t->kind) || i >= t->enum_.n) {
1110
+ if (name) *name = NULL;
1111
+ if (name_len) *name_len = 0;
1112
+ if (value) *value = 0;
1113
+ return;
1114
+ }
1115
+ if (name) *name = t->enum_.items[i].name;
1116
+ if (name_len) *name_len = t->enum_.items[i].name_len;
1117
+ if (value) *value = t->enum_.items[i].value;
1118
+ }
1119
+
1120
+ const char *
1121
+ chc_type_tuple_field_name(const chc_type *t, size_t i, size_t *out_len)
1122
+ {
1123
+ if (!t || t->kind != CHC_TUPLE || !t->field_names || i >= t->n_children) {
1124
+ if (out_len) *out_len = 0;
1125
+ return NULL;
1126
+ }
1127
+ if (out_len) *out_len = t->field_name_lens[i];
1128
+ return t->field_names[i];
1129
+ }
1130
+
1131
+ int
1132
+ chc_type_decimal_precision(const chc_type *t)
1133
+ {
1134
+ if (!t || !chc__kind_is_decimal(t->kind)) return 0;
1135
+ if (t->decimal.precision) return t->decimal.precision;
1136
+ switch (t->kind) {
1137
+ case CHC_DECIMAL32: return 9;
1138
+ case CHC_DECIMAL64: return 18;
1139
+ case CHC_DECIMAL128: return 38;
1140
+ case CHC_DECIMAL256: return 76;
1141
+ default: return 0;
1142
+ }
1143
+ }
1144
+
1145
+ /* -------- type parser ---------- */
1146
+
1147
+ /* Tokens & lexer mirror clickhouse-cpp/types/type_parser.cpp. The parser
1148
+ * is structurally identical (recursive on '(' / ')' / ','). */
1149
+ typedef enum {
1150
+ CHC__TOK_EOS = 0, CHC__TOK_NAME, CHC__TOK_NUMBER, CHC__TOK_STRING,
1151
+ CHC__TOK_LPAREN, CHC__TOK_RPAREN, CHC__TOK_COMMA, CHC__TOK_EQ,
1152
+ CHC__TOK_INVALID
1153
+ } chc__tok_kind;
1154
+
1155
+ typedef struct {
1156
+ chc__tok_kind kind;
1157
+ const char *start;
1158
+ size_t len;
1159
+ /* For CHC__TOK_NAME: 0 = bare identifier; '`' or '"' = quoted, & start/len
1160
+ * span the body between the outer quotes (still raw -- doubled-quote &
1161
+ * backslash escapes are resolved when copied out). */
1162
+ char quote;
1163
+ } chc__tok;
1164
+
1165
+ typedef struct {
1166
+ const char *cur, *end;
1167
+ chc__tok peeked;
1168
+ bool has_peek;
1169
+ } chc__lex;
1170
+
1171
+ static chc__tok
1172
+ chc__next_tok(chc__lex *lx)
1173
+ {
1174
+ while (lx->cur < lx->end) {
1175
+ char c = *lx->cur;
1176
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { lx->cur++; continue; }
1177
+ const char *st = lx->cur;
1178
+ if (c == '(') { lx->cur++; return (chc__tok){CHC__TOK_LPAREN, st, 1, 0}; }
1179
+ if (c == ')') { lx->cur++; return (chc__tok){CHC__TOK_RPAREN, st, 1, 0}; }
1180
+ if (c == ',') { lx->cur++; return (chc__tok){CHC__TOK_COMMA, st, 1, 0}; }
1181
+ if (c == '=') { lx->cur++; return (chc__tok){CHC__TOK_EQ, st, 1, 0}; }
1182
+ if (c == '\'') {
1183
+ /* single-quoted string; clickhouse-cpp does not escape, so we
1184
+ * accept anything up to the next unescaped quote. */
1185
+ lx->cur++;
1186
+ const char *body = lx->cur;
1187
+ while (lx->cur < lx->end && *lx->cur != '\'') lx->cur++;
1188
+ if (lx->cur >= lx->end) return (chc__tok){CHC__TOK_INVALID, st, 0, 0};
1189
+ size_t blen = (size_t) (lx->cur - body);
1190
+ lx->cur++; /* eat closing ' */
1191
+ return (chc__tok){CHC__TOK_STRING, body, blen, 0};
1192
+ }
1193
+ if (c == '`' || c == '"') {
1194
+ /* Quoted identifier, matching ClickHouse Lexer.cpp `quotedString`:
1195
+ * doubled quote (`` `` `` or `""`) & backslash-escapes are skipped
1196
+ * during scanning, resolved at copy time. */
1197
+ char q = c;
1198
+ lx->cur++;
1199
+ const char *body = lx->cur;
1200
+ while (lx->cur < lx->end) {
1201
+ char d = *lx->cur;
1202
+ if (d == '\\') {
1203
+ lx->cur++;
1204
+ if (lx->cur < lx->end) lx->cur++;
1205
+ continue;
1206
+ }
1207
+ if (d == q) {
1208
+ if (lx->cur + 1 < lx->end && lx->cur[1] == q) {
1209
+ lx->cur += 2;
1210
+ continue;
1211
+ }
1212
+ break;
1213
+ }
1214
+ lx->cur++;
1215
+ }
1216
+ if (lx->cur >= lx->end) return (chc__tok){CHC__TOK_INVALID, st, 0, 0};
1217
+ size_t blen = (size_t) (lx->cur - body);
1218
+ lx->cur++; /* eat closing quote */
1219
+ return (chc__tok){CHC__TOK_NAME, body, blen, q};
1220
+ }
1221
+ if (isalpha((unsigned char) c) || c == '_') {
1222
+ while (lx->cur < lx->end) {
1223
+ char d = *lx->cur;
1224
+ if (!(isalnum((unsigned char) d) || d == '_')) break;
1225
+ lx->cur++;
1226
+ }
1227
+ return (chc__tok){CHC__TOK_NAME, st, (size_t) (lx->cur - st), 0};
1228
+ }
1229
+ if (isdigit((unsigned char) c) || c == '-') {
1230
+ lx->cur++;
1231
+ while (lx->cur < lx->end && isdigit((unsigned char) *lx->cur)) lx->cur++;
1232
+ return (chc__tok){CHC__TOK_NUMBER, st, (size_t) (lx->cur - st), 0};
1233
+ }
1234
+ return (chc__tok){CHC__TOK_INVALID, st, 0, 0};
1235
+ }
1236
+ return (chc__tok){CHC__TOK_EOS, lx->end, 0, 0};
1237
+ }
1238
+
1239
+ static chc__tok
1240
+ chc__peek_tok(chc__lex *lx)
1241
+ {
1242
+ if (!lx->has_peek) {
1243
+ lx->peeked = chc__next_tok(lx);
1244
+ lx->has_peek = true;
1245
+ }
1246
+ return lx->peeked;
1247
+ }
1248
+
1249
+ static chc__tok
1250
+ chc__eat_tok(chc__lex *lx)
1251
+ {
1252
+ chc__tok t = chc__peek_tok(lx);
1253
+ lx->has_peek = false;
1254
+ return t;
1255
+ }
1256
+
1257
+ static bool
1258
+ chc__atoi64(const char *s, size_t n, int64_t *out)
1259
+ {
1260
+ bool neg = n && *s == '-';
1261
+ if (neg) { s++; n--; }
1262
+ if (!n) return false;
1263
+ uint64_t v = 0, lim = (uint64_t) INT64_MAX + neg;
1264
+ while (n--) {
1265
+ uint64_t d = (uint64_t) (*s++ - '0');
1266
+ if (v > (lim - d) / 10) return false;
1267
+ v = v * 10 + d;
1268
+ }
1269
+ *out = neg ? (v == lim ? INT64_MIN : -(int64_t) v) : (int64_t) v;
1270
+ return true;
1271
+ }
1272
+
1273
+ /* AUTO-GENERATED-NAME-TABLE-BEGIN -- regenerate via tools/regen_name_table.sh */
1274
+ #define CHC__NAME_TABLE_M 256u
1275
+ #define CHC__NAME_TABLE_SEED 720ull
1276
+ struct chc__name_row { const char *name; chc_kind kind; };
1277
+ static const struct chc__name_row chc__name_table[CHC__NAME_TABLE_M] = {
1278
+ [ 4] = {"Int32", CHC_INT32},
1279
+ [ 8] = {"Float32", CHC_FLOAT32},
1280
+ [ 13] = {"MultiPolygon", CHC_MULTI_POLYGON},
1281
+ [ 20] = {"DateTime", CHC_DATETIME},
1282
+ [ 21] = {"Dynamic", CHC_DYNAMIC},
1283
+ [ 30] = {"IntervalMinute", CHC_INTERVAL},
1284
+ [ 33] = {"Ring", CHC_RING},
1285
+ [ 36] = {"IntervalMicrosecond", CHC_INTERVAL},
1286
+ [ 37] = {"Decimal64", CHC_DECIMAL64},
1287
+ [ 40] = {"DateTime64", CHC_DATETIME64},
1288
+ [ 43] = {"Int128", CHC_INT128},
1289
+ [ 44] = {"Tuple", CHC_TUPLE},
1290
+ [ 48] = {"IntervalDay", CHC_INTERVAL},
1291
+ [ 49] = {"Map", CHC_MAP},
1292
+ [ 50] = {"IntervalSecond", CHC_INTERVAL},
1293
+ [ 52] = {"UInt8", CHC_UINT8},
1294
+ [ 55] = {"Enum16", CHC_ENUM16},
1295
+ [ 57] = {"IntervalMillisecond", CHC_INTERVAL},
1296
+ [ 60] = {"Int8", CHC_INT8},
1297
+ [ 65] = {"IntervalHour", CHC_INTERVAL},
1298
+ [ 68] = {"UInt256", CHC_UINT256},
1299
+ [ 73] = {"Date32", CHC_DATE32},
1300
+ [ 74] = {"BFloat16", CHC_BFLOAT16},
1301
+ [ 83] = {"Nullable", CHC_NULLABLE},
1302
+ [ 89] = {"IntervalMonth", CHC_INTERVAL},
1303
+ [101] = {"UInt128", CHC_UINT128},
1304
+ [106] = {"Enum8", CHC_ENUM8},
1305
+ [111] = {"Void", CHC_VOID},
1306
+ [115] = {"IPv4", CHC_IPV4},
1307
+ [120] = {"Variant", CHC_VARIANT},
1308
+ [121] = {"LowCardinality", CHC_LOW_CARDINALITY},
1309
+ [122] = {"Time64", CHC_TIME64},
1310
+ [123] = {"Decimal128", CHC_DECIMAL128},
1311
+ [130] = {"UInt64", CHC_UINT64},
1312
+ [132] = {"UInt32", CHC_UINT32},
1313
+ [133] = {"Int16", CHC_INT16},
1314
+ [134] = {"JSON", CHC_JSON},
1315
+ [135] = {"SimpleAggregateFunction", CHC_SIMPLE_AGGREGATE_FUNCTION},
1316
+ [136] = {"IntervalNanosecond", CHC_INTERVAL},
1317
+ [140] = {"QBit", CHC_QBIT},
1318
+ [150] = {"Nothing", CHC_NOTHING},
1319
+ [151] = {"Date", CHC_DATE},
1320
+ [157] = {"IPv6", CHC_IPV6},
1321
+ [168] = {"Array", CHC_ARRAY},
1322
+ [172] = {"Time", CHC_TIME},
1323
+ [177] = {"Object", CHC_OBJECT},
1324
+ [178] = {"Decimal32", CHC_DECIMAL32},
1325
+ [183] = {"Decimal256", CHC_DECIMAL256},
1326
+ [189] = {"UUID", CHC_UUID},
1327
+ [206] = {"Nested", CHC_NESTED},
1328
+ [211] = {"Polygon", CHC_POLYGON},
1329
+ [214] = {"String", CHC_STRING},
1330
+ [218] = {"AggregateFunction", CHC_AGGREGATE_FUNCTION},
1331
+ [219] = {"Int256", CHC_INT256},
1332
+ [223] = {"UInt16", CHC_UINT16},
1333
+ [224] = {"IntervalQuarter", CHC_INTERVAL},
1334
+ [232] = {"Bool", CHC_BOOL},
1335
+ [236] = {"FixedString", CHC_FIXED_STRING},
1336
+ [237] = {"Int64", CHC_INT64},
1337
+ [245] = {"IntervalYear", CHC_INTERVAL},
1338
+ [246] = {"Float64", CHC_FLOAT64},
1339
+ [253] = {"IntervalWeek", CHC_INTERVAL},
1340
+ [254] = {"Point", CHC_POINT},
1341
+ };
1342
+ /* AUTO-GENERATED-NAME-TABLE-END */
1343
+
1344
+ /* Plain "Decimal" is intentionally absent from the table; the parser's
1345
+ * decimal_alias branch resolves it from precision. Miss -> CHC_VOID, also
1346
+ * the sentinel for unknown names; caller disambiguates with an explicit
1347
+ * memcmp against "Void". */
1348
+ static chc_kind
1349
+ chc__name_to_kind(const char *s, size_t n) CHC_REPRODUCIBLE
1350
+ {
1351
+ if (n == 0 || n > 23) return CHC_VOID;
1352
+ size_t h_len = n < 16 ? n : 16;
1353
+ uint64_t h = chc__city_hash_len_16(
1354
+ chc__city_hash_len_0_to_16(s, h_len) + (uint64_t) n,
1355
+ CHC__NAME_TABLE_SEED);
1356
+ const struct chc__name_row *r = &chc__name_table[h & (CHC__NAME_TABLE_M - 1)];
1357
+ if (r->name && strlen(r->name) == n && memcmp(r->name, s, n) == 0)
1358
+ return r->kind;
1359
+ return CHC_VOID;
1360
+ }
1361
+
1362
+ static int chc__parse_type(chc__lex *lx, const chc_alloc *al,
1363
+ const char *whole_start, const char *whole_end,
1364
+ size_t depth, chc_type **out, chc_err *err);
1365
+
1366
+ /* Append a child pointer to parent's children array. */
1367
+ static int
1368
+ chc__type_push_child(const chc_alloc *al, chc_type *parent, chc_type *child,
1369
+ chc_err *err)
1370
+ {
1371
+ size_t n = parent->n_children;
1372
+ chc_type **arr = chc__realloc(al, parent->children,
1373
+ n * sizeof *arr, (n + 1) * sizeof *arr, err);
1374
+ if (!arr) return CHC_ERR_OOM;
1375
+ arr[n] = child;
1376
+ parent->children = arr;
1377
+ parent->n_children = n + 1;
1378
+ return CHC_OK;
1379
+ }
1380
+
1381
+ static int
1382
+ chc__type_push_enum(const chc_alloc *al, chc_type *parent,
1383
+ const char *name, size_t name_len, int64_t value,
1384
+ chc_err *err)
1385
+ {
1386
+ size_t n = parent->enum_.n;
1387
+ void *arr = chc__realloc(al, parent->enum_.items,
1388
+ n * sizeof *parent->enum_.items,
1389
+ (n + 1) * sizeof *parent->enum_.items, err);
1390
+ if (!arr) return CHC_ERR_OOM;
1391
+ parent->enum_.items = arr;
1392
+ parent->enum_.items[n].name = chc__strdup(al, name, name_len, err);
1393
+ if (!parent->enum_.items[n].name) return CHC_ERR_OOM;
1394
+ parent->enum_.items[n].name_len = name_len;
1395
+ parent->enum_.items[n].value = value;
1396
+ parent->enum_.n = n + 1;
1397
+ return CHC_OK;
1398
+ }
1399
+
1400
+ static int
1401
+ chc__parse_type(chc__lex *lx, const chc_alloc *al,
1402
+ const char *whole_start, const char *whole_end,
1403
+ size_t depth, chc_type **out, chc_err *err)
1404
+ {
1405
+ if (depth > CHC_MAX_TYPE_DEPTH)
1406
+ return chc__err_set(err, CHC_ERR_TYPE,
1407
+ "type nested too deeply (max %llu)",
1408
+ (unsigned long long) CHC_MAX_TYPE_DEPTH);
1409
+
1410
+ chc__tok head = chc__eat_tok(lx);
1411
+ if (head.kind != CHC__TOK_NAME || head.quote)
1412
+ return chc__err_set(err, CHC_ERR_TYPE, "expected type name");
1413
+
1414
+ chc_type *t = chc__calloc(al, sizeof *t, err);
1415
+ if (!t) return CHC_ERR_OOM;
1416
+ bool decimal_alias = (head.len == 7 && memcmp(head.start, "Decimal", 7) == 0);
1417
+ if (decimal_alias) {
1418
+ t->kind = CHC_DECIMAL128; /* placeholder; refined from precision */
1419
+ } else {
1420
+ t->kind = chc__name_to_kind(head.start, head.len);
1421
+ if (t->kind == CHC_VOID && !(head.len == 4 && memcmp(head.start, "Void", 4) == 0)) {
1422
+ chc_type_destroy(t, al);
1423
+ return chc__err_set(err, CHC_ERR_TYPE, "unknown type: %.*s",
1424
+ (int) head.len, head.start);
1425
+ }
1426
+ }
1427
+
1428
+ const char *name_start = head.start;
1429
+ const char *name_end = head.start + head.len;
1430
+
1431
+ /* Optional parameter list. */
1432
+ if (chc__peek_tok(lx).kind == CHC__TOK_LPAREN) {
1433
+ chc__eat_tok(lx);
1434
+
1435
+ if (t->kind == CHC_ENUM8 || t->kind == CHC_ENUM16) {
1436
+ /* 'name' = value, 'name' = value, ... */
1437
+ while (chc__peek_tok(lx).kind != CHC__TOK_RPAREN) {
1438
+ chc__tok s = chc__eat_tok(lx);
1439
+ if (s.kind != CHC__TOK_STRING) {
1440
+ chc_type_destroy(t, al);
1441
+ return chc__err_set(err, CHC_ERR_TYPE, "Enum: expected quoted name");
1442
+ }
1443
+ chc__tok eq = chc__eat_tok(lx);
1444
+ if (eq.kind != CHC__TOK_EQ) {
1445
+ chc_type_destroy(t, al);
1446
+ return chc__err_set(err, CHC_ERR_TYPE, "Enum: expected '='");
1447
+ }
1448
+ chc__tok num = chc__eat_tok(lx);
1449
+ if (num.kind != CHC__TOK_NUMBER) {
1450
+ chc_type_destroy(t, al);
1451
+ return chc__err_set(err, CHC_ERR_TYPE, "Enum: expected value");
1452
+ }
1453
+ int64_t ev;
1454
+ int64_t lo = t->kind == CHC_ENUM8 ? INT8_MIN : INT16_MIN;
1455
+ int64_t hi = t->kind == CHC_ENUM8 ? INT8_MAX : INT16_MAX;
1456
+ if (!chc__atoi64(num.start, num.len, &ev) || ev < lo || ev > hi) {
1457
+ chc_type_destroy(t, al);
1458
+ return chc__err_set(err, CHC_ERR_TYPE,
1459
+ "Enum: value out of range: %.*s",
1460
+ (int) num.len, num.start);
1461
+ }
1462
+ int rc = chc__type_push_enum(al, t, s.start, s.len, ev, err);
1463
+ if (rc != CHC_OK) { chc_type_destroy(t, al); return rc; }
1464
+ if (chc__peek_tok(lx).kind == CHC__TOK_COMMA) chc__eat_tok(lx);
1465
+ }
1466
+ } else if (t->kind == CHC_FIXED_STRING) {
1467
+ chc__tok num = chc__eat_tok(lx);
1468
+ if (num.kind != CHC__TOK_NUMBER) {
1469
+ chc_type_destroy(t, al);
1470
+ return chc__err_set(err, CHC_ERR_TYPE, "FixedString: expected N");
1471
+ }
1472
+ int64_t n;
1473
+ if (!chc__atoi64(num.start, num.len, &n)
1474
+ || n <= 0 || (uint64_t) n > CHC_MAX_FIXEDSTRING_SIZE) {
1475
+ chc_type_destroy(t, al);
1476
+ return chc__err_set(err, CHC_ERR_TYPE,
1477
+ "FixedString: N out of range: %.*s",
1478
+ (int) num.len, num.start);
1479
+ }
1480
+ t->fixed_string.n = (int) n;
1481
+ } else if (decimal_alias) {
1482
+ chc__tok np = chc__eat_tok(lx);
1483
+ if (np.kind != CHC__TOK_NUMBER) {
1484
+ chc_type_destroy(t, al);
1485
+ return chc__err_set(err, CHC_ERR_TYPE, "Decimal: expected precision");
1486
+ }
1487
+ chc__tok cm = chc__eat_tok(lx);
1488
+ if (cm.kind != CHC__TOK_COMMA) {
1489
+ chc_type_destroy(t, al);
1490
+ return chc__err_set(err, CHC_ERR_TYPE, "Decimal: expected ','");
1491
+ }
1492
+ chc__tok ns = chc__eat_tok(lx);
1493
+ if (ns.kind != CHC__TOK_NUMBER) {
1494
+ chc_type_destroy(t, al);
1495
+ return chc__err_set(err, CHC_ERR_TYPE, "Decimal: expected scale");
1496
+ }
1497
+ int64_t prec, scale;
1498
+ if (!chc__atoi64(np.start, np.len, &prec)
1499
+ || !chc__atoi64(ns.start, ns.len, &scale)
1500
+ || prec < 1 || prec > 76 || scale < 0 || scale > prec) {
1501
+ chc_type_destroy(t, al);
1502
+ return chc__err_set(err, CHC_ERR_TYPE,
1503
+ "Decimal: precision or scale out of range: %.*s, %.*s",
1504
+ (int) np.len, np.start, (int) ns.len, ns.start);
1505
+ }
1506
+ t->decimal.precision = (int) prec;
1507
+ t->decimal.scale = (int) scale;
1508
+ if (prec <= 9) t->kind = CHC_DECIMAL32;
1509
+ else if (prec <= 18) t->kind = CHC_DECIMAL64;
1510
+ else if (prec <= 38) t->kind = CHC_DECIMAL128;
1511
+ else t->kind = CHC_DECIMAL256;
1512
+ } else if (t->kind == CHC_DECIMAL32 || t->kind == CHC_DECIMAL64
1513
+ || t->kind == CHC_DECIMAL128 || t->kind == CHC_DECIMAL256) {
1514
+ chc__tok num = chc__eat_tok(lx);
1515
+ if (num.kind != CHC__TOK_NUMBER) {
1516
+ chc_type_destroy(t, al);
1517
+ return chc__err_set(err, CHC_ERR_TYPE, "Decimal: expected scale");
1518
+ }
1519
+ int64_t scale;
1520
+ if (!chc__atoi64(num.start, num.len, &scale)
1521
+ || scale < 0 || scale > chc_type_decimal_precision(t)) {
1522
+ chc_type_destroy(t, al);
1523
+ return chc__err_set(err, CHC_ERR_TYPE,
1524
+ "Decimal: scale out of range: %.*s",
1525
+ (int) num.len, num.start);
1526
+ }
1527
+ t->decimal.scale = (int) scale;
1528
+ } else if (t->kind == CHC_DATETIME64 || t->kind == CHC_TIME64) {
1529
+ chc__tok num = chc__eat_tok(lx);
1530
+ if (num.kind != CHC__TOK_NUMBER) {
1531
+ chc_type_destroy(t, al);
1532
+ return chc__err_set(err, CHC_ERR_TYPE, "DateTime64: expected precision");
1533
+ }
1534
+ int64_t scale;
1535
+ if (!chc__atoi64(num.start, num.len, &scale)
1536
+ || scale < 0 || scale > 9) {
1537
+ chc_type_destroy(t, al);
1538
+ return chc__err_set(err, CHC_ERR_TYPE,
1539
+ "DateTime64: precision out of range: %.*s",
1540
+ (int) num.len, num.start);
1541
+ }
1542
+ t->temporal.scale = (int) scale;
1543
+ if (chc__peek_tok(lx).kind == CHC__TOK_COMMA) {
1544
+ chc__eat_tok(lx);
1545
+ chc__tok s = chc__eat_tok(lx);
1546
+ if (s.kind != CHC__TOK_STRING) {
1547
+ chc_type_destroy(t, al);
1548
+ return chc__err_set(err, CHC_ERR_TYPE, "DateTime64: expected tz");
1549
+ }
1550
+ t->temporal.tz = chc__strdup(al, s.start, s.len, err);
1551
+ if (!t->temporal.tz) { chc_type_destroy(t, al); return CHC_ERR_OOM; }
1552
+ t->temporal.tz_len = s.len;
1553
+ }
1554
+ } else if (t->kind == CHC_DATETIME) {
1555
+ chc__tok s = chc__eat_tok(lx);
1556
+ if (s.kind != CHC__TOK_STRING) {
1557
+ chc_type_destroy(t, al);
1558
+ return chc__err_set(err, CHC_ERR_TYPE, "DateTime: expected tz");
1559
+ }
1560
+ t->temporal.tz = chc__strdup(al, s.start, s.len, err);
1561
+ if (!t->temporal.tz) { chc_type_destroy(t, al); return CHC_ERR_OOM; }
1562
+ t->temporal.tz_len = s.len;
1563
+ } else if (t->kind == CHC_OBJECT) {
1564
+ /* Object('name') -- legacy JSON object syntax. Argument is a
1565
+ * schema identifier (eg 'json'); clickhouse-cpp accepts any
1566
+ * quoted string. Wire format matches CHC_JSON, so we discard
1567
+ * the argument and keep the full source text in t->name for
1568
+ * round-trip & error messages. */
1569
+ chc__tok s = chc__eat_tok(lx);
1570
+ if (s.kind != CHC__TOK_STRING) {
1571
+ chc_type_destroy(t, al);
1572
+ return chc__err_set(err, CHC_ERR_TYPE, "Object: expected name");
1573
+ }
1574
+ } else if (t->kind == CHC_QBIT) {
1575
+ /* QBit(ElementType, N): element float type then positive dimension.
1576
+ * Wire form is Tuple(FixedString(ceil(N/8)) x element_size); keep
1577
+ * the element type as the sole child & stash N for the decoder. */
1578
+ chc_type *elem = NULL;
1579
+ int rc = chc__parse_type(lx, al, whole_start, whole_end, depth + 1, &elem, err);
1580
+ if (rc != CHC_OK) { chc_type_destroy(t, al); return rc; }
1581
+ rc = chc__type_push_child(al, t, elem, err);
1582
+ if (rc != CHC_OK) { chc_type_destroy(elem, al); chc_type_destroy(t, al); return rc; }
1583
+ if (elem->kind != CHC_BFLOAT16 && elem->kind != CHC_FLOAT32 && elem->kind != CHC_FLOAT64) {
1584
+ chc_type_destroy(t, al);
1585
+ return chc__err_set(err, CHC_ERR_TYPE,
1586
+ "QBit: element type must be BFloat16, Float32, or Float64");
1587
+ }
1588
+ chc__tok cm = chc__eat_tok(lx);
1589
+ if (cm.kind != CHC__TOK_COMMA) {
1590
+ chc_type_destroy(t, al);
1591
+ return chc__err_set(err, CHC_ERR_TYPE, "QBit: expected ','");
1592
+ }
1593
+ chc__tok num = chc__eat_tok(lx);
1594
+ if (num.kind != CHC__TOK_NUMBER) {
1595
+ chc_type_destroy(t, al);
1596
+ return chc__err_set(err, CHC_ERR_TYPE, "QBit: expected dimension");
1597
+ }
1598
+ int64_t n;
1599
+ /* Nested FixedString width is ceil(N/8); bound it as FixedString is. */
1600
+ if (!chc__atoi64(num.start, num.len, &n)
1601
+ || n <= 0 || ((uint64_t) n + 7) / 8 > CHC_MAX_FIXEDSTRING_SIZE) {
1602
+ chc_type_destroy(t, al);
1603
+ return chc__err_set(err, CHC_ERR_TYPE,
1604
+ "QBit: dimension out of range: %.*s",
1605
+ (int) num.len, num.start);
1606
+ }
1607
+ t->qbit.dimension = (size_t) n;
1608
+ } else {
1609
+ /* Generic composite: comma-separated type list. Tuple children
1610
+ * may carry an optional leading NAME (field label) before the
1611
+ * type. Field names are stored in a parallel array on the
1612
+ * parent. */
1613
+ bool is_tuple = (t->kind == CHC_TUPLE);
1614
+ char **fn_buf = NULL;
1615
+ size_t *fn_lens = NULL;
1616
+ size_t fn_cap = 0;
1617
+ bool any_named = false;
1618
+ for (;;) {
1619
+ chc__tok la = chc__peek_tok(lx);
1620
+ if (la.kind == CHC__TOK_RPAREN) break;
1621
+
1622
+ chc__tok field = {};
1623
+ bool has_field = false;
1624
+ if (is_tuple && la.kind == CHC__TOK_NAME) {
1625
+ chc__eat_tok(lx);
1626
+ if (la.quote) {
1627
+ /* `\`x\`` or `"x"` is never a type head, so it must be
1628
+ * the field label. */
1629
+ field = la;
1630
+ has_field = true;
1631
+ } else {
1632
+ chc__tok la2 = chc__peek_tok(lx);
1633
+ if (la2.kind == CHC__TOK_NAME) {
1634
+ /* `la` is a field-name; `la2` starts the type. */
1635
+ field = la;
1636
+ has_field = true;
1637
+ } else {
1638
+ /* `la` was the type's leading NAME (terminal or
1639
+ * parametric like `Tuple(LowCardinality(...))`).
1640
+ * Put it back & rewind cur to la2's start so the
1641
+ * next peek re-lexes la2. */
1642
+ lx->peeked = la;
1643
+ lx->has_peek = true;
1644
+ lx->cur = la2.start;
1645
+ }
1646
+ }
1647
+ }
1648
+
1649
+ chc_type *child = NULL;
1650
+ int rc = chc__parse_type(lx, al, whole_start, whole_end, depth + 1, &child, err);
1651
+ if (rc == CHC_OK)
1652
+ rc = chc__type_push_child(al, t, child, err);
1653
+ else
1654
+ child = NULL;
1655
+ if (rc != CHC_OK) {
1656
+ if (child) chc_type_destroy(child, al);
1657
+ if (fn_buf) {
1658
+ for (size_t i = 0; i < fn_cap; i++)
1659
+ al->free(al->ud, fn_buf[i], fn_lens[i] + 1);
1660
+ al->free(al->ud, fn_buf, fn_cap * sizeof *fn_buf);
1661
+ al->free(al->ud, fn_lens, fn_cap * sizeof *fn_lens);
1662
+ }
1663
+ chc_type_destroy(t, al);
1664
+ return rc;
1665
+ }
1666
+
1667
+ if (is_tuple) {
1668
+ size_t new_cap = t->n_children;
1669
+ char **nfn = chc__realloc(al, fn_buf,
1670
+ fn_cap * sizeof *fn_buf,
1671
+ new_cap * sizeof *fn_buf, err);
1672
+ if (!nfn) { chc_type_destroy(t, al); return CHC_ERR_OOM; }
1673
+ size_t *nfl = chc__realloc(al, fn_lens,
1674
+ fn_cap * sizeof *fn_lens,
1675
+ new_cap * sizeof *fn_lens, err);
1676
+ if (!nfl) {
1677
+ al->free(al->ud, nfn, new_cap * sizeof *nfn);
1678
+ chc_type_destroy(t, al); return CHC_ERR_OOM;
1679
+ }
1680
+ fn_buf = nfn;
1681
+ fn_lens = nfl;
1682
+ fn_buf[fn_cap] = NULL;
1683
+ fn_lens[fn_cap] = 0;
1684
+ fn_cap = new_cap;
1685
+ if (has_field) {
1686
+ size_t flen = field.len;
1687
+ if (field.quote)
1688
+ fn_buf[fn_cap - 1] = chc__strdup_unquote(al, field.start,
1689
+ field.len, field.quote,
1690
+ &flen, err);
1691
+ else
1692
+ fn_buf[fn_cap - 1] = chc__strdup(al, field.start,
1693
+ field.len, err);
1694
+ if (!fn_buf[fn_cap - 1]) {
1695
+ for (size_t i = 0; i < fn_cap - 1; i++)
1696
+ al->free(al->ud, fn_buf[i], fn_lens[i] + 1);
1697
+ al->free(al->ud, fn_buf, fn_cap * sizeof *fn_buf);
1698
+ al->free(al->ud, fn_lens, fn_cap * sizeof *fn_lens);
1699
+ chc_type_destroy(t, al); return CHC_ERR_OOM;
1700
+ }
1701
+ fn_lens[fn_cap - 1] = flen;
1702
+ any_named = true;
1703
+ }
1704
+ }
1705
+
1706
+ chc__tok c = chc__peek_tok(lx);
1707
+ if (c.kind == CHC__TOK_COMMA) { chc__eat_tok(lx); continue; }
1708
+ if (c.kind == CHC__TOK_RPAREN) break;
1709
+ if (fn_buf) {
1710
+ for (size_t i = 0; i < fn_cap; i++)
1711
+ al->free(al->ud, fn_buf[i], fn_lens[i] + 1);
1712
+ al->free(al->ud, fn_buf, fn_cap * sizeof *fn_buf);
1713
+ al->free(al->ud, fn_lens, fn_cap * sizeof *fn_lens);
1714
+ }
1715
+ chc_type_destroy(t, al);
1716
+ return chc__err_set(err, CHC_ERR_TYPE, "expected ',' or ')'");
1717
+ }
1718
+ if (any_named) {
1719
+ t->field_names = fn_buf;
1720
+ t->field_name_lens = fn_lens;
1721
+ } else {
1722
+ al->free(al->ud, fn_buf, fn_cap * sizeof *fn_buf);
1723
+ al->free(al->ud, fn_lens, fn_cap * sizeof *fn_lens);
1724
+ }
1725
+ }
1726
+
1727
+ chc__tok rp = chc__eat_tok(lx);
1728
+ if (rp.kind != CHC__TOK_RPAREN) {
1729
+ chc_type_destroy(t, al);
1730
+ return chc__err_set(err, CHC_ERR_TYPE, "expected ')'");
1731
+ }
1732
+ name_end = rp.start + 1;
1733
+ }
1734
+
1735
+ /* Decimal(P, S) compatibility: width selected by precision. */
1736
+ if (t->kind == CHC_DECIMAL128 && head.len == 7
1737
+ && memcmp(head.start, "Decimal", 7) == 0 && t->n_children == 0) {
1738
+ /* unparenthesised "Decimal" without (P, S) — treat as Decimal128 */
1739
+ }
1740
+
1741
+ t->name = chc__strdup(al, name_start, (size_t) (name_end - name_start), err);
1742
+ if (!t->name) { chc_type_destroy(t, al); return CHC_ERR_OOM; }
1743
+ t->name_len = (size_t) (name_end - name_start);
1744
+
1745
+ *out = t;
1746
+ return CHC_OK;
1747
+ }
1748
+
1749
+ int
1750
+ chc_type_parse(const char *name, size_t name_len,
1751
+ const chc_alloc *al, chc_type **out, chc_err *err)
1752
+ {
1753
+ chc__lex lx = { name, name + name_len, {}, false };
1754
+ int rc = chc__parse_type(&lx, al, name, name + name_len, 0, out, err);
1755
+ if (rc != CHC_OK) return rc;
1756
+ chc__tok tail = chc__eat_tok(&lx);
1757
+ if (tail.kind != CHC__TOK_EOS) {
1758
+ chc_type_destroy(*out, al);
1759
+ *out = NULL;
1760
+ return chc__err_set(err, CHC_ERR_TYPE, "trailing tokens in type name");
1761
+ }
1762
+ return CHC_OK;
1763
+ }
1764
+
1765
+ size_t
1766
+ chc_type_format(const chc_type *t, char *buf, size_t buf_len)
1767
+ {
1768
+ if (!t) return 0;
1769
+ if (t->name && t->name_len) {
1770
+ if (buf && buf_len) {
1771
+ size_t take = t->name_len < buf_len - 1 ? t->name_len : buf_len - 1;
1772
+ memcpy(buf, t->name, take);
1773
+ buf[take] = '\0';
1774
+ }
1775
+ return t->name_len;
1776
+ }
1777
+ return 0;
1778
+ }
1779
+
1780
+ /* -------- column internals ---------- */
1781
+
1782
+ struct chc_column {
1783
+ chc_col_kind layout;
1784
+ size_t n_rows;
1785
+ union {
1786
+ struct { void *data; size_t elem_size; } fixed;
1787
+ struct { uint8_t *data; uint64_t *offsets; size_t bytes; } str;
1788
+ struct { uint8_t *null_map; chc_column *inner; } nullable;
1789
+ struct { uint64_t *offsets; chc_column *values; } array;
1790
+ struct { chc_column **children; size_t arity; } tuple;
1791
+ struct { int key_size; void *keys; chc_column *dict; size_t dict_n; } lc;
1792
+ };
1793
+ };
1794
+
1795
+ chc_col_kind chc_column_layout(const chc_column *c) { return c ? c->layout : (chc_col_kind) 0; }
1796
+ size_t chc_column_n_rows(const chc_column *c) { return c ? c->n_rows : 0; }
1797
+
1798
+ const void *chc_column_fixed_data(const chc_column *c, size_t *elem_size)
1799
+ {
1800
+ if (!c || c->layout != CHC_COL_FIXED) { if (elem_size) *elem_size = 0; return NULL; }
1801
+ if (elem_size) *elem_size = c->fixed.elem_size;
1802
+ return c->fixed.data;
1803
+ }
1804
+ const uint8_t *chc_column_string_data(const chc_column *c)
1805
+ { return (c && c->layout == CHC_COL_STRING) ? c->str.data : NULL; }
1806
+ const uint64_t *chc_column_string_offsets(const chc_column *c)
1807
+ { return (c && c->layout == CHC_COL_STRING) ? c->str.offsets : NULL; }
1808
+ const uint8_t *chc_column_null_map(const chc_column *c)
1809
+ { return (c && c->layout == CHC_COL_NULLABLE) ? c->nullable.null_map : NULL; }
1810
+ const chc_column *chc_column_nullable_inner(const chc_column *c)
1811
+ { return (c && c->layout == CHC_COL_NULLABLE) ? c->nullable.inner : NULL; }
1812
+ const uint64_t *chc_column_array_offsets(const chc_column *c)
1813
+ { return (c && c->layout == CHC_COL_ARRAY) ? c->array.offsets : NULL; }
1814
+ const chc_column *chc_column_array_values(const chc_column *c)
1815
+ { return (c && c->layout == CHC_COL_ARRAY) ? c->array.values : NULL; }
1816
+ size_t chc_column_tuple_arity(const chc_column *c)
1817
+ { return (c && c->layout == CHC_COL_TUPLE) ? c->tuple.arity : 0; }
1818
+ const chc_column *chc_column_tuple_child(const chc_column *c, size_t i)
1819
+ { return (c && c->layout == CHC_COL_TUPLE && i < c->tuple.arity) ? c->tuple.children[i] : NULL; }
1820
+ int chc_column_lc_key_size(const chc_column *c)
1821
+ { return (c && c->layout == CHC_COL_LOW_CARDINALITY) ? c->lc.key_size : 0; }
1822
+ const void *chc_column_lc_keys(const chc_column *c)
1823
+ { return (c && c->layout == CHC_COL_LOW_CARDINALITY) ? c->lc.keys : NULL; }
1824
+ const chc_column *chc_column_lc_dict(const chc_column *c)
1825
+ { return (c && c->layout == CHC_COL_LOW_CARDINALITY) ? c->lc.dict : NULL; }
1826
+
1827
+ static void chc__column_destroy(chc_column *c, const chc_alloc *al);
1828
+
1829
+ static void
1830
+ chc__column_destroy(chc_column *c, const chc_alloc *al)
1831
+ {
1832
+ if (!c) return;
1833
+ switch (c->layout) {
1834
+ case CHC_COL_FIXED:
1835
+ al->free(al->ud, c->fixed.data, c->n_rows * c->fixed.elem_size);
1836
+ break;
1837
+ case CHC_COL_STRING:
1838
+ al->free(al->ud, c->str.data, c->str.bytes);
1839
+ al->free(al->ud, c->str.offsets, c->n_rows * sizeof(uint64_t));
1840
+ break;
1841
+ case CHC_COL_NULLABLE:
1842
+ al->free(al->ud, c->nullable.null_map, c->n_rows);
1843
+ chc__column_destroy(c->nullable.inner, al);
1844
+ break;
1845
+ case CHC_COL_ARRAY:
1846
+ al->free(al->ud, c->array.offsets, c->n_rows * sizeof(uint64_t));
1847
+ chc__column_destroy(c->array.values, al);
1848
+ break;
1849
+ case CHC_COL_TUPLE:
1850
+ for (size_t i = 0; i < c->tuple.arity; i++)
1851
+ chc__column_destroy(c->tuple.children[i], al);
1852
+ al->free(al->ud, c->tuple.children, c->tuple.arity * sizeof *c->tuple.children);
1853
+ break;
1854
+ case CHC_COL_LOW_CARDINALITY:
1855
+ al->free(al->ud, c->lc.keys, c->n_rows * c->lc.key_size);
1856
+ chc__column_destroy(c->lc.dict, al);
1857
+ break;
1858
+ case CHC_COL_NOTHING:
1859
+ break;
1860
+ }
1861
+ al->free(al->ud, c, sizeof *c);
1862
+ }
1863
+
1864
+ int
1865
+ chc_column_validate(const chc_column *c, chc_err *err)
1866
+ {
1867
+ if (!c) return CHC_OK;
1868
+ switch (c->layout) {
1869
+ case CHC_COL_ARRAY: {
1870
+ const uint64_t *offs = c->array.offsets;
1871
+ uint64_t prev = 0;
1872
+ for (size_t i = 0; i < c->n_rows; i++) {
1873
+ if (offs[i] < prev)
1874
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
1875
+ "array offsets not monotonic at row %zu: %llu < %llu",
1876
+ i, (unsigned long long) offs[i], (unsigned long long) prev);
1877
+ prev = offs[i];
1878
+ }
1879
+ return chc_column_validate(c->array.values, err);
1880
+ }
1881
+ case CHC_COL_LOW_CARDINALITY: {
1882
+ size_t dn = c->lc.dict_n;
1883
+ const void *k = c->lc.keys;
1884
+ for (size_t i = 0; i < c->n_rows; i++) {
1885
+ uint64_t v;
1886
+ switch (c->lc.key_size) {
1887
+ case 1: v = ((const uint8_t *) k)[i]; break;
1888
+ case 2: v = ((const uint16_t *) k)[i]; break;
1889
+ case 4: v = ((const uint32_t *) k)[i]; break;
1890
+ case 8: v = ((const uint64_t *) k)[i]; break;
1891
+ default:
1892
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
1893
+ "LowCardinality: invalid key_size %d", c->lc.key_size);
1894
+ }
1895
+ if (v >= dn)
1896
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
1897
+ "LowCardinality key out of range at row %zu: %llu >= dict_n %zu",
1898
+ i, (unsigned long long) v, dn);
1899
+ }
1900
+ return chc_column_validate(c->lc.dict, err);
1901
+ }
1902
+ case CHC_COL_NULLABLE:
1903
+ return chc_column_validate(c->nullable.inner, err);
1904
+ case CHC_COL_TUPLE:
1905
+ for (size_t i = 0; i < c->tuple.arity; i++) {
1906
+ int rc = chc_column_validate(c->tuple.children[i], err);
1907
+ if (rc != CHC_OK) return rc;
1908
+ }
1909
+ return CHC_OK;
1910
+ default:
1911
+ return CHC_OK;
1912
+ }
1913
+ }
1914
+
1915
+ /* -------- column reader (recursive on type kind) ---------- */
1916
+
1917
+ /* Elem-size table for FIXED kinds. Returns 0 if `t` isn't a FIXED kind. */
1918
+ size_t
1919
+ chc_type_elem_size(const chc_type *t)
1920
+ {
1921
+ switch (t->kind) {
1922
+ case CHC_INT8: case CHC_UINT8: case CHC_BOOL: return 1;
1923
+ case CHC_INT16: case CHC_UINT16: case CHC_DATE:
1924
+ case CHC_ENUM16: case CHC_BFLOAT16: return 2;
1925
+ case CHC_INT32: case CHC_UINT32: case CHC_DATE32:
1926
+ case CHC_DATETIME: case CHC_FLOAT32: case CHC_DECIMAL32:
1927
+ case CHC_TIME: case CHC_IPV4: return 4;
1928
+ case CHC_INT64: case CHC_UINT64: case CHC_DATETIME64:
1929
+ case CHC_FLOAT64: case CHC_DECIMAL64: case CHC_TIME64:
1930
+ case CHC_INTERVAL: return 8;
1931
+ case CHC_INT128: case CHC_UINT128: case CHC_DECIMAL128:
1932
+ case CHC_UUID: case CHC_IPV6: return 16;
1933
+ case CHC_INT256: case CHC_UINT256: case CHC_DECIMAL256: return 32;
1934
+ case CHC_ENUM8: return 1;
1935
+ case CHC_FIXED_STRING: return (size_t) t->fixed_string.n;
1936
+ default: return 0;
1937
+ }
1938
+ }
1939
+
1940
+ /* LowCardinality on-wire flag word constants. */
1941
+ #define CHC__LC_INDEX_TYPE_MASK 0xffu
1942
+ #define CHC__LC_NEED_GLOBAL_DICT (1u << 8)
1943
+ #define CHC__LC_HAS_ADDITIONAL_KEYS (1u << 9)
1944
+ #define CHC__LC_NEED_UPDATE_DICT (1u << 10)
1945
+
1946
+ static int chc__col_read(chc_in *in, const chc_type *t,
1947
+ size_t n_rows, chc_column **out, chc_err *err);
1948
+
1949
+ static int
1950
+ chc__col_read_fixed(chc_in *in, size_t elem_size, size_t n_rows,
1951
+ chc_column **out, chc_err *err)
1952
+ {
1953
+ const chc_alloc *al = in->al;
1954
+ chc_column *c = chc__calloc(al, sizeof *c, err);
1955
+ if (!c) return CHC_ERR_OOM;
1956
+ c->layout = CHC_COL_FIXED;
1957
+ c->n_rows = n_rows;
1958
+ c->fixed.elem_size = elem_size;
1959
+ if (n_rows && elem_size) {
1960
+ size_t nbytes;
1961
+ if (chc__mul_size(n_rows, elem_size, &nbytes)) {
1962
+ chc__column_destroy(c, al);
1963
+ return chc__err_set(err, CHC_ERR_PROTOCOL, "column size overflow");
1964
+ }
1965
+ c->fixed.data = chc__alloc(al, nbytes, err);
1966
+ if (!c->fixed.data) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
1967
+ int rc = chc__read_bytes(in, c->fixed.data, nbytes, err);
1968
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
1969
+ }
1970
+ *out = c;
1971
+ return CHC_OK;
1972
+ }
1973
+
1974
+ static int
1975
+ chc__col_read_string(chc_in *in, size_t n_rows,
1976
+ chc_column **out, chc_err *err)
1977
+ {
1978
+ const chc_alloc *al = in->al;
1979
+ chc_column *c = chc__calloc(al, sizeof *c, err);
1980
+ if (!c) return CHC_ERR_OOM;
1981
+ c->layout = CHC_COL_STRING;
1982
+ c->n_rows = n_rows;
1983
+ if (!n_rows) { *out = c; return CHC_OK; }
1984
+ size_t offs_bytes;
1985
+ if (chc__mul_size(n_rows, sizeof(uint64_t), &offs_bytes)) {
1986
+ chc__column_destroy(c, al);
1987
+ return chc__err_set(err, CHC_ERR_PROTOCOL, "string column size overflow");
1988
+ }
1989
+ c->str.offsets = chc__alloc(al, offs_bytes, err);
1990
+ if (!c->str.offsets) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
1991
+ size_t cap = 256;
1992
+ size_t total = 0;
1993
+ c->str.data = chc__alloc(al, cap, err);
1994
+ if (!c->str.data) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
1995
+ c->str.bytes = cap;
1996
+ for (size_t i = 0; i < n_rows; i++) {
1997
+ uint64_t len;
1998
+ int rc = chc__read_varuint(in, &len, err);
1999
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2000
+ if (len > CHC_MAX_STRING_SIZE) { chc__column_destroy(c, al);
2001
+ return chc__err_set(err, CHC_ERR_PROTOCOL, "string row too long"); }
2002
+ if (total + len > cap) {
2003
+ size_t new_cap = cap;
2004
+ while (new_cap < total + len) new_cap *= 2;
2005
+ uint8_t *r = chc__realloc(al, c->str.data, cap, new_cap, err);
2006
+ if (!r) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2007
+ c->str.data = r;
2008
+ cap = new_cap;
2009
+ c->str.bytes = cap;
2010
+ }
2011
+ if (len) {
2012
+ rc = chc__read_bytes(in, c->str.data + total, (size_t) len, err);
2013
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2014
+ }
2015
+ total += len;
2016
+ c->str.offsets[i] = total;
2017
+ }
2018
+ *out = c;
2019
+ return CHC_OK;
2020
+ }
2021
+
2022
+ /* Composite columns might have a prefix sub-stream. Only LowCardinality
2023
+ * actually emits one in the formats we handle: a uint64 key version. */
2024
+ static int
2025
+ chc__col_read_prefix(chc_in *in, const chc_type *t, chc_err *err)
2026
+ {
2027
+ if (t->kind == CHC_LOW_CARDINALITY) {
2028
+ uint64_t v;
2029
+ int rc = chc__read_u64_le(in, &v, err);
2030
+ if (rc != CHC_OK) return rc;
2031
+ if (v != 1)
2032
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
2033
+ "LowCardinality: unexpected key version %llu", (unsigned long long) v);
2034
+ return CHC_OK;
2035
+ }
2036
+ if (t->kind == CHC_NULLABLE || t->kind == CHC_ARRAY
2037
+ || t->kind == CHC_TUPLE || t->kind == CHC_MAP
2038
+ || t->kind == CHC_SIMPLE_AGGREGATE_FUNCTION) {
2039
+ for (size_t i = 0; i < t->n_children; i++) {
2040
+ int rc = chc__col_read_prefix(in, t->children[i], err);
2041
+ if (rc != CHC_OK) return rc;
2042
+ }
2043
+ }
2044
+ return CHC_OK;
2045
+ }
2046
+
2047
+ /* Geo types are aliases for nested Array(...(Tuple(Float64,Float64))). depth
2048
+ * 0 = Point, 1 = Ring (Array(Point)), 2 = Polygon (Array(Ring)),
2049
+ * 3 = MultiPolygon (Array(Polygon)). Defined ahead of chc__col_read so it
2050
+ * can call back into here. */
2051
+ static int chc__col_read_geo(chc_in *in, int depth, size_t n_rows,
2052
+ chc_column **out, chc_err *err);
2053
+
2054
+ /* Byte-swap a host-typed uint64/keys array in place on BE hosts. No-op on LE. */
2055
+ static void
2056
+ chc__swap_offsets(CHC_MAYBE_UNUSED uint64_t *p, CHC_MAYBE_UNUSED size_t n)
2057
+ {
2058
+ #if CHC_BIG_ENDIAN
2059
+ for (size_t i = 0; i < n; i++) p[i] = chc__bswap64(p[i]);
2060
+ #endif
2061
+ }
2062
+
2063
+ static void
2064
+ chc__swap_keys(CHC_MAYBE_UNUSED void *p, CHC_MAYBE_UNUSED size_t n,
2065
+ CHC_MAYBE_UNUSED int key_size)
2066
+ {
2067
+ #if CHC_BIG_ENDIAN
2068
+ switch (key_size) {
2069
+ case 1: break;
2070
+ case 2: { uint16_t *a = p; for (size_t i = 0; i < n; i++) a[i] = chc__bswap16(a[i]); break; }
2071
+ case 4: { uint32_t *a = p; for (size_t i = 0; i < n; i++) a[i] = chc__bswap32(a[i]); break; }
2072
+ case 8: { uint64_t *a = p; for (size_t i = 0; i < n; i++) a[i] = chc__bswap64(a[i]); break; }
2073
+ }
2074
+ #endif
2075
+ }
2076
+
2077
+ static int
2078
+ chc__col_read(chc_in *in, const chc_type *t,
2079
+ size_t n_rows, chc_column **out, chc_err *err)
2080
+ {
2081
+ const chc_alloc *al = in->al;
2082
+ /* Tier 1 / FIXED scalar */
2083
+ size_t es = chc_type_elem_size(t);
2084
+ if (es) return chc__col_read_fixed(in, es, n_rows, out, err);
2085
+
2086
+ switch (t->kind) {
2087
+ case CHC_STRING:
2088
+ return chc__col_read_string(in, n_rows, out, err);
2089
+
2090
+ case CHC_JSON:
2091
+ case CHC_OBJECT: {
2092
+ /* JSON / Object('json') stream prefix: 8-byte LE serialization
2093
+ * version (SerializationObject.cpp:275). Only STRING (=1) is in
2094
+ * scope; other versions need the consumer to set
2095
+ * output_format_native_write_json_as_string=1 on the SELECT.
2096
+ * Body bytes per row are writeStringBinary, identical to a String
2097
+ * column — reuse chc__col_read_string and keep CHC_COL_STRING
2098
+ * layout so callers reuse string accessors. */
2099
+ uint64_t version;
2100
+ int rc = chc__read_u64_le(in, &version, err);
2101
+ if (rc != CHC_OK) return rc;
2102
+ if (version != 1)
2103
+ return chc__err_set(err, CHC_ERR_TYPE,
2104
+ "unsupported JSON serialization version %llu "
2105
+ "(set output_format_native_write_json_as_string=1)",
2106
+ (unsigned long long) version);
2107
+ return chc__col_read_string(in, n_rows, out, err);
2108
+ }
2109
+
2110
+ case CHC_NULLABLE: {
2111
+ if (t->n_children != 1)
2112
+ return chc__err_set(err, CHC_ERR_TYPE, "Nullable expects 1 child");
2113
+ chc_column *c = chc__calloc(al, sizeof *c, err);
2114
+ if (!c) return CHC_ERR_OOM;
2115
+ c->layout = CHC_COL_NULLABLE;
2116
+ c->n_rows = n_rows;
2117
+ if (n_rows) {
2118
+ c->nullable.null_map = chc__alloc(al, n_rows, err);
2119
+ if (!c->nullable.null_map) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2120
+ int rc = chc__read_bytes(in, c->nullable.null_map, n_rows, err);
2121
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2122
+ }
2123
+ int rc = chc__col_read(in, t->children[0], n_rows, &c->nullable.inner, err);
2124
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2125
+ *out = c;
2126
+ return CHC_OK;
2127
+ }
2128
+
2129
+ case CHC_ARRAY:
2130
+ case CHC_MAP: {
2131
+ if (t->kind == CHC_ARRAY && t->n_children != 1)
2132
+ return chc__err_set(err, CHC_ERR_TYPE, "Array expects 1 child");
2133
+ if (t->kind == CHC_MAP && t->n_children != 2)
2134
+ return chc__err_set(err, CHC_ERR_TYPE, "Map expects 2 children");
2135
+ chc_column *c = chc__calloc(al, sizeof *c, err);
2136
+ if (!c) return CHC_ERR_OOM;
2137
+ c->layout = CHC_COL_ARRAY;
2138
+ c->n_rows = n_rows;
2139
+ uint64_t total = 0;
2140
+ if (n_rows) {
2141
+ size_t offs_bytes;
2142
+ if (chc__mul_size(n_rows, sizeof(uint64_t), &offs_bytes)) {
2143
+ chc__column_destroy(c, al);
2144
+ return chc__err_set(err, CHC_ERR_PROTOCOL, "array offsets size overflow");
2145
+ }
2146
+ c->array.offsets = chc__alloc(al, offs_bytes, err);
2147
+ if (!c->array.offsets) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2148
+ int rc = chc__read_bytes(in, c->array.offsets, offs_bytes, err);
2149
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2150
+ chc__swap_offsets(c->array.offsets, n_rows);
2151
+ total = c->array.offsets[n_rows - 1];
2152
+ if (total > CHC_MAX_NUM_ROWS) {
2153
+ chc__column_destroy(c, al);
2154
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
2155
+ "array nested length too large: %llu",
2156
+ (unsigned long long) total);
2157
+ }
2158
+ }
2159
+ if (t->kind == CHC_ARRAY) {
2160
+ int rc = chc__col_read(in, t->children[0], (size_t) total,
2161
+ &c->array.values, err);
2162
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2163
+ } else {
2164
+ /* Map: synthesise an implicit Tuple(K, V) column. */
2165
+ chc_column *tup = chc__calloc(al, sizeof *tup, err);
2166
+ if (!tup) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2167
+ tup->layout = CHC_COL_TUPLE;
2168
+ tup->n_rows = (size_t) total;
2169
+ tup->tuple.arity = 2;
2170
+ tup->tuple.children = chc__calloc(al, 2 * sizeof *tup->tuple.children, err);
2171
+ if (!tup->tuple.children) { chc__column_destroy(tup, al); chc__column_destroy(c, al); return CHC_ERR_OOM; }
2172
+ int rc = chc__col_read(in, t->children[0], (size_t) total,
2173
+ &tup->tuple.children[0], err);
2174
+ if (rc != CHC_OK) { chc__column_destroy(tup, al); chc__column_destroy(c, al); return rc; }
2175
+ rc = chc__col_read(in, t->children[1], (size_t) total,
2176
+ &tup->tuple.children[1], err);
2177
+ if (rc != CHC_OK) { chc__column_destroy(tup, al); chc__column_destroy(c, al); return rc; }
2178
+ c->array.values = tup;
2179
+ }
2180
+ *out = c;
2181
+ return CHC_OK;
2182
+ }
2183
+
2184
+ case CHC_TUPLE: {
2185
+ chc_column *c = chc__calloc(al, sizeof *c, err);
2186
+ if (!c) return CHC_ERR_OOM;
2187
+ c->layout = CHC_COL_TUPLE;
2188
+ c->n_rows = n_rows;
2189
+ c->tuple.arity = t->n_children;
2190
+ /* LOCAL PATCH (ch_connect): ClickHouse serializes the empty Tuple()
2191
+ * as one UInt8 (zero) per row; skipping nothing desyncs the stream. */
2192
+ if (t->n_children == 0) {
2193
+ uint8_t scratch[256];
2194
+ size_t left = n_rows;
2195
+ while (left) {
2196
+ size_t take = left > sizeof scratch ? sizeof scratch : left;
2197
+ int rc = chc__read_bytes(in, scratch, take, err);
2198
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2199
+ left -= take;
2200
+ }
2201
+ *out = c;
2202
+ return CHC_OK;
2203
+ }
2204
+ c->tuple.children = chc__calloc(al, t->n_children * sizeof *c->tuple.children, err);
2205
+ if (!c->tuple.children) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2206
+ for (size_t i = 0; i < t->n_children; i++) {
2207
+ int rc = chc__col_read(in, t->children[i], n_rows,
2208
+ &c->tuple.children[i], err);
2209
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2210
+ }
2211
+ *out = c;
2212
+ return CHC_OK;
2213
+ }
2214
+
2215
+ case CHC_QBIT: {
2216
+ /* Wire form is Tuple(FixedString(ceil(N/8)) x element_size): one
2217
+ * bit-plane column per element bit, MSB plane first. */
2218
+ size_t bits = chc_type_qbit_element_size(t);
2219
+ if (!bits || t->n_children != 1)
2220
+ return chc__err_set(err, CHC_ERR_TYPE, "QBit: invalid element type");
2221
+ size_t bytes_per_plane = (t->qbit.dimension + 7) / 8;
2222
+ chc_column *c = chc__calloc(al, sizeof *c, err);
2223
+ if (!c) return CHC_ERR_OOM;
2224
+ c->layout = CHC_COL_TUPLE;
2225
+ c->n_rows = n_rows;
2226
+ c->tuple.arity = bits;
2227
+ c->tuple.children = chc__calloc(al, bits * sizeof *c->tuple.children, err);
2228
+ if (!c->tuple.children) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2229
+ for (size_t i = 0; i < bits; i++) {
2230
+ int rc = chc__col_read_fixed(in, bytes_per_plane, n_rows,
2231
+ &c->tuple.children[i], err);
2232
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2233
+ }
2234
+ *out = c;
2235
+ return CHC_OK;
2236
+ }
2237
+
2238
+ case CHC_LOW_CARDINALITY: {
2239
+ if (t->n_children != 1)
2240
+ return chc__err_set(err, CHC_ERR_TYPE, "LowCardinality expects 1 child");
2241
+ const chc_type *inner = t->children[0];
2242
+ const chc_type *dict_type = inner;
2243
+ bool nullable_wrap = false;
2244
+ if (inner->kind == CHC_NULLABLE) {
2245
+ nullable_wrap = true;
2246
+ dict_type = inner->children[0];
2247
+ }
2248
+
2249
+ chc_column *c = chc__calloc(al, sizeof *c, err);
2250
+ if (!c) return CHC_ERR_OOM;
2251
+ c->layout = CHC_COL_LOW_CARDINALITY;
2252
+ c->n_rows = n_rows;
2253
+
2254
+ if (n_rows == 0) {
2255
+ /* Empty LC column has no body at all. */
2256
+ chc_column *empty_dict = chc__calloc(al, sizeof *empty_dict, err);
2257
+ if (!empty_dict) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2258
+ empty_dict->layout = (dict_type->kind == CHC_STRING) ? CHC_COL_STRING
2259
+ : (chc_type_elem_size(dict_type) ? CHC_COL_FIXED : CHC_COL_NOTHING);
2260
+ if (empty_dict->layout == CHC_COL_FIXED)
2261
+ empty_dict->fixed.elem_size = chc_type_elem_size(dict_type);
2262
+ c->lc.dict = empty_dict;
2263
+ c->lc.key_size = 1;
2264
+ *out = c;
2265
+ return CHC_OK;
2266
+ }
2267
+
2268
+ uint64_t flags;
2269
+ int rc = chc__read_u64_le(in, &flags, err);
2270
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2271
+ if (flags & CHC__LC_NEED_GLOBAL_DICT) {
2272
+ chc__column_destroy(c, al);
2273
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
2274
+ "LowCardinality: global dictionary not supported");
2275
+ }
2276
+ if (!(flags & CHC__LC_HAS_ADDITIONAL_KEYS)) {
2277
+ chc__column_destroy(c, al);
2278
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
2279
+ "LowCardinality: HasAdditionalKeys missing");
2280
+ }
2281
+ unsigned idx_type = (unsigned) (flags & CHC__LC_INDEX_TYPE_MASK);
2282
+ switch (idx_type) {
2283
+ case 0: c->lc.key_size = 1; break;
2284
+ case 1: c->lc.key_size = 2; break;
2285
+ case 2: c->lc.key_size = 4; break;
2286
+ case 3: c->lc.key_size = 8; break;
2287
+ default: chc__column_destroy(c, al);
2288
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
2289
+ "LowCardinality: invalid index type %u", idx_type);
2290
+ }
2291
+
2292
+ uint64_t dict_n;
2293
+ rc = chc__read_u64_le(in, &dict_n, err);
2294
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2295
+ if (dict_n > CHC_MAX_NUM_ROWS) {
2296
+ chc__column_destroy(c, al);
2297
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
2298
+ "LowCardinality dictionary too large: %llu",
2299
+ (unsigned long long) dict_n);
2300
+ }
2301
+ chc_column *inner_dict = NULL;
2302
+ rc = chc__col_read(in, dict_type, (size_t) dict_n, &inner_dict, err);
2303
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2304
+ c->lc.dict_n = (size_t) dict_n;
2305
+
2306
+ if (nullable_wrap) {
2307
+ /* Wire convention: slot 0 of the inner-typed dict is the NULL
2308
+ * sentinel (clickhouse-cpp/columns/lowcardinality.cpp 287-295).
2309
+ * Wrap the dict in a Nullable column so the caller's standard
2310
+ * null-map dispatch covers the LC(Nullable) case. */
2311
+ chc_column *wrapped = chc__calloc(al, sizeof *wrapped, err);
2312
+ if (!wrapped) { chc__column_destroy(inner_dict, al); chc__column_destroy(c, al); return CHC_ERR_OOM; }
2313
+ wrapped->layout = CHC_COL_NULLABLE;
2314
+ wrapped->n_rows = (size_t) dict_n;
2315
+ wrapped->nullable.inner = inner_dict;
2316
+ if (dict_n) {
2317
+ wrapped->nullable.null_map = chc__calloc(al, (size_t) dict_n, err);
2318
+ if (!wrapped->nullable.null_map) {
2319
+ chc__column_destroy(wrapped, al); chc__column_destroy(c, al);
2320
+ return CHC_ERR_OOM;
2321
+ }
2322
+ wrapped->nullable.null_map[0] = 1;
2323
+ }
2324
+ c->lc.dict = wrapped;
2325
+ } else {
2326
+ c->lc.dict = inner_dict;
2327
+ }
2328
+
2329
+ uint64_t key_rows;
2330
+ rc = chc__read_u64_le(in, &key_rows, err);
2331
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2332
+ if (key_rows != n_rows) {
2333
+ chc__column_destroy(c, al);
2334
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
2335
+ "LowCardinality: key_rows %llu != block rows %zu",
2336
+ (unsigned long long) key_rows, n_rows);
2337
+ }
2338
+ size_t keys_bytes;
2339
+ if (chc__mul_size(n_rows, c->lc.key_size, &keys_bytes)) {
2340
+ chc__column_destroy(c, al);
2341
+ return chc__err_set(err, CHC_ERR_PROTOCOL, "LowCardinality keys size overflow");
2342
+ }
2343
+ c->lc.keys = chc__alloc(al, keys_bytes, err);
2344
+ if (!c->lc.keys) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2345
+ rc = chc__read_bytes(in, c->lc.keys, keys_bytes, err);
2346
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2347
+ chc__swap_keys(c->lc.keys, n_rows, c->lc.key_size);
2348
+
2349
+ *out = c;
2350
+ return CHC_OK;
2351
+ }
2352
+
2353
+ case CHC_SIMPLE_AGGREGATE_FUNCTION:
2354
+ /* Wire form is the inner type's stream. Last child is the data type. */
2355
+ if (t->n_children < 1)
2356
+ return chc__err_set(err, CHC_ERR_TYPE, "SimpleAggregateFunction has no inner type");
2357
+ return chc__col_read(in, t->children[t->n_children - 1], n_rows, out, err);
2358
+
2359
+ /* Geo types: aliases for nested Array layers terminating in
2360
+ * Tuple(Float64, Float64). Per clickhouse-cpp factory.cpp 120-130. */
2361
+ case CHC_POINT: return chc__col_read_geo(in, 0, n_rows, out, err);
2362
+ case CHC_RING: return chc__col_read_geo(in, 1, n_rows, out, err);
2363
+ case CHC_POLYGON: return chc__col_read_geo(in, 2, n_rows, out, err);
2364
+ case CHC_MULTI_POLYGON: return chc__col_read_geo(in, 3, n_rows, out, err);
2365
+
2366
+ case CHC_NOTHING:
2367
+ case CHC_VOID: {
2368
+ chc_column *c = chc__calloc(al, sizeof *c, err);
2369
+ if (!c) return CHC_ERR_OOM;
2370
+ c->layout = CHC_COL_NOTHING;
2371
+ c->n_rows = n_rows;
2372
+ /* Wire shape for Nothing is a sequence of UInt8 bytes per row. */
2373
+ if (n_rows) {
2374
+ uint8_t throwaway[256];
2375
+ size_t left = n_rows;
2376
+ while (left) {
2377
+ size_t take = left < sizeof throwaway ? left : sizeof throwaway;
2378
+ int rc = chc__read_bytes(in, throwaway, take, err);
2379
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2380
+ left -= take;
2381
+ }
2382
+ }
2383
+ *out = c;
2384
+ return CHC_OK;
2385
+ }
2386
+
2387
+ default: {
2388
+ size_t nl;
2389
+ const char *nm = chc_type_name(t, &nl);
2390
+ return chc__err_set(err, CHC_ERR_TYPE,
2391
+ "unsupported column type: %.*s", (int) nl, nm ? nm : "");
2392
+ }
2393
+ }
2394
+ }
2395
+
2396
+ static int
2397
+ chc__col_read_geo(chc_in *in, int depth, size_t n_rows,
2398
+ chc_column **out, chc_err *err)
2399
+ {
2400
+ const chc_alloc *al = in->al;
2401
+ if (depth == 0) {
2402
+ /* Point = Tuple(Float64, Float64). */
2403
+ chc_column *c = chc__calloc(al, sizeof *c, err);
2404
+ if (!c) return CHC_ERR_OOM;
2405
+ c->layout = CHC_COL_TUPLE;
2406
+ c->n_rows = n_rows;
2407
+ c->tuple.arity = 2;
2408
+ c->tuple.children = chc__calloc(al, 2 * sizeof *c->tuple.children, err);
2409
+ if (!c->tuple.children) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2410
+ for (int i = 0; i < 2; i++) {
2411
+ int rc = chc__col_read_fixed(in, 8, n_rows, &c->tuple.children[i], err);
2412
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2413
+ }
2414
+ *out = c;
2415
+ return CHC_OK;
2416
+ }
2417
+ /* Array(geo(depth-1)). */
2418
+ chc_column *c = chc__calloc(al, sizeof *c, err);
2419
+ if (!c) return CHC_ERR_OOM;
2420
+ c->layout = CHC_COL_ARRAY;
2421
+ c->n_rows = n_rows;
2422
+ uint64_t total = 0;
2423
+ if (n_rows) {
2424
+ size_t offs_bytes;
2425
+ if (chc__mul_size(n_rows, sizeof(uint64_t), &offs_bytes)) {
2426
+ chc__column_destroy(c, al);
2427
+ return chc__err_set(err, CHC_ERR_PROTOCOL, "array offsets size overflow");
2428
+ }
2429
+ c->array.offsets = chc__alloc(al, offs_bytes, err);
2430
+ if (!c->array.offsets) { chc__column_destroy(c, al); return CHC_ERR_OOM; }
2431
+ int rc = chc__read_bytes(in, c->array.offsets, offs_bytes, err);
2432
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2433
+ chc__swap_offsets(c->array.offsets, n_rows);
2434
+ total = c->array.offsets[n_rows - 1];
2435
+ if (total > CHC_MAX_NUM_ROWS) {
2436
+ chc__column_destroy(c, al);
2437
+ return chc__err_set(err, CHC_ERR_PROTOCOL,
2438
+ "array nested length too large: %llu",
2439
+ (unsigned long long) total);
2440
+ }
2441
+ }
2442
+ int rc = chc__col_read_geo(in, depth - 1, (size_t) total,
2443
+ &c->array.values, err);
2444
+ if (rc != CHC_OK) { chc__column_destroy(c, al); return rc; }
2445
+ *out = c;
2446
+ return CHC_OK;
2447
+ }
2448
+
2449
+ /* -------- block reader ---------- */
2450
+
2451
+ struct chc_block {
2452
+ size_t n_columns;
2453
+ size_t n_rows;
2454
+ bool is_overflows;
2455
+ int32_t bucket_num;
2456
+ char **names;
2457
+ size_t *name_lens;
2458
+ chc_type **types;
2459
+ chc_column **columns;
2460
+ };
2461
+
2462
+ void
2463
+ chc_block_destroy(chc_block *b, const chc_alloc *al)
2464
+ {
2465
+ if (!b) return;
2466
+ for (size_t i = 0; i < b->n_columns; i++) {
2467
+ if (b->names && b->names[i])
2468
+ al->free(al->ud, b->names[i], b->name_lens[i] + 1);
2469
+ if (b->types) chc_type_destroy(b->types[i], al);
2470
+ if (b->columns) chc__column_destroy(b->columns[i], al);
2471
+ }
2472
+ al->free(al->ud, b->names, b->n_columns * sizeof *b->names);
2473
+ al->free(al->ud, b->name_lens, b->n_columns * sizeof *b->name_lens);
2474
+ al->free(al->ud, b->types, b->n_columns * sizeof *b->types);
2475
+ al->free(al->ud, b->columns, b->n_columns * sizeof *b->columns);
2476
+ al->free(al->ud, b, sizeof *b);
2477
+ }
2478
+
2479
+ size_t chc_block_n_rows(const chc_block *b) { return b ? b->n_rows : 0; }
2480
+ size_t chc_block_n_columns(const chc_block *b) { return b ? b->n_columns : 0; }
2481
+ const char *chc_block_column_name(const chc_block *b, size_t i, size_t *out_len) {
2482
+ if (!b || i >= b->n_columns) { if (out_len) *out_len = 0; return NULL; }
2483
+ if (out_len) *out_len = b->name_lens[i];
2484
+ return b->names[i];
2485
+ }
2486
+ const chc_type *chc_block_column_type(const chc_block *b, size_t i)
2487
+ { return (b && i < b->n_columns) ? b->types[i] : NULL; }
2488
+ const chc_column *chc_block_column(const chc_block *b, size_t i)
2489
+ { return (b && i < b->n_columns) ? b->columns[i] : NULL; }
2490
+ bool chc_block_is_overflows(const chc_block *b) { return b ? b->is_overflows : false; }
2491
+ int32_t chc_block_bucket_num(const chc_block *b) { return b ? b->bucket_num : 0; }
2492
+
2493
+ /* Resumable block reader. Continues a (possibly partial) block parse across
2494
+ * CHC_WOULD_BLOCK without re-parsing completed columns. Owns the `in`
2495
+ * checkpoint/rewind at packet-start and per-column granularity.
2496
+ *
2497
+ * *blk == NULL : fresh packet. Checkpoint packet start, alloc block, parse
2498
+ * block-info header + ncols/nrows + per-column arrays, then
2499
+ * read columns from index 0.
2500
+ * *blk != NULL : resume. Header already parsed; continue at *next_col,
2501
+ * re-reading only the in-progress column.
2502
+ *
2503
+ * Per-column retain is active only for ioless in (in->io == NULL). For
2504
+ * io-backed in (blocking socket, or the compressed dec_in wrapper) it behaves
2505
+ * as baseline: never sets a checkpoint (io-backed refill clobbers the mark),
2506
+ * and on any non-OK frees the partial block and returns.
2507
+ *
2508
+ * On CHC_WOULD_BLOCK (ioless only):
2509
+ * - mid-header (only when *blk was NULL): rewind to packet start, free the
2510
+ * block, leave *blk == NULL.
2511
+ * - mid-column i: retain *blk with columns [0,i) intact, free column i's
2512
+ * partial slot allocations, set *next_col = i, rewind in to the column-i
2513
+ * checkpoint.
2514
+ * On CHC_OK: *blk is the completed block (ownership to caller), *next_col == ncols.
2515
+ * On real error: free *blk, set it NULL. */
2516
+ static int
2517
+ chc__block_resume_in(chc_in *in, const chc_alloc *al,
2518
+ const chc_block_opts *opts,
2519
+ chc_block **blk, size_t *next_col, chc_err *err)
2520
+ {
2521
+ bool ioless = CHC__IOLESS(in);
2522
+ chc_block *b = *blk;
2523
+ int rc;
2524
+
2525
+ if (b == NULL) {
2526
+ b = chc__calloc(al, sizeof *b, err);
2527
+ if (!b) return CHC_ERR_OOM;
2528
+ uint64_t consumed_before = in->consumed;
2529
+
2530
+ /* Checkpoint packet start so a mid-header would-block rewinds here. */
2531
+ if (ioless) chc__in_checkpoint(in);
2532
+
2533
+ if (opts->has_block_info) {
2534
+ uint64_t fid;
2535
+ rc = chc__read_varuint(in, &fid, err);
2536
+ if (rc == CHC_ERR_EOF && in->consumed == consumed_before) {
2537
+ chc_block_destroy(b, al);
2538
+ *blk = NULL;
2539
+ chc_err_reset(err);
2540
+ return CHC_OK;
2541
+ }
2542
+ if (rc != CHC_OK) goto header_fail;
2543
+ if (fid != 1) { rc = chc__err_set(err, CHC_ERR_PROTOCOL,
2544
+ "BlockInfo: expected field 1, got %llu",
2545
+ (unsigned long long) fid); goto header_fail; }
2546
+ uint8_t ov;
2547
+ rc = chc__read_byte(in, &ov, err); if (rc != CHC_OK) goto header_fail;
2548
+ b->is_overflows = ov != 0;
2549
+ rc = chc__read_varuint(in, &fid, err); if (rc != CHC_OK) goto header_fail;
2550
+ if (fid != 2) { rc = chc__err_set(err, CHC_ERR_PROTOCOL,
2551
+ "BlockInfo: expected field 2"); goto header_fail; }
2552
+ uint32_t bn;
2553
+ rc = chc__read_u32_le(in, &bn, err); if (rc != CHC_OK) goto header_fail;
2554
+ b->bucket_num = (int32_t) bn;
2555
+ rc = chc__read_varuint(in, &fid, err); if (rc != CHC_OK) goto header_fail;
2556
+ if (fid != 0) { rc = chc__err_set(err, CHC_ERR_PROTOCOL,
2557
+ "BlockInfo: expected terminator"); goto header_fail; }
2558
+ }
2559
+
2560
+ uint64_t ncols, nrows;
2561
+ rc = chc__read_varuint(in, &ncols, err);
2562
+ if (rc == CHC_ERR_EOF && !opts->has_block_info
2563
+ && in->consumed == consumed_before) {
2564
+ chc_block_destroy(b, al);
2565
+ *blk = NULL;
2566
+ chc_err_reset(err);
2567
+ return CHC_OK;
2568
+ }
2569
+ if (rc != CHC_OK) goto header_fail;
2570
+ rc = chc__read_varuint(in, &nrows, err);
2571
+ if (rc != CHC_OK) goto header_fail;
2572
+
2573
+ if (ncols > CHC_MAX_NUM_COLUMNS) {
2574
+ rc = chc__err_set(err, CHC_ERR_PROTOCOL,
2575
+ "suspiciously many columns: %llu", (unsigned long long) ncols);
2576
+ goto header_fail;
2577
+ }
2578
+ if (nrows > CHC_MAX_NUM_ROWS) {
2579
+ rc = chc__err_set(err, CHC_ERR_PROTOCOL,
2580
+ "suspiciously many rows: %llu", (unsigned long long) nrows);
2581
+ goto header_fail;
2582
+ }
2583
+
2584
+ b->n_columns = (size_t) ncols;
2585
+ b->n_rows = (size_t) nrows;
2586
+ if (ncols) {
2587
+ b->names = chc__calloc(al, ncols * sizeof *b->names, err);
2588
+ b->name_lens = chc__calloc(al, ncols * sizeof *b->name_lens, err);
2589
+ b->types = chc__calloc(al, ncols * sizeof *b->types, err);
2590
+ b->columns = chc__calloc(al, ncols * sizeof *b->columns, err);
2591
+ if (!b->names || !b->name_lens || !b->types || !b->columns) {
2592
+ rc = CHC_ERR_OOM; goto header_fail;
2593
+ }
2594
+ }
2595
+
2596
+ /* Header fully parsed; commit the block and start at column 0. */
2597
+ *blk = b;
2598
+ *next_col = 0;
2599
+ }
2600
+
2601
+ size_t nrows = b->n_rows;
2602
+ for (size_t i = *next_col; i < b->n_columns; i++) {
2603
+ char *type_name = NULL; size_t type_len = 0;
2604
+
2605
+ /* Checkpoint column start so a mid-column would-block rewinds here,
2606
+ * dropping completed columns' wire bytes on the caller's next submit. */
2607
+ if (ioless) chc__in_checkpoint(in);
2608
+
2609
+ rc = chc__read_string(in, &b->names[i], &b->name_lens[i], err);
2610
+ if (rc != CHC_OK) goto col_fail;
2611
+
2612
+ rc = chc__read_string(in, &type_name, &type_len, err);
2613
+ if (rc != CHC_OK) goto col_fail;
2614
+
2615
+ if (opts->has_custom_serialization) {
2616
+ uint8_t hcs;
2617
+ rc = chc__read_byte(in, &hcs, err);
2618
+ if (rc != CHC_OK) goto col_fail;
2619
+ if (hcs) {
2620
+ rc = chc__err_set(err, CHC_ERR_PROTOCOL,
2621
+ "custom serialization not supported on column '%s'", b->names[i]);
2622
+ goto col_fail;
2623
+ }
2624
+ }
2625
+
2626
+ rc = chc_type_parse(type_name, type_len, al, &b->types[i], err);
2627
+ al->free(al->ud, type_name, type_len + 1);
2628
+ type_name = NULL;
2629
+ if (rc != CHC_OK) goto col_fail;
2630
+
2631
+ if (nrows) {
2632
+ rc = chc__col_read_prefix(in, b->types[i], err);
2633
+ if (rc != CHC_OK) goto col_fail;
2634
+
2635
+ rc = chc__col_read(in, b->types[i], nrows, &b->columns[i], err);
2636
+ if (rc != CHC_OK) goto col_fail;
2637
+ }
2638
+ continue;
2639
+
2640
+ col_fail:
2641
+ if (ioless && rc == CHC_WOULD_BLOCK) {
2642
+ /* Retain columns [0,i); reset slot i to a destroy-safe NULL state
2643
+ * and rewind so column i re-parses from its checkpoint. */
2644
+ if (type_name) al->free(al->ud, type_name, type_len + 1);
2645
+ if (b->names[i]) {
2646
+ al->free(al->ud, b->names[i], b->name_lens[i] + 1);
2647
+ b->names[i] = NULL;
2648
+ b->name_lens[i] = 0;
2649
+ }
2650
+ chc_type_destroy(b->types[i], al);
2651
+ b->types[i] = NULL;
2652
+ chc__column_destroy(b->columns[i], al);
2653
+ b->columns[i] = NULL;
2654
+ chc__in_rewind(in);
2655
+ *next_col = i;
2656
+ return CHC_WOULD_BLOCK;
2657
+ }
2658
+ if (type_name) al->free(al->ud, type_name, type_len + 1);
2659
+ chc_block_destroy(b, al);
2660
+ *blk = NULL;
2661
+ return rc;
2662
+ }
2663
+
2664
+ *next_col = b->n_columns;
2665
+ return CHC_OK;
2666
+
2667
+ header_fail:
2668
+ if (ioless && rc == CHC_WOULD_BLOCK) chc__in_rewind(in);
2669
+ chc_block_destroy(b, al);
2670
+ *blk = NULL;
2671
+ return rc;
2672
+ }
2673
+
2674
+ /* Block read from an already-initialised chc_in. Also used by
2675
+ * clickhouse-client.h's recv_packet (persistent buffer). Thin non-looping
2676
+ * wrapper over chc__block_resume_in. Returns 0 with *out == NULL on clean EOF
2677
+ * at block boundary (only when opts->has_block_info is false; TCP path has no
2678
+ * clean-EOF concept). */
2679
+ int
2680
+ chc_block_read(chc_in *in, const chc_alloc *al,
2681
+ const chc_block_opts *opts, chc_block **out, chc_err *err)
2682
+ {
2683
+ chc_block_opts def = {};
2684
+ if (!opts) opts = &def;
2685
+ /* Snapshot to roll back resume's per-column checkpoint progress: this
2686
+ * non-resuming entry is baseline (rewind to packet start), so a would-block
2687
+ * must leave `in` exactly as found. Callers that own the packet-start
2688
+ * checkpoint/rewind (io-backed in, the legacy ioless driver) then see
2689
+ * untouched mark/pos/consumed. */
2690
+ #ifndef CHC_NO_ASYNC
2691
+ size_t entry_pos = in->pos, entry_mark = in->mark;
2692
+ uint64_t entry_consumed = in->consumed;
2693
+ #endif
2694
+ chc_block *blk = NULL;
2695
+ size_t next_col = 0;
2696
+ int rc = chc__block_resume_in(in, al, opts, &blk, &next_col, err);
2697
+ #ifndef CHC_NO_ASYNC
2698
+ /* io-backed callers never see WOULD_BLOCK; the compressed dec_in path may
2699
+ * (propagated from the underlying ioless raw in), in which case the async
2700
+ * recv driver rewinds the raw in to packet start -- baseline. */
2701
+ if (rc == CHC_WOULD_BLOCK) {
2702
+ if (blk) { chc_block_destroy(blk, al); blk = NULL; }
2703
+ in->pos = entry_pos;
2704
+ in->mark = entry_mark;
2705
+ in->consumed = entry_consumed;
2706
+ }
2707
+ #endif
2708
+ *out = blk;
2709
+ return rc;
2710
+ }
2711
+
2712
+ /* -------- block writer ---------- */
2713
+
2714
+ typedef enum {
2715
+ CHC__BLD_FIXED = 1,
2716
+ CHC__BLD_STRING = 2,
2717
+ CHC__BLD_NULL_FIXED = 3,
2718
+ CHC__BLD_NULL_STRING = 4,
2719
+ CHC__BLD_ARRAY_FIXED = 5,
2720
+ CHC__BLD_ARRAY_STRING = 6,
2721
+ CHC__BLD_LC_STRING = 7,
2722
+ CHC__BLD_JSON_STRING = 8,
2723
+ CHC__BLD_ARRAY_NESTED_FIXED = 9,
2724
+ CHC__BLD_ARRAY_NESTED_STRING = 10,
2725
+ } chc__bld_kind;
2726
+
2727
+ typedef struct {
2728
+ const char *name;
2729
+ size_t name_len;
2730
+ const chc_type *type; /* NULL only for legacy STRING entries */
2731
+ chc__bld_kind kind;
2732
+ size_t n_rows;
2733
+ size_t inner_n; /* element count of the inner array/string/dict body */
2734
+ /* Pointers into caller-owned memory; library never copies. */
2735
+ /* Base representation: fixed-width xor variable-length. */
2736
+ union {
2737
+ struct { const void *data; size_t elem_size; } fixed; /* *_FIXED */
2738
+ struct { const uint64_t *offsets; const uint8_t *data; } str; /* *_STRING / LC dict */
2739
+ };
2740
+ /* Structural modifier over base; absent for plain FIXED / STRING / JSON. */
2741
+ union {
2742
+ struct { const uint8_t *null_map; } nullable; /* NULL_* */
2743
+ struct { const uint64_t *offsets; } array; /* ARRAY_FIXED / ARRAY_STRING (cumulative ends) */
2744
+ struct { /* ARRAY_NESTED_*, ndim >= 2 */
2745
+ int ndim;
2746
+ const uint64_t * const *level_offsets; /* ndim cumulative-end arrays */
2747
+ const size_t *level_offsets_len; /* count per level */
2748
+ } nested;
2749
+ struct { int key_size; const void *keys; } lc; /* LC_STRING */
2750
+ };
2751
+ } chc__col_entry;
2752
+
2753
+ struct chc_block_builder {
2754
+ const chc_alloc *al; /* captured at init */
2755
+ chc__col_entry *cols;
2756
+ size_t n_cols;
2757
+ size_t cap;
2758
+ size_t n_rows; /* common across all columns */
2759
+ bool n_rows_set;
2760
+ };
2761
+
2762
+ int
2763
+ chc_block_builder_init(chc_block_builder **out, const chc_alloc *al,
2764
+ chc_err *err)
2765
+ {
2766
+ chc_block_builder *bb = chc__calloc(al, sizeof *bb, err);
2767
+ if (!bb) return CHC_ERR_OOM;
2768
+ bb->al = al;
2769
+ *out = bb;
2770
+ return CHC_OK;
2771
+ }
2772
+
2773
+ void
2774
+ chc_block_builder_destroy(chc_block_builder *bb)
2775
+ {
2776
+ if (!bb) return;
2777
+ const chc_alloc *al = bb->al;
2778
+ al->free(al->ud, bb->cols, bb->cap * sizeof *bb->cols);
2779
+ al->free(al->ud, bb, sizeof *bb);
2780
+ }
2781
+
2782
+ static int
2783
+ chc__bld_grow(chc_block_builder *bb, chc_err *err)
2784
+ {
2785
+ if (bb->n_cols < bb->cap) return CHC_OK;
2786
+ size_t new_cap = bb->cap ? bb->cap * 2 : 4;
2787
+ chc__col_entry *p = chc__realloc(bb->al, bb->cols,
2788
+ bb->cap * sizeof *bb->cols,
2789
+ new_cap * sizeof *bb->cols, err);
2790
+ if (!p) return CHC_ERR_OOM;
2791
+ bb->cols = p;
2792
+ bb->cap = new_cap;
2793
+ return CHC_OK;
2794
+ }
2795
+
2796
+ static int
2797
+ chc__bld_check_rows(chc_block_builder *bb, size_t n_rows, chc_err *err)
2798
+ {
2799
+ if (!bb->n_rows_set) { bb->n_rows = n_rows; bb->n_rows_set = true; return CHC_OK; }
2800
+ if (bb->n_rows != n_rows)
2801
+ return chc__err_set(err, CHC_ERR_USAGE,
2802
+ "block_builder: row count mismatch (%zu vs %zu)", bb->n_rows, n_rows);
2803
+ return CHC_OK;
2804
+ }
2805
+
2806
+ static int
2807
+ chc__bld_add(chc_block_builder *bb, const char *name, size_t name_len,
2808
+ const chc_type *type, chc__bld_kind kind, size_t n_rows,
2809
+ chc__col_entry **out, chc_err *err)
2810
+ {
2811
+ int rc = chc__bld_check_rows(bb, n_rows, err);
2812
+ if (rc != CHC_OK) return rc;
2813
+ rc = chc__bld_grow(bb, err);
2814
+ if (rc != CHC_OK) return rc;
2815
+ chc__col_entry *e = &bb->cols[bb->n_cols++];
2816
+ *e = (chc__col_entry) {
2817
+ .name = name, .name_len = name_len, .type = type,
2818
+ .kind = kind, .n_rows = n_rows,
2819
+ };
2820
+ *out = e;
2821
+ return CHC_OK;
2822
+ }
2823
+
2824
+ int
2825
+ chc_block_builder_append_fixed(chc_block_builder *bb,
2826
+ const char *name, size_t name_len,
2827
+ const chc_type *t,
2828
+ const void *data, size_t n_rows,
2829
+ chc_err *err)
2830
+ {
2831
+ size_t es = chc_type_elem_size(t);
2832
+ if (!es) return chc__err_set(err, CHC_ERR_TYPE,
2833
+ "append_fixed: type is not fixed-size");
2834
+ chc__col_entry *e;
2835
+ int rc = chc__bld_add(bb, name, name_len, t, CHC__BLD_FIXED,
2836
+ n_rows, &e, err);
2837
+ if (rc != CHC_OK) return rc;
2838
+ e->fixed.data = data;
2839
+ e->fixed.elem_size = es;
2840
+ return CHC_OK;
2841
+ }
2842
+
2843
+ int
2844
+ chc_block_builder_append_string(chc_block_builder *bb,
2845
+ const char *name, size_t name_len,
2846
+ const uint64_t *offsets,
2847
+ const uint8_t *data, size_t n_rows,
2848
+ chc_err *err)
2849
+ {
2850
+ chc__col_entry *e;
2851
+ int rc = chc__bld_add(bb, name, name_len, NULL, CHC__BLD_STRING,
2852
+ n_rows, &e, err);
2853
+ if (rc != CHC_OK) return rc;
2854
+ e->str.offsets = offsets;
2855
+ e->str.data = data;
2856
+ e->inner_n = n_rows;
2857
+ return CHC_OK;
2858
+ }
2859
+
2860
+ /* Extract the inner fixed-elem size from a Nullable(<fixed>) /
2861
+ * Array(<fixed>) type. 0 if `t` is not the expected shape. */
2862
+ static size_t
2863
+ chc__bld_inner_fixed_size(const chc_type *t, chc_kind outer)
2864
+ {
2865
+ if (!t || t->kind != outer || t->n_children != 1) return 0;
2866
+ return chc_type_elem_size(t->children[0]);
2867
+ }
2868
+
2869
+ /* True iff `t` is Array(String) / Nullable(String). */
2870
+ static bool
2871
+ chc__bld_inner_is_string(const chc_type *t, chc_kind outer)
2872
+ {
2873
+ return t && t->kind == outer && t->n_children == 1
2874
+ && t->children[0]->kind == CHC_STRING;
2875
+ }
2876
+
2877
+ /* True iff `t` is LowCardinality(String) or LowCardinality(Nullable(String)). */
2878
+ static bool
2879
+ chc__bld_lc_inner_is_string(const chc_type *t)
2880
+ {
2881
+ if (!t || t->kind != CHC_LOW_CARDINALITY || t->n_children != 1) return false;
2882
+ const chc_type *inner = t->children[0];
2883
+ if (inner->kind == CHC_STRING) return true;
2884
+ if (inner->kind == CHC_NULLABLE && inner->n_children == 1
2885
+ && inner->children[0]->kind == CHC_STRING)
2886
+ return true;
2887
+ return false;
2888
+ }
2889
+
2890
+ int
2891
+ chc_block_builder_append_nullable_fixed(chc_block_builder *bb,
2892
+ const char *name, size_t name_len,
2893
+ const chc_type *t,
2894
+ const uint8_t *null_map,
2895
+ const void *inner_data,
2896
+ size_t n_rows, chc_err *err)
2897
+ {
2898
+ size_t es = chc__bld_inner_fixed_size(t, CHC_NULLABLE);
2899
+ if (!es) return chc__err_set(err, CHC_ERR_TYPE,
2900
+ "append_nullable_fixed: type is not Nullable(<fixed>)");
2901
+ chc__col_entry *e;
2902
+ int rc = chc__bld_add(bb, name, name_len, t, CHC__BLD_NULL_FIXED,
2903
+ n_rows, &e, err);
2904
+ if (rc != CHC_OK) return rc;
2905
+ e->nullable.null_map = null_map;
2906
+ e->fixed.data = inner_data;
2907
+ e->fixed.elem_size = es;
2908
+ return CHC_OK;
2909
+ }
2910
+
2911
+ int
2912
+ chc_block_builder_append_nullable_string(chc_block_builder *bb,
2913
+ const char *name, size_t name_len,
2914
+ const chc_type *t,
2915
+ const uint8_t *null_map,
2916
+ const uint64_t *inner_offsets,
2917
+ const uint8_t *inner_data,
2918
+ size_t n_rows, chc_err *err)
2919
+ {
2920
+ if (!chc__bld_inner_is_string(t, CHC_NULLABLE))
2921
+ return chc__err_set(err, CHC_ERR_TYPE,
2922
+ "append_nullable_string: type is not Nullable(String)");
2923
+ chc__col_entry *e;
2924
+ int rc = chc__bld_add(bb, name, name_len, t, CHC__BLD_NULL_STRING,
2925
+ n_rows, &e, err);
2926
+ if (rc != CHC_OK) return rc;
2927
+ e->nullable.null_map = null_map;
2928
+ e->str.offsets = inner_offsets;
2929
+ e->str.data = inner_data;
2930
+ e->inner_n = n_rows;
2931
+ return CHC_OK;
2932
+ }
2933
+
2934
+ int
2935
+ chc_block_builder_append_array_fixed(chc_block_builder *bb,
2936
+ const char *name, size_t name_len,
2937
+ const chc_type *t,
2938
+ const uint64_t *offsets,
2939
+ const void *values,
2940
+ size_t n_rows, chc_err *err)
2941
+ {
2942
+ size_t es = chc__bld_inner_fixed_size(t, CHC_ARRAY);
2943
+ if (!es) return chc__err_set(err, CHC_ERR_TYPE,
2944
+ "append_array_fixed: type is not Array(<fixed>)");
2945
+ chc__col_entry *e;
2946
+ int rc = chc__bld_add(bb, name, name_len, t, CHC__BLD_ARRAY_FIXED,
2947
+ n_rows, &e, err);
2948
+ if (rc != CHC_OK) return rc;
2949
+ e->array.offsets = offsets;
2950
+ e->fixed.data = values;
2951
+ e->fixed.elem_size = es;
2952
+ e->inner_n = n_rows ? (size_t) offsets[n_rows - 1] : 0;
2953
+ return CHC_OK;
2954
+ }
2955
+
2956
+ int
2957
+ chc_block_builder_append_array_string(chc_block_builder *bb,
2958
+ const char *name, size_t name_len,
2959
+ const chc_type *t,
2960
+ const uint64_t *offsets,
2961
+ const uint64_t *values_offsets,
2962
+ const uint8_t *values_data,
2963
+ size_t n_rows, chc_err *err)
2964
+ {
2965
+ if (!chc__bld_inner_is_string(t, CHC_ARRAY))
2966
+ return chc__err_set(err, CHC_ERR_TYPE,
2967
+ "append_array_string: type is not Array(String)");
2968
+ chc__col_entry *e;
2969
+ int rc = chc__bld_add(bb, name, name_len, t, CHC__BLD_ARRAY_STRING,
2970
+ n_rows, &e, err);
2971
+ if (rc != CHC_OK) return rc;
2972
+ e->array.offsets = offsets;
2973
+ e->str.offsets = values_offsets;
2974
+ e->str.data = values_data;
2975
+ e->inner_n = n_rows ? (size_t) offsets[n_rows - 1] : 0;
2976
+ return CHC_OK;
2977
+ }
2978
+
2979
+ /* Walk past ndim Array(...) layers, return leaf type or NULL on
2980
+ * shape mismatch */
2981
+ static const chc_type *
2982
+ chc__bld_array_leaf(const chc_type *t, int ndim)
2983
+ {
2984
+ while (ndim-- > 0) {
2985
+ if (!t || t->kind != CHC_ARRAY || t->n_children != 1) return NULL;
2986
+ t = t->children[0];
2987
+ }
2988
+ return t;
2989
+ }
2990
+
2991
+ int
2992
+ chc_block_builder_append_array_nested_fixed(chc_block_builder *bb,
2993
+ const char *name, size_t name_len,
2994
+ const chc_type *t,
2995
+ int ndim,
2996
+ const uint64_t * const *level_offsets,
2997
+ const size_t *level_offsets_len,
2998
+ const void *values,
2999
+ size_t n_rows, chc_err *err)
3000
+ {
3001
+ if (ndim < 2)
3002
+ return chc__err_set(err, CHC_ERR_USAGE,
3003
+ "append_array_nested_fixed: ndim must be >= 2");
3004
+ const chc_type *leaf = chc__bld_array_leaf(t, ndim);
3005
+ if (!leaf)
3006
+ return chc__err_set(err, CHC_ERR_TYPE,
3007
+ "append_array_nested_fixed: type does not match ndim");
3008
+ size_t es = chc_type_elem_size(leaf);
3009
+ if (!es)
3010
+ return chc__err_set(err, CHC_ERR_TYPE,
3011
+ "append_array_nested_fixed: leaf is not fixed-size");
3012
+ if (n_rows != level_offsets_len[0])
3013
+ return chc__err_set(err, CHC_ERR_USAGE,
3014
+ "append_array_nested_fixed: n_rows != level_offsets_len[0]");
3015
+ chc__col_entry *e;
3016
+ int rc = chc__bld_add(bb, name, name_len, t,
3017
+ CHC__BLD_ARRAY_NESTED_FIXED, n_rows, &e, err);
3018
+ if (rc != CHC_OK) return rc;
3019
+ e->nested.ndim = ndim;
3020
+ e->nested.level_offsets = level_offsets;
3021
+ e->nested.level_offsets_len = level_offsets_len;
3022
+ e->fixed.data = values;
3023
+ e->fixed.elem_size = es;
3024
+ /* inner_n holds leaf element count: last cumulative end of innermost level */
3025
+ {
3026
+ size_t ilen = level_offsets_len[ndim - 1];
3027
+ e->inner_n = ilen ? (size_t) level_offsets[ndim - 1][ilen - 1] : 0;
3028
+ }
3029
+ return CHC_OK;
3030
+ }
3031
+
3032
+ int
3033
+ chc_block_builder_append_array_nested_string(chc_block_builder *bb,
3034
+ const char *name, size_t name_len,
3035
+ const chc_type *t,
3036
+ int ndim,
3037
+ const uint64_t * const *level_offsets,
3038
+ const size_t *level_offsets_len,
3039
+ const uint64_t *values_offsets,
3040
+ const uint8_t *values_data,
3041
+ size_t n_rows, chc_err *err)
3042
+ {
3043
+ if (ndim < 2)
3044
+ return chc__err_set(err, CHC_ERR_USAGE,
3045
+ "append_array_nested_string: ndim must be >= 2");
3046
+ const chc_type *leaf = chc__bld_array_leaf(t, ndim);
3047
+ if (!leaf || leaf->kind != CHC_STRING)
3048
+ return chc__err_set(err, CHC_ERR_TYPE,
3049
+ "append_array_nested_string: leaf is not String");
3050
+ if (n_rows != level_offsets_len[0])
3051
+ return chc__err_set(err, CHC_ERR_USAGE,
3052
+ "append_array_nested_string: n_rows != level_offsets_len[0]");
3053
+ chc__col_entry *e;
3054
+ int rc = chc__bld_add(bb, name, name_len, t,
3055
+ CHC__BLD_ARRAY_NESTED_STRING, n_rows, &e, err);
3056
+ if (rc != CHC_OK) return rc;
3057
+ e->nested.ndim = ndim;
3058
+ e->nested.level_offsets = level_offsets;
3059
+ e->nested.level_offsets_len = level_offsets_len;
3060
+ e->str.offsets = values_offsets;
3061
+ e->str.data = values_data;
3062
+ {
3063
+ size_t ilen = level_offsets_len[ndim - 1];
3064
+ e->inner_n = ilen ? (size_t) level_offsets[ndim - 1][ilen - 1] : 0;
3065
+ }
3066
+ return CHC_OK;
3067
+ }
3068
+
3069
+ int
3070
+ chc_block_builder_append_json_string(chc_block_builder *bb,
3071
+ const char *name, size_t name_len,
3072
+ const chc_type *t,
3073
+ const uint64_t *offsets,
3074
+ const uint8_t *data,
3075
+ size_t n_rows, chc_err *err)
3076
+ {
3077
+ if (!t || t->kind != CHC_JSON)
3078
+ return chc__err_set(err, CHC_ERR_TYPE,
3079
+ "append_json_string requires CHC_JSON type, got %d",
3080
+ (int) (t ? t->kind : 0));
3081
+ chc__col_entry *e;
3082
+ int rc = chc__bld_add(bb, name, name_len, t, CHC__BLD_JSON_STRING,
3083
+ n_rows, &e, err);
3084
+ if (rc != CHC_OK) return rc;
3085
+ e->str.offsets = offsets;
3086
+ e->str.data = data;
3087
+ e->inner_n = n_rows;
3088
+ return CHC_OK;
3089
+ }
3090
+
3091
+ int
3092
+ chc_block_builder_append_low_cardinality_string(chc_block_builder *bb,
3093
+ const char *name, size_t name_len,
3094
+ const chc_type *t,
3095
+ int key_size,
3096
+ const void *keys,
3097
+ const uint64_t *dict_offsets,
3098
+ const uint8_t *dict_data,
3099
+ size_t dict_n,
3100
+ size_t n_rows, chc_err *err)
3101
+ {
3102
+ if (!chc__bld_lc_inner_is_string(t))
3103
+ return chc__err_set(err, CHC_ERR_TYPE,
3104
+ "append_low_cardinality_string: type is not LowCardinality(String) or LowCardinality(Nullable(String))");
3105
+ if (key_size != 1 && key_size != 2 && key_size != 4 && key_size != 8)
3106
+ return chc__err_set(err, CHC_ERR_USAGE,
3107
+ "append_low_cardinality_string: key_size must be 1/2/4/8 (got %d)", key_size);
3108
+ chc__col_entry *e;
3109
+ int rc = chc__bld_add(bb, name, name_len, t, CHC__BLD_LC_STRING,
3110
+ n_rows, &e, err);
3111
+ if (rc != CHC_OK) return rc;
3112
+ e->lc.key_size = key_size;
3113
+ e->lc.keys = keys;
3114
+ e->str.offsets = dict_offsets;
3115
+ e->str.data = dict_data;
3116
+ e->inner_n = dict_n;
3117
+ return CHC_OK;
3118
+ }
3119
+
3120
+ /* -------- write helpers ---------- */
3121
+
3122
+ static int
3123
+ chc__write_bytes(chc_io *io, const void *buf, size_t n, chc_err *err)
3124
+ {
3125
+ return io->write(io->ud, buf, n, err);
3126
+ }
3127
+
3128
+ static int
3129
+ chc__write_varuint(chc_io *io, uint64_t v, chc_err *err)
3130
+ {
3131
+ uint8_t b[10];
3132
+ int n = 0;
3133
+ do {
3134
+ uint8_t byte = (uint8_t) (v & 0x7f);
3135
+ v >>= 7;
3136
+ if (v) byte |= 0x80;
3137
+ b[n++] = byte;
3138
+ } while (v);
3139
+ return chc__write_bytes(io, b, (size_t) n, err);
3140
+ }
3141
+
3142
+ static int
3143
+ chc__write_u32_le(chc_io *io, uint32_t v, chc_err *err)
3144
+ {
3145
+ v = chc__bswap32(v);
3146
+ return chc__write_bytes(io, &v, sizeof v, err);
3147
+ }
3148
+
3149
+ static int
3150
+ chc__write_u64_le(chc_io *io, uint64_t v, chc_err *err)
3151
+ {
3152
+ v = chc__bswap64(v);
3153
+ return chc__write_bytes(io, &v, sizeof v, err);
3154
+ }
3155
+
3156
+ static int
3157
+ chc__write_block_info(chc_io *io, chc_err *err)
3158
+ {
3159
+ int rc;
3160
+ uint8_t overflows = 0;
3161
+ if ((rc = chc__write_varuint(io, 1, err))) return rc;
3162
+ if ((rc = chc__write_bytes(io, &overflows, 1, err))) return rc;
3163
+ if ((rc = chc__write_varuint(io, 2, err))) return rc;
3164
+ if ((rc = chc__write_u32_le(io, (uint32_t) -1, err))) return rc;
3165
+ return chc__write_varuint(io, 0, err);
3166
+ }
3167
+
3168
+ static int
3169
+ chc__write_string(chc_io *io, const char *s, size_t n, chc_err *err)
3170
+ {
3171
+ int rc = chc__write_varuint(io, (uint64_t) n, err);
3172
+ if (rc != CHC_OK) return rc;
3173
+ if (n) return chc__write_bytes(io, s, n, err);
3174
+ return CHC_OK;
3175
+ }
3176
+
3177
+ /* Emit a contiguous u64 array as little-endian. */
3178
+ static int
3179
+ chc__write_u64_le_array(chc_io *io, const uint64_t *p, size_t n, chc_err *err)
3180
+ {
3181
+ #if CHC_BIG_ENDIAN
3182
+ for (size_t i = 0; i < n; i++) {
3183
+ int rc = chc__write_u64_le(io, p[i], err);
3184
+ if (rc != CHC_OK) return rc;
3185
+ }
3186
+ return CHC_OK;
3187
+ #else
3188
+ if (!n) return CHC_OK;
3189
+ return chc__write_bytes(io, p, n * sizeof(uint64_t), err);
3190
+ #endif
3191
+ }
3192
+
3193
+ /* Emit LC keys (host BO -> LE on BE hosts). */
3194
+ static int
3195
+ chc__write_keys_array(chc_io *io, const void *p, size_t n, int key_size,
3196
+ chc_err *err)
3197
+ {
3198
+ #if CHC_BIG_ENDIAN
3199
+ int rc;
3200
+ switch (key_size) {
3201
+ case 1: return n ? chc__write_bytes(io, p, n, err) : CHC_OK;
3202
+ case 2: { const uint16_t *a = p;
3203
+ for (size_t i = 0; i < n; i++) {
3204
+ uint8_t b[2] = { (uint8_t) a[i], (uint8_t) (a[i] >> 8) };
3205
+ if ((rc = chc__write_bytes(io, b, 2, err))) return rc;
3206
+ }
3207
+ return CHC_OK;
3208
+ }
3209
+ case 4: { const uint32_t *a = p;
3210
+ for (size_t i = 0; i < n; i++)
3211
+ if ((rc = chc__write_u32_le(io, a[i], err))) return rc;
3212
+ return CHC_OK;
3213
+ }
3214
+ case 8: { const uint64_t *a = p;
3215
+ for (size_t i = 0; i < n; i++)
3216
+ if ((rc = chc__write_u64_le(io, a[i], err))) return rc;
3217
+ return CHC_OK;
3218
+ }
3219
+ }
3220
+ return chc__err_set(err, CHC_ERR_USAGE, "bad key_size %d", key_size);
3221
+ #else
3222
+ if (!n) return CHC_OK;
3223
+ return chc__write_bytes(io, p, n * (size_t) key_size, err);
3224
+ #endif
3225
+ }
3226
+
3227
+ /* Emit a String column body (varuint length + bytes, per row). */
3228
+ static int
3229
+ chc__write_string_body(chc_io *io, const uint64_t *offsets,
3230
+ const uint8_t *data, size_t n, chc_err *err)
3231
+ {
3232
+ uint64_t prev = 0;
3233
+ for (size_t r = 0; r < n; r++) {
3234
+ uint64_t end = offsets[r];
3235
+ uint64_t len = end - prev;
3236
+ int rc = chc__write_varuint(io, len, err);
3237
+ if (rc != CHC_OK) return rc;
3238
+ if (len) {
3239
+ rc = chc__write_bytes(io, data + prev, (size_t) len, err);
3240
+ if (rc != CHC_OK) return rc;
3241
+ }
3242
+ prev = end;
3243
+ }
3244
+ return CHC_OK;
3245
+ }
3246
+
3247
+ /* Emit the entry's column body (no prefix). Assumes n_rows > 0. */
3248
+ static int
3249
+ chc__bld_write_body(chc_io *io, const chc__col_entry *e, chc_err *err)
3250
+ {
3251
+ int rc;
3252
+ switch (e->kind) {
3253
+ case CHC__BLD_FIXED:
3254
+ if (e->fixed.elem_size)
3255
+ return chc__write_bytes(io, e->fixed.data,
3256
+ e->n_rows * e->fixed.elem_size, err);
3257
+ return CHC_OK;
3258
+
3259
+ case CHC__BLD_STRING:
3260
+ case CHC__BLD_JSON_STRING:
3261
+ return chc__write_string_body(io, e->str.offsets, e->str.data,
3262
+ e->n_rows, err);
3263
+
3264
+ case CHC__BLD_NULL_FIXED:
3265
+ if ((rc = chc__write_bytes(io, e->nullable.null_map, e->n_rows, err))) return rc;
3266
+ if (e->fixed.elem_size)
3267
+ return chc__write_bytes(io, e->fixed.data,
3268
+ e->n_rows * e->fixed.elem_size, err);
3269
+ return CHC_OK;
3270
+
3271
+ case CHC__BLD_NULL_STRING:
3272
+ if ((rc = chc__write_bytes(io, e->nullable.null_map, e->n_rows, err))) return rc;
3273
+ return chc__write_string_body(io, e->str.offsets, e->str.data,
3274
+ e->n_rows, err);
3275
+
3276
+ case CHC__BLD_ARRAY_FIXED:
3277
+ if ((rc = chc__write_u64_le_array(io, e->array.offsets, e->n_rows, err)))
3278
+ return rc;
3279
+ if (e->inner_n && e->fixed.elem_size)
3280
+ return chc__write_bytes(io, e->fixed.data,
3281
+ e->inner_n * e->fixed.elem_size, err);
3282
+ return CHC_OK;
3283
+
3284
+ case CHC__BLD_ARRAY_STRING:
3285
+ if ((rc = chc__write_u64_le_array(io, e->array.offsets, e->n_rows, err)))
3286
+ return rc;
3287
+ return chc__write_string_body(io, e->str.offsets, e->str.data,
3288
+ e->inner_n, err);
3289
+
3290
+ case CHC__BLD_ARRAY_NESTED_FIXED:
3291
+ for (int lvl = 0; lvl < e->nested.ndim; lvl++) {
3292
+ if ((rc = chc__write_u64_le_array(io, e->nested.level_offsets[lvl],
3293
+ e->nested.level_offsets_len[lvl], err)))
3294
+ return rc;
3295
+ }
3296
+ if (e->inner_n && e->fixed.elem_size)
3297
+ return chc__write_bytes(io, e->fixed.data,
3298
+ e->inner_n * e->fixed.elem_size, err);
3299
+ return CHC_OK;
3300
+
3301
+ case CHC__BLD_ARRAY_NESTED_STRING:
3302
+ for (int lvl = 0; lvl < e->nested.ndim; lvl++) {
3303
+ if ((rc = chc__write_u64_le_array(io, e->nested.level_offsets[lvl],
3304
+ e->nested.level_offsets_len[lvl], err)))
3305
+ return rc;
3306
+ }
3307
+ return chc__write_string_body(io, e->str.offsets, e->str.data,
3308
+ e->inner_n, err);
3309
+
3310
+ case CHC__BLD_LC_STRING: {
3311
+ uint64_t flags = 0;
3312
+ switch (e->lc.key_size) {
3313
+ case 1: flags |= 0; break;
3314
+ case 2: flags |= 1; break;
3315
+ case 4: flags |= 2; break;
3316
+ case 8: flags |= 3; break;
3317
+ }
3318
+ flags |= CHC__LC_HAS_ADDITIONAL_KEYS;
3319
+ flags |= CHC__LC_NEED_UPDATE_DICT;
3320
+ if ((rc = chc__write_u64_le(io, flags, err))) return rc;
3321
+ if ((rc = chc__write_u64_le(io, (uint64_t) e->inner_n, err))) return rc;
3322
+ if ((rc = chc__write_string_body(io, e->str.offsets, e->str.data,
3323
+ e->inner_n, err))) return rc;
3324
+ if ((rc = chc__write_u64_le(io, (uint64_t) e->n_rows, err))) return rc;
3325
+ return chc__write_keys_array(io, e->lc.keys, e->n_rows,
3326
+ e->lc.key_size, err);
3327
+ }
3328
+ }
3329
+ return chc__err_set(err, CHC_ERR_USAGE, "unknown builder kind %d", e->kind);
3330
+ }
3331
+
3332
+ int
3333
+ chc_block_write(chc_io *io, const chc_block_builder *bb,
3334
+ const chc_block_opts *opts, chc_err *err)
3335
+ {
3336
+ chc_block_opts def = {};
3337
+ if (!opts) opts = &def;
3338
+
3339
+ if (opts->has_block_info) {
3340
+ int rc = chc__write_block_info(io, err);
3341
+ if (rc != CHC_OK) return rc;
3342
+ }
3343
+
3344
+ size_t n_rows = bb->n_rows_set ? bb->n_rows : 0;
3345
+ int rc = chc__write_varuint(io, (uint64_t) bb->n_cols, err);
3346
+ if (rc != CHC_OK) return rc;
3347
+ rc = chc__write_varuint(io, (uint64_t) n_rows, err);
3348
+ if (rc != CHC_OK) return rc;
3349
+
3350
+ for (size_t i = 0; i < bb->n_cols; i++) {
3351
+ const chc__col_entry *e = &bb->cols[i];
3352
+ rc = chc__write_string(io, e->name, e->name_len, err);
3353
+ if (rc != CHC_OK) return rc;
3354
+
3355
+ /* Type name: legacy STRING path has no e->type; emit "String". */
3356
+ if (e->kind == CHC__BLD_STRING && !e->type) {
3357
+ rc = chc__write_string(io, "String", 6, err);
3358
+ } else {
3359
+ char tbuf[256];
3360
+ size_t need = chc_type_format(e->type, tbuf, sizeof tbuf);
3361
+ if (need >= sizeof tbuf)
3362
+ return chc__err_set(err, CHC_ERR_USAGE,
3363
+ "type name too long for inline buffer");
3364
+ rc = chc__write_string(io, tbuf, need, err);
3365
+ }
3366
+ if (rc != CHC_OK) return rc;
3367
+
3368
+ if (opts->has_custom_serialization) {
3369
+ uint8_t z = 0;
3370
+ rc = chc__write_bytes(io, &z, 1, err);
3371
+ if (rc != CHC_OK) return rc;
3372
+ }
3373
+
3374
+ if (e->n_rows == 0) continue;
3375
+
3376
+ /* LC and JSON prefixes use version 1. */
3377
+ if (e->kind == CHC__BLD_LC_STRING || e->kind == CHC__BLD_JSON_STRING) {
3378
+ rc = chc__write_u64_le(io, 1, err);
3379
+ if (rc != CHC_OK) return rc;
3380
+ }
3381
+
3382
+ rc = chc__bld_write_body(io, e, err);
3383
+ if (rc != CHC_OK) return rc;
3384
+ }
3385
+ return CHC_OK;
3386
+ }
3387
+
3388
+ #endif /* CHC_IMPLEMENTATION */
3389
+
3390
+ #ifdef __cplusplus
3391
+ }
3392
+ #endif
3393
+
3394
+ #endif /* CLICKHOUSE_H */