herringbone 0.1.0 → 0.2.0
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/README.md +146 -74
- data/bin/herringbone +38 -26
- data/lib/herringbone/active_record.rb +10 -13
- data/lib/herringbone/bloom_filter.rb +270 -0
- data/lib/herringbone/compression.rb +73 -16
- data/lib/herringbone/encodings/delta.rb +7 -7
- data/lib/herringbone/encodings/plain.rb +7 -7
- data/lib/herringbone/encodings/rle.rb +2 -2
- data/lib/herringbone/format.rb +53 -0
- data/lib/herringbone/inspector.rb +1388 -0
- data/lib/herringbone/reader/column_chunk_reader.rb +296 -0
- data/lib/herringbone/reader/column_cursor.rb +181 -0
- data/lib/herringbone/reader/page_stream.rb +339 -0
- data/lib/herringbone/reader/scan.rb +256 -0
- data/lib/herringbone/reader.rb +294 -272
- data/lib/herringbone/schema.rb +21 -76
- data/lib/herringbone/types.rb +4 -4
- data/lib/herringbone/version.rb +1 -1
- data/lib/herringbone/visualizer.rb +1096 -0
- data/lib/herringbone/writer.rb +258 -90
- data/lib/herringbone/xxhash.rb +319 -0
- data/lib/herringbone.rb +36 -9
- metadata +12 -32
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Herringbone
|
|
4
|
+
# XXH64 (seed 0), the hash Parquet bloom filters use.
|
|
5
|
+
#
|
|
6
|
+
# When the optional "xxhash" gem (a C extension) can be loaded it is used, which is 20-40x
|
|
7
|
+
# faster; otherwise hashing is pure Ruby. The gem is only a speedup, so nothing fails without
|
|
8
|
+
# it. +XXHash.backend = :ruby+ forces pure Ruby (for tests and benchmarks).
|
|
9
|
+
#
|
|
10
|
+
# The pure-Ruby version keeps every 64-bit value as two 32-bit halves, and multiplies by the
|
|
11
|
+
# XXH64 primes split into 16-bit pieces, so that no intermediate result leaves the Fixnum range.
|
|
12
|
+
# Masked 64-bit Integer arithmetic is shorter, but allocates a Bignum for almost every operation
|
|
13
|
+
# (about 25 per hash), and with a large live heap (a writer holding a row group) those
|
|
14
|
+
# allocations trigger so many garbage collections that hashing becomes up to 10x slower. The
|
|
15
|
+
# arithmetic is written once with the small code generators below, whose output is inlined into
|
|
16
|
+
# the hashing methods: no method calls or allocations in the hot paths.
|
|
17
|
+
module XXHash
|
|
18
|
+
M = 0xFFFF_FFFF_FFFF_FFFF
|
|
19
|
+
P1 = 11_400_714_785_074_694_791
|
|
20
|
+
P2 = 14_029_467_366_897_019_727
|
|
21
|
+
P3 = 1_609_587_929_392_839_161
|
|
22
|
+
P4 = 9_650_029_242_287_828_579
|
|
23
|
+
P5 = 2_870_177_450_012_600_261
|
|
24
|
+
M32 = 0xFFFF_FFFF
|
|
25
|
+
WORDS = "V*" # frozen, unlike a literal in the generated code
|
|
26
|
+
|
|
27
|
+
# Gem providing the native implementation
|
|
28
|
+
NATIVE_GEM = "xxhash"
|
|
29
|
+
|
|
30
|
+
# Code generators for the pure-Ruby hash: each returns Ruby source operating on a 64-bit
|
|
31
|
+
# value held in two local variables (+hi+ and +lo+, 32 bits each), using t0/t1 as scratch
|
|
32
|
+
module Generator
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# Shifts are written as multiplications and divisions by powers of two, which YARV has
|
|
36
|
+
# specialized instructions for (<< and >> on Integers are method calls).
|
|
37
|
+
#
|
|
38
|
+
# (hi:lo) = (hi:lo) * c mod 2**64. Products are at most 48 bits wide: the low halves are
|
|
39
|
+
# multiplied by 16-bit pieces of c, and only the low 32 bits of the cross terms are kept.
|
|
40
|
+
def mul(hi, lo, c, hi_zero: false)
|
|
41
|
+
ch = c >> 32
|
|
42
|
+
cl = c & M32
|
|
43
|
+
cross = "#{lo} * #{ch & 0xFFFF} + (#{lo} * #{ch >> 16} & 0xFFFF) * 65536"
|
|
44
|
+
cross += " + #{hi} * #{cl & 0xFFFF} + (#{hi} * #{cl >> 16} & 0xFFFF) * 65536" unless hi_zero
|
|
45
|
+
<<~RUBY
|
|
46
|
+
t1 = #{lo} * #{cl >> 16}
|
|
47
|
+
t0 = #{lo} * #{cl & 0xFFFF} + (t1 & 0xFFFF) * 65536
|
|
48
|
+
#{hi} = (t0 / 4294967296 + t1 / 65536 + #{cross}) & 0xFFFFFFFF
|
|
49
|
+
#{lo} = t0 & 0xFFFFFFFF
|
|
50
|
+
RUBY
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Rotate (hi:lo) left by r bits (0 < r < 32)
|
|
54
|
+
def rotl(hi, lo, r)
|
|
55
|
+
mask = (1 << (32 - r)) - 1
|
|
56
|
+
<<~RUBY
|
|
57
|
+
t0 = #{hi}
|
|
58
|
+
#{hi} = (#{hi} & #{mask}) * #{1 << r} | #{lo} / #{1 << (32 - r)}
|
|
59
|
+
#{lo} = (#{lo} & #{mask}) * #{1 << r} | t0 / #{1 << (32 - r)}
|
|
60
|
+
RUBY
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# (hi:lo) += (a_hi:a_lo), where the addend is two expressions (constants or variables)
|
|
64
|
+
def add(hi, lo, a_hi, a_lo)
|
|
65
|
+
<<~RUBY
|
|
66
|
+
#{lo} += #{a_lo}
|
|
67
|
+
#{hi} = (#{hi} + #{a_hi} + #{lo} / 4294967296) & 0xFFFFFFFF
|
|
68
|
+
#{lo} &= 0xFFFFFFFF
|
|
69
|
+
RUBY
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def add_const(hi, lo, c)
|
|
73
|
+
add(hi, lo, c >> 32, c & M32)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Assigns the 64-bit constant c to (hi:lo)
|
|
77
|
+
def set(hi, lo, c)
|
|
78
|
+
"#{hi} = #{c >> 32}\n#{lo} = #{c & M32}\n"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# XXH64 round with a zero accumulator: (hi:lo) = rotl(lane * P2, 31) * P1
|
|
82
|
+
def round0(hi, lo)
|
|
83
|
+
mul(hi, lo, P2) + rotl(hi, lo, 31) + mul(hi, lo, P1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Stripe round: acc = rotl(acc + lane * P2, 31) * P1, with the lane in (xh:xl)
|
|
87
|
+
def round(hi, lo, xh, xl)
|
|
88
|
+
mul(xh, xl, P2) + add(hi, lo, xh, xl) + rotl(hi, lo, 31) + mul(hi, lo, P1)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# h ^= rotl(v * P2, 31) * P1; h = h * P1 + P4, with v in (vh:vl) (left unchanged)
|
|
92
|
+
def merge_round(hi, lo, vh, vl)
|
|
93
|
+
"xh = #{vh}\nxl = #{vl}\n" + round0("xh", "xl") +
|
|
94
|
+
"#{hi} ^= xh\n#{lo} ^= xl\n" + mul(hi, lo, P1) + add_const(hi, lo, P4)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Consumes an 8-byte lane in (xh:xl)
|
|
98
|
+
def lane8(hi, lo)
|
|
99
|
+
round0("xh", "xl") + "#{hi} ^= xh\n#{lo} ^= xl\n" + rotl(hi, lo, 27) + mul(hi, lo, P1) + add_const(hi, lo, P4)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Consumes a 4-byte word in xl
|
|
103
|
+
def lane4(hi, lo)
|
|
104
|
+
"xh = 0\n" + mul("xh", "xl", P1, hi_zero: true) + "#{hi} ^= xh\n#{lo} ^= xl\n" +
|
|
105
|
+
rotl(hi, lo, 23) + mul(hi, lo, P2) + add_const(hi, lo, P3)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Consumes one byte in xl (as it is below 2**16, byte * P5 needs no splitting)
|
|
109
|
+
def lane1(hi, lo)
|
|
110
|
+
<<~RUBY + rotl(hi, lo, 11) + mul(hi, lo, P1)
|
|
111
|
+
t0 = xl * #{P5 & M32}
|
|
112
|
+
#{hi} ^= (t0 / 4294967296 + xl * #{P5 >> 32}) & 0xFFFFFFFF
|
|
113
|
+
#{lo} ^= t0 & 0xFFFFFFFF
|
|
114
|
+
RUBY
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Final mix; evaluates to the hash as one Integer
|
|
118
|
+
def avalanche(hi, lo)
|
|
119
|
+
"#{lo} ^= #{hi} / 2\n" + mul(hi, lo, P2) +
|
|
120
|
+
"#{lo} ^= (#{hi} & 0x1FFFFFFF) * 8 | #{lo} / 536870912\n#{hi} ^= #{hi} / 536870912\n" +
|
|
121
|
+
mul(hi, lo, P3) + "#{lo} ^= #{hi}\n(#{hi} << 32) | #{lo}\n"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def source
|
|
125
|
+
g = self
|
|
126
|
+
<<~RUBY
|
|
127
|
+
# XXH64 of 8 bytes given as two little-endian 32-bit halves
|
|
128
|
+
def self.ruby_xxh64_lane(xh, xl)
|
|
129
|
+
#{g.set("h", "l", P5 + 8)}
|
|
130
|
+
#{g.lane8("h", "l")}
|
|
131
|
+
#{g.avalanche("h", "l")}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# XXH64 of 4 bytes given as a little-endian 32-bit word
|
|
135
|
+
def self.ruby_xxh64_u32(xl)
|
|
136
|
+
#{g.set("h", "l", P5 + 4)}
|
|
137
|
+
#{g.lane4("h", "l")}
|
|
138
|
+
#{g.avalanche("h", "l")}
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.ruby_xxh64(bytes)
|
|
142
|
+
len = bytes.bytesize
|
|
143
|
+
words = bytes.unpack(WORDS)
|
|
144
|
+
i = 0
|
|
145
|
+
if len >= 32
|
|
146
|
+
#{g.set("ah", "al", (P1 + P2) & M)}
|
|
147
|
+
#{g.set("bh", "bl", P2)}
|
|
148
|
+
ch = 0
|
|
149
|
+
cl = 0
|
|
150
|
+
#{g.set("dh", "dl", (-P1) & M)}
|
|
151
|
+
limit = (len >> 5) << 3
|
|
152
|
+
while i < limit
|
|
153
|
+
xl = words[i]
|
|
154
|
+
xh = words[i + 1]
|
|
155
|
+
#{g.round("ah", "al", "xh", "xl")}
|
|
156
|
+
xl = words[i + 2]
|
|
157
|
+
xh = words[i + 3]
|
|
158
|
+
#{g.round("bh", "bl", "xh", "xl")}
|
|
159
|
+
xl = words[i + 4]
|
|
160
|
+
xh = words[i + 5]
|
|
161
|
+
#{g.round("ch", "cl", "xh", "xl")}
|
|
162
|
+
xl = words[i + 6]
|
|
163
|
+
xh = words[i + 7]
|
|
164
|
+
#{g.round("dh", "dl", "xh", "xl")}
|
|
165
|
+
i += 8
|
|
166
|
+
end
|
|
167
|
+
h = ah
|
|
168
|
+
l = al
|
|
169
|
+
#{g.rotl("h", "l", 1)}
|
|
170
|
+
yh = bh
|
|
171
|
+
yl = bl
|
|
172
|
+
#{g.rotl("yh", "yl", 7)}
|
|
173
|
+
#{g.add("h", "l", "yh", "yl")}
|
|
174
|
+
yh = ch
|
|
175
|
+
yl = cl
|
|
176
|
+
#{g.rotl("yh", "yl", 12)}
|
|
177
|
+
#{g.add("h", "l", "yh", "yl")}
|
|
178
|
+
yh = dh
|
|
179
|
+
yl = dl
|
|
180
|
+
#{g.rotl("yh", "yl", 18)}
|
|
181
|
+
#{g.add("h", "l", "yh", "yl")}
|
|
182
|
+
#{g.merge_round("h", "l", "ah", "al")}
|
|
183
|
+
#{g.merge_round("h", "l", "bh", "bl")}
|
|
184
|
+
#{g.merge_round("h", "l", "ch", "cl")}
|
|
185
|
+
#{g.merge_round("h", "l", "dh", "dl")}
|
|
186
|
+
#{g.add("h", "l", 0, "len")}
|
|
187
|
+
else
|
|
188
|
+
#{g.set("h", "l", P5)}
|
|
189
|
+
#{g.add("h", "l", 0, "len")}
|
|
190
|
+
end
|
|
191
|
+
nwords = words.size
|
|
192
|
+
while i + 2 <= nwords
|
|
193
|
+
xl = words[i]
|
|
194
|
+
xh = words[i + 1]
|
|
195
|
+
#{g.lane8("h", "l")}
|
|
196
|
+
i += 2
|
|
197
|
+
end
|
|
198
|
+
if i < nwords
|
|
199
|
+
xl = words[i]
|
|
200
|
+
#{g.lane4("h", "l")}
|
|
201
|
+
i += 1
|
|
202
|
+
end
|
|
203
|
+
pos = i << 2
|
|
204
|
+
while pos < len
|
|
205
|
+
xl = bytes.getbyte(pos)
|
|
206
|
+
#{g.lane1("h", "l")}
|
|
207
|
+
pos += 1
|
|
208
|
+
end
|
|
209
|
+
#{g.avalanche("h", "l")}
|
|
210
|
+
end
|
|
211
|
+
RUBY
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
module_eval(Generator.source, __FILE__, __LINE__)
|
|
216
|
+
|
|
217
|
+
@native = nil # nil: not resolved yet, false: pure Ruby, else the native module
|
|
218
|
+
@native_lib = nil
|
|
219
|
+
|
|
220
|
+
class << self
|
|
221
|
+
# XXH64 of a String's bytes, as an unsigned 64-bit Integer
|
|
222
|
+
def xxh64(bytes)
|
|
223
|
+
native = @native
|
|
224
|
+
native = resolve_backend if native.nil?
|
|
225
|
+
native ? native.xxh64(bytes, 0) : ruby_xxh64(bytes)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# XXH64 of 8 bytes given as a little-endian 64-bit Integer (an INT64 or DOUBLE's PLAIN
|
|
229
|
+
# encoding), signed or unsigned: only its low 64 bits are used
|
|
230
|
+
def xxh64_u64(lane)
|
|
231
|
+
native = @native
|
|
232
|
+
native = resolve_backend if native.nil?
|
|
233
|
+
return native.xxh64([lane].pack("Q<"), 0) if native
|
|
234
|
+
ruby_xxh64_lane((lane >> 32) & M32, lane & M32)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# XXH64 of 4 bytes given as a little-endian 32-bit Integer (INT32, FLOAT), signed or unsigned
|
|
238
|
+
def xxh64_u32(word)
|
|
239
|
+
native = @native
|
|
240
|
+
native = resolve_backend if native.nil?
|
|
241
|
+
return native.xxh64([word].pack("L<"), 0) if native
|
|
242
|
+
ruby_xxh64_u32(word & M32)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Hashes of many 64-bit Integers (low 64 bits of each)
|
|
246
|
+
def xxh64_u64_all(lanes)
|
|
247
|
+
native = @native
|
|
248
|
+
native = resolve_backend if native.nil?
|
|
249
|
+
if native
|
|
250
|
+
packed = lanes.pack("Q<*")
|
|
251
|
+
Array.new(lanes.size) { |i| native.xxh64(packed.byteslice(i << 3, 8), 0) }
|
|
252
|
+
else
|
|
253
|
+
lanes.map { |v| ruby_xxh64_lane(v / 4_294_967_296 & M32, v & M32) }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Hashes of many 32-bit Integers (low 32 bits of each)
|
|
258
|
+
def xxh64_u32_all(words)
|
|
259
|
+
native = @native
|
|
260
|
+
native = resolve_backend if native.nil?
|
|
261
|
+
if native
|
|
262
|
+
packed = words.pack("L<*")
|
|
263
|
+
Array.new(words.size) { |i| native.xxh64(packed.byteslice(i << 2, 4), 0) }
|
|
264
|
+
else
|
|
265
|
+
words.map { |v| ruby_xxh64_u32(v & M32) }
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Hashes of many Strings
|
|
270
|
+
def xxh64_all(strings)
|
|
271
|
+
native = @native
|
|
272
|
+
native = resolve_backend if native.nil?
|
|
273
|
+
native ? strings.map { |s| native.xxh64(s, 0) } : strings.map { |s| ruby_xxh64(s) }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# :native when the xxhash gem is used, :ruby otherwise
|
|
277
|
+
def backend
|
|
278
|
+
native = @native
|
|
279
|
+
native = resolve_backend if native.nil?
|
|
280
|
+
native ? :native : :ruby
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# :ruby forces pure Ruby, :native requires the xxhash gem (UnsupportedError if it cannot be
|
|
284
|
+
# loaded), nil goes back to the default: native when available
|
|
285
|
+
def backend=(name)
|
|
286
|
+
@native = case name
|
|
287
|
+
when :ruby then false
|
|
288
|
+
when :native
|
|
289
|
+
native_library || raise(UnsupportedError, "The \"#{NATIVE_GEM}\" gem could not be loaded")
|
|
290
|
+
when nil then nil
|
|
291
|
+
else raise ArgumentError, "Unknown XXHash backend #{name.inspect} (expected :ruby, :native or nil)"
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Whether the native xxhash gem can be loaded (whatever the selected backend)
|
|
296
|
+
def native_available?
|
|
297
|
+
!!native_library
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
private
|
|
301
|
+
|
|
302
|
+
def resolve_backend
|
|
303
|
+
@native = native_library || false
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def native_library
|
|
307
|
+
if @native_lib.nil?
|
|
308
|
+
@native_lib = begin
|
|
309
|
+
require NATIVE_GEM
|
|
310
|
+
defined?(::XXhash::XXhashInternal) ? ::XXhash::XXhashInternal : ::XXhash
|
|
311
|
+
rescue LoadError
|
|
312
|
+
false
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
@native_lib
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
data/lib/herringbone.rb
CHANGED
|
@@ -7,8 +7,9 @@ require_relative "herringbone/version"
|
|
|
7
7
|
# Pure-Ruby reader and writer for Apache Parquet files
|
|
8
8
|
module Herringbone
|
|
9
9
|
class Error < StandardError; end
|
|
10
|
+
# The file is not valid Parquet (bad metadata, corrupt pages...)
|
|
10
11
|
class FormatError < Error; end
|
|
11
|
-
|
|
12
|
+
# A value cannot be written to its column
|
|
12
13
|
class EncodeError < Error; end
|
|
13
14
|
class UnsupportedError < Error; end
|
|
14
15
|
end
|
|
@@ -28,18 +29,44 @@ require_relative "herringbone/active_record"
|
|
|
28
29
|
require_relative "herringbone/reader"
|
|
29
30
|
require_relative "herringbone/byte_values"
|
|
30
31
|
require_relative "herringbone/writer"
|
|
32
|
+
require_relative "herringbone/xxhash"
|
|
33
|
+
require_relative "herringbone/bloom_filter"
|
|
34
|
+
require_relative "herringbone/inspector"
|
|
35
|
+
require_relative "herringbone/visualizer"
|
|
31
36
|
|
|
32
37
|
module Herringbone
|
|
33
|
-
Delta = Encodings::Delta
|
|
34
|
-
|
|
35
38
|
module_function
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
# Writes +records+ to +io+ (any IO responding to #write; Herringbone never opens files by path)
|
|
41
|
+
# and returns the number of rows written. +records+ is an Enumerable of rows, or an ActiveRecord
|
|
42
|
+
# model or relation, which is read with find_each. Without +schema+, the schema comes from the
|
|
43
|
+
# model's columns (Schema.from_active_record) or is inferred from the first rows (Schema.infer).
|
|
44
|
+
# Other options go to Writer.
|
|
45
|
+
#
|
|
46
|
+
# File.open("orders.parquet", "wb") { |f| Herringbone.write(f, Order.where(created_at: 1.year.ago..)) }
|
|
47
|
+
def write(io, records, schema: nil, **options)
|
|
48
|
+
model = if records.respond_to?(:klass) then records.klass
|
|
49
|
+
elsif records.respond_to?(:columns) && records.respond_to?(:find_each) then records
|
|
50
|
+
end
|
|
51
|
+
schema ||= model ? Schema.from_active_record(model) : Schema.infer(records)
|
|
52
|
+
Writer.open(io, schema, **options) do |writer|
|
|
53
|
+
if records.respond_to?(:find_each)
|
|
54
|
+
records.find_each { |record| writer << record }
|
|
55
|
+
else
|
|
56
|
+
records.each { |record| writer << record }
|
|
57
|
+
end
|
|
58
|
+
writer.rows_written
|
|
59
|
+
end
|
|
60
|
+
end
|
|
39
61
|
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
# Compression codecs this process can read and write, e.g. [:none, :snappy, :gzip, :lz4, :lz4_hadoop, :zstd].
|
|
63
|
+
# :zstd and :brotli are listed when the zstd-ruby / brotli gems can be loaded.
|
|
64
|
+
def codecs
|
|
65
|
+
Compression::NAMES.values.select do |name|
|
|
66
|
+
Compression.ensure_available!(name)
|
|
67
|
+
true
|
|
68
|
+
rescue UnsupportedError
|
|
69
|
+
false
|
|
70
|
+
end
|
|
44
71
|
end
|
|
45
72
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: herringbone
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Julik Tarkhanov
|
|
@@ -23,37 +23,9 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0'
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
requirements:
|
|
30
|
-
- - ">="
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0'
|
|
33
|
-
type: :runtime
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - ">="
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0'
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: zstd-ruby
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - ">="
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0'
|
|
47
|
-
type: :runtime
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - ">="
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0'
|
|
54
|
-
description: Reads and writes Apache Parquet files without native extensions or Thrift.
|
|
55
|
-
Snappy and LZ4 are implemented in Ruby; ZSTD and Brotli are used when their gems
|
|
56
|
-
are present.
|
|
26
|
+
description: Reads and writes Apache Parquet files in Ruby, without Thrift or native
|
|
27
|
+
extensions of its own. Snappy and LZ4 are implemented in Ruby and GZIP uses zlib;
|
|
28
|
+
ZSTD and Brotli are available when the zstd-ruby / brotli gems are installed.
|
|
57
29
|
email:
|
|
58
30
|
- me@julik.nl
|
|
59
31
|
executables:
|
|
@@ -66,6 +38,7 @@ files:
|
|
|
66
38
|
- bin/herringbone
|
|
67
39
|
- lib/herringbone.rb
|
|
68
40
|
- lib/herringbone/active_record.rb
|
|
41
|
+
- lib/herringbone/bloom_filter.rb
|
|
69
42
|
- lib/herringbone/byte_values.rb
|
|
70
43
|
- lib/herringbone/codecs/lz4.rb
|
|
71
44
|
- lib/herringbone/codecs/snappy.rb
|
|
@@ -74,13 +47,20 @@ files:
|
|
|
74
47
|
- lib/herringbone/encodings/plain.rb
|
|
75
48
|
- lib/herringbone/encodings/rle.rb
|
|
76
49
|
- lib/herringbone/format.rb
|
|
50
|
+
- lib/herringbone/inspector.rb
|
|
77
51
|
- lib/herringbone/io_buffer_support.rb
|
|
78
52
|
- lib/herringbone/reader.rb
|
|
53
|
+
- lib/herringbone/reader/column_chunk_reader.rb
|
|
54
|
+
- lib/herringbone/reader/column_cursor.rb
|
|
55
|
+
- lib/herringbone/reader/page_stream.rb
|
|
56
|
+
- lib/herringbone/reader/scan.rb
|
|
79
57
|
- lib/herringbone/schema.rb
|
|
80
58
|
- lib/herringbone/thrift.rb
|
|
81
59
|
- lib/herringbone/types.rb
|
|
82
60
|
- lib/herringbone/version.rb
|
|
61
|
+
- lib/herringbone/visualizer.rb
|
|
83
62
|
- lib/herringbone/writer.rb
|
|
63
|
+
- lib/herringbone/xxhash.rb
|
|
84
64
|
licenses:
|
|
85
65
|
- MIT
|
|
86
66
|
metadata: {}
|