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[:]
 

Reply via email to