Repository navigation
Expand file tree
/
Copy pathpgq.go
More file actions
134 lines (122 loc) · 3.03 KB
/
Copy pathpgq.go
File metadata and controls
134 lines (122 loc) · 3.03 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
// package pgq provides a fluent SQL generator.
//
// See https://github.com/henvic/pgq for examples.
package pgq
import (
"bytes"
"fmt"
"io"
"strings"
)
// SQLizer is the interface that wraps the SQL method.
//
// SQL() returns a SQL representation of the SQLizer, along with a slice of arguments.
// If the query cannot be created, it returns an error.
type SQLizer interface {
SQL() (string, []any, error)
}
// rawSQLizer is expected to do what SQLizer does, but without finalizing placeholders.
// This is useful for nested queries.
type rawSQLizer interface {
unfinalizedSQL() (string, []any, error)
}
// Debug calls SQL on s and shows the approximate SQL to be executed
//
// If SQL returns an error, the result of this method will look like:
// "[SQL error: %s]" or "[DebugSQLizer error: %s]"
//
// IMPORTANT: As its name suggests, this function should only be used for
// debugging. While the string result *might* be valid SQL, this function does
// not try very hard to ensure it. Additionally, executing the output of this
// function with any untrusted user input is certainly insecure.
func Debug(s SQLizer) string {
sql, args, err := nestedSQL(s)
if err != nil {
return fmt.Sprintf("[SQL error: %s]", err)
}
buf := &bytes.Buffer{}
i := 0
for {
p := strings.Index(sql, "?")
if p == -1 {
break
}
if len(sql[p:]) > 1 && sql[p:p+2] == "??" { // escape ?? => ?
buf.WriteString(sql[:p])
buf.WriteString("?")
if len(sql[p:]) == 1 {
break
}
sql = sql[p+2:]
} else {
if i+1 > len(args) {
return fmt.Sprintf(
"[DebugSQLizer error: too many placeholders in %#v for %d args]",
sql, len(args))
}
buf.WriteString(sql[:p])
fmt.Fprintf(buf, "'%v'", args[i])
// advance our sql string "cursor" beyond the arg we placed
sql = sql[p+1:]
i++
}
}
if i < len(args) {
return fmt.Sprintf(
"[DebugSQLizer error: not enough placeholders in %#v for %d args]",
sql, len(args))
}
// "append" any remaning sql that won't need interpolating
buf.WriteString(sql)
return buf.String()
}
type part struct {
pred any
args []any
}
func newPart(pred any, args ...any) SQLizer {
return &part{pred, args}
}
func (p part) SQL() (sql string, args []any, err error) {
switch pred := p.pred.(type) {
case nil:
// no-op
case SQLizer:
sql, args, err = nestedSQL(pred)
case string:
sql = pred
args = p.args
default:
err = fmt.Errorf("expected string or SQLizer, not %T", pred)
}
return
}
func nestedSQL(s SQLizer) (string, []any, error) {
if raw, ok := s.(rawSQLizer); ok {
return raw.unfinalizedSQL()
} else {
return s.SQL()
}
}
func appendSQL(parts []SQLizer, w io.Writer, sep string, args []any) ([]any, error) {
for i, p := range parts {
partSQL, partArgs, err := nestedSQL(p)
if err != nil {
return nil, err
} else if partSQL == "" {
continue
}
if i > 0 {
_, err := io.WriteString(w, sep)
if err != nil {
return nil, err
}
}
_, err = io.WriteString(w, partSQL)
if err != nil {
return nil, err
}
args = append(args, partArgs...)
}
return args, nil
}