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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +111 -0
- data/GET_STARTED.md +207 -0
- data/README.md +69 -0
- data/db_core/include/mcdb_format.h +113 -0
- data/db_core/src/mcdb_format.c +594 -0
- data/db_deployment/mysql/Makefile +48 -0
- data/db_deployment/mysql/README.md +61 -0
- data/db_deployment/mysql/bin/common +212 -0
- data/db_deployment/mysql/bin/disable +40 -0
- data/db_deployment/mysql/bin/doctor +64 -0
- data/db_deployment/mysql/bin/enable +61 -0
- data/db_deployment/mysql/bin/install +64 -0
- data/db_deployment/mysql/bin/status +67 -0
- data/db_deployment/mysql/bin/uninstall +47 -0
- data/db_deployment/mysql/bin/upgrade +127 -0
- data/db_deployment/mysql/bin/verify +14 -0
- data/db_deployment/postgres/Makefile +45 -0
- data/db_deployment/postgres/README.md +84 -0
- data/db_deployment/postgres/bin/doctor +61 -0
- data/db_deployment/postgres/bin/enable +138 -0
- data/db_deployment/postgres/bin/install +106 -0
- data/db_deployment/postgres/bin/uninstall +54 -0
- data/db_deployment/postgres/bin/verify +14 -0
- data/docs/database-envelope-v1.md +159 -0
- data/docs/database-envelope-v2.md +147 -0
- data/docs/rfcs/0002-mcdb2-dictionaries.md +75 -0
- data/exe/multi_compress +228 -0
- data/ext/multi_compress/extconf.rb +4 -0
- data/ext/multi_compress/multi_compress.c +62 -0
- data/lib/multi_compress/active_record.rb +61 -0
- data/lib/multi_compress/codec.rb +164 -0
- data/lib/multi_compress/database.rb +312 -0
- data/lib/multi_compress/db_deployment.rb +868 -0
- data/lib/multi_compress/version.rb +1 -1
- data/multi_compress.gemspec +55 -0
- data/mysql_udf/Makefile +96 -0
- data/mysql_udf/README.md +140 -0
- data/mysql_udf/sql/examples.sql +16 -0
- data/mysql_udf/sql/install.sql +39 -0
- data/mysql_udf/sql/uninstall.sql +10 -0
- data/mysql_udf/sql/views.sql +20 -0
- data/mysql_udf/src/multi_compress_mysql.c +472 -0
- data/mysql_udf/src/mysql_udf_abi_57.h +34 -0
- data/mysql_udf/test/mcdb_cli.c +60 -0
- data/mysql_udf/test/run_e2e.sh +414 -0
- data/postgres_extension/Makefile +74 -0
- data/postgres_extension/README.md +160 -0
- data/postgres_extension/multi_compress.control +4 -0
- data/postgres_extension/multi_compress_pg.exports +24 -0
- data/postgres_extension/sql/examples.sql +23 -0
- data/postgres_extension/sql/multi_compress--0.5.0--0.6.0.sql +59 -0
- data/postgres_extension/sql/multi_compress--0.5.0.sql +24 -0
- data/postgres_extension/sql/multi_compress--0.6.0.sql +85 -0
- data/postgres_extension/sql/uninstall.sql +2 -0
- data/postgres_extension/sql/views.sql +7 -0
- data/postgres_extension/src/mcdb_format.c +2 -0
- data/postgres_extension/src/multi_compress_pg.c +381 -0
- data/postgres_extension/test/run_e2e.sh +411 -0
- metadata +56 -3
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
cd "$(dirname "$0")/../.."
|
|
4
|
+
|
|
5
|
+
# Platform-specific manifest for postgres:17.9-bookworm on linux/amd64.
|
|
6
|
+
DEFAULT_POSTGRES_IMAGE="postgres:17.9-bookworm@sha256:3a69b9de644363f30110fbaa78b9e3298ce71bb1aa9124c0b0ceb0a2602c3283"
|
|
7
|
+
IMAGE="${MCDB_POSTGRES_IMAGE:-$DEFAULT_POSTGRES_IMAGE}"
|
|
8
|
+
PLATFORM="${MCDB_POSTGRES_PLATFORM:-linux/amd64}"
|
|
9
|
+
CONTAINER="${MCDB_POSTGRES_CONTAINER:-mcdb-postgres-e2e}"
|
|
10
|
+
WAIT_SECONDS="${MCDB_POSTGRES_WAIT_SECONDS:-90}"
|
|
11
|
+
WORK_ROOT="${MCDB_POSTGRES_WORK_ROOT:-$(mktemp -d -t multi-compress-postgres.XXXXXX)}"
|
|
12
|
+
REMOVE_WORK_ROOT=0
|
|
13
|
+
ARCHIVE="$WORK_ROOT/multi_compress-postgres.tar.gz"
|
|
14
|
+
GEM_UNPACK_DIR="$WORK_ROOT/unpacked"
|
|
15
|
+
GEM_FILE="$WORK_ROOT/multi_compress.gem"
|
|
16
|
+
PACKAGED_GEM_ROOT=""
|
|
17
|
+
BUNDLE_PARENT="/opt"
|
|
18
|
+
|
|
19
|
+
command -v docker >/dev/null 2>&1 || { echo "docker is required" >&2; exit 2; }
|
|
20
|
+
|
|
21
|
+
cleanup() {
|
|
22
|
+
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
|
|
23
|
+
[ "$REMOVE_WORK_ROOT" -eq 1 ] && rm -rf "$WORK_ROOT"
|
|
24
|
+
}
|
|
25
|
+
trap cleanup EXIT
|
|
26
|
+
|
|
27
|
+
if [ -z "${MCDB_POSTGRES_WORK_ROOT:-}" ]; then
|
|
28
|
+
REMOVE_WORK_ROOT=1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
ensure_ruby_extension() {
|
|
32
|
+
if ruby -Ilib -e 'require "multi_compress"; require "multi_compress/database"' >/dev/null 2>&1; then
|
|
33
|
+
return
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
echo "building Ruby extension..."
|
|
37
|
+
if command -v bundle >/dev/null 2>&1; then
|
|
38
|
+
bundle exec rake compile
|
|
39
|
+
else
|
|
40
|
+
rake compile
|
|
41
|
+
fi
|
|
42
|
+
ruby -Ilib -e 'require "multi_compress"; require "multi_compress/database"' >/dev/null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
require_fixture() {
|
|
46
|
+
local path="$1"
|
|
47
|
+
[ -f "$path" ] || { echo "missing committed fixture: $path" >&2; exit 2; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fixture_hex() {
|
|
51
|
+
ruby -e 'print File.binread(ARGV.fetch(0)).unpack1("H*")' "$1"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
prepare_packaged_gem() {
|
|
55
|
+
gem build multi_compress.gemspec --output "$GEM_FILE" >/dev/null
|
|
56
|
+
mkdir -p "$GEM_UNPACK_DIR"
|
|
57
|
+
gem unpack "$GEM_FILE" --target "$GEM_UNPACK_DIR" >/dev/null
|
|
58
|
+
shopt -s nullglob
|
|
59
|
+
roots=("$GEM_UNPACK_DIR"/*)
|
|
60
|
+
shopt -u nullglob
|
|
61
|
+
[ "${#roots[@]}" -eq 1 ] && [ -d "${roots[0]}" ] || { echo "could not unpack exactly one multi_compress gem root" >&2; exit 2; }
|
|
62
|
+
PACKAGED_GEM_ROOT="${roots[0]}"
|
|
63
|
+
[ -f "$PACKAGED_GEM_ROOT/multi_compress.gemspec" ] || { echo "packaged gem lacks gemspec" >&2; exit 2; }
|
|
64
|
+
[ -d "$PACKAGED_GEM_ROOT/lib" ] || { echo "packaged gem lacks lib" >&2; exit 2; }
|
|
65
|
+
[ -d "$PACKAGED_GEM_ROOT/exe" ] || { echo "packaged gem lacks exe" >&2; exit 2; }
|
|
66
|
+
[ -f "$PACKAGED_GEM_ROOT/lib/multi_compress/db_deployment.rb" ] || { echo "packaged gem lacks db deployment CLI" >&2; exit 2; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
db_cli() {
|
|
70
|
+
ruby -I "$PACKAGED_GEM_ROOT/lib" "$PACKAGED_GEM_ROOT/exe/multi_compress" db "$@"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
compress_hex() {
|
|
74
|
+
MCDB_TEXT="$1" ruby -Ilib -r multi_compress -r multi_compress/database \
|
|
75
|
+
-e 'print MultiCompress::Database.compress(ENV.fetch("MCDB_TEXT")).unpack1("H*")'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
mcdb2_fixture() {
|
|
79
|
+
MCDB_TEXT="$1" ruby -Ilib -r multi_compress -r multi_compress/database <<'RUBY'
|
|
80
|
+
samples = 256.times.map do |i|
|
|
81
|
+
%({"kind":"event","tenant":#{i % 8},"metadata":{"source":"worker","version":1,"name":"same-shape-#{i % 16}"},"payload":"#{"x" * (48 + i % 64)}"})
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
dictionary = MultiCompress::Database::Dictionary.train(samples, id: 42, size: 4096)
|
|
85
|
+
blob = MultiCompress::Database.compress(ENV.fetch("MCDB_TEXT"), dictionary: dictionary)
|
|
86
|
+
|
|
87
|
+
puts dictionary.id
|
|
88
|
+
puts dictionary.zstd_id
|
|
89
|
+
puts dictionary.sha256.unpack1("H*")
|
|
90
|
+
puts dictionary.bytes.unpack1("H*")
|
|
91
|
+
puts blob.unpack1("H*")
|
|
92
|
+
RUBY
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
bundle_name() {
|
|
96
|
+
ruby -I "$PACKAGED_GEM_ROOT/lib" -r multi_compress/version \
|
|
97
|
+
-e 'print "multi_compress-postgres-#{MultiCompress::VERSION}"'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
build_deployment_bundle() {
|
|
101
|
+
db_cli package postgres --output "$ARCHIVE" --force
|
|
102
|
+
test -s "$ARCHIVE"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
install_postgres_build_tools() {
|
|
106
|
+
local runtime_pg_config="$1"
|
|
107
|
+
|
|
108
|
+
docker exec -u 0 \
|
|
109
|
+
-e "MCDB_RUNTIME_PG_CONFIG=$runtime_pg_config" \
|
|
110
|
+
"$CONTAINER" sh -ceu '
|
|
111
|
+
runtime_pg_config="${MCDB_RUNTIME_PG_CONFIG:?missing runtime pg_config}"
|
|
112
|
+
pg_major="$("$runtime_pg_config" --version | awk '\''{print $2}'\'' | cut -d. -f1)"
|
|
113
|
+
case "$pg_major" in
|
|
114
|
+
""|*[!0-9]*)
|
|
115
|
+
echo "could not determine PostgreSQL major version" >&2
|
|
116
|
+
exit 2
|
|
117
|
+
;;
|
|
118
|
+
esac
|
|
119
|
+
|
|
120
|
+
if ! command -v make >/dev/null 2>&1 || ! command -v cc >/dev/null 2>&1; then
|
|
121
|
+
if ! command -v apt-get >/dev/null 2>&1; then
|
|
122
|
+
echo "PostgreSQL container has no supported package manager" >&2
|
|
123
|
+
exit 2
|
|
124
|
+
fi
|
|
125
|
+
apt-get update >&2
|
|
126
|
+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
127
|
+
build-essential ca-certificates curl gnupg tar gzip >&2
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
runtime_pgxs="$("$runtime_pg_config" --pgxs 2>/dev/null || true)"
|
|
131
|
+
runtime_headers="$("$runtime_pg_config" --includedir-server 2>/dev/null || true)"
|
|
132
|
+
if [ ! -f "$runtime_pgxs" ] || [ ! -f "$runtime_headers/postgres.h" ]; then
|
|
133
|
+
if ! command -v apt-get >/dev/null 2>&1; then
|
|
134
|
+
echo "PostgreSQL development headers are unavailable" >&2
|
|
135
|
+
exit 2
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
install -d -m 0755 /etc/apt/keyrings
|
|
139
|
+
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | \
|
|
140
|
+
gpg --dearmor --yes -o /etc/apt/keyrings/postgresql.gpg
|
|
141
|
+
. /etc/os-release
|
|
142
|
+
echo "deb [signed-by=/etc/apt/keyrings/postgresql.gpg] https://apt.postgresql.org/pub/repos/apt ${VERSION_CODENAME}-pgdg main" \
|
|
143
|
+
>/etc/apt/sources.list.d/pgdg.list
|
|
144
|
+
apt-get update >&2
|
|
145
|
+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
146
|
+
"postgresql-server-dev-${pg_major}" >&2
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
build_pg_config=""
|
|
150
|
+
for candidate in "/usr/lib/postgresql/${pg_major}/bin/pg_config" "$(command -v pg_config || true)"; do
|
|
151
|
+
[ -n "$candidate" ] && [ -x "$candidate" ] || continue
|
|
152
|
+
pgxs="$("$candidate" --pgxs 2>/dev/null || true)"
|
|
153
|
+
headers="$("$candidate" --includedir-server 2>/dev/null || true)"
|
|
154
|
+
if [ -f "$pgxs" ] && [ -f "$headers/postgres.h" ]; then
|
|
155
|
+
build_pg_config="$candidate"
|
|
156
|
+
break
|
|
157
|
+
fi
|
|
158
|
+
done
|
|
159
|
+
|
|
160
|
+
[ -n "$build_pg_config" ] || {
|
|
161
|
+
echo "PostgreSQL PGXS/server headers are unavailable" >&2
|
|
162
|
+
exit 2
|
|
163
|
+
}
|
|
164
|
+
printf "%s\n" "$build_pg_config"
|
|
165
|
+
'
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
install_bundle_on_db_host() {
|
|
169
|
+
local bundle_name="$1"
|
|
170
|
+
local build_pg_config="$2"
|
|
171
|
+
local runtime_pg_config="$3"
|
|
172
|
+
local bundle_root="${BUNDLE_PARENT}/${bundle_name}"
|
|
173
|
+
|
|
174
|
+
tar -tzf "$ARCHIVE" | grep -Fx "${bundle_name}/THIRD_PARTY_NOTICES.md" >/dev/null
|
|
175
|
+
if tar -tzf "$ARCHIVE" | grep -E '/\.DS_Store$' >/dev/null; then
|
|
176
|
+
echo 'deployment bundle must not contain .DS_Store' >&2
|
|
177
|
+
exit 2
|
|
178
|
+
fi
|
|
179
|
+
docker cp "$ARCHIVE" "$CONTAINER:/tmp/${bundle_name}.tar.gz"
|
|
180
|
+
docker exec -u 0 \
|
|
181
|
+
-e "MCDB_BUNDLE_ROOT=$bundle_root" \
|
|
182
|
+
-e "MCDB_BUILD_PG_CONFIG=$build_pg_config" \
|
|
183
|
+
-e "MCDB_RUNTIME_PG_CONFIG=$runtime_pg_config" \
|
|
184
|
+
"$CONTAINER" sh -ceu '
|
|
185
|
+
rm -rf "$MCDB_BUNDLE_ROOT"
|
|
186
|
+
mkdir -p "$(dirname "$MCDB_BUNDLE_ROOT")"
|
|
187
|
+
tar -xzf "/tmp/$(basename "$MCDB_BUNDLE_ROOT").tar.gz" -C "$(dirname "$MCDB_BUNDLE_ROOT")"
|
|
188
|
+
cd "$MCDB_BUNDLE_ROOT"
|
|
189
|
+
make verify
|
|
190
|
+
make doctor PG_CONFIG="$MCDB_BUILD_PG_CONFIG"
|
|
191
|
+
make install BUILD_PG_CONFIG="$MCDB_BUILD_PG_CONFIG" RUNTIME_PG_CONFIG="$MCDB_RUNTIME_PG_CONFIG"
|
|
192
|
+
'
|
|
193
|
+
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
apply_readable_view() {
|
|
197
|
+
local path="$1"
|
|
198
|
+
db_cli view postgres \
|
|
199
|
+
--table app.events \
|
|
200
|
+
--column payload_compressed \
|
|
201
|
+
--view admin.events_readable \
|
|
202
|
+
--columns id \
|
|
203
|
+
--output "$path"
|
|
204
|
+
docker exec -i "$CONTAINER" psql -X -U app_migrations -d postgres -v ON_ERROR_STOP=1 < "$path"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
apply_dictionary_readable_view() {
|
|
208
|
+
local path="$1"
|
|
209
|
+
db_cli view postgres \
|
|
210
|
+
--table app.dictionary_events \
|
|
211
|
+
--column payload_compressed \
|
|
212
|
+
--dictionary-table app.mcdb_dictionary_versions \
|
|
213
|
+
--dictionary-id-column payload_dictionary_id \
|
|
214
|
+
--view admin.dictionary_events_readable \
|
|
215
|
+
--columns id \
|
|
216
|
+
--output "$path"
|
|
217
|
+
docker exec -i "$CONTAINER" psql -X -U app_migrations -d postgres -v ON_ERROR_STOP=1 < "$path"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
ensure_ruby_extension
|
|
221
|
+
prepare_packaged_gem
|
|
222
|
+
build_deployment_bundle
|
|
223
|
+
|
|
224
|
+
for name in corrupt_magic corrupt_payload corrupt_crc invalid_utf8 nul_text trailing_skippable trailing_frame; do
|
|
225
|
+
require_fixture "test/fixtures/database_v1/${name}.mcdb"
|
|
226
|
+
done
|
|
227
|
+
|
|
228
|
+
TEXT='Привет, DBeaver! Сәлем! 🌍'
|
|
229
|
+
BIG=$(ruby -e 'print "a" * 300')
|
|
230
|
+
VALID_HEX=$(compress_hex "$TEXT")
|
|
231
|
+
BIG_HEX=$(compress_hex "$BIG")
|
|
232
|
+
MCDB2_FIXTURE_OUTPUT="$(mcdb2_fixture "$TEXT")"
|
|
233
|
+
readarray -t MCDB2_FIXTURE <<< "$MCDB2_FIXTURE_OUTPUT"
|
|
234
|
+
if [ "${#MCDB2_FIXTURE[@]}" -ne 5 ] || [ -z "${MCDB2_FIXTURE[0]}" ] || [ -z "${MCDB2_FIXTURE[4]}" ]; then
|
|
235
|
+
echo "MCDB2 fixture generator returned an invalid payload" >&2
|
|
236
|
+
exit 2
|
|
237
|
+
fi
|
|
238
|
+
DICT_REF="${MCDB2_FIXTURE[0]}"
|
|
239
|
+
DICT_ZSTD_ID="${MCDB2_FIXTURE[1]}"
|
|
240
|
+
DICT_SHA_HEX="${MCDB2_FIXTURE[2]}"
|
|
241
|
+
DICT_HEX="${MCDB2_FIXTURE[3]}"
|
|
242
|
+
DICT_BLOB_HEX="${MCDB2_FIXTURE[4]}"
|
|
243
|
+
TEXT_BYTES=$(MCDB_TEXT="$TEXT" ruby -e 'print ENV.fetch("MCDB_TEXT").bytesize')
|
|
244
|
+
BUNDLE_NAME="$(bundle_name)"
|
|
245
|
+
VIEW_SQL="$(mktemp -t multi-compress-postgres-view.XXXXXX.sql)"
|
|
246
|
+
DICT_VIEW_SQL="$(mktemp -t multi-compress-postgres-dictionary-view.XXXXXX.sql)"
|
|
247
|
+
REGISTRY_SQL="$(mktemp -t multi-compress-postgres-registry.XXXXXX.sql)"
|
|
248
|
+
trap 'rm -f "$VIEW_SQL" "$DICT_VIEW_SQL" "$REGISTRY_SQL"; cleanup' EXIT
|
|
249
|
+
|
|
250
|
+
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
|
|
251
|
+
docker run --platform "$PLATFORM" -d --name "$CONTAINER" \
|
|
252
|
+
-e POSTGRES_HOST_AUTH_METHOD=trust "$IMAGE" >/dev/null
|
|
253
|
+
|
|
254
|
+
echo "waiting for PostgreSQL in $IMAGE..."
|
|
255
|
+
deadline=$(( $(date +%s) + WAIT_SECONDS ))
|
|
256
|
+
until docker exec "$CONTAINER" pg_isready -U postgres -d postgres >/dev/null 2>&1; do
|
|
257
|
+
if [ "$(date +%s)" -ge "$deadline" ]; then
|
|
258
|
+
echo "PostgreSQL did not become ready within ${WAIT_SECONDS}s" >&2
|
|
259
|
+
docker logs "$CONTAINER" >&2 || true
|
|
260
|
+
exit 1
|
|
261
|
+
fi
|
|
262
|
+
sleep 2
|
|
263
|
+
done
|
|
264
|
+
|
|
265
|
+
q() { docker exec "$CONTAINER" psql -X -U postgres -d postgres -Atq -v ON_ERROR_STOP=1 -c "$1"; }
|
|
266
|
+
qfail() {
|
|
267
|
+
if q "$1" >/dev/null 2>&1; then return 1; else return 0; fi
|
|
268
|
+
}
|
|
269
|
+
q_read() { docker exec "$CONTAINER" psql -X -U dbeaver_readonly -d postgres -Atq -v ON_ERROR_STOP=1 -c "$1"; }
|
|
270
|
+
q_read_fail() {
|
|
271
|
+
if q_read "$1" >/dev/null 2>&1; then return 1; else return 0; fi
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
RUNTIME_PG_CONFIG=$(docker exec "$CONTAINER" sh -ceu 'command -v pg_config' | tr -d '\r\n')
|
|
275
|
+
[ -n "$RUNTIME_PG_CONFIG" ] || { echo "PostgreSQL runtime does not provide pg_config" >&2; exit 2; }
|
|
276
|
+
BUILD_PG_CONFIG=$(install_postgres_build_tools "$RUNTIME_PG_CONFIG")
|
|
277
|
+
install_bundle_on_db_host "$BUNDLE_NAME" "$BUILD_PG_CONFIG" "$RUNTIME_PG_CONFIG"
|
|
278
|
+
|
|
279
|
+
q "CREATE ROLE app_migrations LOGIN;
|
|
280
|
+
CREATE ROLE dbeaver_readonly LOGIN;
|
|
281
|
+
CREATE ROLE mcdb_dictionary_owner NOLOGIN;
|
|
282
|
+
CREATE SCHEMA app;
|
|
283
|
+
CREATE SCHEMA admin;
|
|
284
|
+
CREATE TABLE app.events (id integer PRIMARY KEY, payload_compressed bytea NULL);
|
|
285
|
+
INSERT INTO app.events VALUES
|
|
286
|
+
(1, decode('$VALID_HEX', 'hex')),
|
|
287
|
+
(2, NULL),
|
|
288
|
+
(3, decode('$BIG_HEX', 'hex')),
|
|
289
|
+
(4, decode('$VALID_HEX', 'hex'));
|
|
290
|
+
GRANT USAGE ON SCHEMA app, admin TO app_migrations, dbeaver_readonly;
|
|
291
|
+
GRANT CREATE ON SCHEMA admin TO app_migrations;
|
|
292
|
+
GRANT SELECT ON app.events TO app_migrations;"
|
|
293
|
+
docker exec -u postgres -e "MCDB_BUNDLE_ROOT=${BUNDLE_PARENT}/${BUNDLE_NAME}" "$CONTAINER" sh -ceu '
|
|
294
|
+
cd "$MCDB_BUNDLE_ROOT"
|
|
295
|
+
make enable DB=postgres MIGRATION_ROLE=app_migrations READ_ROLE=dbeaver_readonly
|
|
296
|
+
'
|
|
297
|
+
apply_readable_view "$VIEW_SQL"
|
|
298
|
+
q 'GRANT SELECT ON admin.events_readable TO dbeaver_readonly;'
|
|
299
|
+
|
|
300
|
+
# MCDB2: package-provided registry DDL, immutable dictionary row, generated INNER JOIN view.
|
|
301
|
+
q "CREATE TABLE app.dictionary_events (
|
|
302
|
+
id integer PRIMARY KEY,
|
|
303
|
+
payload_compressed bytea NOT NULL,
|
|
304
|
+
payload_dictionary_id bigint NOT NULL
|
|
305
|
+
);
|
|
306
|
+
GRANT SELECT ON app.dictionary_events TO app_migrations;"
|
|
307
|
+
db_cli registry postgres \
|
|
308
|
+
--schema app --owner mcdb_dictionary_owner --migration-role app_migrations \
|
|
309
|
+
--payload-table dictionary_events --payload-column payload_compressed \
|
|
310
|
+
--payload-dictionary-id-column payload_dictionary_id \
|
|
311
|
+
--output "$REGISTRY_SQL"
|
|
312
|
+
docker exec -i "$CONTAINER" psql -X -U postgres -d postgres -v ON_ERROR_STOP=1 < "$REGISTRY_SQL"
|
|
313
|
+
q "
|
|
314
|
+
INSERT INTO app.mcdb_dictionary_versions (id, family, zstd_dict_id, sha256, bytes)
|
|
315
|
+
VALUES ($DICT_REF, 'events_payload_v1', $DICT_ZSTD_ID, decode('$DICT_SHA_HEX', 'hex'), decode('$DICT_HEX', 'hex'));
|
|
316
|
+
INSERT INTO app.dictionary_events (id, payload_compressed, payload_dictionary_id)
|
|
317
|
+
SELECT g, decode('$DICT_BLOB_HEX', 'hex'), $DICT_REF FROM generate_series(1, 1000) AS g;
|
|
318
|
+
ANALYZE app.dictionary_events;"
|
|
319
|
+
apply_dictionary_readable_view "$DICT_VIEW_SQL"
|
|
320
|
+
q 'GRANT SELECT ON admin.dictionary_events_readable TO dbeaver_readonly;'
|
|
321
|
+
|
|
322
|
+
q "CREATE DATABASE mcdb_latin1 WITH TEMPLATE template0 ENCODING 'LATIN1' LC_COLLATE 'C' LC_CTYPE 'C';"
|
|
323
|
+
docker exec -u postgres -e "MCDB_BUNDLE_ROOT=${BUNDLE_PARENT}/${BUNDLE_NAME}" "$CONTAINER" sh -ceu '
|
|
324
|
+
cd "$MCDB_BUNDLE_ROOT"
|
|
325
|
+
if make enable DB=mcdb_latin1 MIGRATION_ROLE=app_migrations READ_ROLE=dbeaver_readonly >/tmp/mcdb-latin1.out 2>&1; then
|
|
326
|
+
echo "LATIN1 enable unexpectedly succeeded" >&2
|
|
327
|
+
exit 1
|
|
328
|
+
fi
|
|
329
|
+
grep -F "requires UTF8" /tmp/mcdb-latin1.out
|
|
330
|
+
'
|
|
331
|
+
q "CREATE DATABASE mcdb_other_schema WITH TEMPLATE template0 ENCODING 'UTF8';"
|
|
332
|
+
docker exec "$CONTAINER" psql -X -U postgres -d mcdb_other_schema -v ON_ERROR_STOP=1 -c 'CREATE SCHEMA wrong_schema; CREATE EXTENSION multi_compress WITH SCHEMA wrong_schema;' >/dev/null
|
|
333
|
+
docker exec -u postgres -e "MCDB_BUNDLE_ROOT=${BUNDLE_PARENT}/${BUNDLE_NAME}" "$CONTAINER" sh -ceu '
|
|
334
|
+
cd "$MCDB_BUNDLE_ROOT"
|
|
335
|
+
if make enable DB=mcdb_other_schema SCHEMA=multi_compress MIGRATION_ROLE=app_migrations READ_ROLE=dbeaver_readonly >/tmp/mcdb-schema.out 2>&1; then
|
|
336
|
+
echo "mismatched extension schema unexpectedly succeeded" >&2
|
|
337
|
+
exit 1
|
|
338
|
+
fi
|
|
339
|
+
grep -F "already installed in schema wrong_schema" /tmp/mcdb-schema.out
|
|
340
|
+
'
|
|
341
|
+
|
|
342
|
+
fails=0
|
|
343
|
+
check() {
|
|
344
|
+
if [ "$2" = "$3" ]; then echo " PASS $1"; else echo " FAIL $1 (got [$2] want [$3])"; fails=$((fails + 1)); fi
|
|
345
|
+
}
|
|
346
|
+
check_err() {
|
|
347
|
+
if qfail "$2"; then echo " PASS $1 (rejected)"; else echo " FAIL $1 (should have errored)"; fails=$((fails + 1)); fi
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
check "UTF8 database" "$(q 'SHOW server_encoding;' | tr -d '\r\n')" "UTF8"
|
|
351
|
+
check "deployment bundle version() smoke" "$(q 'SELECT strpos(multi_compress.multi_compress_db_version(), chr(77) || chr(67) || chr(68) || chr(66) || chr(49)) > 0;' | tr -d '\r\n')" "t"
|
|
352
|
+
check "PostgreSQL reader uses vendored zstd" "$(q "SELECT strpos(multi_compress.multi_compress_db_version(), 'zstd 1.5.7') > 0;" | tr -d '\r\n')" "t"
|
|
353
|
+
check "decode roundtrip" "$(q 'SELECT multi_compress.multi_compress_db_decompress(payload_compressed) FROM app.events WHERE id=1;' | tr -d '\r')" "$TEXT"
|
|
354
|
+
check "NULL -> NULL" "$(q 'SELECT multi_compress.multi_compress_db_decompress(payload_compressed) IS NULL FROM app.events WHERE id=2;' | tr -d '\r\n')" "t"
|
|
355
|
+
check "big (>255, >input)" "$(q 'SELECT octet_length(multi_compress.multi_compress_db_decompress(payload_compressed)) FROM app.events WHERE id=3;' | tr -d '\r\n')" "300"
|
|
356
|
+
check "generated readable view" "$(q 'SELECT count(*) FROM admin.events_readable WHERE id IN (1, 4);' | tr -d '\r\n')" "2"
|
|
357
|
+
check "read role reads generated Unicode view" "$(q_read 'SELECT payload FROM admin.events_readable WHERE id=1;' | tr -d '\r')" "$TEXT"
|
|
358
|
+
if q_read_fail 'SELECT payload_compressed FROM app.events WHERE id=1;'; then
|
|
359
|
+
echo " PASS read role cannot read source table"
|
|
360
|
+
else
|
|
361
|
+
echo " FAIL read role unexpectedly read source table"
|
|
362
|
+
fails=$((fails + 1))
|
|
363
|
+
fi
|
|
364
|
+
RUNTIME_MAJOR="$(docker exec "$CONTAINER" "$RUNTIME_PG_CONFIG" --version | awk '{print $2}' | cut -d. -f1 | tr -d '\r\n')"
|
|
365
|
+
SERVER_MAJOR="$(q 'SHOW server_version;' | cut -d. -f1 | tr -d '\r\n')"
|
|
366
|
+
check "connected server major matches PG_CONFIG" "$SERVER_MAJOR" "$RUNTIME_MAJOR"
|
|
367
|
+
check "is_valid true" "$(q 'SELECT multi_compress.multi_compress_db_is_valid(payload_compressed) FROM app.events WHERE id=1;' | tr -d '\r\n')" "t"
|
|
368
|
+
check "is_valid on NULL" "$(q 'SELECT multi_compress.multi_compress_db_is_valid(payload_compressed) IS NULL FROM app.events WHERE id=2;' | tr -d '\r\n')" "t"
|
|
369
|
+
|
|
370
|
+
for name in corrupt_magic corrupt_payload corrupt_crc invalid_utf8 nul_text trailing_skippable trailing_frame; do
|
|
371
|
+
hex=$(fixture_hex "test/fixtures/database_v1/${name}.mcdb")
|
|
372
|
+
check_err "$name errors" "SELECT multi_compress.multi_compress_db_decompress(decode('$hex', 'hex'));"
|
|
373
|
+
done
|
|
374
|
+
check "is_valid false (crc)" "$(q "SELECT multi_compress.multi_compress_db_is_valid(decode('$(fixture_hex test/fixtures/database_v1/corrupt_crc.mcdb)', 'hex'));" | tr -d '\r\n')" "f"
|
|
375
|
+
check "MCDB2 registry owner has schema usage" "$(q "SELECT has_schema_privilege('mcdb_dictionary_owner', 'app', 'USAGE');" | tr -d '\r\n')" "t"
|
|
376
|
+
check "MCDB2 dictionary reference" "$(q 'SELECT multi_compress.multi_compress_db_dictionary_ref(payload_compressed) FROM app.dictionary_events WHERE id=1;' | tr -d '\r\n')" "$DICT_REF"
|
|
377
|
+
check "MCDB2 original size" "$(q 'SELECT multi_compress.multi_compress_db_original_size(payload_compressed) FROM app.dictionary_events WHERE id=1;' | tr -d '\r\n')" "$TEXT_BYTES"
|
|
378
|
+
check "MCDB2 dictionary view roundtrip" "$(q_read 'SELECT payload FROM admin.dictionary_events_readable WHERE id=1;' | tr -d '\r')" "$TEXT"
|
|
379
|
+
check "MCDB2 dictionary is valid" "$(q "SELECT multi_compress.multi_compress_db_is_valid_dict(e.payload_compressed, e.payload_dictionary_id, d.sha256, d.bytes) FROM app.dictionary_events e JOIN app.mcdb_dictionary_versions d ON d.id = e.payload_dictionary_id WHERE e.id=1;" | tr -d '\r\n')" "t"
|
|
380
|
+
check_err "MCDB2 payload/FK mismatch rejected" "INSERT INTO app.dictionary_events VALUES (1001, decode('$DICT_BLOB_HEX', 'hex'), $((DICT_REF + 1)));"
|
|
381
|
+
check "MCDB2 rejects wrong dictionary digest" "$(q "SELECT multi_compress.multi_compress_db_is_valid_dict(e.payload_compressed, e.payload_dictionary_id, decode(repeat('00', 32), 'hex'), d.bytes) FROM app.dictionary_events e JOIN app.mcdb_dictionary_versions d ON d.id = e.payload_dictionary_id WHERE e.id=1;" | tr -d '\r\n')" "f"
|
|
382
|
+
if q_read_fail 'SELECT bytes FROM app.mcdb_dictionary_versions WHERE id=42;'; then
|
|
383
|
+
echo " PASS read role cannot read dictionary registry"
|
|
384
|
+
else
|
|
385
|
+
echo " FAIL read role unexpectedly read dictionary registry"
|
|
386
|
+
fails=$((fails + 1))
|
|
387
|
+
fi
|
|
388
|
+
PG_DICT_PLAN="$(q 'SET enable_seqscan = off; EXPLAIN (COSTS OFF) SELECT id, payload FROM admin.dictionary_events_readable WHERE id >= 3 ORDER BY id LIMIT 100;')"
|
|
389
|
+
if printf '%s\n' "$PG_DICT_PLAN" | grep -Eq 'Index Scan|Index Only Scan'; then
|
|
390
|
+
echo " PASS MCDB2 view preserves indexed LIMIT plan"
|
|
391
|
+
else
|
|
392
|
+
echo " FAIL MCDB2 view did not expose an indexed LIMIT plan"
|
|
393
|
+
printf '%s\n' "$PG_DICT_PLAN" >&2
|
|
394
|
+
fails=$((fails + 1))
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
q 'DROP VIEW admin.events_readable; DROP VIEW admin.dictionary_events_readable; DROP EXTENSION multi_compress;'
|
|
398
|
+
docker exec "$CONTAINER" psql -X -U postgres -d mcdb_other_schema -v ON_ERROR_STOP=1 -c 'DROP EXTENSION multi_compress;' >/dev/null
|
|
399
|
+
docker exec -u 0 -e "MCDB_BUNDLE_ROOT=${BUNDLE_PARENT}/${BUNDLE_NAME}" -e "MCDB_RUNTIME_PG_CONFIG=$RUNTIME_PG_CONFIG" "$CONTAINER" sh -ceu '
|
|
400
|
+
cd "$MCDB_BUNDLE_ROOT"
|
|
401
|
+
if make uninstall RUNTIME_PG_CONFIG="$MCDB_RUNTIME_PG_CONFIG" >/dev/null 2>&1; then
|
|
402
|
+
echo "uninstall succeeded without CONFIRM" >&2
|
|
403
|
+
exit 1
|
|
404
|
+
fi
|
|
405
|
+
make uninstall CONFIRM=REMOVE_MULTI_COMPRESS RUNTIME_PG_CONFIG="$MCDB_RUNTIME_PG_CONFIG"
|
|
406
|
+
'
|
|
407
|
+
check_err "uninstall removes functions" "SELECT multi_compress.multi_compress_db_version();"
|
|
408
|
+
check_err "uninstall removes extension files" "CREATE EXTENSION multi_compress;"
|
|
409
|
+
|
|
410
|
+
if [ "$fails" -ne 0 ]; then echo "E2E FAILED ($fails)"; exit 1; fi
|
|
411
|
+
echo "E2E OK"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: multi_compress
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Haydarov
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -72,7 +72,8 @@ description: 'Unified C-extension gem for zstd, lz4, and brotli compression. One
|
|
|
72
72
|
sources — no system libraries required.'
|
|
73
73
|
email:
|
|
74
74
|
- romnhajdarov@gmail.com
|
|
75
|
-
executables:
|
|
75
|
+
executables:
|
|
76
|
+
- multi_compress
|
|
76
77
|
extensions:
|
|
77
78
|
- ext/multi_compress/extconf.rb
|
|
78
79
|
extra_rdoc_files: []
|
|
@@ -82,6 +83,30 @@ files:
|
|
|
82
83
|
- LICENSE.txt
|
|
83
84
|
- README.md
|
|
84
85
|
- THIRD_PARTY_NOTICES.md
|
|
86
|
+
- db_core/include/mcdb_format.h
|
|
87
|
+
- db_core/src/mcdb_format.c
|
|
88
|
+
- db_deployment/mysql/Makefile
|
|
89
|
+
- db_deployment/mysql/README.md
|
|
90
|
+
- db_deployment/mysql/bin/common
|
|
91
|
+
- db_deployment/mysql/bin/disable
|
|
92
|
+
- db_deployment/mysql/bin/doctor
|
|
93
|
+
- db_deployment/mysql/bin/enable
|
|
94
|
+
- db_deployment/mysql/bin/install
|
|
95
|
+
- db_deployment/mysql/bin/status
|
|
96
|
+
- db_deployment/mysql/bin/uninstall
|
|
97
|
+
- db_deployment/mysql/bin/upgrade
|
|
98
|
+
- db_deployment/mysql/bin/verify
|
|
99
|
+
- db_deployment/postgres/Makefile
|
|
100
|
+
- db_deployment/postgres/README.md
|
|
101
|
+
- db_deployment/postgres/bin/doctor
|
|
102
|
+
- db_deployment/postgres/bin/enable
|
|
103
|
+
- db_deployment/postgres/bin/install
|
|
104
|
+
- db_deployment/postgres/bin/uninstall
|
|
105
|
+
- db_deployment/postgres/bin/verify
|
|
106
|
+
- docs/database-envelope-v1.md
|
|
107
|
+
- docs/database-envelope-v2.md
|
|
108
|
+
- docs/rfcs/0002-mcdb2-dictionaries.md
|
|
109
|
+
- exe/multi_compress
|
|
85
110
|
- ext/multi_compress/brotli_dec_static_init.c
|
|
86
111
|
- ext/multi_compress/brotli_enc_static_init.c
|
|
87
112
|
- ext/multi_compress/extconf.rb
|
|
@@ -320,7 +345,35 @@ files:
|
|
|
320
345
|
- ext/multi_compress/vendor/zstd/lib/zstd.h
|
|
321
346
|
- ext/multi_compress/vendor/zstd/lib/zstd_errors.h
|
|
322
347
|
- lib/multi_compress.rb
|
|
348
|
+
- lib/multi_compress/active_record.rb
|
|
349
|
+
- lib/multi_compress/codec.rb
|
|
350
|
+
- lib/multi_compress/database.rb
|
|
351
|
+
- lib/multi_compress/db_deployment.rb
|
|
323
352
|
- lib/multi_compress/version.rb
|
|
353
|
+
- multi_compress.gemspec
|
|
354
|
+
- mysql_udf/Makefile
|
|
355
|
+
- mysql_udf/README.md
|
|
356
|
+
- mysql_udf/sql/examples.sql
|
|
357
|
+
- mysql_udf/sql/install.sql
|
|
358
|
+
- mysql_udf/sql/uninstall.sql
|
|
359
|
+
- mysql_udf/sql/views.sql
|
|
360
|
+
- mysql_udf/src/multi_compress_mysql.c
|
|
361
|
+
- mysql_udf/src/mysql_udf_abi_57.h
|
|
362
|
+
- mysql_udf/test/mcdb_cli.c
|
|
363
|
+
- mysql_udf/test/run_e2e.sh
|
|
364
|
+
- postgres_extension/Makefile
|
|
365
|
+
- postgres_extension/README.md
|
|
366
|
+
- postgres_extension/multi_compress.control
|
|
367
|
+
- postgres_extension/multi_compress_pg.exports
|
|
368
|
+
- postgres_extension/sql/examples.sql
|
|
369
|
+
- postgres_extension/sql/multi_compress--0.5.0--0.6.0.sql
|
|
370
|
+
- postgres_extension/sql/multi_compress--0.5.0.sql
|
|
371
|
+
- postgres_extension/sql/multi_compress--0.6.0.sql
|
|
372
|
+
- postgres_extension/sql/uninstall.sql
|
|
373
|
+
- postgres_extension/sql/views.sql
|
|
374
|
+
- postgres_extension/src/mcdb_format.c
|
|
375
|
+
- postgres_extension/src/multi_compress_pg.c
|
|
376
|
+
- postgres_extension/test/run_e2e.sh
|
|
324
377
|
homepage: https://github.com/roman-haidarov/multi_compress
|
|
325
378
|
licenses:
|
|
326
379
|
- MIT
|