This is an automated email from the ASF dual-hosted git repository.
clesaec 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 c3b31f6cc AVRO-530: Allow recursive types in protocol (#1768)
c3b31f6cc is described below
commit c3b31f6ccff4bffd96af1543164412beb900c92b
Author: Christophe Le Saec <[email protected]>
AuthorDate: Tue Sep 12 08:33:48 2023 +0200
AVRO-530: Allow recursive types in protocol (#1768)
* AVRO-530 recursives types in protocol
---
.../src/main/java/org/apache/avro/Protocol.java | 12 +++-
.../avro/src/main/java/org/apache/avro/Schema.java | 2 +-
.../test/java/org/apache/avro/TestProtocol.java | 75 +++++++++++++++++++++-
.../java/org/apache/avro/reflect/TestReflect.java | 3 +-
4 files changed, 86 insertions(+), 6 deletions(-)
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Protocol.java
b/lang/java/avro/src/main/java/org/apache/avro/Protocol.java
index e01a3c73e..f99df533b 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Protocol.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Protocol.java
@@ -174,7 +174,6 @@ public class Protocol extends JsonProperties {
public String getDoc() {
return doc;
}
-
}
private class TwoWayMessage extends Message {
@@ -466,7 +465,9 @@ public class Protocol extends JsonProperties {
/** Read a protocol from a Json file. */
public static Protocol parse(File file) throws IOException {
- return parse(Schema.FACTORY.createParser(file));
+ try (JsonParser jsonParser = Schema.FACTORY.createParser(file)) {
+ return parse(jsonParser);
+ }
}
/** Read a protocol from a Json stream. */
@@ -537,10 +538,15 @@ public class Protocol extends JsonProperties {
return; // no types defined
if (!defs.isArray())
throw new SchemaParseException("Types not an array: " + defs);
+
for (JsonNode type : defs) {
if (!type.isObject())
throw new SchemaParseException("Type not an object: " + type);
- Schema.parse(type, types);
+ Schema.parseNamesDeclared(type, types, types.space());
+
+ }
+ for (JsonNode type : defs) {
+ Schema.parseCompleteSchema(type, types, types.space());
}
}
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Schema.java
b/lang/java/avro/src/main/java/org/apache/avro/Schema.java
index db349872d..cec128ba6 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Schema.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Schema.java
@@ -1865,7 +1865,7 @@ public abstract class Schema extends JsonProperties
implements Serializable {
* @param currentNameSpace : current working name space.
* @return schema.
*/
- private static Schema parseNamesDeclared(JsonNode schema, Names names,
String currentNameSpace) {
+ static Schema parseNamesDeclared(JsonNode schema, Names names, String
currentNameSpace) {
if (schema == null) {
return null;
}
diff --git a/lang/java/avro/src/test/java/org/apache/avro/TestProtocol.java
b/lang/java/avro/src/test/java/org/apache/avro/TestProtocol.java
index 3eadbb32d..f7859e1c8 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/TestProtocol.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/TestProtocol.java
@@ -17,13 +17,86 @@
*/
package org.apache.avro;
-import org.junit.jupiter.api.Test;
+import org.apache.avro.generic.GenericData;
+import org.apache.avro.generic.GenericDatumWriter;
+import org.apache.avro.generic.IndexedRecord;
+import org.apache.avro.io.EncoderFactory;
+import org.apache.avro.io.JsonEncoder;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.junit.jupiter.api.Assertions.*;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
public class TestProtocol {
+ @Test
+ public void parse() throws IOException {
+ File fic = new File("../../../share/test/schemas/namespace.avpr");
+ Protocol protocol = Protocol.parse(fic);
+ assertNotNull(protocol);
+ assertEquals("TestNamespace", protocol.getName());
+ }
+
+ /**
+ * record type 'User' contains a field of type 'Status', which contains a
field
+ * of type 'User'.
+ */
+ @Test
+ public void crossProtocol() {
+ String userStatus = "{ \"protocol\" : \"p1\", " + "\"types\": ["
+ + "{\"name\": \"User\", \"type\": \"record\", \"fields\": [{\"name\":
\"current_status\", \"type\": \"Status\"}]},\n"
+ + "\n"
+ + "{\"name\": \"Status\", \"type\": \"record\", \"fields\":
[{\"name\": \"author\", \"type\": \"User\"}]}"
+ + "]}";
+
+ Protocol protocol = Protocol.parse(userStatus);
+ Schema userSchema = protocol.getType("User");
+ Schema statusSchema = protocol.getType("Status");
+ assertSame(statusSchema, userSchema.getField("current_status").schema());
+ assertSame(userSchema, statusSchema.getField("author").schema());
+
+ String parsingFormUser = SchemaNormalization.toParsingForm(userSchema);
+ assertEquals(
+
"{\"name\":\"User\",\"type\":\"record\",\"fields\":[{\"name\":\"current_status\",\"type\":{\"name\":\"Status\",\"type\":\"record\",\"fields\":[{\"name\":\"author\",\"type\":\"User\"}]}}]}",
+ parsingFormUser);
+
+ String parsingFormStatus = SchemaNormalization.toParsingForm(statusSchema);
+ assertEquals(
+
"{\"name\":\"Status\",\"type\":\"record\",\"fields\":[{\"name\":\"author\",\"type\":{\"name\":\"User\",\"type\":\"record\",\"fields\":[{\"name\":\"current_status\",\"type\":\"Status\"}]}}]}",
+ parsingFormStatus);
+ }
+
+ /**
+ * When one schema with a type used before it is defined, test normalization
+ * defined schema before it is used.
+ */
+ @Test
+ void normalization() {
+ final String schema = "{\n" + " \"type\":\"record\", \"name\": \"Main\",
" + " \"fields\":[\n"
+ + " { \"name\":\"f1\", \"type\":\"Sub\" },\n" // use Sub
+ + " { \"name\":\"f2\", " + " \"type\":{\n" + "
\"type\":\"enum\", \"name\":\"Sub\",\n" // define
+
// Sub
+ + " \"symbols\":[\"OPEN\",\"CLOSE\"]\n" + " }\n" + "
}\n" + " ]\n" + "}";
+ Schema s = new Schema.Parser().parse(schema);
+ assertNotNull(s);
+
+ String parsingForm = SchemaNormalization.toParsingForm(s);
+ assertEquals(
+
"{\"name\":\"Main\",\"type\":\"record\",\"fields\":[{\"name\":\"f1\",\"type\":{\"name\":\"Sub\",\"type\":\"enum\",\"symbols\":[\"OPEN\",\"CLOSE\"]}},{\"name\":\"f2\",\"type\":\"Sub\"}]}",
+ parsingForm);
+ }
+
@Test
void namespaceAndNameRules() {
Protocol p1 = new Protocol("P", null, "foo");
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 2915f96e6..470010e6f 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
@@ -977,7 +977,8 @@ public class TestReflect {
void forwardReference() {
ReflectData data = ReflectData.get();
Protocol reflected = data.getProtocol(C.class);
- Protocol reparsed = Protocol.parse(reflected.toString());
+ String ref = reflected.toString();
+ Protocol reparsed = Protocol.parse(ref);
assertEquals(reflected, reparsed);
assert (reparsed.getTypes().contains(data.getSchema(A.class)));
assert (reparsed.getTypes().contains(data.getSchema(B1.class)));