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 5fb2a200 fix(parquet/variant): return "" for an empty long-form string
(#1353)
5fb2a200 is described below
commit 5fb2a2008bd0498ee3adcb22fbaa06b3dbf8179a
Author: Animesh Kumar <[email protected]>
AuthorDate: Tue Oct 6 19:52:54 2026 +0530
fix(parquet/variant): return "" for an empty long-form string (#1353)
### Rationale for this change
Fixes #1344. `Value.Value()` built a long-form string with
`unsafe.String(&v.value[5], sz)`. For an empty string the encoded value
is only the 5-byte header and length, so `&v.value[5]` was out of range
and it panicked. `String()` and `MarshalJSON()` go through `Value()` and
panicked the same way. `validatePrimitiveValue` accepts the encoding, so
`variant.New` returns the value without an error.
### What changes are included in this PR?
The `PrimitiveString` branch now returns `""` when the length is zero,
the same guard the short-string branch already has.
### Are these changes tested?
Yes. `TestLongFormString` builds the 5-byte encoding from the issue and
a non-empty long-form string, and checks `Type()`, `Value()` and
`String()` for both. The empty case panics on main with `index out of
range [5] with length 5` and passes with the fix.
`go test ./parquet/...` passes (with the testing submodules), as do the
other packages that import `parquet/variant` (`arrow/compute`,
`arrow/extensions`, `parquet/pqarrow`). golangci-lint v2.13.2 reports no
issues.
### Are there any user-facing changes?
An empty string in the long-form encoding now reads as `""` instead of
panicking.
---
parquet/variant/variant.go | 5 ++++-
parquet/variant/variant_test.go | 24 ++++++++++++++++++++++++
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/parquet/variant/variant.go b/parquet/variant/variant.go
index 35f72c73..8a998d66 100644
--- a/parquet/variant/variant.go
+++ b/parquet/variant/variant.go
@@ -1102,7 +1102,10 @@ func (v Value) Value() any {
return v.value[5 : 5+sz]
case PrimitiveString:
sz := binary.LittleEndian.Uint32(v.value[1:5])
- return unsafe.String(&v.value[5], sz)
+ if sz > 0 {
+ return unsafe.String(&v.value[5], sz)
+ }
+ return ""
case PrimitiveDecimal4:
scale := uint8(v.value[1])
val := decimal.Decimal32(readExact[int32](v.value[2:]))
diff --git a/parquet/variant/variant_test.go b/parquet/variant/variant_test.go
index 89a5b03f..8444b4b7 100644
--- a/parquet/variant/variant_test.go
+++ b/parquet/variant/variant_test.go
@@ -289,6 +289,30 @@ func TestNullValue(t *testing.T) {
assert.Equal(t, "null", string(out))
}
+func TestLongFormString(t *testing.T) {
+ // Builder writes strings of up to 63 bytes in the short form, but the
long form
+ // (header 0x40 and a 4-byte length) is valid for any length, including
zero.
+ metadata := []byte{0x01, 0x00, 0x00}
+ tests := []struct {
+ name string
+ value []byte
+ want string
+ }{
+ {"empty", []byte{0x40, 0x00, 0x00, 0x00, 0x00}, ""},
+ {"non-empty", []byte{0x40, 0x03, 0x00, 0x00, 0x00, 'a', 'b',
'c'}, "abc"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ v, err := variant.New(metadata, tt.value)
+ require.NoError(t, err)
+ assert.Equal(t, variant.String, v.Type())
+ assert.Equal(t, tt.want, v.Value())
+ assert.Equal(t, `"`+tt.want+`"`, v.String())
+ })
+ }
+}
+
func TestSimpleInt64(t *testing.T) {
metaBytes := variant.EmptyMetadataBytes[:]