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")