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
@@ -1,3 +1,3 @@
1
1
  zstd=1.5.7
2
2
  lz4=1.10.0
3
- brotli=1.2.0
3
+ brotli=1.2.1
@@ -19,7 +19,7 @@
19
19
 
20
20
  #define BROTLI_VERSION_MAJOR 1
21
21
  #define BROTLI_VERSION_MINOR 2
22
- #define BROTLI_VERSION_PATCH 0
22
+ #define BROTLI_VERSION_PATCH 1
23
23
 
24
24
  #define BROTLI_VERSION BROTLI_MAKE_HEX_VERSION( \
25
25
  BROTLI_VERSION_MAJOR, BROTLI_VERSION_MINOR, BROTLI_VERSION_PATCH)
@@ -33,7 +33,7 @@
33
33
  */
34
34
 
35
35
  #define BROTLI_ABI_CURRENT 3
36
- #define BROTLI_ABI_REVISION 0
36
+ #define BROTLI_ABI_REVISION 1
37
37
  #define BROTLI_ABI_AGE 2
38
38
 
39
39
  #if BROTLI_VERSION_MAJOR != (BROTLI_ABI_CURRENT - BROTLI_ABI_AGE)
@@ -1176,7 +1176,7 @@ static BrotliDecoderErrorCode DecodeContextMap(brotli_reg_t context_map_size,
1176
1176
 
1177
1177
  /* Decodes a command or literal and updates block type ring-buffer.
1178
1178
  Reads 3..54 bits. */
1179
- static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
1179
+ static BROTLI_INLINE BrotliDecoderErrorCode DecodeBlockTypeAndLength(
1180
1180
  int safe, BrotliDecoderState* s, int tree_type) {
1181
1181
  brotli_reg_t max_block_type = s->num_block_types[tree_type];
1182
1182
  const HuffmanCode* type_tree = &s->block_type_trees[
@@ -1187,7 +1187,7 @@ static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
1187
1187
  brotli_reg_t* ringbuffer = &s->block_type_rb[tree_type * 2];
1188
1188
  brotli_reg_t block_type;
1189
1189
  if (max_block_type <= 1) {
1190
- return BROTLI_FALSE;
1190
+ return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH;
1191
1191
  }
1192
1192
 
1193
1193
  /* Read 0..15 + 3..39 bits. */
@@ -1197,11 +1197,13 @@ static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
1197
1197
  } else {
1198
1198
  BrotliBitReaderState memento;
1199
1199
  BrotliBitReaderSaveState(br, &memento);
1200
- if (!SafeReadSymbol(type_tree, br, &block_type)) return BROTLI_FALSE;
1200
+ if (!SafeReadSymbol(type_tree, br, &block_type)) {
1201
+ return BROTLI_DECODER_NEEDS_MORE_INPUT;
1202
+ }
1201
1203
  if (!SafeReadBlockLength(s, &s->block_length[tree_type], len_tree, br)) {
1202
1204
  s->substate_read_block_length = BROTLI_STATE_READ_BLOCK_LENGTH_NONE;
1203
1205
  BrotliBitReaderRestoreState(br, &memento);
1204
- return BROTLI_FALSE;
1206
+ return BROTLI_DECODER_NEEDS_MORE_INPUT;
1205
1207
  }
1206
1208
  }
1207
1209
 
@@ -1217,7 +1219,7 @@ static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
1217
1219
  }
1218
1220
  ringbuffer[0] = ringbuffer[1];
1219
1221
  ringbuffer[1] = block_type;
1220
- return BROTLI_TRUE;
1222
+ return BROTLI_DECODER_SUCCESS;
1221
1223
  }
1222
1224
 
1223
1225
  static BROTLI_INLINE void DetectTrivialLiteralBlockTypes(
@@ -1254,62 +1256,68 @@ static BROTLI_INLINE void PrepareLiteralDecoding(BrotliDecoderState* s) {
1254
1256
 
1255
1257
  /* Decodes the block type and updates the state for literal context.
1256
1258
  Reads 3..54 bits. */
1257
- static BROTLI_INLINE BROTLI_BOOL DecodeLiteralBlockSwitchInternal(
1259
+ static BROTLI_INLINE BrotliDecoderErrorCode DecodeLiteralBlockSwitchInternal(
1258
1260
  int safe, BrotliDecoderState* s) {
1259
- if (!DecodeBlockTypeAndLength(safe, s, 0)) {
1260
- return BROTLI_FALSE;
1261
+ BrotliDecoderErrorCode result = DecodeBlockTypeAndLength(safe, s, 0);
1262
+ if (result != BROTLI_DECODER_SUCCESS) {
1263
+ return result;
1261
1264
  }
1262
1265
  PrepareLiteralDecoding(s);
1263
- return BROTLI_TRUE;
1266
+ return BROTLI_DECODER_SUCCESS;
1264
1267
  }
1265
1268
 
1266
- static void BROTLI_NOINLINE DecodeLiteralBlockSwitch(BrotliDecoderState* s) {
1267
- DecodeLiteralBlockSwitchInternal(0, s);
1269
+ static BROTLI_NOINLINE BrotliDecoderErrorCode
1270
+ DecodeLiteralBlockSwitch(BrotliDecoderState* s) {
1271
+ return DecodeLiteralBlockSwitchInternal(0, s);
1268
1272
  }
1269
1273
 
1270
- static BROTLI_BOOL BROTLI_NOINLINE SafeDecodeLiteralBlockSwitch(
1274
+ static BROTLI_NOINLINE BrotliDecoderErrorCode SafeDecodeLiteralBlockSwitch(
1271
1275
  BrotliDecoderState* s) {
1272
1276
  return DecodeLiteralBlockSwitchInternal(1, s);
1273
1277
  }
1274
1278
 
1275
1279
  /* Block switch for insert/copy length.
1276
1280
  Reads 3..54 bits. */
1277
- static BROTLI_INLINE BROTLI_BOOL DecodeCommandBlockSwitchInternal(
1281
+ static BROTLI_INLINE BrotliDecoderErrorCode DecodeCommandBlockSwitchInternal(
1278
1282
  int safe, BrotliDecoderState* s) {
1279
- if (!DecodeBlockTypeAndLength(safe, s, 1)) {
1280
- return BROTLI_FALSE;
1283
+ BrotliDecoderErrorCode result = DecodeBlockTypeAndLength(safe, s, 1);
1284
+ if (result != BROTLI_DECODER_SUCCESS) {
1285
+ return result;
1281
1286
  }
1282
1287
  s->htree_command = s->insert_copy_hgroup.htrees[s->block_type_rb[3]];
1283
- return BROTLI_TRUE;
1288
+ return BROTLI_DECODER_SUCCESS;
1284
1289
  }
1285
1290
 
1286
- static void BROTLI_NOINLINE DecodeCommandBlockSwitch(BrotliDecoderState* s) {
1287
- DecodeCommandBlockSwitchInternal(0, s);
1291
+ static BROTLI_NOINLINE BrotliDecoderErrorCode
1292
+ DecodeCommandBlockSwitch(BrotliDecoderState* s) {
1293
+ return DecodeCommandBlockSwitchInternal(0, s);
1288
1294
  }
1289
1295
 
1290
- static BROTLI_BOOL BROTLI_NOINLINE SafeDecodeCommandBlockSwitch(
1291
- BrotliDecoderState* s) {
1296
+ static BROTLI_NOINLINE BrotliDecoderErrorCode
1297
+ SafeDecodeCommandBlockSwitch(BrotliDecoderState* s) {
1292
1298
  return DecodeCommandBlockSwitchInternal(1, s);
1293
1299
  }
1294
1300
 
1295
1301
  /* Block switch for distance codes.
1296
1302
  Reads 3..54 bits. */
1297
- static BROTLI_INLINE BROTLI_BOOL DecodeDistanceBlockSwitchInternal(
1303
+ static BROTLI_INLINE BrotliDecoderErrorCode DecodeDistanceBlockSwitchInternal(
1298
1304
  int safe, BrotliDecoderState* s) {
1299
- if (!DecodeBlockTypeAndLength(safe, s, 2)) {
1300
- return BROTLI_FALSE;
1305
+ BrotliDecoderErrorCode result = DecodeBlockTypeAndLength(safe, s, 2);
1306
+ if (result != BROTLI_DECODER_SUCCESS) {
1307
+ return result;
1301
1308
  }
1302
1309
  s->dist_context_map_slice = s->dist_context_map +
1303
1310
  (s->block_type_rb[5] << BROTLI_DISTANCE_CONTEXT_BITS);
1304
1311
  s->dist_htree_index = s->dist_context_map_slice[s->distance_context];
1305
- return BROTLI_TRUE;
1312
+ return BROTLI_DECODER_SUCCESS;
1306
1313
  }
1307
1314
 
1308
- static void BROTLI_NOINLINE DecodeDistanceBlockSwitch(BrotliDecoderState* s) {
1309
- DecodeDistanceBlockSwitchInternal(0, s);
1315
+ static BROTLI_NOINLINE BrotliDecoderErrorCode
1316
+ DecodeDistanceBlockSwitch(BrotliDecoderState* s) {
1317
+ return DecodeDistanceBlockSwitchInternal(0, s);
1310
1318
  }
1311
1319
 
1312
- static BROTLI_BOOL BROTLI_NOINLINE SafeDecodeDistanceBlockSwitch(
1320
+ static BROTLI_NOINLINE BrotliDecoderErrorCode SafeDecodeDistanceBlockSwitch(
1313
1321
  BrotliDecoderState* s) {
1314
1322
  return DecodeDistanceBlockSwitchInternal(1, s);
1315
1323
  }
@@ -1940,6 +1948,9 @@ static BROTLI_INLINE BROTLI_BOOL CheckInputAmount(
1940
1948
  return BrotliCheckInputAmount(br);
1941
1949
  }
1942
1950
 
1951
+ /* NB: METHOD should return BROTLI_FALSE only in case there is not enough input;
1952
+ in case of "unsafe" execution, when input is guaranteed to be sufficient,
1953
+ result is ignored. */
1943
1954
  #define BROTLI_SAFE(METHOD) \
1944
1955
  { \
1945
1956
  if (safe) { \
@@ -1952,6 +1963,22 @@ static BROTLI_INLINE BROTLI_BOOL CheckInputAmount(
1952
1963
  } \
1953
1964
  }
1954
1965
 
1966
+ /* NB: METHOD should return BROTLI_DECODER_SUCCESS, BROTLI_DECODER_ERROR_*, or
1967
+ BROTLI_DECODER_NEEDS_MORE_INPUT; the later two break the processing. */
1968
+ #define BROTLI_SAFE_WITH_STATUS(METHOD) \
1969
+ { \
1970
+ BrotliDecoderErrorCode status; \
1971
+ if (safe) { \
1972
+ status = Safe##METHOD; \
1973
+ } else { \
1974
+ status = METHOD; \
1975
+ } \
1976
+ if (status != BROTLI_DECODER_SUCCESS) { \
1977
+ result = status; \
1978
+ goto saveStateAndReturn; \
1979
+ } \
1980
+ }
1981
+
1955
1982
  static BROTLI_INLINE BrotliDecoderErrorCode ProcessCommandsInternal(
1956
1983
  int safe, BrotliDecoderState* s) {
1957
1984
  int pos = s->pos;
@@ -1991,7 +2018,7 @@ CommandBegin:
1991
2018
  goto saveStateAndReturn;
1992
2019
  }
1993
2020
  if (BROTLI_PREDICT_FALSE(s->block_length[1] == 0)) {
1994
- BROTLI_SAFE(DecodeCommandBlockSwitch(s));
2021
+ BROTLI_SAFE_WITH_STATUS(DecodeCommandBlockSwitch(s));
1995
2022
  goto CommandBegin;
1996
2023
  }
1997
2024
  /* Read the insert/copy length in the command. */
@@ -2136,7 +2163,7 @@ CommandPostDecodeLiterals:
2136
2163
  } else {
2137
2164
  /* Read distance code in the command, unless it was implicitly zero. */
2138
2165
  if (BROTLI_PREDICT_FALSE(s->block_length[2] == 0)) {
2139
- BROTLI_SAFE(DecodeDistanceBlockSwitch(s));
2166
+ BROTLI_SAFE_WITH_STATUS(DecodeDistanceBlockSwitch(s));
2140
2167
  }
2141
2168
  BROTLI_SAFE(ReadDistance(s, br));
2142
2169
  }
@@ -2330,7 +2357,7 @@ CommandPostWrapCopy:
2330
2357
  }
2331
2358
 
2332
2359
  NextLiteralBlock:
2333
- BROTLI_SAFE(DecodeLiteralBlockSwitch(s));
2360
+ BROTLI_SAFE_WITH_STATUS(DecodeLiteralBlockSwitch(s));
2334
2361
  goto CommandInner;
2335
2362
 
2336
2363
  saveStateAndReturn:
@@ -2552,6 +2579,8 @@ BrotliDecoderResult BrotliDecoderDecompressStream(
2552
2579
  if (result != BROTLI_DECODER_SUCCESS) {
2553
2580
  break;
2554
2581
  }
2582
+ BROTLI_DCHECK(s->meta_block_remaining_len <=
2583
+ (int)BROTLI_BLOCK_SIZE_CAP);
2555
2584
  BROTLI_LOG_UINT(s->is_last_metablock);
2556
2585
  BROTLI_LOG_UINT(s->meta_block_remaining_len);
2557
2586
  BROTLI_LOG_UINT(s->is_metadata);
@@ -85,9 +85,7 @@ typedef enum {
85
85
  BROTLI_ERROR_CODE(_ERROR_FORMAT_, PADDING_1, -14) SEPARATOR \
86
86
  BROTLI_ERROR_CODE(_ERROR_FORMAT_, PADDING_2, -15) SEPARATOR \
87
87
  BROTLI_ERROR_CODE(_ERROR_FORMAT_, DISTANCE, -16) SEPARATOR \
88
- \
89
- /* -17 code is reserved */ \
90
- \
88
+ BROTLI_ERROR_CODE(_ERROR_FORMAT_, BLOCK_SWITCH, -17) SEPARATOR \
91
89
  BROTLI_ERROR_CODE(_ERROR_, COMPOUND_DICTIONARY, -18) SEPARATOR \
92
90
  BROTLI_ERROR_CODE(_ERROR_, DICTIONARY_NOT_SET, -19) SEPARATOR \
93
91
  BROTLI_ERROR_CODE(_ERROR_, INVALID_ARGUMENTS, -20) SEPARATOR \
@@ -12,6 +12,9 @@ end
12
12
  module MultiCompress
13
13
  module ActiveRecordSupport
14
14
  class Type < ActiveModel::Type::Value
15
+ attr_reader :codec, :mutable
16
+ private :codec, :mutable
17
+
15
18
  def initialize(mutable: false, **codec_opts)
16
19
  @codec = MultiCompress::Codec.new(**codec_opts)
17
20
  @mutable = mutable
@@ -29,32 +32,35 @@ module MultiCompress
29
32
 
30
33
  # DB -> in-memory (unwrap)
31
34
  def deserialize(value)
32
- @codec.load(value)
35
+ codec.load(value)
33
36
  end
34
37
 
35
38
  # in-memory -> DB (compress)
36
39
  def serialize(value)
37
- @codec.dump(value)
40
+ codec.dump(value)
38
41
  end
39
42
 
40
43
  def changed_in_place?(raw_old_value, new_value)
41
- return false unless @mutable
44
+ return false unless mutable
42
45
 
43
46
  deserialize(raw_old_value) != new_value
44
47
  end
45
48
  end
46
49
 
47
50
  class Coder
51
+ attr_reader :codec
52
+ private :codec
53
+
48
54
  def initialize(**codec_opts)
49
55
  @codec = MultiCompress::Codec.new(**codec_opts)
50
56
  end
51
57
 
52
58
  def dump(value)
53
- @codec.dump(value)
59
+ codec.dump(value)
54
60
  end
55
61
 
56
62
  def load(value)
57
- @codec.load(value)
63
+ codec.load(value)
58
64
  end
59
65
  end
60
66
  end
@@ -4,51 +4,88 @@ require "multi_compress"
4
4
 
5
5
  module MultiCompress
6
6
  class Codec
7
- MAGIC = "\x89MCDB".b # reserved 5-byte namespace; 0x89 is an
8
- # invalid UTF-8 lead byte, so it won't
9
- # collide with valid text. For BINARY data
10
- # this prefix is reserved, not collision-free.
7
+ MAGIC = "\x89MCDB".b # reserved 5-byte namespace; 0x89 is an
8
+ # invalid UTF-8 lead byte, so it won't
9
+ # collide with valid text. For BINARY data
10
+ # this prefix is reserved, not collision-free.
11
11
  VERSION = 1
12
12
  HEADER_SIZE = MAGIC.bytesize + 2 # magic + version byte + algo byte
13
13
  B64_PREFIX = "mc1:"
14
- MAX_NATIVE_OUTPUT = (2**(0.size * 8 - 1)) - 1
14
+ MAX_NATIVE_OUTPUT = (2**((0.size * 8) - 1)) - 1
15
15
 
16
16
  ALGO_ID = { zstd: 1, lz4: 2, brotli: 3 }.freeze
17
17
  ID_ALGO = ALGO_ID.invert.freeze
18
18
 
19
- attr_reader :algo, :level, :max_output_size
19
+ Options = Struct.new(
20
+ :algo, :level, :encode, :serializer, :encoding, :max_output_size, :legacy, :dictionary,
21
+ keyword_init: true
22
+ )
20
23
 
21
- def initialize(algo: :zstd, level: nil, encode: nil, serializer: nil,
22
- encoding: Encoding::UTF_8, max_output_size: nil,
23
- legacy: :reject, dictionary: nil)
24
+ DEFAULT_OPTIONS = {
25
+ algo: :zstd, level: nil, encode: nil, serializer: nil, encoding: Encoding::UTF_8,
26
+ max_output_size: nil, legacy: :reject, dictionary: nil
27
+ }.freeze
28
+
29
+ private_constant :DEFAULT_OPTIONS, :Options
30
+
31
+ def initialize(**values)
32
+ resolved_options = DEFAULT_OPTIONS.merge(values)
33
+ @options = Options.new(**resolved_options)
34
+ validate_options!
35
+ end
36
+
37
+ def algo
38
+ @options.algo
39
+ end
40
+
41
+ def level
42
+ @options.level
43
+ end
44
+
45
+ def max_output_size
46
+ @options.max_output_size
47
+ end
48
+
49
+ private
50
+
51
+ def validate_options!
52
+ validate_algorithm!
53
+ validate_encoding!
54
+ @options.max_output_size = normalized_output_limit
55
+ @options.legacy = normalize_legacy(@options.legacy)
56
+ end
57
+
58
+ def validate_algorithm!
24
59
  raise ArgumentError, "unknown algo #{algo.inspect}" unless ALGO_ID.key?(algo)
25
- raise ArgumentError, "encode must be nil or :base64" unless [nil, :base64].include?(encode)
26
-
27
- @algo = algo
28
- @level = level
29
- @encode = encode
30
- @serializer = serializer
31
- @encoding = encoding
32
- limit = Integer(max_output_size || MultiCompress.config.max_output_size)
60
+ end
61
+
62
+ def validate_encoding!
63
+ return if [nil, :base64].include?(@options.encode)
64
+
65
+ raise ArgumentError, "encode must be nil or :base64"
66
+ end
67
+
68
+ def normalized_output_limit
69
+ limit = Integer(@options.max_output_size || MultiCompress.config.max_output_size)
33
70
  raise ArgumentError, "max_output_size must be greater than 0" unless limit.positive?
34
71
  if limit > MAX_NATIVE_OUTPUT
35
- raise ArgumentError,
36
- "max_output_size must be at most #{MAX_NATIVE_OUTPUT} bytes on this platform"
72
+ raise ArgumentError, "max_output_size must be at most #{MAX_NATIVE_OUTPUT} bytes on this platform"
37
73
  end
38
74
 
39
- @max_output_size = limit
40
- @legacy = normalize_legacy(legacy)
41
- @dictionary = dictionary
75
+ limit
42
76
  end
43
77
 
78
+ public
79
+
44
80
  def dump(value)
45
81
  return nil if value.nil?
46
82
 
47
83
  payload = serialize_payload(value)
48
- body = MultiCompress.compress(payload, algo: @algo, level: @level, dictionary: @dictionary)
84
+ body = MultiCompress.compress(payload, algo: @options.algo, level: @options.level,
85
+ dictionary: @options.dictionary)
49
86
  framed = header + body
50
87
 
51
- @encode == :base64 ? B64_PREFIX + [framed].pack("m0") : framed
88
+ @options.encode == :base64 ? B64_PREFIX + [framed].pack("m0") : framed
52
89
  end
53
90
 
54
91
  def load(stored)
@@ -60,105 +97,138 @@ module MultiCompress
60
97
  read_legacy(stored)
61
98
  end
62
99
 
63
- private
100
+ module Envelope
101
+ private
64
102
 
65
- def header
66
- (MAGIC + [VERSION, ALGO_ID.fetch(@algo)].pack("C2")).b
67
- end
103
+ def header
104
+ (MAGIC + [VERSION, ALGO_ID.fetch(@options.algo)].pack("C2")).b
105
+ end
68
106
 
69
- def decode_transport(stored)
70
- if @encode == :base64
71
- s = stored.to_s
72
- return nil unless s.start_with?(B64_PREFIX)
107
+ def decode_transport(stored)
108
+ if @options.encode == :base64
109
+ s = stored.to_s
110
+ return nil unless s.start_with?(B64_PREFIX)
73
111
 
74
- decoded = optional_base64(s.byteslice(B64_PREFIX.bytesize, s.bytesize - B64_PREFIX.bytesize))
75
- decoded && decoded.start_with?(MAGIC) ? decoded : nil
76
- else
77
- b = stored.b
78
- b.start_with?(MAGIC) ? b : nil
112
+ decoded = optional_base64(s.byteslice(B64_PREFIX.bytesize, s.bytesize - B64_PREFIX.bytesize))
113
+ decoded&.start_with?(MAGIC) ? decoded : nil
114
+ else
115
+ b = stored.b
116
+ b.start_with?(MAGIC) ? b : nil
117
+ end
79
118
  end
80
- end
81
119
 
82
- def unwrap(framed)
83
- if framed.bytesize < HEADER_SIZE
84
- raise MultiCompress::DataError, "MultiCompress::Codec: truncated envelope"
120
+ def unwrap(framed)
121
+ raise MultiCompress::DataError, "MultiCompress::Codec: truncated envelope" if framed.bytesize < HEADER_SIZE
122
+
123
+ validate_envelope_version!(framed)
124
+ algo = envelope_algorithm(framed)
125
+ body = envelope_body(framed)
126
+ MultiCompress.decompress(body, algo: algo, dictionary: @options.dictionary,
127
+ max_output_size: @options.max_output_size)
85
128
  end
86
129
 
87
- version = framed.getbyte(MAGIC.bytesize)
88
- algo_id = framed.getbyte(MAGIC.bytesize + 1)
89
- unless version == VERSION
130
+ def validate_envelope_version!(framed)
131
+ version = framed.getbyte(MAGIC.bytesize)
132
+ return if version == VERSION
133
+
90
134
  raise MultiCompress::DataError, "MultiCompress::Codec: unsupported envelope version #{version}"
91
135
  end
92
- algo = ID_ALGO[algo_id]
93
- raise MultiCompress::DataError, "MultiCompress::Codec: unknown algorithm id #{algo_id}" unless algo
94
136
 
95
- body = framed.byteslice(HEADER_SIZE, framed.bytesize - HEADER_SIZE) || +"".b
96
- MultiCompress.decompress(body, algo: algo, dictionary: @dictionary, max_output_size: @max_output_size)
97
- end
137
+ def envelope_algorithm(framed)
138
+ algo_id = framed.getbyte(MAGIC.bytesize + 1)
139
+ ID_ALGO.fetch(algo_id) do
140
+ raise MultiCompress::DataError, "MultiCompress::Codec: unknown algorithm id #{algo_id}"
141
+ end
142
+ end
98
143
 
99
- def read_legacy(stored)
100
- case @legacy[:mode]
101
- when :reject
102
- raise MultiCompress::DataError,
103
- "MultiCompress::Codec: value has no envelope and legacy: :reject is set"
104
- when :plain
105
- decode_payload(stored.b.dup)
106
- when :compressed
144
+ def envelope_body(framed)
145
+ framed.byteslice(HEADER_SIZE, framed.bytesize - HEADER_SIZE) || +"".b
146
+ end
147
+
148
+ def read_legacy(stored)
149
+ case @options.legacy[:mode]
150
+ when :reject
151
+ raise MultiCompress::DataError,
152
+ "MultiCompress::Codec: value has no envelope and legacy: :reject is set"
153
+ when :plain
154
+ decode_payload(stored.b.dup)
155
+ when :compressed
156
+ decode_legacy_compressed(stored)
157
+ end
158
+ end
159
+
160
+ def decode_legacy_compressed(stored)
107
161
  payload = MultiCompress.decompress(
108
- stored.b, algo: @legacy[:algo], dictionary: @dictionary, max_output_size: @max_output_size
162
+ stored.b,
163
+ algo: @options.legacy[:algo],
164
+ dictionary: @options.dictionary,
165
+ max_output_size: @options.max_output_size
109
166
  )
110
167
  decode_payload(payload)
111
168
  end
112
- end
113
169
 
114
- def normalize_legacy(legacy)
115
- case legacy
116
- when :reject, :plain
117
- { mode: legacy }
118
- when Hash
119
- algo = legacy[:compressed]
120
- raise ArgumentError, "legacy: { compressed: <algo> } required" unless ALGO_ID.key?(algo)
121
-
122
- { mode: :compressed, algo: algo }
123
- else
124
- raise ArgumentError, "legacy: must be :reject, :plain, or { compressed: :algo }"
170
+ def normalize_legacy(legacy)
171
+ case legacy
172
+ when :reject, :plain
173
+ { mode: legacy }
174
+ when Hash
175
+ algo = legacy[:compressed]
176
+ raise ArgumentError, "legacy: { compressed: <algo> } required" unless ALGO_ID.key?(algo)
177
+
178
+ { mode: :compressed, algo: algo }
179
+ else
180
+ raise ArgumentError, "legacy: must be :reject, :plain, or { compressed: :algo }"
181
+ end
125
182
  end
126
183
  end
127
184
 
128
- def serialize_payload(value)
129
- if @serializer
130
- @serializer.dump(value).b
131
- else
185
+ module Payload
186
+ private
187
+
188
+ def serialize_payload(value)
189
+ if @options.serializer
190
+ @options.serializer.dump(value).b
191
+ else
192
+ serialize_string(value)
193
+ end
194
+ end
195
+
196
+ def serialize_string(value)
132
197
  unless value.is_a?(String)
133
198
  raise TypeError, "MultiCompress::Codec expects a String (or pass serializer:); got #{value.class}"
134
199
  end
135
-
136
- unless @encoding == Encoding::BINARY || value.dup.force_encoding(@encoding).valid_encoding?
137
- raise ArgumentError, "MultiCompress::Codec: value is not valid #{@encoding} " \
138
- "(use encoding: Encoding::BINARY for arbitrary bytes)"
200
+ if @options.encoding == Encoding::BINARY || value.dup.force_encoding(@options.encoding).valid_encoding?
201
+ return value.b
139
202
  end
140
- value.b
203
+
204
+ raise ArgumentError, "MultiCompress::Codec: value is not valid #{@options.encoding} " \
205
+ "(use encoding: Encoding::BINARY for arbitrary bytes)"
141
206
  end
142
- end
143
207
 
144
- def decode_payload(bytes)
145
- return @serializer.load(bytes) if @serializer
208
+ def decode_payload(bytes)
209
+ return @options.serializer.load(bytes) if @options.serializer
146
210
 
147
- finalize_string(bytes)
148
- end
211
+ finalize_string(bytes)
212
+ end
213
+
214
+ def finalize_string(bytes)
215
+ s = bytes.dup.force_encoding(@options.encoding)
216
+ unless @options.encoding == Encoding::BINARY || s.valid_encoding?
217
+ raise MultiCompress::DataError, "MultiCompress::Codec: decoded bytes are not valid #{@options.encoding}"
218
+ end
149
219
 
150
- def finalize_string(bytes)
151
- s = bytes.dup.force_encoding(@encoding)
152
- unless @encoding == Encoding::BINARY || s.valid_encoding?
153
- raise MultiCompress::DataError, "MultiCompress::Codec: decoded bytes are not valid #{@encoding}"
220
+ s
154
221
  end
155
- s
156
- end
157
222
 
158
- def optional_base64(str)
159
- str.unpack1("m0")&.b
160
- rescue ArgumentError
161
- nil
223
+ def optional_base64(str)
224
+ str.unpack1("m0")&.b
225
+ rescue ArgumentError
226
+ nil
227
+ end
162
228
  end
229
+
230
+ private_constant :Envelope, :Payload
231
+ include Envelope
232
+ include Payload
163
233
  end
164
234
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MultiCompress
4
+ module Database
5
+ ENGINE = Engine.new
6
+ private_constant :ENGINE
7
+
8
+ module_function
9
+
10
+ def compress(text, dictionary: nil)
11
+ ENGINE.compress(text, dictionary: dictionary)
12
+ end
13
+
14
+ def compress_v1(text)
15
+ ENGINE.compress_v1(text)
16
+ end
17
+
18
+ def compress_v2(text, dictionary:)
19
+ ENGINE.compress_v2(text, dictionary: dictionary)
20
+ end
21
+
22
+ def decompress(blob, dictionary: nil)
23
+ ENGINE.decompress(blob, dictionary: dictionary)
24
+ end
25
+
26
+ def valid?(blob, dictionary: nil)
27
+ ENGINE.valid?(blob, dictionary: dictionary)
28
+ end
29
+
30
+ def dictionary_ref(blob)
31
+ ENGINE.dictionary_ref(blob)
32
+ end
33
+
34
+ def original_size(blob)
35
+ ENGINE.original_size(blob)
36
+ end
37
+
38
+ def version_string
39
+ ENGINE.version_string
40
+ end
41
+ end
42
+ end