Skip to content

Commit c1cab59

Browse files
authored
Add some default macros (#45)
1 parent 1c981c0 commit c1cab59

File tree

3 files changed

+130
-0
lines changed

3 files changed

+130
-0
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,16 @@ if err := datasource.Manage("my-datasource", ds.NewDatasource, datasource.Manage
1717
os.Exit(1)
1818
}
1919
```
20+
21+
## Standardization
22+
23+
### Macros
24+
25+
The `sqlds` package defines a set of default macros:
26+
27+
- `$__timeFilter(time_column)`: Filters by timestamp using the query period. Resolves to: `time >= '0001-01-01T00:00:00Z' AND time <= '0001-01-01T00:00:00Z'`
28+
- `$__timeFrom(time_column)`: Filters by timestamp using the start point of the query period. Resolves to `time >= '0001-01-01T00:00:00Z'`
29+
- `$__timeTo(time_column)`: Filters by timestamp using the end point of the query period. Resolves to `time <= '0001-01-01T00:00:00Z'`
30+
- `$__timeGroup(time_column, period)`: To group times based on a period. Resolves to (minute example): `"datepart(year, time), datepart(month, time)'"`
31+
- `$__table`: Returns the `table` configured in the query.
32+
- `$__column`: Returns the `column` configured in the query.

macros.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"regexp"
77
"strings"
8+
"time"
89
)
910

1011
var (
@@ -20,6 +21,103 @@ type MacroFunc func(*Query, []string) (string, error)
2021
// The "string" key is the name of the macro function. This name has to be regex friendly.
2122
type Macros map[string]MacroFunc
2223

24+
// Default time filter for SQL based on the query time range.
25+
// It requires one argument, the time column to filter.
26+
// Example:
27+
// $__timeFilter(time) => "time BETWEEN '2006-01-02T15:04:05Z07:00' AND '2006-01-02T15:04:05Z07:00'"
28+
func macroTimeFilter(query *Query, args []string) (string, error) {
29+
if len(args) != 1 {
30+
return "", fmt.Errorf("%w: expected 1 argument, received %d", ErrorBadArgumentCount, len(args))
31+
}
32+
33+
var (
34+
column = args[0]
35+
from = query.TimeRange.From.UTC().Format(time.RFC3339)
36+
to = query.TimeRange.To.UTC().Format(time.RFC3339)
37+
)
38+
39+
return fmt.Sprintf("%s >= '%s' AND %s <= '%s'", column, from, column, to), nil
40+
}
41+
42+
// Default time filter for SQL based on the starting query time range.
43+
// It requires one argument, the time column to filter.
44+
// Example:
45+
// $__timeFrom(time) => "time > '2006-01-02T15:04:05Z07:00'"
46+
func macroTimeFrom(query *Query, args []string) (string, error) {
47+
if len(args) != 1 {
48+
return "", fmt.Errorf("%w: expected 1 argument, received %d", ErrorBadArgumentCount, len(args))
49+
}
50+
51+
return fmt.Sprintf("%s >= '%s'", args[0], query.TimeRange.From.UTC().Format(time.RFC3339)), nil
52+
53+
}
54+
55+
// Default time filter for SQL based on the ending query time range.
56+
// It requires one argument, the time column to filter.
57+
// Example:
58+
// $__timeTo(time) => "time < '2006-01-02T15:04:05Z07:00'"
59+
func macroTimeTo(query *Query, args []string) (string, error) {
60+
if len(args) != 1 {
61+
return "", fmt.Errorf("%w: expected 1 argument, received %d", ErrorBadArgumentCount, len(args))
62+
}
63+
64+
return fmt.Sprintf("%s <= '%s'", args[0], query.TimeRange.To.UTC().Format(time.RFC3339)), nil
65+
}
66+
67+
// Default time group for SQL based the given period.
68+
// This basic example is meant to be customized with more complex periods.
69+
// It requires two arguments, the column to filter and the period.
70+
// Example:
71+
// $__timeTo(time, month) => "datepart(year, time), datepart(month, time)'"
72+
func macroTimeGroup(query *Query, args []string) (string, error) {
73+
if len(args) != 2 {
74+
return "", fmt.Errorf("%w: expected 1 argument, received %d", ErrorBadArgumentCount, len(args))
75+
}
76+
77+
res := ""
78+
switch args[1] {
79+
case "minute":
80+
res += fmt.Sprintf("datepart(minute, %s),", args[0])
81+
fallthrough
82+
case "hour":
83+
res += fmt.Sprintf("datepart(hour, %s),", args[0])
84+
fallthrough
85+
case "day":
86+
res += fmt.Sprintf("datepart(day, %s),", args[0])
87+
fallthrough
88+
case "month":
89+
res += fmt.Sprintf("datepart(month, %s),", args[0])
90+
fallthrough
91+
case "year":
92+
res += fmt.Sprintf("datepart(year, %s)", args[0])
93+
}
94+
95+
return res, nil
96+
}
97+
98+
// Default macro to return the query table name.
99+
// Example:
100+
// $__table => "my_table"
101+
func macroTable(query *Query, args []string) (string, error) {
102+
return query.Table, nil
103+
}
104+
105+
// Default macro to return the query column name.
106+
// Example:
107+
// $__column => "my_col"
108+
func macroColumn(query *Query, args []string) (string, error) {
109+
return query.Column, nil
110+
}
111+
112+
var DefaultMacros Macros = Macros{
113+
"timeFilter": macroTimeFilter,
114+
"timeFrom": macroTimeFrom,
115+
"timeGroup": macroTimeGroup,
116+
"timeTo": macroTimeTo,
117+
"table": macroTable,
118+
"column": macroColumn,
119+
}
120+
23121
func trimAll(s []string) []string {
24122
r := make([]string, len(s))
25123
for i, v := range s {
@@ -35,6 +133,12 @@ func getMacroRegex(name string) string {
35133

36134
func interpolate(driver Driver, query *Query) (string, error) {
37135
macros := driver.Macros()
136+
for key, defaultMacro := range DefaultMacros {
137+
if _, ok := macros[key]; !ok {
138+
// If the driver doesn't define some macro, use the default one
139+
macros[key] = defaultMacro
140+
}
141+
}
38142
rawSQL := query.RawSQL
39143
for key, macro := range macros {
40144
rgx, err := regexp.Compile(getMacroRegex(key))

macros_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ func (h *MockDB) Macros() (macros Macros) {
2525
}
2626
return "bar", nil
2727
},
28+
// overwrite a default macro
29+
"timeGroup": func(query *Query, args []string) (out string, err error) {
30+
return "grouped!", nil
31+
},
2832
}
2933
}
3034

@@ -33,6 +37,8 @@ func (h *MockDB) Timeout(backend.DataSourceInstanceSettings) time.Duration {
3337
}
3438

3539
func TestInterpolate(t *testing.T) {
40+
tableName := "my_table"
41+
tableColumn := "my_col"
3642
type test struct {
3743
name string
3844
input string
@@ -49,12 +55,19 @@ func TestInterpolate(t *testing.T) {
4955
{input: "select * from $__params(hello) AND $__params(hello)", output: "select * from bar_hello AND bar_hello", name: "same macro multiple times with same param"},
5056
{input: "select * from $__params(hello) AND $__params(world)", output: "select * from bar_hello AND bar_world", name: "same macro multiple times with different param"},
5157
{input: "select * from $__params(world) AND $__foo() AND $__params(hello)", output: "select * from bar_world AND bar AND bar_hello", name: "different macros with different params"},
58+
{input: "select * from foo where $__timeFilter(time)", output: "select * from foo where time >= '0001-01-01T00:00:00Z' AND time <= '0001-01-01T00:00:00Z'", name: "default timeFilter"},
59+
{input: "select * from foo where $__timeTo(time)", output: "select * from foo where time <= '0001-01-01T00:00:00Z'", name: "default timeTo macro"},
60+
{input: "select * from foo where $__timeFrom(time)", output: "select * from foo where time >= '0001-01-01T00:00:00Z'", name: "default timeFrom macro"},
61+
{input: "select * from foo where $__timeGroup(time,minute)", output: "select * from foo where grouped!", name: "overriden timeGroup macro"},
62+
{input: "select $__column from $__table", output: "select my_col from my_table", name: "table and column macros"},
5263
}
5364
for i, tc := range tests {
5465
driver := MockDB{}
5566
t.Run(fmt.Sprintf("[%d/%d] %s", i+1, len(tests), tc.name), func(t *testing.T) {
5667
query := &Query{
5768
RawSQL: tc.input,
69+
Table: tableName,
70+
Column: tableColumn,
5871
}
5972
interpolatedQuery, err := interpolate(&driver, query)
6073
require.Nil(t, err)

0 commit comments

Comments
 (0)