multi_compress 0.6.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/GET_STARTED.md +1 -1
  4. data/README.md +1 -1
  5. data/THIRD_PARTY_NOTICES.md +1 -1
  6. data/ext/multi_compress/multi_compress.c +230 -77
  7. data/ext/multi_compress/vendor/.vendored +1 -1
  8. data/ext/multi_compress/vendor/brotli/c/common/version.h +2 -2
  9. data/ext/multi_compress/vendor/brotli/c/dec/decode.c +59 -30
  10. data/ext/multi_compress/vendor/brotli/c/include/brotli/decode.h +1 -3
  11. data/lib/multi_compress/active_record.rb +11 -5
  12. data/lib/multi_compress/codec.rb +164 -94
  13. data/lib/multi_compress/database/api.rb +42 -0
  14. data/lib/multi_compress/database/decoding.rb +42 -0
  15. data/lib/multi_compress/database/dictionary.rb +72 -0
  16. data/lib/multi_compress/database/encoding.rb +31 -0
  17. data/lib/multi_compress/database/header.rb +71 -0
  18. data/lib/multi_compress/database/operations.rb +73 -0
  19. data/lib/multi_compress/database/validation.rb +95 -0
  20. data/lib/multi_compress/database.rb +10 -282
  21. data/lib/multi_compress/db_deployment/bundle_archiver.rb +70 -0
  22. data/lib/multi_compress/db_deployment/bundle_metadata.rb +56 -0
  23. data/lib/multi_compress/db_deployment/bundle_stager.rb +62 -0
  24. data/lib/multi_compress/db_deployment/commands.rb +309 -0
  25. data/lib/multi_compress/db_deployment/deployment_bundle.rb +80 -0
  26. data/lib/multi_compress/db_deployment/deterministic_tar_writer.rb +76 -0
  27. data/lib/multi_compress/db_deployment/dictionary_registry.rb +244 -0
  28. data/lib/multi_compress/db_deployment/readable_view.rb +112 -0
  29. data/lib/multi_compress/db_deployment/readable_view_options.rb +51 -0
  30. data/lib/multi_compress/db_deployment/sql_identifiers.rb +45 -0
  31. data/lib/multi_compress/db_deployment.rb +10 -844
  32. data/lib/multi_compress/reader/buffer_operations.rb +57 -0
  33. data/lib/multi_compress/reader/read_operations.rb +67 -0
  34. data/lib/multi_compress/reader.rb +97 -0
  35. data/lib/multi_compress/stream_io.rb +13 -0
  36. data/lib/multi_compress/version.rb +1 -1
  37. data/lib/multi_compress/writer.rb +85 -0
  38. data/lib/multi_compress.rb +42 -333
  39. metadata +25 -6
@@ -5,11 +5,11 @@ require "multi_compress/version"
5
5
  begin
6
6
  require "multi_compress/multi_compress"
7
7
  rescue LoadError
8
- ext_dir = File.expand_path("../multi_compress", __FILE__)
8
+ ext_dir = File.expand_path("multi_compress", __dir__)
9
9
 
10
10
  so_path = %w[.bundle .so]
11
- .map { |ext| File.join(ext_dir, "multi_compress#{ext}") }
12
- .find { |path| File.exist?(path) }
11
+ .map { |ext| File.join(ext_dir, "multi_compress#{ext}") }
12
+ .find { |path| File.exist?(path) }
13
13
 
14
14
  raise LoadError, "Could not find compiled MultiCompress extension. Run: bundle exec rake compile" unless so_path
15
15
 
@@ -25,10 +25,10 @@ module MultiCompress
25
25
  DEFAULT_STREAMING_MAX_OUTPUT_SIZE = 2 * 1024 * 1024 * 1024
26
26
 
27
27
  EXTENSION_MAP = {
28
- ".zst" => :zstd,
28
+ ".zst" => :zstd,
29
29
  ".zstd" => :zstd,
30
- ".lz4" => :lz4,
31
- ".br" => :brotli,
30
+ ".lz4" => :lz4,
31
+ ".br" => :brotli
32
32
  }.freeze
33
33
 
34
34
  private_constant :EXTENSION_MAP
@@ -91,361 +91,70 @@ module MultiCompress
91
91
  end
92
92
 
93
93
  unless private_method_defined?(:_c_decompress)
94
- alias_method :_c_decompress, :decompress
94
+ alias _c_decompress decompress
95
95
  private :_c_decompress
96
96
  end
97
- end
98
-
99
- def self.zstd(data, level: nil)
100
- compress(data, algo: :zstd, level: level)
101
- end
102
-
103
- def self.lz4(data, level: nil, format: nil)
104
- if format
105
- compress(data, algo: :lz4, level: level, format: format)
106
- else
107
- compress(data, algo: :lz4, level: level)
108
- end
109
- end
110
-
111
- def self.brotli(data, level: nil)
112
- compress(data, algo: :brotli, level: level)
113
- end
114
-
115
- def self.decompress(data, **opts)
116
- _c_decompress(data, **resolved_one_shot_options(opts))
117
- end
118
-
119
- def self.zstd_decompress(data)
120
- decompress(data, algo: :zstd)
121
- end
122
-
123
- def self.lz4_decompress(data, format: nil)
124
- if format
125
- decompress(data, algo: :lz4, format: format)
126
- else
127
- decompress(data, algo: :lz4)
128
- end
129
- end
130
-
131
- def self.brotli_decompress(data)
132
- decompress(data, algo: :brotli)
133
- end
134
-
135
- def self.algo_from_ext(path)
136
- EXTENSION_MAP[File.extname(path).downcase]
137
- end
138
-
139
- def self.resolved_one_shot_options(opts)
140
- return opts.merge(max_output_size: config.max_output_size) unless opts.key?(:max_output_size)
141
-
142
- opts
143
- end
144
-
145
- private_class_method :resolved_one_shot_options
146
-
147
- module InflaterDefaults
148
- def initialize(*args, **opts)
149
- resolved = opts.dup
150
- resolved[:max_output_size] = MultiCompress.config.streaming_max_output_size unless resolved.key?(:max_output_size)
151
- super(*args, **resolved)
152
- end
153
- end
154
-
155
- Inflater.prepend(InflaterDefaults)
156
97
 
157
- class Writer
158
- CHUNK_BUFFER_SIZE = 8192
159
-
160
- def self.open(path_or_io, algo: nil, level: nil, dictionary: nil, &block)
161
- io, algo, owned = resolve_io(path_or_io, algo, mode: "wb")
162
- writer = new(io, algo: algo || :zstd, level: level, dictionary: dictionary)
163
- writer.instance_variable_set(:@owned_io, owned)
164
-
165
- return writer unless block
166
-
167
- begin
168
- yield writer
169
- ensure
170
- writer.close
171
- end
98
+ def zstd(data, level: nil)
99
+ compress(data, algo: :zstd, level: level)
172
100
  end
173
101
 
174
- def initialize(io, algo: :zstd, level: nil, dictionary: nil)
175
- @io = io
176
- @deflater = Deflater.new(algo: algo, level: level, dictionary: dictionary)
177
- @closed = false
178
- @owned_io = false
179
- end
180
-
181
- def write(data)
182
- ensure_open!
183
- bytes = data.to_s
184
- flush_compressed(@deflater.write(bytes))
185
- bytes.bytesize
186
- end
187
-
188
- def flush
189
- ensure_open!
190
- flush_compressed(@deflater.flush)
191
- @io.flush if @io.respond_to?(:flush)
192
- self
193
- end
194
-
195
- def close
196
- return if @closed
197
-
198
- flush_compressed(@deflater.finish)
199
- @deflater.close
200
- @io.close if @owned_io && @io.respond_to?(:close)
201
- @closed = true
202
- nil
203
- end
204
-
205
- def closed?
206
- @closed
207
- end
208
-
209
- def <<(data)
210
- write(data)
211
- self
212
- end
213
-
214
- def puts(*args)
215
- args.flatten.each do |arg|
216
- str = arg.to_s
217
- write(str)
218
- write("\n") unless str.end_with?("\n")
219
- end
220
- nil
221
- end
222
-
223
- def print(*args)
224
- args.each { |arg| write(arg.to_s) }
225
- nil
226
- end
227
-
228
- private
229
-
230
- def ensure_open!
231
- raise StreamError, "writer is closed" if @closed
232
- end
233
-
234
- def flush_compressed(compressed)
235
- @io.write(compressed) if compressed && !compressed.empty?
236
- end
237
-
238
- def self.resolve_io(path_or_io, algo, mode:)
239
- if path_or_io.is_a?(String)
240
- [File.open(path_or_io, mode), algo || MultiCompress.algo_from_ext(path_or_io), true]
102
+ def lz4(data, level: nil, format: nil)
103
+ if format
104
+ compress(data, algo: :lz4, level: level, format: format)
241
105
  else
242
- [path_or_io, algo, false]
106
+ compress(data, algo: :lz4, level: level)
243
107
  end
244
108
  end
245
109
 
246
- private_class_method :resolve_io
247
- end
248
-
249
- class Reader
250
- CHUNK_SIZE = 8192
251
- BUFFER_COMPACT_THRESHOLD = 64 * 1024
252
-
253
- def self.open(path_or_io, algo: nil, dictionary: nil, **opts, &block)
254
- io, algo, owned = resolve_io(path_or_io, algo, mode: "rb")
255
- reader = new(io, algo: algo, dictionary: dictionary, **opts)
256
- reader.instance_variable_set(:@owned_io, owned)
257
-
258
- return reader unless block
259
-
260
- begin
261
- yield reader
262
- ensure
263
- reader.close
264
- end
110
+ def brotli(data, level: nil)
111
+ compress(data, algo: :brotli, level: level)
265
112
  end
266
113
 
267
- def initialize(io, algo: nil, dictionary: nil, **opts)
268
- @io = io
269
- @inflater = Inflater.new(algo: algo, dictionary: dictionary, **opts)
270
- @closed = false
271
- @owned_io = false
272
- @buffer = +"".b
273
- @buffer_pos = 0
274
- @eof = false
114
+ def decompress(data, **opts)
115
+ _c_decompress(data, **resolved_one_shot_options(opts))
275
116
  end
276
117
 
277
- def read(length = nil)
278
- ensure_open!
279
- return read_all if length.nil?
280
-
281
- read_exactly(length)
282
- end
283
-
284
- def gets(separator = "\n")
285
- ensure_open!
286
- return nil if @eof && buffer_empty?
287
-
288
- fill_buffer_until { buffer_includes?(separator) }
289
-
290
- return extract_line(separator) if buffer_includes?(separator)
291
- return consume_buffer unless buffer_empty?
292
-
293
- nil
118
+ def zstd_decompress(data)
119
+ decompress(data, algo: :zstd)
294
120
  end
295
121
 
296
- def close
297
- return if @closed
298
-
299
- @inflater.close
300
- @io.close if @owned_io && @io.respond_to?(:close)
301
- @closed = true
302
- nil
303
- end
304
-
305
- def closed?
306
- @closed
307
- end
308
-
309
- def eof?
310
- @eof && buffer_empty?
311
- end
312
-
313
- def each_line
314
- return enum_for(:each_line) unless block_given?
315
-
316
- while (line = gets)
317
- yield line
122
+ def lz4_decompress(data, format: nil)
123
+ if format
124
+ decompress(data, algo: :lz4, format: format)
125
+ else
126
+ decompress(data, algo: :lz4)
318
127
  end
319
128
  end
320
129
 
321
- def each_chunk(size)
322
- return enum_for(:each_chunk, size) unless block_given?
323
-
324
- while (chunk = read(size))
325
- yield chunk
326
- end
130
+ def brotli_decompress(data)
131
+ decompress(data, algo: :brotli)
327
132
  end
328
133
 
329
- def readline
330
- gets || raise(EOFError, "end of file reached")
134
+ def algo_from_ext(path)
135
+ EXTENSION_MAP[File.extname(path).downcase]
331
136
  end
332
137
 
333
138
  private
334
139
 
335
- def ensure_open!
336
- raise StreamError, "reader is closed" if @closed
337
- end
338
-
339
- def buffer_size
340
- @buffer.bytesize - @buffer_pos
341
- end
342
-
343
- def buffer_empty?
344
- @buffer_pos >= @buffer.bytesize
345
- end
346
-
347
- def buffer_append(data)
348
- compact_buffer_if_needed
349
- @buffer << data
350
- end
351
-
352
- def compact_buffer_if_needed
353
- return if @buffer_pos == 0
354
-
355
- total = @buffer.bytesize
356
- return unless @buffer_pos >= BUFFER_COMPACT_THRESHOLD && @buffer_pos * 2 >= total
140
+ def resolved_one_shot_options(opts)
141
+ return opts.merge(max_output_size: config.max_output_size) unless opts.key?(:max_output_size)
357
142
 
358
- @buffer = @buffer.byteslice(@buffer_pos, total - @buffer_pos)
359
- @buffer_pos = 0
360
- end
361
-
362
- def buffer_includes?(separator)
363
- idx = @buffer.index(separator, @buffer_pos)
364
- !idx.nil?
365
- end
366
-
367
- def read_all
368
- return nil if @eof && buffer_empty?
369
-
370
- result = if buffer_empty?
371
- +"".b
372
- else
373
- @buffer.byteslice(@buffer_pos, @buffer.bytesize - @buffer_pos) || +"".b
374
- end
375
- @buffer.clear
376
- @buffer_pos = 0
377
-
378
- until @eof
379
- chunk = read_compressed_chunk
380
- break unless chunk && !chunk.empty?
381
-
382
- decompressed = @inflater.write(chunk)
383
- result << decompressed if decompressed
384
- end
385
-
386
- final = @inflater.finish
387
- result << final if final && !final.empty?
388
-
389
- result.empty? ? nil : result
390
- end
391
-
392
- def read_exactly(length)
393
- return nil if @eof && buffer_empty?
394
-
395
- fill_buffer_until { buffer_size >= length }
396
-
397
- if buffer_size >= length
398
- result = @buffer.byteslice(@buffer_pos, length)
399
- @buffer_pos += length
400
- compact_buffer_if_needed
401
- result
402
- elsif !buffer_empty?
403
- consume_buffer
404
- end
405
- end
406
-
407
- def fill_buffer_until
408
- until yield || @eof
409
- chunk = read_compressed_chunk
410
- if chunk.nil? || chunk.empty?
411
- @eof = true
412
- break
413
- end
414
- decompressed = @inflater.write(chunk)
415
- buffer_append(decompressed) if decompressed
416
- end
417
- end
418
-
419
- def extract_line(separator)
420
- idx = @buffer.index(separator, @buffer_pos)
421
- end_pos = idx + separator.bytesize
422
- result = @buffer.byteslice(@buffer_pos, end_pos - @buffer_pos)
423
- @buffer_pos = end_pos
424
- compact_buffer_if_needed
425
- result
426
- end
427
-
428
- def consume_buffer
429
- result = @buffer.byteslice(@buffer_pos, @buffer.bytesize - @buffer_pos) || +"".b
430
- @buffer.clear
431
- @buffer_pos = 0
432
- result
143
+ opts
433
144
  end
145
+ end
434
146
 
435
- def read_compressed_chunk
436
- @io.read(CHUNK_SIZE)
437
- rescue EOFError
438
- nil
147
+ module InflaterDefaults
148
+ def initialize(*args, **opts)
149
+ resolved = opts.dup
150
+ resolved[:max_output_size] = MultiCompress.config.streaming_max_output_size unless resolved.key?(:max_output_size)
151
+ super(*args, **resolved)
439
152
  end
153
+ end
440
154
 
441
- def self.resolve_io(path_or_io, algo, mode:)
442
- if path_or_io.is_a?(String)
443
- [File.open(path_or_io, mode), algo || MultiCompress.algo_from_ext(path_or_io), true]
444
- else
445
- [path_or_io, algo, false]
446
- end
447
- end
155
+ Inflater.prepend(InflaterDefaults)
448
156
 
449
- private_class_method :resolve_io
450
- end
157
+ require "multi_compress/stream_io"
158
+ require "multi_compress/writer"
159
+ require "multi_compress/reader"
451
160
  end
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.6.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-06 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
@@ -348,8 +347,30 @@ files:
348
347
  - lib/multi_compress/active_record.rb
349
348
  - lib/multi_compress/codec.rb
350
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
351
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
352
372
  - lib/multi_compress/version.rb
373
+ - lib/multi_compress/writer.rb
353
374
  - multi_compress.gemspec
354
375
  - mysql_udf/Makefile
355
376
  - mysql_udf/README.md
@@ -381,7 +402,6 @@ metadata:
381
402
  homepage_uri: https://github.com/roman-haidarov/multi_compress
382
403
  source_code_uri: https://github.com/roman-haidarov/multi_compress
383
404
  changelog_uri: https://github.com/roman-haidarov/multi_compress/blob/main/CHANGELOG.md
384
- post_install_message:
385
405
  rdoc_options: []
386
406
  require_paths:
387
407
  - lib
@@ -396,8 +416,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
396
416
  - !ruby/object:Gem::Version
397
417
  version: '0'
398
418
  requirements: []
399
- rubygems_version: 3.4.22
400
- signing_key:
419
+ rubygems_version: 3.6.7
401
420
  specification_version: 4
402
421
  summary: 'Modern fiber-friendly compression for Ruby: zstd, lz4, brotli in one gem'
403
422
  test_files: []