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
@@ -8,8 +8,9 @@
8
8
  #include <brotli/decode.h>
9
9
  #include <brotli/encode.h>
10
10
  #include <lz4.h>
11
- #include <lz4hc.h>
11
+ #include <limits.h>
12
12
  #include <lz4frame.h>
13
+ #include <lz4hc.h>
13
14
  #include <pthread.h>
14
15
  #include <stdio.h>
15
16
  #include <stdint.h>
@@ -518,30 +519,42 @@ static compress_algo_t detect_algo(const uint8_t *data, size_t len) {
518
519
  return ALGO_ZSTD;
519
520
  }
520
521
 
521
- static inline VALUE rb_binary_str_new(const char *ptr, long len) {
522
- VALUE str = rb_str_new(ptr, len);
522
+ static inline long rb_checked_string_length(size_t len) {
523
+ if ((uintmax_t)len > (uintmax_t)LONG_MAX)
524
+ rb_raise(eDataError, "string size exceeds Ruby's maximum length");
525
+
526
+ return (long)len;
527
+ }
528
+
529
+ static inline VALUE rb_binary_str_new(const char *ptr, size_t len) {
530
+ VALUE str = rb_str_new(ptr, rb_checked_string_length(len));
523
531
  rb_enc_associate(str, binary_encoding);
524
532
  return str;
525
533
  }
526
534
 
527
- static inline VALUE rb_binary_str_buf_new(long capa) {
528
- VALUE str = rb_str_buf_new(capa);
535
+ static inline VALUE rb_binary_str_buf_new(size_t capa) {
536
+ VALUE str = rb_str_buf_new(rb_checked_string_length(capa));
529
537
  rb_enc_associate(str, binary_encoding);
530
538
  return str;
531
539
  }
532
540
 
533
- static inline VALUE rb_binary_str_buf_reserve(long capa) {
534
- VALUE str = rb_str_buf_new(capa);
541
+ static inline VALUE rb_binary_str_buf_reserve(size_t capa) {
542
+ VALUE str = rb_str_buf_new(rb_checked_string_length(capa));
535
543
  rb_enc_associate(str, binary_encoding);
536
544
  return str;
537
545
  }
538
546
 
547
+ static inline void rb_binary_str_set_len(VALUE str, size_t len) {
548
+ rb_str_set_len(str, rb_checked_string_length(len));
549
+ }
550
+
539
551
  static inline void grow_binary_str(VALUE str, size_t cur_len, size_t new_cap) {
540
552
  size_t cur_cap = (size_t)rb_str_capacity(str);
541
553
  if (new_cap <= cur_cap)
542
554
  return;
543
- rb_str_set_len(str, (long)cur_len);
544
- rb_str_modify_expand(str, (long)(new_cap - cur_len));
555
+ (void)rb_checked_string_length(new_cap);
556
+ rb_binary_str_set_len(str, cur_len);
557
+ rb_str_modify_expand(str, rb_checked_string_length(new_cap - cur_len));
545
558
  }
546
559
 
547
560
  typedef struct {
@@ -562,6 +575,8 @@ static void limits_config_apply_parsed(const mc_opts_t *opts, limits_config_t *l
562
575
  size_t max_output_size = NUM2SIZET(val);
563
576
  if (max_output_size == 0)
564
577
  rb_raise(rb_eArgError, "max_output_size must be greater than 0");
578
+ if ((uintmax_t)max_output_size > (uintmax_t)LONG_MAX)
579
+ rb_raise(rb_eArgError, "max_output_size exceeds Ruby's maximum string length");
565
580
  limits->max_output_size = max_output_size;
566
581
  }
567
582
 
@@ -1306,7 +1321,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1306
1321
  rb_raise(eMemError, "zstd: failed to create context");
1307
1322
  if (ZSTD_isError(csize))
1308
1323
  rb_raise(eError, "zstd compress: %s", ZSTD_getErrorName(csize));
1309
- rb_str_set_len(dst, (long)csize);
1324
+ rb_binary_str_set_len(dst, csize);
1310
1325
  RB_GC_GUARD(data);
1311
1326
  RB_GC_GUARD(dict_val);
1312
1327
  return dst;
@@ -1343,7 +1358,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1343
1358
  rb_raise(eError, "zstd compress: %s", ZSTD_getErrorName(fargs.result));
1344
1359
  }
1345
1360
 
1346
- VALUE result = rb_binary_str_new(out_buf, (long)fargs.result);
1361
+ VALUE result = rb_binary_str_new(out_buf, fargs.result);
1347
1362
  free(out_buf);
1348
1363
  RB_GC_GUARD(data);
1349
1364
  RB_GC_GUARD(dict_val);
@@ -1370,7 +1385,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1370
1385
  if (ZSTD_isError(args.result))
1371
1386
  rb_raise(eError, "zstd compress: %s", ZSTD_getErrorName(args.result));
1372
1387
 
1373
- rb_str_set_len(dst, (long)args.result);
1388
+ rb_binary_str_set_len(dst, args.result);
1374
1389
  RB_GC_GUARD(data);
1375
1390
  RB_GC_GUARD(dict_val);
1376
1391
  return dst;
@@ -1383,7 +1398,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1383
1398
  prefs.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled;
1384
1399
  prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
1385
1400
  size_t bound = LZ4F_compressFrameBound(slen, &prefs);
1386
- VALUE dst = rb_binary_str_buf_reserve((long)bound);
1401
+ VALUE dst = rb_binary_str_buf_reserve(bound);
1387
1402
  lz4frame_compress_args_t args = {
1388
1403
  .src = src,
1389
1404
  .src_len = slen,
@@ -1401,7 +1416,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1401
1416
  if (args.error_code)
1402
1417
  rb_raise(eError, "lz4 frame compress failed: %s",
1403
1418
  LZ4F_getErrorName(args.error_code));
1404
- rb_str_set_len(dst, (long)args.result);
1419
+ rb_binary_str_set_len(dst, args.result);
1405
1420
  RB_GC_GUARD(data);
1406
1421
  return dst;
1407
1422
  }
@@ -1438,7 +1453,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1438
1453
  size_t total = 8 + (size_t)csize;
1439
1454
  write_le_u32((uint8_t *)out + total, 0);
1440
1455
 
1441
- rb_str_set_len(dst, (long)(total + 4));
1456
+ rb_binary_str_set_len(dst, total + 4);
1442
1457
  RB_GC_GUARD(data);
1443
1458
  return dst;
1444
1459
  } else {
@@ -1460,7 +1475,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1460
1475
  size_t total = 8 + csize;
1461
1476
  write_le_u32((uint8_t *)out + total, 0);
1462
1477
 
1463
- rb_str_set_len(dst, total + 4);
1478
+ rb_binary_str_set_len(dst, total + 4);
1464
1479
  RB_GC_GUARD(data);
1465
1480
  return dst;
1466
1481
  }
@@ -1516,7 +1531,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1516
1531
  if (!ok)
1517
1532
  rb_raise(eError, "brotli compress with dict failed");
1518
1533
 
1519
- rb_str_set_len(dst, initial_out - available_out);
1534
+ rb_binary_str_set_len(dst, initial_out - available_out);
1520
1535
  RB_GC_GUARD(data);
1521
1536
  RB_GC_GUARD(dict_val);
1522
1537
  return dst;
@@ -1540,7 +1555,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1540
1555
  if (!args.result)
1541
1556
  rb_raise(eError, "brotli compress failed");
1542
1557
 
1543
- rb_str_set_len(dst, (long)actual_out_len);
1558
+ rb_binary_str_set_len(dst, actual_out_len);
1544
1559
  RB_GC_GUARD(data);
1545
1560
  return dst;
1546
1561
  } else {
@@ -1550,7 +1565,7 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1550
1565
  (const uint8_t *)src, &out_len, (uint8_t *)RSTRING_PTR(dst));
1551
1566
  if (!ok)
1552
1567
  rb_raise(eError, "brotli compress failed");
1553
- rb_str_set_len(dst, out_len);
1568
+ rb_binary_str_set_len(dst, out_len);
1554
1569
  RB_GC_GUARD(data);
1555
1570
  return dst;
1556
1571
  }
@@ -1560,6 +1575,20 @@ static VALUE compress_compress(int argc, VALUE *argv, VALUE self) {
1560
1575
  return Qnil;
1561
1576
  }
1562
1577
 
1578
+ static VALUE zstd_frame_dictionary_id(VALUE self, VALUE data) {
1579
+ (void)self;
1580
+ StringValue(data);
1581
+
1582
+ const void *src = RSTRING_PTR(data);
1583
+ size_t src_len = (size_t)RSTRING_LEN(data);
1584
+ size_t frame_size = ZSTD_findFrameCompressedSize(src, src_len);
1585
+ if (ZSTD_isError(frame_size) || frame_size != src_len)
1586
+ rb_raise(eDataError, "zstd: expected exactly one valid frame with no trailing bytes");
1587
+
1588
+ RB_GC_GUARD(data);
1589
+ return UINT2NUM(ZSTD_getDictID_fromFrame(src, src_len));
1590
+ }
1591
+
1563
1592
  static VALUE zstd_single_frame_info(VALUE self, VALUE data) {
1564
1593
  (void)self;
1565
1594
  StringValue(data);
@@ -1676,7 +1705,7 @@ static VALUE compress_decompress(int argc, VALUE *argv, VALUE self) {
1676
1705
 
1677
1706
  enforce_output_and_ratio_limits(dsize, slen, limits.max_output_size,
1678
1707
  limits.max_ratio_enabled, limits.max_ratio);
1679
- rb_str_set_len(dst, (long)dsize);
1708
+ rb_binary_str_set_len(dst, dsize);
1680
1709
  RB_GC_GUARD(data);
1681
1710
  RB_GC_GUARD(dict_val);
1682
1711
  return dst;
@@ -1700,7 +1729,7 @@ static VALUE compress_decompress(int argc, VALUE *argv, VALUE self) {
1700
1729
  rb_raise(eDataError, "zstd decompress: %s", ZSTD_getErrorName(dsize));
1701
1730
  enforce_output_and_ratio_limits(dsize, slen, limits.max_output_size,
1702
1731
  limits.max_ratio_enabled, limits.max_ratio);
1703
- rb_str_set_len(dst, dsize);
1732
+ rb_binary_str_set_len(dst, dsize);
1704
1733
  RB_GC_GUARD(data);
1705
1734
  RB_GC_GUARD(dict_val);
1706
1735
  return dst;
@@ -1773,7 +1802,7 @@ static VALUE compress_decompress(int argc, VALUE *argv, VALUE self) {
1773
1802
  break;
1774
1803
  }
1775
1804
 
1776
- rb_str_set_len(dst, total_out);
1805
+ rb_binary_str_set_len(dst, total_out);
1777
1806
  RB_GC_GUARD(data);
1778
1807
  RB_GC_GUARD(dict_val);
1779
1808
  return dst;
@@ -1786,7 +1815,7 @@ static VALUE compress_decompress(int argc, VALUE *argv, VALUE self) {
1786
1815
  alloc_size = limits.max_output_size < 4096 ? limits.max_output_size : 4096;
1787
1816
  if (alloc_size == 0)
1788
1817
  alloc_size = limits.max_output_size;
1789
- VALUE dst = rb_binary_str_buf_reserve((long)alloc_size);
1818
+ VALUE dst = rb_binary_str_buf_reserve(alloc_size);
1790
1819
  while (1) {
1791
1820
  lz4frame_decompress_args_t args = {
1792
1821
  .src = src,
@@ -1821,7 +1850,7 @@ static VALUE compress_decompress(int argc, VALUE *argv, VALUE self) {
1821
1850
  LZ4F_getErrorName(args.error_code));
1822
1851
  enforce_output_and_ratio_limits(args.result, slen, limits.max_output_size,
1823
1852
  limits.max_ratio_enabled, limits.max_ratio);
1824
- rb_str_set_len(dst, (long)args.result);
1853
+ rb_binary_str_set_len(dst, args.result);
1825
1854
  RB_GC_GUARD(data);
1826
1855
  return dst;
1827
1856
  }
@@ -1876,7 +1905,7 @@ static VALUE compress_decompress(int argc, VALUE *argv, VALUE self) {
1876
1905
 
1877
1906
  enforce_output_and_ratio_limits(args.out_offset, slen, limits.max_output_size,
1878
1907
  limits.max_ratio_enabled, limits.max_ratio);
1879
- rb_str_set_len(result, args.out_offset);
1908
+ rb_binary_str_set_len(result, args.out_offset);
1880
1909
  RB_GC_GUARD(data);
1881
1910
  return result;
1882
1911
  }
@@ -1962,7 +1991,7 @@ static VALUE compress_decompress(int argc, VALUE *argv, VALUE self) {
1962
1991
  if (res != BROTLI_DECODER_RESULT_SUCCESS) {
1963
1992
  rb_raise(eDataError, "brotli decompress failed");
1964
1993
  }
1965
- rb_str_set_len(dst, total_out);
1994
+ rb_binary_str_set_len(dst, total_out);
1966
1995
  RB_GC_GUARD(data);
1967
1996
  RB_GC_GUARD(dict_val);
1968
1997
  return dst;
@@ -2117,11 +2146,45 @@ static VALUE compress_version(VALUE self, VALUE algo_sym) {
2117
2146
  _Static_assert(LZ4_RING_BUFFER_TOTAL == 2 * LZ4_RING_BUFFER_SIZE,
2118
2147
  "ring buffer total must be exactly twice the window size");
2119
2148
 
2149
+ typedef struct {
2150
+ int *active;
2151
+ VALUE self;
2152
+ VALUE argument;
2153
+ VALUE (*operation)(VALUE, VALUE);
2154
+ } stream_operation_t;
2155
+
2156
+ static VALUE stream_operation_body(VALUE opaque) {
2157
+ stream_operation_t *call = (stream_operation_t *)(uintptr_t)opaque;
2158
+ return call->operation(call->self, call->argument);
2159
+ }
2160
+
2161
+ static VALUE stream_operation_ensure(VALUE opaque) {
2162
+ stream_operation_t *call = (stream_operation_t *)(uintptr_t)opaque;
2163
+ *call->active = 0;
2164
+ return Qnil;
2165
+ }
2166
+
2167
+ static VALUE stream_operation_call(int *active, VALUE self, VALUE argument,
2168
+ VALUE (*operation)(VALUE, VALUE)) {
2169
+ if (*active)
2170
+ rb_raise(eStreamError, "stream operation already in progress");
2171
+
2172
+ stream_operation_t call = {
2173
+ .active = active, .self = self, .argument = argument, .operation = operation};
2174
+ *active = 1;
2175
+ VALUE result = rb_ensure(stream_operation_body, (VALUE)(uintptr_t)&call,
2176
+ stream_operation_ensure, (VALUE)(uintptr_t)&call);
2177
+ RB_GC_GUARD(self);
2178
+ RB_GC_GUARD(argument);
2179
+ return result;
2180
+ }
2181
+
2120
2182
  typedef struct {
2121
2183
  compress_algo_t algo;
2122
2184
  int level;
2123
2185
  int closed;
2124
2186
  int finished;
2187
+ int operation_active;
2125
2188
 
2126
2189
  union {
2127
2190
  ZSTD_CStream *zstd;
@@ -2165,6 +2228,9 @@ static size_t deflater_memsize(const void *ptr) {
2165
2228
  return 0;
2166
2229
 
2167
2230
  size_t s = sizeof(deflater_t);
2231
+ if (d->operation_active)
2232
+ return s;
2233
+
2168
2234
  switch (d->algo) {
2169
2235
  case ALGO_ZSTD:
2170
2236
  if (d->ctx.zstd)
@@ -2311,7 +2377,7 @@ static VALUE lz4_compress_ring_block(deflater_t *d) {
2311
2377
 
2312
2378
  write_le_u32((uint8_t *)out + 4, (uint32_t)csize);
2313
2379
 
2314
- rb_str_set_len(output, 8 + csize);
2380
+ rb_binary_str_set_len(output, 8 + csize);
2315
2381
  d->lz4_ring.pending = 0;
2316
2382
 
2317
2383
  if (d->lz4_ring.ring_offset >= LZ4_RING_BUFFER_SIZE) {
@@ -2321,7 +2387,7 @@ static VALUE lz4_compress_ring_block(deflater_t *d) {
2321
2387
  return output;
2322
2388
  }
2323
2389
 
2324
- static VALUE deflater_write(VALUE self, VALUE chunk) {
2390
+ static VALUE deflater_write_impl(VALUE self, VALUE chunk) {
2325
2391
  deflater_t *d;
2326
2392
  TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2327
2393
  if (d->closed)
@@ -2391,7 +2457,7 @@ static VALUE deflater_write(VALUE self, VALUE chunk) {
2391
2457
  }
2392
2458
  result_len += output.pos;
2393
2459
  }
2394
- rb_str_set_len(result, result_len);
2460
+ rb_binary_str_set_len(result, result_len);
2395
2461
  RB_GC_GUARD(chunk);
2396
2462
  return result;
2397
2463
  }
@@ -2457,7 +2523,7 @@ static VALUE deflater_write(VALUE self, VALUE chunk) {
2457
2523
  }
2458
2524
  }
2459
2525
  }
2460
- rb_str_set_len(result, result_len);
2526
+ rb_binary_str_set_len(result, result_len);
2461
2527
  RB_GC_GUARD(chunk);
2462
2528
  return result;
2463
2529
  }
@@ -2503,7 +2569,7 @@ static VALUE deflater_write(VALUE self, VALUE chunk) {
2503
2569
  }
2504
2570
  }
2505
2571
  }
2506
- rb_str_set_len(result, result_len);
2572
+ rb_binary_str_set_len(result, result_len);
2507
2573
  RB_GC_GUARD(chunk);
2508
2574
  return result;
2509
2575
  }
@@ -2511,7 +2577,14 @@ static VALUE deflater_write(VALUE self, VALUE chunk) {
2511
2577
  return rb_binary_str_new("", 0);
2512
2578
  }
2513
2579
 
2514
- static VALUE deflater_flush(VALUE self) {
2580
+ static VALUE deflater_write(VALUE self, VALUE chunk) {
2581
+ deflater_t *d;
2582
+ TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2583
+ return stream_operation_call(&d->operation_active, self, chunk, deflater_write_impl);
2584
+ }
2585
+
2586
+ static VALUE deflater_flush_impl(VALUE self, VALUE unused) {
2587
+ (void)unused;
2515
2588
  deflater_t *d;
2516
2589
  TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2517
2590
  if (d->closed)
@@ -2540,7 +2613,7 @@ static VALUE deflater_flush(VALUE self) {
2540
2613
  result_len += output.pos;
2541
2614
  } while (ret > 0);
2542
2615
 
2543
- rb_str_set_len(result, result_len);
2616
+ rb_binary_str_set_len(result, result_len);
2544
2617
  return result;
2545
2618
  }
2546
2619
  case ALGO_BROTLI: {
@@ -2572,7 +2645,7 @@ static VALUE deflater_flush(VALUE self) {
2572
2645
  }
2573
2646
  } while (BrotliEncoderHasMoreOutput(d->ctx.brotli));
2574
2647
 
2575
- rb_str_set_len(result, result_len);
2648
+ rb_binary_str_set_len(result, result_len);
2576
2649
  return result;
2577
2650
  }
2578
2651
  case ALGO_LZ4:
@@ -2581,7 +2654,14 @@ static VALUE deflater_flush(VALUE self) {
2581
2654
  return rb_binary_str_new("", 0);
2582
2655
  }
2583
2656
 
2584
- static VALUE deflater_finish(VALUE self) {
2657
+ static VALUE deflater_flush(VALUE self) {
2658
+ deflater_t *d;
2659
+ TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2660
+ return stream_operation_call(&d->operation_active, self, Qnil, deflater_flush_impl);
2661
+ }
2662
+
2663
+ static VALUE deflater_finish_impl(VALUE self, VALUE unused) {
2664
+ (void)unused;
2585
2665
  deflater_t *d;
2586
2666
  TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2587
2667
  if (d->closed)
@@ -2611,7 +2691,7 @@ static VALUE deflater_finish(VALUE self) {
2611
2691
  result_len += output.pos;
2612
2692
  } while (ret > 0);
2613
2693
 
2614
- rb_str_set_len(result, result_len);
2694
+ rb_binary_str_set_len(result, result_len);
2615
2695
  return result;
2616
2696
  }
2617
2697
  case ALGO_BROTLI: {
@@ -2644,7 +2724,7 @@ static VALUE deflater_finish(VALUE self) {
2644
2724
  } while (BrotliEncoderHasMoreOutput(d->ctx.brotli) ||
2645
2725
  !BrotliEncoderIsFinished(d->ctx.brotli));
2646
2726
 
2647
- rb_str_set_len(result, result_len);
2727
+ rb_binary_str_set_len(result, result_len);
2648
2728
  return result;
2649
2729
  }
2650
2730
  case ALGO_LZ4: {
@@ -2675,14 +2755,21 @@ static VALUE deflater_finish(VALUE self) {
2675
2755
  write_le_u32((uint8_t *)out, 0);
2676
2756
  result_len += 4;
2677
2757
 
2678
- rb_str_set_len(result, result_len);
2758
+ rb_binary_str_set_len(result, result_len);
2679
2759
  return result;
2680
2760
  }
2681
2761
  }
2682
2762
  return rb_binary_str_new("", 0);
2683
2763
  }
2684
2764
 
2685
- static VALUE deflater_reset(VALUE self) {
2765
+ static VALUE deflater_finish(VALUE self) {
2766
+ deflater_t *d;
2767
+ TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2768
+ return stream_operation_call(&d->operation_active, self, Qnil, deflater_finish_impl);
2769
+ }
2770
+
2771
+ static VALUE deflater_reset_impl(VALUE self, VALUE unused) {
2772
+ (void)unused;
2686
2773
  deflater_t *d;
2687
2774
  TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2688
2775
 
@@ -2738,7 +2825,14 @@ static VALUE deflater_reset(VALUE self) {
2738
2825
  return self;
2739
2826
  }
2740
2827
 
2741
- static VALUE deflater_close(VALUE self) {
2828
+ static VALUE deflater_reset(VALUE self) {
2829
+ deflater_t *d;
2830
+ TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2831
+ return stream_operation_call(&d->operation_active, self, Qnil, deflater_reset_impl);
2832
+ }
2833
+
2834
+ static VALUE deflater_close_impl(VALUE self, VALUE unused) {
2835
+ (void)unused;
2742
2836
  deflater_t *d;
2743
2837
  TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2744
2838
  if (d->closed)
@@ -2768,6 +2862,12 @@ static VALUE deflater_close(VALUE self) {
2768
2862
  return Qnil;
2769
2863
  }
2770
2864
 
2865
+ static VALUE deflater_close(VALUE self) {
2866
+ deflater_t *d;
2867
+ TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
2868
+ return stream_operation_call(&d->operation_active, self, Qnil, deflater_close_impl);
2869
+ }
2870
+
2771
2871
  static VALUE deflater_closed_p(VALUE self) {
2772
2872
  deflater_t *d;
2773
2873
  TypedData_Get_Struct(self, deflater_t, &deflater_type, d);
@@ -2778,6 +2878,7 @@ typedef struct {
2778
2878
  compress_algo_t algo;
2779
2879
  int closed;
2780
2880
  int finished;
2881
+ int operation_active;
2781
2882
  size_t max_output_size;
2782
2883
  size_t total_output;
2783
2884
  size_t total_input;
@@ -2824,6 +2925,9 @@ static size_t inflater_memsize(const void *ptr) {
2824
2925
  return 0;
2825
2926
 
2826
2927
  size_t s = sizeof(inflater_t);
2928
+ if (inf->operation_active)
2929
+ return s;
2930
+
2827
2931
  switch (inf->algo) {
2828
2932
  case ALGO_ZSTD:
2829
2933
  if (inf->ctx.zstd)
@@ -2918,7 +3022,7 @@ static VALUE inflater_initialize(int argc, VALUE *argv, VALUE self) {
2918
3022
  return self;
2919
3023
  }
2920
3024
 
2921
- static VALUE inflater_write(VALUE self, VALUE chunk) {
3025
+ static VALUE inflater_write_impl(VALUE self, VALUE chunk) {
2922
3026
  inflater_t *inf;
2923
3027
  TypedData_Get_Struct(self, inflater_t, &inflater_type, inf);
2924
3028
  if (inf->closed)
@@ -3003,7 +3107,7 @@ static VALUE inflater_write(VALUE self, VALUE chunk) {
3003
3107
  "compressed input exceeds representable size");
3004
3108
  inf->total_output = checked_add_size(inf->total_output, result_len,
3005
3109
  "decompressed output exceeds representable size");
3006
- rb_str_set_len(result, result_len);
3110
+ rb_binary_str_set_len(result, result_len);
3007
3111
  RB_GC_GUARD(chunk);
3008
3112
  return result;
3009
3113
  }
@@ -3084,7 +3188,7 @@ static VALUE inflater_write(VALUE self, VALUE chunk) {
3084
3188
  "compressed input exceeds representable size");
3085
3189
  inf->total_output = checked_add_size(inf->total_output, result_len,
3086
3190
  "decompressed output exceeds representable size");
3087
- rb_str_set_len(result, result_len);
3191
+ rb_binary_str_set_len(result, result_len);
3088
3192
  RB_GC_GUARD(chunk);
3089
3193
  return result;
3090
3194
  }
@@ -3187,7 +3291,7 @@ static VALUE inflater_write(VALUE self, VALUE chunk) {
3187
3291
  inf->lz4_buf.offset = pos;
3188
3292
  inf->total_output = checked_add_size(inf->total_output, result_len,
3189
3293
  "decompressed output exceeds representable size");
3190
- rb_str_set_len(result, result_len);
3294
+ rb_binary_str_set_len(result, result_len);
3191
3295
  RB_GC_GUARD(chunk);
3192
3296
  return result;
3193
3297
  }
@@ -3195,7 +3299,14 @@ static VALUE inflater_write(VALUE self, VALUE chunk) {
3195
3299
  return rb_binary_str_new("", 0);
3196
3300
  }
3197
3301
 
3198
- static VALUE inflater_finish(VALUE self) {
3302
+ static VALUE inflater_write(VALUE self, VALUE chunk) {
3303
+ inflater_t *inf;
3304
+ TypedData_Get_Struct(self, inflater_t, &inflater_type, inf);
3305
+ return stream_operation_call(&inf->operation_active, self, chunk, inflater_write_impl);
3306
+ }
3307
+
3308
+ static VALUE inflater_finish_impl(VALUE self, VALUE unused) {
3309
+ (void)unused;
3199
3310
  inflater_t *inf;
3200
3311
  TypedData_Get_Struct(self, inflater_t, &inflater_type, inf);
3201
3312
  if (inf->closed)
@@ -3204,7 +3315,14 @@ static VALUE inflater_finish(VALUE self) {
3204
3315
  return rb_binary_str_new("", 0);
3205
3316
  }
3206
3317
 
3207
- static VALUE inflater_reset(VALUE self) {
3318
+ static VALUE inflater_finish(VALUE self) {
3319
+ inflater_t *inf;
3320
+ TypedData_Get_Struct(self, inflater_t, &inflater_type, inf);
3321
+ return stream_operation_call(&inf->operation_active, self, Qnil, inflater_finish_impl);
3322
+ }
3323
+
3324
+ static VALUE inflater_reset_impl(VALUE self, VALUE unused) {
3325
+ (void)unused;
3208
3326
  inflater_t *inf;
3209
3327
  TypedData_Get_Struct(self, inflater_t, &inflater_type, inf);
3210
3328
 
@@ -3249,7 +3367,14 @@ static VALUE inflater_reset(VALUE self) {
3249
3367
  return self;
3250
3368
  }
3251
3369
 
3252
- static VALUE inflater_close(VALUE self) {
3370
+ static VALUE inflater_reset(VALUE self) {
3371
+ inflater_t *inf;
3372
+ TypedData_Get_Struct(self, inflater_t, &inflater_type, inf);
3373
+ return stream_operation_call(&inf->operation_active, self, Qnil, inflater_reset_impl);
3374
+ }
3375
+
3376
+ static VALUE inflater_close_impl(VALUE self, VALUE unused) {
3377
+ (void)unused;
3253
3378
  inflater_t *inf;
3254
3379
  TypedData_Get_Struct(self, inflater_t, &inflater_type, inf);
3255
3380
  if (inf->closed)
@@ -3275,6 +3400,12 @@ static VALUE inflater_close(VALUE self) {
3275
3400
  return Qnil;
3276
3401
  }
3277
3402
 
3403
+ static VALUE inflater_close(VALUE self) {
3404
+ inflater_t *inf;
3405
+ TypedData_Get_Struct(self, inflater_t, &inflater_type, inf);
3406
+ return stream_operation_call(&inf->operation_active, self, Qnil, inflater_close_impl);
3407
+ }
3408
+
3278
3409
  static VALUE inflater_closed_p(VALUE self) {
3279
3410
  inflater_t *inf;
3280
3411
  TypedData_Get_Struct(self, inflater_t, &inflater_type, inf);
@@ -3395,6 +3526,59 @@ static VALUE brotli_train_dictionary(int argc, VALUE *argv, VALUE self) {
3395
3526
  return train_dictionary_internal(samples, parsed_opts.size, ALGO_BROTLI);
3396
3527
  }
3397
3528
 
3529
+ typedef struct {
3530
+ FILE *file;
3531
+ VALUE path_value;
3532
+ const char *path;
3533
+ uint8_t *data;
3534
+ size_t size;
3535
+ int loaded;
3536
+ } dict_file_read_t;
3537
+
3538
+ static VALUE dict_file_read_body(VALUE opaque) {
3539
+ dict_file_read_t *read = (dict_file_read_t *)(uintptr_t)opaque;
3540
+
3541
+ if (fseek(read->file, 0, SEEK_END) != 0)
3542
+ rb_sys_fail(read->path);
3543
+
3544
+ long file_size = ftell(read->file);
3545
+ if (file_size < 0)
3546
+ rb_sys_fail(read->path);
3547
+ if (file_size == 0)
3548
+ rb_raise(eDataError, "dictionary file is empty: %s", read->path);
3549
+ if ((unsigned long long)file_size > DICT_FILE_MAX_SIZE)
3550
+ rb_raise(eDataError, "dictionary file too large (%ld bytes, max=%d)", file_size,
3551
+ (int)DICT_FILE_MAX_SIZE);
3552
+ if (fseek(read->file, 0, SEEK_SET) != 0)
3553
+ rb_sys_fail(read->path);
3554
+
3555
+ read->data = ALLOC_N(uint8_t, file_size);
3556
+ read->path = RSTRING_PTR(read->path_value);
3557
+ read->size = (size_t)file_size;
3558
+ size_t read_bytes = fread(read->data, 1, read->size, read->file);
3559
+ if (read_bytes != read->size) {
3560
+ xfree(read->data);
3561
+ read->data = NULL;
3562
+ rb_raise(eDataError, "failed to read dictionary: %s", read->path);
3563
+ }
3564
+
3565
+ read->loaded = 1;
3566
+ return Qnil;
3567
+ }
3568
+
3569
+ static VALUE dict_file_read_ensure(VALUE opaque) {
3570
+ dict_file_read_t *read = (dict_file_read_t *)(uintptr_t)opaque;
3571
+ if (read->file) {
3572
+ fclose(read->file);
3573
+ read->file = NULL;
3574
+ }
3575
+ if (!read->loaded && read->data) {
3576
+ xfree(read->data);
3577
+ read->data = NULL;
3578
+ }
3579
+ return Qnil;
3580
+ }
3581
+
3398
3582
  static VALUE dict_load(int argc, VALUE *argv, VALUE self) {
3399
3583
  VALUE path, opts;
3400
3584
  scan_one_required_keywords(argc, argv, &path, &opts);
@@ -3411,40 +3595,23 @@ static VALUE dict_load(int argc, VALUE *argv, VALUE self) {
3411
3595
  if (algo == ALGO_LZ4)
3412
3596
  rb_raise(eUnsupportedError, "LZ4 does not support dictionaries");
3413
3597
 
3598
+ VALUE dict_obj = dict_alloc(cDictionary);
3599
+ dictionary_t *d;
3600
+ TypedData_Get_Struct(dict_obj, dictionary_t, &dictionary_type, d);
3601
+
3414
3602
  const char *cpath = RSTRING_PTR(path);
3415
- FILE *f = fopen(cpath, "rb");
3416
- if (!f)
3603
+ FILE *file = fopen(cpath, "rb");
3604
+ if (!file)
3417
3605
  rb_sys_fail(cpath);
3418
3606
 
3419
- fseek(f, 0, SEEK_END);
3420
- long file_size = ftell(f);
3421
- fseek(f, 0, SEEK_SET);
3422
-
3423
- if (file_size <= 0) {
3424
- fclose(f);
3425
- rb_raise(eDataError, "dictionary file is empty: %s", cpath);
3426
- }
3427
- if ((unsigned long long)file_size > DICT_FILE_MAX_SIZE) {
3428
- fclose(f);
3429
- rb_raise(eDataError, "dictionary file too large (%ld bytes, max=%d)", file_size,
3430
- (int)DICT_FILE_MAX_SIZE);
3431
- }
3432
-
3433
- uint8_t *buf = ALLOC_N(uint8_t, file_size);
3434
- size_t read_bytes = fread(buf, 1, file_size, f);
3435
- fclose(f);
3436
-
3437
- if ((long)read_bytes != file_size) {
3438
- xfree(buf);
3439
- rb_raise(eDataError, "failed to read dictionary: %s", cpath);
3440
- }
3607
+ dict_file_read_t read = {.file = file, .path_value = path, .path = cpath};
3608
+ rb_ensure(dict_file_read_body, (VALUE)(uintptr_t)&read, dict_file_read_ensure,
3609
+ (VALUE)(uintptr_t)&read);
3441
3610
 
3442
- VALUE dict_obj = dict_alloc(cDictionary);
3443
- dictionary_t *d;
3444
- TypedData_Get_Struct(dict_obj, dictionary_t, &dictionary_type, d);
3445
3611
  d->algo = algo;
3446
- d->data = buf;
3447
- d->size = (size_t)file_size;
3612
+ d->data = read.data;
3613
+ d->size = read.size;
3614
+ read.data = NULL;
3448
3615
  return dict_obj;
3449
3616
  }
3450
3617
 
@@ -3471,6 +3638,20 @@ static VALUE dict_algo(VALUE self) {
3471
3638
  return algo_to_sym(d->algo);
3472
3639
  }
3473
3640
 
3641
+ static VALUE dict_bytes(VALUE self) {
3642
+ dictionary_t *d;
3643
+ TypedData_Get_Struct(self, dictionary_t, &dictionary_type, d);
3644
+ return rb_binary_str_new((const char *)d->data, d->size);
3645
+ }
3646
+
3647
+ static VALUE dict_zstd_id(VALUE self) {
3648
+ dictionary_t *d;
3649
+ TypedData_Get_Struct(self, dictionary_t, &dictionary_type, d);
3650
+ if (d->algo != ALGO_ZSTD)
3651
+ rb_raise(eUnsupportedError, "zstd_id is available only for zstd dictionaries");
3652
+ return UINT2NUM(ZSTD_getDictID_fromDict(d->data, d->size));
3653
+ }
3654
+
3474
3655
  static VALUE dict_size(VALUE self) {
3475
3656
  dictionary_t *d;
3476
3657
  TypedData_Get_Struct(self, dictionary_t, &dictionary_type, d);
@@ -3508,6 +3689,8 @@ void Init_multi_compress(void) {
3508
3689
  rb_define_module_function(mMultiCompress, "compress", compress_compress, -1);
3509
3690
  rb_define_module_function(mMultiCompress, "decompress", compress_decompress, -1);
3510
3691
  rb_define_module_function(mMultiCompress, "zstd_single_frame_info", zstd_single_frame_info, 1);
3692
+ rb_define_module_function(mMultiCompress, "zstd_frame_dictionary_id", zstd_frame_dictionary_id,
3693
+ 1);
3511
3694
  rb_define_module_function(mMultiCompress, "crc32", compress_crc32, -1);
3512
3695
  rb_define_module_function(mMultiCompress, "adler32", compress_adler32, -1);
3513
3696
  rb_define_module_function(mMultiCompress, "algorithms", compress_algorithms, 0);
@@ -3542,6 +3725,8 @@ void Init_multi_compress(void) {
3542
3725
  rb_define_singleton_method(cDictionary, "load", dict_load, -1);
3543
3726
  rb_define_method(cDictionary, "save", dict_save, 1);
3544
3727
  rb_define_method(cDictionary, "algo", dict_algo, 0);
3728
+ rb_define_method(cDictionary, "bytes", dict_bytes, 0);
3729
+ rb_define_method(cDictionary, "zstd_id", dict_zstd_id, 0);
3545
3730
  rb_define_method(cDictionary, "size", dict_size, 0);
3546
3731
  rb_define_singleton_method(mZstd, "train_dictionary", zstd_train_dictionary, -1);
3547
3732
  rb_define_singleton_method(mBrotli, "train_dictionary", brotli_train_dictionary, -1);