用于将字段从基元演变为联合的Avro模式解析

时间:2012-08-21 19:32:14

标签: java avro

我正在使用其Java's generic representation API使用Avro 1.7.0,我在处理当前的架构演变案例时遇到了问题。我们在这里讨论的场景是通过将字段更改为null和原始类型的并集来使原始类型字段可选。

我将使用一个简单的例子。基本上,我们的模式是:

  • 初始:具有int
  • 类型的一个字段的记录
  • 第二个版本:相同的记录,相同的字段名称,但类型现在是nullint的联合

根据Avro规范的schema resolution chapter,此类案件的解决方案应为:

  

如果读者是工会,但作家不是
  读者联合中与编写者模式匹配的第一个模式以递归方式解决。如果不匹配,则发出错误信号。

我的解释是我们应该正确解析使用初始模式序列化的数据,因为int是读者模式中联合的一部分。

但是,当运行使用版本2读回使用版本1序列化的记录的测试时,我得到了

org.apache.avro.AvroTypeException: Attempt to process a int when a union was expected.

这是一个测试,显示了这一点:

@Test
public void testReadingUnionFromValueWrittenAsPrimitive() throws Exception {
    Schema writerSchema = new Schema.Parser().parse("{\n" +
            "    \"type\":\"record\",\n" +
            "    \"name\":\"NeighborComparisons\",\n" +
            "    \"fields\": [\n" +
            "      {\"name\": \"test\",\n" +
            "      \"type\": \"int\" }]} ");

    Schema readersSchema = new Schema.Parser().parse(" {\n" +
            "    \"type\":\"record\",\n" +
            "    \"name\":\"NeighborComparisons\",\n" +
            "    \"fields\": [ {\n" +
            "      \"name\": \"test\",\n" +
            "      \"type\": [\"null\", \"int\"],\n" +
            "      \"default\": null } ]  }");

    // Writing a record using the initial schema with the 
    // test field defined as an int
    GenericData.Record record = new GenericData.Record(writerSchema);
    record.put("test", Integer.valueOf(10));        
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    JsonEncoder jsonEncoder = EncoderFactory.get().
       jsonEncoder(writerSchema, output);
    GenericDatumWriter<GenericData.Record> writer = new 
       GenericDatumWriter<GenericData.Record>(writerSchema);
    writer.write(record, jsonEncoder);
    jsonEncoder.flush();
    output.flush();

    System.out.println(output.toString());

    // We try reading it back using the second schema 
    // version where the test field is defined as a union of null and int
    JsonDecoder jsonDecoder = DecoderFactory.get().
        jsonDecoder(readersSchema, output.toString());
    GenericDatumReader<GenericData.Record> reader =
            new GenericDatumReader<GenericData.Record>(writerSchema, 
                readersSchema);
    GenericData.Record read = reader.read(null, jsonDecoder);

    // We should be able to assert that the value is 10 but it
    // fails on reading the record before getting here
    assertEquals(10, read.get("test"));
}

我想知道我的期望是否正确(这应该成功解决?)或者我没有正确使用avro处理这种情况。

1 个答案:

答案 0 :(得分:4)

将原始模式迁移到null和基元的并集的期望是正确的。

上面代码的问题在于如何创建解码器。解码器需要编写器的架构而不是读者的架构。

而不是这样做:

JsonDecoder jsonDecoder = DecoderFactory.get().
    jsonDecoder(readersSchema, output.toString());

应该是这样的:

JsonDecoder jsonDecoder = DecoderFactory.get().
    jsonDecoder(writerSchema, output.toString());

有关avro的用户邮件列表上的答案可以归功于Doug Cutting: http://mail-archives.apache.org/mod_mbox/avro-user/201208.mbox/browser