chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
// Copyright 2025 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 (
|
||||
"github.com/dolthub/doltgresql/core/extensions/pg_extension"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/uuid"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
var cConversionToDatumMap = map[id.Type]func(val any) (pg_extension.NullableDatum, error){
|
||||
pgtypes.Text.ID: textToDatum,
|
||||
pgtypes.Uuid.ID: uuidToDatum,
|
||||
}
|
||||
var cConversionFromDatumMap = map[id.Type]func(datum pg_extension.Datum) (any, error){
|
||||
pgtypes.Text.ID: textFromDatum,
|
||||
pgtypes.Uuid.ID: uuidFromDatum,
|
||||
}
|
||||
|
||||
// textFromDatum converts from a Datum to a TEXT value.
|
||||
func textFromDatum(datum pg_extension.Datum) (any, error) {
|
||||
convertedVal := pg_extension.FromDatumGoString(datum)
|
||||
pg_extension.FreeDatum(datum)
|
||||
return convertedVal, nil
|
||||
}
|
||||
|
||||
// textToDatum converts from a TEXT value to a NullableDatum.
|
||||
func textToDatum(val any) (pg_extension.NullableDatum, error) {
|
||||
if val == nil {
|
||||
return pg_extension.NullableDatum{
|
||||
Value: 0,
|
||||
IsNull: true,
|
||||
}, nil
|
||||
}
|
||||
return pg_extension.NullableDatum{
|
||||
Value: pg_extension.ToDatumGoString(val.(string)),
|
||||
IsNull: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// uuidFromDatum converts from a Datum to a UUID value.
|
||||
func uuidFromDatum(datum pg_extension.Datum) (any, error) {
|
||||
convertedVal := pg_extension.FromDatumGoBytes(datum, 16)
|
||||
pg_extension.FreeDatum(datum)
|
||||
return uuid.FromBytes(convertedVal)
|
||||
}
|
||||
|
||||
// uuidToDatum converts from a UUID value to a NullableDatum.
|
||||
func uuidToDatum(val any) (pg_extension.NullableDatum, error) {
|
||||
if val == nil {
|
||||
return pg_extension.NullableDatum{
|
||||
Value: 0,
|
||||
IsNull: true,
|
||||
}, nil
|
||||
}
|
||||
return pg_extension.NullableDatum{
|
||||
Value: pg_extension.ToDatumGoBytes(val.(uuid.UUID).GetBytes()),
|
||||
IsNull: false,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2025 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 (
|
||||
"github.com/dolthub/doltgresql/core/extensions"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// CFunction is the implementation of functions that host their logic in a shared library.
|
||||
type CFunction struct {
|
||||
ID id.Function
|
||||
ReturnType *pgtypes.DoltgresType
|
||||
ParameterTypes []*pgtypes.DoltgresType
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
ExtensionName extensions.LibraryIdentifier
|
||||
ExtensionSymbol string
|
||||
}
|
||||
|
||||
var _ FunctionInterface = CFunction{}
|
||||
|
||||
// GetExpectedParameterCount implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) GetExpectedParameterCount() int {
|
||||
return len(cFunc.ParameterTypes)
|
||||
}
|
||||
|
||||
// GetName implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) GetName() string {
|
||||
return cFunc.ID.FunctionName()
|
||||
}
|
||||
|
||||
// GetParameters implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) GetParameters() []*pgtypes.DoltgresType {
|
||||
return cFunc.ParameterTypes
|
||||
}
|
||||
|
||||
// GetReturn implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) GetReturn() *pgtypes.DoltgresType {
|
||||
return cFunc.ReturnType
|
||||
}
|
||||
|
||||
// InternalID implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) InternalID() id.Id {
|
||||
return cFunc.ID.AsId()
|
||||
}
|
||||
|
||||
// IsStrict implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) IsStrict() bool {
|
||||
return cFunc.Strict
|
||||
}
|
||||
|
||||
// NonDeterministic implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) NonDeterministic() bool {
|
||||
return cFunc.IsNonDeterministic
|
||||
}
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (cFunc CFunction) IsCVariadic() bool {
|
||||
// TODO: implement c-language variadic
|
||||
return false
|
||||
}
|
||||
|
||||
// VariadicIndex implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) VariadicIndex() int {
|
||||
// TODO: implement variadic
|
||||
return -1
|
||||
}
|
||||
|
||||
// IsSRF implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) IsSRF() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the interface FunctionInterface.
|
||||
func (cFunc CFunction) enforceInterfaceInheritance(error) {}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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 (
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// TODO: no need to use these functions, should instead add everything directly to built-in.
|
||||
// For now, this just makes the transition easier since it's less to rewrite
|
||||
|
||||
// TypeCast is used to cast from one type to another.
|
||||
type TypeCast struct {
|
||||
FromType *pgtypes.DoltgresType
|
||||
ToType *pgtypes.DoltgresType
|
||||
Function pgtypes.TypeCastFunction
|
||||
}
|
||||
|
||||
// MustAddExplicitTypeCast registers the given explicit type cast. Panics if an error occurs.
|
||||
func MustAddExplicitTypeCast(builtInCasts map[id.Cast]casts.Cast, cast TypeCast) {
|
||||
castID := id.NewCast(cast.FromType.ID, cast.ToType.ID)
|
||||
if _, ok := builtInCasts[castID]; ok {
|
||||
panic("duplicate built-in cast")
|
||||
}
|
||||
builtInCasts[castID] = casts.Cast{
|
||||
ID: castID,
|
||||
CastType: casts.CastType_Explicit,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: cast.Function,
|
||||
UseInOut: false,
|
||||
}
|
||||
}
|
||||
|
||||
// MustAddAssignmentTypeCast registers the given assignment type cast. Panics if an error occurs.
|
||||
func MustAddAssignmentTypeCast(builtInCasts map[id.Cast]casts.Cast, cast TypeCast) {
|
||||
castID := id.NewCast(cast.FromType.ID, cast.ToType.ID)
|
||||
if _, ok := builtInCasts[castID]; ok {
|
||||
panic("duplicate built-in cast")
|
||||
}
|
||||
builtInCasts[castID] = casts.Cast{
|
||||
ID: castID,
|
||||
CastType: casts.CastType_Assignment,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: cast.Function,
|
||||
UseInOut: false,
|
||||
}
|
||||
}
|
||||
|
||||
// MustAddImplicitTypeCast registers the given implicit type cast. Panics if an error occurs.
|
||||
func MustAddImplicitTypeCast(builtInCasts map[id.Cast]casts.Cast, cast TypeCast) {
|
||||
castID := id.NewCast(cast.FromType.ID, cast.ToType.ID)
|
||||
if _, ok := builtInCasts[castID]; ok {
|
||||
panic("duplicate built-in cast")
|
||||
}
|
||||
builtInCasts[castID] = casts.Cast{
|
||||
ID: castID,
|
||||
CastType: casts.CastType_Implicit,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: cast.Function,
|
||||
UseInOut: false,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
// 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"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression/function"
|
||||
"github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/parser"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// Catalog contains all of the PostgreSQL functions.
|
||||
var Catalog = map[string][]FunctionInterface{}
|
||||
|
||||
// AggregateCatalog contains all of the PostgreSQL aggregate functions.
|
||||
var AggregateCatalog = map[string][]AggregateFunctionInterface{}
|
||||
|
||||
// initializedFunctions simply states whether Initialize has been called yet.
|
||||
var initializedFunctions = false
|
||||
|
||||
// RegisterFunction registers the given function, so that it will be usable from a running server. This should be called
|
||||
// from within an init().
|
||||
func RegisterFunction(f FunctionInterface) {
|
||||
if initializedFunctions {
|
||||
panic("attempted to register a function after the init() phase")
|
||||
}
|
||||
switch f := f.(type) {
|
||||
case Function0:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case Function1:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case Function1N:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case Function2:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case Function2N:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case Function3:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case Function4:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case Function5:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case Function6:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case Function7:
|
||||
name := strings.ToLower(f.Name)
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
case InterpretedFunction:
|
||||
name := strings.ToLower(f.ID.FunctionName())
|
||||
Catalog[name] = append(Catalog[name], f)
|
||||
default:
|
||||
panic("unhandled function type")
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterAggregateFunction registers the given function, so that it will be usable from a running server. This should be called
|
||||
// from within an init().
|
||||
func RegisterAggregateFunction(f AggregateFunctionInterface) {
|
||||
if initializedFunctions {
|
||||
panic("attempted to register a function after the init() phase")
|
||||
}
|
||||
switch f := f.(type) {
|
||||
case Func1Aggregate:
|
||||
name := strings.ToLower(f.Name)
|
||||
AggregateCatalog[name] = append(AggregateCatalog[name], f)
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled function type %T", f))
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize handles the initialization of the catalog by overwriting the built-in GMS functions, since they do not
|
||||
// apply to PostgreSQL (and functions of the same name often have different behavior).
|
||||
func Initialize(astConvert func(parser.Statement) (sqlparser.Statement, error)) {
|
||||
// This should only be called once. We don't use sync.Once since we also want to panic if someone attempts to
|
||||
// register a function after initialization.
|
||||
if initializedFunctions {
|
||||
return
|
||||
}
|
||||
initializedFunctions = true
|
||||
|
||||
convertToVitess = astConvert
|
||||
pgtypes.LoadFunctionFromCatalog = getQuickFunctionForTypes
|
||||
analyzer.ExternalFunctionProvider = &FunctionProvider{}
|
||||
replaceGmsBuiltIns()
|
||||
validateFunctions()
|
||||
compileFunctions()
|
||||
compileAggs()
|
||||
}
|
||||
|
||||
// replaceGmsBuiltIns replaces all GMS built-ins that have conflicting names with PostgreSQL functions.
|
||||
func replaceGmsBuiltIns() {
|
||||
functionNames := make(map[string]struct{})
|
||||
for name := range Catalog {
|
||||
functionNames[strings.ToLower(name)] = struct{}{}
|
||||
}
|
||||
var newBuiltIns []sql.Function
|
||||
for _, f := range function.BuiltIns {
|
||||
if _, ok := functionNames[strings.ToLower(f.FunctionName())]; !ok {
|
||||
newBuiltIns = append(newBuiltIns, f)
|
||||
}
|
||||
}
|
||||
function.BuiltIns = newBuiltIns
|
||||
}
|
||||
|
||||
// validateFunctions panics if any functions are defined incorrectly or ambiguously.
|
||||
func validateFunctions() {
|
||||
for funcName, overloads := range Catalog {
|
||||
if err := validateFunction(funcName, overloads); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validateFunction validates whether functions are defined incorrectly or ambiguously.
|
||||
func validateFunction(funcName string, overloads []FunctionInterface) error {
|
||||
// Verify that each function uses the correct Function overload
|
||||
for _, functionOverload := range overloads {
|
||||
if functionOverload.GetExpectedParameterCount() >= 0 &&
|
||||
len(functionOverload.GetParameters()) != functionOverload.GetExpectedParameterCount() {
|
||||
return errors.Errorf("function `%s` should have %d arguments but has %d arguments",
|
||||
funcName, functionOverload.GetExpectedParameterCount(), len(functionOverload.GetParameters()))
|
||||
}
|
||||
}
|
||||
// Verify that all overloads are unique
|
||||
for functionIndex, f1 := range overloads {
|
||||
for _, f2 := range overloads[functionIndex+1:] {
|
||||
sameCount := 0
|
||||
if f1.GetExpectedParameterCount() == f2.GetExpectedParameterCount() {
|
||||
f2Parameters := f2.GetParameters()
|
||||
for parameterIndex, f1Parameter := range f1.GetParameters() {
|
||||
if f1Parameter.Equals(f2Parameters[parameterIndex]) {
|
||||
sameCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
if sameCount == f1.GetExpectedParameterCount() && f1.GetExpectedParameterCount() > 0 {
|
||||
return errors.Errorf("duplicate function overloads on `%s`", funcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// compileNonOperatorFunction creates a CompiledFunction for each overload of the given function.
|
||||
func compileNonOperatorFunction(funcName string, overloads []FunctionInterface) {
|
||||
overloadTree := NewOverloads()
|
||||
for _, functionOverload := range overloads {
|
||||
if err := overloadTree.Add(functionOverload); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Store the compiled function into the engine's built-in functions
|
||||
// TODO: don't do this, use an actual contract for communicating these functions to the engine catalog
|
||||
createFunc := func(ctx *sql.Context, params ...sql.Expression) (sql.Expression, error) {
|
||||
return NewCompiledFunction(ctx, funcName, params, overloadTree, false), nil
|
||||
}
|
||||
function.BuiltIns = append(function.BuiltIns, sql.FunctionN{
|
||||
Name: funcName,
|
||||
Fn: createFunc,
|
||||
})
|
||||
compiledCatalog[funcName] = createFunc
|
||||
}
|
||||
|
||||
// compileNonOperatorFunction creates a CompiledFunction for each overload of the given function.
|
||||
func compileAggFunction(funcName string, overloads []AggregateFunctionInterface) {
|
||||
var newBuffer NewBufferFn
|
||||
overloadTree := NewOverloads()
|
||||
for _, functionOverload := range overloads {
|
||||
newBuffer = functionOverload.NewBuffer
|
||||
if err := overloadTree.Add(functionOverload); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Store the compiled function into the engine's built-in functions
|
||||
// TODO: don't do this, use an actual contract for communicating these functions to the engine catalog
|
||||
createFunc := func(ctx *sql.Context, params ...sql.Expression) (sql.Expression, error) {
|
||||
return NewCompiledAggregateFunction(ctx, funcName, params, overloadTree, newBuffer), nil
|
||||
}
|
||||
function.BuiltIns = append(function.BuiltIns, sql.FunctionN{
|
||||
Name: funcName,
|
||||
Fn: createFunc,
|
||||
})
|
||||
compiledCatalog[funcName] = createFunc
|
||||
}
|
||||
|
||||
// compileFunctions creates a CompiledFunction for each overload of each function in the catalog.
|
||||
func compileFunctions() {
|
||||
for funcName, overloads := range Catalog {
|
||||
compileNonOperatorFunction(funcName, overloads)
|
||||
}
|
||||
|
||||
// Build the overload for all unary and binary functions based on their operator. This will be used for fallback if
|
||||
// an exact match is not found. Compiled functions (which wrap the overload deducer) handle upcasting and other
|
||||
// special rules, so it's far more efficient to reuse it for operators. Operators are also a special case since they
|
||||
// all have different names, while standard overload deducers work on a function-name basis.
|
||||
for signature, functionOverload := range unaryFunctions {
|
||||
overloads, ok := unaryOperatorOverloads[signature.Operator]
|
||||
if !ok {
|
||||
overloads = NewOverloads()
|
||||
unaryOperatorOverloads[signature.Operator] = overloads
|
||||
}
|
||||
if err := overloads.Add(functionOverload); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
for signature, functionOverload := range binaryFunctions {
|
||||
overloads, ok := binaryOperatorOverloads[signature.Operator]
|
||||
if !ok {
|
||||
overloads = NewOverloads()
|
||||
binaryOperatorOverloads[signature.Operator] = overloads
|
||||
}
|
||||
if err := overloads.Add(functionOverload); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add all permutations for the unary and binary operators
|
||||
for operator, overload := range unaryOperatorOverloads {
|
||||
unaryOperatorPermutations[operator] = overload.overloadsForParams(1)
|
||||
}
|
||||
for operator, overload := range binaryOperatorOverloads {
|
||||
binaryOperatorPermutations[operator] = overload.overloadsForParams(2)
|
||||
}
|
||||
}
|
||||
|
||||
func compileAggs() {
|
||||
for funcName, overloads := range AggregateCatalog {
|
||||
compileAggFunction(funcName, overloads)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// 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 (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// FindCommonType returns the common type that given types can convert to. Returns false if no implicit casts are needed
|
||||
// to resolve the given types as the returned common type.
|
||||
// https://www.postgresql.org/docs/15/typeconv-union-case.html
|
||||
func FindCommonType(ctx *sql.Context, types []*pgtypes.DoltgresType) (_ *pgtypes.DoltgresType, requiresCasts bool, err error) {
|
||||
candidateType := pgtypes.Unknown
|
||||
differentTypes := false
|
||||
for _, typ := range types {
|
||||
if typ.ID == candidateType.ID {
|
||||
continue
|
||||
} else if candidateType.ID == pgtypes.Unknown.ID {
|
||||
candidateType = typ
|
||||
} else {
|
||||
candidateType = pgtypes.Unknown
|
||||
differentTypes = true
|
||||
}
|
||||
}
|
||||
if !differentTypes {
|
||||
if candidateType.ID == pgtypes.Unknown.ID {
|
||||
// We require implicit casts from `unknown` to `text`
|
||||
return pgtypes.Text, true, nil
|
||||
}
|
||||
return candidateType, false, nil
|
||||
}
|
||||
// We have different types if we've made it this far, so we're guaranteed to require implicit casts
|
||||
requiresCasts = true
|
||||
for _, typ := range types {
|
||||
if candidateType.ID == pgtypes.Unknown.ID {
|
||||
candidateType = typ
|
||||
}
|
||||
if typ.ID != pgtypes.Unknown.ID && candidateType.TypCategory != typ.TypCategory {
|
||||
return nil, false, errors.Errorf("types %s and %s cannot be matched", candidateType.String(), typ.String())
|
||||
}
|
||||
}
|
||||
castsColl, err := core.GetCastsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// Attempt to find the most general type (or the preferred type in the type category)
|
||||
for _, typ := range types {
|
||||
if typ.ID == pgtypes.Unknown.ID || typ.ID == candidateType.ID {
|
||||
continue
|
||||
} else if cast, err := castsColl.GetImplicitCast(ctx, typ, candidateType); err != nil || cast.ID.IsValid() {
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// typ can convert to the candidate type, so the candidate type is at least as general
|
||||
continue
|
||||
} else if cast, err = castsColl.GetImplicitCast(ctx, candidateType, typ); err != nil || cast.ID.IsValid() {
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// the candidate type can convert to typ, but not vice versa, so typ is likely more general
|
||||
candidateType = typ
|
||||
if candidateType.IsPreferred {
|
||||
// We stop considering more types once we've found a preferred type
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Verify that all types have an implicit conversion to the candidate type
|
||||
for _, typ := range types {
|
||||
if typ.ID == pgtypes.Unknown.ID || typ.ID == candidateType.ID {
|
||||
continue
|
||||
} else if cast, err := castsColl.GetImplicitCast(ctx, typ, candidateType); err != nil {
|
||||
return nil, false, err
|
||||
} else if !cast.ID.IsValid() {
|
||||
return nil, false, errors.Errorf("cannot find implicit cast function from %s to %s", candidateType.String(), typ.String())
|
||||
}
|
||||
}
|
||||
return candidateType, requiresCasts, nil
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// 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 (
|
||||
"strings"
|
||||
|
||||
cerrors "github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
)
|
||||
|
||||
// AggregateFunction is an expression that represents CompiledAggregateFunction
|
||||
type AggregateFunction interface {
|
||||
sql.FunctionExpression
|
||||
sql.Aggregation
|
||||
specificFuncImpl()
|
||||
}
|
||||
|
||||
type NewBufferFn func([]sql.Expression) (sql.AggregationBuffer, error)
|
||||
|
||||
// CompiledAggregateFunction is an expression that represents a fully-analyzed PostgreSQL aggregate function.
|
||||
type CompiledAggregateFunction struct {
|
||||
*CompiledFunction
|
||||
aggId sql.ColumnId
|
||||
newBuffer NewBufferFn
|
||||
}
|
||||
|
||||
var _ AggregateFunction = (*CompiledAggregateFunction)(nil)
|
||||
|
||||
// NewCompiledAggregateFunction returns a newly compiled function.
|
||||
// TODO: newBuffer probably needs to be parameterized in the overloads
|
||||
func NewCompiledAggregateFunction(ctx *sql.Context, name string, args []sql.Expression, functions *Overloads, newBuffer NewBufferFn) *CompiledAggregateFunction {
|
||||
return newCompiledAggregateFunctionInternal(ctx, name, args, functions, functions.overloadsForParams(len(args)), newBuffer)
|
||||
}
|
||||
|
||||
// newCompiledAggregateFunctionInternal is called internally, which skips steps that may have already been processed.
|
||||
func newCompiledAggregateFunctionInternal(ctx *sql.Context, name string, args []sql.Expression, overloads *Overloads, fnOverloads []Overload, newBuffer NewBufferFn) *CompiledAggregateFunction {
|
||||
cf := newCompiledFunctionInternal(ctx, name, args, overloads, fnOverloads, false, nil)
|
||||
c := &CompiledAggregateFunction{
|
||||
CompiledFunction: cf,
|
||||
newBuffer: newBuffer,
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Eval implements the interface sql.Expression.
|
||||
func (c *CompiledAggregateFunction) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
|
||||
return nil, cerrors.New("Eval should not be called on CompiledAggregateFunction")
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.Expression.
|
||||
func (c *CompiledAggregateFunction) 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 newCompiledAggregateFunctionInternal(ctx, c.Name, children, c.overloads, c.fnOverloads, c.newBuffer), nil
|
||||
}
|
||||
|
||||
// SetStatementRunner implements the interface analyzer.Interpreter.
|
||||
func (c *CompiledAggregateFunction) SetStatementRunner(ctx *sql.Context, runner sql.StatementRunner) sql.Expression {
|
||||
nc := *c
|
||||
nc.runner = runner
|
||||
return &nc
|
||||
}
|
||||
|
||||
// specificFuncImpl implements the interface sql.Expression.
|
||||
func (*CompiledAggregateFunction) specificFuncImpl() {}
|
||||
|
||||
func (c *CompiledAggregateFunction) DebugString(ctx *sql.Context) string {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString("CompiledAggregateFunction:")
|
||||
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(sql.DebugString(ctx, param))
|
||||
}
|
||||
sb.WriteString(")")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// NewBuffer implements the interface sql.Aggregation.
|
||||
func (c *CompiledAggregateFunction) NewBuffer(ctx *sql.Context) (sql.AggregationBuffer, error) {
|
||||
return c.newBuffer(c.Arguments)
|
||||
}
|
||||
|
||||
// Id implements the interface sql.Aggregation.
|
||||
func (c *CompiledAggregateFunction) Id() sql.ColumnId {
|
||||
return c.aggId
|
||||
}
|
||||
|
||||
// WithId implements the interface sql.Aggregation.
|
||||
func (c *CompiledAggregateFunction) WithId(id sql.ColumnId) sql.IdExpression {
|
||||
nc := *c
|
||||
nc.aggId = id
|
||||
return &nc
|
||||
}
|
||||
|
||||
// NewWindowFunction implements the interface sql.WindowAdaptableExpression.
|
||||
func (c *CompiledAggregateFunction) NewWindowFunction(ctx *sql.Context) (sql.WindowFunction, error) {
|
||||
panic("windows are not implemented yet")
|
||||
}
|
||||
|
||||
// WithWindow implements the interface sql.WindowAdaptableExpression.
|
||||
func (c *CompiledAggregateFunction) WithWindow(ctx *sql.Context, window *sql.WindowDefinition) sql.WindowAdaptableExpression {
|
||||
panic("windows are not implemented yet")
|
||||
}
|
||||
|
||||
// Window implements the interface sql.WindowAdaptableExpression.
|
||||
func (c *CompiledAggregateFunction) Window() *sql.WindowDefinition {
|
||||
panic("windows are not implemented yet")
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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 (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// compiledCatalog contains all of PostgreSQL functions in their compiled forms.
|
||||
var compiledCatalog = map[string]sql.CreateFuncNArgs{}
|
||||
|
||||
// GetFunction returns the compiled function with the given name and parameters. Returns false if the function could not
|
||||
// be found.
|
||||
func GetFunction(ctx *sql.Context, functionName string, params ...sql.Expression) (*CompiledFunction, bool, error) {
|
||||
if createFunc, ok := compiledCatalog[functionName]; ok {
|
||||
expr, err := createFunc(ctx, params...)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return expr.(*CompiledFunction), true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// dummyExpression is a simple expression that exists solely to capture type information for a parameter. This is used
|
||||
// exclusively by the getQuickFunctionForTypes function.
|
||||
type dummyExpression struct {
|
||||
t *pgtypes.DoltgresType
|
||||
}
|
||||
|
||||
var _ sql.Expression = dummyExpression{}
|
||||
|
||||
func (d dummyExpression) Resolved() bool { return true }
|
||||
func (d dummyExpression) String() string { return d.t.String() }
|
||||
func (d dummyExpression) Type(ctx *sql.Context) sql.Type { return d.t }
|
||||
func (d dummyExpression) IsNullable(ctx *sql.Context) bool { return false }
|
||||
func (d dummyExpression) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
|
||||
panic("cannot Eval dummyExpression")
|
||||
}
|
||||
func (d dummyExpression) Children() []sql.Expression { return nil }
|
||||
func (d dummyExpression) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// getQuickFunctionForTypes is used by the types package to load quick functions. This is declared here to work around
|
||||
// import cycles. Returns nil if a QuickFunction could not be constructed.
|
||||
func getQuickFunctionForTypes(ctx *sql.Context, functionName string, params []*pgtypes.DoltgresType) any {
|
||||
exprs := make([]sql.Expression, len(params))
|
||||
for i := range params {
|
||||
exprs[i] = dummyExpression{t: params[i]}
|
||||
}
|
||||
cf, ok, err := GetFunction(ctx, functionName, exprs...)
|
||||
if err != nil || !ok {
|
||||
return nil
|
||||
}
|
||||
return cf.GetQuickFunction()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,614 @@
|
||||
// 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 (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// FunctionInterface is an interface for PostgreSQL functions.
|
||||
type FunctionInterface interface {
|
||||
// GetName returns the name of the function. The name is case-insensitive, so the casing does not matter.
|
||||
GetName() string
|
||||
// GetReturn returns the return type.
|
||||
GetReturn() *pgtypes.DoltgresType
|
||||
// GetParameters returns the parameter types for the function.
|
||||
GetParameters() []*pgtypes.DoltgresType
|
||||
// VariadicIndex returns the index of the variadic parameter, if it exists, or -1 otherwise
|
||||
VariadicIndex() int
|
||||
// GetExpectedParameterCount returns the number of parameters that are valid for this function.
|
||||
GetExpectedParameterCount() int
|
||||
// NonDeterministic returns whether the function is non-deterministic.
|
||||
NonDeterministic() bool
|
||||
// IsCVariadic returns whether this function uses a variadic form restricted to C-language functions.
|
||||
// https://www.postgresql.org/docs/15/xfunc-c.html#id-1.8.3.13.13
|
||||
IsCVariadic() bool
|
||||
// IsStrict returns whether the function is STRICT, which means if any parameter is NULL, then it returns NULL.
|
||||
// Otherwise, if it's not, the NULL input must be handled by user.
|
||||
IsStrict() bool
|
||||
// IsSRF returns whether the function is a set returning function, meaning whether the
|
||||
// function returns one or more rows as a result.
|
||||
IsSRF() bool
|
||||
// InternalID returns the ID associated with this function.
|
||||
InternalID() id.Id
|
||||
// enforceInterfaceInheritance is a special function that ensures only the expected types inherit this interface.
|
||||
enforceInterfaceInheritance(error)
|
||||
}
|
||||
|
||||
// AggregateFunctionInterface is an interface for PostgreSQL aggregate functions
|
||||
type AggregateFunctionInterface interface {
|
||||
FunctionInterface
|
||||
// TODO: this maybe needs to take the place of the Callable function
|
||||
NewBuffer([]sql.Expression) (sql.AggregationBuffer, error)
|
||||
}
|
||||
|
||||
// Function0 is a function that does not take any parameters.
|
||||
type Function0 struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context) (any, error)
|
||||
}
|
||||
|
||||
// Function1 is a function that takes one parameter. The parameter and return type is passed into the Callable function
|
||||
// when the parameter (and possibly return type) is a polymorphic type. The return type is the last type in the array.
|
||||
type Function1 struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
Parameters [1]*pgtypes.DoltgresType
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context, paramsAndReturn [2]*pgtypes.DoltgresType, val1 any) (any, error)
|
||||
}
|
||||
|
||||
// Function1N is a function that takes at least one parameter. This is different from a SQL variadic function, as the
|
||||
// additional parameters may have different types (that do not contribute to type deduction like with `anyelement`), and
|
||||
// is only usable by C-language functions (notably built-in ones such as `concat`). The field `paramsAndReturn` works
|
||||
// similarly to standard functions, with the last type being the return type.
|
||||
type Function1N struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
Parameters [1]*pgtypes.DoltgresType
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context, paramsAndReturn []*pgtypes.DoltgresType, val1 any, vals []any) (any, error)
|
||||
}
|
||||
|
||||
// Function2 is a function that takes two parameters. The parameter and return types are passed into the Callable
|
||||
// function when the parameters (and possibly return type) have at least one polymorphic type. The return type is the
|
||||
// last type in the array.
|
||||
type Function2 struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
Parameters [2]*pgtypes.DoltgresType
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context, paramsAndReturn [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error)
|
||||
}
|
||||
|
||||
// Function2N is a function that takes at least two parameters. This is different from a SQL variadic function, as the
|
||||
// additional parameters may have different types (that do not contribute to type deduction like with `anyelement`), and
|
||||
// is only usable by C-language functions (notably built-in ones such as `concat_ws`). The field `paramsAndReturn` works
|
||||
// similarly to standard functions, with the last type being the return type.
|
||||
type Function2N struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
Parameters [2]*pgtypes.DoltgresType
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context, paramsAndReturn []*pgtypes.DoltgresType, val1 any, val2 any, vals []any) (any, error)
|
||||
}
|
||||
|
||||
// Function3 is a function that takes three parameters. The parameter and return types are passed into the Callable
|
||||
// function when the parameters (and possibly return type) have at least one polymorphic type. The return type is the
|
||||
// last type in the array.
|
||||
type Function3 struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
Parameters [3]*pgtypes.DoltgresType
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context, paramsAndReturn [4]*pgtypes.DoltgresType, val1 any, val2 any, val3 any) (any, error)
|
||||
}
|
||||
|
||||
// Function4 is a function that takes four parameters. The parameter and return types are passed into the Callable
|
||||
// function when the parameters (and possibly return type) have at least one polymorphic type. The return type is the
|
||||
// last type in the array.
|
||||
type Function4 struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
Parameters [4]*pgtypes.DoltgresType
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context, paramsAndReturn [5]*pgtypes.DoltgresType, val1 any, val2 any, val3 any, val4 any) (any, error)
|
||||
}
|
||||
|
||||
// Function5 is a function that takes five parameters. The parameter and return types are passed into the Callable
|
||||
// function when the parameters (and possibly return type) have at least one polymorphic type. The return type is the
|
||||
// last type in the array.
|
||||
type Function5 struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
Parameters [5]*pgtypes.DoltgresType
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context, paramsAndReturn [6]*pgtypes.DoltgresType, val1 any, val2 any, val3 any, val4 any, val5 any) (any, error)
|
||||
}
|
||||
|
||||
// Function6 is a function that takes six parameters. The parameter and return types are passed into the Callable
|
||||
// function when the parameters (and possibly return type) have at least one polymorphic type. The return type is the
|
||||
// last type in the array.
|
||||
type Function6 struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
Parameters [6]*pgtypes.DoltgresType
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context, paramsAndReturn [7]*pgtypes.DoltgresType, val1 any, val2 any, val3 any, val4 any, val5 any, val6 any) (any, error)
|
||||
}
|
||||
|
||||
// Function7 is a function that takes seven parameters. The parameter and return types are passed into the Callable
|
||||
// function when the parameters (and possibly return type) have at least one polymorphic type. The return type is the
|
||||
// last type in the array.
|
||||
type Function7 struct {
|
||||
Name string
|
||||
Return *pgtypes.DoltgresType
|
||||
Parameters [7]*pgtypes.DoltgresType
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Callable func(ctx *sql.Context, paramsAndReturn [8]*pgtypes.DoltgresType, val1 any, val2 any, val3 any, val4 any, val5 any, val6 any, val7 any) (any, error)
|
||||
}
|
||||
|
||||
var _ FunctionInterface = Function0{}
|
||||
var _ FunctionInterface = Function1{}
|
||||
var _ FunctionInterface = Function1N{}
|
||||
var _ FunctionInterface = Function2{}
|
||||
var _ FunctionInterface = Function2N{}
|
||||
var _ FunctionInterface = Function3{}
|
||||
var _ FunctionInterface = Function4{}
|
||||
var _ FunctionInterface = Function5{}
|
||||
var _ FunctionInterface = Function6{}
|
||||
var _ FunctionInterface = Function7{}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function0) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function0) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function0) GetParameters() []*pgtypes.DoltgresType { return nil }
|
||||
|
||||
func (f Function0) VariadicIndex() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function0) GetExpectedParameterCount() int { return 0 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function0) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function0) IsCVariadic() bool { return false }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function0) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function0) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function0) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function0) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function1) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function1) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function1) GetParameters() []*pgtypes.DoltgresType { return f.Parameters[:] }
|
||||
|
||||
// VariadicIndex implements the FunctionInterface interface.
|
||||
func (f Function1) VariadicIndex() int {
|
||||
if f.Variadic {
|
||||
return 0
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function1) GetExpectedParameterCount() int { return 1 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function1) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function1) IsCVariadic() bool { return false }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function1) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function1) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function1) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name, f.Parameters[0].ID).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function1) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function1N) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function1N) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function1N) GetParameters() []*pgtypes.DoltgresType { return f.Parameters[:] }
|
||||
|
||||
// VariadicIndex implements the FunctionInterface interface.
|
||||
func (f Function1N) VariadicIndex() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function1N) GetExpectedParameterCount() int { return 1 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function1N) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function1N) IsCVariadic() bool { return true }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function1N) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function1N) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function1N) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name, f.Parameters[0].ID).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function1N) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function2) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function2) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function2) GetParameters() []*pgtypes.DoltgresType { return f.Parameters[:] }
|
||||
|
||||
// VariadicIndex implements the FunctionInterface interface.
|
||||
func (f Function2) VariadicIndex() int {
|
||||
if f.Variadic {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function2) GetExpectedParameterCount() int { return 2 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function2) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function2) IsCVariadic() bool { return false }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function2) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function2) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function2) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name, f.Parameters[0].ID, f.Parameters[1].ID).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function2) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function2N) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function2N) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function2N) GetParameters() []*pgtypes.DoltgresType { return f.Parameters[:] }
|
||||
|
||||
// VariadicIndex implements the FunctionInterface interface.
|
||||
func (f Function2N) VariadicIndex() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function2N) GetExpectedParameterCount() int { return 2 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function2N) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function2N) IsCVariadic() bool { return true }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function2N) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function2N) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function2N) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name, f.Parameters[0].ID, f.Parameters[1].ID).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function2N) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function3) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function3) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function3) GetParameters() []*pgtypes.DoltgresType { return f.Parameters[:] }
|
||||
|
||||
// VariadicIndex implements the FunctionInterface interface.
|
||||
func (f Function3) VariadicIndex() int {
|
||||
if f.Variadic {
|
||||
return 2
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function3) GetExpectedParameterCount() int { return 3 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function3) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function3) IsCVariadic() bool { return false }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function3) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function3) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function3) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name, f.Parameters[0].ID, f.Parameters[1].ID, f.Parameters[2].ID).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function3) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function4) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function4) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function4) GetParameters() []*pgtypes.DoltgresType { return f.Parameters[:] }
|
||||
|
||||
// VariadicIndex implements the FunctionInterface interface.
|
||||
func (f Function4) VariadicIndex() int {
|
||||
if f.Variadic {
|
||||
return 3
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function4) GetExpectedParameterCount() int { return 4 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function4) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function4) IsCVariadic() bool { return false }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function4) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function4) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function4) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name, f.Parameters[0].ID, f.Parameters[1].ID, f.Parameters[2].ID, f.Parameters[3].ID).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function4) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function5) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function5) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function5) GetParameters() []*pgtypes.DoltgresType { return f.Parameters[:] }
|
||||
|
||||
// VariadicIndex implements the FunctionInterface interface.
|
||||
func (f Function5) VariadicIndex() int {
|
||||
if f.Variadic {
|
||||
return 4
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function5) GetExpectedParameterCount() int { return 5 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function5) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function5) IsCVariadic() bool { return false }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function5) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function5) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function5) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name, f.Parameters[0].ID, f.Parameters[1].ID, f.Parameters[2].ID, f.Parameters[3].ID, f.Parameters[4].ID).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function5) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function6) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function6) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function6) GetParameters() []*pgtypes.DoltgresType { return f.Parameters[:] }
|
||||
|
||||
// VariadicIndex implements the FunctionInterface interface.
|
||||
func (f Function6) VariadicIndex() int {
|
||||
if f.Variadic {
|
||||
return 5
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function6) GetExpectedParameterCount() int { return 6 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function6) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function6) IsCVariadic() bool { return false }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function6) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function6) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function6) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name, f.Parameters[0].ID, f.Parameters[1].ID, f.Parameters[2].ID, f.Parameters[3].ID, f.Parameters[4].ID, f.Parameters[5].ID).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function6) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// GetName implements the FunctionInterface interface.
|
||||
func (f Function7) GetName() string { return f.Name }
|
||||
|
||||
// GetReturn implements the FunctionInterface interface.
|
||||
func (f Function7) GetReturn() *pgtypes.DoltgresType { return getTypeIfRowType(f.IsSRF(), f.Return) }
|
||||
|
||||
// GetParameters implements the FunctionInterface interface.
|
||||
func (f Function7) GetParameters() []*pgtypes.DoltgresType { return f.Parameters[:] }
|
||||
|
||||
// VariadicIndex implements the FunctionInterface interface.
|
||||
func (f Function7) VariadicIndex() int {
|
||||
if f.Variadic {
|
||||
return 6
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// GetExpectedParameterCount implements the FunctionInterface interface.
|
||||
func (f Function7) GetExpectedParameterCount() int { return 7 }
|
||||
|
||||
// NonDeterministic implements the FunctionInterface interface.
|
||||
func (f Function7) NonDeterministic() bool { return f.IsNonDeterministic }
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (f Function7) IsCVariadic() bool { return false }
|
||||
|
||||
// IsStrict implements the FunctionInterface interface.
|
||||
func (f Function7) IsStrict() bool { return f.Strict }
|
||||
|
||||
// IsSRF implements the FunctionInterface interface.
|
||||
func (f Function7) IsSRF() bool { return f.SRF }
|
||||
|
||||
// InternalID implements the FunctionInterface interface.
|
||||
func (f Function7) InternalID() id.Id {
|
||||
return id.NewFunction("pg_catalog", f.Name, f.Parameters[0].ID, f.Parameters[1].ID, f.Parameters[2].ID, f.Parameters[3].ID, f.Parameters[4].ID, f.Parameters[5].ID, f.Parameters[6].ID).AsId()
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the FunctionInterface interface.
|
||||
func (f Function7) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// Func1Aggregate is a function that takes one parameter and is an aggregate function.
|
||||
type Func1Aggregate struct {
|
||||
Function1
|
||||
NewAggBuffer func([]sql.Expression) (sql.AggregationBuffer, error)
|
||||
}
|
||||
|
||||
func (f Func1Aggregate) NewBuffer(exprs []sql.Expression) (sql.AggregationBuffer, error) {
|
||||
return f.NewAggBuffer(exprs)
|
||||
}
|
||||
|
||||
var _ AggregateFunctionInterface = Func1Aggregate{}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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 (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
)
|
||||
|
||||
// IntermediateFunction is an expression that represents an incomplete PostgreSQL function.
|
||||
type IntermediateFunction struct {
|
||||
Functions *Overloads
|
||||
AllOverloads []Overload
|
||||
IsOperator bool
|
||||
}
|
||||
|
||||
// Compile returns a CompiledFunction created from the calling IntermediateFunction. Returns a nil function if it could
|
||||
// not be compiled.
|
||||
func (f IntermediateFunction) Compile(ctx *sql.Context, name string, parameters ...sql.Expression) *CompiledFunction {
|
||||
if f.Functions == nil {
|
||||
return nil
|
||||
}
|
||||
return newCompiledFunctionInternal(ctx, name, parameters, f.Functions, f.AllOverloads, f.IsOperator, nil)
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
// Copyright 2025 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// InterpretedFunction is the implementation of functions created using PL/pgSQL. The created functions are converted to
|
||||
// a collection of operations, and an interpreter iterates over those operations to handle the logic.
|
||||
type InterpretedFunction struct {
|
||||
ID id.Function
|
||||
ReturnType *pgtypes.DoltgresType
|
||||
ParameterNames []string
|
||||
ParameterTypes []*pgtypes.DoltgresType
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SRF bool
|
||||
Statements []plpgsql.InterpreterOperation
|
||||
}
|
||||
|
||||
var _ FunctionInterface = InterpretedFunction{}
|
||||
var _ plpgsql.InterpretedFunction = InterpretedFunction{}
|
||||
|
||||
// GetExpectedParameterCount implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) GetExpectedParameterCount() int {
|
||||
return len(iFunc.ParameterTypes)
|
||||
}
|
||||
|
||||
// GetName implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) GetName() string {
|
||||
return iFunc.ID.FunctionName()
|
||||
}
|
||||
|
||||
// GetParameters implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) GetParameters() []*pgtypes.DoltgresType {
|
||||
return iFunc.ParameterTypes
|
||||
}
|
||||
|
||||
// GetParameterNames returns the names of all parameters.
|
||||
func (iFunc InterpretedFunction) GetParameterNames() []string {
|
||||
return iFunc.ParameterNames
|
||||
}
|
||||
|
||||
// GetReturn implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) GetReturn() *pgtypes.DoltgresType {
|
||||
return iFunc.ReturnType
|
||||
}
|
||||
|
||||
// GetStatements returns the contained statements.
|
||||
func (iFunc InterpretedFunction) GetStatements() []plpgsql.InterpreterOperation {
|
||||
return iFunc.Statements
|
||||
}
|
||||
|
||||
// InternalID implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) InternalID() id.Id {
|
||||
return iFunc.ID.AsId()
|
||||
}
|
||||
|
||||
// IsStrict implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) IsStrict() bool {
|
||||
return iFunc.Strict
|
||||
}
|
||||
|
||||
// IsSRF implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) IsSRF() bool {
|
||||
return iFunc.SRF
|
||||
}
|
||||
|
||||
// NonDeterministic implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) NonDeterministic() bool {
|
||||
return iFunc.IsNonDeterministic
|
||||
}
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (iFunc InterpretedFunction) IsCVariadic() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// VariadicIndex implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) VariadicIndex() int {
|
||||
// TODO: implement variadic
|
||||
return -1
|
||||
}
|
||||
|
||||
// QuerySingleReturn handles queries that are supposed to return a single value.
|
||||
func (iFunc InterpretedFunction) QuerySingleReturn(ctx *sql.Context, stack plpgsql.InterpreterStack, stmt string, targetType *pgtypes.DoltgresType, bindings []string) (val any, err error) {
|
||||
stmt, _, err = iFunc.ApplyBindings(ctx, stack, stmt, bindings, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sql.RunInterpreted(ctx, func(subCtx *sql.Context) (any, error) {
|
||||
sch, rowIter, _, err := stack.Runner().QueryWithBindings(subCtx, stmt, nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := sql.RowIterToRows(subCtx, rowIter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(sch) != 1 {
|
||||
return nil, errors.New("expression does not result in a single value")
|
||||
}
|
||||
if len(rows) != 1 {
|
||||
return nil, errors.New("expression returned multiple result sets")
|
||||
}
|
||||
if len(rows[0]) != 1 {
|
||||
return nil, errors.New("expression returned multiple results")
|
||||
}
|
||||
if targetType == nil {
|
||||
return rows[0][0], nil
|
||||
}
|
||||
if rows[0][0] == nil {
|
||||
return nil, nil
|
||||
}
|
||||
sourceType, ok := sch[0].Type.(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
// TODO: We ensure we have a DoltgresType, but we should also convert the value to
|
||||
// ensure it's in the correct form for the DoltgresType. This logic lives in
|
||||
// pgexpressions.GMSCast, but need to be extracted to avoid a dependency cycle
|
||||
// so it can be used here and from server.plpgsql.
|
||||
sourceType, err = pgtypes.FromGmsTypeToDoltgresType(sch[0].Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
castsColl, err := core.GetCastsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cast, err := castsColl.GetAssignmentCast(ctx, sourceType, targetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !cast.ID.IsValid() {
|
||||
// TODO: We're using assignment casting, but for some reason we have to use I/O casting here, which is incorrect?
|
||||
// We need to dig into this and figure out exactly what's happening, as this is "wrong" according to what
|
||||
// I understand. This lines up more with explicit casting, but it's supposed to be assignment.
|
||||
// Maybe there are specific rules for pgsql?
|
||||
if sourceType.TypCategory == pgtypes.TypeCategory_StringTypes {
|
||||
cast.ID = id.NewCast(sourceType.ID, targetType.ID)
|
||||
cast.UseInOut = true
|
||||
} else {
|
||||
return nil, errors.New("no valid cast for return value")
|
||||
}
|
||||
}
|
||||
return cast.Eval(subCtx, rows[0][0], sourceType, targetType)
|
||||
})
|
||||
}
|
||||
|
||||
// QueryMultiReturn handles queries that may return multiple values over multiple rows.
|
||||
func (iFunc InterpretedFunction) QueryMultiReturn(ctx *sql.Context, stack plpgsql.InterpreterStack, stmt string, bindings []string) (schema sql.Schema, rows []sql.Row, err error) {
|
||||
stmt, _, err = iFunc.ApplyBindings(ctx, stack, stmt, bindings, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rows, err = sql.RunInterpreted(ctx, func(subCtx *sql.Context) (rows []sql.Row, err error) {
|
||||
var rowIter sql.RowIter
|
||||
schema, rowIter, _, err = stack.Runner().QueryWithBindings(subCtx, stmt, nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: we should come up with a good way of carrying the RowIter out of the function without needing to wrap
|
||||
// each call to QueryMultiReturn with RunInterpreted. For now, we don't check the returned rows, so this is
|
||||
// fine.
|
||||
return sql.RowIterToRows(subCtx, rowIter)
|
||||
})
|
||||
return schema, rows, err
|
||||
}
|
||||
|
||||
// ApplyBindings applies the given bindings to the statement. If `varFound` is false, then the error will state that
|
||||
// the variable was not found (which means the error may be ignored if you're only concerned with finding a variable).
|
||||
// If `varFound` is true, then the error is related to formatting the variable. `enforceType` adds casting and quotes to
|
||||
// ensure that the value is correctly represented in the string.
|
||||
func (InterpretedFunction) ApplyBindings(ctx *sql.Context, stack plpgsql.InterpreterStack, stmt string, bindings []string, enforceType bool) (newStmt string, varFound bool, err error) {
|
||||
if len(bindings) == 0 {
|
||||
return stmt, false, nil
|
||||
}
|
||||
newStmt = stmt
|
||||
for i, bindingName := range bindings {
|
||||
variable := stack.GetVariable(bindingName)
|
||||
if variable.Type == nil {
|
||||
return newStmt, false, fmt.Errorf("variable `%s` could not be found", bindingName)
|
||||
}
|
||||
var formattedVar string
|
||||
if *variable.Value != nil {
|
||||
formattedVar, err = variable.Type.FormatValueWithContext(ctx, *variable.Value)
|
||||
if err != nil {
|
||||
return newStmt, true, err
|
||||
}
|
||||
if enforceType {
|
||||
switch variable.Type.TypCategory {
|
||||
case pgtypes.TypeCategory_ArrayTypes, pgtypes.TypeCategory_CompositeTypes, pgtypes.TypeCategory_DateTimeTypes, pgtypes.TypeCategory_StringTypes, pgtypes.TypeCategory_UserDefinedTypes:
|
||||
formattedVar = pq.QuoteLiteral(formattedVar)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
formattedVar = "NULL"
|
||||
}
|
||||
if enforceType {
|
||||
if variable.Type.TypCategory == pgtypes.TypeCategory_CompositeTypes {
|
||||
newStmt = strings.Replace(newStmt, "$"+strconv.Itoa(i+1), fmt.Sprintf(`(%s::%s)`, formattedVar, variable.Type.String()), 1)
|
||||
} else {
|
||||
newStmt = strings.Replace(newStmt, "$"+strconv.Itoa(i+1), fmt.Sprintf(`((%s)::%s)`, formattedVar, variable.Type.String()), 1)
|
||||
}
|
||||
} else {
|
||||
newStmt = strings.Replace(newStmt, "$"+strconv.Itoa(i+1), formattedVar, 1)
|
||||
}
|
||||
}
|
||||
return newStmt, true, nil
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the interface FunctionInterface.
|
||||
func (iFunc InterpretedFunction) enforceInterfaceInheritance(error) {}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2026 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_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/server/functions"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// TestApplyBindings_RendersRegclassVariableThroughSessionContext asserts
|
||||
// that ApplyBindings renders a regclass-typed variable using the
|
||||
// session context it is given.
|
||||
//
|
||||
// See https://github.com/dolthub/doltgresql/issues/1142.
|
||||
func TestApplyBindings_RendersRegclassVariableThroughSessionContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
functions.Init()
|
||||
framework.Initialize(nil)
|
||||
ctx := sql.NewEmptyContext()
|
||||
stack := plpgsql.NewInterpreterStack(nil)
|
||||
stack.NewVariableWithValue("rel", pgtypes.Regclass, id.NewOID(1259).AsId())
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
_, _, _ = framework.InterpretedFunction{}.ApplyBindings(ctx, stack, "SELECT $1", []string{"rel"}, false)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// 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 (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
)
|
||||
|
||||
// Operator is a unary or binary operator.
|
||||
type Operator byte
|
||||
|
||||
const (
|
||||
Operator_BinaryPlus Operator = iota // +
|
||||
Operator_BinaryMinus // -
|
||||
Operator_BinaryMultiply // *
|
||||
Operator_BinaryDivide // /
|
||||
Operator_BinaryMod // %
|
||||
Operator_BinaryShiftLeft // <<
|
||||
Operator_BinaryShiftRight // >>
|
||||
Operator_BinaryLessThan // <
|
||||
Operator_BinaryGreaterThan // >
|
||||
Operator_BinaryLessOrEqual // <=
|
||||
Operator_BinaryGreaterOrEqual // >=
|
||||
Operator_BinaryEqual // =
|
||||
Operator_BinaryNotEqual // <> or != (they're equivalent in all cases)
|
||||
Operator_BinaryBitAnd // &
|
||||
Operator_BinaryBitOr // |
|
||||
Operator_BinaryBitXor // ^
|
||||
Operator_BinaryConcatenate // ||
|
||||
Operator_BinaryJSONExtractJson // ->
|
||||
Operator_BinaryJSONExtractText // ->>
|
||||
Operator_BinaryJSONExtractPathJson // #>
|
||||
Operator_BinaryJSONExtractPathText // #>>
|
||||
Operator_BinaryJSONContainsRight // @>
|
||||
Operator_BinaryJSONContainsLeft // <@
|
||||
Operator_BinaryJSONTopLevel // ?
|
||||
Operator_BinaryJSONTopLevelAny // ?|
|
||||
Operator_BinaryJSONTopLevelAll // ?&
|
||||
Operator_UnaryPlus // +
|
||||
Operator_UnaryMinus // -
|
||||
// NOTE: Any new operator should also be added to Operator.String() and GetOperatorFromString() functions.
|
||||
)
|
||||
|
||||
// unaryFunction represents the signature for a unary function.
|
||||
type unaryFunction struct {
|
||||
Operator Operator
|
||||
TypeID id.Type
|
||||
}
|
||||
|
||||
// binaryFunction represents the signature for a binary function.
|
||||
type binaryFunction struct {
|
||||
Operator Operator
|
||||
Left id.Type
|
||||
Right id.Type
|
||||
}
|
||||
|
||||
var (
|
||||
// unaryFunctions is a map from a unaryFunction signature to the associated function.
|
||||
unaryFunctions = map[unaryFunction]Function1{}
|
||||
// binaryFunctions is a map from a binaryFunction signature to the associated function.
|
||||
binaryFunctions = map[binaryFunction]Function2{}
|
||||
// unaryOperatorOverloads is a map from an operator to an Overload deducer that is the aggregate of all functions
|
||||
// for that operator.
|
||||
unaryOperatorOverloads = map[Operator]*Overloads{}
|
||||
// binaryOperatorOverloads is a map from an operator to an Overload deducer that is the aggregate of all functions
|
||||
// for that operator.
|
||||
binaryOperatorOverloads = map[Operator]*Overloads{}
|
||||
// unaryOperatorPermutations contains all of the permutations for each unary operator.
|
||||
unaryOperatorPermutations = map[Operator][]Overload{}
|
||||
// unaryOperatorPermutations contains all of the permutations for each binary operator.
|
||||
binaryOperatorPermutations = map[Operator][]Overload{}
|
||||
)
|
||||
|
||||
// RegisterUnaryFunction registers the given function, so that it will be usable from a running server. This should
|
||||
// only be used for unary functions, which are the underlying functions for unary operators such as negation, etc. This
|
||||
// should be called from within an init().
|
||||
func RegisterUnaryFunction(operator Operator, f Function1) {
|
||||
if !operator.IsUnary() {
|
||||
panic("non-unary operator: " + operator.String())
|
||||
}
|
||||
RegisterFunction(f)
|
||||
sig := unaryFunction{
|
||||
Operator: operator,
|
||||
TypeID: f.Parameters[0].ID,
|
||||
}
|
||||
if existingFunction, ok := unaryFunctions[sig]; ok {
|
||||
panic(errors.Errorf("duplicate unary function for `%s`: `%s` and `%s`",
|
||||
operator.String(), existingFunction.Name, f.Name))
|
||||
}
|
||||
unaryFunctions[sig] = f
|
||||
}
|
||||
|
||||
// RegisterBinaryFunction registers the given function, so that it will be usable from a running server. This should
|
||||
// only be used for binary functions, which are the underlying functions for binary operators such as addition,
|
||||
// subtraction, etc. This should be called from within an init().
|
||||
func RegisterBinaryFunction(operator Operator, f Function2) {
|
||||
if !operator.IsBinary() {
|
||||
panic("non-binary operator: " + operator.String())
|
||||
}
|
||||
RegisterFunction(f)
|
||||
sig := binaryFunction{
|
||||
Operator: operator,
|
||||
Left: f.Parameters[0].ID,
|
||||
Right: f.Parameters[1].ID,
|
||||
}
|
||||
if existingFunction, ok := binaryFunctions[sig]; ok {
|
||||
panic(errors.Errorf("duplicate binary function for `%s`: `%s` and `%s`",
|
||||
operator.String(), existingFunction.Name, f.Name))
|
||||
}
|
||||
binaryFunctions[sig] = f
|
||||
}
|
||||
|
||||
// GetUnaryFunction returns the unary function that matches the given operator.
|
||||
func GetUnaryFunction(operator Operator) IntermediateFunction {
|
||||
// Returns nil if not found, which is fine as IntermediateFunction will handle the nil deducer
|
||||
return IntermediateFunction{
|
||||
Functions: unaryOperatorOverloads[operator],
|
||||
AllOverloads: unaryOperatorPermutations[operator],
|
||||
IsOperator: true,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBinaryFunction returns the binary function that matches the given operator.
|
||||
func GetBinaryFunction(operator Operator) IntermediateFunction {
|
||||
// Returns nil if not found, which is fine as IntermediateFunction will handle the nil deducer
|
||||
return IntermediateFunction{
|
||||
Functions: binaryOperatorOverloads[operator],
|
||||
AllOverloads: binaryOperatorPermutations[operator],
|
||||
IsOperator: true,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the string form of the operator.
|
||||
func (o Operator) String() string {
|
||||
switch o {
|
||||
case Operator_BinaryPlus, Operator_UnaryPlus:
|
||||
return "+"
|
||||
case Operator_BinaryMinus, Operator_UnaryMinus:
|
||||
return "-"
|
||||
case Operator_BinaryMultiply:
|
||||
return "*"
|
||||
case Operator_BinaryDivide:
|
||||
return "/"
|
||||
case Operator_BinaryMod:
|
||||
return "%"
|
||||
case Operator_BinaryShiftLeft:
|
||||
return "<<"
|
||||
case Operator_BinaryShiftRight:
|
||||
return ">>"
|
||||
case Operator_BinaryBitAnd:
|
||||
return "&"
|
||||
case Operator_BinaryBitOr:
|
||||
return "|"
|
||||
case Operator_BinaryBitXor:
|
||||
return "#"
|
||||
case Operator_BinaryConcatenate:
|
||||
return "||"
|
||||
case Operator_BinaryEqual:
|
||||
return "="
|
||||
case Operator_BinaryNotEqual:
|
||||
return "<>"
|
||||
case Operator_BinaryGreaterThan:
|
||||
return ">"
|
||||
case Operator_BinaryGreaterOrEqual:
|
||||
return ">="
|
||||
case Operator_BinaryLessThan:
|
||||
return "<"
|
||||
case Operator_BinaryLessOrEqual:
|
||||
return "<="
|
||||
case Operator_BinaryJSONExtractJson:
|
||||
return "->"
|
||||
case Operator_BinaryJSONExtractText:
|
||||
return "->>"
|
||||
case Operator_BinaryJSONExtractPathJson:
|
||||
return "#>"
|
||||
case Operator_BinaryJSONExtractPathText:
|
||||
return "#>>"
|
||||
case Operator_BinaryJSONContainsRight:
|
||||
return "@>"
|
||||
case Operator_BinaryJSONContainsLeft:
|
||||
return "<@"
|
||||
case Operator_BinaryJSONTopLevel:
|
||||
return "?"
|
||||
case Operator_BinaryJSONTopLevelAny:
|
||||
return "?|"
|
||||
case Operator_BinaryJSONTopLevelAll:
|
||||
return "?&"
|
||||
default:
|
||||
return "unknown operator"
|
||||
}
|
||||
}
|
||||
|
||||
// IsUnary returns whether the operator is a unary operator.
|
||||
func (o Operator) IsUnary() bool {
|
||||
switch o {
|
||||
case Operator_UnaryPlus, Operator_UnaryMinus:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsBinary returns whether the operator is a binary operator.
|
||||
func (o Operator) IsBinary() bool {
|
||||
return !o.IsUnary()
|
||||
}
|
||||
|
||||
// GetOperatorFromString returns the binary operator for the given subOperator.
|
||||
func GetOperatorFromString(op string) (Operator, error) {
|
||||
switch op {
|
||||
case "+":
|
||||
// TODO: Operator_UnaryPlus
|
||||
return Operator_BinaryPlus, nil
|
||||
case "-":
|
||||
// TODO: Operator_UnaryMinus
|
||||
return Operator_BinaryMinus, nil
|
||||
case "*":
|
||||
return Operator_BinaryMultiply, nil
|
||||
case "/":
|
||||
return Operator_BinaryDivide, nil
|
||||
case "%":
|
||||
return Operator_BinaryMod, nil
|
||||
case "<<":
|
||||
return Operator_BinaryShiftLeft, nil
|
||||
case ">>":
|
||||
return Operator_BinaryShiftRight, nil
|
||||
case "<":
|
||||
return Operator_BinaryLessThan, nil
|
||||
case ">":
|
||||
return Operator_BinaryGreaterThan, nil
|
||||
case "<=":
|
||||
return Operator_BinaryLessOrEqual, nil
|
||||
case ">=":
|
||||
return Operator_BinaryGreaterOrEqual, nil
|
||||
case "=":
|
||||
return Operator_BinaryEqual, nil
|
||||
case "<>":
|
||||
return Operator_BinaryNotEqual, nil
|
||||
case "&":
|
||||
return Operator_BinaryBitAnd, nil
|
||||
case "|":
|
||||
return Operator_BinaryBitOr, nil
|
||||
case "#":
|
||||
return Operator_BinaryBitXor, nil
|
||||
case "||":
|
||||
return Operator_BinaryConcatenate, nil
|
||||
case "->":
|
||||
return Operator_BinaryJSONExtractJson, nil
|
||||
case "->>":
|
||||
return Operator_BinaryJSONExtractText, nil
|
||||
case "#>":
|
||||
return Operator_BinaryJSONExtractPathJson, nil
|
||||
case "#>>":
|
||||
return Operator_BinaryJSONExtractPathText, nil
|
||||
case "@>":
|
||||
return Operator_BinaryJSONContainsRight, nil
|
||||
case "<@":
|
||||
return Operator_BinaryJSONContainsLeft, nil
|
||||
case "?":
|
||||
return Operator_BinaryJSONTopLevel, nil
|
||||
case "?|":
|
||||
return Operator_BinaryJSONTopLevelAny, nil
|
||||
case "?&":
|
||||
return Operator_BinaryJSONTopLevelAll, nil
|
||||
default:
|
||||
return 0, errors.Errorf("unhandled Operator `%s`", op)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// 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 (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// Overloads is the collection of all overloads for a given function name.
|
||||
type Overloads struct {
|
||||
// ByParamType contains all overloads for the function with this name, indexed by the key of the parameter types.
|
||||
ByParamType map[string]FunctionInterface
|
||||
// AllOverloads contains all overloads for the function with this name
|
||||
AllOverloads []FunctionInterface
|
||||
}
|
||||
|
||||
// NewOverloads creates a new empty overload collection.
|
||||
func NewOverloads() *Overloads {
|
||||
return &Overloads{
|
||||
ByParamType: make(map[string]FunctionInterface),
|
||||
AllOverloads: make([]FunctionInterface, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds the given function to the overload collection. Returns an error if the there's a problem with the
|
||||
// function's declaration.
|
||||
func (o *Overloads) Add(function FunctionInterface) error {
|
||||
key := keyForParamTypes(function.GetParameters())
|
||||
if _, ok := o.ByParamType[key]; ok {
|
||||
return errors.Errorf("duplicate function overload for `%s`", function.GetName())
|
||||
}
|
||||
|
||||
if function.VariadicIndex() >= 0 {
|
||||
varArgsType := function.GetParameters()[function.VariadicIndex()]
|
||||
if !varArgsType.IsArrayType() {
|
||||
return errors.Errorf("variadic parameter must be an array type for function `%s`", function.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
o.ByParamType[key] = function
|
||||
o.AllOverloads = append(o.AllOverloads, function)
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyForParamTypes returns a string key to match an overload with the given parameter types.
|
||||
func keyForParamTypes(types []*pgtypes.DoltgresType) string {
|
||||
sb := strings.Builder{}
|
||||
for i, typ := range types {
|
||||
if i > 0 {
|
||||
sb.WriteByte(',')
|
||||
}
|
||||
sb.WriteString(typ.String())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// overloadsForParams returns all overloads matching the number of params given, without regard for types.
|
||||
func (o *Overloads) overloadsForParams(numParams int) []Overload {
|
||||
results := make([]Overload, 0, len(o.AllOverloads))
|
||||
for _, overload := range o.AllOverloads {
|
||||
params := overload.GetParameters()
|
||||
variadicIndex := overload.VariadicIndex()
|
||||
if overload.IsCVariadic() {
|
||||
if len(params) <= numParams {
|
||||
paramTypes := make([]*pgtypes.DoltgresType, numParams)
|
||||
argTypes := make([]*pgtypes.DoltgresType, numParams)
|
||||
copy(paramTypes, params)
|
||||
copy(argTypes, params)
|
||||
for i := len(params); i < numParams; i++ {
|
||||
paramTypes[i] = pgtypes.Any
|
||||
argTypes[i] = pgtypes.Any
|
||||
}
|
||||
results = append(results, Overload{
|
||||
function: overload,
|
||||
paramTypes: paramTypes,
|
||||
argTypes: argTypes,
|
||||
variadic: -1,
|
||||
})
|
||||
}
|
||||
} else if variadicIndex >= 0 && len(params) <= numParams {
|
||||
// Variadic functions may only match when the function is declared with parameters that are fewer or equal
|
||||
// to our target length. If our target length is less, then we cannot expand, so we do not treat it as
|
||||
// variadic.
|
||||
extendedParams := make([]*pgtypes.DoltgresType, numParams)
|
||||
copy(extendedParams, params[:variadicIndex])
|
||||
// This is copying the parameters after the variadic index, so we need to add 1. We subtract the declared
|
||||
// parameter count from the target parameter count to obtain the additional parameter count.
|
||||
firstValueAfterVariadic := variadicIndex + 1 + (numParams - len(params))
|
||||
copy(extendedParams[firstValueAfterVariadic:], params[variadicIndex+1:])
|
||||
paramType := overload.GetParameters()[variadicIndex]
|
||||
|
||||
var variadicBaseType *pgtypes.DoltgresType
|
||||
|
||||
// special case: anyArray takes any args, pass as is
|
||||
if paramType == pgtypes.AnyArray {
|
||||
for variadicParamIdx := 0; variadicParamIdx < 1+(numParams-len(params)); variadicParamIdx++ {
|
||||
variadicBaseType = pgtypes.AnyElement
|
||||
}
|
||||
} else {
|
||||
// ToArrayType immediately followed by BaseType is a way to get the base type without having to cast.
|
||||
// For array types, ToArrayType causes them to return themselves.
|
||||
variadicBaseType = paramType.ToArrayType().ArrayBaseType()
|
||||
}
|
||||
|
||||
for variadicParamIdx := 0; variadicParamIdx < 1+(numParams-len(params)); variadicParamIdx++ {
|
||||
extendedParams[variadicParamIdx+variadicIndex] = variadicBaseType
|
||||
}
|
||||
results = append(results, Overload{
|
||||
function: overload,
|
||||
paramTypes: params,
|
||||
argTypes: extendedParams,
|
||||
variadic: variadicIndex,
|
||||
})
|
||||
} else if len(params) == numParams {
|
||||
results = append(results, Overload{
|
||||
function: overload,
|
||||
paramTypes: params,
|
||||
argTypes: params,
|
||||
variadic: -1,
|
||||
})
|
||||
} else if sqlFunc, ok := overload.(SQLFunction); ok && len(params) > numParams {
|
||||
// if it's SQL function and has fewer arguments than parameters, check for default values.
|
||||
restIsDefaultValues := true
|
||||
for _, d := range sqlFunc.ParameterDefaults[numParams:] {
|
||||
if d == "" {
|
||||
restIsDefaultValues = false
|
||||
}
|
||||
}
|
||||
if restIsDefaultValues {
|
||||
results = append(results, Overload{
|
||||
function: overload,
|
||||
paramTypes: params,
|
||||
argTypes: params,
|
||||
variadic: -1,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// ExactMatchForTypes returns the function that exactly matches the given parameter types, or nil if no overload with
|
||||
// those types exists.
|
||||
func (o *Overloads) ExactMatchForTypes(types ...*pgtypes.DoltgresType) (FunctionInterface, bool) {
|
||||
key := keyForParamTypes(types)
|
||||
fn, ok := o.ByParamType[key]
|
||||
return fn, ok
|
||||
}
|
||||
|
||||
// Overload is a single overload of a given function, used during evaluation to match the arguments provided
|
||||
// to a particular overload.
|
||||
type Overload struct {
|
||||
// function is the actual function to call to invoke this overload
|
||||
function FunctionInterface
|
||||
// paramTypes is the base IDs of the parameters that the function expects
|
||||
paramTypes []*pgtypes.DoltgresType
|
||||
// argTypes is the base IDs of the parameters that the function expects, extended to match the number of args
|
||||
// provided in the case of a variadic function.
|
||||
argTypes []*pgtypes.DoltgresType
|
||||
// variadic is the index of the variadic parameter, or -1 if the function is not variadic
|
||||
variadic int
|
||||
}
|
||||
|
||||
// coalesceVariadicValues returns a new value set that coalesces all variadic parameters into an array parameter
|
||||
func (o *Overload) coalesceVariadicValues(returnValues []any) []any {
|
||||
// If the overload is not variadic, then we don't need to do anything
|
||||
if o.variadic < 0 {
|
||||
return returnValues
|
||||
}
|
||||
coalescedValues := make([]any, len(o.paramTypes))
|
||||
copy(coalescedValues, returnValues[:o.variadic])
|
||||
// This is for the values after the variadic index, so we need to add 1. We subtract the extended parameter count
|
||||
// from the actual parameter count to obtain the additional parameter count.
|
||||
firstValueAfterVariadic := o.variadic + 1 + (len(o.argTypes) - len(o.paramTypes))
|
||||
copy(coalescedValues[o.variadic+1:], returnValues[firstValueAfterVariadic:])
|
||||
// We can just take the relevant slice out of the given values to represent our array, since all arrays use []any
|
||||
coalescedValues[o.variadic] = returnValues[o.variadic:firstValueAfterVariadic]
|
||||
return coalescedValues
|
||||
}
|
||||
|
||||
// overloadMatch is the result of a successful overload resolution, containing the types of the parameters as well
|
||||
// as the type cast functions required to convert every argument to its appropriate parameter type
|
||||
type overloadMatch struct {
|
||||
params Overload
|
||||
casts []casts.Cast
|
||||
}
|
||||
|
||||
// Valid returns whether this overload is valid (has a callable function)
|
||||
func (o overloadMatch) Valid() bool {
|
||||
return o.params.function != nil
|
||||
}
|
||||
|
||||
// Function returns the function for this overload
|
||||
func (o overloadMatch) Function() FunctionInterface {
|
||||
return o.params.function
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright 2025 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 (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/extensions"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// FunctionProvider is the special sql.FunctionProvider for Doltgres that allows us to handle functions that
|
||||
// are created by users.
|
||||
type FunctionProvider struct{}
|
||||
|
||||
var _ sql.FunctionProvider = (*FunctionProvider)(nil)
|
||||
|
||||
// Function implements the interface sql.FunctionProvider.
|
||||
func (fp *FunctionProvider) Function(ctx *sql.Context, schema, name string) (sql.Function, bool) {
|
||||
// TODO: this should be configurable from within Dolt, rather than set on an external variable
|
||||
if !core.IsContextValid(ctx) {
|
||||
return nil, false
|
||||
}
|
||||
funcCollection, err := core.GetFunctionsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
typesCollection, err := core.GetTypesCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
// TODO: this should search all schemas in the search path, but the search path doesn't handle pg_catalog yet
|
||||
funcName := id.NewFunction("pg_catalog", name)
|
||||
overloads, err := funcCollection.GetFunctionOverloads(ctx, funcName)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(overloads) == 0 {
|
||||
if schema == "" {
|
||||
currentSchema, err := core.GetCurrentSchema(ctx)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
schema = currentSchema
|
||||
}
|
||||
funcName = id.NewFunction(schema, name)
|
||||
overloads, err = funcCollection.GetFunctionOverloads(ctx, funcName)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(overloads) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
overloadTree := NewOverloads()
|
||||
for _, overload := range overloads {
|
||||
returnType, err := typesCollection.GetType(ctx, overload.ReturnType)
|
||||
if err != nil || returnType == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
paramTypes := make([]*pgtypes.DoltgresType, len(overload.ParameterTypes))
|
||||
for i, paramType := range overload.ParameterTypes {
|
||||
paramTypes[i], err = typesCollection.GetType(ctx, paramType)
|
||||
if err != nil || paramTypes[i] == nil {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if len(overload.ExtensionName) > 0 {
|
||||
if err = overloadTree.Add(CFunction{
|
||||
ID: overload.ID,
|
||||
ReturnType: returnType,
|
||||
ParameterTypes: paramTypes,
|
||||
Variadic: overload.Variadic,
|
||||
IsNonDeterministic: overload.IsNonDeterministic,
|
||||
Strict: overload.Strict,
|
||||
ExtensionName: extensions.LibraryIdentifier(overload.ExtensionName),
|
||||
ExtensionSymbol: overload.ExtensionSymbol,
|
||||
}); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
} else if len(overload.SQLDefinition) > 0 {
|
||||
if err = overloadTree.Add(SQLFunction{
|
||||
ID: overload.ID,
|
||||
ReturnType: returnType,
|
||||
ParameterNames: overload.ParameterNames,
|
||||
ParameterTypes: paramTypes,
|
||||
ParameterDefaults: overload.ParameterDefaults,
|
||||
Variadic: overload.Variadic,
|
||||
IsNonDeterministic: overload.IsNonDeterministic,
|
||||
Strict: overload.Strict,
|
||||
SqlStatement: overload.SQLDefinition,
|
||||
SetOf: overload.SetOf,
|
||||
}); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
} else {
|
||||
if err = overloadTree.Add(InterpretedFunction{
|
||||
ID: overload.ID,
|
||||
ReturnType: returnType,
|
||||
ParameterNames: overload.ParameterNames,
|
||||
ParameterTypes: paramTypes,
|
||||
Variadic: overload.Variadic,
|
||||
IsNonDeterministic: overload.IsNonDeterministic,
|
||||
Strict: overload.Strict,
|
||||
SRF: overload.SetOf,
|
||||
Statements: overload.Operations,
|
||||
}); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return sql.FunctionN{
|
||||
Name: name,
|
||||
Fn: func(ctx *sql.Context, params ...sql.Expression) (sql.Expression, error) {
|
||||
return NewCompiledFunction(ctx, name, params, overloadTree, false), nil
|
||||
},
|
||||
}, true
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
// 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"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// QuickFunction represents an optimized function expression that has specific criteria in order to streamline
|
||||
// evaluation. This will only apply to very specific functions that are generally performance-critical.
|
||||
type QuickFunction interface {
|
||||
Function
|
||||
sql.RowIterExpression
|
||||
// CallVariadic is the variadic form of the Call function that is specific to each implementation of QuickFunction.
|
||||
// The implementation will not verify that the correct number of arguments have been passed.
|
||||
CallVariadic(ctx *sql.Context, args ...any) (interface{}, error)
|
||||
// ResolvedTypes returns the types that were resolved with this function.
|
||||
ResolvedTypes() []*pgtypes.DoltgresType
|
||||
// WithResolvedTypes returns a new QuickFunction with the replaced resolved types. The implementation will not
|
||||
// verify that the new types are correct in any way. This returns a QuickFunction, however it's typed as "any" due
|
||||
// to potential import cycles.
|
||||
WithResolvedTypes(newTypes []*pgtypes.DoltgresType) any
|
||||
}
|
||||
|
||||
// QuickFunction1 is an implementation of QuickFunction that handles a single parameter.
|
||||
type QuickFunction1 struct {
|
||||
Name string
|
||||
Argument sql.Expression
|
||||
IsStrict bool
|
||||
IsSRF bool
|
||||
callResolved [2]*pgtypes.DoltgresType
|
||||
function Function1
|
||||
}
|
||||
|
||||
var _ QuickFunction = (*QuickFunction1)(nil)
|
||||
|
||||
// FunctionName implements the interface sql.Expression.
|
||||
func (q *QuickFunction1) FunctionName() string {
|
||||
return q.Name
|
||||
}
|
||||
|
||||
// Description implements the interface sql.Expression.
|
||||
func (q *QuickFunction1) Description() string {
|
||||
return fmt.Sprintf("The PostgreSQL function `%s`", q.Name)
|
||||
}
|
||||
|
||||
// Resolved implements the interface sql.Expression.
|
||||
func (q *QuickFunction1) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// String implements the interface sql.Expression.
|
||||
func (q *QuickFunction1) String() string {
|
||||
// We'll reuse the compiled function's output so that the logic is centralized
|
||||
c := CompiledFunction{
|
||||
Name: q.Name,
|
||||
Arguments: []sql.Expression{q.Argument},
|
||||
}
|
||||
return c.String()
|
||||
}
|
||||
|
||||
// Type implements the interface sql.Expression.
|
||||
func (q *QuickFunction1) Type(ctx *sql.Context) sql.Type {
|
||||
return getTypeIfRowType(q.IsSRF, q.callResolved[1])
|
||||
}
|
||||
|
||||
// IsNullable implements the interface sql.Expression.
|
||||
func (q *QuickFunction1) IsNullable(ctx *sql.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsNonDeterministic implements the interface sql.NonDeterministicExpression.
|
||||
func (q *QuickFunction1) IsNonDeterministic() bool {
|
||||
return q.function.IsNonDeterministic
|
||||
}
|
||||
|
||||
// Eval implements the interface sql.Expression.
|
||||
func (q *QuickFunction1) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
|
||||
arg, err := q.Argument.Eval(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if arg == nil && q.IsStrict {
|
||||
return nil, nil
|
||||
}
|
||||
return q.function.Callable(ctx, q.callResolved, arg)
|
||||
}
|
||||
|
||||
// EvalRowIter implements the interface sql.RowIterExpression.
|
||||
func (q *QuickFunction1) EvalRowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
eval, err := q.Eval(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch eval := eval.(type) {
|
||||
case sql.RowIter:
|
||||
return eval, nil
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, errors.Errorf("function %s returned a value of type %T, which is not a RowIter", q.Name, eval)
|
||||
}
|
||||
}
|
||||
|
||||
// ReturnsRowIter implements the interface sql.RowIterExpression.
|
||||
func (q *QuickFunction1) ReturnsRowIter() bool {
|
||||
return q.IsSRF
|
||||
}
|
||||
|
||||
// Call directly calls the underlying function with the given arguments. This does not perform any form of NULL checking
|
||||
// as it is assumed that it was done prior to this call. It also does not validate any types. This exists purely for
|
||||
// performance, when we can guarantee that the input is always valid and well-formed.
|
||||
func (q *QuickFunction1) Call(ctx *sql.Context, arg0 any) (interface{}, error) {
|
||||
return q.function.Callable(ctx, q.callResolved, arg0)
|
||||
}
|
||||
|
||||
// CallVariadic implements the interface QuickFunction.
|
||||
func (q *QuickFunction1) CallVariadic(ctx *sql.Context, args ...any) (interface{}, error) {
|
||||
return q.function.Callable(ctx, q.callResolved, args[0])
|
||||
}
|
||||
|
||||
// ResolvedTypes implements the interface QuickFunction.
|
||||
func (q *QuickFunction1) ResolvedTypes() []*pgtypes.DoltgresType {
|
||||
return q.callResolved[:]
|
||||
}
|
||||
|
||||
// WithResolvedTypes implements the interface QuickFunction.
|
||||
func (q *QuickFunction1) WithResolvedTypes(newTypes []*pgtypes.DoltgresType) any {
|
||||
return &QuickFunction1{
|
||||
Name: q.Name,
|
||||
Argument: q.Argument,
|
||||
IsStrict: q.IsStrict,
|
||||
IsSRF: q.IsSRF,
|
||||
callResolved: [2]*pgtypes.DoltgresType(newTypes),
|
||||
function: q.function,
|
||||
}
|
||||
}
|
||||
|
||||
// Children implements the interface sql.Expression.
|
||||
func (q *QuickFunction1) Children() []sql.Expression {
|
||||
return []sql.Expression{q.Argument}
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.Expression.
|
||||
func (q *QuickFunction1) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
|
||||
if len(children) != 1 {
|
||||
return nil, sql.ErrInvalidChildrenNumber.New(q, len(children), 1)
|
||||
}
|
||||
|
||||
if children[0].Type(ctx).Equals(q.Argument.Type(ctx)) {
|
||||
nq := *q
|
||||
nq.Argument = children[0]
|
||||
return &nq, nil
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("cannot change the types of children for `%T`", q)
|
||||
}
|
||||
|
||||
// specificFuncImpl implements the interface sql.Expression.
|
||||
func (*QuickFunction1) specificFuncImpl() {}
|
||||
|
||||
// QuickFunction2 is an implementation of QuickFunction that handles two parameters.
|
||||
type QuickFunction2 struct {
|
||||
Name string
|
||||
Arguments [2]sql.Expression
|
||||
IsStrict bool
|
||||
IsSRF bool
|
||||
callResolved [3]*pgtypes.DoltgresType
|
||||
function Function2
|
||||
}
|
||||
|
||||
var _ QuickFunction = (*QuickFunction2)(nil)
|
||||
|
||||
// FunctionName implements the interface sql.Expression.
|
||||
func (q *QuickFunction2) FunctionName() string {
|
||||
return q.Name
|
||||
}
|
||||
|
||||
// Description implements the interface sql.Expression.
|
||||
func (q *QuickFunction2) Description() string {
|
||||
return fmt.Sprintf("The PostgreSQL function `%s`", q.Name)
|
||||
}
|
||||
|
||||
// Resolved implements the interface sql.Expression.
|
||||
func (q *QuickFunction2) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// String implements the interface sql.Expression.
|
||||
func (q *QuickFunction2) String() string {
|
||||
// We'll reuse the compiled function's output so that the logic is centralized
|
||||
c := CompiledFunction{
|
||||
Name: q.Name,
|
||||
Arguments: q.Arguments[:],
|
||||
}
|
||||
return c.String()
|
||||
}
|
||||
|
||||
// Type implements the interface sql.Expression.
|
||||
func (q *QuickFunction2) Type(ctx *sql.Context) sql.Type {
|
||||
return getTypeIfRowType(q.IsSRF, q.callResolved[2])
|
||||
}
|
||||
|
||||
// IsNullable implements the interface sql.Expression.
|
||||
func (q *QuickFunction2) IsNullable(ctx *sql.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsNonDeterministic implements the interface sql.NonDeterministicExpression.
|
||||
func (q *QuickFunction2) IsNonDeterministic() bool {
|
||||
return q.function.IsNonDeterministic
|
||||
}
|
||||
|
||||
// Eval implements the interface sql.Expression.
|
||||
func (q *QuickFunction2) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
|
||||
var args [2]any
|
||||
for i := range q.Arguments {
|
||||
var err error
|
||||
args[i], err = q.Arguments[i].Eval(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args[i] == nil && q.IsStrict {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return q.function.Callable(ctx, q.callResolved, args[0], args[1])
|
||||
}
|
||||
|
||||
// EvalRowIter implements the interface sql.RowIterExpression.
|
||||
func (q *QuickFunction2) EvalRowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
eval, err := q.Eval(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch eval := eval.(type) {
|
||||
case sql.RowIter:
|
||||
return eval, nil
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, errors.Errorf("function %s returned a value of type %T, which is not a RowIter", q.Name, eval)
|
||||
}
|
||||
}
|
||||
|
||||
// ReturnsRowIter implements the interface sql.RowIterExpression.
|
||||
func (q *QuickFunction2) ReturnsRowIter() bool {
|
||||
return q.IsSRF
|
||||
}
|
||||
|
||||
// Call directly calls the underlying function with the given arguments. This does not perform any form of NULL checking
|
||||
// as it is assumed that it was done prior to this call. It also does not validate any types. This exists purely for
|
||||
// performance, when we can guarantee that the input is always valid and well-formed.
|
||||
func (q *QuickFunction2) Call(ctx *sql.Context, arg0 any, arg1 any) (interface{}, error) {
|
||||
return q.function.Callable(ctx, q.callResolved, arg0, arg1)
|
||||
}
|
||||
|
||||
// CallVariadic implements the interface QuickFunction.
|
||||
func (q *QuickFunction2) CallVariadic(ctx *sql.Context, args ...any) (interface{}, error) {
|
||||
return q.function.Callable(ctx, q.callResolved, args[0], args[1])
|
||||
}
|
||||
|
||||
// ResolvedTypes implements the interface QuickFunction.
|
||||
func (q *QuickFunction2) ResolvedTypes() []*pgtypes.DoltgresType {
|
||||
return q.callResolved[:]
|
||||
}
|
||||
|
||||
// WithResolvedTypes implements the interface QuickFunction.
|
||||
func (q *QuickFunction2) WithResolvedTypes(newTypes []*pgtypes.DoltgresType) any {
|
||||
return &QuickFunction2{
|
||||
Name: q.Name,
|
||||
Arguments: q.Arguments,
|
||||
IsStrict: q.IsStrict,
|
||||
IsSRF: q.IsSRF,
|
||||
callResolved: [3]*pgtypes.DoltgresType(newTypes),
|
||||
function: q.function,
|
||||
}
|
||||
}
|
||||
|
||||
// Children implements the interface sql.Expression.
|
||||
func (q *QuickFunction2) Children() []sql.Expression {
|
||||
return q.Arguments[:]
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.Expression.
|
||||
func (q *QuickFunction2) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
|
||||
if len(children) != 2 {
|
||||
return nil, sql.ErrInvalidChildrenNumber.New(q, len(children), 2)
|
||||
}
|
||||
|
||||
if children[0].Type(ctx).Equals(q.Arguments[0].Type(ctx)) &&
|
||||
children[1].Type(ctx).Equals(q.Arguments[1].Type(ctx)) {
|
||||
nq := *q
|
||||
nq.Arguments = [2]sql.Expression{children[0], children[1]}
|
||||
return &nq, nil
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("cannot change the types of children for `%T`", q)
|
||||
}
|
||||
|
||||
// specificFuncImpl implements the interface sql.Expression.
|
||||
func (*QuickFunction2) specificFuncImpl() {}
|
||||
|
||||
// QuickFunction3 is an implementation of QuickFunction that handles three parameters.
|
||||
type QuickFunction3 struct {
|
||||
Name string
|
||||
Arguments [3]sql.Expression
|
||||
IsStrict bool
|
||||
IsSRF bool
|
||||
callResolved [4]*pgtypes.DoltgresType
|
||||
function Function3
|
||||
}
|
||||
|
||||
var _ QuickFunction = (*QuickFunction3)(nil)
|
||||
|
||||
// FunctionName implements the interface sql.Expression.
|
||||
func (q *QuickFunction3) FunctionName() string {
|
||||
return q.Name
|
||||
}
|
||||
|
||||
// Description implements the interface sql.Expression.
|
||||
func (q *QuickFunction3) Description() string {
|
||||
return fmt.Sprintf("The PostgreSQL function `%s`", q.Name)
|
||||
}
|
||||
|
||||
// Resolved implements the interface sql.Expression.
|
||||
func (q *QuickFunction3) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// String implements the interface sql.Expression.
|
||||
func (q *QuickFunction3) String() string {
|
||||
// We'll reuse the compiled function's output so that the logic is centralized
|
||||
c := CompiledFunction{
|
||||
Name: q.Name,
|
||||
Arguments: q.Arguments[:],
|
||||
}
|
||||
return c.String()
|
||||
}
|
||||
|
||||
// Type implements the interface sql.Expression.
|
||||
func (q *QuickFunction3) Type(ctx *sql.Context) sql.Type {
|
||||
return getTypeIfRowType(q.IsSRF, q.callResolved[3])
|
||||
}
|
||||
|
||||
// IsNullable implements the interface sql.Expression.
|
||||
func (q *QuickFunction3) IsNullable(ctx *sql.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsNonDeterministic implements the interface sql.NonDeterministicExpression.
|
||||
func (q *QuickFunction3) IsNonDeterministic() bool {
|
||||
return q.function.IsNonDeterministic
|
||||
}
|
||||
|
||||
// Eval implements the interface sql.Expression.
|
||||
func (q *QuickFunction3) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
|
||||
var args [3]any
|
||||
for i := range q.Arguments {
|
||||
var err error
|
||||
args[i], err = q.Arguments[i].Eval(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args[i] == nil && q.IsStrict {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return q.function.Callable(ctx, q.callResolved, args[0], args[1], args[2])
|
||||
}
|
||||
|
||||
// EvalRowIter implements the interface sql.RowIterExpression.
|
||||
func (q *QuickFunction3) EvalRowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
eval, err := q.Eval(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch eval := eval.(type) {
|
||||
case sql.RowIter:
|
||||
return eval, nil
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, errors.Errorf("function %s returned a value of type %T, which is not a RowIter", q.Name, eval)
|
||||
}
|
||||
}
|
||||
|
||||
// ReturnsRowIter implements the interface sql.RowIterExpression.
|
||||
func (q *QuickFunction3) ReturnsRowIter() bool {
|
||||
return q.IsSRF
|
||||
}
|
||||
|
||||
// Call directly calls the underlying function with the given arguments. This does not perform any form of NULL checking
|
||||
// as it is assumed that it was done prior to this call. It also does not validate any types. This exists purely for
|
||||
// performance, when we can guarantee that the input is always valid and well-formed.
|
||||
func (q *QuickFunction3) Call(ctx *sql.Context, arg0 any, arg1 any, arg2 any) (interface{}, error) {
|
||||
return q.function.Callable(ctx, q.callResolved, arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// CallVariadic implements the interface QuickFunction.
|
||||
func (q *QuickFunction3) CallVariadic(ctx *sql.Context, args ...any) (interface{}, error) {
|
||||
return q.function.Callable(ctx, q.callResolved, args[0], args[1], args[2])
|
||||
}
|
||||
|
||||
// ResolvedTypes implements the interface QuickFunction.
|
||||
func (q *QuickFunction3) ResolvedTypes() []*pgtypes.DoltgresType {
|
||||
return q.callResolved[:]
|
||||
}
|
||||
|
||||
// WithResolvedTypes implements the interface QuickFunction.
|
||||
func (q *QuickFunction3) WithResolvedTypes(newTypes []*pgtypes.DoltgresType) any {
|
||||
return &QuickFunction3{
|
||||
Name: q.Name,
|
||||
Arguments: q.Arguments,
|
||||
IsStrict: q.IsStrict,
|
||||
IsSRF: q.IsSRF,
|
||||
callResolved: [4]*pgtypes.DoltgresType(newTypes),
|
||||
function: q.function,
|
||||
}
|
||||
}
|
||||
|
||||
// Children implements the interface sql.Expression.
|
||||
func (q *QuickFunction3) Children() []sql.Expression {
|
||||
return q.Arguments[:]
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.Expression.
|
||||
func (q *QuickFunction3) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
|
||||
if len(children) != 3 {
|
||||
return nil, sql.ErrInvalidChildrenNumber.New(q, len(children), 3)
|
||||
}
|
||||
|
||||
if children[0].Type(ctx).Equals(q.Arguments[0].Type(ctx)) &&
|
||||
children[1].Type(ctx).Equals(q.Arguments[1].Type(ctx)) &&
|
||||
children[2].Type(ctx).Equals(q.Arguments[2].Type(ctx)) {
|
||||
nq := *q
|
||||
nq.Arguments = [3]sql.Expression{children[0], children[1], children[2]}
|
||||
return &nq, nil
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("cannot change the types of children for `%T`", q)
|
||||
}
|
||||
|
||||
// specificFuncImpl implements the interface sql.Expression.
|
||||
func (*QuickFunction3) specificFuncImpl() {}
|
||||
@@ -0,0 +1,332 @@
|
||||
// Copyright 2025 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"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/parser"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// SQLFunction is the implementation of functions created using SQL.
|
||||
type SQLFunction struct {
|
||||
ID id.Function
|
||||
ReturnType *pgtypes.DoltgresType
|
||||
ParameterNames []string
|
||||
ParameterTypes []*pgtypes.DoltgresType
|
||||
ParameterDefaults []string
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
SqlStatement string
|
||||
SetOf bool
|
||||
ReturnTableType []*pgtypes.DoltgresType
|
||||
}
|
||||
|
||||
var _ FunctionInterface = SQLFunction{}
|
||||
|
||||
// GetExpectedParameterCount implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) GetExpectedParameterCount() int {
|
||||
return len(sqlFunc.ParameterTypes)
|
||||
}
|
||||
|
||||
// GetName implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) GetName() string {
|
||||
return sqlFunc.ID.FunctionName()
|
||||
}
|
||||
|
||||
// GetParameters implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) GetParameters() []*pgtypes.DoltgresType {
|
||||
return sqlFunc.ParameterTypes
|
||||
}
|
||||
|
||||
// GetReturn implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) GetReturn() *pgtypes.DoltgresType {
|
||||
return sqlFunc.ReturnType
|
||||
}
|
||||
|
||||
// InternalID implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) InternalID() id.Id {
|
||||
return sqlFunc.ID.AsId()
|
||||
}
|
||||
|
||||
// IsStrict implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) IsStrict() bool {
|
||||
return sqlFunc.Strict
|
||||
}
|
||||
|
||||
// NonDeterministic implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) NonDeterministic() bool {
|
||||
return sqlFunc.IsNonDeterministic
|
||||
}
|
||||
|
||||
// IsCVariadic implements the FunctionInterface interface.
|
||||
func (sqlFunc SQLFunction) IsCVariadic() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// VariadicIndex implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) VariadicIndex() int {
|
||||
// TODO: implement variadic
|
||||
return -1
|
||||
}
|
||||
|
||||
// IsSRF implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) IsSRF() bool {
|
||||
return sqlFunc.SetOf
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the interface FunctionInterface.
|
||||
func (sqlFunc SQLFunction) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// CallSqlFunction runs the given SQL definition inside the function on the given runner.
|
||||
func CallSqlFunction(ctx *sql.Context, f SQLFunction, runner sql.StatementRunner, args []any) (any, error) {
|
||||
paramMap := make(map[string]*ParamTypAndValue)
|
||||
for i, name := range f.ParameterNames {
|
||||
if name == "" {
|
||||
// This allows for positional references such as $1, $2, etc.
|
||||
name = fmt.Sprintf("$%d", i+1)
|
||||
}
|
||||
paramMap[name] = &ParamTypAndValue{
|
||||
Typ: f.ParameterTypes[i],
|
||||
Val: args[i],
|
||||
FromCreate: false,
|
||||
}
|
||||
}
|
||||
|
||||
if lower := strings.ToLower(f.SqlStatement); strings.HasPrefix(lower, "return") {
|
||||
f.SqlStatement = fmt.Sprintf("SELECT%s", f.SqlStatement[6:])
|
||||
}
|
||||
|
||||
parseds, err := parser.Parse(f.SqlStatement)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(parseds) > 1 {
|
||||
// of multiple statements, the function returns the result of the final statement in the execution block
|
||||
var res any
|
||||
for _, parsed := range parseds {
|
||||
err = ReplaceFunctionColumn(parsed.AST, paramMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
convertedAST, err := convertToVitess(parsed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err = sql.RunInterpreted(ctx, func(subCtx *sql.Context) (any, error) {
|
||||
sch, rowIter, _, err := runner.QueryWithBindings(ctx, parsed.AST.String(), convertedAST, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := sql.RowIterToRows(subCtx, rowIter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(sch) != 1 {
|
||||
return nil, errors.New("expression does not result in a single value")
|
||||
}
|
||||
if len(rows) != 1 {
|
||||
return nil, errors.New("expression returned multiple result sets")
|
||||
}
|
||||
if len(rows[0]) != 1 {
|
||||
return nil, errors.New("expression returned multiple results")
|
||||
}
|
||||
return rows[0][0], nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if f.ReturnType.ID == pgtypes.Void.ID {
|
||||
return nil, nil
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// single statement
|
||||
parsed := parseds[0]
|
||||
err = ReplaceFunctionColumn(parsed.AST, paramMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
convertedAST, err := convertToVitess(parsed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// stmt.AST is updated at this point with FunctionColumn
|
||||
return sql.RunInterpreted(ctx, func(subCtx *sql.Context) (any, error) {
|
||||
sch, rowIter, _, err := runner.QueryWithBindings(ctx, parsed.AST.String(), convertedAST, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !f.SetOf {
|
||||
rows, err := sql.RowIterToRows(subCtx, rowIter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// single column row result
|
||||
if len(sch) != 1 {
|
||||
return nil, errors.New("expression does not result in a single value")
|
||||
}
|
||||
if len(rows) != 1 {
|
||||
return nil, errors.New("expression returned multiple result sets")
|
||||
}
|
||||
if len(rows[0]) != 1 {
|
||||
return nil, errors.New("expression returned multiple results")
|
||||
}
|
||||
return rows[0][0], nil
|
||||
}
|
||||
// multiple column row result
|
||||
if f.ReturnType.TypCategory == pgtypes.TypeCategory_CompositeTypes {
|
||||
// record type
|
||||
return rowIterToRecord(ctx, rowIter, sch)
|
||||
}
|
||||
return rowIter, nil
|
||||
})
|
||||
}
|
||||
|
||||
// ParamTypAndValue contains the parameter type and
|
||||
// string value of argument if applicable
|
||||
type ParamTypAndValue struct {
|
||||
Typ *pgtypes.DoltgresType
|
||||
Val any
|
||||
FromCreate bool
|
||||
}
|
||||
|
||||
// ReplaceFunctionColumn parses and replaces UnresolvedName and Placeholder expressions
|
||||
// with FunctionColumn expression containing parameter type and arguments if applicable.
|
||||
// It also replaces empty parameter name with binding variable name to match the name used in FunctionColumn.
|
||||
// This function should be used for FUNCTION with SQL language statements only.
|
||||
func ReplaceFunctionColumn(parsedAST tree.Statement, params map[string]*ParamTypAndValue) error {
|
||||
if len(params) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Function's final statement must be SELECT or INSERT/UPDATE/DELETE RETURNING
|
||||
switch s := parsedAST.(type) {
|
||||
case *tree.Select:
|
||||
switch t := s.Select.(type) {
|
||||
case *tree.SelectClause:
|
||||
for i, e := range t.Exprs {
|
||||
t.Exprs[i].Expr = ReplaceUnresolvedToFunctionColumn(params, e.Expr)
|
||||
}
|
||||
if t.Where != nil {
|
||||
t.Where.Expr = ReplaceUnresolvedToFunctionColumn(params, t.Where.Expr)
|
||||
}
|
||||
case *tree.ValuesClause:
|
||||
for i, row := range t.Rows {
|
||||
for j, e := range row {
|
||||
row[j] = ReplaceUnresolvedToFunctionColumn(params, e)
|
||||
}
|
||||
t.Rows[i] = row
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case *tree.Insert:
|
||||
err := ReplaceFunctionColumn(s.Rows, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case *tree.Update:
|
||||
for i, e := range s.Exprs {
|
||||
s.Exprs[i].Expr = ReplaceUnresolvedToFunctionColumn(params, e.Expr)
|
||||
}
|
||||
if s.Where != nil {
|
||||
s.Where.Expr = ReplaceUnresolvedToFunctionColumn(params, s.Where.Expr)
|
||||
}
|
||||
return nil
|
||||
case *tree.Delete:
|
||||
if s.Where != nil {
|
||||
s.Where.Expr = ReplaceUnresolvedToFunctionColumn(params, s.Where.Expr)
|
||||
}
|
||||
return nil
|
||||
case *tree.Truncate:
|
||||
return nil
|
||||
case *tree.Return:
|
||||
s.Expr = ReplaceUnresolvedToFunctionColumn(params, s.Expr)
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("unsupported statement defined in function: %T", parsedAST)
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceUnresolvedToFunctionColumn replaces Placeholder and UnresolvedName expressions with FunctionColumn containing
|
||||
// parameter type and argument value if applicable when the name of expression matches function parameter.
|
||||
func ReplaceUnresolvedToFunctionColumn(paramMap map[string]*ParamTypAndValue, expr tree.Expr) tree.Expr {
|
||||
e, _ := tree.SimpleVisit(expr, func(visitingExpr tree.Expr) (recurse bool, newExpr tree.Expr, err error) {
|
||||
switch v := visitingExpr.(type) {
|
||||
case *tree.Placeholder:
|
||||
name := fmt.Sprintf("$%d", v.Idx+1)
|
||||
if tv, ok := paramMap[name]; ok {
|
||||
return false, tree.FunctionColumn{
|
||||
Name: name,
|
||||
Typ: tv.Typ,
|
||||
Idx: uint16(v.Idx),
|
||||
FromCreate: tv.FromCreate,
|
||||
Val: tv.Val,
|
||||
}, nil
|
||||
}
|
||||
case *tree.UnresolvedName:
|
||||
name := v.String()
|
||||
if tv, ok := paramMap[name]; ok {
|
||||
return false, tree.FunctionColumn{
|
||||
Name: name,
|
||||
Typ: tv.Typ,
|
||||
FromCreate: tv.FromCreate,
|
||||
Val: tv.Val,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return true, visitingExpr, nil
|
||||
})
|
||||
return e
|
||||
}
|
||||
|
||||
// rowIterToRecord converts given rows with schema provided to rowIter containing array of pgtypes.RecordValue.
|
||||
func rowIterToRecord(ctx *sql.Context, rowIter sql.RowIter, sch sql.Schema) (sql.RowIter, error) {
|
||||
rows, err := sql.RowIterToRows(ctx, rowIter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var newRows = make([]sql.Row, len(rows))
|
||||
for i, row := range rows {
|
||||
if len(row) != len(sch) {
|
||||
return nil, errors.New("number of row values does not match number of schema columns")
|
||||
}
|
||||
var r = make([]pgtypes.RecordValue, len(sch))
|
||||
for j, col := range sch {
|
||||
r[j] = pgtypes.RecordValue{
|
||||
Type: col.Type.(*pgtypes.DoltgresType),
|
||||
Value: row[j],
|
||||
}
|
||||
}
|
||||
newRows[i] = sql.Row{r}
|
||||
}
|
||||
return sql.RowsToRowIter(newRows...), nil
|
||||
}
|
||||
|
||||
// convertToVitess is set by init and is used to avoid import cycles
|
||||
var convertToVitess func(postgresStmt parser.Statement) (sqlparser.Statement, error)
|
||||
Reference in New Issue
Block a user