ch_connect 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.standard.yml +5 -0
- data/CHANGELOG.md +10 -1
- data/README.md +156 -87
- data/ext/ch_connect_native/ch_connect_native.c +1001 -0
- data/ext/ch_connect_native/extconf.rb +32 -0
- data/lib/ch_connect/config.rb +60 -19
- data/lib/ch_connect/connection.rb +382 -13
- data/lib/ch_connect/response.rb +0 -8
- data/lib/ch_connect/version.rb +1 -1
- data/lib/ch_connect.rb +14 -11
- data/vendor/clickhouse-c/LICENSE +203 -0
- data/vendor/clickhouse-c/VENDOR.md +24 -0
- data/vendor/clickhouse-c/clickhouse-async.h +301 -0
- data/vendor/clickhouse-c/clickhouse-client.h +995 -0
- data/vendor/clickhouse-c/clickhouse-compression.h +634 -0
- data/vendor/clickhouse-c/clickhouse.h +3394 -0
- metadata +19 -14
- data/lib/ch_connect/body_reader.rb +0 -79
- data/lib/ch_connect/http_transport.rb +0 -59
- data/lib/ch_connect/native_format_parser.rb +0 -405
- data/lib/ch_connect/transport_result.rb +0 -12
|
@@ -0,0 +1,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 */
|