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
|
@@ -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.
|
|
@@ -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("
|
|
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
|
|
58
|
+
errmsg("multi_compress: unable to allocate MCDB output")));
|
|
35
59
|
|
|
36
60
|
ereport(ERROR,
|
|
37
|
-
(errcode(ERRCODE_DATA_CORRUPTED), errmsg("multi_compress: invalid
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|