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
1039 lines
29 KiB
Go
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
|
|
}
|