Files
upstream-mirror 0f4df23290
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
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

202 lines
4.6 KiB
Go

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
}