@@ -22,6 +22,7 @@ import (
2222 "fmt"
2323 "io"
2424 "math/rand"
25+ "os"
2526 "strconv"
2627 "strings"
2728 "testing"
@@ -688,3 +689,259 @@ func TestArrowBinaryIPCWriterTruncatedVOffsets(t *testing.T) {
688689
689690 require .False (t , reader .Next ())
690691}
692+
693+ func TestRecordBatchCustomMetadataRoundtrip (t * testing.T ) {
694+ mem := memory .NewGoAllocator ()
695+ schema := arrow .NewSchema (
696+ []arrow.Field {{Name : "x" , Type : arrow .PrimitiveTypes .Int32 }},
697+ nil ,
698+ )
699+
700+ bldr := array .NewInt32Builder (mem )
701+ defer bldr .Release ()
702+ bldr .AppendValues ([]int32 {1 , 2 , 3 }, nil )
703+ col := bldr .NewArray ()
704+ defer col .Release ()
705+
706+ meta := arrow .NewMetadata ([]string {"k1" , "k2" }, []string {"v1" , "v2" })
707+ rec := array .NewRecordBatchWithMetadata (schema , []arrow.Array {col }, 3 , meta )
708+ defer rec .Release ()
709+
710+ // Write to IPC stream
711+ var buf bytes.Buffer
712+ writer := ipc .NewWriter (& buf , ipc .WithSchema (schema ))
713+ require .NoError (t , writer .Write (rec ))
714+ require .NoError (t , writer .Close ())
715+
716+ // Read back
717+ reader , err := ipc .NewReader (bytes .NewReader (buf .Bytes ()))
718+ require .NoError (t , err )
719+ defer reader .Release ()
720+
721+ require .True (t , reader .Next ())
722+ got := reader .RecordBatch ()
723+
724+ rm , ok := got .(arrow.RecordBatchWithMetadata )
725+ require .True (t , ok , "record batch should implement RecordBatchWithMetadata" )
726+
727+ require .Equal (t , meta .Keys (), rm .Metadata ().Keys ())
728+ require .Equal (t , meta .Values (), rm .Metadata ().Values ())
729+ }
730+
731+ func TestRecordBatchCustomMetadataFileRoundtrip (t * testing.T ) {
732+ mem := memory .NewGoAllocator ()
733+ schema := arrow .NewSchema (
734+ []arrow.Field {{Name : "x" , Type : arrow .PrimitiveTypes .Int32 }},
735+ nil ,
736+ )
737+
738+ bldr := array .NewInt32Builder (mem )
739+ defer bldr .Release ()
740+ bldr .AppendValues ([]int32 {10 , 20 }, nil )
741+ col := bldr .NewArray ()
742+ defer col .Release ()
743+
744+ meta := arrow .NewMetadata ([]string {"file_key" }, []string {"file_value" })
745+ rec := array .NewRecordBatchWithMetadata (schema , []arrow.Array {col }, 2 , meta )
746+ defer rec .Release ()
747+
748+ // Write to IPC file
749+ var buf bytes.Buffer
750+ writer , err := ipc .NewFileWriter (& buf , ipc .WithSchema (schema ))
751+ require .NoError (t , err )
752+ require .NoError (t , writer .Write (rec ))
753+ require .NoError (t , writer .Close ())
754+
755+ // Read back
756+ reader , err := ipc .NewFileReader (bytes .NewReader (buf .Bytes ()))
757+ require .NoError (t , err )
758+ defer reader .Close ()
759+
760+ require .Equal (t , 1 , reader .NumRecords ())
761+ got , err := reader .RecordBatchAt (0 )
762+ require .NoError (t , err )
763+ defer got .Release ()
764+
765+ rm , ok := got .(arrow.RecordBatchWithMetadata )
766+ require .True (t , ok , "record batch should implement RecordBatchWithMetadata" )
767+
768+ require .Equal (t , meta .Keys (), rm .Metadata ().Keys ())
769+ require .Equal (t , meta .Values (), rm .Metadata ().Values ())
770+ }
771+
772+ func TestRecordBatchCustomMetadataInterop (t * testing.T ) {
773+ t .Run ("file" , func (t * testing.T ) {
774+ f , err := os .Open ("testdata/custom_metadata.arrow" )
775+ require .NoError (t , err )
776+ defer f .Close ()
777+
778+ reader , err := ipc .NewFileReader (f )
779+ require .NoError (t , err )
780+ defer reader .Close ()
781+
782+ // Verify schema metadata
783+ schemaMeta := reader .Schema ().Metadata ()
784+ idx := schemaMeta .FindKey ("schema_key" )
785+ require .GreaterOrEqual (t , idx , 0 )
786+ require .Equal (t , "schema_value" , schemaMeta .Values ()[idx ])
787+
788+ require .Equal (t , 2 , reader .NumRecords ())
789+
790+ // Batch 1
791+ rec0 , err := reader .RecordBatchAt (0 )
792+ require .NoError (t , err )
793+ defer rec0 .Release ()
794+ rm0 , ok := rec0 .(arrow.RecordBatchWithMetadata )
795+ require .True (t , ok )
796+ m0 := rm0 .Metadata ()
797+ require .Equal (t , "1" , m0 .Values ()[m0 .FindKey ("batch_num" )])
798+ require .Equal (t , "value1" , m0 .Values ()[m0 .FindKey ("key1" )])
799+
800+ // Batch 2
801+ rec1 , err := reader .RecordBatchAt (1 )
802+ require .NoError (t , err )
803+ defer rec1 .Release ()
804+ rm1 , ok := rec1 .(arrow.RecordBatchWithMetadata )
805+ require .True (t , ok )
806+ m1 := rm1 .Metadata ()
807+ require .Equal (t , "2" , m1 .Values ()[m1 .FindKey ("batch_num" )])
808+ require .Equal (t , "value2" , m1 .Values ()[m1 .FindKey ("key2" )])
809+ })
810+
811+ t .Run ("stream" , func (t * testing.T ) {
812+ data , err := os .ReadFile ("testdata/custom_metadata_stream.arrows" )
813+ require .NoError (t , err )
814+
815+ reader , err := ipc .NewReader (bytes .NewReader (data ))
816+ require .NoError (t , err )
817+ defer reader .Release ()
818+
819+ // Verify schema metadata
820+ schemaMeta := reader .Schema ().Metadata ()
821+ idx := schemaMeta .FindKey ("schema_key" )
822+ require .GreaterOrEqual (t , idx , 0 )
823+ require .Equal (t , "schema_value" , schemaMeta .Values ()[idx ])
824+
825+ // Batch 1
826+ require .True (t , reader .Next ())
827+ rec0 := reader .RecordBatch ()
828+ rm0 , ok := rec0 .(arrow.RecordBatchWithMetadata )
829+ require .True (t , ok )
830+ m0 := rm0 .Metadata ()
831+ require .Equal (t , "1" , m0 .Values ()[m0 .FindKey ("batch_num" )])
832+ require .Equal (t , "value1" , m0 .Values ()[m0 .FindKey ("key1" )])
833+
834+ // Batch 2
835+ require .True (t , reader .Next ())
836+ rec1 := reader .RecordBatch ()
837+ rm1 , ok := rec1 .(arrow.RecordBatchWithMetadata )
838+ require .True (t , ok )
839+ m1 := rm1 .Metadata ()
840+ require .Equal (t , "2" , m1 .Values ()[m1 .FindKey ("batch_num" )])
841+ require .Equal (t , "value2" , m1 .Values ()[m1 .FindKey ("key2" )])
842+
843+ require .False (t , reader .Next ())
844+ })
845+ }
846+
847+ func TestRecordBatchCustomMetadataSlice (t * testing.T ) {
848+ mem := memory .NewGoAllocator ()
849+ schema := arrow .NewSchema (
850+ []arrow.Field {{Name : "x" , Type : arrow .PrimitiveTypes .Int32 }},
851+ nil ,
852+ )
853+
854+ bldr := array .NewInt32Builder (mem )
855+ defer bldr .Release ()
856+ bldr .AppendValues ([]int32 {1 , 2 , 3 , 4 }, nil )
857+ col := bldr .NewArray ()
858+ defer col .Release ()
859+
860+ meta := arrow .NewMetadata ([]string {"slice_key" }, []string {"slice_value" })
861+ rec := array .NewRecordBatchWithMetadata (schema , []arrow.Array {col }, 4 , meta )
862+ defer rec .Release ()
863+
864+ sliced := rec .NewSlice (1 , 3 )
865+ defer sliced .Release ()
866+
867+ rm , ok := sliced .(arrow.RecordBatchWithMetadata )
868+ require .True (t , ok , "sliced record should implement RecordBatchWithMetadata" )
869+ require .Equal (t , meta .Keys (), rm .Metadata ().Keys ())
870+ require .Equal (t , meta .Values (), rm .Metadata ().Values ())
871+ require .EqualValues (t , 2 , sliced .NumRows ())
872+ }
873+
874+ func TestRecordBatchCustomMetadataSetColumn (t * testing.T ) {
875+ mem := memory .NewGoAllocator ()
876+ schema := arrow .NewSchema (
877+ []arrow.Field {
878+ {Name : "x" , Type : arrow .PrimitiveTypes .Int32 },
879+ {Name : "y" , Type : arrow .PrimitiveTypes .Int32 },
880+ },
881+ nil ,
882+ )
883+
884+ bldr := array .NewInt32Builder (mem )
885+ defer bldr .Release ()
886+ bldr .AppendValues ([]int32 {1 , 2 }, nil )
887+ col1 := bldr .NewArray ()
888+ defer col1 .Release ()
889+
890+ bldr .AppendValues ([]int32 {3 , 4 }, nil )
891+ col2 := bldr .NewArray ()
892+ defer col2 .Release ()
893+
894+ meta := arrow .NewMetadata ([]string {"set_key" }, []string {"set_value" })
895+ rec := array .NewRecordBatchWithMetadata (schema , []arrow.Array {col1 , col2 }, 2 , meta )
896+ defer rec .Release ()
897+
898+ bldr .AppendValues ([]int32 {5 , 6 }, nil )
899+ newCol := bldr .NewArray ()
900+ defer newCol .Release ()
901+
902+ updated , err := rec .SetColumn (0 , newCol )
903+ require .NoError (t , err )
904+ defer updated .Release ()
905+
906+ rm , ok := updated .(arrow.RecordBatchWithMetadata )
907+ require .True (t , ok , "updated record should implement RecordBatchWithMetadata" )
908+ require .Equal (t , meta .Keys (), rm .Metadata ().Keys ())
909+ require .Equal (t , meta .Values (), rm .Metadata ().Values ())
910+ }
911+
912+ func TestRecordBatchNoMetadataRoundtrip (t * testing.T ) {
913+ mem := memory .NewGoAllocator ()
914+ schema := arrow .NewSchema (
915+ []arrow.Field {{Name : "x" , Type : arrow .PrimitiveTypes .Int32 }},
916+ nil ,
917+ )
918+
919+ bldr := array .NewInt32Builder (mem )
920+ defer bldr .Release ()
921+ bldr .AppendValues ([]int32 {1 , 2 , 3 }, nil )
922+ col := bldr .NewArray ()
923+ defer col .Release ()
924+
925+ rec := array .NewRecordBatch (schema , []arrow.Array {col }, 3 )
926+ defer rec .Release ()
927+
928+ // Write to IPC stream
929+ var buf bytes.Buffer
930+ writer := ipc .NewWriter (& buf , ipc .WithSchema (schema ))
931+ require .NoError (t , writer .Write (rec ))
932+ require .NoError (t , writer .Close ())
933+
934+ // Read back
935+ reader , err := ipc .NewReader (bytes .NewReader (buf .Bytes ()))
936+ require .NoError (t , err )
937+ defer reader .Release ()
938+
939+ require .True (t , reader .Next ())
940+ got := reader .RecordBatch ()
941+
942+ rm , ok := got .(arrow.RecordBatchWithMetadata )
943+ require .True (t , ok , "record batch should implement RecordBatchWithMetadata" )
944+
945+ // Metadata should be empty, not nil
946+ require .Equal (t , 0 , rm .Metadata ().Len ())
947+ }
0 commit comments