Skip to content

Commit 0d83955

Browse files
authored
Support delimiter for mysql-tester (#135)
1 parent 314107b commit 0d83955

File tree

3 files changed

+185
-82
lines changed

3 files changed

+185
-82
lines changed

src/main.go

+75-32
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const (
7272
type query struct {
7373
firstWord string
7474
Query string
75+
delimiter string
7576
Line int
7677
tp int
7778
}
@@ -148,6 +149,9 @@ type tester struct {
148149

149150
// replace output result through --replace_regex /\.dll/.so/
150151
replaceRegex []*ReplaceRegex
152+
153+
// the delimter for TiDB, default value is ";"
154+
delimiter string
151155
}
152156

153157
func newTester(name string) *tester {
@@ -161,6 +165,7 @@ func newTester(name string) *tester {
161165
t.enableWarning = false
162166
t.enableConcurrent = false
163167
t.enableInfo = false
168+
t.delimiter = ";"
164169

165170
return t
166171
}
@@ -462,10 +467,7 @@ func (t *tester) Run() error {
462467
t.replaceColumn = append(t.replaceColumn, ReplaceColumn{col: colNr, replace: []byte(cols[i+1])})
463468
}
464469
case Q_CONNECT:
465-
q.Query = strings.TrimSpace(q.Query)
466-
if q.Query[len(q.Query)-1] == ';' {
467-
q.Query = q.Query[:len(q.Query)-1]
468-
}
470+
q.Query = strings.TrimSuffix(strings.TrimSpace(q.Query), q.delimiter)
469471
q.Query = q.Query[1 : len(q.Query)-1]
470472
args := strings.Split(q.Query, ",")
471473
for i := range args {
@@ -476,16 +478,10 @@ func (t *tester) Run() error {
476478
}
477479
t.addConnection(args[0], args[1], args[2], args[3], args[4])
478480
case Q_CONNECTION:
479-
q.Query = strings.TrimSpace(q.Query)
480-
if q.Query[len(q.Query)-1] == ';' {
481-
q.Query = q.Query[:len(q.Query)-1]
482-
}
481+
q.Query = strings.TrimSuffix(strings.TrimSpace(q.Query), q.delimiter)
483482
t.switchConnection(q.Query)
484483
case Q_DISCONNECT:
485-
q.Query = strings.TrimSpace(q.Query)
486-
if q.Query[len(q.Query)-1] == ';' {
487-
q.Query = q.Query[:len(q.Query)-1]
488-
}
484+
q.Query = strings.TrimSuffix(strings.TrimSpace(q.Query), q.delimiter)
489485
t.disconnect(q.Query)
490486
case Q_LET:
491487
q.Query = strings.TrimSpace(q.Query)
@@ -622,7 +618,7 @@ func (t *tester) concurrentExecute(querys []query, wg *sync.WaitGroup, errOccure
622618
return
623619
}
624620

625-
err := tt.stmtExecute(query.Query)
621+
err := tt.stmtExecute(query)
626622
if err != nil && len(t.expectedErrs) > 0 {
627623
for _, tStr := range t.expectedErrs {
628624
if strings.Contains(err.Error(), tStr) {
@@ -650,43 +646,90 @@ func (t *tester) loadQueries() ([]query, error) {
650646

651647
seps := bytes.Split(data, []byte("\n"))
652648
queries := make([]query, 0, len(seps))
653-
newStmt := true
649+
buffer := ""
654650
for i, v := range seps {
655651
v := bytes.TrimSpace(v)
656652
s := string(v)
657653
// we will skip # comment here
658654
if strings.HasPrefix(s, "#") {
659-
newStmt = true
655+
if len(buffer) != 0 {
656+
return nil, errors.Errorf("Has remained message(%s) before COMMENTS", buffer)
657+
}
660658
continue
661659
} else if strings.HasPrefix(s, "--") {
662-
queries = append(queries, query{Query: s, Line: i + 1})
663-
newStmt = true
660+
if len(buffer) != 0 {
661+
return nil, errors.Errorf("Has remained message(%s) before COMMANDS", buffer)
662+
}
663+
q, err := ParseQuery(query{Query: s, Line: i + 1, delimiter: t.delimiter})
664+
if err != nil {
665+
return nil, err
666+
}
667+
if q == nil {
668+
continue
669+
}
670+
if q.tp == Q_DELIMITER {
671+
tokens := strings.Split(strings.TrimSpace(q.Query), " ")
672+
if len(tokens) == 0 {
673+
return nil, errors.Errorf("DELIMITER must be followed by a 'delimiter' character or string")
674+
}
675+
t.delimiter = tokens[0]
676+
} else {
677+
queries = append(queries, *q)
678+
}
679+
continue
680+
} else if strings.HasPrefix(strings.ToLower(strings.TrimSpace(s)), "delimiter ") {
681+
if len(buffer) != 0 {
682+
return nil, errors.Errorf("Has remained message(%s) before DELIMITER COMMAND", buffer)
683+
}
684+
tokens := strings.Split(strings.TrimSpace(s), " ")
685+
if len(tokens) <= 1 {
686+
return nil, errors.Errorf("DELIMITER must be followed by a 'delimiter' character or string")
687+
}
688+
t.delimiter = tokens[1]
664689
continue
665690
} else if len(s) == 0 {
666691
continue
667692
}
668693

669-
if newStmt {
670-
queries = append(queries, query{Query: s, Line: i + 1})
671-
} else {
672-
lastQuery := queries[len(queries)-1]
673-
lastQuery = query{Query: fmt.Sprintf("%s\n%s", lastQuery.Query, s), Line: lastQuery.Line}
674-
queries[len(queries)-1] = lastQuery
694+
if len(buffer) != 0 {
695+
buffer += "\n"
675696
}
697+
buffer += s
698+
for {
699+
idx := strings.LastIndex(buffer, t.delimiter)
700+
if idx == -1 {
701+
break
702+
}
676703

677-
// if the line has a ; in the end, we will treat new line as the new statement.
678-
newStmt = strings.HasSuffix(s, ";")
704+
queryStr := buffer[:idx+len(t.delimiter)]
705+
buffer = buffer[idx+len(t.delimiter):]
706+
q, err := ParseQuery(query{Query: strings.TrimSpace(queryStr), Line: i + 1, delimiter: t.delimiter})
707+
if err != nil {
708+
return nil, err
709+
}
710+
if q == nil {
711+
continue
712+
}
713+
queries = append(queries, *q)
714+
}
715+
// If has remained comments, ignore them.
716+
if len(buffer) != 0 && strings.HasPrefix(strings.TrimSpace(buffer), "#") {
717+
buffer = ""
718+
}
679719
}
680-
681-
return ParseQueries(queries...)
720+
if len(buffer) != 0 {
721+
return nil, errors.Errorf("Has remained text(%s) in file", buffer)
722+
}
723+
return queries, nil
682724
}
683725

684-
func (t *tester) stmtExecute(query string) (err error) {
726+
func (t *tester) stmtExecute(query query) (err error) {
685727
if t.enableQueryLog {
686-
t.buf.WriteString(query)
728+
t.buf.WriteString(query.Query)
687729
t.buf.WriteString("\n")
688730
}
689-
return t.executeStmt(query)
731+
732+
return t.executeStmt(strings.TrimSuffix(query.Query, query.delimiter))
690733
}
691734

692735
// checkExpectedError check if error was expected
@@ -784,7 +827,7 @@ func (t *tester) execute(query query) error {
784827
}
785828

786829
offset := t.buf.Len()
787-
err := t.stmtExecute(query.Query)
830+
err := t.stmtExecute(query)
788831

789832
err = t.checkExpectedError(query, err)
790833
if err != nil {
@@ -967,7 +1010,7 @@ func (t *tester) executeStmt(query string) error {
9671010
}
9681011

9691012
if t.enableWarning {
970-
raw, err := t.curr.conn.QueryContext(context.Background(), "show warnings;")
1013+
raw, err := t.curr.conn.QueryContext(context.Background(), "show warnings")
9711014
if err != nil {
9721015
return errors.Trace(err)
9731016
}

src/query.go

+42-41
Original file line numberDiff line numberDiff line change
@@ -126,56 +126,57 @@ const (
126126
Q_EMPTY_LINE
127127
)
128128

129-
// ParseQueries parses an array of string into an array of query object.
129+
// ParseQuery parses an array of string into an array of query object.
130130
// Note: a query statement may reside in several lines.
131-
func ParseQueries(qs ...query) ([]query, error) {
132-
queries := make([]query, 0, len(qs))
133-
for _, rs := range qs {
134-
realS := rs.Query
135-
s := rs.Query
136-
q := query{}
137-
q.tp = Q_UNKNOWN
138-
q.Line = rs.Line
139-
// a valid query's length should be at least 3.
140-
if len(s) < 3 {
141-
continue
142-
}
143-
// we will skip #comment and line with zero characters here
144-
if s[0] == '#' {
145-
q.tp = Q_COMMENT
146-
} else if s[0:2] == "--" {
147-
q.tp = Q_COMMENT_WITH_COMMAND
148-
if s[2] == ' ' {
149-
s = s[3:]
150-
} else {
151-
s = s[2:]
152-
}
153-
} else if s[0] == '\n' {
154-
q.tp = Q_EMPTY_LINE
131+
func ParseQuery(rs query) (*query, error) {
132+
realS := rs.Query
133+
s := rs.Query
134+
q := query{delimiter: rs.delimiter, Line: rs.Line}
135+
q.tp = Q_UNKNOWN
136+
// a valid query's length should be at least 3.
137+
if len(s) < 3 {
138+
return nil, nil
139+
}
140+
// we will skip #comment and line with zero characters here
141+
if s[0] == '#' {
142+
q.tp = Q_COMMENT
143+
} else if s[0:2] == "--" {
144+
q.tp = Q_COMMENT_WITH_COMMAND
145+
if s[2] == ' ' {
146+
s = s[3:]
147+
} else {
148+
s = s[2:]
155149
}
150+
} else if s[0] == '\n' {
151+
q.tp = Q_EMPTY_LINE
152+
}
156153

157-
if q.tp != Q_COMMENT {
158-
// Calculate first word length(the command), terminated
159-
// by 'space' , '(' or 'delimiter'
160-
var i int
161-
for i = 0; i < len(s) && s[i] != '(' && s[i] != ' ' && s[i] != ';' && s[i] != '\n'; i++ {
154+
if q.tp != Q_COMMENT {
155+
// Calculate first word length(the command), terminated
156+
// by 'space' , '(' and delimiter
157+
var i int
158+
for i = 0; i < len(s); i++ {
159+
if s[i] == '(' || s[i] == ' ' || s[i] == '\n' {
160+
break
162161
}
163-
if i > 0 {
164-
q.firstWord = s[:i]
162+
if i+len(rs.delimiter) <= len(s) && s[i:i+len(rs.delimiter)] == rs.delimiter {
163+
break
165164
}
166-
s = s[i:]
165+
}
166+
if i > 0 {
167+
q.firstWord = s[:i]
168+
}
169+
s = s[i:]
167170

168-
q.Query = s
169-
if q.tp == Q_UNKNOWN || q.tp == Q_COMMENT_WITH_COMMAND {
170-
if err := q.getQueryType(realS); err != nil {
171-
return nil, err
172-
}
171+
q.Query = s
172+
if q.tp == Q_UNKNOWN || q.tp == Q_COMMENT_WITH_COMMAND {
173+
if err := q.getQueryType(realS); err != nil {
174+
return nil, err
173175
}
174176
}
175-
176-
queries = append(queries, q)
177177
}
178-
return queries, nil
178+
179+
return &q, nil
179180
}
180181

181182
// for a single query, it has some prefix. Prefix mapps to a query type.

src/query_test.go

+68-9
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ package main
1515

1616
import (
1717
"fmt"
18+
"os"
19+
"path/filepath"
1820
"testing"
21+
22+
"github.com/stretchr/testify/assert"
1923
)
2024

2125
func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
@@ -31,28 +35,83 @@ func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
3135
func TestParseQueryies(t *testing.T) {
3236
sql := "select * from t;"
3337

34-
if q, err := ParseQueries(query{Query: sql, Line: 1}); err == nil {
35-
assertEqual(t, q[0].tp, Q_QUERY, fmt.Sprintf("Expected: %d, got: %d", Q_QUERY, q[0].tp))
36-
assertEqual(t, q[0].Query, sql, fmt.Sprintf("Expected: %s, got: %s", sql, q[0].Query))
38+
if q, err := ParseQuery(query{Query: sql, Line: 1, delimiter: ";"}); err == nil {
39+
assertEqual(t, q.tp, Q_QUERY, fmt.Sprintf("Expected: %d, got: %d", Q_QUERY, q.tp))
40+
assertEqual(t, q.Query, sql, fmt.Sprintf("Expected: %s, got: %s", sql, q.Query))
3741
} else {
3842
t.Fatalf("error is not nil. %v", err)
3943
}
4044

4145
sql = "--sorted_result select * from t;"
42-
if q, err := ParseQueries(query{Query: sql, Line: 1}); err == nil {
43-
assertEqual(t, q[0].tp, Q_SORTED_RESULT, "sorted_result")
44-
assertEqual(t, q[0].Query, "select * from t;", fmt.Sprintf("Expected: '%s', got '%s'", "select * from t;", q[0].Query))
46+
if q, err := ParseQuery(query{Query: sql, Line: 1, delimiter: ";"}); err == nil {
47+
assertEqual(t, q.tp, Q_SORTED_RESULT, "sorted_result")
48+
assertEqual(t, q.Query, "select * from t;", fmt.Sprintf("Expected: '%s', got '%s'", "select * from t;", q.Query))
4549
} else {
4650
t.Fatalf("error is not nil. %s", err)
4751
}
4852

4953
// invalid comment command style
5054
sql = "--abc select * from t;"
51-
_, err := ParseQueries(query{Query: sql, Line: 1})
55+
_, err := ParseQuery(query{Query: sql, Line: 1, delimiter: ";"})
5256
assertEqual(t, err, ErrInvalidCommand, fmt.Sprintf("Expected: %v, got %v", ErrInvalidCommand, err))
5357

5458
sql = "--let $foo=`SELECT 1`"
55-
if q, err := ParseQueries(query{Query: sql, Line: 1}); err == nil {
56-
assertEqual(t, q[0].tp, Q_LET, fmt.Sprintf("Expected: %d, got: %d", Q_LET, q[0].tp))
59+
if q, err := ParseQuery(query{Query: sql, Line: 1, delimiter: ";"}); err == nil {
60+
assertEqual(t, q.tp, Q_LET, fmt.Sprintf("Expected: %d, got: %d", Q_LET, q.tp))
61+
}
62+
}
63+
64+
func TestLoadQueries(t *testing.T) {
65+
dir := t.TempDir()
66+
err := os.Chdir(dir)
67+
assert.NoError(t, err)
68+
69+
err = os.Mkdir("t", 0755)
70+
assert.NoError(t, err)
71+
72+
testCases := []struct {
73+
input string
74+
queries []query
75+
}{
76+
{
77+
input: "delimiter |\n do something; select something; |\n delimiter ; \nselect 1;",
78+
queries: []query{
79+
{Query: "do something; select something; |", tp: Q_QUERY, delimiter: "|"},
80+
{Query: "select 1;", tp: Q_QUERY, delimiter: ";"},
81+
},
82+
},
83+
{
84+
input: "delimiter |\ndrop procedure if exists scopel\ncreate procedure scope(a int, b float)\nbegin\ndeclare b int;\ndeclare c float;\nbegin\ndeclare c int;\nend;\nend |\ndrop procedure scope|\ndelimiter ;\n",
85+
queries: []query{
86+
{Query: "drop procedure if exists scopel\ncreate procedure scope(a int, b float)\nbegin\ndeclare b int;\ndeclare c float;\nbegin\ndeclare c int;\nend;\nend |", tp: Q_QUERY, delimiter: "|"},
87+
{Query: "drop procedure scope|", tp: Q_QUERY, delimiter: "|"},
88+
},
89+
},
90+
{
91+
input: "--error 1054\nselect 1;",
92+
queries: []query{
93+
{Query: " 1054", tp: Q_ERROR, delimiter: ";"},
94+
{Query: "select 1;", tp: Q_QUERY, delimiter: ";"},
95+
},
96+
},
97+
}
98+
99+
for _, testCase := range testCases {
100+
fileName := filepath.Join("t", "test.test")
101+
f, err := os.Create(fileName)
102+
assert.NoError(t, err)
103+
104+
f.WriteString(testCase.input)
105+
f.Close()
106+
107+
test := newTester("test")
108+
queries, err := test.loadQueries()
109+
assert.NoError(t, err)
110+
assert.Len(t, queries, len(testCase.queries))
111+
for i, query := range testCase.queries {
112+
assert.Equal(t, queries[i].Query, query.Query)
113+
assert.Equal(t, queries[i].tp, query.tp)
114+
assert.Equal(t, queries[i].delimiter, query.delimiter)
115+
}
57116
}
58117
}

0 commit comments

Comments
 (0)