Repository: grafana/grafana-plugin-sdk-go (go)

Issue:
data: Check for consistent Field Len in MarshalArrow
Building an arrow table will panic if the Frame has unequal length fields.

This can be avoided by the user by checking the error of frame.RowLen(), that being said I think we should do the same in MarshalArrow to avoid any possible panic there.

Pull request:
data: check field length in MarshalArrow
check the row length inside Frame's MarshalArrow method before building the arrow frame to error instead of panicing.

fixes #203

Patch:
diff --git a/data/arrow.go b/data/arrow.go
index 4f52b85..b5db27a 100644
--- a/data/arrow.go
+++ b/data/arrow.go
@@ -16,7 +16,12 @@ import (
 
 // MarshalArrow converts the Frame to an arrow table and returns a byte
 // representation of that table.
+// All fields of a Frame must be of the same length or an error is returned.
 func (f *Frame) MarshalArrow() ([]byte, error) {
+	if _, err := f.RowLen(); err != nil {
+		return nil, err
+	}
+
 	arrowFields, err := buildArrowFields(f)
 	if err != nil {
 		return nil, err
diff --git a/data/frame.go b/data/frame.go
index 8b1b91c..d67d127 100644
--- a/data/frame.go
+++ b/data/frame.go
@@ -237,15 +237,13 @@ func (f *Frame) ConcreteAt(fieldIdx int, rowIdx int) (val interface{}, ok bool)
 
 // RowLen returns the the length of the Frame Fields.
 // If the Length of all the Fields is not the same then error is returned.
-// If the Frame's Fields are nil an error is returned.
 func (f *Frame) RowLen() (int, error) {
-	if f.Fields == nil || len(f.Fields) == 0 {
-		return 0, fmt.Errorf("frame's fields are nil or of zero length")
+	if len(f.Fields) == 0 {
+		return 0, nil
 	}
-
 	var l int
 	for i := 0; i < len(f.Fields); i++ {
-		if f.Fields[i].vector == nil {
+		if f.Fields[i] == nil || f.Fields[i].vector == nil {
 			return 0, fmt.Errorf("frame's field at index %v is nil", i)
 		}
 		if i == 0 {


Test patch:
diff --git a/data/arrow_test.go b/data/arrow_test.go
index dded1d4..ef5e058 100644
--- a/data/arrow_test.go
+++ b/data/arrow_test.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/grafana/grafana-plugin-sdk-go/data"
+	"github.com/stretchr/testify/require"
 )
 
 var update = flag.Bool("update", false, "update .golden.arrow files")
@@ -299,3 +300,18 @@ func TestEncodeAndDecodeDuplicateFieldNames(t *testing.T) {
 		t.Errorf("Result mismatch (-want +got):\n%s", diff)
 	}
 }
+
+func TestFrameMarshalArrowRowLenError(t *testing.T) {
+	f := data.NewFrame("unequal length fields",
+		data.NewField("1", nil, []string{}),
+		data.NewField("1", nil, []string{"a"}),
+	)
+	_, err := f.MarshalArrow()
+	require.Error(t, err)
+}
+
+func TestFrameMarshalArrowNoFields(t *testing.T) {
+	f := data.NewFrame("no fields")
+	_, err := f.MarshalArrow()
+	require.NoError(t, err)
+}