-
Notifications
You must be signed in to change notification settings - Fork 1
/
sqlt.go
135 lines (120 loc) · 3.24 KB
/
sqlt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package sqlt
import (
"bytes"
"database/sql"
"regexp"
"text/template"
"time"
)
const (
// LeftDelim is start delimiter for SQL template.
LeftDelim = "/*%"
// RightDelim is end delimiter for SQL template.
RightDelim = "%*/"
// Connector is delimiter that is used when `sqlt` makes original argument name.
Connector = "__"
)
var (
strRegex = regexp.MustCompile(`%\*/'[^']*'`)
inRegex = regexp.MustCompile(`%\*/\([^()]*\)`)
valRegex = regexp.MustCompile(`%\*/\S*`)
)
// Config is configuration for executing template.
type config struct {
timeFunc func() time.Time
annotative bool
}
func (conf *config) clone() *config {
return &config{
timeFunc: conf.timeFunc,
annotative: conf.annotative,
}
}
func (conf *config) apply(opts []Option) *config {
if len(opts) > 0 {
cf := conf.clone()
for _, opt := range opts {
opt(cf)
}
return cf
}
return conf
}
// SQLTemplate is template struct.
type SQLTemplate struct {
dialect Dialect
// customFuncs are custom functions that are used in template.
customFuncs map[string]interface{}
config *config
}
// New template initialized with dialect.
func New(dialect Dialect) *SQLTemplate {
return &SQLTemplate{
dialect: dialect,
customFuncs: make(map[string]interface{}),
config: &config{},
}
}
// AddFunc add custom template func.
func (st *SQLTemplate) AddFunc(name string, fn interface{}) *SQLTemplate {
st.customFuncs[name] = fn
return st
}
// AddFuncs add custom template functions.
func (st *SQLTemplate) AddFuncs(funcs map[string]interface{}) *SQLTemplate {
for k, v := range funcs {
st.customFuncs[k] = v
}
return st
}
// WithOptions apply given options.
func (st *SQLTemplate) WithOptions(opts ...Option) *SQLTemplate {
for _, opt := range opts {
opt(st.config)
}
return st
}
// Exec executes given template with given map parameters.
// This function replaces to normal placeholder.
func (st *SQLTemplate) Exec(text string, m map[string]interface{}, opts ...Option) (string, []interface{}, error) {
conf := st.config.apply(opts)
c := newContext(false, st.dialect, m, conf)
s, err := st.exec(c, text, m)
if err != nil {
return "", nil, err
}
if c.err != nil && !c.config.annotative {
return "", nil, c.err
}
return s, c.Args(), c.err
}
// ExecNamed executes given template with given map parameters.
// This function replaces to named placeholder.
func (st *SQLTemplate) ExecNamed(text string, m map[string]interface{}, opts ...Option) (string, []sql.NamedArg, error) {
conf := st.config.apply(opts)
c := newContext(true, st.dialect, m, conf)
s, err := st.exec(c, text, m)
if err != nil {
return "", nil, err
}
if c.err != nil && !c.config.annotative {
return "", nil, c.err
}
return s, c.NamedArgs(), c.err
}
func (st *SQLTemplate) exec(c *context, text string, m map[string]interface{}) (string, error) {
t, err := template.New("").Funcs(c.funcMap(st.customFuncs)).Delims(LeftDelim, RightDelim).Parse(dropSample(text))
if err != nil {
return "", err
}
buf := &bytes.Buffer{}
if err = t.Execute(buf, nil); err != nil {
return "", err
}
return buf.String(), nil
}
func dropSample(text string) string {
s := strRegex.ReplaceAllString(text, RightDelim)
s = inRegex.ReplaceAllString(s, RightDelim)
return valRegex.ReplaceAllString(s, RightDelim)
}