@@ -270,9 +270,15 @@ void parameterizedUdtAllParamKindsRoundTrip() {
270270 assertRoundTrip (literal );
271271 }
272272
273+ /**
274+ * Test field-level nullability: struct is non-nullable, but fields are nullable.
275+ *
276+ * <p>Note: The YAML schema declares point fields as non-nullable (i32), but we're using nullable
277+ * fields here. This works because we don't currently validate against the schema. See
278+ * https://github.com/substrait-io/substrait-java/issues/614
279+ */
273280 @ Test
274281 void nullableFieldsInStructUdtRoundTrip () {
275- // Test field-level nullability: struct is non-nullable, but fields are nullable
276282 Expression .UserDefinedStructLiteral literal =
277283 ExpressionCreator .userDefinedLiteralStruct (
278284 false ,
@@ -286,10 +292,16 @@ void nullableFieldsInStructUdtRoundTrip() {
286292 assertRoundTrip (literal );
287293 }
288294
295+ /**
296+ * Test mixed field nullability: struct is non-nullable, first field nullable, second
297+ * non-nullable.
298+ *
299+ * <p>Note: The YAML schema declares point fields as non-nullable (i32), but we're using a
300+ * nullable field here. This works because we don't currently validate against the schema. See
301+ * https://github.com/substrait-io/substrait-java/issues/614
302+ */
289303 @ Test
290304 void mixedFieldNullabilityInStructUdtRoundTrip () {
291- // Test mixed field nullability: struct is non-nullable, first field nullable, second
292- // non-nullable
293305 Expression .UserDefinedStructLiteral literal =
294306 ExpressionCreator .userDefinedLiteralStruct (
295307 false ,
@@ -303,9 +315,9 @@ void mixedFieldNullabilityInStructUdtRoundTrip() {
303315 assertRoundTrip (literal );
304316 }
305317
318+ /** Test struct-level nullability: struct is nullable, fields are non-nullable. */
306319 @ Test
307320 void nullableStructEncodedUdtRoundTrip () {
308- // Test struct-level nullability: struct is nullable, fields are non-nullable
309321 Expression .UserDefinedStructLiteral literal =
310322 ExpressionCreator .userDefinedLiteralStruct (
311323 true ,
@@ -319,9 +331,15 @@ void nullableStructEncodedUdtRoundTrip() {
319331 assertRoundTrip (literal );
320332 }
321333
334+ /**
335+ * Test the critical case: nullable struct with mixed field nullability.
336+ *
337+ * <p>Note: The YAML schema declares point fields as non-nullable (i32), but we're using a
338+ * nullable field here. This works because we don't currently validate against the schema. See
339+ * https://github.com/substrait-io/substrait-java/issues/614
340+ */
322341 @ Test
323342 void nullableStructWithMixedFieldNullabilityRoundTrip () {
324- // Test the critical case: nullable struct with mixed field nullability
325343 Expression .UserDefinedStructLiteral literal =
326344 ExpressionCreator .userDefinedLiteralStruct (
327345 true ,
@@ -396,4 +414,50 @@ void listAndMapFieldsInStructUdtRoundTrip() {
396414
397415 assertRoundTrip (literal );
398416 }
417+
418+ /**
419+ * Test with an actual null value (NullLiteral) in a struct field.
420+ *
421+ * <p>Note: The YAML schema declares point fields as non-nullable (i32), but we're using nullable
422+ * fields here. This works because we don't currently validate against the schema. When
423+ * https://github.com/substrait-io/substrait-java/issues/614 is implemented, this test will need
424+ * to be updated to use a UDT with nullable fields in its schema.
425+ */
426+ @ Test
427+ void nullValueInStructUdtFieldRoundTrip () {
428+ Expression .UserDefinedStructLiteral literal =
429+ ExpressionCreator .userDefinedLiteralStruct (
430+ false ,
431+ NESTED_TYPES_URN ,
432+ "point" ,
433+ Collections .emptyList (),
434+ Arrays .asList (
435+ ExpressionCreator .typedNull (Type .I32 .builder ().nullable (true ).build ()),
436+ ExpressionCreator .i32 (true , 100 )));
437+
438+ assertRoundTrip (literal );
439+ }
440+
441+ /**
442+ * Test with a mix of null and non-null values in struct fields.
443+ *
444+ * <p>Note: The YAML schema declares point fields as non-nullable (i32), but we're using nullable
445+ * fields here. This works because we don't currently validate against the schema. When
446+ * https://github.com/substrait-io/substrait-java/issues/614 is implemented, this test will need
447+ * to be updated to use a UDT with nullable fields in its schema.
448+ */
449+ @ Test
450+ void mixedNullAndNonNullValuesInStructUdtRoundTrip () {
451+ Expression .UserDefinedStructLiteral literal =
452+ ExpressionCreator .userDefinedLiteralStruct (
453+ false ,
454+ NESTED_TYPES_URN ,
455+ "point" ,
456+ Collections .emptyList (),
457+ Arrays .asList (
458+ ExpressionCreator .i32 (true , 42 ),
459+ ExpressionCreator .typedNull (Type .I32 .builder ().nullable (true ).build ())));
460+
461+ assertRoundTrip (literal );
462+ }
399463}
0 commit comments