chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
// 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 functions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
)
|
||||
|
||||
// Collection contains a collection of functions.
|
||||
type Collection struct {
|
||||
accessCache map[id.Function]Function // This cache is used for general access when you know the exact ID
|
||||
overloadCache map[id.Function][]id.Function // This cache is used to find overloads if you know the name
|
||||
idCache []id.Function // This cache simply contains the name of every function
|
||||
mapHash hash.Hash // This is cached so that we don't have to calculate the hash every time
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// Function represents a created function.
|
||||
type Function struct {
|
||||
ID id.Function
|
||||
ReturnType id.Type
|
||||
ParameterNames []string
|
||||
ParameterTypes []id.Type
|
||||
ParameterDefaults []string
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
Definition string
|
||||
ExtensionName string // Only used when this is an extension function
|
||||
ExtensionSymbol string // Only used when this is an extension function
|
||||
Operations []plpgsql.InterpreterOperation // Only used when this is a plpgsql language
|
||||
SQLDefinition string // Only used when this is a sql language
|
||||
SetOf bool
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Function{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(ctx context.Context, underlyingMap prolly.AddressMap, ns tree.NodeStore) (*Collection, error) {
|
||||
collection := &Collection{
|
||||
accessCache: make(map[id.Function]Function),
|
||||
overloadCache: make(map[id.Function][]id.Function),
|
||||
idCache: nil,
|
||||
mapHash: hash.Hash{},
|
||||
underlyingMap: underlyingMap,
|
||||
ns: ns,
|
||||
}
|
||||
return collection, collection.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// GetFunction returns the function with the given ID. Returns a function with an invalid ID if it cannot be found
|
||||
// (Function.ID.IsValid() == false).
|
||||
func (pgf *Collection) GetFunction(ctx context.Context, funcID id.Function) (Function, error) {
|
||||
if f, ok := pgf.accessCache[funcID]; ok {
|
||||
return f, nil
|
||||
}
|
||||
return Function{}, nil
|
||||
}
|
||||
|
||||
// GetFunctionOverloads returns the overloads for the function matching the schema and the function name. The parameter
|
||||
// types are ignored when searching for overloads.
|
||||
func (pgf *Collection) GetFunctionOverloads(ctx context.Context, funcID id.Function) ([]Function, error) {
|
||||
overloads, ok := pgf.overloadCache[id.NewFunction(funcID.SchemaName(), funcID.FunctionName())]
|
||||
if !ok || len(overloads) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
funcs := make([]Function, len(overloads))
|
||||
for i, overload := range overloads {
|
||||
funcs[i] = pgf.accessCache[overload]
|
||||
}
|
||||
return funcs, nil
|
||||
}
|
||||
|
||||
// HasFunction returns whether the function is present.
|
||||
func (pgf *Collection) HasFunction(ctx context.Context, funcID id.Function) bool {
|
||||
_, ok := pgf.accessCache[funcID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AddFunction adds a new function.
|
||||
func (pgf *Collection) AddFunction(ctx context.Context, f Function) error {
|
||||
// First we'll check to see if it exists
|
||||
if _, ok := pgf.accessCache[f.ID]; ok {
|
||||
return errors.Errorf(`function "%s" already exists with same argument types`, f.ID.FunctionName())
|
||||
}
|
||||
|
||||
// Now we'll add the function to our map
|
||||
data, err := f.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pgf.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapEditor := pgf.underlyingMap.Editor()
|
||||
if err = mapEditor.Add(ctx, string(f.ID), h); err != nil {
|
||||
return err
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgf.underlyingMap = newMap
|
||||
pgf.mapHash = pgf.underlyingMap.HashOf()
|
||||
return pgf.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// DropFunction drops an existing function.
|
||||
func (pgf *Collection) DropFunction(ctx context.Context, funcIDs ...id.Function) error {
|
||||
if len(funcIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Check that each name exists before performing any deletions
|
||||
for _, funcID := range funcIDs {
|
||||
if _, ok := pgf.accessCache[funcID]; !ok {
|
||||
return errors.Errorf(`function %s does not exist`, funcID.FunctionName())
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll remove the functions from the map
|
||||
mapEditor := pgf.underlyingMap.Editor()
|
||||
for _, funcID := range funcIDs {
|
||||
err := mapEditor.Delete(ctx, string(funcID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgf.underlyingMap = newMap
|
||||
pgf.mapHash = pgf.underlyingMap.HashOf()
|
||||
return pgf.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given function. Returns an error if the name is ambiguous.
|
||||
//
|
||||
// The following formats are examples of a formatted name:
|
||||
// name()
|
||||
// name(type1, schema.type2)
|
||||
// name(,,)
|
||||
func (pgf *Collection) resolveName(ctx context.Context, schemaName string, formattedName string) (id.Function, error) {
|
||||
if len(pgf.accessCache) == 0 || len(formattedName) == 0 {
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
|
||||
// Extract the actual name from the format
|
||||
leftParenIndex := strings.IndexByte(formattedName, '(')
|
||||
if leftParenIndex == -1 {
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
if formattedName[len(formattedName)-1] != ')' {
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
functionName := strings.TrimSpace(formattedName[:leftParenIndex])
|
||||
var typeIDs []id.Type
|
||||
typePortion := strings.TrimSpace(formattedName[leftParenIndex+1 : len(formattedName)-1])
|
||||
if len(typePortion) > 0 {
|
||||
// If the type portion is just an empty string, then we don't want any type IDs
|
||||
typeStrings := strings.Split(strings.TrimSpace(formattedName[leftParenIndex+1:len(formattedName)-1]), ",")
|
||||
typeIDs = make([]id.Type, len(typeStrings))
|
||||
for i, typeString := range typeStrings {
|
||||
typeParts := strings.Split(typeString, ".")
|
||||
switch len(typeParts) {
|
||||
case 1:
|
||||
typeIDs[i] = id.NewType("", strings.TrimSpace(typeParts[0]))
|
||||
case 2:
|
||||
typeIDs[i] = id.NewType(strings.TrimSpace(typeParts[0]), strings.TrimSpace(typeParts[1]))
|
||||
default:
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's an exact match, then we return exactly that
|
||||
fullID := id.NewFunction(schemaName, functionName, typeIDs...)
|
||||
if _, ok := pgf.accessCache[fullID]; ok {
|
||||
return fullID, nil
|
||||
}
|
||||
|
||||
// Otherwise we'll iterate over all the names
|
||||
var resolvedID id.Function
|
||||
OuterLoop:
|
||||
for _, funcID := range pgf.idCache {
|
||||
if !strings.EqualFold(functionName, funcID.FunctionName()) {
|
||||
continue
|
||||
}
|
||||
if len(schemaName) > 0 && !strings.EqualFold(schemaName, funcID.SchemaName()) {
|
||||
continue
|
||||
}
|
||||
if len(typeIDs) > 0 {
|
||||
if funcID.ParameterCount() != len(typeIDs) {
|
||||
continue
|
||||
}
|
||||
for i, param := range funcID.Parameters() {
|
||||
if len(typeIDs[i].TypeName()) > 0 && !strings.EqualFold(typeIDs[i].TypeName(), param.TypeName()) {
|
||||
continue OuterLoop
|
||||
}
|
||||
if len(typeIDs[i].SchemaName()) > 0 && !strings.EqualFold(typeIDs[i].SchemaName(), param.SchemaName()) {
|
||||
continue OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
// Everything must have matched to have made it here
|
||||
if resolvedID.IsValid() {
|
||||
funcTableName := FunctionIDToTableName(funcID)
|
||||
resolvedTableName := FunctionIDToTableName(resolvedID)
|
||||
return id.NullFunction, fmt.Errorf("`%s.%s` is ambiguous, matches `%s` and `%s`",
|
||||
schemaName, formattedName, funcTableName.String(), resolvedTableName.String())
|
||||
}
|
||||
resolvedID = funcID
|
||||
}
|
||||
return resolvedID, nil
|
||||
}
|
||||
|
||||
// iterateIDs iterates over all function IDs in the collection.
|
||||
func (pgf *Collection) iterateIDs(ctx context.Context, callback func(funcID id.Function) (stop bool, err error)) error {
|
||||
for _, funcID := range pgf.idCache {
|
||||
stop, err := callback(funcID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterateFunctions iterates over all functions in the collection.
|
||||
func (pgf *Collection) IterateFunctions(ctx context.Context, callback func(f Function) (stop bool, err error)) error {
|
||||
for _, funcID := range pgf.idCache {
|
||||
stop, err := callback(pgf.accessCache[funcID])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pgf *Collection) Clone(ctx context.Context) *Collection {
|
||||
return &Collection{
|
||||
accessCache: maps.Clone(pgf.accessCache),
|
||||
overloadCache: maps.Clone(pgf.overloadCache),
|
||||
idCache: slices.Clone(pgf.idCache),
|
||||
underlyingMap: pgf.underlyingMap,
|
||||
mapHash: pgf.mapHash,
|
||||
ns: pgf.ns,
|
||||
}
|
||||
}
|
||||
|
||||
// Map writes any cached sequences to the underlying map, and then returns the underlying map.
|
||||
func (pgf *Collection) Map(ctx context.Context) (prolly.AddressMap, error) {
|
||||
return pgf.underlyingMap, nil
|
||||
}
|
||||
|
||||
// DiffersFrom returns true when the hash that is associated with the underlying map for this collection is different
|
||||
// from the hash in the given root.
|
||||
func (pgf *Collection) DiffersFrom(ctx context.Context, root objinterface.RootValue) bool {
|
||||
hashOnGivenRoot, err := pgf.LoadCollectionHash(ctx, root)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if pgf.mapHash.Equal(hashOnGivenRoot) {
|
||||
return false
|
||||
}
|
||||
// An empty map should match an uninitialized collection on the root
|
||||
count, err := pgf.underlyingMap.Count()
|
||||
if err == nil && count == 0 && hashOnGivenRoot.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// reloadCaches writes the underlying map's contents to the caches.
|
||||
func (pgf *Collection) reloadCaches(ctx context.Context) error {
|
||||
count, err := pgf.underlyingMap.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clear(pgf.accessCache)
|
||||
clear(pgf.overloadCache)
|
||||
pgf.mapHash = pgf.underlyingMap.HashOf()
|
||||
pgf.idCache = make([]id.Function, 0, count)
|
||||
|
||||
return pgf.underlyingMap.IterAll(ctx, func(_ string, h hash.Hash) error {
|
||||
if h.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
data, err := pgf.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := DeserializeFunction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgf.accessCache[f.ID] = f
|
||||
partialID := id.NewFunction(f.ID.SchemaName(), f.ID.FunctionName())
|
||||
pgf.overloadCache[partialID] = append(pgf.overloadCache[partialID], f.ID)
|
||||
pgf.idCache = append(pgf.idCache, f.ID)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// tableNameToID returns the ID that was encoded via the Name() call, as the returned TableName contains additional
|
||||
// information (which this is able to process).
|
||||
func (pgf *Collection) tableNameToID(schemaName string, formattedName string) id.Function {
|
||||
leftParenIndex := strings.IndexByte(formattedName, '(')
|
||||
if leftParenIndex == -1 {
|
||||
return id.NullFunction
|
||||
}
|
||||
if formattedName[len(formattedName)-1] != ')' {
|
||||
return id.NullFunction
|
||||
}
|
||||
functionName := strings.TrimSpace(formattedName[:leftParenIndex])
|
||||
var typeIDs []id.Type
|
||||
typePortion := strings.TrimSpace(formattedName[leftParenIndex+1 : len(formattedName)-1])
|
||||
if len(typePortion) > 0 {
|
||||
// If the type portion is just an empty string, then we don't want any type IDs
|
||||
typeStrings := strings.Split(strings.TrimSpace(formattedName[leftParenIndex+1:len(formattedName)-1]), ",")
|
||||
typeIDs = make([]id.Type, len(typeStrings))
|
||||
for i, typeString := range typeStrings {
|
||||
typeParts := strings.Split(typeString, ".")
|
||||
switch len(typeParts) {
|
||||
case 1:
|
||||
typeIDs[i] = id.NewType("", strings.TrimSpace(typeParts[0]))
|
||||
case 2:
|
||||
typeIDs[i] = id.NewType(strings.TrimSpace(typeParts[0]), strings.TrimSpace(typeParts[1]))
|
||||
default:
|
||||
return id.NullFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
return id.NewFunction(schemaName, functionName, typeIDs...)
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (function Function) GetID() id.Id {
|
||||
return function.ID.AsId()
|
||||
}
|
||||
|
||||
// GetInnerDefinition returns the inner definition inside the CREATE FUNCTION statement.
|
||||
func (function Function) GetInnerDefinition() string {
|
||||
// TODO: right now we're hardcode searching for $$, which will fail for some definition strings
|
||||
start := strings.Index(function.Definition, "$$")
|
||||
end := strings.LastIndex(function.Definition, "$$")
|
||||
if start == -1 || end == -1 {
|
||||
// Return the whole definition for now
|
||||
return function.Definition
|
||||
}
|
||||
return strings.TrimSpace(function.Definition[start+2 : end])
|
||||
}
|
||||
|
||||
// ReplaceDefinition returns a new definition with the inner portion replaced with the given string.
|
||||
func (function Function) ReplaceDefinition(newInner string) string {
|
||||
return strings.Replace(function.Definition, function.GetInnerDefinition(), newInner, 1)
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (function Function) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Functions
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (function Function) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := function.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (function Function) Name() doltdb.TableName {
|
||||
return FunctionIDToTableName(function.ID)
|
||||
}
|
||||
|
||||
// FunctionIDToTableName returns the ID in a format that's better for user consumption.
|
||||
func FunctionIDToTableName(funcID id.Function) doltdb.TableName {
|
||||
paramTypes := funcID.Parameters()
|
||||
strTypes := make([]string, len(paramTypes))
|
||||
for i, paramType := range paramTypes {
|
||||
if paramType.SchemaName() == "pg_catalog" || paramType.SchemaName() == funcID.SchemaName() {
|
||||
strTypes[i] = paramType.TypeName()
|
||||
} else {
|
||||
strTypes[i] = fmt.Sprintf("%s.%s", paramType.SchemaName(), paramType.TypeName())
|
||||
}
|
||||
}
|
||||
return doltdb.TableName{
|
||||
Name: fmt.Sprintf("%s(%s)", funcID.FunctionName(), strings.Join(strTypes, ",")),
|
||||
Schema: funcID.SchemaName(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// 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 functions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgmerge "github.com/dolthub/doltgresql/core/merge"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
||||
)
|
||||
|
||||
// storage is used to read from and write to the root.
|
||||
var storage = objinterface.RootObjectSerializer{
|
||||
Bytes: (*serial.RootValue).FunctionsBytes,
|
||||
RootValueAdd: serial.RootValueAddFunctions,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourFunc := mro.OurRootObj.(Function)
|
||||
theirFunc := mro.TheirRootObj.(Function)
|
||||
// Ensure that they have the same identifier
|
||||
if ourFunc.ID != theirFunc.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different functions: `%s` and `%s`",
|
||||
ourFunc.Name().String(), theirFunc.Name().String())
|
||||
}
|
||||
ourHash, err := ourFunc.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirFunc.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ourHash.Equal(theirHash) {
|
||||
return mro.OurRootObj, &merge.MergeStats{
|
||||
Operation: merge.TableUnmodified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
return pgmerge.CreateConflict(ctx, mro.RightSrc, ourFunc, theirFunc, mro.AncestorRootObj)
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadFunctions(ctx, root)
|
||||
}
|
||||
|
||||
// LoadCollectionHash implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollectionHash(ctx context.Context, root objinterface.RootValue) (hash.Hash, error) {
|
||||
m, ok, err := storage.GetProllyMap(ctx, root)
|
||||
if err != nil || !ok {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return m.HashOf(), nil
|
||||
}
|
||||
|
||||
// LoadFunctions loads the functions collection from the given root.
|
||||
func LoadFunctions(ctx context.Context, root objinterface.RootValue) (*Collection, error) {
|
||||
m, ok, err := storage.GetProllyMap(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
m, err = prolly.NewEmptyAddressMap(root.NodeStore())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return NewCollection(ctx, m, root.NodeStore())
|
||||
}
|
||||
|
||||
// ResolveNameFromObjects implements the interface objinterface.Collection.
|
||||
func (*Collection) ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
tempCollection := Collection{
|
||||
accessCache: make(map[id.Function]Function),
|
||||
idCache: make([]id.Function, 0, len(rootObjects)),
|
||||
}
|
||||
for _, rootObject := range rootObjects {
|
||||
if obj, ok := rootObject.(Function); ok {
|
||||
tempCollection.accessCache[obj.ID] = obj
|
||||
tempCollection.idCache = append(tempCollection.idCache, obj.ID)
|
||||
}
|
||||
}
|
||||
return tempCollection.ResolveName(ctx, name)
|
||||
}
|
||||
|
||||
// Serializer implements the interface objinterface.Collection.
|
||||
func (*Collection) Serializer() objinterface.RootObjectSerializer {
|
||||
return storage
|
||||
}
|
||||
|
||||
// UpdateRoot implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
||||
m, err := pgf.Map(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.WriteProllyMap(ctx, root, m)
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
// 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 functions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgmerge "github.com/dolthub/doltgresql/core/merge"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
const (
|
||||
FIELD_NAME_PARAMETER_NAMES = "parameter_names"
|
||||
FIELD_NAME_RETURN_TYPE = "return_type"
|
||||
FIELD_NAME_NON_DETERMINISTIC = "non_deterministic"
|
||||
FIELD_NAME_STRICT = "strict"
|
||||
FIELD_NAME_DEFINITION = "definition"
|
||||
FIELD_NAME_EXTENSION_NAME = "extension_name"
|
||||
FIELD_NAME_EXTENSION_SYMBOL = "extension_symbol"
|
||||
FIELD_NAME_SQL_DEFINITION = "sql_definition"
|
||||
FIELD_NAME_SET_OF = "set_of"
|
||||
)
|
||||
|
||||
// DeserializeRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeFunction(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) DiffRootObjects(ctx context.Context, fromHash string, o objinterface.RootObject, t objinterface.RootObject, a objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
// We ignore many fields when diffing, as differences in these fields would result in a different function due to overloading
|
||||
// For example, "func_name(text)" and "func_name(varchar)" cannot produce a conflict as they're different functions
|
||||
ours := o.(Function)
|
||||
theirs := t.(Function)
|
||||
ancestor, hasAncestor := a.(Function)
|
||||
var diffs []objinterface.RootObjectDiff
|
||||
{
|
||||
ourParamNames := strings.Join(ours.ParameterNames, ",")
|
||||
theirParamNames := strings.Join(theirs.ParameterNames, ",")
|
||||
ancParamNames := strings.Join(ancestor.ParameterNames, ",")
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_PARAMETER_NAMES,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ourParamNames, theirParamNames, ancParamNames, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ParameterNames = strings.Split(diff.OurValue.(string), ",")
|
||||
}
|
||||
}
|
||||
if ours.ReturnType != theirs.ReturnType {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_RETURN_TYPE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.ReturnType.TypeName(), theirs.ReturnType.TypeName(), ancestor.ReturnType.TypeName(), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ReturnType = id.NewType(ours.ReturnType.SchemaName(), diff.OurValue.(string))
|
||||
}
|
||||
}
|
||||
if ours.IsNonDeterministic != theirs.IsNonDeterministic {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_NON_DETERMINISTIC,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.IsNonDeterministic, theirs.IsNonDeterministic, ancestor.IsNonDeterministic, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.IsNonDeterministic = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.Strict != theirs.Strict {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_STRICT,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Strict, theirs.Strict, ancestor.Strict, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Strict = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.Definition != theirs.Definition {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_DEFINITION,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.GetInnerDefinition(), theirs.GetInnerDefinition(), ancestor.GetInnerDefinition(), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Definition = ours.ReplaceDefinition(diff.OurValue.(string))
|
||||
}
|
||||
}
|
||||
if ours.ExtensionName != theirs.ExtensionName {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_EXTENSION_NAME,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.ExtensionName, theirs.ExtensionName, ancestor.ExtensionName, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ExtensionName = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
if ours.ExtensionSymbol != theirs.ExtensionSymbol {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_EXTENSION_SYMBOL,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.ExtensionSymbol, theirs.ExtensionSymbol, ancestor.ExtensionSymbol, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ExtensionSymbol = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
if ours.SQLDefinition != theirs.SQLDefinition {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_SQL_DEFINITION,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.SQLDefinition, theirs.SQLDefinition, ancestor.SQLDefinition, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.SQLDefinition = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
if ours.SetOf != theirs.SetOf {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_SET_OF,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.SetOf, theirs.SetOf, ancestor.SetOf, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.SetOf = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
return diffs, ours, nil
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return errors.Errorf(`function %s does not exist`, identifier.String())
|
||||
}
|
||||
return pgf.DropFunction(ctx, id.Function(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
switch fieldName {
|
||||
case FIELD_NAME_PARAMETER_NAMES:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_RETURN_TYPE:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_NON_DETERMINISTIC:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_STRICT:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_DEFINITION:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_EXTENSION_NAME:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_EXTENSION_SYMBOL:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_SQL_DEFINITION:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_SET_OF:
|
||||
return pgtypes.Bool
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Functions
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return nil, false, nil
|
||||
}
|
||||
f, err := pgf.GetFunction(ctx, id.Function(identifier))
|
||||
return f, err == nil && f.ID.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return false, nil
|
||||
}
|
||||
return pgf.HasFunction(ctx, id.Function(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
return FunctionIDToTableName(id.Function(identifier))
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pgf.IterateFunctions(ctx, func(f Function) (stop bool, err error) {
|
||||
return callback(f)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pgf.iterateIDs(ctx, func(funcID id.Function) (stop bool, err error) {
|
||||
return callback(funcID.AsId())
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
f, ok := rootObj.(Function)
|
||||
if !ok {
|
||||
return errors.Newf("invalid function root object: %T", rootObj)
|
||||
}
|
||||
return pgf.AddFunction(ctx, f)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) RenameRootObject(ctx context.Context, oldName id.Id, newName id.Id) error {
|
||||
if !oldName.IsValid() || !newName.IsValid() || oldName.Section() != newName.Section() || oldName.Section() != id.Section_Function {
|
||||
return errors.New("cannot rename function due to invalid name")
|
||||
}
|
||||
oldFuncName := id.Function(oldName)
|
||||
newFuncName := id.Function(newName)
|
||||
if oldFuncName.ParameterCount() != newFuncName.ParameterCount() {
|
||||
return errors.Newf(`old function id had "%d" parameters, new function id has "%d" parameters`,
|
||||
oldFuncName.ParameterCount(), newFuncName.ParameterCount())
|
||||
}
|
||||
f, err := pgf.GetFunction(ctx, oldFuncName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pgf.DropFunction(ctx, oldFuncName); err != nil {
|
||||
return err
|
||||
}
|
||||
f.ID = newFuncName
|
||||
return pgf.AddFunction(ctx, f)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pgf.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return FunctionIDToTableName(rawID), rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return pgf.tableNameToID(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) UpdateField(ctx context.Context, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
function := rootObject.(Function)
|
||||
switch fieldName {
|
||||
case FIELD_NAME_PARAMETER_NAMES:
|
||||
function.ParameterNames = strings.Split(newValue.(string), ",")
|
||||
case FIELD_NAME_RETURN_TYPE:
|
||||
function.ReturnType = id.NewType(function.ReturnType.SchemaName(), newValue.(string))
|
||||
case FIELD_NAME_NON_DETERMINISTIC:
|
||||
function.IsNonDeterministic = newValue.(bool)
|
||||
case FIELD_NAME_STRICT:
|
||||
function.Strict = newValue.(bool)
|
||||
case FIELD_NAME_DEFINITION:
|
||||
function.Definition = function.ReplaceDefinition(newValue.(string))
|
||||
parsedBody, err := plpgsql.Parse(function.Definition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
function.Operations = parsedBody
|
||||
case FIELD_NAME_EXTENSION_NAME:
|
||||
function.ExtensionName = newValue.(string)
|
||||
case FIELD_NAME_EXTENSION_SYMBOL:
|
||||
function.ExtensionSymbol = newValue.(string)
|
||||
case FIELD_NAME_SQL_DEFINITION:
|
||||
function.SQLDefinition = newValue.(string)
|
||||
case FIELD_NAME_SET_OF:
|
||||
function.SetOf = newValue.(bool)
|
||||
default:
|
||||
return nil, errors.Newf("unknown field name: `%s`", fieldName)
|
||||
}
|
||||
return function, nil
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// 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 functions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Function as a byte slice. If the Function is invalid, then this returns a nil slice.
|
||||
func (function Function) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if !function.ID.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Write all of the functions to the writer
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(3) // Version
|
||||
// Write the function data
|
||||
writer.Id(function.ID.AsId())
|
||||
writer.Id(function.ReturnType.AsId())
|
||||
writer.StringSlice(function.ParameterNames)
|
||||
writer.IdTypeSlice(function.ParameterTypes)
|
||||
writer.Bool(function.Variadic)
|
||||
writer.Bool(function.IsNonDeterministic)
|
||||
writer.Bool(function.Strict)
|
||||
writer.String(function.Definition)
|
||||
// Write the operations
|
||||
writer.VariableUint(uint64(len(function.Operations)))
|
||||
for _, op := range function.Operations {
|
||||
writer.Uint16(uint16(op.OpCode))
|
||||
writer.String(op.PrimaryData)
|
||||
writer.StringSlice(op.SecondaryData)
|
||||
writer.String(op.Target)
|
||||
writer.Int32(int32(op.Index))
|
||||
writer.StringMap(op.Options)
|
||||
}
|
||||
// Write version 1 data
|
||||
writer.String(function.ExtensionName)
|
||||
writer.String(function.ExtensionSymbol)
|
||||
// Write version 2 data
|
||||
writer.String(function.SQLDefinition)
|
||||
writer.Bool(function.SetOf)
|
||||
// Write version 3 data
|
||||
writer.StringSlice(function.ParameterDefaults)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeFunction returns the Function that was serialized in the byte slice. Returns an empty Function (invalid
|
||||
// ID) if data is nil or empty.
|
||||
func DeserializeFunction(ctx context.Context, data []byte) (Function, error) {
|
||||
if len(data) == 0 {
|
||||
return Function{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version > 3 {
|
||||
return Function{}, errors.Errorf("version %d of functions is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
f := Function{}
|
||||
f.ID = id.Function(reader.Id())
|
||||
f.ReturnType = id.Type(reader.Id())
|
||||
f.ParameterNames = reader.StringSlice()
|
||||
f.ParameterTypes = reader.IdTypeSlice()
|
||||
f.Variadic = reader.Bool()
|
||||
f.IsNonDeterministic = reader.Bool()
|
||||
f.Strict = reader.Bool()
|
||||
f.Definition = reader.String()
|
||||
// Read the operations
|
||||
opCount := reader.VariableUint()
|
||||
f.Operations = make([]plpgsql.InterpreterOperation, opCount)
|
||||
for opIdx := uint64(0); opIdx < opCount; opIdx++ {
|
||||
op := plpgsql.InterpreterOperation{}
|
||||
op.OpCode = plpgsql.OpCode(reader.Uint16())
|
||||
op.PrimaryData = reader.String()
|
||||
op.SecondaryData = reader.StringSlice()
|
||||
op.Target = reader.String()
|
||||
op.Index = int(reader.Int32())
|
||||
op.Options = reader.StringMap()
|
||||
f.Operations[opIdx] = op
|
||||
}
|
||||
if version >= 1 {
|
||||
f.ExtensionName = reader.String()
|
||||
f.ExtensionSymbol = reader.String()
|
||||
}
|
||||
if version >= 2 {
|
||||
f.SQLDefinition = reader.String()
|
||||
f.SetOf = reader.Bool()
|
||||
}
|
||||
if version >= 3 {
|
||||
f.ParameterDefaults = reader.StringSlice()
|
||||
}
|
||||
if !reader.IsEmpty() {
|
||||
return Function{}, errors.Errorf("extra data found while deserializing a function")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return f, nil
|
||||
}
|
||||
Reference in New Issue
Block a user