herringbone 0.1.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.
@@ -0,0 +1,334 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Herringbone
4
+ # Minimal Thrift Compact Protocol implementation, just enough for Parquet metadata.
5
+ # Structs are described declaratively (see Herringbone::Thrift::Struct) so that
6
+ # both the reader and the writer are driven by the same field tables.
7
+ module Thrift
8
+ class Error < StandardError; end
9
+
10
+ # Compact protocol wire types
11
+ T_STOP = 0
12
+ T_TRUE = 1
13
+ T_FALSE = 2
14
+ T_BYTE = 3
15
+ T_I16 = 4
16
+ T_I32 = 5
17
+ T_I64 = 6
18
+ T_DOUBLE = 7
19
+ T_BINARY = 8
20
+ T_LIST = 9
21
+ T_SET = 10
22
+ T_MAP = 11
23
+ T_STRUCT = 12
24
+
25
+ # Declared field types used in struct definitions
26
+ # :bool, :byte, :i16, :i32, :i64, :double, :binary, :string, [:list, elem], StructClass
27
+ WIRE_TYPES = {
28
+ bool: T_TRUE, byte: T_BYTE, i16: T_I16, i32: T_I32, i64: T_I64,
29
+ double: T_DOUBLE, binary: T_BINARY, string: T_BINARY
30
+ }.freeze
31
+
32
+ def self.wire_type_for(type)
33
+ case type
34
+ when Symbol then WIRE_TYPES.fetch(type)
35
+ when Array then T_LIST
36
+ else T_STRUCT
37
+ end
38
+ end
39
+
40
+ # Whether a value of wire type +wire+ can be read as declared +type+
41
+ def self.compatible?(wire, type)
42
+ case type
43
+ when :bool then wire == T_TRUE || wire == T_FALSE
44
+ when :byte then wire == T_BYTE
45
+ when :i16, :i32, :i64 then wire == T_I16 || wire == T_I32 || wire == T_I64
46
+ when :double then wire == T_DOUBLE
47
+ when :binary, :string then wire == T_BINARY
48
+ when Array then wire == T_LIST || wire == T_SET
49
+ else wire == T_STRUCT
50
+ end
51
+ end
52
+
53
+ class Reader
54
+ attr_reader :pos
55
+
56
+ def initialize(buf, pos = 0)
57
+ @buf = buf
58
+ @pos = pos
59
+ end
60
+
61
+ def read_byte
62
+ b = @buf.getbyte(@pos)
63
+ raise Error, "Unexpected end of Thrift data at #{@pos}" unless b
64
+ @pos += 1
65
+ b
66
+ end
67
+
68
+ def read_varint
69
+ result = 0
70
+ shift = 0
71
+ while true
72
+ b = read_byte
73
+ result |= (b & 0x7F) << shift
74
+ return result if b < 0x80
75
+ shift += 7
76
+ raise Error, "Varint too long" if shift > 70
77
+ end
78
+ end
79
+
80
+ def read_zigzag
81
+ n = read_varint
82
+ (n >> 1) ^ -(n & 1)
83
+ end
84
+
85
+ def read_binary
86
+ len = read_varint
87
+ raise Error, "Binary length #{len} exceeds buffer" if @pos + len > @buf.bytesize
88
+ s = @buf.byteslice(@pos, len)
89
+ @pos += len
90
+ s
91
+ end
92
+
93
+ def read_double
94
+ v = @buf.byteslice(@pos, 8).unpack1("E")
95
+ @pos += 8
96
+ v
97
+ end
98
+
99
+ # Reads a struct of the given class, returning an instance.
100
+ def read_struct(klass)
101
+ obj = klass.new
102
+ fields = klass.fields_by_id
103
+ last_id = 0
104
+ while true
105
+ header = read_byte
106
+ wire = header & 0x0F
107
+ break if wire == T_STOP
108
+ delta = header >> 4
109
+ fid = delta.zero? ? read_zigzag : last_id + delta
110
+ last_id = fid
111
+ field = fields[fid]
112
+ if field && Thrift.compatible?(wire, field.type)
113
+ obj.instance_variable_set(field.ivar, read_value(wire, field.type))
114
+ else
115
+ skip(wire)
116
+ end
117
+ end
118
+ obj
119
+ end
120
+
121
+ def read_value(wire, type)
122
+ case wire
123
+ when T_TRUE then true
124
+ when T_FALSE then false
125
+ when T_BYTE
126
+ b = read_byte
127
+ b >= 0x80 ? b - 0x100 : b
128
+ when T_I16, T_I32, T_I64 then read_zigzag
129
+ when T_DOUBLE then read_double
130
+ when T_BINARY
131
+ s = read_binary
132
+ type == :string ? s.force_encoding(Encoding::UTF_8) : s
133
+ when T_LIST, T_SET then read_list(type)
134
+ when T_STRUCT then read_struct(type)
135
+ else
136
+ raise Error, "Unsupported wire type #{wire}"
137
+ end
138
+ end
139
+
140
+ def read_list(type)
141
+ header = read_byte
142
+ size = header >> 4
143
+ size = read_varint if size == 15
144
+ elem_wire = header & 0x0F
145
+ elem_type = type[1]
146
+ bool_elems = elem_wire == T_TRUE || elem_wire == T_FALSE
147
+ unless size.zero? || (bool_elems && elem_type == :bool) || Thrift.compatible?(elem_wire, elem_type)
148
+ size.times { bool_elems ? read_byte : skip(elem_wire) }
149
+ return nil
150
+ end
151
+ Array.new(size) do
152
+ if elem_wire == T_TRUE || elem_wire == T_FALSE
153
+ # Booleans inside lists are encoded as full bytes
154
+ read_byte == T_TRUE
155
+ else
156
+ read_value(elem_wire, elem_type)
157
+ end
158
+ end
159
+ end
160
+
161
+ def skip(wire)
162
+ case wire
163
+ when T_TRUE, T_FALSE then nil
164
+ when T_BYTE then read_byte
165
+ when T_I16, T_I32, T_I64 then read_varint
166
+ when T_DOUBLE then @pos += 8
167
+ when T_BINARY then read_binary
168
+ when T_LIST, T_SET
169
+ header = read_byte
170
+ size = header >> 4
171
+ size = read_varint if size == 15
172
+ elem = header & 0x0F
173
+ size.times { (elem == T_TRUE || elem == T_FALSE) ? read_byte : skip(elem) }
174
+ when T_MAP
175
+ size = read_varint
176
+ unless size.zero?
177
+ kv = read_byte
178
+ size.times do
179
+ [kv >> 4, kv & 0x0F].each { |w| (w == T_TRUE || w == T_FALSE) ? read_byte : skip(w) }
180
+ end
181
+ end
182
+ when T_STRUCT
183
+ while true
184
+ header = read_byte
185
+ w = header & 0x0F
186
+ break if w == T_STOP
187
+ read_zigzag if (header >> 4).zero?
188
+ skip(w)
189
+ end
190
+ else
191
+ raise Error, "Cannot skip wire type #{wire}"
192
+ end
193
+ end
194
+ end
195
+
196
+ class Writer
197
+ attr_reader :buf
198
+
199
+ def initialize(buf = String.new(capacity: 1024, encoding: Encoding::BINARY))
200
+ @buf = buf
201
+ end
202
+
203
+ def write_varint(n)
204
+ raise Error, "Negative varint" if n.negative?
205
+ while n >= 0x80
206
+ @buf << ((n & 0x7F) | 0x80)
207
+ n >>= 7
208
+ end
209
+ @buf << n
210
+ end
211
+
212
+ def write_zigzag(n)
213
+ write_varint(n.negative? ? ((-n) << 1) - 1 : n << 1)
214
+ end
215
+
216
+ def write_binary(s)
217
+ write_varint(s.bytesize)
218
+ @buf << s.b
219
+ end
220
+
221
+ def write_struct(obj)
222
+ last_id = 0
223
+ obj.class.fields.each do |field|
224
+ value = obj.instance_variable_get(field.ivar)
225
+ next if value.nil?
226
+ wire = if field.type == :bool
227
+ value ? T_TRUE : T_FALSE
228
+ else
229
+ Thrift.wire_type_for(field.type)
230
+ end
231
+ delta = field.id - last_id
232
+ if delta.positive? && delta <= 15
233
+ @buf << ((delta << 4) | wire)
234
+ else
235
+ @buf << wire
236
+ write_zigzag(field.id)
237
+ end
238
+ last_id = field.id
239
+ write_value(field.type, value) unless field.type == :bool
240
+ end
241
+ @buf << T_STOP
242
+ end
243
+
244
+ def write_value(type, value)
245
+ case type
246
+ when :byte then @buf << (value & 0xFF)
247
+ when :i16, :i32, :i64 then write_zigzag(value)
248
+ when :double then @buf << [value].pack("E")
249
+ when :binary, :string then write_binary(value)
250
+ when Array then write_list(type[1], value)
251
+ else write_struct(value)
252
+ end
253
+ end
254
+
255
+ def write_list(elem_type, values)
256
+ elem_wire = Thrift.wire_type_for(elem_type)
257
+ if values.size < 15
258
+ @buf << ((values.size << 4) | elem_wire)
259
+ else
260
+ @buf << (0xF0 | elem_wire)
261
+ write_varint(values.size)
262
+ end
263
+ values.each do |v|
264
+ if elem_type == :bool
265
+ @buf << (v ? T_TRUE : T_FALSE)
266
+ else
267
+ write_value(elem_type, v)
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ Field = ::Struct.new(:id, :name, :type, :ivar)
274
+
275
+ # Base class for Thrift structs. Subclasses declare fields with
276
+ # field 1, :name, :i32
277
+ class Struct
278
+ class << self
279
+ def fields
280
+ @fields ||= []
281
+ end
282
+
283
+ def fields_by_id
284
+ @fields_by_id ||= fields.to_h { |f| [f.id, f] }
285
+ end
286
+
287
+ def field(id, name, type)
288
+ fields << Field.new(id, name, type, :"@#{name}")
289
+ fields.sort_by!(&:id)
290
+ @fields_by_id = nil
291
+ attr_accessor name
292
+ end
293
+
294
+ def decode(buf, pos = 0)
295
+ reader = Reader.new(buf, pos)
296
+ [reader.read_struct(self), reader.pos]
297
+ end
298
+ end
299
+
300
+ def initialize(**attrs)
301
+ attrs.each do |k, v|
302
+ raise ArgumentError, "Unknown field #{k} for #{self.class}" unless respond_to?(:"#{k}=")
303
+ public_send(:"#{k}=", v)
304
+ end
305
+ end
306
+
307
+ def encode
308
+ w = Writer.new
309
+ w.write_struct(self)
310
+ w.buf
311
+ end
312
+
313
+ def to_h
314
+ self.class.fields.each_with_object({}) do |f, h|
315
+ v = instance_variable_get(f.ivar)
316
+ next if v.nil?
317
+ h[f.name] = case v
318
+ when Struct then v.to_h
319
+ when Array then v.map { |e| e.is_a?(Struct) ? e.to_h : e }
320
+ else v
321
+ end
322
+ end
323
+ end
324
+
325
+ def ==(other)
326
+ other.class == self.class && other.to_h == to_h
327
+ end
328
+
329
+ def inspect
330
+ "#<#{self.class.name.split("::").last} #{to_h.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")}>"
331
+ end
332
+ end
333
+ end
334
+ end