This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 805290eb fix(arrow/csv): validate custom converter row counts (#1148)
805290eb is described below

commit 805290eb7bfd3b977004394f07d6bf52495e3d37
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 12 20:39:22 2026 +0200

    fix(arrow/csv): validate custom converter row counts (#1148)
    
    ## What
    
    The custom CSV writer converter is documented to return one string per
    array value, but the writer trusted the result. Short results silently
    produced empty cells and long results could panic while filling the
    record matrix. This returns arrow.ErrInvalid for either mismatch.
    
    ## Test
    
    - go test ./arrow/csv -run TestCustomTypeConverterValidatesRowCount
    -count=1
---
 arrow/csv/common.go      |  2 +-
 arrow/csv/transformer.go |  3 +++
 arrow/csv/writer_test.go | 24 ++++++++++++++++++++++++
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/arrow/csv/common.go b/arrow/csv/common.go
index 78283cd3..d84b05b5 100644
--- a/arrow/csv/common.go
+++ b/arrow/csv/common.go
@@ -239,7 +239,7 @@ func WithStringsReplacer(replacer *strings.Replacer) Option 
{
 
 // WithCustomTypeConverter allows specifying a custom type converter for the 
CSV writer.
 //
-// returns a slice of strings that must match the number of columns in the 
output csv.
+// The returned slice must contain one string for each value in col.
 // the second return value is a boolean that indicates if the conversion was 
handled.
 // if it is set to false, the library will attempt to use default conversion.
 //
diff --git a/arrow/csv/transformer.go b/arrow/csv/transformer.go
index 5bcf26e0..9321a490 100644
--- a/arrow/csv/transformer.go
+++ b/arrow/csv/transformer.go
@@ -31,6 +31,9 @@ func (w *Writer) transformColToStringArr(typ arrow.DataType, 
col arrow.Array, st
        if w.customTypeConverter != nil {
                result, handled := w.customTypeConverter(typ, col)
                if handled {
+                       if len(result) != col.Len() {
+                               return nil, fmt.Errorf("%w: custom type 
converter returned %d values for column with %d rows", arrow.ErrInvalid, 
len(result), col.Len())
+                       }
                        return result, nil
                }
        }
diff --git a/arrow/csv/writer_test.go b/arrow/csv/writer_test.go
index 66c664f8..17a09bcf 100644
--- a/arrow/csv/writer_test.go
+++ b/arrow/csv/writer_test.go
@@ -463,6 +463,30 @@ func TestCSVWriterPreservesDecimalScaleAndPrecision(t 
*testing.T) {
        assert.Equal(t, 
value128.ToString(type128.Scale)+","+value256.ToString(type256.Scale)+"\n", 
output.String())
 }
 
+func TestCustomTypeConverterValidatesRowCount(t *testing.T) {
+       mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+       defer mem.AssertSize(t, 0)
+
+       schema := arrow.NewSchema([]arrow.Field{{Name: "value", Type: 
arrow.PrimitiveTypes.Int32}}, nil)
+       builder := array.NewRecordBuilder(mem, schema)
+       builder.Field(0).(*array.Int32Builder).AppendValues([]int32{1, 2}, nil)
+       record := builder.NewRecordBatch()
+       builder.Release()
+       defer record.Release()
+
+       for name, result := range map[string][]string{
+               "too few":  {"one"},
+               "too many": {"one", "two", "three"},
+       } {
+               t.Run(name, func(t *testing.T) {
+                       writer := csv.NewWriter(io.Discard, schema, 
csv.WithCustomTypeConverter(func(arrow.DataType, arrow.Array) ([]string, bool) {
+                               return result, true
+                       }))
+                       require.ErrorIs(t, writer.Write(record), 
arrow.ErrInvalid)
+               })
+       }
+}
+
 // TestParquetTestingCSVWriter tests that the CSV writer successfully convert 
arrow/parquet-testing files to CSV
 func TestParquetTestingCSVWriter(t *testing.T) {
        dir := os.Getenv("PARQUET_TEST_DATA")

Reply via email to