From b003934fd3f0ff02a6cb9d6e01c0a8fcd7fee7ca Mon Sep 17 00:00:00 2001 From: "Wessel W. Bakker" Date: Fri, 2 Feb 2024 18:35:02 +0100 Subject: [PATCH] - Add support for mapping to the underlying Int or Long for date/time related fields, as a custom mapper. --- README.md | 28 +++---- .../specific/converters/JavaConverter.scala | 7 ++ .../specific/converters/ScalaConverter.scala | 7 ++ .../custom/CustomDefaultParamMatcher.scala | 7 ++ .../custom/CustomDefaultValueMatcher.scala | 3 + .../matchers/custom/CustomTypeMatcher.scala | 7 ++ .../scala/types/LogicalAvroScalaTypes.scala | 2 +- .../test/avro/date_time_related_fields.avsc | 44 +++++++++++ .../DateTimeRelatedFields.scala | 76 +++++++++++++++++++ .../DateTimeRelatedFields.scala | 14 ++++ .../specific/SpecificFileToFileSpec.scala | 13 ++++ .../standard/StandardFileToFileSpec.scala | 12 +++ build.sbt | 2 +- 13 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 avrohugger-core/src/test/avro/date_time_related_fields.avsc create mode 100644 avrohugger-core/src/test/expected/specific/example/datetimerelatedfields/DateTimeRelatedFields.scala create mode 100644 avrohugger-core/src/test/expected/standard/example/datetimerelatedfields/DateTimeRelatedFields.scala diff --git a/README.md b/README.md index 87667a62..1ec2da62 100644 --- a/README.md +++ b/README.md @@ -66,13 +66,13 @@ Scalding, Spark, Avro, etc.). |UNION|Option
Either
Shapeless Coproduct|Option
Either
Shapeless Coproduct| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| |RECORD|case class
case class + schema|case class extending `SpecificRecordBase`| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| |PROTOCOL|_No Type_
Scala ADT|RPC trait
Scala ADT| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| -|Date|java.time.LocalDate
java.sql.Date|java.time.LocalDate
java.sql.Date| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| -|TimeMillis|java.time.LocalTime|java.time.LocalTime| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| -|TimeMicros|java.time.LocalTime|java.time.LocalTime| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| -|TimestampMillis|java.time.Instant
java.sql.Timestamp|java.time.Instant
java.sql.Timestamp| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| -|TimestampMicros|java.time.Instant
java.sql.Timestamp|java.time.Instant
java.sql.Timestamp| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| -|LocalTimestampMillis|java.time.LocalDateTime|java.time.LocalDateTime| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| -|LocalTimestampMicros|java.time.LocalDateTime|java.time.LocalDateTime| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| +|Date|java.time.LocalDate
java.sql.Date
Int|java.time.LocalDate
java.sql.Date
Int| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| +|TimeMillis|java.time.LocalTime
Int|java.time.LocalTime
Int| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| +|TimeMicros|java.time.LocalTime
Long|java.time.LocalTime
Long| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| +|TimestampMillis|java.time.Instant
java.sql.Timestamp
Long|java.time.Instant
java.sql.Timestamp
Long| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| +|TimestampMicros|java.time.Instant
java.sql.Timestamp
Long|java.time.Instant
java.sql.Timestamp
Long| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| +|LocalTimestampMillis|java.time.LocalDateTime
Long|java.time.LocalDateTime
Long| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| +|LocalTimestampMicros|java.time.LocalDateTime
Long|java.time.LocalDateTime
Long| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| |UUID|java.util.UUID|java.util.UUID| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| |Decimal|BigDecimal|BigDecimal| See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)| @@ -82,9 +82,9 @@ _NOTE: Currently logical types are only supported for `Standard` and `SpecificRe * `date`: Annotates Avro `int` schemas to generate `java.time.LocalDate` or `java.sql.Date` (See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)). Examples: [avdl](https://github.com/julianpeeters/sbt-avrohugger/blob/master/src/sbt-test/avrohugger/GenericSerializationTests/src/main/avro/logical.avdl#L9), [avsc](https://github.com/julianpeeters/sbt-avrohugger/blob/master/src/sbt-test/avrohugger/GenericSerializationTests/src/main/avro/logical.avsc#L22-L27). * `decimal`: Annotates Avro `bytes` and `fixed` schemas to generate `BigDecimal`. Examples: [avdl](https://github.com/julianpeeters/sbt-avrohugger/blob/master/src/sbt-test/avrohugger/GenericSerializationTests/src/main/avro/logical.avdl#L6), [avsc](https://github.com/julianpeeters/sbt-avrohugger/blob/master/src/sbt-test/avrohugger/GenericSerializationTests/src/main/avro/logical.avsc#L6-L14). -* `timestamp-millis`: Annotates Avro `long` schemas to genarate `java.time.Instant` or `java.sql.Timestamp` (See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)). Examples: [avdl](https://github.com/julianpeeters/sbt-avrohugger/blob/master/src/sbt-test/avrohugger/GenericSerializationTests/src/main/avro/logical.avdl#L8), [avsc](https://github.com/julianpeeters/sbt-avrohugger/blob/master/src/sbt-test/avrohugger/GenericSerializationTests/src/main/avro/logical.avsc#L15-L21). +* `timestamp-millis`: Annotates Avro `long` schemas to genarate `java.time.Instant` or `java.sql.Timestamp` or `long` (See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)). Examples: [avdl](https://github.com/julianpeeters/sbt-avrohugger/blob/master/src/sbt-test/avrohugger/GenericSerializationTests/src/main/avro/logical.avdl#L8), [avsc](https://github.com/julianpeeters/sbt-avrohugger/blob/master/src/sbt-test/avrohugger/GenericSerializationTests/src/main/avro/logical.avsc#L15-L21). * `uuid`: Annotates Avro `string` schemas and idls to generate `java.util.UUID` (See [Customizable Type Mapping](https://github.com/julianpeeters/avrohugger#customizable-type-mapping)). Example: [avsc](https://github.com/julianpeeters/sbt-avrohugger/blob/master/src/sbt-test/avrohugger/GenericSerializationTests/src/main/avro/logical.avsc#L29-L35). -* `time-millis`: Annotates Avro `int` schemas to genarate `java.time.LocalTime` or `java.sql.Time` +* `time-millis`: Annotates Avro `int` schemas to genarate `java.time.LocalTime` or `java.sql.Time` or `int` ##### Protocol Support: @@ -119,7 +119,7 @@ _Note:_ Currently [Treehugger](http://eed3si9n.com/treehugger/comments.html#Scal ##### Get the dependency with: - "com.julianpeeters" %% "avrohugger-core" % "2.8.2" + "com.julianpeeters" %% "avrohugger-core" % "2.8.3" ##### Description: @@ -211,7 +211,7 @@ namespace rewritten. Multiple conflicting wildcards are not permitted. ##### Get the dependency with: - "com.julianpeeters" %% "avrohugger-filesorter" % "2.8.2" + "com.julianpeeters" %% "avrohugger-filesorter" % "2.8.3" ##### Description: @@ -231,17 +231,17 @@ To ensure dependent schemas are compiled in the proper order (thus avoiding `org #### `avrohugger-tools` - Download the avrohugger-tools jar for Scala [2.12](https://search.maven.org/remotecontent?filepath=com/julianpeeters/avrohugger-tools_2.12/2.8.2/avrohugger-tools_2.12-2.8.2-assembly.jar), or Scala [2.13](https://search.maven.org/remotecontent?filepath=com/julianpeeters/avrohugger-tools_2.13/2.8.2/avrohugger-tools_2.13-2.8.2-assembly.jar) (>30MB!) and use it like the avro-tools jar `Usage: [-string] (schema|protocol|datafile) input... outputdir`: + Download the avrohugger-tools jar for Scala [2.12](https://search.maven.org/remotecontent?filepath=com/julianpeeters/avrohugger-tools_2.12/2.8.3/avrohugger-tools_2.12-2.8.3-assembly.jar), or Scala [2.13](https://search.maven.org/remotecontent?filepath=com/julianpeeters/avrohugger-tools_2.13/2.8.3/avrohugger-tools_2.13-2.8.3-assembly.jar) (>30MB!) and use it like the avro-tools jar `Usage: [-string] (schema|protocol|datafile) input... outputdir`: * `generate` generates Scala case class definitions: -`java -jar /path/to/avrohugger-tools_2.12-2.8.2-assembly.jar generate schema user.avsc . ` +`java -jar /path/to/avrohugger-tools_2.12-2.8.3-assembly.jar generate schema user.avsc . ` * `generate-specific` generates definitions that extend Avro's `SpecificRecordBase`: -`java -jar /path/to/avrohugger-tools_2.12-2.8.2-assembly.jar generate-specific schema user.avsc . ` +`java -jar /path/to/avrohugger-tools_2.12-2.8.3-assembly.jar generate-specific schema user.avsc . ` ## Warnings diff --git a/avrohugger-core/src/main/scala/format/specific/converters/JavaConverter.scala b/avrohugger-core/src/main/scala/format/specific/converters/JavaConverter.scala index 48bd9d0a..962e676c 100644 --- a/avrohugger-core/src/main/scala/format/specific/converters/JavaConverter.scala +++ b/avrohugger-core/src/main/scala/format/specific/converters/JavaConverter.scala @@ -154,18 +154,23 @@ object JavaConverter { case timestamp: LogicalTypes.TimestampMillis => typeMatcher.avroScalaTypes.timestampMillis match { case JavaSqlTimestamp => BLOCK(tree.DOT("getTime").APPLY()) case JavaTimeInstant => BLOCK(tree.DOT("toEpochMilli")) + case UnderlyingPrimitive => tree } case _: LogicalTypes.TimestampMicros => (typeMatcher.avroScalaTypes.timestampMicros match { case JavaTimeZonedDateTime => BLOCK(tree.DOT("toEpochSecond").INFIX("*", LIT(1000000L)).INFIX("+", tree.DOT("getNano").INFIX("/", LIT(1000L)))) + case UnderlyingPrimitive => tree }) withComment "avro timestamp-micros long stores the number of microseconds from the unix epoch, 1 January 1970 00:00:00.000000 UTC" case _: LogicalTypes.LocalTimestampMillis => (typeMatcher.avroScalaTypes.localTimestampMillis match { case JavaTimeLocalDateTime => BLOCK(tree.DOT("toEpochSecond").APPLY(RootClass.newClass("java.time.ZoneOffset").DOT("UTC")).INFIX("*", LIT(1000L)).INFIX("+", tree.DOT("getNano").INFIX("/", LIT(1000000L)))) + case UnderlyingPrimitive => tree }) withComment "avro local-timestamp-millis long stores the number of millis, from 1 January 1970 00:00:00.000000" case _: LogicalTypes.LocalTimestampMicros => (typeMatcher.avroScalaTypes.localTimestampMicros match { case JavaTimeLocalDateTime => BLOCK(tree.DOT("toEpochSecond").APPLY(RootClass.newClass("java.time.ZoneOffset").DOT("UTC")).INFIX("*", LIT(1000000L)).INFIX("+", tree.DOT("getNano").INFIX("/", LIT(1000L)))) + case UnderlyingPrimitive => tree }) withComment "avro local-timestamp-micros long stores the number of microseconds, from 1 January 1970 00:00:00.000000" case _: LogicalTypes.TimeMicros => (typeMatcher.avroScalaTypes.timeMicros match { case JavaTimeLocalTime => BLOCK(tree.DOT("toNanoOfDay").INFIX("/", LIT(1000L))) + case UnderlyingPrimitive => tree }) withComment "avro time-micros long stores the number of microseconds after midnight, 00:00:00.000000" case _ => tree } @@ -173,10 +178,12 @@ object JavaConverter { case date: LogicalTypes.Date => typeMatcher.avroScalaTypes.date match { case JavaSqlDate => tree.DOT("getTime").APPLY().DOT("/").APPLY(LIT(86400000)) case JavaTimeLocalDate => tree.DOT("toEpochDay").DOT("toInt") + case UnderlyingPrimitive => tree } case timeMillis: LogicalTypes.TimeMillis => typeMatcher.avroScalaTypes.timeMillis match { case JavaSqlTime => tree.DOT("getTime").APPLY() case JavaTimeLocalTime => tree.DOT("get").APPLY(REF("java.time.temporal.ChronoField").DOT("MILLI_OF_DAY")) + case UnderlyingPrimitive => tree } case _ => tree } diff --git a/avrohugger-core/src/main/scala/format/specific/converters/ScalaConverter.scala b/avrohugger-core/src/main/scala/format/specific/converters/ScalaConverter.scala index 369e89af..84379d19 100644 --- a/avrohugger-core/src/main/scala/format/specific/converters/ScalaConverter.scala +++ b/avrohugger-core/src/main/scala/format/specific/converters/ScalaConverter.scala @@ -206,6 +206,7 @@ object ScalaConverter { val LocalTimeClass = RootClass.newClass("java.time.LocalTime") val resultExpr = BLOCK(LocalTimeClass.DOT("ofNanoOfDay").APPLY(REF("l").INFIX("*", LIT(1000L)))) tree MATCH caseLWithTypeLong ==> resultExpr + case UnderlyingPrimitive => tree }) withComment "avro time-micros long stores the number of microseconds after midnight, 00:00:00.000000" } else if (logicalType.getName == "timestamp-millis") { typeMatcher.avroScalaTypes.timestampMillis match { @@ -221,6 +222,7 @@ object ScalaConverter { val longConversion = CASE(ID("l") withType (LongClass)) ==> resultExpr tree MATCH longConversion } + case UnderlyingPrimitive => tree } } else if (logicalType.getName == "timestamp-micros") { (typeMatcher.avroScalaTypes.timestampMicros match { @@ -235,6 +237,7 @@ object ScalaConverter { ZoneOffset DOT "UTC" ), ZoneId DOT "of" APPLY LIT("UTC"))) tree MATCH CASE(ID("l") withType (LongClass)) ==> resultExpr + case UnderlyingPrimitive => tree }) withComment "avro timestamp-micros long stores the number of microseconds from the unix epoch, 1 January 1970 00:00:00.000000 UTC" } else if (logicalType.getName == "local-timestamp-millis") { (typeMatcher.avroScalaTypes.localTimestampMillis match { @@ -247,6 +250,7 @@ object ScalaConverter { ZoneOffset DOT "UTC" )) tree MATCH CASE(ID("l") withType (LongClass)) ==> resultExpr + case UnderlyingPrimitive => tree }) withComment "avro local-timestamp-millis long stores the number of millis, from 1 January 1970 00:00:00.000000" } else if (logicalType.getName == "local-timestamp-micros") { (typeMatcher.avroScalaTypes.localTimestampMicros match { @@ -259,6 +263,7 @@ object ScalaConverter { ZoneOffset DOT "UTC" )) tree MATCH CASE(ID("l") withType (LongClass)) ==> resultExpr + case UnderlyingPrimitive => tree }) withComment "avro local-timestamp-micros long stores the number of microseconds, from 1 January 1970 00:00:00.000000" } else tree @@ -285,6 +290,7 @@ object ScalaConverter { val integerConversion = CASE(ID("i") withType (IntegerClass)) ==> resultExpr tree MATCH integerConversion } + case UnderlyingPrimitive => tree } } else if (logicalType.getName == "time-millis") { @@ -303,6 +309,7 @@ object ScalaConverter { val integerConversion = CASE(ID("i") withType (IntegerClass)) ==> resultExpr tree MATCH integerConversion } + case UnderlyingPrimitive => tree } } else tree diff --git a/avrohugger-core/src/main/scala/matchers/custom/CustomDefaultParamMatcher.scala b/avrohugger-core/src/main/scala/matchers/custom/CustomDefaultParamMatcher.scala index 2f53fb5e..9db3bdfe 100644 --- a/avrohugger-core/src/main/scala/matchers/custom/CustomDefaultParamMatcher.scala +++ b/avrohugger-core/src/main/scala/matchers/custom/CustomDefaultParamMatcher.scala @@ -33,12 +33,14 @@ object CustomDefaultParamMatcher { def checkCustomDateType(dateType: AvroScalaDateType) = dateType match { case JavaSqlDate => NEW(REF("java.sql.Date"), LIT(0L)) case JavaTimeLocalDate => REF("java.time.LocalDate.now") + case UnderlyingPrimitive => LIT(0) } def checkCustomTimestampMillisType(timestampMillisType: AvroScalaTimestampMillisType) = timestampMillisType match { case JavaSqlTimestamp => NEW(REF("java.sql.Timestamp"), LIT(0L)) case JavaTimeInstant => REF("java.time.Instant.now") + case UnderlyingPrimitive => LIT(0L) } def checkCustomDecimalType(decimalType: AvroScalaDecimalType, schema: Schema, default: => Tree, decimalValue: => Option[String] = None) = { @@ -57,25 +59,30 @@ object CustomDefaultParamMatcher { timeMillisType match { case JavaSqlTime => NEW(REF("java.sql.Time"), LIT(0L)) case JavaTimeLocalTime => REF("java.time.LocalTime.now") + case UnderlyingPrimitive => LIT(0L) } def checkCustomTimeMicrosType(timeMillisType: AvroScalaTimeType): Tree = timeMillisType match { case JavaTimeLocalTime => REF("java.time.LocalTime.MIDNIGHT") + case UnderlyingPrimitive => LIT(0L) } def checkCustomTimestampMicrosType(timeMillisType: AvroScalaTimestampType): Tree = timeMillisType match { case JavaTimeZonedDateTime => REF("java.time.ZonedDateTime.of").APPLY(REF("java.time.LocalDateTime") DOT "MIN", REF("java.time.ZoneId") DOT "of" APPLY LIT("UTC")) + case UnderlyingPrimitive => LIT(0L) } def checkCustomLocalTimestampMillisType(timeMillisType: AvroScalaLocalTimestampType): Tree = timeMillisType match { case JavaTimeLocalDateTime => REF("java.time.LocalDateTime") DOT "MIN" + case UnderlyingPrimitive => LIT(0L) } def checkCustomLocalTimestampMicrosType(timeMillisType: AvroScalaLocalTimestampType): Tree = timeMillisType match { case JavaTimeLocalDateTime => REF("java.time.LocalDateTime") DOT "MIN" + case UnderlyingPrimitive => LIT(0L) } } \ No newline at end of file diff --git a/avrohugger-core/src/main/scala/matchers/custom/CustomDefaultValueMatcher.scala b/avrohugger-core/src/main/scala/matchers/custom/CustomDefaultValueMatcher.scala index 408b90da..0947abed 100644 --- a/avrohugger-core/src/main/scala/matchers/custom/CustomDefaultValueMatcher.scala +++ b/avrohugger-core/src/main/scala/matchers/custom/CustomDefaultValueMatcher.scala @@ -12,17 +12,20 @@ object CustomDefaultValueMatcher { def checkCustomDateType(value: Long, dateType: AvroScalaDateType) = dateType match { case JavaSqlDate => NEW("java.sql.Date", LIT(value)) case JavaTimeLocalDate => REF("java.time.LocalDate.ofEpochDay") APPLY LIT(value) + case UnderlyingPrimitive => LIT(value) } def checkCustomTimestampMillisType(value: Long, timestampMillisType: AvroScalaTimestampMillisType) = timestampMillisType match { case JavaSqlTimestamp => NEW("java.sql.Timestamp", LIT(value)) case JavaTimeInstant => REF("java.time.Instant.ofEpochMilli") APPLY LIT(value) + case UnderlyingPrimitive => LIT(value) } def checkCustomTimeMillisType(value: Long, timeMillisType: AvroScalaTimeMillisType) = timeMillisType match { case JavaSqlTime => NEW("java.sql.Time", LIT(value)) case JavaTimeLocalTime => REF("java.time.LocalTime.ofNanoOfDay").APPLY(LIT(value).INFIX("*", LIT(1000000L))) + case UnderlyingPrimitive => LIT(value) } } \ No newline at end of file diff --git a/avrohugger-core/src/main/scala/matchers/custom/CustomTypeMatcher.scala b/avrohugger-core/src/main/scala/matchers/custom/CustomTypeMatcher.scala index 8e803344..a6961c37 100644 --- a/avrohugger-core/src/main/scala/matchers/custom/CustomTypeMatcher.scala +++ b/avrohugger-core/src/main/scala/matchers/custom/CustomTypeMatcher.scala @@ -41,16 +41,19 @@ object CustomTypeMatcher { def checkCustomDateType(dateType: AvroScalaDateType) = dateType match { case JavaTimeLocalDate => RootClass.newClass(nme.createNameType("java.time.LocalDate")) case JavaSqlDate => RootClass.newClass(nme.createNameType("java.sql.Date")) + case UnderlyingPrimitive => IntClass } def checkCustomTimestampMillisType(timestampType: AvroScalaTimestampMillisType) = timestampType match { case JavaSqlTimestamp => RootClass.newClass(nme.createNameType("java.sql.Timestamp")) case JavaTimeInstant => RootClass.newClass(nme.createNameType("java.time.Instant")) + case UnderlyingPrimitive => LongClass } def checkCustomTimeMillisType(timeType: AvroScalaTimeMillisType) = timeType match { case JavaSqlTime => RootClass.newClass(nme.createNameType("java.sql.Time")) case JavaTimeLocalTime => RootClass.newClass(nme.createNameType("java.time.LocalTime")) + case UnderlyingPrimitive => LongClass } def checkCustomDecimalType(decimalType: AvroScalaDecimalType, schema: Schema): Type = @@ -65,17 +68,21 @@ object CustomTypeMatcher { def checkCustomTimeMicrosType(timeType: AvroScalaTimeType) = timeType match { case JavaTimeLocalTime => RootClass.newClass(nme.createNameType("java.time.LocalTime")) + case UnderlyingPrimitive => LongClass } def checkCustomTimestampMicrosType(timeType: AvroScalaTimestampType) = timeType match { case JavaTimeZonedDateTime => RootClass.newClass(nme.createNameType("java.time.ZonedDateTime")) + case UnderlyingPrimitive => LongClass } def checkCustomLocalTimestampMicrosType(timeType: AvroScalaLocalTimestampType) = timeType match { case JavaTimeLocalDateTime => RootClass.newClass(nme.createNameType("java.time.LocalDateTime")) + case UnderlyingPrimitive => LongClass } def checkCustomLocalTimestampMillisType(timeType: AvroScalaLocalTimestampType) = timeType match { case JavaTimeLocalDateTime => RootClass.newClass(nme.createNameType("java.time.LocalDateTime")) + case UnderlyingPrimitive => LongClass } } \ No newline at end of file diff --git a/avrohugger-core/src/main/scala/types/LogicalAvroScalaTypes.scala b/avrohugger-core/src/main/scala/types/LogicalAvroScalaTypes.scala index 733d0022..b5b4ed73 100644 --- a/avrohugger-core/src/main/scala/types/LogicalAvroScalaTypes.scala +++ b/avrohugger-core/src/main/scala/types/LogicalAvroScalaTypes.scala @@ -28,7 +28,7 @@ case object JavaTimeZonedDateTime extends AvroScalaTimestampType sealed trait AvroScalaLocalTimestampType extends Serializable case object JavaTimeLocalDateTime extends AvroScalaLocalTimestampType - +case object UnderlyingPrimitive extends AvroScalaTimeType with AvroScalaTimeMillisType with AvroScalaTimestampMillisType with AvroScalaTimestampType with AvroScalaLocalTimestampType with AvroScalaDateType sealed abstract class LogicalType(name: String) case class Decimal(precision: Int, scale: Int) extends LogicalType("decimal") case object Date extends LogicalType("date") diff --git a/avrohugger-core/src/test/avro/date_time_related_fields.avsc b/avrohugger-core/src/test/avro/date_time_related_fields.avsc new file mode 100644 index 00000000..32347ea9 --- /dev/null +++ b/avrohugger-core/src/test/avro/date_time_related_fields.avsc @@ -0,0 +1,44 @@ +{ + "type" : "record", + "name" : "DateTimeRelatedFields", + "namespace": "example.datetimerelatedfields", + "doc" : "Record with the fields that relate to date and time", + "fields" : [ + { + "name" : "field01", + "type" : "int", + "logicalType": "date" + }, + { + "name" : "field02", + "type" : "long", + "logicalType": "timestamp-millis" + }, + { + "name" : "field03", + "type" : "long", + "logicalType": "timestamp-micros" + }, + { + "name" : "field04", + "type" : "long", + "logicalType": "local-timestamp-micros" + }, + { + "name" : "field05", + "type" : "long", + "logicalType": "local-timestamp-millis" + }, + { + "name" : "field06", + "type" : "int", + "logicalType": "time-millis" + }, + { + "name" : "field07", + "type" : "int", + "logicalType": "time-micros" + } + ], + "messages" : { } +} diff --git a/avrohugger-core/src/test/expected/specific/example/datetimerelatedfields/DateTimeRelatedFields.scala b/avrohugger-core/src/test/expected/specific/example/datetimerelatedfields/DateTimeRelatedFields.scala new file mode 100644 index 00000000..8cb01d16 --- /dev/null +++ b/avrohugger-core/src/test/expected/specific/example/datetimerelatedfields/DateTimeRelatedFields.scala @@ -0,0 +1,76 @@ +/** MACHINE-GENERATED FROM AVRO SCHEMA. DO NOT EDIT DIRECTLY */ +package example.datetimerelatedfields + +import scala.annotation.switch + +/** + * Record with the fields that relate to date and time + * @param field01 + * @param field02 + * @param field03 + * @param field04 + * @param field05 + * @param field06 + * @param field07 + */ +final case class DateTimeRelatedFields(var field01: Int, var field02: Long, var field03: Long, var field04: Long, var field05: Long, var field06: Int, var field07: Int) extends org.apache.avro.specific.SpecificRecordBase { + def this() = this(0, 0L, 0L, 0L, 0L, 0, 0) + def get(field$: Int): AnyRef = { + (field$: @switch) match { + case 0 => { + field01 + }.asInstanceOf[AnyRef] + case 1 => { + field02 + }.asInstanceOf[AnyRef] + case 2 => { + field03 + }.asInstanceOf[AnyRef] + case 3 => { + field04 + }.asInstanceOf[AnyRef] + case 4 => { + field05 + }.asInstanceOf[AnyRef] + case 5 => { + field06 + }.asInstanceOf[AnyRef] + case 6 => { + field07 + }.asInstanceOf[AnyRef] + case _ => new org.apache.avro.AvroRuntimeException("Bad index") + } + } + def put(field$: Int, value: Any): Unit = { + (field$: @switch) match { + case 0 => this.field01 = { + value + }.asInstanceOf[Int] + case 1 => this.field02 = { + value + }.asInstanceOf[Long] + case 2 => this.field03 = { + value + }.asInstanceOf[Long] + case 3 => this.field04 = { + value + }.asInstanceOf[Long] + case 4 => this.field05 = { + value + }.asInstanceOf[Long] + case 5 => this.field06 = { + value + }.asInstanceOf[Int] + case 6 => this.field07 = { + value + }.asInstanceOf[Int] + case _ => new org.apache.avro.AvroRuntimeException("Bad index") + } + () + } + def getSchema: org.apache.avro.Schema = example.datetimerelatedfields.DateTimeRelatedFields.SCHEMA$ +} + +object DateTimeRelatedFields { + val SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"DateTimeRelatedFields\",\"namespace\":\"example.datetimerelatedfields\",\"doc\":\"Record with the fields that relate to date and time\",\"fields\":[{\"name\":\"field01\",\"type\":\"int\",\"logicalType\":\"date\"},{\"name\":\"field02\",\"type\":\"long\",\"logicalType\":\"timestamp-millis\"},{\"name\":\"field03\",\"type\":\"long\",\"logicalType\":\"timestamp-micros\"},{\"name\":\"field04\",\"type\":\"long\",\"logicalType\":\"local-timestamp-micros\"},{\"name\":\"field05\",\"type\":\"long\",\"logicalType\":\"local-timestamp-millis\"},{\"name\":\"field06\",\"type\":\"int\",\"logicalType\":\"time-millis\"},{\"name\":\"field07\",\"type\":\"int\",\"logicalType\":\"time-micros\"}],\"messages\":{}}") +} \ No newline at end of file diff --git a/avrohugger-core/src/test/expected/standard/example/datetimerelatedfields/DateTimeRelatedFields.scala b/avrohugger-core/src/test/expected/standard/example/datetimerelatedfields/DateTimeRelatedFields.scala new file mode 100644 index 00000000..826997be --- /dev/null +++ b/avrohugger-core/src/test/expected/standard/example/datetimerelatedfields/DateTimeRelatedFields.scala @@ -0,0 +1,14 @@ +/** MACHINE-GENERATED FROM AVRO SCHEMA. DO NOT EDIT DIRECTLY */ +package example.datetimerelatedfields + +/** + * Record with the fields that relate to date and time + * @param field01 + * @param field02 + * @param field03 + * @param field04 + * @param field05 + * @param field06 + * @param field07 + */ +final case class DateTimeRelatedFields(field01: Int, field02: Long, field03: Long, field04: Long, field05: Long, field06: Int, field07: Int) \ No newline at end of file diff --git a/avrohugger-core/src/test/scala/specific/SpecificFileToFileSpec.scala b/avrohugger-core/src/test/scala/specific/SpecificFileToFileSpec.scala index 3e476290..aada7eaf 100644 --- a/avrohugger-core/src/test/scala/specific/SpecificFileToFileSpec.scala +++ b/avrohugger-core/src/test/scala/specific/SpecificFileToFileSpec.scala @@ -51,6 +51,7 @@ class SpecificFileToFileSpec extends Specification { correctly generate a protocol with special strings $e28 correctly generate a simple case class with a wildcarded custom namespace $e29 correctly handle namespaces for complex types $e30 + correctly handle underlyingType for date/time related fields $e31 """ // correctly generate logical types from IDL $e26 @@ -449,4 +450,16 @@ class SpecificFileToFileSpec extends Specification { source2 === util.Util.readFile("avrohugger-core/src/test/expected/specific/example/fixedtwo/one/fixed.scala") and source3 === util.Util.readFile("avrohugger-core/src/test/expected/specific/example/fixedtwo/two/fixed.scala") } + + def e31 = { + val infile = new java.io.File("avrohugger-core/src/test/avro/date_time_related_fields.avsc") + val avroScalaCustomTypes = SpecificRecord.defaultTypes.copy(date = UnderlyingPrimitive, timestampMillis = UnderlyingPrimitive, timestampMicros = UnderlyingPrimitive, localTimestampMicros = UnderlyingPrimitive, localTimestampMillis = UnderlyingPrimitive, timeMillis = UnderlyingPrimitive, timeMicros = UnderlyingPrimitive) + val gen = new Generator(SpecificRecord, avroScalaCustomTypes = Some(avroScalaCustomTypes)) + val outDir = gen.defaultOutputDir + "/specific/" + gen.fileToFile(infile, outDir) + + val source = util.Util.readFile("target/generated-sources/specific/example/datetimerelatedfields/DateTimeRelatedFields.scala") + + source === util.Util.readFile("avrohugger-core/src/test/expected/specific/example/datetimerelatedfields/DateTimeRelatedFields.scala") + } } diff --git a/avrohugger-core/src/test/scala/standard/StandardFileToFileSpec.scala b/avrohugger-core/src/test/scala/standard/StandardFileToFileSpec.scala index a3c206cc..18fa5f80 100644 --- a/avrohugger-core/src/test/scala/standard/StandardFileToFileSpec.scala +++ b/avrohugger-core/src/test/scala/standard/StandardFileToFileSpec.scala @@ -64,6 +64,7 @@ class StandardFileToFileSpec extends Specification { correctly generate nested fixed bytes from schema $e43 correctly generate nested references with custom namespaces $e44 + correctly handle underlyingType for date/time related fields $e45 """ // correctly generate logical types from IDL $e30 @@ -647,4 +648,15 @@ class StandardFileToFileSpec extends Specification { source === util.Util.readFile("avrohugger-core/src/test/expected/standard/aa/bb/cc/Test.scala") } + def e45 = { + val infile = new java.io.File("avrohugger-core/src/test/avro/date_time_related_fields.avsc") + val avroScalaCustomTypes = Standard.defaultTypes.copy(date = UnderlyingPrimitive, timestampMillis = UnderlyingPrimitive, timestampMicros = UnderlyingPrimitive, localTimestampMicros = UnderlyingPrimitive, localTimestampMillis = UnderlyingPrimitive, timeMillis = UnderlyingPrimitive, timeMicros = UnderlyingPrimitive) + val gen = new Generator(Standard, avroScalaCustomTypes = Some(avroScalaCustomTypes)) + val outDir = gen.defaultOutputDir + "/standard/" + gen.fileToFile(infile, outDir) + + val source = util.Util.readFile("target/generated-sources/standard/example/datetimerelatedfields/DateTimeRelatedFields.scala") + source === util.Util.readFile("avrohugger-core/src/test/expected/standard/example/datetimerelatedfields/DateTimeRelatedFields.scala") + } + } \ No newline at end of file diff --git a/build.sbt b/build.sbt index 94f9e76d..3c989e22 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ lazy val avroVersion = "1.11.3" lazy val commonSettings = Seq( organization := "com.julianpeeters", - version := "2.8.2", + version := "2.8.3", ThisBuild / versionScheme := Some("semver-spec"), scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature"), Test / scalacOptions ++= Seq("-Yrangepos"),