Skip to content

Commit e5824c8

Browse files
authored
query retries (#77)
query retries
1 parent c88be27 commit e5824c8

File tree

3 files changed

+113
-8
lines changed

3 files changed

+113
-8
lines changed

datasource.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net/http"
1010
"sync"
11+
"time"
1112

1213
"github.com/grafana/grafana-plugin-sdk-go/backend"
1314
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
@@ -224,13 +225,22 @@ func (ds *SQLDatasource) handleQuery(ctx context.Context, req backend.DataQuery,
224225
// If there's a query error that didn't exceed the
225226
// context deadline retry the query
226227
if errors.Is(err, ErrorQuery) && !errors.Is(err, context.DeadlineExceeded) {
227-
db, err := ds.c.Connect(dbConn.settings, q.ConnectionArgs)
228-
if err != nil {
229-
return nil, err
230-
}
231-
ds.storeDBConnection(cacheKey, dbConnection{db, dbConn.settings})
228+
for i := 0; i < ds.driverSettings.Retries; i++ {
229+
backend.Logger.Warn(fmt.Sprintf("query failed. retrying %d times", i))
230+
db, err := ds.c.Connect(dbConn.settings, q.ConnectionArgs)
231+
if err != nil {
232+
return nil, err
233+
}
234+
ds.storeDBConnection(cacheKey, dbConnection{db, dbConn.settings})
232235

233-
return QueryDB(ctx, db, ds.c.Converters(), fillMode, q)
236+
if ds.driverSettings.Pause > 0 {
237+
time.Sleep(time.Duration(ds.driverSettings.Pause * int(time.Second)))
238+
}
239+
res, err = QueryDB(ctx, db, ds.c.Converters(), fillMode, q)
240+
if err == nil {
241+
return res, err
242+
}
243+
}
234244
}
235245

236246
// allow retries on timeouts
@@ -243,7 +253,10 @@ func (ds *SQLDatasource) handleQuery(ctx context.Context, req backend.DataQuery,
243253
}
244254
ds.storeDBConnection(cacheKey, dbConnection{db, dbConn.settings})
245255

246-
return QueryDB(ctx, db, ds.c.Converters(), fillMode, q)
256+
res, err = QueryDB(ctx, db, ds.c.Converters(), fillMode, q)
257+
if err == nil {
258+
return res, err
259+
}
247260
}
248261
}
249262

datasource_test.go

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ package sqlds
33
import (
44
"context"
55
"database/sql"
6+
"database/sql/driver"
67
"encoding/json"
78
"errors"
89
"fmt"
10+
"io"
911
"testing"
1012
"time"
1113

1214
"github.com/grafana/grafana-plugin-sdk-go/backend"
15+
"github.com/grafana/grafana-plugin-sdk-go/data/sqlutil"
1316
"github.com/grafana/sqlds/v2/mock"
1417
"github.com/stretchr/testify/assert"
1518
)
@@ -24,6 +27,14 @@ func (d fakeDriver) Connect(backend.DataSourceInstanceSettings, json.RawMessage)
2427
return d.db, nil
2528
}
2629

30+
func (d fakeDriver) Macros() Macros {
31+
return Macros{}
32+
}
33+
34+
func (d fakeDriver) Converters() []sqlutil.Converter {
35+
return []sqlutil.Converter{}
36+
}
37+
2738
// func (d fakeDriver) Settings(backend.DataSourceInstanceSettings) DriverSettings
2839

2940
func Test_getDBConnectionFromQuery(t *testing.T) {
@@ -121,7 +132,7 @@ func Test_Dispose(t *testing.T) {
121132
})
122133
}
123134

124-
func Test_retries(t *testing.T) {
135+
func Test_timeout_retries(t *testing.T) {
125136
dsUID := "timeout"
126137
settings := backend.DataSourceInstanceSettings{UID: dsUID}
127138

@@ -157,14 +168,67 @@ func Test_retries(t *testing.T) {
157168
assert.Equal(t, expected, result.Message)
158169
}
159170

171+
func Test_error_retries(t *testing.T) {
172+
testCounter = 0
173+
dsUID := "error"
174+
settings := backend.DataSourceInstanceSettings{UID: dsUID}
175+
176+
handler := testSqlHandler{
177+
error: errors.New("foo"),
178+
}
179+
mockDriver := "sqlmock-error"
180+
mock.RegisterDriver(mockDriver, handler)
181+
db, err := sql.Open(mockDriver, "")
182+
if err != nil {
183+
t.Errorf("failed to connect to mock driver: %v", err)
184+
}
185+
timeoutDriver := fakeDriver{
186+
db: db,
187+
}
188+
retries := 5
189+
max := time.Duration(10) * time.Second
190+
driverSettings := DriverSettings{Retries: retries, Timeout: max, Pause: 1}
191+
ds := &SQLDatasource{c: timeoutDriver, driverSettings: driverSettings}
192+
193+
key := defaultKey(dsUID)
194+
// Add the mandatory default db
195+
ds.storeDBConnection(key, dbConnection{db, settings})
196+
ctx := context.Background()
197+
198+
qry := `{ "rawSql": "foo" }`
199+
200+
req := &backend.QueryDataRequest{
201+
PluginContext: backend.PluginContext{
202+
DataSourceInstanceSettings: &settings,
203+
},
204+
Queries: []backend.DataQuery{
205+
{
206+
RefID: "foo",
207+
JSON: []byte(qry),
208+
},
209+
},
210+
}
211+
212+
data, err := ds.QueryData(ctx, req)
213+
assert.Nil(t, err)
214+
assert.Equal(t, retries+1, testCounter)
215+
assert.NotNil(t, data.Responses)
216+
217+
}
218+
160219
var testCounter = 0
161220
var testTimeout = 1
221+
var testRows = 0
162222

163223
type testSqlHandler struct {
164224
mock.DBHandler
225+
error
165226
}
166227

167228
func (s testSqlHandler) Ping(ctx context.Context) error {
229+
if s.error != nil {
230+
return s.error
231+
}
168232
testCounter++ // track the retries for the test assertion
169233
time.Sleep(time.Duration(testTimeout + 1)) // simulate a connection delay
170234
select {
@@ -173,3 +237,30 @@ func (s testSqlHandler) Ping(ctx context.Context) error {
173237
return ctx.Err()
174238
}
175239
}
240+
241+
func (s testSqlHandler) Query(args []driver.Value) (driver.Rows, error) {
242+
fmt.Println("query")
243+
if s.error != nil {
244+
testCounter++
245+
return s, s.error
246+
}
247+
return s, nil
248+
}
249+
250+
func (s testSqlHandler) Columns() []string {
251+
return []string{"foo", "bar"}
252+
}
253+
254+
func (s testSqlHandler) Next(dest []driver.Value) error {
255+
testRows++
256+
if testRows > 5 {
257+
return io.EOF
258+
}
259+
dest[0] = "foo"
260+
dest[1] = "bar"
261+
return nil
262+
}
263+
264+
func (s testSqlHandler) Close() error {
265+
return nil
266+
}

driver.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type DriverSettings struct {
1515
Timeout time.Duration
1616
FillMode *data.FillMissing
1717
Retries int
18+
Pause int
1819
}
1920

2021
// Driver is a simple interface that defines how to connect to a backend SQL datasource

0 commit comments

Comments
 (0)