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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/GET_STARTED.md +1 -1
- data/README.md +1 -1
- data/THIRD_PARTY_NOTICES.md +1 -1
- data/ext/multi_compress/multi_compress.c +230 -77
- data/ext/multi_compress/vendor/.vendored +1 -1
- data/ext/multi_compress/vendor/brotli/c/common/version.h +2 -2
- data/ext/multi_compress/vendor/brotli/c/dec/decode.c +59 -30
- data/ext/multi_compress/vendor/brotli/c/include/brotli/decode.h +1 -3
- data/lib/multi_compress/active_record.rb +11 -5
- data/lib/multi_compress/codec.rb +164 -94
- data/lib/multi_compress/database/api.rb +42 -0
- data/lib/multi_compress/database/decoding.rb +42 -0
- data/lib/multi_compress/database/dictionary.rb +72 -0
- data/lib/multi_compress/database/encoding.rb +31 -0
- data/lib/multi_compress/database/header.rb +71 -0
- data/lib/multi_compress/database/operations.rb +73 -0
- data/lib/multi_compress/database/validation.rb +95 -0
- data/lib/multi_compress/database.rb +10 -282
- data/lib/multi_compress/db_deployment/bundle_archiver.rb +70 -0
- data/lib/multi_compress/db_deployment/bundle_metadata.rb +56 -0
- data/lib/multi_compress/db_deployment/bundle_stager.rb +62 -0
- data/lib/multi_compress/db_deployment/commands.rb +309 -0
- data/lib/multi_compress/db_deployment/deployment_bundle.rb +80 -0
- data/lib/multi_compress/db_deployment/deterministic_tar_writer.rb +76 -0
- data/lib/multi_compress/db_deployment/dictionary_registry.rb +244 -0
- data/lib/multi_compress/db_deployment/readable_view.rb +112 -0
- data/lib/multi_compress/db_deployment/readable_view_options.rb +51 -0
- data/lib/multi_compress/db_deployment/sql_identifiers.rb +45 -0
- data/lib/multi_compress/db_deployment.rb +10 -844
- data/lib/multi_compress/reader/buffer_operations.rb +57 -0
- data/lib/multi_compress/reader/read_operations.rb +67 -0
- data/lib/multi_compress/reader.rb +97 -0
- data/lib/multi_compress/stream_io.rb +13 -0
- data/lib/multi_compress/version.rb +1 -1
- data/lib/multi_compress/writer.rb +85 -0
- data/lib/multi_compress.rb +42 -333
- metadata +25 -6
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiCompress
|
|
4
|
+
class Reader
|
|
5
|
+
BUFFER_COMPACT_THRESHOLD = 64 * 1024
|
|
6
|
+
|
|
7
|
+
module BufferOperations
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def buffer_size
|
|
11
|
+
@buffer.bytesize - @buffer_pos
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def buffer_empty?
|
|
15
|
+
@buffer_pos >= @buffer.bytesize
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def buffer_append(data)
|
|
19
|
+
compact_buffer_if_needed
|
|
20
|
+
@buffer << data
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def compact_buffer_if_needed
|
|
24
|
+
return if @buffer_pos.zero?
|
|
25
|
+
|
|
26
|
+
total = @buffer.bytesize
|
|
27
|
+
return unless @buffer_pos >= BUFFER_COMPACT_THRESHOLD && @buffer_pos * 2 >= total
|
|
28
|
+
|
|
29
|
+
@buffer = @buffer.byteslice(@buffer_pos, total - @buffer_pos)
|
|
30
|
+
@buffer_pos = 0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def buffer_includes?(separator)
|
|
34
|
+
idx = @buffer.index(separator, @buffer_pos)
|
|
35
|
+
!idx.nil?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def extract_line(separator)
|
|
39
|
+
idx = @buffer.index(separator, @buffer_pos)
|
|
40
|
+
end_pos = idx + separator.bytesize
|
|
41
|
+
result = @buffer.byteslice(@buffer_pos, end_pos - @buffer_pos)
|
|
42
|
+
@buffer_pos = end_pos
|
|
43
|
+
compact_buffer_if_needed
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def consume_buffer
|
|
48
|
+
result = @buffer.byteslice(@buffer_pos, @buffer.bytesize - @buffer_pos) || +"".b
|
|
49
|
+
@buffer.clear
|
|
50
|
+
@buffer_pos = 0
|
|
51
|
+
result
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private_constant :BufferOperations
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiCompress
|
|
4
|
+
class Reader
|
|
5
|
+
module ReadOperations
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def read_all
|
|
9
|
+
return nil if @eof && buffer_empty?
|
|
10
|
+
|
|
11
|
+
result = consume_buffer
|
|
12
|
+
result = +"".b if result.empty?
|
|
13
|
+
append_remaining_stream(result)
|
|
14
|
+
result.empty? ? nil : result
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def append_remaining_stream(result)
|
|
18
|
+
until @eof
|
|
19
|
+
chunk = read_compressed_chunk
|
|
20
|
+
break unless chunk && !chunk.empty?
|
|
21
|
+
|
|
22
|
+
decompressed = @inflater.write(chunk)
|
|
23
|
+
result << decompressed if decompressed
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
final = @inflater.finish
|
|
27
|
+
result << final if final && !final.empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def read_exactly(length)
|
|
31
|
+
return nil if @eof && buffer_empty?
|
|
32
|
+
|
|
33
|
+
fill_buffer_until { buffer_size >= length }
|
|
34
|
+
return read_buffer_slice(length) if buffer_size >= length
|
|
35
|
+
|
|
36
|
+
consume_buffer unless buffer_empty?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def read_buffer_slice(length)
|
|
40
|
+
result = @buffer.byteslice(@buffer_pos, length)
|
|
41
|
+
@buffer_pos += length
|
|
42
|
+
compact_buffer_if_needed
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def fill_buffer_until
|
|
47
|
+
until yield || @eof
|
|
48
|
+
chunk = read_compressed_chunk
|
|
49
|
+
if chunk.nil? || chunk.empty?
|
|
50
|
+
@eof = true
|
|
51
|
+
break
|
|
52
|
+
end
|
|
53
|
+
decompressed = @inflater.write(chunk)
|
|
54
|
+
buffer_append(decompressed) if decompressed
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def read_compressed_chunk
|
|
59
|
+
@io.read(CHUNK_SIZE)
|
|
60
|
+
rescue EOFError
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private_constant :ReadOperations
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiCompress
|
|
4
|
+
class Reader
|
|
5
|
+
CHUNK_SIZE = 8192
|
|
6
|
+
require "multi_compress/reader/buffer_operations"
|
|
7
|
+
require "multi_compress/reader/read_operations"
|
|
8
|
+
include BufferOperations
|
|
9
|
+
include ReadOperations
|
|
10
|
+
|
|
11
|
+
def self.open(path_or_io, algo: nil, dictionary: nil, **opts, &block)
|
|
12
|
+
io, algo, owned = StreamIO.resolve_io(path_or_io, algo, mode: "rb")
|
|
13
|
+
reader = new(io, algo: algo, dictionary: dictionary, **opts)
|
|
14
|
+
reader.instance_variable_set(:@owned_io, owned)
|
|
15
|
+
|
|
16
|
+
return reader unless block
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
yield reader
|
|
20
|
+
ensure
|
|
21
|
+
reader.close
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(io, algo: nil, dictionary: nil, **opts)
|
|
26
|
+
@io = io
|
|
27
|
+
@inflater = Inflater.new(algo: algo, dictionary: dictionary, **opts)
|
|
28
|
+
@closed = false
|
|
29
|
+
@owned_io = false
|
|
30
|
+
@buffer = +"".b
|
|
31
|
+
@buffer_pos = 0
|
|
32
|
+
@eof = false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def read(length = nil)
|
|
36
|
+
ensure_open!
|
|
37
|
+
return read_all if length.nil?
|
|
38
|
+
|
|
39
|
+
read_exactly(length)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def gets(separator = "\n")
|
|
43
|
+
ensure_open!
|
|
44
|
+
return nil if @eof && buffer_empty?
|
|
45
|
+
|
|
46
|
+
fill_buffer_until { buffer_includes?(separator) }
|
|
47
|
+
|
|
48
|
+
return extract_line(separator) if buffer_includes?(separator)
|
|
49
|
+
return consume_buffer unless buffer_empty?
|
|
50
|
+
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def close
|
|
55
|
+
return if @closed
|
|
56
|
+
|
|
57
|
+
@inflater.close
|
|
58
|
+
@io.close if @owned_io && @io.respond_to?(:close)
|
|
59
|
+
@closed = true
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def closed?
|
|
64
|
+
@closed
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def eof?
|
|
68
|
+
@eof && buffer_empty?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def each_line
|
|
72
|
+
return enum_for(:each_line) unless block_given?
|
|
73
|
+
|
|
74
|
+
while (line = gets)
|
|
75
|
+
yield line
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def each_chunk(size)
|
|
80
|
+
return enum_for(:each_chunk, size) unless block_given?
|
|
81
|
+
|
|
82
|
+
while (chunk = read(size))
|
|
83
|
+
yield chunk
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def readline
|
|
88
|
+
gets || raise(EOFError, "end of file reached")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def ensure_open!
|
|
94
|
+
raise StreamError, "reader is closed" if @closed
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiCompress
|
|
4
|
+
module StreamIO
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def resolve_io(path_or_io, algo, mode:)
|
|
8
|
+
return [path_or_io, algo, false] unless path_or_io.is_a?(String)
|
|
9
|
+
|
|
10
|
+
[File.open(path_or_io, mode), algo || MultiCompress.algo_from_ext(path_or_io), true]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiCompress
|
|
4
|
+
class Writer
|
|
5
|
+
CHUNK_BUFFER_SIZE = 8192
|
|
6
|
+
|
|
7
|
+
def self.open(path_or_io, algo: nil, level: nil, dictionary: nil, &block)
|
|
8
|
+
io, algo, owned = StreamIO.resolve_io(path_or_io, algo, mode: "wb")
|
|
9
|
+
writer = new(io, algo: algo || :zstd, level: level, dictionary: dictionary)
|
|
10
|
+
writer.instance_variable_set(:@owned_io, owned)
|
|
11
|
+
|
|
12
|
+
return writer unless block
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
yield writer
|
|
16
|
+
ensure
|
|
17
|
+
writer.close
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(io, algo: :zstd, level: nil, dictionary: nil)
|
|
22
|
+
@io = io
|
|
23
|
+
@deflater = Deflater.new(algo: algo, level: level, dictionary: dictionary)
|
|
24
|
+
@closed = false
|
|
25
|
+
@owned_io = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def write(data)
|
|
29
|
+
ensure_open!
|
|
30
|
+
bytes = data.to_s
|
|
31
|
+
flush_compressed(@deflater.write(bytes))
|
|
32
|
+
bytes.bytesize
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def flush
|
|
36
|
+
ensure_open!
|
|
37
|
+
flush_compressed(@deflater.flush)
|
|
38
|
+
@io.flush if @io.respond_to?(:flush)
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def close
|
|
43
|
+
return if @closed
|
|
44
|
+
|
|
45
|
+
flush_compressed(@deflater.finish)
|
|
46
|
+
@deflater.close
|
|
47
|
+
@io.close if @owned_io && @io.respond_to?(:close)
|
|
48
|
+
@closed = true
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def closed?
|
|
53
|
+
@closed
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def <<(data)
|
|
57
|
+
write(data)
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def puts(*args)
|
|
62
|
+
args.flatten.each do |arg|
|
|
63
|
+
str = arg.to_s
|
|
64
|
+
write(str)
|
|
65
|
+
write("\n") unless str.end_with?("\n")
|
|
66
|
+
end
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def print(*args)
|
|
71
|
+
args.each { |arg| write(arg.to_s) }
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def ensure_open!
|
|
78
|
+
raise StreamError, "writer is closed" if @closed
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def flush_compressed(compressed)
|
|
82
|
+
@io.write(compressed) if compressed && !compressed.empty?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|