From 7e0aab2c9cd2c26cd4ed7fb46c938fe4a15560f0 Mon Sep 17 00:00:00 2001 From: glywk Date: Wed, 11 Sep 2024 23:02:58 +0200 Subject: [PATCH] [AVRO-4056][Java] Add time-nanos logical type --- .../java/org/apache/avro/LogicalTypes.java | 25 ++++ .../org/apache/avro/data/TimeConversions.java | 32 ++++ .../apache/avro/data/TestTimeConversions.java | 31 ++++ .../avro/reflect/TestReflectLogicalTypes.java | 1 + .../specific/TestRecordWithLogicalTypes.java | 141 ++++++++++++++---- .../specific/TestSpecificToFromByteArray.java | 7 +- .../compiler/specific/SpecificCompiler.java | 1 + .../specific/TestSpecificCompiler.java | 3 + .../src/test/compiler/input/fieldtest.avsc | 3 +- .../avro/examples/baseball/FieldTest.java | 81 +++++++++- 10 files changed, 287 insertions(+), 38 deletions(-) diff --git a/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java b/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java index 6a894f05104..f5ab94127a7 100644 --- a/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java +++ b/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java @@ -158,6 +158,9 @@ private static LogicalType fromSchemaImpl(Schema schema, boolean throwErrors) { case TIME_MICROS: logicalType = TIME_MICROS_TYPE; break; + case TIME_NANOS: + logicalType = TIME_NANOS_TYPE; + break; case LOCAL_TIMESTAMP_MICROS: logicalType = LOCAL_TIMESTAMP_MICROS_TYPE; break; @@ -197,6 +200,7 @@ private static LogicalType fromSchemaImpl(Schema schema, boolean throwErrors) { private static final String DATE = "date"; private static final String TIME_MILLIS = "time-millis"; private static final String TIME_MICROS = "time-micros"; + private static final String TIME_NANOS = "time-nanos"; private static final String TIMESTAMP_MILLIS = "timestamp-millis"; private static final String TIMESTAMP_MICROS = "timestamp-micros"; private static final String TIMESTAMP_NANOS = "timestamp-nanos"; @@ -251,6 +255,12 @@ public static TimeMicros timeMicros() { return TIME_MICROS_TYPE; } + private static final TimeNanos TIME_NANOS_TYPE = new TimeNanos(); + + public static TimeNanos timeNanos() { + return TIME_NANOS_TYPE; + } + private static final TimestampMillis TIMESTAMP_MILLIS_TYPE = new TimestampMillis(); public static TimestampMillis timestampMillis() { @@ -498,6 +508,21 @@ public void validate(Schema schema) { } } + /** TimeNanos represents a time in nanoseconds without a date */ + public static class TimeNanos extends LogicalType { + private TimeNanos() { + super(TIME_NANOS); + } + + @Override + public void validate(Schema schema) { + super.validate(schema); + if (schema.getType() != Schema.Type.LONG) { + throw new IllegalArgumentException("Time (nanos) can only be used with an underlying long type"); + } + } + } + /** TimestampMillis represents a date and time in milliseconds */ public static class TimestampMillis extends LogicalType { private TimestampMillis() { diff --git a/lang/java/avro/src/main/java/org/apache/avro/data/TimeConversions.java b/lang/java/avro/src/main/java/org/apache/avro/data/TimeConversions.java index e63ebaae6e0..c16e092b975 100644 --- a/lang/java/avro/src/main/java/org/apache/avro/data/TimeConversions.java +++ b/lang/java/avro/src/main/java/org/apache/avro/data/TimeConversions.java @@ -125,6 +125,38 @@ public Schema getRecommendedSchema() { } } + public static class TimeNanosConversion extends Conversion { + @Override + public Class getConvertedType() { + return LocalTime.class; + } + + @Override + public String getLogicalTypeName() { + return "time-nanos"; + } + + @Override + public String adjustAndSetValue(String varName, String valParamName) { + return varName + " = " + valParamName + ".truncatedTo(java.time.temporal.ChronoUnit.NANOS);"; + } + + @Override + public LocalTime fromLong(Long nanosFromMidnight, Schema schema, LogicalType type) { + return LocalTime.ofNanoOfDay(TimeUnit.NANOSECONDS.toNanos(nanosFromMidnight)); + } + + @Override + public Long toLong(LocalTime time, Schema schema, LogicalType type) { + return TimeUnit.NANOSECONDS.toNanos(time.toNanoOfDay()); + } + + @Override + public Schema getRecommendedSchema() { + return LogicalTypes.timeNanos().addToSchema(Schema.create(Schema.Type.LONG)); + } + } + public static class TimestampMillisConversion extends Conversion { @Override public Class getConvertedType() { diff --git a/lang/java/avro/src/test/java/org/apache/avro/data/TestTimeConversions.java b/lang/java/avro/src/test/java/org/apache/avro/data/TestTimeConversions.java index 089915803a0..db63f228748 100644 --- a/lang/java/avro/src/test/java/org/apache/avro/data/TestTimeConversions.java +++ b/lang/java/avro/src/test/java/org/apache/avro/data/TestTimeConversions.java @@ -31,6 +31,7 @@ import org.apache.avro.data.TimeConversions.DateConversion; import org.apache.avro.data.TimeConversions.TimeMicrosConversion; import org.apache.avro.data.TimeConversions.TimeMillisConversion; +import org.apache.avro.data.TimeConversions.TimeNanosConversion; import org.apache.avro.data.TimeConversions.TimestampMicrosConversion; import org.apache.avro.data.TimeConversions.TimestampMillisConversion; import org.apache.avro.reflect.ReflectData; @@ -42,6 +43,7 @@ public class TestTimeConversions { public static Schema DATE_SCHEMA; public static Schema TIME_MILLIS_SCHEMA; public static Schema TIME_MICROS_SCHEMA; + public static Schema TIME_NANOS_SCHEMA; public static Schema TIMESTAMP_MILLIS_SCHEMA; public static Schema TIMESTAMP_MICROS_SCHEMA; @@ -50,6 +52,7 @@ public static void createSchemas() { TestTimeConversions.DATE_SCHEMA = LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT)); TestTimeConversions.TIME_MILLIS_SCHEMA = LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT)); TestTimeConversions.TIME_MICROS_SCHEMA = LogicalTypes.timeMicros().addToSchema(Schema.create(Schema.Type.LONG)); + TestTimeConversions.TIME_NANOS_SCHEMA = LogicalTypes.timeNanos().addToSchema(Schema.create(Schema.Type.LONG)); TestTimeConversions.TIMESTAMP_MILLIS_SCHEMA = LogicalTypes.timestampMillis() .addToSchema(Schema.create(Schema.Type.LONG)); TestTimeConversions.TIMESTAMP_MICROS_SCHEMA = LogicalTypes.timestampMicros() @@ -116,6 +119,28 @@ void timeMicrosConversion() throws Exception { "15:14:15.926551 should be " + afternoonMicros); } + @Test + void timeNanosConversion() throws Exception { + TimeNanosConversion conversion = new TimeNanosConversion(); + LocalTime oneAM = LocalTime.of(1, 0); + LocalTime afternoon = LocalTime.of(15, 14, 15, 926_551_123); + long afternoonNanos = ((long) (15 * 60 + 14) * 60 + 15) * 1_000_000_000 + 926_551_123; + + assertEquals(LocalTime.MIDNIGHT, conversion.fromLong(0L, TIME_NANOS_SCHEMA, LogicalTypes.timeNanos()), + "Midnight should be 0"); + assertEquals(oneAM, conversion.fromLong(3_600_000_000_000L, TIME_NANOS_SCHEMA, LogicalTypes.timeNanos()), + "01:00 should be 3,600,000,000,000"); + assertEquals(afternoon, conversion.fromLong(afternoonNanos, TIME_NANOS_SCHEMA, LogicalTypes.timeNanos()), + "15:14:15.926551123 should be " + afternoonNanos); + + assertEquals(0, (long) conversion.toLong(LocalTime.MIDNIGHT, TIME_NANOS_SCHEMA, LogicalTypes.timeNanos()), + "Midnight should be 0"); + assertEquals(3_600_000_000_000L, (long) conversion.toLong(oneAM, TIME_NANOS_SCHEMA, LogicalTypes.timeNanos()), + "01:00 should be 3,600,000,000,000"); + assertEquals(afternoonNanos, (long) conversion.toLong(afternoon, TIME_NANOS_SCHEMA, LogicalTypes.timeNanos()), + "15:14:15.926551123 should be " + afternoonNanos); + } + @Test void timestampMillisConversion() throws Exception { TimestampMillisConversion conversion = new TimestampMillisConversion(); @@ -209,6 +234,12 @@ void dynamicSchemaWithTimeMicrosConversion() throws ClassNotFoundException { assertEquals(TIME_MICROS_SCHEMA, schema, "Reflected schema should be logicalType timeMicros"); } + @Test + void dynamicSchemaWithTimeNanosConversion() throws ClassNotFoundException { + Schema schema = getReflectedSchemaByName("java.time.LocalTime", new TimeConversions.TimeNanosConversion()); + assertEquals(TIME_NANOS_SCHEMA, schema, "Reflected schema should be logicalType timeNanos"); + } + @Test void dynamicSchemaWithDateTimeConversion() throws ClassNotFoundException { Schema schema = getReflectedSchemaByName("java.time.Instant", new TimeConversions.TimestampMillisConversion()); diff --git a/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectLogicalTypes.java b/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectLogicalTypes.java index 851ab95e3ea..5bff317154f 100644 --- a/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectLogicalTypes.java +++ b/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectLogicalTypes.java @@ -65,6 +65,7 @@ public class TestReflectLogicalTypes { public static void addUUID() { REFLECT.addLogicalTypeConversion(new Conversions.UUIDConversion()); REFLECT.addLogicalTypeConversion(new Conversions.DecimalConversion()); + REFLECT.addLogicalTypeConversion(new TimeConversions.TimeNanosConversion()); REFLECT.addLogicalTypeConversion(new TimeConversions.LocalTimestampMillisConversion()); } diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java index 1763a73144c..d962273fddd 100644 --- a/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java +++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java @@ -29,7 +29,7 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi implements org.apache.avro.specific.SpecificRecord { private static final long serialVersionUID = 3313339903648295220L; public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse( - "{\"type\":\"record\",\"name\":\"TestRecordWithLogicalTypes\",\"namespace\":\"org.apache.avro.specific\",\"fields\":[{\"name\":\"b\",\"type\":\"boolean\"},{\"name\":\"i32\",\"type\":\"int\"},{\"name\":\"i64\",\"type\":\"long\"},{\"name\":\"f32\",\"type\":\"float\"},{\"name\":\"f64\",\"type\":\"double\"},{\"name\":\"s\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"d\",\"type\":{\"type\":\"int\",\"logicalType\":\"date\"}},{\"name\":\"t\",\"type\":{\"type\":\"int\",\"logicalType\":\"time-millis\"}},{\"name\":\"ts\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}},{\"name\":\"dec\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":9,\"scale\":2}},{\"name\":\"bd\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"big-decimal\"}}]}"); + "{\"type\":\"record\",\"name\":\"TestRecordWithLogicalTypes\",\"namespace\":\"org.apache.avro.specific\",\"fields\":[{\"name\":\"b\",\"type\":\"boolean\"},{\"name\":\"i32\",\"type\":\"int\"},{\"name\":\"i64\",\"type\":\"long\"},{\"name\":\"f32\",\"type\":\"float\"},{\"name\":\"f64\",\"type\":\"double\"},{\"name\":\"s\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"d\",\"type\":{\"type\":\"int\",\"logicalType\":\"date\"}},{\"name\":\"t\",\"type\":{\"type\":\"int\",\"logicalType\":\"time-millis\"}},{\"name\":\"tn\",\"type\":{\"type\":\"long\",\"logicalType\":\"time-nanos\"}},{\"name\":\"ts\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}},{\"name\":\"dec\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":9,\"scale\":2}},{\"name\":\"bd\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"big-decimal\"}}]}"); public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; @@ -87,6 +87,8 @@ public static TestRecordWithLogicalTypes fromByteBuffer(java.nio.ByteBuffer b) t @Deprecated public java.time.LocalTime t; @Deprecated + public java.time.LocalTime tn; + @Deprecated public java.time.Instant ts; @Deprecated public java.math.BigDecimal dec; @@ -112,13 +114,14 @@ public TestRecordWithLogicalTypes() { * @param s The new value for s * @param d The new value for d * @param t The new value for t + * @param tn The new value for tn * @param ts The new value for ts * @param dec The new value for dec * @param bd The new value for bd */ public TestRecordWithLogicalTypes(java.lang.Boolean b, java.lang.Integer i32, java.lang.Long i64, java.lang.Float f32, java.lang.Double f64, java.lang.CharSequence s, java.time.LocalDate d, java.time.LocalTime t, - java.time.Instant ts, java.math.BigDecimal dec, java.math.BigDecimal bd) { + java.time.LocalTime tn, java.time.Instant ts, java.math.BigDecimal dec, java.math.BigDecimal bd) { this.b = b; this.i32 = i32; this.i64 = i64; @@ -127,6 +130,7 @@ public TestRecordWithLogicalTypes(java.lang.Boolean b, java.lang.Integer i32, ja this.s = s; this.d = d; this.t = t; + this.tn = tn; this.ts = ts; this.dec = dec; this.bd = bd; @@ -158,10 +162,12 @@ public java.lang.Object get(int field$) { case 7: return t; case 8: - return ts; + return tn; case 9: - return dec; + return ts; case 10: + return dec; + case 11: return bd; default: throw new org.apache.avro.AvroRuntimeException("Bad index " + field$); @@ -174,11 +180,12 @@ public java.lang.Object get(int field$) { protected static final TimeConversions.DateConversion DATE_CONVERSION = new TimeConversions.DateConversion(); protected static final TimeConversions.TimeMillisConversion TIME_CONVERSION = new TimeConversions.TimeMillisConversion(); + protected static final TimeConversions.TimeNanosConversion TIME_NANOS_CONVERSION = new TimeConversions.TimeNanosConversion(); protected static final TimeConversions.TimestampMillisConversion TIMESTAMP_CONVERSION = new TimeConversions.TimestampMillisConversion(); private static final org.apache.avro.Conversion[] conversions = new org.apache.avro.Conversion[] { null, null, - null, null, null, null, DATE_CONVERSION, TIME_CONVERSION, TIMESTAMP_CONVERSION, DECIMAL_CONVERSION, - BIG_DECIMAL_CONVERSION }; + null, null, null, null, DATE_CONVERSION, TIME_CONVERSION, TIME_NANOS_CONVERSION, TIMESTAMP_CONVERSION, + DECIMAL_CONVERSION, BIG_DECIMAL_CONVERSION }; @Override public org.apache.avro.Conversion getConversion(int field) { @@ -215,12 +222,15 @@ public void put(int field$, java.lang.Object value$) { t = (java.time.LocalTime) value$; break; case 8: - ts = (java.time.Instant) value$; + tn = (java.time.LocalTime) value$; break; case 9: - dec = (java.math.BigDecimal) value$; + ts = (java.time.Instant) value$; break; case 10: + dec = (java.math.BigDecimal) value$; + break; + case 11: bd = (java.math.BigDecimal) value$; break; default: @@ -372,6 +382,24 @@ public void setT(java.time.LocalTime value) { this.t = value; } + /** + * Gets the value of the 'tn' field. + * + * @return The value of the 'tn' field. + */ + public java.time.LocalTime getTn() { + return t; + } + + /** + * Sets the value of the 'tn' field. + * + * @param value the value to set. + */ + public void setTn(java.time.LocalTime value) { + this.tn = value; + } + /** * Gets the value of the 'ts' field. * @@ -461,6 +489,7 @@ public static class Builder extends org.apache.avro.specific.SpecificRecordBuild private java.lang.CharSequence s; private java.time.LocalDate d; private java.time.LocalTime t; + private java.time.LocalTime tn; private java.time.Instant ts; private java.math.BigDecimal dec; @@ -510,18 +539,22 @@ private Builder(TestRecordWithLogicalTypes.Builder other) { this.t = data().deepCopy(fields()[7].schema(), other.t); fieldSetFlags()[7] = other.fieldSetFlags()[7]; } - if (isValidValue(fields()[8], other.ts)) { - this.ts = data().deepCopy(fields()[8].schema(), other.ts); + if (isValidValue(fields()[8], other.tn)) { + this.tn = data().deepCopy(fields()[8].schema(), other.tn); fieldSetFlags()[8] = other.fieldSetFlags()[8]; } - if (isValidValue(fields()[9], other.dec)) { - this.dec = data().deepCopy(fields()[9].schema(), other.dec); + if (isValidValue(fields()[9], other.ts)) { + this.ts = data().deepCopy(fields()[9].schema(), other.ts); fieldSetFlags()[9] = other.fieldSetFlags()[9]; } - if (isValidValue(fields()[10], other.bd)) { - this.bd = data().deepCopy(fields()[10].schema(), other.bd); + if (isValidValue(fields()[10], other.dec)) { + this.dec = data().deepCopy(fields()[10].schema(), other.dec); fieldSetFlags()[10] = other.fieldSetFlags()[10]; } + if (isValidValue(fields()[11], other.bd)) { + this.bd = data().deepCopy(fields()[11].schema(), other.bd); + fieldSetFlags()[11] = other.fieldSetFlags()[11]; + } } /** @@ -563,18 +596,22 @@ private Builder(TestRecordWithLogicalTypes other) { this.t = data().deepCopy(fields()[7].schema(), other.t); fieldSetFlags()[7] = true; } - if (isValidValue(fields()[8], other.ts)) { - this.ts = data().deepCopy(fields()[8].schema(), other.ts); + if (isValidValue(fields()[8], other.tn)) { + this.tn = data().deepCopy(fields()[8].schema(), other.tn); fieldSetFlags()[8] = true; } - if (isValidValue(fields()[9], other.dec)) { - this.dec = data().deepCopy(fields()[9].schema(), other.dec); + if (isValidValue(fields()[9], other.ts)) { + this.ts = data().deepCopy(fields()[9].schema(), other.ts); fieldSetFlags()[9] = true; } - if (isValidValue(fields()[10], other.bd)) { - this.bd = data().deepCopy(fields()[10].schema(), other.bd); + if (isValidValue(fields()[10], other.dec)) { + this.dec = data().deepCopy(fields()[10].schema(), other.dec); fieldSetFlags()[10] = true; } + if (isValidValue(fields()[11], other.bd)) { + this.bd = data().deepCopy(fields()[11].schema(), other.bd); + fieldSetFlags()[11] = true; + } } /** @@ -906,6 +943,47 @@ public TestRecordWithLogicalTypes.Builder clearT() { return this; } + /** + * Gets the value of the 'tn' field. + * + * @return The value. + */ + public java.time.LocalTime getTn() { + return tn; + } + + /** + * Sets the value of the 'tn' field. + * + * @param value The value of 'tn'. + * @return This builder. + */ + public TestRecordWithLogicalTypes.Builder setTn(java.time.LocalTime value) { + validate(fields()[8], value); + this.tn = value; + fieldSetFlags()[8] = true; + return this; + } + + /** + * Checks whether the 'tn' field has been set. + * + * @return True if the 'tn' field has been set, false otherwise. + */ + public boolean hasTn() { + return fieldSetFlags()[8]; + } + + /** + * Clears the value of the 'tn' field. + * + * @return This builder. + */ + public TestRecordWithLogicalTypes.Builder clearTn() { + fieldSetFlags()[8] = false; + return this; + } + /** * Gets the value of the 'ts' field. * @@ -922,9 +1000,9 @@ public java.time.Instant getTs() { * @return This builder. */ public TestRecordWithLogicalTypes.Builder setTs(java.time.Instant value) { - validate(fields()[8], value); + validate(fields()[9], value); this.ts = value; - fieldSetFlags()[8] = true; + fieldSetFlags()[9] = true; return this; } @@ -934,7 +1012,7 @@ public TestRecordWithLogicalTypes.Builder setTs(java.time.Instant value) { * @return True if the 'ts' field has been set, false otherwise. */ public boolean hasTs() { - return fieldSetFlags()[8]; + return fieldSetFlags()[9]; } /** @@ -943,7 +1021,7 @@ public boolean hasTs() { * @return This builder. */ public TestRecordWithLogicalTypes.Builder clearTs() { - fieldSetFlags()[8] = false; + fieldSetFlags()[9] = false; return this; } @@ -963,9 +1041,9 @@ public java.math.BigDecimal getDec() { * @return This builder. */ public TestRecordWithLogicalTypes.Builder setDec(java.math.BigDecimal value) { - validate(fields()[9], value); + validate(fields()[10], value); this.dec = value; - fieldSetFlags()[9] = true; + fieldSetFlags()[10] = true; return this; } @@ -975,7 +1053,7 @@ public TestRecordWithLogicalTypes.Builder setDec(java.math.BigDecimal value) { * @return True if the 'dec' field has been set, false otherwise. */ public boolean hasDec() { - return fieldSetFlags()[9]; + return fieldSetFlags()[10]; } /** @@ -985,7 +1063,7 @@ public boolean hasDec() { */ public TestRecordWithLogicalTypes.Builder clearDec() { dec = null; - fieldSetFlags()[9] = false; + fieldSetFlags()[10] = false; return this; } @@ -1002,9 +1080,10 @@ public TestRecordWithLogicalTypes build() { record.s = fieldSetFlags()[5] ? this.s : (java.lang.CharSequence) defaultValue(fields()[5]); record.d = fieldSetFlags()[6] ? this.d : (java.time.LocalDate) defaultValue(fields()[6]); record.t = fieldSetFlags()[7] ? this.t : (java.time.LocalTime) defaultValue(fields()[7]); - record.ts = fieldSetFlags()[8] ? this.ts : (java.time.Instant) defaultValue(fields()[8]); - record.dec = fieldSetFlags()[9] ? this.dec : (java.math.BigDecimal) defaultValue(fields()[9]); - record.bd = fieldSetFlags()[10] ? this.dec : (java.math.BigDecimal) defaultValue(fields()[10]); + record.tn = fieldSetFlags()[8] ? this.tn : (java.time.LocalTime) defaultValue(fields()[8]); + record.ts = fieldSetFlags()[9] ? this.ts : (java.time.Instant) defaultValue(fields()[9]); + record.dec = fieldSetFlags()[10] ? this.dec : (java.math.BigDecimal) defaultValue(fields()[10]); + record.bd = fieldSetFlags()[11] ? this.dec : (java.math.BigDecimal) defaultValue(fields()[11]); return record; } catch (java.lang.Exception e) { throw new org.apache.avro.AvroRuntimeException(e); diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificToFromByteArray.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificToFromByteArray.java index f81dde37407..c61c530d511 100644 --- a/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificToFromByteArray.java +++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificToFromByteArray.java @@ -44,7 +44,8 @@ void specificToFromByteBufferWithLogicalTypes() throws IOException { Instant instant = Instant.now().truncatedTo(ChronoUnit.MILLIS); final TestRecordWithLogicalTypes record = new TestRecordWithLogicalTypes(true, 34, 35L, 3.14F, 3019.34, null, - LocalDate.now(), t, instant, new BigDecimal("123.45"), new BigDecimal(-23.456562323)); + LocalDate.now(), t, LocalTime.of(10, 54, 35, 123456), instant, new BigDecimal("123.45"), + new BigDecimal(-23.456562323)); final ByteBuffer b = record.toByteBuffer(); final TestRecordWithLogicalTypes copy = TestRecordWithLogicalTypes.fromByteBuffer(b); @@ -81,8 +82,8 @@ void specificByteArrayIncompatibleWithLogicalTypes() throws IOException { void specificByteArrayIncompatibleWithoutLogicalTypes() throws IOException { assertThrows(MissingSchemaException.class, () -> { final TestRecordWithLogicalTypes withLogicalTypes = new TestRecordWithLogicalTypes(true, 34, 35L, 3.14F, 3019.34, - null, LocalDate.now(), LocalTime.now(), Instant.now(), new BigDecimal("123.45"), - new BigDecimal(-23.456562323)); + null, LocalDate.now(), LocalTime.now(), LocalTime.of(10, 54, 35, 123456), Instant.now(), + new BigDecimal("123.45"), new BigDecimal(-23.456562323)); final ByteBuffer b = withLogicalTypes.toByteBuffer(); TestRecordWithoutLogicalTypes.fromByteBuffer(b); diff --git a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java index cad00e943f2..31a2899f459 100644 --- a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java +++ b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java @@ -104,6 +104,7 @@ void addLogicalTypeConversions(SpecificData specificData) { specificData.addLogicalTypeConversion(new TimeConversions.DateConversion()); specificData.addLogicalTypeConversion(new TimeConversions.TimeMillisConversion()); specificData.addLogicalTypeConversion(new TimeConversions.TimeMicrosConversion()); + specificData.addLogicalTypeConversion(new TimeConversions.TimeNanosConversion()); specificData.addLogicalTypeConversion(new TimeConversions.TimestampMillisConversion()); specificData.addLogicalTypeConversion(new TimeConversions.TimestampMicrosConversion()); specificData.addLogicalTypeConversion(new TimeConversions.TimestampNanosConversion()); diff --git a/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java b/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java index 19d63d033c7..a8a3e3a7b17 100644 --- a/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java +++ b/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java @@ -393,6 +393,7 @@ void javaTypeWithDateTimeTypes() throws Exception { Schema dateSchema = LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT)); Schema timeSchema = LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT)); Schema timeMicrosSchema = LogicalTypes.timeMicros().addToSchema(Schema.create(Schema.Type.LONG)); + Schema timeNanosSchema = LogicalTypes.timeNanos().addToSchema(Schema.create(Schema.Type.LONG)); Schema timestampSchema = LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG)); Schema timestampMicrosSchema = LogicalTypes.timestampMicros().addToSchema(Schema.create(Schema.Type.LONG)); Schema timestampNanosSchema = LogicalTypes.timestampNanos().addToSchema(Schema.create(Schema.Type.LONG)); @@ -407,6 +408,8 @@ void javaTypeWithDateTimeTypes() throws Exception { "Should use java.time.LocalTime for time-micros type"); assertEquals("java.time.Instant", compiler.javaType(timestampMicrosSchema), "Should use java.time.Instant for timestamp-micros type"); + assertEquals("java.time.LocalTime", compiler.javaType(timeNanosSchema), + "Should use java.time.LocalTime for time-nanos type"); assertEquals("java.time.Instant", compiler.javaType(timestampNanosSchema), "Should use java.time.Instant for timestamp-nanos type"); } diff --git a/lang/java/tools/src/test/compiler/input/fieldtest.avsc b/lang/java/tools/src/test/compiler/input/fieldtest.avsc index fad6355f8af..c920befc04b 100644 --- a/lang/java/tools/src/test/compiler/input/fieldtest.avsc +++ b/lang/java/tools/src/test/compiler/input/fieldtest.avsc @@ -5,6 +5,7 @@ {"name": "timestamp", "type": { "type": "long", "logicalType": "timestamp-millis"}}, {"name": "timestampMicros", "type": { "type": "long", "logicalType": "timestamp-micros"}}, {"name": "timeMillis", "type": { "type": "int", "logicalType": "time-millis"}}, - {"name": "timeMicros", "type": { "type": "long", "logicalType": "time-micros"}} + {"name": "timeMicros", "type": { "type": "long", "logicalType": "time-micros"}}, + {"name": "timeNanos", "type": { "type": "long", "logicalType": "time-nanos"}} ] } diff --git a/lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/FieldTest.java b/lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/FieldTest.java index 8e567f6de7e..4f6bb56ba4a 100644 --- a/lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/FieldTest.java +++ b/lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/FieldTest.java @@ -14,8 +14,10 @@ /** Test various field types */ @org.apache.avro.specific.AvroGenerated public class FieldTest extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { - private static final long serialVersionUID = 4609235620572341636L; - public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"FieldTest\",\"namespace\":\"avro.examples.baseball\",\"doc\":\"Test various field types\",\"fields\":[{\"name\":\"number\",\"type\":\"int\",\"doc\":\"The number of the player\"},{\"name\":\"last_name\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"timestamp\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}},{\"name\":\"timestampMicros\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-micros\"}},{\"name\":\"timeMillis\",\"type\":{\"type\":\"int\",\"logicalType\":\"time-millis\"}},{\"name\":\"timeMicros\",\"type\":{\"type\":\"long\",\"logicalType\":\"time-micros\"}}]}"); + private static final long serialVersionUID = 6639509827245659311L; + + + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"FieldTest\",\"namespace\":\"avro.examples.baseball\",\"doc\":\"Test various field types\",\"fields\":[{\"name\":\"number\",\"type\":\"int\",\"doc\":\"The number of the player\"},{\"name\":\"last_name\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"timestamp\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}},{\"name\":\"timestampMicros\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-micros\"}},{\"name\":\"timeMillis\",\"type\":{\"type\":\"int\",\"logicalType\":\"time-millis\"}},{\"name\":\"timeMicros\",\"type\":{\"type\":\"long\",\"logicalType\":\"time-micros\"}},{\"name\":\"timeNanos\",\"type\":{\"type\":\"long\",\"logicalType\":\"time-nanos\"}}]}"); public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } private static final SpecificData MODEL$ = new SpecificData(); @@ -23,6 +25,7 @@ public class FieldTest extends org.apache.avro.specific.SpecificRecordBase imple MODEL$.addLogicalTypeConversion(new org.apache.avro.data.TimeConversions.TimestampMillisConversion()); MODEL$.addLogicalTypeConversion(new org.apache.avro.data.TimeConversions.TimeMicrosConversion()); MODEL$.addLogicalTypeConversion(new org.apache.avro.data.TimeConversions.TimestampMicrosConversion()); + MODEL$.addLogicalTypeConversion(new org.apache.avro.data.TimeConversions.TimeNanosConversion()); MODEL$.addLogicalTypeConversion(new org.apache.avro.data.TimeConversions.TimeMillisConversion()); } @@ -84,6 +87,7 @@ public static FieldTest fromByteBuffer( private java.time.Instant timestampMicros; private java.time.LocalTime timeMillis; private java.time.LocalTime timeMicros; + private java.time.LocalTime timeNanos; /** * Default constructor. Note that this does not initialize fields @@ -100,14 +104,16 @@ public FieldTest() {} * @param timestampMicros The new value for timestampMicros * @param timeMillis The new value for timeMillis * @param timeMicros The new value for timeMicros + * @param timeNanos The new value for timeNanos */ - public FieldTest(java.lang.Integer number, java.lang.String last_name, java.time.Instant timestamp, java.time.Instant timestampMicros, java.time.LocalTime timeMillis, java.time.LocalTime timeMicros) { + public FieldTest(java.lang.Integer number, java.lang.String last_name, java.time.Instant timestamp, java.time.Instant timestampMicros, java.time.LocalTime timeMillis, java.time.LocalTime timeMicros, java.time.LocalTime timeNanos) { this.number = number; this.last_name = last_name; this.timestamp = timestamp.truncatedTo(java.time.temporal.ChronoUnit.MILLIS); this.timestampMicros = timestampMicros.truncatedTo(java.time.temporal.ChronoUnit.MICROS); this.timeMillis = timeMillis.truncatedTo(java.time.temporal.ChronoUnit.MILLIS); this.timeMicros = timeMicros.truncatedTo(java.time.temporal.ChronoUnit.MICROS); + this.timeNanos = timeNanos.truncatedTo(java.time.temporal.ChronoUnit.NANOS); } @Override @@ -126,6 +132,7 @@ public java.lang.Object get(int field$) { case 3: return timestampMicros; case 4: return timeMillis; case 5: return timeMicros; + case 6: return timeNanos; default: throw new IndexOutOfBoundsException("Invalid index: " + field$); } } @@ -138,6 +145,7 @@ public java.lang.Object get(int field$) { new org.apache.avro.data.TimeConversions.TimestampMicrosConversion(), new org.apache.avro.data.TimeConversions.TimeMillisConversion(), new org.apache.avro.data.TimeConversions.TimeMicrosConversion(), + new org.apache.avro.data.TimeConversions.TimeNanosConversion(), null }; @@ -157,6 +165,7 @@ public void put(int field$, java.lang.Object value$) { case 3: timestampMicros = (java.time.Instant)value$; break; case 4: timeMillis = (java.time.LocalTime)value$; break; case 5: timeMicros = (java.time.LocalTime)value$; break; + case 6: timeNanos = (java.time.LocalTime)value$; break; default: throw new IndexOutOfBoundsException("Invalid index: " + field$); } } @@ -264,6 +273,23 @@ public void setTimeMicros(java.time.LocalTime value) { this.timeMicros = value.truncatedTo(java.time.temporal.ChronoUnit.MICROS); } + /** + * Gets the value of the 'timeNanos' field. + * @return The value of the 'timeNanos' field. + */ + public java.time.LocalTime getTimeNanos() { + return timeNanos; + } + + + /** + * Sets the value of the 'timeNanos' field. + * @param value the value to set. + */ + public void setTimeNanos(java.time.LocalTime value) { + this.timeNanos = value.truncatedTo(java.time.temporal.ChronoUnit.NANOS); + } + /** * Creates a new FieldTest RecordBuilder. * @return A new FieldTest RecordBuilder @@ -312,6 +338,7 @@ public static class Builder extends org.apache.avro.specific.SpecificRecordBuild private java.time.Instant timestampMicros; private java.time.LocalTime timeMillis; private java.time.LocalTime timeMicros; + private java.time.LocalTime timeNanos; /** Creates a new Builder */ private Builder() { @@ -348,6 +375,10 @@ private Builder(avro.examples.baseball.FieldTest.Builder other) { this.timeMicros = data().deepCopy(fields()[5].schema(), other.timeMicros); fieldSetFlags()[5] = other.fieldSetFlags()[5]; } + if (isValidValue(fields()[6], other.timeNanos)) { + this.timeNanos = data().deepCopy(fields()[6].schema(), other.timeNanos); + fieldSetFlags()[6] = other.fieldSetFlags()[6]; + } } /** @@ -380,6 +411,10 @@ private Builder(avro.examples.baseball.FieldTest other) { this.timeMicros = data().deepCopy(fields()[5].schema(), other.timeMicros); fieldSetFlags()[5] = true; } + if (isValidValue(fields()[6], other.timeNanos)) { + this.timeNanos = data().deepCopy(fields()[6].schema(), other.timeNanos); + fieldSetFlags()[6] = true; + } } /** @@ -621,6 +656,45 @@ public avro.examples.baseball.FieldTest.Builder clearTimeMicros() { return this; } + /** + * Gets the value of the 'timeNanos' field. + * @return The value. + */ + public java.time.LocalTime getTimeNanos() { + return timeNanos; + } + + + /** + * Sets the value of the 'timeNanos' field. + * @param value The value of 'timeNanos'. + * @return This builder. + */ + public avro.examples.baseball.FieldTest.Builder setTimeNanos(java.time.LocalTime value) { + validate(fields()[6], value); + this.timeNanos = value.truncatedTo(java.time.temporal.ChronoUnit.NANOS); + fieldSetFlags()[6] = true; + return this; + } + + /** + * Checks whether the 'timeNanos' field has been set. + * @return True if the 'timeNanos' field has been set, false otherwise. + */ + public boolean hasTimeNanos() { + return fieldSetFlags()[6]; + } + + + /** + * Clears the value of the 'timeNanos' field. + * @return This builder. + */ + public avro.examples.baseball.FieldTest.Builder clearTimeNanos() { + fieldSetFlags()[6] = false; + return this; + } + @Override @SuppressWarnings("unchecked") public FieldTest build() { @@ -632,6 +706,7 @@ public FieldTest build() { record.timestampMicros = fieldSetFlags()[3] ? this.timestampMicros : (java.time.Instant) defaultValue(fields()[3]); record.timeMillis = fieldSetFlags()[4] ? this.timeMillis : (java.time.LocalTime) defaultValue(fields()[4]); record.timeMicros = fieldSetFlags()[5] ? this.timeMicros : (java.time.LocalTime) defaultValue(fields()[5]); + record.timeNanos = fieldSetFlags()[6] ? this.timeNanos : (java.time.LocalTime) defaultValue(fields()[6]); return record; } catch (org.apache.avro.AvroMissingFieldException e) { throw e;