-
Notifications
You must be signed in to change notification settings - Fork 0
/
sq.go
96 lines (88 loc) · 2.44 KB
/
sq.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
// Package sq provides a fluent SQL generator.
//
// See https://github.com/userhubdev/sq for examples.
package sq
import (
"fmt"
"strings"
)
// Sqlizer is the interface that wraps the ToSql method.
//
// ToSql returns a SQL representation of the Sqlizer, along with a slice of args
// as passed to e.g. database/sql.Exec. It can also return an error.
type Sqlizer interface {
ToSql() (string, []any, error)
}
// Raw is a Sqlizer that contains no arguments
type Raw string
// String returns to raw SQL.
func (r Raw) String() string {
return string(r)
}
// ToSql implements the Sqlizer interface.
func (r Raw) ToSql() (string, []any, error) {
return string(r), nil, nil
}
// RawSqlizer is expected to do what Sqlizer does, but without finalizing placeholders.
// This is useful for nested queries.
type RawSqlizer interface {
ToSqlRaw() (string, []any, error)
}
// Debug calls ToSql on s and shows the approximate SQL to be executed
//
// If ToSql returns an error, the result of this method will look like:
// "[ToSql error: %s]" or "[Debug 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 := s.ToSql()
if err != nil {
return fmt.Sprintf("[ToSql error: %s]", err)
}
var placeholder string
downCast, ok := s.(placeholderDebugger)
if !ok {
placeholder = "?"
} else {
placeholder = downCast.debugPlaceholder()
}
// TODO: dedupe this with placeholder.go
b := &strings.Builder{}
i := 0
for {
p := strings.Index(sql, placeholder)
if p == -1 {
break
}
if len(sql[p:]) > 1 && sql[p:p+2] == "??" { // escape ?? => ?
b.WriteString(sql[:p])
b.WriteString("?")
if len(sql[p:]) == 1 {
break
}
sql = sql[p+2:]
} else {
if i+1 > len(args) {
return fmt.Sprintf(
"[Debug error: too many placeholders in %#v for %d args]",
sql, len(args))
}
b.WriteString(sql[:p])
fmt.Fprintf(b, "'%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(
"[Debug error: not enough placeholders in %#v for %d args]",
sql, len(args))
}
// "append" any remaning sql that won't need interpolating
b.WriteString(sql)
return b.String()
}