herringbone 0.2.0 → 0.3.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 +56 -4
- data/lib/herringbone/active_record.rb +44 -12
- data/lib/herringbone/bloom_filter.rb +112 -12
- data/lib/herringbone/byte_values.rb +29 -0
- data/lib/herringbone/codecs/lz4.rb +66 -3
- data/lib/herringbone/codecs/snappy.rb +132 -3
- data/lib/herringbone/compression.rb +48 -0
- data/lib/herringbone/encodings/delta.rb +70 -13
- data/lib/herringbone/encodings/plain.rb +22 -0
- data/lib/herringbone/encodings/rle.rb +53 -1
- data/lib/herringbone/format.rb +150 -0
- data/lib/herringbone/inspector.rb +477 -81
- data/lib/herringbone/io_buffer_support.rb +3 -1
- data/lib/herringbone/reader/column_chunk_reader.rb +98 -6
- data/lib/herringbone/reader/column_cursor.rb +46 -2
- data/lib/herringbone/reader/numo.rb +365 -0
- data/lib/herringbone/reader/page_stream.rb +280 -4
- data/lib/herringbone/reader/scan.rb +103 -9
- data/lib/herringbone/reader.rb +308 -17
- data/lib/herringbone/schema.rb +306 -15
- data/lib/herringbone/thrift.rb +155 -4
- data/lib/herringbone/types.rb +176 -41
- data/lib/herringbone/version.rb +2 -1
- data/lib/herringbone/visualizer.rb +51 -11
- data/lib/herringbone/writer.rb +308 -31
- data/lib/herringbone/xxhash.rb +108 -2
- data/lib/herringbone.rb +28 -0
- metadata +3 -2
data/lib/herringbone/thrift.rb
CHANGED
|
@@ -5,30 +5,52 @@ module Herringbone
|
|
|
5
5
|
# Structs are described declaratively (see Herringbone::Thrift::Struct) so that
|
|
6
6
|
# both the reader and the writer are driven by the same field tables.
|
|
7
7
|
module Thrift
|
|
8
|
+
# Raised on malformed or truncated Thrift data, and on values the writer cannot encode
|
|
8
9
|
class Error < StandardError; end
|
|
9
10
|
|
|
10
|
-
# Compact protocol wire
|
|
11
|
+
# Compact protocol wire type: end of a struct's fields
|
|
11
12
|
T_STOP = 0
|
|
13
|
+
# Compact protocol wire type: boolean true (the value lives in the field header)
|
|
12
14
|
T_TRUE = 1
|
|
15
|
+
# Compact protocol wire type: boolean false (the value lives in the field header)
|
|
13
16
|
T_FALSE = 2
|
|
17
|
+
# Compact protocol wire type: signed 8-bit integer, one raw byte
|
|
14
18
|
T_BYTE = 3
|
|
19
|
+
# Compact protocol wire type: 16-bit integer, zigzag varint
|
|
15
20
|
T_I16 = 4
|
|
21
|
+
# Compact protocol wire type: 32-bit integer, zigzag varint
|
|
16
22
|
T_I32 = 5
|
|
23
|
+
# Compact protocol wire type: 64-bit integer, zigzag varint
|
|
17
24
|
T_I64 = 6
|
|
25
|
+
# Compact protocol wire type: little-endian IEEE 754 double, 8 bytes
|
|
18
26
|
T_DOUBLE = 7
|
|
27
|
+
# Compact protocol wire type: varint length followed by raw bytes
|
|
19
28
|
T_BINARY = 8
|
|
29
|
+
# Compact protocol wire type: list (size and element type header, then the elements)
|
|
20
30
|
T_LIST = 9
|
|
31
|
+
# Compact protocol wire type: set (encoded like a list)
|
|
21
32
|
T_SET = 10
|
|
33
|
+
# Compact protocol wire type: map (only skipped, Parquet metadata has none)
|
|
22
34
|
T_MAP = 11
|
|
35
|
+
# Compact protocol wire type: nested struct, terminated by T_STOP
|
|
23
36
|
T_STRUCT = 12
|
|
24
37
|
|
|
25
38
|
# Declared field types used in struct definitions
|
|
26
39
|
# :bool, :byte, :i16, :i32, :i64, :double, :binary, :string, [:list, elem], StructClass
|
|
40
|
+
#
|
|
41
|
+
# Maps the scalar declared types to the wire type written for them. +:bool+ maps to T_TRUE,
|
|
42
|
+
# but the writer picks T_TRUE or T_FALSE from the value.
|
|
27
43
|
WIRE_TYPES = {
|
|
28
44
|
bool: T_TRUE, byte: T_BYTE, i16: T_I16, i32: T_I32, i64: T_I64,
|
|
29
45
|
double: T_DOUBLE, binary: T_BINARY, string: T_BINARY
|
|
30
46
|
}.freeze
|
|
31
47
|
|
|
48
|
+
# Wire type written for a declared field type
|
|
49
|
+
#
|
|
50
|
+
# @param type [Symbol, Array, Class] a scalar type from WIRE_TYPES, a +[:list, elem]+ Array,
|
|
51
|
+
# or a Struct subclass
|
|
52
|
+
# @return [Integer] one of the T_* wire type constants (T_TRUE for +:bool+)
|
|
53
|
+
# @raise [KeyError] for an unknown scalar type Symbol
|
|
32
54
|
def self.wire_type_for(type)
|
|
33
55
|
case type
|
|
34
56
|
when Symbol then WIRE_TYPES.fetch(type)
|
|
@@ -38,6 +60,13 @@ module Herringbone
|
|
|
38
60
|
end
|
|
39
61
|
|
|
40
62
|
# Whether a value of wire type +wire+ can be read as declared +type+
|
|
63
|
+
#
|
|
64
|
+
# Integer widths are interchangeable (any of i16/i32/i64 on the wire is read for any declared
|
|
65
|
+
# integer type), and a set is accepted where a list is declared.
|
|
66
|
+
#
|
|
67
|
+
# @param wire [Integer] wire type from the field or list header
|
|
68
|
+
# @param type [Symbol, Array, Class] declared field type (see WIRE_TYPES)
|
|
69
|
+
# @return [Boolean] true when the value can be decoded as +type+, false when it must be skipped
|
|
41
70
|
def self.compatible?(wire, type)
|
|
42
71
|
case type
|
|
43
72
|
when :bool then wire == T_TRUE || wire == T_FALSE
|
|
@@ -50,14 +79,22 @@ module Herringbone
|
|
|
50
79
|
end
|
|
51
80
|
end
|
|
52
81
|
|
|
82
|
+
# Decodes compact protocol data from a String, keeping a byte position into it
|
|
53
83
|
class Reader
|
|
84
|
+
# @return [Integer] byte offset of the next byte to read
|
|
54
85
|
attr_reader :pos
|
|
55
86
|
|
|
87
|
+
# @param buf [String] the encoded data (binary)
|
|
88
|
+
# @param pos [Integer] byte offset to start reading at
|
|
56
89
|
def initialize(buf, pos = 0)
|
|
57
90
|
@buf = buf
|
|
58
91
|
@pos = pos
|
|
59
92
|
end
|
|
60
93
|
|
|
94
|
+
# Reads one raw byte
|
|
95
|
+
#
|
|
96
|
+
# @return [Integer] the byte, 0..255
|
|
97
|
+
# @raise [Error] at the end of the buffer
|
|
61
98
|
def read_byte
|
|
62
99
|
b = @buf.getbyte(@pos)
|
|
63
100
|
raise Error, "Unexpected end of Thrift data at #{@pos}" unless b
|
|
@@ -65,6 +102,10 @@ module Herringbone
|
|
|
65
102
|
b
|
|
66
103
|
end
|
|
67
104
|
|
|
105
|
+
# Reads an unsigned LEB128 varint
|
|
106
|
+
#
|
|
107
|
+
# @return [Integer] the decoded non-negative value
|
|
108
|
+
# @raise [Error] when the varint runs past the buffer or is longer than 64 bits allow
|
|
68
109
|
def read_varint
|
|
69
110
|
result = 0
|
|
70
111
|
shift = 0
|
|
@@ -77,11 +118,19 @@ module Herringbone
|
|
|
77
118
|
end
|
|
78
119
|
end
|
|
79
120
|
|
|
121
|
+
# Reads a zigzag-encoded varint (how i16, i32, i64 and field id deltas are stored)
|
|
122
|
+
#
|
|
123
|
+
# @return [Integer] the decoded signed value
|
|
124
|
+
# @raise [Error] when the varint runs past the buffer
|
|
80
125
|
def read_zigzag
|
|
81
126
|
n = read_varint
|
|
82
127
|
(n >> 1) ^ -(n & 1)
|
|
83
128
|
end
|
|
84
129
|
|
|
130
|
+
# Reads a length-prefixed byte string
|
|
131
|
+
#
|
|
132
|
+
# @return [String] a slice of the buffer (with the buffer's encoding)
|
|
133
|
+
# @raise [Error] when the length runs past the end of the buffer
|
|
85
134
|
def read_binary
|
|
86
135
|
len = read_varint
|
|
87
136
|
raise Error, "Binary length #{len} exceeds buffer" if @pos + len > @buf.bytesize
|
|
@@ -90,6 +139,9 @@ module Herringbone
|
|
|
90
139
|
s
|
|
91
140
|
end
|
|
92
141
|
|
|
142
|
+
# Reads an 8-byte little-endian double
|
|
143
|
+
#
|
|
144
|
+
# @return [Float, nil] the value (nil when fewer than 8 bytes are left: this is not checked)
|
|
93
145
|
def read_double
|
|
94
146
|
v = @buf.byteslice(@pos, 8).unpack1("E")
|
|
95
147
|
@pos += 8
|
|
@@ -97,6 +149,13 @@ module Herringbone
|
|
|
97
149
|
end
|
|
98
150
|
|
|
99
151
|
# Reads a struct of the given class, returning an instance.
|
|
152
|
+
#
|
|
153
|
+
# Fields the class does not declare, or whose wire type does not match the declared type,
|
|
154
|
+
# are skipped, so newer writers' additions do not break reading.
|
|
155
|
+
#
|
|
156
|
+
# @param klass [Class] a Thrift::Struct subclass
|
|
157
|
+
# @return [Struct] an instance of +klass+ with the fields that were present set
|
|
158
|
+
# @raise [Error] on truncated or malformed data
|
|
100
159
|
def read_struct(klass)
|
|
101
160
|
obj = klass.new
|
|
102
161
|
fields = klass.fields_by_id
|
|
@@ -118,18 +177,26 @@ module Herringbone
|
|
|
118
177
|
obj
|
|
119
178
|
end
|
|
120
179
|
|
|
180
|
+
# Reads one value of wire type +wire+ as declared +type+
|
|
181
|
+
#
|
|
182
|
+
# @param wire [Integer] wire type from the field or list header
|
|
183
|
+
# @param type [Symbol, Array, Class] declared type; +:string+ makes binary values UTF-8,
|
|
184
|
+
# an Array or Struct subclass gives the element type or struct to read
|
|
185
|
+
# @return [Object] true/false, an Integer (bytes are signed), a Float, a String, an Array
|
|
186
|
+
# (or nil, see #read_list) or a Struct
|
|
187
|
+
# @raise [Error] on an unsupported wire type or truncated data
|
|
121
188
|
def read_value(wire, type)
|
|
122
189
|
case wire
|
|
123
190
|
when T_TRUE then true
|
|
124
191
|
when T_FALSE then false
|
|
125
192
|
when T_BYTE
|
|
126
193
|
b = read_byte
|
|
127
|
-
b >= 0x80 ? b - 0x100 : b
|
|
194
|
+
(b >= 0x80) ? b - 0x100 : b
|
|
128
195
|
when T_I16, T_I32, T_I64 then read_zigzag
|
|
129
196
|
when T_DOUBLE then read_double
|
|
130
197
|
when T_BINARY
|
|
131
198
|
s = read_binary
|
|
132
|
-
type == :string ? s.force_encoding(Encoding::UTF_8) : s
|
|
199
|
+
(type == :string) ? s.force_encoding(Encoding::UTF_8) : s
|
|
133
200
|
when T_LIST, T_SET then read_list(type)
|
|
134
201
|
when T_STRUCT then read_struct(type)
|
|
135
202
|
else
|
|
@@ -137,6 +204,13 @@ module Herringbone
|
|
|
137
204
|
end
|
|
138
205
|
end
|
|
139
206
|
|
|
207
|
+
# Reads a list or set, whose header holds the size (or 15 and a varint size) and the
|
|
208
|
+
# element wire type
|
|
209
|
+
#
|
|
210
|
+
# @param type [Array] declared list type, +[:list, elem_type]+
|
|
211
|
+
# @return [Array, nil] the elements, or nil (with the list skipped) when the element wire
|
|
212
|
+
# type does not match +elem_type+
|
|
213
|
+
# @raise [Error] on truncated or malformed data
|
|
140
214
|
def read_list(type)
|
|
141
215
|
header = read_byte
|
|
142
216
|
size = header >> 4
|
|
@@ -144,7 +218,7 @@ module Herringbone
|
|
|
144
218
|
elem_wire = header & 0x0F
|
|
145
219
|
elem_type = type[1]
|
|
146
220
|
bool_elems = elem_wire == T_TRUE || elem_wire == T_FALSE
|
|
147
|
-
|
|
221
|
+
if size.positive? && !(bool_elems && elem_type == :bool) && !Thrift.compatible?(elem_wire, elem_type)
|
|
148
222
|
size.times { bool_elems ? read_byte : skip(elem_wire) }
|
|
149
223
|
return nil
|
|
150
224
|
end
|
|
@@ -158,6 +232,11 @@ module Herringbone
|
|
|
158
232
|
end
|
|
159
233
|
end
|
|
160
234
|
|
|
235
|
+
# Advances past a value of wire type +wire+ without decoding it
|
|
236
|
+
#
|
|
237
|
+
# @param wire [Integer] wire type of the value to skip
|
|
238
|
+
# @return [void]
|
|
239
|
+
# @raise [Error] on an unknown wire type (including T_STOP) or truncated data
|
|
161
240
|
def skip(wire)
|
|
162
241
|
case wire
|
|
163
242
|
when T_TRUE, T_FALSE then nil
|
|
@@ -193,13 +272,21 @@ module Herringbone
|
|
|
193
272
|
end
|
|
194
273
|
end
|
|
195
274
|
|
|
275
|
+
# Encodes compact protocol data by appending to a binary String
|
|
196
276
|
class Writer
|
|
277
|
+
# @return [String] the encoded data so far
|
|
197
278
|
attr_reader :buf
|
|
198
279
|
|
|
280
|
+
# @param buf [String] binary String to append to
|
|
199
281
|
def initialize(buf = String.new(capacity: 1024, encoding: Encoding::BINARY))
|
|
200
282
|
@buf = buf
|
|
201
283
|
end
|
|
202
284
|
|
|
285
|
+
# Writes an unsigned LEB128 varint
|
|
286
|
+
#
|
|
287
|
+
# @param n [Integer] non-negative value
|
|
288
|
+
# @return [void]
|
|
289
|
+
# @raise [Error] when +n+ is negative
|
|
203
290
|
def write_varint(n)
|
|
204
291
|
raise Error, "Negative varint" if n.negative?
|
|
205
292
|
while n >= 0x80
|
|
@@ -209,15 +296,29 @@ module Herringbone
|
|
|
209
296
|
@buf << n
|
|
210
297
|
end
|
|
211
298
|
|
|
299
|
+
# Writes a signed integer as a zigzag varint
|
|
300
|
+
#
|
|
301
|
+
# @param n [Integer] signed value
|
|
302
|
+
# @return [void]
|
|
212
303
|
def write_zigzag(n)
|
|
213
304
|
write_varint(n.negative? ? ((-n) << 1) - 1 : n << 1)
|
|
214
305
|
end
|
|
215
306
|
|
|
307
|
+
# Writes a length-prefixed byte string
|
|
308
|
+
#
|
|
309
|
+
# @param s [String] value whose bytes are written (in any encoding)
|
|
310
|
+
# @return [void]
|
|
216
311
|
def write_binary(s)
|
|
217
312
|
write_varint(s.bytesize)
|
|
218
313
|
@buf << s.b
|
|
219
314
|
end
|
|
220
315
|
|
|
316
|
+
# Writes the non-nil fields of a struct in field id order, then T_STOP. Field ids are
|
|
317
|
+
# written as a delta in the header when it is 1..15, else as a separate zigzag varint.
|
|
318
|
+
# Booleans are carried by the header's wire type alone.
|
|
319
|
+
#
|
|
320
|
+
# @param obj [Struct] instance of a Thrift::Struct subclass
|
|
321
|
+
# @return [void]
|
|
221
322
|
def write_struct(obj)
|
|
222
323
|
last_id = 0
|
|
223
324
|
obj.class.fields.each do |field|
|
|
@@ -241,6 +342,12 @@ module Herringbone
|
|
|
241
342
|
@buf << T_STOP
|
|
242
343
|
end
|
|
243
344
|
|
|
345
|
+
# Writes one value of a declared type. Not for +:bool+, whose value goes in a field or
|
|
346
|
+
# list header (see #write_struct and #write_list).
|
|
347
|
+
#
|
|
348
|
+
# @param type [Symbol, Array, Class] declared type (see WIRE_TYPES)
|
|
349
|
+
# @param value [Object] Integer, Float, String, Array or Struct matching +type+
|
|
350
|
+
# @return [void]
|
|
244
351
|
def write_value(type, value)
|
|
245
352
|
case type
|
|
246
353
|
when :byte then @buf << (value & 0xFF)
|
|
@@ -252,6 +359,12 @@ module Herringbone
|
|
|
252
359
|
end
|
|
253
360
|
end
|
|
254
361
|
|
|
362
|
+
# Writes a list: a header with the size (inline when below 15) and element wire type,
|
|
363
|
+
# then the elements. Booleans in lists are written as full bytes.
|
|
364
|
+
#
|
|
365
|
+
# @param elem_type [Symbol, Array, Class] declared element type
|
|
366
|
+
# @param values [Array] the elements
|
|
367
|
+
# @return [void]
|
|
255
368
|
def write_list(elem_type, values)
|
|
256
369
|
elem_wire = Thrift.wire_type_for(elem_type)
|
|
257
370
|
if values.size < 15
|
|
@@ -270,20 +383,39 @@ module Herringbone
|
|
|
270
383
|
end
|
|
271
384
|
end
|
|
272
385
|
|
|
386
|
+
# A field declared on a Thrift::Struct subclass
|
|
387
|
+
#
|
|
388
|
+
# @!attribute [rw] id
|
|
389
|
+
# @return [Integer] Thrift field id
|
|
390
|
+
# @!attribute [rw] name
|
|
391
|
+
# @return [Symbol] accessor name
|
|
392
|
+
# @!attribute [rw] type
|
|
393
|
+
# @return [Symbol, Array, Class] declared type (see WIRE_TYPES)
|
|
394
|
+
# @!attribute [rw] ivar
|
|
395
|
+
# @return [Symbol] instance variable holding the value (+:@name+)
|
|
273
396
|
Field = ::Struct.new(:id, :name, :type, :ivar)
|
|
274
397
|
|
|
275
398
|
# Base class for Thrift structs. Subclasses declare fields with
|
|
276
399
|
# field 1, :name, :i32
|
|
277
400
|
class Struct
|
|
278
401
|
class << self
|
|
402
|
+
# @return [Array<Field>] declared fields, sorted by id
|
|
279
403
|
def fields
|
|
280
404
|
@fields ||= []
|
|
281
405
|
end
|
|
282
406
|
|
|
407
|
+
# @return [Hash{Integer => Field}] declared fields by id, for decoding
|
|
283
408
|
def fields_by_id
|
|
284
409
|
@fields_by_id ||= fields.to_h { |f| [f.id, f] }
|
|
285
410
|
end
|
|
286
411
|
|
|
412
|
+
# Declares a field and defines its accessor
|
|
413
|
+
#
|
|
414
|
+
# @param id [Integer] Thrift field id from parquet.thrift
|
|
415
|
+
# @param name [Symbol] accessor name
|
|
416
|
+
# @param type [Symbol, Array, Class] declared type: a WIRE_TYPES key, +[:list, elem]+
|
|
417
|
+
# or a Struct subclass
|
|
418
|
+
# @return [void]
|
|
287
419
|
def field(id, name, type)
|
|
288
420
|
fields << Field.new(id, name, type, :"@#{name}")
|
|
289
421
|
fields.sort_by!(&:id)
|
|
@@ -291,12 +423,22 @@ module Herringbone
|
|
|
291
423
|
attr_accessor name
|
|
292
424
|
end
|
|
293
425
|
|
|
426
|
+
# Decodes an instance from +buf+ starting at +pos+
|
|
427
|
+
#
|
|
428
|
+
# @param buf [String] encoded data
|
|
429
|
+
# @param pos [Integer] byte offset of the struct in +buf+
|
|
430
|
+
# @return [Array(Struct, Integer)] the instance and the offset just past it
|
|
431
|
+
# @raise [Error] on truncated or malformed data
|
|
294
432
|
def decode(buf, pos = 0)
|
|
295
433
|
reader = Reader.new(buf, pos)
|
|
296
434
|
[reader.read_struct(self), reader.pos]
|
|
297
435
|
end
|
|
298
436
|
end
|
|
299
437
|
|
|
438
|
+
# @param attrs [Hash{Symbol => Object}] initial field values, by accessor name
|
|
439
|
+
# @option attrs [Object] :any_declared_field value for that field (nil leaves it unset); the
|
|
440
|
+
# keys are whatever fields the subclass declares
|
|
441
|
+
# @raise [ArgumentError] for a name that is not a declared field
|
|
300
442
|
def initialize(**attrs)
|
|
301
443
|
attrs.each do |k, v|
|
|
302
444
|
raise ArgumentError, "Unknown field #{k} for #{self.class}" unless respond_to?(:"#{k}=")
|
|
@@ -304,12 +446,16 @@ module Herringbone
|
|
|
304
446
|
end
|
|
305
447
|
end
|
|
306
448
|
|
|
449
|
+
# @return [String] the compact protocol encoding (binary)
|
|
307
450
|
def encode
|
|
308
451
|
w = Writer.new
|
|
309
452
|
w.write_struct(self)
|
|
310
453
|
w.buf
|
|
311
454
|
end
|
|
312
455
|
|
|
456
|
+
# Set fields as a Hash, with nested structs (also inside lists) converted too
|
|
457
|
+
#
|
|
458
|
+
# @return [Hash{Symbol => Object}] values by field name, nil fields left out
|
|
313
459
|
def to_h
|
|
314
460
|
self.class.fields.each_with_object({}) do |f, h|
|
|
315
461
|
v = instance_variable_get(f.ivar)
|
|
@@ -322,10 +468,15 @@ module Herringbone
|
|
|
322
468
|
end
|
|
323
469
|
end
|
|
324
470
|
|
|
471
|
+
# Structs are equal when they are of the same class and have the same field values
|
|
472
|
+
#
|
|
473
|
+
# @param other [Object] object to compare with
|
|
474
|
+
# @return [Boolean] true when +other+ is an equal struct
|
|
325
475
|
def ==(other)
|
|
326
476
|
other.class == self.class && other.to_h == to_h
|
|
327
477
|
end
|
|
328
478
|
|
|
479
|
+
# @return [String] short class name and the set fields
|
|
329
480
|
def inspect
|
|
330
481
|
"#<#{self.class.name.split("::").last} #{to_h.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")}>"
|
|
331
482
|
end
|