Skip to content

Commit b06ee14

Browse files
feat(compiler): Parse query parameter metadata from comments (#2850)
* feat(compiler): Parse query parameter metadata from comments * update comment stripping, fix a test * parse `@param` name correctly * add a test for metadata param parsing * use a word scanner for flag and param comment line parsing * forgot that I partially cleaned comments already * slightly more useful metadata test output * drop unnecessary metadata test cases * walk back changes, implement comment cleaning in source package * cleanup * simplify diff * ensure at least one test case for parsing @whatever without a leading space
1 parent 0cfadb0 commit b06ee14

File tree

15 files changed

+228
-71
lines changed

15 files changed

+228
-71
lines changed

internal/cmd/shim.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,13 @@ func pluginQueries(r *compiler.Result) []*plugin.Query {
209209
}
210210
}
211211
out = append(out, &plugin.Query{
212-
Name: q.Name,
213-
Cmd: q.Cmd,
212+
Name: q.Metadata.Name,
213+
Cmd: q.Metadata.Cmd,
214214
Text: q.SQL,
215-
Comments: q.Comments,
215+
Comments: q.Metadata.Comments,
216216
Columns: columns,
217217
Params: params,
218-
Filename: q.Filename,
218+
Filename: q.Metadata.Filename,
219219
InsertIntoTable: iit,
220220
})
221221
}

internal/cmd/vet.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error {
545545
req := codeGenRequest(result, combo)
546546
cfg := vetConfig(req)
547547
for i, query := range req.Queries {
548-
if result.Queries[i].Flags[QueryFlagSqlcVetDisable] {
548+
if result.Queries[i].Metadata.Flags[QueryFlagSqlcVetDisable] {
549549
if debug.Active {
550550
log.Printf("Skipping vet rules for query: %s\n", query.Name)
551551
}

internal/compiler/compile.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88
"path/filepath"
99
"strings"
1010

11-
"github.com/sqlc-dev/sqlc/internal/metadata"
1211
"github.com/sqlc-dev/sqlc/internal/migrations"
1312
"github.com/sqlc-dev/sqlc/internal/multierr"
1413
"github.com/sqlc-dev/sqlc/internal/opts"
14+
"github.com/sqlc-dev/sqlc/internal/source"
1515
"github.com/sqlc-dev/sqlc/internal/sql/ast"
1616
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
1717
"github.com/sqlc-dev/sqlc/internal/sql/sqlpath"
@@ -20,7 +20,7 @@ import (
2020
// TODO: Rename this interface Engine
2121
type Parser interface {
2222
Parse(io.Reader) ([]ast.Statement, error)
23-
CommentSyntax() metadata.CommentSyntax
23+
CommentSyntax() source.CommentSyntax
2424
IsReservedKeyword(string) bool
2525
}
2626

@@ -90,14 +90,15 @@ func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) {
9090
merr.Add(filename, src, loc, err)
9191
continue
9292
}
93-
if query.Name != "" {
94-
if _, exists := set[query.Name]; exists {
95-
merr.Add(filename, src, stmt.Raw.Pos(), fmt.Errorf("duplicate query name: %s", query.Name))
93+
queryName := query.Metadata.Name
94+
if queryName != "" {
95+
if _, exists := set[queryName]; exists {
96+
merr.Add(filename, src, stmt.Raw.Pos(), fmt.Errorf("duplicate query name: %s", queryName))
9697
continue
9798
}
98-
set[query.Name] = struct{}{}
99+
set[queryName] = struct{}{}
99100
}
100-
query.Filename = filepath.Base(filename)
101+
query.Metadata.Filename = filepath.Base(filename)
101102
if query != nil {
102103
q = append(q, query)
103104
}

internal/compiler/parse.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,31 @@ func (c *Compiler) parseQuery(stmt ast.Node, src string, o opts.Parser) (*Query,
4343
return nil, errors.New("missing semicolon at end of file")
4444
}
4545

46-
name, cmd, err := metadata.ParseQueryNameAndType(strings.TrimSpace(rawSQL), c.parser.CommentSyntax())
46+
name, cmd, err := metadata.ParseQueryNameAndType(rawSQL, metadata.CommentSyntax(c.parser.CommentSyntax()))
4747
if err != nil {
4848
return nil, err
4949
}
50+
5051
if err := validate.Cmd(raw.Stmt, name, cmd); err != nil {
5152
return nil, err
5253
}
5354

55+
md := metadata.Metadata{
56+
Name: name,
57+
Cmd: cmd,
58+
}
59+
60+
// TODO eventually can use this for name and type/cmd parsing too
61+
cleanedComments, err := source.CleanedComments(rawSQL, c.parser.CommentSyntax())
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
md.Params, md.Flags, err = metadata.ParseParamsAndFlags(cleanedComments)
67+
if err != nil {
68+
return nil, err
69+
}
70+
5471
var anlys *analysis
5572
if c.analyzer != nil {
5673
// TODO: Handle panics
@@ -90,17 +107,11 @@ func (c *Compiler) parseQuery(stmt ast.Node, src string, o opts.Parser) (*Query,
90107
return nil, err
91108
}
92109

93-
flags, err := metadata.ParseQueryFlags(comments)
94-
if err != nil {
95-
return nil, err
96-
}
110+
md.Comments = comments
97111

98112
return &Query{
99113
RawStmt: raw,
100-
Cmd: cmd,
101-
Comments: comments,
102-
Name: name,
103-
Flags: flags,
114+
Metadata: md,
104115
Params: anlys.Parameters,
105116
Columns: anlys.Columns,
106117
SQL: trimmed,

internal/compiler/query.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package compiler
22

33
import (
4+
"github.com/sqlc-dev/sqlc/internal/metadata"
45
"github.com/sqlc-dev/sqlc/internal/sql/ast"
56
)
67

@@ -41,15 +42,9 @@ type Column struct {
4142

4243
type Query struct {
4344
SQL string
44-
Name string
45-
Cmd string // TODO: Pick a better name. One of: one, many, exec, execrows, copyFrom
46-
Flags map[string]bool
45+
Metadata metadata.Metadata
4746
Columns []*Column
4847
Params []Parameter
49-
Comments []string
50-
51-
// XXX: Hack
52-
Filename string
5348

5449
// Needed for CopyFrom
5550
InsertIntoTable *ast.TableName

internal/endtoend/testdata/comment_syntax/mysql/go/query.sql.go

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/invalid_queries_foo/pgx/v4/stderr.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# package querytest
2-
query.sql:1:1: invalid query comment: -- name: ListFoos
2+
query.sql:1:1: missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: -- name: ListFoos
33
query.sql:5:1: invalid query comment: -- name: ListFoos :one :many
44
query.sql:8:1: invalid query type: :two
55
query.sql:11:1: query "DeleteFoo" specifies parameter ":one" without containing a RETURNING clause

internal/endtoend/testdata/invalid_queries_foo/pgx/v5/stderr.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# package querytest
2-
query.sql:1:1: invalid query comment: -- name: ListFoos
2+
query.sql:1:1: missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: -- name: ListFoos
33
query.sql:5:1: invalid query comment: -- name: ListFoos :one :many
44
query.sql:8:1: invalid query type: :two
55
query.sql:11:1: query "DeleteFoo" specifies parameter ":one" without containing a RETURNING clause

internal/endtoend/testdata/invalid_queries_foo/stdlib/stderr.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# package querytest
2-
query.sql:1:1: invalid query comment: -- name: ListFoos
2+
query.sql:1:1: missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: -- name: ListFoos
33
query.sql:5:1: invalid query comment: -- name: ListFoos :one :many
44
query.sql:8:1: invalid query type: :two
55
query.sql:11:1: query "DeleteFoo" specifies parameter ":one" without containing a RETURNING clause

internal/engine/dolphin/parse.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/pingcap/tidb/parser"
1111
_ "github.com/pingcap/tidb/parser/test_driver"
1212

13-
"github.com/sqlc-dev/sqlc/internal/metadata"
13+
"github.com/sqlc-dev/sqlc/internal/source"
1414
"github.com/sqlc-dev/sqlc/internal/sql/ast"
1515
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
1616
)
@@ -86,8 +86,8 @@ func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
8686
}
8787

8888
// https://dev.mysql.com/doc/refman/8.0/en/comments.html
89-
func (p *Parser) CommentSyntax() metadata.CommentSyntax {
90-
return metadata.CommentSyntax{
89+
func (p *Parser) CommentSyntax() source.CommentSyntax {
90+
return source.CommentSyntax{
9191
Dash: true,
9292
SlashStar: true,
9393
Hash: true,

internal/engine/postgresql/parse.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
nodes "github.com/pganalyze/pg_query_go/v4"
1313
"github.com/pganalyze/pg_query_go/v4/parser"
1414

15-
"github.com/sqlc-dev/sqlc/internal/metadata"
15+
"github.com/sqlc-dev/sqlc/internal/source"
1616
"github.com/sqlc-dev/sqlc/internal/sql/ast"
1717
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
1818
)
@@ -199,8 +199,8 @@ func normalizeErr(err error) error {
199199
}
200200

201201
// https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-COMMENTS
202-
func (p *Parser) CommentSyntax() metadata.CommentSyntax {
203-
return metadata.CommentSyntax{
202+
func (p *Parser) CommentSyntax() source.CommentSyntax {
203+
return source.CommentSyntax{
204204
Dash: true,
205205
SlashStar: true,
206206
}

internal/engine/sqlite/parse.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
99

1010
"github.com/sqlc-dev/sqlc/internal/engine/sqlite/parser"
11-
"github.com/sqlc-dev/sqlc/internal/metadata"
11+
"github.com/sqlc-dev/sqlc/internal/source"
1212
"github.com/sqlc-dev/sqlc/internal/sql/ast"
1313
)
1414

@@ -86,8 +86,8 @@ func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
8686
return stmts, nil
8787
}
8888

89-
func (p *Parser) CommentSyntax() metadata.CommentSyntax {
90-
return metadata.CommentSyntax{
89+
func (p *Parser) CommentSyntax() source.CommentSyntax {
90+
return source.CommentSyntax{
9191
Dash: true,
9292
Hash: false,
9393
SlashStar: true,

internal/metadata/meta.go

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
package metadata
22

33
import (
4+
"bufio"
45
"fmt"
56
"strings"
67
"unicode"
8+
9+
"github.com/sqlc-dev/sqlc/internal/source"
710
)
811

9-
type CommentSyntax struct {
10-
Dash bool
11-
Hash bool
12-
SlashStar bool
12+
type CommentSyntax source.CommentSyntax
13+
14+
type Metadata struct {
15+
Name string
16+
Cmd string
17+
Comments []string
18+
Params map[string]string
19+
Flags map[string]bool
20+
21+
Filename string
1322
}
1423

1524
const (
@@ -83,7 +92,7 @@ func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string
8392
if prefix == "/*" {
8493
part = part[:len(part)-1] // removes the trailing "*/" element
8594
}
86-
if len(part) == 2 {
95+
if len(part) == 3 {
8796
return "", "", fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: %s", line)
8897
}
8998
if len(part) != 4 {
@@ -104,19 +113,39 @@ func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string
104113
return "", "", nil
105114
}
106115

107-
func ParseQueryFlags(comments []string) (map[string]bool, error) {
116+
func ParseParamsAndFlags(comments []string) (map[string]string, map[string]bool, error) {
117+
params := make(map[string]string)
108118
flags := make(map[string]bool)
119+
109120
for _, line := range comments {
110-
cleanLine := strings.TrimPrefix(line, "--")
111-
cleanLine = strings.TrimPrefix(cleanLine, "/*")
112-
cleanLine = strings.TrimPrefix(cleanLine, "#")
113-
cleanLine = strings.TrimSuffix(cleanLine, "*/")
114-
cleanLine = strings.TrimSpace(cleanLine)
115-
if strings.HasPrefix(cleanLine, "@") {
116-
flagName := strings.SplitN(cleanLine, " ", 2)[0]
117-
flags[flagName] = true
121+
s := bufio.NewScanner(strings.NewReader(line))
122+
s.Split(bufio.ScanWords)
123+
124+
s.Scan()
125+
token := s.Text()
126+
127+
if !strings.HasPrefix(token, "@") {
118128
continue
119129
}
130+
131+
switch token {
132+
case "@param":
133+
s.Scan()
134+
name := s.Text()
135+
var rest []string
136+
for s.Scan() {
137+
paramToken := s.Text()
138+
rest = append(rest, paramToken)
139+
}
140+
params[name] = strings.Join(rest, " ")
141+
default:
142+
flags[token] = true
143+
}
144+
145+
if s.Err() != nil {
146+
return params, flags, s.Err()
147+
}
120148
}
121-
return flags, nil
149+
150+
return params, flags, nil
122151
}

0 commit comments

Comments
 (0)