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,634 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* clickhouse-compression.h -- compressed-frame layout, CityHash128, codec
|
|
3
|
+
* vtable, and ready-made LZ4 / ZSTD chc_codec wrappers.
|
|
4
|
+
*
|
|
5
|
+
* Exactly one TU must `#define CHC_IMPLEMENTATION` before including;
|
|
6
|
+
* the implementation uses static helpers from clickhouse.h.
|
|
7
|
+
*
|
|
8
|
+
* LZ4 wrapper (chc_lz4_codec_init) is compiled in by default & pulls
|
|
9
|
+
* <lz4.h>; link -llz4. Define CHC_NO_LZ4 before including to opt out
|
|
10
|
+
* (drops the include & the wrapper).
|
|
11
|
+
*
|
|
12
|
+
* ZSTD wrapper (chc_zstd_codec_init) is compiled in by default & pulls
|
|
13
|
+
* <zstd.h>; link -lzstd. Define CHC_NO_ZSTD before including to opt out.
|
|
14
|
+
*
|
|
15
|
+
* Frame layout (matches ClickHouse server / clickhouse-cpp
|
|
16
|
+
* base/compressed.cpp):
|
|
17
|
+
*
|
|
18
|
+
* [ 16 B CityHash128 of the rest of the frame ]
|
|
19
|
+
* [ 1 B method (0x82 LZ4, 0x90 ZSTD, 0x02 none) ]
|
|
20
|
+
* [ 4 B LE: compressed_size_with_header (= 9 + payload bytes)]
|
|
21
|
+
* [ 4 B LE: original_size ]
|
|
22
|
+
* [ payload ]
|
|
23
|
+
*
|
|
24
|
+
* The CityHash128 covers the method byte, the two size fields, and the
|
|
25
|
+
* payload bytes -- i.e. everything starting at offset 16.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
#ifndef CLICKHOUSE_COMPRESSION_H
|
|
29
|
+
#define CLICKHOUSE_COMPRESSION_H
|
|
30
|
+
|
|
31
|
+
#include <stdbool.h>
|
|
32
|
+
#include <stddef.h>
|
|
33
|
+
#include <stdint.h>
|
|
34
|
+
|
|
35
|
+
#include "clickhouse.h"
|
|
36
|
+
|
|
37
|
+
#ifdef __cplusplus
|
|
38
|
+
extern "C" {
|
|
39
|
+
#endif
|
|
40
|
+
|
|
41
|
+
/* Compression mode for client Data packets. Shared between
|
|
42
|
+
* clickhouse-client.h and clickhouse-compression.h so consumers can
|
|
43
|
+
* include the latter in isolation if driving frame format directly. */
|
|
44
|
+
typedef enum chc_compression {
|
|
45
|
+
CHC_COMP_NONE = 0,
|
|
46
|
+
CHC_COMP_LZ4 = 1,
|
|
47
|
+
CHC_COMP_ZSTD = 2,
|
|
48
|
+
} chc_compression;
|
|
49
|
+
|
|
50
|
+
typedef struct chc_codec chc_codec;
|
|
51
|
+
|
|
52
|
+
struct chc_codec {
|
|
53
|
+
void *ud;
|
|
54
|
+
|
|
55
|
+
/* Compress src[0..src_len] into dst[0..dst_cap]. On success returns
|
|
56
|
+
* CHC_OK and writes compressed-size into *dst_n. On insufficient
|
|
57
|
+
* destination capacity returns CHC_ERR_OOM. */
|
|
58
|
+
int (*lz4_compress) (void *ud, const void *src, size_t src_len,
|
|
59
|
+
void *dst, size_t dst_cap, size_t *dst_n,
|
|
60
|
+
chc_err *err);
|
|
61
|
+
/* original_size is the known uncompressed payload length. */
|
|
62
|
+
int (*lz4_decompress)(void *ud, const void *src, size_t src_len,
|
|
63
|
+
void *dst, size_t original_size,
|
|
64
|
+
chc_err *err);
|
|
65
|
+
|
|
66
|
+
int (*zstd_compress) (void *ud, const void *src, size_t src_len,
|
|
67
|
+
void *dst, size_t dst_cap, size_t *dst_n,
|
|
68
|
+
chc_err *err);
|
|
69
|
+
int (*zstd_decompress)(void *ud, const void *src, size_t src_len,
|
|
70
|
+
void *dst, size_t original_size,
|
|
71
|
+
chc_err *err);
|
|
72
|
+
|
|
73
|
+
/* Worst-case compressed size for a payload of src_len bytes. May be
|
|
74
|
+
* NULL; chc__compress_emit_frame() falls back to src_len + 256 +
|
|
75
|
+
* src_len/255 in that case (LZ4's classic bound formula). */
|
|
76
|
+
size_t (*lz4_bound) (size_t src_len);
|
|
77
|
+
size_t (*zstd_bound)(size_t src_len);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/* Default chunk size for outgoing frames (matches clickhouse-cpp). */
|
|
81
|
+
#define CHC_COMPRESS_MAX_CHUNK 65535u
|
|
82
|
+
|
|
83
|
+
/* Internal-but-exported: 128-bit CityHash. Returned as the two 64-bit
|
|
84
|
+
* halves the wire-format encodes (lo first, hi second). Frozen variant
|
|
85
|
+
* matching CH server / clickhouse-cpp. */
|
|
86
|
+
void chc_cityhash128(const void *data, size_t len,
|
|
87
|
+
uint64_t *out_lo, uint64_t *out_hi);
|
|
88
|
+
|
|
89
|
+
#ifndef CHC_NO_LZ4
|
|
90
|
+
/* Fill lz4_* slots of `out` with wrappers around <lz4.h>. Leaves zstd_*
|
|
91
|
+
* untouched, so callers wanting both codecs can call both init helpers
|
|
92
|
+
* on the same struct in either order. Caller links -llz4. */
|
|
93
|
+
void chc_lz4_codec_init(chc_codec *out);
|
|
94
|
+
#endif
|
|
95
|
+
|
|
96
|
+
#ifndef CHC_NO_ZSTD
|
|
97
|
+
/* Fill zstd_* slots of `out` with wrappers around <zstd.h>. Leaves lz4_*
|
|
98
|
+
* untouched. Caller links -lzstd. */
|
|
99
|
+
void chc_zstd_codec_init(chc_codec *out);
|
|
100
|
+
#endif
|
|
101
|
+
|
|
102
|
+
#ifdef CHC_IMPLEMENTATION
|
|
103
|
+
|
|
104
|
+
#include <limits.h>
|
|
105
|
+
#include <string.h>
|
|
106
|
+
|
|
107
|
+
/* ============================================================
|
|
108
|
+
* CityHash128 (frozen v1.0.3 variant, ported from city.cc).
|
|
109
|
+
* Original: Copyright (c) 2011 Google, Inc. (MIT licence).
|
|
110
|
+
* Short-string helpers live in clickhouse.h; this block layers
|
|
111
|
+
* the 128-bit driver on top.
|
|
112
|
+
* ============================================================ */
|
|
113
|
+
|
|
114
|
+
static uint64_t chc__city_rotate(uint64_t v, int s)
|
|
115
|
+
{
|
|
116
|
+
return s == 0 ? v : ((v >> s) | (v << (64 - s)));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
typedef struct { uint64_t a, b; } chc__u128;
|
|
120
|
+
|
|
121
|
+
static chc__u128
|
|
122
|
+
chc__city_weak_seeds(uint64_t w, uint64_t x, uint64_t y, uint64_t z,
|
|
123
|
+
uint64_t a, uint64_t b)
|
|
124
|
+
{
|
|
125
|
+
a += w;
|
|
126
|
+
b = chc__city_rotate(b + a + z, 21);
|
|
127
|
+
uint64_t c = a;
|
|
128
|
+
a += x;
|
|
129
|
+
a += y;
|
|
130
|
+
b += chc__city_rotate(a, 44);
|
|
131
|
+
chc__u128 r = { a + z, b + c };
|
|
132
|
+
return r;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static chc__u128 chc__city_weak_s(const char *s, uint64_t a, uint64_t b)
|
|
136
|
+
{
|
|
137
|
+
return chc__city_weak_seeds(chc__city_fetch64(s),
|
|
138
|
+
chc__city_fetch64(s + 8),
|
|
139
|
+
chc__city_fetch64(s + 16),
|
|
140
|
+
chc__city_fetch64(s + 24),
|
|
141
|
+
a, b);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static chc__u128
|
|
145
|
+
chc__city_murmur(const char *s, size_t len, chc__u128 seed)
|
|
146
|
+
{
|
|
147
|
+
uint64_t a = seed.a;
|
|
148
|
+
uint64_t b = seed.b;
|
|
149
|
+
uint64_t c = 0;
|
|
150
|
+
uint64_t d = 0;
|
|
151
|
+
if (len <= 16) {
|
|
152
|
+
a = chc__city_shift_mix(a * chc__city_k1) * chc__city_k1;
|
|
153
|
+
c = b * chc__city_k1 + chc__city_hash_len_0_to_16(s, len);
|
|
154
|
+
d = chc__city_shift_mix(a + (len >= 8 ? chc__city_fetch64(s) : c));
|
|
155
|
+
} else {
|
|
156
|
+
c = chc__city_hash_len_16(chc__city_fetch64(s + len - 8) + chc__city_k1, a);
|
|
157
|
+
d = chc__city_hash_len_16(b + len, c + chc__city_fetch64(s + len - 16));
|
|
158
|
+
a += d;
|
|
159
|
+
do {
|
|
160
|
+
a ^= chc__city_shift_mix(chc__city_fetch64(s) * chc__city_k1) * chc__city_k1;
|
|
161
|
+
a *= chc__city_k1;
|
|
162
|
+
b ^= a;
|
|
163
|
+
c ^= chc__city_shift_mix(chc__city_fetch64(s + 8) * chc__city_k1) * chc__city_k1;
|
|
164
|
+
c *= chc__city_k1;
|
|
165
|
+
d ^= c;
|
|
166
|
+
s += 16;
|
|
167
|
+
len -= 16;
|
|
168
|
+
} while (len > 16);
|
|
169
|
+
}
|
|
170
|
+
a = chc__city_hash_len_16(a, c);
|
|
171
|
+
b = chc__city_hash_len_16(d, b);
|
|
172
|
+
chc__u128 r = { a ^ b, chc__city_hash_len_16(b, a) };
|
|
173
|
+
return r;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static chc__u128
|
|
177
|
+
chc__city_hash128_with_seed(const char *s, size_t len, chc__u128 seed)
|
|
178
|
+
{
|
|
179
|
+
if (len < 128) return chc__city_murmur(s, len, seed);
|
|
180
|
+
|
|
181
|
+
chc__u128 v = {}, w = {};
|
|
182
|
+
uint64_t x = seed.a, y = seed.b;
|
|
183
|
+
uint64_t z = len * chc__city_k1;
|
|
184
|
+
v.a = chc__city_rotate(y ^ chc__city_k1, 49) * chc__city_k1 + chc__city_fetch64(s);
|
|
185
|
+
v.b = chc__city_rotate(v.a, 42) * chc__city_k1 + chc__city_fetch64(s + 8);
|
|
186
|
+
w.a = chc__city_rotate(y + z, 35) * chc__city_k1 + x;
|
|
187
|
+
w.b = chc__city_rotate(x + chc__city_fetch64(s + 88), 53) * chc__city_k1;
|
|
188
|
+
|
|
189
|
+
do {
|
|
190
|
+
x = chc__city_rotate(x + y + v.a + chc__city_fetch64(s + 16), 37) * chc__city_k1;
|
|
191
|
+
y = chc__city_rotate(y + v.b + chc__city_fetch64(s + 48), 42) * chc__city_k1;
|
|
192
|
+
x ^= w.b; y ^= v.a;
|
|
193
|
+
z = chc__city_rotate(z ^ w.a, 33);
|
|
194
|
+
v = chc__city_weak_s(s, v.b * chc__city_k1, x + w.a);
|
|
195
|
+
w = chc__city_weak_s(s + 32, z + w.b, y);
|
|
196
|
+
{ uint64_t tmp = z; z = x; x = tmp; }
|
|
197
|
+
s += 64;
|
|
198
|
+
x = chc__city_rotate(x + y + v.a + chc__city_fetch64(s + 16), 37) * chc__city_k1;
|
|
199
|
+
y = chc__city_rotate(y + v.b + chc__city_fetch64(s + 48), 42) * chc__city_k1;
|
|
200
|
+
x ^= w.b; y ^= v.a;
|
|
201
|
+
z = chc__city_rotate(z ^ w.a, 33);
|
|
202
|
+
v = chc__city_weak_s(s, v.b * chc__city_k1, x + w.a);
|
|
203
|
+
w = chc__city_weak_s(s + 32, z + w.b, y);
|
|
204
|
+
{ uint64_t tmp = z; z = x; x = tmp; }
|
|
205
|
+
s += 64;
|
|
206
|
+
len -= 128;
|
|
207
|
+
} while (len >= 128);
|
|
208
|
+
|
|
209
|
+
y += chc__city_rotate(w.a, 37) * chc__city_k0 + z;
|
|
210
|
+
x += chc__city_rotate(v.a + z, 49) * chc__city_k0;
|
|
211
|
+
for (size_t td = 0; td < len; ) {
|
|
212
|
+
td += 32;
|
|
213
|
+
y = chc__city_rotate(y - x, 42) * chc__city_k0 + v.b;
|
|
214
|
+
w.a += chc__city_fetch64(s + len - td + 16);
|
|
215
|
+
x = chc__city_rotate(x, 49) * chc__city_k0 + w.a;
|
|
216
|
+
w.a += v.a;
|
|
217
|
+
v = chc__city_weak_s(s + len - td, v.a, v.b);
|
|
218
|
+
}
|
|
219
|
+
x = chc__city_hash_len_16(x, v.a);
|
|
220
|
+
y = chc__city_hash_len_16(y, w.a);
|
|
221
|
+
chc__u128 r = { chc__city_hash_len_16(x + v.b, w.b) + y,
|
|
222
|
+
chc__city_hash_len_16(x + w.b, y + v.b) };
|
|
223
|
+
return r;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
static chc__u128 chc__city_hash128_impl(const char *s, size_t len)
|
|
227
|
+
{
|
|
228
|
+
chc__u128 seed;
|
|
229
|
+
if (len >= 16) {
|
|
230
|
+
seed.a = chc__city_fetch64(s) ^ chc__city_k3;
|
|
231
|
+
seed.b = chc__city_fetch64(s + 8);
|
|
232
|
+
return chc__city_hash128_with_seed(s + 16, len - 16, seed);
|
|
233
|
+
} else if (len >= 8) {
|
|
234
|
+
seed.a = chc__city_fetch64(s) ^ (len * chc__city_k0);
|
|
235
|
+
seed.b = chc__city_fetch64(s + len - 8) ^ chc__city_k1;
|
|
236
|
+
return chc__city_hash128_with_seed(NULL, 0, seed);
|
|
237
|
+
} else {
|
|
238
|
+
seed.a = chc__city_k0;
|
|
239
|
+
seed.b = chc__city_k1;
|
|
240
|
+
return chc__city_hash128_with_seed(s, len, seed);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
void
|
|
245
|
+
chc_cityhash128(const void *data, size_t len,
|
|
246
|
+
uint64_t *out_lo, uint64_t *out_hi)
|
|
247
|
+
{
|
|
248
|
+
chc__u128 r = chc__city_hash128_impl((const char *) data, len);
|
|
249
|
+
*out_lo = r.a;
|
|
250
|
+
*out_hi = r.b;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* ============================================================
|
|
254
|
+
* Frame I/O
|
|
255
|
+
* ============================================================ */
|
|
256
|
+
|
|
257
|
+
#define CHC__COMP_LZ4 0x82u
|
|
258
|
+
#define CHC__COMP_ZSTD 0x90u
|
|
259
|
+
#define CHC__COMP_HEADER_BYTES 9u
|
|
260
|
+
|
|
261
|
+
/* lz4-style fallback bound. */
|
|
262
|
+
static size_t chc__comp_default_bound(size_t n) { return n + 256 + n / 255; }
|
|
263
|
+
|
|
264
|
+
/* Write one compressed frame to io. Caller has already supplied the
|
|
265
|
+
* codec for the chosen method. */
|
|
266
|
+
static int
|
|
267
|
+
chc__comp_emit_frame(chc_io *io, const chc_codec *codec, chc_compression m,
|
|
268
|
+
const void *src, size_t src_len,
|
|
269
|
+
const chc_alloc *al, chc_err *err)
|
|
270
|
+
{
|
|
271
|
+
int rc;
|
|
272
|
+
uint8_t method;
|
|
273
|
+
size_t bound;
|
|
274
|
+
switch (m) {
|
|
275
|
+
case CHC_COMP_LZ4:
|
|
276
|
+
method = CHC__COMP_LZ4;
|
|
277
|
+
bound = codec->lz4_bound ? codec->lz4_bound(src_len)
|
|
278
|
+
: chc__comp_default_bound(src_len);
|
|
279
|
+
break;
|
|
280
|
+
case CHC_COMP_ZSTD:
|
|
281
|
+
method = CHC__COMP_ZSTD;
|
|
282
|
+
bound = codec->zstd_bound ? codec->zstd_bound(src_len)
|
|
283
|
+
: chc__comp_default_bound(src_len);
|
|
284
|
+
break;
|
|
285
|
+
default:
|
|
286
|
+
return chc__err_set(err, CHC_ERR_USAGE,
|
|
287
|
+
"chc__comp_emit_frame: unsupported method");
|
|
288
|
+
}
|
|
289
|
+
size_t buf_cap = CHC__COMP_HEADER_BYTES + bound;
|
|
290
|
+
uint8_t *buf = chc__alloc(al, buf_cap, err);
|
|
291
|
+
if (!buf) return CHC_ERR_OOM;
|
|
292
|
+
|
|
293
|
+
size_t comp_sz = 0;
|
|
294
|
+
if (m == CHC_COMP_LZ4) {
|
|
295
|
+
rc = codec->lz4_compress(codec->ud, src, src_len,
|
|
296
|
+
buf + CHC__COMP_HEADER_BYTES,
|
|
297
|
+
buf_cap - CHC__COMP_HEADER_BYTES,
|
|
298
|
+
&comp_sz, err);
|
|
299
|
+
} else {
|
|
300
|
+
rc = codec->zstd_compress(codec->ud, src, src_len,
|
|
301
|
+
buf + CHC__COMP_HEADER_BYTES,
|
|
302
|
+
buf_cap - CHC__COMP_HEADER_BYTES,
|
|
303
|
+
&comp_sz, err);
|
|
304
|
+
}
|
|
305
|
+
if (rc != CHC_OK) { al->free(al->ud, buf, buf_cap); return rc; }
|
|
306
|
+
|
|
307
|
+
uint32_t comp_with_hdr = (uint32_t) (comp_sz + CHC__COMP_HEADER_BYTES);
|
|
308
|
+
uint32_t orig = (uint32_t) src_len;
|
|
309
|
+
uint32_t comp_le = chc__bswap32(comp_with_hdr);
|
|
310
|
+
uint32_t orig_le = chc__bswap32(orig);
|
|
311
|
+
buf[0] = method;
|
|
312
|
+
memcpy(buf + 1, &comp_le, sizeof comp_le);
|
|
313
|
+
memcpy(buf + 5, &orig_le, sizeof orig_le);
|
|
314
|
+
|
|
315
|
+
uint64_t lo, hi;
|
|
316
|
+
chc_cityhash128(buf, CHC__COMP_HEADER_BYTES + comp_sz, &lo, &hi);
|
|
317
|
+
|
|
318
|
+
rc = chc__write_u64_le(io, lo, err);
|
|
319
|
+
if (rc == CHC_OK) rc = chc__write_u64_le(io, hi, err);
|
|
320
|
+
if (rc == CHC_OK) rc = chc__write_bytes(io, buf,
|
|
321
|
+
CHC__COMP_HEADER_BYTES + comp_sz, err);
|
|
322
|
+
al->free(al->ud, buf, buf_cap);
|
|
323
|
+
return rc;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* Mem sink chc_io. Used to capture a block's bytes before compressing. */
|
|
327
|
+
typedef struct chc__mem_sink {
|
|
328
|
+
uint8_t *buf;
|
|
329
|
+
size_t cap;
|
|
330
|
+
size_t len;
|
|
331
|
+
const chc_alloc *al;
|
|
332
|
+
int oom;
|
|
333
|
+
} chc__mem_sink;
|
|
334
|
+
|
|
335
|
+
static int
|
|
336
|
+
chc__mem_sink_write(void *ud, const void *p, size_t n, chc_err *err)
|
|
337
|
+
{
|
|
338
|
+
chc__mem_sink *s = ud;
|
|
339
|
+
if (s->oom) return chc__err_set(err, CHC_ERR_OOM, "mem sink oom");
|
|
340
|
+
if (n > SIZE_MAX - s->len) {
|
|
341
|
+
s->oom = 1;
|
|
342
|
+
return chc__err_set(err, CHC_ERR_OOM, "mem sink size overflow");
|
|
343
|
+
}
|
|
344
|
+
size_t need = s->len + n;
|
|
345
|
+
if (need > s->cap) {
|
|
346
|
+
size_t new_cap = s->cap ? s->cap : 4096;
|
|
347
|
+
while (new_cap < need) {
|
|
348
|
+
if (new_cap > SIZE_MAX / 2) { new_cap = need; break; }
|
|
349
|
+
new_cap *= 2;
|
|
350
|
+
}
|
|
351
|
+
uint8_t *nb = chc__realloc(s->al, s->buf, s->cap, new_cap, err);
|
|
352
|
+
if (!nb) { s->oom = 1; return CHC_ERR_OOM; }
|
|
353
|
+
s->buf = nb; s->cap = new_cap;
|
|
354
|
+
}
|
|
355
|
+
memcpy(s->buf + s->len, p, n);
|
|
356
|
+
s->len += n;
|
|
357
|
+
return CHC_OK;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
CHC_MAYBE_UNUSED static void
|
|
361
|
+
chc__mem_sink_init(chc__mem_sink *s, chc_io *io, const chc_alloc *al)
|
|
362
|
+
{
|
|
363
|
+
*s = (chc__mem_sink) { .al = al };
|
|
364
|
+
*io = (chc_io) { .ud = s, .write = chc__mem_sink_write };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
CHC_MAYBE_UNUSED static void
|
|
368
|
+
chc__mem_sink_free(chc__mem_sink *s)
|
|
369
|
+
{
|
|
370
|
+
s->al->free(s->al->ud, s->buf, s->cap);
|
|
371
|
+
s->buf = NULL;
|
|
372
|
+
s->cap = s->len = 0;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* Emit a buffered block as one or more compressed frames to io. */
|
|
376
|
+
static int
|
|
377
|
+
chc__comp_emit_chunks(chc_io *io, const chc_codec *codec, chc_compression m,
|
|
378
|
+
const void *src, size_t src_len,
|
|
379
|
+
const chc_alloc *al, chc_err *err)
|
|
380
|
+
{
|
|
381
|
+
const uint8_t *p = src;
|
|
382
|
+
size_t left = src_len;
|
|
383
|
+
while (left) {
|
|
384
|
+
size_t take = left > CHC_COMPRESS_MAX_CHUNK
|
|
385
|
+
? CHC_COMPRESS_MAX_CHUNK : left;
|
|
386
|
+
int rc = chc__comp_emit_frame(io, codec, m, p, take, al, err);
|
|
387
|
+
if (rc != CHC_OK) return rc;
|
|
388
|
+
p += take;
|
|
389
|
+
left -= take;
|
|
390
|
+
}
|
|
391
|
+
return CHC_OK;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/* ---------- decompression side ---------- */
|
|
395
|
+
|
|
396
|
+
/* Adapter chc_io that produces decompressed bytes from a compressed
|
|
397
|
+
* frame stream sourced from a chc_in. Each "frame" is read on demand;
|
|
398
|
+
* we hold one frame's uncompressed bytes in scratch. */
|
|
399
|
+
typedef struct chc__decomp_src {
|
|
400
|
+
chc_in *raw;
|
|
401
|
+
const chc_codec *codec;
|
|
402
|
+
const chc_alloc *al;
|
|
403
|
+
uint8_t *frame_buf;
|
|
404
|
+
size_t frame_cap;
|
|
405
|
+
size_t frame_pos;
|
|
406
|
+
size_t frame_fill;
|
|
407
|
+
uint8_t *comp_buf;
|
|
408
|
+
size_t comp_cap;
|
|
409
|
+
} chc__decomp_src;
|
|
410
|
+
|
|
411
|
+
static int
|
|
412
|
+
chc__decomp_read_frame(chc__decomp_src *s, chc_err *err)
|
|
413
|
+
{
|
|
414
|
+
int rc;
|
|
415
|
+
uint64_t lo, hi;
|
|
416
|
+
rc = chc__read_u64_le(s->raw, &lo, err); if (rc) return rc;
|
|
417
|
+
rc = chc__read_u64_le(s->raw, &hi, err); if (rc) return rc;
|
|
418
|
+
uint8_t header[CHC__COMP_HEADER_BYTES];
|
|
419
|
+
rc = chc__read_bytes(s->raw, header, CHC__COMP_HEADER_BYTES, err);
|
|
420
|
+
if (rc) return rc;
|
|
421
|
+
uint8_t method = header[0];
|
|
422
|
+
uint32_t comp_with_hdr, orig;
|
|
423
|
+
memcpy(&comp_with_hdr, header + 1, sizeof comp_with_hdr);
|
|
424
|
+
memcpy(&orig, header + 5, sizeof orig);
|
|
425
|
+
comp_with_hdr = chc__bswap32(comp_with_hdr);
|
|
426
|
+
orig = chc__bswap32(orig);
|
|
427
|
+
if (comp_with_hdr < CHC__COMP_HEADER_BYTES)
|
|
428
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
429
|
+
"compressed frame too short: %u", comp_with_hdr);
|
|
430
|
+
if (comp_with_hdr > 0x40000000u)
|
|
431
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
432
|
+
"compressed frame oversized: %u", comp_with_hdr);
|
|
433
|
+
/* LZ4 API sizes are int; reject dst outside domain before (int)
|
|
434
|
+
* narrowing in codec adapter. src side bounded by frame cap above */
|
|
435
|
+
if (method == CHC__COMP_LZ4 && orig > (uint32_t) INT_MAX)
|
|
436
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
437
|
+
"LZ4 original size oversized: %u", orig);
|
|
438
|
+
|
|
439
|
+
size_t payload = comp_with_hdr - CHC__COMP_HEADER_BYTES;
|
|
440
|
+
/* Capture the bytes hashed: header (9B) + payload. */
|
|
441
|
+
if (comp_with_hdr > s->comp_cap) {
|
|
442
|
+
uint8_t *nb = chc__realloc(s->al, s->comp_buf, s->comp_cap,
|
|
443
|
+
comp_with_hdr, err);
|
|
444
|
+
if (!nb) return CHC_ERR_OOM;
|
|
445
|
+
s->comp_buf = nb; s->comp_cap = comp_with_hdr;
|
|
446
|
+
}
|
|
447
|
+
memcpy(s->comp_buf, header, CHC__COMP_HEADER_BYTES);
|
|
448
|
+
rc = chc__read_bytes(s->raw, s->comp_buf + CHC__COMP_HEADER_BYTES,
|
|
449
|
+
payload, err);
|
|
450
|
+
if (rc) return rc;
|
|
451
|
+
|
|
452
|
+
uint64_t got_lo, got_hi;
|
|
453
|
+
chc_cityhash128(s->comp_buf, comp_with_hdr, &got_lo, &got_hi);
|
|
454
|
+
if (got_lo != lo || got_hi != hi)
|
|
455
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
456
|
+
"compressed frame hash mismatch");
|
|
457
|
+
|
|
458
|
+
if (orig > s->frame_cap) {
|
|
459
|
+
uint8_t *nb = chc__realloc(s->al, s->frame_buf, s->frame_cap,
|
|
460
|
+
orig, err);
|
|
461
|
+
if (!nb) return CHC_ERR_OOM;
|
|
462
|
+
s->frame_buf = nb; s->frame_cap = orig;
|
|
463
|
+
}
|
|
464
|
+
switch (method) {
|
|
465
|
+
case CHC__COMP_LZ4:
|
|
466
|
+
if (!s->codec || !s->codec->lz4_decompress)
|
|
467
|
+
return chc__err_set(err, CHC_ERR_USAGE,
|
|
468
|
+
"LZ4 frame received but no codec configured");
|
|
469
|
+
rc = s->codec->lz4_decompress(s->codec->ud,
|
|
470
|
+
s->comp_buf + CHC__COMP_HEADER_BYTES, payload,
|
|
471
|
+
s->frame_buf, orig, err);
|
|
472
|
+
if (rc) return rc;
|
|
473
|
+
break;
|
|
474
|
+
case CHC__COMP_ZSTD:
|
|
475
|
+
if (!s->codec || !s->codec->zstd_decompress)
|
|
476
|
+
return chc__err_set(err, CHC_ERR_USAGE,
|
|
477
|
+
"ZSTD frame received but no codec configured");
|
|
478
|
+
rc = s->codec->zstd_decompress(s->codec->ud,
|
|
479
|
+
s->comp_buf + CHC__COMP_HEADER_BYTES, payload,
|
|
480
|
+
s->frame_buf, orig, err);
|
|
481
|
+
if (rc) return rc;
|
|
482
|
+
break;
|
|
483
|
+
default:
|
|
484
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
485
|
+
"unknown compression method 0x%02x", method);
|
|
486
|
+
}
|
|
487
|
+
s->frame_pos = 0;
|
|
488
|
+
s->frame_fill = orig;
|
|
489
|
+
return CHC_OK;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
static int
|
|
493
|
+
chc__decomp_io_read(void *ud, void *buf, size_t len, size_t *out_n,
|
|
494
|
+
chc_err *err)
|
|
495
|
+
{
|
|
496
|
+
chc__decomp_src *s = ud;
|
|
497
|
+
if (s->frame_pos == s->frame_fill) {
|
|
498
|
+
int rc = chc__decomp_read_frame(s, err);
|
|
499
|
+
if (rc) { *out_n = 0; return rc; }
|
|
500
|
+
}
|
|
501
|
+
size_t avail = s->frame_fill - s->frame_pos;
|
|
502
|
+
size_t take = len < avail ? len : avail;
|
|
503
|
+
memcpy(buf, s->frame_buf + s->frame_pos, take);
|
|
504
|
+
s->frame_pos += take;
|
|
505
|
+
*out_n = take;
|
|
506
|
+
return CHC_OK;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
static void
|
|
510
|
+
chc__decomp_src_init(chc__decomp_src *s, chc_in *raw, const chc_codec *codec,
|
|
511
|
+
const chc_alloc *al, chc_io *out_io)
|
|
512
|
+
{
|
|
513
|
+
*s = (chc__decomp_src) { .raw = raw, .codec = codec, .al = al };
|
|
514
|
+
*out_io = (chc_io) { .ud = s, .read = chc__decomp_io_read };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
static void
|
|
518
|
+
chc__decomp_src_free(chc__decomp_src *s)
|
|
519
|
+
{
|
|
520
|
+
s->al->free(s->al->ud, s->frame_buf, s->frame_cap);
|
|
521
|
+
s->al->free(s->al->ud, s->comp_buf, s->comp_cap);
|
|
522
|
+
s->frame_buf = s->comp_buf = NULL;
|
|
523
|
+
s->frame_cap = s->comp_cap = 0;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/* ============================================================
|
|
527
|
+
* LZ4 adapter (opt out with CHC_NO_LZ4 before including).
|
|
528
|
+
* ============================================================ */
|
|
529
|
+
|
|
530
|
+
#ifndef CHC_NO_LZ4
|
|
531
|
+
|
|
532
|
+
#include <lz4.h>
|
|
533
|
+
|
|
534
|
+
static int
|
|
535
|
+
chc__lz4_compress(void *ud, const void *src, size_t src_len,
|
|
536
|
+
void *dst, size_t dst_cap, size_t *dst_n, chc_err *err)
|
|
537
|
+
{
|
|
538
|
+
(void) ud;
|
|
539
|
+
if (src_len > (size_t) LZ4_MAX_INPUT_SIZE)
|
|
540
|
+
return chc__err_set(err, CHC_ERR_USAGE,
|
|
541
|
+
"LZ4 input too big: %zu", src_len);
|
|
542
|
+
int n = LZ4_compress_default((const char *) src, (char *) dst,
|
|
543
|
+
(int) src_len, (int) dst_cap);
|
|
544
|
+
if (n <= 0)
|
|
545
|
+
return chc__err_set(err, CHC_ERR_OOM,
|
|
546
|
+
"LZ4_compress_default failed (rc=%d)", n);
|
|
547
|
+
*dst_n = (size_t) n;
|
|
548
|
+
return CHC_OK;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
static int
|
|
552
|
+
chc__lz4_decompress(void *ud, const void *src, size_t src_len,
|
|
553
|
+
void *dst, size_t original, chc_err *err)
|
|
554
|
+
{
|
|
555
|
+
(void) ud;
|
|
556
|
+
int n = LZ4_decompress_safe((const char *) src, (char *) dst,
|
|
557
|
+
(int) src_len, (int) original);
|
|
558
|
+
if (n < 0 || (size_t) n != original)
|
|
559
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
560
|
+
"LZ4_decompress_safe rc=%d, expected %zu",
|
|
561
|
+
n, original);
|
|
562
|
+
return CHC_OK;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
static size_t chc__lz4_bound(size_t n) { return (size_t) LZ4_compressBound((int) n); }
|
|
566
|
+
|
|
567
|
+
void
|
|
568
|
+
chc_lz4_codec_init(chc_codec *out)
|
|
569
|
+
{
|
|
570
|
+
out->ud = NULL;
|
|
571
|
+
out->lz4_compress = chc__lz4_compress;
|
|
572
|
+
out->lz4_decompress = chc__lz4_decompress;
|
|
573
|
+
out->lz4_bound = chc__lz4_bound;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
#endif /* !CHC_NO_LZ4 */
|
|
577
|
+
|
|
578
|
+
/* ============================================================
|
|
579
|
+
* ZSTD adapter (opt out with CHC_NO_ZSTD before including).
|
|
580
|
+
* ============================================================ */
|
|
581
|
+
|
|
582
|
+
#ifndef CHC_NO_ZSTD
|
|
583
|
+
|
|
584
|
+
#include <zstd.h>
|
|
585
|
+
|
|
586
|
+
static int
|
|
587
|
+
chc__zstd_compress(void *ud, const void *src, size_t src_len,
|
|
588
|
+
void *dst, size_t dst_cap, size_t *dst_n, chc_err *err)
|
|
589
|
+
{
|
|
590
|
+
(void) ud;
|
|
591
|
+
size_t n = ZSTD_compress(dst, dst_cap, src, src_len, ZSTD_CLEVEL_DEFAULT);
|
|
592
|
+
if (ZSTD_isError(n))
|
|
593
|
+
return chc__err_set(err, CHC_ERR_OOM,
|
|
594
|
+
"ZSTD_compress failed: %s", ZSTD_getErrorName(n));
|
|
595
|
+
*dst_n = n;
|
|
596
|
+
return CHC_OK;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
static int
|
|
600
|
+
chc__zstd_decompress(void *ud, const void *src, size_t src_len,
|
|
601
|
+
void *dst, size_t original, chc_err *err)
|
|
602
|
+
{
|
|
603
|
+
(void) ud;
|
|
604
|
+
size_t n = ZSTD_decompress(dst, original, src, src_len);
|
|
605
|
+
if (ZSTD_isError(n))
|
|
606
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
607
|
+
"ZSTD_decompress failed: %s", ZSTD_getErrorName(n));
|
|
608
|
+
if (n != original)
|
|
609
|
+
return chc__err_set(err, CHC_ERR_PROTOCOL,
|
|
610
|
+
"ZSTD_decompress produced %zu bytes, expected %zu",
|
|
611
|
+
n, original);
|
|
612
|
+
return CHC_OK;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
static size_t chc__zstd_bound(size_t n) { return ZSTD_compressBound(n); }
|
|
616
|
+
|
|
617
|
+
void
|
|
618
|
+
chc_zstd_codec_init(chc_codec *out)
|
|
619
|
+
{
|
|
620
|
+
out->ud = NULL;
|
|
621
|
+
out->zstd_compress = chc__zstd_compress;
|
|
622
|
+
out->zstd_decompress = chc__zstd_decompress;
|
|
623
|
+
out->zstd_bound = chc__zstd_bound;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
#endif /* !CHC_NO_ZSTD */
|
|
627
|
+
|
|
628
|
+
#endif /* CHC_IMPLEMENTATION */
|
|
629
|
+
|
|
630
|
+
#ifdef __cplusplus
|
|
631
|
+
}
|
|
632
|
+
#endif
|
|
633
|
+
|
|
634
|
+
#endif /* CLICKHOUSE_COMPRESSION_H */
|