-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimpler.go
214 lines (176 loc) · 4.35 KB
/
simpler.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package simpler
import (
"bufio"
"fmt"
"os"
"path"
"path/filepath"
"strings"
dbx "github.com/go-ozzo/ozzo-dbx"
)
// Query represents data loaded from a SQL file
type Query struct {
Prefix string
Name string
SQL string
}
// NewQuery returns new empty query with a given prefix
func NewQuery(prefix string) *Query {
return &Query{Prefix: prefix}
}
func (q *Query) processMeta(meta *metaData) error {
switch meta.Key {
case "name":
q.Name = fmt.Sprintf("%s/%s", q.Prefix, meta.Value)
default:
return fmt.Errorf(`Unknown metadata key %s with value %s`, meta.Key, meta.Value)
}
return nil
}
func (q *Query) readSQL(line string) error {
q.SQL = strings.Trim(fmt.Sprintf("%s %s", q.SQL, line), "\n")
return nil
}
// Registry holds queries and *dbx.DB object
// is capable of loading queries from a folder
type Registry struct {
registry map[string]*Query
db *dbx.DB
}
// NewRegistry creates a new registry
// and loads queries from a list of directiories
func NewRegistry(dirs ...string) (*Registry, error) {
r := &Registry{
registry: map[string]*Query{},
}
for _, dir := range dirs {
err := r.readDirectory(dir)
if err != nil {
return nil, err
}
}
return r, nil
}
// DB returns pointer to *dbx.DB instance
func (r *Registry) DB() *dbx.DB {
return r.db
}
// Connect registry to a dabatase using adapter and url
func (r *Registry) Connect(adapter string, dbURL string) error {
db, err := dbx.MustOpen(adapter, dbURL)
if err != nil {
return err
}
r.db = db
return nil
}
// HasQuery returns true if registry has loaded query named name
func (r *Registry) HasQuery(name string) bool {
_, ok := r.registry[name]
return ok
}
// Query returns *dbx.Query created from a query found in *.sql files with name `name`
func (r *Registry) Query(name string) *dbx.Query {
if r.db == nil {
panic("Must connect first before creating a query")
}
if !r.HasQuery(name) {
panic(fmt.Sprintf("Query not found with name %s", name))
}
q := r.queryByName(name)
return r.db.NewQuery(q.SQL)
}
// QueryString returns raw SQL string
// string will be empty if registry has no such query
func (r *Registry) QueryString(name string) string {
if !r.HasQuery(name) {
return ""
}
return r.queryByName(name).SQL
}
// LoadDirectory reads all *.sql files in a given directory
// All nested folders will be processed also
// Nested folder names are preserved and used as a prefix in query name
// so query `delete-user` from a file `sql/user-queries/users` will have name
// `user-queries/users/delete-user`
func (r *Registry) LoadDirectory(dir string) error {
return r.readDirectory(dir)
}
func (r *Registry) saveQuery(query *Query) error {
if len(query.Name) == 0 {
return fmt.Errorf("Found query without a name: %#v", query)
}
r.registry[query.Name] = query
return nil
}
func (r *Registry) readDirectory(dir string) error {
return filepath.Walk(dir, func(file string, info os.FileInfo, err error) error {
if path.Ext(file) == ".sql" {
err := r.readFile(dir, file)
if err != nil {
return err
}
}
return nil
})
}
func (r *Registry) readFile(dir string, file string) error {
fileNoDir := strings.Replace(file, dir, "", 1)
prefix := strings.Replace(fileNoDir, path.Ext(fileNoDir), "", 1)
if strings.HasPrefix(prefix, "/") {
prefix = strings.Replace(prefix, "/", "", 1)
}
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
// maybe later this will be needed to fix "token too long" error
// const maxCapacity = 1024 * 1024
// buf := make([]byte, maxCapacity)
// scanner.Buffer(buf, maxCapacity)
var qerr error
var line string
var query *Query
for scanner.Scan() {
line = scanner.Text()
meta, isMeta, metaErr := parseMeta(line)
if strings.HasPrefix(line, "--") && metaErr != nil {
return metaErr
}
if isMeta && meta.Key == "name" {
if query != nil {
qerr = r.saveQuery(query)
if qerr != nil {
return qerr
}
}
query = NewQuery(prefix)
}
if isMeta {
qerr = query.processMeta(meta)
if qerr != nil {
return qerr
}
} else {
qerr = query.readSQL(line)
if qerr != nil {
return qerr
}
}
}
if err := scanner.Err(); err != nil {
return err
}
if query != nil {
qerr = r.saveQuery(query)
if qerr != nil {
return qerr
}
}
return nil
}
func (r *Registry) queryByName(name string) *Query {
return r.registry[name]
}