@@ -3,6 +3,7 @@ package sqlds
3
3
import (
4
4
"context"
5
5
"database/sql"
6
+ "encoding/json"
6
7
"errors"
7
8
"fmt"
8
9
"net/http"
@@ -14,15 +15,32 @@ import (
14
15
"github.com/grafana/grafana-plugin-sdk-go/data"
15
16
)
16
17
17
- const defaultKey = "_default"
18
+ const defaultKeySuffix = "default"
19
+
20
+ var (
21
+ MissingMultipleConnectionsConfig = errors .New ("received connection arguments but the feature is not enabled" )
22
+ MissingDBConnection = errors .New ("unable to get default db connection" )
23
+ )
24
+
25
+ func defaultKey (datasourceID int64 ) string {
26
+ return fmt .Sprintf ("%d-%s" , datasourceID , defaultKeySuffix )
27
+ }
28
+
29
+ func keyWithConnectionArgs (datasourceID int64 , connArgs json.RawMessage ) string {
30
+ return fmt .Sprintf ("%d-%s" , datasourceID , string (connArgs ))
31
+ }
32
+
33
+ type dbConnection struct {
34
+ db * sql.DB
35
+ settings backend.DataSourceInstanceSettings
36
+ }
18
37
19
38
type sqldatasource struct {
20
39
Completable
21
40
22
41
dbConnections sync.Map
23
42
c Driver
24
43
driverSettings DriverSettings
25
- settings backend.DataSourceInstanceSettings
26
44
27
45
backend.CallResourceHandler
28
46
CustomRoutes map [string ]func (http.ResponseWriter , * http.Request )
@@ -32,15 +50,28 @@ type sqldatasource struct {
32
50
EnableMultipleConnections bool
33
51
}
34
52
53
+ func (ds * sqldatasource ) getDBConnection (key string ) (dbConnection , bool ) {
54
+ conn , ok := ds .dbConnections .Load (key )
55
+ if ! ok {
56
+ return dbConnection {}, false
57
+ }
58
+ return conn .(dbConnection ), true
59
+ }
60
+
61
+ func (ds * sqldatasource ) storeDBConnection (key string , dbConn dbConnection ) {
62
+ ds .dbConnections .Store (key , dbConn )
63
+ }
64
+
35
65
// NewDatasource creates a new `sqldatasource`.
36
66
// It uses the provided settings argument to call the ds.Driver to connect to the SQL server
37
67
func (ds * sqldatasource ) NewDatasource (settings backend.DataSourceInstanceSettings ) (instancemgmt.Instance , error ) {
38
68
db , err := ds .c .Connect (settings , nil )
39
69
if err != nil {
40
70
return nil , err
41
71
}
42
- ds .dbConnections .Store (defaultKey , db )
43
- ds .settings = settings
72
+ key := defaultKey (settings .ID )
73
+ ds .storeDBConnection (key , dbConnection {db , settings })
74
+
44
75
mux := http .NewServeMux ()
45
76
err = ds .registerRoutes (mux )
46
77
if err != nil {
@@ -62,8 +93,8 @@ func NewDatasource(c Driver) *sqldatasource {
62
93
63
94
// Dispose cleans up datasource instance resources.
64
95
func (ds * sqldatasource ) Dispose () {
65
- ds .dbConnections .Range (func (key , db interface {}) bool {
66
- err := db .( * sql. DB ) .Close ()
96
+ ds .dbConnections .Range (func (key , dbConn interface {}) bool {
97
+ err := dbConn .( dbConnection ). db .Close ()
67
98
if err != nil {
68
99
backend .Logger .Error (err .Error ())
69
100
}
@@ -100,32 +131,36 @@ func (ds *sqldatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
100
131
101
132
}
102
133
103
- func (ds * sqldatasource ) getDB (q * Query ) (* sql.DB , string , error ) {
134
+ func (ds * sqldatasource ) getDBConnectionFromQuery (q * Query ) (string , dbConnection , error ) {
135
+ if ! ds .EnableMultipleConnections && len (q .ConnectionArgs ) > 0 {
136
+ return "" , dbConnection {}, MissingMultipleConnectionsConfig
137
+ }
104
138
// The database connection may vary depending on query arguments
105
139
// The raw arguments are used as key to store the db connection in memory so they can be reused
106
- key := defaultKey
107
- db , ok := ds .dbConnections . Load (key )
140
+ key := defaultKey ( q . DatasourceID )
141
+ dbConn , ok := ds .getDBConnection (key )
108
142
if ! ok {
109
- return nil , "" , fmt . Errorf ( "unable to get default db connection" )
143
+ return "" , dbConnection {}, MissingDBConnection
110
144
}
111
145
if ! ds .EnableMultipleConnections || len (q .ConnectionArgs ) == 0 {
112
- return db .( * sql. DB ), key , nil
146
+ return key , dbConn , nil
113
147
}
114
148
115
- key = string ( q .ConnectionArgs )
116
- if cachedDB , ok := ds .dbConnections . Load (key ); ok {
117
- return cachedDB .( * sql. DB ), key , nil
149
+ key = keyWithConnectionArgs ( q . DatasourceID , q .ConnectionArgs )
150
+ if cachedConn , ok := ds .getDBConnection (key ); ok {
151
+ return key , cachedConn , nil
118
152
}
119
153
120
154
var err error
121
- db , err = ds .c .Connect (ds .settings , q .ConnectionArgs )
155
+ db , err : = ds .c .Connect (dbConn .settings , q .ConnectionArgs )
122
156
if err != nil {
123
- return nil , "" , err
157
+ return "" , dbConnection {} , err
124
158
}
125
159
// Assign this connection in the cache
126
- ds .dbConnections .Store (key , db )
160
+ dbConn = dbConnection {db , dbConn .settings }
161
+ ds .storeDBConnection (key , dbConn )
127
162
128
- return db .( * sql. DB ), key , nil
163
+ return key , dbConn , nil
129
164
}
130
165
131
166
// handleQuery will call query, and attempt to reconnect if the query failed
@@ -149,7 +184,7 @@ func (ds *sqldatasource) handleQuery(ctx context.Context, req backend.DataQuery)
149
184
}
150
185
151
186
// Retrieve the database connection
152
- db , cacheKey , err := ds .getDB (q )
187
+ cacheKey , dbConn , err := ds .getDBConnectionFromQuery (q )
153
188
if err != nil {
154
189
return getErrorFrameFromQuery (q ), err
155
190
}
@@ -165,7 +200,7 @@ func (ds *sqldatasource) handleQuery(ctx context.Context, req backend.DataQuery)
165
200
// * Some datasources (snowflake) expire connections or have an authentication token that expires if not used in 1 or 4 hours.
166
201
// Because the datasource driver does not include an option for permanent connections, we retry the connection
167
202
// if the query fails. NOTE: this does not include some errors like "ErrNoRows"
168
- res , err := query (ctx , db , ds .c .Converters (), fillMode , q )
203
+ res , err := query (ctx , dbConn . db , ds .c .Converters (), fillMode , q )
169
204
if err == nil {
170
205
return res , nil
171
206
}
@@ -175,11 +210,11 @@ func (ds *sqldatasource) handleQuery(ctx context.Context, req backend.DataQuery)
175
210
}
176
211
177
212
if errors .Is (err , ErrorQuery ) {
178
- db , err = ds .c .Connect (ds .settings , q .ConnectionArgs )
213
+ db , err : = ds .c .Connect (dbConn .settings , q .ConnectionArgs )
179
214
if err != nil {
180
215
return nil , err
181
216
}
182
- ds .dbConnections . Store (cacheKey , db )
217
+ ds .storeDBConnection (cacheKey , dbConnection { db , dbConn . settings } )
183
218
184
219
return query (ctx , db , ds .c .Converters (), fillMode , q )
185
220
}
@@ -189,11 +224,12 @@ func (ds *sqldatasource) handleQuery(ctx context.Context, req backend.DataQuery)
189
224
190
225
// CheckHealth pings the connected SQL database
191
226
func (ds * sqldatasource ) CheckHealth (ctx context.Context , req * backend.CheckHealthRequest ) (* backend.CheckHealthResult , error ) {
192
- db , ok := ds .dbConnections .Load (defaultKey )
227
+ key := defaultKey (req .PluginContext .DataSourceInstanceSettings .ID )
228
+ dbConn , ok := ds .getDBConnection (key )
193
229
if ! ok {
194
- return nil , fmt . Errorf ( "unable to get default db connection" )
230
+ return nil , MissingDBConnection
195
231
}
196
- if err := db .( * sql. DB ) .Ping (); err != nil {
232
+ if err := dbConn . db .Ping (); err != nil {
197
233
return & backend.CheckHealthResult {
198
234
Status : backend .HealthStatusError ,
199
235
Message : err .Error (),
0 commit comments