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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/GET_STARTED.md +19 -0
  4. data/README.md +14 -1
  5. data/db_core/include/mcdb_format.h +67 -9
  6. data/db_core/src/mcdb_format.c +392 -139
  7. data/db_deployment/mysql/README.md +19 -0
  8. data/db_deployment/mysql/bin/common +70 -18
  9. data/db_deployment/mysql/bin/enable +5 -1
  10. data/db_deployment/mysql/bin/status +2 -0
  11. data/db_deployment/mysql/bin/upgrade +17 -5
  12. data/db_deployment/postgres/README.md +24 -0
  13. data/db_deployment/postgres/bin/enable +2 -0
  14. data/db_deployment/postgres/bin/install +4 -2
  15. data/docs/database-envelope-v2.md +147 -0
  16. data/docs/rfcs/0002-mcdb2-dictionaries.md +75 -0
  17. data/ext/multi_compress/multi_compress.c +32 -0
  18. data/lib/multi_compress/database.rb +255 -49
  19. data/lib/multi_compress/db_deployment.rb +423 -11
  20. data/lib/multi_compress/version.rb +1 -1
  21. data/mysql_udf/README.md +45 -0
  22. data/mysql_udf/sql/examples.sql +5 -0
  23. data/mysql_udf/sql/install.sql +25 -1
  24. data/mysql_udf/sql/uninstall.sql +7 -1
  25. data/mysql_udf/sql/views.sql +16 -4
  26. data/mysql_udf/src/multi_compress_mysql.c +383 -44
  27. data/mysql_udf/test/run_e2e.sh +119 -1
  28. data/postgres_extension/Makefile +2 -2
  29. data/postgres_extension/README.md +48 -0
  30. data/postgres_extension/multi_compress.control +2 -2
  31. data/postgres_extension/multi_compress_pg.exports +12 -0
  32. data/postgres_extension/sql/examples.sql +11 -0
  33. data/postgres_extension/sql/multi_compress--0.5.0--0.6.0.sql +59 -0
  34. data/postgres_extension/sql/multi_compress--0.6.0.sql +85 -0
  35. data/postgres_extension/src/multi_compress_pg.c +301 -16
  36. data/postgres_extension/test/run_e2e.sh +90 -2
  37. metadata +6 -2
@@ -110,3 +110,51 @@ make e2e
110
110
 
111
111
  `test/run_e2e.sh` builds against a pinned PostgreSQL Docker target and is the
112
112
  server-side acceptance gate. It is not part of the end-user installation flow.
113
+
114
+ ## MCDB2 dictionary-backed columns
115
+
116
+ MCDB2 is an opt-in companion for one homogeneous column containing many similar
117
+ small JSON/text values. It does not replace MCDB1. First install the 0.6 reader
118
+ on every host, then run `make enable` once again to execute `ALTER EXTENSION
119
+ multi_compress UPDATE` and refresh function grants.
120
+
121
+ The dictionary is application data, not an extension file. Create the append-only
122
+ registry under a dedicated NOLOGIN owner and retain every version referenced by a
123
+ payload:
124
+
125
+ ```bash
126
+ bundle exec multi_compress db registry postgres \
127
+ --schema app \
128
+ --owner mcdb_dictionary_owner \
129
+ --migration-role app_migrations \
130
+ --output db/mcdb_dictionary_registry.sql
131
+ ```
132
+
133
+ For a new MCDB2 column, add a `payload_dictionary_id bigint NOT NULL` FK to
134
+ `app.mcdb_dictionary_versions`, register one strict
135
+ `MultiCompress::Database::Dictionary`, then generate the readable view:
136
+
137
+ ```bash
138
+ bundle exec multi_compress db view postgres \
139
+ --table app.events \
140
+ --column payload_compressed \
141
+ --dictionary-table app.mcdb_dictionary_versions \
142
+ --dictionary-id-column payload_dictionary_id \
143
+ --view admin.events_readable \
144
+ --columns id,created_at,status \
145
+ --as payload \
146
+ --output db/views/events_readable.sql
147
+ ```
148
+
149
+ The generated view uses an `INNER JOIN` and calls the four-argument native
150
+ function with payload, registry id, dictionary SHA-256 and bytes. It keeps raw
151
+ payloads/dictionary bytes out of the DBeaver role. The view owner needs source
152
+ and registry access; DBeaver needs `SELECT` on the view and the decoder
153
+ `EXECUTE` grant made by `make enable`.
154
+
155
+ Check production query shape with `EXPLAIN (ANALYZE, BUFFERS)` on an indexed
156
+ filter plus `LIMIT`. Do not search/order/group by decoded payload text. See
157
+ [`docs/database-envelope-v2.md`](../docs/database-envelope-v2.md) for the full
158
+ format and rollback contract.
159
+
160
+ 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.
@@ -1,4 +1,4 @@
1
- comment = 'MCDB1 reader for MultiCompress database envelopes'
2
- default_version = '0.5.0'
1
+ comment = 'MultiCompress MCDB1/MCDB2 zstd reader'
2
+ default_version = '0.6.0'
3
3
  module_pathname = '$libdir/multi_compress_pg'
4
4
  relocatable = true
@@ -4,9 +4,21 @@
4
4
  pg_finfo_multi_compress_db_version;
5
5
  pg_finfo_multi_compress_db_is_valid;
6
6
  pg_finfo_multi_compress_db_decompress;
7
+ pg_finfo_multi_compress_db_original_size;
8
+ pg_finfo_multi_compress_db_dictionary_ref;
9
+ pg_finfo_multi_compress_db_dictionary_zstd_id;
10
+ pg_finfo_multi_compress_db_dictionary_sha256;
11
+ pg_finfo_multi_compress_db_is_valid_dict;
12
+ pg_finfo_multi_compress_db_decompress_dict;
7
13
  multi_compress_db_version;
8
14
  multi_compress_db_is_valid;
9
15
  multi_compress_db_decompress;
16
+ multi_compress_db_original_size;
17
+ multi_compress_db_dictionary_ref;
18
+ multi_compress_db_dictionary_zstd_id;
19
+ multi_compress_db_dictionary_sha256;
20
+ multi_compress_db_is_valid_dict;
21
+ multi_compress_db_decompress_dict;
10
22
  local:
11
23
  *;
12
24
  };
@@ -10,3 +10,14 @@ WHERE id = 123;
10
10
  SELECT multi_compress.multi_compress_db_decompress(payload_compressed)
11
11
  FROM events
12
12
  WHERE id = 123;
13
+
14
+ -- MCDB2: dictionary-backed payloads. The generated view should be preferred:
15
+ -- SELECT id, payload FROM admin.events_readable WHERE id >= 100 ORDER BY id LIMIT 100;
16
+ --
17
+ -- Direct diagnostic shape:
18
+ -- SELECT multi_compress_db_decompress_dict(
19
+ -- e.payload_compressed, e.payload_dictionary_id, d.sha256, d.bytes
20
+ -- )
21
+ -- FROM app.events e
22
+ -- JOIN app.mcdb_dictionary_versions d ON d.id = e.payload_dictionary_id
23
+ -- WHERE e.id = 123;
@@ -0,0 +1,59 @@
1
+ \echo Use "ALTER EXTENSION multi_compress UPDATE" to load this file. \quit
2
+
3
+ CREATE FUNCTION multi_compress_db_original_size(blob bytea)
4
+ RETURNS bigint
5
+ AS 'MODULE_PATHNAME', 'multi_compress_db_original_size'
6
+ LANGUAGE C
7
+ IMMUTABLE
8
+ STRICT
9
+ PARALLEL SAFE;
10
+
11
+ CREATE FUNCTION multi_compress_db_dictionary_ref(blob bytea)
12
+ RETURNS bigint
13
+ AS 'MODULE_PATHNAME', 'multi_compress_db_dictionary_ref'
14
+ LANGUAGE C
15
+ IMMUTABLE
16
+ STRICT
17
+ PARALLEL SAFE;
18
+
19
+ CREATE FUNCTION multi_compress_db_dictionary_zstd_id(dictionary bytea)
20
+ RETURNS bigint
21
+ AS 'MODULE_PATHNAME', 'multi_compress_db_dictionary_zstd_id'
22
+ LANGUAGE C
23
+ IMMUTABLE
24
+ STRICT
25
+ PARALLEL SAFE;
26
+
27
+ CREATE FUNCTION multi_compress_db_dictionary_sha256(dictionary bytea)
28
+ RETURNS bytea
29
+ AS 'MODULE_PATHNAME', 'multi_compress_db_dictionary_sha256'
30
+ LANGUAGE C
31
+ IMMUTABLE
32
+ STRICT
33
+ PARALLEL SAFE;
34
+
35
+ CREATE FUNCTION multi_compress_db_is_valid_dict(
36
+ blob bytea,
37
+ dictionary_ref bigint,
38
+ dictionary_sha256 bytea,
39
+ dictionary bytea
40
+ )
41
+ RETURNS boolean
42
+ AS 'MODULE_PATHNAME', 'multi_compress_db_is_valid_dict'
43
+ LANGUAGE C
44
+ IMMUTABLE
45
+ STRICT
46
+ PARALLEL SAFE;
47
+
48
+ CREATE FUNCTION multi_compress_db_decompress_dict(
49
+ blob bytea,
50
+ dictionary_ref bigint,
51
+ dictionary_sha256 bytea,
52
+ dictionary bytea
53
+ )
54
+ RETURNS text
55
+ AS 'MODULE_PATHNAME', 'multi_compress_db_decompress_dict'
56
+ LANGUAGE C
57
+ IMMUTABLE
58
+ STRICT
59
+ PARALLEL SAFE;
@@ -0,0 +1,85 @@
1
+ \echo Use "CREATE EXTENSION multi_compress" to load this file. \quit
2
+
3
+ CREATE FUNCTION multi_compress_db_version()
4
+ RETURNS text
5
+ AS 'MODULE_PATHNAME', 'multi_compress_db_version'
6
+ LANGUAGE C
7
+ IMMUTABLE
8
+ PARALLEL SAFE;
9
+
10
+ -- MCDB1: dictionary-free, one blob argument.
11
+ CREATE FUNCTION multi_compress_db_is_valid(blob bytea)
12
+ RETURNS boolean
13
+ AS 'MODULE_PATHNAME', 'multi_compress_db_is_valid'
14
+ LANGUAGE C
15
+ IMMUTABLE
16
+ STRICT
17
+ PARALLEL SAFE;
18
+
19
+ CREATE FUNCTION multi_compress_db_decompress(blob bytea)
20
+ RETURNS text
21
+ AS 'MODULE_PATHNAME', 'multi_compress_db_decompress'
22
+ LANGUAGE C
23
+ IMMUTABLE
24
+ STRICT
25
+ PARALLEL SAFE;
26
+
27
+ -- MCDB2 metadata and dictionary registry validation helpers.
28
+ CREATE FUNCTION multi_compress_db_original_size(blob bytea)
29
+ RETURNS bigint
30
+ AS 'MODULE_PATHNAME', 'multi_compress_db_original_size'
31
+ LANGUAGE C
32
+ IMMUTABLE
33
+ STRICT
34
+ PARALLEL SAFE;
35
+
36
+ CREATE FUNCTION multi_compress_db_dictionary_ref(blob bytea)
37
+ RETURNS bigint
38
+ AS 'MODULE_PATHNAME', 'multi_compress_db_dictionary_ref'
39
+ LANGUAGE C
40
+ IMMUTABLE
41
+ STRICT
42
+ PARALLEL SAFE;
43
+
44
+ CREATE FUNCTION multi_compress_db_dictionary_zstd_id(dictionary bytea)
45
+ RETURNS bigint
46
+ AS 'MODULE_PATHNAME', 'multi_compress_db_dictionary_zstd_id'
47
+ LANGUAGE C
48
+ IMMUTABLE
49
+ STRICT
50
+ PARALLEL SAFE;
51
+
52
+ CREATE FUNCTION multi_compress_db_dictionary_sha256(dictionary bytea)
53
+ RETURNS bytea
54
+ AS 'MODULE_PATHNAME', 'multi_compress_db_dictionary_sha256'
55
+ LANGUAGE C
56
+ IMMUTABLE
57
+ STRICT
58
+ PARALLEL SAFE;
59
+
60
+ -- MCDB2: blob, immutable application registry id, SHA-256 bytes, dictionary bytes.
61
+ CREATE FUNCTION multi_compress_db_is_valid_dict(
62
+ blob bytea,
63
+ dictionary_ref bigint,
64
+ dictionary_sha256 bytea,
65
+ dictionary bytea
66
+ )
67
+ RETURNS boolean
68
+ AS 'MODULE_PATHNAME', 'multi_compress_db_is_valid_dict'
69
+ LANGUAGE C
70
+ IMMUTABLE
71
+ STRICT
72
+ PARALLEL SAFE;
73
+
74
+ CREATE FUNCTION multi_compress_db_decompress_dict(
75
+ blob bytea,
76
+ dictionary_ref bigint,
77
+ dictionary_sha256 bytea,
78
+ dictionary bytea
79
+ )
80
+ RETURNS text
81
+ AS 'MODULE_PATHNAME', 'multi_compress_db_decompress_dict'
82
+ LANGUAGE C
83
+ IMMUTABLE
84
+ STRICT
85
+ PARALLEL SAFE;
@@ -3,6 +3,7 @@
3
3
  #include "fmgr.h"
4
4
  #include "mb/pg_wchar.h"
5
5
  #include "utils/builtins.h"
6
+ #include "utils/memutils.h"
6
7
  #include "varatt.h"
7
8
 
8
9
  #include <stdio.h>
@@ -18,6 +19,29 @@ PG_MODULE_MAGIC;
18
19
  PG_FUNCTION_INFO_V1(multi_compress_db_version);
19
20
  PG_FUNCTION_INFO_V1(multi_compress_db_is_valid);
20
21
  PG_FUNCTION_INFO_V1(multi_compress_db_decompress);
22
+ PG_FUNCTION_INFO_V1(multi_compress_db_original_size);
23
+ PG_FUNCTION_INFO_V1(multi_compress_db_dictionary_ref);
24
+ PG_FUNCTION_INFO_V1(multi_compress_db_dictionary_zstd_id);
25
+ PG_FUNCTION_INFO_V1(multi_compress_db_dictionary_sha256);
26
+ PG_FUNCTION_INFO_V1(multi_compress_db_is_valid_dict);
27
+ PG_FUNCTION_INFO_V1(multi_compress_db_decompress_dict);
28
+
29
+ #define MCDB_PG_DDICT_CACHE_ENTRIES 4
30
+
31
+ typedef struct {
32
+ uint64_t dictionary_ref;
33
+ unsigned char sha256[MCDB_SHA256_BYTES];
34
+ unsigned char *bytes;
35
+ size_t bytes_len;
36
+ ZSTD_DDict *ddict;
37
+ uint64_t used_at;
38
+ } mcdb_pg_cache_entry;
39
+
40
+ typedef struct {
41
+ ZSTD_DCtx *dctx;
42
+ uint64_t clock;
43
+ mcdb_pg_cache_entry entries[MCDB_PG_DDICT_CACHE_ENTRIES];
44
+ } mcdb_pg_dictionary_cache;
21
45
 
22
46
  static void mcdb_require_utf8_database(void) {
23
47
  if (GetDatabaseEncoding() != PG_UTF8)
@@ -25,25 +49,158 @@ static void mcdb_require_utf8_database(void) {
25
49
  ERROR,
26
50
  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
27
51
  errmsg("multi_compress requires a UTF-8 PostgreSQL database"),
28
- errdetail("MCDB1 stores UTF-8 text and multi_compress_db_decompress returns text.")));
52
+ errdetail("MCDB stores UTF-8 text and multi_compress_db_decompress returns text.")));
29
53
  }
30
54
 
31
55
  static void mcdb_raise(mcdb_status status, const char *detail) {
32
56
  if (status == MCDB_ERR_ALLOC)
33
57
  ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY),
34
- errmsg("multi_compress: unable to allocate MCDB1 output")));
58
+ errmsg("multi_compress: unable to allocate MCDB output")));
35
59
 
36
60
  ereport(ERROR,
37
- (errcode(ERRCODE_DATA_CORRUPTED), errmsg("multi_compress: invalid MCDB1 envelope"),
61
+ (errcode(ERRCODE_DATA_CORRUPTED), errmsg("multi_compress: invalid MCDB envelope"),
38
62
  errdetail("%s", detail && detail[0] ? detail : mcdb_status_str(status))));
39
63
  }
40
64
 
41
- Datum multi_compress_db_version(PG_FUNCTION_ARGS) {
42
- char version[64];
65
+ static void mcdb_raise_dictionary(const char *detail) {
66
+ ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED),
67
+ errmsg("multi_compress: invalid MCDB2 dictionary"), errdetail("%s", detail)));
68
+ }
43
69
 
44
- (void)fcinfo;
70
+ static void mcdb_pg_cache_release(void *arg) {
71
+ mcdb_pg_dictionary_cache *cache = (mcdb_pg_dictionary_cache *)arg;
72
+ int i;
73
+
74
+ if (cache == NULL)
75
+ return;
76
+ for (i = 0; i < MCDB_PG_DDICT_CACHE_ENTRIES; i++) {
77
+ if (cache->entries[i].ddict)
78
+ ZSTD_freeDDict(cache->entries[i].ddict);
79
+ }
80
+ if (cache->dctx)
81
+ ZSTD_freeDCtx(cache->dctx);
82
+ }
83
+
84
+ static mcdb_pg_dictionary_cache *mcdb_pg_cache(FunctionCallInfo fcinfo) {
85
+ FmgrInfo *flinfo = fcinfo->flinfo;
86
+ mcdb_pg_dictionary_cache *cache;
87
+ MemoryContext oldcontext;
88
+
89
+ if (flinfo->fn_extra)
90
+ return (mcdb_pg_dictionary_cache *)flinfo->fn_extra;
91
+
92
+ oldcontext = MemoryContextSwitchTo(flinfo->fn_mcxt);
93
+ cache = (mcdb_pg_dictionary_cache *)palloc0(sizeof(*cache));
94
+ cache->dctx = ZSTD_createDCtx();
95
+ if (!cache->dctx) {
96
+ MemoryContextSwitchTo(oldcontext);
97
+ ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY),
98
+ errmsg("multi_compress: unable to allocate zstd context")));
99
+ }
100
+ {
101
+ MemoryContextCallback *callback = (MemoryContextCallback *)palloc0(sizeof(*callback));
102
+ callback->func = mcdb_pg_cache_release;
103
+ callback->arg = cache;
104
+ MemoryContextRegisterResetCallback(flinfo->fn_mcxt, callback);
105
+ }
106
+ flinfo->fn_extra = cache;
107
+ MemoryContextSwitchTo(oldcontext);
108
+ return cache;
109
+ }
110
+
111
+ static int mcdb_pg_cache_victim(mcdb_pg_dictionary_cache *cache, int same_key) {
112
+ int i;
113
+ int victim = 0;
114
+ uint64_t oldest = UINT64_MAX;
115
+
116
+ if (same_key >= 0)
117
+ return same_key;
118
+ for (i = 0; i < MCDB_PG_DDICT_CACHE_ENTRIES; i++) {
119
+ if (!cache->entries[i].ddict)
120
+ return i;
121
+ if (cache->entries[i].used_at < oldest) {
122
+ oldest = cache->entries[i].used_at;
123
+ victim = i;
124
+ }
125
+ }
126
+ return victim;
127
+ }
128
+
129
+ static ZSTD_DDict *mcdb_pg_cached_ddict(FunctionCallInfo fcinfo, int64 dictionary_ref,
130
+ const unsigned char *dictionary_sha256,
131
+ const unsigned char *dictionary, size_t dictionary_len,
132
+ char *errbuf, mcdb_status *out_status) {
133
+ mcdb_pg_dictionary_cache *cache = mcdb_pg_cache(fcinfo);
134
+ unsigned char actual_sha256[MCDB_SHA256_BYTES];
135
+ int i;
136
+ int same_key = -1;
137
+ int victim;
138
+ ZSTD_DDict *ddict;
139
+ MemoryContext oldcontext;
140
+
141
+ *out_status = MCDB_OK;
142
+ for (i = 0; i < MCDB_PG_DDICT_CACHE_ENTRIES; i++) {
143
+ mcdb_pg_cache_entry *entry = &cache->entries[i];
144
+ if (!entry->ddict || entry->dictionary_ref != (uint64_t)dictionary_ref ||
145
+ memcmp(entry->sha256, dictionary_sha256, MCDB_SHA256_BYTES) != 0)
146
+ continue;
147
+ same_key = i;
148
+ if (entry->bytes_len == dictionary_len &&
149
+ memcmp(entry->bytes, dictionary, dictionary_len) == 0) {
150
+ entry->used_at = ++cache->clock;
151
+ return entry->ddict;
152
+ }
153
+ break;
154
+ }
155
+
156
+ mcdb_sha256(dictionary, dictionary_len, actual_sha256);
157
+ if (memcmp(actual_sha256, dictionary_sha256, MCDB_SHA256_BYTES) != 0) {
158
+ snprintf(errbuf, MCDB_ERRLEN, "dictionary sha256 does not match dictionary bytes");
159
+ *out_status = MCDB_ERR_DICTIONARY_ID;
160
+ return NULL;
161
+ }
162
+ *out_status = mcdb_validate_dictionary(dictionary, dictionary_len, NULL);
163
+ if (*out_status != MCDB_OK) {
164
+ snprintf(errbuf, MCDB_ERRLEN,
165
+ "dictionary is not a conformant zstd dictionary with a non-zero DictID");
166
+ return NULL;
167
+ }
168
+
169
+ ddict = ZSTD_createDDict(dictionary, dictionary_len);
170
+ if (!ddict) {
171
+ *out_status = MCDB_ERR_ALLOC;
172
+ return NULL;
173
+ }
174
+
175
+ victim = mcdb_pg_cache_victim(cache, same_key);
176
+ if (cache->entries[victim].ddict)
177
+ ZSTD_freeDDict(cache->entries[victim].ddict);
178
+ if (cache->entries[victim].bytes)
179
+ pfree(cache->entries[victim].bytes);
180
+
181
+ oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
182
+ cache->entries[victim].bytes = (unsigned char *)palloc(dictionary_len);
183
+ memcpy(cache->entries[victim].bytes, dictionary, dictionary_len);
184
+ MemoryContextSwitchTo(oldcontext);
185
+ cache->entries[victim].dictionary_ref = (uint64_t)dictionary_ref;
186
+ memcpy(cache->entries[victim].sha256, dictionary_sha256, MCDB_SHA256_BYTES);
187
+ cache->entries[victim].bytes_len = dictionary_len;
188
+ cache->entries[victim].ddict = ddict;
189
+ cache->entries[victim].used_at = ++cache->clock;
190
+ return ddict;
191
+ }
45
192
 
46
- snprintf(version, sizeof(version), "multi_compress reader %s; MCDB1; zstd %s",
193
+ static bytea *mcdb_bytea_sha256(const unsigned char *bytes, size_t bytes_len) {
194
+ bytea *result = (bytea *)palloc(VARHDRSZ + MCDB_SHA256_BYTES);
195
+ mcdb_sha256(bytes, bytes_len, (unsigned char *)VARDATA(result));
196
+ SET_VARSIZE(result, VARHDRSZ + MCDB_SHA256_BYTES);
197
+ return result;
198
+ }
199
+
200
+ Datum multi_compress_db_version(PG_FUNCTION_ARGS) {
201
+ char version[96];
202
+ (void)fcinfo;
203
+ snprintf(version, sizeof(version), "multi_compress reader %s; MCDB1 + MCDB2; zstd %s",
47
204
  MCDB_READER_VERSION, ZSTD_versionString());
48
205
  PG_RETURN_TEXT_P(cstring_to_text(version));
49
206
  }
@@ -52,14 +209,11 @@ Datum multi_compress_db_is_valid(PG_FUNCTION_ARGS) {
52
209
  bytea *input;
53
210
  char err[MCDB_ERRLEN];
54
211
  mcdb_status status;
55
-
56
212
  if (PG_ARGISNULL(0))
57
213
  PG_RETURN_NULL();
58
-
59
214
  input = PG_GETARG_BYTEA_PP(0);
60
215
  status = mcdb_decode((const unsigned char *)VARDATA_ANY(input),
61
216
  (size_t)VARSIZE_ANY_EXHDR(input), NULL, NULL, err);
62
-
63
217
  PG_RETURN_BOOL(status == MCDB_OK);
64
218
  }
65
219
 
@@ -70,27 +224,158 @@ Datum multi_compress_db_decompress(PG_FUNCTION_ARGS) {
70
224
  mcdb_status status;
71
225
  uint64_t original_size = 0;
72
226
  text *result;
73
-
74
227
  if (PG_ARGISNULL(0))
75
228
  PG_RETURN_NULL();
76
-
77
229
  mcdb_require_utf8_database();
78
-
79
230
  input = PG_GETARG_BYTEA_PP(0);
80
231
  status = mcdb_validate_header((const unsigned char *)VARDATA_ANY(input),
81
232
  (size_t)VARSIZE_ANY_EXHDR(input), &original_size);
82
233
  if (status != MCDB_OK)
83
234
  mcdb_raise(status, mcdb_status_str(status));
84
-
85
235
  result = (text *)palloc(VARHDRSZ + (size_t)original_size);
86
-
87
236
  status = mcdb_decode_into((const unsigned char *)VARDATA_ANY(input),
88
237
  (size_t)VARSIZE_ANY_EXHDR(input), (unsigned char *)VARDATA(result),
89
238
  (size_t)original_size, &output_len, err);
90
239
  if (status != MCDB_OK)
91
240
  mcdb_raise(status, err);
92
-
93
241
  SET_VARSIZE(result, VARHDRSZ + output_len);
242
+ PG_RETURN_TEXT_P(result);
243
+ }
244
+
245
+ Datum multi_compress_db_original_size(PG_FUNCTION_ARGS) {
246
+ bytea *input;
247
+ mcdb_header header;
248
+ mcdb_status status;
249
+ if (PG_ARGISNULL(0))
250
+ PG_RETURN_NULL();
251
+ input = PG_GETARG_BYTEA_PP(0);
252
+ status = mcdb_parse_header((const unsigned char *)VARDATA_ANY(input),
253
+ (size_t)VARSIZE_ANY_EXHDR(input), &header);
254
+ if (status != MCDB_OK)
255
+ mcdb_raise(status, mcdb_status_str(status));
256
+ PG_RETURN_INT64((int64)header.original_size);
257
+ }
258
+
259
+ Datum multi_compress_db_dictionary_ref(PG_FUNCTION_ARGS) {
260
+ bytea *input;
261
+ uint64_t ref;
262
+ mcdb_status status;
263
+ if (PG_ARGISNULL(0))
264
+ PG_RETURN_NULL();
265
+ input = PG_GETARG_BYTEA_PP(0);
266
+ ref = mcdb_dictionary_ref((const unsigned char *)VARDATA_ANY(input),
267
+ (size_t)VARSIZE_ANY_EXHDR(input), &status);
268
+ if (status != MCDB_OK)
269
+ mcdb_raise(status, mcdb_status_str(status));
270
+ PG_RETURN_INT64((int64)ref);
271
+ }
272
+
273
+ Datum multi_compress_db_dictionary_zstd_id(PG_FUNCTION_ARGS) {
274
+ bytea *dictionary;
275
+ uint32_t dict_id;
276
+ mcdb_status status;
277
+ if (PG_ARGISNULL(0))
278
+ PG_RETURN_NULL();
279
+ dictionary = PG_GETARG_BYTEA_PP(0);
280
+ status = mcdb_validate_dictionary((const unsigned char *)VARDATA_ANY(dictionary),
281
+ (size_t)VARSIZE_ANY_EXHDR(dictionary), &dict_id);
282
+ if (status != MCDB_OK)
283
+ mcdb_raise_dictionary(mcdb_status_str(status));
284
+ PG_RETURN_INT64((int64)dict_id);
285
+ }
286
+
287
+ Datum multi_compress_db_dictionary_sha256(PG_FUNCTION_ARGS) {
288
+ bytea *dictionary;
289
+ if (PG_ARGISNULL(0))
290
+ PG_RETURN_NULL();
291
+ dictionary = PG_GETARG_BYTEA_PP(0);
292
+ PG_RETURN_BYTEA_P(mcdb_bytea_sha256((const unsigned char *)VARDATA_ANY(dictionary),
293
+ (size_t)VARSIZE_ANY_EXHDR(dictionary)));
294
+ }
295
+
296
+ Datum multi_compress_db_is_valid_dict(PG_FUNCTION_ARGS) {
297
+ bytea *input;
298
+ int64 dictionary_ref;
299
+ bytea *dictionary_sha256;
300
+ bytea *dictionary;
301
+ ZSTD_DDict *ddict;
302
+ char err[MCDB_ERRLEN];
303
+ mcdb_status status;
304
+ uint64_t original_size;
305
+ unsigned char *output;
306
+ size_t output_len;
307
+
308
+ if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3))
309
+ PG_RETURN_NULL();
310
+ dictionary_ref = PG_GETARG_INT64(1);
311
+ if (dictionary_ref <= 0)
312
+ PG_RETURN_BOOL(false);
313
+ dictionary_sha256 = PG_GETARG_BYTEA_PP(2);
314
+ if (VARSIZE_ANY_EXHDR(dictionary_sha256) != MCDB_SHA256_BYTES)
315
+ PG_RETURN_BOOL(false);
316
+ input = PG_GETARG_BYTEA_PP(0);
317
+ dictionary = PG_GETARG_BYTEA_PP(3);
318
+ ddict = mcdb_pg_cached_ddict(fcinfo, dictionary_ref,
319
+ (const unsigned char *)VARDATA_ANY(dictionary_sha256),
320
+ (const unsigned char *)VARDATA_ANY(dictionary),
321
+ (size_t)VARSIZE_ANY_EXHDR(dictionary), err, &status);
322
+ if (!ddict || status != MCDB_OK)
323
+ PG_RETURN_BOOL(false);
324
+ status = mcdb_validate_v2_header((const unsigned char *)VARDATA_ANY(input),
325
+ (size_t)VARSIZE_ANY_EXHDR(input), &original_size, NULL);
326
+ if (status != MCDB_OK)
327
+ PG_RETURN_BOOL(false);
328
+ output = (unsigned char *)palloc((size_t)original_size ? (size_t)original_size : 1);
329
+ status = mcdb_decode_v2_into((const unsigned char *)VARDATA_ANY(input),
330
+ (size_t)VARSIZE_ANY_EXHDR(input), (uint64_t)dictionary_ref,
331
+ (const unsigned char *)VARDATA_ANY(dictionary),
332
+ (size_t)VARSIZE_ANY_EXHDR(dictionary), mcdb_pg_cache(fcinfo)->dctx,
333
+ ddict, output, (size_t)original_size, &output_len, err);
334
+ pfree(output);
335
+ PG_RETURN_BOOL(status == MCDB_OK);
336
+ }
94
337
 
338
+ Datum multi_compress_db_decompress_dict(PG_FUNCTION_ARGS) {
339
+ bytea *input;
340
+ int64 dictionary_ref;
341
+ bytea *dictionary_sha256;
342
+ bytea *dictionary;
343
+ ZSTD_DDict *ddict;
344
+ char err[MCDB_ERRLEN];
345
+ mcdb_status status;
346
+ uint64_t original_size;
347
+ size_t output_len = 0;
348
+ text *result;
349
+
350
+ if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3))
351
+ PG_RETURN_NULL();
352
+ mcdb_require_utf8_database();
353
+ dictionary_ref = PG_GETARG_INT64(1);
354
+ if (dictionary_ref <= 0)
355
+ mcdb_raise_dictionary("dictionary reference must be a positive signed bigint");
356
+ dictionary_sha256 = PG_GETARG_BYTEA_PP(2);
357
+ if (VARSIZE_ANY_EXHDR(dictionary_sha256) != MCDB_SHA256_BYTES)
358
+ mcdb_raise_dictionary("dictionary sha256 must be exactly 32 bytes");
359
+ input = PG_GETARG_BYTEA_PP(0);
360
+ dictionary = PG_GETARG_BYTEA_PP(3);
361
+ ddict = mcdb_pg_cached_ddict(fcinfo, dictionary_ref,
362
+ (const unsigned char *)VARDATA_ANY(dictionary_sha256),
363
+ (const unsigned char *)VARDATA_ANY(dictionary),
364
+ (size_t)VARSIZE_ANY_EXHDR(dictionary), err, &status);
365
+ if (!ddict || status != MCDB_OK)
366
+ mcdb_raise(status, err);
367
+ status = mcdb_validate_v2_header((const unsigned char *)VARDATA_ANY(input),
368
+ (size_t)VARSIZE_ANY_EXHDR(input), &original_size, NULL);
369
+ if (status != MCDB_OK)
370
+ mcdb_raise(status, mcdb_status_str(status));
371
+ result = (text *)palloc(VARHDRSZ + (size_t)original_size);
372
+ status = mcdb_decode_v2_into(
373
+ (const unsigned char *)VARDATA_ANY(input), (size_t)VARSIZE_ANY_EXHDR(input),
374
+ (uint64_t)dictionary_ref, (const unsigned char *)VARDATA_ANY(dictionary),
375
+ (size_t)VARSIZE_ANY_EXHDR(dictionary), mcdb_pg_cache(fcinfo)->dctx, ddict,
376
+ (unsigned char *)VARDATA(result), (size_t)original_size, &output_len, err);
377
+ if (status != MCDB_OK)
378
+ mcdb_raise(status, err);
379
+ SET_VARSIZE(result, VARHDRSZ + output_len);
95
380
  PG_RETURN_TEXT_P(result);
96
381
  }