chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,945 @@
|
||||
// Copyright 2024 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package framework
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
cerrors "github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/procedures"
|
||||
"gopkg.in/src-d/go-errors.v1"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
"github.com/dolthub/doltgresql/core/extensions"
|
||||
"github.com/dolthub/doltgresql/core/extensions/pg_extension"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// ErrFunctionDoesNotExist is returned when the function in use cannot be found.
|
||||
var ErrFunctionDoesNotExist = errors.NewKind(`function %s does not exist`)
|
||||
|
||||
// Function is an expression that represents either a CompiledFunction or a QuickFunction.
|
||||
type Function interface {
|
||||
sql.FunctionExpression
|
||||
sql.NonDeterministicExpression
|
||||
specificFuncImpl()
|
||||
}
|
||||
|
||||
// CompiledFunction is an expression that represents a fully-analyzed PostgreSQL function.
|
||||
type CompiledFunction struct {
|
||||
Name string
|
||||
Arguments []sql.Expression
|
||||
IsOperator bool
|
||||
overloads *Overloads
|
||||
fnOverloads []Overload
|
||||
overload overloadMatch
|
||||
originalTypes []*pgtypes.DoltgresType
|
||||
callResolved []*pgtypes.DoltgresType
|
||||
runner sql.StatementRunner
|
||||
stashedErr error
|
||||
}
|
||||
|
||||
var _ sql.FunctionExpression = (*CompiledFunction)(nil)
|
||||
var _ sql.NonDeterministicExpression = (*CompiledFunction)(nil)
|
||||
var _ procedures.InterpreterExpr = (*CompiledFunction)(nil)
|
||||
var _ sql.RowIterExpression = (*CompiledFunction)(nil)
|
||||
|
||||
// NewCompiledFunction returns a newly compiled function.
|
||||
func NewCompiledFunction(ctx *sql.Context, name string, args []sql.Expression, functions *Overloads, isOperator bool) *CompiledFunction {
|
||||
return newCompiledFunctionInternal(ctx, name, args, functions, functions.overloadsForParams(len(args)), isOperator, nil)
|
||||
}
|
||||
|
||||
// newCompiledFunctionInternal is called internally, which skips steps that may have already been processed.
|
||||
func newCompiledFunctionInternal(
|
||||
ctx *sql.Context,
|
||||
name string,
|
||||
args []sql.Expression,
|
||||
overloads *Overloads,
|
||||
fnOverloads []Overload,
|
||||
isOperator bool,
|
||||
runner sql.StatementRunner,
|
||||
) *CompiledFunction {
|
||||
c := &CompiledFunction{
|
||||
Name: name,
|
||||
Arguments: args,
|
||||
IsOperator: isOperator,
|
||||
overloads: overloads,
|
||||
fnOverloads: fnOverloads,
|
||||
runner: runner,
|
||||
}
|
||||
// First we'll analyze all the parameters.
|
||||
originalTypes, err := c.analyzeParameters(ctx)
|
||||
if err != nil {
|
||||
// Errors should be returned from the call to Eval, so we'll stash it for now
|
||||
c.stashedErr = err
|
||||
return c
|
||||
}
|
||||
// Next we'll resolve the overload based on the parameters given.
|
||||
overload, err := c.resolve(ctx, overloads, fnOverloads, originalTypes)
|
||||
if err != nil {
|
||||
c.stashedErr = err
|
||||
return c
|
||||
}
|
||||
// If we do not receive an overload, then the parameters given did not result in a valid match
|
||||
if !overload.Valid() {
|
||||
if isOperator {
|
||||
if strings.HasPrefix(name, "internal_binary_operator_func_") {
|
||||
opStr := strings.TrimPrefix(name, "internal_binary_operator_func_")
|
||||
var leftType, rightType string
|
||||
if len(originalTypes) > 0 {
|
||||
leftType = originalTypes[0].String()
|
||||
}
|
||||
if len(originalTypes) > 1 {
|
||||
rightType = originalTypes[1].String()
|
||||
}
|
||||
c.stashedErr = cerrors.Errorf("operator does not exist: %s %s %s", leftType, opStr, rightType)
|
||||
return c
|
||||
} else if strings.HasPrefix(name, "internal_unary_operator_func_") {
|
||||
opStr := strings.TrimPrefix(name, "internal_unary_operator_func_")
|
||||
var childType string
|
||||
if len(originalTypes) > 0 {
|
||||
childType = originalTypes[0].String()
|
||||
}
|
||||
c.stashedErr = cerrors.Errorf("operator does not exist: %s%s", opStr, childType)
|
||||
return c
|
||||
}
|
||||
}
|
||||
c.stashedErr = ErrFunctionDoesNotExist.New(c.OverloadString(originalTypes))
|
||||
return c
|
||||
}
|
||||
|
||||
fn := overload.Function()
|
||||
|
||||
// Then we'll handle the polymorphic types
|
||||
// https://www.postgresql.org/docs/15/extend-type-system.html#EXTEND-TYPES-POLYMORPHIC
|
||||
c.callResolved = make([]*pgtypes.DoltgresType, len(overload.params.paramTypes)+1)
|
||||
hasPolymorphicParam := false
|
||||
for i, param := range overload.params.paramTypes {
|
||||
if param.IsPolymorphicType() {
|
||||
// resolve will ensure that the parameter types are valid, so we can just assign them here
|
||||
hasPolymorphicParam = true
|
||||
c.callResolved[i] = originalTypes[i]
|
||||
} else if param.ID == pgtypes.Any.ID {
|
||||
c.callResolved[i] = originalTypes[i]
|
||||
} else if i < len(args) {
|
||||
if d, ok := args[i].Type(ctx).(*pgtypes.DoltgresType); ok {
|
||||
if param.IsRecordType() && d.IsCompositeType() {
|
||||
// Preserve the composite type's field info (CompositeAttrs) so that
|
||||
// functions like row_to_json can access column names at call time.
|
||||
param = d
|
||||
} else {
|
||||
// `param` is a default type which does not have type modifier set
|
||||
param = param.WithAttTypMod(d.GetAttTypMod())
|
||||
}
|
||||
}
|
||||
c.callResolved[i] = param
|
||||
}
|
||||
}
|
||||
returnType := fn.GetReturn()
|
||||
c.callResolved[len(c.callResolved)-1] = returnType
|
||||
if returnType.IsPolymorphicType() {
|
||||
if hasPolymorphicParam {
|
||||
c.callResolved[len(c.callResolved)-1] = c.resolvePolymorphicReturnType(overload.params.paramTypes, originalTypes, returnType)
|
||||
} else if c.Name == "array_in" || c.Name == "array_recv" || c.Name == "enum_in" || c.Name == "enum_recv" || c.Name == "anyenum_in" || c.Name == "anyenum_recv" {
|
||||
// The return type should resolve to the type of OID value passed in as second argument.
|
||||
// TODO: Possible that the oid type has a special property with polymorphic return types,
|
||||
// in that perhaps their value will set the return type in the absence of another polymorphic type in the parameter list
|
||||
} else {
|
||||
c.stashedErr = cerrors.Errorf("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.", returnType.String())
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, we assign everything to the function struct
|
||||
c.overload = overload
|
||||
c.originalTypes = originalTypes
|
||||
return c
|
||||
}
|
||||
|
||||
// FunctionName implements the interface sql.Expression.
|
||||
func (c *CompiledFunction) FunctionName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
// Description implements the interface sql.Expression.
|
||||
func (c *CompiledFunction) Description() string {
|
||||
return fmt.Sprintf("The PostgreSQL function `%s`", c.Name)
|
||||
}
|
||||
|
||||
// Resolved implements the interface sql.Expression.
|
||||
func (c *CompiledFunction) Resolved() bool {
|
||||
for _, param := range c.Arguments {
|
||||
if !param.Resolved() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// We don't error until evaluation time, so we need to tell the engine we're resolved if there was a stashed error
|
||||
return c.stashedErr != nil || c.overload.Valid()
|
||||
}
|
||||
|
||||
// StashedError returns the stashed error if one exists. Otherwise, returns nil.
|
||||
func (c *CompiledFunction) StashedError() error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.stashedErr
|
||||
}
|
||||
|
||||
// String implements the interface sql.Expression.
|
||||
func (c *CompiledFunction) String() string {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString(c.Name + "(")
|
||||
for i, param := range c.Arguments {
|
||||
// Aliases will output the string "x as x", which is an artifact of how we build the AST, so we'll bypass it
|
||||
if alias, ok := param.(*expression.Alias); ok {
|
||||
param = alias.Child
|
||||
}
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(param.String())
|
||||
}
|
||||
sb.WriteString(")")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// OverloadString returns the name of the function represented by the given overload.
|
||||
func (c *CompiledFunction) OverloadString(types []*pgtypes.DoltgresType) string {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString(c.Name + "(")
|
||||
for i, t := range types {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(t.String())
|
||||
}
|
||||
sb.WriteString(")")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Type implements the interface sql.Expression.
|
||||
func (c *CompiledFunction) Type(ctx *sql.Context) sql.Type {
|
||||
if len(c.callResolved) > 0 {
|
||||
rt := c.callResolved[len(c.callResolved)-1]
|
||||
rt = getTypeIfRowType(c.IsSRF(), rt)
|
||||
// If the return type is polymorphic type, we need the underlying type to be able to
|
||||
// convert the result value using the base type.
|
||||
// TODO: need to add underlying to these types for IO input and output uses
|
||||
if rt.IsPolymorphicType() && len(c.originalTypes) > 0 {
|
||||
rt = c.originalTypes[0]
|
||||
if rt.IsArrayType() {
|
||||
return rt.ArrayBaseType()
|
||||
}
|
||||
}
|
||||
return rt
|
||||
}
|
||||
// Compilation must have errored, so we'll return the unknown type
|
||||
return pgtypes.Unknown
|
||||
}
|
||||
|
||||
// IsNullable implements the interface sql.Expression.
|
||||
func (c *CompiledFunction) IsNullable(ctx *sql.Context) bool {
|
||||
// All functions seem to return NULL when given a NULL value
|
||||
return true
|
||||
}
|
||||
|
||||
// IsNonDeterministic implements the interface sql.NonDeterministicExpression.
|
||||
func (c *CompiledFunction) IsNonDeterministic() bool {
|
||||
if c.overload.Valid() {
|
||||
return c.overload.Function().NonDeterministic()
|
||||
}
|
||||
// Compilation must have errored, so we'll just return true
|
||||
return true
|
||||
}
|
||||
|
||||
// IsStrict returns whether this function has the STRICT property regarding nulls.
|
||||
func (c *CompiledFunction) IsStrict() bool {
|
||||
if c.overload.Valid() {
|
||||
return c.overload.Function().IsStrict()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsSRF returns whether this function is a set returning function.
|
||||
func (c *CompiledFunction) IsSRF() bool {
|
||||
if c.overload.Valid() {
|
||||
return c.overload.Function().IsSRF()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsVariadic returns whether this function has any variadic parameters.
|
||||
func (c *CompiledFunction) IsVariadic() bool {
|
||||
if c.overload.Valid() {
|
||||
return c.overload.params.variadic != -1
|
||||
}
|
||||
// Compilation must have errored, so we'll just return true
|
||||
return true
|
||||
}
|
||||
|
||||
// Eval implements the interface sql.Expression.
|
||||
func (c *CompiledFunction) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
|
||||
// If we have a stashed error, then we should return that now. Errors are stashed when they're supposed to be
|
||||
// returned during the call to Eval. This helps to ensure consistency with how errors are returned in Postgres.
|
||||
if c.stashedErr != nil {
|
||||
return nil, c.stashedErr
|
||||
}
|
||||
|
||||
// Evaluate all arguments, returning immediately if we encounter a null argument and the function is marked STRICT
|
||||
var err error
|
||||
isStrict := c.overload.Function().IsStrict()
|
||||
args := make([]any, len(c.Arguments))
|
||||
exprTypes := make([]*pgtypes.DoltgresType, len(args))
|
||||
for i, arg := range c.Arguments {
|
||||
args[i], err = arg.Eval(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ok bool
|
||||
if exprTypes[i], ok = arg.Type(ctx).(*pgtypes.DoltgresType); !ok {
|
||||
dt, err := pgtypes.FromGmsTypeToDoltgresType(arg.Type(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args[i], _, _ = dt.Convert(ctx, args[i])
|
||||
exprTypes[i] = dt
|
||||
}
|
||||
if args[i] == nil && isStrict {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.overload.casts) > 0 {
|
||||
targetParamTypes := c.overload.params.paramTypes
|
||||
for i, arg := range args {
|
||||
// For variadic params, we need to identify the corresponding target type
|
||||
var targetType *pgtypes.DoltgresType
|
||||
isVariadicArg := c.overload.params.variadic >= 0 && i >= len(c.overload.params.paramTypes)-1
|
||||
if isVariadicArg {
|
||||
targetType = targetParamTypes[c.overload.params.variadic]
|
||||
if !targetType.IsArrayType() {
|
||||
// should be impossible, we check this at function compile time
|
||||
return nil, cerrors.Errorf("variadic arguments must be array types, was %T", targetType)
|
||||
}
|
||||
targetType = targetType.ArrayBaseType()
|
||||
} else {
|
||||
targetType = targetParamTypes[i]
|
||||
// When the declared parameter type is anyarray, the implicit cast from an
|
||||
// unknown/text argument (via UseInOut) would target anyarray.IoInput which
|
||||
// cannot be loaded as a QuickFunction. Resolve to the concrete array type
|
||||
// (e.g. _aggtype) so the cast uses the real element-type I/O path instead.
|
||||
// TODO: If targetType.ID can be resolved to the concrete type earlier in
|
||||
// processing, then we don't need this check here anymore.
|
||||
if targetType.ID == pgtypes.AnyArray.ID {
|
||||
targetType = c.resolvePolymorphicReturnType(targetParamTypes, exprTypes, targetType)
|
||||
}
|
||||
}
|
||||
|
||||
if c.overload.casts[i].ID.IsValid() {
|
||||
args[i], err = c.overload.casts[i].Eval(ctx, arg, exprTypes[i], targetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, cerrors.Errorf("function %s is missing the appropriate implicit cast", c.OverloadString(c.originalTypes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args = c.overload.params.coalesceVariadicValues(args)
|
||||
|
||||
// Call the function
|
||||
switch f := c.overload.Function().(type) {
|
||||
case Function0:
|
||||
return f.Callable(ctx)
|
||||
case Function1:
|
||||
return f.Callable(ctx, ([2]*pgtypes.DoltgresType)(c.callResolved), args[0])
|
||||
case Function1N:
|
||||
return f.Callable(ctx, c.callResolved, args[0], args[1:])
|
||||
case Function2:
|
||||
return f.Callable(ctx, ([3]*pgtypes.DoltgresType)(c.callResolved), args[0], args[1])
|
||||
case Function2N:
|
||||
return f.Callable(ctx, c.callResolved, args[0], args[1], args[2:])
|
||||
case Function3:
|
||||
return f.Callable(ctx, ([4]*pgtypes.DoltgresType)(c.callResolved), args[0], args[1], args[2])
|
||||
case Function4:
|
||||
return f.Callable(ctx, ([5]*pgtypes.DoltgresType)(c.callResolved), args[0], args[1], args[2], args[3])
|
||||
case Function5:
|
||||
return f.Callable(ctx, ([6]*pgtypes.DoltgresType)(c.callResolved), args[0], args[1], args[2], args[3], args[4])
|
||||
case Function6:
|
||||
return f.Callable(ctx, ([7]*pgtypes.DoltgresType)(c.callResolved), args[0], args[1], args[2], args[3], args[4], args[5])
|
||||
case Function7:
|
||||
return f.Callable(ctx, ([8]*pgtypes.DoltgresType)(c.callResolved), args[0], args[1], args[2], args[3], args[4], args[5], args[6])
|
||||
case InterpretedFunction:
|
||||
return plpgsql.Call(ctx, f, c.runner, c.callResolved, args)
|
||||
case CFunction:
|
||||
cfunc, err := extensions.GetExtensionFunction(f.ExtensionName, f.ExtensionSymbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cargs := make([]pg_extension.NullableDatum, len(args))
|
||||
for i, argType := range f.ParameterTypes { // TODO: ParameterTypes does not account for variadic parameters
|
||||
cConvFunc, ok := cConversionToDatumMap[argType.ID]
|
||||
if !ok {
|
||||
return nil, cerrors.Errorf("no conversion function from Go to C for `%s`", argType.ID.TypeName())
|
||||
}
|
||||
cargs[i], err = cConvFunc(args[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
result, isNotNull := pg_extension.CallFmgrFunction(cfunc.Ptr, cargs...)
|
||||
if isNotNull {
|
||||
cConvFunc, ok := cConversionFromDatumMap[f.ReturnType.ID]
|
||||
if !ok {
|
||||
return nil, cerrors.Errorf("no conversion function from C to Go for `%s`", f.ReturnType.ID.TypeName())
|
||||
}
|
||||
retVal, err := cConvFunc(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return retVal, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
case SQLFunction:
|
||||
return CallSqlFunction(ctx, f, c.runner, args)
|
||||
default:
|
||||
return nil, cerrors.Errorf("unknown function type in CompiledFunction::Eval %T", f)
|
||||
}
|
||||
}
|
||||
|
||||
// EvalRowIter implements sql.RowIterExpression
|
||||
func (c *CompiledFunction) EvalRowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
eval, err := c.Eval(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch v := eval.(type) {
|
||||
case sql.RowIter:
|
||||
return v, nil
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, cerrors.Errorf("function %s returned a value of type %T, which is not a RowIter", c.Name, eval)
|
||||
}
|
||||
}
|
||||
|
||||
// ReturnsRowIter implements the interface sql.RowIterExpression
|
||||
func (c *CompiledFunction) ReturnsRowIter() bool {
|
||||
return c.IsSRF()
|
||||
}
|
||||
|
||||
// Children implements the interface sql.Expression.
|
||||
func (c *CompiledFunction) Children() []sql.Expression {
|
||||
return c.Arguments
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.Expression.
|
||||
func (c *CompiledFunction) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
|
||||
if len(children) != len(c.Arguments) {
|
||||
return nil, sql.ErrInvalidChildrenNumber.New(len(children), len(c.Arguments))
|
||||
}
|
||||
|
||||
// We have to re-resolve here, since the change in children may require it (e.g. we have more type info than we did)
|
||||
return newCompiledFunctionInternal(ctx, c.Name, children, c.overloads, c.fnOverloads, c.IsOperator, c.runner), nil
|
||||
}
|
||||
|
||||
// SetStatementRunner implements the interface analyzer.Interpreter.
|
||||
func (c *CompiledFunction) SetStatementRunner(ctx *sql.Context, runner sql.StatementRunner) sql.Expression {
|
||||
nc := *c
|
||||
nc.runner = runner
|
||||
return &nc
|
||||
}
|
||||
|
||||
// GetQuickFunction returns the QuickFunction form of this function, if it exists. If one does not exist, then this
|
||||
// return nil.
|
||||
func (c *CompiledFunction) GetQuickFunction() QuickFunction {
|
||||
if c.stashedErr != nil || !c.Resolved() || !c.overload.Valid() || c.overload.params.variadic != -1 ||
|
||||
len(c.overload.casts) > 0 {
|
||||
return nil
|
||||
}
|
||||
switch f := c.overload.Function().(type) {
|
||||
case Function1:
|
||||
return &QuickFunction1{
|
||||
Name: c.Name,
|
||||
Argument: c.Arguments[0],
|
||||
IsStrict: c.overload.Function().IsStrict(),
|
||||
IsSRF: c.IsSRF(),
|
||||
callResolved: ([2]*pgtypes.DoltgresType)(c.callResolved),
|
||||
function: f,
|
||||
}
|
||||
case Function2:
|
||||
return &QuickFunction2{
|
||||
Name: c.Name,
|
||||
Arguments: ([2]sql.Expression)(c.Arguments),
|
||||
IsStrict: c.overload.Function().IsStrict(),
|
||||
IsSRF: c.IsSRF(),
|
||||
callResolved: ([3]*pgtypes.DoltgresType)(c.callResolved),
|
||||
function: f,
|
||||
}
|
||||
case Function3:
|
||||
return &QuickFunction3{
|
||||
Name: c.Name,
|
||||
Arguments: ([3]sql.Expression)(c.Arguments),
|
||||
IsStrict: c.overload.Function().IsStrict(),
|
||||
IsSRF: c.IsSRF(),
|
||||
callResolved: ([4]*pgtypes.DoltgresType)(c.callResolved),
|
||||
function: f,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// resolve returns an overloadMatch that either matches the given parameters exactly, or is a viable match after casting.
|
||||
// Returns an invalid overloadMatch if a viable match is not found.
|
||||
func (c *CompiledFunction) resolve(ctx *sql.Context, overloads *Overloads, fnOverloads []Overload, argTypes []*pgtypes.DoltgresType) (overloadMatch, error) {
|
||||
// First check for an exact match
|
||||
exactMatch, found := overloads.ExactMatchForTypes(argTypes...)
|
||||
if found {
|
||||
return overloadMatch{
|
||||
params: Overload{
|
||||
function: exactMatch,
|
||||
paramTypes: argTypes,
|
||||
argTypes: argTypes,
|
||||
variadic: -1,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
// There are no exact matches, so now we'll look through all overloads to determine the best match. This is
|
||||
// much more work, but there's a performance penalty for runtime overload resolution in Postgres as well.
|
||||
if c.IsOperator {
|
||||
return c.resolveOperator(ctx, argTypes, overloads, fnOverloads)
|
||||
} else {
|
||||
return c.resolveFunction(ctx, argTypes, fnOverloads)
|
||||
}
|
||||
}
|
||||
|
||||
// resolveOperator resolves an operator according to the rules defined by Postgres.
|
||||
// https://www.postgresql.org/docs/15/typeconv-oper.html
|
||||
func (c *CompiledFunction) resolveOperator(ctx *sql.Context, argTypes []*pgtypes.DoltgresType, overloads *Overloads, fnOverloads []Overload) (overloadMatch, error) {
|
||||
// Binary operators treat unknown literals as the other type, so we'll account for that here to see if we can find
|
||||
// an "exact" match.
|
||||
if len(argTypes) == 2 {
|
||||
leftUnknownType := argTypes[0].ID == pgtypes.Unknown.ID
|
||||
rightUnknownType := argTypes[1].ID == pgtypes.Unknown.ID
|
||||
if (leftUnknownType && !rightUnknownType) || (!leftUnknownType && rightUnknownType) {
|
||||
var typ *pgtypes.DoltgresType
|
||||
identity := casts.Cast{
|
||||
ID: id.NewCast(argTypes[0].ID, argTypes[1].ID),
|
||||
CastType: casts.CastType_Explicit,
|
||||
Function: id.NullFunction,
|
||||
UseInOut: false,
|
||||
}
|
||||
opCasts := []casts.Cast{identity, identity}
|
||||
if leftUnknownType {
|
||||
opCasts[0].UseInOut = true
|
||||
typ = argTypes[1]
|
||||
} else {
|
||||
opCasts[1].UseInOut = true
|
||||
typ = argTypes[0]
|
||||
}
|
||||
if exactMatch, ok := overloads.ExactMatchForTypes(typ, typ); ok {
|
||||
return overloadMatch{
|
||||
params: Overload{
|
||||
function: exactMatch,
|
||||
paramTypes: []*pgtypes.DoltgresType{typ, typ},
|
||||
argTypes: []*pgtypes.DoltgresType{typ, typ},
|
||||
variadic: -1,
|
||||
},
|
||||
casts: opCasts,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// From this point, the steps appear to be the same for functions and operators
|
||||
return c.resolveFunction(ctx, argTypes, fnOverloads)
|
||||
}
|
||||
|
||||
// resolveFunction resolves a function according to the rules defined by Postgres.
|
||||
// https://www.postgresql.org/docs/15/typeconv-func.html
|
||||
func (c *CompiledFunction) resolveFunction(ctx *sql.Context, argTypes []*pgtypes.DoltgresType, overloads []Overload) (overloadMatch, error) {
|
||||
// First we'll discard all overloads that do not have implicitly-convertible param types
|
||||
compatibleOverloads, err := c.typeCompatibleOverloads(ctx, overloads, argTypes)
|
||||
if err != nil {
|
||||
return overloadMatch{}, err
|
||||
}
|
||||
|
||||
// No compatible overloads available, return early
|
||||
if len(compatibleOverloads) == 0 {
|
||||
return overloadMatch{}, nil
|
||||
}
|
||||
|
||||
// If we've found exactly one match then we'll return that one
|
||||
// TODO: we need to also prefer non-variadic functions here over variadic ones (no such conflict can exist for now)
|
||||
// https://www.postgresql.org/docs/15/typeconv-func.html
|
||||
if len(compatibleOverloads) == 1 {
|
||||
return compatibleOverloads[0], nil
|
||||
}
|
||||
|
||||
// Next rank the candidates by the number of params whose types match exactly
|
||||
closestMatches := c.closestTypeMatches(argTypes, compatibleOverloads)
|
||||
|
||||
// Now check again for exactly one match
|
||||
if len(closestMatches) == 1 {
|
||||
return closestMatches[0], nil
|
||||
}
|
||||
|
||||
// If there was more than a single match, try to find the one with the most preferred type conversions
|
||||
preferredOverloads := c.preferredTypeMatches(argTypes, closestMatches)
|
||||
|
||||
// Check once more for exactly one match
|
||||
if len(preferredOverloads) == 1 {
|
||||
return preferredOverloads[0], nil
|
||||
}
|
||||
|
||||
// Next we'll check the type categories for `unknown` types
|
||||
unknownOverloads, ok := c.unknownTypeCategoryMatches(argTypes, preferredOverloads)
|
||||
if !ok {
|
||||
return overloadMatch{}, nil
|
||||
}
|
||||
|
||||
// Check again for exactly one match
|
||||
if len(unknownOverloads) == 1 {
|
||||
return unknownOverloads[0], nil
|
||||
}
|
||||
|
||||
// No matching function overload found
|
||||
return overloadMatch{}, nil
|
||||
}
|
||||
|
||||
// typeCompatibleOverloads returns all overloads that have a matching number of params whose types can be
|
||||
// implicitly converted to the ones provided. This is the set of all possible overloads that could be used with the
|
||||
// param types provided.
|
||||
func (c *CompiledFunction) typeCompatibleOverloads(ctx *sql.Context, fnOverloads []Overload, argTypes []*pgtypes.DoltgresType) ([]overloadMatch, error) {
|
||||
castsColl, err := core.GetCastsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var compatible []overloadMatch
|
||||
for _, overload := range fnOverloads {
|
||||
isConvertible := true
|
||||
overloadCasts := make([]casts.Cast, len(argTypes))
|
||||
// Polymorphic parameters must be gathered so that we can later verify that they all have matching base types
|
||||
var polymorphicParameters []*pgtypes.DoltgresType
|
||||
var polymorphicTargets []*pgtypes.DoltgresType
|
||||
for i := range argTypes {
|
||||
paramType := overload.argTypes[i]
|
||||
if paramType.IsValidForPolymorphicType(argTypes[i]) {
|
||||
overloadCasts[i] = casts.Cast{
|
||||
ID: id.NewCast(argTypes[i].ID, paramType.ID),
|
||||
CastType: casts.CastType_Explicit,
|
||||
Function: id.NullFunction,
|
||||
UseInOut: false,
|
||||
}
|
||||
polymorphicParameters = append(polymorphicParameters, paramType)
|
||||
polymorphicTargets = append(polymorphicTargets, argTypes[i])
|
||||
} else if paramType.IsRecordType() && argTypes[i].IsCompositeType() {
|
||||
// Composite types (e.g. table row types) are compatible with the generic Record parameter.
|
||||
overloadCasts[i] = casts.Cast{
|
||||
ID: id.NewCast(argTypes[i].ID, paramType.ID),
|
||||
CastType: casts.CastType_Explicit,
|
||||
Function: id.NullFunction,
|
||||
UseInOut: false,
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
overloadCasts[i], err = castsColl.GetImplicitCast(ctx, argTypes[i], paramType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !overloadCasts[i].ID.IsValid() {
|
||||
isConvertible = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isConvertible && c.polymorphicTypesCompatible(polymorphicParameters, polymorphicTargets) {
|
||||
compatible = append(compatible, overloadMatch{params: overload, casts: overloadCasts})
|
||||
}
|
||||
}
|
||||
return compatible, nil
|
||||
}
|
||||
|
||||
// closestTypeMatches returns the set of overload candidates that have the most exact type matches for the arg types
|
||||
// provided.
|
||||
func (*CompiledFunction) closestTypeMatches(argTypes []*pgtypes.DoltgresType, candidates []overloadMatch) []overloadMatch {
|
||||
matchCount := 0
|
||||
var matches []overloadMatch
|
||||
for _, cand := range candidates {
|
||||
currentMatchCount := 0
|
||||
for argIdx := range argTypes {
|
||||
argType := cand.params.argTypes[argIdx]
|
||||
if argTypes[argIdx].ID == argType.ID || (argTypes[argIdx].ID == pgtypes.Unknown.ID && argType.ID == pgtypes.Text.ID) {
|
||||
currentMatchCount++
|
||||
}
|
||||
}
|
||||
if currentMatchCount > matchCount {
|
||||
matchCount = currentMatchCount
|
||||
matches = append([]overloadMatch{}, cand)
|
||||
} else if currentMatchCount == matchCount {
|
||||
matches = append(matches, cand)
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// preferredTypeMatches returns the overload candidates that have the most preferred types for args that require casts.
|
||||
func (*CompiledFunction) preferredTypeMatches(argTypes []*pgtypes.DoltgresType, candidates []overloadMatch) []overloadMatch {
|
||||
preferredCount := 0
|
||||
var preferredOverloads []overloadMatch
|
||||
for _, cand := range candidates {
|
||||
currentPreferredCount := 0
|
||||
for argIdx := range argTypes {
|
||||
argType := cand.params.argTypes[argIdx]
|
||||
if argTypes[argIdx].ID != argType.ID && argType.IsPreferred {
|
||||
currentPreferredCount++
|
||||
}
|
||||
}
|
||||
|
||||
if currentPreferredCount > preferredCount {
|
||||
preferredCount = currentPreferredCount
|
||||
preferredOverloads = append([]overloadMatch{}, cand)
|
||||
} else if currentPreferredCount == preferredCount {
|
||||
preferredOverloads = append(preferredOverloads, cand)
|
||||
}
|
||||
}
|
||||
return preferredOverloads
|
||||
}
|
||||
|
||||
// unknownTypeCategoryMatches checks the type categories of `unknown` types. These types have an inherent bias toward
|
||||
// the string category since an `unknown` literal resembles a string. Returns false if the resolution should fail.
|
||||
func (c *CompiledFunction) unknownTypeCategoryMatches(argTypes []*pgtypes.DoltgresType, candidates []overloadMatch) ([]overloadMatch, bool) {
|
||||
matches := make([]overloadMatch, len(candidates))
|
||||
copy(matches, candidates)
|
||||
// For our first loop, we'll filter matches based on whether they accept the string category
|
||||
for argIdx := range argTypes {
|
||||
// We're only concerned with `unknown` types
|
||||
if argTypes[argIdx].ID != pgtypes.Unknown.ID {
|
||||
continue
|
||||
}
|
||||
var newMatches []overloadMatch
|
||||
for _, match := range matches {
|
||||
if match.params.argTypes[argIdx].TypCategory == pgtypes.TypeCategory_StringTypes {
|
||||
newMatches = append(newMatches, match)
|
||||
}
|
||||
}
|
||||
// If we've found matches in this step, then we'll update our match set
|
||||
if len(newMatches) > 0 {
|
||||
matches = newMatches
|
||||
}
|
||||
}
|
||||
// Return early if we've filtered down to a single match
|
||||
if len(matches) == 1 {
|
||||
return matches, true
|
||||
}
|
||||
// TODO: implement the remainder of step 4.e. from the documentation (following code assumes it has been implemented)
|
||||
// ...
|
||||
|
||||
// If we've discarded every function, then we'll actually return all original candidates
|
||||
if len(matches) == 0 {
|
||||
return candidates, true
|
||||
}
|
||||
// In this case, we've trimmed at least one candidate, so we'll return our new matches
|
||||
return matches, true
|
||||
}
|
||||
|
||||
// polymorphicTypesCompatible returns whether any polymorphic types given are compatible with the expression types given
|
||||
func (*CompiledFunction) polymorphicTypesCompatible(paramTypes []*pgtypes.DoltgresType, exprTypes []*pgtypes.DoltgresType) bool {
|
||||
if len(paramTypes) != len(exprTypes) {
|
||||
return false
|
||||
}
|
||||
// If there are less than two parameters then we don't even need to check
|
||||
if len(paramTypes) < 2 {
|
||||
return true
|
||||
}
|
||||
|
||||
// If one of the types is anyarray, then anyelement behaves as anynonarray, so we can convert them to anynonarray
|
||||
for _, paramType := range paramTypes {
|
||||
if paramType.ID == pgtypes.AnyArray.ID {
|
||||
// At least one parameter is anyarray, so copy all parameters to a new slice and replace anyelement with anynonarray
|
||||
newParamTypes := make([]*pgtypes.DoltgresType, len(paramTypes))
|
||||
copy(newParamTypes, paramTypes)
|
||||
for i := range newParamTypes {
|
||||
if paramTypes[i].ID == pgtypes.AnyElement.ID {
|
||||
newParamTypes[i] = pgtypes.AnyNonArray
|
||||
}
|
||||
}
|
||||
paramTypes = newParamTypes
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The base type is the type that must match between all polymorphic types.
|
||||
var baseType *pgtypes.DoltgresType
|
||||
for i, paramType := range paramTypes {
|
||||
if paramType.IsPolymorphicType() && exprTypes[i].ID != pgtypes.Unknown.ID {
|
||||
// Although we do this check before we ever reach this function, we do it again as we may convert anyelement
|
||||
// to anynonarray, which changes type validity
|
||||
if !paramType.IsValidForPolymorphicType(exprTypes[i]) {
|
||||
return false
|
||||
}
|
||||
// Get the base expression type that we'll compare against
|
||||
baseExprType := exprTypes[i]
|
||||
if baseExprType.IsArrayType() {
|
||||
baseExprType = baseExprType.ArrayBaseType()
|
||||
}
|
||||
// TODO: handle range types
|
||||
// Check that the base expression type matches the previously-found base type
|
||||
if baseType.IsEmptyType() {
|
||||
baseType = baseExprType
|
||||
} else if baseType.ID != baseExprType.ID {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// resolvePolymorphicReturnType returns the type that should be used for the return type. If the return type is not a
|
||||
// polymorphic type, then the return type is directly returned. However, if the return type is a polymorphic type, then
|
||||
// the type is determined using the expression types and parameter types. This makes the assumption that everything has
|
||||
// already been validated.
|
||||
func (c *CompiledFunction) resolvePolymorphicReturnType(functionInterfaceTypes []*pgtypes.DoltgresType, originalTypes []*pgtypes.DoltgresType, returnType *pgtypes.DoltgresType) *pgtypes.DoltgresType {
|
||||
if !returnType.IsPolymorphicType() {
|
||||
return returnType
|
||||
}
|
||||
// We can use the first polymorphic non-unknown type that we find, since we can morph it into any type that we need.
|
||||
// We've verified that all polymorphic types are compatible in a previous step, so this is safe to do.
|
||||
var firstPolymorphicType *pgtypes.DoltgresType
|
||||
for i, functionInterfaceType := range functionInterfaceTypes {
|
||||
if functionInterfaceType.IsPolymorphicType() && originalTypes[i].ID != pgtypes.Unknown.ID {
|
||||
firstPolymorphicType = originalTypes[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if all types are `unknown`, use `text` type
|
||||
if firstPolymorphicType.IsEmptyType() {
|
||||
firstPolymorphicType = pgtypes.Text
|
||||
}
|
||||
|
||||
switch returnType.ID {
|
||||
case pgtypes.AnyElement.ID, pgtypes.AnyNonArray.ID:
|
||||
// For return types, anyelement behaves the same as anynonarray.
|
||||
// This isn't explicitly in the documentation, however it does note that:
|
||||
// "...anynonarray and anyenum do not represent separate type variables; they are the same type as anyelement..."
|
||||
// The implication of this being that anyelement will always return the base type even for array types,
|
||||
// just like anynonarray would.
|
||||
if firstPolymorphicType.IsArrayType() {
|
||||
return firstPolymorphicType.ArrayBaseType()
|
||||
} else {
|
||||
return firstPolymorphicType
|
||||
}
|
||||
case pgtypes.AnyArray.ID:
|
||||
// Array types will return themselves, so this is safe
|
||||
if firstPolymorphicType.IsArrayType() {
|
||||
return firstPolymorphicType
|
||||
} else if firstPolymorphicType.ID == pgtypes.Internal.ID {
|
||||
return pgtypes.IDToBuiltInDoltgresType[firstPolymorphicType.BaseTypeForInternal]
|
||||
} else {
|
||||
return firstPolymorphicType.ToArrayType()
|
||||
}
|
||||
default:
|
||||
panic(cerrors.Errorf("`%s` is not yet handled during function compilation", returnType.String()))
|
||||
}
|
||||
}
|
||||
|
||||
// analyzeParameters analyzes the parameters within an Eval call.
|
||||
func (c *CompiledFunction) analyzeParameters(ctx *sql.Context) (originalTypes []*pgtypes.DoltgresType, err error) {
|
||||
originalTypes = make([]*pgtypes.DoltgresType, len(c.Arguments))
|
||||
for i, param := range c.Arguments {
|
||||
returnType := param.Type(ctx)
|
||||
if extendedType, ok := returnType.(*pgtypes.DoltgresType); ok && !extendedType.IsEmptyType() {
|
||||
if extendedType.TypType == pgtypes.TypeType_Domain {
|
||||
extendedType = extendedType.DomainUnderlyingBaseType()
|
||||
}
|
||||
originalTypes[i] = extendedType
|
||||
} else {
|
||||
// TODO: we need to remove GMS types from all of our expressions so that we can remove this
|
||||
dt, err := pgtypes.FromGmsTypeToDoltgresType(returnType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
originalTypes[i] = dt
|
||||
}
|
||||
}
|
||||
return originalTypes, nil
|
||||
}
|
||||
|
||||
// specificFuncImpl implements the interface sql.Expression.
|
||||
func (*CompiledFunction) specificFuncImpl() {}
|
||||
|
||||
// getTypeIfRowType returns the underlying type if it's Row Type;
|
||||
// otherwise, it returns the type that is passed.
|
||||
func getTypeIfRowType(isSRF bool, t *pgtypes.DoltgresType) *pgtypes.DoltgresType {
|
||||
if isSRF {
|
||||
// TODO: need support for used defined types
|
||||
if typ, ok := pgtypes.IDToBuiltInDoltgresType[t.Elem.ID]; ok {
|
||||
return typ
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ResolveDefaultValues adds missing arguments if there is any using the default value set on the parameter.
|
||||
// It checks if it's a valid SQL function that has fewer arguments than defined parameters.
|
||||
func (c *CompiledFunction) ResolveDefaultValues(ctx *sql.Context, getDefExpr func(defExpr string) (sql.Expression, error)) error {
|
||||
if !c.overload.Valid() {
|
||||
return nil
|
||||
}
|
||||
sqlFunc, ok := c.overload.params.function.(SQLFunction)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(c.Arguments) < len(sqlFunc.ParameterTypes) {
|
||||
castsColl, err := core.GetCastsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, param := range sqlFunc.ParameterTypes {
|
||||
if i < len(c.Arguments) {
|
||||
if exprTypeId := c.Arguments[i].Type(ctx).(*pgtypes.DoltgresType).ID; exprTypeId != pgtypes.Unknown.ID && param.ID != exprTypeId {
|
||||
// if non-matching type, then skip appending defaults
|
||||
break
|
||||
}
|
||||
} else if sqlFunc.ParameterDefaults[i] != "" {
|
||||
// only if there is default, then append
|
||||
cdv, err := getDefExpr(sqlFunc.ParameterDefaults[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Arguments = append(c.Arguments, cdv)
|
||||
implicitCast, err := castsColl.GetImplicitCast(ctx, cdv.Type(ctx).(*pgtypes.DoltgresType), sqlFunc.ParameterTypes[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.overload.casts = append(c.overload.casts, implicitCast)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user