You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by nk...@apache.org on 2022/11/16 17:31:15 UTC

[avro] branch master updated: AVRO-3536: Inherit conversions for Union type (#1721)

This is an automated email from the ASF dual-hosted git repository.

nkollar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/master by this push:
     new 000338a7a AVRO-3536: Inherit conversions for Union type (#1721)
000338a7a is described below

commit 000338a7ace22d39c994040db64a39fdae7d3432
Author: Prathamesh <pr...@gmail.com>
AuthorDate: Wed Nov 16 23:01:07 2022 +0530

    AVRO-3536: Inherit conversions for Union type (#1721)
    
    * AVRO-3536: Inherit conversions for Union type
    
    * AVRO-3536: Inherit conversions for Union type
    
    * AVRO-3536: added test cases
    
    * AVRO-3536: fix RAT failure
    
    * AVRO-3536: generated new class with @Override annotation
---
 .../org/apache/avro/specific/SpecificData.java     |   8 +-
 .../avro/specific/TestSpecificRecordWithUnion.java |  69 +++++
 .../org/apache/avro/specific/TestUnionRecord.java  | 324 +++++++++++++++++++++
 .../avro/src/test/resources/TestUnionRecord.avsc   |  23 ++
 4 files changed, 420 insertions(+), 4 deletions(-)

diff --git a/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificData.java b/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificData.java
index 072b1bf90..966acb5fc 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificData.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificData.java
@@ -150,16 +150,16 @@ public class SpecificData extends GenericData {
   }
 
   /**
-   * For RECORD type schemas, this method returns the SpecificData instance of the
-   * class associated with the schema, in order to get the right conversions for
-   * any logical types used.
+   * For RECORD and UNION type schemas, this method returns the SpecificData
+   * instance of the class associated with the schema, in order to get the right
+   * conversions for any logical types used.
    *
    * @param reader the reader schema
    * @return the SpecificData associated with the schema's class, or the default
    *         instance.
    */
   public static SpecificData getForSchema(Schema reader) {
-    if (reader != null && reader.getType() == Type.RECORD) {
+    if (reader != null && (reader.getType() == Type.RECORD || reader.getType() == Type.UNION)) {
       final Class<?> clazz = SpecificData.get().getClass(reader);
       if (clazz != null) {
         return getForClass(clazz);
diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificRecordWithUnion.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificRecordWithUnion.java
new file mode 100644
index 000000000..c3b330b28
--- /dev/null
+++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificRecordWithUnion.java
@@ -0,0 +1,69 @@
+/*
+ * 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.specific;
+
+import org.apache.avro.Schema;
+import org.apache.avro.SchemaBuilder;
+import org.apache.avro.generic.GenericDatumReader;
+import org.apache.avro.generic.GenericDatumWriter;
+
+import org.apache.avro.io.EncoderFactory;
+import org.apache.avro.io.DecoderFactory;
+import org.apache.avro.io.DatumReader;
+import org.apache.avro.io.DatumWriter;
+import org.apache.avro.io.BinaryEncoder;
+import org.apache.avro.io.Decoder;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestSpecificRecordWithUnion {
+  @Test
+  public void testUnionLogicalDecimalConversion() throws IOException {
+    final TestUnionRecord record = TestUnionRecord.newBuilder().setAmount(BigDecimal.ZERO).build();
+    final Schema schema = SchemaBuilder.unionOf().nullType().and().type(record.getSchema()).endUnion();
+
+    byte[] recordBytes = serializeRecord(
+        "{ \"org.apache.avro.specific.TestUnionRecord\": { \"amount\": { \"bytes\": \"\\u0000\" } } }", schema);
+
+    SpecificDatumReader<SpecificRecord> specificDatumReader = new SpecificDatumReader<>(schema);
+    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(recordBytes);
+    Decoder decoder = DecoderFactory.get().binaryDecoder(byteArrayInputStream, null);
+    final SpecificRecord deserialized = specificDatumReader.read(null, decoder);
+    assertEquals(record, deserialized);
+  }
+
+  public static byte[] serializeRecord(String value, Schema schema) throws IOException {
+    DatumReader<Object> reader = new GenericDatumReader<>(schema);
+    Object object = reader.read(null, DecoderFactory.get().jsonDecoder(schema, value));
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    BinaryEncoder encoder = EncoderFactory.get().directBinaryEncoder(out, null);
+    DatumWriter<Object> writer = new GenericDatumWriter<>(schema);
+    writer.write(object, encoder);
+    encoder.flush();
+    byte[] bytes = out.toByteArray();
+    out.close();
+    return bytes;
+  }
+}
diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestUnionRecord.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestUnionRecord.java
new file mode 100644
index 000000000..57c86a96f
--- /dev/null
+++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestUnionRecord.java
@@ -0,0 +1,324 @@
+/*
+ * 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.specific;
+
+import org.apache.avro.message.BinaryMessageDecoder;
+import org.apache.avro.message.BinaryMessageEncoder;
+import org.apache.avro.message.SchemaStore;
+
+@SuppressWarnings("all")
+@AvroGenerated
+public class TestUnionRecord extends SpecificRecordBase implements SpecificRecord {
+  private static final long serialVersionUID = -3829374192747523457L;
+
+  public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse(
+      "{\"type\":\"record\",\"name\":\"TestUnionRecord\",\"namespace\":\"org.apache.avro.specific\",\"fields\":[{\"name\":\"amount\",\"type\":[\"null\",{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":31,\"scale\":8}],\"default\":null}]}");
+
+  public static org.apache.avro.Schema getClassSchema() {
+    return SCHEMA$;
+  }
+
+  private static final SpecificData MODEL$ = new SpecificData();
+  static {
+    MODEL$.addLogicalTypeConversion(new org.apache.avro.Conversions.DecimalConversion());
+  }
+
+  private static final BinaryMessageEncoder<TestUnionRecord> ENCODER = new BinaryMessageEncoder<TestUnionRecord>(MODEL$,
+      SCHEMA$);
+
+  private static final BinaryMessageDecoder<TestUnionRecord> DECODER = new BinaryMessageDecoder<TestUnionRecord>(MODEL$,
+      SCHEMA$);
+
+  /**
+   * Return the BinaryMessageEncoder instance used by this class.
+   *
+   * @return the message encoder used by this class
+   */
+  public static BinaryMessageEncoder<TestUnionRecord> getEncoder() {
+    return ENCODER;
+  }
+
+  /**
+   * Return the BinaryMessageDecoder instance used by this class.
+   *
+   * @return the message decoder used by this class
+   */
+  public static BinaryMessageDecoder<TestUnionRecord> getDecoder() {
+    return DECODER;
+  }
+
+  /**
+   * Create a new BinaryMessageDecoder instance for this class that uses the
+   * specified {@link SchemaStore}.
+   *
+   * @param resolver a {@link SchemaStore} used to find schemas by fingerprint
+   * @return a BinaryMessageDecoder instance for this class backed by the given
+   *         SchemaStore
+   */
+  public static BinaryMessageDecoder<TestUnionRecord> createDecoder(SchemaStore resolver) {
+    return new BinaryMessageDecoder<TestUnionRecord>(MODEL$, SCHEMA$, resolver);
+  }
+
+  /**
+   * Serializes this TestUnionRecord to a ByteBuffer.
+   *
+   * @return a buffer holding the serialized data for this instance
+   * @throws java.io.IOException if this instance could not be serialized
+   */
+  public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException {
+    return ENCODER.encode(this);
+  }
+
+  /**
+   * Deserializes a TestUnionRecord from a ByteBuffer.
+   *
+   * @param b a byte buffer holding serialized data for an instance of this class
+   * @return a TestUnionRecord instance decoded from the given buffer
+   * @throws java.io.IOException if the given bytes could not be deserialized into
+   *                             an instance of this class
+   */
+  public static TestUnionRecord fromByteBuffer(java.nio.ByteBuffer b) throws java.io.IOException {
+    return DECODER.decode(b);
+  }
+
+  private java.math.BigDecimal amount;
+
+  /**
+   * Default constructor. Note that this does not initialize fields to their
+   * default values from the schema. If that is desired then one should use
+   * <code>newBuilder()</code>.
+   */
+  public TestUnionRecord() {
+  }
+
+  /**
+   * All-args constructor.
+   *
+   * @param amount The new value for amount
+   */
+  public TestUnionRecord(java.math.BigDecimal amount) {
+    this.amount = amount;
+  }
+
+  @Override
+  public SpecificData getSpecificData() {
+    return MODEL$;
+  }
+
+  @Override
+  public org.apache.avro.Schema getSchema() {
+    return SCHEMA$;
+  }
+
+  // Used by DatumWriter. Applications should not call.
+  @Override
+  public Object get(int field$) {
+    switch (field$) {
+    case 0:
+      return amount;
+    default:
+      throw new IndexOutOfBoundsException("Invalid index: " + field$);
+    }
+  }
+
+  // Used by DatumReader. Applications should not call.
+  @Override
+  @SuppressWarnings(value = "unchecked")
+  public void put(int field$, Object value$) {
+    switch (field$) {
+    case 0:
+      amount = (java.math.BigDecimal) value$;
+      break;
+    default:
+      throw new IndexOutOfBoundsException("Invalid index: " + field$);
+    }
+  }
+
+  /**
+   * Gets the value of the 'amount' field.
+   *
+   * @return The value of the 'amount' field.
+   */
+  public java.math.BigDecimal getAmount() {
+    return amount;
+  }
+
+  /**
+   * Sets the value of the 'amount' field.
+   *
+   * @param value the value to set.
+   */
+  public void setAmount(java.math.BigDecimal value) {
+    this.amount = value;
+  }
+
+  /**
+   * Creates a new TestUnionRecord RecordBuilder.
+   *
+   * @return A new TestUnionRecord RecordBuilder
+   */
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  /**
+   * Creates a new TestUnionRecord RecordBuilder by copying an existing Builder.
+   *
+   * @param other The existing builder to copy.
+   * @return A new TestUnionRecord RecordBuilder
+   */
+  public static Builder newBuilder(Builder other) {
+    if (other == null) {
+      return new Builder();
+    } else {
+      return new Builder(other);
+    }
+  }
+
+  /**
+   * Creates a new TestUnionRecord RecordBuilder by copying an existing
+   * TestUnionRecord instance.
+   *
+   * @param other The existing instance to copy.
+   * @return A new TestUnionRecord RecordBuilder
+   */
+  public static Builder newBuilder(TestUnionRecord other) {
+    if (other == null) {
+      return new Builder();
+    } else {
+      return new Builder(other);
+    }
+  }
+
+  /**
+   * RecordBuilder for TestUnionRecord instances.
+   */
+  @AvroGenerated
+  public static class Builder extends SpecificRecordBuilderBase<TestUnionRecord>
+      implements org.apache.avro.data.RecordBuilder<TestUnionRecord> {
+
+    private java.math.BigDecimal amount;
+
+    /** Creates a new Builder */
+    private Builder() {
+      super(SCHEMA$, MODEL$);
+    }
+
+    /**
+     * Creates a Builder by copying an existing Builder.
+     *
+     * @param other The existing Builder to copy.
+     */
+    private Builder(Builder other) {
+      super(other);
+      if (isValidValue(fields()[0], other.amount)) {
+        this.amount = data().deepCopy(fields()[0].schema(), other.amount);
+        fieldSetFlags()[0] = other.fieldSetFlags()[0];
+      }
+    }
+
+    /**
+     * Creates a Builder by copying an existing TestUnionRecord instance
+     *
+     * @param other The existing instance to copy.
+     */
+    private Builder(TestUnionRecord other) {
+      super(SCHEMA$, MODEL$);
+      if (isValidValue(fields()[0], other.amount)) {
+        this.amount = data().deepCopy(fields()[0].schema(), other.amount);
+        fieldSetFlags()[0] = true;
+      }
+    }
+
+    /**
+     * Gets the value of the 'amount' field.
+     *
+     * @return The value.
+     */
+    public java.math.BigDecimal getAmount() {
+      return amount;
+    }
+
+    /**
+     * Sets the value of the 'amount' field.
+     *
+     * @param value The value of 'amount'.
+     * @return This builder.
+     */
+    public Builder setAmount(java.math.BigDecimal value) {
+      validate(fields()[0], value);
+      this.amount = value;
+      fieldSetFlags()[0] = true;
+      return this;
+    }
+
+    /**
+     * Checks whether the 'amount' field has been set.
+     *
+     * @return True if the 'amount' field has been set, false otherwise.
+     */
+    public boolean hasAmount() {
+      return fieldSetFlags()[0];
+    }
+
+    /**
+     * Clears the value of the 'amount' field.
+     *
+     * @return This builder.
+     */
+    public Builder clearAmount() {
+      amount = null;
+      fieldSetFlags()[0] = false;
+      return this;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public TestUnionRecord build() {
+      try {
+        TestUnionRecord record = new TestUnionRecord();
+        record.amount = fieldSetFlags()[0] ? this.amount : (java.math.BigDecimal) defaultValue(fields()[0]);
+        return record;
+      } catch (org.apache.avro.AvroMissingFieldException e) {
+        throw e;
+      } catch (Exception e) {
+        throw new org.apache.avro.AvroRuntimeException(e);
+      }
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private static final org.apache.avro.io.DatumWriter<TestUnionRecord> WRITER$ = (org.apache.avro.io.DatumWriter<TestUnionRecord>) MODEL$
+      .createDatumWriter(SCHEMA$);
+
+  @Override
+  public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
+    WRITER$.write(this, SpecificData.getEncoder(out));
+  }
+
+  @SuppressWarnings("unchecked")
+  private static final org.apache.avro.io.DatumReader<TestUnionRecord> READER$ = (org.apache.avro.io.DatumReader<TestUnionRecord>) MODEL$
+      .createDatumReader(SCHEMA$);
+
+  @Override
+  public void readExternal(java.io.ObjectInput in) throws java.io.IOException {
+    READER$.read(this, SpecificData.getDecoder(in));
+  }
+
+}
diff --git a/lang/java/avro/src/test/resources/TestUnionRecord.avsc b/lang/java/avro/src/test/resources/TestUnionRecord.avsc
new file mode 100644
index 000000000..36241c8b6
--- /dev/null
+++ b/lang/java/avro/src/test/resources/TestUnionRecord.avsc
@@ -0,0 +1,23 @@
+[
+  "null",
+  {
+    "namespace": "org.apache.avro.specific",
+    "type": "record",
+    "name": "TestUnionRecord",
+    "fields": [
+      {
+        "name": "amount",
+        "type": [
+          "null",
+          {
+            "type": "bytes",
+            "logicalType": "decimal",
+            "precision": 31,
+            "scale": 8
+          }
+        ],
+        "default": null
+      }
+    ]
+  }
+]