multi_compress 0.3.5 → 0.6.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -0
  3. data/GET_STARTED.md +207 -0
  4. data/README.md +69 -0
  5. data/db_core/include/mcdb_format.h +113 -0
  6. data/db_core/src/mcdb_format.c +594 -0
  7. data/db_deployment/mysql/Makefile +48 -0
  8. data/db_deployment/mysql/README.md +61 -0
  9. data/db_deployment/mysql/bin/common +212 -0
  10. data/db_deployment/mysql/bin/disable +40 -0
  11. data/db_deployment/mysql/bin/doctor +64 -0
  12. data/db_deployment/mysql/bin/enable +61 -0
  13. data/db_deployment/mysql/bin/install +64 -0
  14. data/db_deployment/mysql/bin/status +67 -0
  15. data/db_deployment/mysql/bin/uninstall +47 -0
  16. data/db_deployment/mysql/bin/upgrade +127 -0
  17. data/db_deployment/mysql/bin/verify +14 -0
  18. data/db_deployment/postgres/Makefile +45 -0
  19. data/db_deployment/postgres/README.md +84 -0
  20. data/db_deployment/postgres/bin/doctor +61 -0
  21. data/db_deployment/postgres/bin/enable +138 -0
  22. data/db_deployment/postgres/bin/install +106 -0
  23. data/db_deployment/postgres/bin/uninstall +54 -0
  24. data/db_deployment/postgres/bin/verify +14 -0
  25. data/docs/database-envelope-v1.md +159 -0
  26. data/docs/database-envelope-v2.md +147 -0
  27. data/docs/rfcs/0002-mcdb2-dictionaries.md +75 -0
  28. data/exe/multi_compress +228 -0
  29. data/ext/multi_compress/extconf.rb +4 -0
  30. data/ext/multi_compress/multi_compress.c +62 -0
  31. data/lib/multi_compress/active_record.rb +61 -0
  32. data/lib/multi_compress/codec.rb +164 -0
  33. data/lib/multi_compress/database.rb +312 -0
  34. data/lib/multi_compress/db_deployment.rb +868 -0
  35. data/lib/multi_compress/version.rb +1 -1
  36. data/multi_compress.gemspec +55 -0
  37. data/mysql_udf/Makefile +96 -0
  38. data/mysql_udf/README.md +140 -0
  39. data/mysql_udf/sql/examples.sql +16 -0
  40. data/mysql_udf/sql/install.sql +39 -0
  41. data/mysql_udf/sql/uninstall.sql +10 -0
  42. data/mysql_udf/sql/views.sql +20 -0
  43. data/mysql_udf/src/multi_compress_mysql.c +472 -0
  44. data/mysql_udf/src/mysql_udf_abi_57.h +34 -0
  45. data/mysql_udf/test/mcdb_cli.c +60 -0
  46. data/mysql_udf/test/run_e2e.sh +414 -0
  47. data/postgres_extension/Makefile +74 -0
  48. data/postgres_extension/README.md +160 -0
  49. data/postgres_extension/multi_compress.control +4 -0
  50. data/postgres_extension/multi_compress_pg.exports +24 -0
  51. data/postgres_extension/sql/examples.sql +23 -0
  52. data/postgres_extension/sql/multi_compress--0.5.0--0.6.0.sql +59 -0
  53. data/postgres_extension/sql/multi_compress--0.5.0.sql +24 -0
  54. data/postgres_extension/sql/multi_compress--0.6.0.sql +85 -0
  55. data/postgres_extension/sql/uninstall.sql +2 -0
  56. data/postgres_extension/sql/views.sql +7 -0
  57. data/postgres_extension/src/mcdb_format.c +2 -0
  58. data/postgres_extension/src/multi_compress_pg.c +381 -0
  59. data/postgres_extension/test/run_e2e.sh +411 -0
  60. metadata +56 -3
@@ -0,0 +1,594 @@
1
+ #include "mcdb_format.h"
2
+
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+ #include <string.h>
6
+
7
+ #include <zstd.h>
8
+
9
+ typedef char mcdb_v1_envelope_bound_check
10
+ [(MCDB_MAX_ENVELOPE_V1 == (unsigned)(MCDB_V1_HEADER_SIZE + ZSTD_COMPRESSBOUND(MCDB_MAX_OUTPUT)))
11
+ ? 1
12
+ : -1];
13
+ typedef char mcdb_v2_envelope_bound_check
14
+ [(MCDB_MAX_ENVELOPE_V2 == (unsigned)(MCDB_V2_HEADER_SIZE + ZSTD_COMPRESSBOUND(MCDB_MAX_OUTPUT)))
15
+ ? 1
16
+ : -1];
17
+
18
+ static uint32_t mcdb_crc32(const unsigned char *data, size_t len) {
19
+ uint32_t crc = 0xFFFFFFFFU;
20
+ size_t i;
21
+ int bit;
22
+
23
+ for (i = 0; i < len; i++) {
24
+ crc ^= data[i];
25
+ for (bit = 0; bit < 8; bit++)
26
+ crc = (crc & 1U) ? ((crc >> 1) ^ 0xEDB88320U) : (crc >> 1);
27
+ }
28
+ return crc ^ 0xFFFFFFFFU;
29
+ }
30
+
31
+ static uint64_t read_u64_le(const unsigned char *p) {
32
+ uint64_t v = 0;
33
+ int i;
34
+ for (i = 0; i < 8; i++)
35
+ v |= (uint64_t)p[i] << (8 * i);
36
+ return v;
37
+ }
38
+
39
+ static uint32_t read_u32_le(const unsigned char *p) {
40
+ return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
41
+ }
42
+
43
+ static int mcdb_is_valid_utf8(const unsigned char *s, size_t len) {
44
+ size_t i = 0;
45
+ while (i < len) {
46
+ unsigned char c = s[i];
47
+ size_t n;
48
+ unsigned char lo2 = 0x80;
49
+ unsigned char hi2 = 0xBF;
50
+ size_t k;
51
+
52
+ if (c < 0x80) {
53
+ if (c == 0)
54
+ return 0;
55
+ i++;
56
+ continue;
57
+ }
58
+ if ((c & 0xE0) == 0xC0) {
59
+ if (c < 0xC2)
60
+ return 0;
61
+ n = 2;
62
+ } else if ((c & 0xF0) == 0xE0) {
63
+ n = 3;
64
+ if (c == 0xE0)
65
+ lo2 = 0xA0;
66
+ if (c == 0xED)
67
+ hi2 = 0x9F;
68
+ } else if ((c & 0xF8) == 0xF0) {
69
+ if (c > 0xF4)
70
+ return 0;
71
+ n = 4;
72
+ if (c == 0xF0)
73
+ lo2 = 0x90;
74
+ if (c == 0xF4)
75
+ hi2 = 0x8F;
76
+ } else {
77
+ return 0;
78
+ }
79
+ if (i + n > len)
80
+ return 0;
81
+ for (k = 1; k < n; k++) {
82
+ unsigned char cc = s[i + k];
83
+ unsigned char lo = k == 1 ? lo2 : 0x80;
84
+ unsigned char hi = k == 1 ? hi2 : 0xBF;
85
+ if (cc < lo || cc > hi)
86
+ return 0;
87
+ }
88
+ i += n;
89
+ }
90
+ return 1;
91
+ }
92
+
93
+ mcdb_status mcdb_parse_header(const unsigned char *in, size_t in_len, mcdb_header *out_header) {
94
+ mcdb_header header;
95
+ size_t max_envelope;
96
+
97
+ if (in == NULL || in_len < MCDB_V1_HEADER_SIZE)
98
+ return MCDB_ERR_TRUNCATED;
99
+ if (in[0] != MCDB_MAGIC0 || in[1] != MCDB_MAGIC1 || in[2] != MCDB_MAGIC2 ||
100
+ in[3] != MCDB_MAGIC3)
101
+ return MCDB_ERR_MAGIC;
102
+ if (in[4] != MCDB_VERSION_V1 && in[4] != MCDB_VERSION_V2)
103
+ return MCDB_ERR_VERSION;
104
+
105
+ memset(&header, 0, sizeof(header));
106
+ header.version = in[4];
107
+ header.header_size =
108
+ header.version == MCDB_VERSION_V2 ? MCDB_V2_HEADER_SIZE : MCDB_V1_HEADER_SIZE;
109
+ max_envelope = header.version == MCDB_VERSION_V2 ? MCDB_MAX_ENVELOPE_V2 : MCDB_MAX_ENVELOPE_V1;
110
+
111
+ if (in_len < header.header_size)
112
+ return MCDB_ERR_TRUNCATED;
113
+ if (in_len > max_envelope)
114
+ return MCDB_ERR_ENVELOPE_SIZE;
115
+ if (in[5] != MCDB_CODEC_ZSTD)
116
+ return MCDB_ERR_CODEC;
117
+ if (in[6] != MCDB_FLAGS_NONE)
118
+ return MCDB_ERR_FLAGS;
119
+
120
+ header.original_size = read_u64_le(in + MCDB_ORIGINAL_SIZE_OFFSET);
121
+ if (header.original_size > MCDB_MAX_OUTPUT)
122
+ return MCDB_ERR_SIZE_LIMIT;
123
+
124
+ if (header.version == MCDB_VERSION_V2) {
125
+ header.dictionary_ref = read_u64_le(in + MCDB_DICTIONARY_REF_OFFSET);
126
+ if (header.dictionary_ref == 0 || header.dictionary_ref > MCDB_MAX_DICTIONARY_REF)
127
+ return MCDB_ERR_DICTIONARY_REF;
128
+ }
129
+
130
+ if (out_header)
131
+ *out_header = header;
132
+ return MCDB_OK;
133
+ }
134
+
135
+ mcdb_status mcdb_validate_header(const unsigned char *in, size_t in_len,
136
+ uint64_t *out_original_size) {
137
+ mcdb_header header;
138
+ mcdb_status st = mcdb_parse_header(in, in_len, &header);
139
+ if (st != MCDB_OK)
140
+ return st;
141
+ if (header.version != MCDB_VERSION_V1)
142
+ return MCDB_ERR_VERSION;
143
+ if (out_original_size)
144
+ *out_original_size = header.original_size;
145
+ return MCDB_OK;
146
+ }
147
+
148
+ mcdb_status mcdb_validate_v2_header(const unsigned char *in, size_t in_len,
149
+ uint64_t *out_original_size, uint64_t *out_dictionary_ref) {
150
+ mcdb_header header;
151
+ mcdb_status st = mcdb_parse_header(in, in_len, &header);
152
+ if (st != MCDB_OK)
153
+ return st;
154
+ if (header.version != MCDB_VERSION_V2)
155
+ return MCDB_ERR_VERSION;
156
+ if (out_original_size)
157
+ *out_original_size = header.original_size;
158
+ if (out_dictionary_ref)
159
+ *out_dictionary_ref = header.dictionary_ref;
160
+ return MCDB_OK;
161
+ }
162
+
163
+ mcdb_status mcdb_validate_dictionary(const unsigned char *dictionary, size_t dictionary_len,
164
+ uint32_t *out_zstd_dictionary_id) {
165
+ uint32_t dict_id;
166
+ if (dictionary == NULL || dictionary_len == 0 || dictionary_len > MCDB_MAX_DICTIONARY_BYTES)
167
+ return MCDB_ERR_DICTIONARY_SIZE;
168
+ dict_id = ZSTD_getDictID_fromDict(dictionary, dictionary_len);
169
+ if (dict_id == 0)
170
+ return MCDB_ERR_DICTIONARY_ID;
171
+ if (out_zstd_dictionary_id)
172
+ *out_zstd_dictionary_id = dict_id;
173
+ return MCDB_OK;
174
+ }
175
+
176
+ uint32_t mcdb_zstd_frame_dictionary_id(const unsigned char *frame, size_t frame_len) {
177
+ if (frame == NULL || frame_len == 0)
178
+ return 0;
179
+ return ZSTD_getDictID_fromFrame(frame, frame_len);
180
+ }
181
+
182
+ uint64_t mcdb_dictionary_ref(const unsigned char *in, size_t in_len, mcdb_status *out_status) {
183
+ mcdb_header header;
184
+ mcdb_status st = mcdb_parse_header(in, in_len, &header);
185
+ if (out_status)
186
+ *out_status = st;
187
+ if (st != MCDB_OK || header.version != MCDB_VERSION_V2) {
188
+ if (out_status && st == MCDB_OK)
189
+ *out_status = MCDB_ERR_VERSION;
190
+ return 0;
191
+ }
192
+ return header.dictionary_ref;
193
+ }
194
+
195
+ static mcdb_status mcdb_validate_frame(const unsigned char *in, size_t in_len,
196
+ const mcdb_header *header, const unsigned char **out_frame,
197
+ size_t *out_frame_len, char *errbuf) {
198
+ const unsigned char *frame = in + header->header_size;
199
+ size_t frame_len = in_len - header->header_size;
200
+ size_t frame_size;
201
+ unsigned long long content_size;
202
+
203
+ if (frame_len == 0) {
204
+ if (errbuf)
205
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: missing zstd frame");
206
+ return MCDB_ERR_DECOMPRESS;
207
+ }
208
+ frame_size = ZSTD_findFrameCompressedSize(frame, frame_len);
209
+ if (ZSTD_isError(frame_size)) {
210
+ if (errbuf)
211
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: invalid zstd frame: %s",
212
+ ZSTD_getErrorName(frame_size));
213
+ return MCDB_ERR_DECOMPRESS;
214
+ }
215
+ if (frame_size != frame_len) {
216
+ if (errbuf)
217
+ snprintf(errbuf, MCDB_ERRLEN,
218
+ "MCDB: zstd frame has trailing bytes or concatenated frames");
219
+ return MCDB_ERR_TRAILING_DATA;
220
+ }
221
+ content_size = ZSTD_getFrameContentSize(frame, frame_len);
222
+ if (content_size == ZSTD_CONTENTSIZE_ERROR || content_size == ZSTD_CONTENTSIZE_UNKNOWN ||
223
+ content_size != header->original_size) {
224
+ if (errbuf)
225
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: zstd frame content size does not match header");
226
+ return MCDB_ERR_FRAME_CONTENT_SIZE;
227
+ }
228
+ if (out_frame)
229
+ *out_frame = frame;
230
+ if (out_frame_len)
231
+ *out_frame_len = frame_len;
232
+ return MCDB_OK;
233
+ }
234
+
235
+ static mcdb_status mcdb_finish_decoded(const unsigned char *in, const mcdb_header *header,
236
+ unsigned char *out, size_t produced, size_t *out_len,
237
+ char *errbuf) {
238
+ uint32_t expected_crc;
239
+ uint32_t actual_crc;
240
+ if (produced != header->original_size) {
241
+ if (errbuf)
242
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: size mismatch (header %llu, got %zu)",
243
+ (unsigned long long)header->original_size, produced);
244
+ return MCDB_ERR_SIZE_MISMATCH;
245
+ }
246
+ expected_crc = read_u32_le(in + MCDB_CRC_OFFSET);
247
+ actual_crc = mcdb_crc32(out, produced);
248
+ if (actual_crc != expected_crc) {
249
+ if (errbuf)
250
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: crc32 mismatch (header %u, computed %u)",
251
+ expected_crc, actual_crc);
252
+ return MCDB_ERR_CRC;
253
+ }
254
+ if (!mcdb_is_valid_utf8(out, produced)) {
255
+ if (errbuf)
256
+ snprintf(errbuf, MCDB_ERRLEN,
257
+ "MCDB: payload is not valid UTF-8 text or contains a NUL byte");
258
+ return MCDB_ERR_UTF8;
259
+ }
260
+ if (out_len)
261
+ *out_len = produced;
262
+ return MCDB_OK;
263
+ }
264
+
265
+ mcdb_status mcdb_decode_into(const unsigned char *in, size_t in_len, unsigned char *out,
266
+ size_t out_capacity, size_t *out_len, char *errbuf) {
267
+ mcdb_header header;
268
+ const unsigned char *frame;
269
+ size_t frame_len;
270
+ size_t produced;
271
+ mcdb_status st;
272
+
273
+ if (out_len)
274
+ *out_len = 0;
275
+ if (errbuf)
276
+ errbuf[0] = '\0';
277
+ st = mcdb_parse_header(in, in_len, &header);
278
+ if (st != MCDB_OK)
279
+ return st;
280
+ if (header.version != MCDB_VERSION_V1)
281
+ return MCDB_ERR_VERSION;
282
+ if (out == NULL || out_capacity < header.original_size)
283
+ return MCDB_ERR_SIZE_MISMATCH;
284
+ st = mcdb_validate_frame(in, in_len, &header, &frame, &frame_len, errbuf);
285
+ if (st != MCDB_OK)
286
+ return st;
287
+ produced = ZSTD_decompress(out, (size_t)header.original_size, frame, frame_len);
288
+ if (ZSTD_isError(produced)) {
289
+ if (errbuf)
290
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: zstd error: %s", ZSTD_getErrorName(produced));
291
+ return MCDB_ERR_DECOMPRESS;
292
+ }
293
+ return mcdb_finish_decoded(in, &header, out, produced, out_len, errbuf);
294
+ }
295
+
296
+ mcdb_status mcdb_decode(const unsigned char *in, size_t in_len, unsigned char **out,
297
+ size_t *out_len, char *errbuf) {
298
+ mcdb_header header;
299
+ unsigned char *buf;
300
+ mcdb_status st;
301
+ if (out)
302
+ *out = NULL;
303
+ if (out_len)
304
+ *out_len = 0;
305
+ if (errbuf)
306
+ errbuf[0] = '\0';
307
+ st = mcdb_validate_header(in, in_len, &header.original_size);
308
+ if (st != MCDB_OK) {
309
+ if (errbuf)
310
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: %s", mcdb_status_str(st));
311
+ return st;
312
+ }
313
+ buf = (unsigned char *)malloc(header.original_size ? (size_t)header.original_size : 1);
314
+ if (buf == NULL) {
315
+ if (errbuf)
316
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: out of memory");
317
+ return MCDB_ERR_ALLOC;
318
+ }
319
+ st = mcdb_decode_into(in, in_len, buf, (size_t)header.original_size, out_len, errbuf);
320
+ if (st != MCDB_OK) {
321
+ free(buf);
322
+ return st;
323
+ }
324
+ if (out)
325
+ *out = buf;
326
+ else
327
+ free(buf);
328
+ return MCDB_OK;
329
+ }
330
+
331
+ mcdb_status mcdb_decode_v2_into(const unsigned char *in, size_t in_len,
332
+ uint64_t supplied_dictionary_ref, const unsigned char *dictionary,
333
+ size_t dictionary_len, ZSTD_DCtx *dctx, ZSTD_DDict *ddict,
334
+ unsigned char *out, size_t out_capacity, size_t *out_len,
335
+ char *errbuf) {
336
+ mcdb_header header;
337
+ const unsigned char *frame;
338
+ size_t frame_len;
339
+ size_t produced;
340
+ uint32_t dictionary_id;
341
+ uint32_t frame_dictionary_id;
342
+ mcdb_status st;
343
+ int own_dctx = 0;
344
+
345
+ if (out_len)
346
+ *out_len = 0;
347
+ if (errbuf)
348
+ errbuf[0] = '\0';
349
+ st = mcdb_parse_header(in, in_len, &header);
350
+ if (st != MCDB_OK)
351
+ return st;
352
+ if (header.version != MCDB_VERSION_V2)
353
+ return MCDB_ERR_VERSION;
354
+ if (supplied_dictionary_ref != header.dictionary_ref)
355
+ return MCDB_ERR_DICTIONARY_REF;
356
+ if (out == NULL || out_capacity < header.original_size)
357
+ return MCDB_ERR_SIZE_MISMATCH;
358
+ st = mcdb_validate_dictionary(dictionary, dictionary_len, &dictionary_id);
359
+ if (st != MCDB_OK)
360
+ return st;
361
+ st = mcdb_validate_frame(in, in_len, &header, &frame, &frame_len, errbuf);
362
+ if (st != MCDB_OK)
363
+ return st;
364
+ frame_dictionary_id = mcdb_zstd_frame_dictionary_id(frame, frame_len);
365
+ if (frame_dictionary_id == 0 || frame_dictionary_id != dictionary_id)
366
+ return MCDB_ERR_DICTIONARY_ID;
367
+ if (ddict == NULL)
368
+ return MCDB_ERR_DICTIONARY_REQUIRED;
369
+ if (dctx == NULL) {
370
+ dctx = ZSTD_createDCtx();
371
+ if (dctx == NULL)
372
+ return MCDB_ERR_ALLOC;
373
+ own_dctx = 1;
374
+ }
375
+ produced = ZSTD_decompress_usingDDict(dctx, out, (size_t)header.original_size, frame, frame_len,
376
+ ddict);
377
+ if (own_dctx)
378
+ ZSTD_freeDCtx(dctx);
379
+ if (ZSTD_isError(produced)) {
380
+ if (errbuf)
381
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: zstd dictionary error: %s",
382
+ ZSTD_getErrorName(produced));
383
+ return MCDB_ERR_DECOMPRESS;
384
+ }
385
+ return mcdb_finish_decoded(in, &header, out, produced, out_len, errbuf);
386
+ }
387
+
388
+ mcdb_status mcdb_decode_v2(const unsigned char *in, size_t in_len, uint64_t supplied_dictionary_ref,
389
+ const unsigned char *dictionary, size_t dictionary_len,
390
+ unsigned char **out, size_t *out_len, char *errbuf) {
391
+ mcdb_header header;
392
+ ZSTD_DDict *ddict;
393
+ mcdb_status st;
394
+ unsigned char *buf;
395
+ if (out)
396
+ *out = NULL;
397
+ if (out_len)
398
+ *out_len = 0;
399
+ st = mcdb_validate_v2_header(in, in_len, &header.original_size, NULL);
400
+ if (st != MCDB_OK)
401
+ return st;
402
+ st = mcdb_validate_dictionary(dictionary, dictionary_len, NULL);
403
+ if (st != MCDB_OK)
404
+ return st;
405
+ ddict = ZSTD_createDDict(dictionary, dictionary_len);
406
+ if (ddict == NULL)
407
+ return MCDB_ERR_ALLOC;
408
+ buf = (unsigned char *)malloc(header.original_size ? (size_t)header.original_size : 1);
409
+ if (buf == NULL) {
410
+ ZSTD_freeDDict(ddict);
411
+ return MCDB_ERR_ALLOC;
412
+ }
413
+ st = mcdb_decode_v2_into(in, in_len, supplied_dictionary_ref, dictionary, dictionary_len, NULL,
414
+ ddict, buf, (size_t)header.original_size, out_len, errbuf);
415
+ ZSTD_freeDDict(ddict);
416
+ if (st != MCDB_OK) {
417
+ free(buf);
418
+ return st;
419
+ }
420
+ if (out)
421
+ *out = buf;
422
+ else
423
+ free(buf);
424
+ return MCDB_OK;
425
+ }
426
+
427
+ /* Tiny SHA-256 implementation; dictionary registry validation must not depend on pgcrypto/OpenSSL.
428
+ */
429
+ typedef struct {
430
+ uint32_t state[8];
431
+ uint64_t bitlen;
432
+ unsigned char data[64];
433
+ size_t datalen;
434
+ } mcdb_sha256_ctx;
435
+
436
+ #define MCDB_ROTR32(x, n) (((x) >> (n)) | ((x) << (32u - (n))))
437
+ #define MCDB_CH(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
438
+ #define MCDB_MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
439
+ #define MCDB_EP0(x) (MCDB_ROTR32((x), 2) ^ MCDB_ROTR32((x), 13) ^ MCDB_ROTR32((x), 22))
440
+ #define MCDB_EP1(x) (MCDB_ROTR32((x), 6) ^ MCDB_ROTR32((x), 11) ^ MCDB_ROTR32((x), 25))
441
+ #define MCDB_SIG0(x) (MCDB_ROTR32((x), 7) ^ MCDB_ROTR32((x), 18) ^ ((x) >> 3))
442
+ #define MCDB_SIG1(x) (MCDB_ROTR32((x), 17) ^ MCDB_ROTR32((x), 19) ^ ((x) >> 10))
443
+
444
+ static const uint32_t MCDB_SHA256_K[64] = {
445
+ 0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, 0x3956c25bU, 0x59f111f1U, 0x923f82a4U,
446
+ 0xab1c5ed5U, 0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, 0x72be5d74U, 0x80deb1feU,
447
+ 0x9bdc06a7U, 0xc19bf174U, 0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, 0x2de92c6fU,
448
+ 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU, 0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U,
449
+ 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U, 0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU,
450
+ 0x53380d13U, 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U, 0xa2bfe8a1U, 0xa81a664bU,
451
+ 0xc24b8b70U, 0xc76c51a3U, 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U, 0x19a4c116U,
452
+ 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U,
453
+ 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U,
454
+ 0xc67178f2U,
455
+ };
456
+
457
+ static void mcdb_sha256_transform(mcdb_sha256_ctx *ctx, const unsigned char data[64]) {
458
+ uint32_t m[64];
459
+ uint32_t a, b, c, d, e, f, g, h;
460
+ uint32_t t1, t2;
461
+ size_t i;
462
+ for (i = 0; i < 16; i++) {
463
+ m[i] = ((uint32_t)data[i * 4] << 24) | ((uint32_t)data[i * 4 + 1] << 16) |
464
+ ((uint32_t)data[i * 4 + 2] << 8) | (uint32_t)data[i * 4 + 3];
465
+ }
466
+ for (i = 16; i < 64; i++)
467
+ m[i] = MCDB_SIG1(m[i - 2]) + m[i - 7] + MCDB_SIG0(m[i - 15]) + m[i - 16];
468
+ a = ctx->state[0];
469
+ b = ctx->state[1];
470
+ c = ctx->state[2];
471
+ d = ctx->state[3];
472
+ e = ctx->state[4];
473
+ f = ctx->state[5];
474
+ g = ctx->state[6];
475
+ h = ctx->state[7];
476
+ for (i = 0; i < 64; i++) {
477
+ t1 = h + MCDB_EP1(e) + MCDB_CH(e, f, g) + MCDB_SHA256_K[i] + m[i];
478
+ t2 = MCDB_EP0(a) + MCDB_MAJ(a, b, c);
479
+ h = g;
480
+ g = f;
481
+ f = e;
482
+ e = d + t1;
483
+ d = c;
484
+ c = b;
485
+ b = a;
486
+ a = t1 + t2;
487
+ }
488
+ ctx->state[0] += a;
489
+ ctx->state[1] += b;
490
+ ctx->state[2] += c;
491
+ ctx->state[3] += d;
492
+ ctx->state[4] += e;
493
+ ctx->state[5] += f;
494
+ ctx->state[6] += g;
495
+ ctx->state[7] += h;
496
+ }
497
+
498
+ static void mcdb_sha256_init(mcdb_sha256_ctx *ctx) {
499
+ static const uint32_t initial[8] = {0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU,
500
+ 0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U};
501
+ memcpy(ctx->state, initial, sizeof(initial));
502
+ ctx->bitlen = 0;
503
+ ctx->datalen = 0;
504
+ }
505
+
506
+ static void mcdb_sha256_update(mcdb_sha256_ctx *ctx, const unsigned char *data, size_t len) {
507
+ size_t i;
508
+ for (i = 0; i < len; i++) {
509
+ ctx->data[ctx->datalen++] = data[i];
510
+ if (ctx->datalen == 64) {
511
+ mcdb_sha256_transform(ctx, ctx->data);
512
+ ctx->bitlen += 512;
513
+ ctx->datalen = 0;
514
+ }
515
+ }
516
+ }
517
+
518
+ static void mcdb_sha256_final(mcdb_sha256_ctx *ctx, unsigned char out[MCDB_SHA256_BYTES]) {
519
+ size_t i;
520
+ uint64_t bitlen;
521
+ ctx->bitlen += (uint64_t)ctx->datalen * 8u;
522
+ ctx->data[ctx->datalen++] = 0x80;
523
+ if (ctx->datalen > 56) {
524
+ while (ctx->datalen < 64)
525
+ ctx->data[ctx->datalen++] = 0;
526
+ mcdb_sha256_transform(ctx, ctx->data);
527
+ ctx->datalen = 0;
528
+ }
529
+ while (ctx->datalen < 56)
530
+ ctx->data[ctx->datalen++] = 0;
531
+ bitlen = ctx->bitlen;
532
+ for (i = 0; i < 8; i++)
533
+ ctx->data[63 - i] = (unsigned char)(bitlen >> (8u * i));
534
+ mcdb_sha256_transform(ctx, ctx->data);
535
+ for (i = 0; i < 8; i++) {
536
+ out[i * 4] = (unsigned char)(ctx->state[i] >> 24);
537
+ out[i * 4 + 1] = (unsigned char)(ctx->state[i] >> 16);
538
+ out[i * 4 + 2] = (unsigned char)(ctx->state[i] >> 8);
539
+ out[i * 4 + 3] = (unsigned char)ctx->state[i];
540
+ }
541
+ }
542
+
543
+ void mcdb_sha256(const unsigned char *data, size_t len, unsigned char out[MCDB_SHA256_BYTES]) {
544
+ mcdb_sha256_ctx ctx;
545
+ mcdb_sha256_init(&ctx);
546
+ if (data != NULL && len > 0)
547
+ mcdb_sha256_update(&ctx, data, len);
548
+ mcdb_sha256_final(&ctx, out);
549
+ }
550
+
551
+ const char *mcdb_status_str(mcdb_status s) {
552
+ switch (s) {
553
+ case MCDB_OK:
554
+ return "ok";
555
+ case MCDB_ERR_TRUNCATED:
556
+ return "truncated envelope";
557
+ case MCDB_ERR_ENVELOPE_SIZE:
558
+ return "stored envelope too large";
559
+ case MCDB_ERR_MAGIC:
560
+ return "bad magic";
561
+ case MCDB_ERR_VERSION:
562
+ return "unsupported version";
563
+ case MCDB_ERR_CODEC:
564
+ return "unsupported codec";
565
+ case MCDB_ERR_FLAGS:
566
+ return "reserved flags must be 0";
567
+ case MCDB_ERR_SIZE_LIMIT:
568
+ return "declared size over limit";
569
+ case MCDB_ERR_DICTIONARY_REQUIRED:
570
+ return "dictionary is required";
571
+ case MCDB_ERR_DICTIONARY_REF:
572
+ return "dictionary reference mismatch or invalid reference";
573
+ case MCDB_ERR_DICTIONARY_SIZE:
574
+ return "dictionary is empty or over limit";
575
+ case MCDB_ERR_DICTIONARY_ID:
576
+ return "zstd dictionary identifier mismatch or missing identifier";
577
+ case MCDB_ERR_DECOMPRESS:
578
+ return "corrupt zstd payload";
579
+ case MCDB_ERR_TRAILING_DATA:
580
+ return "trailing bytes or concatenated zstd frames";
581
+ case MCDB_ERR_FRAME_CONTENT_SIZE:
582
+ return "zstd frame content size mismatch";
583
+ case MCDB_ERR_SIZE_MISMATCH:
584
+ return "size mismatch";
585
+ case MCDB_ERR_CRC:
586
+ return "crc32 mismatch";
587
+ case MCDB_ERR_UTF8:
588
+ return "payload is not valid UTF-8 text or contains a NUL byte";
589
+ case MCDB_ERR_ALLOC:
590
+ return "out of memory";
591
+ default:
592
+ return "unknown error";
593
+ }
594
+ }
@@ -0,0 +1,48 @@
1
+ SHELL := /bin/bash
2
+
3
+ MYSQL ?= mysql
4
+ MYSQL_DEFAULTS_FILE ?=
5
+ MYSQL_SOCKET ?=
6
+ CONFIRM ?=
7
+
8
+ .PHONY: help verify doctor status install enable upgrade disable uninstall
9
+
10
+ help:
11
+ @printf '%s\n' \
12
+ 'MultiCompress MySQL 5.7 deployment bundle' \
13
+ '' \
14
+ ' make verify' \
15
+ ' make doctor [MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf] [MYSQL_SOCKET=/run/mysqld/mysqld.sock]' \
16
+ ' make status [MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf] [MYSQL_SOCKET=/run/mysqld/mysqld.sock]' \
17
+ ' sudo make install [MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf] [MYSQL_SOCKET=/run/mysqld/mysqld.sock]' \
18
+ ' make enable [MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf] [MYSQL_SOCKET=/run/mysqld/mysqld.sock]' \
19
+ ' sudo make upgrade CONFIRM=UPGRADE_MULTI_COMPRESS [MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf] [MYSQL_SOCKET=/run/mysqld/mysqld.sock]' \
20
+ ' make disable CONFIRM=DISABLE_MULTI_COMPRESS [MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf] [MYSQL_SOCKET=/run/mysqld/mysqld.sock]' \
21
+ ' sudo make uninstall CONFIRM=REMOVE_MULTI_COMPRESS [MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf] [MYSQL_SOCKET=/run/mysqld/mysqld.sock]'
22
+
23
+ verify:
24
+ ./bin/verify
25
+
26
+ doctor:
27
+ MYSQL_SOCKET="$(MYSQL_SOCKET)" ./bin/doctor --mysql "$(MYSQL)" $(if $(MYSQL_DEFAULTS_FILE),--defaults-extra-file "$(MYSQL_DEFAULTS_FILE)")
28
+
29
+ status:
30
+ MYSQL_SOCKET="$(MYSQL_SOCKET)" ./bin/status --mysql "$(MYSQL)" $(if $(MYSQL_DEFAULTS_FILE),--defaults-extra-file "$(MYSQL_DEFAULTS_FILE)")
31
+
32
+ install: verify
33
+ MYSQL_SOCKET="$(MYSQL_SOCKET)" ./bin/install --mysql "$(MYSQL)" $(if $(MYSQL_DEFAULTS_FILE),--defaults-extra-file "$(MYSQL_DEFAULTS_FILE)")
34
+
35
+ enable:
36
+ MYSQL_SOCKET="$(MYSQL_SOCKET)" ./bin/enable --mysql "$(MYSQL)" $(if $(MYSQL_DEFAULTS_FILE),--defaults-extra-file "$(MYSQL_DEFAULTS_FILE)")
37
+
38
+ upgrade:
39
+ @test "$(CONFIRM)" = "UPGRADE_MULTI_COMPRESS" || { echo 'upgrade requires CONFIRM=UPGRADE_MULTI_COMPRESS' >&2; exit 2; }
40
+ MYSQL_SOCKET="$(MYSQL_SOCKET)" ./bin/upgrade --mysql "$(MYSQL)" $(if $(MYSQL_DEFAULTS_FILE),--defaults-extra-file "$(MYSQL_DEFAULTS_FILE)")
41
+
42
+ disable:
43
+ @test "$(CONFIRM)" = "DISABLE_MULTI_COMPRESS" || { echo 'disable requires CONFIRM=DISABLE_MULTI_COMPRESS' >&2; exit 2; }
44
+ MYSQL_SOCKET="$(MYSQL_SOCKET)" ./bin/disable --mysql "$(MYSQL)" $(if $(MYSQL_DEFAULTS_FILE),--defaults-extra-file "$(MYSQL_DEFAULTS_FILE)")
45
+
46
+ uninstall:
47
+ @test "$(CONFIRM)" = "REMOVE_MULTI_COMPRESS" || { echo 'uninstall requires CONFIRM=REMOVE_MULTI_COMPRESS' >&2; exit 2; }
48
+ MYSQL_SOCKET="$(MYSQL_SOCKET)" ./bin/uninstall --mysql "$(MYSQL)" $(if $(MYSQL_DEFAULTS_FILE),--defaults-extra-file "$(MYSQL_DEFAULTS_FILE)")
@@ -0,0 +1,61 @@
1
+ # MultiCompress MySQL 5.7 deployment bundle
2
+
3
+ This bundle is generated by the exact `multi_compress` gem version used by the
4
+ application. Build the native UDF on the MySQL host so its libc and server ABI
5
+ match the running MySQL 5.7 process.
6
+
7
+ ```bash
8
+ make verify
9
+ make doctor MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
10
+ sudo make install MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
11
+ make enable MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
12
+ make status MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
13
+ ```
14
+
15
+ The bundle forces a local Unix-socket connection so its library is installed in
16
+ the plugin directory of that same mysqld. `install` is fresh-install only; a
17
+ healthy second `enable` is a no-op. Use the controlled maintenance flow for an
18
+ upgrade:
19
+
20
+ ```bash
21
+ sudo make upgrade CONFIRM=UPGRADE_MULTI_COMPRESS \
22
+ MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
23
+ ```
24
+
25
+ Destructive actions require exact confirmation:
26
+
27
+ ```bash
28
+ make disable CONFIRM=DISABLE_MULTI_COMPRESS \
29
+ MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
30
+ sudo make uninstall CONFIRM=REMOVE_MULTI_COMPRESS \
31
+ MYSQL_DEFAULTS_FILE=/etc/mysql/admin.cnf MYSQL_SOCKET=/run/mysqld/mysqld.sock
32
+ ```
33
+
34
+ The application stores `MultiCompress::Database.compress(text)` in a `LONGBLOB`
35
+ column. Generate the DBeaver/read-side view in the application repository and
36
+ grant the DBeaver role `SELECT` on it. The generated MySQL view uses
37
+ `CONVERT(... USING utf8mb4)` so Cyrillic, Kazakh text and emoji display
38
+ correctly. Filter by indexed, uncompressed columns before selecting through it.
39
+
40
+ `make verify` catches accidental corruption after extraction; it does not
41
+ authenticate an archive. The mysql client uses `--max_allowed_packet=32M`, but
42
+ mysqld, the Ruby driver and DBeaver/JDBC must each allow the 16 MiB MCDB1 limit.
43
+
44
+ ## MCDB2 after reader installation
45
+
46
+ After the 0.6 UDF is installed/enabled, the application owns dictionaries:
47
+
48
+ ```bash
49
+ multi_compress db registry mysql --database app
50
+ multi_compress db view mysql \
51
+ --table app.events --column payload_compressed \
52
+ --dictionary-table app.mcdb_dictionary_versions \
53
+ --dictionary-id-column payload_dictionary_id \
54
+ --view admin.events_readable --columns id,created_at,status
55
+ ```
56
+
57
+ Do not put dictionary files in this bundle. MCDB2 dictionary bytes are immutable
58
+ application records that must travel with backups and replication. Keep generated
59
+ MySQL views MERGE-able and verify `EXPLAIN` before granting DBeaver access.
60
+
61
+ For MCDB2 registry DDL, pass `--payload-table`, `--payload-column`, and `--payload-dictionary-id-column` to `multi_compress db registry`; this creates the payload FK and enforces header dictionary-reference consistency.