Skip to content

Commit 20c9ac2

Browse files
authored
feat: add custom error type for unsupported column types (#1326)
1 parent e447dec commit 20c9ac2

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

data/sqlutil/scanrow.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ import (
88
"github.com/grafana/grafana-plugin-sdk-go/backend"
99
)
1010

11+
// ErrColumnTypeNotSupported is returned when an SQL column has a type that cannot be processed.
12+
// This typically occurs when a database driver doesn't provide a valid scan type for a column
13+
// or when encountering custom/exotic data types that don't have appropriate conversion handlers.
14+
type ErrColumnTypeNotSupported struct {
15+
Type string
16+
Column string
17+
}
18+
19+
func (e ErrColumnTypeNotSupported) Error() string {
20+
return fmt.Sprintf("type %q is not supported (column %q)", e.Type, e.Column)
21+
}
22+
1123
// A ScanRow is a container for SQL metadata for a single row.
1224
// The row metadata is used to generate dataframe fields and a slice that can be used with sql.Scan
1325
type ScanRow struct {
@@ -87,7 +99,7 @@ func MakeScanRow(colTypes []*sql.ColumnType, colNames []string, converters ...Co
8799
if !rc.hasConverter(i) {
88100
scanTypeValue := colType.ScanType()
89101
if scanTypeValue == nil {
90-
return nil, fmt.Errorf(`type %s is not supported for column %s`, colType.DatabaseTypeName(), colName)
102+
return nil, ErrColumnTypeNotSupported{Type: colType.DatabaseTypeName(), Column: colName}
91103
}
92104
v := NewDefaultConverter(colName, nullable, scanTypeValue)
93105
rc.append(colName, scanType(v, colType.ScanType()), v)

data/sqlutil/sql_test.go

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"database/sql/driver"
77
"errors"
88
"io"
9+
"reflect"
910
"sync"
1011
"testing"
1112

@@ -36,6 +37,7 @@ type singleResultSet struct {
3637

3738
rows [][]interface{}
3839
currentRow int
40+
scanTypes []reflect.Type
3941
}
4042

4143
func (rows *singleResultSet) Next(dest []driver.Value) error {
@@ -50,6 +52,13 @@ func (rows *singleResultSet) Next(dest []driver.Value) error {
5052
return nil
5153
}
5254

55+
func (rows *singleResultSet) ColumnTypeScanType(index int) reflect.Type {
56+
if index >= len(rows.scanTypes) {
57+
return reflect.TypeFor[any]()
58+
}
59+
return rows.scanTypes[index]
60+
}
61+
5362
type multipleResultSets struct {
5463
baseRows
5564

@@ -148,6 +157,24 @@ func makeSingleResultSet(
148157
return rows
149158
}
150159

160+
func makeSingleResultSetWithScanTypes(
161+
columnNames []string,
162+
scanTypes []reflect.Type,
163+
data ...[]interface{},
164+
) *sql.Rows {
165+
rows, _ := sql.OpenDB(&fakeDB{
166+
rows: &singleResultSet{
167+
baseRows: baseRows{
168+
columnNames: columnNames,
169+
},
170+
rows: data,
171+
currentRow: -1,
172+
scanTypes: scanTypes,
173+
},
174+
}).Query("")
175+
return rows
176+
}
177+
151178
func makeMultipleResultSets(
152179
columnNames []string,
153180
resultSets ...[][]interface{},
@@ -175,7 +202,7 @@ func TestFrameFromRows(t *testing.T) {
175202
rowLimit int64
176203
converters []sqlutil.Converter
177204
frame *data.Frame
178-
err bool
205+
err error
179206
}{
180207
{
181208
name: "rows not implements driver.RowsNextResultSet",
@@ -204,7 +231,7 @@ func TestFrameFromRows(t *testing.T) {
204231
data.NewField("c", nil, []*string{ptr("3"), ptr("6"), ptr("9")}),
205232
},
206233
},
207-
err: false,
234+
err: nil,
208235
},
209236
{
210237
name: "rows not implements driver.RowsNextResultSet, limit reached",
@@ -241,7 +268,7 @@ func TestFrameFromRows(t *testing.T) {
241268
},
242269
},
243270
},
244-
err: false,
271+
err: nil,
245272
},
246273
{
247274
name: "rows implements driver.RowsNextResultSet, but contains only one result set",
@@ -272,7 +299,7 @@ func TestFrameFromRows(t *testing.T) {
272299
data.NewField("c", nil, []*string{ptr("3"), ptr("6"), ptr("9")}),
273300
},
274301
},
275-
err: false,
302+
err: nil,
276303
},
277304
{
278305
name: "rows implements driver.RowsNextResultSet, but contains more then one result set",
@@ -305,7 +332,7 @@ func TestFrameFromRows(t *testing.T) {
305332
data.NewField("c", nil, []*string{ptr("3"), ptr("6"), ptr("9")}),
306333
},
307334
},
308-
err: false,
335+
err: nil,
309336
},
310337
{
311338
name: "rows implements driver.RowsNextResultSet, limit reached",
@@ -338,13 +365,24 @@ func TestFrameFromRows(t *testing.T) {
338365
data.NewField("c", nil, []*string{ptr("3"), ptr("6")}),
339366
},
340367
},
341-
err: false,
368+
err: nil,
369+
},
370+
{
371+
name: "row contains unsupported column type",
372+
rows: makeSingleResultSetWithScanTypes( //nolint:rowserrcheck
373+
[]string{"a"},
374+
[]reflect.Type{nil},
375+
[]interface{}{1},
376+
),
377+
rowLimit: 100,
378+
converters: nil,
379+
err: sqlutil.ErrColumnTypeNotSupported{},
342380
},
343381
} {
344382
t.Run(tt.name, func(t *testing.T) {
345383
frame, err := sqlutil.FrameFromRows(tt.rows, tt.rowLimit, tt.converters...)
346-
if tt.err {
347-
require.Error(t, err)
384+
if tt.err != nil {
385+
require.ErrorAs(t, err, &tt.err)
348386
} else {
349387
require.NoError(t, err)
350388
require.Equal(t, tt.frame, frame)

0 commit comments

Comments
 (0)