66 "errors"
77 "fmt"
88 "net/http"
9+ "regexp"
910 "strings"
1011 "time"
1112
@@ -22,36 +23,36 @@ import (
2223)
2324
2425type QueryModel struct {
25- Format string `json:"format"`
26+ Format string `json:"format"`
2627 // Constant string `json:"constant"`
27- Dataset string `json:"dataset"`
28+ Dataset string `json:"dataset"`
2829 // Group []Group `json:"group"`
29- MetricColumn string `json:"metricColumn"`
30- OrderByCol string /*int32*/ `json:"orderByCol"`
31- OrderBySort string /*int32*/ `json:"orderBySort"`
32- Partitioned bool `json:"partitioned"`
33- PartitionedField string `json:"partitionedField"`
34- ProjectID string `json:"project"`
35- RawQuery bool `json:"rawQuery"`
36- RawSQL string `json:"rawSql"`
37- RefID string `json:"refId"`
30+ MetricColumn string `json:"metricColumn"`
31+ OrderByCol string /*int32*/ `json:"orderByCol"`
32+ OrderBySort string /*int32*/ `json:"orderBySort"`
33+ Partitioned bool `json:"partitioned"`
34+ PartitionedField string `json:"partitionedField"`
35+ ProjectID string `json:"project"`
36+ RawQuery bool `json:"rawQuery"`
37+ RawSQL string `json:"rawSql"`
38+ RefID string `json:"refId"`
3839 // Select []string `json:"select"`
39- Sharded bool `json:"sharded"`
40- Table string `json:"table"`
41- TimeColumn string `json:"timeColumn"`
42- TimeColumnType string `json:"timeColumnType"`
43- Location string `json:"location"`
40+ Sharded bool `json:"sharded"`
41+ Table string `json:"table"`
42+ TimeColumn string `json:"timeColumn"`
43+ TimeColumnType string `json:"timeColumnType"`
44+ Location string `json:"location"`
4445 // Where []string `json:"where"`
4546}
4647
4748// JSONData holds the req.PluginContext.DataSourceInstanceSettings.JSONData struct
4849type JSONData struct {
49- AuthenticationType string `json:"authenticationType"`
50- ClientEmail string `json:"clientEmail"`
51- DefaultProject string `json:"defaultProject"`
52- ProcessingLocation string `json:"processingLocation"`
53- QueryPriority string `json:"queryPriority"`
54- TokenURI string `json:"tokenUri"`
50+ AuthenticationType string `json:"authenticationType"`
51+ ClientEmail string `json:"clientEmail"`
52+ DefaultProject string `json:"defaultProject"`
53+ ProcessingLocation string `json:"processingLocation"`
54+ QueryPriority string `json:"queryPriority"`
55+ TokenURI string `json:"tokenUri"`
5556}
5657
5758// BigQueryResult represents a full resultset.
@@ -149,6 +150,7 @@ func (td *BigQueryDatasource) query(ctx context.Context, query backend.DataQuery
149150 return response
150151 }
151152 values = append (values , v )
153+ break
152154 default :
153155 response .Error = fmt .Errorf ("unexpected type for field '%s': %s" , fieldSchema .Name , fieldSchema .Type )
154156 return response
@@ -157,6 +159,7 @@ func (td *BigQueryDatasource) query(ctx context.Context, query backend.DataQuery
157159 frame .Fields = append (frame .Fields ,
158160 data .NewField (fieldSchema .Name , nil , values ),
159161 )
162+ break
160163 default :
161164 switch fieldSchema .Type {
162165 case bigquery .IntegerFieldType :
@@ -173,6 +176,7 @@ func (td *BigQueryDatasource) query(ctx context.Context, query backend.DataQuery
173176 frame .Fields = append (frame .Fields ,
174177 data .NewField ("values" , nil , values ),
175178 )
179+ break
176180 case bigquery .FloatFieldType :
177181 values := make ([]float64 , 0 )
178182 for _ , row := range bigQueryResult .Rows {
@@ -187,12 +191,15 @@ func (td *BigQueryDatasource) query(ctx context.Context, query backend.DataQuery
187191 frame .Fields = append (frame .Fields ,
188192 data .NewField (fieldSchema .Name , nil , values ),
189193 )
194+ break
190195 default :
191196 response .Error = fmt .Errorf ("unexpected type for field '%s': %s" , fieldSchema .Name , fieldSchema .Type )
192197 return response
193198 }
199+ break
194200 }
195201 }
202+ break
196203 default :
197204 response .Error = fmt .Errorf ("unimplemented format '%s'. expected one of ['time_series']" , queryModel .Format )
198205 return response
@@ -308,7 +315,7 @@ func (td *BigQueryDatasource) executeQuery(ctx context.Context, queryModel Query
308315 }
309316
310317 // substituteVariables replaces any placeholder variables
311- sql := substituteVariables (queryModel .RawSQL , originalQuery )
318+ sql := substituteVariables (queryModel .RawSQL , queryModel , originalQuery )
312319 log .DefaultLogger .Debug (fmt .Sprintf ("query: %s\n " , sql ))
313320
314321 // create the query
@@ -356,7 +363,7 @@ func (td *BigQueryDatasource) executeQuery(ctx context.Context, queryModel Query
356363}
357364
358365// substituteVariables replaces standard grafana variables in an input string and returns the result
359- func substituteVariables (sql string , originalQuery backend.DataQuery ) string {
366+ func substituteVariables (sql string , queryModel QueryModel , originalQuery backend.DataQuery ) string {
360367 // __from
361368 sql = strings .Replace (sql , "${__from}" , fmt .Sprintf ("%d" , originalQuery .TimeRange .From .UnixNano ()/ int64 (time .Millisecond )), - 1 )
362369 sql = strings .Replace (sql , "${__from:date}" , originalQuery .TimeRange .From .Format (time .RFC3339 ), - 1 )
@@ -371,5 +378,46 @@ func substituteVariables(sql string, originalQuery backend.DataQuery) string {
371378 sql = strings .Replace (sql , "${__to:date:seconds}" , fmt .Sprintf ("%d" , originalQuery .TimeRange .To .Unix ()), - 1 )
372379 sql = strings .Replace (sql , "${__to:date:YYYY-MM}" , originalQuery .TimeRange .To .Format ("2006-01" ), - 1 )
373380
381+ // Macros
382+ from := adjustTime (originalQuery .TimeRange .From , queryModel .TimeColumnType )
383+ to := adjustTime (originalQuery .TimeRange .To , queryModel .TimeColumnType )
384+ wholeRange := quoteTimeColumn (queryModel .TimeColumn ) + " BETWEEN " + from + " AND " + to
385+ fromRange := quoteTimeColumn (queryModel .TimeColumn ) + " > " + from + " "
386+ toRange := quoteTimeColumn (queryModel .TimeColumn ) + " < " + to + " "
387+
388+ timeFilterRegex := regexp .MustCompile (`\$__timeFilter\((.*?)\)` )
389+ sql = timeFilterRegex .ReplaceAllString (sql , wholeRange )
390+ timeFromRegex := regexp .MustCompile (`\$__timeFrom\(([\w_.]+)\)` )
391+ sql = timeFromRegex .ReplaceAllString (sql , fromRange )
392+ timeToRegex := regexp .MustCompile (`\$__timeTo\(([\w_.]+)\)` )
393+ sql = timeToRegex .ReplaceAllString (sql , toRange )
394+ millisTimeToRegex := regexp .MustCompile (`\$__millisTimeTo\(([\w_.]+)\)` )
395+ sql = millisTimeToRegex .ReplaceAllString (sql , to )
396+ millisTimeFromRegex := regexp .MustCompile (`\$__millisTimeFrom\(([\w_.]+)\)` )
397+ sql = millisTimeFromRegex .ReplaceAllString (sql , from )
398+
374399 return sql
375400}
401+
402+ func adjustTime (value time.Time , timeType string ) string {
403+ switch timeType {
404+ case "DATE" :
405+ return fmt .Sprintf ("'%s'" , value .Format ("2006-01-01" )) // '2006-01-01'
406+ case "DATETIME" :
407+ return fmt .Sprintf ("'%s'" , value .Format ("2006-01-01 15:04:05" )) // '2006-01-01 15:04:05'
408+ default :
409+ return fmt .Sprintf ("TIMESTAMP_MILLIS (%d)" , value .UnixMilli ()) // TIMESTAMP_MILLIS (1612056873199)
410+ }
411+ }
412+
413+ func quoteTimeColumn (value string ) string {
414+ vals := strings .Split (value , "." )
415+ res := ""
416+ for i := 0 ; i < len (vals ); i ++ {
417+ res = res + "`" + vals [i ] + "`"
418+ if len (vals ) > 1 && i + 1 < len (vals ) {
419+ res = res + "."
420+ }
421+ }
422+ return res
423+ }
0 commit comments