2017-10-10 08:19:14 -05:00
package postgres
import (
"fmt"
"regexp"
"strings"
2017-10-27 04:26:25 -05:00
"time"
2017-10-10 08:19:14 -05:00
2019-05-30 02:58:29 -05:00
"github.com/grafana/grafana/pkg/components/gtime"
2017-10-10 08:19:14 -05:00
"github.com/grafana/grafana/pkg/tsdb"
2019-09-10 14:50:04 -05:00
"github.com/grafana/grafana/pkg/tsdb/sqleng"
2017-10-10 08:19:14 -05:00
)
const rsIdentifier = ` ([_a-zA-Z0-9]+) `
const sExpr = ` \$ ` + rsIdentifier + ` \(([^\)]*)\) `
2018-07-26 11:10:17 -05:00
type postgresMacroEngine struct {
2019-09-10 14:50:04 -05:00
* sqleng . SqlMacroEngineBase
2018-08-14 01:34:20 -05:00
timeRange * tsdb . TimeRange
query * tsdb . Query
timescaledb bool
2017-10-10 08:19:14 -05:00
}
2019-09-10 14:50:04 -05:00
func newPostgresMacroEngine ( timescaledb bool ) sqleng . SqlMacroEngine {
2018-09-13 09:51:00 -05:00
return & postgresMacroEngine {
2019-09-10 14:50:04 -05:00
SqlMacroEngineBase : sqleng . NewSqlMacroEngineBase ( ) ,
2018-09-13 09:51:00 -05:00
timescaledb : timescaledb ,
}
2017-10-10 08:19:14 -05:00
}
2018-07-26 11:10:17 -05:00
func ( m * postgresMacroEngine ) Interpolate ( query * tsdb . Query , timeRange * tsdb . TimeRange , sql string ) ( string , error ) {
m . timeRange = timeRange
m . query = query
2017-10-10 08:19:14 -05:00
rExp , _ := regexp . Compile ( sExpr )
var macroError error
2018-09-13 09:51:00 -05:00
sql = m . ReplaceAllStringSubmatchFunc ( rExp , sql , func ( groups [ ] string ) string {
2018-08-01 08:06:18 -05:00
// detect if $__timeGroup is supposed to add AS time for pre 5.3 compatibility
// if there is a ',' directly after the macro call $__timeGroup is probably used
// in the old way. Inside window function ORDER BY $__timeGroup will be followed
// by ')'
if groups [ 1 ] == "__timeGroup" {
if index := strings . Index ( sql , groups [ 0 ] ) ; index >= 0 {
index += len ( groups [ 0 ] )
2020-12-01 02:53:27 -06:00
// check for character after macro expression
if len ( sql ) > index && sql [ index ] == ',' {
groups [ 1 ] = "__timeGroupAlias"
2018-08-01 08:06:18 -05:00
}
}
}
2018-03-02 12:20:30 -06:00
args := strings . Split ( groups [ 2 ] , "," )
for i , arg := range args {
args [ i ] = strings . Trim ( arg , " " )
}
res , err := m . evaluateMacro ( groups [ 1 ] , args )
2017-10-10 08:19:14 -05:00
if err != nil && macroError == nil {
macroError = err
return "macro_error()"
}
return res
} )
if macroError != nil {
return "" , macroError
}
return sql , nil
}
2020-09-07 15:10:06 -05:00
//nolint: gocyclo
2018-07-26 11:10:17 -05:00
func ( m * postgresMacroEngine ) evaluateMacro ( name string , args [ ] string ) ( string , error ) {
2017-10-10 08:19:14 -05:00
switch name {
case "__time" :
if len ( args ) == 0 {
return "" , fmt . Errorf ( "missing time column argument for macro %v" , name )
}
return fmt . Sprintf ( "%s AS \"time\"" , args [ 0 ] ) , nil
case "__timeEpoch" :
if len ( args ) == 0 {
return "" , fmt . Errorf ( "missing time column argument for macro %v" , name )
}
return fmt . Sprintf ( "extract(epoch from %s) as \"time\"" , args [ 0 ] ) , nil
case "__timeFilter" :
if len ( args ) == 0 {
return "" , fmt . Errorf ( "missing time column argument for macro %v" , name )
}
2018-04-15 11:38:20 -05:00
2019-01-02 16:38:09 -06:00
return fmt . Sprintf ( "%s BETWEEN '%s' AND '%s'" , args [ 0 ] , m . timeRange . GetFromAsTimeUTC ( ) . Format ( time . RFC3339Nano ) , m . timeRange . GetToAsTimeUTC ( ) . Format ( time . RFC3339Nano ) ) , nil
2018-11-30 07:18:33 -06:00
case "__timeFrom" :
2019-01-02 16:38:09 -06:00
return fmt . Sprintf ( "'%s'" , m . timeRange . GetFromAsTimeUTC ( ) . Format ( time . RFC3339Nano ) ) , nil
2018-11-30 07:18:33 -06:00
case "__timeTo" :
2019-01-02 16:38:09 -06:00
return fmt . Sprintf ( "'%s'" , m . timeRange . GetToAsTimeUTC ( ) . Format ( time . RFC3339Nano ) ) , nil
2017-10-10 08:19:14 -05:00
case "__timeGroup" :
2017-12-10 02:59:33 -06:00
if len ( args ) < 2 {
return "" , fmt . Errorf ( "macro %v needs time column and interval and optional fill value" , name )
2017-10-10 08:19:14 -05:00
}
2019-05-30 02:58:29 -05:00
interval , err := gtime . ParseInterval ( strings . Trim ( args [ 1 ] , ` ' ` ) )
2017-10-27 04:26:25 -05:00
if err != nil {
return "" , fmt . Errorf ( "error parsing interval %v" , args [ 1 ] )
}
2017-12-10 02:59:33 -06:00
if len ( args ) == 3 {
2019-09-10 14:50:04 -05:00
err := sqleng . SetupFillmode ( m . query , interval , args [ 2 ] )
2018-08-12 03:51:58 -05:00
if err != nil {
return "" , err
2017-12-10 02:59:33 -06:00
}
}
2018-07-21 13:00:26 -05:00
2018-08-14 01:34:20 -05:00
if m . timescaledb {
2018-08-09 03:14:14 -05:00
return fmt . Sprintf ( "time_bucket('%vs',%s)" , interval . Seconds ( ) , args [ 0 ] ) , nil
2018-07-21 13:00:26 -05:00
}
2019-04-23 03:24:47 -05:00
return fmt . Sprintf (
"floor(extract(epoch from %s)/%v)*%v" , args [ 0 ] ,
interval . Seconds ( ) ,
interval . Seconds ( ) ,
) , nil
2018-08-01 01:48:22 -05:00
case "__timeGroupAlias" :
tg , err := m . evaluateMacro ( "__timeGroup" , args )
if err == nil {
2020-11-17 04:27:45 -06:00
return tg + " AS \"time\"" , nil
2018-08-01 01:48:22 -05:00
}
return "" , err
2017-10-10 08:19:14 -05:00
case "__unixEpochFilter" :
if len ( args ) == 0 {
return "" , fmt . Errorf ( "missing time column argument for macro %v" , name )
}
2018-07-26 11:10:17 -05:00
return fmt . Sprintf ( "%s >= %d AND %s <= %d" , args [ 0 ] , m . timeRange . GetFromAsSecondsEpoch ( ) , args [ 0 ] , m . timeRange . GetToAsSecondsEpoch ( ) ) , nil
2019-01-03 13:40:17 -06:00
case "__unixEpochNanoFilter" :
2019-01-02 16:00:21 -06:00
if len ( args ) == 0 {
return "" , fmt . Errorf ( "missing time column argument for macro %v" , name )
}
return fmt . Sprintf ( "%s >= %d AND %s <= %d" , args [ 0 ] , m . timeRange . GetFromAsTimeUTC ( ) . UnixNano ( ) , args [ 0 ] , m . timeRange . GetToAsTimeUTC ( ) . UnixNano ( ) ) , nil
2019-01-03 14:31:47 -06:00
case "__unixEpochNanoFrom" :
2019-01-03 15:25:10 -06:00
return fmt . Sprintf ( "%d" , m . timeRange . GetFromAsTimeUTC ( ) . UnixNano ( ) ) , nil
2019-01-03 14:31:47 -06:00
case "__unixEpochNanoTo" :
2019-01-03 15:25:10 -06:00
return fmt . Sprintf ( "%d" , m . timeRange . GetToAsTimeUTC ( ) . UnixNano ( ) ) , nil
2018-08-13 05:08:14 -05:00
case "__unixEpochGroup" :
if len ( args ) < 2 {
return "" , fmt . Errorf ( "macro %v needs time column and interval and optional fill value" , name )
}
2019-05-30 02:58:29 -05:00
interval , err := gtime . ParseInterval ( strings . Trim ( args [ 1 ] , ` ' ` ) )
2018-08-13 05:08:14 -05:00
if err != nil {
return "" , fmt . Errorf ( "error parsing interval %v" , args [ 1 ] )
}
if len ( args ) == 3 {
2019-09-10 14:50:04 -05:00
err := sqleng . SetupFillmode ( m . query , interval , args [ 2 ] )
2018-08-13 05:08:14 -05:00
if err != nil {
return "" , err
}
}
return fmt . Sprintf ( "floor(%s/%v)*%v" , args [ 0 ] , interval . Seconds ( ) , interval . Seconds ( ) ) , nil
case "__unixEpochGroupAlias" :
tg , err := m . evaluateMacro ( "__unixEpochGroup" , args )
if err == nil {
2020-11-17 04:27:45 -06:00
return tg + " AS \"time\"" , nil
2018-08-13 05:08:14 -05:00
}
return "" , err
2017-10-10 08:19:14 -05:00
default :
2020-11-05 04:29:39 -06:00
return "" , fmt . Errorf ( "unknown macro %q" , name )
2017-10-10 08:19:14 -05:00
}
}