blob: faa3fc26d0ff93912425d7b5baea359e20f51ad8 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'test_help'
class TestSchema < Test::Unit::TestCase
def hash_to_schema(hash)
Avro::Schema.parse(hash.to_json)
end
def test_default_namespace
schema = Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "OuterRecord", "fields": [
{"name": "field1", "type": {
"type": "record", "name": "InnerRecord", "fields": []
}},
{"name": "field2", "type": "InnerRecord"}
]}
SCHEMA
assert_equal 'OuterRecord', schema.name
assert_equal 'OuterRecord', schema.fullname
assert_nil schema.namespace
schema.fields.each do |field|
assert_equal 'InnerRecord', field.type.name
assert_equal 'InnerRecord', field.type.fullname
assert_nil field.type.namespace
end
end
def test_inherited_namespace
schema = Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "OuterRecord", "namespace": "my.name.space",
"fields": [
{"name": "definition", "type": {
"type": "record", "name": "InnerRecord", "fields": []
}},
{"name": "relativeReference", "type": "InnerRecord"},
{"name": "absoluteReference", "type": "my.name.space.InnerRecord"}
]}
SCHEMA
assert_equal 'OuterRecord', schema.name
assert_equal 'my.name.space.OuterRecord', schema.fullname
assert_equal 'my.name.space', schema.namespace
schema.fields.each do |field|
assert_equal 'InnerRecord', field.type.name
assert_equal 'my.name.space.InnerRecord', field.type.fullname
assert_equal 'my.name.space', field.type.namespace
end
end
def test_inherited_namespace_from_dotted_name
schema = Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "my.name.space.OuterRecord", "fields": [
{"name": "definition", "type": {
"type": "enum", "name": "InnerEnum", "symbols": ["HELLO", "WORLD"]
}},
{"name": "relativeReference", "type": "InnerEnum"},
{"name": "absoluteReference", "type": "my.name.space.InnerEnum"}
]}
SCHEMA
assert_equal 'OuterRecord', schema.name
assert_equal 'my.name.space.OuterRecord', schema.fullname
assert_equal 'my.name.space', schema.namespace
schema.fields.each do |field|
assert_equal 'InnerEnum', field.type.name
assert_equal 'my.name.space.InnerEnum', field.type.fullname
assert_equal 'my.name.space', field.type.namespace
end
end
def test_nested_namespaces
schema = Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "outer.OuterRecord", "fields": [
{"name": "middle", "type": {
"type": "record", "name": "middle.MiddleRecord", "fields": [
{"name": "inner", "type": {
"type": "record", "name": "InnerRecord", "fields": [
{"name": "recursive", "type": "MiddleRecord"}
]
}}
]
}}
]}
SCHEMA
assert_equal 'OuterRecord', schema.name
assert_equal 'outer.OuterRecord', schema.fullname
assert_equal 'outer', schema.namespace
middle = schema.fields.first.type
assert_equal 'MiddleRecord', middle.name
assert_equal 'middle.MiddleRecord', middle.fullname
assert_equal 'middle', middle.namespace
inner = middle.fields.first.type
assert_equal 'InnerRecord', inner.name
assert_equal 'middle.InnerRecord', inner.fullname
assert_equal 'middle', inner.namespace
assert_equal middle, inner.fields.first.type
end
def test_to_avro_includes_namespaces
schema = Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "my.name.space.OuterRecord", "fields": [
{"name": "definition", "type": {
"type": "fixed", "name": "InnerFixed", "size": 16
}},
{"name": "reference", "type": "InnerFixed"}
]}
SCHEMA
assert_equal({
'type' => 'record', 'name' => 'OuterRecord', 'namespace' => 'my.name.space',
'fields' => [
{'name' => 'definition', 'type' => {
'type' => 'fixed', 'name' => 'InnerFixed', 'namespace' => 'my.name.space',
'size' => 16
}},
{'name' => 'reference', 'type' => 'my.name.space.InnerFixed'}
]
}, schema.to_avro)
end
def test_to_avro_includes_logical_type
schema = Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "has_logical", "fields": [
{"name": "dt", "type": {"type": "int", "logicalType": "date"}}]
}
SCHEMA
assert_equal schema.to_avro, {
'type' => 'record', 'name' => 'has_logical',
'fields' => [
{'name' => 'dt', 'type' => {'type' => 'int', 'logicalType' => 'date'}}
]
}
end
def test_unknown_named_type
error = assert_raise Avro::UnknownSchemaError do
Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "my.name.space.Record", "fields": [
{"name": "reference", "type": "MissingType"}
]}
SCHEMA
end
assert_equal '"MissingType" is not a schema we know about.', error.message
end
def test_invalid_name
error = assert_raise Avro::SchemaParseError do
Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "my-invalid-name", "fields": [
{"name": "id", "type": "int"}
]}
SCHEMA
end
assert_equal "Name my-invalid-name is invalid for type record!", error.message
end
def test_invalid_name_with_two_periods
error = assert_raise Avro::SchemaParseError do
Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "my..invalid.name", "fields": [
{"name": "id", "type": "int"}
]}
SCHEMA
end
assert_equal "Name my..invalid.name is invalid for type record!", error.message
end
def test_invalid_name_with_validation_disabled
Avro.disable_schema_name_validation = true
assert_nothing_raised do
Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "my-invalid-name", "fields": [
{"name": "id", "type": "int"}
]}
SCHEMA
end
Avro.disable_schema_name_validation = false
end
def test_to_avro_handles_falsey_defaults
schema = Avro::Schema.parse <<-SCHEMA
{"type": "record", "name": "Record", "namespace": "my.name.space",
"fields": [
{"name": "is_usable", "type": "boolean", "default": false}
]
}
SCHEMA
assert_equal schema.to_avro, {
'type' => 'record', 'name' => 'Record', 'namespace' => 'my.name.space',
'fields' => [
{'name' => 'is_usable', 'type' => 'boolean', 'default' => false}
]
}
end
def test_record_field_doc_attribute
field_schema_json = Avro::Schema.parse <<-SCHEMA
{
"type": "record",
"name": "Record",
"namespace": "my.name.space",
"fields": [
{
"name": "name",
"type": "boolean",
"doc": "documentation"
}
]
}
SCHEMA
field_schema_hash =
{
'type' => 'record',
'name' => 'Record',
'namespace' => 'my.name.space',
'fields' => [
{
'name' => 'name',
'type' => 'boolean',
'doc' => 'documentation'
}
]
}
assert_equal field_schema_hash, field_schema_json.to_avro
end
def test_record_doc_attribute
record_schema_json = Avro::Schema.parse <<-SCHEMA
{
"type": "record",
"name": "Record",
"namespace": "my.name.space",
"doc": "documentation",
"fields": [
{
"name": "name",
"type": "boolean"
}
]
}
SCHEMA
record_schema_hash =
{
'type' => 'record',
'name' => 'Record',
'namespace' => 'my.name.space',
'doc' => 'documentation',
'fields' => [
{
'name' => 'name',
'type' => 'boolean'
}
]
}
assert_equal record_schema_hash, record_schema_json.to_avro
end
def test_enum_doc_attribute
enum_schema_json = Avro::Schema.parse <<-SCHEMA
{
"type": "enum",
"name": "Enum",
"namespace": "my.name.space",
"doc": "documentation",
"symbols" : [
"SPADES",
"HEARTS",
"DIAMONDS",
"CLUBS"
]
}
SCHEMA
enum_schema_hash =
{
'type' => 'enum',
'name' => 'Enum',
'namespace' => 'my.name.space',
'doc' => 'documentation',
'symbols' => [
'SPADES',
'HEARTS',
'DIAMONDS',
'CLUBS'
]
}
assert_equal enum_schema_hash, enum_schema_json.to_avro
end
def test_enum_default_attribute
enum_schema = Avro::Schema.parse <<-SCHEMA
{
"type": "enum",
"name": "fruit",
"default": "apples",
"symbols": ["apples", "oranges"]
}
SCHEMA
enum_schema_hash = {
'type' => 'enum',
'name' => 'fruit',
'default' => 'apples',
'symbols' => %w(apples oranges)
}
assert_equal(enum_schema.default, "apples")
assert_equal(enum_schema_hash, enum_schema.to_avro)
end
def test_validate_enum_default
exception = assert_raise(Avro::SchemaParseError) do
hash_to_schema(
type: 'enum',
name: 'fruit',
default: 'bananas',
symbols: %w(apples oranges)
)
end
assert_equal("Default 'bananas' is not a valid symbol for enum fruit",
exception.to_s)
end
def test_empty_record
schema = Avro::Schema.parse('{"type":"record", "name":"Empty"}')
assert_empty(schema.fields)
end
def test_empty_union
schema = Avro::Schema.parse('[]')
assert_equal(schema.to_s, '[]')
end
def test_read
schema = Avro::Schema.parse('"string"')
writer_schema = Avro::Schema.parse('"int"')
assert_false(schema.read?(writer_schema))
assert_true(schema.read?(schema))
end
def test_be_read
schema = Avro::Schema.parse('"string"')
writer_schema = Avro::Schema.parse('"int"')
assert_false(schema.be_read?(writer_schema))
assert_true(schema.be_read?(schema))
end
def test_mutual_read
schema = Avro::Schema.parse('"string"')
writer_schema = Avro::Schema.parse('"int"')
default1 = Avro::Schema.parse('{"type":"record", "name":"Default", "fields":[{"name":"i", "type":"int", "default": 1}]}')
default2 = Avro::Schema.parse('{"type":"record", "name":"Default", "fields":[{"name:":"s", "type":"string", "default": ""}]}')
assert_false(schema.mutual_read?(writer_schema))
assert_true(schema.mutual_read?(schema))
assert_true(default1.mutual_read?(default2))
end
def test_validate_defaults
exception = assert_raise(Avro::SchemaParseError) do
hash_to_schema(
type: 'record',
name: 'fruits',
fields: [
{
name: 'veggies',
type: 'string',
default: nil
}
]
)
end
assert_equal('Error validating default for veggies: at . expected type string, got null',
exception.to_s)
end
def test_field_default_validation_disabled
Avro.disable_field_default_validation = true
assert_nothing_raised do
hash_to_schema(
type: 'record',
name: 'fruits',
fields: [
{
name: 'veggies',
type: 'string',
default: nil
}
]
)
end
ensure
Avro.disable_field_default_validation = false
end
def test_field_default_validation_disabled_via_env
Avro.disable_field_default_validation = false
ENV['AVRO_DISABLE_FIELD_DEFAULT_VALIDATION'] = "1"
assert_nothing_raised do
hash_to_schema(
type: 'record',
name: 'fruits',
fields: [
{
name: 'veggies',
type: 'string',
default: nil
}
]
)
end
ensure
ENV.delete('AVRO_DISABLE_FIELD_DEFAULT_VALIDATION')
Avro.disable_field_default_validation = false
end
def test_validate_record_valid_default
assert_nothing_raised(Avro::SchemaParseError) do
hash_to_schema(
type: 'record',
name: 'with_subrecord',
fields: [
{
name: 'sub',
type: {
name: 'subrecord',
type: 'record',
fields: [
{ type: 'string', name: 'x' }
]
},
default: {
x: "y"
}
}
]
)
end
end
def test_validate_record_invalid_default
exception = assert_raise(Avro::SchemaParseError) do
hash_to_schema(
type: 'record',
name: 'with_subrecord',
fields: [
{
name: 'sub',
type: {
name: 'subrecord',
type: 'record',
fields: [
{ type: 'string', name: 'x' }
]
},
default: {
a: 1
}
}
]
)
end
assert_equal('Error validating default for sub: at .x expected type string, got null',
exception.to_s)
end
def test_validate_union_defaults
exception = assert_raise(Avro::SchemaParseError) do
hash_to_schema(
type: 'record',
name: 'fruits',
fields: [
{
name: 'veggies',
type: %w(string null),
default: 5
}
]
)
end
assert_equal('Error validating default for veggies: at . expected type string, got int with value 5',
exception.to_s)
end
def test_validate_union_default_first_type
exception = assert_raise(Avro::SchemaParseError) do
hash_to_schema(
type: 'record',
name: 'fruits',
fields: [
{
name: 'veggies',
type: %w(null string),
default: 'apple'
}
]
)
end
assert_equal('Error validating default for veggies: at . expected type null, got string with value "apple"',
exception.to_s)
end
def test_bytes_decimal_to_include_precision_scale
schema = Avro::Schema.parse <<-SCHEMA
{
"type": "bytes",
"logicalType": "decimal",
"precision": 9,
"scale": 2
}
SCHEMA
schema_hash =
{
'type' => 'bytes',
'logicalType' => 'decimal',
'precision' => 9,
'scale' => 2
}
assert_equal schema_hash, schema.to_avro
end
def test_bytes_decimal_to_without_precision_scale
schema = Avro::Schema.parse <<-SCHEMA
{
"type": "bytes",
"logicalType": "decimal"
}
SCHEMA
schema_hash =
{
'type' => 'bytes',
'logicalType' => 'decimal'
}
assert_equal schema_hash, schema.to_avro
end
def test_bytes_schema
schema = Avro::Schema.parse <<-SCHEMA
{
"type": "bytes"
}
SCHEMA
schema_str = 'bytes'
assert_equal schema_str, schema.to_avro
end
end