Skip to content

Commit 92f68e8

Browse files
authored
chore: remove regex-based BATCH statements (#509)
Replace the regex-based BATCH statements with statements that are parsed using the token-based parser. This will allow us to remove the entire regex-based parsing and related code.
1 parent c8d811d commit 92f68e8

File tree

4 files changed

+180
-41
lines changed

4 files changed

+180
-41
lines changed

client_side_statements_json.go

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -41,40 +41,6 @@ var jsonFile = `{
4141
"regex": "(?is)\\A\\s*show\\s+variable\\s+read_only_staleness\\s*\\z",
4242
"method": "statementShowReadOnlyStaleness",
4343
"exampleStatements": ["show variable read_only_staleness"]
44-
},
45-
{
46-
"name": "START BATCH DDL",
47-
"executorName": "ClientSideStatementNoParamExecutor",
48-
"resultType": "NO_RESULT",
49-
"regex": "(?is)\\A\\s*(?:start)(?:\\s+batch)(?:\\s+ddl)\\s*\\z",
50-
"method": "statementStartBatchDdl",
51-
"exampleStatements": ["start batch ddl"]
52-
},
53-
{
54-
"name": "START BATCH DML",
55-
"executorName": "ClientSideStatementNoParamExecutor",
56-
"resultType": "NO_RESULT",
57-
"regex": "(?is)\\A\\s*(?:start)(?:\\s+batch)(?:\\s+dml)\\s*\\z",
58-
"method": "statementStartBatchDml",
59-
"exampleStatements": ["start batch dml"]
60-
},
61-
{
62-
"name": "RUN BATCH",
63-
"executorName": "ClientSideStatementNoParamExecutor",
64-
"resultType": "NO_RESULT",
65-
"regex": "(?is)\\A\\s*(?:run)(?:\\s+batch)\\s*\\z",
66-
"method": "statementRunBatch",
67-
"exampleStatements": ["run batch"],
68-
"examplePrerequisiteStatements": ["start batch ddl"]
69-
},
70-
{
71-
"name": "ABORT BATCH",
72-
"executorName": "ClientSideStatementNoParamExecutor",
73-
"resultType": "NO_RESULT",
74-
"regex": "(?is)\\A\\s*(?:abort)(?:\\s+batch)\\s*\\z",
75-
"method": "statementAbortBatch",
76-
"exampleStatements": ["abort batch"],
77-
"examplePrerequisiteStatements": ["start batch ddl"]
7844
},
7945
{
8046
"name": "SET RETRY_ABORTS_INTERNALLY = TRUE|FALSE",

statement_parser.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ var dropStatements = map[string]bool{"DROP": true}
5555
var showStatements = map[string]bool{"SHOW": true}
5656
var setStatements = map[string]bool{"SET": true}
5757
var resetStatements = map[string]bool{"RESET": true}
58+
var startStatements = map[string]bool{"START": true}
59+
var runStatements = map[string]bool{"RUN": true}
60+
var abortStatements = map[string]bool{"ABORT": true}
5861

5962
func union(m1 map[string]bool, m2 map[string]bool) map[string]bool {
6063
res := make(map[string]bool, len(m1)+len(m2))
@@ -291,6 +294,20 @@ func (p *simpleParser) eatLiteral() (literal, error) {
291294
return literal{value: value}, nil
292295
}
293296

297+
// eatKeywords eats the given keywords in the order of the slice.
298+
// Returns true and updates the position of the parser if all the keywords were successfully eaten.
299+
// Returns false and does not update the position of the parser if not all keywords could be eaten.
300+
func (p *simpleParser) eatKeywords(keywords []string) bool {
301+
startPos := p.pos
302+
for _, keyword := range keywords {
303+
if _, ok := p.eatKeyword(keyword); !ok {
304+
p.pos = startPos
305+
return false
306+
}
307+
}
308+
return true
309+
}
310+
294311
// eatKeyword eats the given keyword at the current position of the parser if it exists.
295312
//
296313
// Returns the actual keyword that was read and true if the keyword is found, and updates the position of the parser.
@@ -1008,6 +1025,18 @@ func isResetStatementKeyword(keyword string) bool {
10081025
return isStatementKeyword(keyword, resetStatements)
10091026
}
10101027

1028+
func isStartStatementKeyword(keyword string) bool {
1029+
return isStatementKeyword(keyword, startStatements)
1030+
}
1031+
1032+
func isRunStatementKeyword(keyword string) bool {
1033+
return isStatementKeyword(keyword, runStatements)
1034+
}
1035+
1036+
func isAbortStatementKeyword(keyword string) bool {
1037+
return isStatementKeyword(keyword, abortStatements)
1038+
}
1039+
10111040
func isStatementKeyword(keyword string, keywords map[string]bool) bool {
10121041
_, ok := keywords[keyword]
10131042
return ok

statement_parser_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,31 +1559,31 @@ func TestParseClientSideStatement(t *testing.T) {
15591559
{
15601560
name: "Start DDL batch",
15611561
input: "START BATCH DDL",
1562-
want: "START BATCH DDL",
1562+
want: "START BATCH",
15631563
exec: true,
15641564
},
15651565
{
15661566
name: "Start DDL batch using line feeds",
15671567
input: "START\nBATCH\nDDL",
1568-
want: "START BATCH DDL",
1568+
want: "START BATCH",
15691569
exec: true,
15701570
},
15711571
{
15721572
name: "Start DDL batch lower case",
15731573
input: "start batch ddl",
1574-
want: "START BATCH DDL",
1574+
want: "START BATCH",
15751575
exec: true,
15761576
},
15771577
{
15781578
name: "Start DDL batch with extra spaces",
15791579
input: "\tSTART BATCH\n\nDDL",
1580-
want: "START BATCH DDL",
1580+
want: "START BATCH",
15811581
exec: true,
15821582
},
15831583
{
15841584
name: "Start DML batch",
15851585
input: "START BATCH DML",
1586-
want: "START BATCH DML",
1586+
want: "START BATCH",
15871587
exec: true,
15881588
},
15891589
{
@@ -1638,11 +1638,11 @@ func TestParseClientSideStatement(t *testing.T) {
16381638
got = statement.Name
16391639
}
16401640
if got != tc.want {
1641-
t.Errorf("parseClientSideStatement test failed: %s\nGot: %s\nWant: %s.", tc.name, got, tc.want)
1641+
t.Errorf("parseClientSideStatement test failed: %s\n Got: %s\nWant: %s.", tc.name, got, tc.want)
16421642
}
16431643
if tc.wantParams != "" {
16441644
if g, w := statement.params, tc.wantParams; g != w {
1645-
t.Errorf("params mismatch for %s\nGot: %v\nWant: %v", tc.name, g, w)
1645+
t.Errorf("params mismatch for %s\n Got: %v\nWant: %v", tc.name, g, w)
16461646
}
16471647
}
16481648
})

statements.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ func parseStatement(parser *statementParser, keyword, query string) (parsedState
3030
stmt = &parsedCreateDatabaseStatement{}
3131
} else if isDropKeyword(keyword) && isDropDatabase(parser, query) {
3232
stmt = &parsedDropDatabaseStatement{}
33+
} else if isStartStatementKeyword(keyword) {
34+
stmt = &parsedStartBatchStatement{}
35+
} else if isRunStatementKeyword(keyword) {
36+
stmt = &parsedRunBatchStatement{}
37+
} else if isAbortStatementKeyword(keyword) {
38+
stmt = &parsedAbortBatchStatement{}
3339
} else {
3440
return nil, nil
3541
}
@@ -400,3 +406,141 @@ func (s *parsedDropDatabaseStatement) executableStatement(c *conn) *executableCl
400406
},
401407
}
402408
}
409+
410+
type parsedStartBatchStatement struct {
411+
query string
412+
tp batchType
413+
}
414+
415+
func (s *parsedStartBatchStatement) parse(parser *statementParser, query string) error {
416+
// Parse a statement of the form
417+
// START BATCH {DDL | DML}
418+
sp := &simpleParser{sql: []byte(query), statementParser: parser}
419+
if !sp.eatKeywords([]string{"START", "BATCH"}) {
420+
return status.Error(codes.InvalidArgument, "statement does not start with START BATCH")
421+
}
422+
if _, ok := sp.eatKeyword("DML"); ok {
423+
s.tp = dml
424+
} else if _, ok := sp.eatKeyword("DDL"); ok {
425+
s.tp = ddl
426+
} else {
427+
return status.Errorf(codes.InvalidArgument, "unexpected token at pos %d in %q, expected DML or DDL", sp.pos, sp.sql)
428+
}
429+
if sp.hasMoreTokens() {
430+
return status.Errorf(codes.InvalidArgument, "unexpected tokens at position %d in %q", sp.pos, sp.sql)
431+
}
432+
s.query = query
433+
return nil
434+
}
435+
436+
func (s *parsedStartBatchStatement) execContext(ctx context.Context, c *conn, params string, opts *ExecOptions, args []driver.NamedValue) (driver.Result, error) {
437+
switch s.tp {
438+
case dml:
439+
return c.startBatchDML( /*automatic = */ false)
440+
case ddl:
441+
return c.startBatchDDL()
442+
default:
443+
return nil, status.Errorf(codes.FailedPrecondition, "unknown batch type: %v", s.tp)
444+
}
445+
}
446+
447+
func (s *parsedStartBatchStatement) queryContext(ctx context.Context, c *conn, params string, opts *ExecOptions, args []driver.NamedValue) (driver.Rows, error) {
448+
if _, err := s.execContext(ctx, c, params, opts, args); err != nil {
449+
return nil, err
450+
}
451+
return createEmptyRows(opts), nil
452+
}
453+
454+
func (s *parsedStartBatchStatement) executableStatement(c *conn) *executableClientSideStatement {
455+
return &executableClientSideStatement{
456+
conn: c,
457+
query: s.query,
458+
clientSideStatement: &clientSideStatement{
459+
Name: "START BATCH",
460+
execContext: s.execContext,
461+
queryContext: s.queryContext,
462+
},
463+
}
464+
}
465+
466+
type parsedRunBatchStatement struct {
467+
query string
468+
}
469+
470+
func (s *parsedRunBatchStatement) parse(parser *statementParser, query string) error {
471+
// Parse a statement of the form
472+
// RUN BATCH
473+
sp := &simpleParser{sql: []byte(query), statementParser: parser}
474+
if !sp.eatKeywords([]string{"RUN", "BATCH"}) {
475+
return status.Error(codes.InvalidArgument, "statement does not start with RUN BATCH")
476+
}
477+
if sp.hasMoreTokens() {
478+
return status.Errorf(codes.InvalidArgument, "unexpected tokens at position %d in %q", sp.pos, sp.sql)
479+
}
480+
s.query = query
481+
return nil
482+
}
483+
484+
func (s *parsedRunBatchStatement) execContext(ctx context.Context, c *conn, params string, opts *ExecOptions, args []driver.NamedValue) (driver.Result, error) {
485+
return c.runBatch(ctx)
486+
}
487+
488+
func (s *parsedRunBatchStatement) queryContext(ctx context.Context, c *conn, params string, opts *ExecOptions, args []driver.NamedValue) (driver.Rows, error) {
489+
if _, err := s.execContext(ctx, c, params, opts, args); err != nil {
490+
return nil, err
491+
}
492+
return createEmptyRows(opts), nil
493+
}
494+
495+
func (s *parsedRunBatchStatement) executableStatement(c *conn) *executableClientSideStatement {
496+
return &executableClientSideStatement{
497+
conn: c,
498+
query: s.query,
499+
clientSideStatement: &clientSideStatement{
500+
Name: "RUN BATCH",
501+
execContext: s.execContext,
502+
queryContext: s.queryContext,
503+
},
504+
}
505+
}
506+
507+
type parsedAbortBatchStatement struct {
508+
query string
509+
}
510+
511+
func (s *parsedAbortBatchStatement) parse(parser *statementParser, query string) error {
512+
// Parse a statement of the form
513+
// ABORT BATCH
514+
sp := &simpleParser{sql: []byte(query), statementParser: parser}
515+
if !sp.eatKeywords([]string{"ABORT", "BATCH"}) {
516+
return status.Error(codes.InvalidArgument, "statement does not start with ABORT BATCH")
517+
}
518+
if sp.hasMoreTokens() {
519+
return status.Errorf(codes.InvalidArgument, "unexpected tokens at position %d in %q", sp.pos, sp.sql)
520+
}
521+
s.query = query
522+
return nil
523+
}
524+
525+
func (s *parsedAbortBatchStatement) execContext(ctx context.Context, c *conn, params string, opts *ExecOptions, args []driver.NamedValue) (driver.Result, error) {
526+
return c.abortBatch()
527+
}
528+
529+
func (s *parsedAbortBatchStatement) queryContext(ctx context.Context, c *conn, params string, opts *ExecOptions, args []driver.NamedValue) (driver.Rows, error) {
530+
if _, err := s.execContext(ctx, c, params, opts, args); err != nil {
531+
return nil, err
532+
}
533+
return createEmptyRows(opts), nil
534+
}
535+
536+
func (s *parsedAbortBatchStatement) executableStatement(c *conn) *executableClientSideStatement {
537+
return &executableClientSideStatement{
538+
conn: c,
539+
query: s.query,
540+
clientSideStatement: &clientSideStatement{
541+
Name: "ABORT BATCH",
542+
execContext: s.execContext,
543+
queryContext: s.queryContext,
544+
},
545+
}
546+
}

0 commit comments

Comments
 (0)