191 lines
7.4 KiB
Go
191 lines
7.4 KiB
Go
// Copyright 2025 Dolthub, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package sequences
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
|
"github.com/dolthub/dolt/go/store/hash"
|
|
"github.com/dolthub/dolt/go/store/prolly"
|
|
"github.com/dolthub/dolt/go/store/prolly/tree"
|
|
|
|
"github.com/dolthub/doltgresql/core/id"
|
|
merge2 "github.com/dolthub/doltgresql/core/merge"
|
|
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
|
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
|
pgtypes "github.com/dolthub/doltgresql/server/types"
|
|
"github.com/dolthub/doltgresql/utils"
|
|
)
|
|
|
|
// storage is used to read from and write to the root.
|
|
var storage = objinterface.RootObjectSerializer{
|
|
Bytes: (*serial.RootValue).SequencesBytes,
|
|
RootValueAdd: serial.RootValueAddSequences,
|
|
}
|
|
|
|
// HandleMerge implements the interface objinterface.Collection.
|
|
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
|
ourSeq := mro.OurRootObj.(*Sequence)
|
|
theirSeq := mro.TheirRootObj.(*Sequence)
|
|
// Ensure that they have the same identifier
|
|
if ourSeq.Id != theirSeq.Id {
|
|
return nil, nil, errors.Newf("attempted to merge different sequences: `%s` and `%s`",
|
|
ourSeq.Name().String(), theirSeq.Name().String())
|
|
}
|
|
// Check if an ancestor is present
|
|
var ancSeq Sequence
|
|
hasAncestor := false
|
|
if mro.AncestorRootObj != nil {
|
|
ancSeq = *(mro.AncestorRootObj.(*Sequence))
|
|
hasAncestor = true
|
|
}
|
|
// Take the min/max of fields that aren't dependent on the increment direction
|
|
mergedSeq := *ourSeq
|
|
mergedSeq.Minimum = merge2.ResolveMergeValuesVariadic(ourSeq.Minimum, theirSeq.Minimum, ancSeq.Minimum, hasAncestor, utils.Min)
|
|
mergedSeq.Maximum = merge2.ResolveMergeValuesVariadic(ourSeq.Maximum, theirSeq.Maximum, ancSeq.Maximum, hasAncestor, utils.Max)
|
|
mergedSeq.Cache = merge2.ResolveMergeValuesVariadic(ourSeq.Cache, theirSeq.Cache, ancSeq.Cache, hasAncestor, utils.Min)
|
|
mergedSeq.Cycle = merge2.ResolveMergeValues(ourSeq.Cycle, theirSeq.Cycle, ancSeq.Cycle, hasAncestor, func(ourCycle, theirCycle bool) bool {
|
|
return ourCycle || theirCycle
|
|
})
|
|
// Take the largest type specified
|
|
mergedSeq.DataTypeID = merge2.ResolveMergeValues(ourSeq.DataTypeID, theirSeq.DataTypeID, ancSeq.DataTypeID, hasAncestor, func(ourID, theirID id.Type) id.Type {
|
|
if (ourID == pgtypes.Int16.ID && (theirID == pgtypes.Int32.ID || theirID == pgtypes.Int64.ID)) ||
|
|
(ourID == pgtypes.Int32.ID && theirID == pgtypes.Int64.ID) {
|
|
return theirID
|
|
} else {
|
|
return ourID
|
|
}
|
|
})
|
|
// Handle the fields that are dependent on the increment direction.
|
|
// We'll always take the increment size that's the smallest for the most granularity, along with the one that
|
|
// has progressed the furthest.
|
|
// For opposing increment directions, we'll take whatever is in our collection.
|
|
mergedSeq.Increment = merge2.ResolveMergeValues(ourSeq.Increment, theirSeq.Increment, ancSeq.Increment, hasAncestor, func(ourIncrement, theirIncrement int64) int64 {
|
|
if ourSeq.Increment >= 0 && theirSeq.Increment >= 0 {
|
|
return utils.Min(ourIncrement, theirIncrement)
|
|
} else if ourSeq.Increment < 0 && theirSeq.Increment < 0 {
|
|
return utils.Max(ourIncrement, theirIncrement)
|
|
} else {
|
|
return ourIncrement
|
|
}
|
|
})
|
|
mergedSeq.Start = merge2.ResolveMergeValues(ourSeq.Start, theirSeq.Start, ancSeq.Start, hasAncestor, func(ourStart, theirStart int64) int64 {
|
|
if ourSeq.Increment >= 0 && theirSeq.Increment >= 0 {
|
|
return utils.Min(ourStart, theirStart)
|
|
} else if ourSeq.Increment < 0 && theirSeq.Increment < 0 {
|
|
return utils.Max(ourStart, theirStart)
|
|
} else {
|
|
return ourStart
|
|
}
|
|
})
|
|
mergedSeq.Current = merge2.ResolveMergeValues(ourSeq.Current, theirSeq.Current, ancSeq.Current, hasAncestor, func(ourCurrent, theirCurrent int64) int64 {
|
|
if ourSeq.Increment >= 0 && theirSeq.Increment >= 0 {
|
|
return utils.Max(ourCurrent, theirCurrent)
|
|
} else if ourSeq.Increment < 0 && theirSeq.Increment < 0 {
|
|
return utils.Min(ourCurrent, theirCurrent)
|
|
} else {
|
|
return ourCurrent
|
|
}
|
|
})
|
|
mergedSeq.HasBeenCalled = merge2.ResolveMergeValues(ourSeq.HasBeenCalled, theirSeq.HasBeenCalled, ancSeq.HasBeenCalled, hasAncestor, func(ourcalled, theirCalled bool) bool {
|
|
return ourcalled || theirCalled
|
|
})
|
|
return &mergedSeq, &merge.MergeStats{
|
|
Operation: merge.TableModified,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 1,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
}
|
|
|
|
// LoadCollection implements the interface objinterface.Collection.
|
|
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
|
return LoadSequences(ctx, root)
|
|
}
|
|
|
|
// LoadCollectionHash implements the interface objinterface.Collection.
|
|
func (*Collection) LoadCollectionHash(ctx context.Context, root objinterface.RootValue) (hash.Hash, error) {
|
|
m, ok, err := storage.GetProllyMap(ctx, root)
|
|
if err != nil || !ok {
|
|
return hash.Hash{}, err
|
|
}
|
|
return m.HashOf(), nil
|
|
}
|
|
|
|
// LoadSequences loads the sequences collection from the given root.
|
|
func LoadSequences(ctx context.Context, root objinterface.RootValue) (*Collection, error) {
|
|
m, ok, err := storage.GetProllyMap(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
m, err = prolly.NewEmptyAddressMap(root.NodeStore())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &Collection{
|
|
accessedMap: make(map[id.Sequence]*Sequence),
|
|
underlyingMap: m,
|
|
ns: root.NodeStore(),
|
|
}, nil
|
|
}
|
|
|
|
// ResolveNameFromObjects implements the interface objinterface.Collection.
|
|
func (*Collection) ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
|
// First we'll check if there are any objects to search through in the first place
|
|
accessedMap := make(map[id.Sequence]*Sequence)
|
|
for _, rootObject := range rootObjects {
|
|
if obj, ok := rootObject.(*Sequence); ok {
|
|
accessedMap[obj.Id] = obj
|
|
}
|
|
}
|
|
if len(accessedMap) == 0 {
|
|
return doltdb.TableName{}, id.Null, nil
|
|
}
|
|
// There are root objects to search through, so we'll create a temporary store
|
|
ns := tree.NewTestNodeStore()
|
|
addressMap, err := prolly.NewEmptyAddressMap(ns)
|
|
if err != nil {
|
|
return doltdb.TableName{}, id.Null, err
|
|
}
|
|
tempCollection := Collection{
|
|
accessedMap: accessedMap,
|
|
underlyingMap: addressMap,
|
|
ns: ns,
|
|
}
|
|
return tempCollection.ResolveName(ctx, name)
|
|
}
|
|
|
|
// Serializer implements the interface objinterface.Collection.
|
|
func (*Collection) Serializer() objinterface.RootObjectSerializer {
|
|
return storage
|
|
}
|
|
|
|
// UpdateRoot implements the interface objinterface.Collection.
|
|
func (pgs *Collection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
|
m, err := pgs.Map(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return storage.WriteProllyMap(ctx, root, m)
|
|
}
|