Skip to content

Commit 4520bff

Browse files
authored
Create new metadata-pkg (#508)
* Create new metadata-pkg * sql/validate: Port over validateCmd
1 parent 326120c commit 4520bff

File tree

7 files changed

+131
-80
lines changed

7 files changed

+131
-80
lines changed

internal/compiler/runtime/parse.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package runtime
22

33
import (
44
"errors"
5+
"strings"
56

7+
"github.com/kyleconroy/sqlc/internal/metadata"
68
"github.com/kyleconroy/sqlc/internal/source"
79
"github.com/kyleconroy/sqlc/internal/sql/ast"
810
"github.com/kyleconroy/sqlc/internal/sql/ast/pg"
@@ -49,6 +51,13 @@ func parseQuery(c *catalog.Catalog, stmt ast.Node, src string, rewriteParameters
4951
if err := validate.FuncCall(c, raw); err != nil {
5052
return nil, err
5153
}
54+
name, cmd, err := metadata.Parse(strings.TrimSpace(rawSQL), metadata.CommentSyntaxDash)
55+
if err != nil {
56+
return nil, err
57+
}
58+
if err := validate.Cmd(raw.Stmt, name, cmd); err != nil {
59+
return nil, err
60+
}
5261

5362
return &Query{}, nil
5463
}

internal/dinosql/parser.go

Lines changed: 2 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import (
99
"sort"
1010
"strconv"
1111
"strings"
12-
"unicode"
1312

1413
"github.com/davecgh/go-spew/spew"
1514
pg "github.com/lfittl/pg_query_go"
1615
nodes "github.com/lfittl/pg_query_go/nodes"
1716

1817
"github.com/kyleconroy/sqlc/internal/catalog"
18+
"github.com/kyleconroy/sqlc/internal/metadata"
1919
"github.com/kyleconroy/sqlc/internal/migrations"
2020
"github.com/kyleconroy/sqlc/internal/multierr"
2121
core "github.com/kyleconroy/sqlc/internal/pg"
@@ -210,67 +210,6 @@ func rangeVars(root nodes.Node) []nodes.RangeVar {
210210
return vars
211211
}
212212

213-
// A query name must be a valid Go identifier
214-
//
215-
// https://golang.org/ref/spec#Identifiers
216-
func validateQueryName(name string) error {
217-
if len(name) == 0 {
218-
return fmt.Errorf("invalid query name: %q", name)
219-
}
220-
for i, c := range name {
221-
isLetter := unicode.IsLetter(c) || c == '_'
222-
isDigit := unicode.IsDigit(c)
223-
if i == 0 && !isLetter {
224-
return fmt.Errorf("invalid query name %q", name)
225-
} else if !(isLetter || isDigit) {
226-
return fmt.Errorf("invalid query name %q", name)
227-
}
228-
}
229-
return nil
230-
}
231-
232-
type CommentSyntax int
233-
234-
const (
235-
CommentSyntaxDash CommentSyntax = iota
236-
CommentSyntaxStar // Note: this is the only style supported by the MySQL sqlparser
237-
CommentSyntaxHash
238-
)
239-
240-
func ParseMetadata(t string, commentStyle CommentSyntax) (string, string, error) {
241-
for _, line := range strings.Split(t, "\n") {
242-
if commentStyle == CommentSyntaxDash && !strings.HasPrefix(line, "-- name:") {
243-
continue
244-
}
245-
if commentStyle == CommentSyntaxStar && !strings.HasPrefix(line, "/* name:") {
246-
continue
247-
}
248-
part := strings.Split(strings.TrimSpace(line), " ")
249-
250-
if commentStyle == CommentSyntaxStar {
251-
part = part[:len(part)-1] // removes the trailing "*/" element
252-
}
253-
if len(part) == 2 {
254-
return "", "", fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows']: %s", line)
255-
}
256-
if len(part) != 4 {
257-
return "", "", fmt.Errorf("invalid query comment: %s", line)
258-
}
259-
queryName := part[2]
260-
queryType := strings.TrimSpace(part[3])
261-
switch queryType {
262-
case ":one", ":many", ":exec", ":execrows":
263-
default:
264-
return "", "", fmt.Errorf("invalid query type: %s", queryType)
265-
}
266-
if err := validateQueryName(queryName); err != nil {
267-
return "", "", err
268-
}
269-
return queryName, queryType, nil
270-
}
271-
return "", "", nil
272-
}
273-
274213
func validateCmd(n nodes.Node, name, cmd string) error {
275214
// TODO: Convert cmd to an enum
276215
if !(cmd == ":many" || cmd == ":one") {
@@ -331,7 +270,7 @@ func parseQuery(c core.Catalog, stmt nodes.Node, src string, rewriteParameters b
331270
if err := validate.FuncCall(&c, raw); err != nil {
332271
return nil, err
333272
}
334-
name, cmd, err := ParseMetadata(strings.TrimSpace(rawSQL), CommentSyntaxDash)
273+
name, cmd, err := metadata.Parse(strings.TrimSpace(rawSQL), metadata.CommentSyntaxDash)
335274
if err != nil {
336275
return nil, err
337276
}

internal/dinosql/parser_test.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -158,21 +158,6 @@ func TestRewriteParameters(t *testing.T) {
158158
}
159159
}
160160

161-
func TestParseMetadata(t *testing.T) {
162-
for _, query := range []string{
163-
`-- name: CreateFoo, :one`,
164-
`-- name: 9Foo_, :one`,
165-
`-- name: CreateFoo :two`,
166-
`-- name: CreateFoo`,
167-
`-- name: CreateFoo :one something`,
168-
`-- name: `,
169-
} {
170-
if _, _, err := ParseMetadata(query, CommentSyntaxDash); err == nil {
171-
t.Errorf("expected invalid metadata: %q", query)
172-
}
173-
}
174-
}
175-
176161
func TestExpand(t *testing.T) {
177162
// pretend that foo has two columns, a and b
178163
raw := `SELECT *, *, foo.* FROM foo`

internal/metadata/meta.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package metadata
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"unicode"
7+
)
8+
9+
type CommentSyntax int
10+
11+
const (
12+
CommentSyntaxDash CommentSyntax = iota
13+
CommentSyntaxStar // Note: this is the only style supported by the MySQL sqlparser
14+
CommentSyntaxHash
15+
)
16+
17+
// A query name must be a valid Go identifier
18+
//
19+
// https://golang.org/ref/spec#Identifiers
20+
func validateQueryName(name string) error {
21+
if len(name) == 0 {
22+
return fmt.Errorf("invalid query name: %q", name)
23+
}
24+
for i, c := range name {
25+
isLetter := unicode.IsLetter(c) || c == '_'
26+
isDigit := unicode.IsDigit(c)
27+
if i == 0 && !isLetter {
28+
return fmt.Errorf("invalid query name %q", name)
29+
} else if !(isLetter || isDigit) {
30+
return fmt.Errorf("invalid query name %q", name)
31+
}
32+
}
33+
return nil
34+
}
35+
36+
func Parse(t string, commentStyle CommentSyntax) (string, string, error) {
37+
for _, line := range strings.Split(t, "\n") {
38+
if commentStyle == CommentSyntaxDash && !strings.HasPrefix(line, "-- name:") {
39+
continue
40+
}
41+
if commentStyle == CommentSyntaxStar && !strings.HasPrefix(line, "/* name:") {
42+
continue
43+
}
44+
part := strings.Split(strings.TrimSpace(line), " ")
45+
46+
if commentStyle == CommentSyntaxStar {
47+
part = part[:len(part)-1] // removes the trailing "*/" element
48+
}
49+
if len(part) == 2 {
50+
return "", "", fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows']: %s", line)
51+
}
52+
if len(part) != 4 {
53+
return "", "", fmt.Errorf("invalid query comment: %s", line)
54+
}
55+
queryName := part[2]
56+
queryType := strings.TrimSpace(part[3])
57+
switch queryType {
58+
case ":one", ":many", ":exec", ":execrows":
59+
default:
60+
return "", "", fmt.Errorf("invalid query type: %s", queryType)
61+
}
62+
if err := validateQueryName(queryName); err != nil {
63+
return "", "", err
64+
}
65+
return queryName, queryType, nil
66+
}
67+
return "", "", nil
68+
}

internal/metadata/meta_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package metadata
2+
3+
import "testing"
4+
5+
func TestParseMetadata(t *testing.T) {
6+
for _, query := range []string{
7+
`-- name: CreateFoo, :one`,
8+
`-- name: 9Foo_, :one`,
9+
`-- name: CreateFoo :two`,
10+
`-- name: CreateFoo`,
11+
`-- name: CreateFoo :one something`,
12+
`-- name: `,
13+
} {
14+
if _, _, err := Parse(query, CommentSyntaxDash); err == nil {
15+
t.Errorf("expected invalid metadata: %q", query)
16+
}
17+
}
18+
}

internal/mysql/parse.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"vitess.io/vitess/go/vt/sqlparser"
1111

1212
"github.com/kyleconroy/sqlc/internal/config"
13-
"github.com/kyleconroy/sqlc/internal/dinosql"
13+
"github.com/kyleconroy/sqlc/internal/metadata"
1414
"github.com/kyleconroy/sqlc/internal/migrations"
1515
"github.com/kyleconroy/sqlc/internal/multierr"
1616
"github.com/kyleconroy/sqlc/internal/sql/sqlpath"
@@ -143,7 +143,7 @@ func (q *Query) parseNameAndCmd() error {
143143
return fmt.Errorf("cannot parse name and cmd from null query")
144144
}
145145
_, comments := sqlparser.SplitMarginComments(q.SQL)
146-
name, cmd, err := dinosql.ParseMetadata(comments.Leading, dinosql.CommentSyntaxStar)
146+
name, cmd, err := metadata.Parse(comments.Leading, metadata.CommentSyntaxStar)
147147
if err != nil {
148148
return err
149149
} else if name == "" || cmd == "" {

internal/sql/validate/cmd.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package validate
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/kyleconroy/sqlc/internal/sql/ast"
7+
"github.com/kyleconroy/sqlc/internal/sql/ast/pg"
8+
)
9+
10+
func Cmd(n ast.Node, name, cmd string) error {
11+
// TODO: Convert cmd to an enum
12+
if !(cmd == ":many" || cmd == ":one") {
13+
return nil
14+
}
15+
var list *ast.List
16+
switch stmt := n.(type) {
17+
case *pg.SelectStmt:
18+
return nil
19+
case *pg.DeleteStmt:
20+
list = stmt.ReturningList
21+
case *pg.InsertStmt:
22+
list = stmt.ReturningList
23+
case *pg.UpdateStmt:
24+
list = stmt.ReturningList
25+
default:
26+
return nil
27+
}
28+
if list == nil || len(list.Items) == 0 {
29+
return fmt.Errorf("query %q specifies parameter %q without containing a RETURNING clause", name, cmd)
30+
}
31+
return nil
32+
}

0 commit comments

Comments
 (0)