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
data/lib/herringbone/schema.rb
CHANGED
|
@@ -176,53 +176,34 @@ module Herringbone
|
|
|
176
176
|
new(root)
|
|
177
177
|
end
|
|
178
178
|
|
|
179
|
-
# Builds a schema with the DSL
|
|
180
|
-
|
|
181
|
-
# Schema.define(id: {type: :int64, null: false}, name: :string, tags: [:string],
|
|
182
|
-
# address: {city: :string, zip: :string}, price: {type: :decimal, precision: 10, scale: 2})
|
|
183
|
-
#
|
|
184
|
-
# Hash values: a type Symbol; [element] for a list; a Hash with :type (plus DSL options such as
|
|
185
|
-
# null:, precision:, of: for lists, key:/value: for maps); or a Hash without :type for a struct.
|
|
186
|
-
def self.define(spec = nil, &block)
|
|
179
|
+
# Builds a schema with the DSL, see Schema::Builder
|
|
180
|
+
def self.define(&block)
|
|
187
181
|
builder = Builder.new
|
|
188
|
-
builder.fields(spec) if spec
|
|
189
182
|
builder.instance_eval(&block) if block
|
|
190
183
|
raise ArgumentError, "A schema needs at least one field" if builder.nodes.empty?
|
|
191
184
|
new(Node.new(name: "schema", repetition: :required, children: builder.nodes))
|
|
192
185
|
end
|
|
193
186
|
|
|
194
|
-
|
|
195
|
-
def self.coerce(schema)
|
|
196
|
-
case schema
|
|
197
|
-
when Schema then schema
|
|
198
|
-
when Hash then define(schema)
|
|
199
|
-
else raise ArgumentError, "Expected a Herringbone::Schema or a Hash, got #{schema.class}"
|
|
200
|
-
end
|
|
201
|
-
end
|
|
187
|
+
INFER_SAMPLE = 1000
|
|
202
188
|
|
|
203
|
-
# Infers a schema from
|
|
204
|
-
# All fields are nullable. Integer -> int64, Integer mixed with Float -> double,
|
|
189
|
+
# Infers a schema from the first 1000 rows (Hashes, or objects responding to #attributes or
|
|
190
|
+
# #to_h). All fields are nullable. Integer -> int64, Integer mixed with Float -> double,
|
|
205
191
|
# String/Symbol -> string (binary if not valid UTF-8), true/false -> boolean,
|
|
206
192
|
# Time/DateTime -> timestamp(micros), Date -> date, BigDecimal -> decimal(38, max scale seen),
|
|
207
193
|
# Hash -> struct, Array -> list. Columns that are nil in every sampled row become strings.
|
|
208
|
-
#
|
|
209
|
-
# Schema.infer(rows
|
|
210
|
-
def self.infer(rows,
|
|
211
|
-
sample_rows = rows.first(
|
|
194
|
+
# Fields declared in the block (Builder DSL) replace the inferred ones of the same name:
|
|
195
|
+
# Schema.infer(rows) { json :payload }
|
|
196
|
+
def self.infer(rows, &block)
|
|
197
|
+
sample_rows = rows.first(INFER_SAMPLE).map { |r| Inference.row_hash(r) }
|
|
212
198
|
raise ArgumentError, "Cannot infer a schema from zero rows" if sample_rows.empty?
|
|
213
|
-
overrides =
|
|
199
|
+
overrides = Builder.new
|
|
200
|
+
overrides.instance_eval(&block) if block
|
|
201
|
+
declared = overrides.nodes.to_h { |n| [n.name, n] }
|
|
214
202
|
names = sample_rows.flat_map { |r| r.keys.map(&:to_s) }.uniq
|
|
215
203
|
nodes = names.map do |name|
|
|
216
|
-
|
|
217
|
-
builder = Builder.new
|
|
218
|
-
builder.fields(name => overrides[name])
|
|
219
|
-
builder.nodes.first
|
|
220
|
-
else
|
|
221
|
-
values = sample_rows.map { |r| r.fetch(name) { r[name.to_sym] } }
|
|
222
|
-
Inference.node_for(name, values)
|
|
223
|
-
end
|
|
204
|
+
declared.delete(name) || Inference.node_for(name, sample_rows.map { |r| r.fetch(name) { r[name.to_sym] } })
|
|
224
205
|
end
|
|
225
|
-
new(Node.new(name: "schema", repetition: :required, children: nodes))
|
|
206
|
+
new(Node.new(name: "schema", repetition: :required, children: nodes + declared.values))
|
|
226
207
|
end
|
|
227
208
|
|
|
228
209
|
module Inference
|
|
@@ -274,7 +255,7 @@ module Herringbone
|
|
|
274
255
|
else
|
|
275
256
|
classes = values.map(&:class).uniq
|
|
276
257
|
raise ArgumentError, "Cannot infer a Parquet type for #{name} from #{classes.map(&:name).join(", ")}; " \
|
|
277
|
-
"
|
|
258
|
+
"declare it in a block: Schema.infer(rows) { string :#{name} }"
|
|
278
259
|
end
|
|
279
260
|
end
|
|
280
261
|
end
|
|
@@ -314,7 +295,12 @@ module Herringbone
|
|
|
314
295
|
else
|
|
315
296
|
"group"
|
|
316
297
|
end
|
|
317
|
-
|
|
298
|
+
# A logical type this version does not know decodes as an empty union
|
|
299
|
+
kind = node.logical_type&.kind
|
|
300
|
+
ann = if kind then kind.first.to_s.upcase
|
|
301
|
+
elsif node.logical_type then "UNKNOWN LOGICAL TYPE"
|
|
302
|
+
else Format::ConvertedType::NAMES[node.converted_type]
|
|
303
|
+
end
|
|
318
304
|
lines << "#{" " * depth}#{node.repetition} #{desc} #{node.name}#{ann ? " (#{ann})" : ""}"
|
|
319
305
|
node.children&.each { |c| walk.call(c, depth + 1) }
|
|
320
306
|
end
|
|
@@ -470,47 +456,6 @@ module Herringbone
|
|
|
470
456
|
column(name, :enum, values: values, parquet_enum: parquet_enum, **opts)
|
|
471
457
|
end
|
|
472
458
|
|
|
473
|
-
# Declares fields from a Hash spec, see Schema.define
|
|
474
|
-
def fields(spec)
|
|
475
|
-
spec.each { |name, desc| declare(name, desc) }
|
|
476
|
-
self
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
# Declares one field from a Hash spec value
|
|
480
|
-
def declare(name, desc, null: true)
|
|
481
|
-
case desc
|
|
482
|
-
when Symbol, String
|
|
483
|
-
column(name, desc.to_sym, null: null)
|
|
484
|
-
when Array
|
|
485
|
-
raise ArgumentError, "List spec for #{name} must have exactly one element type" unless desc.size == 1
|
|
486
|
-
element = desc.first
|
|
487
|
-
list(name, null: null) { declare(:element, element) }
|
|
488
|
-
when Hash
|
|
489
|
-
opts = desc.to_h { |k, v| [k.to_sym, v] }
|
|
490
|
-
type = opts.delete(:type)
|
|
491
|
-
null = opts.delete(:null) { null }
|
|
492
|
-
# A Hash without :type is a struct; its keys (other than null:) are the fields
|
|
493
|
-
return struct(name, null: null) { fields(opts) } if type.nil?
|
|
494
|
-
case type.to_sym
|
|
495
|
-
when :list
|
|
496
|
-
of = opts.delete(:of) or raise ArgumentError, "list #{name} needs of:"
|
|
497
|
-
element_null = opts.delete(:element_null) { true }
|
|
498
|
-
list(name, null: null, **opts) { declare(:element, of, null: element_null) }
|
|
499
|
-
when :map
|
|
500
|
-
key = opts.delete(:key) { :string }
|
|
501
|
-
value = opts.delete(:value) or raise ArgumentError, "map #{name} needs value:"
|
|
502
|
-
value_null = opts.delete(:value_null) { true }
|
|
503
|
-
map(name, key, null: null, **opts) { declare(:value, value, null: value_null) }
|
|
504
|
-
when :struct
|
|
505
|
-
struct(name, null: null) { fields(opts.fetch(:fields)) }
|
|
506
|
-
else
|
|
507
|
-
column(name, type.to_sym, null: null, **opts)
|
|
508
|
-
end
|
|
509
|
-
else
|
|
510
|
-
raise ArgumentError, "Cannot declare #{name} from #{desc.inspect}"
|
|
511
|
-
end
|
|
512
|
-
end
|
|
513
|
-
|
|
514
459
|
private
|
|
515
460
|
|
|
516
461
|
def add(node)
|
data/lib/herringbone/types.rb
CHANGED
|
@@ -53,7 +53,7 @@ module Herringbone
|
|
|
53
53
|
# Physical attributes (type, logical type etc.) for a DSL type name
|
|
54
54
|
def physical_attributes(type, **opts)
|
|
55
55
|
case type.to_sym
|
|
56
|
-
when :boolean
|
|
56
|
+
when :boolean then { type: T::BOOLEAN }
|
|
57
57
|
when :int8 then int_type(8, true)
|
|
58
58
|
when :int16 then int_type(16, true)
|
|
59
59
|
when :int32 then { type: T::INT32 }
|
|
@@ -65,8 +65,8 @@ module Herringbone
|
|
|
65
65
|
when :float then { type: T::FLOAT }
|
|
66
66
|
when :double then { type: T::DOUBLE }
|
|
67
67
|
when :float16 then { type: T::FIXED_LEN_BYTE_ARRAY, type_length: 2, logical_type: lt(float16: Format::Float16Type.new) }
|
|
68
|
-
when :string
|
|
69
|
-
when :binary
|
|
68
|
+
when :string then { type: T::BYTE_ARRAY, converted_type: C::UTF8, logical_type: lt(string: Format::StringType.new) }
|
|
69
|
+
when :binary then { type: T::BYTE_ARRAY }
|
|
70
70
|
when :json then { type: T::BYTE_ARRAY, converted_type: C::JSON, logical_type: lt(json: Format::JsonType.new) }
|
|
71
71
|
when :bson then { type: T::BYTE_ARRAY, converted_type: C::BSON, logical_type: lt(bson: Format::BsonType.new) }
|
|
72
72
|
when :enum
|
|
@@ -287,7 +287,7 @@ module Herringbone
|
|
|
287
287
|
if !b && (type == T::INT32 || type == T::INT64)
|
|
288
288
|
bits = type == T::INT32 ? 32 : 64
|
|
289
289
|
check = int_checker(0, (1 << a) - 1)
|
|
290
|
-
return ->(v) { Delta.wrap(check.call(v), bits) }
|
|
290
|
+
return ->(v) { Encodings::Delta.wrap(check.call(v), bits) }
|
|
291
291
|
elsif type == T::INT32 || type == T::INT64
|
|
292
292
|
return int_checker(-(1 << (a - 1)), (1 << (a - 1)) - 1)
|
|
293
293
|
end
|
data/lib/herringbone/version.rb
CHANGED