You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by cu...@apache.org on 2014/09/30 00:58:29 UTC

svn commit: r1628335 - in /avro/trunk: ./ lang/java/avro/src/main/java/org/apache/avro/io/parsing/ lang/java/avro/src/test/java/org/apache/avro/ lang/java/avro/src/test/java/org/apache/avro/io/parsing/

Author: cutting
Date: Mon Sep 29 22:58:29 2014
New Revision: 1628335

URL: http://svn.apache.org/r1628335
Log:
AVRO-1590. Java: In resolving records in unions, permit structural and shortname matches when fullname matching fails.  Contributed by Ryan Blue.

Modified:
    avro/trunk/CHANGES.txt
    avro/trunk/lang/java/avro/src/main/java/org/apache/avro/io/parsing/ResolvingGrammarGenerator.java
    avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaValidation.java
    avro/trunk/lang/java/avro/src/test/java/org/apache/avro/io/parsing/TestResolvingGrammarGenerator2.java

Modified: avro/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/avro/trunk/CHANGES.txt?rev=1628335&r1=1628334&r2=1628335&view=diff
==============================================================================
--- avro/trunk/CHANGES.txt (original)
+++ avro/trunk/CHANGES.txt Mon Sep 29 22:58:29 2014
@@ -13,6 +13,10 @@ Trunk (not yet released)
     AVRO-739. Add date, time, timestamp, and duration binary types to
     specification. (Dmitry Kovalev and Ryan Blue via tomwhite)
 
+    AVRO-1590. Java: In resolving records in unions, permit structural
+    and shortname matches when fullname matching fails.
+    (Ryan Blue via cutting)
+
   OPTIMIZATIONS
 
   IMPROVEMENTS

Modified: avro/trunk/lang/java/avro/src/main/java/org/apache/avro/io/parsing/ResolvingGrammarGenerator.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/main/java/org/apache/avro/io/parsing/ResolvingGrammarGenerator.java?rev=1628335&r1=1628334&r2=1628335&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/main/java/org/apache/avro/io/parsing/ResolvingGrammarGenerator.java (original)
+++ avro/trunk/lang/java/avro/src/main/java/org/apache/avro/io/parsing/ResolvingGrammarGenerator.java Mon Sep 29 22:58:29 2014
@@ -166,7 +166,7 @@ public class ResolvingGrammarGenerator e
         break;
   
       case UNION:
-        int j = bestBranch(reader, writer);
+        int j = bestBranch(reader, writer, seen);
         if (j >= 0) {
           Symbol s = generate(writer, reader.getTypes().get(j), seen);
           return Symbol.seq(Symbol.unionAdjustAction(j, s), Symbol.UNION);
@@ -426,24 +426,64 @@ public class ResolvingGrammarGenerator e
     return Symbol.enumAdjustAction(rsymbols.size(), adjustments);
   }
 
-  private static int bestBranch(Schema r, Schema w) {
+  /**
+   * This checks if the symbol itself is an error or if there is an error in
+   * its production.
+   *
+   * When the symbol is created for a record, this checks whether the record
+   * fields are present (the symbol is not an error action) and that all of the
+   * fields have a non-error action. Record fields may have nested error
+   * actions.
+   *
+   * @return true if the symbol is an error or if its production has an error
+   */
+  private boolean hasMatchError(Symbol sym) {
+    if (sym instanceof Symbol.ErrorAction) {
+      return true;
+    } else {
+      for (int i = 0; i < sym.production.length; i += 1) {
+        if (sym.production[i] instanceof Symbol.ErrorAction) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private int bestBranch(Schema r, Schema w, Map<LitS, Symbol> seen) throws IOException {
     Schema.Type vt = w.getType();
       // first scan for exact match
       int j = 0;
+      int structureMatch = -1;
       for (Schema b : r.getTypes()) {
         if (vt == b.getType())
-          if (vt == Schema.Type.RECORD || vt == Schema.Type.ENUM || 
+          if (vt == Schema.Type.RECORD || vt == Schema.Type.ENUM ||
               vt == Schema.Type.FIXED) {
             String vname = w.getFullName();
             String bname = b.getFullName();
-            if ((vname != null && vname.equals(bname))
-                || vname == bname && vt == Schema.Type.RECORD)
+            // return immediately if the name matches exactly according to spec
+            if (vname != null && vname.equals(bname))
               return j;
+
+            if (vt == Schema.Type.RECORD &&
+                !hasMatchError(resolveRecords(w, b, seen))) {
+              String vShortName = w.getName();
+              String bShortName = b.getName();
+              // use the first structure match or one where the name matches
+              if ((structureMatch < 0) ||
+                  (vShortName != null && vShortName.equals(bShortName))) {
+                structureMatch = j;
+              }
+            }
           } else
             return j;
         j++;
       }
 
+      // if there is a record structure match, return it
+      if (structureMatch >= 0)
+        return structureMatch;
+
       // then scan match via numeric promotion
       j = 0;
       for (Schema b : r.getTypes()) {

Modified: avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaValidation.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaValidation.java?rev=1628335&r1=1628334&r2=1628335&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaValidation.java (original)
+++ avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaValidation.java Mon Sep 29 22:58:29 2014
@@ -20,6 +20,7 @@ package org.apache.avro;
 
 import java.util.ArrayList;
 
+import org.apache.avro.reflect.ReflectData;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -119,6 +120,48 @@ public class TestSchemaValidation {
     builder.strategy(null).validateAll();
   }
 
+  public static class Point {
+    double x;
+    double y;
+  }
+
+  public static class Circle {
+    Point center;
+    double radius;
+  }
+
+  public static final Schema circleSchema = SchemaBuilder.record("Circle")
+      .fields()
+      .name("center").type().record("Point")
+          .fields()
+          .requiredDouble("x")
+          .requiredDouble("y")
+          .endRecord().noDefault()
+      .requiredDouble("radius")
+      .endRecord();
+
+  public static final Schema circleSchemaDifferentNames = SchemaBuilder
+      .record("crcl").fields()
+      .name("center").type().record("pt")
+      .fields()
+      .requiredDouble("x")
+      .requiredDouble("y")
+      .endRecord().noDefault()
+      .requiredDouble("radius")
+      .endRecord();
+
+  @Test
+  public void testReflectMatchStructure() throws SchemaValidationException {
+    testValidatorPasses(builder.canBeReadStrategy().validateAll(),
+        circleSchemaDifferentNames, ReflectData.get().getSchema(Circle.class));
+  }
+
+  @Test
+  public void testReflectWithAllowNullMatchStructure() throws SchemaValidationException {
+    testValidatorPasses(builder.canBeReadStrategy().validateAll(),
+        circleSchemaDifferentNames, ReflectData.AllowNull.get().getSchema(Circle.class));
+  }
+
   private void testValidatorPasses(SchemaValidator validator,
       Schema schema, Schema... prev) throws SchemaValidationException {
     ArrayList<Schema> prior = new ArrayList<Schema>();

Modified: avro/trunk/lang/java/avro/src/test/java/org/apache/avro/io/parsing/TestResolvingGrammarGenerator2.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/test/java/org/apache/avro/io/parsing/TestResolvingGrammarGenerator2.java?rev=1628335&r1=1628334&r2=1628335&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/test/java/org/apache/avro/io/parsing/TestResolvingGrammarGenerator2.java (original)
+++ avro/trunk/lang/java/avro/src/test/java/org/apache/avro/io/parsing/TestResolvingGrammarGenerator2.java Mon Sep 29 22:58:29 2014
@@ -17,8 +17,12 @@
  */
 package org.apache.avro.io.parsing;
 
-import org.apache.avro.io.parsing.ResolvingGrammarGenerator;
+import java.util.Arrays;
+import org.apache.avro.SchemaBuilder;
+import org.apache.avro.SchemaValidationException;
+import org.apache.avro.SchemaValidatorBuilder;
 import org.apache.avro.Schema;
+import org.junit.Assert;
 import org.junit.Test;
 
 /** ResolvingGrammarGenerator tests that are not Parameterized.*/
@@ -31,4 +35,109 @@ public class TestResolvingGrammarGenerat
       (Schema.create(Schema.Type.BYTES),
        Schema.createFixed("MyFixed", null, null, 10));
   }
+
+  Schema point2dFullname = SchemaBuilder.record("Point").namespace("written")
+      .fields()
+      .requiredDouble("x")
+      .requiredDouble("y")
+      .endRecord();
+
+  Schema point3dNoDefault = SchemaBuilder.record("Point").fields()
+      .requiredDouble("x")
+      .requiredDouble("y")
+      .requiredDouble("z")
+      .endRecord();
+
+  Schema point2d = SchemaBuilder.record("Point2D")
+      .fields()
+      .requiredDouble("x")
+      .requiredDouble("y")
+      .endRecord();
+
+  Schema point3d = SchemaBuilder.record("Point3D").fields()
+      .requiredDouble("x")
+      .requiredDouble("y")
+      .name("z").type().doubleType().doubleDefault(0.0)
+      .endRecord();
+
+  Schema point3dMatchName = SchemaBuilder.record("Point").fields()
+      .requiredDouble("x")
+      .requiredDouble("y")
+      .name("z").type().doubleType().doubleDefault(0.0)
+      .endRecord();
+
+  @Test(expected=SchemaValidationException.class)
+  public void testUnionResolutionNoStructureMatch() throws Exception {
+    // there is a short name match, but the structure does not match
+    Schema read = Schema.createUnion(Arrays.asList(
+        Schema.create(Schema.Type.NULL),
+        point3dNoDefault));
+
+    new SchemaValidatorBuilder().canBeReadStrategy().validateAll()
+        .validate(point2dFullname, Arrays.asList(read));
+  }
+
+  @Test
+  public void testUnionResolutionFirstStructureMatch2d() throws Exception {
+    // multiple structure matches with no short or full name matches
+    Schema read = Schema.createUnion(Arrays.asList(
+        Schema.create(Schema.Type.NULL),
+        point3dNoDefault, point2d, point3d));
+
+    Symbol grammar = new ResolvingGrammarGenerator().generate(
+        point2dFullname, read);
+    Assert.assertTrue(grammar.production[1] instanceof Symbol.UnionAdjustAction);
+
+    Symbol.UnionAdjustAction action = (Symbol.UnionAdjustAction)
+        grammar.production[1];
+    Assert.assertEquals(2, action.rindex);
+  }
+
+  @Test
+  public void testUnionResolutionFirstStructureMatch3d() throws Exception {
+    // multiple structure matches with no short or full name matches
+    Schema read = Schema.createUnion(Arrays.asList(
+        Schema.create(Schema.Type.NULL),
+        point3dNoDefault, point3d, point2d));
+
+    Symbol grammar = new ResolvingGrammarGenerator().generate(
+        point2dFullname, read);
+    Assert.assertTrue(grammar.production[1] instanceof Symbol.UnionAdjustAction);
+
+    Symbol.UnionAdjustAction action = (Symbol.UnionAdjustAction)
+        grammar.production[1];
+    Assert.assertEquals(2, action.rindex);
+  }
+
+  @Test
+  public void testUnionResolutionNamedStructureMatch() throws Exception {
+    // multiple structure matches with a short name match
+    Schema read = Schema.createUnion(Arrays.asList(
+        Schema.create(Schema.Type.NULL),
+        point2d, point3dMatchName, point3d));
+
+    Symbol grammar = new ResolvingGrammarGenerator().generate(
+        point2dFullname, read);
+    Assert.assertTrue(grammar.production[1] instanceof Symbol.UnionAdjustAction);
+
+    Symbol.UnionAdjustAction action = (Symbol.UnionAdjustAction)
+        grammar.production[1];
+    Assert.assertEquals(2, action.rindex);
+  }
+
+  @Test
+  public void testUnionResolutionFullNameMatch() throws Exception {
+    // there is a full name match, so it should be chosen
+    Schema read = Schema.createUnion(Arrays.asList(
+        Schema.create(Schema.Type.NULL),
+        point2d, point3dMatchName, point3d, point2dFullname));
+
+    Symbol grammar = new ResolvingGrammarGenerator().generate(
+        point2dFullname, read);
+    Assert.assertTrue(grammar.production[1] instanceof Symbol.UnionAdjustAction);
+
+    Symbol.UnionAdjustAction action = (Symbol.UnionAdjustAction)
+        grammar.production[1];
+    Assert.assertEquals(4, action.rindex);
+  }
 }