herringbone 0.9.1 → 1.0.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/CHANGELOG.md +17 -0
- data/MANUAL.md +46 -20
- data/README.md +10 -9
- data/lib/herringbone/compression.rb +9 -1
- data/lib/herringbone/encryption_configuration.rb +1 -1
- data/lib/herringbone/inferring_writer.rb +59 -17
- data/lib/herringbone/inspector.rb +2 -0
- data/lib/herringbone/key.rb +2 -3
- data/lib/herringbone/reader/column_chunk_reader.rb +2 -1
- data/lib/herringbone/reader.rb +59 -27
- data/lib/herringbone/redaction.rb +7 -0
- data/lib/herringbone/schema.rb +5 -1
- data/lib/herringbone/thrift.rb +5 -2
- data/lib/herringbone/version.rb +1 -1
- data/lib/herringbone/writer.rb +25 -6
- data/lib/herringbone.rb +23 -7
- data/rbi/herringbone.rbi +12003 -0
- data/sig/herringbone.rbs +10521 -0
- data/sig/manifest.yaml +2 -0
- metadata +12 -4
- data/lib/herringbone/simple_writer.rb +0 -190
data/lib/herringbone/writer.rb
CHANGED
|
@@ -86,8 +86,18 @@ module Herringbone
|
|
|
86
86
|
# returns and the block's value is returned; if the block raises, the writer is aborted and no
|
|
87
87
|
# footer is written. Without a block, call #close to finish. The IO is never closed.
|
|
88
88
|
#
|
|
89
|
+
# Without a schema, the column types are inferred from the first Schema::INFER_SAMPLE rows
|
|
90
|
+
# (see InferringWriter), and +headers:+ names the columns of Array rows, as with the CSV gem:
|
|
91
|
+
#
|
|
92
|
+
# Herringbone::Writer.open(io, headers: [:id, :name, :age]) do |w|
|
|
93
|
+
# w << [123, "John", 12]
|
|
94
|
+
# w << {id: 124, name: "Jane"} # Hashes work too; missing columns are nulls
|
|
95
|
+
# end
|
|
96
|
+
#
|
|
89
97
|
# @param io [IO, #write] destination
|
|
90
|
-
# @param schema [Schema] schema of the rows
|
|
98
|
+
# @param schema [Schema, nil] schema of the rows; inferred from them when nil
|
|
99
|
+
# @param headers [Array<String, Symbol>, nil] column names, in the order Array rows list their
|
|
100
|
+
# values, when the schema is inferred
|
|
91
101
|
# @param options [Hash{Symbol => Object}] see the class description and #initialize
|
|
92
102
|
# @option options [Symbol] :compression (:snappy) codec, see Herringbone.codecs
|
|
93
103
|
# @option options [Integer, nil] :compression_level (nil) level for :zstd, :gzip or :brotli
|
|
@@ -106,12 +116,21 @@ module Herringbone
|
|
|
106
116
|
# @option options [EncryptionConfiguration, Hash{Symbol => Object}, nil] :encryption (nil) modular
|
|
107
117
|
# encryption settings, see #initialize
|
|
108
118
|
# @yield [writer] the open writer
|
|
109
|
-
# @yieldparam writer [Writer] writer to append rows to
|
|
119
|
+
# @yieldparam writer [Writer, InferringWriter] writer to append rows to
|
|
110
120
|
# @yieldreturn [Object] returned by open
|
|
111
|
-
# @return [Writer, Object] the writer without a block, the block's value with one
|
|
112
|
-
# @raise [ArgumentError] for an invalid IO, schema or option
|
|
113
|
-
def self.open(io, schema, **options)
|
|
114
|
-
|
|
121
|
+
# @return [Writer, InferringWriter, Object] the writer without a block, the block's value with one
|
|
122
|
+
# @raise [ArgumentError] for an invalid IO, schema or option, or +headers:+ given with a schema
|
|
123
|
+
def self.open(io, schema = nil, headers: nil, **options)
|
|
124
|
+
if schema && headers
|
|
125
|
+
raise ArgumentError, "headers: names the columns of Array rows whose types are inferred, but the " \
|
|
126
|
+
"schema already names the columns (Array rows list their values in schema order). Pass one or the other."
|
|
127
|
+
end
|
|
128
|
+
writer = if schema
|
|
129
|
+
new(io, schema, **options)
|
|
130
|
+
else
|
|
131
|
+
fix = "Herringbone::Writer.open(io, Herringbone::Schema.define { |s| ...; s.%s })"
|
|
132
|
+
InferringWriter.new(io, fix: fix, headers: headers, **options) { |sample| Schema.infer(sample) }
|
|
133
|
+
end
|
|
115
134
|
return writer unless block_given?
|
|
116
135
|
begin
|
|
117
136
|
result = yield writer
|
data/lib/herringbone.rb
CHANGED
|
@@ -32,8 +32,8 @@ module Herringbone
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# A row does not fit a schema that was inferred from earlier rows (Herringbone.write without
|
|
35
|
-
# +schema:+,
|
|
36
|
-
# declare the column instead.
|
|
35
|
+
# +schema:+, Writer.open without a schema). The message says what was inferred, from how many
|
|
36
|
+
# rows, and how to declare the column instead.
|
|
37
37
|
class SchemaMismatch < EncodeError; end
|
|
38
38
|
|
|
39
39
|
# The file (or the writer configuration) uses a Parquet feature Herringbone does not implement,
|
|
@@ -99,7 +99,6 @@ module Herringbone
|
|
|
99
99
|
autoload :Redaction, "#{LIB}/redaction"
|
|
100
100
|
autoload :RestrictedReadableIO, "#{LIB}/restricted_readable_io"
|
|
101
101
|
autoload :Schema, "#{LIB}/schema"
|
|
102
|
-
autoload :SimpleWriter, "#{LIB}/simple_writer"
|
|
103
102
|
autoload :Thrift, "#{LIB}/thrift"
|
|
104
103
|
autoload :Types, "#{LIB}/types"
|
|
105
104
|
autoload :Visualizer, "#{LIB}/visualizer"
|
|
@@ -153,10 +152,21 @@ module Herringbone
|
|
|
153
152
|
# File.open("orders.parquet", "wb") { |f| Herringbone.write(f, Order.where(created_at: 1.year.ago..)) }
|
|
154
153
|
# Herringbone.write(io, events.lazy.map(&:to_h)) { |s| s.json :payload }
|
|
155
154
|
#
|
|
155
|
+
# +headers:+ names the columns of Array rows, as with the CSV gem, and the types are inferred.
|
|
156
|
+
# Rows produced in a loop can be given through an Enumerator:
|
|
157
|
+
#
|
|
158
|
+
# rows = Enumerator.new do |y|
|
|
159
|
+
# y << [123, "John", 12]
|
|
160
|
+
# y << {id: 124, name: "Jane"} # Hashes work too; missing columns are nulls
|
|
161
|
+
# end
|
|
162
|
+
# Herringbone.write(io, rows, headers: [:id, :name, :age])
|
|
163
|
+
#
|
|
156
164
|
# @param io [IO, #write] destination; written sequentially, never closed
|
|
157
165
|
# @param records [Enumerable<Hash, Array, Object>, Class, #find_each] rows (Hashes, Arrays in schema
|
|
158
166
|
# order, or objects responding to #attributes or #to_h), or an ActiveRecord model or relation
|
|
159
167
|
# @param schema [Schema, nil] schema to write with; derived from +records+ when nil
|
|
168
|
+
# @param headers [Array<String, Symbol>, nil] column names, in the order Array rows list their
|
|
169
|
+
# values; the types are inferred, so it can't be combined with +schema+
|
|
160
170
|
# @param options [Hash{Symbol => Object}] passed to Writer.new
|
|
161
171
|
# @option options [Symbol] :compression (:snappy) codec, see Herringbone.codecs
|
|
162
172
|
# @option options [Integer, nil] :compression_level (nil) level for :zstd, :gzip or :brotli
|
|
@@ -178,20 +188,26 @@ module Herringbone
|
|
|
178
188
|
# @yieldreturn [void]
|
|
179
189
|
# @return [Integer] number of rows written
|
|
180
190
|
# @raise [ArgumentError] when the schema has to be inferred and +records+ is empty or holds Array
|
|
181
|
-
# rows
|
|
191
|
+
# rows without +headers+, +headers+ is given with +schema+ or an ActiveRecord source, a row does
|
|
192
|
+
# not match the headers, an option is invalid, or the block takes no parameter
|
|
182
193
|
# @raise [EncodeError] when a row does not fit the schema
|
|
183
194
|
# @raise [SchemaMismatch] when a row does not fit the inferred schema; the file is left unfinished
|
|
184
|
-
def write(io, records, schema: nil, **options, &overrides)
|
|
195
|
+
def write(io, records, schema: nil, headers: nil, **options, &overrides)
|
|
185
196
|
Schema::Builder.check_block!(overrides, "Herringbone.write(io, rows) { |s| s.json :payload }")
|
|
186
197
|
model = if records.respond_to?(:klass) then records.klass
|
|
187
198
|
elsif records.respond_to?(:columns) && records.respond_to?(:find_each) then records
|
|
188
199
|
end
|
|
200
|
+
if headers && (schema || model)
|
|
201
|
+
raise ArgumentError, "headers: names the columns of Array rows whose types are inferred, but " \
|
|
202
|
+
"#{schema ? "schema: already names the columns (Array rows list their values in schema order)" : "an ActiveRecord source has its own columns"}. " \
|
|
203
|
+
"Pass one or the other."
|
|
204
|
+
end
|
|
189
205
|
schema ||= Schema.from_active_record(model) if model
|
|
190
206
|
writer = if schema
|
|
191
207
|
Writer.new(io, schema, **options)
|
|
192
208
|
else
|
|
193
|
-
fix = "Herringbone.write(io, rows) { |s| s.%s }"
|
|
194
|
-
InferringWriter.new(io, fix: fix, **options) { |sample| Schema.infer(sample, &overrides) }
|
|
209
|
+
fix = "Herringbone.write(io, rows#{", headers: [...]" if headers}) { |s| s.%s }"
|
|
210
|
+
InferringWriter.new(io, fix: fix, headers: headers, **options) { |sample| Schema.infer(sample, &overrides) }
|
|
195
211
|
end
|
|
196
212
|
begin
|
|
197
213
|
if records.respond_to?(:find_each)
|