Repository: parquet-go/parquet-go (go)

Issue:
panic: reflect: call of reflect.Value.Field on zero Value
I'm trying to read records into a struct that contains a slice of elements with nested struct-pointers and seeing the following panic:

```
panic: reflect: call of reflect.Value.Field on zero Value
```

Below a test I wrote to reproduce the issue. It writes a single record to a parquet file, then tries to read it.

```go
func TestNestedPointer(t *testing.T) {
	type InnerStruct struct {
		InnerField string
	}

	type SliceElement struct {
		Inner *InnerStruct
	}

	type Outer struct {
		Slice []*SliceElement
	}

	in := &Outer{
		Slice: []*SliceElement{
			{
				Inner: &InnerStruct{
					InnerField: "inner-string",
				},
			},
		},
	}

	tmpDir := t.TempDir()
	parquetFile := filepath.Join(tmpDir, "data.parquet")
	f, err := os.Create(parquetFile)
	if err != nil {
		t.Fatal(err)
	}

	pw := parquet.NewGenericWriter[*Outer](f)
	_, err = pw.Write([]*Outer{in})
	if err != nil {
		t.Fatal(err)
	}

	err = pw.Close()
	if err != nil {
		t.Fatal(err)
	}

	err = f.Close()
	if err != nil {
		t.Fatal(err)
	}

	f, err = os.Open(parquetFile)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	pr := parquet.NewGenericReader[*Outer](f)

	out := make([]*Outer, 1)
	_, err = pr.Read(out)
	if err != nil {
		t.Fatal(err)
	}

	pr.Close()
}
```

The code above works as expected if `InnerStruct` is not a pointer in the slice element.

```go
	type SliceElement struct {
		Inner InnerStruct
	}
```

Any suggestions on how to fix this? I can't remove the pointers since I'm using generated protobuf messages which always use pointers. The pointers don't appear to be a problem in other places, only if used in a struct that's part of a slice from what I can tell. Any help with this is appreciated. 

Pull request:
fix: zero value of nested field pointer
fixes #2

When reconstructing the schema for nested structs we call function `fieldByIndex` to get the pointer to the underlying value of the field that we assign the values we read into.

This is the comment on fieldByIndex function

```go
// fieldByIndex is like reflect.Value.FieldByIndex but returns the zero-value of
// reflect.Value if one of the fields was a nil pointer instead of panicking.
```

In theory if we have a field with value `*Foo` a call to fieldByIndex should do the following when this field is not initialized yet (i.e is Nil)

- Create a zero value of `Foo` : `new(Foo)`
- Assign the zero value created to the field.

Before this change this is what fieldByIndex was doing

```go
			if v.IsNil() {
				v = reflect.Value{}
				break
```

`reflect.Value{}` IS NOT A ZERO VALUE of anything , it is an invalid value.

This commit ensures that we correctly initialize zero values of nested pointer fields.

Patch:
diff --git a/schema.go b/schema.go
index e716919..50a4763 100644
--- a/schema.go
+++ b/schema.go
@@ -410,7 +410,8 @@ func fieldByIndex(v reflect.Value, index []int) reflect.Value {
 	for _, i := range index {
 		if v = v.Field(i); v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
 			if v.IsNil() {
-				v = reflect.Value{}
+				v.Set(reflect.New(v.Type().Elem()))
+				v = v.Elem()
 				break
 			} else {
 				v = v.Elem()


Test patch:
diff --git a/parquet_go18_test.go b/parquet_go18_test.go
index ae26b1a..2e63e13 100644
--- a/parquet_go18_test.go
+++ b/parquet_go18_test.go
@@ -490,3 +490,52 @@ func logRows[T any](t *testing.T, rows []T) {
 		t.Logf(". %#v\n", row)
 	}
 }
+
+func TestNestedPointer(t *testing.T) {
+	type InnerStruct struct {
+		InnerField string
+	}
+
+	type SliceElement struct {
+		Inner *InnerStruct
+	}
+
+	type Outer struct {
+		Slice []*SliceElement
+	}
+	value := "inner-string"
+	in := &Outer{
+		Slice: []*SliceElement{
+			{
+				Inner: &InnerStruct{
+					InnerField: value,
+				},
+			},
+		},
+	}
+
+	var f bytes.Buffer
+
+	pw := parquet.NewGenericWriter[*Outer](&f)
+	_, err := pw.Write([]*Outer{in})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = pw.Close()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	pr := parquet.NewGenericReader[*Outer](bytes.NewReader(f.Bytes()))
+
+	out := make([]*Outer, 1)
+	_, err = pr.Read(out)
+	if err != nil {
+		t.Fatal(err)
+	}
+	pr.Close()
+	if want, got := value, out[0].Slice[0].Inner.InnerField; want != got {
+		t.Error("failed to set inner field pointer")
+	}
+}