Skip to content

Commit fc7c6dc

Browse files
authored
fix: handling named parameter in string literals (#31)
1 parent 332c56a commit fc7c6dc

File tree

2 files changed

+74
-2
lines changed

2 files changed

+74
-2
lines changed

named.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,14 +332,58 @@ func compileNamedQuery(qs []byte, bindType int) (query string, names []string, e
332332
names = make([]string, 0, 10)
333333
rebound := make([]byte, 0, len(qs))
334334

335+
// the type of quote (' or ") that started the string literal
336+
var quoteLeft byte
337+
inString := false
338+
var escaped bool
335339
inName := false
336340
last := len(qs) - 1
337341
currentVar := 1
338342
name := make([]byte, 0, 10)
339343

340344
for i, b := range qs {
341-
// a ':' while we're in a name is an error
342-
if b == ':' {
345+
if b == '\'' || b == '"' {
346+
// start of a string literal
347+
if !inString {
348+
inString = true
349+
quoteLeft = b
350+
rebound = append(rebound, b)
351+
352+
continue
353+
}
354+
355+
// ignore the quote if it is escaped
356+
if i > 0 && qs[i-1] == '\\' {
357+
rebound = append(rebound, b)
358+
continue
359+
}
360+
361+
// end of the string literal if matching quote is found
362+
if quoteLeft == b {
363+
inString = false
364+
rebound = append(rebound, b)
365+
continue
366+
}
367+
368+
// handle other quotes inside the string literal (ex: "'name'" or '"name"')
369+
rebound = append(rebound, b)
370+
continue
371+
372+
// a ':' while we're in a name is an error
373+
} else if b == ':' {
374+
if inString {
375+
// mark as escaped if '::' sequence is found
376+
if i > 0 && qs[i-1] == ':' && !escaped {
377+
escaped = true
378+
continue
379+
}
380+
381+
// if not escaped, reset the flag and append colon as it's part of the string
382+
rebound = append(rebound, b)
383+
escaped = false
384+
continue
385+
}
386+
343387
// if this is the second ':' in a '::' escape sequence, append a ':'
344388
if inName && i > 0 && qs[i-1] == ':' {
345389
rebound = append(rebound, ':')
@@ -402,6 +446,10 @@ func compileNamedQuery(qs []byte, bindType int) (query string, names []string, e
402446
}
403447
}
404448

449+
if inString {
450+
return query, names, errors.New("string literal not closed, missing terminating quote")
451+
}
452+
405453
return string(rebound), names, err
406454
}
407455

named_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,30 @@ func TestCompileQuery(t *testing.T) {
5353
T: `SELECT @name := "name", @p1, @p2, @p3`,
5454
V: []string{"age", "first", "last"},
5555
},
56+
{
57+
Q: `SELECT * FROM users WHERE id = :id AND name = ':name'`,
58+
R: `SELECT * FROM users WHERE id = ? AND name = ':name'`,
59+
D: `SELECT * FROM users WHERE id = $1 AND name = ':name'`,
60+
T: `SELECT * FROM users WHERE id = @p1 AND name = ':name'`,
61+
N: `SELECT * FROM users WHERE id = :id AND name = ':name'`,
62+
V: []string{"id"},
63+
},
64+
{
65+
Q: `SELECT * FROM users WHERE id = :id AND name = '":name"'`,
66+
R: `SELECT * FROM users WHERE id = ? AND name = '":name"'`,
67+
D: `SELECT * FROM users WHERE id = $1 AND name = '":name"'`,
68+
T: `SELECT * FROM users WHERE id = @p1 AND name = '":name"'`,
69+
N: `SELECT * FROM users WHERE id = :id AND name = '":name"'`,
70+
V: []string{"id"},
71+
},
72+
{
73+
Q: `SELECT * FROM users WHERE id = :id AND name = '\':name\''`,
74+
R: `SELECT * FROM users WHERE id = ? AND name = '\':name\''`,
75+
D: `SELECT * FROM users WHERE id = $1 AND name = '\':name\''`,
76+
T: `SELECT * FROM users WHERE id = @p1 AND name = '\':name\''`,
77+
N: `SELECT * FROM users WHERE id = :id AND name = '\':name\''`,
78+
V: []string{"id"},
79+
},
5680
/* This unicode awareness test sadly fails, because of our byte-wise worldview.
5781
* We could certainly iterate by Rune instead, though it's a great deal slower,
5882
* it's probably the RightWay(tm)

0 commit comments

Comments
 (0)