snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
This commit is contained in:
201
internal/compiler/parse.go
Normal file
201
internal/compiler/parse.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/debug"
|
||||
"github.com/sqlc-dev/sqlc/internal/metadata"
|
||||
"github.com/sqlc-dev/sqlc/internal/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/source"
|
||||
"github.com/sqlc-dev/sqlc/internal/sql/ast"
|
||||
"github.com/sqlc-dev/sqlc/internal/sql/astutils"
|
||||
"github.com/sqlc-dev/sqlc/internal/sql/preprocess"
|
||||
"github.com/sqlc-dev/sqlc/internal/sql/validate"
|
||||
"github.com/sqlc-dev/sqlc/internal/sqlcdebug"
|
||||
)
|
||||
|
||||
var debugDumpAST = sqlcdebug.New("dumpast")
|
||||
|
||||
func (c *Compiler) parseQuery(stmt ast.Node, pp *preprocess.Result, o opts.Parser) (*Query, error) {
|
||||
raw, ok := stmt.(*ast.RawStmt)
|
||||
if !ok {
|
||||
return nil, errors.New("node is not a statement")
|
||||
}
|
||||
|
||||
// The preprocessor already replaced every sqlc.* construct with native SQL
|
||||
// and recorded what it replaced.
|
||||
pre := pp.Statement(raw.StmtLocation)
|
||||
if pre.Err != nil {
|
||||
return nil, pre.Err
|
||||
}
|
||||
|
||||
// Engines number bind parameters in whatever order they convert the AST.
|
||||
// Restore the numbering the preprocessor assigned in source order.
|
||||
renumberParams(raw, pre.Numbers)
|
||||
|
||||
if c.coreCatalog != nil {
|
||||
return c.parseQueryCore(raw, pp.Text, pre)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if debugDumpAST.Value() == "1" {
|
||||
debug.Dump(stmt)
|
||||
}
|
||||
|
||||
rawSQL, err := source.Pluck(pp.Text, raw.StmtLocation, raw.StmtLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rawSQL == "" {
|
||||
return nil, errors.New("missing semicolon at end of file")
|
||||
}
|
||||
|
||||
name, cmd, err := metadata.ParseQueryNameAndType(rawSQL, metadata.CommentSyntax(c.parser.CommentSyntax()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := validate.Cmd(raw.Stmt, name, cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
md := metadata.Metadata{
|
||||
Name: name,
|
||||
Cmd: cmd,
|
||||
}
|
||||
|
||||
// TODO eventually can use this for name and type/cmd parsing too
|
||||
cleanedComments, err := source.CleanedComments(rawSQL, c.parser.CommentSyntax())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
md.Params, md.Flags, md.RuleSkiplist, err = metadata.ParseCommentFlags(cleanedComments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var anlys *analysis
|
||||
if c.analyzer != nil {
|
||||
inference, _ := c.inferQuery(raw, rawSQL, pre)
|
||||
if inference == nil {
|
||||
inference = &analysis{}
|
||||
}
|
||||
if inference.Query == "" {
|
||||
inference.Query = rawSQL
|
||||
}
|
||||
|
||||
result, err := c.analyzer.Analyze(ctx, raw, inference.Query, c.schema, inference.Named)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the query uses star expansion, verify that it was edited. If not,
|
||||
// return an error.
|
||||
stars := astutils.Search(raw, func(node ast.Node) bool {
|
||||
_, ok := node.(*ast.A_Star)
|
||||
return ok
|
||||
})
|
||||
hasStars := len(stars.Items) > 0
|
||||
unchanged := inference.Query == rawSQL
|
||||
if unchanged && hasStars {
|
||||
return nil, fmt.Errorf("star expansion failed for query")
|
||||
}
|
||||
|
||||
// FOOTGUN: combineAnalysis mutates inference
|
||||
anlys = combineAnalysis(inference, result)
|
||||
} else {
|
||||
anlys, err = c.analyzeQuery(raw, rawSQL, pre)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
expanded := anlys.Query
|
||||
|
||||
// If the query string was edited, make sure the syntax is valid
|
||||
if expanded != rawSQL {
|
||||
if _, err := c.parser.Parse(strings.NewReader(expanded)); err != nil {
|
||||
return nil, fmt.Errorf("edited query syntax is invalid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
trimmed, comments, err := source.StripComments(expanded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
md.Comments = comments
|
||||
|
||||
return &Query{
|
||||
RawStmt: raw,
|
||||
Metadata: md,
|
||||
Params: anlys.Parameters,
|
||||
Columns: anlys.Columns,
|
||||
SQL: trimmed,
|
||||
InsertIntoTable: anlys.Table,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// renumberParams applies the parameter numbers the preprocessor assigned,
|
||||
// matching each node by the location of its placeholder in the query text.
|
||||
func renumberParams(raw *ast.RawStmt, numbers map[int]int) {
|
||||
if len(numbers) == 0 {
|
||||
return
|
||||
}
|
||||
astutils.Walk(astutils.VisitorFunc(func(node ast.Node) {
|
||||
ref, ok := node.(*ast.ParamRef)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if n, ok := numbers[ref.Location]; ok {
|
||||
ref.Number = n
|
||||
}
|
||||
}), raw)
|
||||
}
|
||||
|
||||
func rangeVars(root ast.Node) []*ast.RangeVar {
|
||||
var vars []*ast.RangeVar
|
||||
find := astutils.VisitorFunc(func(node ast.Node) {
|
||||
switch n := node.(type) {
|
||||
case *ast.RangeVar:
|
||||
vars = append(vars, n)
|
||||
}
|
||||
})
|
||||
astutils.Walk(find, root)
|
||||
return vars
|
||||
}
|
||||
|
||||
func uniqueParamRefs(in []paramRef, dollar bool) []paramRef {
|
||||
m := make(map[int]bool, len(in))
|
||||
o := make([]paramRef, 0, len(in))
|
||||
for _, v := range in {
|
||||
if !m[v.ref.Number] {
|
||||
m[v.ref.Number] = true
|
||||
if v.ref.Number != 0 {
|
||||
o = append(o, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !dollar {
|
||||
start := 1
|
||||
for _, v := range in {
|
||||
if v.ref.Number == 0 {
|
||||
for m[start] {
|
||||
start++
|
||||
}
|
||||
v.ref.Number = start
|
||||
o = append(o, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
Reference in New Issue
Block a user