Skip to content

Commit 23c5a53

Browse files
committed
fix quote escaping
1 parent 87fca4d commit 23c5a53

File tree

2 files changed

+25
-16
lines changed

2 files changed

+25
-16
lines changed

sql/tokenizer.go

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,20 @@ func tokenize(s string) ([]token, error) {
133133
// + and - might be binary or unary. let the lexer figure that out
134134
res = append(res, stoken(int(c), string(c)))
135135
case '\'':
136-
bt, bl := readSingleQuoted(s[i+1:])
136+
bt, bl := readQuoted('\'', s[i+1:], true)
137137
if bl == -1 {
138138
return res, errors.New("no terminating ' found")
139139
}
140140
res = append(res, stoken(tLiteral, bt))
141141
i += bl
142142
case '"', '`', '[':
143143
close := c
144+
allowEscape := true
144145
if close == '[' {
145146
close = ']'
147+
allowEscape = false
146148
}
147-
bt, bl := readQuoted(close, s[i+1:])
149+
bt, bl := readQuoted(close, s[i+1:], allowEscape)
148150
if bl == -1 {
149151
return res, fmt.Errorf("no terminating %q found", close)
150152
}
@@ -225,23 +227,18 @@ loop:
225227
return ntoken(n), len(s)
226228
}
227229

228-
// parse a 'bareword'. Opening ' is already gone. No escape sequences.
229-
func readSingleQuoted(s string) (string, int) {
230-
for i, r := range s {
231-
switch r {
232-
case '\'':
233-
return s[:i], i + 1
234-
default:
235-
}
236-
}
237-
return "", -1
238-
}
239-
240-
// parse a quoted string until `close`. Opening char is already gone. No escape sequences.
241-
func readQuoted(close rune, s string) (string, int) {
230+
// parse a quoted string until `close`. Opening char is already gone.
231+
// > A single quote within the string can be encoded by putting two single
232+
// > quotes in a row - as in Pascal. C-style escapes using the backslash
233+
// > character are not supported because they are not standard SQL.
234+
func readQuoted(close rune, s string, allowEscape bool) (string, int) {
242235
for i, r := range s {
243236
switch r {
244237
case close:
238+
if allowEscape && len(s) > i+1 && rune(s[i+1]) == close {
239+
ss, si := readQuoted(close, s[i+2:], allowEscape)
240+
return s[:i+1] + ss, i + si + 2
241+
}
245242
return s[:i], i + 1
246243
default:
247244
}

sql/tokenizer_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,18 @@ func TestTokens(t *testing.T) {
137137
stoken(tLiteral, "lit 1"),
138138
},
139139
)
140+
testOK(
141+
`'foo''bar' '''' '\n' "fo""o"`,
142+
[]token{
143+
stoken(tLiteral, "foo'bar"),
144+
stoken(tLiteral, "'"),
145+
stoken(tLiteral, `\n`),
146+
stoken(tIdentifier, `fo"o`),
147+
},
148+
)
149+
testError(`'foo''`, errors.New("no terminating ' found"))
150+
testError(`[foo]]]`, errors.New("unexpected char at pos:5: ']'"))
151+
140152
testOK(
141153
"|| * / % + - << >> & | < <= > >= = == != <> ~",
142154
[]token{

0 commit comments

Comments
 (0)