44import java .util .List ;
55import java .util .Map ;
66import java .util .Objects ;
7-
87import org .apache .avro .Schema ;
98import org .apache .calcite .plan .RelOptUtil ;
109import org .apache .calcite .rel .type .RelDataType ;
@@ -103,7 +102,7 @@ public void testAvroKeyPayloadSchemaNoKeyOptions() {
103102 assertNull (result .getKey ()); // Key schema should be null
104103 assertNotNull (result .getValue ()); // Payload schema should not be null
105104 assertEquals ("payloadSchema" , result .getValue ().getName ());
106- assertEquals ("namespace" , result .getValue ().getNamespace ());
105+ assertEquals ("namespace.payloadSchema " , result .getValue ().getNamespace ());
107106 assertEquals ("record" , result .getValue ().getType ().getName ());
108107 assertEquals (1 , result .getValue ().getFields ().size ());
109108 assertEquals ("field1" , result .getValue ().getFields ().get (0 ).name ());
@@ -138,14 +137,14 @@ public void testAvroKeyPayloadSchemaValidKeyOptions() {
138137
139138 assertNotNull (result .getKey ()); // Key schema should not be null
140139 assertEquals ("keySchema" , result .getKey ().getName ());
141- assertEquals ("namespace" , result .getKey ().getNamespace ());
140+ assertEquals ("namespace.keySchema " , result .getKey ().getNamespace ());
142141 assertEquals ("record" , result .getKey ().getType ().getName ());
143142 assertEquals (1 , result .getKey ().getFields ().size ());
144143 assertEquals ("field1" , result .getKey ().getFields ().get (0 ).name ()); // prefix should be stripped
145144 assertEquals ("string" , result .getKey ().getFields ().get (0 ).schema ().getType ().getName ());
146145 assertNotNull (result .getValue ()); // Payload schema should not be null
147146 assertEquals ("payloadSchema" , result .getValue ().getName ());
148- assertEquals ("namespace" , result .getValue ().getNamespace ());
147+ assertEquals ("namespace.payloadSchema " , result .getValue ().getNamespace ());
149148 assertEquals ("record" , result .getValue ().getType ().getName ());
150149 assertEquals (1 , result .getValue ().getFields ().size ());
151150 assertEquals ("field2" , result .getValue ().getFields ().get (0 ).name ());
@@ -168,7 +167,7 @@ public void testAvroKeyPayloadSchemaPrimitiveKey() {
168167 assertEquals ("int" , result .getKey ().getType ().getName ());
169168 assertNotNull (result .getValue ()); // Payload schema should not be null
170169 assertEquals ("payloadSchema" , result .getValue ().getName ());
171- assertEquals ("namespace" , result .getValue ().getNamespace ());
170+ assertEquals ("namespace.payloadSchema " , result .getValue ().getNamespace ());
172171 assertEquals ("record" , result .getValue ().getType ().getName ());
173172 assertEquals (1 , result .getValue ().getFields ().size ());
174173 assertEquals ("field1" , result .getValue ().getFields ().get (0 ).name ());
@@ -208,4 +207,55 @@ public void convertsNestedArray() {
208207 assertEquals ("field1" , structElementSchema .getFields ().get (0 ).name ());
209208 assertEquals ("field2" , structElementSchema .getFields ().get (1 ).name ());
210209 }
210+
211+ @ Test
212+ public void handlesNamespaceInNestedArrayAndMapElements () {
213+ RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl (RelDataTypeSystem .DEFAULT );
214+
215+ // Create a "location" record type that will be reused - this mimics the real scenario
216+ RelDataType locationType1 = typeFactory .createStructType (
217+ List .of (typeFactory .createSqlType (SqlTypeName .VARCHAR ), typeFactory .createSqlType (SqlTypeName .VARCHAR )),
218+ List .of ("countryCode" , "postalCode" ));
219+
220+ // Create another "location" record type with slightly different structure
221+ RelDataType locationType2 = typeFactory .createStructType (
222+ List .of (typeFactory .createSqlType (SqlTypeName .VARCHAR ), typeFactory .createSqlType (SqlTypeName .INTEGER )),
223+ List .of ("countryCode" , "regionCode" ));
224+
225+ // Create structures that use these location types in different contexts
226+ // This simulates the real scenario where multiple fields have the same name but different contexts
227+ RelDataType profileStruct = typeFactory .createStructType (
228+ List .of (locationType1 ),
229+ List .of ("location" ));
230+
231+ RelDataType positionStruct = typeFactory .createStructType (
232+ List .of (locationType2 ),
233+ List .of ("location" ));
234+
235+ // Put both in a map structure - this creates the collision scenario
236+ // Both will try to generate records named "location" with the same namespace
237+ RelDataType positionsMap = typeFactory .createMapType (
238+ typeFactory .createSqlType (SqlTypeName .VARCHAR ),
239+ positionStruct );
240+
241+ // Create the main record that contains both location types
242+ RelDataType mainRecord = typeFactory .createStructType (
243+ List .of (profileStruct , positionsMap ),
244+ List .of ("profile" , "positions" ));
245+
246+ // Schema creation should succeed
247+ Schema schema = AvroConverter .avro ("com.linkedin" , "MemberProfile" , mainRecord );
248+ assertNotNull (schema );
249+
250+ // Without the namespace-appending behavior in AvroConverter, this would fail with error "Can't redefine: com.linkedin.location"
251+ // The issue occurs because multiple records named "location" are created with the same namespace,
252+ // causing a collision when schema.toString(true) tries to serialize them
253+ String schemaJson = schema .toString (true );
254+ assertNotNull ("Schema toString(true) should succeed without 'Can't redefine' errors" , schemaJson );
255+
256+ // Verify the schema can be parsed back
257+ Schema .Parser parser = new Schema .Parser ();
258+ Schema reparsedSchema = parser .parse (schemaJson );
259+ assertNotNull ("Generated schema must be parseable" , reparsedSchema );
260+ }
211261}
0 commit comments