blob: 61c354ef1438302d0be73f0faeed58f698ae92b3 [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.
*/
package org.apache.avro;
import static org.apache.avro.TestSchemas.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.avro.reflect.ReflectData;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class TestSchemaValidation {
@Rule
public ExpectedException expectedException = ExpectedException.none();
/** Collection of reader/writer schema pair that are compatible. */
public static final List<ReaderWriter> COMPATIBLE_READER_WRITER_TEST_CASES = list(
new ReaderWriter(BOOLEAN_SCHEMA, BOOLEAN_SCHEMA),
new ReaderWriter(INT_SCHEMA, INT_SCHEMA),
new ReaderWriter(LONG_SCHEMA, INT_SCHEMA), new ReaderWriter(LONG_SCHEMA, LONG_SCHEMA),
// Avro spec says INT/LONG can be promoted to FLOAT/DOUBLE.
// This is arguable as this causes a loss of precision.
new ReaderWriter(FLOAT_SCHEMA, INT_SCHEMA), new ReaderWriter(FLOAT_SCHEMA, LONG_SCHEMA),
new ReaderWriter(DOUBLE_SCHEMA, LONG_SCHEMA),
new ReaderWriter(DOUBLE_SCHEMA, INT_SCHEMA), new ReaderWriter(DOUBLE_SCHEMA, FLOAT_SCHEMA),
new ReaderWriter(STRING_SCHEMA, STRING_SCHEMA),
new ReaderWriter(BYTES_SCHEMA, BYTES_SCHEMA),
new ReaderWriter(INT_ARRAY_SCHEMA, INT_ARRAY_SCHEMA), new ReaderWriter(LONG_ARRAY_SCHEMA, INT_ARRAY_SCHEMA),
new ReaderWriter(INT_MAP_SCHEMA, INT_MAP_SCHEMA), new ReaderWriter(LONG_MAP_SCHEMA, INT_MAP_SCHEMA),
new ReaderWriter(ENUM1_AB_SCHEMA, ENUM1_AB_SCHEMA), new ReaderWriter(ENUM1_ABC_SCHEMA, ENUM1_AB_SCHEMA),
// String-to/from-bytes, introduced in Avro 1.7.7
new ReaderWriter(STRING_SCHEMA, BYTES_SCHEMA), new ReaderWriter(BYTES_SCHEMA, STRING_SCHEMA),
// Tests involving unions:
new ReaderWriter(EMPTY_UNION_SCHEMA, EMPTY_UNION_SCHEMA), new ReaderWriter(INT_UNION_SCHEMA, INT_UNION_SCHEMA),
new ReaderWriter(INT_STRING_UNION_SCHEMA, STRING_INT_UNION_SCHEMA),
new ReaderWriter(INT_UNION_SCHEMA, EMPTY_UNION_SCHEMA), new ReaderWriter(LONG_UNION_SCHEMA, INT_UNION_SCHEMA),
new ReaderWriter(FLOAT_UNION_SCHEMA, INT_UNION_SCHEMA), new ReaderWriter(FLOAT_UNION_SCHEMA, LONG_UNION_SCHEMA),
new ReaderWriter(DOUBLE_UNION_SCHEMA, INT_UNION_SCHEMA), new ReaderWriter(LONG_UNION_SCHEMA, EMPTY_UNION_SCHEMA),
new ReaderWriter(DOUBLE_UNION_SCHEMA, LONG_UNION_SCHEMA),
new ReaderWriter(FLOAT_UNION_SCHEMA, EMPTY_UNION_SCHEMA),
new ReaderWriter(DOUBLE_UNION_SCHEMA, FLOAT_UNION_SCHEMA),
new ReaderWriter(STRING_UNION_SCHEMA, EMPTY_UNION_SCHEMA),
new ReaderWriter(STRING_UNION_SCHEMA, BYTES_UNION_SCHEMA),
new ReaderWriter(BYTES_UNION_SCHEMA, EMPTY_UNION_SCHEMA),
new ReaderWriter(BYTES_UNION_SCHEMA, STRING_UNION_SCHEMA),
new ReaderWriter(DOUBLE_UNION_SCHEMA, INT_FLOAT_UNION_SCHEMA),
new ReaderWriter(NULL_INT_ARRAY_UNION_SCHEMA, INT_ARRAY_SCHEMA),
new ReaderWriter(NULL_INT_MAP_UNION_SCHEMA, INT_MAP_SCHEMA),
// Readers capable of reading all branches of a union are compatible
new ReaderWriter(FLOAT_SCHEMA, INT_FLOAT_UNION_SCHEMA), new ReaderWriter(LONG_SCHEMA, INT_LONG_UNION_SCHEMA),
new ReaderWriter(DOUBLE_SCHEMA, INT_FLOAT_UNION_SCHEMA),
new ReaderWriter(DOUBLE_SCHEMA, INT_LONG_FLOAT_DOUBLE_UNION_SCHEMA),
// Special case of singleton unions:
new ReaderWriter(FLOAT_SCHEMA, FLOAT_UNION_SCHEMA), new ReaderWriter(INT_UNION_SCHEMA, INT_SCHEMA),
new ReaderWriter(INT_SCHEMA, INT_UNION_SCHEMA),
// Tests involving records:
new ReaderWriter(EMPTY_RECORD1, EMPTY_RECORD1), new ReaderWriter(EMPTY_RECORD1, A_INT_RECORD1),
new ReaderWriter(A_INT_RECORD1, A_INT_RECORD1), new ReaderWriter(A_DINT_RECORD1, A_INT_RECORD1),
new ReaderWriter(A_DINT_RECORD1, A_DINT_RECORD1), new ReaderWriter(A_INT_RECORD1, A_DINT_RECORD1),
new ReaderWriter(A_LONG_RECORD1, A_INT_RECORD1),
new ReaderWriter(A_INT_RECORD1, A_INT_B_INT_RECORD1), new ReaderWriter(A_DINT_RECORD1, A_INT_B_INT_RECORD1),
new ReaderWriter(A_INT_B_DINT_RECORD1, A_INT_RECORD1), new ReaderWriter(A_DINT_B_DINT_RECORD1, EMPTY_RECORD1),
new ReaderWriter(A_DINT_B_DINT_RECORD1, A_INT_RECORD1),
new ReaderWriter(A_INT_B_INT_RECORD1, A_DINT_B_DINT_RECORD1),
// The SchemaValidator, unlike the SchemaCompatibility class, cannot cope with
// recursive schemas
// See AVRO-2074
// new ReaderWriter(INT_LIST_RECORD, INT_LIST_RECORD),
// new ReaderWriter(LONG_LIST_RECORD, LONG_LIST_RECORD),
// new ReaderWriter(LONG_LIST_RECORD, INT_LIST_RECORD),
new ReaderWriter(NULL_SCHEMA, NULL_SCHEMA),
// This is comparing two records that have an inner array of records with
// different namespaces.
new ReaderWriter(NS_RECORD1, NS_RECORD2));
/** Collection of reader/writer schema pair that are incompatible. */
public static final List<ReaderWriter> INCOMPATIBLE_READER_WRITER_TEST_CASES = list(
new ReaderWriter(NULL_SCHEMA, INT_SCHEMA), new ReaderWriter(NULL_SCHEMA, LONG_SCHEMA),
new ReaderWriter(BOOLEAN_SCHEMA, INT_SCHEMA),
new ReaderWriter(INT_SCHEMA, NULL_SCHEMA), new ReaderWriter(INT_SCHEMA, BOOLEAN_SCHEMA),
new ReaderWriter(INT_SCHEMA, LONG_SCHEMA), new ReaderWriter(INT_SCHEMA, FLOAT_SCHEMA),
new ReaderWriter(INT_SCHEMA, DOUBLE_SCHEMA),
new ReaderWriter(LONG_SCHEMA, FLOAT_SCHEMA), new ReaderWriter(LONG_SCHEMA, DOUBLE_SCHEMA),
new ReaderWriter(FLOAT_SCHEMA, DOUBLE_SCHEMA),
new ReaderWriter(STRING_SCHEMA, BOOLEAN_SCHEMA), new ReaderWriter(STRING_SCHEMA, INT_SCHEMA),
new ReaderWriter(BYTES_SCHEMA, NULL_SCHEMA), new ReaderWriter(BYTES_SCHEMA, INT_SCHEMA),
new ReaderWriter(INT_ARRAY_SCHEMA, LONG_ARRAY_SCHEMA), new ReaderWriter(INT_MAP_SCHEMA, INT_ARRAY_SCHEMA),
new ReaderWriter(INT_ARRAY_SCHEMA, INT_MAP_SCHEMA), new ReaderWriter(INT_MAP_SCHEMA, LONG_MAP_SCHEMA),
new ReaderWriter(ENUM1_AB_SCHEMA, ENUM1_ABC_SCHEMA), new ReaderWriter(ENUM1_BC_SCHEMA, ENUM1_ABC_SCHEMA),
new ReaderWriter(ENUM1_AB_SCHEMA, ENUM2_AB_SCHEMA), new ReaderWriter(INT_SCHEMA, ENUM2_AB_SCHEMA),
new ReaderWriter(ENUM2_AB_SCHEMA, INT_SCHEMA),
// Tests involving unions:
new ReaderWriter(INT_UNION_SCHEMA, INT_STRING_UNION_SCHEMA),
new ReaderWriter(STRING_UNION_SCHEMA, INT_STRING_UNION_SCHEMA),
new ReaderWriter(FLOAT_SCHEMA, INT_LONG_FLOAT_DOUBLE_UNION_SCHEMA),
new ReaderWriter(LONG_SCHEMA, INT_FLOAT_UNION_SCHEMA), new ReaderWriter(INT_SCHEMA, INT_FLOAT_UNION_SCHEMA),
new ReaderWriter(EMPTY_RECORD2, EMPTY_RECORD1), new ReaderWriter(A_INT_RECORD1, EMPTY_RECORD1),
new ReaderWriter(A_INT_B_DINT_RECORD1, EMPTY_RECORD1),
new ReaderWriter(INT_LIST_RECORD, LONG_LIST_RECORD),
new ReaderWriter(NULL_SCHEMA, INT_SCHEMA));
SchemaValidatorBuilder builder = new SchemaValidatorBuilder();
Schema rec = SchemaBuilder.record("test.Rec").fields().name("a").type().intType().intDefault(1).name("b").type()
.longType().noDefault().endRecord();
Schema rec2 = SchemaBuilder.record("test.Rec").fields().name("a").type().intType().intDefault(1).name("b").type()
.longType().noDefault().name("c").type().intType().intDefault(0).endRecord();
Schema rec3 = SchemaBuilder.record("test.Rec").fields().name("b").type().longType().noDefault().name("c").type()
.intType().intDefault(0).endRecord();
Schema rec4 = SchemaBuilder.record("test.Rec").fields().name("b").type().longType().noDefault().name("c").type()
.intType().noDefault().endRecord();
Schema rec5 = SchemaBuilder.record("test.Rec").fields().name("a").type().stringType().stringDefault("") // different
// type from
// original
.name("b").type().longType().noDefault().name("c").type().intType().intDefault(0).endRecord();
@Test
public void testAllTypes() throws SchemaValidationException {
Schema s = SchemaBuilder.record("r").fields().requiredBoolean("boolF").requiredInt("intF").requiredLong("longF")
.requiredFloat("floatF").requiredDouble("doubleF").requiredString("stringF").requiredBytes("bytesF")
.name("fixedF1").type().fixed("F1").size(1).noDefault().name("enumF").type().enumeration("E1").symbols("S")
.noDefault().name("mapF").type().map().values().stringType().noDefault().name("arrayF").type().array().items()
.stringType().noDefault().name("recordF").type().record("inner").fields().name("f").type().intType().noDefault()
.endRecord().noDefault().optionalBoolean("boolO").endRecord();
testValidatorPasses(builder.mutualReadStrategy().validateLatest(), s, s);
}
@Test
public void testReadOnePrior() throws SchemaValidationException {
testValidatorPasses(builder.canReadStrategy().validateLatest(), rec3, rec);
testValidatorPasses(builder.canReadStrategy().validateLatest(), rec5, rec3);
testValidatorFails(builder.canReadStrategy().validateLatest(), rec4, rec);
}
@Test
public void testReadAllPrior() throws SchemaValidationException {
testValidatorPasses(builder.canReadStrategy().validateAll(), rec3, rec, rec2);
testValidatorFails(builder.canReadStrategy().validateAll(), rec4, rec, rec2, rec3);
testValidatorFails(builder.canReadStrategy().validateAll(), rec5, rec, rec2, rec3);
}
@Test
public void testOnePriorCanRead() throws SchemaValidationException {
testValidatorPasses(builder.canBeReadStrategy().validateLatest(), rec, rec3);
testValidatorFails(builder.canBeReadStrategy().validateLatest(), rec, rec4);
}
@Test
public void testAllPriorCanRead() throws SchemaValidationException {
testValidatorPasses(builder.canBeReadStrategy().validateAll(), rec, rec3, rec2);
testValidatorFails(builder.canBeReadStrategy().validateAll(), rec, rec4, rec3, rec2);
}
@Test
public void testOnePriorCompatible() throws SchemaValidationException {
testValidatorPasses(builder.mutualReadStrategy().validateLatest(), rec, rec3);
testValidatorFails(builder.mutualReadStrategy().validateLatest(), rec, rec4);
}
@Test
public void testAllPriorCompatible() throws SchemaValidationException {
testValidatorPasses(builder.mutualReadStrategy().validateAll(), rec, rec3, rec2);
testValidatorFails(builder.mutualReadStrategy().validateAll(), rec, rec4, rec3, rec2);
}
@Test(expected = AvroRuntimeException.class)
public void testInvalidBuild() {
builder.strategy(null).validateAll();
}
public static class Point {
double x;
double y;
}
public static class Circle {
Point center;
double radius;
}
public static final Schema circleSchema = SchemaBuilder.record("Circle").fields().name("center").type()
.record("Point").fields().requiredDouble("x").requiredDouble("y").endRecord().noDefault().requiredDouble("radius")
.endRecord();
public static final Schema circleSchemaDifferentNames = SchemaBuilder.record("crcl").fields().name("center").type()
.record("pt").fields().requiredDouble("x").requiredDouble("y").endRecord().noDefault().requiredDouble("radius")
.endRecord();
@Test
public void testReflectMatchStructure() throws SchemaValidationException {
testValidatorPasses(builder.canBeReadStrategy().validateAll(), circleSchemaDifferentNames,
ReflectData.get().getSchema(Circle.class));
}
@Test
public void testReflectWithAllowNullMatchStructure() throws SchemaValidationException {
testValidatorPasses(builder.canBeReadStrategy().validateAll(), circleSchemaDifferentNames,
ReflectData.AllowNull.get().getSchema(Circle.class));
}
@Test
public void testUnionWithIncompatibleElements() throws SchemaValidationException {
Schema union1 = Schema.createUnion(Collections.singletonList(rec));
Schema union2 = Schema.createUnion(Collections.singletonList(rec4));
testValidatorFails(builder.canReadStrategy().validateAll(), union2, union1);
}
@Test
public void testUnionWithCompatibleElements() throws SchemaValidationException {
Schema union1 = Schema.createUnion(Collections.singletonList(rec));
Schema union2 = Schema.createUnion(Collections.singletonList(rec3));
testValidatorPasses(builder.canReadStrategy().validateAll(), union2, union1);
}
@Test
public void testSchemaCompatibilitySuccesses() throws SchemaValidationException {
// float-union-to-int/long-union does not work...
// and neither does recursive types
for (ReaderWriter tc : COMPATIBLE_READER_WRITER_TEST_CASES) {
testValidatorPasses(builder.canReadStrategy().validateAll(), tc.getReader(), tc.getWriter());
}
}
@Test
public void testSchemaCompatibilityFailures() throws SchemaValidationException {
for (ReaderWriter tc : INCOMPATIBLE_READER_WRITER_TEST_CASES) {
Schema reader = tc.getReader();
Schema writer = tc.getWriter();
expectedException.expect(SchemaValidationException.class);
expectedException.expectMessage("Unable to read schema: \n" + writer.toString());
SchemaValidator validator = builder.canReadStrategy().validateAll();
validator.validate(reader, Collections.singleton(writer));
}
}
private void testValidatorPasses(SchemaValidator validator, Schema schema, Schema... prev)
throws SchemaValidationException {
ArrayList<Schema> prior = new ArrayList<>();
for (int i = prev.length - 1; i >= 0; i--) {
prior.add(prev[i]);
}
validator.validate(schema, prior);
}
private void testValidatorFails(SchemaValidator validator, Schema schemaFails, Schema... prev)
throws SchemaValidationException {
ArrayList<Schema> prior = new ArrayList<>();
for (int i = prev.length - 1; i >= 0; i--) {
prior.add(prev[i]);
}
boolean threw = false;
try {
// should fail
validator.validate(schemaFails, prior);
} catch (SchemaValidationException sve) {
threw = true;
}
Assert.assertTrue(threw);
}
public static final org.apache.avro.Schema recursiveSchema = new org.apache.avro.Schema.Parser().parse(
"{\"type\":\"record\",\"name\":\"Node\",\"namespace\":\"avro\",\"fields\":[{\"name\":\"value\",\"type\":[\"null\",\"Node\"],\"default\":null}]}");
/**
* Unit test to verify that recursive schemas can be validated. See AVRO-2122.
*/
@Test
public void testRecursiveSchemaValidation() throws SchemaValidationException {
// before AVRO-2122, this would cause a StackOverflowError
final SchemaValidator backwardValidator = builder.canReadStrategy().validateLatest();
backwardValidator.validate(recursiveSchema, Collections.singletonList(recursiveSchema));
}
}