-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathquery.go
More file actions
150 lines (127 loc) · 4.31 KB
/
query.go
File metadata and controls
150 lines (127 loc) · 4.31 KB
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
package papergres
import (
"fmt"
"reflect"
"github.com/jmoiron/sqlx"
)
// Query holds the SQL to execute and the connection string
type Query struct {
SQL string
Database *Database
Args []interface{}
insert bool
}
// SelectParamsFn is a function that takes in the iteration and
// returns the destination and args for a SQL execution.
type SelectParamsFn func(i int) (dest interface{}, args []interface{})
// Exec runs a sql command given a connection and expects LastInsertId or RowsAffected
// to be returned by the script. Use this for INSERTs
func (q *Query) Exec() *Result {
return exec(q, false)
}
// ExecAll gets many rows and populates the given slice
// dest should be a pointer to a slice
func (q *Query) ExecAll(dest interface{}) *Result {
all := func(db *sqlx.DB, r *Result) error {
err := db.Select(dest, q.SQL, q.Args...)
r.RowsReturned = getLen(dest)
return err
}
return execDB(q, all)
}
// ExecAllIn works with IN queries. It will take a slice of values and
// attach the slice to the query as a list of values.
// One key difference with bindvar used for IN query is a `?` (question mark)
// the query then has to be rebinded to change default bindvar to target bindvar
// like `$1` (dollar sign followed by a number) for postgres etc.
func (q *Query) ExecAllIn(dest interface{}) *Result {
all := func(db *sqlx.DB, r *Result) error {
query, args, err := sqlx.In(q.SQL, q.Args...)
if err != nil {
return err
}
// Rebind the query to replace the ? with $1, $2, etc
query = db.Rebind(query)
// Execute a select query using this DB
err = db.Select(dest, query, args...)
r.RowsReturned = getLen(dest)
return err
}
return execDB(q, all)
}
// ExecNonQuery runs the SQL and doesn't look for any results
func (q *Query) ExecNonQuery() *Result {
return exec(q, true)
}
// ExecSingle fetches a single row from the database and puts it into dest.
// If more than 1 row is returned it takes the first one.
// Expects at least 1 row or it will return an error.
func (q *Query) ExecSingle(dest interface{}) *Result {
single := func(db *sqlx.DB, r *Result) error {
err := db.Get(dest, q.SQL, q.Args...)
if err == nil {
r.RowsReturned = 1
}
return err
}
return execDB(q, single)
}
// Repeat will execute a query N times. The param selector function will pass in
// the current iteration and expect back the destination obj and args for that
// index. Make sure to use pointers to ensure the sql results fill your structs.
// Use this when you want to run the same query for many different parameters,
// like getting data for child entities for a collection of parents.
// This function executes the iterations concurrently so each loop should not
// rely on state from a previous loops execution. The function should be
// extremely fast and efficient with DB resources.
// Returned error will contain all errors that occurred in any iterations.
//
// Example usage:
// params := func(i int) (dest interface{}, args []interface{}) {
// p := &parents[i] // get parent at i to derive parameters
// args := MakeArgs(p.Id, true, "current") // create arg list, variadic
// return &p.Child, args // &p.Child will be filled with returned data
// }
// // len(parents) is parent slice and determines how many times to execute query
// results, err := db.Query(sql).Repeat(len(parents), params).Exec()
//
func (q *Query) Repeat(times int, pSelectorFn SelectParamsFn) *Repeat {
return &Repeat{
q,
pSelectorFn,
times,
}
}
// String returns a SQL query and it's arguments along with connection info in a
// pretty format.
func (q *Query) String() string {
return fmt.Sprintf(`
Query:
%s
%s
Connection: %s
`,
q.SQL, argsToString(q.Args), prettifyConnString(q.Database.ConnectionString()))
}
// argsToString iterates over each argument and returns them in a neatly
// formatted string.
func argsToString(args []interface{}) string {
s := ""
if len(args) > 0 {
s = "Args:"
}
// The idea here is to keep adding each argument on a separate line
for i, a := range args {
s += fmt.Sprintf("\n\t$%v: %v", i+1, a)
}
return s
}
// getLen returns the number of items in a slice. It can be used to populate
// number of rows returned from a query execution
func getLen(i interface{}) int {
val := reflect.Indirect(reflect.ValueOf(i))
if val.Kind() == reflect.Slice {
return val.Len()
}
return 0
}