-
Notifications
You must be signed in to change notification settings - Fork 1.8k
AVRO-1891: Fix specific nested logical types #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,12 +18,15 @@ | |
| package org.apache.avro.specific; | ||
|
|
||
| import org.apache.avro.Conversion; | ||
| import org.apache.avro.LogicalType; | ||
| import org.apache.avro.Schema; | ||
| import org.apache.avro.AvroRuntimeException; | ||
| import org.apache.avro.generic.GenericDatumReader; | ||
| import org.apache.avro.io.ResolvingDecoder; | ||
| import org.apache.avro.util.ClassUtils; | ||
| import java.io.IOException; | ||
| import java.util.LinkedList; | ||
| import java.util.Map; | ||
|
|
||
| /** {@link org.apache.avro.io.DatumReader DatumReader} for generated Java classes. */ | ||
| public class SpecificDatumReader<T> extends GenericDatumReader<T> { | ||
|
|
@@ -62,6 +65,42 @@ public SpecificDatumReader(SpecificData data) { | |
| /** Return the contained {@link SpecificData}. */ | ||
| public SpecificData getSpecificData() { return (SpecificData)getData(); } | ||
|
|
||
| private final ThreadLocal<LinkedList<Map<String, Conversion<?>>>> conversions | ||
| = new ThreadLocal<LinkedList<Map<String, Conversion<?>>>>() { | ||
| @Override | ||
| protected LinkedList<Map<String, Conversion<?>>> initialValue() { | ||
| return new LinkedList<Map<String, Conversion<?>>>(); | ||
| } | ||
| }; | ||
|
|
||
| protected Conversion<?> getConversionFor(LogicalType logicalType) { | ||
| if (logicalType == null) { | ||
| return null; | ||
| } | ||
| Conversion<?> conversion = conversions.get().peekLast() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. peekLast() could return null. We need to check it to avoid NPE. |
||
| .get(logicalType.getName()); | ||
| if (conversion == null) { | ||
| conversion = super.getConversionFor(logicalType); | ||
| } | ||
| return conversion; | ||
| } | ||
|
|
||
| protected Conversion<?> getConversionByClass(Class<?> type, | ||
| LogicalType logicalType) { | ||
| if (logicalType == null) { | ||
| return null; | ||
| } | ||
| // Check for logical types conversions used by the specific compiler. No | ||
| // need to use the class because the compiler always produces code with the | ||
| // same class if it used a conversion. | ||
| Conversion<?> conversion = conversions.get().peekLast() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need check whether peekLast returns null
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. Thanks! |
||
| .get(logicalType.getName()); | ||
| if (conversion == null) { | ||
| conversion = super.getConversionByClass(type, logicalType); | ||
| } | ||
| return conversion; | ||
| } | ||
|
|
||
| @Override | ||
| public void setSchema(Schema actual) { | ||
| // if expected is unset and actual is a specific record, | ||
|
|
@@ -122,5 +161,19 @@ protected void readField(Object r, Schema.Field f, Object oldDatum, | |
| super.readField(r, f, oldDatum, in, state); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected Object readRecord(Object old, final Schema expected, | ||
| ResolvingDecoder in) | ||
| throws IOException { | ||
| // Update the current set of conversions for the record class | ||
| Map<String, Conversion<?>> conversionMap = | ||
| getSpecificData().getConversionMap(expected); | ||
| conversions.get().addLast(conversionMap); | ||
| Object record = super.readRecord(old, expected, in); | ||
| conversions.get().removeLast(); | ||
| return record; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -136,7 +136,19 @@ public class ${this.mangle($schema.getName())}#if ($schema.isError()) extends or | |
| protected static final org.apache.avro.data.TimeConversions.DateConversion DATE_CONVERSION = new org.apache.avro.data.TimeConversions.DateConversion(); | ||
| protected static final org.apache.avro.data.TimeConversions.TimeConversion TIME_CONVERSION = new org.apache.avro.data.TimeConversions.TimeConversion(); | ||
| protected static final org.apache.avro.data.TimeConversions.TimestampConversion TIMESTAMP_CONVERSION = new org.apache.avro.data.TimeConversions.TimestampConversion(); | ||
| #if ($this.isDecimalEnabled()) | ||
| protected static final org.apache.avro.Conversions.DecimalConversion DECIMAL_CONVERSION = new org.apache.avro.Conversions.DecimalConversion(); | ||
| #end | ||
|
|
||
| public static final Collection<Conversion<?>> CONVERSIONS = new ArrayList<Conversion<?>>(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are missing imports from the template: import java.util.ArrayList; What is CONVERSIONS used for? Is this needed? |
||
| static { | ||
| CONVERSIONS.add(DATE_CONVERSION); | ||
| CONVERSIONS.add(TIME_CONVERSION); | ||
| CONVERSIONS.add(TIMESTAMP_CONVERSION); | ||
| #if ($this.isDecimalEnabled()) | ||
| CONVERSIONS.add(DECIMAL_CONVERSION); | ||
| #end | ||
| } | ||
|
|
||
| private static final org.apache.avro.Conversion<?>[] conversions = | ||
| new org.apache.avro.Conversion<?>[] { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this section is to add support for the legacy records that have a conversion for each field. Problem is that the
getConversionmethod in generated classes doesn't work properly for complex data structures such as array, map and union. It doesn't work for nested records either. Do we want to support them even if it only partially works?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, there's a better way we can handle this. I didn't realize it at the time, but when I added the CONVERSIONS field I found that the DATE_CONVERSION, TIME_CONVERSION, etc. are set on classes that were compiled with date/time types. So we can check for those and add them to the set if they are present. That should cover all existing classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good idea. But we may need to
SpecificRecordCompilerto make sure the proper conversion is generated for complex types. The trouble here is union type. In theory, an union field can use multiple conversions, and the current field conversion array may not be able to hold them.