Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

codegen: Getters for collection fields no longer return unmodifiable collection #526

Merged
merged 14 commits into from
Feb 20, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"name": "str",
"type": "string"
},
{
"name": "intAr",
"type": {
"type": "array",
"items": "int"
}
},
{
"name": "strAr",
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1834,7 +1834,7 @@ public void testNewBuilder() throws Exception {
public static Object[][] testPrivateModifierOnChunkMethodProvider() {
return new Object[][]{{vs14.ThousandField.class}, {vs19.ThousandField.class}};
}

@Test(dataProvider = "testPrivateModifierOnChunkMethodProvider")
public <T extends IndexedRecord> void testPrivateModifierOnChunkMethod(Class<T> clazz) {

Expand All @@ -1854,6 +1854,33 @@ public <T extends IndexedRecord> void testPrivateModifierOnChunkMethod(Class<T>
chunkMethodNames.forEach(method -> Assert.assertTrue(Modifier.isPrivate(method.getModifiers())));
}

@Test
public void modifiablePrimitiveCollectionTest() {
String tba = "NewElement";
RandomRecordGenerator generator = new RandomRecordGenerator();
TestCollections instance = generator.randomSpecific(TestCollections.class, RecordGenerationConfig.newConfig().withAvoidNulls(true));

// array of string
instance.getStrAr().add(tba);
Assert.assertTrue(instance.getStrAr().contains(tba));

// union[null, List<String>]
instance.getUnionOfArray().add(tba);
Assert.assertTrue(instance.getUnionOfArray().contains(tba));

// array (union[null, string])
instance.getArOfUnionOfStr().add(tba);
Assert.assertTrue(instance.getArOfUnionOfStr().contains(tba));


// Union (null, Map<String, String>)
instance.getUnionOfMap().put("key1", tba);
Assert.assertEquals(tba, instance.getUnionOfMap().get("key1"));

instance.getIntAr().add(Integer.MAX_VALUE);
Assert.assertEquals((int) instance.getIntAr().get(instance.getIntAr().size() - 1), Integer.MAX_VALUE);
}

@BeforeClass
public void setup() {
System.setProperty("org.apache.avro.specific.use_custom_coders", "true");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1701,12 +1701,14 @@ private void addGetByIndexMethod(TypeSpec.Builder classBuilder, AvroRecordSchema
switchBuilder.addStatement("case $L: return com.linkedin.avroutil1.compatibility.StringConverterUtil.get$L(this.$L)", fieldIndex++, fieldClass.getSimpleName(), escapedFieldName);
} else if (SpecificRecordGeneratorUtil.isListTransformerApplicableForSchema(field.getSchema())) {
switchBuilder.addStatement(
"case $L: return com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.get$LList(this.$L)",
fieldIndex++, config.getDefaultMethodStringRepresentation().getJsonValue(), escapedFieldName);
"case $L: return com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.get$LList(this.$L, $L)",
fieldIndex++, config.getDefaultMethodStringRepresentation().getJsonValue(), escapedFieldName,
SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()));
} else if (SpecificRecordGeneratorUtil.isMapTransformerApplicable(field.getSchema())) {
switchBuilder.addStatement(
"case $L: return com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.get$LMap(this.$L)",
fieldIndex++, config.getDefaultMethodStringRepresentation().getJsonValue(), escapedFieldName);
"case $L: return com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.get$LMap(this.$L, $L)",
fieldIndex++, config.getDefaultMethodStringRepresentation().getJsonValue(), escapedFieldName,
SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()));
} else if (field.getSchema() != null && AvroType.UNION.equals(field.getSchema().type())) {

switchBuilder.addStatement("case $L:", fieldIndex++);
Expand All @@ -1726,16 +1728,16 @@ private void addGetByIndexMethod(TypeSpec.Builder classBuilder, AvroRecordSchema
unionMemberSchema.getSchema())) {
switchBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, List.class)
.addStatement(
"return com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.get$1LList($2L)",
config.getDefaultMethodStringRepresentation().getJsonValue(),
escapedFieldName)
"return com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.get$1LList($2L, $3L)",
config.getDefaultMethodStringRepresentation().getJsonValue(), escapedFieldName,
SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()))
.endControlFlow();
} else if (SpecificRecordGeneratorUtil.isMapTransformerApplicable(unionMemberSchema.getSchema())) {
switchBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, Map.class)
.addStatement(
"return com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.get$1LMap($2L)",
config.getDefaultMethodStringRepresentation().getJsonValue(),
escapedFieldName)
"return com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.get$1LMap($2L, $3L)",
config.getDefaultMethodStringRepresentation().getJsonValue(), escapedFieldName,
SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()))
.endControlFlow();
}
}
Expand Down Expand Up @@ -1920,25 +1922,25 @@ private MethodSpec getSetterMethodSpec(AvroSchemaField field, SpecificRecordGene
methodSpecBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, List.class);
if (config.getDefaultFieldStringRepresentation().equals(AvroJavaStringRepresentation.STRING)) {
methodSpecBuilder.addStatement(
"this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.getStringList($1L)",
escapedFieldName);
"this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.getStringList($1L, $2L)",
escapedFieldName, SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()));
} else {
methodSpecBuilder.addStatement(
"this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.getUtf8List($1L)",
escapedFieldName);
"this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.getUtf8List($1L, $2L)",
escapedFieldName, SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()));
}
methodSpecBuilder.endControlFlow();

} else if (SpecificRecordGeneratorUtil.isMapTransformerApplicable(unionMemberSchema.getSchema())) {
methodSpecBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, Map.class);
if (config.getDefaultFieldStringRepresentation().equals(AvroJavaStringRepresentation.STRING)) {
methodSpecBuilder.addStatement(
"this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.getStringMap($1L)",
escapedFieldName);
"this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.getStringMap($1L, $2L)",
escapedFieldName, SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()));
} else {
methodSpecBuilder.addStatement(
"this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.getUtf8Map($1L)",
escapedFieldName);
"this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.getUtf8Map($1L, $2L)",
escapedFieldName, SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()));
}
methodSpecBuilder.endControlFlow();
}
Expand Down Expand Up @@ -1983,14 +1985,14 @@ private MethodSpec getGetterMethodSpec(AvroSchemaField field, SpecificRecordGene
escapedFieldName, config.getDefaultMethodStringRepresentation().getJsonValue());
} else if (SpecificRecordGeneratorUtil.isListTransformerApplicableForSchema(field.getSchema())) {
methodSpecBuilder.addStatement(
"return com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.get$LList(this.$L)",
config.getDefaultMethodStringRepresentation().getJsonValue(),
escapedFieldName);
"return com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.get$LList(this.$L, $L)",
config.getDefaultMethodStringRepresentation().getJsonValue(), escapedFieldName,
SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()));
} else if (SpecificRecordGeneratorUtil.isMapTransformerApplicable(field.getSchema())) {
methodSpecBuilder.addStatement(
"return com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.get$LMap(this.$L)",
"return com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.get$LMap(this.$L, $L)",
config.getDefaultMethodStringRepresentation().getJsonValue(),
escapedFieldName);
escapedFieldName, SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()));
} else if (field.getSchema() != null && AvroType.UNION.equals(field.getSchema().type())) {

methodSpecBuilder.beginControlFlow("if (this.$1L == null)", escapedFieldName)
Expand All @@ -2007,14 +2009,16 @@ private MethodSpec getGetterMethodSpec(AvroSchemaField field, SpecificRecordGene
} else if (SpecificRecordGeneratorUtil.isListTransformerApplicableForSchema(unionMemberSchema.getSchema())) {
methodSpecBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, List.class)
.addStatement(
"return com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.get$2LList($1L)",
escapedFieldName, config.getDefaultMethodStringRepresentation().getJsonValue())
"return com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.get$2LList($1L, $3L)",
escapedFieldName, config.getDefaultMethodStringRepresentation().getJsonValue(),
SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()))
.endControlFlow();
} else if (SpecificRecordGeneratorUtil.isMapTransformerApplicable(unionMemberSchema.getSchema())) {
methodSpecBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, Map.class)
.addStatement(
"return com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.get$2LMap($1L)",
escapedFieldName, config.getDefaultMethodStringRepresentation().getJsonValue())
"return com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.get$2LMap($1L, $3L)",
escapedFieldName, config.getDefaultMethodStringRepresentation().getJsonValue(),
SpecificRecordGeneratorUtil.isCollectionSchemaValuePrimitive(field.getSchema()))
.endControlFlow();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,54 @@ public static boolean isMapTransformerApplicable(AvroSchema schema) {
return isNullUnionOf(AvroType.MAP, schema);
}

/**
* If a schema is primitive type or a null union of primitive type, it can be handled as a primitive type
* @param schema schema to be validated
* @return true if schema can be handled as primitive, else false
*/
private static boolean canBeHandledAsPrimitiveType(AvroSchema schema) {
if (schema instanceof AvroUnionSchema) {
if (((AvroUnionSchema) schema).getTypes().size() != 2) {
li-ukumar marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

return ((AvroUnionSchema) schema).getTypes().get(0).getSchema().type().equals(AvroType.NULL)
&& ((AvroUnionSchema) schema).getTypes().get(1).getSchema().type().isPrimitive()
|| ((AvroUnionSchema) schema).getTypes().get(1).getSchema().type().equals(AvroType.NULL)
&& ((AvroUnionSchema) schema).getTypes().get(0).getSchema().type().isPrimitive();
Comment on lines +263 to +266
Copy link
Collaborator

@dg-builder dg-builder Feb 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the order of operations is OK here, but better to make it clearer IMO https://stackoverflow.com/a/2263692

Suggested change
return ((AvroUnionSchema) schema).getTypes().get(0).getSchema().type().equals(AvroType.NULL)
&& ((AvroUnionSchema) schema).getTypes().get(1).getSchema().type().isPrimitive()
|| ((AvroUnionSchema) schema).getTypes().get(1).getSchema().type().equals(AvroType.NULL)
&& ((AvroUnionSchema) schema).getTypes().get(0).getSchema().type().isPrimitive();
bool isUnionOfNullAndPrimitive = ((AvroUnionSchema) schema).getTypes().get(0).getSchema().type().equals(AvroType.NULL)
&& ((AvroUnionSchema) schema).getTypes().get(1).getSchema().type().isPrimitive();
bool isUnionOfPrimitiveAndNull = ((AvroUnionSchema) schema).getTypes().get(1).getSchema().type().equals(AvroType.NULL)
&& ((AvroUnionSchema) schema).getTypes().get(0).getSchema().type().isPrimitive();
return isUnionOfNullAndPrimitive || isUnionOfPrimitiveAndNull;

}

return schema.type().isPrimitive();
}

/**
* schema type must be either a List of Map (or a null union of List/Map)
* @return true if schema value is primitive
*/
public static boolean isCollectionSchemaValuePrimitive(AvroSchema schema) {
if(!isListTransformerApplicableForSchema(schema) && !isMapTransformerApplicable(schema)) {
return false;
}
if (schema.type().equals(AvroType.UNION)) {
if (((AvroUnionSchema) schema).getTypes().get(0).getSchema().type().equals(AvroType.NULL)) {
schema = ((AvroUnionSchema) schema).getTypes().get(1).getSchema();
} else if (((AvroUnionSchema) schema).getTypes().get(1).getSchema().type().equals(AvroType.NULL)) {
schema = ((AvroUnionSchema) schema).getTypes().get(0).getSchema();
} else {
// checks with isListTransformerApplicable and isMapTransformerApplicable should prevent this from happening
throw new IllegalArgumentException("schema type must be either a List of Map (or a null union of List/Map)");
}
}

if(AvroType.ARRAY.equals(schema.type())) {
return canBeHandledAsPrimitiveType(((AvroArraySchema) schema).getValueSchema());
} else if (AvroType.MAP.equals(schema.type())) {
return canBeHandledAsPrimitiveType(((AvroMapSchema) schema).getValueSchema());
} else {
// checks with isListTransformerApplicable and isMapTransformerApplicable should prevent this from happening
throw new IllegalArgumentException("schema type must be either a List of Map (or a null union of List/Map)");
}
}

public static boolean schemaContainsString(AvroSchema schema) {
if (schema == null) {
Expand Down
Loading
Loading