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,23 @@
1
+ -- Install into a dedicated schema.
2
+ CREATE SCHEMA IF NOT EXISTS multi_compress;
3
+ CREATE EXTENSION multi_compress WITH SCHEMA multi_compress;
4
+
5
+ SELECT multi_compress.multi_compress_db_version();
6
+ SELECT multi_compress.multi_compress_db_is_valid(payload_compressed)
7
+ FROM events
8
+ WHERE id = 123;
9
+
10
+ SELECT multi_compress.multi_compress_db_decompress(payload_compressed)
11
+ FROM events
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,24 @@
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
+ CREATE FUNCTION multi_compress_db_is_valid(blob bytea)
11
+ RETURNS boolean
12
+ AS 'MODULE_PATHNAME', 'multi_compress_db_is_valid'
13
+ LANGUAGE C
14
+ IMMUTABLE
15
+ STRICT
16
+ PARALLEL SAFE;
17
+
18
+ CREATE FUNCTION multi_compress_db_decompress(blob bytea)
19
+ RETURNS text
20
+ AS 'MODULE_PATHNAME', 'multi_compress_db_decompress'
21
+ LANGUAGE C
22
+ IMMUTABLE
23
+ STRICT
24
+ 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;
@@ -0,0 +1,2 @@
1
+ -- Drop dependent views first, then remove the extension from this database.
2
+ DROP EXTENSION IF EXISTS multi_compress;
@@ -0,0 +1,7 @@
1
+ -- Replace multi_compress with the schema used in CREATE EXTENSION ... WITH SCHEMA.
2
+ CREATE OR REPLACE VIEW events_readable AS
3
+ SELECT
4
+ id,
5
+ multi_compress.multi_compress_db_decompress(payload_compressed) AS payload,
6
+ created_at
7
+ FROM events;
@@ -0,0 +1,2 @@
1
+ /* Shared MCDB1 decoder. The implementation lives once in db_core/. */
2
+ #include "../../db_core/src/mcdb_format.c"
@@ -0,0 +1,381 @@
1
+ #include "postgres.h"
2
+
3
+ #include "fmgr.h"
4
+ #include "mb/pg_wchar.h"
5
+ #include "utils/builtins.h"
6
+ #include "utils/memutils.h"
7
+ #include "varatt.h"
8
+
9
+ #include <stdio.h>
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+
13
+ #include <zstd.h>
14
+
15
+ #include "mcdb_format.h"
16
+
17
+ PG_MODULE_MAGIC;
18
+
19
+ PG_FUNCTION_INFO_V1(multi_compress_db_version);
20
+ PG_FUNCTION_INFO_V1(multi_compress_db_is_valid);
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;
45
+
46
+ static void mcdb_require_utf8_database(void) {
47
+ if (GetDatabaseEncoding() != PG_UTF8)
48
+ ereport(
49
+ ERROR,
50
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
51
+ errmsg("multi_compress requires a UTF-8 PostgreSQL database"),
52
+ errdetail("MCDB stores UTF-8 text and multi_compress_db_decompress returns text.")));
53
+ }
54
+
55
+ static void mcdb_raise(mcdb_status status, const char *detail) {
56
+ if (status == MCDB_ERR_ALLOC)
57
+ ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY),
58
+ errmsg("multi_compress: unable to allocate MCDB output")));
59
+
60
+ ereport(ERROR,
61
+ (errcode(ERRCODE_DATA_CORRUPTED), errmsg("multi_compress: invalid MCDB envelope"),
62
+ errdetail("%s", detail && detail[0] ? detail : mcdb_status_str(status))));
63
+ }
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
+ }
69
+
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
+ }
192
+
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",
204
+ MCDB_READER_VERSION, ZSTD_versionString());
205
+ PG_RETURN_TEXT_P(cstring_to_text(version));
206
+ }
207
+
208
+ Datum multi_compress_db_is_valid(PG_FUNCTION_ARGS) {
209
+ bytea *input;
210
+ char err[MCDB_ERRLEN];
211
+ mcdb_status status;
212
+ if (PG_ARGISNULL(0))
213
+ PG_RETURN_NULL();
214
+ input = PG_GETARG_BYTEA_PP(0);
215
+ status = mcdb_decode((const unsigned char *)VARDATA_ANY(input),
216
+ (size_t)VARSIZE_ANY_EXHDR(input), NULL, NULL, err);
217
+ PG_RETURN_BOOL(status == MCDB_OK);
218
+ }
219
+
220
+ Datum multi_compress_db_decompress(PG_FUNCTION_ARGS) {
221
+ bytea *input;
222
+ size_t output_len = 0;
223
+ char err[MCDB_ERRLEN];
224
+ mcdb_status status;
225
+ uint64_t original_size = 0;
226
+ text *result;
227
+ if (PG_ARGISNULL(0))
228
+ PG_RETURN_NULL();
229
+ mcdb_require_utf8_database();
230
+ input = PG_GETARG_BYTEA_PP(0);
231
+ status = mcdb_validate_header((const unsigned char *)VARDATA_ANY(input),
232
+ (size_t)VARSIZE_ANY_EXHDR(input), &original_size);
233
+ if (status != MCDB_OK)
234
+ mcdb_raise(status, mcdb_status_str(status));
235
+ result = (text *)palloc(VARHDRSZ + (size_t)original_size);
236
+ status = mcdb_decode_into((const unsigned char *)VARDATA_ANY(input),
237
+ (size_t)VARSIZE_ANY_EXHDR(input), (unsigned char *)VARDATA(result),
238
+ (size_t)original_size, &output_len, err);
239
+ if (status != MCDB_OK)
240
+ mcdb_raise(status, err);
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
+ }
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);
380
+ PG_RETURN_TEXT_P(result);
381
+ }