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
|
@@ -5,6 +5,8 @@ module Herringbone
|
|
|
5
5
|
# It still carries an "experimental" warning, which is silenced once here: the gem only uses
|
|
6
6
|
# new/for/copy/get_string/free, and falls back to String operations where it is missing.
|
|
7
7
|
module IOBufferSupport
|
|
8
|
+
# true when IO::Buffer has the methods the decompressors use and HERRINGBONE_NO_IO_BUFFER is
|
|
9
|
+
# not set in the environment (which forces the String fallback)
|
|
8
10
|
AVAILABLE = begin
|
|
9
11
|
if defined?(IO::Buffer) && IO::Buffer.method_defined?(:copy) && IO::Buffer.method_defined?(:get_string)
|
|
10
12
|
previous = Warning[:experimental]
|
|
@@ -18,7 +20,7 @@ module Herringbone
|
|
|
18
20
|
else
|
|
19
21
|
false
|
|
20
22
|
end
|
|
21
|
-
rescue
|
|
23
|
+
rescue
|
|
22
24
|
false
|
|
23
25
|
end
|
|
24
26
|
end
|
|
@@ -14,22 +14,35 @@ module Herringbone
|
|
|
14
14
|
# The declared total_compressed_size of the chunk is not relied on (some old writers
|
|
15
15
|
# under-report it): pages are read until the chunk's num_values have been seen.
|
|
16
16
|
class ColumnChunkReader
|
|
17
|
+
# Shorthand for the physical type constants
|
|
17
18
|
T = Format::Type
|
|
19
|
+
# Shorthand for the encoding constants
|
|
18
20
|
E = Format::Encoding
|
|
19
21
|
|
|
20
22
|
# Bytes read past a page body, so that the next page header usually needs no extra read
|
|
21
23
|
READ_AHEAD = 64 * 1024
|
|
24
|
+
# Bytes read for a page header whose size is not known; grown 4x while decoding fails
|
|
22
25
|
HEADER_GUESS = 1024
|
|
26
|
+
# Largest header read attempted before a page header is declared corrupt
|
|
23
27
|
MAX_HEADER = 64 * 1024 * 1024
|
|
24
28
|
|
|
29
|
+
# @return [Schema::Column] the leaf column this chunk belongs to
|
|
25
30
|
attr_reader :column
|
|
26
31
|
|
|
27
32
|
# With +lazy+, values of data pages that are not dictionary-encoded are returned as
|
|
28
33
|
# physical values, and #page_converter is what still has to be applied to them. This keeps
|
|
29
34
|
# a decoded page small (Integers instead of Time or BigDecimal objects) when only a slice
|
|
30
35
|
# of it is needed at a time.
|
|
36
|
+
#
|
|
37
|
+
# @return [Proc, nil] converter of the last page returned by #next_page, nil when none
|
|
31
38
|
attr_reader :page_converter
|
|
32
39
|
|
|
40
|
+
# @param io [IO, StringIO] the file, read with #seek and #read
|
|
41
|
+
# @param chunk [Format::ColumnChunk] the chunk's footer entry
|
|
42
|
+
# @param column [Schema::Column] the leaf column the chunk stores
|
|
43
|
+
# @param converter [Proc, nil] physical value => Ruby value, applied to values and dictionaries
|
|
44
|
+
# @param lazy [Boolean] leave non-dictionary values physical in #next_page (see #page_converter)
|
|
45
|
+
# @raise [UnsupportedError] for chunks without metadata (encrypted) or stored in another file
|
|
33
46
|
def initialize(io, chunk, column, converter: column.converter, lazy: false)
|
|
34
47
|
@io = io
|
|
35
48
|
@chunk = chunk
|
|
@@ -43,7 +56,7 @@ module Herringbone
|
|
|
43
56
|
start = @meta.data_page_offset
|
|
44
57
|
dict = @meta.dictionary_page_offset
|
|
45
58
|
# Some writers store 0 when there is no dictionary page
|
|
46
|
-
start = dict if dict
|
|
59
|
+
start = dict if dict&.positive? && dict < start
|
|
47
60
|
@pos = start
|
|
48
61
|
@start = start
|
|
49
62
|
@locations = nil # OffsetIndex page locations, when jumping between pages
|
|
@@ -55,6 +68,10 @@ module Herringbone
|
|
|
55
68
|
end
|
|
56
69
|
|
|
57
70
|
# All pages concatenated: [definition_levels, repetition_levels, values]
|
|
71
|
+
#
|
|
72
|
+
# @return [Array(Array<Integer>, Array<Integer>, Array)] levels are nil when the column's
|
|
73
|
+
# max level is 0
|
|
74
|
+
# @raise [FormatError] when a page is corrupt or overruns the file
|
|
58
75
|
def read
|
|
59
76
|
defs = @max_def.positive? ? [] : nil
|
|
60
77
|
reps = @max_rep.positive? ? [] : nil
|
|
@@ -69,6 +86,10 @@ module Herringbone
|
|
|
69
86
|
end
|
|
70
87
|
|
|
71
88
|
# The next data page as [defs, reps, values], or nil after the last one
|
|
89
|
+
#
|
|
90
|
+
# @return [Array(Array<Integer>, Array<Integer>, Array), nil] levels are nil when the
|
|
91
|
+
# column's max level is 0; values hold the non-null entries
|
|
92
|
+
# @raise [FormatError] when a page is corrupt or overruns the file
|
|
72
93
|
def next_page
|
|
73
94
|
page = next_stream or return nil
|
|
74
95
|
n = page.remaining
|
|
@@ -89,6 +110,11 @@ module Herringbone
|
|
|
89
110
|
|
|
90
111
|
# The next data page as a PageStream::Page that decodes its levels and values on demand,
|
|
91
112
|
# or nil after the last one. Values come out physical; apply Page#converter to them.
|
|
113
|
+
# Dictionary pages are read on the way.
|
|
114
|
+
#
|
|
115
|
+
# @return [PageStream::Page, nil] the next data page
|
|
116
|
+
# @raise [FormatError] when a page header is corrupt or a page overruns the file
|
|
117
|
+
# @raise [UnsupportedError] for an unsupported encoding or compression codec
|
|
92
118
|
def next_stream
|
|
93
119
|
while more_pages?
|
|
94
120
|
header, body = read_page
|
|
@@ -112,11 +138,17 @@ module Herringbone
|
|
|
112
138
|
raise FormatError, "Corrupt page header in #{@column.dotted_path}: #{e.message}"
|
|
113
139
|
end
|
|
114
140
|
|
|
115
|
-
#
|
|
141
|
+
# @return [Array<Format::PageLocation>, nil] the chunk's data page locations from its
|
|
142
|
+
# OffsetIndex (enables #jump_to_page)
|
|
116
143
|
attr_accessor :locations
|
|
117
144
|
|
|
118
145
|
# Continues reading at data page +index+ of the OffsetIndex. The dictionary page (which
|
|
119
146
|
# the OffsetIndex does not list) is read first if it has not been yet.
|
|
147
|
+
#
|
|
148
|
+
# @param index [Integer] position of the page in #locations
|
|
149
|
+
# @return [void]
|
|
150
|
+
# @raise [ArgumentError] when no #locations are set
|
|
151
|
+
# @raise [IndexError] when +index+ is outside #locations
|
|
120
152
|
def jump_to_page(index)
|
|
121
153
|
raise ArgumentError, "No OffsetIndex for #{@column.dotted_path}" unless @locations
|
|
122
154
|
load_dictionary
|
|
@@ -125,18 +157,29 @@ module Herringbone
|
|
|
125
157
|
end
|
|
126
158
|
|
|
127
159
|
# Whether all of the chunk's values have been returned
|
|
160
|
+
#
|
|
161
|
+
# @return [Boolean] true once num_values entries have been seen
|
|
128
162
|
def done? = @seen >= @total
|
|
129
163
|
|
|
130
|
-
|
|
131
|
-
|
|
164
|
+
# @return [Integer] entries (levels, nulls included) of the data pages returned so far
|
|
165
|
+
attr_reader :seen
|
|
166
|
+
|
|
167
|
+
# @return [Integer] the chunk's num_values from its ColumnMetaData
|
|
168
|
+
attr_reader :total
|
|
132
169
|
|
|
133
170
|
private
|
|
134
171
|
|
|
172
|
+
# Whether another data page is due: up to the last OffsetIndex location after a jump,
|
|
173
|
+
# otherwise until num_values entries have been seen
|
|
174
|
+
#
|
|
175
|
+
# @return [Boolean] true when #next_stream should read on
|
|
135
176
|
def more_pages?
|
|
136
177
|
@page_number ? @page_number < @locations.size : @seen < @total
|
|
137
178
|
end
|
|
138
179
|
|
|
139
180
|
# Reads the dictionary page at the start of the chunk, if there is one
|
|
181
|
+
#
|
|
182
|
+
# @return [void]
|
|
140
183
|
def load_dictionary
|
|
141
184
|
return if @dictionary || @dictionary_checked
|
|
142
185
|
@dictionary_checked = true
|
|
@@ -150,6 +193,9 @@ module Herringbone
|
|
|
150
193
|
end
|
|
151
194
|
|
|
152
195
|
# Reads the page header at @pos and the page body after it
|
|
196
|
+
#
|
|
197
|
+
# @return [Array(Format::PageHeader, String)] the header and the (still compressed) body
|
|
198
|
+
# @raise [FormatError] for a negative page size or a page that overruns the file
|
|
153
199
|
def read_page
|
|
154
200
|
# With an OffsetIndex the page's size (header included) is known, so read exactly that
|
|
155
201
|
loc = @page_number && @locations[@page_number]
|
|
@@ -171,13 +217,18 @@ module Herringbone
|
|
|
171
217
|
if buf.bytesize - off < size
|
|
172
218
|
raise FormatError, "Column #{@column.dotted_path}: page overruns the file (read #{@seen} of #{@total} values)"
|
|
173
219
|
end
|
|
174
|
-
body = off.zero? && buf.bytesize == size ? buf : buf.byteslice(off, size)
|
|
220
|
+
body = (off.zero? && buf.bytesize == size) ? buf : buf.byteslice(off, size)
|
|
175
221
|
@pos += hlen + size
|
|
176
222
|
[header, body]
|
|
177
223
|
end
|
|
178
224
|
|
|
179
225
|
# Returns [buffer, offset] where buffer[offset..] holds at least +need+ bytes from file
|
|
180
226
|
# position +pos+ (fewer only at EOF), reading +len+ bytes when the buffer does not cover it.
|
|
227
|
+
#
|
|
228
|
+
# @param pos [Integer] file offset wanted
|
|
229
|
+
# @param len [Integer] bytes to read when the buffer has to be refilled
|
|
230
|
+
# @param need [Integer] bytes that must be available from +pos+ to reuse the buffer
|
|
231
|
+
# @return [Array(String, Integer)] binary buffer and the offset of +pos+ in it
|
|
181
232
|
def window(pos, len, need = len)
|
|
182
233
|
off = pos - @buf_pos
|
|
183
234
|
return [@buf, off] if off >= 0 && off + need <= @buf.bytesize
|
|
@@ -188,12 +239,23 @@ module Herringbone
|
|
|
188
239
|
[@buf, 0]
|
|
189
240
|
end
|
|
190
241
|
|
|
242
|
+
# Decompresses a page body with the chunk's codec
|
|
243
|
+
#
|
|
244
|
+
# @param body [String] compressed bytes
|
|
245
|
+
# @param size [Integer] uncompressed size from the page header
|
|
246
|
+
# @return [String] uncompressed bytes
|
|
247
|
+
# @raise [UnsupportedError] for an unsupported codec, naming the column
|
|
191
248
|
def decompress(body, size)
|
|
192
249
|
Compression.decompress(@meta.codec, body, size)
|
|
193
250
|
rescue UnsupportedError => e
|
|
194
251
|
raise e, "#{e.message} (column #{@column.dotted_path})"
|
|
195
252
|
end
|
|
196
253
|
|
|
254
|
+
# Decodes a dictionary page (always PLAIN) and keeps its converted values
|
|
255
|
+
#
|
|
256
|
+
# @param header [Format::PageHeader] the dictionary page header
|
|
257
|
+
# @param body [String] the compressed page body
|
|
258
|
+
# @return [Array] the dictionary values
|
|
197
259
|
def read_dictionary(header, body)
|
|
198
260
|
dh = header.dictionary_page_header
|
|
199
261
|
data = decompress(body, header.uncompressed_page_size)
|
|
@@ -202,6 +264,11 @@ module Herringbone
|
|
|
202
264
|
@dictionary = vals
|
|
203
265
|
end
|
|
204
266
|
|
|
267
|
+
# A DATA_PAGE: the whole body is compressed, levels come first with their own length prefix
|
|
268
|
+
#
|
|
269
|
+
# @param header [Format::PageHeader] the data page header
|
|
270
|
+
# @param body [String] the compressed page body
|
|
271
|
+
# @return [PageStream::Page] the page, decoded on demand
|
|
205
272
|
def data_page_v1(header, body)
|
|
206
273
|
dh = header.data_page_header
|
|
207
274
|
n = dh.num_values
|
|
@@ -214,6 +281,11 @@ module Herringbone
|
|
|
214
281
|
PageStream::Page.new(n, defs, reps, values, conv)
|
|
215
282
|
end
|
|
216
283
|
|
|
284
|
+
# A DATA_PAGE_V2: levels are stored uncompressed before the (optionally compressed) values
|
|
285
|
+
#
|
|
286
|
+
# @param header [Format::PageHeader] the data page header
|
|
287
|
+
# @param body [String] the page body
|
|
288
|
+
# @return [PageStream::Page] the page, decoded on demand
|
|
217
289
|
def data_page_v2(header, body)
|
|
218
290
|
dh = header.data_page_header_v2
|
|
219
291
|
n = dh.num_values
|
|
@@ -230,9 +302,20 @@ module Herringbone
|
|
|
230
302
|
PageStream::Page.new(n, defs, reps, values, conv)
|
|
231
303
|
end
|
|
232
304
|
|
|
305
|
+
# Bit width of levels up to a max level (memoized Integer#bit_length)
|
|
233
306
|
RLE_WIDTH = Hash.new { |h, k| h[k] = k.bit_length }
|
|
234
307
|
|
|
235
308
|
# [decoder, position after the levels]
|
|
309
|
+
#
|
|
310
|
+
# @param data [String] the uncompressed page
|
|
311
|
+
# @param pos [Integer] offset of the levels in +data+
|
|
312
|
+
# @param encoding [Integer] Format::Encoding of the levels (RLE or BIT_PACKED)
|
|
313
|
+
# @param max [Integer] max level of the column, which sets the bit width
|
|
314
|
+
# @param n [Integer] number of entries in the page
|
|
315
|
+
# @return [Array(PageStream::HybridDecoder, Integer), Array(PageStream::ArrayDecoder, Integer)]
|
|
316
|
+
# the decoder and the offset just past the levels
|
|
317
|
+
# @raise [FormatError] when the RLE length prefix is cut off
|
|
318
|
+
# @raise [UnsupportedError] for any other level encoding
|
|
236
319
|
def level_decoder(data, pos, encoding, max, n)
|
|
237
320
|
width = RLE_WIDTH[max]
|
|
238
321
|
case encoding
|
|
@@ -248,11 +331,20 @@ module Herringbone
|
|
|
248
331
|
end
|
|
249
332
|
end
|
|
250
333
|
|
|
334
|
+
# Physical type => [String#unpack directive, byte width] for PLAIN fixed-width values
|
|
251
335
|
FIXED_FORMATS = {
|
|
252
336
|
T::INT32 => ["l<", 4], T::INT64 => ["q<", 8], T::FLOAT => ["e", 4], T::DOUBLE => ["E", 8]
|
|
253
337
|
}.freeze
|
|
254
338
|
|
|
255
339
|
# [value decoder, converter still to apply to its values (nil for dictionary pages)]
|
|
340
|
+
#
|
|
341
|
+
# @param data [String] the uncompressed values section
|
|
342
|
+
# @param pos [Integer] offset of the values in +data+
|
|
343
|
+
# @param encoding [Integer] Format::Encoding of the values
|
|
344
|
+
# @return [Array(Object, Proc)] a PageStream decoder (responds to #read) and the converter,
|
|
345
|
+
# which may be nil
|
|
346
|
+
# @raise [FormatError] for a dictionary-encoded page in a chunk without a dictionary page
|
|
347
|
+
# @raise [UnsupportedError] for an encoding not valid for the column's type, or unknown
|
|
256
348
|
def value_decoder(data, pos, encoding)
|
|
257
349
|
type = @column.type
|
|
258
350
|
decoder = case encoding
|
|
@@ -272,7 +364,7 @@ module Herringbone
|
|
|
272
364
|
raise UnsupportedError, "RLE value encoding is only supported for BOOLEAN" unless type == T::BOOLEAN
|
|
273
365
|
PageStream::RleBooleanDecoder.new(data, pos)
|
|
274
366
|
when E::DELTA_BINARY_PACKED
|
|
275
|
-
bits = type == T::INT32 ? 32 : 64
|
|
367
|
+
bits = (type == T::INT32) ? 32 : 64
|
|
276
368
|
PageStream::ArrayDecoder.new(Encodings::Delta.decode_binary_packed(data, pos, bits).first)
|
|
277
369
|
when E::DELTA_LENGTH_BYTE_ARRAY
|
|
278
370
|
PageStream::DeltaLengthDecoder.new(data, pos)
|
|
@@ -11,6 +11,7 @@ module Herringbone
|
|
|
11
11
|
# Levels decoded ahead at a time when looking for row starts in repeated columns
|
|
12
12
|
LOOKAHEAD = 4096
|
|
13
13
|
|
|
14
|
+
# @param chunk_reader [ColumnChunkReader] reader of the chunk, positioned at its start
|
|
14
15
|
def initialize(chunk_reader)
|
|
15
16
|
@src = chunk_reader
|
|
16
17
|
col = chunk_reader.column
|
|
@@ -26,12 +27,17 @@ module Herringbone
|
|
|
26
27
|
@page_idx = -1 # index of the current data page within the chunk
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
#
|
|
30
|
+
# @return [Integer] rows handed out or skipped so far (the chunk row the cursor is at)
|
|
30
31
|
attr_reader :row
|
|
31
32
|
|
|
32
33
|
# Moves forward to row +target+ of the chunk (0-based). With an OffsetIndex, pages before
|
|
33
34
|
# the one holding +target+ are not read at all; otherwise rows are skipped page by page,
|
|
34
35
|
# decoding levels but not building values.
|
|
36
|
+
#
|
|
37
|
+
# @param target [Integer] row of the chunk to stop at
|
|
38
|
+
# @return [void]
|
|
39
|
+
# @raise [ArgumentError] when +target+ is before the current row
|
|
40
|
+
# @raise [FormatError] when the chunk runs out of pages before +target+
|
|
35
41
|
def seek(target)
|
|
36
42
|
raise ArgumentError, "Cannot seek backwards (at row #{@row}, asked for #{target})" if target < @row
|
|
37
43
|
return if target == @row
|
|
@@ -53,6 +59,10 @@ module Herringbone
|
|
|
53
59
|
end
|
|
54
60
|
|
|
55
61
|
# Moves past the next +k+ rows without building their values
|
|
62
|
+
#
|
|
63
|
+
# @param k [Integer] number of rows to skip; zero or less does nothing
|
|
64
|
+
# @return [void]
|
|
65
|
+
# @raise [FormatError] when the chunk runs out of pages
|
|
56
66
|
def skip(k)
|
|
57
67
|
return if k <= 0
|
|
58
68
|
@repeated ? take_repeated(k, false) : take_flat(k, false)
|
|
@@ -62,6 +72,11 @@ module Herringbone
|
|
|
62
72
|
|
|
63
73
|
# [definition_levels, repetition_levels, values] of the next +k+ rows. Levels are nil
|
|
64
74
|
# when the column's max level is 0.
|
|
75
|
+
#
|
|
76
|
+
# @param k [Integer] number of rows to hand out
|
|
77
|
+
# @return [Array(Array<Integer>, Array<Integer>, Array)] definition levels, repetition
|
|
78
|
+
# levels and the (converted) values of the non-null entries
|
|
79
|
+
# @raise [FormatError] when the chunk runs out of pages or does not start at a row boundary
|
|
65
80
|
def take(k)
|
|
66
81
|
pieces = @repeated ? take_repeated(k) : take_flat(k)
|
|
67
82
|
@row += k
|
|
@@ -79,6 +94,13 @@ module Herringbone
|
|
|
79
94
|
|
|
80
95
|
private
|
|
81
96
|
|
|
97
|
+
# Non-repeated columns, where every entry is a row: reads page by page until +k+ entries
|
|
98
|
+
#
|
|
99
|
+
# @param k [Integer] number of rows
|
|
100
|
+
# @param keep [Boolean] false to skip the values instead of decoding them
|
|
101
|
+
# @return [Array<Array(Array<Integer>, nil, Array)>] [defs, nil, values] per page touched;
|
|
102
|
+
# empty when +keep+ is false
|
|
103
|
+
# @raise [FormatError] when the chunk runs out of pages
|
|
82
104
|
def take_flat(k, keep = true)
|
|
83
105
|
pieces = []
|
|
84
106
|
while k > 0
|
|
@@ -99,6 +121,12 @@ module Herringbone
|
|
|
99
121
|
|
|
100
122
|
# Collects entries until +k+ rows have started and the next row start (or the end of the
|
|
101
123
|
# column) is reached. Values are read from a page before moving on to the next one.
|
|
124
|
+
#
|
|
125
|
+
# @param k [Integer] number of rows
|
|
126
|
+
# @param keep [Boolean] false to skip the values instead of decoding them
|
|
127
|
+
# @return [Array<Array(Array<Integer>, Array<Integer>, Array)>] [defs, reps, values] per
|
|
128
|
+
# page touched (defs nil without definition levels); empty when +keep+ is false
|
|
129
|
+
# @raise [FormatError] when the first page does not start at a row boundary
|
|
102
130
|
def take_repeated(k, keep = true)
|
|
103
131
|
pieces = []
|
|
104
132
|
rows = 0
|
|
@@ -106,7 +134,7 @@ module Herringbone
|
|
|
106
134
|
reps = []
|
|
107
135
|
while true
|
|
108
136
|
if @bi >= @br.to_a.size
|
|
109
|
-
if @page
|
|
137
|
+
if @page&.remaining&.positive?
|
|
110
138
|
@bd, @br = @page.read_levels(LOOKAHEAD)
|
|
111
139
|
@bi = 0
|
|
112
140
|
else
|
|
@@ -147,6 +175,12 @@ module Herringbone
|
|
|
147
175
|
end
|
|
148
176
|
|
|
149
177
|
# Reads (or skips) the values belonging to the collected entries of the current page
|
|
178
|
+
#
|
|
179
|
+
# @param pieces [Array<Array>] output list a [defs, reps, values] piece is appended to
|
|
180
|
+
# @param defs [Array<Integer>, nil] collected definition levels (nil without them)
|
|
181
|
+
# @param reps [Array<Integer>] collected repetition levels; nothing happens when empty
|
|
182
|
+
# @param keep [Boolean] false to skip the values instead of appending a piece
|
|
183
|
+
# @return [void]
|
|
150
184
|
def flush(pieces, defs, reps, keep)
|
|
151
185
|
return if reps.empty?
|
|
152
186
|
nv = defs ? defs.count(@max_def) : reps.size
|
|
@@ -157,18 +191,28 @@ module Herringbone
|
|
|
157
191
|
end
|
|
158
192
|
end
|
|
159
193
|
|
|
194
|
+
# The next +n+ values of the current page, converted when the page has a converter
|
|
195
|
+
#
|
|
196
|
+
# @param n [Integer] number of values (non-null entries)
|
|
197
|
+
# @return [Array] Ruby values
|
|
160
198
|
def values(n)
|
|
161
199
|
vals = @page.read_values(n)
|
|
162
200
|
conv = @page.converter
|
|
163
201
|
conv ? vals.map!(&conv) : vals
|
|
164
202
|
end
|
|
165
203
|
|
|
204
|
+
# Like #load_page, but running out of pages is an error
|
|
205
|
+
#
|
|
206
|
+
# @return [void]
|
|
207
|
+
# @raise [FormatError] when the chunk has no more data pages
|
|
166
208
|
def load_page!
|
|
167
209
|
return if load_page
|
|
168
210
|
raise FormatError, "Column #{@path}: ran out of pages after #{@src.seen} of #{@src.total} values"
|
|
169
211
|
end
|
|
170
212
|
|
|
171
213
|
# Moves to the next data page; false at the end of the chunk
|
|
214
|
+
#
|
|
215
|
+
# @return [Boolean] whether a page was loaded
|
|
172
216
|
def load_page
|
|
173
217
|
@page = @src.next_stream or return false
|
|
174
218
|
@page_idx += 1
|