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

1039 lines
29 KiB
Go

package analyzer
import (
"fmt"
"slices"
"strings"
"github.com/sqlc-dev/sqlc/internal/core"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)
type exprType struct {
// typeOID is the type's row: the instance when the catalog holds one,
// else the family, else 0 for a type no dialect seeded and no schema
// declared.
typeOID int64
// expr is the whole expression when it says more than the row does — a
// cast to numeric(5, 1) when only numeric is a row — or names a type
// the catalog does not hold. Analysis never adds a type: it reports the
// expression the query used and carries on, so a query can be analyzed
// against a catalog it cannot write to.
expr *core.TypeExpr
nullable bool
sourceClassOID int64
sourceAttributeOID int64
sourceTableAlias string
}
func (a *analyzer) typeExpr(n ast.Node) (exprType, error) {
switch e := n.(type) {
case nil:
return exprType{}, nil
case *ast.TODO:
return exprType{}, nil
case *ast.A_Const:
return a.typeConst(e)
case *ast.ColumnRef:
return a.typeColumnRef(e)
case *ast.ParamRef:
return a.typeParamRef(e)
case *ast.A_Expr:
return a.typeAExpr(e)
case *ast.BoolExpr:
return a.typeBoolExpr(e)
case *ast.FuncCall:
return a.typeFuncCall(e)
case *ast.TypeCast:
return a.typeTypeCast(e)
case *ast.NullTest:
if _, err := a.typeExpr(e.Arg); err != nil {
return exprType{}, err
}
return a.boolType(false)
case *ast.In:
return a.typeIn(e)
case *ast.BetweenExpr:
return a.typeBetween(e)
case *ast.CaseExpr:
return a.typeCase(e)
case *ast.CoalesceExpr:
return a.typeCoalesce(e)
case *ast.MinMaxExpr:
return a.typeFirstOf(listItems(e.Args), false)
case *ast.A_ArrayExpr:
return a.typeArrayExpr(e)
case *ast.SubLink:
return a.typeSubLink(e)
case *ast.CollateExpr:
return a.typeExpr(e.Arg)
case *ast.IntervalExpr:
// A dialect with no interval type of its own leaves it untyped.
oid, err := a.cat.TypeOID("interval")
if err != nil {
return exprType{}, nil
}
return exprType{typeOID: oid}, nil
// A user variable and an ordinal in GROUP BY / ORDER BY carry no type of
// their own, and neither is something to resolve.
case *ast.VariableExpr, *ast.Integer, *ast.String, *ast.Float:
return exprType{}, nil
case *ast.List:
for _, item := range listItems(e) {
if _, err := a.typeExpr(item); err != nil {
return exprType{}, err
}
}
return exprType{}, nil
}
return exprType{}, fmt.Errorf("typeExpr: unsupported %T", n)
}
func (a *analyzer) typeConst(c *ast.A_Const) (exprType, error) {
switch v := c.Val.(type) {
case *ast.Integer:
_ = v
oid, err := a.cat.ConstTypeOID(core.ConstInteger)
if err != nil {
return exprType{}, err
}
return exprType{typeOID: oid}, nil
case *ast.Float:
oid, err := a.cat.ConstTypeOID(core.ConstFloat)
if err != nil {
return exprType{}, err
}
return exprType{typeOID: oid}, nil
case *ast.String:
oid, err := a.cat.ConstTypeOID(core.ConstString)
if err != nil {
return exprType{}, err
}
return exprType{typeOID: oid}, nil
case *ast.Boolean:
return a.boolType(false)
case *ast.Null, nil:
return exprType{nullable: true}, nil
}
return exprType{}, fmt.Errorf("typeConst: unsupported %T", c.Val)
}
func (a *analyzer) boolType(nullable bool) (exprType, error) {
oid, err := a.cat.BoolTypeOID()
if err != nil {
return exprType{}, err
}
return exprType{typeOID: oid, nullable: nullable}, nil
}
func (a *analyzer) typeColumnRef(c *ast.ColumnRef) (exprType, error) {
parts := flattenFields(c.Fields)
if len(parts) == 0 {
return exprType{}, fmt.Errorf("column ref: empty")
}
relation := ""
column := parts[0]
if len(parts) >= 2 {
relation = parts[0]
column = parts[1]
}
rel, col, ok, err := a.resolveColumn(relation, column)
if err != nil {
return exprType{}, err
}
// A dotted name may be a column's own, as ClickHouse names the columns
// a Nested column stores as n.a.
if !ok && relation != "" {
rel, col, ok, err = a.resolveColumn("", relation+"."+column)
if err != nil {
return exprType{}, err
}
}
if !ok {
if relation != "" {
return exprType{}, fmt.Errorf("unknown column %q.%q", relation, column)
}
// The name may be one the target list assigned rather than one a
// relation offers.
if t, ok, err := a.typeAlias(column); err != nil || ok {
return t, err
}
return exprType{}, fmt.Errorf("unknown column %q", column)
}
return columnType(rel, col), nil
}
func flattenFields(fields *ast.List) []string {
if fields == nil {
return nil
}
out := make([]string, 0, len(fields.Items))
for _, item := range fields.Items {
switch v := item.(type) {
case *ast.String:
out = append(out, v.Str)
case *ast.A_Star:
out = append(out, "*")
return out
}
}
return out
}
func (a *analyzer) typeParamRef(p *ast.ParamRef) (exprType, error) {
cur, ok := a.params[p.Number]
if !ok {
cur = core.Parameter{Number: p.Number, Name: p.Name}
a.params[p.Number] = cur
}
return exprType{typeOID: cur.TypeOID, expr: cur.Type.WithNullable(false), nullable: !cur.NotNull}, nil
}
func (a *analyzer) inferParam(number int, t exprType) {
cur, ok := a.params[number]
if !ok {
cur = core.Parameter{Number: number}
}
typed := cur.TypeOID == 0 && cur.Type == nil && (t.typeOID != 0 || t.expr != nil)
if typed {
cur.TypeOID = t.typeOID
cur.DataType, cur.IsArray = a.typeNameOf(t)
cur.NotNull = !t.nullable
cur.Type = a.typeExprOf(t)
}
if cur.Source == nil && t.sourceAttributeOID != 0 {
ad, err := a.cat.LookupAttribute(t.sourceAttributeOID)
if err == nil {
cur.Source = &core.ColumnSource{
Schema: ad.Schema,
Table: ad.Table,
TableAlias: t.sourceTableAlias,
Column: ad.Column,
}
}
}
a.params[number] = cur
}
// nameParamAfter names a placeholder compared with a function call after
// the function, the way a placeholder compared with a column is named after
// the column.
func (a *analyzer) nameParamAfter(number int, other ast.Node) {
fc, ok := other.(*ast.FuncCall)
if !ok {
return
}
cur := a.params[number]
if cur.Name == "" && cur.Source == nil {
cur.Name = funcCallName(fc)
a.params[number] = cur
}
}
func (a *analyzer) typeAExpr(e *ast.A_Expr) (exprType, error) {
// Not every engine classifies its operators, so a zero kind is a plain
// operator application rather than an unset field. LIKE and its relatives
// name a real operator too, so they resolve the same way.
switch e.Kind {
case ast.A_Expr_Kind_OP, ast.A_Expr_Kind_LIKE, ast.A_Expr_Kind_ILIKE, ast.A_Expr_Kind_SIMILAR, 0:
case ast.A_Expr_Kind_OP_ANY, ast.A_Expr_Kind_OP_ALL:
return a.typeQuantifiedExpr(e)
case ast.A_Expr_Kind_IN, ast.A_Expr_Kind_BETWEEN, ast.A_Expr_Kind_NOT_BETWEEN,
ast.A_Expr_Kind_BETWEEN_SYM, ast.A_Expr_Kind_NOT_BETWEEN_SYM:
return a.typePredicateList(e)
case ast.A_Expr_Kind_DISTINCT, ast.A_Expr_Kind_NOT_DISTINCT:
return a.typeComparison(e)
case ast.A_Expr_Kind_NULLIF:
return a.typeNullIf(e)
default:
return exprType{}, fmt.Errorf("a_expr: unsupported kind %v", e.Kind)
}
opName := normalizeOperator(opNameFromList(e.Name))
if opName == "" {
return exprType{}, fmt.Errorf("a_expr: unnamed operator")
}
// Engines that do not have a dedicated boolean node report AND and OR as
// operators. They combine predicates whatever the dialect.
if opName == "AND" || opName == "OR" {
if _, err := a.typeExpr(e.Lexpr); err != nil {
return exprType{}, err
}
if _, err := a.typeExpr(e.Rexpr); err != nil {
return exprType{}, err
}
return a.boolType(false)
}
leftT, err := a.typeExpr(e.Lexpr)
if err != nil {
return exprType{}, err
}
// The postfix null tests SQLite has — x ISNULL, x NOTNULL, x NOT NULL —
// arrive as operators with no right operand, and are predicates that
// are never NULL.
if e.Rexpr == nil && isNullTest(opName) {
return a.boolType(false)
}
rightT, err := a.typeExpr(e.Rexpr)
if err != nil {
return exprType{}, err
}
if pr, ok := e.Lexpr.(*ast.ParamRef); ok && rightT.typeOID != 0 {
a.inferParam(pr.Number, rightT)
a.nameParamAfter(pr.Number, e.Rexpr)
leftT = rightT
} else if pr := castParamRef(e.Lexpr); pr != nil {
a.inferParam(pr.Number, rightT)
a.nameParamAfter(pr.Number, e.Rexpr)
}
if pr, ok := e.Rexpr.(*ast.ParamRef); ok && leftT.typeOID != 0 {
a.inferParam(pr.Number, leftT)
a.nameParamAfter(pr.Number, e.Lexpr)
rightT = leftT
} else if pr := castParamRef(e.Rexpr); pr != nil {
a.inferParam(pr.Number, leftT)
a.nameParamAfter(pr.Number, e.Lexpr)
}
overload, err := a.resolveOperator(opName, leftT, rightT)
if err != nil {
return exprType{}, err
}
// An operator's result is NULL when an operand is, except for IS and
// IS NOT, which test for NULL rather than propagate it: x IS NULL and
// x IS y are never NULL, whatever x and y are.
return exprType{
typeOID: overload.ResultTypeOID,
nullable: (leftT.nullable || rightT.nullable) && !isNullTest(opName),
}, nil
}
// castParamRef is the placeholder a cast wraps, as ClickHouse's {p:UInt64}
// is written, or nil. The cast has typed it already; what it is compared
// with still names it and says which column it stands in for.
func castParamRef(n ast.Node) *ast.ParamRef {
tc, ok := n.(*ast.TypeCast)
if !ok {
return nil
}
pr, _ := tc.Arg.(*ast.ParamRef)
return pr
}
// isNullTest reports whether an operator compares with NULL as a value
// rather than propagating it, so that its result is never NULL.
func isNullTest(opName string) bool {
switch opName {
case "IS", "IS NOT", "ISNULL", "NOTNULL", "NOT NULL":
return true
}
return false
}
// typeQuantifiedExpr types "x = ANY($1)" and "x > ALL(...)": the right side
// holds values of the left side's type, and the result is a predicate.
func (a *analyzer) typeQuantifiedExpr(e *ast.A_Expr) (exprType, error) {
leftT, err := a.typeExpr(e.Lexpr)
if err != nil {
return exprType{}, err
}
if err := a.typeOperands(e.Rexpr, leftT); err != nil {
return exprType{}, err
}
return a.boolType(false)
}
// typePredicateList types IN and BETWEEN, where the right side is a list of
// values compared against the left.
func (a *analyzer) typePredicateList(e *ast.A_Expr) (exprType, error) {
leftT, err := a.typeExpr(e.Lexpr)
if err != nil {
return exprType{}, err
}
if l, ok := e.Rexpr.(*ast.List); ok {
for _, item := range listItems(l) {
if err := a.typeOperands(item, leftT); err != nil {
return exprType{}, err
}
}
return a.boolType(false)
}
if err := a.typeOperands(e.Rexpr, leftT); err != nil {
return exprType{}, err
}
return a.boolType(false)
}
// typeIn types the IN node the engines that have one report, where the values
// compared against are held apart from the expression.
func (a *analyzer) typeIn(e *ast.In) (exprType, error) {
leftT, err := a.typeExpr(e.Expr)
if err != nil {
return exprType{}, err
}
for _, item := range e.List {
if err := a.typeOperands(item, leftT); err != nil {
return exprType{}, err
}
}
// "x IN (SELECT ...)" compares x against the subquery's column, and the
// subquery's own placeholders are reported with the rest.
if sel, ok := e.Sel.(*ast.SelectStmt); ok {
cols, err := a.subqueryColumns(sel)
if err != nil {
return exprType{}, err
}
if pr, ok := e.Expr.(*ast.ParamRef); ok && len(cols) > 0 {
if err := a.typeOperands(pr, columnExprType(cols[0])); err != nil {
return exprType{}, err
}
}
}
return a.boolType(false)
}
// typeBetween types the BETWEEN node the engines that have one report.
func (a *analyzer) typeBetween(e *ast.BetweenExpr) (exprType, error) {
leftT, err := a.typeExpr(e.Expr)
if err != nil {
return exprType{}, err
}
for _, bound := range []ast.Node{e.Left, e.Right} {
if err := a.typeOperands(bound, leftT); err != nil {
return exprType{}, err
}
}
return a.boolType(false)
}
// typeCase types CASE. Its result is the first branch's, and it is nullable
// unless every branch and the default are.
func (a *analyzer) typeCase(e *ast.CaseExpr) (exprType, error) {
argT, err := a.typeExpr(e.Arg)
if err != nil {
return exprType{}, err
}
results := make([]ast.Node, 0, len(listItems(e.Args))+1)
for _, item := range listItems(e.Args) {
when, ok := item.(*ast.CaseWhen)
if !ok {
continue
}
// "CASE x WHEN y" compares y against x; "CASE WHEN y" is a predicate.
if e.Arg != nil {
if err := a.typeOperands(when.Expr, argT); err != nil {
return exprType{}, err
}
} else if _, err := a.typeExpr(when.Expr); err != nil {
return exprType{}, err
}
results = append(results, when.Result)
}
if e.Defresult != nil {
results = append(results, e.Defresult)
}
t, err := a.typeFirstOf(results, e.Defresult == nil)
if err != nil {
return exprType{}, err
}
return t, nil
}
// typeCoalesce types COALESCE, which is its first typed argument's type and
// is null only when every argument is.
func (a *analyzer) typeCoalesce(e *ast.CoalesceExpr) (exprType, error) {
var out exprType
found := false
nullable := true
for _, n := range listItems(e.Args) {
t, err := a.typeExpr(n)
if err != nil {
return exprType{}, err
}
if !found && (t.typeOID != 0 || t.expr != nil) {
// The result is an expression's, not the column's it came from.
out = exprType{typeOID: t.typeOID, expr: t.expr}
found = true
}
nullable = nullable && t.nullable
}
out.nullable = nullable
return out, nil
}
// typeFirstOf types a set of alternative results, taking the first one that has
// a type. The result is nullable when any alternative is, or when the caller
// says the set is not exhaustive.
func (a *analyzer) typeFirstOf(nodes []ast.Node, nullable bool) (exprType, error) {
var out exprType
found := false
for _, n := range nodes {
t, err := a.typeExpr(n)
if err != nil {
return exprType{}, err
}
if !found && (t.typeOID != 0 || t.expr != nil) {
out = t
found = true
}
nullable = nullable || t.nullable
}
out.nullable = nullable
return out, nil
}
// typeArrayExpr types ARRAY[...], whose type is an array of its elements'.
func (a *analyzer) typeArrayExpr(e *ast.A_ArrayExpr) (exprType, error) {
elemT, err := a.typeFirstOf(listItems(e.Elements), false)
if err != nil {
return exprType{}, err
}
element := a.exprOf(elemT)
if element == nil {
return exprType{}, nil
}
return a.lookupType(core.Array(element.WithNullable(false))), nil
}
// lookupType is the type an expression refers to: its row when the catalog
// holds one, its family's row when it holds only that, and the expression
// alone when it holds neither.
func (a *analyzer) lookupType(t *core.TypeExpr) exprType {
if t == nil {
return exprType{}
}
found, ok := a.cat.LookupTypeExpr(t)
if !ok {
return exprType{expr: t.WithNullable(false)}
}
out := exprType{typeOID: found.OID}
if found.OID == found.FamilyOID && len(found.Expr.Args) > 0 {
out.expr = found.Expr
}
return out
}
// namedType is the type a name refers to, or the name itself when the catalog
// has no such type.
func (a *analyzer) namedType(name string) exprType {
return a.lookupType(core.ParseTypeExpr(name))
}
// exprOf is a type's expression: the one the analysis carries, or the one
// its row stands for. It is a copy, and nil for an untyped expression.
func (a *analyzer) exprOf(t exprType) *core.TypeExpr {
if t.expr != nil {
return t.expr.Clone()
}
if t.typeOID == 0 {
return nil
}
e, err := a.cat.TypeExprOf(t.typeOID)
if err != nil {
return nil
}
return e
}
// typeExprOf writes a type as the expression a result reports, with the
// expression's own nullability set from the analysis.
func (a *analyzer) typeExprOf(t exprType) *core.TypeExpr {
e := a.exprOf(t)
if e == nil {
return nil
}
e.Nullable = t.nullable
return e
}
// typeNameOf reports a type's innermost family name and whether the type is
// an array, which is the flat view the legacy compiler reads.
func (a *analyzer) typeNameOf(t exprType) (string, bool) {
e := a.exprOf(t)
if e == nil {
return "", false
}
// MySQL's unsigned families are their own types, but codegen reads
// the signed family and an unsigned flag, which the bridge derives
// from the expression.
return strings.TrimSuffix(e.Innermost().Name, " unsigned"), e.IsArray()
}
// columnExprType is the type a result column of a nested query has, as an
// operand of the query around it.
func columnExprType(col core.Column) exprType {
return exprType{typeOID: col.TypeOID, expr: col.Type.WithNullable(false), nullable: !col.NotNull}
}
// familyOID is the row everything about a type is registered on: the end of
// its resolution chain.
func (a *analyzer) familyOID(oid int64) int64 {
chain := a.cat.ResolutionChain(oid)
return chain[len(chain)-1]
}
// typeSubLink types a subquery used as an expression: EXISTS and IN yield a
// predicate, and a scalar subquery yields its first column.
func (a *analyzer) typeSubLink(e *ast.SubLink) (exprType, error) {
if _, err := a.typeExpr(e.Testexpr); err != nil {
return exprType{}, err
}
sel, ok := e.Subselect.(*ast.SelectStmt)
if !ok {
if e.SubLinkType == ast.EXPR_SUBLINK || e.SubLinkType == ast.ARRAY_SUBLINK {
return exprType{nullable: true}, nil
}
return a.boolType(false)
}
cols, err := a.subqueryColumns(sel)
if err != nil {
return exprType{}, err
}
switch e.SubLinkType {
case ast.EXPR_SUBLINK, ast.ARRAY_SUBLINK:
if len(cols) == 0 {
return exprType{nullable: true}, nil
}
// A subquery that matches no row yields NULL.
t := columnExprType(cols[0])
t.nullable = true
return t, nil
default:
return a.boolType(false)
}
}
// typeComparison types IS DISTINCT FROM and its negation, which compare any two
// values and never return NULL.
func (a *analyzer) typeComparison(e *ast.A_Expr) (exprType, error) {
leftT, err := a.typeExpr(e.Lexpr)
if err != nil {
return exprType{}, err
}
rightT, err := a.typeExpr(e.Rexpr)
if err != nil {
return exprType{}, err
}
if err := a.typeOperands(e.Rexpr, leftT); err != nil {
return exprType{}, err
}
if err := a.typeOperands(e.Lexpr, rightT); err != nil {
return exprType{}, err
}
return a.boolType(false)
}
// typeNullIf types NULLIF(x, y), which is x, made nullable.
func (a *analyzer) typeNullIf(e *ast.A_Expr) (exprType, error) {
leftT, err := a.typeExpr(e.Lexpr)
if err != nil {
return exprType{}, err
}
if err := a.typeOperands(e.Rexpr, leftT); err != nil {
return exprType{}, err
}
leftT.nullable = true
return leftT, nil
}
// typeOperands types a node standing opposite one of known type, giving a bare
// placeholder that type.
func (a *analyzer) typeOperands(n ast.Node, other exprType) error {
if pr, ok := n.(*ast.ParamRef); ok {
// Registering the placeholder first keeps the name its syntax gave
// it, as ClickHouse's {name:Type} does.
if _, err := a.typeParamRef(pr); err != nil {
return err
}
if other.typeOID != 0 || other.expr != nil {
a.inferParam(pr.Number, other)
}
return nil
}
_, err := a.typeExpr(n)
return err
}
// normalizeOperator upper-cases a word operator ("like", "is not") so that the
// spelling a user wrote matches the one a dialect seeded. Symbolic operators
// are already canonical.
func normalizeOperator(name string) string {
for _, r := range name {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
return strings.ToUpper(name)
}
}
return name
}
func opNameFromList(l *ast.List) string {
if l == nil {
return ""
}
parts := make([]string, 0, len(l.Items))
for _, item := range l.Items {
if s, ok := item.(*ast.String); ok {
parts = append(parts, s.Str)
}
}
return strings.Join(parts, ".")
}
// resolveOperator finds the overload of an operator over two operand types.
// An operator is registered on a family, so an operand that is an instance,
// an alias or a domain is looked up along its resolution chain: numeric(10,
// 2) + numeric(5, 1) resolves on numeric.
func (a *analyzer) resolveOperator(name string, leftT, rightT exprType) (core.OperatorOverload, error) {
leftChain := a.cat.ResolutionChain(leftT.typeOID)
rightChain := a.cat.ResolutionChain(rightT.typeOID)
all, err := a.cat.FindOperators(name, 0, 0)
if err != nil {
return core.OperatorOverload{}, err
}
// The operator's overloads are read once; the pairs along the two
// chains are tried against them in order, nearest first.
byOperands := make(map[[2]int64]core.OperatorOverload, len(all))
for _, ov := range all {
key := [2]int64{ov.LeftTypeOID, ov.RightTypeOID}
if _, seen := byOperands[key]; !seen {
byOperands[key] = ov
}
}
for _, l := range leftChain {
for _, r := range rightChain {
if ov, ok := byOperands[[2]int64{l, r}]; ok && l != 0 && r != 0 {
return ov, nil
}
}
}
leftOID := leftChain[len(leftChain)-1]
rightOID := rightChain[len(rightChain)-1]
for _, ov := range all {
if leftOID != 0 && ov.LeftTypeOID != 0 && leftOID != ov.LeftTypeOID {
ok, _ := a.cat.CastAllowed(leftOID, ov.LeftTypeOID, "i")
if !ok {
continue
}
}
if rightOID != 0 && ov.RightTypeOID != 0 && rightOID != ov.RightTypeOID {
ok, _ := a.cat.CastAllowed(rightOID, ov.RightTypeOID, "i")
if !ok {
continue
}
}
if (leftOID == 0) != (ov.LeftTypeOID == 0) {
continue
}
if (rightOID == 0) != (ov.RightTypeOID == 0) {
continue
}
return ov, nil
}
// No dialect declares every operator over every type — extensions add
// them, and an operator over a type the schema declared may never have
// been registered. Rather than fail the query, assume the shape the
// operator's name implies: a comparison yields a boolean and anything
// else yields the type it was applied to.
if a.cat.IsComparisonOperator(name) {
boolOID, err := a.cat.BoolTypeOID()
if err != nil {
return core.OperatorOverload{}, err
}
return core.OperatorOverload{Name: name, ResultTypeOID: boolOID}, nil
}
result := leftOID
if result == 0 {
result = rightOID
}
return core.OperatorOverload{Name: name, ResultTypeOID: result}, nil
}
func (a *analyzer) typeBoolExpr(b *ast.BoolExpr) (exprType, error) {
for _, item := range listItems(b.Args) {
if _, err := a.typeExpr(item); err != nil {
return exprType{}, err
}
}
return a.boolType(false)
}
func (a *analyzer) typeFuncCall(f *ast.FuncCall) (exprType, error) {
name := funcCallName(f)
if name == "" {
return exprType{}, fmt.Errorf("func call: missing name")
}
if f.AggStar && (name == "count" || name == "count.*") {
if overloads, err := a.cat.FindProcs("count", nil); err == nil && len(overloads) > 0 {
return exprType{typeOID: overloads[0].ReturnTypeOID, nullable: overloads[0].ReturnNullable}, nil
}
oid, err := a.cat.TypeOID("bigint")
if err != nil {
return exprType{}, err
}
return exprType{typeOID: oid, nullable: false}, nil
}
args := listItems(f.Args)
argTypes := make([]exprType, 0, len(args))
argOIDs := make([]int64, 0, len(args))
anyNullable := false
for _, arg := range args {
t, err := a.typeExpr(arg)
if err != nil {
return exprType{}, err
}
argTypes = append(argTypes, t)
argOIDs = append(argOIDs, t.typeOID)
anyNullable = anyNullable || t.nullable
}
overloads, err := a.cat.FindProcs(name, nil)
if err != nil {
return exprType{}, err
}
if len(overloads) == 0 {
// A dialect's function list is never complete — extensions add to it,
// and so does the user. An unknown function leaves the result untyped
// rather than failing the query.
return exprType{nullable: true}, nil
}
p := a.pickOverload(overloads, argOIDs)
// An argument that is a bare placeholder takes the parameter's type,
// unless the parameter is polymorphic and says nothing.
for i, arg := range args {
if i >= len(p.ArgTypes) {
break
}
if a.isPolymorphicOID(p.ArgTypes[i]) {
continue
}
if err := a.typeOperands(arg, exprType{typeOID: p.ArgTypes[i]}); err != nil {
return exprType{}, err
}
}
ret := a.returnType(p, argTypes)
// A result that depends on an argument's value is spelled by the seed
// as a template over the arguments, filled in from the call.
if computed := a.returnTemplate(p, args, argTypes); computed != nil {
ret = a.lookupType(computed)
}
ret.nullable = p.ReturnNullable
if !p.NeverNull && anyNullable && a.cat.PropagatesNullable() {
ret.nullable = true
}
return ret, nil
}
// returnTemplate fills a seed's return template — Decimal(18, $2) — from
// the call: a $n that stands for an integer literal takes its value, and
// one that stands for a typed argument takes its type. A template that
// cannot be filled leaves the answer to the catalog.
func (a *analyzer) returnTemplate(p core.ProcOverload, args []ast.Node, argTypes []exprType) *core.TypeExpr {
if p.ReturnTemplate == "" {
return nil
}
template := core.ParseTypeExpr(p.ReturnTemplate)
fill := func(arg core.TypeArg) (core.TypeArg, bool) {
n, ok := argIndex(arg.Type.Name)
if !ok || n >= len(args) {
return core.TypeArg{}, false
}
if c, ok := args[n].(*ast.A_Const); ok {
if lit, ok := c.Val.(*ast.Integer); ok {
v := lit.Ival
return core.TypeArg{Label: arg.Label, Int: &v}, true
}
if lit, ok := c.Val.(*ast.String); ok {
v := lit.Str
return core.TypeArg{Label: arg.Label, String: &v}, true
}
}
if t := a.exprOf(argTypes[n]); t != nil {
return core.TypeArg{Label: arg.Label, Type: t.WithNullable(false)}, true
}
return core.TypeArg{}, false
}
for i, arg := range template.Args {
if arg.Type == nil || !strings.HasPrefix(arg.Type.Name, "$") {
continue
}
filled, ok := fill(arg)
if !ok {
return nil
}
template.Args[i] = filled
}
return template
}
// returnType resolves a polymorphic return type — max(anyelement), or a
// seed's "$2" for the type of the second argument — to the type the call was
// made with.
func (a *analyzer) returnType(p core.ProcOverload, argTypes []exprType) exprType {
if p.ReturnTypeOID == 0 || len(argTypes) == 0 {
return exprType{typeOID: p.ReturnTypeOID}
}
name, err := a.cat.TypeName(p.ReturnTypeOID)
if err != nil {
return exprType{typeOID: p.ReturnTypeOID}
}
if n, ok := argIndex(name); ok {
if n < len(argTypes) {
return exprType{typeOID: argTypes[n].typeOID, expr: argTypes[n].expr}
}
return exprType{}
}
if isPolymorphic(name) && (argTypes[0].typeOID != 0 || argTypes[0].expr != nil) {
return exprType{typeOID: argTypes[0].typeOID, expr: argTypes[0].expr}
}
return exprType{typeOID: p.ReturnTypeOID}
}
// argIndex reads a seed's "$n" pseudo-type as the zero-based index of the
// argument whose type it stands for.
func argIndex(typeName string) (int, bool) {
rest, ok := strings.CutPrefix(typeName, "$")
if !ok {
return 0, false
}
n := 0
for _, r := range rest {
if r < '0' || r > '9' {
return 0, false
}
n = n*10 + int(r-'0')
}
if n == 0 {
return 0, false
}
return n - 1, true
}
// isPolymorphicOID reports whether a parameter type accepts any argument.
func (a *analyzer) isPolymorphicOID(oid int64) bool {
if oid == 0 {
return true
}
name, err := a.cat.TypeName(oid)
if err != nil {
return false
}
if _, ok := argIndex(name); ok {
return true
}
return isPolymorphic(name)
}
func isPolymorphic(typeName string) bool {
switch typeName {
case "any", "anyelement", "anyarray", "anynonarray", "anyenum", "anyrange",
"anymultirange", "anycompatible", "anycompatiblearray",
"anycompatiblenonarray", "anycompatiblerange":
return true
}
return false
}
// pickOverload chooses the overload whose parameters the call's arguments
// match best: an exact type match on a parameter beats a match on the
// argument's family, which beats a polymorphic parameter, which beats a
// mismatch, and any overload of the right arity beats one of the wrong
// arity.
func (a *analyzer) pickOverload(overloads []core.ProcOverload, argTypes []int64) core.ProcOverload {
best := -1
bestScore := -1
chains := make([][]int64, len(argTypes))
for j, oid := range argTypes {
if oid != 0 {
chains[j] = a.cat.ResolutionChain(oid)
}
}
for i := range overloads {
ov := &overloads[i]
if len(ov.ArgTypes) != len(argTypes) {
continue
}
score := 0
for j, oid := range argTypes {
switch {
case oid != 0 && oid == ov.ArgTypes[j]:
score += 3
case oid != 0 && slices.Contains(chains[j], ov.ArgTypes[j]):
score += 2
case a.isPolymorphicOID(ov.ArgTypes[j]):
score += 1
}
}
if score > bestScore {
best, bestScore = i, score
}
}
if best >= 0 {
return overloads[best]
}
return overloads[0]
}
func funcCallName(f *ast.FuncCall) string {
if f.Funcname != nil {
return opNameFromList(f.Funcname)
}
if f.Func != nil {
return strings.ToLower(f.Func.Name)
}
return ""
}
func (a *analyzer) typeTypeCast(c *ast.TypeCast) (exprType, error) {
if c.TypeName == nil {
return exprType{}, fmt.Errorf("cast: missing target type")
}
target := core.TypeExprOfTypeName(c.TypeName)
if target == nil {
return exprType{}, fmt.Errorf("cast: missing target type")
}
t := a.lookupType(target)
// A cast is how a query says what an otherwise untyped placeholder
// holds, and a placeholder so typed is not null unless the type says
// otherwise, as ClickHouse's Nullable(String) does. Anything else
// cast is NULL when it was NULL before, or when the type says so.
t.nullable = target.Nullable
if pr, ok := c.Arg.(*ast.ParamRef); ok {
if err := a.typeOperands(pr, t); err != nil {
return exprType{}, err
}
return t, nil
}
arg, err := a.typeExpr(c.Arg)
if err != nil {
return exprType{}, err
}
t.nullable = t.nullable || arg.nullable
return t, nil
}