/**
* 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
*
* http://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 java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import org.apache.avro.Conversion;
import org.apache.avro.Conversions;
import org.apache.avro.LogicalType;
import org.apache.avro.LogicalTypes;
import org.apache.avro.Schema;
import org.apache.avro.file.DataFileReader;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.file.FileReader;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DatumWriter;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class TestGenericLogicalTypes {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
public static final GenericData GENERIC = new GenericData();
@BeforeClass
public static void addDecimalAndUUID() {
GENERIC.addLogicalTypeConversion(new Conversions.DecimalConversion());
GENERIC.addLogicalTypeConversion(new Conversions.UUIDConversion());
}
@Test
public void testReadUUID() throws IOException {
Schema uuidSchema = Schema.create(Schema.Type.STRING);
LogicalTypes.uuid().addToSchema(uuidSchema);
UUID u1 = UUID.randomUUID();
UUID u2 = UUID.randomUUID();
List<UUID> expected = Arrays.asList(u1, u2);
File test = write(Schema.create(Schema.Type.STRING),
u1.toString(), u2.toString());
Assert.assertEquals("Should convert Strings to UUIDs",
expected, read(GENERIC.createDatumReader(uuidSchema), test));
}
@Test
public void testWriteUUID() throws IOException {
Schema stringSchema = Schema.create(Schema.Type.STRING);
stringSchema.addProp(GenericData.STRING_PROP, "String");
Schema uuidSchema = Schema.create(Schema.Type.STRING);
LogicalTypes.uuid().addToSchema(uuidSchema);
UUID u1 = UUID.randomUUID();
UUID u2 = UUID.randomUUID();
List<String> expected = Arrays.asList(u1.toString(), u2.toString());
File test = write(GENERIC, uuidSchema, u1, u2);
Assert.assertEquals("Should read UUIDs as Strings",
expected, read(GenericData.get().createDatumReader(stringSchema), test));
}
@Test
public void testWriteNullableUUID() throws IOException {
Schema stringSchema = Schema.create(Schema.Type.STRING);
stringSchema.addProp(GenericData.STRING_PROP, "String");
Schema nullableStringSchema = Schema.createUnion(
Schema.create(Schema.Type.NULL), stringSchema);
Schema uuidSchema = Schema.create(Schema.Type.STRING);
LogicalTypes.uuid().addToSchema(uuidSchema);
Schema nullableUuidSchema = Schema.createUnion(
Schema.create(Schema.Type.NULL), uuidSchema);
UUID u1 = UUID.randomUUID();
UUID u2 = UUID.randomUUID();
List<String> expected = Arrays.asList(u1.toString(), u2.toString());
File test = write(GENERIC, nullableUuidSchema, u1, u2);
Assert.assertEquals("Should read UUIDs as Strings",
expected,
read(GenericData.get().createDatumReader(nullableStringSchema), test));
}
@Test
public void testReadDecimalFixed() throws IOException {
LogicalType decimal = LogicalTypes.decimal(9, 2);
Schema fixedSchema = Schema.createFixed("aFixed", null, null, 4);
Schema decimalSchema = decimal.addToSchema(
Schema.createFixed("aFixed", null, null, 4));
BigDecimal d1 = new BigDecimal("-34.34");
BigDecimal d2 = new BigDecimal("117230.00");
List<BigDecimal> expected = Arrays.asList(d1, d2);
Conversion<BigDecimal> conversion = new Conversions.DecimalConversion();
// use the conversion directly instead of relying on the write side
GenericFixed d1fixed = conversion.toFixed(d1, fixedSchema, decimal);
GenericFixed d2fixed = conversion.toFixed(d2, fixedSchema, decimal);
File test = write(fixedSchema, d1fixed, d2fixed);
Assert.assertEquals("Should convert fixed to BigDecimals",
expected, read(GENERIC.createDatumReader(decimalSchema), test));
}
@Test
public void testWriteDecimalFixed() throws IOException {
LogicalType decimal = LogicalTypes.decimal(9, 2);
Schema fixedSchema = Schema.createFixed("aFixed", null, null, 4);
Schema decimalSchema = decimal.addToSchema(
Schema.createFixed("aFixed", null, null, 4));
BigDecimal d1 = new BigDecimal("-34.34");
BigDecimal d2 = new BigDecimal("117230.00");
Conversion<BigDecimal> conversion = new Conversions.DecimalConversion();
GenericFixed d1fixed = conversion.toFixed(d1, fixedSchema, decimal);
GenericFixed d2fixed = conversion.toFixed(d2, fixedSchema, decimal);
List<GenericFixed> expected = Arrays.asList(d1fixed, d2fixed);
File test = write(GENERIC, decimalSchema, d1, d2);
Assert.assertEquals("Should read BigDecimals as fixed",
expected, read(GenericData.get().createDatumReader(fixedSchema), test));
}
@Test
public void testReadDecimalBytes() throws IOException {
LogicalType decimal = LogicalTypes.decimal(9, 2);
Schema bytesSchema = Schema.create(Schema.Type.BYTES);
Schema decimalSchema = decimal.addToSchema(Schema.create(Schema.Type.BYTES));
BigDecimal d1 = new BigDecimal("-34.34");
BigDecimal d2 = new BigDecimal("117230.00");
List<BigDecimal> expected = Arrays.asList(d1, d2);
Conversion<BigDecimal> conversion = new Conversions.DecimalConversion();
// use the conversion directly instead of relying on the write side
ByteBuffer d1bytes = conversion.toBytes(d1, bytesSchema, decimal);
ByteBuffer d2bytes = conversion.toBytes(d2, bytesSchema, decimal);
File test = write(bytesSchema, d1bytes, d2bytes);
Assert.assertEquals("Should convert bytes to BigDecimals",
expected, read(GENERIC.createDatumReader(decimalSchema), test));
}
@Test
public void testWriteDecimalBytes() throws IOException {
LogicalType decimal = LogicalTypes.decimal(9, 2);
Schema bytesSchema = Schema.create(Schema.Type.BYTES);
Schema decimalSchema = decimal.addToSchema(Schema.create(Schema.Type.BYTES));
BigDecimal d1 = new BigDecimal("-34.34");
BigDecimal d2 = new BigDecimal("117230.00");
Conversion<BigDecimal> conversion = new Conversions.DecimalConversion();
// use the conversion directly instead of relying on the write side
ByteBuffer d1bytes = conversion.toBytes(d1, bytesSchema, decimal);
ByteBuffer d2bytes = conversion.toBytes(d2, bytesSchema, decimal);
List<ByteBuffer> expected = Arrays.asList(d1bytes, d2bytes);
File test = write(GENERIC, decimalSchema, d1bytes, d2bytes);
Assert.assertEquals("Should read BigDecimals as bytes",
expected, read(GenericData.get().createDatumReader(bytesSchema), test));
}
private <D> List<D> read(DatumReader<D> reader, File file) throws IOException {
List<D> data = new ArrayList<D>();
FileReader<D> fileReader = null;
try {
fileReader = new DataFileReader<D>(file, reader);
for (D datum : fileReader) {
data.add(datum);
}
} finally {
if (fileReader != null) {
fileReader.close();
}
}
return data;
}
private <D> File write(Schema schema, D... data) throws IOException {
return write(GenericData.get(), schema, data);
}
@SuppressWarnings("unchecked")
private <D> File write(GenericData model, Schema schema, D... data) throws IOException {
File file = temp.newFile();
DatumWriter<D> writer = model.createDatumWriter(schema);
DataFileWriter<D> fileWriter = new DataFileWriter<D>(writer);
try {
fileWriter.create(schema, file);
for (D datum : data) {
fileWriter.append(datum);
}
} finally {
fileWriter.close();
}
return file;
}
@Test
public void testCopyUuid() {
testCopy(LogicalTypes.uuid().addToSchema(Schema.create(Schema.Type.STRING)),
UUID.randomUUID(),
GENERIC);
}
@Test
public void testCopyUuidRaw() {
testCopy(LogicalTypes.uuid().addToSchema(Schema.create(Schema.Type.STRING)),
UUID.randomUUID().toString(), // use raw type
GenericData.get()); // with no conversions
}
@Test
public void testCopyDecimal() {
testCopy(LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES)),
new BigDecimal("-34.34"),
GENERIC);
}
@Test
public void testCopyDecimalRaw() {
testCopy(LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES)),
ByteBuffer.wrap(new BigDecimal("-34.34").unscaledValue().toByteArray()),
GenericData.get()); // no conversions
}
private void testCopy(Schema schema, Object value, GenericData model) {
// test direct copy of instance
checkCopy(value, model.deepCopy(schema, value), false);
// test nested in a record
Schema recordSchema = Schema.createRecord("X", "", "test", false);
List<Schema.Field> fields = new ArrayList<Schema.Field>();
fields.add(new Schema.Field("x", schema, "", null));
recordSchema.setFields(fields);
GenericRecordBuilder builder = new GenericRecordBuilder(recordSchema);
builder.set("x", value);
GenericData.Record record = builder.build();
checkCopy(record, model.deepCopy(recordSchema, record), true);
// test nested in array
Schema arraySchema = Schema.createArray(schema);
ArrayList array = new ArrayList(Arrays.asList(value));
checkCopy(array, model.deepCopy(arraySchema, array), true);
// test record nested in array
Schema recordArraySchema = Schema.createArray(recordSchema);
ArrayList recordArray = new ArrayList(Arrays.asList(record));
checkCopy(recordArray, model.deepCopy(recordArraySchema, recordArray), true);
}
private void checkCopy(Object original, Object copy, boolean notSame) {
if (notSame)
Assert.assertNotSame(original, copy);
Assert.assertEquals(original, copy);
}
}