Skip to content

Commit 014ce72

Browse files
cmoogkyleconroy
authored andcommitted
improves mysql query metadata parsing (#266)
1 parent 65c5de3 commit 014ce72

File tree

6 files changed

+60
-63
lines changed

6 files changed

+60
-63
lines changed

internal/dinosql/parser.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -340,20 +340,35 @@ func validateQueryName(name string) error {
340340
isLetter := unicode.IsLetter(c) || c == '_'
341341
isDigit := unicode.IsDigit(c)
342342
if i == 0 && !isLetter {
343-
return fmt.Errorf("invalid query name: %q", name)
343+
return fmt.Errorf("invalid query name %q", name)
344344
} else if !(isLetter || isDigit) {
345-
return fmt.Errorf("invalid query name: %q", name)
345+
return fmt.Errorf("invalid query name %q", name)
346346
}
347347
}
348348
return nil
349349
}
350350

351-
func parseMetadata(t string) (string, string, error) {
351+
type CommentSyntax int
352+
353+
const (
354+
CommentSyntaxDash CommentSyntax = iota
355+
CommentSyntaxStar // Note: this is the only style supported by the MySQL sqlparser
356+
CommentSyntaxHash
357+
)
358+
359+
func ParseMetadata(t string, commentStyle CommentSyntax) (string, string, error) {
352360
for _, line := range strings.Split(t, "\n") {
353-
if !strings.HasPrefix(line, "-- name:") {
361+
if commentStyle == CommentSyntaxDash && !strings.HasPrefix(line, "-- name:") {
362+
continue
363+
}
364+
if commentStyle == CommentSyntaxStar && !strings.HasPrefix(line, "/* name:") {
354365
continue
355366
}
356367
part := strings.Split(strings.TrimSpace(line), " ")
368+
369+
if commentStyle == CommentSyntaxStar {
370+
part = part[:len(part)-1] // removes the trailing "*/" element
371+
}
357372
if len(part) == 2 {
358373
return "", "", fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows']: %s", line)
359374
}
@@ -428,7 +443,7 @@ func parseQuery(c core.Catalog, stmt nodes.Node, source string) (*Query, error)
428443
if err := validateFuncCall(&c, raw); err != nil {
429444
return nil, err
430445
}
431-
name, cmd, err := parseMetadata(strings.TrimSpace(rawSQL))
446+
name, cmd, err := ParseMetadata(strings.TrimSpace(rawSQL), CommentSyntaxDash)
432447
if err != nil {
433448
return nil, err
434449
}

internal/dinosql/parser_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func TestParseMetadata(t *testing.T) {
121121
`-- name: CreateFoo :one something`,
122122
`-- name: `,
123123
} {
124-
if _, _, err := parseMetadata(query); err == nil {
124+
if _, _, err := ParseMetadata(query, CommentSyntaxDash); err == nil {
125125
t.Errorf("expected invalid metadata: %q", query)
126126
}
127127
}

internal/mysql/param_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func TestInsertParamSearcher(t *testing.T) {
118118

119119
tests := []testCase{
120120
testCase{
121-
input: "INSERT INTO users (first_name, last_name) VALUES (?, ?)",
121+
input: "/* name: InsertNewUser :exec */\nINSERT INTO users (first_name, last_name) VALUES (?, ?)",
122122
output: []*Param{
123123
&Param{
124124
OriginalName: ":v1",

internal/mysql/parse.go

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"io"
66
"io/ioutil"
77
"path/filepath"
8-
"strings"
98

109
"github.com/davecgh/go-spew/spew"
1110
"github.com/kyleconroy/sqlc/internal/dinosql"
@@ -153,10 +152,14 @@ func (q *Query) parseNameAndCmd() error {
153152
return fmt.Errorf("cannot parse name and cmd from null query")
154153
}
155154
_, comments := sqlparser.SplitMarginComments(q.SQL)
156-
err := q.parseLeadingComment(comments.Leading)
155+
name, cmd, err := dinosql.ParseMetadata(comments.Leading, dinosql.CommentSyntaxStar)
157156
if err != nil {
158-
return fmt.Errorf("failed to parse leading comment %w", err)
157+
return err
158+
} else if name == "" || cmd == "" {
159+
return fmt.Errorf("failed to parse query leading comment")
159160
}
161+
q.Name = name
162+
q.Cmd = cmd
160163
return nil
161164
}
162165

@@ -257,7 +260,7 @@ func parseFrom(from sqlparser.TableExprs, isLeftJoined bool) (FromTables, string
257260
}
258261
return right, leftMostTableName, nil
259262
default:
260-
return nil, "", fmt.Errorf("failed to parse table expr: %v", spew.Sdump(v))
263+
return nil, "", fmt.Errorf("failed to parse table expr: %v", spew.Sdump(v))
261264
}
262265
}
263266
return tables, defaultTableName, nil
@@ -303,7 +306,10 @@ func parseUpdate(node *sqlparser.Update, query string, s *Schema, settings dinos
303306
DefaultTableName: defaultTable,
304307
SchemaLookup: s,
305308
}
306-
parsedQuery.parseNameAndCmd()
309+
err = parsedQuery.parseNameAndCmd()
310+
if err != nil {
311+
return nil, err
312+
}
307313

308314
return &parsedQuery, nil
309315
}
@@ -356,7 +362,11 @@ func parseInsert(node *sqlparser.Insert, query string, s *Schema, settings dinos
356362
DefaultTableName: tableName,
357363
SchemaLookup: s,
358364
}
359-
parsedQuery.parseNameAndCmd()
365+
366+
err := parsedQuery.parseNameAndCmd()
367+
if err != nil {
368+
return nil, err
369+
}
360370
return parsedQuery, nil
361371
}
362372

@@ -390,34 +400,6 @@ func parseDelete(node *sqlparser.Delete, query string, s *Schema, settings dinos
390400
return parsedQuery, nil
391401
}
392402

393-
func (q *Query) parseLeadingComment(comment string) error {
394-
for _, line := range strings.Split(comment, "\n") {
395-
if !strings.HasPrefix(line, "/* name:") {
396-
continue
397-
}
398-
part := strings.Split(strings.TrimSpace(line), " ")
399-
if len(part) == 3 {
400-
return fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows']: %s", line)
401-
}
402-
if len(part) != 5 {
403-
return fmt.Errorf("invalid query comment: %s", line)
404-
}
405-
queryName := part[2]
406-
queryType := strings.TrimSpace(part[3])
407-
switch queryType {
408-
case ":one", ":many", ":exec", ":execrows":
409-
default:
410-
return fmt.Errorf("invalid query type: %s", queryType)
411-
}
412-
// if err := validateQueryName(queryName); err != nil {
413-
// return err
414-
// }
415-
q.Name = queryName
416-
q.Cmd = queryType
417-
}
418-
return nil
419-
}
420-
421403
func parseSelectAliasExpr(exprs sqlparser.SelectExprs, s *Schema, tableAliasMap FromTables, defaultTable string) ([]Column, error) {
422404
cols := []Column{}
423405
for _, col := range exprs {

internal/mysql/parse_test.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"reflect"
77
"testing"
88

9-
"github.com/davecgh/go-spew/spew"
109
"github.com/google/go-cmp/cmp"
1110
"github.com/kyleconroy/sqlc/internal/dinosql"
1211
"vitess.io/vitess/go/vt/sqlparser"
@@ -44,7 +43,13 @@ func TestParseConfig(t *testing.T) {
4443
func TestGeneratePkg(t *testing.T) {
4544
_, err := GeneratePkg(mockSettings.Packages[0].Name, filename, filename, mockSettings)
4645
if err != nil {
47-
t.Fatal(err)
46+
if pErr, ok := err.(*dinosql.ParserErr); ok {
47+
for _, fileErr := range pErr.Errs {
48+
t.Errorf("%s:%d:%d: %s\n", fileErr.Filename, fileErr.Line, fileErr.Column, fileErr.Err)
49+
}
50+
} else {
51+
t.Errorf("failed to generate pkg %s", err)
52+
}
4853
}
4954
}
5055

@@ -303,33 +308,28 @@ func TestParseSelect(t *testing.T) {
303308
}
304309

305310
func TestParseLeadingComment(t *testing.T) {
306-
type expected struct {
307-
name string
308-
cmd string
311+
type output struct {
312+
Name string
313+
Cmd string
309314
}
310-
type testCase struct {
315+
tests := []struct {
311316
input string
312-
output expected
313-
}
314-
315-
tests := []testCase{
316-
testCase{
317+
output output
318+
}{
319+
{
317320
input: "/* name: GetPeopleByID :many */",
318-
output: expected{name: "GetPeopleByID", cmd: ":many"},
321+
output: output{Name: "GetPeopleByID", Cmd: ":many"},
319322
},
320323
}
321324

322325
for _, tCase := range tests {
323-
qu := &Query{}
324-
err := qu.parseLeadingComment(tCase.input)
326+
name, cmd, err := dinosql.ParseMetadata(tCase.input, dinosql.CommentSyntaxStar)
327+
result := output{name, cmd}
325328
if err != nil {
326-
t.Errorf("Failed to parse leading comment %v", err)
329+
t.Errorf("failed to parse leading comment: %w", err)
330+
} else if diff := cmp.Diff(tCase.output, result); diff != "" {
331+
t.Errorf("unexpectd result of query metadata parse: %s", diff)
327332
}
328-
if qu.Name != tCase.output.name || qu.Cmd != tCase.output.cmd {
329-
t.Errorf("Leading comment parser returned unexpcted result: %v\n:\n Expected: [%v]\nRecieved:[%v]\n",
330-
err, spew.Sdump(tCase.output), spew.Sdump(qu))
331-
}
332-
333333
}
334334
}
335335

internal/mysql/test_data/queries.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ CREATE TABLE teachers (
1010
/* name: GetTeachersByID :one */
1111
SELECT * FROM teachers WHERE id = ?;
1212

13-
-- name: GetSomeTeachers :one
13+
/* name: GetSomeTeachers :one */
1414
SELECT school_id, id FROM teachers WHERE school_lng > ? AND school_lat < ?;
1515

16-
-- name: TeachersByID :one
16+
/* name: TeachersByID :one */
1717
SELECT id, school_lat FROM teachers WHERE id = ? LIMIT 10;

0 commit comments

Comments
 (0)