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
@@ -75,6 +75,23 @@ compress_hex() {
75
75
  -e 'print MultiCompress::Database.compress(ENV.fetch("MCDB_TEXT")).unpack1("H*")'
76
76
  }
77
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
+
78
95
  bundle_name() {
79
96
  ruby -I "$PACKAGED_GEM_ROOT/lib" -r multi_compress/version \
80
97
  -e 'print "multi_compress-postgres-#{MultiCompress::VERSION}"'
@@ -187,6 +204,19 @@ apply_readable_view() {
187
204
  docker exec -i "$CONTAINER" psql -X -U app_migrations -d postgres -v ON_ERROR_STOP=1 < "$path"
188
205
  }
189
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
+
190
220
  ensure_ruby_extension
191
221
  prepare_packaged_gem
192
222
  build_deployment_bundle
@@ -199,9 +229,23 @@ TEXT='Привет, DBeaver! Сәлем! 🌍'
199
229
  BIG=$(ruby -e 'print "a" * 300')
200
230
  VALID_HEX=$(compress_hex "$TEXT")
201
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')
202
244
  BUNDLE_NAME="$(bundle_name)"
203
245
  VIEW_SQL="$(mktemp -t multi-compress-postgres-view.XXXXXX.sql)"
204
- trap 'rm -f "$VIEW_SQL"; cleanup' EXIT
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
205
249
 
206
250
  docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
207
251
  docker run --platform "$PLATFORM" -d --name "$CONTAINER" \
@@ -234,6 +278,7 @@ install_bundle_on_db_host "$BUNDLE_NAME" "$BUILD_PG_CONFIG" "$RUNTIME_PG_CONFIG"
234
278
 
235
279
  q "CREATE ROLE app_migrations LOGIN;
236
280
  CREATE ROLE dbeaver_readonly LOGIN;
281
+ CREATE ROLE mcdb_dictionary_owner NOLOGIN;
237
282
  CREATE SCHEMA app;
238
283
  CREATE SCHEMA admin;
239
284
  CREATE TABLE app.events (id integer PRIMARY KEY, payload_compressed bytea NULL);
@@ -252,6 +297,28 @@ docker exec -u postgres -e "MCDB_BUNDLE_ROOT=${BUNDLE_PARENT}/${BUNDLE_NAME}" "$
252
297
  apply_readable_view "$VIEW_SQL"
253
298
  q 'GRANT SELECT ON admin.events_readable TO dbeaver_readonly;'
254
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
+
255
322
  q "CREATE DATABASE mcdb_latin1 WITH TEMPLATE template0 ENCODING 'LATIN1' LC_COLLATE 'C' LC_CTYPE 'C';"
256
323
  docker exec -u postgres -e "MCDB_BUNDLE_ROOT=${BUNDLE_PARENT}/${BUNDLE_NAME}" "$CONTAINER" sh -ceu '
257
324
  cd "$MCDB_BUNDLE_ROOT"
@@ -305,8 +372,29 @@ for name in corrupt_magic corrupt_payload corrupt_crc invalid_utf8 nul_text trai
305
372
  check_err "$name errors" "SELECT multi_compress.multi_compress_db_decompress(decode('$hex', 'hex'));"
306
373
  done
307
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
308
396
 
309
- q 'DROP VIEW admin.events_readable; DROP EXTENSION multi_compress;'
397
+ q 'DROP VIEW admin.events_readable; DROP VIEW admin.dictionary_events_readable; DROP EXTENSION multi_compress;'
310
398
  docker exec "$CONTAINER" psql -X -U postgres -d mcdb_other_schema -v ON_ERROR_STOP=1 -c 'DROP EXTENSION multi_compress;' >/dev/null
311
399
  docker exec -u 0 -e "MCDB_BUNDLE_ROOT=${BUNDLE_PARENT}/${BUNDLE_NAME}" -e "MCDB_RUNTIME_PG_CONFIG=$RUNTIME_PG_CONFIG" "$CONTAINER" sh -ceu '
312
400
  cd "$MCDB_BUNDLE_ROOT"
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.5.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-04 00:00:00.000000000 Z
11
+ date: 2026-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -104,6 +104,8 @@ files:
104
104
  - db_deployment/postgres/bin/uninstall
105
105
  - db_deployment/postgres/bin/verify
106
106
  - docs/database-envelope-v1.md
107
+ - docs/database-envelope-v2.md
108
+ - docs/rfcs/0002-mcdb2-dictionaries.md
107
109
  - exe/multi_compress
108
110
  - ext/multi_compress/brotli_dec_static_init.c
109
111
  - ext/multi_compress/brotli_enc_static_init.c
@@ -364,7 +366,9 @@ files:
364
366
  - postgres_extension/multi_compress.control
365
367
  - postgres_extension/multi_compress_pg.exports
366
368
  - postgres_extension/sql/examples.sql
369
+ - postgres_extension/sql/multi_compress--0.5.0--0.6.0.sql
367
370
  - postgres_extension/sql/multi_compress--0.5.0.sql
371
+ - postgres_extension/sql/multi_compress--0.6.0.sql
368
372
  - postgres_extension/sql/uninstall.sql
369
373
  - postgres_extension/sql/views.sql
370
374
  - postgres_extension/src/mcdb_format.c