Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(compiler): Parse query parameter metadata from comments
  • Loading branch information
andrewmbenton committed Oct 13, 2023
commit 4db5c66374eee141029c7e7af70b7a70aa05de3c
8 changes: 4 additions & 4 deletions internal/cmd/shim.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,13 @@ func pluginQueries(r *compiler.Result) []*plugin.Query {
}
}
out = append(out, &plugin.Query{
Name: q.Name,
Cmd: q.Cmd,
Name: q.Metadata.Name,
Cmd: q.Metadata.Cmd,
Text: q.SQL,
Comments: q.Comments,
Comments: q.Metadata.Comments,
Columns: columns,
Params: params,
Filename: q.Filename,
Filename: q.Metadata.Filename,
InsertIntoTable: iit,
})
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/vet.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error {
req := codeGenRequest(result, combo)
cfg := vetConfig(req)
for i, query := range req.Queries {
if result.Queries[i].Flags[QueryFlagSqlcVetDisable] {
if result.Queries[i].Metadata.Flags[QueryFlagSqlcVetDisable] {
if debug.Active {
log.Printf("Skipping vet rules for query: %s\n", query.Name)
}
Expand Down
11 changes: 6 additions & 5 deletions internal/compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,15 @@ func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) {
merr.Add(filename, src, loc, err)
continue
}
if query.Name != "" {
if _, exists := set[query.Name]; exists {
merr.Add(filename, src, stmt.Raw.Pos(), fmt.Errorf("duplicate query name: %s", query.Name))
queryName := query.Metadata.Name
if queryName != "" {
if _, exists := set[queryName]; exists {
merr.Add(filename, src, stmt.Raw.Pos(), fmt.Errorf("duplicate query name: %s", queryName))
continue
}
set[query.Name] = struct{}{}
set[queryName] = struct{}{}
}
query.Filename = filepath.Base(filename)
query.Metadata.Filename = filepath.Base(filename)
if query != nil {
q = append(q, query)
}
Expand Down
17 changes: 5 additions & 12 deletions internal/compiler/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ func (c *Compiler) parseQuery(stmt ast.Node, src string, o opts.Parser) (*Query,
return nil, errors.New("missing semicolon at end of file")
}

name, cmd, err := metadata.ParseQueryNameAndType(strings.TrimSpace(rawSQL), c.parser.CommentSyntax())
md, err := metadata.ParseQueryMetadata(rawSQL, c.parser.CommentSyntax())
if err != nil {
return nil, err
}
if err := validate.Cmd(raw.Stmt, name, cmd); err != nil {

if err := validate.Cmd(raw.Stmt, md.Name, md.Cmd); err != nil {
return nil, err
}

Expand Down Expand Up @@ -85,22 +86,14 @@ func (c *Compiler) parseQuery(stmt ast.Node, src string, o opts.Parser) (*Query,
}
}

trimmed, comments, err := source.StripComments(expanded)
if err != nil {
return nil, err
}

flags, err := metadata.ParseQueryFlags(comments)
trimmed, _, err := source.StripComments(expanded)
if err != nil {
return nil, err
}

return &Query{
RawStmt: raw,
Cmd: cmd,
Comments: comments,
Name: name,
Flags: flags,
Metadata: md,
Params: anlys.Parameters,
Columns: anlys.Columns,
SQL: trimmed,
Expand Down
9 changes: 2 additions & 7 deletions internal/compiler/query.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package compiler

import (
"github.com/sqlc-dev/sqlc/internal/metadata"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)

Expand Down Expand Up @@ -41,15 +42,9 @@ type Column struct {

type Query struct {
SQL string
Name string
Cmd string // TODO: Pick a better name. One of: one, many, exec, execrows, copyFrom
Flags map[string]bool
Metadata metadata.Metadata
Columns []*Column
Params []Parameter
Comments []string

// XXX: Hack
Filename string

// Needed for CopyFrom
InsertIntoTable *ast.TableName
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# package querytest
query.sql:1:1: invalid query comment: -- name: ListFoos
query.sql:1:1: missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: -- name: ListFoos
query.sql:5:1: invalid query comment: -- name: ListFoos :one :many
query.sql:8:1: invalid query type: :two
query.sql:11:1: query "DeleteFoo" specifies parameter ":one" without containing a RETURNING clause
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# package querytest
query.sql:1:1: invalid query comment: -- name: ListFoos
query.sql:1:1: missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: -- name: ListFoos
query.sql:5:1: invalid query comment: -- name: ListFoos :one :many
query.sql:8:1: invalid query type: :two
query.sql:11:1: query "DeleteFoo" specifies parameter ":one" without containing a RETURNING clause
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# package querytest
query.sql:1:1: invalid query comment: -- name: ListFoos
query.sql:1:1: missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: -- name: ListFoos
query.sql:5:1: invalid query comment: -- name: ListFoos :one :many
query.sql:8:1: invalid query type: :two
query.sql:11:1: query "DeleteFoo" specifies parameter ":one" without containing a RETURNING clause
Expand Down
74 changes: 53 additions & 21 deletions internal/metadata/meta.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package metadata

import (
"bufio"
"fmt"
"strings"
"unicode"
)

type Metadata struct {
Name string
Cmd string
Comments []string
Params map[string]string
Flags map[string]bool

Filename string
}

type CommentSyntax struct {
Dash bool
Hash bool
Expand Down Expand Up @@ -44,8 +55,12 @@ func validateQueryName(name string) error {
return nil
}

func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string, error) {
for _, line := range strings.Split(t, "\n") {
func ParseQueryMetadata(rawSql string, commentStyle CommentSyntax) (Metadata, error) {
md := Metadata{}
s := bufio.NewScanner(strings.NewReader(strings.TrimSpace(rawSql)))
var lines, comments []string
for s.Scan() {
line := s.Text()
var prefix string
if strings.HasPrefix(line, "--") {
if !commentStyle.Dash {
Expand All @@ -66,45 +81,56 @@ func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string
prefix = "#"
}
if prefix == "" {
lines = append(lines, line)
continue
}

rest := line[len(prefix):]
rest = strings.TrimSuffix(rest, "*/")
comments = append(comments, rest)

if !strings.HasPrefix(strings.TrimSpace(rest), "name") {
continue
}
if !strings.Contains(rest, ":") {
continue
}
if !strings.HasPrefix(rest, " name: ") {
return "", "", fmt.Errorf("invalid metadata: %s", line)
return md, fmt.Errorf("invalid metadata: %s", line)
}

part := strings.Split(strings.TrimSpace(line), " ")
if prefix == "/*" {
part = part[:len(part)-1] // removes the trailing "*/" element
}
if len(part) == 2 {
return "", "", fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: %s", line)
comments = comments[:len(comments)-1] // Remove tha name line from returned comments

parts := strings.Split(strings.TrimSpace(rest), " ")

if len(parts) == 2 {
return md, fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: %s", line)
}
if len(part) != 4 {
return "", "", fmt.Errorf("invalid query comment: %s", line)
if len(parts) > 3 {
return md, fmt.Errorf("invalid query comment: %s", line)
}
queryName := part[2]
queryType := strings.TrimSpace(part[3])
queryName := parts[1]
queryType := parts[2]
switch queryType {
case CmdOne, CmdMany, CmdExec, CmdExecResult, CmdExecRows, CmdExecLastId, CmdCopyFrom, CmdBatchExec, CmdBatchMany, CmdBatchOne:
default:
return "", "", fmt.Errorf("invalid query type: %s", queryType)
return md, fmt.Errorf("invalid query type: %s", queryType)
}
if err := validateQueryName(queryName); err != nil {
return "", "", err
return md, err
}
return queryName, queryType, nil
md.Name = queryName
md.Cmd = queryType
}
return "", "", nil

md.Comments = comments
md.Params, md.Flags = parseParamsAndFlags(md.Comments)

return md, s.Err()
}

func ParseQueryFlags(comments []string) (map[string]bool, error) {
func parseParamsAndFlags(comments []string) (map[string]string, map[string]bool) {
params := make(map[string]string)
flags := make(map[string]bool)
for _, line := range comments {
cleanLine := strings.TrimPrefix(line, "--")
Expand All @@ -113,10 +139,16 @@ func ParseQueryFlags(comments []string) (map[string]bool, error) {
cleanLine = strings.TrimSuffix(cleanLine, "*/")
cleanLine = strings.TrimSpace(cleanLine)
if strings.HasPrefix(cleanLine, "@") {
flagName := strings.SplitN(cleanLine, " ", 2)[0]
flags[flagName] = true
parts := strings.SplitN(cleanLine, " ", 2)
name := parts[0]
switch name {
case "param":
params[name] = parts[1]
default:
flags[name] = true
}
continue
}
}
return flags, nil
return params, flags
}
52 changes: 35 additions & 17 deletions internal/metadata/meta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ func TestParseQueryNameAndType(t *testing.T) {
`--name: CreateFoo :two`,
Comment thread
andrewmbenton marked this conversation as resolved.
"-- name:CreateFoo",
`--name:CreateFoo :two`,
`-- name: CreateFoo :two`,
`-- name: CreateFoo :two`,
`-- name: CreateFoo :two`,
} {
if _, _, err := ParseQueryNameAndType(query, CommentSyntax{Dash: true}); err == nil {
if _, err := ParseQueryMetadata(query, CommentSyntax{Dash: true}); err == nil {
t.Errorf("expected invalid metadata: %q", query)
}
}
Expand All @@ -27,21 +30,26 @@ func TestParseQueryNameAndType(t *testing.T) {
`-- name comment`,
`--name comment`,
} {
if _, _, err := ParseQueryNameAndType(query, CommentSyntax{Dash: true}); err != nil {
if _, err := ParseQueryMetadata(query, CommentSyntax{Dash: true}); err != nil {
t.Errorf("expected valid comment: %q", query)
}
}

query := `-- name: CreateFoo :one`
queryName, queryType, err := ParseQueryNameAndType(query, CommentSyntax{Dash: true})
if err != nil {
t.Errorf("expected valid metadata: %q", query)
}
if queryName != "CreateFoo" {
t.Errorf("incorrect queryName parsed: %q", query)
}
if queryType != CmdOne {
t.Errorf("incorrect queryType parsed: %q", query)
for query, cs := range map[string]CommentSyntax{
`-- name: CreateFoo :one`: {Dash: true},
`# name: CreateFoo :one`: {Hash: true},
`/* name: CreateFoo :one */`: {SlashStar: true},
} {
queryMetadata, err := ParseQueryMetadata(query, cs)
if err != nil {
t.Errorf("expected valid metadata: %q", query)
}
if queryMetadata.Name != "CreateFoo" {
t.Errorf("incorrect queryName parsed: %q", query)
}
if queryMetadata.Cmd != CmdOne {
t.Errorf("incorrect queryType parsed: %q", query)
}
}

}
Expand All @@ -52,14 +60,24 @@ func TestParseQueryFlags(t *testing.T) {
"-- name: CreateFoo :one",
"-- @flag-foo",
},
{
"-- name: CreateFoo :one",
"-- @flag-foo @flag-bar",
},
{
"-- name: GetFoos :many",
"-- @param @flag-bar UUID",
"-- @flag-foo",
},
} {
flags, err := ParseQueryFlags(comments)
if err != nil {
t.Errorf("expected query flags to parse, got error: %s", err)
}
_, flags := parseParamsAndFlags(comments)

if !flags["@flag-foo"] {
t.Errorf("expected flag not found")
}

if flags["@flag-bar"] {
t.Errorf("unexpected flag found")
}
}
}
}
1 change: 1 addition & 0 deletions internal/source/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func Mutate(raw string, a []Edit) (string, error) {
return s, nil
}

// TODO remove and roll into query parsing
func StripComments(sql string) (string, []string, error) {
s := bufio.NewScanner(strings.NewReader(strings.TrimSpace(sql)))
var lines, comments []string
Expand Down