avro 1.10.1 → 1.11.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/avro/schema.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
@@ -29,13 +30,17 @@ module Avro
29
30
  NAMED_TYPES_SYM = Set.new(NAMED_TYPES.map(&:to_sym))
30
31
  VALID_TYPES_SYM = Set.new(VALID_TYPES.map(&:to_sym))
31
32
 
32
- NAME_REGEX = /^([A-Za-z_][A-Za-z0-9_]*)(\.([A-Za-z_][A-Za-z0-9_]*))*$/
33
+ NAME_REGEX = /^([A-Za-z_][A-Za-z0-9_]*)(\.([A-Za-z_][A-Za-z0-9_]*))*$/.freeze
33
34
 
34
35
  INT_MIN_VALUE = -(1 << 31)
35
36
  INT_MAX_VALUE = (1 << 31) - 1
36
37
  LONG_MIN_VALUE = -(1 << 63)
37
38
  LONG_MAX_VALUE = (1 << 63) - 1
38
39
 
40
+ DEFAULT_VALIDATE_OPTIONS = { recursive: true, encoded: false }.freeze
41
+
42
+ DECIMAL_LOGICAL_TYPE = 'decimal'
43
+
39
44
  def self.parse(json_string)
40
45
  real_parse(MultiJson.load(json_string), {})
41
46
  end
@@ -73,7 +78,9 @@ module Avro
73
78
  case type_sym
74
79
  when :fixed
75
80
  size = json_obj['size']
76
- return FixedSchema.new(name, namespace, size, names, logical_type, aliases)
81
+ precision = json_obj['precision']
82
+ scale = json_obj['scale']
83
+ return FixedSchema.new(name, namespace, size, names, logical_type, aliases, precision, scale)
77
84
  when :enum
78
85
  symbols = json_obj['symbols']
79
86
  doc = json_obj['doc']
@@ -109,7 +116,7 @@ module Avro
109
116
  end
110
117
 
111
118
  # Determine if a ruby datum is an instance of a schema
112
- def self.validate(expected_schema, logical_datum, options = { recursive: true, encoded: false })
119
+ def self.validate(expected_schema, logical_datum, options = DEFAULT_VALIDATE_OPTIONS)
113
120
  SchemaValidator.validate!(expected_schema, logical_datum, options)
114
121
  true
115
122
  rescue SchemaValidator::ValidationError
@@ -129,7 +136,7 @@ module Avro
129
136
  def type; @type_sym.to_s; end
130
137
 
131
138
  def type_adapter
132
- @type_adapter ||= LogicalTypes.type_adapter(type, logical_type) || LogicalTypes::Identity
139
+ @type_adapter ||= LogicalTypes.type_adapter(type, logical_type, self) || LogicalTypes::Identity
133
140
  end
134
141
 
135
142
  # Returns the MD5 fingerprint of the schema as an Integer.
@@ -173,7 +180,7 @@ module Avro
173
180
  fp
174
181
  end
175
182
 
176
- SINGLE_OBJECT_MAGIC_NUMBER = [0xC3, 0x01]
183
+ SINGLE_OBJECT_MAGIC_NUMBER = [0xC3, 0x01].freeze
177
184
  def single_object_encoding_header
178
185
  [SINGLE_OBJECT_MAGIC_NUMBER, single_object_schema_fingerprint].flatten
179
186
  end
@@ -281,6 +288,10 @@ module Avro
281
288
  def match_fullname?(name)
282
289
  name == fullname || fullname_aliases.include?(name)
283
290
  end
291
+
292
+ def match_schema?(schema)
293
+ type_sym == schema.type_sym && match_fullname?(schema.fullname)
294
+ end
284
295
  end
285
296
 
286
297
  class RecordSchema < NamedSchema
@@ -411,7 +422,7 @@ module Avro
411
422
  end
412
423
 
413
424
  class EnumSchema < NamedSchema
414
- SYMBOL_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/
425
+ SYMBOL_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/.freeze
415
426
 
416
427
  attr_reader :symbols, :doc, :default
417
428
 
@@ -465,14 +476,27 @@ module Avro
465
476
  hsh = super
466
477
  hsh.size == 1 ? type : hsh
467
478
  end
479
+
480
+ def match_schema?(schema)
481
+ return type_sym == schema.type_sym
482
+ # TODO: eventually this could handle schema promotion for primitive schemas too
483
+ end
468
484
  end
469
485
 
470
486
  class BytesSchema < PrimitiveSchema
487
+ ERROR_INVALID_SCALE = 'Scale must be greater than or equal to 0'
488
+ ERROR_INVALID_PRECISION = 'Precision must be positive'
489
+ ERROR_PRECISION_TOO_SMALL = 'Precision must be greater than scale'
490
+
471
491
  attr_reader :precision, :scale
492
+
472
493
  def initialize(type, logical_type=nil, precision=nil, scale=nil)
473
494
  super(type.to_sym, logical_type)
474
- @precision = precision
475
- @scale = scale
495
+
496
+ @precision = precision.to_i if precision
497
+ @scale = scale.to_i if scale
498
+
499
+ validate_decimal! if logical_type == DECIMAL_LOGICAL_TYPE
476
500
  end
477
501
 
478
502
  def to_avro(names=nil)
@@ -483,29 +507,64 @@ module Avro
483
507
  avro['scale'] = scale if scale
484
508
  avro
485
509
  end
510
+
511
+ def match_schema?(schema)
512
+ return true if super
513
+
514
+ if logical_type == DECIMAL_LOGICAL_TYPE && schema.logical_type == DECIMAL_LOGICAL_TYPE
515
+ return precision == schema.precision && (scale || 0) == (schema.scale || 0)
516
+ end
517
+
518
+ false
519
+ end
520
+
521
+ private
522
+
523
+ def validate_decimal!
524
+ raise Avro::SchemaParseError, ERROR_INVALID_PRECISION unless precision.to_i.positive?
525
+ raise Avro::SchemaParseError, ERROR_INVALID_SCALE if scale.to_i.negative?
526
+ raise Avro::SchemaParseError, ERROR_PRECISION_TOO_SMALL if precision < scale.to_i
527
+ end
486
528
  end
487
529
 
488
530
  class FixedSchema < NamedSchema
489
- attr_reader :size
490
- def initialize(name, space, size, names=nil, logical_type=nil, aliases=nil)
531
+ attr_reader :size, :precision, :scale
532
+ def initialize(name, space, size, names=nil, logical_type=nil, aliases=nil, precision=nil, scale=nil)
491
533
  # Ensure valid cto args
492
534
  unless size.is_a?(Integer)
493
535
  raise AvroError, 'Fixed Schema requires a valid integer for size property.'
494
536
  end
495
537
  super(:fixed, name, space, names, nil, logical_type, aliases)
496
538
  @size = size
539
+ @precision = precision
540
+ @scale = scale
497
541
  end
498
542
 
499
543
  def to_avro(names=Set.new)
500
544
  avro = super
501
- avro.is_a?(Hash) ? avro.merge('size' => size) : avro
545
+ return avro if avro.is_a?(String)
546
+
547
+ avro['size'] = size
548
+ avro['precision'] = precision if precision
549
+ avro['scale'] = scale if scale
550
+ avro
551
+ end
552
+
553
+ def match_schema?(schema)
554
+ return true if super && size == schema.size
555
+
556
+ if logical_type == DECIMAL_LOGICAL_TYPE && schema.logical_type == DECIMAL_LOGICAL_TYPE
557
+ return precision == schema.precision && (scale || 0) == (schema.scale || 0)
558
+ end
559
+
560
+ false
502
561
  end
503
562
  end
504
563
 
505
564
  class Field < Schema
506
565
  attr_reader :type, :name, :default, :order, :doc, :aliases
507
566
 
508
- def initialize(type, name, default=:no_default, order=nil, names=nil, namespace=nil, doc=nil, aliases=nil)
567
+ def initialize(type, name, default=:no_default, order=nil, names=nil, namespace=nil, doc=nil, aliases=nil) # rubocop:disable Lint/MissingSuper
509
568
  @type = subparse(type, names, namespace)
510
569
  @name = name
511
570
  @default = default
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
@@ -15,6 +16,9 @@
15
16
  # limitations under the License.
16
17
  module Avro
17
18
  module SchemaCompatibility
19
+ INT_COERCIBLE_TYPES_SYM = [:long, :float, :double].freeze
20
+ LONG_COERCIBLE_TYPES_SYM = [:float, :double].freeze
21
+
18
22
  # Perform a full, recursive check that a datum written using the writers_schema
19
23
  # can be read using the readers_schema.
20
24
  def self.can_read?(writers_schema, readers_schema)
@@ -31,6 +35,9 @@ module Avro
31
35
  # be read using the readers_schema. This check includes matching the types,
32
36
  # including schema promotion, and matching the full name (including aliases) for named types.
33
37
  def self.match_schemas(writers_schema, readers_schema)
38
+ # Bypass deeper checks if the schemas are the same Ruby objects
39
+ return true if writers_schema.equal?(readers_schema)
40
+
34
41
  w_type = writers_schema.type_sym
35
42
  r_type = readers_schema.type_sym
36
43
 
@@ -40,31 +47,25 @@ module Avro
40
47
  end
41
48
 
42
49
  if w_type == r_type
43
- return true if Schema::PRIMITIVE_TYPES_SYM.include?(r_type)
50
+ return readers_schema.match_schema?(writers_schema) if Schema::PRIMITIVE_TYPES_SYM.include?(r_type)
44
51
 
45
52
  case r_type
46
- when :record
47
- return readers_schema.match_fullname?(writers_schema.fullname)
48
- when :error
49
- return readers_schema.match_fullname?(writers_schema.fullname)
50
53
  when :request
51
54
  return true
52
- when :fixed
53
- return readers_schema.match_fullname?(writers_schema.fullname) &&
54
- writers_schema.size == readers_schema.size
55
- when :enum
56
- return readers_schema.match_fullname?(writers_schema.fullname)
57
55
  when :map
58
56
  return match_schemas(writers_schema.values, readers_schema.values)
59
57
  when :array
60
58
  return match_schemas(writers_schema.items, readers_schema.items)
59
+ else
60
+ return readers_schema.match_schema?(writers_schema)
61
61
  end
62
62
  end
63
63
 
64
64
  # Handle schema promotion
65
- if w_type == :int && [:long, :float, :double].include?(r_type)
65
+ # rubocop:disable Lint/DuplicateBranch
66
+ if w_type == :int && INT_COERCIBLE_TYPES_SYM.include?(r_type)
66
67
  return true
67
- elsif w_type == :long && [:float, :double].include?(r_type)
68
+ elsif w_type == :long && LONG_COERCIBLE_TYPES_SYM.include?(r_type)
68
69
  return true
69
70
  elsif w_type == :float && r_type == :double
70
71
  return true
@@ -73,8 +74,13 @@ module Avro
73
74
  elsif w_type == :bytes && r_type == :string
74
75
  return true
75
76
  end
77
+ # rubocop:enable Lint/DuplicateBranch
76
78
 
77
- return false
79
+ if readers_schema.respond_to?(:match_schema?)
80
+ readers_schema.match_schema?(writers_schema)
81
+ else
82
+ false
83
+ end
78
84
  end
79
85
 
80
86
  class Checker
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
@@ -16,22 +17,24 @@
16
17
 
17
18
  module Avro
18
19
  class SchemaValidator
19
- ROOT_IDENTIFIER = '.'.freeze
20
- PATH_SEPARATOR = '.'.freeze
21
- INT_RANGE = Schema::INT_MIN_VALUE..Schema::INT_MAX_VALUE
22
- LONG_RANGE = Schema::LONG_MIN_VALUE..Schema::LONG_MAX_VALUE
20
+ ROOT_IDENTIFIER = '.'
21
+ PATH_SEPARATOR = '.'
22
+ INT_RANGE = (Schema::INT_MIN_VALUE..Schema::INT_MAX_VALUE).freeze
23
+ LONG_RANGE = (Schema::LONG_MIN_VALUE..Schema::LONG_MAX_VALUE).freeze
23
24
  COMPLEX_TYPES = [:array, :error, :map, :record, :request].freeze
24
25
  BOOLEAN_VALUES = [true, false].freeze
26
+ DEFAULT_VALIDATION_OPTIONS = { recursive: true, encoded: false, fail_on_extra_fields: false }.freeze
27
+ RECURSIVE_SIMPLE_VALIDATION_OPTIONS = { encoded: true }.freeze
28
+ RUBY_CLASS_TO_AVRO_TYPE = {
29
+ NilClass => 'null',
30
+ String => 'string',
31
+ Float => 'float',
32
+ Hash => 'record'
33
+ }.freeze
25
34
 
26
35
  class Result
27
- attr_reader :errors
28
-
29
- def initialize
30
- @errors = []
31
- end
32
-
33
36
  def <<(error)
34
- @errors << error
37
+ errors << error
35
38
  end
36
39
 
37
40
  def add_error(path, message)
@@ -39,11 +42,16 @@ module Avro
39
42
  end
40
43
 
41
44
  def failure?
42
- @errors.any?
45
+ defined?(@errors) && errors.any?
43
46
  end
44
47
 
45
48
  def to_s
46
- errors.join("\n")
49
+ failure? ? errors.join("\n") : ''
50
+ end
51
+
52
+ def errors
53
+ # Use less memory for success results by lazily creating the errors array
54
+ @errors ||= []
47
55
  end
48
56
  end
49
57
 
@@ -63,12 +71,9 @@ module Avro
63
71
  TypeMismatchError = Class.new(ValidationError)
64
72
 
65
73
  class << self
66
- def validate!(expected_schema, logical_datum, options = { recursive: true, encoded: false, fail_on_extra_fields: false })
67
- options ||= {}
68
- options[:recursive] = true unless options.key?(:recursive)
69
-
74
+ def validate!(expected_schema, logical_datum, options = DEFAULT_VALIDATION_OPTIONS)
70
75
  result = Result.new
71
- if options[:recursive]
76
+ if options.fetch(:recursive, true)
72
77
  validate_recursive(expected_schema, logical_datum, ROOT_IDENTIFIER, result, options)
73
78
  else
74
79
  validate_simple(expected_schema, logical_datum, ROOT_IDENTIFIER, result, options)
@@ -79,10 +84,10 @@ module Avro
79
84
 
80
85
  private
81
86
 
82
- def validate_recursive(expected_schema, logical_datum, path, result, options = {})
87
+ def validate_recursive(expected_schema, logical_datum, path, result, options)
83
88
  datum = resolve_datum(expected_schema, logical_datum, options[:encoded])
84
89
 
85
- validate_simple(expected_schema, datum, path, result, encoded: true)
90
+ validate_simple(expected_schema, datum, path, result, RECURSIVE_SIMPLE_VALIDATION_OPTIONS)
86
91
 
87
92
  case expected_schema.type_sym
88
93
  when :array
@@ -95,7 +100,8 @@ module Avro
95
100
  fail TypeMismatchError unless datum.is_a?(Hash)
96
101
  expected_schema.fields.each do |field|
97
102
  deeper_path = deeper_path_for_hash(field.name, path)
98
- validate_recursive(field.type, datum[field.name], deeper_path, result, options)
103
+ nested_value = datum.key?(field.name) ? datum[field.name] : datum[field.name.to_sym]
104
+ validate_recursive(field.type, nested_value, deeper_path, result, options)
99
105
  end
100
106
  if options[:fail_on_extra_fields]
101
107
  datum_fields = datum.keys.map(&:to_s)
@@ -109,7 +115,7 @@ module Avro
109
115
  result.add_error(path, "expected type #{expected_schema.type_sym}, got #{actual_value_message(datum)}")
110
116
  end
111
117
 
112
- def validate_simple(expected_schema, logical_datum, path, result, options = {})
118
+ def validate_simple(expected_schema, logical_datum, path, result, options)
113
119
  datum = resolve_datum(expected_schema, logical_datum, options[:encoded])
114
120
  validate_type(expected_schema)
115
121
 
@@ -127,7 +133,7 @@ module Avro
127
133
  fail TypeMismatchError unless datum.is_a?(Integer)
128
134
  result.add_error(path, "out of bound value #{datum}") unless LONG_RANGE.cover?(datum)
129
135
  when :float, :double
130
- fail TypeMismatchError unless datum.is_a?(Float) || datum.is_a?(Integer)
136
+ fail TypeMismatchError unless datum.is_a?(Float) || datum.is_a?(Integer) || datum.is_a?(BigDecimal)
131
137
  when :fixed
132
138
  if datum.is_a? String
133
139
  result.add_error(path, fixed_string_message(expected_schema.size, datum)) unless datum.bytesize == expected_schema.size
@@ -163,14 +169,14 @@ module Avro
163
169
  "expected enum with values #{symbols}, got #{actual_value_message(datum)}"
164
170
  end
165
171
 
166
- def validate_array(expected_schema, datum, path, result, options = {})
172
+ def validate_array(expected_schema, datum, path, result, options)
167
173
  fail TypeMismatchError unless datum.is_a?(Array)
168
174
  datum.each_with_index do |d, i|
169
- validate_recursive(expected_schema.items, d, path + "[#{i}]", result, options)
175
+ validate_recursive(expected_schema.items, d, "#{path}[#{i}]", result, options)
170
176
  end
171
177
  end
172
178
 
173
- def validate_map(expected_schema, datum, path, result, options = {})
179
+ def validate_map(expected_schema, datum, path, result, options)
174
180
  fail TypeMismatchError unless datum.is_a?(Hash)
175
181
  datum.keys.each do |k|
176
182
  result.add_error(path, "unexpected key type '#{ruby_to_avro_type(k.class)}' in map") unless k.is_a?(String)
@@ -181,7 +187,7 @@ module Avro
181
187
  end
182
188
  end
183
189
 
184
- def validate_union(expected_schema, datum, path, result, options = {})
190
+ def validate_union(expected_schema, datum, path, result, options)
185
191
  if expected_schema.schemas.size == 1
186
192
  validate_recursive(expected_schema.schemas.first, datum, path, result, options)
187
193
  return
@@ -201,6 +207,9 @@ module Avro
201
207
 
202
208
  def first_compatible_type(datum, expected_schema, path, failures, options = {})
203
209
  expected_schema.schemas.find do |schema|
210
+ # Avoid expensive validation if we're just validating a nil
211
+ next datum.nil? if schema.type_sym == :null
212
+
204
213
  result = Result.new
205
214
  validate_recursive(schema, datum, path, result, options)
206
215
  failures << { type: schema.type_sym, result: result } if result.failure?
@@ -209,7 +218,9 @@ module Avro
209
218
  end
210
219
 
211
220
  def deeper_path_for_hash(sub_key, path)
212
- "#{path}#{PATH_SEPARATOR}#{sub_key}".squeeze(PATH_SEPARATOR)
221
+ deeper_path = +"#{path}#{PATH_SEPARATOR}#{sub_key}"
222
+ deeper_path.squeeze!(PATH_SEPARATOR)
223
+ deeper_path.freeze
213
224
  end
214
225
 
215
226
  def actual_value_message(value)
@@ -226,12 +237,7 @@ module Avro
226
237
  end
227
238
 
228
239
  def ruby_to_avro_type(ruby_class)
229
- {
230
- NilClass => 'null',
231
- String => 'string',
232
- Float => 'float',
233
- Hash => 'record'
234
- }.fetch(ruby_class, ruby_class)
240
+ RUBY_CLASS_TO_AVRO_TYPE.fetch(ruby_class, ruby_class)
235
241
  end
236
242
 
237
243
  def ruby_integer_to_avro_type(value)
data/lib/avro.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
data/test/case_finder.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
data/test/random_data.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
@@ -5,9 +6,9 @@
5
6
  # to you under the Apache License, Version 2.0 (the
6
7
  # "License"); you may not use this file except in compliance
7
8
  # with the License. You may obtain a copy of the License at
8
- #
9
+ #
9
10
  # https://www.apache.org/licenses/LICENSE-2.0
10
- #
11
+ #
11
12
  # Unless required by applicable law or agreed to in writing, software
12
13
  # distributed under the License is distributed on an "AS IS" BASIS,
13
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -74,7 +75,7 @@ class RandomData
74
75
  return nil if len == 0
75
76
  symbols[rand(len)]
76
77
  when :fixed
77
- f = ""
78
+ f = +""
78
79
  schm.size.times { f << BYTEPOOL[rand(BYTEPOOL.size), 1] }
79
80
  f
80
81
  end
@@ -95,7 +96,7 @@ class RandomData
95
96
  BYTEPOOL = '12345abcd'
96
97
 
97
98
  def randstr(chars=CHARPOOL, length=20)
98
- str = ''
99
+ str = +''
99
100
  rand(length+1).times { str << chars[rand(chars.size)] }
100
101
  str
101
102
  end
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
4
5
  # distributed with this work for additional information
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
4
5
  # distributed with this work for additional information
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
4
5
  # distributed with this work for additional information
@@ -1,4 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
4
5
  # distributed with this work for additional information
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
data/test/test_help.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
data/test/test_io.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Licensed to the Apache Software Foundation (ASF) under one
2
3
  # or more contributor license agreements. See the NOTICE file
3
4
  # distributed with this work for additional information
@@ -217,7 +218,7 @@ EOS
217
218
  [64, '80 01'],
218
219
  [8192, '80 80 01'],
219
220
  [-8193, '81 80 01'],
220
- ]
221
+ ].freeze
221
222
 
222
223
  def avro_hexlify(reader)
223
224
  bytes = []
@@ -266,46 +267,46 @@ EOS
266
267
 
267
268
  def test_utf8_string_encoding
268
269
  [
269
- "\xC3".force_encoding('ISO-8859-1'),
270
- "\xC3\x83".force_encoding('UTF-8')
270
+ String.new("\xC3", encoding: 'ISO-8859-1'),
271
+ String.new("\xC3\x83", encoding: 'UTF-8')
271
272
  ].each do |value|
272
- output = ''.force_encoding('BINARY')
273
+ output = String.new('', encoding: 'BINARY')
273
274
  encoder = Avro::IO::BinaryEncoder.new(StringIO.new(output))
274
275
  datum_writer = Avro::IO::DatumWriter.new(Avro::Schema.parse('"string"'))
275
276
  datum_writer.write(value, encoder)
276
277
 
277
- assert_equal "\x04\xc3\x83".force_encoding('BINARY'), output
278
+ assert_equal String.new("\x04\xc3\x83", encoding: 'BINARY'), output
278
279
  end
279
280
  end
280
281
 
281
282
  def test_bytes_encoding
282
283
  [
283
- "\xC3\x83".force_encoding('BINARY'),
284
- "\xC3\x83".force_encoding('ISO-8859-1'),
285
- "\xC3\x83".force_encoding('UTF-8')
284
+ String.new("\xC3\x83", encoding: 'BINARY'),
285
+ String.new("\xC3\x83", encoding: 'ISO-8859-1'),
286
+ String.new("\xC3\x83", encoding: 'UTF-8')
286
287
  ].each do |value|
287
- output = ''.force_encoding('BINARY')
288
+ output = String.new('', encoding: 'BINARY')
288
289
  encoder = Avro::IO::BinaryEncoder.new(StringIO.new(output))
289
290
  datum_writer = Avro::IO::DatumWriter.new(Avro::Schema.parse('"bytes"'))
290
291
  datum_writer.write(value, encoder)
291
292
 
292
- assert_equal "\x04\xc3\x83".force_encoding('BINARY'), output
293
+ assert_equal String.new("\x04\xc3\x83", encoding: 'BINARY'), output
293
294
  end
294
295
  end
295
296
 
296
297
  def test_fixed_encoding
297
298
  [
298
- "\xC3\x83".force_encoding('BINARY'),
299
- "\xC3\x83".force_encoding('ISO-8859-1'),
300
- "\xC3\x83".force_encoding('UTF-8')
299
+ String.new("\xC3\x83", encoding: 'BINARY'),
300
+ String.new("\xC3\x83", encoding: 'ISO-8859-1'),
301
+ String.new("\xC3\x83", encoding: 'UTF-8')
301
302
  ].each do |value|
302
- output = ''.force_encoding('BINARY')
303
+ output = String.new('', encoding: 'BINARY')
303
304
  encoder = Avro::IO::BinaryEncoder.new(StringIO.new(output))
304
305
  schema = '{"type": "fixed", "name": "TwoBytes", "size": 2}'
305
306
  datum_writer = Avro::IO::DatumWriter.new(Avro::Schema.parse(schema))
306
307
  datum_writer.write(value, encoder)
307
308
 
308
- assert_equal "\xc3\x83".force_encoding('BINARY'), output
309
+ assert_equal String.new("\xC3\x83", encoding: 'BINARY'), output
309
310
  end
310
311
  end
311
312
 
@@ -489,6 +490,20 @@ EOS
489
490
  assert_equal(datum_read, { 'field2' => 1 })
490
491
  end
491
492
 
493
+ def test_big_decimal_datum_for_float
494
+ writers_schema = Avro::Schema.parse('"float"')
495
+ writer, * = write_datum(BigDecimal('1.2'), writers_schema)
496
+ datum_read = read_datum(writer, writers_schema)
497
+ assert_in_delta(1.2, datum_read)
498
+ end
499
+
500
+ def test_big_decimal_datum_for_double
501
+ writers_schema = Avro::Schema.parse('"double"')
502
+ writer, * = write_datum(BigDecimal("1.2"), writers_schema)
503
+ datum_read = read_datum(writer, writers_schema)
504
+ assert_in_delta(1.2, datum_read)
505
+ end
506
+
492
507
  def test_snappy_backward_compat
493
508
  # a snappy-compressed block payload without the checksum
494
509
  # this has no back-references, just one literal so the last 9
@@ -567,7 +582,7 @@ EOS
567
582
  datum = randomdata.next
568
583
  assert validate(schm, datum), 'datum is not valid for schema'
569
584
  w = Avro::IO::DatumWriter.new(schm)
570
- writer = StringIO.new "", "w"
585
+ writer = StringIO.new(+"", "w")
571
586
  w.write(datum, Avro::IO::BinaryEncoder.new(writer))
572
587
  r = datum_reader(schm)
573
588
  reader = StringIO.new(writer.string)