multi_compress 0.3.5 → 0.5.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/GET_STARTED.md +188 -0
  4. data/README.md +56 -0
  5. data/db_core/include/mcdb_format.h +55 -0
  6. data/db_core/src/mcdb_format.c +341 -0
  7. data/db_deployment/mysql/Makefile +48 -0
  8. data/db_deployment/mysql/README.md +42 -0
  9. data/db_deployment/mysql/bin/common +160 -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 +57 -0
  13. data/db_deployment/mysql/bin/install +64 -0
  14. data/db_deployment/mysql/bin/status +65 -0
  15. data/db_deployment/mysql/bin/uninstall +47 -0
  16. data/db_deployment/mysql/bin/upgrade +115 -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 +60 -0
  20. data/db_deployment/postgres/bin/doctor +61 -0
  21. data/db_deployment/postgres/bin/enable +136 -0
  22. data/db_deployment/postgres/bin/install +104 -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/exe/multi_compress +228 -0
  27. data/ext/multi_compress/extconf.rb +4 -0
  28. data/ext/multi_compress/multi_compress.c +30 -0
  29. data/lib/multi_compress/active_record.rb +61 -0
  30. data/lib/multi_compress/codec.rb +164 -0
  31. data/lib/multi_compress/database.rb +106 -0
  32. data/lib/multi_compress/db_deployment.rb +456 -0
  33. data/lib/multi_compress/version.rb +1 -1
  34. data/multi_compress.gemspec +55 -0
  35. data/mysql_udf/Makefile +96 -0
  36. data/mysql_udf/README.md +95 -0
  37. data/mysql_udf/sql/examples.sql +11 -0
  38. data/mysql_udf/sql/install.sql +15 -0
  39. data/mysql_udf/sql/uninstall.sql +4 -0
  40. data/mysql_udf/sql/views.sql +8 -0
  41. data/mysql_udf/src/multi_compress_mysql.c +133 -0
  42. data/mysql_udf/src/mysql_udf_abi_57.h +34 -0
  43. data/mysql_udf/test/mcdb_cli.c +60 -0
  44. data/mysql_udf/test/run_e2e.sh +296 -0
  45. data/postgres_extension/Makefile +74 -0
  46. data/postgres_extension/README.md +112 -0
  47. data/postgres_extension/multi_compress.control +4 -0
  48. data/postgres_extension/multi_compress_pg.exports +12 -0
  49. data/postgres_extension/sql/examples.sql +12 -0
  50. data/postgres_extension/sql/multi_compress--0.5.0.sql +24 -0
  51. data/postgres_extension/sql/uninstall.sql +2 -0
  52. data/postgres_extension/sql/views.sql +7 -0
  53. data/postgres_extension/src/mcdb_format.c +2 -0
  54. data/postgres_extension/src/multi_compress_pg.c +96 -0
  55. data/postgres_extension/test/run_e2e.sh +323 -0
  56. metadata +52 -3
@@ -0,0 +1,341 @@
1
+ #include "mcdb_format.h"
2
+
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+ #include <stdio.h>
6
+
7
+ #include <zstd.h>
8
+
9
+ typedef char
10
+ mcdb_envelope_bound_check[(MCDB_MAX_ENVELOPE ==
11
+ (unsigned)(MCDB_HEADER_SIZE + ZSTD_COMPRESSBOUND(MCDB_MAX_OUTPUT)))
12
+ ? 1
13
+ : -1];
14
+
15
+ static const uint32_t MCDB_CRC32_TABLE[256] = {
16
+ 0x00000000U, 0x77073096U, 0xEE0E612CU, 0x990951BAU, 0x076DC419U, 0x706AF48FU, 0xE963A535U,
17
+ 0x9E6495A3U, 0x0EDB8832U, 0x79DCB8A4U, 0xE0D5E91EU, 0x97D2D988U, 0x09B64C2BU, 0x7EB17CBDU,
18
+ 0xE7B82D07U, 0x90BF1D91U, 0x1DB71064U, 0x6AB020F2U, 0xF3B97148U, 0x84BE41DEU, 0x1ADAD47DU,
19
+ 0x6DDDE4EBU, 0xF4D4B551U, 0x83D385C7U, 0x136C9856U, 0x646BA8C0U, 0xFD62F97AU, 0x8A65C9ECU,
20
+ 0x14015C4FU, 0x63066CD9U, 0xFA0F3D63U, 0x8D080DF5U, 0x3B6E20C8U, 0x4C69105EU, 0xD56041E4U,
21
+ 0xA2677172U, 0x3C03E4D1U, 0x4B04D447U, 0xD20D85FDU, 0xA50AB56BU, 0x35B5A8FAU, 0x42B2986CU,
22
+ 0xDBBBC9D6U, 0xACBCF940U, 0x32D86CE3U, 0x45DF5C75U, 0xDCD60DCFU, 0xABD13D59U, 0x26D930ACU,
23
+ 0x51DE003AU, 0xC8D75180U, 0xBFD06116U, 0x21B4F4B5U, 0x56B3C423U, 0xCFBA9599U, 0xB8BDA50FU,
24
+ 0x2802B89EU, 0x5F058808U, 0xC60CD9B2U, 0xB10BE924U, 0x2F6F7C87U, 0x58684C11U, 0xC1611DABU,
25
+ 0xB6662D3DU, 0x76DC4190U, 0x01DB7106U, 0x98D220BCU, 0xEFD5102AU, 0x71B18589U, 0x06B6B51FU,
26
+ 0x9FBFE4A5U, 0xE8B8D433U, 0x7807C9A2U, 0x0F00F934U, 0x9609A88EU, 0xE10E9818U, 0x7F6A0DBBU,
27
+ 0x086D3D2DU, 0x91646C97U, 0xE6635C01U, 0x6B6B51F4U, 0x1C6C6162U, 0x856530D8U, 0xF262004EU,
28
+ 0x6C0695EDU, 0x1B01A57BU, 0x8208F4C1U, 0xF50FC457U, 0x65B0D9C6U, 0x12B7E950U, 0x8BBEB8EAU,
29
+ 0xFCB9887CU, 0x62DD1DDFU, 0x15DA2D49U, 0x8CD37CF3U, 0xFBD44C65U, 0x4DB26158U, 0x3AB551CEU,
30
+ 0xA3BC0074U, 0xD4BB30E2U, 0x4ADFA541U, 0x3DD895D7U, 0xA4D1C46DU, 0xD3D6F4FBU, 0x4369E96AU,
31
+ 0x346ED9FCU, 0xAD678846U, 0xDA60B8D0U, 0x44042D73U, 0x33031DE5U, 0xAA0A4C5FU, 0xDD0D7CC9U,
32
+ 0x5005713CU, 0x270241AAU, 0xBE0B1010U, 0xC90C2086U, 0x5768B525U, 0x206F85B3U, 0xB966D409U,
33
+ 0xCE61E49FU, 0x5EDEF90EU, 0x29D9C998U, 0xB0D09822U, 0xC7D7A8B4U, 0x59B33D17U, 0x2EB40D81U,
34
+ 0xB7BD5C3BU, 0xC0BA6CADU, 0xEDB88320U, 0x9ABFB3B6U, 0x03B6E20CU, 0x74B1D29AU, 0xEAD54739U,
35
+ 0x9DD277AFU, 0x04DB2615U, 0x73DC1683U, 0xE3630B12U, 0x94643B84U, 0x0D6D6A3EU, 0x7A6A5AA8U,
36
+ 0xE40ECF0BU, 0x9309FF9DU, 0x0A00AE27U, 0x7D079EB1U, 0xF00F9344U, 0x8708A3D2U, 0x1E01F268U,
37
+ 0x6906C2FEU, 0xF762575DU, 0x806567CBU, 0x196C3671U, 0x6E6B06E7U, 0xFED41B76U, 0x89D32BE0U,
38
+ 0x10DA7A5AU, 0x67DD4ACCU, 0xF9B9DF6FU, 0x8EBEEFF9U, 0x17B7BE43U, 0x60B08ED5U, 0xD6D6A3E8U,
39
+ 0xA1D1937EU, 0x38D8C2C4U, 0x4FDFF252U, 0xD1BB67F1U, 0xA6BC5767U, 0x3FB506DDU, 0x48B2364BU,
40
+ 0xD80D2BDAU, 0xAF0A1B4CU, 0x36034AF6U, 0x41047A60U, 0xDF60EFC3U, 0xA867DF55U, 0x316E8EEFU,
41
+ 0x4669BE79U, 0xCB61B38CU, 0xBC66831AU, 0x256FD2A0U, 0x5268E236U, 0xCC0C7795U, 0xBB0B4703U,
42
+ 0x220216B9U, 0x5505262FU, 0xC5BA3BBEU, 0xB2BD0B28U, 0x2BB45A92U, 0x5CB36A04U, 0xC2D7FFA7U,
43
+ 0xB5D0CF31U, 0x2CD99E8BU, 0x5BDEAE1DU, 0x9B64C2B0U, 0xEC63F226U, 0x756AA39CU, 0x026D930AU,
44
+ 0x9C0906A9U, 0xEB0E363FU, 0x72076785U, 0x05005713U, 0x95BF4A82U, 0xE2B87A14U, 0x7BB12BAEU,
45
+ 0x0CB61B38U, 0x92D28E9BU, 0xE5D5BE0DU, 0x7CDCEFB7U, 0x0BDBDF21U, 0x86D3D2D4U, 0xF1D4E242U,
46
+ 0x68DDB3F8U, 0x1FDA836EU, 0x81BE16CDU, 0xF6B9265BU, 0x6FB077E1U, 0x18B74777U, 0x88085AE6U,
47
+ 0xFF0F6A70U, 0x66063BCAU, 0x11010B5CU, 0x8F659EFFU, 0xF862AE69U, 0x616BFFD3U, 0x166CCF45U,
48
+ 0xA00AE278U, 0xD70DD2EEU, 0x4E048354U, 0x3903B3C2U, 0xA7672661U, 0xD06016F7U, 0x4969474DU,
49
+ 0x3E6E77DBU, 0xAED16A4AU, 0xD9D65ADCU, 0x40DF0B66U, 0x37D83BF0U, 0xA9BCAE53U, 0xDEBB9EC5U,
50
+ 0x47B2CF7FU, 0x30B5FFE9U, 0xBDBDF21CU, 0xCABAC28AU, 0x53B39330U, 0x24B4A3A6U, 0xBAD03605U,
51
+ 0xCDD70693U, 0x54DE5729U, 0x23D967BFU, 0xB3667A2EU, 0xC4614AB8U, 0x5D681B02U, 0x2A6F2B94U,
52
+ 0xB40BBE37U, 0xC30C8EA1U, 0x5A05DF1BU, 0x2D02EF8DU,
53
+ };
54
+
55
+ static uint32_t mcdb_crc32(const unsigned char *data, size_t len) {
56
+ uint32_t crc = 0xFFFFFFFFU;
57
+ size_t i;
58
+
59
+ for (i = 0; i < len; i++) {
60
+ crc = MCDB_CRC32_TABLE[(crc ^ data[i]) & 0xFFU] ^ (crc >> 8);
61
+ }
62
+ return crc ^ 0xFFFFFFFFU;
63
+ }
64
+
65
+ static uint64_t read_u64_le(const unsigned char *p) {
66
+ uint64_t v = 0;
67
+ int i;
68
+
69
+ for (i = 0; i < 8; i++)
70
+ v |= (uint64_t)p[i] << (8 * i);
71
+ return v;
72
+ }
73
+
74
+ static uint32_t read_u32_le(const unsigned char *p) {
75
+ return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
76
+ }
77
+
78
+ static int mcdb_is_valid_utf8(const unsigned char *s, size_t len) {
79
+ size_t i = 0;
80
+
81
+ while (i < len) {
82
+ unsigned char c;
83
+ size_t n;
84
+ uint32_t cp;
85
+ unsigned char lo2;
86
+ unsigned char hi2;
87
+ size_t k;
88
+
89
+ c = s[i];
90
+ n = 0;
91
+ cp = 0;
92
+ lo2 = 0x80;
93
+ hi2 = 0xBF;
94
+
95
+ if (c < 0x80) {
96
+ if (c == 0)
97
+ return 0;
98
+ i += 1;
99
+ continue;
100
+ }
101
+
102
+ if ((c & 0xE0) == 0xC0) {
103
+ n = 2;
104
+ cp = c & 0x1F;
105
+ if (c < 0xC2)
106
+ return 0;
107
+ } else if ((c & 0xF0) == 0xE0) {
108
+ n = 3;
109
+ cp = c & 0x0F;
110
+ if (c == 0xE0)
111
+ lo2 = 0xA0; /* no overlong */
112
+ if (c == 0xED)
113
+ hi2 = 0x9F; /* no surrogates */
114
+ } else if ((c & 0xF8) == 0xF0) {
115
+ n = 4;
116
+ cp = c & 0x07;
117
+ if (c < 0xF0 || c > 0xF4)
118
+ return 0;
119
+ if (c == 0xF0)
120
+ lo2 = 0x90; /* no overlong */
121
+ if (c == 0xF4)
122
+ hi2 = 0x8F; /* <= U+10FFFF */
123
+ } else {
124
+ return 0;
125
+ }
126
+
127
+ if (i + n > len)
128
+ return 0;
129
+ for (k = 1; k < n; k++) {
130
+ unsigned char cc;
131
+ unsigned char lo;
132
+ unsigned char hi;
133
+
134
+ cc = s[i + k];
135
+ lo = (k == 1) ? lo2 : 0x80;
136
+ hi = (k == 1) ? hi2 : 0xBF;
137
+ if (cc < lo || cc > hi)
138
+ return 0;
139
+ cp = (cp << 6) | (cc & 0x3F);
140
+ }
141
+ (void)cp;
142
+ i += n;
143
+ }
144
+ return 1;
145
+ }
146
+
147
+ mcdb_status mcdb_validate_header(const unsigned char *in, size_t in_len,
148
+ uint64_t *out_original_size) {
149
+ uint64_t original_size;
150
+
151
+ if (in == NULL || in_len < MCDB_HEADER_SIZE)
152
+ return MCDB_ERR_TRUNCATED;
153
+ if (in_len > MCDB_MAX_ENVELOPE)
154
+ return MCDB_ERR_ENVELOPE_SIZE;
155
+ if (in[0] != MCDB_MAGIC0 || in[1] != MCDB_MAGIC1 || in[2] != MCDB_MAGIC2 ||
156
+ in[3] != MCDB_MAGIC3)
157
+ return MCDB_ERR_MAGIC;
158
+ if (in[4] != MCDB_VERSION)
159
+ return MCDB_ERR_VERSION;
160
+ if (in[5] != MCDB_CODEC_ZSTD)
161
+ return MCDB_ERR_CODEC;
162
+ if (in[6] != MCDB_FLAGS_V1)
163
+ return MCDB_ERR_FLAGS;
164
+
165
+ original_size = read_u64_le(in + 7);
166
+ if (original_size > MCDB_MAX_OUTPUT)
167
+ return MCDB_ERR_SIZE_LIMIT;
168
+
169
+ if (out_original_size)
170
+ *out_original_size = original_size;
171
+ return MCDB_OK;
172
+ }
173
+
174
+ static mcdb_status mcdb_decode_payload(const unsigned char *in, size_t in_len,
175
+ uint64_t original_size, unsigned char *out,
176
+ size_t out_capacity, size_t *out_len, char *errbuf) {
177
+ const unsigned char *frame;
178
+ size_t frame_len;
179
+ size_t frame_size;
180
+ unsigned long long frame_content_size;
181
+ size_t produced;
182
+ uint32_t expected_crc;
183
+ uint32_t actual_crc;
184
+
185
+ if (out_len)
186
+ *out_len = 0;
187
+
188
+ if (out == NULL || out_capacity < (size_t)original_size) {
189
+ if (errbuf)
190
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: output buffer is too small");
191
+ return MCDB_ERR_SIZE_MISMATCH;
192
+ }
193
+
194
+ frame = in + MCDB_HEADER_SIZE;
195
+ frame_len = in_len - MCDB_HEADER_SIZE;
196
+ frame_size = ZSTD_findFrameCompressedSize(frame, frame_len);
197
+ if (ZSTD_isError(frame_size)) {
198
+ if (errbuf)
199
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: invalid zstd frame: %s",
200
+ ZSTD_getErrorName(frame_size));
201
+ return MCDB_ERR_DECOMPRESS;
202
+ }
203
+ if (frame_size != frame_len) {
204
+ if (errbuf)
205
+ snprintf(errbuf, MCDB_ERRLEN,
206
+ "MCDB: zstd frame has trailing bytes or concatenated frames");
207
+ return MCDB_ERR_TRAILING_DATA;
208
+ }
209
+
210
+ frame_content_size = ZSTD_getFrameContentSize(frame, frame_len);
211
+ if (frame_content_size == ZSTD_CONTENTSIZE_ERROR ||
212
+ frame_content_size == ZSTD_CONTENTSIZE_UNKNOWN || frame_content_size != original_size) {
213
+ if (errbuf)
214
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: zstd frame content size does not match header");
215
+ return MCDB_ERR_FRAME_CONTENT_SIZE;
216
+ }
217
+
218
+ produced = ZSTD_decompress(out, (size_t)original_size, frame, frame_len);
219
+ if (ZSTD_isError(produced)) {
220
+ if (errbuf)
221
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: zstd error: %s", ZSTD_getErrorName(produced));
222
+ return MCDB_ERR_DECOMPRESS;
223
+ }
224
+ if (produced != original_size) {
225
+ if (errbuf)
226
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: size mismatch (header %llu, got %zu)",
227
+ (unsigned long long)original_size, produced);
228
+ return MCDB_ERR_SIZE_MISMATCH;
229
+ }
230
+
231
+ expected_crc = read_u32_le(in + MCDB_CRC_OFFSET);
232
+ actual_crc = mcdb_crc32(out, produced);
233
+ if (actual_crc != expected_crc) {
234
+ if (errbuf)
235
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: crc32 mismatch (header %u, computed %u)",
236
+ expected_crc, actual_crc);
237
+ return MCDB_ERR_CRC;
238
+ }
239
+
240
+ if (!mcdb_is_valid_utf8(out, produced)) {
241
+ if (errbuf)
242
+ snprintf(errbuf, MCDB_ERRLEN,
243
+ "MCDB: payload is not valid UTF-8 text or contains a NUL byte");
244
+ return MCDB_ERR_UTF8;
245
+ }
246
+
247
+ if (out_len)
248
+ *out_len = produced;
249
+ return MCDB_OK;
250
+ }
251
+
252
+ mcdb_status mcdb_decode_into(const unsigned char *in, size_t in_len, unsigned char *out,
253
+ size_t out_capacity, size_t *out_len, char *errbuf) {
254
+ uint64_t original_size = 0;
255
+
256
+ if (out_len)
257
+ *out_len = 0;
258
+ if (errbuf)
259
+ errbuf[0] = '\0';
260
+
261
+ original_size = read_u64_le(in + 7);
262
+ return mcdb_decode_payload(in, in_len, original_size, out, out_capacity, out_len, errbuf);
263
+ }
264
+
265
+ mcdb_status mcdb_decode(const unsigned char *in, size_t in_len, unsigned char **out,
266
+ size_t *out_len, char *errbuf) {
267
+ uint64_t original_size = 0;
268
+ mcdb_status st;
269
+ unsigned char *buf;
270
+
271
+ if (out)
272
+ *out = NULL;
273
+ if (out_len)
274
+ *out_len = 0;
275
+ if (errbuf)
276
+ errbuf[0] = '\0';
277
+
278
+ st = mcdb_validate_header(in, in_len, &original_size);
279
+ if (st != MCDB_OK) {
280
+ if (errbuf)
281
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: %s", mcdb_status_str(st));
282
+ return st;
283
+ }
284
+
285
+ buf = (unsigned char *)malloc(original_size ? original_size : 1);
286
+ if (buf == NULL) {
287
+ if (errbuf)
288
+ snprintf(errbuf, MCDB_ERRLEN, "MCDB: out of memory");
289
+ return MCDB_ERR_ALLOC;
290
+ }
291
+
292
+ st =
293
+ mcdb_decode_payload(in, in_len, original_size, buf, (size_t)original_size, out_len, errbuf);
294
+ if (st != MCDB_OK) {
295
+ free(buf);
296
+ return st;
297
+ }
298
+
299
+ if (out)
300
+ *out = buf;
301
+ else
302
+ free(buf);
303
+ return MCDB_OK;
304
+ }
305
+
306
+ const char *mcdb_status_str(mcdb_status s) {
307
+ switch (s) {
308
+ case MCDB_OK:
309
+ return "ok";
310
+ case MCDB_ERR_TRUNCATED:
311
+ return "truncated envelope";
312
+ case MCDB_ERR_ENVELOPE_SIZE:
313
+ return "stored envelope too large";
314
+ case MCDB_ERR_MAGIC:
315
+ return "bad magic";
316
+ case MCDB_ERR_VERSION:
317
+ return "unsupported version";
318
+ case MCDB_ERR_CODEC:
319
+ return "unsupported codec";
320
+ case MCDB_ERR_FLAGS:
321
+ return "reserved flags must be 0";
322
+ case MCDB_ERR_SIZE_LIMIT:
323
+ return "declared size over limit";
324
+ case MCDB_ERR_DECOMPRESS:
325
+ return "corrupt zstd payload";
326
+ case MCDB_ERR_TRAILING_DATA:
327
+ return "trailing bytes or concatenated zstd frames";
328
+ case MCDB_ERR_FRAME_CONTENT_SIZE:
329
+ return "zstd frame content size mismatch";
330
+ case MCDB_ERR_SIZE_MISMATCH:
331
+ return "size mismatch";
332
+ case MCDB_ERR_CRC:
333
+ return "crc32 mismatch";
334
+ case MCDB_ERR_UTF8:
335
+ return "payload is not valid UTF-8 text or contains a NUL byte";
336
+ case MCDB_ERR_ALLOC:
337
+ return "out of memory";
338
+ default:
339
+ return "unknown error";
340
+ }
341
+ }
@@ -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,42 @@
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.
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env bash
2
+
3
+ MCDB_UDF_LIBRARY='multi_compress_mysql.so'
4
+ MCDB_EMPTY_ENVELOPE_HEX='4d43444201010000000000000000000000000028b52ffd2000010000'
5
+ MCDB_UDF_NAMES=(
6
+ multi_compress_db_version
7
+ multi_compress_db_is_valid
8
+ multi_compress_db_decompress
9
+ )
10
+
11
+ mcdb_die() {
12
+ echo "multi_compress mysql: $*" >&2
13
+ exit 2
14
+ }
15
+
16
+ mcdb_resolve_mysql() {
17
+ if [[ "$MYSQL" == */* ]]; then
18
+ [ -x "$MYSQL" ] || mcdb_die "mysql client is not executable: $MYSQL"
19
+ else
20
+ MYSQL="$(command -v "$MYSQL" || true)"
21
+ [ -n "$MYSQL" ] || mcdb_die "mysql client was not found"
22
+ fi
23
+ }
24
+
25
+ mcdb_mysql_args() {
26
+ MYSQL_ARGS=(--batch --skip-column-names --raw --max_allowed_packet=32M --protocol=SOCKET)
27
+ if [ -n "$DEFAULTS_FILE" ]; then
28
+ [ -f "$DEFAULTS_FILE" ] || mcdb_die "defaults file does not exist: $DEFAULTS_FILE"
29
+ MYSQL_ARGS=("--defaults-extra-file=$DEFAULTS_FILE" "${MYSQL_ARGS[@]}")
30
+ fi
31
+ if [ -n "${MYSQL_SOCKET:-}" ]; then
32
+ MYSQL_ARGS+=("--socket=$MYSQL_SOCKET")
33
+ fi
34
+ }
35
+
36
+ mcdb_query() {
37
+ "$MYSQL" "${MYSQL_ARGS[@]}" -e "$1"
38
+ }
39
+
40
+ mcdb_server_version() {
41
+ mcdb_query 'SELECT VERSION();' | tr -d '\r\n'
42
+ }
43
+
44
+ mcdb_require_mysql57() {
45
+ local version
46
+ version="$(mcdb_server_version)"
47
+ case "$version" in
48
+ 5.7.*) ;;
49
+ *) mcdb_die "this bundle targets MySQL 5.7; server reports $version" ;;
50
+ esac
51
+ }
52
+
53
+ mcdb_plugin_dir() {
54
+ mcdb_query 'SELECT @@plugin_dir;' | tr -d '\r\n'
55
+ }
56
+
57
+ mcdb_server_socket() {
58
+ mcdb_query 'SELECT @@socket;' | tr -d '\r\n'
59
+ }
60
+
61
+ mcdb_server_hostname() {
62
+ mcdb_query 'SELECT @@hostname;' | tr -d '\r\n'
63
+ }
64
+
65
+ mcdb_server_max_allowed_packet() {
66
+ mcdb_query 'SELECT @@GLOBAL.max_allowed_packet;' | tr -d '\r\n'
67
+ }
68
+
69
+ mcdb_require_expected_socket() {
70
+ local actual_socket
71
+ actual_socket="$(mcdb_server_socket)"
72
+ [ -n "$actual_socket" ] || mcdb_die 'MySQL returned an empty @@socket'
73
+ if [ -n "${MYSQL_SOCKET:-}" ] && [ "$MYSQL_SOCKET" != "$actual_socket" ]; then
74
+ mcdb_die "connected server socket $actual_socket does not match MYSQL_SOCKET $MYSQL_SOCKET"
75
+ fi
76
+ }
77
+
78
+ mcdb_read_udf_state() {
79
+ local rows name dl
80
+
81
+ MCDB_EXPECTED_OWNED=0
82
+ MCDB_FOREIGN_EXPECTED=0
83
+ MCDB_UNEXPECTED_LIBRARY=0
84
+ MCDB_REGISTERED_ROWS=()
85
+
86
+ rows="$(mcdb_query "SELECT name, dl FROM mysql.func WHERE dl = '${MCDB_UDF_LIBRARY}' OR name IN ('multi_compress_db_version', 'multi_compress_db_is_valid', 'multi_compress_db_decompress') ORDER BY name;")"
87
+ while IFS=$'\t' read -r name dl; do
88
+ [ -n "$name" ] || continue
89
+ MCDB_REGISTERED_ROWS+=("$name"$'\t'"$dl")
90
+ case "$name" in
91
+ multi_compress_db_version|multi_compress_db_is_valid|multi_compress_db_decompress)
92
+ if [ "$dl" = "$MCDB_UDF_LIBRARY" ]; then
93
+ MCDB_EXPECTED_OWNED=$((MCDB_EXPECTED_OWNED + 1))
94
+ else
95
+ MCDB_FOREIGN_EXPECTED=$((MCDB_FOREIGN_EXPECTED + 1))
96
+ fi
97
+ ;;
98
+ *)
99
+ if [ "$dl" = "$MCDB_UDF_LIBRARY" ]; then
100
+ MCDB_UNEXPECTED_LIBRARY=$((MCDB_UNEXPECTED_LIBRARY + 1))
101
+ fi
102
+ ;;
103
+ esac
104
+ done <<< "$rows"
105
+ }
106
+
107
+ mcdb_state_is_clean_absent() {
108
+ [ "$MCDB_EXPECTED_OWNED" -eq 0 ] && [ "$MCDB_FOREIGN_EXPECTED" -eq 0 ] && [ "$MCDB_UNEXPECTED_LIBRARY" -eq 0 ]
109
+ }
110
+
111
+ mcdb_state_is_clean_enabled() {
112
+ [ "$MCDB_EXPECTED_OWNED" -eq 3 ] && [ "$MCDB_FOREIGN_EXPECTED" -eq 0 ] && [ "$MCDB_UNEXPECTED_LIBRARY" -eq 0 ]
113
+ }
114
+
115
+ mcdb_require_clean_owned_state() {
116
+ if [ "$MCDB_FOREIGN_EXPECTED" -ne 0 ]; then
117
+ mcdb_die 'one or more expected UDF names are registered to a foreign SONAME; refusing to modify them'
118
+ fi
119
+ if [ "$MCDB_UNEXPECTED_LIBRARY" -ne 0 ]; then
120
+ mcdb_die "unexpected registrations reference ${MCDB_UDF_LIBRARY}; refusing to modify the shared library"
121
+ fi
122
+ }
123
+
124
+ mcdb_smoke_check() {
125
+ local version decoded
126
+ version="$(mcdb_query 'SELECT multi_compress_db_version();' | tr -d '\r\n')"
127
+ case "$version" in
128
+ *'MCDB1'*) ;;
129
+ *) mcdb_die "version() smoke check failed: $version" ;;
130
+ esac
131
+ decoded="$(mcdb_query "SELECT multi_compress_db_decompress(UNHEX('${MCDB_EMPTY_ENVELOPE_HEX}')) = '';" | tr -d '\r\n')"
132
+ [ "$decoded" = '1' ] || mcdb_die 'decode smoke check failed'
133
+ }
134
+
135
+ mcdb_drop_owned_udfs() {
136
+ mcdb_read_udf_state
137
+ mcdb_require_clean_owned_state
138
+ for name in "${MCDB_UDF_NAMES[@]}"; do
139
+ if mcdb_query "SELECT COUNT(*) FROM mysql.func WHERE name = '${name}' AND dl = '${MCDB_UDF_LIBRARY}';" | tr -d '\r\n' | grep -qx '1'; then
140
+ mcdb_query "DROP FUNCTION ${name};"
141
+ fi
142
+ done
143
+ }
144
+
145
+ mcdb_register_expected_udfs() {
146
+ "$MYSQL" "${MYSQL_ARGS[@]}" < "$ROOT/mysql_udf/sql/install.sql"
147
+ }
148
+
149
+ mcdb_library_checksum() {
150
+ local library="$1"
151
+ if [ ! -f "$library" ]; then
152
+ printf '%s\n' 'missing'
153
+ elif command -v sha256sum >/dev/null 2>&1; then
154
+ sha256sum "$library" | awk '{print $1}'
155
+ elif command -v shasum >/dev/null 2>&1; then
156
+ shasum -a 256 "$library" | awk '{print $1}'
157
+ else
158
+ printf '%s\n' 'unavailable (sha256sum or shasum is required)'
159
+ fi
160
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ MYSQL="${MYSQL:-mysql}"
6
+ DEFAULTS_FILE=""
7
+ MYSQL_SOCKET="${MYSQL_SOCKET:-}"
8
+
9
+ usage() {
10
+ cat <<'TEXT'
11
+ Usage: MYSQL_SOCKET=/run/mysqld/mysqld.sock ./bin/disable [--mysql PATH] [--defaults-extra-file PATH]
12
+
13
+ Removes only MultiCompress-owned UDF registrations and leaves the native library in place.
14
+ TEXT
15
+ }
16
+
17
+ while [ "$#" -gt 0 ]; do
18
+ case "$1" in
19
+ --mysql)
20
+ [ "$#" -ge 2 ] || { echo '--mysql requires a path' >&2; exit 2; }
21
+ MYSQL="$2"; shift 2 ;;
22
+ --defaults-extra-file)
23
+ [ "$#" -ge 2 ] || { echo '--defaults-extra-file requires a path' >&2; exit 2; }
24
+ DEFAULTS_FILE="$2"; shift 2 ;;
25
+ --help|-h)
26
+ usage; exit 0 ;;
27
+ *)
28
+ echo "multi_compress mysql disable: unknown option: $1" >&2; exit 2 ;;
29
+ esac
30
+ done
31
+
32
+ # shellcheck source=common
33
+ source "$ROOT/bin/common"
34
+ mcdb_resolve_mysql
35
+ mcdb_mysql_args
36
+ mcdb_require_mysql57
37
+ mcdb_require_expected_socket
38
+ mcdb_drop_owned_udfs
39
+
40
+ echo 'MultiCompress MySQL UDFs are disabled.'