Skip to content

Commit eed9c9b

Browse files
committed
Added support to pass limit values as bind variables, tests
1 parent e0f7bee commit eed9c9b

6 files changed

+121
-4
lines changed

clickhouse_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,42 @@ func Test_Select(t *testing.T) {
344344
assert.Equal(t, int(3), count)
345345
}
346346
}
347+
if rows, err := connect.Query("SELECT id FROM clickhouse_test_select ORDER BY id LIMIT ?", 1); assert.NoError(t, err) {
348+
i := 0
349+
for rows.Next() {
350+
var (
351+
id int32
352+
)
353+
if err := rows.Scan(&id); assert.NoError(t, err) {
354+
if i == 0 {
355+
assert.Equal(t, id, int32(1))
356+
} else {
357+
t.Error("Should return exactly one record")
358+
}
359+
}
360+
i++
361+
}
362+
rows.Close()
363+
}
364+
if rows, err := connect.Query("SELECT id FROM clickhouse_test_select ORDER BY id LIMIT ?,?", 1, 2); assert.NoError(t, err) {
365+
i := 0
366+
for rows.Next() {
367+
var (
368+
id int32
369+
)
370+
if err := rows.Scan(&id); assert.NoError(t, err) {
371+
if i == 0 {
372+
assert.Equal(t, id, int32(2))
373+
} else if i == 1 {
374+
assert.Equal(t, id, int32(3))
375+
} else {
376+
t.Error("Should return exactly two records")
377+
}
378+
}
379+
i++
380+
}
381+
rows.Close()
382+
}
347383
}
348384

349385
{

helpers.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,13 @@ func (datetime DateTime) convert() time.Time {
4646
}
4747

4848
func numInput(query string) int {
49+
4950
var (
5051
count int
5152
args = make(map[string]struct{})
5253
reader = bytes.NewReader([]byte(query))
5354
quote, keyword bool
55+
limit = newMatcher("limit")
5456
)
5557
for {
5658
if char, _, err := reader.ReadRune(); err == nil {
@@ -81,7 +83,11 @@ func numInput(query string) int {
8183
char == '[':
8284
keyword = true
8385
default:
84-
keyword = keyword && (char == ' ' || char == '\t' || char == '\n')
86+
if limit.matchRune(char) {
87+
keyword = true
88+
} else {
89+
keyword = keyword && (char == ' ' || char == '\t' || char == '\n')
90+
}
8591
}
8692
} else {
8793
break

helpers_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ func Test_NumInput(t *testing.T) {
2727
?
2828
)
2929
`: 3,
30-
"SELECT * from EXAMPLE LIMIT ?": 1,
31-
"SELECT * from EXAMPLE LIMIT ? OFFSET ?": 2,
30+
"SELECT * from EXAMPLE LIMIT ?": 1,
31+
"SELECT * from EXAMPLE LIMIT ?, ?": 2,
3232
} {
3333
assert.Equal(t, num, numInput(query), query)
3434
}

stmt.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func (stmt *stmt) bind(args []driver.NamedValue) string {
106106
buf bytes.Buffer
107107
index int
108108
keyword bool
109+
limit = newMatcher("limit")
109110
)
110111
switch {
111112
case stmt.NumInput() != 0:
@@ -140,7 +141,11 @@ func (stmt *stmt) bind(args []driver.NamedValue) string {
140141
char == '[':
141142
keyword = true
142143
default:
143-
keyword = keyword && (char == ' ' || char == '\t' || char == '\n')
144+
if limit.matchRune(char) {
145+
keyword = true
146+
} else {
147+
keyword = keyword && (char == ' ' || char == '\t' || char == '\n')
148+
}
144149
}
145150
buf.WriteRune(char)
146151
}

word_matcher.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package clickhouse
2+
3+
import (
4+
"strings"
5+
"unicode"
6+
)
7+
8+
// wordMatcher is a simple automata to match a single word (case insensitive)
9+
type wordMatcher struct {
10+
word []rune
11+
position uint8
12+
}
13+
14+
// newMatcher returns matcher for word needle
15+
func newMatcher(needle string) *wordMatcher {
16+
return &wordMatcher{word: []rune(strings.ToUpper(needle)),
17+
position: 0}
18+
}
19+
20+
func (m *wordMatcher) matchRune(r rune) bool {
21+
if m.word[m.position] == unicode.ToUpper(r) {
22+
if m.position == uint8(len(m.word)-1) {
23+
m.position = 0
24+
return true
25+
}
26+
m.position++
27+
} else {
28+
m.position = 0
29+
}
30+
return false
31+
}

word_matcher_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package clickhouse
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
type wmTest struct {
10+
haystack string
11+
needle string
12+
expect bool
13+
}
14+
15+
func checkMatch(haystack, needle string) bool {
16+
m := newMatcher(needle)
17+
for _, r := range []rune(haystack) {
18+
if m.matchRune(r) {
19+
return true
20+
}
21+
}
22+
return false
23+
}
24+
25+
func TestWordMatcher(t *testing.T) {
26+
27+
table := []wmTest{
28+
wmTest{"select * from test", "select", true},
29+
wmTest{"select * from test", "*", true},
30+
wmTest{"select * from test", "elect", true},
31+
wmTest{"select * from test", "zelect", false},
32+
wmTest{"select * from test", "sElEct", true},
33+
}
34+
35+
for _, test := range table {
36+
assert.Equal(t, checkMatch(test.haystack, test.needle), test.expect, test.haystack)
37+
}
38+
39+
}

0 commit comments

Comments
 (0)