Added CREATE OPERATOR and CREATE AGGREGATE
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
// 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 aggregates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/functions"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
)
|
||||
|
||||
// Collection contains a collection of aggregate functions.
|
||||
type Collection struct {
|
||||
objinterface.RootObjectMap
|
||||
}
|
||||
|
||||
// Aggregate represents a created aggregate function.
|
||||
type Aggregate struct {
|
||||
ID id.Function
|
||||
ReturnType id.Type
|
||||
SFunc id.Function // State transition function
|
||||
SType id.Type // Internal state type
|
||||
FinalFunc id.Function // Final calculation function
|
||||
CombineFunc id.Function
|
||||
InitCond string
|
||||
HasInitCond bool
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Aggregate{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(ctx context.Context, rom objinterface.RootObjectMap) *Collection {
|
||||
return &Collection{RootObjectMap: rom}
|
||||
}
|
||||
|
||||
// GetAggregate returns the aggregate with the given ID. Returns an Aggregate with an invalid ID if it cannot be found
|
||||
// (Aggregate.ID.IsValid() == false).
|
||||
func (pga *Collection) GetAggregate(ctx context.Context, aggregateID id.Function) (Aggregate, error) {
|
||||
h, err := pga.Contents().Get(ctx, string(aggregateID))
|
||||
if err != nil || h.IsEmpty() {
|
||||
return Aggregate{}, err
|
||||
}
|
||||
data, err := pga.NodeStore().ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return Aggregate{}, err
|
||||
}
|
||||
return DeserializeAggregate(ctx, data)
|
||||
}
|
||||
|
||||
// GetAggregateOverloads returns every aggregate that shares the given aggregate's schema and name.
|
||||
func (pga *Collection) GetAggregateOverloads(ctx context.Context, aggregateID id.Function) ([]Aggregate, error) {
|
||||
var overloads []Aggregate
|
||||
err := pga.IterateAggregates(ctx, func(a Aggregate) (stop bool, err error) {
|
||||
if a.ID.SchemaName() == aggregateID.SchemaName() && a.ID.FunctionName() == aggregateID.FunctionName() {
|
||||
overloads = append(overloads, a)
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
return overloads, err
|
||||
}
|
||||
|
||||
// HasAggregate returns whether the given aggregate exists.
|
||||
func (pga *Collection) HasAggregate(ctx context.Context, aggregateID id.Function) bool {
|
||||
ok, err := pga.Contents().Has(ctx, string(aggregateID))
|
||||
return err == nil && ok
|
||||
}
|
||||
|
||||
// HasAggregateName returns whether an aggregate with the given name exists in any schema.
|
||||
func (pga *Collection) HasAggregateName(ctx context.Context, name string) (bool, error) {
|
||||
found := false
|
||||
err := pga.Contents().IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
if id.Function(k).FunctionName() == name {
|
||||
found = true
|
||||
return io.EOF
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil && err != io.EOF {
|
||||
return false, err
|
||||
}
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// AddAggregate adds a new aggregate.
|
||||
func (pga *Collection) AddAggregate(ctx context.Context, a Aggregate) error {
|
||||
if pga.HasAggregate(ctx, a.ID) {
|
||||
return errors.Errorf(`aggregate "%s" already exists with same argument types`, a.ID.FunctionName())
|
||||
}
|
||||
data, err := a.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pga.NodeStore().WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapEditor := pga.Contents().Editor()
|
||||
if err = mapEditor.Add(ctx, string(a.ID), h); err != nil {
|
||||
return err
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pga.SetContents(newMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropAggregate drops an existing aggregate.
|
||||
func (pga *Collection) DropAggregate(ctx context.Context, aggregateIDs ...id.Function) error {
|
||||
if len(aggregateIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, aggregateID := range aggregateIDs {
|
||||
if ok, err := pga.Contents().Has(ctx, string(aggregateID)); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return errors.Errorf(`aggregate %s does not exist`, aggregateID.DisplayString())
|
||||
}
|
||||
}
|
||||
|
||||
mapEditor := pga.Contents().Editor()
|
||||
for _, aggregateID := range aggregateIDs {
|
||||
if err := mapEditor.Delete(ctx, string(aggregateID)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pga.SetContents(newMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given aggregate. Returns an error if the name is ambiguous.
|
||||
func (pga *Collection) resolveName(ctx context.Context, schemaName string, formattedName string) (id.Function, error) {
|
||||
partialID := functions.TableNameToFunctionID(schemaName, formattedName)
|
||||
if !partialID.IsValid() {
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
|
||||
// Check for an exact match
|
||||
if pga.HasAggregate(ctx, partialID) {
|
||||
return partialID, nil
|
||||
}
|
||||
|
||||
// Otherwise we'll iterate over all the names
|
||||
var resolvedID id.Function
|
||||
partialParams := partialID.Parameters()
|
||||
err := pga.IterateAggregates(ctx, func(a Aggregate) (stop bool, err error) {
|
||||
if !strings.EqualFold(a.ID.FunctionName(), partialID.FunctionName()) {
|
||||
return false, nil
|
||||
}
|
||||
if len(partialID.SchemaName()) > 0 && !strings.EqualFold(a.ID.SchemaName(), partialID.SchemaName()) {
|
||||
return false, nil
|
||||
}
|
||||
if len(partialParams) > 0 {
|
||||
if a.ID.ParameterCount() != len(partialParams) {
|
||||
return false, nil
|
||||
}
|
||||
for i, param := range a.ID.Parameters() {
|
||||
if len(partialParams[i].TypeName()) > 0 && !strings.EqualFold(partialParams[i].TypeName(), param.TypeName()) {
|
||||
return false, nil
|
||||
}
|
||||
if len(partialParams[i].SchemaName()) > 0 && !strings.EqualFold(partialParams[i].SchemaName(), param.SchemaName()) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// Everything must have matched to have made it here
|
||||
if resolvedID.IsValid() {
|
||||
aggregateTableName := functions.FunctionIDToTableName(a.ID)
|
||||
resolvedTableName := functions.FunctionIDToTableName(resolvedID)
|
||||
return true, fmt.Errorf("`%s` is ambiguous, matches `%s` and `%s`",
|
||||
formattedName, aggregateTableName.String(), resolvedTableName.String())
|
||||
}
|
||||
resolvedID = a.ID
|
||||
return false, nil
|
||||
})
|
||||
return resolvedID, err
|
||||
}
|
||||
|
||||
// IterateAggregates iterates over all aggregates in the collection.
|
||||
func (pga *Collection) IterateAggregates(ctx context.Context, callback func(a Aggregate) (stop bool, err error)) error {
|
||||
return pga.Contents().IterAll(ctx, func(_ string, v hash.Hash) error {
|
||||
data, err := pga.NodeStore().ReadBytes(ctx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a, err := DeserializeAggregate(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stop, err := callback(a)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (aggregate Aggregate) GetID() id.Id {
|
||||
return aggregate.ID.AsId()
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (aggregate Aggregate) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Aggregates
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (aggregate Aggregate) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := aggregate.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (aggregate Aggregate) Name() doltdb.TableName {
|
||||
return functions.FunctionIDToTableName(aggregate.ID)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// 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 aggregates
|
||||
|
||||
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/prolly/tree"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"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).AggregatesBytes,
|
||||
RootValueAdd: serial.RootValueAddAggregates,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourAggregate := mro.OurRootObj.(Aggregate)
|
||||
theirAggregate := mro.TheirRootObj.(Aggregate)
|
||||
// Ensure that they have the same identifier
|
||||
if ourAggregate.ID != theirAggregate.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different aggregates: `%s` and `%s`",
|
||||
ourAggregate.Name().String(), theirAggregate.Name().String())
|
||||
}
|
||||
ourHash, err := ourAggregate.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirAggregate.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,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
// TODO: figure out a decent merge strategy
|
||||
return nil, nil, errors.Errorf("unable to merge `%s`", theirAggregate.Name().String())
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadAggregates(ctx, root)
|
||||
}
|
||||
|
||||
// LoadAggregates loads the aggregates collection from the given root.
|
||||
func LoadAggregates(ctx context.Context, root objinterface.RootValue) (*Collection, error) {
|
||||
rom, err := objinterface.NewRootObjectMap(ctx, storage, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCollection(ctx, rom), nil
|
||||
}
|
||||
|
||||
// ResolveNameFromObjects implements the interface objinterface.Collection.
|
||||
func (*Collection) ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
rom, err := objinterface.NewDetachedRootObjectMap(storage, tree.NewTestNodeStore())
|
||||
if err != nil {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
tempCollection := NewCollection(ctx, rom)
|
||||
for _, rootObject := range rootObjects {
|
||||
if a, ok := rootObject.(Aggregate); ok {
|
||||
if err = tempCollection.AddAggregate(ctx, a); err != nil {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return tempCollection.ResolveName(ctx, name)
|
||||
}
|
||||
|
||||
// Serializer implements the interface objinterface.Collection.
|
||||
func (*Collection) Serializer() objinterface.RootObjectSerializer {
|
||||
return storage
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// 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 aggregates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/functions"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// DeserializeRootObject implements the interface objinterface.Collection.
|
||||
func (pga *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeAggregate(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pga *Collection) DiffRootObjects(ctx context.Context, fromHash string, ours objinterface.RootObject, theirs objinterface.RootObject, ancestor objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
return nil, nil, errors.New("aggregate conflict detection has not yet been implemented")
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pga *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return errors.Errorf(`aggregate %s does not exist`, identifier.String())
|
||||
}
|
||||
return pga.DropAggregate(ctx, id.Function(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pga *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pga *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Aggregates
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pga *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return nil, false, nil
|
||||
}
|
||||
a, err := pga.GetAggregate(ctx, id.Function(identifier))
|
||||
return a, err == nil && a.ID.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pga *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return false, nil
|
||||
}
|
||||
return pga.HasAggregate(ctx, id.Function(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pga *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
return functions.FunctionIDToTableName(id.Function(identifier))
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pga *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pga.IterateAggregates(ctx, func(a Aggregate) (stop bool, err error) {
|
||||
return callback(a)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pga *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pga.Contents().IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
stop, err := callback(id.Id(k))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pga *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
a, ok := rootObj.(Aggregate)
|
||||
if !ok {
|
||||
return errors.Newf("invalid aggregate root object: %T", rootObj)
|
||||
}
|
||||
return pga.AddAggregate(ctx, a)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pga *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 aggregate due to invalid id")
|
||||
}
|
||||
a, err := pga.GetAggregate(ctx, id.Function(oldName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pga.DropAggregate(ctx, id.Function(oldName)); err != nil {
|
||||
return err
|
||||
}
|
||||
a.ID = id.Function(newName)
|
||||
return pga.AddAggregate(ctx, a)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pga *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pga.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return functions.FunctionIDToTableName(rawID), rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pga *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return functions.TableNameToFunctionID(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pga *Collection) UpdateField(ctx context.Context, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
return nil, errors.New("updating through the conflicts table for this object type is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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 aggregates
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Aggregate as a byte slice. If the Aggregate is invalid, then this returns a nil slice.
|
||||
func (aggregate Aggregate) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if !aggregate.ID.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Initialize the writer and version
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(0) // Version
|
||||
// Write the aggregate data
|
||||
writer.Id(aggregate.ID.AsId())
|
||||
writer.Id(aggregate.ReturnType.AsId())
|
||||
writer.Id(aggregate.SFunc.AsId())
|
||||
writer.Id(aggregate.SType.AsId())
|
||||
writer.Id(aggregate.FinalFunc.AsId())
|
||||
writer.Id(aggregate.CombineFunc.AsId())
|
||||
writer.String(aggregate.InitCond)
|
||||
writer.Bool(aggregate.HasInitCond)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeAggregate returns the Aggregate that was serialized in the byte slice. Returns an empty Aggregate
|
||||
// (invalid ID) if data is nil or empty.
|
||||
func DeserializeAggregate(ctx context.Context, data []byte) (Aggregate, error) {
|
||||
if len(data) == 0 {
|
||||
return Aggregate{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version != 0 {
|
||||
return Aggregate{}, errors.Errorf("version %d of aggregates is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
a := Aggregate{}
|
||||
a.ID = id.Function(reader.Id())
|
||||
a.ReturnType = id.Type(reader.Id())
|
||||
a.SFunc = id.Function(reader.Id())
|
||||
a.SType = id.Type(reader.Id())
|
||||
a.FinalFunc = id.Function(reader.Id())
|
||||
a.CombineFunc = id.Function(reader.Id())
|
||||
a.InitCond = reader.String()
|
||||
a.HasInitCond = reader.Bool()
|
||||
if !reader.IsEmpty() {
|
||||
return Aggregate{}, errors.Errorf("extra data found while deserializing an aggregate")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return a, nil
|
||||
}
|
||||
@@ -27,9 +27,11 @@ import (
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/aggregates"
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
"github.com/dolthub/doltgresql/core/extensions"
|
||||
"github.com/dolthub/doltgresql/core/functions"
|
||||
"github.com/dolthub/doltgresql/core/operators"
|
||||
"github.com/dolthub/doltgresql/core/procedures"
|
||||
"github.com/dolthub/doltgresql/core/rootobject"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
@@ -354,6 +356,26 @@ func GetCastsCollectionFromContext(ctx *sql.Context, database string) (*casts.Co
|
||||
return coll.(*casts.Collection), nil
|
||||
}
|
||||
|
||||
// GetAggregatesCollectionFromContext returns the given aggregates collection from the context.
|
||||
// Will always return a collection if no error is returned.
|
||||
func GetAggregatesCollectionFromContext(ctx *sql.Context, database string) (*aggregates.Collection, error) {
|
||||
coll, err := collectionFromContext(ctx, database, objinterface.RootObjectID_Aggregates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return coll.(*aggregates.Collection), nil
|
||||
}
|
||||
|
||||
// GetOperatorsCollectionFromContext returns the given operators collection from the context.
|
||||
// Will always return a collection if no error is returned.
|
||||
func GetOperatorsCollectionFromContext(ctx *sql.Context, database string) (*operators.Collection, error) {
|
||||
coll, err := collectionFromContext(ctx, database, objinterface.RootObjectID_Operators)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return coll.(*operators.Collection), nil
|
||||
}
|
||||
|
||||
// GetExtensionsCollectionFromContext returns the extensions collection from the given context. Will always return a
|
||||
// collection if no error is returned.
|
||||
func GetExtensionsCollectionFromContext(ctx *sql.Context, database string) (*extensions.Collection, error) {
|
||||
|
||||
@@ -288,38 +288,6 @@ func (pgf *Collection) reloadCaches(ctx context.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
// 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()
|
||||
@@ -372,6 +340,38 @@ func (function Function) Name() doltdb.TableName {
|
||||
return FunctionIDToTableName(function.ID)
|
||||
}
|
||||
|
||||
// TableNameToFunctionID returns the ID that was encoded via the Name() call, as the returned TableName contains
|
||||
// additional information (which this is able to process).
|
||||
func TableNameToFunctionID(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...)
|
||||
}
|
||||
|
||||
// FunctionIDToTableName returns the ID in a format that's better for user consumption.
|
||||
func FunctionIDToTableName(funcID id.Function) doltdb.TableName {
|
||||
paramTypes := funcID.Parameters()
|
||||
|
||||
@@ -291,7 +291,7 @@ func (pgf *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return pgf.tableNameToID(name.Schema, name.Name).AsId()
|
||||
return TableNameToFunctionID(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
|
||||
@@ -73,6 +73,8 @@ const (
|
||||
NullIndex Index = ""
|
||||
// NullNamespace is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullNamespace Namespace = ""
|
||||
// NullOperator is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullOperator Operator = ""
|
||||
// NullProcedure is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullProcedure Procedure = ""
|
||||
// NullSequence is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
|
||||
@@ -62,6 +62,9 @@ type Namespace Id
|
||||
// Oid is an Id wrapper for OIDs. This wrapper must not be returned to the client.
|
||||
type Oid Id
|
||||
|
||||
// Operator is an Id wrapper for operators. This wrapper must not be returned to the client.
|
||||
type Operator Id
|
||||
|
||||
// Procedure is an Id wrapper for procedures. This wrapper must not be returned to the client.
|
||||
type Procedure Id
|
||||
|
||||
@@ -198,6 +201,14 @@ func NewOID(val uint32) Oid {
|
||||
return Oid(NewId(Section_OID, strconv.FormatUint(uint64(val), 10)))
|
||||
}
|
||||
|
||||
// NewOperator returns a new Operator. This wrapper must not be returned to the client.
|
||||
func NewOperator(schemaName string, symbol string, leftType Type, rightType Type) Operator {
|
||||
if len(symbol) == 0 {
|
||||
return NullOperator
|
||||
}
|
||||
return Operator(NewId(Section_Operator, schemaName, symbol, string(leftType), string(rightType)))
|
||||
}
|
||||
|
||||
// NewProcedure returns a new Procedure. This wrapper must not be returned to the client.
|
||||
func NewProcedure(schemaName string, procName string, params ...Type) Procedure {
|
||||
if len(schemaName) == 0 && len(procName) == 0 && len(params) == 0 {
|
||||
@@ -419,6 +430,26 @@ func (id Oid) OID() uint32 {
|
||||
return uint32(val)
|
||||
}
|
||||
|
||||
// LeftType returns the type of the operator's left operand.
|
||||
func (id Operator) LeftType() Type {
|
||||
return Type(Id(id).Segment(2))
|
||||
}
|
||||
|
||||
// RightType returns the type of the operator's right operand.
|
||||
func (id Operator) RightType() Type {
|
||||
return Type(Id(id).Segment(3))
|
||||
}
|
||||
|
||||
// SchemaName returns the name of the schema that the operator belongs to.
|
||||
func (id Operator) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// Symbol returns the operator's symbol.
|
||||
func (id Operator) Symbol() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// ProcedureName returns the procedure's name.
|
||||
func (id Procedure) ProcedureName() string {
|
||||
return Id(id).Segment(1)
|
||||
@@ -546,6 +577,9 @@ func (id Namespace) IsValid() bool { return Id(id).IsValid() }
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Oid) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Operator) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Procedure) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
@@ -609,6 +643,9 @@ func (id Namespace) AsId() Id { return Id(id) }
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Oid) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Operator) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Procedure) AsId() Id { return Id(id) }
|
||||
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
// 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 operators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// Collection contains a collection of operators.
|
||||
type Collection struct {
|
||||
objinterface.RootObjectMap
|
||||
}
|
||||
|
||||
// Operator represents a user-defined operator.
|
||||
type Operator struct {
|
||||
ID id.Operator
|
||||
Function id.Function
|
||||
ReturnType id.Type
|
||||
Commutator string
|
||||
Negator string
|
||||
Hashes bool
|
||||
Merges bool
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Operator{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(rom objinterface.RootObjectMap) *Collection {
|
||||
return &Collection{RootObjectMap: rom}
|
||||
}
|
||||
|
||||
// GetOperator returns the operator with the given ID. Returns an Operator with an invalid ID if it cannot be found
|
||||
// (Operator.ID.IsValid() == false).
|
||||
func (pgo *Collection) GetOperator(ctx context.Context, operatorID id.Operator) (Operator, error) {
|
||||
h, err := pgo.Contents().Get(ctx, string(operatorID))
|
||||
if err != nil || h.IsEmpty() {
|
||||
return Operator{}, err
|
||||
}
|
||||
data, err := pgo.NodeStore().ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return Operator{}, err
|
||||
}
|
||||
return DeserializeOperator(ctx, data)
|
||||
}
|
||||
|
||||
// ResolveOperator returns the operator matching the given symbol and operand types, searching the given schemas in
|
||||
// order and allowing unknown operand types to match any type. Returns false if no operator matches.
|
||||
func (pgo *Collection) ResolveOperator(ctx context.Context, schemaNames []string, symbol string, leftType id.Type, rightType id.Type) (Operator, bool, error) {
|
||||
for _, schemaName := range schemaNames {
|
||||
o, err := pgo.GetOperator(ctx, id.NewOperator(schemaName, symbol, leftType, rightType))
|
||||
if err != nil {
|
||||
return Operator{}, false, err
|
||||
}
|
||||
if o.ID.IsValid() {
|
||||
return o, true, nil
|
||||
}
|
||||
}
|
||||
leftUnknown := leftType == pgtypes.Unknown.ID
|
||||
rightUnknown := rightType == pgtypes.Unknown.ID
|
||||
if !leftUnknown && !rightUnknown {
|
||||
return Operator{}, false, nil
|
||||
}
|
||||
for _, schemaName := range schemaNames {
|
||||
var resolved Operator
|
||||
err := pgo.IterateOperators(ctx, func(o Operator) (stop bool, err error) {
|
||||
if o.ID.SchemaName() != schemaName || o.ID.Symbol() != symbol {
|
||||
return false, nil
|
||||
}
|
||||
if (!leftUnknown && o.ID.LeftType() != leftType) || (!rightUnknown && o.ID.RightType() != rightType) {
|
||||
return false, nil
|
||||
}
|
||||
if resolved.ID.IsValid() {
|
||||
return true, errors.Errorf("operator is not unique: %s", symbol)
|
||||
}
|
||||
resolved = o
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return Operator{}, false, err
|
||||
}
|
||||
if resolved.ID.IsValid() {
|
||||
return resolved, true, nil
|
||||
}
|
||||
}
|
||||
return Operator{}, false, nil
|
||||
}
|
||||
|
||||
// HasOperator returns whether the given operator exists.
|
||||
func (pgo *Collection) HasOperator(ctx context.Context, operatorID id.Operator) bool {
|
||||
ok, err := pgo.Contents().Has(ctx, string(operatorID))
|
||||
return err == nil && ok
|
||||
}
|
||||
|
||||
// AddOperator adds a new operator.
|
||||
func (pgo *Collection) AddOperator(ctx context.Context, o Operator) error {
|
||||
if pgo.HasOperator(ctx, o.ID) {
|
||||
return errors.Errorf(`operator %s already exists`, o.ID.Symbol())
|
||||
}
|
||||
data, err := o.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pgo.NodeStore().WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapEditor := pgo.Contents().Editor()
|
||||
if err = mapEditor.Add(ctx, string(o.ID), h); err != nil {
|
||||
return err
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgo.SetContents(newMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropOperator drops an existing operator.
|
||||
func (pgo *Collection) DropOperator(ctx context.Context, operatorIDs ...id.Operator) error {
|
||||
if len(operatorIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, operatorID := range operatorIDs {
|
||||
if ok, err := pgo.Contents().Has(ctx, string(operatorID)); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return errors.Errorf(`operator does not exist: %s %s %s`,
|
||||
operatorID.LeftType().TypeName(), operatorID.Symbol(), operatorID.RightType().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
mapEditor := pgo.Contents().Editor()
|
||||
for _, operatorID := range operatorIDs {
|
||||
if err := mapEditor.Delete(ctx, string(operatorID)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgo.SetContents(newMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given operator. Returns an error if the name is ambiguous.
|
||||
func (pgo *Collection) resolveName(ctx context.Context, schemaName string, formattedName string) (id.Operator, error) {
|
||||
if len(formattedName) == 0 {
|
||||
return id.NullOperator, nil
|
||||
}
|
||||
|
||||
// Check for an exact match
|
||||
fullID := pgo.tableNameToID(schemaName, formattedName)
|
||||
if pgo.HasOperator(ctx, fullID) {
|
||||
return fullID, nil
|
||||
}
|
||||
|
||||
// Otherwise we'll iterate over all the names
|
||||
var resolvedID id.Operator
|
||||
err := pgo.IterateOperators(ctx, func(o Operator) (stop bool, err error) {
|
||||
if !strings.EqualFold(string(o.ID), string(fullID)) {
|
||||
return false, nil
|
||||
}
|
||||
// The above matches, so this counts as a match
|
||||
if resolvedID.IsValid() {
|
||||
operatorTableName := OperatorIDToTableName(o.ID)
|
||||
resolvedTableName := OperatorIDToTableName(resolvedID)
|
||||
return true, fmt.Errorf("`%s` is ambiguous, matches `%s` and `%s`",
|
||||
formattedName, operatorTableName.String(), resolvedTableName.String())
|
||||
}
|
||||
resolvedID = o.ID
|
||||
return false, nil
|
||||
})
|
||||
return resolvedID, err
|
||||
}
|
||||
|
||||
// IterateOperators iterates over all operators in the collection.
|
||||
func (pgo *Collection) IterateOperators(ctx context.Context, callback func(o Operator) (stop bool, err error)) error {
|
||||
return pgo.Contents().IterAll(ctx, func(_ string, v hash.Hash) error {
|
||||
data, err := pgo.NodeStore().ReadBytes(ctx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o, err := DeserializeOperator(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stop, err := callback(o)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
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 (pgo *Collection) tableNameToID(schema string, formattedName string) id.Operator {
|
||||
sections := strings.Split(strings.TrimSuffix(strings.TrimPrefix(formattedName, "("), ")"), ")|(")
|
||||
if len(sections) != 5 {
|
||||
return id.NullOperator
|
||||
}
|
||||
return id.NewOperator(schema, sections[0],
|
||||
id.NewType(sections[1], sections[2]), id.NewType(sections[3], sections[4]))
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (operator Operator) GetID() id.Id {
|
||||
return operator.ID.AsId()
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (operator Operator) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Operators
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (operator Operator) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := operator.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (operator Operator) Name() doltdb.TableName {
|
||||
return OperatorIDToTableName(operator.ID)
|
||||
}
|
||||
|
||||
// OperatorIDToTableName returns the ID in a format that's better for user consumption.
|
||||
func OperatorIDToTableName(operatorID id.Operator) doltdb.TableName {
|
||||
name := fmt.Sprintf(`(%s)|(%s)|(%s)|(%s)|(%s)`,
|
||||
operatorID.Symbol(),
|
||||
operatorID.LeftType().SchemaName(),
|
||||
operatorID.LeftType().TypeName(),
|
||||
operatorID.RightType().SchemaName(),
|
||||
operatorID.RightType().TypeName())
|
||||
return doltdb.TableName{
|
||||
Name: name,
|
||||
Schema: operatorID.SchemaName(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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 operators
|
||||
|
||||
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/prolly/tree"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"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).OperatorsBytes,
|
||||
RootValueAdd: serial.RootValueAddOperators,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourOperator := mro.OurRootObj.(Operator)
|
||||
theirOperator := mro.TheirRootObj.(Operator)
|
||||
// Ensure that they have the same identifier
|
||||
if ourOperator.ID != theirOperator.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different operators: `%s` and `%s`",
|
||||
ourOperator.Name().String(), theirOperator.Name().String())
|
||||
}
|
||||
ourHash, err := ourOperator.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirOperator.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,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
// TODO: figure out a decent merge strategy
|
||||
return nil, nil, errors.Errorf("unable to merge `%s`", theirOperator.Name().String())
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadOperators(ctx, root)
|
||||
}
|
||||
|
||||
// LoadOperators loads the operators collection from the given root.
|
||||
func LoadOperators(ctx context.Context, root objinterface.RootValue) (*Collection, error) {
|
||||
rom, err := objinterface.NewRootObjectMap(ctx, storage, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCollection(rom), nil
|
||||
}
|
||||
|
||||
// ResolveNameFromObjects implements the interface objinterface.Collection.
|
||||
func (*Collection) ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
// There are root objects to search through, so we'll create a temporary store
|
||||
rom, err := objinterface.NewDetachedRootObjectMap(storage, tree.NewTestNodeStore())
|
||||
if err != nil {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
tempCollection := NewCollection(rom)
|
||||
for _, rootObject := range rootObjects {
|
||||
if o, ok := rootObject.(Operator); ok {
|
||||
if err = tempCollection.AddOperator(ctx, o); err != nil {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return tempCollection.ResolveName(ctx, name)
|
||||
}
|
||||
|
||||
// Serializer implements the interface objinterface.Collection.
|
||||
func (*Collection) Serializer() objinterface.RootObjectSerializer {
|
||||
return storage
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// 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 operators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// DeserializeRootObject implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeOperator(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) DiffRootObjects(ctx context.Context, fromHash string, ours objinterface.RootObject, theirs objinterface.RootObject, ancestor objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
return nil, nil, errors.New("operator conflict detection has not yet been implemented")
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Operator {
|
||||
return errors.Errorf(`operator %s does not exist`, identifier.String())
|
||||
}
|
||||
return pgo.DropOperator(ctx, id.Operator(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Operators
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Operator {
|
||||
return nil, false, nil
|
||||
}
|
||||
o, err := pgo.GetOperator(ctx, id.Operator(identifier))
|
||||
return o, err == nil && o.ID.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Operator {
|
||||
return false, nil
|
||||
}
|
||||
return pgo.HasOperator(ctx, id.Operator(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Operator {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
return OperatorIDToTableName(id.Operator(identifier))
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pgo.IterateOperators(ctx, func(o Operator) (stop bool, err error) {
|
||||
return callback(o)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pgo.Contents().IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
stop, err := callback(id.Id(k))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
o, ok := rootObj.(Operator)
|
||||
if !ok {
|
||||
return errors.Newf("invalid operator root object: %T", rootObj)
|
||||
}
|
||||
return pgo.AddOperator(ctx, o)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgo *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_Operator {
|
||||
return errors.New("cannot rename operator due to invalid id")
|
||||
}
|
||||
o, err := pgo.GetOperator(ctx, id.Operator(oldName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pgo.DropOperator(ctx, id.Operator(oldName)); err != nil {
|
||||
return err
|
||||
}
|
||||
o.ID = id.Operator(newName)
|
||||
return pgo.AddOperator(ctx, o)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pgo.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return OperatorIDToTableName(rawID), rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return pgo.tableNameToID(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgo *Collection) UpdateField(ctx context.Context, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
return nil, errors.New("updating through the conflicts table for this object type is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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 operators
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Operator as a byte slice. If the Operator is invalid, then this returns a nil slice.
|
||||
func (operator Operator) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if !operator.ID.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Initialize the writer and version
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(0) // Version
|
||||
// Write the operator data
|
||||
writer.Id(operator.ID.AsId())
|
||||
writer.Id(operator.Function.AsId())
|
||||
writer.Id(operator.ReturnType.AsId())
|
||||
writer.String(operator.Commutator)
|
||||
writer.String(operator.Negator)
|
||||
writer.Bool(operator.Hashes)
|
||||
writer.Bool(operator.Merges)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeOperator returns the Operator that was serialized in the byte slice. Returns an empty Operator (invalid
|
||||
// ID) if data is nil or empty.
|
||||
func DeserializeOperator(ctx context.Context, data []byte) (Operator, error) {
|
||||
if len(data) == 0 {
|
||||
return Operator{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version != 0 {
|
||||
return Operator{}, errors.Errorf("version %d of operators is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
o := Operator{}
|
||||
o.ID = id.Operator(reader.Id())
|
||||
o.Function = id.Function(reader.Id())
|
||||
o.ReturnType = id.Type(reader.Id())
|
||||
o.Commutator = reader.String()
|
||||
o.Negator = reader.String()
|
||||
o.Hashes = reader.Bool()
|
||||
o.Merges = reader.Bool()
|
||||
if !reader.IsEmpty() {
|
||||
return Operator{}, errors.Errorf("extra data found while deserializing an operator")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return o, nil
|
||||
}
|
||||
@@ -23,11 +23,13 @@ import (
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/aggregates"
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
"github.com/dolthub/doltgresql/core/conflicts"
|
||||
"github.com/dolthub/doltgresql/core/extensions"
|
||||
"github.com/dolthub/doltgresql/core/functions"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/operators"
|
||||
"github.com/dolthub/doltgresql/core/procedures"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/core/sequences"
|
||||
@@ -48,6 +50,8 @@ var (
|
||||
&conflicts.Collection{},
|
||||
&procedures.Collection{},
|
||||
&casts.Collection{},
|
||||
&operators.Collection{},
|
||||
&aggregates.Collection{},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ const (
|
||||
RootObjectID_Conflicts
|
||||
RootObjectID_Procedures
|
||||
RootObjectID_Casts
|
||||
RootObjectID_Operators
|
||||
RootObjectID_Aggregates
|
||||
RootObjectID_Count // This must always be last since it represents the count
|
||||
)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/storage"
|
||||
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
||||
)
|
||||
|
||||
@@ -98,7 +99,7 @@ func (serializer RootObjectSerializer) WriteProllyMap(ctx context.Context, root
|
||||
}
|
||||
h = ref.TargetHash()
|
||||
}
|
||||
newStorage, err := root.GetStorage(ctx).SetRootObjectHash(ctx, serializer.Bytes, h)
|
||||
newStorage, err := root.GetStorage(ctx).SetRootObjectHash(ctx, storage.RootObjectSerialization(serializer), h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
doltserial "github.com/dolthub/dolt/go/gen/fb/serial"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
@@ -30,6 +32,8 @@ import (
|
||||
"github.com/dolthub/doltgresql/core/rootobject"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/core/sequences"
|
||||
"github.com/dolthub/doltgresql/core/storage"
|
||||
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -48,8 +52,8 @@ func TestLoadAndSaveEmptyCollectionsPreserveRoot(t *testing.T) {
|
||||
root: newTestRoot,
|
||||
},
|
||||
{
|
||||
name: "root with empty root object fields",
|
||||
root: newTestRootWithFields,
|
||||
name: "legacy root with zero-hash root object fields",
|
||||
root: newLegacyTestRoot,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@@ -81,7 +85,7 @@ func TestLoadAndSaveEmptyCollectionsPreserveRoot(t *testing.T) {
|
||||
func TestEmptiedCollectionMatchesUnwrittenCollection(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
root := newTestRootWithFields(t, ctx)
|
||||
root := newTestRootWithSchema(t, ctx)
|
||||
startHash, err := root.HashOf()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -113,7 +117,7 @@ func TestEmptiedCollectionMatchesUnwrittenCollection(t *testing.T) {
|
||||
func TestCollectionStaleness(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
root := newTestRootWithFields(t, ctx)
|
||||
root := newTestRootWithSchema(t, ctx)
|
||||
|
||||
ours, err := sequences.LoadSequences(ctx, root)
|
||||
require.NoError(t, err)
|
||||
@@ -142,7 +146,7 @@ func TestCollectionStaleness(t *testing.T) {
|
||||
func TestResolutionCollectionsAreReused(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
root := newTestRootWithFields(t, ctx)
|
||||
root := newTestRootWithSchema(t, ctx)
|
||||
|
||||
first, err := root.ReadOnlyCollections(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -191,11 +195,39 @@ func newTestRoot(t testing.TB, ctx context.Context) *RootValue {
|
||||
return root.(*RootValue)
|
||||
}
|
||||
|
||||
// newTestRootWithFields returns an empty in-memory root that has every root object field written as empty.
|
||||
func newTestRootWithFields(t testing.TB, ctx context.Context) *RootValue {
|
||||
// newTestRootWithSchema returns an empty in-memory root that has a database schema and no root object fields.
|
||||
func newTestRootWithSchema(t testing.TB, ctx context.Context) *RootValue {
|
||||
t.Helper()
|
||||
root, err := newTestRoot(t, ctx).CreateDatabaseSchema(ctx, schema.DatabaseSchema{Name: "public"})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, root.(*RootValue).st.SRV.CastsBytes())
|
||||
return root.(*RootValue)
|
||||
}
|
||||
|
||||
// newLegacyTestRoot returns an empty in-memory root with every root object field written as a zero hash, matching
|
||||
// the roots that the previous version serialized.
|
||||
func newLegacyTestRoot(t testing.TB, ctx context.Context) *RootValue {
|
||||
t.Helper()
|
||||
base := newTestRoot(t, ctx)
|
||||
builder := flatbuffers.NewBuilder(80)
|
||||
tablesOffset := builder.CreateByteVector(base.st.SRV.TablesBytes())
|
||||
fkOffset := builder.CreateByteVector(base.st.SRV.ForeignKeyAddrBytes())
|
||||
var empty hash.Hash
|
||||
rootObjOffsets := make([]flatbuffers.UOffsetT, len(storage.RootObjectSerializations))
|
||||
for i := range storage.RootObjectSerializations {
|
||||
rootObjOffsets[i] = builder.CreateByteVector(empty[:])
|
||||
}
|
||||
serial.RootValueStart(builder)
|
||||
serial.RootValueAddFeatureVersion(builder, base.st.SRV.FeatureVersion())
|
||||
serial.RootValueAddCollation(builder, base.st.SRV.Collation())
|
||||
serial.RootValueAddTables(builder, tablesOffset)
|
||||
serial.RootValueAddForeignKeyAddr(builder, fkOffset)
|
||||
for i := range storage.RootObjectSerializations {
|
||||
storage.RootObjectSerializations[i].RootValueAdd(builder, rootObjOffsets[i])
|
||||
}
|
||||
bs := doltserial.FinishMessage(builder, serial.RootValueEnd(builder), []byte(doltserial.DoltgresRootValueFileID))
|
||||
root, err := newRootValue(ctx, base.vrw, base.ns, types.SerialMessage(bs))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, root.(*RootValue).st.SRV.CastsBytes(), hash.ByteLen)
|
||||
return root.(*RootValue)
|
||||
}
|
||||
|
||||
+41
-22
@@ -87,22 +87,33 @@ func (r RootStorage) SetCollation(ctx context.Context, collation schema.Collatio
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// SetRootObjectHash sets the hash of the root object collection read by the given accessor, returning a new storage
|
||||
// object. Roots written before a root object existed have no field for it, so the root value is rebuilt first.
|
||||
func (r RootStorage) SetRootObjectHash(ctx context.Context, bytes func(*serial.RootValue) []byte, h hash.Hash) (RootStorage, error) {
|
||||
// SetRootObjectHash sets the hash of the root object collection handled by the given serialization, returning a new
|
||||
// storage object.
|
||||
func (r RootStorage) SetRootObjectHash(ctx context.Context, serialization RootObjectSerialization, h hash.Hash) (RootStorage, error) {
|
||||
ret := r.Clone()
|
||||
if len(bytes(ret.SRV)) == 0 {
|
||||
dbSchemas, err := r.GetSchemas(ctx)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
fieldBytes := serialization.Bytes(ret.SRV)
|
||||
if len(fieldBytes) == hash.ByteLen {
|
||||
copy(fieldBytes, h[:])
|
||||
if !h.IsEmpty() {
|
||||
return ret, nil
|
||||
}
|
||||
msg, err := r.serializeRootValue(r.SRV.TablesBytes(), dbSchemas)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
ret = RootStorage{msg}
|
||||
} else if h.IsEmpty() {
|
||||
return ret, nil
|
||||
}
|
||||
copy(bytes(ret.SRV), h[:])
|
||||
dbSchemas, err := ret.GetSchemas(ctx)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
var newRootObjAdd func(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT)
|
||||
if !h.IsEmpty() {
|
||||
newRootObjAdd = serialization.RootValueAdd
|
||||
}
|
||||
msg, err := ret.serializeRootValue(ret.SRV.TablesBytes(), dbSchemas, newRootObjAdd)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
ret = RootStorage{msg}
|
||||
copy(serialization.Bytes(ret.SRV), h[:])
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -127,7 +138,7 @@ func (r RootStorage) GetSchemas(ctx context.Context) ([]schema.DatabaseSchema, e
|
||||
|
||||
// SetSchemas sets the given schemas and returns a new storage object.
|
||||
func (r RootStorage) SetSchemas(ctx context.Context, dbSchemas []schema.DatabaseSchema) (RootStorage, error) {
|
||||
msg, err := r.serializeRootValue(r.SRV.TablesBytes(), dbSchemas)
|
||||
msg, err := r.serializeRootValue(r.SRV.TablesBytes(), dbSchemas, nil)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
@@ -259,7 +270,7 @@ func (r RootStorage) EditTablesMap(ctx context.Context, vrw types.ValueReadWrite
|
||||
return RootStorage{}, err
|
||||
}
|
||||
|
||||
msg, err := r.serializeRootValue(ambytes, dbSchemas)
|
||||
msg, err := r.serializeRootValue(ambytes, dbSchemas, nil)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
@@ -267,19 +278,22 @@ func (r RootStorage) EditTablesMap(ctx context.Context, vrw types.ValueReadWrite
|
||||
}
|
||||
|
||||
// serializeRootValue serializes a new serial.RootValue object.
|
||||
func (r RootStorage) serializeRootValue(addressMapBytes []byte, dbSchemas []schema.DatabaseSchema) (*serial.RootValue, error) {
|
||||
func (r RootStorage) serializeRootValue(addressMapBytes []byte, dbSchemas []schema.DatabaseSchema, newRootObjAdd func(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT)) (*serial.RootValue, error) {
|
||||
builder := flatbuffers.NewBuilder(80)
|
||||
tablesOffset := builder.CreateByteVector(addressMapBytes)
|
||||
schemasOffset := serializeDatabaseSchemas(builder, dbSchemas)
|
||||
fkOffset := builder.CreateByteVector(r.SRV.ForeignKeyAddrBytes())
|
||||
rootObjOffsets := make([]flatbuffers.UOffsetT, len(RootObjectSerializations))
|
||||
for i := range RootObjectSerializations {
|
||||
rootObjOffset := RootObjectSerializations[i].Bytes(r.SRV)
|
||||
if len(rootObjOffset) == 0 {
|
||||
h := hash.Hash{}
|
||||
rootObjOffset = h[:]
|
||||
fieldBytes := RootObjectSerializations[i].Bytes(r.SRV)
|
||||
if len(fieldBytes) != hash.ByteLen || hash.New(fieldBytes).IsEmpty() {
|
||||
continue
|
||||
}
|
||||
rootObjOffsets[i] = builder.CreateByteVector(rootObjOffset)
|
||||
rootObjOffsets[i] = builder.CreateByteVector(fieldBytes)
|
||||
}
|
||||
var newRootObjOffset flatbuffers.UOffsetT
|
||||
if newRootObjAdd != nil {
|
||||
newRootObjOffset = builder.CreateByteVector(make([]byte, hash.ByteLen))
|
||||
}
|
||||
|
||||
serial.RootValueStart(builder)
|
||||
@@ -288,7 +302,12 @@ func (r RootStorage) serializeRootValue(addressMapBytes []byte, dbSchemas []sche
|
||||
serial.RootValueAddTables(builder, tablesOffset)
|
||||
serial.RootValueAddForeignKeyAddr(builder, fkOffset)
|
||||
for i := range RootObjectSerializations {
|
||||
RootObjectSerializations[i].RootValueAdd(builder, rootObjOffsets[i])
|
||||
if rootObjOffsets[i] > 0 {
|
||||
RootObjectSerializations[i].RootValueAdd(builder, rootObjOffsets[i])
|
||||
}
|
||||
}
|
||||
if newRootObjAdd != nil {
|
||||
newRootObjAdd(builder, newRootObjOffset)
|
||||
}
|
||||
if schemasOffset > 0 {
|
||||
serial.RootValueAddSchemas(builder, schemasOffset)
|
||||
|
||||
@@ -463,7 +463,75 @@ func (rcv *RootValue) MutateCasts(j int, n byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
const RootValueNumFields = 13
|
||||
func (rcv *RootValue) Operators(j int) byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(30))
|
||||
if o != 0 {
|
||||
a := rcv._tab.Vector(o)
|
||||
return rcv._tab.GetByte(a + flatbuffers.UOffsetT(j*1))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *RootValue) OperatorsLength() int {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(30))
|
||||
if o != 0 {
|
||||
return rcv._tab.VectorLen(o)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *RootValue) OperatorsBytes() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(30))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *RootValue) MutateOperators(j int, n byte) bool {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(30))
|
||||
if o != 0 {
|
||||
a := rcv._tab.Vector(o)
|
||||
return rcv._tab.MutateByte(a+flatbuffers.UOffsetT(j*1), n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (rcv *RootValue) Aggregates(j int) byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(32))
|
||||
if o != 0 {
|
||||
a := rcv._tab.Vector(o)
|
||||
return rcv._tab.GetByte(a + flatbuffers.UOffsetT(j*1))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *RootValue) AggregatesLength() int {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(32))
|
||||
if o != 0 {
|
||||
return rcv._tab.VectorLen(o)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *RootValue) AggregatesBytes() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(32))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *RootValue) MutateAggregates(j int, n byte) bool {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(32))
|
||||
if o != 0 {
|
||||
a := rcv._tab.Vector(o)
|
||||
return rcv._tab.MutateByte(a+flatbuffers.UOffsetT(j*1), n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const RootValueNumFields = 15
|
||||
|
||||
func RootValueStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(RootValueNumFields)
|
||||
@@ -540,6 +608,18 @@ func RootValueAddCasts(builder *flatbuffers.Builder, casts flatbuffers.UOffsetT)
|
||||
func RootValueStartCastsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT {
|
||||
return builder.StartVector(1, numElems, 1)
|
||||
}
|
||||
func RootValueAddOperators(builder *flatbuffers.Builder, operators flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(13, flatbuffers.UOffsetT(operators), 0)
|
||||
}
|
||||
func RootValueStartOperatorsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT {
|
||||
return builder.StartVector(1, numElems, 1)
|
||||
}
|
||||
func RootValueAddAggregates(builder *flatbuffers.Builder, aggregates flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(14, flatbuffers.UOffsetT(aggregates), 0)
|
||||
}
|
||||
func RootValueStartAggregatesVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT {
|
||||
return builder.StartVector(1, numElems, 1)
|
||||
}
|
||||
func RootValueEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ table RootValue {
|
||||
procedures:[ubyte]; // Serialized AddressMap.
|
||||
|
||||
casts:[ubyte]; // Serialized AddressMap.
|
||||
|
||||
operators:[ubyte]; // Serialized AddressMap.
|
||||
|
||||
aggregates:[ubyte]; // Serialized AddressMap.
|
||||
}
|
||||
|
||||
table DatabaseSchema {
|
||||
|
||||
Generated
+24
@@ -83,6 +83,7 @@ var KeywordsCategories = map[string]string{
|
||||
"comments": "U",
|
||||
"commit": "U",
|
||||
"committed": "U",
|
||||
"commutator": "U",
|
||||
"compact": "U",
|
||||
"complete": "U",
|
||||
"compression": "U",
|
||||
@@ -221,6 +222,7 @@ var KeywordsCategories = map[string]string{
|
||||
"groups": "U",
|
||||
"handler": "U",
|
||||
"hash": "U",
|
||||
"hashes": "U",
|
||||
"having": "R",
|
||||
"header": "U",
|
||||
"high": "U",
|
||||
@@ -294,6 +296,7 @@ var KeywordsCategories = map[string]string{
|
||||
"lease": "U",
|
||||
"least": "C",
|
||||
"left": "T",
|
||||
"leftarg": "U",
|
||||
"less": "U",
|
||||
"level": "U",
|
||||
"like": "T",
|
||||
@@ -315,6 +318,7 @@ var KeywordsCategories = map[string]string{
|
||||
"materialized": "U",
|
||||
"maxvalue": "U",
|
||||
"merge": "U",
|
||||
"merges": "U",
|
||||
"method": "U",
|
||||
"mfinalfunc": "U",
|
||||
"mfinalfunc_extra": "U",
|
||||
@@ -347,6 +351,7 @@ var KeywordsCategories = map[string]string{
|
||||
"names": "U",
|
||||
"nan": "U",
|
||||
"natural": "T",
|
||||
"negator": "U",
|
||||
"never": "U",
|
||||
"new": "U",
|
||||
"next": "U",
|
||||
@@ -478,6 +483,7 @@ var KeywordsCategories = map[string]string{
|
||||
"revision_history": "U",
|
||||
"revoke": "U",
|
||||
"right": "T",
|
||||
"rightarg": "U",
|
||||
"role": "U",
|
||||
"roles": "U",
|
||||
"rollback": "U",
|
||||
@@ -723,6 +729,7 @@ var KeywordNames = []string{
|
||||
"comments",
|
||||
"commit",
|
||||
"committed",
|
||||
"commutator",
|
||||
"compact",
|
||||
"complete",
|
||||
"compression",
|
||||
@@ -861,6 +868,7 @@ var KeywordNames = []string{
|
||||
"groups",
|
||||
"handler",
|
||||
"hash",
|
||||
"hashes",
|
||||
"having",
|
||||
"header",
|
||||
"high",
|
||||
@@ -934,6 +942,7 @@ var KeywordNames = []string{
|
||||
"lease",
|
||||
"least",
|
||||
"left",
|
||||
"leftarg",
|
||||
"less",
|
||||
"level",
|
||||
"like",
|
||||
@@ -955,6 +964,7 @@ var KeywordNames = []string{
|
||||
"materialized",
|
||||
"maxvalue",
|
||||
"merge",
|
||||
"merges",
|
||||
"method",
|
||||
"mfinalfunc",
|
||||
"mfinalfunc_extra",
|
||||
@@ -987,6 +997,7 @@ var KeywordNames = []string{
|
||||
"names",
|
||||
"nan",
|
||||
"natural",
|
||||
"negator",
|
||||
"never",
|
||||
"new",
|
||||
"next",
|
||||
@@ -1118,6 +1129,7 @@ var KeywordNames = []string{
|
||||
"revision_history",
|
||||
"revoke",
|
||||
"right",
|
||||
"rightarg",
|
||||
"role",
|
||||
"roles",
|
||||
"rollback",
|
||||
@@ -1448,6 +1460,8 @@ func GetKeywordID(k string) int32 {
|
||||
return COMMIT
|
||||
case "committed":
|
||||
return COMMITTED
|
||||
case "commutator":
|
||||
return COMMUTATOR
|
||||
case "compact":
|
||||
return COMPACT
|
||||
case "complete":
|
||||
@@ -1724,6 +1738,8 @@ func GetKeywordID(k string) int32 {
|
||||
return HANDLER
|
||||
case "hash":
|
||||
return HASH
|
||||
case "hashes":
|
||||
return HASHES
|
||||
case "having":
|
||||
return HAVING
|
||||
case "header":
|
||||
@@ -1870,6 +1886,8 @@ func GetKeywordID(k string) int32 {
|
||||
return LEAST
|
||||
case "left":
|
||||
return LEFT
|
||||
case "leftarg":
|
||||
return LEFTARG
|
||||
case "less":
|
||||
return LESS
|
||||
case "level":
|
||||
@@ -1912,6 +1930,8 @@ func GetKeywordID(k string) int32 {
|
||||
return MAXVALUE
|
||||
case "merge":
|
||||
return MERGE
|
||||
case "merges":
|
||||
return MERGES
|
||||
case "method":
|
||||
return METHOD
|
||||
case "mfinalfunc":
|
||||
@@ -1976,6 +1996,8 @@ func GetKeywordID(k string) int32 {
|
||||
return NAN
|
||||
case "natural":
|
||||
return NATURAL
|
||||
case "negator":
|
||||
return NEGATOR
|
||||
case "never":
|
||||
return NEVER
|
||||
case "new":
|
||||
@@ -2238,6 +2260,8 @@ func GetKeywordID(k string) int32 {
|
||||
return REVOKE
|
||||
case "right":
|
||||
return RIGHT
|
||||
case "rightarg":
|
||||
return RIGHTARG
|
||||
case "role":
|
||||
return ROLE
|
||||
case "roles":
|
||||
|
||||
Generated
+693
-681
File diff suppressed because it is too large
Load Diff
Generated
+433
-403
File diff suppressed because it is too large
Load Diff
@@ -272,7 +272,13 @@ func (s *scanner) scan(lval *sqlSymType) {
|
||||
s.pos++
|
||||
lval.id = NOT_EQUALS
|
||||
return
|
||||
case '=': // <=
|
||||
case '=':
|
||||
if s.peekN(1) == '>' { // <=>
|
||||
s.pos += 2
|
||||
lval.id = COSINE_DISTANCE
|
||||
return
|
||||
}
|
||||
// <=
|
||||
s.pos++
|
||||
lval.id = LESS_EQUALS
|
||||
return
|
||||
@@ -280,6 +286,36 @@ func (s *scanner) scan(lval *sqlSymType) {
|
||||
s.pos++
|
||||
lval.id = CONTAINED_BY
|
||||
return
|
||||
case '-':
|
||||
if s.peekN(1) == '>' { // <->
|
||||
s.pos += 2
|
||||
lval.id = L2_DISTANCE
|
||||
return
|
||||
}
|
||||
case '+':
|
||||
if s.peekN(1) == '>' { // <+>
|
||||
s.pos += 2
|
||||
lval.id = L1_DISTANCE
|
||||
return
|
||||
}
|
||||
case '#':
|
||||
if s.peekN(1) == '>' { // <#>
|
||||
s.pos += 2
|
||||
lval.id = NEG_INNER_PRODUCT
|
||||
return
|
||||
}
|
||||
case '%':
|
||||
if s.peekN(1) == '>' { // <%>
|
||||
s.pos += 2
|
||||
lval.id = JACCARD_DISTANCE
|
||||
return
|
||||
}
|
||||
case '~':
|
||||
if s.peekN(1) == '>' { // <~>
|
||||
s.pos += 2
|
||||
lval.id = HAMMING_DISTANCE
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
|
||||
Generated
+28612
-27849
File diff suppressed because it is too large
Load Diff
@@ -681,6 +681,18 @@ func (u *sqlSymUnion) createAggOptions() []tree.CreateAggOption {
|
||||
func (u *sqlSymUnion) aggregatesToDrop() []tree.AggregateToDrop {
|
||||
return u.val.([]tree.AggregateToDrop)
|
||||
}
|
||||
func (u *sqlSymUnion) createOperatorOption() tree.CreateOperatorOption {
|
||||
return u.val.(tree.CreateOperatorOption)
|
||||
}
|
||||
func (u *sqlSymUnion) createOperatorOptions() []tree.CreateOperatorOption {
|
||||
return u.val.([]tree.CreateOperatorOption)
|
||||
}
|
||||
func (u *sqlSymUnion) operatorToDrop() tree.OperatorToDrop {
|
||||
return u.val.(tree.OperatorToDrop)
|
||||
}
|
||||
func (u *sqlSymUnion) operatorsToDrop() []tree.OperatorToDrop {
|
||||
return u.val.([]tree.OperatorToDrop)
|
||||
}
|
||||
func (u *sqlSymUnion) vacuumOptions() tree.VacuumOptions {
|
||||
return u.val.(tree.VacuumOptions)
|
||||
}
|
||||
@@ -705,6 +717,7 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList {
|
||||
%token <str> TYPECAST TYPEANNOTATE DOT_DOT
|
||||
%token <str> LESS_EQUALS GREATER_EQUALS NOT_EQUALS
|
||||
%token <str> NOT_REGMATCH REGIMATCH NOT_REGIMATCH
|
||||
%token <str> L1_DISTANCE L2_DISTANCE COSINE_DISTANCE NEG_INNER_PRODUCT JACCARD_DISTANCE HAMMING_DISTANCE
|
||||
%token <str> TEXTSEARCHMATCH
|
||||
%token <str> ERROR
|
||||
|
||||
@@ -726,7 +739,7 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList {
|
||||
%token <str> CHANGEFEED BPCHAR CHAR CHARACTER CHARACTERISTICS CHECK CHECK_OPTION CLASS CLOSE
|
||||
%token <str> CLUSTER COALESCE COLLATABLE COLLATE COLLATION COLLATION_VERSION COLUMN COLUMNS COMBINEFUNC COMMENT COMMENTS
|
||||
%token <str> BLOCK_COMMENT HINT
|
||||
%token <str> COMMIT COMMITTED COMPACT COMPLETE COMPRESSION CONCAT CONCURRENTLY CONFIGURATION CONFIGURATIONS CONFIGURE
|
||||
%token <str> COMMIT COMMITTED COMMUTATOR COMPACT COMPLETE COMPRESSION CONCAT CONCURRENTLY CONFIGURATION CONFIGURATIONS CONFIGURE
|
||||
%token <str> CONFLICT CONNECT CONNECTION CONSTRAINT CONSTRAINTS CONTAINS CONTROLCHANGEFEED
|
||||
%token <str> CONTROLJOB CONVERSION CONVERT COPY COST CREATE CREATEDB CREATELOGIN CREATEROLE
|
||||
%token <str> CROSS CUBE CURRENT CURRENT_CATALOG CURRENT_DATE CURRENT_SCHEMA
|
||||
@@ -751,7 +764,7 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList {
|
||||
%token <str> GEOMETRYCOLLECTION GEOMETRYCOLLECTIONM GEOMETRYCOLLECTIONZ GEOMETRYCOLLECTIONZM
|
||||
%token <str> GLOBAL GRANT GRANTED GRANTS GREATEST GROUP GROUPING GROUPS
|
||||
|
||||
%token <str> HANDLER HASH HAVING HIGH HISTOGRAM HOUR HYPOTHETICAL
|
||||
%token <str> HANDLER HASH HASHES HAVING HIGH HISTOGRAM HOUR HYPOTHETICAL
|
||||
|
||||
%token <str> ICU_LOCALE ICU_RULES IDENTITY
|
||||
%token <str> IF IFERROR IFNULL IGNORE_FOREIGN_KEYS ILIKE IMMEDIATE IMPLICIT IMMUTABLE IMPORT
|
||||
@@ -765,16 +778,16 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList {
|
||||
%token <str> KEY KEYS KMS KV
|
||||
|
||||
%token <str> LANGUAGE LARGE LAST LATERAL LATEST LC_CTYPE LC_COLLATE
|
||||
%token <str> LEADING LEAKPROOF LEASE LEAST LEFT LESS LEVEL LIKE LIMIT
|
||||
%token <str> LEADING LEAKPROOF LEASE LEAST LEFT LEFTARG LESS LEVEL LIKE LIMIT
|
||||
%token <str> LINESTRING LINESTRINGM LINESTRINGZ LINESTRINGZM LIST
|
||||
%token <str> LOCAL LOCALE LOCALE_PROVIDER LOCALTIME LOCALTIMESTAMP LOCKED LOGGED LOGIN LOOKUP LOW LSHIFT
|
||||
|
||||
%token <str> MAIN MATCH MATERIALIZED MAXVALUE MERGE METHOD MFINALFUNC MFINALFUNC_EXTRA MFINALFUNC_MODIFY
|
||||
%token <str> MAIN MATCH MATERIALIZED MAXVALUE MERGE MERGES METHOD MFINALFUNC MFINALFUNC_EXTRA MFINALFUNC_MODIFY
|
||||
%token <str> MINITCOND MINUTE MINVALUE MINVFUNC MODIFYCLUSTERSETTING MODULUS MONTH MSFUNC MSPACE MSSPACE MSTYPE
|
||||
%token <str> MULTILINESTRING MULTILINESTRINGM MULTILINESTRINGZ MULTILINESTRINGZM MULTIPOINT MULTIPOINTM
|
||||
%token <str> MULTIPOINTZ MULTIPOINTZM MULTIPOLYGON MULTIPOLYGONM MULTIPOLYGONZ MULTIPOLYGONZM MULTIRANGE_TYPE_NAME
|
||||
|
||||
%token <str> NAN NAME NAMES NATURAL NEVER NEW NEXT NO NOCANCELQUERY NOCONTROLCHANGEFEED NOCONTROLJOB
|
||||
%token <str> NAN NAME NAMES NATURAL NEGATOR NEVER NEW NEXT NO NOCANCELQUERY NOCONTROLCHANGEFEED NOCONTROLJOB
|
||||
%token <str> NOBYPASSRLS NOCREATEDB NOCREATELOGIN NOCREATEROLE NOINHERIT NOLOGIN NOMODIFYCLUSTERSETTING NOREPLICATION NOSUPERUSER NO_INDEX_JOIN
|
||||
%token <str> NONE NORMAL NOT NOTHING NOTNULL NOVIEWACTIVITY NOWAIT NULL NULLIF NULLS NUMERIC YES
|
||||
|
||||
@@ -791,7 +804,7 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList {
|
||||
%token <str> RANGE RANGES READ READ_ONLY READ_WRITE REAL RECEIVE RECURSIVE RECURRING REF REFERENCES REFERENCING REFRESH
|
||||
%token <str> REGCLASS REGPROC REGPROCEDURE REGNAMESPACE REGTYPE REINDEX RELEASE REMAINDER
|
||||
%token <str> REMOVE_PATH RENAME REPEATABLE REPLACE REPLICA REPLICATION RESET RESTART RESTORE RESTRICT RESTRICTED RESUME
|
||||
%token <str> RETRY RETURN RETURNING RETURNS REVISION_HISTORY REVOKE RIGHT
|
||||
%token <str> RETRY RETURN RETURNING RETURNS REVISION_HISTORY REVOKE RIGHT RIGHTARG
|
||||
%token <str> ROLE ROLES ROUTINE ROUTINES ROLLBACK ROLLUP ROW ROWS RSHIFT RULE RUNNING
|
||||
|
||||
%token <str> SAFE SAVEPOINT SCATTER SCHEDULE SCHEDULES SCHEMA SCHEMAS SCRUB SEARCH SECOND SECURITY
|
||||
@@ -947,6 +960,9 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList {
|
||||
%type <tree.Statement> create_aggregate_order_by_args_stmt
|
||||
%type <tree.Statement> create_aggregate_old_syntax_stmt
|
||||
|
||||
%type <tree.Statement> create_operator_stmt
|
||||
%type <tree.Statement> drop_operator_stmt
|
||||
|
||||
%type <tree.Statement> create_stats_stmt
|
||||
%type <*tree.CreateStatsOptions> opt_create_stats_options
|
||||
%type <*tree.CreateStatsOptions> create_stats_option_list
|
||||
@@ -1110,6 +1126,10 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList {
|
||||
%type <tree.CreateAggOption> create_agg_args_only_option create_agg_order_by_args_option
|
||||
%type <tree.CreateAggOption> create_agg_old_syntax_option create_agg_common_option create_agg_parallel_option
|
||||
%type <[]tree.CreateAggOption> create_agg_args_only_option_list create_agg_order_by_args_option_list create_agg_old_syntax_option_list
|
||||
%type <tree.CreateOperatorOption> create_operator_option
|
||||
%type <[]tree.CreateOperatorOption> create_operator_option_list
|
||||
%type <tree.OperatorToDrop> operator_to_drop
|
||||
%type <[]tree.OperatorToDrop> drop_operators
|
||||
%type <[]tree.AggregateToDrop> drop_aggregates
|
||||
%type <tree.CreateCastScope> create_cast_scope_opt
|
||||
|
||||
@@ -1441,6 +1461,7 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList {
|
||||
%nonassoc UNBOUNDED // ideally should have same precedence as IDENT
|
||||
%nonassoc IDENT NULL PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
|
||||
%left CONCAT FETCHVAL FETCHTEXT FETCHVAL_PATH FETCHTEXT_PATH REMOVE_PATH // multi-character ops
|
||||
%left L1_DISTANCE L2_DISTANCE COSINE_DISTANCE NEG_INNER_PRODUCT JACCARD_DISTANCE HAMMING_DISTANCE
|
||||
%left '|'
|
||||
%left '#'
|
||||
%left '&'
|
||||
@@ -4060,6 +4081,7 @@ create_stmt:
|
||||
| create_language_stmt // EXTEND WITH HELP: CREATE LANGUAGE
|
||||
| create_aggregate_stmt // EXTEND WITH HELP: CREATE AGGREGATE
|
||||
| create_cast_stmt // EXTEND WITH HELP: CREATE CAST
|
||||
| create_operator_stmt // EXTEND WITH HELP: CREATE OPERATOR
|
||||
| create_unsupported {}
|
||||
| CREATE error // SHOW HELP: CREATE
|
||||
|
||||
@@ -4067,7 +4089,8 @@ create_unsupported:
|
||||
CREATE CONVERSION error { return unimplemented(sqllex, "create conversion") }
|
||||
| CREATE DEFAULT CONVERSION error { return unimplemented(sqllex, "create def conv") }
|
||||
| CREATE FOREIGN TABLE error { return unimplemented(sqllex, "create foreign table") }
|
||||
| CREATE OPERATOR error { return unimplemented(sqllex, "create operator") }
|
||||
| CREATE OPERATOR CLASS error { return unimplemented(sqllex, "create operator class") }
|
||||
| CREATE OPERATOR FAMILY error { return unimplemented(sqllex, "create operator family") }
|
||||
| CREATE PUBLICATION error { return unimplemented(sqllex, "create publication") }
|
||||
| CREATE opt_or_replace RULE error { return unimplemented(sqllex, "create rule") }
|
||||
| CREATE SERVER error { return unimplemented(sqllex, "create server") }
|
||||
@@ -4190,6 +4213,49 @@ create_agg_parallel_option:
|
||||
| PARALLEL '=' UNSAFE
|
||||
{ $$.val = tree.CreateAggOption{Option: tree.AggOptTypeParallel, Parallel: tree.ParallelSafe} }
|
||||
|
||||
// %Help: CREATE OPERATOR - define a new operator
|
||||
// %Category: DDL
|
||||
// %Text: CREATE OPERATOR name (
|
||||
// {FUNCTION|PROCEDURE} = function_name
|
||||
// [, LEFTARG = left_type ] [, RIGHTARG = right_type ]
|
||||
// [, COMMUTATOR = com_op ] [, NEGATOR = neg_op ]
|
||||
// [, RESTRICT = res_proc ] [, JOIN = join_proc ]
|
||||
// [, HASHES ] [, MERGES ]
|
||||
// )
|
||||
// %SeeAlso: WEBDOCS/sql-createoperator.html
|
||||
create_operator_stmt:
|
||||
CREATE OPERATOR operator '(' create_operator_option_list ')'
|
||||
{ $$.val = &tree.CreateOperator{Name: $3.op(), Options: $5.createOperatorOptions()} }
|
||||
| CREATE OPERATOR error // SHOW HELP: CREATE OPERATOR
|
||||
|
||||
create_operator_option_list:
|
||||
create_operator_option
|
||||
{ $$.val = []tree.CreateOperatorOption{$1.createOperatorOption()} }
|
||||
| create_operator_option_list ',' create_operator_option
|
||||
{ $$.val = append($1.createOperatorOptions(), $3.createOperatorOption()) }
|
||||
|
||||
create_operator_option:
|
||||
FUNCTION '=' routine_name
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeFunction, FuncName: $3.unresolvedObjectName()} }
|
||||
| PROCEDURE '=' routine_name
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeFunction, FuncName: $3.unresolvedObjectName()} }
|
||||
| LEFTARG '=' type_name
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeLeftArg, TypeVal: $3.typeReference()} }
|
||||
| RIGHTARG '=' type_name
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeRightArg, TypeVal: $3.typeReference()} }
|
||||
| COMMUTATOR '=' operator
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeCommutator, OpVal: $3.op()} }
|
||||
| NEGATOR '=' operator
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeNegator, OpVal: $3.op()} }
|
||||
| RESTRICT '=' routine_name
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeRestrict, FuncName: $3.unresolvedObjectName()} }
|
||||
| JOIN '=' routine_name
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeJoin, FuncName: $3.unresolvedObjectName()} }
|
||||
| HASHES
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeHashes} }
|
||||
| MERGES
|
||||
{ $$.val = tree.CreateOperatorOption{Option: tree.OperatorOptTypeMerges} }
|
||||
|
||||
// %Help: CREATE CAST - define a new cast
|
||||
// %Category: DDL
|
||||
// %Text: CREATE CAST (source_type AS target_type) WITH FUNCTION function_name [ (argument_type [, ...]) ] [ AS ASSIGNMENT | AS IMPLICIT ]
|
||||
@@ -4742,7 +4808,8 @@ drop_unsupported:
|
||||
| DROP CONVERSION error { return unimplemented(sqllex, "drop conversion") }
|
||||
| DROP FOREIGN TABLE error { return unimplemented(sqllex, "drop foreign table") }
|
||||
| DROP FOREIGN DATA error { return unimplemented(sqllex, "drop fdw") }
|
||||
| DROP OPERATOR error { return unimplemented(sqllex, "drop operator") }
|
||||
| DROP OPERATOR CLASS error { return unimplemented(sqllex, "drop operator class") }
|
||||
| DROP OPERATOR FAMILY error { return unimplemented(sqllex, "drop operator family") }
|
||||
| DROP PUBLICATION error { return unimplemented(sqllex, "drop publication") }
|
||||
| DROP RULE error { return unimplemented(sqllex, "drop rule") }
|
||||
| DROP SERVER error { return unimplemented(sqllex, "drop server") }
|
||||
@@ -4769,6 +4836,37 @@ drop_aggregates:
|
||||
$$.val = append($1.aggregatesToDrop(), tree.AggregateToDrop{Name: $3.unresolvedObjectName(), AggSig: $5.aggregateSignature()})
|
||||
}
|
||||
|
||||
// %Help: DROP OPERATOR - remove an operator
|
||||
// %Category: DDL
|
||||
// %Text: DROP OPERATOR [ IF EXISTS ] name ( { left_type | NONE } , right_type ) [, ...] [ CASCADE | RESTRICT ]
|
||||
// %SeeAlso: WEBDOCS/sql-dropoperator.html
|
||||
drop_operator_stmt:
|
||||
DROP OPERATOR drop_operators opt_drop_behavior
|
||||
{
|
||||
$$.val = &tree.DropOperator{Operators: $3.operatorsToDrop(), DropBehavior: $4.dropBehavior()}
|
||||
}
|
||||
| DROP OPERATOR IF EXISTS drop_operators opt_drop_behavior
|
||||
{
|
||||
$$.val = &tree.DropOperator{Operators: $5.operatorsToDrop(), IfExists: true, DropBehavior: $6.dropBehavior()}
|
||||
}
|
||||
| DROP OPERATOR error // SHOW HELP: DROP OPERATOR
|
||||
|
||||
drop_operators:
|
||||
operator_to_drop
|
||||
{
|
||||
$$.val = []tree.OperatorToDrop{$1.operatorToDrop()}
|
||||
}
|
||||
| drop_operators ',' operator_to_drop
|
||||
{
|
||||
$$.val = append($1.operatorsToDrop(), $3.operatorToDrop())
|
||||
}
|
||||
|
||||
operator_to_drop:
|
||||
operator '(' typename ',' typename ')'
|
||||
{
|
||||
$$.val = tree.OperatorToDrop{Op: $1.op(), Left: tree.OperatorArgType($3.typeReference()), Right: tree.OperatorArgType($5.typeReference())}
|
||||
}
|
||||
|
||||
drop_domain_stmt:
|
||||
DROP DOMAIN table_name_list opt_drop_behavior
|
||||
{
|
||||
@@ -5047,6 +5145,7 @@ drop_stmt:
|
||||
| drop_extension_stmt // EXTEND WITH HELP: DROP EXTENSION
|
||||
| drop_language_stmt // EXTEND WITH HELP: DROP LANGUAGE
|
||||
| drop_aggregate_stmt // EXTEND WITH HELP: DROP AGGREGATE
|
||||
| drop_operator_stmt // EXTEND WITH HELP: DROP OPERATOR
|
||||
| drop_unsupported {}
|
||||
| DROP error // SHOW HELP: DROP
|
||||
|
||||
@@ -12625,6 +12724,30 @@ a_expr:
|
||||
{
|
||||
$$.val = &tree.FuncExpr{Func: tree.WrapFunction("json_remove_path"), Exprs: tree.Exprs{$1.expr(), $3.expr()}}
|
||||
}
|
||||
| a_expr L2_DISTANCE a_expr
|
||||
{
|
||||
$$.val = &tree.BinaryExpr{Operator: tree.L2Distance, Left: $1.expr(), Right: $3.expr()}
|
||||
}
|
||||
| a_expr L1_DISTANCE a_expr
|
||||
{
|
||||
$$.val = &tree.BinaryExpr{Operator: tree.L1Distance, Left: $1.expr(), Right: $3.expr()}
|
||||
}
|
||||
| a_expr COSINE_DISTANCE a_expr
|
||||
{
|
||||
$$.val = &tree.BinaryExpr{Operator: tree.CosineDistance, Left: $1.expr(), Right: $3.expr()}
|
||||
}
|
||||
| a_expr NEG_INNER_PRODUCT a_expr
|
||||
{
|
||||
$$.val = &tree.BinaryExpr{Operator: tree.NegInnerProduct, Left: $1.expr(), Right: $3.expr()}
|
||||
}
|
||||
| a_expr JACCARD_DISTANCE a_expr
|
||||
{
|
||||
$$.val = &tree.BinaryExpr{Operator: tree.JaccardDistance, Left: $1.expr(), Right: $3.expr()}
|
||||
}
|
||||
| a_expr HAMMING_DISTANCE a_expr
|
||||
{
|
||||
$$.val = &tree.BinaryExpr{Operator: tree.HammingDistance, Left: $1.expr(), Right: $3.expr()}
|
||||
}
|
||||
| a_expr INET_CONTAINED_BY_OR_EQUALS a_expr
|
||||
{
|
||||
$$.val = &tree.FuncExpr{Func: tree.WrapFunction("inet_contained_by_or_equals"), Exprs: tree.Exprs{$1.expr(), $3.expr()}}
|
||||
@@ -13878,6 +14001,12 @@ operator:
|
||||
| FETCHTEXT_PATH { $$.val = tree.JSONFetchTextPath }
|
||||
| AND_AND { $$.val = tree.Overlaps }
|
||||
| TEXTSEARCHMATCH { $$.val = tree.TextSearchMatch }
|
||||
| L2_DISTANCE { $$.val = tree.L2Distance }
|
||||
| L1_DISTANCE { $$.val = tree.L1Distance }
|
||||
| COSINE_DISTANCE { $$.val = tree.CosineDistance }
|
||||
| NEG_INNER_PRODUCT { $$.val = tree.NegInnerProduct }
|
||||
| JACCARD_DISTANCE { $$.val = tree.JaccardDistance }
|
||||
| HAMMING_DISTANCE { $$.val = tree.HammingDistance }
|
||||
|
||||
math_op:
|
||||
'+' { $$.val = tree.Plus }
|
||||
@@ -14863,6 +14992,7 @@ unreserved_keyword:
|
||||
| COMMENTS
|
||||
| COMMIT
|
||||
| COMMITTED
|
||||
| COMMUTATOR
|
||||
| COMPACT
|
||||
| COMPLETE
|
||||
| COMPRESSION
|
||||
@@ -14960,6 +15090,7 @@ unreserved_keyword:
|
||||
| GROUPS
|
||||
| HANDLER
|
||||
| HASH
|
||||
| HASHES
|
||||
| HEADER
|
||||
| HIGH
|
||||
| HISTOGRAM
|
||||
@@ -15009,6 +15140,7 @@ unreserved_keyword:
|
||||
| LC_CTYPE
|
||||
| LEAKPROOF
|
||||
| LEASE
|
||||
| LEFTARG
|
||||
| LESS
|
||||
| LEVEL
|
||||
| LINESTRING
|
||||
@@ -15026,6 +15158,7 @@ unreserved_keyword:
|
||||
| MATERIALIZED
|
||||
| MAXVALUE
|
||||
| MERGE
|
||||
| MERGES
|
||||
| METHOD
|
||||
| MFINALFUNC
|
||||
| MFINALFUNC_EXTRA
|
||||
@@ -15057,6 +15190,7 @@ unreserved_keyword:
|
||||
| NAME
|
||||
| NAMES
|
||||
| NAN
|
||||
| NEGATOR
|
||||
| NEVER
|
||||
| NEW
|
||||
| NEXT
|
||||
@@ -15162,6 +15296,7 @@ unreserved_keyword:
|
||||
| RETURNS
|
||||
| REVISION_HISTORY
|
||||
| REVOKE
|
||||
| RIGHTARG
|
||||
| ROLE
|
||||
| ROLES
|
||||
| ROLLBACK
|
||||
|
||||
@@ -57,7 +57,7 @@ func (node *CreateAggregate) Format(ctx *FmtCtx) {
|
||||
ctx.WriteString("SFUNC = ")
|
||||
ctx.FormatNode(node.SFunc)
|
||||
ctx.WriteString(" , STYPE = ")
|
||||
ctx.WriteString(node.BaseType.SQLString())
|
||||
ctx.WriteString(node.SType.SQLString())
|
||||
if node.AggOptions != nil {
|
||||
ctx.FormatNode(&node.AggOptions)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// 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 tree
|
||||
|
||||
var _ Statement = &CreateOperator{}
|
||||
|
||||
// CreateOperator represents a CREATE OPERATOR statement.
|
||||
type CreateOperator struct {
|
||||
Name Operator
|
||||
Options []CreateOperatorOption
|
||||
}
|
||||
|
||||
// Format implements the NodeFormatter interface.
|
||||
func (node *CreateOperator) Format(ctx *FmtCtx) {
|
||||
ctx.WriteString("CREATE OPERATOR ")
|
||||
ctx.WriteString(OperatorSymbol(node.Name))
|
||||
ctx.WriteString(" ( ")
|
||||
for i := range node.Options {
|
||||
if i > 0 {
|
||||
ctx.WriteString(" , ")
|
||||
}
|
||||
ctx.FormatNode(&node.Options[i])
|
||||
}
|
||||
ctx.WriteString(" )")
|
||||
}
|
||||
|
||||
// CreateOperatorOption represents a single option of a CREATE OPERATOR statement.
|
||||
type CreateOperatorOption struct {
|
||||
Option CreateOperatorOptionType
|
||||
// FuncName is used for Function, Restrict, and Join
|
||||
FuncName *UnresolvedObjectName
|
||||
// TypeVal is used for LeftArg and RightArg
|
||||
TypeVal ResolvableTypeReference
|
||||
// OpVal is used for Commutator and Negator
|
||||
OpVal Operator
|
||||
// Hashes and Merges do not define any stored value.
|
||||
}
|
||||
|
||||
// Format implements the NodeFormatter interface.
|
||||
func (node *CreateOperatorOption) Format(ctx *FmtCtx) {
|
||||
switch node.Option {
|
||||
case OperatorOptTypeFunction:
|
||||
ctx.WriteString("FUNCTION = ")
|
||||
ctx.FormatNode(node.FuncName)
|
||||
case OperatorOptTypeLeftArg:
|
||||
ctx.WriteString("LEFTARG = ")
|
||||
ctx.WriteString(node.TypeVal.SQLString())
|
||||
case OperatorOptTypeRightArg:
|
||||
ctx.WriteString("RIGHTARG = ")
|
||||
ctx.WriteString(node.TypeVal.SQLString())
|
||||
case OperatorOptTypeCommutator:
|
||||
ctx.WriteString("COMMUTATOR = ")
|
||||
ctx.WriteString(OperatorSymbol(node.OpVal))
|
||||
case OperatorOptTypeNegator:
|
||||
ctx.WriteString("NEGATOR = ")
|
||||
ctx.WriteString(OperatorSymbol(node.OpVal))
|
||||
case OperatorOptTypeRestrict:
|
||||
ctx.WriteString("RESTRICT = ")
|
||||
ctx.FormatNode(node.FuncName)
|
||||
case OperatorOptTypeJoin:
|
||||
ctx.WriteString("JOIN = ")
|
||||
ctx.FormatNode(node.FuncName)
|
||||
case OperatorOptTypeHashes:
|
||||
ctx.WriteString("HASHES")
|
||||
case OperatorOptTypeMerges:
|
||||
ctx.WriteString("MERGES")
|
||||
}
|
||||
}
|
||||
|
||||
// OperatorSymbol returns the operator's symbol.
|
||||
func OperatorSymbol(op Operator) string {
|
||||
switch op := op.(type) {
|
||||
case UnaryOperator:
|
||||
return op.String()
|
||||
case BinaryOperator:
|
||||
return op.String()
|
||||
case ComparisonOperator:
|
||||
return op.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// CreateOperatorOptionType represents the type of a CREATE OPERATOR option.
|
||||
type CreateOperatorOptionType int
|
||||
|
||||
const (
|
||||
OperatorOptTypeFunction CreateOperatorOptionType = iota
|
||||
OperatorOptTypeLeftArg
|
||||
OperatorOptTypeRightArg
|
||||
OperatorOptTypeCommutator
|
||||
OperatorOptTypeNegator
|
||||
OperatorOptTypeRestrict
|
||||
OperatorOptTypeJoin
|
||||
OperatorOptTypeHashes
|
||||
OperatorOptTypeMerges
|
||||
)
|
||||
@@ -90,6 +90,65 @@ func (node *DropAggregate) Format(ctx *FmtCtx) {
|
||||
}
|
||||
}
|
||||
|
||||
var _ Statement = &DropOperator{}
|
||||
|
||||
// DropOperator represents a DROP OPERATOR statement.
|
||||
type DropOperator struct {
|
||||
Operators []OperatorToDrop
|
||||
IfExists bool
|
||||
DropBehavior DropBehavior
|
||||
}
|
||||
|
||||
// OperatorToDrop is a single operator listed in a DROP OPERATOR statement.
|
||||
type OperatorToDrop struct {
|
||||
Op Operator
|
||||
// Left is nil when the statement writes NONE, denoting a prefix operator
|
||||
Left ResolvableTypeReference
|
||||
Right ResolvableTypeReference
|
||||
}
|
||||
|
||||
// OperatorArgType returns the given operator operand type, converting the NONE keyword (which parses as a type named
|
||||
// "none") to nil.
|
||||
func OperatorArgType(typ ResolvableTypeReference) ResolvableTypeReference {
|
||||
if uon, ok := typ.(*UnresolvedObjectName); ok && uon.NumParts == 1 && uon.Parts[0] == "none" {
|
||||
return nil
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// Format implements the NodeFormatter interface.
|
||||
func (node *DropOperator) Format(ctx *FmtCtx) {
|
||||
ctx.WriteString("DROP OPERATOR ")
|
||||
if node.IfExists {
|
||||
ctx.WriteString("IF EXISTS ")
|
||||
}
|
||||
for i, op := range node.Operators {
|
||||
if i != 0 {
|
||||
ctx.WriteString(" , ")
|
||||
}
|
||||
ctx.WriteString(OperatorSymbol(op.Op))
|
||||
ctx.WriteString(" ( ")
|
||||
if op.Left != nil {
|
||||
ctx.WriteString(op.Left.SQLString())
|
||||
} else {
|
||||
ctx.WriteString("NONE")
|
||||
}
|
||||
ctx.WriteString(" , ")
|
||||
if op.Right != nil {
|
||||
ctx.WriteString(op.Right.SQLString())
|
||||
} else {
|
||||
ctx.WriteString("NONE")
|
||||
}
|
||||
ctx.WriteString(" )")
|
||||
}
|
||||
switch node.DropBehavior {
|
||||
case DropDefault:
|
||||
default:
|
||||
ctx.WriteByte(' ')
|
||||
ctx.WriteString(dropBehaviorName[node.DropBehavior])
|
||||
}
|
||||
}
|
||||
|
||||
// DropCast represents a DROP CAST statement.
|
||||
type DropCast struct {
|
||||
Source ResolvableTypeReference
|
||||
|
||||
@@ -1101,6 +1101,12 @@ const (
|
||||
JSONFetchText
|
||||
JSONFetchValPath
|
||||
JSONFetchTextPath
|
||||
L2Distance
|
||||
L1Distance
|
||||
CosineDistance
|
||||
NegInnerProduct
|
||||
JaccardDistance
|
||||
HammingDistance
|
||||
|
||||
NumBinaryOperators
|
||||
)
|
||||
@@ -1125,6 +1131,12 @@ var binaryOpName = [...]string{
|
||||
JSONFetchText: "->>",
|
||||
JSONFetchValPath: "#>",
|
||||
JSONFetchTextPath: "#>>",
|
||||
L2Distance: "<->",
|
||||
L1Distance: "<+>",
|
||||
CosineDistance: "<=>",
|
||||
NegInnerProduct: "<#>",
|
||||
JaccardDistance: "<%>",
|
||||
HammingDistance: "<~>",
|
||||
}
|
||||
|
||||
// binaryOpPrio follows the precedence order in the grammar. Used for pretty-printing.
|
||||
@@ -1137,6 +1149,8 @@ var binaryOpPrio = [...]int{
|
||||
Bitxor: 6,
|
||||
Bitor: 7,
|
||||
Concat: 8, JSONFetchVal: 8, JSONFetchText: 8, JSONFetchValPath: 8, JSONFetchTextPath: 8,
|
||||
L2Distance: 8, L1Distance: 8, CosineDistance: 8, NegInnerProduct: 8, JaccardDistance: 8,
|
||||
HammingDistance: 8,
|
||||
}
|
||||
|
||||
// binaryOpFullyAssoc indicates whether an operator is fully associative.
|
||||
|
||||
@@ -525,6 +525,12 @@ func (*CreateMaterializedView) StatementType() StatementType { return DDL }
|
||||
// StatementTag returns a short string identifying the type of statement.
|
||||
func (*CreateMaterializedView) StatementTag() string { return "CREATE MATERIALIZED VIEW" }
|
||||
|
||||
// StatementType implements the Statement interface.
|
||||
func (*CreateOperator) StatementType() StatementType { return DDL }
|
||||
|
||||
// StatementTag returns a short string identifying the type of statement.
|
||||
func (*CreateOperator) StatementTag() string { return "CREATE OPERATOR" }
|
||||
|
||||
// StatementType implements the Statement interface.
|
||||
func (*CreateProcedure) StatementType() StatementType { return DDL }
|
||||
|
||||
@@ -670,6 +676,12 @@ func (*DropLanguage) StatementType() StatementType { return DDL }
|
||||
// StatementTag returns a short string identifying the type of statement.
|
||||
func (*DropLanguage) StatementTag() string { return "DROP LANGUAGE" }
|
||||
|
||||
// StatementType implements the Statement interface.
|
||||
func (*DropOperator) StatementType() StatementType { return DDL }
|
||||
|
||||
// StatementTag returns a short string identifying the type of statement.
|
||||
func (*DropOperator) StatementTag() string { return "DROP OPERATOR" }
|
||||
|
||||
// StatementType implements the Statement interface.
|
||||
func (*DropProcedure) StatementType() StatementType { return DDL }
|
||||
|
||||
@@ -1238,6 +1250,7 @@ func (n *CreateFunction) String() string { return AsString(n) }
|
||||
func (n *CreateIndex) String() string { return AsString(n) }
|
||||
func (n *CreateLanguage) String() string { return AsString(n) }
|
||||
func (n *CreateMaterializedView) String() string { return AsString(n) }
|
||||
func (n *CreateOperator) String() string { return AsString(n) }
|
||||
func (n *CreateProcedure) String() string { return AsString(n) }
|
||||
func (n *CreateRole) String() string { return AsString(n) }
|
||||
func (n *CreateTable) String() string { return AsString(n) }
|
||||
@@ -1257,6 +1270,7 @@ func (n *DropExtension) String() string { return AsString(n) }
|
||||
func (n *DropFunction) String() string { return AsString(n) }
|
||||
func (n *DropIndex) String() string { return AsString(n) }
|
||||
func (n *DropLanguage) String() string { return AsString(n) }
|
||||
func (n *DropOperator) String() string { return AsString(n) }
|
||||
func (n *DropProcedure) String() string { return AsString(n) }
|
||||
func (n *DropSchema) String() string { return AsString(n) }
|
||||
func (n *DropTable) String() string { return AsString(n) }
|
||||
|
||||
+19
-4
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/planbuilder"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
pgexpression "github.com/dolthub/doltgresql/server/expression"
|
||||
)
|
||||
|
||||
@@ -157,23 +158,37 @@ var postgresOnlyWindowFuncNames = map[string]bool{
|
||||
}
|
||||
|
||||
// IsAggregateFunc checks if the given function name is an aggregate function. This is the entire set supported by
|
||||
// MySQL plus some postgres specific ones.
|
||||
// MySQL plus some postgres specific ones, along with every user-defined aggregate.
|
||||
func IsAggregateFunc(ctx *sql.Context, name string) (bool, error) {
|
||||
isAggregate, err := planbuilder.IsMySQLAggregateFuncName(ctx, name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return isAggregate || postgresOnlyAggregateFuncNames[name], nil
|
||||
if isAggregate || postgresOnlyAggregateFuncNames[name] {
|
||||
return true, nil
|
||||
}
|
||||
collection, err := core.GetAggregatesCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return collection.HasAggregateName(ctx, name)
|
||||
}
|
||||
|
||||
// IsWindowFunc checks if the given function name is a window function. This is the entire set supported by
|
||||
// MySQL plus some postgres specific ones.
|
||||
// MySQL plus some postgres specific ones, along with every user-defined aggregate.
|
||||
func IsWindowFunc(ctx *sql.Context, name string) (bool, error) {
|
||||
isWindow, err := planbuilder.IsMySQLWindowFuncName(ctx, name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return isWindow || postgresOnlyAggregateFuncNames[name] || postgresOnlyWindowFuncNames[name], nil
|
||||
if isWindow || postgresOnlyAggregateFuncNames[name] || postgresOnlyWindowFuncNames[name] {
|
||||
return true, nil
|
||||
}
|
||||
collection, err := core.GetAggregatesCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return collection.HasAggregateName(ctx, name)
|
||||
}
|
||||
|
||||
// insertAnalyzerRules inserts the given rule(s) before or after the given analyzer.RuleId, returning an updated slice.
|
||||
|
||||
@@ -65,6 +65,26 @@ func ResolveTypeForNodes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node,
|
||||
col.Type = dt
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.CreateAggregate:
|
||||
if !n.SType.IsResolvedType() {
|
||||
sType, err := resolveType(ctx, db, n.SType)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.SType = sType
|
||||
}
|
||||
for i, argType := range n.ArgTypes {
|
||||
if !argType.IsResolvedType() {
|
||||
dt, err := resolveType(ctx, db, argType)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.ArgTypes[i] = dt
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.CreateCast:
|
||||
if !n.Source.IsResolvedType() {
|
||||
source, err := resolveType(ctx, db, n.Source)
|
||||
@@ -113,6 +133,24 @@ func ResolveTypeForNodes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node,
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.CreateOperator:
|
||||
if n.Left != nil && !n.Left.IsResolvedType() {
|
||||
left, err := resolveType(ctx, db, n.Left)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.Left = left
|
||||
}
|
||||
if !n.Right.IsResolvedType() {
|
||||
right, err := resolveType(ctx, db, n.Right)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.Right = right
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.CreateProcedure:
|
||||
for i := range n.Parameters {
|
||||
var err error
|
||||
@@ -161,6 +199,20 @@ func ResolveTypeForNodes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node,
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.DropAggregate:
|
||||
for _, agg := range n.Aggregates {
|
||||
for i, argType := range agg.ArgTypes {
|
||||
if !argType.IsResolvedType() {
|
||||
dt, err := resolveType(ctx, db, argType)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
agg.ArgTypes[i] = dt
|
||||
}
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.DropCast:
|
||||
if !n.Source.IsResolvedType() {
|
||||
source, err := resolveType(ctx, db, n.Source)
|
||||
@@ -194,6 +246,26 @@ func ResolveTypeForNodes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node,
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.DropOperator:
|
||||
for _, op := range n.Operators {
|
||||
if op.Left != nil && !op.Left.IsResolvedType() {
|
||||
left, err := resolveType(ctx, db, op.Left)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
op.Left = left
|
||||
}
|
||||
if !op.Right.IsResolvedType() {
|
||||
right, err := resolveType(ctx, db, op.Right)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
op.Right = right
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.DropProcedure:
|
||||
for _, r := range n.RoutinesWithArgs {
|
||||
for j, arg := range r.Args {
|
||||
|
||||
@@ -109,6 +109,8 @@ func Convert(postgresStmt parser.Statement) (vitess.Statement, error) {
|
||||
return nodeCreateIndex(ctx, stmt)
|
||||
case *tree.CreateMaterializedView:
|
||||
return nodeCreateMaterializedView(ctx, stmt)
|
||||
case *tree.CreateOperator:
|
||||
return nodeCreateOperator(ctx, stmt)
|
||||
case *tree.CreateProcedure:
|
||||
return nodeCreateProcedure(ctx, stmt)
|
||||
case *tree.CreateRole:
|
||||
@@ -147,6 +149,8 @@ func Convert(postgresStmt parser.Statement) (vitess.Statement, error) {
|
||||
return nodeDropFunction(ctx, stmt)
|
||||
case *tree.DropIndex:
|
||||
return nodeDropIndex(ctx, stmt)
|
||||
case *tree.DropOperator:
|
||||
return nodeDropOperator(ctx, stmt)
|
||||
case *tree.DropProcedure:
|
||||
return nodeDropProcedure(ctx, stmt)
|
||||
case *tree.DropRole:
|
||||
|
||||
@@ -20,6 +20,9 @@ import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeCreateAggregate handles *tree.CreateAggregate nodes.
|
||||
@@ -27,14 +30,91 @@ func nodeCreateAggregate(ctx *Context, node *tree.CreateAggregate) (vitess.State
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !ignoreUnsupportedStatements {
|
||||
if err := validateAggArgMode(ctx, node.Args, node.OrderByArgs); err != nil {
|
||||
if err := validateAggArgMode(ctx, node.Args, node.OrderByArgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(node.OrderByArgs) > 0 {
|
||||
return NotYetSupportedError("ordered-set aggregates are not yet supported")
|
||||
}
|
||||
var argTypeRefs []tree.ResolvableTypeReference
|
||||
if node.Args != nil {
|
||||
for _, arg := range node.Args {
|
||||
if arg.Mode == tree.RoutineArgModeVariadic {
|
||||
return NotYetSupportedError("VARIADIC aggregates are not yet supported")
|
||||
}
|
||||
argTypeRefs = append(argTypeRefs, arg.Type)
|
||||
}
|
||||
} else {
|
||||
argTypeRefs = append(argTypeRefs, node.BaseType)
|
||||
}
|
||||
argTypes := make([]*pgtypes.DoltgresType, len(argTypeRefs))
|
||||
for i, argTypeRef := range argTypeRefs {
|
||||
_, argType, err := nodeResolvableTypeReference(ctx, argTypeRef, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argTypes[i] = argType
|
||||
}
|
||||
|
||||
return NotYetSupportedError("CREATE AGGREGATE is not yet supported")
|
||||
_, sType, err := nodeResolvableTypeReference(ctx, node.SType, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var finalFunc, combineFunc *tree.UnresolvedObjectName
|
||||
var initCond string
|
||||
var hasInitCond bool
|
||||
for _, option := range node.AggOptions {
|
||||
switch option.Option {
|
||||
case tree.AggOptTypeFinalFunc:
|
||||
finalFunc = option.FuncName
|
||||
case tree.AggOptTypeCombineFunc:
|
||||
combineFunc = option.FuncName
|
||||
case tree.AggOptTypeInitCond:
|
||||
if strVal, ok := option.CondVal.(*tree.StrVal); ok {
|
||||
initCond = strVal.RawString()
|
||||
} else {
|
||||
initCond = tree.AsString(option.CondVal)
|
||||
}
|
||||
hasInitCond = true
|
||||
default:
|
||||
return NotYetSupportedError("the given aggregate option is not yet supported")
|
||||
}
|
||||
}
|
||||
var finalFuncSchema, finalFuncName string
|
||||
if finalFunc != nil {
|
||||
finalFuncTableName := finalFunc.ToTableName()
|
||||
finalFuncSchema = finalFuncTableName.Schema()
|
||||
finalFuncName = finalFuncTableName.Table()
|
||||
}
|
||||
var combineFuncSchema, combineFuncName string
|
||||
if combineFunc != nil {
|
||||
combineFuncTableName := combineFunc.ToTableName()
|
||||
combineFuncSchema = combineFuncTableName.Schema()
|
||||
combineFuncName = combineFuncTableName.Table()
|
||||
}
|
||||
name := node.Name.ToTableName()
|
||||
sFuncName := node.SFunc.ToTableName()
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCreateAggregate(
|
||||
name.Schema(),
|
||||
name.Table(),
|
||||
node.Replace,
|
||||
argTypes,
|
||||
sType,
|
||||
sFuncName.Schema(),
|
||||
sFuncName.Table(),
|
||||
finalFuncSchema,
|
||||
finalFuncName,
|
||||
combineFuncSchema,
|
||||
combineFuncName,
|
||||
initCond,
|
||||
hasInitCond,
|
||||
),
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_CREATE,
|
||||
TargetType: auth.AuthTargetType_TODO,
|
||||
TargetNames: []string{},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validateAggArgMode checks routine arguments for `OUT` and `INOUT` modes,
|
||||
@@ -42,12 +122,12 @@ func nodeCreateAggregate(ctx *Context, node *tree.CreateAggregate) (vitess.State
|
||||
func validateAggArgMode(ctx *Context, args, orderByArgs tree.RoutineArgs) error {
|
||||
for _, sig := range args {
|
||||
if sig.Mode == tree.RoutineArgModeOut || sig.Mode == tree.RoutineArgModeInout {
|
||||
return errors.Errorf("aggregate functions do not support OUT or INOUT arguments")
|
||||
return errors.Errorf("aggregates cannot have output arguments")
|
||||
}
|
||||
}
|
||||
for _, sig := range orderByArgs {
|
||||
if sig.Mode == tree.RoutineArgModeOut || sig.Mode == tree.RoutineArgModeInout {
|
||||
return errors.Errorf("aggregate functions do not support OUT or INOUT arguments")
|
||||
return errors.Errorf("aggregates cannot have output arguments")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeCreateOperator handles *tree.CreateOperator nodes.
|
||||
func nodeCreateOperator(ctx *Context, node *tree.CreateOperator) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var function *tree.UnresolvedObjectName
|
||||
var leftArg, rightArg tree.ResolvableTypeReference
|
||||
var commutator, negator string
|
||||
var hashes, merges bool
|
||||
for _, option := range node.Options {
|
||||
switch option.Option {
|
||||
case tree.OperatorOptTypeFunction:
|
||||
function = option.FuncName
|
||||
case tree.OperatorOptTypeLeftArg:
|
||||
leftArg = option.TypeVal
|
||||
case tree.OperatorOptTypeRightArg:
|
||||
rightArg = option.TypeVal
|
||||
case tree.OperatorOptTypeCommutator:
|
||||
commutator = tree.OperatorSymbol(option.OpVal)
|
||||
case tree.OperatorOptTypeNegator:
|
||||
negator = tree.OperatorSymbol(option.OpVal)
|
||||
case tree.OperatorOptTypeRestrict:
|
||||
return NotYetSupportedError("RESTRICT is not yet supported")
|
||||
case tree.OperatorOptTypeJoin:
|
||||
return NotYetSupportedError("JOIN is not yet supported")
|
||||
case tree.OperatorOptTypeHashes:
|
||||
hashes = true
|
||||
case tree.OperatorOptTypeMerges:
|
||||
merges = true
|
||||
}
|
||||
}
|
||||
if function == nil {
|
||||
return nil, errors.New("operator function must be specified")
|
||||
}
|
||||
if leftArg == nil && rightArg == nil {
|
||||
return nil, errors.New("operator argument types must be specified")
|
||||
}
|
||||
if rightArg == nil {
|
||||
return nil, errors.New("operator right argument type must be specified")
|
||||
}
|
||||
var err error
|
||||
var leftType *pgtypes.DoltgresType
|
||||
if leftArg != nil {
|
||||
_, leftType, err = nodeResolvableTypeReference(ctx, leftArg, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_, rightType, err := nodeResolvableTypeReference(ctx, rightArg, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
funcName := function.ToTableName()
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCreateOperator(
|
||||
tree.OperatorSymbol(node.Name),
|
||||
leftType,
|
||||
rightType,
|
||||
funcName.Schema(),
|
||||
funcName.Table(),
|
||||
commutator,
|
||||
negator,
|
||||
hashes,
|
||||
merges,
|
||||
),
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_CREATE,
|
||||
TargetType: auth.AuthTargetType_TODO,
|
||||
TargetNames: []string{},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -15,9 +15,13 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeDropAggregate handles *tree.DropAggregate nodes.
|
||||
@@ -25,14 +29,37 @@ func nodeDropAggregate(ctx *Context, node *tree.DropAggregate) (vitess.Statement
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !ignoreUnsupportedStatements {
|
||||
for _, agg := range node.Aggregates {
|
||||
if err := validateAggArgMode(ctx, agg.AggSig.Args, agg.AggSig.OrderByArgs); err != nil {
|
||||
if node.DropBehavior == tree.DropCascade {
|
||||
return nil, fmt.Errorf("DROP AGGREGATE with CASCADE is not supported yet")
|
||||
}
|
||||
aggs := make([]*pgnodes.AggregateToDrop, len(node.Aggregates))
|
||||
for i, agg := range node.Aggregates {
|
||||
if err := validateAggArgMode(ctx, agg.AggSig.Args, agg.AggSig.OrderByArgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if agg.AggSig.All {
|
||||
return NotYetSupportedError("DROP AGGREGATE with a * signature is not yet supported")
|
||||
}
|
||||
if len(agg.AggSig.OrderByArgs) > 0 {
|
||||
return NotYetSupportedError("ordered-set aggregates are not yet supported")
|
||||
}
|
||||
argTypes := make([]*pgtypes.DoltgresType, len(agg.AggSig.Args))
|
||||
for j, arg := range agg.AggSig.Args {
|
||||
_, argType, err := nodeResolvableTypeReference(ctx, arg.Type, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argTypes[j] = argType
|
||||
}
|
||||
name := agg.Name.ToTableName()
|
||||
aggs[i] = &pgnodes.AggregateToDrop{
|
||||
SchemaName: name.Schema(),
|
||||
Name: name.Table(),
|
||||
ArgTypes: argTypes,
|
||||
}
|
||||
}
|
||||
|
||||
return NotYetSupportedError("DROP AGGREGATE is not yet supported")
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropAggregate(node.IfExists, aggs),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeDropOperator handles *tree.DropOperator nodes.
|
||||
func nodeDropOperator(ctx *Context, node *tree.DropOperator) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if node.DropBehavior == tree.DropCascade {
|
||||
return nil, fmt.Errorf("DROP OPERATOR with CASCADE is not supported yet")
|
||||
}
|
||||
ops := make([]*pgnodes.OperatorToDrop, len(node.Operators))
|
||||
for i, op := range node.Operators {
|
||||
if op.Right == nil {
|
||||
return nil, errors.New("postfix operators are not supported")
|
||||
}
|
||||
var err error
|
||||
var leftType *pgtypes.DoltgresType
|
||||
if op.Left != nil {
|
||||
_, leftType, err = nodeResolvableTypeReference(ctx, op.Left, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_, rightType, err := nodeResolvableTypeReference(ctx, op.Right, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops[i] = &pgnodes.OperatorToDrop{
|
||||
Symbol: tree.OperatorSymbol(op.Op),
|
||||
Left: leftType,
|
||||
Right: rightType,
|
||||
}
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropOperator(node.IfExists, ops),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -195,6 +195,18 @@ func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) {
|
||||
operator = framework.Operator_BinaryJSONExtractPathJson
|
||||
case tree.JSONFetchTextPath:
|
||||
operator = framework.Operator_BinaryJSONExtractPathText
|
||||
case tree.L2Distance:
|
||||
operator = framework.Operator_BinaryL2Distance
|
||||
case tree.L1Distance:
|
||||
operator = framework.Operator_BinaryL1Distance
|
||||
case tree.CosineDistance:
|
||||
operator = framework.Operator_BinaryCosineDistance
|
||||
case tree.NegInnerProduct:
|
||||
operator = framework.Operator_BinaryNegInnerProduct
|
||||
case tree.JaccardDistance:
|
||||
operator = framework.Operator_BinaryJaccardDistance
|
||||
case tree.HammingDistance:
|
||||
operator = framework.Operator_BinaryHammingDistance
|
||||
default:
|
||||
return nil, errors.Errorf("the binary operator used is not yet supported")
|
||||
}
|
||||
|
||||
@@ -21,9 +21,12 @@ import (
|
||||
"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"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// BinaryOperator represents a VALUE OPERATOR VALUE expression.
|
||||
@@ -37,6 +40,7 @@ var _ sql.Expression = (*BinaryOperator)(nil)
|
||||
var _ expression.BinaryExpression = (*BinaryOperator)(nil)
|
||||
var _ expression.Equality = (*BinaryOperator)(nil)
|
||||
var _ sql.IndexComparisonExpression = (*BinaryOperator)(nil)
|
||||
var _ procedures.InterpreterExpr = (*BinaryOperator)(nil)
|
||||
|
||||
// NewBinaryOperator returns a new *BinaryOperator.
|
||||
func NewBinaryOperator(operator framework.Operator) *BinaryOperator {
|
||||
@@ -68,6 +72,18 @@ func (b *BinaryOperator) Resolved() bool {
|
||||
return b.compiledFunc.Resolved()
|
||||
}
|
||||
|
||||
// SetStatementRunner implements the procedures.InterpreterExpr interface.
|
||||
func (b *BinaryOperator) SetStatementRunner(ctx *sql.Context, runner sql.StatementRunner) sql.Expression {
|
||||
interpreterExpr, ok := b.compiledFunc.(procedures.InterpreterExpr)
|
||||
if !ok {
|
||||
return b
|
||||
}
|
||||
return &BinaryOperator{
|
||||
operator: b.operator,
|
||||
compiledFunc: interpreterExpr.SetStatementRunner(ctx, runner).(framework.Function),
|
||||
}
|
||||
}
|
||||
|
||||
// String implements the sql.Expression interface.
|
||||
func (b *BinaryOperator) String() string {
|
||||
if b.compiledFunc == nil {
|
||||
@@ -144,7 +160,21 @@ func (b *BinaryOperator) WithResolvedChildren(ctx context.Context, children []an
|
||||
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[1])
|
||||
}
|
||||
funcName := "internal_binary_operator_func_" + b.operator.String()
|
||||
compiledFunc := framework.GetBinaryFunction(b.operator).Compile(sqlCtx, funcName, left, right)
|
||||
var compiledFunc framework.Function
|
||||
builtInFunc := framework.GetBinaryFunction(b.operator).Compile(sqlCtx, funcName, left, right)
|
||||
if builtInFunc != nil && builtInFunc.StashedError() == nil {
|
||||
compiledFunc = builtInFunc
|
||||
} else {
|
||||
userFunc, err := getUserDefinedOperator(sqlCtx, b.operator, left, right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userFunc != nil {
|
||||
compiledFunc = userFunc
|
||||
} else if builtInFunc != nil {
|
||||
compiledFunc = builtInFunc
|
||||
}
|
||||
}
|
||||
if compiledFunc == nil {
|
||||
return nil, errors.Errorf("operator does not exist: %s %s %s",
|
||||
left.Type(sqlCtx).String(), b.operator.String(), right.Type(sqlCtx).String())
|
||||
@@ -219,3 +249,36 @@ func unwrapIndexScanTarget(expr sql.Expression) sql.Expression {
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
// getUserDefinedOperator returns the function for the user-defined operator matching the given operands. Returns nil if
|
||||
// an operator is not found.
|
||||
func getUserDefinedOperator(ctx *sql.Context, operator framework.Operator, left sql.Expression, right sql.Expression) (framework.Function, error) {
|
||||
leftType, ok := left.Type(ctx).(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
rightType, ok := right.Type(ctx).(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
operatorCollection, err := core.GetOperatorsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
searchPath, err := core.SearchPath(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
op, ok, err := operatorCollection.ResolveOperator(ctx, searchPath, operator.String(), leftType.ID, rightType.ID)
|
||||
if err != nil || !ok {
|
||||
return nil, err
|
||||
}
|
||||
userFunc, err := framework.GetUserFunction(ctx, op.Function.SchemaName(), op.Function.FunctionName(), left, right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userFunc == nil {
|
||||
return nil, sql.ErrFunctionNotFound.New(op.Function.FunctionName())
|
||||
}
|
||||
return userFunc, nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ package extdef
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// Function is the Go implementation of a single function that an extension provides.
|
||||
@@ -35,9 +38,26 @@ type Control struct {
|
||||
|
||||
// Extension is a Postgres extension that Doltgres emulates.
|
||||
type Extension struct {
|
||||
Name string
|
||||
Control Control
|
||||
Routines []Routine
|
||||
Name string
|
||||
Control Control
|
||||
Types []Type
|
||||
Routines []Routine
|
||||
Operators []Operator
|
||||
Casts []Cast
|
||||
Aggregates []Aggregate
|
||||
}
|
||||
|
||||
// Type is a base type that an extension provides. Definition carries every option except the support functions, which
|
||||
// are named here.
|
||||
type Type struct {
|
||||
Name string
|
||||
Definition pgtypes.BaseTypeDefinition
|
||||
Input string
|
||||
Output string
|
||||
Receive string
|
||||
Send string
|
||||
ModIn string
|
||||
ModOut string
|
||||
}
|
||||
|
||||
// Routine is a function that an extension provides. Symbol is its C link symbol, which is unique within the extension.
|
||||
@@ -55,3 +75,36 @@ type Parameter struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
// Operator is an operator that an extension provides.
|
||||
type Operator struct {
|
||||
Symbol string
|
||||
Left string
|
||||
Right string
|
||||
Routine string
|
||||
Commutator string
|
||||
Negator string
|
||||
Hashes bool
|
||||
Merges bool
|
||||
}
|
||||
|
||||
// Cast is a cast that an extension provides.
|
||||
type Cast struct {
|
||||
Source string
|
||||
Target string
|
||||
Routine string
|
||||
CastType casts.CastType
|
||||
}
|
||||
|
||||
// Aggregate is an aggregate function that an extension provides.
|
||||
type Aggregate struct {
|
||||
Name string
|
||||
Parameters []Parameter
|
||||
Returns string
|
||||
StateType string
|
||||
Transition string
|
||||
Final string
|
||||
Combine string
|
||||
InitCond string
|
||||
HasInitCond bool
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ var implementations = map[string]map[string]extdef.Function{}
|
||||
|
||||
// Init adds every emulated extension to the registry, making them installable through CREATE EXTENSION.
|
||||
func Init() {
|
||||
register(uuid_ossp.Extension())
|
||||
Register(uuid_ossp.Extension())
|
||||
}
|
||||
|
||||
// register adds the given extension to the registry.
|
||||
func register(ext *extdef.Extension) {
|
||||
// Register adds the given extension to the registry.
|
||||
func Register(ext *extdef.Extension) {
|
||||
if _, ok := registry[ext.Name]; ok {
|
||||
panic(errors.Errorf(`extension "%s" has already been registered`, ext.Name))
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func testFunction(ctx *sql.Context, args ...any) (any, error) {
|
||||
}
|
||||
|
||||
func TestRegistry(t *testing.T) {
|
||||
register(&extdef.Extension{
|
||||
Register(&extdef.Extension{
|
||||
Name: "doltgres_test",
|
||||
Control: extdef.Control{DefaultVersion: "2.5", Comment: "a test extension", Relocatable: true},
|
||||
Routines: []extdef.Routine{{Name: "alpha", Symbol: "alpha", Returns: "uuid", Impl: testFunction}},
|
||||
@@ -61,11 +61,11 @@ func TestRegistry(t *testing.T) {
|
||||
|
||||
// Registering the same extension twice would silently replace the first, so it panics instead
|
||||
require.Panics(t, func() {
|
||||
register(&extdef.Extension{Name: "doltgres_test", Control: extdef.Control{DefaultVersion: "2.5"}})
|
||||
Register(&extdef.Extension{Name: "doltgres_test", Control: extdef.Control{DefaultVersion: "2.5"}})
|
||||
})
|
||||
// A symbol may only back one routine, since it is what dispatches a call to its implementation
|
||||
require.Panics(t, func() {
|
||||
register(&extdef.Extension{
|
||||
Register(&extdef.Extension{
|
||||
Name: "doltgres_test_symbols",
|
||||
Routines: []extdef.Routine{
|
||||
{Name: "alpha", Symbol: "alpha", Impl: testFunction},
|
||||
|
||||
@@ -58,7 +58,10 @@ func (d dummyExpression) WithChildren(ctx *sql.Context, children ...sql.Expressi
|
||||
|
||||
// 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 {
|
||||
func getQuickFunctionForTypes(ctx *sql.Context, schemaName string, functionName string, params []*pgtypes.DoltgresType) any {
|
||||
if schemaName != "pg_catalog" {
|
||||
return getQuickFunctionFromProvider(ctx, schemaName, functionName, params)
|
||||
}
|
||||
exprs := make([]sql.Expression, len(params))
|
||||
for i := range params {
|
||||
exprs[i] = dummyExpression{t: params[i]}
|
||||
@@ -69,3 +72,12 @@ func getQuickFunctionForTypes(ctx *sql.Context, functionName string, params []*p
|
||||
}
|
||||
return cf.GetQuickFunction(ctx)
|
||||
}
|
||||
|
||||
// getQuickFunctionFromProvider resolves a user-defined function. Returns nil if it could not be resolved.
|
||||
func getQuickFunctionFromProvider(ctx *sql.Context, schemaName string, functionName string, params []*pgtypes.DoltgresType) any {
|
||||
call := NewUserFunctionCall(ctx, schemaName, functionName, params)
|
||||
if call == nil {
|
||||
return nil
|
||||
}
|
||||
return &quickUserFunction{call: call}
|
||||
}
|
||||
|
||||
@@ -469,7 +469,11 @@ func (c *CompiledFunction) Eval(ctx *sql.Context, row sql.Row) (interface{}, err
|
||||
|
||||
args = c.overload.params.coalesceVariadicValues(args)
|
||||
|
||||
// Call the function
|
||||
return c.callFunction(ctx, args)
|
||||
}
|
||||
|
||||
// callFunction invokes the resolved overload with the given argument values.
|
||||
func (c *CompiledFunction) callFunction(ctx *sql.Context, args []any) (interface{}, error) {
|
||||
switch f := c.overload.Function().(type) {
|
||||
case Function0:
|
||||
return f.Callable(ctx)
|
||||
|
||||
@@ -50,6 +50,12 @@ const (
|
||||
Operator_BinaryJSONTopLevel // ?
|
||||
Operator_BinaryJSONTopLevelAny // ?|
|
||||
Operator_BinaryJSONTopLevelAll // ?&
|
||||
Operator_BinaryL2Distance // <->
|
||||
Operator_BinaryL1Distance // <+>
|
||||
Operator_BinaryCosineDistance // <=>
|
||||
Operator_BinaryNegInnerProduct // <#>
|
||||
Operator_BinaryJaccardDistance // <%>
|
||||
Operator_BinaryHammingDistance // <~>
|
||||
Operator_UnaryPlus // +
|
||||
Operator_UnaryMinus // -
|
||||
// NOTE: Any new operator should also be added to Operator.String() and GetOperatorFromString() functions.
|
||||
@@ -199,6 +205,18 @@ func (o Operator) String() string {
|
||||
return "?|"
|
||||
case Operator_BinaryJSONTopLevelAll:
|
||||
return "?&"
|
||||
case Operator_BinaryL2Distance:
|
||||
return "<->"
|
||||
case Operator_BinaryL1Distance:
|
||||
return "<+>"
|
||||
case Operator_BinaryCosineDistance:
|
||||
return "<=>"
|
||||
case Operator_BinaryNegInnerProduct:
|
||||
return "<#>"
|
||||
case Operator_BinaryJaccardDistance:
|
||||
return "<%>"
|
||||
case Operator_BinaryHammingDistance:
|
||||
return "<~>"
|
||||
default:
|
||||
return "unknown operator"
|
||||
}
|
||||
@@ -276,6 +294,18 @@ func GetOperatorFromString(op string) (Operator, error) {
|
||||
return Operator_BinaryJSONTopLevelAny, nil
|
||||
case "?&":
|
||||
return Operator_BinaryJSONTopLevelAll, nil
|
||||
case "<->":
|
||||
return Operator_BinaryL2Distance, nil
|
||||
case "<+>":
|
||||
return Operator_BinaryL1Distance, nil
|
||||
case "<=>":
|
||||
return Operator_BinaryCosineDistance, nil
|
||||
case "<#>":
|
||||
return Operator_BinaryNegInnerProduct, nil
|
||||
case "<%>":
|
||||
return Operator_BinaryJaccardDistance, nil
|
||||
case "<~>":
|
||||
return Operator_BinaryHammingDistance, nil
|
||||
default:
|
||||
return 0, errors.Errorf("unhandled Operator `%s`", op)
|
||||
}
|
||||
|
||||
@@ -42,13 +42,21 @@ func (fp *FunctionProvider) Function(ctx *sql.Context, schema, name string) (sql
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
aggCollection, err := core.GetAggregatesCollectionFromContext(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 {
|
||||
aggOverloads, err := aggCollection.GetAggregateOverloads(ctx, funcName)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(overloads) == 0 && len(aggOverloads) == 0 {
|
||||
if schema == "" {
|
||||
currentSchema, err := core.GetCurrentSchema(ctx)
|
||||
if err != nil {
|
||||
@@ -61,7 +69,11 @@ func (fp *FunctionProvider) Function(ctx *sql.Context, schema, name string) (sql
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(overloads) == 0 {
|
||||
aggOverloads, err = aggCollection.GetAggregateOverloads(ctx, funcName)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(overloads) == 0 && len(aggOverloads) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
@@ -124,6 +136,46 @@ func (fp *FunctionProvider) Function(ctx *sql.Context, schema, name string) (sql
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, aggOverload := range aggOverloads {
|
||||
stateType, err := typesCollection.GetType(ctx, aggOverload.SType)
|
||||
if err != nil || stateType == nil {
|
||||
return nil, false
|
||||
}
|
||||
returnType, err := typesCollection.GetType(ctx, aggOverload.ReturnType)
|
||||
if err != nil || returnType == nil {
|
||||
return nil, false
|
||||
}
|
||||
paramTypes := make([]*pgtypes.DoltgresType, aggOverload.ID.ParameterCount())
|
||||
for i, param := range aggOverload.ID.Parameters() {
|
||||
paramTypes[i], err = typesCollection.GetType(ctx, param)
|
||||
if err != nil || paramTypes[i] == nil {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if err = overloadTree.Add(UserAggregate{
|
||||
ID: aggOverload.ID,
|
||||
ReturnType: returnType,
|
||||
ParameterTypes: paramTypes,
|
||||
StateType: stateType,
|
||||
SFunc: aggOverload.SFunc,
|
||||
FinalFunc: aggOverload.FinalFunc,
|
||||
InitCond: aggOverload.InitCond,
|
||||
HasInitCond: aggOverload.HasInitCond,
|
||||
}); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if err = addBuiltInOverloads(overloadTree, name); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(aggOverloads) > 0 || len(AggregateCatalog[name]) > 0 {
|
||||
return sql.FunctionN{
|
||||
Name: name,
|
||||
Fn: func(ctx *sql.Context, params ...sql.Expression) (sql.Expression, error) {
|
||||
return NewCompiledAggregateFunction(ctx, name, params, overloadTree), nil
|
||||
},
|
||||
}, true
|
||||
}
|
||||
return sql.FunctionN{
|
||||
Name: name,
|
||||
Fn: func(ctx *sql.Context, params ...sql.Expression) (sql.Expression, error) {
|
||||
@@ -131,3 +183,25 @@ func (fp *FunctionProvider) Function(ctx *sql.Context, schema, name string) (sql
|
||||
},
|
||||
}, true
|
||||
}
|
||||
|
||||
// addBuiltInOverloads adds the built-in overloads of the given name to the tree, skipping any signature that a
|
||||
// user-defined overload has already taken.
|
||||
func addBuiltInOverloads(overloadTree *Overloads, name string) error {
|
||||
for _, builtIn := range Catalog[name] {
|
||||
if _, ok := overloadTree.ByParamType[keyForParamTypes(builtIn.GetInputParameterTypes())]; ok {
|
||||
continue
|
||||
}
|
||||
if err := overloadTree.Add(builtIn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, builtIn := range AggregateCatalog[name] {
|
||||
if _, ok := overloadTree.ByParamType[keyForParamTypes(builtIn.GetInputParameterTypes())]; ok {
|
||||
continue
|
||||
}
|
||||
if err := overloadTree.Add(builtIn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -439,3 +439,27 @@ func (q *QuickFunction3) WithChildren(ctx *sql.Context, children ...sql.Expressi
|
||||
|
||||
// specificFuncImpl implements the interface sql.Expression.
|
||||
func (*QuickFunction3) specificFuncImpl() {}
|
||||
|
||||
// quickUserFunction adapts a user-defined function to the QuickFunction interface.
|
||||
type quickUserFunction struct {
|
||||
call *UserFunctionCall
|
||||
}
|
||||
|
||||
var _ pgtypes.QuickFunction = (*quickUserFunction)(nil)
|
||||
|
||||
// CallVariadic implements the interface pgtypes.QuickFunction.
|
||||
func (q *quickUserFunction) CallVariadic(ctx *sql.Context, args ...any) (interface{}, error) {
|
||||
return q.call.Call(ctx, args...)
|
||||
}
|
||||
|
||||
// ResolvedTypes implements the interface pgtypes.QuickFunction.
|
||||
func (q *quickUserFunction) ResolvedTypes() []*pgtypes.DoltgresType {
|
||||
return q.call.compiled.callResolved
|
||||
}
|
||||
|
||||
// WithResolvedTypes implements the interface pgtypes.QuickFunction.
|
||||
func (q *quickUserFunction) WithResolvedTypes(newTypes []*pgtypes.DoltgresType) any {
|
||||
newCompiled := *q.call.compiled
|
||||
newCompiled.callResolved = newTypes
|
||||
return &quickUserFunction{call: &UserFunctionCall{compiled: &newCompiled, strict: q.call.strict}}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// UserAggregate is the implementation of a user-defined aggregate.
|
||||
type UserAggregate struct {
|
||||
ID id.Function
|
||||
ReturnType *pgtypes.DoltgresType
|
||||
ParameterTypes []*pgtypes.DoltgresType
|
||||
StateType *pgtypes.DoltgresType
|
||||
SFunc id.Function
|
||||
FinalFunc id.Function
|
||||
InitCond string
|
||||
HasInitCond bool
|
||||
}
|
||||
|
||||
var _ AggregateFunctionInterface = UserAggregate{}
|
||||
|
||||
// GetExpectedParameterCount implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) GetExpectedParameterCount() int {
|
||||
return len(agg.ParameterTypes)
|
||||
}
|
||||
|
||||
// GetName implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) GetName() string {
|
||||
return agg.ID.FunctionName()
|
||||
}
|
||||
|
||||
// GetOutParameters implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) GetOutParameters() sql.Schema {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInputParameterTypes implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) GetInputParameterTypes() []*pgtypes.DoltgresType {
|
||||
return agg.ParameterTypes
|
||||
}
|
||||
|
||||
// GetReturn implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) GetReturn() *pgtypes.DoltgresType {
|
||||
return agg.ReturnType
|
||||
}
|
||||
|
||||
// InternalID implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) InternalID() id.Id {
|
||||
return agg.ID.AsId()
|
||||
}
|
||||
|
||||
// IsCVariadic implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) IsCVariadic() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsSRF implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) IsSRF() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsStrict implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) IsStrict() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// NonDeterministic implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) NonDeterministic() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// VariadicIndex implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) VariadicIndex() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// NewBuffer implements the interface AggregateFunctionInterface.
|
||||
func (agg UserAggregate) NewBuffer(exprs []sql.Expression) (sql.AggregationBuffer, error) {
|
||||
return &userAggregateBuffer{aggregate: agg, arguments: exprs}, nil
|
||||
}
|
||||
|
||||
// NewWindowFunc implements the interface AggregateFunctionInterface.
|
||||
func (agg UserAggregate) NewWindowFunc() NewWindowFunctionFn {
|
||||
//TODO: support user-defined aggregates within an OVER(...) clause
|
||||
return nil
|
||||
}
|
||||
|
||||
// enforceInterfaceInheritance implements the interface FunctionInterface.
|
||||
func (agg UserAggregate) enforceInterfaceInheritance(error) {}
|
||||
|
||||
// userAggregateBuffer accumulates the transition state of a UserAggregate over the rows of a group.
|
||||
type userAggregateBuffer struct {
|
||||
aggregate UserAggregate
|
||||
arguments []sql.Expression
|
||||
sFunc *UserFunctionCall
|
||||
finalFunc *UserFunctionCall
|
||||
state any
|
||||
stateExists bool
|
||||
}
|
||||
|
||||
var _ sql.AggregationBuffer = (*userAggregateBuffer)(nil)
|
||||
|
||||
// Dispose implements the interface sql.AggregationBuffer.
|
||||
func (b *userAggregateBuffer) Dispose(ctx *sql.Context) {}
|
||||
|
||||
// Eval implements the interface sql.AggregationBuffer.
|
||||
func (b *userAggregateBuffer) Eval(ctx *sql.Context) (interface{}, error) {
|
||||
if err := b.resolve(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.finalFunc == nil {
|
||||
return b.state, nil
|
||||
}
|
||||
return b.finalFunc.Call(ctx, b.state)
|
||||
}
|
||||
|
||||
// Update implements the interface sql.AggregationBuffer.
|
||||
func (b *userAggregateBuffer) Update(ctx *sql.Context, row sql.Row) error {
|
||||
if err := b.resolve(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
args := make([]any, len(b.arguments)+1)
|
||||
for i, argument := range b.arguments {
|
||||
val, err := argument.Eval(ctx, row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val == nil && b.sFunc.strict {
|
||||
return nil
|
||||
}
|
||||
args[i+1] = val
|
||||
}
|
||||
if b.sFunc.strict && !b.stateExists {
|
||||
b.state = args[1]
|
||||
b.stateExists = true
|
||||
return nil
|
||||
}
|
||||
args[0] = b.state
|
||||
newState, err := b.sFunc.Call(ctx, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.state = newState
|
||||
b.stateExists = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolve loads the transition and final functions.
|
||||
func (b *userAggregateBuffer) resolve(ctx *sql.Context) error {
|
||||
if b.sFunc != nil {
|
||||
return nil
|
||||
}
|
||||
sFuncParams := append([]*pgtypes.DoltgresType{b.aggregate.StateType}, b.aggregate.ParameterTypes...)
|
||||
b.sFunc = NewUserFunctionCall(ctx, b.aggregate.SFunc.SchemaName(), b.aggregate.SFunc.FunctionName(), sFuncParams)
|
||||
if b.sFunc == nil {
|
||||
return ErrFunctionDoesNotExist.New(b.aggregate.SFunc.DisplayString())
|
||||
}
|
||||
if b.aggregate.FinalFunc.IsValid() {
|
||||
b.finalFunc = NewUserFunctionCall(ctx, b.aggregate.FinalFunc.SchemaName(), b.aggregate.FinalFunc.FunctionName(),
|
||||
[]*pgtypes.DoltgresType{b.aggregate.StateType})
|
||||
if b.finalFunc == nil {
|
||||
return ErrFunctionDoesNotExist.New(b.aggregate.FinalFunc.DisplayString())
|
||||
}
|
||||
}
|
||||
if b.aggregate.HasInitCond {
|
||||
initCond, err := b.aggregate.StateType.IoInput(ctx, b.aggregate.InitCond)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.state = initCond
|
||||
b.stateExists = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// UserFunctionCall is a resolved call to a user-defined function that takes argument values rather than argument
|
||||
// expressions.
|
||||
type UserFunctionCall struct {
|
||||
compiled *CompiledFunction
|
||||
strict bool
|
||||
}
|
||||
|
||||
// GetUserFunction returns the compiled call to the named function with the given argument expressions. Returns nil if
|
||||
// the function does not exist.
|
||||
func GetUserFunction(ctx *sql.Context, schemaName string, functionName string, args ...sql.Expression) (Function, error) {
|
||||
sqlFunc, ok := (&FunctionProvider{}).Function(ctx, schemaName, functionName)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
functionN, ok := sqlFunc.(sql.FunctionN)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
expr, err := functionN.Fn(ctx, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, ok := expr.(Function)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("function `%s` has an unexpected type: %T", functionName, expr)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// NewUserFunctionCall resolves the named function for the given parameter types. Returns nil if no overload matches.
|
||||
func NewUserFunctionCall(ctx *sql.Context, schemaName string, functionName string, paramTypes []*pgtypes.DoltgresType) *UserFunctionCall {
|
||||
exprs := make([]sql.Expression, len(paramTypes))
|
||||
for i := range paramTypes {
|
||||
exprs[i] = dummyExpression{t: paramTypes[i]}
|
||||
}
|
||||
f, err := GetUserFunction(ctx, schemaName, functionName, exprs...)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
compiled, ok := f.(*CompiledFunction)
|
||||
if !ok || !compiled.Resolved() || !compiled.overload.Valid() {
|
||||
return nil
|
||||
}
|
||||
if runner, err := core.GetRunnerFromContext(ctx); err == nil && runner != nil {
|
||||
compiled = compiled.SetStatementRunner(ctx, runner).(*CompiledFunction)
|
||||
}
|
||||
return &UserFunctionCall{compiled: compiled, strict: compiled.IsStrict()}
|
||||
}
|
||||
|
||||
// Call invokes the function with the given argument values.
|
||||
func (u *UserFunctionCall) Call(ctx *sql.Context, args ...any) (any, error) {
|
||||
if u.strict {
|
||||
for _, arg := range args {
|
||||
if arg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return u.compiled.callFunction(ctx, args)
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// 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 node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/aggregates"
|
||||
"github.com/dolthub/doltgresql/core/functions"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// CreateAggregate implements CREATE AGGREGATE.
|
||||
type CreateAggregate struct {
|
||||
SchemaName string
|
||||
Name string
|
||||
Replace bool
|
||||
ArgTypes []*pgtypes.DoltgresType
|
||||
SType *pgtypes.DoltgresType
|
||||
SFuncSchema string
|
||||
SFuncName string
|
||||
FinalFuncSchema string
|
||||
FinalFuncName string
|
||||
CombineFuncSchema string
|
||||
CombineFuncName string
|
||||
InitCond string
|
||||
HasInitCond bool
|
||||
}
|
||||
|
||||
var _ sql.ExecSourceRel = (*CreateAggregate)(nil)
|
||||
var _ vitess.Injectable = (*CreateAggregate)(nil)
|
||||
|
||||
// NewCreateAggregate returns a new *CreateAggregate.
|
||||
func NewCreateAggregate(
|
||||
schemaName, name string,
|
||||
replace bool,
|
||||
argTypes []*pgtypes.DoltgresType,
|
||||
sType *pgtypes.DoltgresType,
|
||||
sFuncSchema, sFuncName string,
|
||||
finalFuncSchema, finalFuncName string,
|
||||
combineFuncSchema, combineFuncName string,
|
||||
initCond string,
|
||||
hasInitCond bool) *CreateAggregate {
|
||||
return &CreateAggregate{
|
||||
SchemaName: schemaName,
|
||||
Name: name,
|
||||
Replace: replace,
|
||||
ArgTypes: argTypes,
|
||||
SType: sType,
|
||||
SFuncSchema: sFuncSchema,
|
||||
SFuncName: sFuncName,
|
||||
FinalFuncSchema: finalFuncSchema,
|
||||
FinalFuncName: finalFuncName,
|
||||
CombineFuncSchema: combineFuncSchema,
|
||||
CombineFuncName: combineFuncName,
|
||||
InitCond: initCond,
|
||||
HasInitCond: hasInitCond,
|
||||
}
|
||||
}
|
||||
|
||||
// Children implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateAggregate) Children() []sql.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReadOnly implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateAggregate) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Resolved implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateAggregate) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RowIter implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateAggregate) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
funcCollection, err := core.GetFunctionsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argIDs := make([]id.Type, len(c.ArgTypes))
|
||||
argNames := make([]string, len(c.ArgTypes))
|
||||
for i, argType := range c.ArgTypes {
|
||||
argIDs[i] = argType.ID
|
||||
argNames[i] = argType.String()
|
||||
}
|
||||
sFuncID, err := c.lookupFunction(ctx, funcCollection, c.SFuncSchema, c.SFuncName,
|
||||
append([]id.Type{c.SType.ID}, argIDs...), append([]string{c.SType.String()}, argNames...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sFunc, err := funcCollection.GetFunction(ctx, sFuncID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sFunc.ReturnType != c.SType.ID {
|
||||
return nil, errors.Errorf("return type of transition function %s is not %s", c.SFuncName, c.SType.String())
|
||||
}
|
||||
returnType := c.SType.ID
|
||||
finalFuncID := id.NullFunction
|
||||
if c.FinalFuncName != "" {
|
||||
finalFuncID, err = c.lookupFunction(ctx, funcCollection, c.FinalFuncSchema, c.FinalFuncName,
|
||||
[]id.Type{c.SType.ID}, []string{c.SType.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalFunc, err := funcCollection.GetFunction(ctx, finalFuncID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
returnType = finalFunc.ReturnType
|
||||
}
|
||||
combineFuncID := id.NullFunction
|
||||
if c.CombineFuncName != "" {
|
||||
combineFuncID, err = c.lookupFunction(ctx, funcCollection, c.CombineFuncSchema, c.CombineFuncName,
|
||||
[]id.Type{c.SType.ID, c.SType.ID}, []string{c.SType.String(), c.SType.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
combineFunc, err := funcCollection.GetFunction(ctx, combineFuncID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if combineFunc.ReturnType != c.SType.ID {
|
||||
return nil, errors.Errorf("return type of combine function %s is not %s", c.CombineFuncName, c.SType.String())
|
||||
}
|
||||
}
|
||||
schemaName, err := core.GetSchemaName(ctx, nil, c.SchemaName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggCollection, err := core.GetAggregatesCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregateID := id.NewFunction(schemaName, c.Name, argIDs...)
|
||||
if funcCollection.HasFunction(ctx, aggregateID) {
|
||||
return nil, errors.Errorf(`function "%s" already exists with same argument types`, c.Name)
|
||||
}
|
||||
if aggCollection.HasAggregate(ctx, aggregateID) {
|
||||
if !c.Replace {
|
||||
return nil, errors.Errorf(`function "%s" already exists with same argument types`, c.Name)
|
||||
}
|
||||
if err = aggCollection.DropAggregate(ctx, aggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = aggCollection.AddAggregate(ctx, aggregates.Aggregate{
|
||||
ID: aggregateID,
|
||||
ReturnType: returnType,
|
||||
SFunc: sFuncID,
|
||||
SType: c.SType.ID,
|
||||
FinalFunc: finalFuncID,
|
||||
CombineFunc: combineFuncID,
|
||||
InitCond: c.InitCond,
|
||||
HasInitCond: c.HasInitCond,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
// lookupFunction returns the ID of the function with the given name and parameter types. Returns an error if no
|
||||
// function was found.
|
||||
func (c *CreateAggregate) lookupFunction(
|
||||
ctx *sql.Context,
|
||||
funcCollection *functions.Collection,
|
||||
schema string, name string,
|
||||
paramTypes []id.Type,
|
||||
paramNames []string) (id.Function, error) {
|
||||
funcSchema, err := core.GetSchemaName(ctx, nil, schema)
|
||||
if err != nil {
|
||||
return id.NullFunction, err
|
||||
}
|
||||
funcID := id.NewFunction(funcSchema, name, paramTypes...)
|
||||
if !funcCollection.HasFunction(ctx, funcID) {
|
||||
return id.NullFunction, errors.Errorf("function %s(%s) does not exist", name, strings.Join(paramNames, ", "))
|
||||
}
|
||||
return funcID, nil
|
||||
}
|
||||
|
||||
// Schema implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateAggregate) Schema(ctx *sql.Context) sql.Schema {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateAggregate) String() string {
|
||||
return fmt.Sprintf("CREATE AGGREGATE %s", c.Name)
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateAggregate) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
|
||||
return plan.NillaryWithChildren(c, children...)
|
||||
}
|
||||
|
||||
// WithResolvedChildren implements the interface vitess.Injectable.
|
||||
func (c *CreateAggregate) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
|
||||
if len(children) != 0 {
|
||||
return nil, ErrVitessChildCount.New(0, len(children))
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func (c *CreateDomain) Schema(ctx *sql.Context) sql.Schema {
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateDomain) String() string {
|
||||
return "CREATE DOMAIN"
|
||||
return fmt.Sprintf("CREATE DOMAIN %s", c.Name)
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
|
||||
@@ -16,6 +16,7 @@ package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
@@ -24,9 +25,12 @@ import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/aggregates"
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
coreextensions "github.com/dolthub/doltgresql/core/extensions"
|
||||
"github.com/dolthub/doltgresql/core/functions"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/operators"
|
||||
"github.com/dolthub/doltgresql/core/procedures"
|
||||
"github.com/dolthub/doltgresql/core/typecollection"
|
||||
"github.com/dolthub/doltgresql/server/extensions"
|
||||
@@ -98,7 +102,7 @@ func (c *CreateExtension) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = (extensionObjects{ext: ext, schemaName: schemaName}).materialize(ctx, typColl); err != nil {
|
||||
if err = (extensionObjects{ext: ext, schemaName: schemaName, typColl: typColl}).materialize(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = extCollection.AddLoadedExtension(ctx, coreextensions.Extension{
|
||||
@@ -120,7 +124,7 @@ func (c *CreateExtension) Schema(ctx *sql.Context) sql.Schema {
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateExtension) String() string {
|
||||
return "CREATE EXTENSION"
|
||||
return fmt.Sprintf("CREATE EXTENSION %s", c.Name)
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
@@ -140,10 +144,62 @@ func (c *CreateExtension) WithResolvedChildren(ctx context.Context, children []a
|
||||
type extensionObjects struct {
|
||||
ext *extdef.Extension
|
||||
schemaName string
|
||||
typColl *typecollection.TypeCollection
|
||||
}
|
||||
|
||||
// materialize writes every object that the extension declares.
|
||||
func (e extensionObjects) materialize(ctx *sql.Context, typColl *typecollection.TypeCollection) error {
|
||||
func (e extensionObjects) materialize(ctx *sql.Context) error {
|
||||
if err := e.materializeTypes(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.materializeRoutines(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.materializeOperators(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.materializeCasts(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.materializeAggregates(ctx)
|
||||
}
|
||||
|
||||
// materializeTypes writes the declared types into the types collection.
|
||||
func (e extensionObjects) materializeTypes(ctx *sql.Context) error {
|
||||
for _, declared := range e.ext.Types {
|
||||
var err error
|
||||
def := declared.Definition
|
||||
if def.InputFunc, err = e.supportFuncID(ctx, declared.Input); err != nil {
|
||||
return err
|
||||
}
|
||||
if def.OutputFunc, err = e.supportFuncID(ctx, declared.Output); err != nil {
|
||||
return err
|
||||
}
|
||||
if def.ReceiveFunc, err = e.supportFuncID(ctx, declared.Receive); err != nil {
|
||||
return err
|
||||
}
|
||||
if def.SendFunc, err = e.supportFuncID(ctx, declared.Send); err != nil {
|
||||
return err
|
||||
}
|
||||
if def.ModInFunc, err = e.supportFuncID(ctx, declared.ModIn); err != nil {
|
||||
return err
|
||||
}
|
||||
if def.ModOutFunc, err = e.supportFuncID(ctx, declared.ModOut); err != nil {
|
||||
return err
|
||||
}
|
||||
newType := types.NewBaseType(ctx, id.NewType(e.schemaName, declared.Name), def)
|
||||
if err = e.typColl.CreateType(ctx, newType); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = e.typColl.CreateType(ctx, types.CreateArrayTypeFromBaseType(newType)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// materializeRoutines writes the declared routines into the functions collection.
|
||||
func (e extensionObjects) materializeRoutines(ctx *sql.Context) error {
|
||||
if len(e.ext.Routines) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -152,11 +208,11 @@ func (e extensionObjects) materialize(ctx *sql.Context, typColl *typecollection.
|
||||
return err
|
||||
}
|
||||
for _, routine := range e.ext.Routines {
|
||||
returnType, err := e.typeID(ctx, typColl, routine.Returns)
|
||||
returnType, err := e.typeID(ctx, routine.Returns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paramTypes, err := e.parameterTypes(ctx, typColl, routine.Parameters)
|
||||
paramTypes, err := e.parameterTypes(ctx, routine.Parameters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -180,9 +236,146 @@ func (e extensionObjects) materialize(ctx *sql.Context, typColl *typecollection.
|
||||
return nil
|
||||
}
|
||||
|
||||
// materializeOperators writes the declared operators into the operators collection.
|
||||
func (e extensionObjects) materializeOperators(ctx *sql.Context) error {
|
||||
if len(e.ext.Operators) == 0 {
|
||||
return nil
|
||||
}
|
||||
opCollection, err := core.GetOperatorsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, declared := range e.ext.Operators {
|
||||
leftType, err := e.typeID(ctx, declared.Left)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rightType, err := e.typeID(ctx, declared.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routine, err := e.routine(declared.Routine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
returnType, err := e.typeID(ctx, routine.Returns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
funcID, err := e.routineID(ctx, routine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = opCollection.AddOperator(ctx, operators.Operator{
|
||||
ID: id.NewOperator(e.schemaName, declared.Symbol, leftType, rightType),
|
||||
Function: funcID,
|
||||
ReturnType: returnType,
|
||||
Commutator: declared.Commutator,
|
||||
Negator: declared.Negator,
|
||||
Hashes: declared.Hashes,
|
||||
Merges: declared.Merges,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// materializeCasts writes the declared casts into the casts collection.
|
||||
func (e extensionObjects) materializeCasts(ctx *sql.Context) error {
|
||||
if len(e.ext.Casts) == 0 {
|
||||
return nil
|
||||
}
|
||||
castCollection, err := core.GetCastsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, declared := range e.ext.Casts {
|
||||
sourceType, err := e.typeID(ctx, declared.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetType, err := e.typeID(ctx, declared.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
funcID, err := e.optionalRoutineID(ctx, declared.Routine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = castCollection.AddCast(ctx, casts.Cast{
|
||||
ID: id.NewCast(sourceType, targetType),
|
||||
CastType: declared.CastType,
|
||||
Function: funcID,
|
||||
UseInOut: !funcID.IsValid(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// materializeAggregates writes the declared aggregates into the aggregates collection.
|
||||
func (e extensionObjects) materializeAggregates(ctx *sql.Context) error {
|
||||
if len(e.ext.Aggregates) == 0 {
|
||||
return nil
|
||||
}
|
||||
aggCollection, err := core.GetAggregatesCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, declared := range e.ext.Aggregates {
|
||||
returnType, err := e.typeID(ctx, declared.Returns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stateType, err := e.typeID(ctx, declared.StateType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paramTypes, err := e.parameterTypes(ctx, declared.Parameters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transitionFunc, err := e.optionalRoutineID(ctx, declared.Transition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
finalFunc, err := e.optionalRoutineID(ctx, declared.Final)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
combineFunc, err := e.optionalRoutineID(ctx, declared.Combine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = aggCollection.AddAggregate(ctx, aggregates.Aggregate{
|
||||
ID: id.NewFunction(e.schemaName, declared.Name, paramTypes...),
|
||||
ReturnType: returnType,
|
||||
SFunc: transitionFunc,
|
||||
SType: stateType,
|
||||
FinalFunc: finalFunc,
|
||||
CombineFunc: combineFunc,
|
||||
InitCond: declared.InitCond,
|
||||
HasInitCond: declared.HasInitCond,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// typeID returns the type ID matching the given name.
|
||||
func (e extensionObjects) typeID(ctx *sql.Context, typColl *typecollection.TypeCollection, name string) (id.Type, error) {
|
||||
_, typeID, err := typColl.ResolveName(ctx, doltdb.TableName{Name: name})
|
||||
func (e extensionObjects) typeID(ctx *sql.Context, name string) (id.Type, error) {
|
||||
for _, declared := range e.ext.Types {
|
||||
if declared.Name == name {
|
||||
return id.NewType(e.schemaName, name), nil
|
||||
}
|
||||
}
|
||||
_, typeID, err := e.typColl.ResolveName(ctx, doltdb.TableName{Name: name})
|
||||
if err != nil {
|
||||
return id.NullType, err
|
||||
}
|
||||
@@ -193,10 +386,10 @@ func (e extensionObjects) typeID(ctx *sql.Context, typColl *typecollection.TypeC
|
||||
}
|
||||
|
||||
// parameterTypes returns the type IDs of the given parameters.
|
||||
func (e extensionObjects) parameterTypes(ctx *sql.Context, typColl *typecollection.TypeCollection, params []extdef.Parameter) ([]id.Type, error) {
|
||||
func (e extensionObjects) parameterTypes(ctx *sql.Context, params []extdef.Parameter) ([]id.Type, error) {
|
||||
paramTypes := make([]id.Type, len(params))
|
||||
for i, param := range params {
|
||||
paramType, err := e.typeID(ctx, typColl, param.Type)
|
||||
paramType, err := e.typeID(ctx, param.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -204,3 +397,44 @@ func (e extensionObjects) parameterTypes(ctx *sql.Context, typColl *typecollecti
|
||||
}
|
||||
return paramTypes, nil
|
||||
}
|
||||
|
||||
// routine returns the routine that the extension declares under the given symbol.
|
||||
func (e extensionObjects) routine(symbol string) (extdef.Routine, error) {
|
||||
for _, routine := range e.ext.Routines {
|
||||
if routine.Symbol == symbol {
|
||||
return routine, nil
|
||||
}
|
||||
}
|
||||
return extdef.Routine{}, errors.Errorf(`extension "%s" does not declare the function "%s"`, e.ext.Name, symbol)
|
||||
}
|
||||
|
||||
// routineID returns the ID that the given routine is materialized under.
|
||||
func (e extensionObjects) routineID(ctx *sql.Context, routine extdef.Routine) (id.Function, error) {
|
||||
paramTypes, err := e.parameterTypes(ctx, routine.Parameters)
|
||||
if err != nil {
|
||||
return id.NullFunction, err
|
||||
}
|
||||
return id.NewFunction(e.schemaName, routine.Name, paramTypes...), nil
|
||||
}
|
||||
|
||||
// optionalRoutineID returns the ID of the routine with the given symbol, or a null ID when the declaration omitted it.
|
||||
func (e extensionObjects) optionalRoutineID(ctx *sql.Context, symbol string) (id.Function, error) {
|
||||
if len(symbol) == 0 {
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
routine, err := e.routine(symbol)
|
||||
if err != nil {
|
||||
return id.NullFunction, err
|
||||
}
|
||||
return e.routineID(ctx, routine)
|
||||
}
|
||||
|
||||
// supportFuncID returns the function registry ID of the routine with the given symbol, or zero when the type omitted
|
||||
// it.
|
||||
func (e extensionObjects) supportFuncID(ctx *sql.Context, symbol string) (uint32, error) {
|
||||
funcID, err := e.optionalRoutineID(ctx, symbol)
|
||||
if err != nil || !funcID.IsValid() {
|
||||
return 0, err
|
||||
}
|
||||
return types.ToFuncID(funcID), nil
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ func (c *CreateFunction) Schema(ctx *sql.Context) sql.Schema {
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateFunction) String() string {
|
||||
// TODO: fully implement this
|
||||
return "CREATE FUNCTION"
|
||||
return fmt.Sprintf("CREATE FUNCTION %s", c.FunctionName)
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
// 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 node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/operators"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// CreateOperator implements CREATE OPERATOR.
|
||||
type CreateOperator struct {
|
||||
Symbol string
|
||||
Left *pgtypes.DoltgresType // This is nil for prefix operators
|
||||
Right *pgtypes.DoltgresType
|
||||
FuncSchema string
|
||||
FuncName string
|
||||
Commutator string
|
||||
Negator string
|
||||
Hashes bool
|
||||
Merges bool
|
||||
}
|
||||
|
||||
var _ sql.ExecSourceRel = (*CreateOperator)(nil)
|
||||
var _ vitess.Injectable = (*CreateOperator)(nil)
|
||||
|
||||
// NewCreateOperator returns a new *CreateOperator.
|
||||
func NewCreateOperator(
|
||||
symbol string,
|
||||
left, right *pgtypes.DoltgresType,
|
||||
funcSchema, funcName string,
|
||||
commutator, negator string,
|
||||
hashes, merges bool) *CreateOperator {
|
||||
return &CreateOperator{
|
||||
Symbol: symbol,
|
||||
Left: left,
|
||||
Right: right,
|
||||
FuncSchema: funcSchema,
|
||||
FuncName: funcName,
|
||||
Commutator: commutator,
|
||||
Negator: negator,
|
||||
Hashes: hashes,
|
||||
Merges: merges,
|
||||
}
|
||||
}
|
||||
|
||||
// Children implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateOperator) Children() []sql.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReadOnly implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateOperator) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Resolved implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateOperator) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RowIter implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateOperator) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
if c.Left == nil {
|
||||
if c.Commutator != "" {
|
||||
return nil, errors.New("only binary operators can have commutators")
|
||||
}
|
||||
if c.Hashes {
|
||||
return nil, errors.New("only binary operators can hash")
|
||||
}
|
||||
if c.Merges {
|
||||
return nil, errors.New("only binary operators can merge join")
|
||||
}
|
||||
}
|
||||
funcSchema, err := core.GetSchemaName(ctx, nil, c.FuncSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var paramTypes []id.Type
|
||||
var paramNames []string
|
||||
if c.Left != nil {
|
||||
paramTypes = append(paramTypes, c.Left.ID)
|
||||
paramNames = append(paramNames, c.Left.String())
|
||||
}
|
||||
paramTypes = append(paramTypes, c.Right.ID)
|
||||
paramNames = append(paramNames, c.Right.String())
|
||||
funcID := id.NewFunction(funcSchema, c.FuncName, paramTypes...)
|
||||
funcCollection, err := core.GetFunctionsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !funcCollection.HasFunction(ctx, funcID) {
|
||||
return nil, errors.Errorf("function %s(%s) does not exist", c.FuncName, strings.Join(paramNames, ", "))
|
||||
}
|
||||
f, err := funcCollection.GetFunction(ctx, funcID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if f.ReturnType != pgtypes.Bool.ID {
|
||||
if c.Negator != "" {
|
||||
return nil, errors.New("only boolean operators can have negators")
|
||||
}
|
||||
if c.Hashes {
|
||||
return nil, errors.New("only boolean operators can hash")
|
||||
}
|
||||
if c.Merges {
|
||||
return nil, errors.New("only boolean operators can merge join")
|
||||
}
|
||||
}
|
||||
schemaName, err := core.GetSchemaName(ctx, nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leftTypeID := id.NullType
|
||||
if c.Left != nil {
|
||||
leftTypeID = c.Left.ID
|
||||
}
|
||||
opCollection, err := core.GetOperatorsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
operatorID := id.NewOperator(schemaName, c.Symbol, leftTypeID, c.Right.ID)
|
||||
if opCollection.HasOperator(ctx, operatorID) {
|
||||
return nil, errors.Errorf("operator %s already exists", c.Symbol)
|
||||
}
|
||||
err = opCollection.AddOperator(ctx, operators.Operator{
|
||||
ID: operatorID,
|
||||
Function: funcID,
|
||||
ReturnType: f.ReturnType,
|
||||
Commutator: c.Commutator,
|
||||
Negator: c.Negator,
|
||||
Hashes: c.Hashes,
|
||||
Merges: c.Merges,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Commutator != "" {
|
||||
commutatorID := id.NewOperator(schemaName, c.Commutator, c.Right.ID, leftTypeID)
|
||||
if commutatorID != operatorID {
|
||||
if err = setCommutatorBackLink(ctx, opCollection, commutatorID, c.Symbol); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.Negator != "" {
|
||||
negatorID := id.NewOperator(schemaName, c.Negator, leftTypeID, c.Right.ID)
|
||||
if negatorID != operatorID {
|
||||
if err = setNegatorBackLink(ctx, opCollection, negatorID, c.Symbol); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
// setCommutatorBackLink sets the commutator of the given operator to the given symbol if the operator exists and has
|
||||
// no commutator.
|
||||
func setCommutatorBackLink(ctx *sql.Context, opCollection *operators.Collection, operatorID id.Operator, symbol string) error {
|
||||
if !opCollection.HasOperator(ctx, operatorID) {
|
||||
return nil
|
||||
}
|
||||
op, err := opCollection.GetOperator(ctx, operatorID)
|
||||
if err != nil || op.Commutator != "" {
|
||||
return err
|
||||
}
|
||||
op.Commutator = symbol
|
||||
if err = opCollection.DropOperator(ctx, operatorID); err != nil {
|
||||
return err
|
||||
}
|
||||
return opCollection.AddOperator(ctx, op)
|
||||
}
|
||||
|
||||
// setNegatorBackLink sets the negator of the given operator to the given symbol if the operator exists and has no
|
||||
// negator.
|
||||
func setNegatorBackLink(ctx *sql.Context, opCollection *operators.Collection, operatorID id.Operator, symbol string) error {
|
||||
if !opCollection.HasOperator(ctx, operatorID) {
|
||||
return nil
|
||||
}
|
||||
op, err := opCollection.GetOperator(ctx, operatorID)
|
||||
if err != nil || op.Negator != "" {
|
||||
return err
|
||||
}
|
||||
op.Negator = symbol
|
||||
if err = opCollection.DropOperator(ctx, operatorID); err != nil {
|
||||
return err
|
||||
}
|
||||
return opCollection.AddOperator(ctx, op)
|
||||
}
|
||||
|
||||
// Schema implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateOperator) Schema(ctx *sql.Context) sql.Schema {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateOperator) String() string {
|
||||
return fmt.Sprintf("CREATE OPERATOR %s", c.Symbol)
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateOperator) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
|
||||
return plan.NillaryWithChildren(c, children...)
|
||||
}
|
||||
|
||||
// WithResolvedChildren implements the interface vitess.Injectable.
|
||||
func (c *CreateOperator) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
|
||||
if len(children) != 0 {
|
||||
return nil, ErrVitessChildCount.New(0, len(children))
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
@@ -16,6 +16,7 @@ package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
@@ -152,7 +153,7 @@ func (c *CreateProcedure) Schema(ctx *sql.Context) sql.Schema {
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateProcedure) String() string {
|
||||
return "CREATE PROCEDURE"
|
||||
return fmt.Sprintf("CREATE PROCEDURE %s", c.ProcedureName)
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
|
||||
@@ -16,6 +16,7 @@ package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
@@ -148,7 +149,7 @@ func (c *CreateTrigger) Schema(ctx *sql.Context) sql.Schema {
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateTrigger) String() string {
|
||||
return "CREATE TRIGGER"
|
||||
return fmt.Sprintf("CREATE TRIGGER %s", c.Name)
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
|
||||
@@ -16,6 +16,7 @@ package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
@@ -167,7 +168,7 @@ func (c *CreateType) Schema(ctx *sql.Context) sql.Schema {
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (c *CreateType) String() string {
|
||||
return "CREATE TYPE"
|
||||
return fmt.Sprintf("CREATE TYPE %s", c.Name)
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
// 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 node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// AggregateToDrop represents an aggregate in DROP AGGREGATE.
|
||||
type AggregateToDrop struct {
|
||||
SchemaName string
|
||||
Name string
|
||||
ArgTypes []*pgtypes.DoltgresType
|
||||
}
|
||||
|
||||
// DropAggregate implements DROP AGGREGATE.
|
||||
type DropAggregate struct {
|
||||
Aggregates []*AggregateToDrop
|
||||
IfExists bool
|
||||
}
|
||||
|
||||
var _ sql.ExecSourceRel = (*DropAggregate)(nil)
|
||||
var _ vitess.Injectable = (*DropAggregate)(nil)
|
||||
|
||||
// NewDropAggregate returns a new *DropAggregate.
|
||||
func NewDropAggregate(ifExists bool, aggregates []*AggregateToDrop) *DropAggregate {
|
||||
return &DropAggregate{
|
||||
IfExists: ifExists,
|
||||
Aggregates: aggregates,
|
||||
}
|
||||
}
|
||||
|
||||
// Children implements the interface sql.ExecSourceRel.
|
||||
func (d *DropAggregate) Children() []sql.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReadOnly implements the interface sql.ExecSourceRel.
|
||||
func (d *DropAggregate) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Resolved implements the interface sql.ExecSourceRel.
|
||||
func (d *DropAggregate) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RowIter implements the interface sql.ExecSourceRel.
|
||||
func (d *DropAggregate) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
aggCollection, err := core.GetAggregatesCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
funcCollection, err := core.GetFunctionsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, agg := range d.Aggregates {
|
||||
schemaName, err := core.GetSchemaName(ctx, nil, agg.SchemaName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argIDs := make([]id.Type, len(agg.ArgTypes))
|
||||
argNames := make([]string, len(agg.ArgTypes))
|
||||
for i, argType := range agg.ArgTypes {
|
||||
argIDs[i] = argType.ID
|
||||
argNames[i] = argType.String()
|
||||
}
|
||||
aggregateID := id.NewFunction(schemaName, agg.Name, argIDs...)
|
||||
if !aggCollection.HasAggregate(ctx, aggregateID) {
|
||||
if funcCollection.HasFunction(ctx, aggregateID) {
|
||||
return nil, errors.Errorf("function %s(%s) is not an aggregate", agg.Name, strings.Join(argNames, ", "))
|
||||
}
|
||||
if d.IfExists {
|
||||
continue
|
||||
}
|
||||
return nil, errors.Errorf("aggregate %s(%s) does not exist", agg.Name, strings.Join(argNames, ", "))
|
||||
}
|
||||
if err = aggCollection.DropAggregate(ctx, aggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
// Schema implements the interface sql.ExecSourceRel.
|
||||
func (d *DropAggregate) Schema(ctx *sql.Context) sql.Schema {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (d *DropAggregate) String() string {
|
||||
return "DROP AGGREGATE"
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
func (d *DropAggregate) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
|
||||
return plan.NillaryWithChildren(d, children...)
|
||||
}
|
||||
|
||||
// WithResolvedChildren implements the interface vitess.Injectable.
|
||||
func (d *DropAggregate) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
|
||||
if len(children) != 0 {
|
||||
return nil, ErrVitessChildCount.New(0, len(children))
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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 node
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// OperatorToDrop represents an operator in DROP OPERATOR.
|
||||
type OperatorToDrop struct {
|
||||
Symbol string
|
||||
Left *pgtypes.DoltgresType // This is nil for prefix operators
|
||||
Right *pgtypes.DoltgresType
|
||||
}
|
||||
|
||||
// DropOperator implements DROP OPERATOR.
|
||||
type DropOperator struct {
|
||||
Operators []*OperatorToDrop
|
||||
IfExists bool
|
||||
}
|
||||
|
||||
var _ sql.ExecSourceRel = (*DropOperator)(nil)
|
||||
var _ vitess.Injectable = (*DropOperator)(nil)
|
||||
|
||||
// NewDropOperator returns a new *DropOperator.
|
||||
func NewDropOperator(ifExists bool, operators []*OperatorToDrop) *DropOperator {
|
||||
return &DropOperator{
|
||||
IfExists: ifExists,
|
||||
Operators: operators,
|
||||
}
|
||||
}
|
||||
|
||||
// Children implements the interface sql.ExecSourceRel.
|
||||
func (d *DropOperator) Children() []sql.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReadOnly implements the interface sql.ExecSourceRel.
|
||||
func (d *DropOperator) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Resolved implements the interface sql.ExecSourceRel.
|
||||
func (d *DropOperator) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RowIter implements the interface sql.ExecSourceRel.
|
||||
func (d *DropOperator) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
opCollection, err := core.GetOperatorsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
searchPath, err := core.SearchPath(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, op := range d.Operators {
|
||||
leftTypeID := id.NullType
|
||||
if op.Left != nil {
|
||||
leftTypeID = op.Left.ID
|
||||
}
|
||||
operator, found, err := opCollection.ResolveOperator(ctx, searchPath, op.Symbol, leftTypeID, op.Right.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found {
|
||||
if d.IfExists {
|
||||
continue
|
||||
}
|
||||
if op.Left == nil {
|
||||
return nil, errors.Errorf("operator does not exist: %s %s", op.Symbol, op.Right.String())
|
||||
}
|
||||
return nil, errors.Errorf("operator does not exist: %s %s %s", op.Left.String(), op.Symbol, op.Right.String())
|
||||
}
|
||||
if err = opCollection.DropOperator(ctx, operator.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
// Schema implements the interface sql.ExecSourceRel.
|
||||
func (d *DropOperator) Schema(ctx *sql.Context) sql.Schema {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (d *DropOperator) String() string {
|
||||
return "DROP OPERATOR"
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
func (d *DropOperator) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
|
||||
return plan.NillaryWithChildren(d, children...)
|
||||
}
|
||||
|
||||
// WithResolvedChildren implements the interface vitess.Injectable.
|
||||
func (d *DropOperator) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
|
||||
if len(children) != 0 {
|
||||
return nil, ErrVitessChildCount.New(0, len(children))
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
@@ -19,6 +19,9 @@ import (
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/aggregates"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/server/tables"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
@@ -43,9 +46,20 @@ func (p PgAggregateHandler) Name() string {
|
||||
|
||||
// RowIter implements the interface tables.Handler.
|
||||
func (p PgAggregateHandler) RowIter(ctx *sql.Context, partition sql.Partition) (sql.RowIter, error) {
|
||||
// pg_aggregate is currently empty, since built-in aggregate functions do not yet have pg_proc entries.
|
||||
// TODO: fill this in alongside built-in function entries in pg_proc
|
||||
return emptyRowIter()
|
||||
aggregateCollection, err := core.GetAggregatesCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var aggs []aggregates.Aggregate
|
||||
err = aggregateCollection.IterateAggregates(ctx, func(a aggregates.Aggregate) (stop bool, err error) {
|
||||
aggs = append(aggs, a)
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pgAggregateRowIter{aggs: aggs}, nil
|
||||
}
|
||||
|
||||
// PkSchema implements the interface tables.Handler.
|
||||
@@ -71,8 +85,8 @@ var pgAggregateSchema = sql.Schema{
|
||||
{Name: "aggmfinalfn", Type: pgtypes.Regproc, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
{Name: "aggfinalextra", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
{Name: "aggmfinalextra", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
{Name: "aggfinalmodify", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
{Name: "aggmfinalmodify", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
{Name: "aggfinalmodify", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
{Name: "aggmfinalmodify", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
{Name: "aggsortop", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
{Name: "aggtranstype", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
{Name: "aggtransspace", Type: pgtypes.Int32, Default: nil, Nullable: false, Source: PgAggregateName},
|
||||
@@ -84,13 +98,47 @@ var pgAggregateSchema = sql.Schema{
|
||||
|
||||
// pgAggregateRowIter is the sql.RowIter for the pg_aggregate table.
|
||||
type pgAggregateRowIter struct {
|
||||
aggs []aggregates.Aggregate
|
||||
idx int
|
||||
}
|
||||
|
||||
var _ sql.RowIter = (*pgAggregateRowIter)(nil)
|
||||
|
||||
// Next implements the interface sql.RowIter.
|
||||
func (iter *pgAggregateRowIter) Next(ctx *sql.Context) (sql.Row, error) {
|
||||
return nil, io.EOF
|
||||
if iter.idx >= len(iter.aggs) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
agg := iter.aggs[iter.idx]
|
||||
iter.idx++
|
||||
var initVal any
|
||||
if agg.HasInitCond {
|
||||
initVal = agg.InitCond
|
||||
}
|
||||
return sql.Row{
|
||||
agg.ID.AsId(), // aggfnoid
|
||||
"n", // aggkind
|
||||
int16(0), // aggnumdirectargs
|
||||
agg.SFunc.AsId(), // aggtransfn
|
||||
agg.FinalFunc.AsId(), // aggfinalfn
|
||||
agg.CombineFunc.AsId(), // aggcombinefn
|
||||
id.Null, // aggserialfn
|
||||
id.Null, // aggdeserialfn
|
||||
id.Null, // aggmtransfn
|
||||
id.Null, // aggminvtransfn
|
||||
id.Null, // aggmfinalfn
|
||||
false, // aggfinalextra
|
||||
false, // aggmfinalextra
|
||||
"r", // aggfinalmodify
|
||||
"r", // aggmfinalmodify
|
||||
id.Null, // aggsortop
|
||||
agg.SType.AsId(), // aggtranstype
|
||||
int32(0), // aggtransspace
|
||||
id.Null, // aggmtranstype
|
||||
int32(0), // aggmtransspace
|
||||
initVal, // agginitval
|
||||
nil, // aggminitval
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close implements the interface sql.RowIter.
|
||||
|
||||
@@ -19,6 +19,9 @@ import (
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/operators"
|
||||
"github.com/dolthub/doltgresql/server/tables"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
@@ -43,9 +46,20 @@ func (p PgOperatorHandler) Name() string {
|
||||
|
||||
// RowIter implements the interface tables.Handler.
|
||||
func (p PgOperatorHandler) RowIter(ctx *sql.Context, partition sql.Partition) (sql.RowIter, error) {
|
||||
// pg_operator is currently empty, since built-in operators are not yet cataloged with stable OIDs.
|
||||
// TODO: fill this in from the operator framework's built-in operators
|
||||
return emptyRowIter()
|
||||
operatorCollection, err := core.GetOperatorsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ops []operators.Operator
|
||||
err = operatorCollection.IterateOperators(ctx, func(o operators.Operator) (stop bool, err error) {
|
||||
ops = append(ops, o)
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pgOperatorRowIter{ops: ops}, nil
|
||||
}
|
||||
|
||||
// PkSchema implements the interface tables.Handler.
|
||||
@@ -77,13 +91,44 @@ var pgOperatorSchema = sql.Schema{
|
||||
|
||||
// pgOperatorRowIter is the sql.RowIter for the pg_operator table.
|
||||
type pgOperatorRowIter struct {
|
||||
ops []operators.Operator
|
||||
idx int
|
||||
}
|
||||
|
||||
var _ sql.RowIter = (*pgOperatorRowIter)(nil)
|
||||
|
||||
// Next implements the interface sql.RowIter.
|
||||
func (iter *pgOperatorRowIter) Next(ctx *sql.Context) (sql.Row, error) {
|
||||
return nil, io.EOF
|
||||
if iter.idx >= len(iter.ops) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
op := iter.ops[iter.idx]
|
||||
iter.idx++
|
||||
commutator := id.Null
|
||||
if len(op.Commutator) > 0 {
|
||||
commutator = id.NewOperator(op.ID.SchemaName(), op.Commutator, op.ID.RightType(), op.ID.LeftType()).AsId()
|
||||
}
|
||||
negator := id.Null
|
||||
if len(op.Negator) > 0 {
|
||||
negator = id.NewOperator(op.ID.SchemaName(), op.Negator, op.ID.LeftType(), op.ID.RightType()).AsId()
|
||||
}
|
||||
return sql.Row{
|
||||
op.ID.AsId(), // oid
|
||||
op.ID.Symbol(), // oprname
|
||||
id.NewNamespace(op.ID.SchemaName()).AsId(), // oprnamespace
|
||||
id.Null, // oprowner
|
||||
"b", // oprkind
|
||||
op.Merges, // oprcanmerge
|
||||
op.Hashes, // oprcanhash
|
||||
op.ID.LeftType().AsId(), // oprleft
|
||||
op.ID.RightType().AsId(), // oprright
|
||||
op.ReturnType.AsId(), // oprresult
|
||||
commutator, // oprcom
|
||||
negator, // oprnegate
|
||||
op.Function.AsId(), // oprcode
|
||||
id.Null, // oprrest
|
||||
id.Null, // oprjoin
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close implements the interface sql.RowIter.
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/aggregates"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/procedures"
|
||||
"github.com/dolthub/doltgresql/server/functions"
|
||||
@@ -229,6 +231,38 @@ func cachePgProcs(ctx *sql.Context, pgCatalogCache *pgCatalogCache) error {
|
||||
return err
|
||||
}
|
||||
|
||||
aggCollection, err := core.GetAggregatesCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = aggCollection.IterateAggregates(ctx, func(a aggregates.Aggregate) (stop bool, err error) {
|
||||
var argTypes any
|
||||
params := a.ID.Parameters()
|
||||
if len(params) > 0 {
|
||||
types := make([]any, len(params))
|
||||
for i, param := range params {
|
||||
types[i] = param.AsId()
|
||||
}
|
||||
argTypes = types
|
||||
}
|
||||
pprocs = append(pprocs, &pgProc{
|
||||
oid: a.ID.AsId(),
|
||||
name: a.ID.FunctionName(),
|
||||
schemaOid: id.NewNamespace(a.ID.SchemaName()).AsId(),
|
||||
variadic: id.Null,
|
||||
kind: "a",
|
||||
volatile: "i",
|
||||
nArgs: int16(len(params)),
|
||||
retTyp: a.ReturnType.AsId(),
|
||||
argTypes: argTypes,
|
||||
src: "aggregate_dummy",
|
||||
})
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pgCatalogCache.procs = pprocs
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
)
|
||||
|
||||
// BaseTypeDefinition describes a base type, with every support function already resolved to its registry ID.
|
||||
type BaseTypeDefinition struct {
|
||||
InputFunc uint32
|
||||
OutputFunc uint32
|
||||
ReceiveFunc uint32
|
||||
SendFunc uint32
|
||||
ModInFunc uint32
|
||||
ModOutFunc uint32
|
||||
TypLength int16
|
||||
PassedByVal bool
|
||||
Align TypeAlignment
|
||||
Storage TypeStorage
|
||||
TypCategory TypeCategory
|
||||
IsPreferred bool
|
||||
Default string
|
||||
Elem *DoltgresType
|
||||
Delimiter string
|
||||
Collatable bool
|
||||
}
|
||||
|
||||
// NewBaseTypeDefinition returns the definition of a base type whose options have all been omitted.
|
||||
func NewBaseTypeDefinition() BaseTypeDefinition {
|
||||
return BaseTypeDefinition{
|
||||
TypLength: -1,
|
||||
Align: TypeAlignment_Int,
|
||||
Storage: TypeStorage_Plain,
|
||||
TypCategory: TypeCategory_UserDefinedTypes,
|
||||
Delimiter: ",",
|
||||
}
|
||||
}
|
||||
|
||||
// NewBaseType returns a new base type from the given definition.
|
||||
func NewBaseType(ctx *sql.Context, typeID id.Type, def BaseTypeDefinition) *DoltgresType {
|
||||
elem := internalNullType
|
||||
if def.Elem != nil {
|
||||
elem = def.Elem
|
||||
}
|
||||
collation := id.NullCollation
|
||||
if def.Collatable {
|
||||
collation = id.NewCollation("pg_catalog", "default")
|
||||
}
|
||||
return &DoltgresType{
|
||||
ID: typeID,
|
||||
TypLength: def.TypLength,
|
||||
PassedByVal: def.PassedByVal,
|
||||
TypType: TypeType_Base,
|
||||
TypCategory: def.TypCategory,
|
||||
IsPreferred: def.IsPreferred,
|
||||
IsDefined: true,
|
||||
Delimiter: def.Delimiter,
|
||||
RelID: id.Null,
|
||||
SubscriptFunc: toFuncID("-"),
|
||||
Elem: elem,
|
||||
Array: internalNullType,
|
||||
InputFunc: def.InputFunc,
|
||||
OutputFunc: def.OutputFunc,
|
||||
ReceiveFunc: def.ReceiveFunc,
|
||||
SendFunc: def.SendFunc,
|
||||
ModInFunc: def.ModInFunc,
|
||||
ModOutFunc: def.ModOutFunc,
|
||||
AnalyzeFunc: toFuncID("-"),
|
||||
Align: def.Align,
|
||||
Storage: def.Storage,
|
||||
NotNull: false,
|
||||
BaseTypeType: internalNullType,
|
||||
TypMod: -1,
|
||||
NDims: 0,
|
||||
TypCollation: collation,
|
||||
DefaulBin: "",
|
||||
Default: def.Default,
|
||||
Acl: nil,
|
||||
Checks: nil,
|
||||
attTypMod: -1,
|
||||
CompareFunc: toFuncID("-"),
|
||||
SerializationFunc: nil,
|
||||
DeserializationFunc: nil,
|
||||
}
|
||||
}
|
||||
@@ -30,14 +30,13 @@ type QuickFunction interface {
|
||||
WithResolvedTypes(newTypes []*DoltgresType) any
|
||||
}
|
||||
|
||||
// LoadFunctionFromCatalog returns the function matching the given name and parameter types. This is intended solely for
|
||||
// functions that are used for types, as the returned functions are not valid using the Eval function.
|
||||
var LoadFunctionFromCatalog func(ctx *sql.Context, funcName string, parameterTypes []*DoltgresType) any
|
||||
// LoadFunctionFromCatalog returns the function matching the given schema, name and parameter types. This is intended
|
||||
// solely for functions that are used for types, as the returned functions are not valid using the Eval function.
|
||||
var LoadFunctionFromCatalog func(ctx *sql.Context, schemaName string, funcName string, parameterTypes []*DoltgresType) any
|
||||
|
||||
// functionRegistry is a local registry that holds a mapping from ID to QuickFunction. This is done as types are now
|
||||
// passed by struct, meaning that we need to cache the loading of functions somewhere. In addition, we don't yet support
|
||||
// deleting built-in functions, so we can make a global cache. This makes a hard assumption that all functions being
|
||||
// referenced actually exist, which should be true until built-in function deletion is implemented.
|
||||
// passed by struct, meaning that we need to cache the loading of functions somewhere. Only the functions in pg_catalog
|
||||
// are cached, since a user-defined function may be replaced or dropped, and it may differ between databases.
|
||||
//
|
||||
// In a way, one can view this as associated an OID to a function. With a proper OID system, this would not need to
|
||||
// exist. It should be removed once OIDs are figured out.
|
||||
@@ -46,7 +45,7 @@ type functionRegistry struct {
|
||||
counter uint32
|
||||
mapping map[id.Function]uint32
|
||||
revMapping map[uint32]id.Function
|
||||
functions [256]QuickFunction // Arbitrary number, big enough for now to fit every function in it
|
||||
functions []QuickFunction
|
||||
}
|
||||
|
||||
// globalFunctionRegistry is the global functionRegistry. Only one needs to exist since we do not yet allow deleting
|
||||
@@ -56,6 +55,7 @@ var globalFunctionRegistry = functionRegistry{
|
||||
counter: 1,
|
||||
mapping: map[id.Function]uint32{id.NullFunction: 0},
|
||||
revMapping: map[uint32]id.Function{0: id.NullFunction},
|
||||
functions: make([]QuickFunction, 1, 256),
|
||||
}
|
||||
|
||||
// InternalToRegistryID returns an ID for the given Internal ID.
|
||||
@@ -65,11 +65,9 @@ func (r *functionRegistry) InternalToRegistryID(functionID id.Function) uint32 {
|
||||
if registryID, ok := r.mapping[functionID]; ok {
|
||||
return registryID
|
||||
}
|
||||
if r.counter >= uint32(len(r.functions)) {
|
||||
panic("max function count reached in static array")
|
||||
}
|
||||
r.mapping[functionID] = r.counter
|
||||
r.revMapping[r.counter] = functionID
|
||||
r.functions = append(r.functions, nil)
|
||||
r.counter++
|
||||
return r.counter - 1
|
||||
}
|
||||
@@ -122,13 +120,18 @@ func (r *functionRegistry) loadFunction(ctx *sql.Context, id uint32) QuickFuncti
|
||||
if !functionID.IsValid() {
|
||||
return nil
|
||||
}
|
||||
funcName, types := r.toFuncSignature(functionID)
|
||||
potentialFunction := LoadFunctionFromCatalog(ctx, funcName, types)
|
||||
funcName, types, ok := r.toFuncSignature(ctx, functionID)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
potentialFunction := LoadFunctionFromCatalog(ctx, functionID.SchemaName(), funcName, types)
|
||||
if potentialFunction == nil {
|
||||
return nil
|
||||
}
|
||||
f = potentialFunction.(QuickFunction)
|
||||
r.functions[id] = f
|
||||
if functionID.SchemaName() == "pg_catalog" {
|
||||
r.functions[id] = f
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -140,14 +143,33 @@ func (*functionRegistry) nameWithoutParams(functionID id.Function) string {
|
||||
return functionID.FunctionName()
|
||||
}
|
||||
|
||||
// toFuncSignature returns a function signature for the given Internal ID.
|
||||
func (*functionRegistry) toFuncSignature(functionID id.Function) (string, []*DoltgresType) {
|
||||
// toFuncSignature returns a function signature for the given Internal ID. Returns false when a parameter names a type
|
||||
// that cannot be resolved, which may happen when a user-defined type has been dropped.
|
||||
func (*functionRegistry) toFuncSignature(ctx *sql.Context, functionID id.Function) (string, []*DoltgresType, bool) {
|
||||
internalParams := functionID.Parameters()
|
||||
params := make([]*DoltgresType, len(internalParams))
|
||||
var collection TypeCollection
|
||||
for i, internalParam := range internalParams {
|
||||
params[i] = IDToBuiltInDoltgresType[internalParam]
|
||||
if builtIn, ok := IDToBuiltInDoltgresType[internalParam]; ok {
|
||||
params[i] = builtIn
|
||||
continue
|
||||
}
|
||||
if collection == nil {
|
||||
if GetTypesCollectionFromContext == nil {
|
||||
return "", nil, false
|
||||
}
|
||||
var err error
|
||||
if collection, err = GetTypesCollectionFromContext(ctx, ""); err != nil {
|
||||
return "", nil, false
|
||||
}
|
||||
}
|
||||
param, err := collection.GetType(ctx, internalParam)
|
||||
if err != nil || param == nil {
|
||||
return "", nil, false
|
||||
}
|
||||
params[i] = param
|
||||
}
|
||||
return functionID.FunctionName(), params
|
||||
return functionID.FunctionName(), params, true
|
||||
}
|
||||
|
||||
// toFuncID creates a valid function string for the given name and parameters, then registers the name with the
|
||||
@@ -156,7 +178,12 @@ func toFuncID(functionName string, params ...id.Type) uint32 {
|
||||
if functionName == "-" || len(functionName) == 0 {
|
||||
return 0
|
||||
}
|
||||
functionID := id.NewFunction("pg_catalog", functionName, params...)
|
||||
return ToFuncID(id.NewFunction("pg_catalog", functionName, params...))
|
||||
}
|
||||
|
||||
// ToFuncID registers the given function with the global function registry, and returns the ID it was given. Primarily
|
||||
// used by extensions.
|
||||
func ToFuncID(functionID id.Function) uint32 {
|
||||
return globalFunctionRegistry.InternalToRegistryID(functionID)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ var GlobalCustomVariables = map[string]utils.StatementGenerator{
|
||||
"collation": customDefinition(`en_US`),
|
||||
"column_definition": customDefinition(`v1 INTEGER`),
|
||||
"column_number": customDefinition(`1`),
|
||||
"com_op": customDefinition(`<=>`),
|
||||
"connlimit": customDefinition(`-1`),
|
||||
"cycle_mark_default": customDefinition(`'cycle_mark_default'`),
|
||||
"cycle_mark_value": customDefinition(`'cycle_mark_value'`),
|
||||
@@ -55,6 +56,7 @@ var GlobalCustomVariables = map[string]utils.StatementGenerator{
|
||||
"maxvalue": customDefinition(`1`),
|
||||
"minvalue": customDefinition(`1`),
|
||||
"mstate_data_size": customDefinition(`16`),
|
||||
"neg_op": customDefinition(`<~>`),
|
||||
"neighbor_enum_value": customDefinition(`'1'`),
|
||||
"new_enum_value": customDefinition(`'1'`),
|
||||
"numeric_literal": customDefinition(`1`),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Dolthub, Inc.
|
||||
// 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.
|
||||
@@ -18,42 +18,42 @@ import "testing"
|
||||
|
||||
func TestDropOperator(t *testing.T) {
|
||||
tests := []QueryParses{
|
||||
Unimplemented("DROP OPERATOR @@ ( left_type , right_type )"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( left_type , right_type )"),
|
||||
Unimplemented("DROP OPERATOR @@ ( NONE , right_type )"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( NONE , right_type )"),
|
||||
Unimplemented("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( left_type , right_type )"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( left_type , right_type )"),
|
||||
Unimplemented("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( left_type , right_type )"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( left_type , right_type )"),
|
||||
Unimplemented("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( NONE , right_type )"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( NONE , right_type )"),
|
||||
Unimplemented("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( NONE , right_type )"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( NONE , right_type )"),
|
||||
Unimplemented("DROP OPERATOR @@ ( left_type , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR @@ ( NONE , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( left_type , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( left_type , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( left_type , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( left_type , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( NONE , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( NONE , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( NONE , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( NONE , right_type ) CASCADE"),
|
||||
Unimplemented("DROP OPERATOR @@ ( left_type , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR @@ ( NONE , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( left_type , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( left_type , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( left_type , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( left_type , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( NONE , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( NONE , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( NONE , right_type ) RESTRICT"),
|
||||
Unimplemented("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( NONE , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR @@ ( left_type , right_type )"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( left_type , right_type )"),
|
||||
Converts("DROP OPERATOR @@ ( NONE , right_type )"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( NONE , right_type )"),
|
||||
Converts("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( left_type , right_type )"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( left_type , right_type )"),
|
||||
Converts("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( left_type , right_type )"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( left_type , right_type )"),
|
||||
Converts("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( NONE , right_type )"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( NONE , right_type )"),
|
||||
Converts("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( NONE , right_type )"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( NONE , right_type )"),
|
||||
Parses("DROP OPERATOR @@ ( left_type , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR @@ ( NONE , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( left_type , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( left_type , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( left_type , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( left_type , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( NONE , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( NONE , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( NONE , right_type ) CASCADE"),
|
||||
Parses("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( NONE , right_type ) CASCADE"),
|
||||
Converts("DROP OPERATOR @@ ( left_type , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR @@ ( NONE , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( left_type , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( left_type , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( left_type , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( left_type , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR @@ ( left_type , right_type ) , @@ ( NONE , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( left_type , right_type ) , @@ ( NONE , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR @@ ( NONE , right_type ) , @@ ( NONE , right_type ) RESTRICT"),
|
||||
Converts("DROP OPERATOR IF EXISTS @@ ( NONE , right_type ) , @@ ( NONE , right_type ) RESTRICT"),
|
||||
}
|
||||
RunTests(t, tests)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
// 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 _go
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
)
|
||||
|
||||
func TestCreateAggregate(t *testing.T) {
|
||||
RunScripts(t, []ScriptTest{
|
||||
{
|
||||
Name: "CREATE AGGREGATE with SFUNC and STYPE",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION agg_sum_step(state int4, val int4) RETURNS int4
|
||||
AS $$ SELECT state + val $$ LANGUAGE SQL;`,
|
||||
`CREATE TABLE agg_sum_vals (pk int4 PRIMARY KEY, grp text, v int4);`,
|
||||
`INSERT INTO agg_sum_vals VALUES (1, 'a', 10), (2, 'a', 20), (3, 'b', 5), (4, 'b', NULL);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_sum (int4) (SFUNC = agg_sum_step, STYPE = int4, INITCOND = '0');`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_sum(v) FROM agg_sum_vals WHERE grp = 'a';`,
|
||||
Expected: []sql.Row{{30}},
|
||||
},
|
||||
{ // The transition function is not STRICT, so the NULL in group 'b' nulls the state
|
||||
Query: `SELECT grp, agg_sum(v) FROM agg_sum_vals GROUP BY grp ORDER BY grp;`,
|
||||
Expected: []sql.Row{{"a", 30}, {"b", nil}},
|
||||
},
|
||||
{ // The initial condition is the state when the aggregate sees no rows at all
|
||||
Query: `SELECT agg_sum(v) FROM agg_sum_vals WHERE pk = 0;`,
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT aggkind, agginitval, aggfinalfn::text, aggcombinefn::text FROM pg_aggregate WHERE aggtransfn::text = 'agg_sum_step';`,
|
||||
Expected: []sql.Row{{"n", "0", "-", "-"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT proname, prokind FROM pg_proc WHERE proname = 'agg_sum';`,
|
||||
Expected: []sql.Row{{"agg_sum", "a"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE AGGREGATE with STRICT transition function and no INITCOND",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION agg_larger_step(state int4, val int4) RETURNS int4
|
||||
AS $$ SELECT CASE WHEN state > val THEN state ELSE val END $$ LANGUAGE SQL STRICT;`,
|
||||
`CREATE TABLE agg_larger_vals (pk int4 PRIMARY KEY, v int4);`,
|
||||
`INSERT INTO agg_larger_vals VALUES (1, 3), (2, NULL), (3, 8), (4, 5);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_larger (int4) (SFUNC = agg_larger_step, STYPE = int4);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{ // A STRICT transition function skips NULL inputs, and the first non-NULL value seeds the state
|
||||
Query: `SELECT agg_larger(v) FROM agg_larger_vals;`,
|
||||
Expected: []sql.Row{{8}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_larger(v) FROM agg_larger_vals WHERE pk = 2;`,
|
||||
Expected: []sql.Row{{nil}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_larger(v) FROM agg_larger_vals WHERE pk = 0;`,
|
||||
Expected: []sql.Row{{nil}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agginitval IS NULL FROM pg_aggregate WHERE aggtransfn::text = 'agg_larger_step';`,
|
||||
Expected: []sql.Row{{"t"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE AGGREGATE with FINALFUNC",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION agg_charcount_step(state int4, val text) RETURNS int4
|
||||
AS $$ SELECT state + length(val) $$ LANGUAGE SQL;`,
|
||||
`CREATE FUNCTION agg_charcount_final(state int4) RETURNS text
|
||||
AS $$ SELECT 'chars: ' || state $$ LANGUAGE SQL;`,
|
||||
`CREATE TABLE agg_charcount_vals (pk int4 PRIMARY KEY, v text);`,
|
||||
`INSERT INTO agg_charcount_vals VALUES (1, 'ab'), (2, 'cde'), (3, 'f');`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_charcount (text) (SFUNC = agg_charcount_step, STYPE = int4,
|
||||
FINALFUNC = agg_charcount_final, INITCOND = '0');`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_charcount(v) FROM agg_charcount_vals;`,
|
||||
Expected: []sql.Row{{"chars: 6"}},
|
||||
},
|
||||
{ // The final function still runs when the aggregate sees no rows
|
||||
Query: `SELECT agg_charcount(v) FROM agg_charcount_vals WHERE pk = 0;`,
|
||||
Expected: []sql.Row{{"chars: 0"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT aggfinalfn::text FROM pg_aggregate WHERE aggtransfn::text = 'agg_charcount_step';`,
|
||||
Expected: []sql.Row{{"agg_charcount_final"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE AGGREGATE with COMBINEFUNC",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION agg_combined_step(state int8, val int4) RETURNS int8
|
||||
AS $$ SELECT state + val $$ LANGUAGE SQL;`,
|
||||
`CREATE FUNCTION agg_combined_merge(s1 int8, s2 int8) RETURNS int8
|
||||
AS $$ SELECT s1 + s2 $$ LANGUAGE SQL;`,
|
||||
`CREATE TABLE agg_combined_vals (pk int4 PRIMARY KEY, v int4);`,
|
||||
`INSERT INTO agg_combined_vals VALUES (1, 10), (2, 20), (3, 5);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_combined (int4) (SFUNC = agg_combined_step, STYPE = int8,
|
||||
COMBINEFUNC = agg_combined_merge, INITCOND = '0');`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_combined(v) FROM agg_combined_vals;`,
|
||||
Expected: []sql.Row{{35}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT aggcombinefn::text FROM pg_aggregate WHERE aggtransfn::text = 'agg_combined_step';`,
|
||||
Expected: []sql.Row{{"agg_combined_merge"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE AGGREGATE with multiple arguments",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION agg_wsum_step(state int8, val int4, weight int4) RETURNS int8
|
||||
AS $$ SELECT state + (val * weight) $$ LANGUAGE SQL;`,
|
||||
`CREATE TABLE agg_wsum_vals (pk int4 PRIMARY KEY, v int4, w int4);`,
|
||||
`INSERT INTO agg_wsum_vals VALUES (1, 10, 1), (2, 20, 2), (3, 30, 3);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_wsum (int4, int4) (SFUNC = agg_wsum_step, STYPE = int8, INITCOND = '0');`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_wsum(v, w) FROM agg_wsum_vals;`,
|
||||
Expected: []sql.Row{{140}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE AGGREGATE in a custom schema",
|
||||
SetUpScript: []string{
|
||||
`CREATE SCHEMA agg_nsp;`,
|
||||
`CREATE FUNCTION agg_nsp_step(state int4, val int4) RETURNS int4
|
||||
AS $$ SELECT state + 1 $$ LANGUAGE SQL;`,
|
||||
`CREATE TABLE agg_nsp_vals (pk int4 PRIMARY KEY, v int4);`,
|
||||
`INSERT INTO agg_nsp_vals VALUES (1, 10), (2, 20), (3, NULL);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_nsp.agg_rows (int4) (SFUNC = agg_nsp_step, STYPE = int4, INITCOND = '0');`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_nsp.agg_rows(v) FROM agg_nsp_vals;`,
|
||||
Expected: []sql.Row{{3}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT n.nspname FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE p.proname = 'agg_rows';`,
|
||||
Expected: []sql.Row{{"agg_nsp"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_rows(v) FROM agg_nsp_vals;`,
|
||||
ExpectedErr: "function: 'agg_rows' not found",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE OR REPLACE AGGREGATE",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION agg_replace_step(state int4, val int4) RETURNS int4
|
||||
AS $$ SELECT state + val $$ LANGUAGE SQL;`,
|
||||
`CREATE TABLE agg_replace_vals (pk int4 PRIMARY KEY, v int4);`,
|
||||
`INSERT INTO agg_replace_vals VALUES (1, 10), (2, 20);`,
|
||||
`CREATE AGGREGATE agg_replace (int4) (SFUNC = agg_replace_step, STYPE = int4, INITCOND = '0');`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `SELECT agg_replace(v) FROM agg_replace_vals;`,
|
||||
Expected: []sql.Row{{30}},
|
||||
},
|
||||
{
|
||||
Query: `CREATE OR REPLACE AGGREGATE agg_replace (int4) (SFUNC = agg_replace_step, STYPE = int4, INITCOND = '100');`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_replace(v) FROM agg_replace_vals;`,
|
||||
Expected: []sql.Row{{130}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agginitval FROM pg_aggregate WHERE aggtransfn::text = 'agg_replace_step';`,
|
||||
Expected: []sql.Row{{"100"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "DROP AGGREGATE smoke test",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION agg_drop_step(state int4, val int4) RETURNS int4
|
||||
AS $$ SELECT state + val $$ LANGUAGE SQL;`,
|
||||
`CREATE TABLE agg_drop_vals (pk int4 PRIMARY KEY, v int4);`,
|
||||
`INSERT INTO agg_drop_vals VALUES (1, 10), (2, 20);`,
|
||||
`CREATE AGGREGATE agg_drop (int4) (SFUNC = agg_drop_step, STYPE = int4, INITCOND = '0');`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `SELECT agg_drop(v) FROM agg_drop_vals;`,
|
||||
Expected: []sql.Row{{30}},
|
||||
},
|
||||
{
|
||||
Query: `DROP AGGREGATE agg_drop(int4);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_drop(v) FROM agg_drop_vals;`,
|
||||
ExpectedErr: "function: 'agg_drop' not found",
|
||||
},
|
||||
{
|
||||
Query: `SELECT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'agg_drop');`,
|
||||
Expected: []sql.Row{{"f"}},
|
||||
},
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_drop (int4) (SFUNC = agg_drop_step, STYPE = int4, INITCOND = '0');`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT agg_drop(v) FROM agg_drop_vals;`,
|
||||
Expected: []sql.Row{{30}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE AGGREGATE validation",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION agg_valid_step(state int4, val int4) RETURNS int4
|
||||
AS $$ SELECT state + val $$ LANGUAGE SQL;`,
|
||||
`CREATE FUNCTION agg_one_arg(state int4) RETURNS int4
|
||||
AS $$ SELECT state $$ LANGUAGE SQL;`,
|
||||
`CREATE FUNCTION agg_wrong_ret(state int4, val int4) RETURNS text
|
||||
AS $$ SELECT 'x' $$ LANGUAGE SQL;`,
|
||||
`CREATE FUNCTION agg_taken(a int4) RETURNS int4
|
||||
AS $$ SELECT a $$ LANGUAGE SQL;`,
|
||||
`CREATE AGGREGATE agg_valid (int4) (SFUNC = agg_valid_step, STYPE = int4);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_bad1 (int4) (SFUNC = agg_missing_step, STYPE = int4);`,
|
||||
ExpectedErr: "function agg_missing_step(integer, integer) does not exist",
|
||||
},
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_bad2 (int4) (SFUNC = agg_one_arg, STYPE = int4);`,
|
||||
ExpectedErr: "function agg_one_arg(integer, integer) does not exist",
|
||||
},
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_bad3 (int4) (SFUNC = agg_wrong_ret, STYPE = int4);`,
|
||||
ExpectedErr: "return type of transition function agg_wrong_ret is not integer",
|
||||
},
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_bad4 (int4) (SFUNC = agg_valid_step, STYPE = int4, FINALFUNC = agg_missing_final);`,
|
||||
ExpectedErr: "function agg_missing_final(integer) does not exist",
|
||||
},
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_valid (int4) (SFUNC = agg_valid_step, STYPE = int4);`,
|
||||
ExpectedErr: `function "agg_valid" already exists with same argument types`,
|
||||
},
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_taken (int4) (SFUNC = agg_valid_step, STYPE = int4);`,
|
||||
ExpectedErr: `function "agg_taken" already exists with same argument types`,
|
||||
},
|
||||
{
|
||||
Query: `CREATE AGGREGATE agg_bad5 (OUT x int4) (SFUNC = agg_valid_step, STYPE = int4);`,
|
||||
ExpectedErr: "aggregates cannot have output arguments",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
// 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 _go
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
)
|
||||
|
||||
func TestCreateOperator(t *testing.T) {
|
||||
RunScripts(t, []ScriptTest{
|
||||
{
|
||||
Name: "CREATE OPERATOR with LEFTARG, RIGHTARG, and FUNCTION",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION op_int_dist(a int4, b int4) RETURNS int4
|
||||
AS $$ SELECT abs(a - b) $$ LANGUAGE SQL;`,
|
||||
`CREATE TABLE op_points (pk int4 PRIMARY KEY, v int4);`,
|
||||
`INSERT INTO op_points VALUES (1, 4), (2, 9), (3, 15);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE OPERATOR <-> (LEFTARG = int4, RIGHTARG = int4, FUNCTION = op_int_dist);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 3 <-> 10;`,
|
||||
Expected: []sql.Row{{7}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 10 <-> 3;`,
|
||||
Expected: []sql.Row{{7}},
|
||||
},
|
||||
{ // NULL operands are passed through to the non-STRICT backing function
|
||||
Query: `SELECT (NULL::int4 <-> 3) IS NULL;`,
|
||||
Expected: []sql.Row{{"t"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT pk, v <-> 10 FROM op_points ORDER BY pk;`,
|
||||
Expected: []sql.Row{{1, 6}, {2, 1}, {3, 5}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT pk FROM op_points WHERE v <-> 10 < 3 ORDER BY pk;`,
|
||||
Expected: []sql.Row{{2}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT oprname, oprkind, oprcanhash, oprcanmerge, oprleft::regtype::text, oprright::regtype::text, oprresult::regtype::text
|
||||
FROM pg_operator WHERE oprcode::text = 'op_int_dist';`,
|
||||
Expected: []sql.Row{{"<->", "b", "f", "f", "integer", "integer", "integer"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT n.nspname FROM pg_operator o JOIN pg_namespace n ON n.oid = o.oprnamespace WHERE o.oprcode::text = 'op_int_dist';`,
|
||||
Expected: []sql.Row{{"public"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE OPERATOR with COMMUTATOR and NEGATOR",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION op_len_eq(a text, b text) RETURNS boolean
|
||||
AS $$ SELECT length(a) = length(b) $$ LANGUAGE SQL;`,
|
||||
`CREATE FUNCTION op_len_ne(a text, b text) RETURNS boolean
|
||||
AS $$ SELECT length(a) <> length(b) $$ LANGUAGE SQL;`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE OPERATOR <=> (LEFTARG = text, RIGHTARG = text, FUNCTION = op_len_eq, COMMUTATOR = <=>);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `CREATE OPERATOR <~> (LEFTARG = text, RIGHTARG = text, FUNCTION = op_len_ne, COMMUTATOR = <~>, NEGATOR = <=>);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 'abc' <=> 'xyz';`,
|
||||
Expected: []sql.Row{{"t"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 'abc' <=> 'wxyz';`,
|
||||
Expected: []sql.Row{{"f"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 'abc' <~> 'wxyz';`,
|
||||
Expected: []sql.Row{{"t"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT c.oprname FROM pg_operator o JOIN pg_operator c ON c.oid = o.oprcom WHERE o.oprcode::text = 'op_len_eq';`,
|
||||
Expected: []sql.Row{{"<=>"}},
|
||||
},
|
||||
{ // Declaring the negator on one operator links both directions
|
||||
Query: `SELECT n.oprname FROM pg_operator o JOIN pg_operator n ON n.oid = o.oprnegate WHERE o.oprcode::text = 'op_len_ne';`,
|
||||
Expected: []sql.Row{{"<=>"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT n.oprname FROM pg_operator o JOIN pg_operator n ON n.oid = o.oprnegate WHERE o.oprcode::text = 'op_len_eq';`,
|
||||
Expected: []sql.Row{{"<~>"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE OPERATOR with HASHES and MERGES",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION op_ci_eq(a text, b text) RETURNS boolean
|
||||
AS $$ SELECT lower(a) = lower(b) $$ LANGUAGE SQL;`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{ // We're not testing HASHES or MERGES, just that they parse since they're ignored options
|
||||
Query: `CREATE OPERATOR <%> (LEFTARG = text, RIGHTARG = text, FUNCTION = op_ci_eq, COMMUTATOR = <%>, HASHES, MERGES);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 'ABC' <%> 'abc';`,
|
||||
Expected: []sql.Row{{"t"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT oprcanhash, oprcanmerge FROM pg_operator WHERE oprcode::text = 'op_ci_eq';`,
|
||||
Expected: []sql.Row{{"t", "t"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE OPERATOR with composite operands",
|
||||
SetUpScript: []string{
|
||||
`CREATE TABLE op_pair (x int4, y int4);`,
|
||||
`CREATE FUNCTION op_pair_add(a op_pair, b op_pair) RETURNS op_pair
|
||||
AS $$ SELECT ROW((a).x + (b).x, (a).y + (b).y)::op_pair $$ LANGUAGE SQL;`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE OPERATOR <+> (LEFTARG = op_pair, RIGHTARG = op_pair, FUNCTION = op_pair_add);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT ROW(1, 2)::op_pair <+> ROW(3, 4)::op_pair;`,
|
||||
Expected: []sql.Row{{"(4,6)"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT oprleft::regtype::text, oprright::regtype::text, oprresult::regtype::text
|
||||
FROM pg_operator WHERE oprcode::text = 'op_pair_add';`,
|
||||
Expected: []sql.Row{{"op_pair", "op_pair", "op_pair"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE OPERATOR with composite operands on table rows in aggregate",
|
||||
SetUpScript: []string{
|
||||
`CREATE TABLE op_pair (x int4, y int4);`,
|
||||
`CREATE FUNCTION op_pair_add(a op_pair, b op_pair) RETURNS op_pair
|
||||
AS $$ SELECT ROW((a).x + (b).x, (a).y + (b).y)::op_pair $$ LANGUAGE SQL;`,
|
||||
`CREATE OPERATOR <+> (LEFTARG = op_pair, RIGHTARG = op_pair, FUNCTION = op_pair_add);`,
|
||||
`INSERT INTO op_pair VALUES (1, 2), (3, 4), (5, 6);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `SELECT sum(((p <+> ROW(10, 20)::op_pair)).x), sum(((p <+> ROW(10, 20)::op_pair)).y)
|
||||
FROM op_pair p;`,
|
||||
Expected: []sql.Row{{39, 72}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE OPERATOR with mixed operand types",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION op_repeat(a text, b int4) RETURNS text
|
||||
AS $$ SELECT repeat(a, b) $$ LANGUAGE SQL;`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE OPERATOR <#> (LEFTARG = text, RIGHTARG = int4, FUNCTION = op_repeat);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 'ab' <#> 3;`,
|
||||
Expected: []sql.Row{{"ababab"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 3 <#> 'ab';`,
|
||||
ExpectedErr: "operator does not exist",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "DROP OPERATOR smoke test",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION op_drop_dist(a int4, b int4) RETURNS int4
|
||||
AS $$ SELECT abs(a - b) $$ LANGUAGE SQL;`,
|
||||
`CREATE OPERATOR <-> (LEFTARG = int4, RIGHTARG = int4, FUNCTION = op_drop_dist);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `SELECT 3 <-> 10;`,
|
||||
Expected: []sql.Row{{7}},
|
||||
},
|
||||
{
|
||||
Query: `DROP OPERATOR <-> (int4, int4);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 3 <-> 10;`,
|
||||
ExpectedErr: "operator does not exist: integer <-> integer",
|
||||
},
|
||||
{
|
||||
Query: `SELECT EXISTS (SELECT 1 FROM pg_operator WHERE oprcode::text = 'op_drop_dist');`,
|
||||
Expected: []sql.Row{{"f"}},
|
||||
},
|
||||
{
|
||||
Query: `CREATE OPERATOR <-> (LEFTARG = int4, RIGHTARG = int4, FUNCTION = op_drop_dist);`,
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: `SELECT 3 <-> 10;`,
|
||||
Expected: []sql.Row{{7}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CREATE OPERATOR validation",
|
||||
SetUpScript: []string{
|
||||
`CREATE FUNCTION op_valid_dist(a int4, b int4) RETURNS int4
|
||||
AS $$ SELECT abs(a - b) $$ LANGUAGE SQL;`,
|
||||
`CREATE FUNCTION op_one_arg(a int4) RETURNS int4
|
||||
AS $$ SELECT a $$ LANGUAGE SQL;`,
|
||||
`CREATE OPERATOR <-> (LEFTARG = int4, RIGHTARG = int4, FUNCTION = op_valid_dist);`,
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `CREATE OPERATOR <=> (LEFTARG = int4, RIGHTARG = int4);`,
|
||||
ExpectedErr: "operator function must be specified",
|
||||
},
|
||||
{
|
||||
Query: `CREATE OPERATOR <=> (FUNCTION = op_valid_dist);`,
|
||||
ExpectedErr: "operator argument types must be specified",
|
||||
},
|
||||
{
|
||||
Query: `CREATE OPERATOR <=> (LEFTARG = int4, FUNCTION = op_valid_dist);`,
|
||||
ExpectedErr: "operator right argument type must be specified",
|
||||
},
|
||||
{
|
||||
Query: `CREATE OPERATOR <=> (LEFTARG = int4, RIGHTARG = int4, FUNCTION = op_missing_fn);`,
|
||||
ExpectedErr: "function op_missing_fn(integer, integer) does not exist",
|
||||
},
|
||||
{
|
||||
Query: `CREATE OPERATOR <=> (LEFTARG = int4, RIGHTARG = int4, FUNCTION = op_one_arg);`,
|
||||
ExpectedErr: "function op_one_arg(integer, integer) does not exist",
|
||||
},
|
||||
{
|
||||
Query: `CREATE OPERATOR <-> (LEFTARG = int4, RIGHTARG = int4, FUNCTION = op_valid_dist);`,
|
||||
ExpectedErr: "operator <-> already exists",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
// 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 _go
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
"github.com/dolthub/doltgresql/server/extensions"
|
||||
"github.com/dolthub/doltgresql/server/extensions/extdef"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
func TestExtensionEmulation(t *testing.T) {
|
||||
registerTestExtension()
|
||||
RunScripts(t, []ScriptTest{
|
||||
{
|
||||
Name: "Declared types are created with their array type",
|
||||
SetUpScript: []string{
|
||||
"CREATE EXTENSION doltgres_test;",
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `SELECT typname, typtype, typcategory, typlen, typinput::text, typoutput::text FROM pg_type WHERE typname = 'dgtest_upper';`,
|
||||
Expected: []sql.Row{
|
||||
{"dgtest_upper", "b", "U", -1, "dgtest_upper_in", "dgtest_upper_out"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Query: `SELECT typname, typtype, typcategory FROM pg_type WHERE typname = '_dgtest_upper';`,
|
||||
Expected: []sql.Row{{"_dgtest_upper", "b", "A"}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT 'abc'::dgtest_upper;",
|
||||
Expected: []sql.Row{{"ABC"}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT 'MiXeD'::dgtest_upper;",
|
||||
Expected: []sql.Row{{"MIXED"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Declared operators resolve for their operand types",
|
||||
SetUpScript: []string{
|
||||
"CREATE EXTENSION doltgres_test;",
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: "SELECT 'abc'::dgtest_upper = 'ABC'::dgtest_upper;",
|
||||
Expected: []sql.Row{{"t"}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT 'abc'::dgtest_upper = 'xyz'::dgtest_upper;",
|
||||
Expected: []sql.Row{{"f"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT oprname, oprkind, oprcanhash, oprcanmerge FROM pg_operator WHERE oprcode::text = 'dgtest_upper_eq';`,
|
||||
Expected: []sql.Row{{"=", "b", "t", "t"}},
|
||||
},
|
||||
{ // A symbol that no built-in operator resolves through the same path
|
||||
Query: "SELECT 'abc'::dgtest_upper <-> 'wxyz'::dgtest_upper;",
|
||||
Expected: []sql.Row{{1}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT 'abc'::dgtest_upper <-> 'xyz'::dgtest_upper;",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Declared casts convert to their target type",
|
||||
SetUpScript: []string{
|
||||
"CREATE EXTENSION doltgres_test;",
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: "SELECT ('abc'::dgtest_upper)::text || 'def';",
|
||||
Expected: []sql.Row{{"ABCdef"}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT length(('abcd'::dgtest_upper)::text);",
|
||||
Expected: []sql.Row{{4}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Declared aggregates accumulate through their transition and final functions",
|
||||
SetUpScript: []string{
|
||||
"CREATE EXTENSION doltgres_test;",
|
||||
"CREATE TABLE t1 (pk INTEGER PRIMARY KEY, v1 TEXT);",
|
||||
"INSERT INTO t1 VALUES (1, 'ab'), (2, 'cde'), (3, 'f');",
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: "SELECT dgtest_charcount(v1) FROM t1;",
|
||||
Expected: []sql.Row{{"6"}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT dgtest_charcount(v1) FROM t1 WHERE pk = 2;",
|
||||
Expected: []sql.Row{{"3"}},
|
||||
},
|
||||
{ // The initial condition is the state when the aggregate sees no rows at all
|
||||
Query: "SELECT dgtest_charcount(v1) FROM t1 WHERE pk = 0;",
|
||||
Expected: []sql.Row{{"0"}},
|
||||
},
|
||||
{
|
||||
Query: `SELECT aggkind, agginitval, aggcombinefn::text, aggfinalfn::text FROM pg_aggregate WHERE aggtransfn::text = 'dgtest_charcount_transition';`,
|
||||
Expected: []sql.Row{{"n", "0", "dgtest_charcount_combine", "dgtest_charcount_final"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Objects are created in the extension's target schema",
|
||||
SetUpScript: []string{
|
||||
"CREATE EXTENSION doltgres_test;",
|
||||
},
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: `SELECT n.nspname FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace WHERE t.typname = 'dgtest_upper';`,
|
||||
Expected: []sql.Row{{"public"}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT public.dgtest_upper_text('abc'::public.dgtest_upper);",
|
||||
Expected: []sql.Row{{"ABC"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Extension objects are not available before the extension is created",
|
||||
Assertions: []ScriptTestAssertion{
|
||||
{
|
||||
Query: "SELECT 'abc'::dgtest_upper;",
|
||||
ExpectedErr: "unable to resolve type",
|
||||
},
|
||||
{
|
||||
Query: "SELECT dgtest_charcount('abc');",
|
||||
ExpectedErr: "not found",
|
||||
},
|
||||
{
|
||||
Query: "CREATE EXTENSION doltgres_test;",
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: "SELECT dgtest_charcount('abc');",
|
||||
Expected: []sql.Row{{"3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// registerExtensionOnce ensures that registerTestExtension only runs once.
|
||||
var registerExtensionOnce sync.Once
|
||||
|
||||
// registerTestExtension registers a dummy extension for testing, which declares one of every kind of object. Every test
|
||||
// whose expected output includes the dummy extension must call this first.
|
||||
func registerTestExtension() {
|
||||
registerExtensionOnce.Do(func() {
|
||||
extensions.Register(&extdef.Extension{
|
||||
Name: "doltgres_test",
|
||||
Control: extdef.Control{
|
||||
DefaultVersion: "1.0",
|
||||
Comment: "test extension to ensure that emulated extensions behave properly",
|
||||
Relocatable: true,
|
||||
},
|
||||
Types: []extdef.Type{
|
||||
{
|
||||
Name: "dgtest_upper",
|
||||
Definition: pgtypes.NewBaseTypeDefinition(),
|
||||
Input: "dgtest_upper_in",
|
||||
Output: "dgtest_upper_out",
|
||||
},
|
||||
},
|
||||
Routines: []extdef.Routine{
|
||||
{
|
||||
Name: "dgtest_upper_in",
|
||||
Symbol: "dgtest_upper_in",
|
||||
Parameters: []extdef.Parameter{{Type: "cstring"}},
|
||||
Returns: "dgtest_upper",
|
||||
Strict: true,
|
||||
Impl: func(ctx *sql.Context, args ...any) (any, error) {
|
||||
return strings.ToUpper(args[0].(string)), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dgtest_upper_out",
|
||||
Symbol: "dgtest_upper_out",
|
||||
Parameters: []extdef.Parameter{{Type: "dgtest_upper"}},
|
||||
Returns: "cstring",
|
||||
Strict: true,
|
||||
Impl: func(ctx *sql.Context, args ...any) (any, error) {
|
||||
return args[0].(string), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dgtest_upper_eq",
|
||||
Symbol: "dgtest_upper_eq",
|
||||
Parameters: []extdef.Parameter{{Type: "dgtest_upper"}, {Type: "dgtest_upper"}},
|
||||
Returns: "bool",
|
||||
Strict: true,
|
||||
Impl: func(ctx *sql.Context, args ...any) (any, error) {
|
||||
return args[0].(string) == args[1].(string), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dgtest_upper_text",
|
||||
Symbol: "dgtest_upper_text",
|
||||
Parameters: []extdef.Parameter{{Type: "dgtest_upper"}},
|
||||
Returns: "text",
|
||||
Strict: true,
|
||||
Impl: func(ctx *sql.Context, args ...any) (any, error) {
|
||||
return args[0].(string), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dgtest_upper_distance",
|
||||
Symbol: "dgtest_upper_distance",
|
||||
Parameters: []extdef.Parameter{{Type: "dgtest_upper"}, {Type: "dgtest_upper"}},
|
||||
Returns: "int4",
|
||||
Strict: true,
|
||||
Impl: func(ctx *sql.Context, args ...any) (any, error) {
|
||||
return int32(utils.Abs(len(args[0].(string)) - len(args[1].(string)))), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dgtest_charcount_transition",
|
||||
Symbol: "dgtest_charcount_transition",
|
||||
Parameters: []extdef.Parameter{{Type: "int4"}, {Type: "text"}},
|
||||
Returns: "int4",
|
||||
Strict: true,
|
||||
Impl: func(ctx *sql.Context, args ...any) (any, error) {
|
||||
return args[0].(int32) + int32(len([]rune(args[1].(string)))), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dgtest_charcount_combine",
|
||||
Symbol: "dgtest_charcount_combine",
|
||||
Parameters: []extdef.Parameter{{Type: "int4"}, {Type: "int4"}},
|
||||
Returns: "int4",
|
||||
Strict: true,
|
||||
Impl: func(ctx *sql.Context, args ...any) (any, error) {
|
||||
return args[0].(int32) + args[1].(int32), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dgtest_charcount_final",
|
||||
Symbol: "dgtest_charcount_final",
|
||||
Parameters: []extdef.Parameter{{Type: "int4"}},
|
||||
Returns: "text",
|
||||
Strict: true,
|
||||
Impl: func(ctx *sql.Context, args ...any) (any, error) {
|
||||
return strconv.Itoa(int(args[0].(int32))), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
Operators: []extdef.Operator{
|
||||
{
|
||||
Symbol: "=",
|
||||
Left: "dgtest_upper",
|
||||
Right: "dgtest_upper",
|
||||
Routine: "dgtest_upper_eq",
|
||||
Commutator: "=",
|
||||
Hashes: true,
|
||||
Merges: true,
|
||||
},
|
||||
{
|
||||
Symbol: "<->",
|
||||
Left: "dgtest_upper",
|
||||
Right: "dgtest_upper",
|
||||
Routine: "dgtest_upper_distance",
|
||||
Commutator: "<->",
|
||||
},
|
||||
},
|
||||
Casts: []extdef.Cast{
|
||||
{
|
||||
Source: "dgtest_upper",
|
||||
Target: "text",
|
||||
Routine: "dgtest_upper_text",
|
||||
CastType: casts.CastType_Assignment,
|
||||
},
|
||||
},
|
||||
Aggregates: []extdef.Aggregate{
|
||||
{
|
||||
Name: "dgtest_charcount",
|
||||
Parameters: []extdef.Parameter{{Type: "text"}},
|
||||
Returns: "text",
|
||||
StateType: "int4",
|
||||
Transition: "dgtest_charcount_transition",
|
||||
Final: "dgtest_charcount_final",
|
||||
Combine: "dgtest_charcount_combine",
|
||||
InitCond: "0",
|
||||
HasInitCond: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -435,6 +435,7 @@ func TestPgAuthid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPgAvailableExtensionVersions(t *testing.T) {
|
||||
registerTestExtension()
|
||||
RunScripts(t, []ScriptTest{
|
||||
{
|
||||
Name: "pg_available_extension_versions",
|
||||
@@ -447,6 +448,7 @@ func TestPgAvailableExtensionVersions(t *testing.T) {
|
||||
{
|
||||
Query: `SELECT name, version, installed, superuser, trusted, relocatable, schema, requires, comment FROM "pg_catalog"."pg_available_extension_versions" ORDER BY name;`,
|
||||
Expected: []sql.Row{
|
||||
{"doltgres_test", "1.0", "f", "f", "f", "t", nil, nil, "test extension to ensure that emulated extensions behave properly"},
|
||||
{"uuid-ossp", "1.1", "f", "t", "t", "t", nil, nil, "generate universally unique identifiers (UUIDs)"},
|
||||
},
|
||||
},
|
||||
@@ -484,6 +486,7 @@ func TestPgAvailableExtensionVersions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPgAvailableExtensions(t *testing.T) {
|
||||
registerTestExtension()
|
||||
RunScripts(t, []ScriptTest{
|
||||
{
|
||||
Name: "pg_available_extensions",
|
||||
@@ -496,6 +499,7 @@ func TestPgAvailableExtensions(t *testing.T) {
|
||||
{
|
||||
Query: `SELECT name, default_version, installed_version, comment FROM "pg_catalog"."pg_available_extensions" ORDER BY name;`,
|
||||
Expected: []sql.Row{
|
||||
{"doltgres_test", "1.0", nil, "test extension to ensure that emulated extensions behave properly"},
|
||||
{"uuid-ossp", "1.1", nil, "generate universally unique identifiers (UUIDs)"},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user