@@ -2,6 +2,8 @@ package sqlds_test
22
33import (
44 "context"
5+ "database/sql"
6+ "database/sql/driver"
57 "encoding/json"
68 "errors"
79 "fmt"
@@ -10,6 +12,7 @@ import (
1012 "github.com/grafana/grafana-plugin-sdk-go/backend"
1113 "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil"
1214 "github.com/grafana/sqlds/v4"
15+ "github.com/grafana/sqlds/v4/mock"
1316 "github.com/grafana/sqlds/v4/test"
1417 "github.com/stretchr/testify/assert"
1518)
@@ -223,6 +226,68 @@ func Test_query_panic_recovery(t *testing.T) {
223226 assert .Nil (t , res .Frames )
224227}
225228
229+ func Test_query_panic_in_rows_validation (t * testing.T ) {
230+ cfg := `{ "timeout": 0, "retries": 0, "retryOn": [] }`
231+ opts := test.DriverOpts {}
232+
233+ // Set up a driver that returns rows which will cause a panic when accessing columns
234+ // This will be caught by our validateRows function
235+ customQueryFunc := func (args []driver.Value ) (driver.Rows , error ) {
236+ // Return a panicking rows implementation that will cause a panic when columns are accessed
237+ return & panickingRows {}, nil
238+ }
239+
240+ // Create a custom driver with a wrapper for the query method
241+ driverName := "panic-rows-test"
242+
243+ // Create and register the handler
244+ handler := test .NewDriverHandler (test.Data {}, opts )
245+
246+ // Create a custom handler that overrides the Query method
247+ customHandler := & panickingDBHandler {
248+ SqlHandler : handler ,
249+ customQueryFunc : customQueryFunc ,
250+ }
251+
252+ mock .RegisterDriver (driverName , customHandler )
253+
254+ // Create datasource with the custom driver
255+ testDS := & testDriver {driverName : driverName }
256+ ds := sqlds .NewDatasource (testDS )
257+
258+ // Set up the query request
259+ req , settings := setupQueryRequest ("panic-rows-validation" , cfg )
260+ _ , err := ds .NewDatasource (context .Background (), settings )
261+ assert .Nil (t , err )
262+
263+ // Execute the query
264+ data , err := ds .QueryData (context .Background (), req )
265+
266+ // Verify that the panic was caught and converted to an error
267+ assert .Nil (t , err )
268+ assert .NotNil (t , data .Responses )
269+
270+ res := data .Responses ["foo" ]
271+ assert .NotNil (t , res .Error )
272+ assert .Contains (t , res .Error .Error (), "failed to validate rows" )
273+ assert .NotNil (t , res .Frames ) // Error frame is returned, not nil
274+ }
275+
276+ // panickingRows is a custom rows implementation that panics when columns are accessed
277+ type panickingRows struct {}
278+
279+ func (r * panickingRows ) Columns () []string {
280+ panic ("panic in Columns method" )
281+ }
282+
283+ func (r * panickingRows ) Close () error {
284+ return nil
285+ }
286+
287+ func (r * panickingRows ) Next (dest []driver.Value ) error {
288+ return nil
289+ }
290+
226291func queryRequest (t * testing.T , name string , opts test.DriverOpts , cfg string , marcos sqlds.Macros ) (* backend.QueryDataRequest , * test.SqlHandler , * sqlds.SQLDatasource ) {
227292 driver , handler := test .NewDriver (name , test.Data {}, nil , opts , marcos )
228293 ds := sqlds .NewDatasource (driver )
@@ -269,3 +334,50 @@ func setupHealthRequest(id string, cfg string) (backend.CheckHealthRequest, back
269334 }
270335 return req , settings
271336}
337+
338+ // testDriver implements sqlds.Driver interface for testing
339+ type testDriver struct {
340+ driverName string
341+ }
342+
343+ func (d * testDriver ) Connect (ctx context.Context , cfg backend.DataSourceInstanceSettings , msg json.RawMessage ) (* sql.DB , error ) {
344+ return sql .Open (d .driverName , "" )
345+ }
346+
347+ func (d * testDriver ) Settings (ctx context.Context , config backend.DataSourceInstanceSettings ) sqlds.DriverSettings {
348+ settings , _ := test .LoadSettings (ctx , config )
349+ return settings
350+ }
351+
352+ func (d * testDriver ) Macros () sqlds.Macros {
353+ return nil
354+ }
355+
356+ func (d * testDriver ) Converters () []sqlutil.Converter {
357+ return nil
358+ }
359+
360+ // panickingDBHandler implements mock.DBHandler and causes panics when querying
361+ type panickingDBHandler struct {
362+ test.SqlHandler
363+ customQueryFunc func (args []driver.Value ) (driver.Rows , error )
364+ }
365+
366+ func (h * panickingDBHandler ) Query (args []driver.Value ) (driver.Rows , error ) {
367+ if h .customQueryFunc != nil {
368+ return h .customQueryFunc (args )
369+ }
370+ return h .SqlHandler .Query (args )
371+ }
372+
373+ func (h * panickingDBHandler ) Ping (ctx context.Context ) error {
374+ return nil
375+ }
376+
377+ func (h * panickingDBHandler ) Columns () []string {
378+ return []string {"test_column" }
379+ }
380+
381+ func (h * panickingDBHandler ) Next (dest []driver.Value ) error {
382+ return errors .New ("no more rows" )
383+ }
0 commit comments