@@ -325,3 +325,104 @@ func TestBuildSchemaWithResolverNewColumnTypeMappingSeesMetadataCasing(t *testin
325325 assert .Equal (t , "long" , fields [0 ].Type .Type (),
326326 "mapping returns long only when name+path match metadata casing; any other casing means the rename path is broken" )
327327}
328+
329+ // TestBuildSchemaWithResolverUsesMetadataTypeForNilValue verifies that
330+ // table-creation type resolution still applies schema metadata when the
331+ // observed record value is nil. Without this, nil values would be skipped
332+ // before metadata lookup and columns would appear later only after a non-nil
333+ // value is seen.
334+ func TestBuildSchemaWithResolverUsesMetadataTypeForNilValue (t * testing.T ) {
335+ router := & Router {
336+ caseSensitive : true ,
337+ resolver : newTypeResolver ("schema_key" , nil , true , nil ),
338+ }
339+
340+ schemaMeta := schema.Common {
341+ Type : schema .Object ,
342+ Children : []schema.Common {
343+ {Name : "count" , Type : schema .Int32 },
344+ },
345+ }
346+
347+ msg := service .NewMessage (nil )
348+ msg .MetaSetMut ("schema_key" , schemaMeta .ToAny ())
349+
350+ record := map [string ]any {
351+ "count" : nil ,
352+ }
353+
354+ icebergSchema , err := router .buildSchemaWithResolver (record , msg , tableKey {namespace : "ns" , table : "t" })
355+ require .NoError (t , err )
356+
357+ fields := icebergSchema .Fields ()
358+ require .Len (t , fields , 1 )
359+ assert .Equal (t , "count" , fields [0 ].Name )
360+ assert .Equal (t , "int" , fields [0 ].Type .Type ())
361+ }
362+
363+ // TestBuildSchemaWithResolverUsesMetadataTypeForAbsentField verifies that a
364+ // field declared in schema metadata but entirely absent from the record (not
365+ // even present as nil) is still created as an Iceberg column using the metadata
366+ // type.
367+ func TestBuildSchemaWithResolverUsesMetadataTypeForAbsentField (t * testing.T ) {
368+ router := & Router {
369+ caseSensitive : true ,
370+ resolver : newTypeResolver ("schema_key" , nil , true , nil ),
371+ }
372+
373+ schemaMeta := schema.Common {
374+ Type : schema .Object ,
375+ Children : []schema.Common {
376+ {Name : "count" , Type : schema .Int32 },
377+ },
378+ }
379+
380+ msg := service .NewMessage (nil )
381+ msg .MetaSetMut ("schema_key" , schemaMeta .ToAny ())
382+
383+ record := map [string ]any {} // "count" is entirely absent
384+
385+ icebergSchema , err := router .buildSchemaWithResolver (record , msg , tableKey {namespace : "ns" , table : "t" })
386+ require .NoError (t , err )
387+
388+ fields := icebergSchema .Fields ()
389+ require .Len (t , fields , 1 )
390+ assert .Equal (t , "count" , fields [0 ].Name )
391+ assert .Equal (t , "int" , fields [0 ].Type .Type ())
392+ }
393+
394+ // TestBuildSchemaWithResolverMetadataOnlyFieldOrdering verifies that
395+ // metadata-only fields (absent from the record) appear first in the schema in
396+ // metadata declaration order, followed by record-only fields in sorted order.
397+ func TestBuildSchemaWithResolverMetadataOnlyFieldOrdering (t * testing.T ) {
398+ router := & Router {
399+ caseSensitive : true ,
400+ resolver : newTypeResolver ("schema_key" , nil , true , nil ),
401+ }
402+
403+ schemaMeta := schema.Common {
404+ Type : schema .Object ,
405+ Children : []schema.Common {
406+ {Name : "b" , Type : schema .Int32 },
407+ {Name : "a" , Type : schema .String },
408+ },
409+ }
410+
411+ msg := service .NewMessage (nil )
412+ msg .MetaSetMut ("schema_key" , schemaMeta .ToAny ())
413+
414+ // "extra" is in the record but not in metadata; "b" and "a" are in metadata
415+ // but absent from the record.
416+ record := map [string ]any {
417+ "extra" : "hello" ,
418+ }
419+
420+ icebergSchema , err := router .buildSchemaWithResolver (record , msg , tableKey {namespace : "ns" , table : "t" })
421+ require .NoError (t , err )
422+
423+ fields := icebergSchema .Fields ()
424+ require .Len (t , fields , 3 )
425+ assert .Equal (t , "b" , fields [0 ].Name ) // metadata order first
426+ assert .Equal (t , "a" , fields [1 ].Name )
427+ assert .Equal (t , "extra" , fields [2 ].Name ) // record-only field last
428+ }
0 commit comments