Skip to content

Commit 954a133

Browse files
committed
Improve Avro schema compatibility check error message
1 parent 6ffc0cf commit 954a133

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroSchema.java

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.fasterxml.jackson.dataformat.avro;
22

3+
import java.util.Set;
34
import java.util.concurrent.atomic.AtomicReference;
45

56
import org.apache.avro.Schema;
7+
import org.apache.avro.Schema.Type;
68
import org.apache.avro.SchemaCompatibility;
79
import org.apache.avro.SchemaCompatibility.SchemaCompatibilityType;
810
import org.apache.avro.SchemaCompatibility.SchemaPairCompatibility;
@@ -75,21 +77,50 @@ public AvroSchema withReaderSchema(AvroSchema readerSchema)
7577
w = Schema.applyAliases(w, r);
7678

7779
// and then use Avro std lib to validate compatibility
80+
81+
// 16-Jun-2017, tatu: First, a very common case is for Record names not
82+
// to match; so let's check that first
83+
if (r.getType() == w.getType()) {
84+
if (!_schemaNamesEqual(w, r)) {
85+
throw new JsonMappingException(null, String.format(
86+
"Incompatible writer/reader schemas: root %ss have different names (\"%s\" vs \"%s\"), no match via aliases",
87+
r.getType().getName(), w.getFullName(), r.getFullName()));
88+
}
89+
}
90+
7891
SchemaPairCompatibility comp;
7992
try {
8093
comp = SchemaCompatibility.checkReaderWriterCompatibility(r, w);
8194
} catch (Exception e) {
8295
throw new JsonMappingException(null, String.format(
83-
"Failed to resolve given reader/writer schemas, problem: (%s) %s",
96+
"Failed to resolve given writer/reader schemas, problem: (%s) %s",
8497
e.getClass().getName(), e.getMessage()));
8598
}
8699
if (comp.getType() != SchemaCompatibilityType.COMPATIBLE) {
87-
throw new JsonMappingException(null, String.format("Incompatible reader/writer schema: %s",
100+
throw new JsonMappingException(null, String.format("Incompatible writer/reader schemas: %s",
88101
comp.getDescription()));
89102
}
90103
return Resolving.create(w, r);
91104
}
92105

106+
private boolean _schemaNamesEqual(Schema w, Schema r)
107+
{
108+
final String wname = w.getFullName();
109+
final String rname = r.getFullName();
110+
111+
if ((wname == rname) ||
112+
((wname != null) && wname.equals(rname))) {
113+
return true;
114+
}
115+
116+
// but may also have alias. NOTE! Avro lib itself does this, and we rely
117+
// on it, but basically only `NamedSchema` do NOT throw exception. But
118+
// we have no way of checking -- need to trust other cases bail out before
119+
// this (which they do). Unclean but... that's avrolib for you.
120+
Set<String> aliases = r.getAliases();
121+
return aliases.contains(wname);
122+
}
123+
93124
/**
94125
* Similar to {@link #withReaderSchema} but will NOT verify compatibility of schemas:
95126
* this means that certain problems (such as missing default value for a newly added

avro/src/test/java/com/fasterxml/jackson/dataformat/avro/schemaev/SimpleEvolutionTest.java

+32-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class SimpleEvolutionTest extends AvroTestBase
2020
" { 'name':'x', 'type':'int' }\n"+
2121
" ]\n"+
2222
"}\n");
23-
23+
2424
static String SCHEMA_XY_JSON = aposToQuotes("{\n"+
2525
" 'type':'record',\n"+
2626
" 'name':'EvolvingPoint',\n"+
@@ -40,6 +40,16 @@ public class SimpleEvolutionTest extends AvroTestBase
4040
" ]\n"+
4141
"}\n");
4242

43+
static String SCHEMA_XYZ_RENAMED_JSON = aposToQuotes("{\n"+
44+
" 'type':'record',\n"+
45+
" 'name':'EvolvingPoint2',\n"+
46+
" 'fields':[\n"+
47+
" { 'name':'z', 'type':'int', 'default': 99 },\n"+
48+
" { 'name':'y', 'type':'int' },\n"+
49+
" { 'name':'x', 'type':'int' }\n"+
50+
" ]\n"+
51+
"}\n");
52+
4353
static class PointXY {
4454
public int x, y;
4555

@@ -174,8 +184,9 @@ public void testFailNewFieldNoDefault() throws Exception
174184
srcSchema.withReaderSchema(dstSchema);
175185
fail("Should not pass");
176186
} catch (JsonMappingException e) {
177-
// 06-Feb-2016, tatu: Extremely lame error message by avro lib. Should consider
187+
// 06-Feb-2017, tatu: Extremely lame error message by avro lib. Should consider
178188
// rewriting to give some indication of issues...
189+
verifyException(e, "Incompatible writer/reader schemas");
179190
verifyException(e, "Data encoded using writer schema");
180191
verifyException(e, "will or may fail to decode using reader schema");
181192
}
@@ -184,4 +195,23 @@ public void testFailNewFieldNoDefault() throws Exception
184195
AvroSchema risky = srcSchema.withUnsafeReaderSchema(dstSchema);
185196
assertNotNull(risky);
186197
}
198+
199+
public void testFailNameChangeNoAlias() throws Exception
200+
{
201+
final AvroSchema srcSchema = MAPPER.schemaFrom(SCHEMA_XYZ_JSON);
202+
final AvroSchema dstSchema = MAPPER.schemaFrom(SCHEMA_XYZ_RENAMED_JSON);
203+
try {
204+
srcSchema.withReaderSchema(dstSchema);
205+
fail("Should not pass");
206+
} catch (JsonMappingException e) {
207+
// 16-Jun-2017, tatu: As is, names must match, so...
208+
verifyException(e, "Incompatible writer/reader schemas");
209+
verifyException(e, "root records have different names");
210+
verifyException(e, "\"EvolvingPoint\"");
211+
verifyException(e, "\"EvolvingPoint2\"");
212+
}
213+
// However... should be possible with unsafe alternative
214+
AvroSchema risky = srcSchema.withUnsafeReaderSchema(dstSchema);
215+
assertNotNull(risky);
216+
}
187217
}

0 commit comments

Comments
 (0)