blob: 21492d1b0ec4e0d3d4b7a47e0446057722b729fb [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.generic;
import static org.apache.avro.TestCircularReferences.Reference;
import static org.apache.avro.TestCircularReferences.Referenceable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.avro.AvroRuntimeException;
import org.apache.avro.Schema;
import org.apache.avro.Schema.Field;
import org.apache.avro.Schema.Type;
import org.apache.avro.SchemaBuilder;
import org.apache.avro.TestCircularReferences.ReferenceManager;
import org.apache.avro.generic.GenericData.Record;
import org.apache.avro.io.BinaryData;
import org.apache.avro.io.BinaryEncoder;
import org.apache.avro.io.EncoderFactory;
import org.apache.avro.util.Utf8;
import org.junit.Test;
public class TestGenericData {
@Test(expected = AvroRuntimeException.class)
public void testrecordConstructorNullSchema() throws Exception {
new GenericData.Record(null);
}
@Test(expected = AvroRuntimeException.class)
public void testrecordConstructorWrongSchema() throws Exception {
new GenericData.Record(Schema.create(Schema.Type.INT));
}
@Test(expected = AvroRuntimeException.class)
public void testArrayConstructorNullSchema() throws Exception {
new GenericData.Array<>(1, null);
}
@Test(expected = AvroRuntimeException.class)
public void testArrayConstructorWrongSchema() throws Exception {
new GenericData.Array<>(1, Schema.create(Schema.Type.INT));
}
@Test(expected = AvroRuntimeException.class)
public void testRecordCreateEmptySchema() throws Exception {
Schema s = Schema.createRecord("schemaName", "schemaDoc", "namespace", false);
new GenericData.Record(s);
}
@Test(expected = AvroRuntimeException.class)
public void testGetEmptySchemaFields() throws Exception {
Schema s = Schema.createRecord("schemaName", "schemaDoc", "namespace", false);
s.getFields();
}
@Test(expected = AvroRuntimeException.class)
public void testGetEmptySchemaField() throws Exception {
Schema s = Schema.createRecord("schemaName", "schemaDoc", "namespace", false);
s.getField("foo");
}
@Test(expected = AvroRuntimeException.class)
public void testRecordPutInvalidField() throws Exception {
Schema s = Schema.createRecord("schemaName", "schemaDoc", "namespace", false);
List<Schema.Field> fields = new ArrayList<>();
fields.add(new Schema.Field("someFieldName", s, "docs", null));
s.setFields(fields);
Record r = new GenericData.Record(s);
r.put("invalidFieldName", "someValue");
}
@Test
/** Make sure that even with nulls, hashCode() doesn't throw NPE. */
public void testHashCode() {
GenericData.get().hashCode(null, Schema.create(Type.NULL));
GenericData.get().hashCode(null,
Schema.createUnion(Arrays.asList(Schema.create(Type.BOOLEAN), Schema.create(Type.STRING))));
List<CharSequence> stuff = new ArrayList<>();
stuff.add("string");
Schema schema = recordSchema();
GenericRecord r = new GenericData.Record(schema);
r.put(0, stuff);
GenericData.get().hashCode(r, schema);
}
@Test
public void testEquals() {
Schema s = recordSchema();
GenericRecord r0 = new GenericData.Record(s);
GenericRecord r1 = new GenericData.Record(s);
GenericRecord r2 = new GenericData.Record(s);
Collection<CharSequence> l0 = new ArrayDeque<>();
List<CharSequence> l1 = new ArrayList<>();
GenericArray<CharSequence> l2 = new GenericData.Array<>(1, s.getFields().get(0).schema());
String foo = "foo";
l0.add(new StringBuilder(foo));
l1.add(foo);
l2.add(new Utf8(foo));
r0.put(0, l0);
r1.put(0, l1);
r2.put(0, l2);
assertEquals(r0, r1);
assertEquals(r0, r2);
assertEquals(r1, r2);
}
private Schema recordSchema() {
List<Field> fields = new ArrayList<>();
fields.add(new Field("anArray", Schema.createArray(Schema.create(Type.STRING)), null, null));
Schema schema = Schema.createRecord("arrayFoo", "test", "mytest", false);
schema.setFields(fields);
return schema;
}
@Test
public void testEquals2() {
Schema schema1 = Schema.createRecord("r", null, "x", false);
List<Field> fields1 = new ArrayList<>();
fields1.add(new Field("a", Schema.create(Schema.Type.STRING), null, null, Field.Order.IGNORE));
schema1.setFields(fields1);
// only differs in field order
Schema schema2 = Schema.createRecord("r", null, "x", false);
List<Field> fields2 = new ArrayList<>();
fields2.add(new Field("a", Schema.create(Schema.Type.STRING), null, null, Field.Order.ASCENDING));
schema2.setFields(fields2);
GenericRecord record1 = new GenericData.Record(schema1);
record1.put("a", "1");
GenericRecord record2 = new GenericData.Record(schema2);
record2.put("a", "2");
assertFalse(record2.equals(record1));
assertFalse(record1.equals(record2));
}
@Test(expected = AvroRuntimeException.class)
public void testRecordGetFieldDoesntExist() throws Exception {
Schema schema = Schema.createRecord("test", "doc", "test", false, Collections.EMPTY_LIST);
GenericData.Record record = new GenericData.Record(schema);
record.get("does not exist");
}
@Test
public void testArrayReversal() {
Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
GenericArray<Integer> forward = new GenericData.Array<>(10, schema);
GenericArray<Integer> backward = new GenericData.Array<>(10, schema);
for (int i = 0; i <= 9; i++) {
forward.add(i);
}
for (int i = 9; i >= 0; i--) {
backward.add(i);
}
forward.reverse();
assertTrue(forward.equals(backward));
}
@Test
public void testArrayListInterface() {
Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
GenericArray<Integer> array = new GenericData.Array<>(1, schema);
array.add(99);
assertEquals(Integer.valueOf(99), array.get(0));
List<Integer> list = new ArrayList<>();
list.add(99);
assertEquals(array, list);
assertEquals(list, array);
assertEquals(list.hashCode(), array.hashCode());
try {
array.get(2);
fail("Expected IndexOutOfBoundsException getting index 2");
} catch (IndexOutOfBoundsException e) {
}
array.clear();
assertEquals(0, array.size());
try {
array.get(0);
fail("Expected IndexOutOfBoundsException getting index 0 after clear()");
} catch (IndexOutOfBoundsException e) {
}
}
@Test
public void testArrayAddAtLocation() {
Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
GenericArray<Integer> array = new GenericData.Array<>(6, schema);
array.clear();
for (int i = 0; i < 5; ++i)
array.add(i);
assertEquals(5, array.size());
array.add(0, 6);
assertEquals(Integer.valueOf(6), array.get(0));
assertEquals(6, array.size());
assertEquals(Integer.valueOf(0), array.get(1));
assertEquals(Integer.valueOf(4), array.get(5));
array.add(6, 7);
assertEquals(Integer.valueOf(7), array.get(6));
assertEquals(7, array.size());
assertEquals(Integer.valueOf(6), array.get(0));
assertEquals(Integer.valueOf(4), array.get(5));
array.add(1, 8);
assertEquals(Integer.valueOf(8), array.get(1));
assertEquals(Integer.valueOf(0), array.get(2));
assertEquals(Integer.valueOf(6), array.get(0));
assertEquals(8, array.size());
try {
array.get(9);
fail("Expected IndexOutOfBoundsException after adding elements");
} catch (IndexOutOfBoundsException e) {
}
}
@Test
public void testArrayRemove() {
Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
GenericArray<Integer> array = new GenericData.Array<>(10, schema);
array.clear();
for (int i = 0; i < 10; ++i)
array.add(i);
assertEquals(10, array.size());
assertEquals(Integer.valueOf(0), array.get(0));
assertEquals(Integer.valueOf(9), array.get(9));
array.remove(0);
assertEquals(9, array.size());
assertEquals(Integer.valueOf(1), array.get(0));
assertEquals(Integer.valueOf(2), array.get(1));
assertEquals(Integer.valueOf(9), array.get(8));
// Test boundary errors.
try {
array.get(9);
fail("Expected IndexOutOfBoundsException after removing an element");
} catch (IndexOutOfBoundsException e) {
}
try {
array.set(9, 99);
fail("Expected IndexOutOfBoundsException after removing an element");
} catch (IndexOutOfBoundsException e) {
}
try {
array.remove(9);
fail("Expected IndexOutOfBoundsException after removing an element");
} catch (IndexOutOfBoundsException e) {
}
// Test that we can still remove for properly sized arrays, and the rval
assertEquals(Integer.valueOf(9), array.remove(8));
assertEquals(8, array.size());
// Test insertion after remove
array.add(88);
assertEquals(Integer.valueOf(88), array.get(8));
}
@Test
public void testArraySet() {
Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
GenericArray<Integer> array = new GenericData.Array<>(10, schema);
array.clear();
for (int i = 0; i < 10; ++i)
array.add(i);
assertEquals(10, array.size());
assertEquals(Integer.valueOf(0), array.get(0));
assertEquals(Integer.valueOf(5), array.get(5));
assertEquals(Integer.valueOf(5), array.set(5, 55));
assertEquals(10, array.size());
assertEquals(Integer.valueOf(55), array.get(5));
}
@Test
public void testToStringIsJson() throws JsonParseException, IOException {
Field stringField = new Field("string", Schema.create(Type.STRING), null, null);
Field enumField = new Field("enum", Schema.createEnum("my_enum", "doc", null, Arrays.asList("a", "b", "c")), null,
null);
Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
schema.setFields(Arrays.asList(stringField, enumField));
GenericRecord r = new GenericData.Record(schema);
// \u2013 is EN DASH
r.put(stringField.name(), "hello\nthere\"\tyou\u2013}");
r.put(enumField.name(), new GenericData.EnumSymbol(enumField.schema(), "a"));
String json = r.toString();
JsonFactory factory = new JsonFactory();
JsonParser parser = factory.createParser(json);
ObjectMapper mapper = new ObjectMapper();
// will throw exception if string is not parsable json
mapper.readTree(parser);
}
@Test
public void testMapWithNonStringKeyToStringIsJson() throws Exception {
Schema intMapSchema = new Schema.Parser()
.parse("{\"type\": \"map\", \"values\": \"string\", \"java-key-class\" : \"java.lang.Integer\"}");
Field intMapField = new Field("intMap", Schema.createMap(intMapSchema), null, null);
Schema decMapSchema = new Schema.Parser()
.parse("{\"type\": \"map\", \"values\": \"string\", \"java-key-class\" : \"java.math.BigDecimal\"}");
Field decMapField = new Field("decMap", Schema.createMap(decMapSchema), null, null);
Schema boolMapSchema = new Schema.Parser()
.parse("{\"type\": \"map\", \"values\": \"string\", \"java-key-class\" : \"java.lang.Boolean\"}");
Field boolMapField = new Field("boolMap", Schema.createMap(boolMapSchema), null, null);
Schema fileMapSchema = new Schema.Parser()
.parse("{\"type\": \"map\", \"values\": \"string\", \"java-key-class\" : \"java.io.File\"}");
Field fileMapField = new Field("fileMap", Schema.createMap(fileMapSchema), null, null);
Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
schema.setFields(Arrays.asList(intMapField, decMapField, boolMapField, fileMapField));
HashMap<Integer, String> intPair = new HashMap<>();
intPair.put(1, "one");
intPair.put(2, "two");
HashMap<java.math.BigDecimal, String> decPair = new HashMap<>();
decPair.put(java.math.BigDecimal.valueOf(1), "one");
decPair.put(java.math.BigDecimal.valueOf(2), "two");
HashMap<Boolean, String> boolPair = new HashMap<>();
boolPair.put(true, "isTrue");
boolPair.put(false, "isFalse");
boolPair.put(null, null);
HashMap<java.io.File, String> filePair = new HashMap<>();
java.io.File f = new java.io.File(getClass().getResource("/SchemaBuilder.avsc").toURI());
filePair.put(f, "File");
GenericRecord r = new GenericData.Record(schema);
r.put(intMapField.name(), intPair);
r.put(decMapField.name(), decPair);
r.put(boolMapField.name(), boolPair);
r.put(fileMapField.name(), filePair);
String json = r.toString();
JsonFactory factory = new JsonFactory();
JsonParser parser = factory.createParser(json);
ObjectMapper mapper = new ObjectMapper();
// will throw exception if string is not parsable json
mapper.readTree(parser);
}
@Test
public void testToStringEscapesControlCharsInBytes() throws Exception {
GenericData data = GenericData.get();
ByteBuffer bytes = ByteBuffer.wrap(new byte[] { 'a', '\n', 'b' });
assertEquals("\"a\\nb\"", data.toString(bytes));
assertEquals("\"a\\nb\"", data.toString(bytes));
}
@Test
public void testToStringEscapesControlCharsInMap() {
GenericData data = GenericData.get();
Map<String, String> m = new HashMap<>();
m.put("a\n\\b", "a\n\\b");
assertEquals("{\"a\\n\\\\b\": \"a\\n\\\\b\"}", data.toString(m));
}
@Test
public void testToStringFixed() throws Exception {
GenericData data = GenericData.get();
assertEquals("[97, 10, 98]",
data.toString(new GenericData.Fixed(Schema.createFixed("test", null, null, 3), new byte[] { 'a', '\n', 'b' })));
}
@Test
public void testToStringDoesNotEscapeForwardSlash() throws Exception {
GenericData data = GenericData.get();
assertEquals("\"/\"", data.toString("/"));
}
@Test
public void testToStringNanInfinity() throws Exception {
GenericData data = GenericData.get();
assertEquals("\"Infinity\"", data.toString(Float.POSITIVE_INFINITY));
assertEquals("\"-Infinity\"", data.toString(Float.NEGATIVE_INFINITY));
assertEquals("\"NaN\"", data.toString(Float.NaN));
assertEquals("\"Infinity\"", data.toString(Double.POSITIVE_INFINITY));
assertEquals("\"-Infinity\"", data.toString(Double.NEGATIVE_INFINITY));
assertEquals("\"NaN\"", data.toString(Double.NaN));
}
@Test
public void testToStringConvertsDatesAsStrings() throws Exception {
GenericData data = GenericData.get();
assertEquals("\"1961-04-12T06:07:10Z\"", data.toString(Instant.parse("1961-04-12T06:07:10Z")));
assertEquals("\"1961-04-12\"", data.toString(LocalDate.parse("1961-04-12")));
assertEquals("\"1961-04-12T06:07:10\"", data.toString(LocalDateTime.parse("1961-04-12T06:07:10")));
assertEquals("\"10:10:10\"", data.toString(LocalTime.parse("10:10:10")));
}
@Test
public void testToStringConvertsUuidsAsStrings() throws Exception {
GenericData data = GenericData.get();
assertEquals("\"abf2f1e8-cece-4fdc-290a-babaca09ec74\"",
data.toString(UUID.fromString("abf2f1e8-cece-4fdc-290a-babaca09ec74")));
}
@Test
public void testCompare() {
// Prepare a schema for testing.
Field integerField = new Field("test", Schema.create(Type.INT), null, null);
List<Field> fields = new ArrayList<>();
fields.add(integerField);
Schema record = Schema.createRecord("test", null, null, false);
record.setFields(fields);
ByteArrayOutputStream b1 = new ByteArrayOutputStream(5);
ByteArrayOutputStream b2 = new ByteArrayOutputStream(5);
BinaryEncoder b1Enc = EncoderFactory.get().binaryEncoder(b1, null);
BinaryEncoder b2Enc = EncoderFactory.get().binaryEncoder(b2, null);
// Prepare two different datums
Record testDatum1 = new Record(record);
testDatum1.put(0, 1);
Record testDatum2 = new Record(record);
testDatum2.put(0, 2);
GenericDatumWriter<Record> gWriter = new GenericDatumWriter<>(record);
Integer start1 = 0, start2 = 0;
try {
// Write two datums in each stream
// and get the offset length after the first write in each.
gWriter.write(testDatum1, b1Enc);
b1Enc.flush();
start1 = b1.size();
gWriter.write(testDatum1, b1Enc);
b1Enc.flush();
b1.close();
gWriter.write(testDatum2, b2Enc);
b2Enc.flush();
start2 = b2.size();
gWriter.write(testDatum2, b2Enc);
b2Enc.flush();
b2.close();
// Compare to check if offset-based compare works right.
assertEquals(-1, BinaryData.compare(b1.toByteArray(), start1, b2.toByteArray(), start2, record));
} catch (IOException e) {
fail("IOException while writing records to output stream.");
}
}
@Test
public void testEnumCompare() {
Schema s = Schema.createEnum("Kind", null, null, Arrays.asList("Z", "Y", "X"));
GenericEnumSymbol z = new GenericData.EnumSymbol(s, "Z");
GenericEnumSymbol z2 = new GenericData.EnumSymbol(s, "Z");
assertEquals(0, z.compareTo(z2));
GenericEnumSymbol y = new GenericData.EnumSymbol(s, "Y");
assertTrue(y.compareTo(z) > 0);
assertTrue(z.compareTo(y) < 0);
}
@Test
public void testByteBufferDeepCopy() {
// Test that a deep copy of a byte buffer respects the byte buffer
// limits and capacity.
byte[] buffer_value = { 0, 1, 2, 3, 0, 0, 0 };
ByteBuffer buffer = ByteBuffer.wrap(buffer_value, 1, 4);
Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
Field byte_field = new Field("bytes", Schema.create(Type.BYTES), null, null);
schema.setFields(Collections.singletonList(byte_field));
GenericRecord record = new GenericData.Record(schema);
record.put(byte_field.name(), buffer);
GenericRecord copy = GenericData.get().deepCopy(schema, record);
ByteBuffer buffer_copy = (ByteBuffer) copy.get(byte_field.name());
assertEquals(buffer, buffer_copy);
}
@Test
public void testValidateNullableEnum() {
List<Schema> unionTypes = new ArrayList<>();
Schema schema;
Schema nullSchema = Schema.create(Type.NULL);
Schema enumSchema = Schema.createEnum("AnEnum", null, null, Arrays.asList("X", "Y", "Z"));
GenericEnumSymbol w = new GenericData.EnumSymbol(enumSchema, "W");
GenericEnumSymbol x = new GenericData.EnumSymbol(enumSchema, "X");
GenericEnumSymbol y = new GenericData.EnumSymbol(enumSchema, "Y");
GenericEnumSymbol z = new GenericData.EnumSymbol(enumSchema, "Z");
// null is first
unionTypes.clear();
unionTypes.add(nullSchema);
unionTypes.add(enumSchema);
schema = Schema.createUnion(unionTypes);
assertTrue(GenericData.get().validate(schema, z));
assertTrue(GenericData.get().validate(schema, y));
assertTrue(GenericData.get().validate(schema, x));
assertFalse(GenericData.get().validate(schema, w));
assertTrue(GenericData.get().validate(schema, null));
// null is last
unionTypes.clear();
unionTypes.add(enumSchema);
unionTypes.add(nullSchema);
schema = Schema.createUnion(unionTypes);
assertTrue(GenericData.get().validate(schema, z));
assertTrue(GenericData.get().validate(schema, y));
assertTrue(GenericData.get().validate(schema, x));
assertFalse(GenericData.get().validate(schema, w));
assertTrue(GenericData.get().validate(schema, null));
}
private enum anEnum {
ONE, TWO, THREE
};
@Test
public void validateRequiresGenericSymbolForEnumSchema() {
final Schema schema = Schema.createEnum("my_enum", "doc", "namespace", Arrays.asList("ONE", "TWO", "THREE"));
final GenericData gd = GenericData.get();
/* positive cases */
assertTrue(gd.validate(schema, new GenericData.EnumSymbol(schema, "ONE")));
assertTrue(gd.validate(schema, new GenericData.EnumSymbol(schema, anEnum.ONE)));
/* negative cases */
assertFalse("We don't expect GenericData to allow a String datum for an enum schema", gd.validate(schema, "ONE"));
assertFalse("We don't expect GenericData to allow a Java Enum for an enum schema", gd.validate(schema, anEnum.ONE));
}
@Test
public void testValidateUnion() {
Schema type1Schema = SchemaBuilder.record("Type1").fields().requiredString("myString").requiredInt("myInt")
.endRecord();
Schema type2Schema = SchemaBuilder.record("Type2").fields().requiredString("myString").endRecord();
Schema unionSchema = SchemaBuilder.unionOf().type(type1Schema).and().type(type2Schema).endUnion();
GenericRecord record = new GenericData.Record(type2Schema);
record.put("myString", "myValue");
assertTrue(GenericData.get().validate(unionSchema, record));
}
/*
* The toString has a detection for circular references to abort. This detection
* has the risk of detecting that same value as being a circular reference. For
* Record, Map and Array this is correct, for the rest is is not.
*/
@Test
public void testToStringSameValues() throws IOException {
List<Field> fields = new ArrayList<>();
fields.add(new Field("nullstring1", Schema.create(Type.STRING), null, null));
fields.add(new Field("nullstring2", Schema.create(Type.STRING), null, null));
fields.add(new Field("string1", Schema.create(Type.STRING), null, null));
fields.add(new Field("string2", Schema.create(Type.STRING), null, null));
fields.add(new Field("bytes1", Schema.create(Type.BYTES), null, null));
fields.add(new Field("bytes2", Schema.create(Type.BYTES), null, null));
fields.add(new Field("int1", Schema.create(Type.INT), null, null));
fields.add(new Field("int2", Schema.create(Type.INT), null, null));
fields.add(new Field("long1", Schema.create(Type.LONG), null, null));
fields.add(new Field("long2", Schema.create(Type.LONG), null, null));
fields.add(new Field("float1", Schema.create(Type.FLOAT), null, null));
fields.add(new Field("float2", Schema.create(Type.FLOAT), null, null));
fields.add(new Field("double1", Schema.create(Type.DOUBLE), null, null));
fields.add(new Field("double2", Schema.create(Type.DOUBLE), null, null));
fields.add(new Field("boolean1", Schema.create(Type.BOOLEAN), null, null));
fields.add(new Field("boolean2", Schema.create(Type.BOOLEAN), null, null));
List<String> enumValues = new ArrayList<>();
enumValues.add("One");
enumValues.add("Two");
Schema enumSchema = Schema.createEnum("myEnum", null, null, enumValues);
fields.add(new Field("enum1", enumSchema, null, null));
fields.add(new Field("enum2", enumSchema, null, null));
Schema recordSchema = SchemaBuilder.record("aRecord").fields().requiredString("myString").endRecord();
fields.add(new Field("record1", recordSchema, null, null));
fields.add(new Field("record2", recordSchema, null, null));
Schema arraySchema = Schema.createArray(Schema.create(Type.STRING));
fields.add(new Field("array1", arraySchema, null, null));
fields.add(new Field("array2", arraySchema, null, null));
Schema mapSchema = Schema.createMap(Schema.create(Type.STRING));
fields.add(new Field("map1", mapSchema, null, null));
fields.add(new Field("map2", mapSchema, null, null));
Schema schema = Schema.createRecord("Foo", "test", "mytest", false);
schema.setFields(fields);
Record testRecord = new Record(schema);
testRecord.put("nullstring1", null);
testRecord.put("nullstring2", null);
String fortyTwo = "42";
testRecord.put("string1", fortyTwo);
testRecord.put("string2", fortyTwo);
testRecord.put("bytes1", 0x42);
testRecord.put("bytes2", 0x42);
testRecord.put("int1", 42);
testRecord.put("int2", 42);
testRecord.put("long1", 42L);
testRecord.put("long2", 42L);
testRecord.put("float1", 42F);
testRecord.put("float2", 42F);
testRecord.put("double1", 42D);
testRecord.put("double2", 42D);
testRecord.put("boolean1", true);
testRecord.put("boolean2", true);
testRecord.put("enum1", "One");
testRecord.put("enum2", "One");
GenericRecord record = new GenericData.Record(recordSchema);
record.put("myString", "42");
testRecord.put("record1", record);
testRecord.put("record2", record);
GenericArray<String> array = new GenericData.Array<>(1, arraySchema);
array.clear();
array.add("42");
testRecord.put("array1", array);
testRecord.put("array2", array);
Map<String, String> map = new HashMap<>();
map.put("42", "42");
testRecord.put("map1", map);
testRecord.put("map2", map);
String testString = testRecord.toString();
assertFalse("Record with duplicated values results in wrong 'toString()'",
testString.contains("CIRCULAR REFERENCE"));
}
// Test copied from Apache Parquet:
// org.apache.parquet.avro.TestCircularReferences
@Test
public void testToStringRecursive() throws IOException {
ReferenceManager manager = new ReferenceManager();
GenericData model = new GenericData();
model.addLogicalTypeConversion(manager.getTracker());
model.addLogicalTypeConversion(manager.getHandler());
Schema parentSchema = Schema.createRecord("Parent", null, null, false);
Schema placeholderSchema = Schema.createRecord("Placeholder", null, null, false);
List<Schema.Field> placeholderFields = new ArrayList<>();
placeholderFields.add( // at least one field is needed to be a valid schema
new Schema.Field("id", Schema.create(Schema.Type.LONG), null, null));
placeholderSchema.setFields(placeholderFields);
Referenceable idRef = new Referenceable("id");
Schema parentRefSchema = Schema.createUnion(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.LONG),
idRef.addToSchema(placeholderSchema));
Reference parentRef = new Reference("parent");
List<Schema.Field> childFields = new ArrayList<>();
childFields.add(new Schema.Field("c", Schema.create(Schema.Type.STRING), null, null));
childFields.add(new Schema.Field("parent", parentRefSchema, null, null));
Schema childSchema = parentRef.addToSchema(Schema.createRecord("Child", null, null, false, childFields));
List<Schema.Field> parentFields = new ArrayList<>();
parentFields.add(new Schema.Field("id", Schema.create(Schema.Type.LONG), null, null));
parentFields.add(new Schema.Field("p", Schema.create(Schema.Type.STRING), null, null));
parentFields.add(new Schema.Field("child", childSchema, null, null));
parentSchema.setFields(parentFields);
Schema schema = idRef.addToSchema(parentSchema);
Record parent = new Record(schema);
parent.put("id", 1L);
parent.put("p", "parent data!");
Record child = new Record(childSchema);
child.put("c", "child data!");
child.put("parent", parent);
parent.put("child", child);
try {
assertNotNull(parent.toString()); // This should not fail with an infinite recursion (StackOverflowError)
} catch (StackOverflowError e) {
fail("StackOverflowError occurred");
}
}
@Test
/**
* check that GenericArray.reset() retains reusable elements and that
* GenericArray.prune() cleans them up properly.
*/
public void testGenericArrayPeek() {
Schema elementSchema = SchemaBuilder.record("element").fields().requiredString("value").endRecord();
Schema arraySchema = Schema.createArray(elementSchema);
GenericRecord record = new GenericData.Record(elementSchema);
record.put("value", "string");
GenericArray<GenericRecord> list = new GenericData.Array<>(1, arraySchema);
list.add(record);
list.reset();
assertTrue(record == list.peek());
list.prune();
assertNull(list.peek());
}
}