avro 1.9.0 → 1.10.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,7 @@
7
7
  # "License"); you may not use this file except in compliance
8
8
  # with the License. You may obtain a copy of the License at
9
9
  #
10
- # http://www.apache.org/licenses/LICENSE-2.0
10
+ # https://www.apache.org/licenses/LICENSE-2.0
11
11
  #
12
12
  # Unless required by applicable law or agreed to in writing, software
13
13
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -6,7 +6,7 @@
6
6
  # "License"); you may not use this file except in compliance
7
7
  # with the License. You may obtain a copy of the License at
8
8
  #
9
- # http://www.apache.org/licenses/LICENSE-2.0
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
10
  #
11
11
  # Unless required by applicable law or agreed to in writing, software
12
12
  # distributed under the License is distributed on an "AS IS" BASIS,
data/test/test_schema.rb CHANGED
@@ -6,7 +6,7 @@
6
6
  # "License"); you may not use this file except in compliance
7
7
  # with the License. You may obtain a copy of the License at
8
8
  #
9
- # http://www.apache.org/licenses/LICENSE-2.0
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
10
  #
11
11
  # Unless required by applicable law or agreed to in writing, software
12
12
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -151,6 +151,20 @@ class TestSchema < Test::Unit::TestCase
151
151
  }
152
152
  end
153
153
 
154
+ def test_to_avro_includes_aliases
155
+ hash = {
156
+ 'type' => 'record',
157
+ 'name' => 'test_record',
158
+ 'aliases' => %w(alt_record),
159
+ 'fields' => [
160
+ { 'name' => 'f', 'type' => { 'type' => 'fixed', 'size' => 2, 'name' => 'test_fixed', 'aliases' => %w(alt_fixed) } },
161
+ { 'name' => 'e', 'type' => { 'type' => 'enum', 'symbols' => %w(A B), 'name' => 'test_enum', 'aliases' => %w(alt_enum) } }
162
+ ]
163
+ }
164
+ schema = hash_to_schema(hash)
165
+ assert_equal(schema.to_avro, hash)
166
+ end
167
+
154
168
  def test_unknown_named_type
155
169
  error = assert_raise Avro::UnknownSchemaError do
156
170
  Avro::Schema.parse <<-SCHEMA
@@ -163,6 +177,42 @@ class TestSchema < Test::Unit::TestCase
163
177
  assert_equal '"MissingType" is not a schema we know about.', error.message
164
178
  end
165
179
 
180
+ def test_invalid_name
181
+ error = assert_raise Avro::SchemaParseError do
182
+ Avro::Schema.parse <<-SCHEMA
183
+ {"type": "record", "name": "my-invalid-name", "fields": [
184
+ {"name": "id", "type": "int"}
185
+ ]}
186
+ SCHEMA
187
+ end
188
+
189
+ assert_equal "Name my-invalid-name is invalid for type record!", error.message
190
+ end
191
+
192
+ def test_invalid_name_with_two_periods
193
+ error = assert_raise Avro::SchemaParseError do
194
+ Avro::Schema.parse <<-SCHEMA
195
+ {"type": "record", "name": "my..invalid.name", "fields": [
196
+ {"name": "id", "type": "int"}
197
+ ]}
198
+ SCHEMA
199
+ end
200
+
201
+ assert_equal "Name my..invalid.name is invalid for type record!", error.message
202
+ end
203
+
204
+ def test_invalid_name_with_validation_disabled
205
+ Avro.disable_schema_name_validation = true
206
+ assert_nothing_raised do
207
+ Avro::Schema.parse <<-SCHEMA
208
+ {"type": "record", "name": "my-invalid-name", "fields": [
209
+ {"name": "id", "type": "int"}
210
+ ]}
211
+ SCHEMA
212
+ end
213
+ Avro.disable_schema_name_validation = false
214
+ end
215
+
166
216
  def test_to_avro_handles_falsey_defaults
167
217
  schema = Avro::Schema.parse <<-SCHEMA
168
218
  {"type": "record", "name": "Record", "namespace": "my.name.space",
@@ -278,6 +328,40 @@ class TestSchema < Test::Unit::TestCase
278
328
  assert_equal enum_schema_hash, enum_schema_json.to_avro
279
329
  end
280
330
 
331
+ def test_enum_default_attribute
332
+ enum_schema = Avro::Schema.parse <<-SCHEMA
333
+ {
334
+ "type": "enum",
335
+ "name": "fruit",
336
+ "default": "apples",
337
+ "symbols": ["apples", "oranges"]
338
+ }
339
+ SCHEMA
340
+
341
+ enum_schema_hash = {
342
+ 'type' => 'enum',
343
+ 'name' => 'fruit',
344
+ 'default' => 'apples',
345
+ 'symbols' => %w(apples oranges)
346
+ }
347
+
348
+ assert_equal(enum_schema.default, "apples")
349
+ assert_equal(enum_schema_hash, enum_schema.to_avro)
350
+ end
351
+
352
+ def test_validate_enum_default
353
+ exception = assert_raise(Avro::SchemaParseError) do
354
+ hash_to_schema(
355
+ type: 'enum',
356
+ name: 'fruit',
357
+ default: 'bananas',
358
+ symbols: %w(apples oranges)
359
+ )
360
+ end
361
+ assert_equal("Default 'bananas' is not a valid symbol for enum fruit",
362
+ exception.to_s)
363
+ end
364
+
281
365
  def test_empty_record
282
366
  schema = Avro::Schema.parse('{"type":"record", "name":"Empty"}')
283
367
  assert_empty(schema.fields)
@@ -455,5 +539,189 @@ class TestSchema < Test::Unit::TestCase
455
539
  end
456
540
  assert_equal('Error validating default for veggies: at . expected type null, got string with value "apple"',
457
541
  exception.to_s)
542
+ end
543
+
544
+ def test_bytes_decimal_to_include_precision_scale
545
+ schema = Avro::Schema.parse <<-SCHEMA
546
+ {
547
+ "type": "bytes",
548
+ "logicalType": "decimal",
549
+ "precision": 9,
550
+ "scale": 2
551
+ }
552
+ SCHEMA
553
+
554
+ schema_hash =
555
+ {
556
+ 'type' => 'bytes',
557
+ 'logicalType' => 'decimal',
558
+ 'precision' => 9,
559
+ 'scale' => 2
560
+ }
561
+
562
+ assert_equal schema_hash, schema.to_avro
563
+ end
564
+
565
+ def test_bytes_decimal_to_without_precision_scale
566
+ schema = Avro::Schema.parse <<-SCHEMA
567
+ {
568
+ "type": "bytes",
569
+ "logicalType": "decimal"
570
+ }
571
+ SCHEMA
572
+
573
+ schema_hash =
574
+ {
575
+ 'type' => 'bytes',
576
+ 'logicalType' => 'decimal'
577
+ }
578
+
579
+ assert_equal schema_hash, schema.to_avro
580
+ end
581
+
582
+ def test_bytes_schema
583
+ schema = Avro::Schema.parse <<-SCHEMA
584
+ {
585
+ "type": "bytes"
586
+ }
587
+ SCHEMA
588
+
589
+ schema_str = 'bytes'
590
+ assert_equal schema_str, schema.to_avro
591
+ end
592
+
593
+ def test_validate_duplicate_symbols
594
+ exception = assert_raise(Avro::SchemaParseError) do
595
+ hash_to_schema(
596
+ type: 'enum',
597
+ name: 'name',
598
+ symbols: ['erica', 'erica']
599
+ )
600
+ end
601
+ assert_equal(
602
+ 'Duplicate symbol: ["erica", "erica"]',
603
+ exception.to_s
604
+ )
605
+ end
606
+
607
+ def test_validate_enum_symbols
608
+ exception = assert_raise(Avro::SchemaParseError) do
609
+ hash_to_schema(
610
+ type: 'enum',
611
+ name: 'things',
612
+ symbols: ['good_symbol', '_GOOD_SYMBOL_2', '8ad_symbol', 'also-bad-symbol', '>=', '$']
613
+ )
614
+ end
615
+
616
+ assert_equal(
617
+ "Invalid symbols for things: 8ad_symbol, also-bad-symbol, >=, $ don't match #{Avro::Schema::EnumSchema::SYMBOL_REGEX.inspect}",
618
+ exception.to_s
619
+ )
620
+ end
621
+
622
+ def test_enum_symbol_validation_disabled_via_env
623
+ Avro.disable_enum_symbol_validation = nil
624
+ ENV['AVRO_DISABLE_ENUM_SYMBOL_VALIDATION'] = '1'
625
+
626
+ hash_to_schema(
627
+ type: 'enum',
628
+ name: 'things',
629
+ symbols: ['good_symbol', '_GOOD_SYMBOL_2', '8ad_symbol', 'also-bad-symbol', '>=', '$'],
630
+ )
631
+ ensure
632
+ ENV.delete('AVRO_DISABLE_ENUM_SYMBOL_VALIDATION')
633
+ Avro.disable_enum_symbol_validation = nil
634
+ end
635
+
636
+ def test_enum_symbol_validation_disabled_via_class_method
637
+ Avro.disable_enum_symbol_validation = true
638
+
639
+ hash_to_schema(
640
+ type: 'enum',
641
+ name: 'things',
642
+ symbols: ['good_symbol', '_GOOD_SYMBOL_2', '8ad_symbol', 'also-bad-symbol', '>=', '$'],
643
+ )
644
+ ensure
645
+ Avro.disable_enum_symbol_validation = nil
646
+ end
647
+
648
+ def test_validate_field_aliases
649
+ exception = assert_raise(Avro::SchemaParseError) do
650
+ hash_to_schema(
651
+ type: 'record',
652
+ name: 'fruits',
653
+ fields: [
654
+ { name: 'banana', type: 'string', aliases: 'banane' }
655
+ ]
656
+ )
657
+ end
658
+
659
+ assert_match(/Invalid aliases value "banane" for "string" banana/, exception.to_s)
660
+ end
661
+
662
+ def test_validate_same_alias_multiple_fields
663
+ exception = assert_raise(Avro::SchemaParseError) do
664
+ hash_to_schema(
665
+ type: 'record',
666
+ name: 'fruits',
667
+ fields: [
668
+ { name: 'banana', type: 'string', aliases: %w(yellow) },
669
+ { name: 'lemo', type: 'string', aliases: %w(yellow) }
670
+ ]
671
+ )
672
+ end
673
+
674
+ assert_match('Alias ["yellow"] already in use', exception.to_s)
675
+ end
676
+
677
+ def test_validate_repeated_aliases
678
+ assert_nothing_raised do
679
+ hash_to_schema(
680
+ type: 'record',
681
+ name: 'fruits',
682
+ fields: [
683
+ { name: 'banana', type: 'string', aliases: %w(yellow yellow) },
684
+ ]
685
+ )
686
+ end
687
+ end
688
+
689
+ def test_validate_record_aliases
690
+ exception = assert_raise(Avro::SchemaParseError) do
691
+ hash_to_schema(
692
+ type: 'record',
693
+ name: 'fruits',
694
+ aliases: ["foods", 2],
695
+ fields: []
696
+ )
697
+ end
698
+
699
+ assert_match(/Invalid aliases value \["foods", 2\] for record fruits/, exception.to_s)
700
+ end
701
+
702
+ def test_validate_enum_aliases
703
+ exception = assert_raise(Avro::SchemaParseError) do
704
+ hash_to_schema(
705
+ type: 'enum',
706
+ name: 'vowels',
707
+ aliases: [1, 2],
708
+ symbols: %w(A E I O U)
709
+ )
458
710
  end
711
+
712
+ assert_match(/Invalid aliases value \[1, 2\] for enum vowels/, exception.to_s)
713
+ end
714
+
715
+ def test_validate_fixed_aliases
716
+ exception = assert_raise(Avro::SchemaParseError) do
717
+ hash_to_schema(
718
+ type: 'fixed',
719
+ name: 'uuid',
720
+ size: 36,
721
+ aliases: "unique_id"
722
+ )
723
+ end
724
+
725
+ assert_match(/Invalid aliases value "unique_id" for fixed uuid/, exception.to_s)
726
+ end
459
727
  end
@@ -6,7 +6,7 @@
6
6
  # "License"); you may not use this file except in compliance
7
7
  # with the License. You may obtain a copy of the License at
8
8
  #
9
- # http://www.apache.org/licenses/LICENSE-2.0
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
10
  #
11
11
  # Unless required by applicable law or agreed to in writing, software
12
12
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -25,7 +25,9 @@ class TestSchemaCompatibility < Test::Unit::TestCase
25
25
  end
26
26
 
27
27
  def test_compatible_reader_writer_pairs
28
+ cached_schema = a_int_record1_schema
28
29
  [
30
+ cached_schema, cached_schema,
29
31
  long_schema, int_schema,
30
32
  float_schema, int_schema,
31
33
  float_schema, long_schema,
@@ -39,7 +41,12 @@ class TestSchemaCompatibility < Test::Unit::TestCase
39
41
  long_map_schema, int_map_schema,
40
42
 
41
43
  enum1_ab_schema, enum1_ab_schema,
44
+ enum1_ab_aliased_schema, enum1_ab_schema,
42
45
  enum1_abc_schema, enum1_ab_schema,
46
+ enum1_ab_default_schema, enum1_abc_schema,
47
+
48
+ fixed1_schema, fixed1_schema,
49
+ fixed1_aliased_schema, fixed1_schema,
43
50
 
44
51
  string_schema, bytes_schema,
45
52
  bytes_schema, string_schema,
@@ -55,6 +62,7 @@ class TestSchemaCompatibility < Test::Unit::TestCase
55
62
 
56
63
  empty_record1_schema, empty_record1_schema,
57
64
  empty_record1_schema, a_int_record1_schema,
65
+ empty_record1_aliased_schema, empty_record1_schema,
58
66
 
59
67
  a_int_record1_schema, a_int_record1_schema,
60
68
  a_dint_record1_schema, a_int_record1_schema,
@@ -117,16 +125,22 @@ class TestSchemaCompatibility < Test::Unit::TestCase
117
125
  int_map_schema, long_map_schema,
118
126
 
119
127
  enum1_ab_schema, enum1_abc_schema,
128
+ enum1_ab_schema, enum1_ab_aliased_schema,
120
129
  enum1_bc_schema, enum1_abc_schema,
121
130
 
122
131
  enum1_ab_schema, enum2_ab_schema,
123
132
  int_schema, enum2_ab_schema,
124
133
  enum2_ab_schema, int_schema,
125
134
 
135
+ fixed1_schema, fixed2_schema,
136
+ fixed1_schema, fixed1_size3_schema,
137
+ fixed1_schema, fixed1_aliased_schema,
138
+
126
139
  int_union_schema, int_string_union_schema,
127
140
  string_union_schema, int_string_union_schema,
128
141
 
129
142
  empty_record2_schema, empty_record1_schema,
143
+ empty_record1_schema, empty_record1_aliased_schema,
130
144
  a_int_record1_schema, empty_record1_schema,
131
145
  a_int_b_dint_record1_schema, empty_record1_schema,
132
146
 
@@ -169,6 +183,17 @@ class TestSchemaCompatibility < Test::Unit::TestCase
169
183
  assert_false(can_read?(reader_schema, writer_schema))
170
184
  end
171
185
 
186
+ def test_aliased_field
187
+ reader_schema = Avro::Schema.parse(<<-SCHEMA)
188
+ {"type":"record", "name":"Record", "fields":[
189
+ {"name":"newname1", "aliases":["oldfield1"], "type":"int"},
190
+ {"name":"oldfield2", "type":"string"}
191
+ ]}
192
+ SCHEMA
193
+ assert_true(can_read?(writer_schema, reader_schema))
194
+ assert_false(can_read?(reader_schema, writer_schema))
195
+ end
196
+
172
197
  def test_all_fields
173
198
  reader_schema = Avro::Schema.parse <<-SCHEMA
174
199
  {"type":"record", "name":"Record", "fields":[
@@ -250,6 +275,23 @@ class TestSchemaCompatibility < Test::Unit::TestCase
250
275
  assert_true(can_read?(enum_schema1, enum_schema2))
251
276
  end
252
277
 
278
+ def test_crossed_aliases
279
+ writer_schema = Avro::Schema.parse(<<-SCHEMA)
280
+ {"type":"record", "name":"Record", "fields":[
281
+ {"name":"field1", "type": "int"},
282
+ {"name":"field2", "type": "string"}
283
+ ]}
284
+ SCHEMA
285
+ reader_schema = Avro::Schema.parse(<<-SCHEMA)
286
+ {"type":"record", "name":"Record", "fields":[
287
+ {"name":"field1", "aliases":["field2"], "type":"string"},
288
+ {"name":"field2", "aliases":["field1"], "type":"int"}
289
+ ]}
290
+ SCHEMA
291
+ # Not supported; alias is not used if there is a redirect match
292
+ assert_false(can_read?(writer_schema, reader_schema))
293
+ end
294
+
253
295
  # Tests from lang/java/avro/src/test/java/org/apache/avro/io/parsing/TestResolvingGrammarGenerator2.java
254
296
 
255
297
  def point_2d_schema
@@ -373,6 +415,14 @@ class TestSchemaCompatibility < Test::Unit::TestCase
373
415
  Avro::Schema.parse('{"type":"enum", "name":"Enum1", "symbols":["A","B"]}')
374
416
  end
375
417
 
418
+ def enum1_ab_default_schema
419
+ Avro::Schema.parse('{"type":"enum", "name":"Enum1", "symbols":["A","B"], "default":"A"}')
420
+ end
421
+
422
+ def enum1_ab_aliased_schema
423
+ Avro::Schema.parse('{"type":"enum", "name":"Enum2", "aliases":["Enum1"], "symbols":["A","B"]}')
424
+ end
425
+
376
426
  def enum1_abc_schema
377
427
  Avro::Schema.parse('{"type":"enum", "name":"Enum1", "symbols":["A","B","C"]}')
378
428
  end
@@ -385,10 +435,30 @@ class TestSchemaCompatibility < Test::Unit::TestCase
385
435
  Avro::Schema.parse('{"type":"enum", "name":"Enum2", "symbols":["A","B"]}')
386
436
  end
387
437
 
438
+ def fixed1_schema
439
+ Avro::Schema.parse('{"type":"fixed", "name":"Fixed1", "size": 2}')
440
+ end
441
+
442
+ def fixed1_aliased_schema
443
+ Avro::Schema.parse('{"type":"fixed", "name":"Fixed2", "aliases":["Fixed1"], "size": 2}')
444
+ end
445
+
446
+ def fixed2_schema
447
+ Avro::Schema.parse('{"type":"fixed", "name":"Fixed2", "size": 2}')
448
+ end
449
+
450
+ def fixed1_size3_schema
451
+ Avro::Schema.parse('{"type":"fixed", "name":"Fixed1", "size": 3}')
452
+ end
453
+
388
454
  def empty_record1_schema
389
455
  Avro::Schema.parse('{"type":"record", "name":"Record1"}')
390
456
  end
391
457
 
458
+ def empty_record1_aliased_schema
459
+ Avro::Schema.parse('{"type":"record", "name":"Record2", "aliases":["Record1"]}')
460
+ end
461
+
392
462
  def empty_record2_schema
393
463
  Avro::Schema.parse('{"type":"record", "name":"Record2"}')
394
464
  end