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

639 lines
15 KiB
Go

package compiler
import (
"fmt"
"log/slog"
"strconv"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/astutils"
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
"github.com/sqlc-dev/sqlc/internal/sql/named"
"github.com/sqlc-dev/sqlc/internal/sql/preprocess"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
func dataType(n *ast.TypeName) string {
if n.Schema != "" {
return n.Schema + "." + n.Name
} else {
return n.Name
}
}
func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar, args []paramRef, params *named.ParamSet, embeds preprocess.EmbedSet) ([]Parameter, error) {
c := comp.catalog
aliasMap := map[string]*ast.TableName{}
// TODO: Deprecate defaultTable
var defaultTable *ast.TableName
var tables []*ast.TableName
typeMap := map[string]map[string]map[string]*catalog.Column{}
indexTable := func(table catalog.Table) error {
tables = append(tables, table.Rel)
if defaultTable == nil {
defaultTable = table.Rel
}
schema := table.Rel.Schema
if schema == "" {
schema = c.DefaultSchema
}
if _, exists := typeMap[schema]; !exists {
typeMap[schema] = map[string]map[string]*catalog.Column{}
}
typeMap[schema][table.Rel.Name] = map[string]*catalog.Column{}
for _, c := range table.Columns {
cc := c
typeMap[schema][table.Rel.Name][c.Name] = cc
}
return nil
}
for _, rv := range rvs {
if rv.Relname == nil {
continue
}
fqn, err := ParseTableName(rv)
if err != nil {
return nil, err
}
if _, found := aliasMap[fqn.Name]; found {
continue
}
table, err := c.GetTable(fqn)
if err != nil {
if qc == nil {
continue
}
// If the table name doesn't exist, first check if it's a CTE
if _, qcerr := qc.GetTable(fqn); qcerr != nil {
return nil, err
}
continue
}
err = indexTable(table)
if err != nil {
return nil, err
}
if rv.Alias != nil {
aliasMap[*rv.Alias.Aliasname] = fqn
}
}
// resolve a table for an embed
for _, embed := range embeds {
table, err := c.GetTable(embed.Table)
if err == nil {
embed.Table = table.Rel
continue
}
if alias, ok := aliasMap[embed.Table.Name]; ok {
embed.Table = alias
continue
}
return nil, fmt.Errorf("unable to resolve table with %q: %w", embed.Orig(), err)
}
var a []Parameter
addUnknownParam := func(ref paramRef) {
defaultP := named.NewInferredParam(ref.name, false)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: p.Name(),
DataType: "any",
IsNamedParam: isNamed,
},
})
}
for _, ref := range args {
switch n := ref.parent.(type) {
case *limitOffset:
defaultP := named.NewInferredParam("offset", true)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: p.Name(),
DataType: "integer",
NotNull: p.NotNull(),
IsNamedParam: isNamed,
},
})
case *limitCount:
defaultP := named.NewInferredParam("limit", true)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: p.Name(),
DataType: "integer",
NotNull: p.NotNull(),
IsNamedParam: isNamed,
},
})
case *ast.A_Expr:
// TODO: While this works for a wide range of simple expressions,
// more complicated expressions will cause this logic to fail.
list := astutils.Search(n.Lexpr, func(node ast.Node) bool {
_, ok := node.(*ast.ColumnRef)
return ok
})
if len(list.Items) == 0 {
list = astutils.Search(n.Rexpr, func(node ast.Node) bool {
_, ok := node.(*ast.ColumnRef)
return ok
})
}
if len(list.Items) == 0 {
// TODO: Move this to database-specific engine package
dataType := "any"
if astutils.Join(n.Name, ".") == "||" {
dataType = "text"
}
defaultP := named.NewParam("")
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: p.Name(),
DataType: dataType,
IsNamedParam: isNamed,
NotNull: p.NotNull(),
IsSqlcSlice: p.IsSqlcSlice(),
},
})
continue
}
switch node := list.Items[0].(type) {
case *ast.ColumnRef:
items := stringSlice(node.Fields)
var key, alias string
switch len(items) {
case 1:
key = items[0]
case 2:
alias = items[0]
key = items[1]
case 3:
// schema := items[0]
alias = items[1]
key = items[2]
default:
panic("too many field items: " + strconv.Itoa(len(items)))
}
search := tables
if alias != "" {
if original, ok := aliasMap[alias]; ok {
search = []*ast.TableName{original}
} else {
var located bool
for _, fqn := range tables {
if fqn.Name == alias {
located = true
search = []*ast.TableName{fqn}
}
}
if !located {
return nil, &sqlerr.Error{
Code: "42703",
Message: fmt.Sprintf("table alias %q does not exist", alias),
Location: node.Location,
}
}
}
}
var found int
for _, table := range search {
schema := table.Schema
if schema == "" {
schema = c.DefaultSchema
}
if c, ok := typeMap[schema][table.Name][key]; ok {
found += 1
if ref.name != "" {
key = ref.name
}
defaultP := named.NewInferredParam(key, c.IsNotNull)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: p.Name(),
OriginalName: c.Name,
DataType: dataType(&c.Type),
NotNull: p.NotNull(),
Unsigned: c.IsUnsigned,
IsArray: c.IsArray,
ArrayDims: c.ArrayDims,
Length: c.Length,
Table: table,
IsNamedParam: isNamed,
IsSqlcSlice: p.IsSqlcSlice(),
},
})
}
}
if found == 0 {
return nil, &sqlerr.Error{
Code: "42703",
Message: fmt.Sprintf("column %q does not exist", key),
Location: node.Location,
}
}
if found > 1 {
return nil, &sqlerr.Error{
Code: "42703",
Message: fmt.Sprintf("column reference %q is ambiguous", key),
Location: node.Location,
}
}
}
case *ast.BetweenExpr:
if n == nil || n.Expr == nil || n.Left == nil || n.Right == nil {
fmt.Println("ast.BetweenExpr is nil")
continue
}
var key string
if ref, ok := n.Expr.(*ast.ColumnRef); ok {
itemsCount := len(ref.Fields.Items)
if str, ok := ref.Fields.Items[itemsCount-1].(*ast.String); ok {
key = str.Str
}
}
for _, table := range tables {
schema := table.Schema
if schema == "" {
schema = c.DefaultSchema
}
if c, ok := typeMap[schema][table.Name][key]; ok {
defaultP := named.NewInferredParam(key, c.IsNotNull)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
var namePrefix string
if !isNamed {
if ref.ref == n.Left {
namePrefix = "from_"
} else if ref.ref == n.Right {
namePrefix = "to_"
}
}
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: namePrefix + p.Name(),
DataType: dataType(&c.Type),
NotNull: p.NotNull(),
Unsigned: c.IsUnsigned,
IsArray: c.IsArray,
ArrayDims: c.ArrayDims,
Table: table,
IsNamedParam: isNamed,
IsSqlcSlice: p.IsSqlcSlice(),
},
})
}
}
case *ast.FuncCall:
fun, err := c.ResolveFuncCall(n)
if err != nil {
// Synthesize a function on the fly to avoid returning with an error
// for an unknown Postgres function (e.g. defined in an extension)
var args []*catalog.Argument
for range n.Args.Items {
args = append(args, &catalog.Argument{
Type: &ast.TypeName{Name: "any"},
})
}
fun = &catalog.Function{
Name: n.Func.Name,
Args: args,
ReturnType: &ast.TypeName{Name: "any"},
}
}
var added bool
for i, item := range n.Args.Items {
funcName := fun.Name
var argName string
switch inode := item.(type) {
case *ast.ParamRef:
if inode.Number != ref.ref.Number {
continue
}
case *ast.TypeCast:
pr, ok := inode.Arg.(*ast.ParamRef)
if !ok {
continue
}
if pr.Number != ref.ref.Number {
continue
}
case *ast.NamedArgExpr:
pr, ok := inode.Arg.(*ast.ParamRef)
if !ok {
continue
}
if pr.Number != ref.ref.Number {
continue
}
if inode.Name != nil {
argName = *inode.Name
}
default:
continue
}
if fun.Args == nil {
defaultName := funcName
if argName != "" {
defaultName = argName
}
defaultP := named.NewInferredParam(defaultName, false)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
added = true
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: p.Name(),
DataType: "any",
IsNamedParam: isNamed,
NotNull: p.NotNull(),
IsSqlcSlice: p.IsSqlcSlice(),
},
})
continue
}
var paramName string
var paramType *ast.TypeName
if argName == "" {
if i < len(fun.Args) {
paramName = fun.Args[i].Name
paramType = fun.Args[i].Type
}
} else {
paramName = argName
for _, arg := range fun.Args {
if arg.Name == argName {
paramType = arg.Type
}
}
if paramType == nil {
panic(fmt.Sprintf("named argument %s has no type", paramName))
}
}
if paramName == "" {
paramName = funcName
}
if paramType == nil {
paramType = &ast.TypeName{Name: ""}
}
defaultP := named.NewInferredParam(paramName, true)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
added = true
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: p.Name(),
DataType: dataType(paramType),
NotNull: p.NotNull(),
IsNamedParam: isNamed,
IsSqlcSlice: p.IsSqlcSlice(),
},
})
}
if fun.ReturnType == nil {
if !added {
addUnknownParam(ref)
}
continue
}
table, err := c.GetTable(&ast.TableName{
Catalog: fun.ReturnType.Catalog,
Schema: fun.ReturnType.Schema,
Name: fun.ReturnType.Name,
})
if err != nil {
if !added {
addUnknownParam(ref)
}
continue
}
err = indexTable(table)
if err != nil {
return nil, err
}
case *ast.ResTarget:
if n.Name == nil {
return nil, fmt.Errorf("*ast.ResTarget has nil name")
}
key := *n.Name
var schema, rel string
// TODO: Deprecate defaultTable
if defaultTable != nil {
schema = defaultTable.Schema
rel = defaultTable.Name
}
if ref.rv != nil {
fqn, err := ParseTableName(ref.rv)
if err != nil {
return nil, err
}
schema = fqn.Schema
rel = fqn.Name
}
if schema == "" {
schema = c.DefaultSchema
}
tableMap, ok := typeMap[schema][rel]
if !ok {
return nil, sqlerr.RelationNotFound(rel)
}
if c, ok := tableMap[key]; ok {
defaultP := named.NewInferredParam(key, c.IsNotNull)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: p.Name(),
OriginalName: c.Name,
DataType: dataType(&c.Type),
NotNull: p.NotNull(),
Unsigned: c.IsUnsigned,
IsArray: c.IsArray,
ArrayDims: c.ArrayDims,
Table: &ast.TableName{Schema: schema, Name: rel},
Length: c.Length,
IsNamedParam: isNamed,
IsSqlcSlice: p.IsSqlcSlice(),
},
})
} else {
return nil, &sqlerr.Error{
Code: "42703",
Message: fmt.Sprintf("column %q does not exist", key),
Location: n.Location,
}
}
case *ast.TypeCast:
if n.TypeName == nil {
return nil, fmt.Errorf("*ast.TypeCast has nil type name")
}
col := toColumn(n.TypeName)
defaultP := named.NewInferredParam(col.Name, col.NotNull)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
col.Name = p.Name()
col.NotNull = p.NotNull()
col.IsNamedParam = isNamed
a = append(a, Parameter{
Number: ref.ref.Number,
Column: col,
})
case *ast.ParamRef:
a = append(a, Parameter{Number: ref.ref.Number})
case *ast.In:
if n == nil || n.List == nil {
fmt.Println("ast.In is nil")
continue
}
number := 0
if pr, ok := n.List[0].(*ast.ParamRef); ok {
number = pr.Number
}
location := 0
var key, alias string
var items []string
if left, ok := n.Expr.(*ast.ColumnRef); ok {
location = left.Location
items = stringSlice(left.Fields)
} else if left, ok := n.Expr.(*ast.ParamRef); ok {
if len(n.List) <= 0 {
continue
}
if right, ok := n.List[0].(*ast.ColumnRef); ok {
location = left.Location
items = stringSlice(right.Fields)
} else {
continue
}
} else {
continue
}
switch len(items) {
case 1:
key = items[0]
case 2:
alias = items[0]
key = items[1]
default:
panic("too many field items: " + strconv.Itoa(len(items)))
}
var found int
if n.Sel == nil {
search := tables
if alias != "" {
if original, ok := aliasMap[alias]; ok {
search = []*ast.TableName{original}
} else {
for _, fqn := range tables {
if fqn.Name == alias {
search = []*ast.TableName{fqn}
}
}
}
}
for _, table := range search {
schema := table.Schema
if schema == "" {
schema = c.DefaultSchema
}
if c, ok := typeMap[schema][table.Name][key]; ok {
found += 1
if ref.name != "" {
key = ref.name
}
defaultP := named.NewInferredParam(key, c.IsNotNull)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
a = append(a, Parameter{
Number: number,
Column: &Column{
Name: p.Name(),
OriginalName: c.Name,
DataType: dataType(&c.Type),
NotNull: c.IsNotNull,
Unsigned: c.IsUnsigned,
IsArray: c.IsArray,
ArrayDims: c.ArrayDims,
Table: table,
IsNamedParam: isNamed,
IsSqlcSlice: p.IsSqlcSlice(),
},
})
}
}
}
if found == 0 {
return nil, &sqlerr.Error{
Code: "42703",
Message: fmt.Sprintf("396: column %q does not exist", key),
Location: location,
}
}
if found > 1 {
return nil, &sqlerr.Error{
Code: "42703",
Message: fmt.Sprintf("in same name column reference %q is ambiguous", key),
Location: location,
}
}
default:
slog.Debug("unsupported reference type", "type", fmt.Sprintf("%T", n))
addUnknownParam(ref)
}
}
return a, nil
}