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

RyanSkraba pushed a commit to branch branch-1.12
in repository https://gitbox.apache.org/repos/asf/avro.git

commit ace4a89ae4e12382299e1a6cd87a2400c97c3045
Author: Ryan Skraba <[email protected]>
AuthorDate: Mon May 11 20:41:21 2026 +0200

    AVRO-4209: [java] Reflect correctly on recursive schemas (#3765)
    
    * AVRO-4209: [java] Reflect correctly on recursive schemas
    
    * Fix CodeQL / Inconsistent equals and hashCode
    
    ---------
    
    Co-authored-by: Oscar Westra van Holthe - Kind 
<[email protected]>
---
 .../java/org/apache/avro/reflect/ReflectData.java  |  4 ++
 .../java/org/apache/avro/reflect/TestReflect.java  | 58 ++++++++++++++++++++++
 2 files changed, 62 insertions(+)

diff --git 
a/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectData.java 
b/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectData.java
index 1810b27e06..56524a8cd8 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectData.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectData.java
@@ -739,6 +739,7 @@ public class ReflectData extends SpecificData {
           boolean error = Throwable.class.isAssignableFrom(c);
           schema = Schema.createRecord(name, doc, space, error);
           consumeAvroAliasAnnotation(c, schema);
+          names.put(fullName, schema);
           for (Field field : getCachedFields(c))
             if ((field.getModifiers() & (Modifier.TRANSIENT | 
Modifier.STATIC)) == 0
                 && !field.isAnnotationPresent(AvroIgnore.class)) {
@@ -781,6 +782,9 @@ public class ReflectData extends SpecificData {
             }
             schema.addProp(meta.key(), meta.value());
           }
+          // This is added immediately back into the names to ensure that the 
discoverable
+          // order is maintained if its a LinkedHashMap.
+          names.remove(fullName, schema);
         }
         names.put(fullName, schema);
       }
diff --git 
a/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflect.java 
b/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflect.java
index 08e186268c..ffbd543a18 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflect.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflect.java
@@ -33,6 +33,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Random;
 import org.apache.avro.AvroRuntimeException;
@@ -1416,4 +1417,61 @@ public class TestReflect {
             + "{\"name\":\"foo\",\"type\":\"int\",\"doc\":\"Some 
Documentation\"}" + "]}");
   }
 
+  // test recursive record schema
+  public static class TreeNode {
+    public int value = 0;
+    @Nullable
+    public TreeNode left;
+    @Nullable
+    public TreeNode right;
+
+    public TreeNode() {
+    }
+
+    public TreeNode(int value) {
+      this.value = value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof TreeNode))
+        return false;
+      TreeNode that = (TreeNode) o;
+      if (value != that.value || !Objects.equals(left, that.left) || 
!Objects.equals(right, that.right))
+        return false;
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(value, left, right);
+    }
+
+  }
+
+  @Test
+  void recursiveRecord() throws Exception {
+    Schema schema = ReflectData.get().getSchema(TreeNode.class);
+    assertEquals("TreeNode", schema.getName());
+    assertEquals(3, schema.getFields().size());
+
+    // Verify that the left tree node contains the parent schema
+    Schema leftSchema = schema.getField("left").schema();
+    assertEquals(Schema.Type.UNION, leftSchema.getType());
+    assertEquals(2, leftSchema.getTypes().size());
+    assertEquals(Schema.Type.NULL, leftSchema.getTypes().get(0).getType());
+    assertEquals(schema, leftSchema.getTypes().get(1));
+
+    // Verify that the right tree node is the same union
+    Schema rightSchema = schema.getField("right").schema();
+    assertEquals(leftSchema, rightSchema);
+
+    // Test serialization with actual recursive data
+    TreeNode root = new TreeNode(100);
+    root.left = new TreeNode(90);
+    root.right = new TreeNode(101);
+    root.left.left = new TreeNode(-100);
+
+    checkReadWrite(root);
+  }
 }

Reply via email to