multi_compress 0.5.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/GET_STARTED.md +19 -0
- data/README.md +14 -1
- data/db_core/include/mcdb_format.h +67 -9
- data/db_core/src/mcdb_format.c +392 -139
- data/db_deployment/mysql/README.md +19 -0
- data/db_deployment/mysql/bin/common +70 -18
- data/db_deployment/mysql/bin/enable +5 -1
- data/db_deployment/mysql/bin/status +2 -0
- data/db_deployment/mysql/bin/upgrade +17 -5
- data/db_deployment/postgres/README.md +24 -0
- data/db_deployment/postgres/bin/enable +2 -0
- data/db_deployment/postgres/bin/install +4 -2
- data/docs/database-envelope-v2.md +147 -0
- data/docs/rfcs/0002-mcdb2-dictionaries.md +75 -0
- data/ext/multi_compress/multi_compress.c +32 -0
- data/lib/multi_compress/database.rb +255 -49
- data/lib/multi_compress/db_deployment.rb +423 -11
- data/lib/multi_compress/version.rb +1 -1
- data/mysql_udf/README.md +45 -0
- data/mysql_udf/sql/examples.sql +5 -0
- data/mysql_udf/sql/install.sql +25 -1
- data/mysql_udf/sql/uninstall.sql +7 -1
- data/mysql_udf/sql/views.sql +16 -4
- data/mysql_udf/src/multi_compress_mysql.c +383 -44
- data/mysql_udf/test/run_e2e.sh +119 -1
- data/postgres_extension/Makefile +2 -2
- data/postgres_extension/README.md +48 -0
- data/postgres_extension/multi_compress.control +2 -2
- data/postgres_extension/multi_compress_pg.exports +12 -0
- data/postgres_extension/sql/examples.sql +11 -0
- data/postgres_extension/sql/multi_compress--0.5.0--0.6.0.sql +59 -0
- data/postgres_extension/sql/multi_compress--0.6.0.sql +85 -0
- data/postgres_extension/src/multi_compress_pg.c +301 -16
- data/postgres_extension/test/run_e2e.sh +90 -2
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ebc73c83a9de27fcde1080879572c025a30a3f2bb391313d5c21e56fd558cde3
|
|
4
|
+
data.tar.gz: 20a2db05bc0ecafbb399a01275391e5d1c08feafe8fc51f683859463b9c14ff8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aeac010c1419ad5b10919ba1d3e18b2053950555117dffc1075be2ce7b1e6ae9b2a9ff14d2684556a9aa0904ad030f688f4d954cb710692b263e8bb4ca238365
|
|
7
|
+
data.tar.gz: 9493318cf1321bbe525a8f65992675ef7b0760bf853192cf04146b8280f32011c925a5576876787849737d26eea7cc807c739ff0d5182c652b939c0150c3bf5b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **MCDB2** — opt-in dictionary-backed SQL-readable envelopes for a homogeneous
|
|
7
|
+
`bytea`/`LONGBLOB` column. MCDB1 remains immutable and dictionary-free.
|
|
8
|
+
- **`MultiCompress::Database::Dictionary`** — strict zstd dictionary wrapper with
|
|
9
|
+
a signed-bigint-compatible application registry id, non-zero zstd DictID and
|
|
10
|
+
SHA-256 fingerprint. `MultiCompress::Database.compress(..., dictionary:)`
|
|
11
|
+
emits the 27-byte MCDB2 envelope.
|
|
12
|
+
- **Dictionary registry generator** — `multi_compress db registry postgres|mysql`
|
|
13
|
+
emits append-only registry/head DDL, metadata validation and freeze triggers.
|
|
14
|
+
- **Dictionary readable views** — `multi_compress db view ... --dictionary-table
|
|
15
|
+
... --dictionary-id-column ...` emits an INNER JOIN view that supplies the
|
|
16
|
+
registry dictionary to native decoder functions. MySQL output requests
|
|
17
|
+
`ALGORITHM=MERGE SQL SECURITY DEFINER`.
|
|
18
|
+
- **Native MCDB2 readers** for PostgreSQL and MySQL 5.7: dictionary reference,
|
|
19
|
+
dictionary SHA-256/DictID helpers, validity check and dictionary decode functions.
|
|
20
|
+
They use bounded DDict caches keyed by dictionary id + SHA-256 and verify bytes
|
|
21
|
+
before cache reuse.
|
|
22
|
+
- **`multi_compress_db_original_size(blob)`** on PostgreSQL and MySQL for optional
|
|
23
|
+
size-aware administrative filtering.
|
|
24
|
+
- PostgreSQL 17 and MySQL 5.7 E2E coverage for dictionary registry validation,
|
|
25
|
+
generated INNER JOIN views, wrong-dictionary rejection and plan shape checks.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- PostgreSQL deployment `make enable` upgrades an existing extension before
|
|
29
|
+
refreshing grants, so reader deployment precedes MCDB2 writes.
|
|
30
|
+
- MySQL UDF upgrade now preserves registrations for all MCDB1 and MCDB2 functions.
|
|
31
|
+
|
|
32
|
+
### Notes
|
|
33
|
+
- A dictionary is application data, not part of a DBA reader archive. It must stay
|
|
34
|
+
immutable and be backed up/replicated with every payload that references it.
|
|
35
|
+
- After the first MCDB2 write, retain a 0.6 reader on every reader host until all
|
|
36
|
+
MCDB2 rows have been retired; a 0.5 reader cannot decode version 2 envelopes.
|
|
37
|
+
|
|
3
38
|
## [0.5.0]
|
|
4
39
|
|
|
5
40
|
- MCDB1 now rejects trailing bytes, skippable frames, and concatenated zstd frames.
|
data/GET_STARTED.md
CHANGED
|
@@ -1051,3 +1051,22 @@ to the `Type` to enable `changed_in_place?` (which decompresses the old value
|
|
|
1051
1051
|
on each check).
|
|
1052
1052
|
|
|
1053
1053
|
This guide covers comprehensive usage of the MultiCompress gem. For advanced use cases or questions, see the source code or create an issue on GitHub.
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
## MCDB2 dictionary-backed SQL columns
|
|
1057
|
+
|
|
1058
|
+
Use `MultiCompress::Database` when PostgreSQL/MySQL/DBeaver must read a compressed
|
|
1059
|
+
column. MCDB1 is dictionary-free. MCDB2 is for many small, similarly shaped JSON or
|
|
1060
|
+
text rows and requires one immutable dictionary registry version per row:
|
|
1061
|
+
|
|
1062
|
+
```ruby
|
|
1063
|
+
dictionary = MultiCompress::Database::Dictionary.train(samples, id: 42, size: 32 * 1024)
|
|
1064
|
+
blob = MultiCompress::Database.compress(payload_json, dictionary: dictionary)
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
Install/upgrade the native database reader before writing MCDB2 data, generate the
|
|
1068
|
+
append-only registry DDL with `multi_compress db registry`, then generate an INNER
|
|
1069
|
+
JOIN readable view with `multi_compress db view --dictionary-table ...`. The complete
|
|
1070
|
+
protocol and rollout contract are in [`docs/database-envelope-v2.md`](docs/database-envelope-v2.md).
|
|
1071
|
+
|
|
1072
|
+
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.
|
data/README.md
CHANGED
|
@@ -187,7 +187,7 @@ single database column (optional ActiveRecord `Type`/`Coder` adapters and
|
|
|
187
187
|
optional Base64 for text columns). It is **not** the format for SQL-side
|
|
188
188
|
inspection.
|
|
189
189
|
|
|
190
|
-
For MySQL 5.7 or PostgreSQL + DBeaver, use the
|
|
190
|
+
For MySQL 5.7 or PostgreSQL + DBeaver, use the SQL-readable database API. `MCDB1` is dictionary-free; `MCDB2` is opt-in for a homogeneous column with an immutable zstd dictionary registry:
|
|
191
191
|
|
|
192
192
|
```ruby
|
|
193
193
|
require "multi_compress/database"
|
|
@@ -196,6 +196,17 @@ blob = MultiCompress::Database.compress("JSON or UTF-8 text") # store in LONGBLO
|
|
|
196
196
|
text = MultiCompress::Database.decompress(blob)
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
+
For repeated small JSON/text records with the same shape, use MCDB2 and keep the
|
|
200
|
+
dictionary as append-only application data:
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
dictionary = MultiCompress::Database::Dictionary.train(samples, id: 42)
|
|
204
|
+
blob = MultiCompress::Database.compress(payload_json, dictionary: dictionary)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
See [`docs/database-envelope-v2.md`](docs/database-envelope-v2.md) for the
|
|
208
|
+
registry, generated view, rollout and DBeaver flow.
|
|
209
|
+
|
|
199
210
|
For database-side reading, the installed gem creates a **self-contained DBA
|
|
200
211
|
bundle** from the exact gem version — no Git clone, manual `.so` copy, or
|
|
201
212
|
hand-written extension SQL:
|
|
@@ -236,3 +247,5 @@ app-server → DBA → DBeaver flow.
|
|
|
236
247
|
## License
|
|
237
248
|
|
|
238
249
|
MIT — see [LICENSE.txt](LICENSE.txt).
|
|
250
|
+
|
|
251
|
+
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.
|
|
@@ -4,21 +4,42 @@
|
|
|
4
4
|
#include <stddef.h>
|
|
5
5
|
#include <stdint.h>
|
|
6
6
|
|
|
7
|
+
#include <zstd.h>
|
|
8
|
+
|
|
7
9
|
#define MCDB_MAGIC0 0x4D /* 'M' */
|
|
8
10
|
#define MCDB_MAGIC1 0x43 /* 'C' */
|
|
9
11
|
#define MCDB_MAGIC2 0x44 /* 'D' */
|
|
10
12
|
#define MCDB_MAGIC3 0x42 /* 'B' */
|
|
11
13
|
|
|
12
|
-
#define
|
|
13
|
-
#define
|
|
14
|
-
#define
|
|
15
|
-
#define
|
|
16
|
-
|
|
17
|
-
#define
|
|
18
|
-
#define
|
|
14
|
+
#define MCDB_VERSION_V1 1
|
|
15
|
+
#define MCDB_VERSION_V2 2
|
|
16
|
+
#define MCDB_CODEC_ZSTD 1
|
|
17
|
+
#define MCDB_FLAGS_NONE 0
|
|
18
|
+
|
|
19
|
+
#define MCDB_V1_HEADER_SIZE 19u
|
|
20
|
+
#define MCDB_V2_HEADER_SIZE 27u
|
|
21
|
+
#define MCDB_HEADER_SIZE MCDB_V1_HEADER_SIZE /* MCDB1 compatibility alias */
|
|
22
|
+
#define MCDB_ORIGINAL_SIZE_OFFSET 7u
|
|
23
|
+
#define MCDB_CRC_OFFSET 15u
|
|
24
|
+
#define MCDB_DICTIONARY_REF_OFFSET 19u
|
|
25
|
+
|
|
26
|
+
#define MCDB_MAX_OUTPUT (16u * 1024u * 1024u) /* 16 MiB decompressed */
|
|
27
|
+
#define MCDB_MAX_DICTIONARY_BYTES (256u * 1024u)
|
|
28
|
+
#define MCDB_MAX_DICTIONARY_REF UINT64_C(0x7FFFFFFFFFFFFFFF)
|
|
29
|
+
#define MCDB_MAX_ENVELOPE_V1 16842771u
|
|
30
|
+
#define MCDB_MAX_ENVELOPE_V2 16842779u
|
|
31
|
+
#define MCDB_MAX_ENVELOPE MCDB_MAX_ENVELOPE_V2
|
|
19
32
|
|
|
33
|
+
#define MCDB_SHA256_BYTES 32u
|
|
20
34
|
#define MCDB_ERRLEN 256
|
|
21
|
-
#define MCDB_READER_VERSION "0.
|
|
35
|
+
#define MCDB_READER_VERSION "0.6.0"
|
|
36
|
+
|
|
37
|
+
typedef struct {
|
|
38
|
+
uint8_t version;
|
|
39
|
+
uint64_t original_size;
|
|
40
|
+
uint64_t dictionary_ref;
|
|
41
|
+
size_t header_size;
|
|
42
|
+
} mcdb_header;
|
|
22
43
|
|
|
23
44
|
typedef enum {
|
|
24
45
|
MCDB_OK = 0,
|
|
@@ -29,6 +50,10 @@ typedef enum {
|
|
|
29
50
|
MCDB_ERR_CODEC,
|
|
30
51
|
MCDB_ERR_FLAGS,
|
|
31
52
|
MCDB_ERR_SIZE_LIMIT,
|
|
53
|
+
MCDB_ERR_DICTIONARY_REQUIRED,
|
|
54
|
+
MCDB_ERR_DICTIONARY_REF,
|
|
55
|
+
MCDB_ERR_DICTIONARY_SIZE,
|
|
56
|
+
MCDB_ERR_DICTIONARY_ID,
|
|
32
57
|
MCDB_ERR_DECOMPRESS,
|
|
33
58
|
MCDB_ERR_TRAILING_DATA,
|
|
34
59
|
MCDB_ERR_FRAME_CONTENT_SIZE,
|
|
@@ -38,10 +63,19 @@ typedef enum {
|
|
|
38
63
|
MCDB_ERR_ALLOC
|
|
39
64
|
} mcdb_status;
|
|
40
65
|
|
|
66
|
+
/* Parses MCDB1 or MCDB2. It never decompresses. */
|
|
67
|
+
mcdb_status mcdb_parse_header(const unsigned char *in, size_t in_len, mcdb_header *out_header);
|
|
68
|
+
|
|
69
|
+
/* MCDB1 compatibility API. Rejects MCDB2. */
|
|
41
70
|
mcdb_status mcdb_validate_header(const unsigned char *in, size_t in_len,
|
|
42
71
|
uint64_t *out_original_size);
|
|
43
72
|
|
|
44
|
-
/*
|
|
73
|
+
/* MCDB2 parser. Rejects MCDB1. */
|
|
74
|
+
mcdb_status mcdb_validate_v2_header(const unsigned char *in, size_t in_len,
|
|
75
|
+
uint64_t *out_original_size,
|
|
76
|
+
uint64_t *out_dictionary_ref);
|
|
77
|
+
|
|
78
|
+
/* The caller must validate the MCDB1 header before calling. */
|
|
45
79
|
mcdb_status mcdb_decode_into(const unsigned char *in, size_t in_len,
|
|
46
80
|
unsigned char *out, size_t out_capacity,
|
|
47
81
|
size_t *out_len, char *errbuf);
|
|
@@ -50,6 +84,30 @@ mcdb_status mcdb_decode(const unsigned char *in, size_t in_len,
|
|
|
50
84
|
unsigned char **out, size_t *out_len,
|
|
51
85
|
char *errbuf);
|
|
52
86
|
|
|
87
|
+
/* MCDB2 decode. supplied_dictionary_ref must be the registry id passed by SQL. */
|
|
88
|
+
mcdb_status mcdb_decode_v2_into(const unsigned char *in, size_t in_len,
|
|
89
|
+
uint64_t supplied_dictionary_ref,
|
|
90
|
+
const unsigned char *dictionary,
|
|
91
|
+
size_t dictionary_len,
|
|
92
|
+
ZSTD_DCtx *dctx,
|
|
93
|
+
ZSTD_DDict *ddict,
|
|
94
|
+
unsigned char *out, size_t out_capacity,
|
|
95
|
+
size_t *out_len, char *errbuf);
|
|
96
|
+
|
|
97
|
+
mcdb_status mcdb_decode_v2(const unsigned char *in, size_t in_len,
|
|
98
|
+
uint64_t supplied_dictionary_ref,
|
|
99
|
+
const unsigned char *dictionary,
|
|
100
|
+
size_t dictionary_len,
|
|
101
|
+
unsigned char **out, size_t *out_len,
|
|
102
|
+
char *errbuf);
|
|
103
|
+
|
|
104
|
+
mcdb_status mcdb_validate_dictionary(const unsigned char *dictionary, size_t dictionary_len,
|
|
105
|
+
uint32_t *out_zstd_dictionary_id);
|
|
106
|
+
|
|
107
|
+
uint32_t mcdb_zstd_frame_dictionary_id(const unsigned char *frame, size_t frame_len);
|
|
108
|
+
uint64_t mcdb_dictionary_ref(const unsigned char *in, size_t in_len, mcdb_status *out_status);
|
|
109
|
+
void mcdb_sha256(const unsigned char *data, size_t len, unsigned char out[MCDB_SHA256_BYTES]);
|
|
110
|
+
|
|
53
111
|
const char *mcdb_status_str(mcdb_status s);
|
|
54
112
|
|
|
55
113
|
#endif
|