@@ -3,23 +3,27 @@ package sqlds
3
3
import (
4
4
"context"
5
5
"database/sql"
6
+ "errors"
7
+ "fmt"
6
8
"net/http"
7
9
"sync"
10
+ "time"
8
11
9
12
"github.com/grafana/grafana-plugin-sdk-go/backend"
10
13
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
11
14
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
12
15
"github.com/grafana/grafana-plugin-sdk-go/data"
13
- "github.com/pkg/errors"
14
16
)
15
17
16
18
type sqldatasource struct {
19
+ Completable
20
+
17
21
db * sql.DB
18
22
c Driver
19
23
settings backend.DataSourceInstanceSettings
24
+ timeout time.Duration
20
25
21
26
backend.CallResourceHandler
22
- Completable
23
27
CustomRoutes map [string ]func (http.ResponseWriter , * http.Request )
24
28
}
25
29
@@ -37,7 +41,9 @@ func (ds *sqldatasource) NewDatasource(settings backend.DataSourceInstanceSettin
37
41
if err != nil {
38
42
return nil , err
39
43
}
44
+
40
45
ds .CallResourceHandler = httpadapter .New (mux )
46
+ ds .timeout = ds .c .Timeout (settings )
41
47
42
48
return ds , nil
43
49
}
@@ -66,7 +72,7 @@ func (ds *sqldatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
66
72
// Execute each query and store the results by query RefID
67
73
for _ , q := range req .Queries {
68
74
go func (query backend.DataQuery ) {
69
- frames , err := ds .handleQuery (query )
75
+ frames , err := ds .handleQuery (ctx , query )
70
76
71
77
response .Set (query .RefID , backend.DataResponse {
72
78
Frames : frames ,
@@ -83,7 +89,7 @@ func (ds *sqldatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
83
89
}
84
90
85
91
// handleQuery will call query, and attempt to reconnect if the query failed
86
- func (ds * sqldatasource ) handleQuery (req backend.DataQuery ) (data.Frames , error ) {
92
+ func (ds * sqldatasource ) handleQuery (ctx context. Context , req backend.DataQuery ) (data.Frames , error ) {
87
93
// Convert the backend.DataQuery into a Query object
88
94
q , err := GetQuery (req )
89
95
if err != nil {
@@ -93,7 +99,7 @@ func (ds *sqldatasource) handleQuery(req backend.DataQuery) (data.Frames, error)
93
99
// Apply supported macros to the query
94
100
q .RawSQL , err = interpolate (ds .c , q )
95
101
if err != nil {
96
- return nil , errors . WithMessage ( err , "Could not apply macros" )
102
+ return nil , fmt . Errorf ( "%s: %w" , "Could not apply macros" , err )
97
103
}
98
104
99
105
// Apply the default FillMode, overwritting it if the query specifies it
@@ -102,21 +108,35 @@ func (ds *sqldatasource) handleQuery(req backend.DataQuery) (data.Frames, error)
102
108
fillMode = q .FillMissing
103
109
}
104
110
111
+ if ds .timeout != 0 {
112
+ tctx , cancel := context .WithTimeout (ctx , ds .timeout )
113
+ defer cancel ()
114
+
115
+ ctx = tctx
116
+ }
117
+
105
118
// FIXES:
106
119
// * Some datasources (snowflake) expire connections or have an authentication token that expires if not used in 1 or 4 hours.
107
120
// Because the datasource driver does not include an option for permanent connections, we retry the connection
108
121
// if the query fails. NOTE: this does not include some errors like "ErrNoRows"
109
- res , err := query (ds .db , ds .c .Converters (), fillMode , q )
122
+
123
+ // This function will return an "ErrorTimeout" if the context's done channel receives data
124
+ res , err := queryContext (ctx , ds .db , ds .c .Converters (), fillMode , q )
110
125
if err == nil {
111
126
return res , nil
112
127
}
113
128
114
- if errors .Cause (err ) == ErrorQuery {
129
+ if errors .Is (err , ErrorNoResults ) {
130
+ return nil , nil
131
+ }
132
+
133
+ if errors .Is (err , ErrorQuery ) {
115
134
ds .db , err = ds .c .Connect (ds .settings )
116
135
if err != nil {
117
136
return nil , err
118
137
}
119
- return query (ds .db , ds .c .Converters (), fillMode , q )
138
+
139
+ return queryContext (ctx , ds .db , ds .c .Converters (), fillMode , q )
120
140
}
121
141
122
142
return nil , err
0 commit comments