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

390 lines
9.3 KiB
Go

package analyzer
import (
"fmt"
"github.com/sqlc-dev/sqlc/internal/core"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)
func Prepare(cat *core.Catalog, stmt ast.Node) (core.PrepareResult, error) {
if rs, ok := stmt.(*ast.RawStmt); ok {
stmt = rs.Stmt
}
a := &analyzer{
cat: cat,
params: map[int]core.Parameter{},
stars: &[]core.StarExpansion{},
}
switch s := stmt.(type) {
case *ast.SelectStmt:
if err := a.analyzeSelect(s); err != nil {
return core.PrepareResult{}, err
}
a.command = core.CommandSelect
case *ast.InsertStmt:
if err := a.analyzeInsert(s); err != nil {
return core.PrepareResult{}, err
}
a.command = core.CommandInsert
case *ast.UpdateStmt:
if err := a.analyzeUpdate(s); err != nil {
return core.PrepareResult{}, err
}
a.command = core.CommandUpdate
case *ast.DeleteStmt:
if err := a.analyzeDelete(s); err != nil {
return core.PrepareResult{}, err
}
a.command = core.CommandDelete
default:
return core.PrepareResult{}, fmt.Errorf("analyzer: unsupported statement %T", stmt)
}
return a.result(), nil
}
type analyzer struct {
cat *core.Catalog
scope *scope
columns []core.Column
params map[int]core.Parameter
command core.Command
// outer is the scope of the query this one is nested in, which a
// correlated subquery refers to.
outer *scope
// ctes are the relations a WITH clause defined, visible to this query and
// to the ones nested in it.
ctes map[string]scopeRel
// aliases are the names the target list gives its expressions. GROUP BY,
// HAVING and ORDER BY may refer to a result column by its output name.
aliases map[string]ast.Node
// resolving guards against an alias that refers to itself.
resolving map[string]bool
// stars are the expansions every star in the statement asked for, shared
// with the analyzers of the queries nested in it so one statement reports
// all of them.
stars *[]core.StarExpansion
}
func (a *analyzer) recordStar(s core.StarExpansion) {
if a.stars == nil {
return
}
*a.stars = append(*a.stars, s)
}
// subquery analyzes a nested SELECT. It shares the parameter set, so a
// placeholder inside the subquery is reported with the rest, and it sees this
// query's scope, so a correlated reference resolves.
func (a *analyzer) subquery(s *ast.SelectStmt) (*analyzer, error) {
sub := &analyzer{
cat: a.cat,
params: a.params,
outer: a.scope,
ctes: a.ctes,
stars: a.stars,
}
if err := sub.analyzeSelect(s); err != nil {
return nil, err
}
return sub, nil
}
func (a *analyzer) subqueryColumns(s *ast.SelectStmt) ([]core.Column, error) {
sub, err := a.subquery(s)
if err != nil {
return nil, err
}
return sub.columns, nil
}
// derivedRel turns a subquery's result columns into a relation, so that the
// query selecting from it resolves columns the same way as from a table.
func derivedRel(alias string, cols []core.Column) scopeRel {
rel := scopeRel{alias: alias, cols: make([]core.ClassColumn, 0, len(cols))}
for _, col := range cols {
rel.cols = append(rel.cols, core.ClassColumn{
AttOID: col.SourceAttributeOID,
Name: col.Name,
TypeOID: col.TypeOID,
Type: col.Type.WithNullable(false),
NotNull: col.NotNull,
})
}
return rel
}
func (a *analyzer) result() core.PrepareResult {
// A placeholder nothing constrained takes the dialect's type for one, when
// the dialect has such a type.
if oid, ok := a.cat.UntypedTypeOID(); ok {
for n, p := range a.params {
if p.TypeOID == 0 && p.Type == nil {
t := exprType{typeOID: oid, nullable: true}
p.TypeOID = oid
p.DataType, p.IsArray = a.typeNameOf(t)
p.NotNull = false
p.Type = a.typeExprOf(t)
a.params[n] = p
}
}
}
res := core.PrepareResult{
Command: a.command,
Columns: a.columns,
Parameters: orderedParams(a.params),
}
if a.stars != nil {
res.Stars = *a.stars
}
return res
}
func orderedParams(m map[int]core.Parameter) []core.Parameter {
if len(m) == 0 {
return nil
}
maxN := 0
for n := range m {
if n > maxN {
maxN = n
}
}
out := make([]core.Parameter, 0, len(m))
for i := 1; i <= maxN; i++ {
if p, ok := m[i]; ok {
out = append(out, p)
}
}
return out
}
func (a *analyzer) analyzeSelect(s *ast.SelectStmt) error {
if err := a.bindCTEs(s.WithClause); err != nil {
return err
}
// A set operation reports the columns of its first branch. The other
// branches still get analyzed, for the placeholders they hold.
if s.Larg != nil && s.Rarg != nil {
if err := a.analyzeSetOperation(s); err != nil {
return err
}
return nil
}
sc, err := a.buildScope(s.FromClause)
if err != nil {
return err
}
a.scope = sc
a.bindAliases(s.TargetList)
for _, item := range listItems(s.FromClause) {
if err := a.typeJoinConditions(item); err != nil {
return fmt.Errorf("join: %w", err)
}
}
if s.WhereClause != nil {
if _, err := a.typeExpr(s.WhereClause); err != nil {
return fmt.Errorf("where: %w", err)
}
}
if items := listItems(s.GroupClause); items != nil {
for _, g := range items {
if _, err := a.typeExpr(g); err != nil {
return fmt.Errorf("group by: %w", err)
}
}
}
if s.HavingClause != nil {
if _, err := a.typeExpr(s.HavingClause); err != nil {
return fmt.Errorf("having: %w", err)
}
}
targets := listItems(s.TargetList)
if targets == nil {
// A VALUES list produces rows without naming columns.
if s.ValuesLists != nil {
return a.typeValuesLists(s.ValuesLists)
}
return fmt.Errorf("select: empty target list")
}
a.columns = make([]core.Column, 0, len(targets))
for _, t := range targets {
rt, ok := t.(*ast.ResTarget)
if !ok {
continue
}
if err := a.projectTarget(rt); err != nil {
return err
}
}
for _, item := range listItems(s.SortClause) {
if sb, ok := item.(*ast.SortBy); ok {
if _, err := a.typeExpr(sb.Node); err != nil {
return fmt.Errorf("order by: %w", err)
}
}
}
for _, n := range []ast.Node{s.LimitCount, s.LimitOffset} {
if err := a.typeLimit(n); err != nil {
return fmt.Errorf("limit: %w", err)
}
}
return nil
}
// typeLimit types a LIMIT or OFFSET count. A bare placeholder there holds
// whatever the dialect counts rows in.
func (a *analyzer) typeLimit(n ast.Node) error {
if n == nil {
return nil
}
if pr, ok := n.(*ast.ParamRef); ok {
oid, err := a.cat.LimitTypeOID()
if err != nil {
return err
}
a.inferParam(pr.Number, exprType{typeOID: oid})
return nil
}
_, err := a.typeExpr(n)
return err
}
func (a *analyzer) typeValuesLists(l *ast.List) error {
for _, row := range listItems(l) {
if _, err := a.typeExpr(row); err != nil {
return err
}
}
return nil
}
// analyzeSetOperation types both sides of UNION, INTERSECT or EXCEPT and
// reports the first branch's columns, the way the databases name the result.
func (a *analyzer) analyzeSetOperation(s *ast.SelectStmt) error {
left, err := a.subquery(s.Larg)
if err != nil {
return err
}
if _, err := a.subquery(s.Rarg); err != nil {
return err
}
a.columns = left.columns
a.scope = left.scope
return nil
}
// bindCTEs analyzes a WITH clause, making each common table expression
// available to the query as a relation.
func (a *analyzer) bindCTEs(with *ast.WithClause) error {
if with == nil {
return nil
}
for _, item := range listItems(with.Ctes) {
cte, ok := item.(*ast.CommonTableExpr)
if !ok || cte.Ctename == nil {
continue
}
sel, ok := cte.Ctequery.(*ast.SelectStmt)
if !ok {
continue
}
cols, err := a.subqueryColumns(sel)
if err != nil {
return fmt.Errorf("with %s: %w", *cte.Ctename, err)
}
rel := derivedRel(*cte.Ctename, cols)
renameColumns(&rel, cte.Aliascolnames)
if a.ctes == nil {
a.ctes = map[string]scopeRel{}
}
a.ctes[*cte.Ctename] = rel
}
return nil
}
// bindAliases records the output names the target list assigns, so a later
// clause can refer to a result column by name.
func (a *analyzer) bindAliases(targets *ast.List) {
for _, item := range listItems(targets) {
rt, ok := item.(*ast.ResTarget)
if !ok || rt.Name == nil || *rt.Name == "" {
continue
}
if a.aliases == nil {
a.aliases = map[string]ast.Node{}
}
a.aliases[*rt.Name] = rt.Val
}
}
// typeAlias types the expression an output name stands for, which is how a
// GROUP BY or HAVING clause refers to a result column.
func (a *analyzer) typeAlias(name string) (exprType, bool, error) {
val, ok := a.aliases[name]
if !ok || a.resolving[name] {
return exprType{}, false, nil
}
if a.resolving == nil {
a.resolving = map[string]bool{}
}
a.resolving[name] = true
defer delete(a.resolving, name)
t, err := a.typeExpr(val)
if err != nil {
return exprType{}, false, err
}
return t, true, nil
}
// renameColumns applies the column aliases a derived relation was declared
// with, as in "WITH t(a, b) AS (...)".
func renameColumns(rel *scopeRel, names *ast.List) {
for i, item := range listItems(names) {
if i >= len(rel.cols) {
break
}
if s, ok := item.(*ast.String); ok && s.Str != "" {
rel.cols[i].Name = s.Str
}
}
}
func listItems(l *ast.List) []ast.Node {
if l == nil {
return nil
}
return l.Items
}
func (a *analyzer) typeJoinConditions(item ast.Node) error {
je, ok := item.(*ast.JoinExpr)
if !ok {
return nil
}
if err := a.typeJoinConditions(je.Larg); err != nil {
return err
}
if err := a.typeJoinConditions(je.Rarg); err != nil {
return err
}
if je.Quals != nil {
if _, err := a.typeExpr(je.Quals); err != nil {
return fmt.Errorf("ON: %w", err)
}
}
return nil
}