forked from mdcnz/sqlanywhere
/
args.go
103 lines (82 loc) · 1.64 KB
/
args.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
package sqlanywhere
import "strings"
type arg struct {
start int
end int
}
const (
quote = '\''
escape = '\\'
param = ':'
placeholder = '?'
)
func isArg(b rune) bool {
return b >= '0' && b <= '9' ||
b >= 'A' && b <= 'Z' ||
b >= 'a' && b <= 'z' ||
b == '_'
}
type split struct {
sql []rune
args []*arg
}
//splitNamed finds named parameters, returning the sql with replacement placeholders
//and a slice of the named parameters.
func splitNamed(sql string) (string, []string) {
s := &split{
args: []*arg{},
sql: []rune(sql),
}
var a *arg
quoted := false
for i, c := range s.sql {
var prior rune
if i > 0 {
prior = s.sql[i-1]
}
if c == quote && prior != escape {
quoted = !quoted
}
if quoted {
continue
}
var next rune
if i < len(s.sql)-1 {
next = s.sql[i+1]
}
if c == param && isArg(next) {
a = &arg{start: i}
continue
}
if a != nil {
if !isArg(next) {
a.end = i + 1
s.args = append(s.args, a)
a = nil
}
}
}
return s.collapse(), s.arguments()
}
//collapse returns sql with the named parameters collapsed into placeholders.
func (s *split) collapse() string {
b := strings.Builder{}
from := 0
for _, a := range s.args {
b.WriteString(string(s.sql[from:a.start]))
b.WriteByte(placeholder)
from = a.end
}
if from < len(s.sql) {
b.WriteString(string(s.sql[from:]))
}
return b.String()
}
//arguments returns the argument names (excluding the argument token)
func (s *split) arguments() []string {
arguments := make([]string, len(s.args))
for i, a := range s.args {
arguments[i] = string(s.sql[a.start+1 : a.end])
}
return arguments
}