multi_compress 0.5.0 → 0.6.1

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/GET_STARTED.md +20 -1
  4. data/README.md +15 -2
  5. data/THIRD_PARTY_NOTICES.md +1 -1
  6. data/db_core/include/mcdb_format.h +67 -9
  7. data/db_core/src/mcdb_format.c +392 -139
  8. data/db_deployment/mysql/README.md +19 -0
  9. data/db_deployment/mysql/bin/common +70 -18
  10. data/db_deployment/mysql/bin/enable +5 -1
  11. data/db_deployment/mysql/bin/status +2 -0
  12. data/db_deployment/mysql/bin/upgrade +17 -5
  13. data/db_deployment/postgres/README.md +24 -0
  14. data/db_deployment/postgres/bin/enable +2 -0
  15. data/db_deployment/postgres/bin/install +4 -2
  16. data/docs/database-envelope-v2.md +147 -0
  17. data/docs/rfcs/0002-mcdb2-dictionaries.md +75 -0
  18. data/ext/multi_compress/multi_compress.c +261 -76
  19. data/ext/multi_compress/vendor/.vendored +1 -1
  20. data/ext/multi_compress/vendor/brotli/c/common/version.h +2 -2
  21. data/ext/multi_compress/vendor/brotli/c/dec/decode.c +59 -30
  22. data/ext/multi_compress/vendor/brotli/c/include/brotli/decode.h +1 -3
  23. data/lib/multi_compress/active_record.rb +11 -5
  24. data/lib/multi_compress/codec.rb +164 -94
  25. data/lib/multi_compress/database/api.rb +42 -0
  26. data/lib/multi_compress/database/decoding.rb +42 -0
  27. data/lib/multi_compress/database/dictionary.rb +72 -0
  28. data/lib/multi_compress/database/encoding.rb +31 -0
  29. data/lib/multi_compress/database/header.rb +71 -0
  30. data/lib/multi_compress/database/operations.rb +73 -0
  31. data/lib/multi_compress/database/validation.rb +95 -0
  32. data/lib/multi_compress/database.rb +29 -95
  33. data/lib/multi_compress/db_deployment/bundle_archiver.rb +70 -0
  34. data/lib/multi_compress/db_deployment/bundle_metadata.rb +56 -0
  35. data/lib/multi_compress/db_deployment/bundle_stager.rb +62 -0
  36. data/lib/multi_compress/db_deployment/commands.rb +309 -0
  37. data/lib/multi_compress/db_deployment/deployment_bundle.rb +80 -0
  38. data/lib/multi_compress/db_deployment/deterministic_tar_writer.rb +76 -0
  39. data/lib/multi_compress/db_deployment/dictionary_registry.rb +244 -0
  40. data/lib/multi_compress/db_deployment/readable_view.rb +112 -0
  41. data/lib/multi_compress/db_deployment/readable_view_options.rb +51 -0
  42. data/lib/multi_compress/db_deployment/sql_identifiers.rb +45 -0
  43. data/lib/multi_compress/db_deployment.rb +13 -435
  44. data/lib/multi_compress/reader/buffer_operations.rb +57 -0
  45. data/lib/multi_compress/reader/read_operations.rb +67 -0
  46. data/lib/multi_compress/reader.rb +97 -0
  47. data/lib/multi_compress/stream_io.rb +13 -0
  48. data/lib/multi_compress/version.rb +1 -1
  49. data/lib/multi_compress/writer.rb +85 -0
  50. data/lib/multi_compress.rb +42 -333
  51. data/mysql_udf/README.md +45 -0
  52. data/mysql_udf/sql/examples.sql +5 -0
  53. data/mysql_udf/sql/install.sql +25 -1
  54. data/mysql_udf/sql/uninstall.sql +7 -1
  55. data/mysql_udf/sql/views.sql +16 -4
  56. data/mysql_udf/src/multi_compress_mysql.c +383 -44
  57. data/mysql_udf/test/run_e2e.sh +119 -1
  58. data/postgres_extension/Makefile +2 -2
  59. data/postgres_extension/README.md +48 -0
  60. data/postgres_extension/multi_compress.control +2 -2
  61. data/postgres_extension/multi_compress_pg.exports +12 -0
  62. data/postgres_extension/sql/examples.sql +11 -0
  63. data/postgres_extension/sql/multi_compress--0.5.0--0.6.0.sql +59 -0
  64. data/postgres_extension/sql/multi_compress--0.6.0.sql +85 -0
  65. data/postgres_extension/src/multi_compress_pg.c +301 -16
  66. data/postgres_extension/test/run_e2e.sh +90 -2
  67. metadata +29 -6
@@ -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,13 @@
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Haydarov
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-07-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bundler
@@ -104,6 +103,8 @@ files:
104
103
  - db_deployment/postgres/bin/uninstall
105
104
  - db_deployment/postgres/bin/verify
106
105
  - docs/database-envelope-v1.md
106
+ - docs/database-envelope-v2.md
107
+ - docs/rfcs/0002-mcdb2-dictionaries.md
107
108
  - exe/multi_compress
108
109
  - ext/multi_compress/brotli_dec_static_init.c
109
110
  - ext/multi_compress/brotli_enc_static_init.c
@@ -346,8 +347,30 @@ files:
346
347
  - lib/multi_compress/active_record.rb
347
348
  - lib/multi_compress/codec.rb
348
349
  - lib/multi_compress/database.rb
350
+ - lib/multi_compress/database/api.rb
351
+ - lib/multi_compress/database/decoding.rb
352
+ - lib/multi_compress/database/dictionary.rb
353
+ - lib/multi_compress/database/encoding.rb
354
+ - lib/multi_compress/database/header.rb
355
+ - lib/multi_compress/database/operations.rb
356
+ - lib/multi_compress/database/validation.rb
349
357
  - lib/multi_compress/db_deployment.rb
358
+ - lib/multi_compress/db_deployment/bundle_archiver.rb
359
+ - lib/multi_compress/db_deployment/bundle_metadata.rb
360
+ - lib/multi_compress/db_deployment/bundle_stager.rb
361
+ - lib/multi_compress/db_deployment/commands.rb
362
+ - lib/multi_compress/db_deployment/deployment_bundle.rb
363
+ - lib/multi_compress/db_deployment/deterministic_tar_writer.rb
364
+ - lib/multi_compress/db_deployment/dictionary_registry.rb
365
+ - lib/multi_compress/db_deployment/readable_view.rb
366
+ - lib/multi_compress/db_deployment/readable_view_options.rb
367
+ - lib/multi_compress/db_deployment/sql_identifiers.rb
368
+ - lib/multi_compress/reader.rb
369
+ - lib/multi_compress/reader/buffer_operations.rb
370
+ - lib/multi_compress/reader/read_operations.rb
371
+ - lib/multi_compress/stream_io.rb
350
372
  - lib/multi_compress/version.rb
373
+ - lib/multi_compress/writer.rb
351
374
  - multi_compress.gemspec
352
375
  - mysql_udf/Makefile
353
376
  - mysql_udf/README.md
@@ -364,7 +387,9 @@ files:
364
387
  - postgres_extension/multi_compress.control
365
388
  - postgres_extension/multi_compress_pg.exports
366
389
  - postgres_extension/sql/examples.sql
390
+ - postgres_extension/sql/multi_compress--0.5.0--0.6.0.sql
367
391
  - postgres_extension/sql/multi_compress--0.5.0.sql
392
+ - postgres_extension/sql/multi_compress--0.6.0.sql
368
393
  - postgres_extension/sql/uninstall.sql
369
394
  - postgres_extension/sql/views.sql
370
395
  - postgres_extension/src/mcdb_format.c
@@ -377,7 +402,6 @@ metadata:
377
402
  homepage_uri: https://github.com/roman-haidarov/multi_compress
378
403
  source_code_uri: https://github.com/roman-haidarov/multi_compress
379
404
  changelog_uri: https://github.com/roman-haidarov/multi_compress/blob/main/CHANGELOG.md
380
- post_install_message:
381
405
  rdoc_options: []
382
406
  require_paths:
383
407
  - lib
@@ -392,8 +416,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
392
416
  - !ruby/object:Gem::Version
393
417
  version: '0'
394
418
  requirements: []
395
- rubygems_version: 3.4.22
396
- signing_key:
419
+ rubygems_version: 3.6.7
397
420
  specification_version: 4
398
421
  summary: 'Modern fiber-friendly compression for Ruby: zstd, lz4, brotli in one gem'
399
422
  test_files: []