chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,474 @@
|
||||
// Copyright 2024 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sequences
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
)
|
||||
|
||||
// Collection contains a collection of sequences.
|
||||
type Collection struct {
|
||||
accessedMap map[id.Sequence]*Sequence // Whenever a sequence is accessed, it is added to the access map for faster retrieval
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// Persistence controls the persistence of a Sequence.
|
||||
type Persistence uint8
|
||||
|
||||
const (
|
||||
Persistence_Permanent Persistence = 0
|
||||
Persistence_Temporary Persistence = 1
|
||||
Persistence_Unlogged Persistence = 2
|
||||
)
|
||||
|
||||
// Sequence represents a single sequence within the pg_sequence table.
|
||||
type Sequence struct {
|
||||
Id id.Sequence
|
||||
DataTypeID id.Type
|
||||
Persistence Persistence
|
||||
Start int64
|
||||
Current int64
|
||||
Increment int64
|
||||
Minimum int64
|
||||
Maximum int64
|
||||
Cache int64
|
||||
Cycle bool
|
||||
IsAtEnd bool
|
||||
HasBeenCalled bool
|
||||
OwnerTable id.Table
|
||||
OwnerColumn string
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = (*Sequence)(nil)
|
||||
var _ doltdb.RootObject = (*Sequence)(nil)
|
||||
|
||||
// GetSequence returns the sequence with the given schema and name. Returns nil if the sequence cannot be found.
|
||||
func (pgs *Collection) GetSequence(ctx context.Context, name id.Sequence) (*Sequence, error) {
|
||||
return pgs.getSequence(ctx, name)
|
||||
}
|
||||
|
||||
// GetSequencesWithTable returns all sequences with the given table as the owner.
|
||||
func (pgs *Collection) GetSequencesWithTable(ctx context.Context, name doltdb.TableName) ([]*Sequence, error) {
|
||||
// For now, this function isn't used in a critical path, so we're not too worried about performance
|
||||
if err := pgs.cacheAllSequences(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var seqs []*Sequence
|
||||
nameID := id.NewTable(name.Schema, name.Name)
|
||||
for _, seq := range pgs.accessedMap {
|
||||
if seq.OwnerTable == nameID {
|
||||
seqs = append(seqs, seq)
|
||||
}
|
||||
}
|
||||
return seqs, nil
|
||||
}
|
||||
|
||||
// GetAllSequences returns a map containing all sequences in the collection, grouped by the schema they're contained in.
|
||||
// Each sequence array is also sorted by the sequence name.
|
||||
func (pgs *Collection) GetAllSequences(ctx context.Context) (sequences map[string][]*Sequence, schemaNames []string, totalCount int, err error) {
|
||||
// For now, this function is only used by the "reg" types, so we're not too worried about performance
|
||||
if err = pgs.cacheAllSequences(ctx); err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
totalCount = len(pgs.accessedMap)
|
||||
schemaNamesMap := make(map[string]struct{})
|
||||
sequences = make(map[string][]*Sequence)
|
||||
for seqID, seq := range pgs.accessedMap {
|
||||
schemaNamesMap[seqID.SchemaName()] = struct{}{}
|
||||
sequences[seqID.SchemaName()] = append(sequences[seqID.SchemaName()], seq)
|
||||
}
|
||||
// Sort the sequences in the sequence map
|
||||
for _, seqs := range sequences {
|
||||
sort.Slice(seqs, func(i, j int) bool {
|
||||
return seqs[i].Id < seqs[j].Id
|
||||
})
|
||||
}
|
||||
// Create and sort the schema names
|
||||
schemaNames = make([]string, 0, len(schemaNamesMap))
|
||||
for name := range schemaNamesMap {
|
||||
schemaNames = append(schemaNames, name)
|
||||
}
|
||||
sort.Slice(schemaNames, func(i, j int) bool {
|
||||
return schemaNames[i] < schemaNames[j]
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// HasSequence returns whether the sequence is present.
|
||||
func (pgs *Collection) HasSequence(ctx context.Context, name id.Sequence) bool {
|
||||
// Subsequent loads are cached
|
||||
if _, ok := pgs.accessedMap[name]; ok {
|
||||
return true
|
||||
}
|
||||
// The initial load is from the internal map
|
||||
ok, err := pgs.underlyingMap.Has(ctx, string(name))
|
||||
if err == nil && ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CreateSequence creates a new sequence.
|
||||
func (pgs *Collection) CreateSequence(ctx context.Context, seq *Sequence) error {
|
||||
// Ensure that the sequence does not already exist
|
||||
if _, ok := pgs.accessedMap[seq.Id]; ok {
|
||||
return errors.Errorf(`relation "%s" already exists`, seq.Id.SequenceName())
|
||||
}
|
||||
if ok, err := pgs.underlyingMap.Has(ctx, string(seq.Id)); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
return errors.Errorf(`relation "%s" already exists`, seq.Id.SequenceName())
|
||||
}
|
||||
// Add it to our cache, which will be emptied when we do anything permanent
|
||||
pgs.accessedMap[seq.Id] = seq
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropSequence drops existing sequences.
|
||||
func (pgs *Collection) DropSequence(ctx context.Context, names ...id.Sequence) (err error) {
|
||||
// We need to clear the cache so that we only need to worry about the underlying map
|
||||
if err = pgs.writeCache(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range names {
|
||||
if ok, err := pgs.underlyingMap.Has(ctx, string(name)); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return errors.Errorf(`sequence "%s" does not exist`, name.SequenceName())
|
||||
}
|
||||
}
|
||||
// Now we'll remove the sequences from the underlying map
|
||||
mapEditor := pgs.underlyingMap.Editor()
|
||||
for _, name := range names {
|
||||
if err = mapEditor.Delete(ctx, string(name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
flushed, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgs.underlyingMap = flushed
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given sequence. Returns an error if the name is ambiguous.
|
||||
func (pgs *Collection) resolveName(ctx context.Context, schemaName string, sequenceName string) (id.Sequence, error) {
|
||||
if err := pgs.writeCache(ctx); err != nil {
|
||||
return id.NullSequence, err
|
||||
}
|
||||
count, err := pgs.underlyingMap.Count()
|
||||
if err != nil || count == 0 {
|
||||
return id.NullSequence, err
|
||||
}
|
||||
|
||||
// First check for an exact match
|
||||
inputID := id.NewSequence(schemaName, sequenceName)
|
||||
ok, err := pgs.underlyingMap.Has(ctx, string(inputID))
|
||||
if err != nil {
|
||||
return id.NullSequence, err
|
||||
} else if ok {
|
||||
return inputID, nil
|
||||
}
|
||||
|
||||
// Now we'll iterate over all the names
|
||||
var resolvedID id.Sequence
|
||||
if len(schemaName) > 0 {
|
||||
err = pgs.underlyingMap.IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
seqID := id.Sequence(k)
|
||||
if strings.EqualFold(sequenceName, seqID.SequenceName()) &&
|
||||
strings.EqualFold(schemaName, seqID.SchemaName()) {
|
||||
if resolvedID.IsValid() {
|
||||
return fmt.Errorf("`%s.%s` is ambiguous, matches `%s.%s` and `%s.%s`",
|
||||
schemaName, sequenceName, seqID.SchemaName(), seqID.SequenceName(), resolvedID.SchemaName(), resolvedID.SequenceName())
|
||||
}
|
||||
resolvedID = seqID
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return id.NullSequence, err
|
||||
}
|
||||
} else {
|
||||
err = pgs.underlyingMap.IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
seqID := id.Sequence(k)
|
||||
if strings.EqualFold(sequenceName, seqID.SequenceName()) {
|
||||
if resolvedID.IsValid() {
|
||||
return fmt.Errorf("`%s` is ambiguous, matches `%s.%s` and `%s.%s`",
|
||||
sequenceName, seqID.SchemaName(), seqID.SequenceName(), resolvedID.SchemaName(), resolvedID.SequenceName())
|
||||
}
|
||||
resolvedID = seqID
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return id.NullSequence, err
|
||||
}
|
||||
}
|
||||
return resolvedID, nil
|
||||
}
|
||||
|
||||
// iterateIDs iterates over all sequence IDs in the collection.
|
||||
func (pgs *Collection) iterateIDs(ctx context.Context, f func(seqID id.Sequence) (stop bool, err error)) (err error) {
|
||||
if err = pgs.writeCache(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return pgs.underlyingMap.IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
seqID := id.Sequence(k)
|
||||
stop, err := f(seqID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// IterateSequences iterates over all sequences in the collection.
|
||||
func (pgs *Collection) IterateSequences(ctx context.Context, f func(seq *Sequence) (stop bool, err error)) (err error) {
|
||||
// For now, this function isn't used in a critical path, so we're not too worried about performance
|
||||
if err = pgs.cacheAllSequences(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, seq := range pgs.accessedMap {
|
||||
if stop, err := f(seq); err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextVal returns the next value in the sequence.
|
||||
func (pgs *Collection) NextVal(ctx context.Context, name id.Sequence) (int64, error) {
|
||||
seq, err := pgs.getSequence(ctx, name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if seq == nil {
|
||||
return 0, errors.Errorf(`relation "%s" does not exist`, name.SequenceName())
|
||||
}
|
||||
return seq.nextValForSequence()
|
||||
}
|
||||
|
||||
// SetVal sets the sequence to the
|
||||
func (pgs *Collection) SetVal(ctx context.Context, name id.Sequence, newValue int64, autoAdvance bool) error {
|
||||
seq, err := pgs.getSequence(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if seq == nil {
|
||||
return errors.Errorf(`relation "%s" does not exist`, name.SequenceName())
|
||||
}
|
||||
if newValue < seq.Minimum || newValue > seq.Maximum {
|
||||
return errors.Errorf(`setval: value %d is out of bounds for sequence "%s" (%d..%d)`,
|
||||
newValue, name, seq.Minimum, seq.Maximum)
|
||||
}
|
||||
seq.Current = newValue
|
||||
seq.IsAtEnd = false
|
||||
seq.HasBeenCalled = false
|
||||
if autoAdvance {
|
||||
_, err := seq.nextValForSequence()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pgs *Collection) Clone(ctx context.Context) *Collection {
|
||||
newCollection := &Collection{
|
||||
accessedMap: make(map[id.Sequence]*Sequence),
|
||||
underlyingMap: pgs.underlyingMap,
|
||||
ns: pgs.ns,
|
||||
}
|
||||
for seqID, seq := range pgs.accessedMap {
|
||||
newCollection.accessedMap[seqID] = seq
|
||||
}
|
||||
return newCollection
|
||||
}
|
||||
|
||||
// Map writes any cached sequences to the underlying map, and then returns the underlying map.
|
||||
func (pgs *Collection) Map(ctx context.Context) (prolly.AddressMap, error) {
|
||||
if err := pgs.writeCache(ctx); err != nil {
|
||||
return prolly.AddressMap{}, err
|
||||
}
|
||||
return pgs.underlyingMap, nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (sequence *Sequence) GetID() id.Id {
|
||||
return sequence.Id.AsId()
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (sequence *Sequence) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Sequences
|
||||
}
|
||||
|
||||
// HashOf implements the interface rootobject.RootObject.
|
||||
func (sequence *Sequence) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := sequence.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface rootobject.RootObject.
|
||||
func (sequence *Sequence) Name() doltdb.TableName {
|
||||
return doltdb.TableName{
|
||||
Name: sequence.Id.SequenceName(),
|
||||
Schema: sequence.Id.SchemaName(),
|
||||
}
|
||||
}
|
||||
|
||||
// cacheAllSequences loads every sequence from the Dolt map into our local map. This exists to simplify any iteration
|
||||
// logic, and shouldn't be used on a performance-critical path.
|
||||
func (pgs *Collection) cacheAllSequences(ctx context.Context) error {
|
||||
found := make(map[id.Sequence]struct{})
|
||||
for seqID := range pgs.accessedMap {
|
||||
found[seqID] = struct{}{}
|
||||
}
|
||||
return pgs.underlyingMap.IterAll(ctx, func(k string, v hash.Hash) error {
|
||||
seqID := id.Sequence(k)
|
||||
if _, ok := found[seqID]; ok {
|
||||
return nil
|
||||
}
|
||||
found[seqID] = struct{}{}
|
||||
data, err := pgs.ns.ReadBytes(ctx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seq, err := DeserializeSequence(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgs.accessedMap[seq.Id] = seq
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// getSequence gets the sequence matching the given name.
|
||||
func (pgs *Collection) getSequence(ctx context.Context, name id.Sequence) (*Sequence, error) {
|
||||
// Subsequent loads are cached
|
||||
if seq, ok := pgs.accessedMap[name]; ok {
|
||||
return seq, nil
|
||||
}
|
||||
// The initial load is from the internal map
|
||||
h, err := pgs.underlyingMap.Get(ctx, string(name))
|
||||
if err != nil || h.IsEmpty() {
|
||||
return nil, err
|
||||
}
|
||||
data, err := pgs.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seq, err := DeserializeSequence(ctx, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pgs.accessedMap[seq.Id] = seq
|
||||
return seq, nil
|
||||
}
|
||||
|
||||
// writeCache writes every Sequence in the cache to the underlying map.
|
||||
func (pgs *Collection) writeCache(ctx context.Context) (err error) {
|
||||
if len(pgs.accessedMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
mapEditor := pgs.underlyingMap.Editor()
|
||||
for _, seq := range pgs.accessedMap {
|
||||
data, err := seq.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pgs.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mapEditor.Update(ctx, string(seq.Id), h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Assign underlyingMap only after the error check. Flush returns a
|
||||
// zero AddressMap on failure, which would corrupt the Collection.
|
||||
flushed, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgs.underlyingMap = flushed
|
||||
clear(pgs.accessedMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextValForSequence increments the calling sequence.
|
||||
func (sequence *Sequence) nextValForSequence() (int64, error) {
|
||||
// First we'll check if we've reached the end, and cycle or error as necessary
|
||||
if sequence.IsAtEnd {
|
||||
if !sequence.Cycle {
|
||||
if sequence.Increment > 0 {
|
||||
return 0, errors.Errorf(`nextval: reached maximum value of sequence "%s" (%d)`, sequence.Id, sequence.Maximum)
|
||||
} else {
|
||||
return 0, errors.Errorf(`nextval: reached minimum value of sequence "%s" (%d)`, sequence.Id, sequence.Minimum)
|
||||
}
|
||||
}
|
||||
sequence.IsAtEnd = false
|
||||
if sequence.Increment > 0 {
|
||||
sequence.Current = sequence.Minimum
|
||||
} else {
|
||||
sequence.Current = sequence.Maximum
|
||||
}
|
||||
}
|
||||
// We'll return the current value, so everything after this sets the value for the next call
|
||||
sequence.HasBeenCalled = true
|
||||
valueToReturn := sequence.Current
|
||||
// Increment the current value
|
||||
if sequence.Increment > 0 {
|
||||
// Check for overflow or crossing the maximum, meaning we're at the end
|
||||
if sequence.Current > math.MaxInt64-sequence.Increment || sequence.Current+sequence.Increment > sequence.Maximum {
|
||||
sequence.IsAtEnd = true
|
||||
} else {
|
||||
sequence.Current += sequence.Increment
|
||||
}
|
||||
} else {
|
||||
// Check for underflow or crossing the minimum, meaning we're at the end
|
||||
if sequence.Current < math.MinInt64-sequence.Increment || sequence.Current+sequence.Increment < sequence.Minimum {
|
||||
sequence.IsAtEnd = true
|
||||
} else {
|
||||
sequence.Current += sequence.Increment
|
||||
}
|
||||
}
|
||||
return valueToReturn, nil
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// 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 sequences
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
)
|
||||
|
||||
// TestMap_RemainsUsableAfterFlushFailure asserts that Collection.Map
|
||||
// remains usable after a flush returns an error. A subsequent Map call
|
||||
// against a healthy store must succeed.
|
||||
func TestMap_RemainsUsableAfterFlushFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ns := newCountingFailNodeStore(t)
|
||||
coll := newTestCollection(t, ns)
|
||||
|
||||
coll.accessedMap[id.NewSequence("public", "seq_one")] = newTestSequence("public", "seq_one")
|
||||
_, err := coll.Map(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
ns.failAfter(0)
|
||||
coll.accessedMap[id.NewSequence("public", "seq_two")] = newTestSequence("public", "seq_two")
|
||||
_, err = coll.Map(ctx)
|
||||
require.Error(t, err)
|
||||
|
||||
ns.allowAll()
|
||||
require.NotPanics(t, func() {
|
||||
_, _ = coll.Map(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
// TestDropSequence_RemainsUsableAfterFlushFailure asserts the same
|
||||
// recovery contract for DropSequence.
|
||||
func TestDropSequence_RemainsUsableAfterFlushFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ns := newCountingFailNodeStore(t)
|
||||
coll := newTestCollection(t, ns)
|
||||
|
||||
pending := newTestSequence("public", "pending")
|
||||
coll.accessedMap[pending.Id] = pending
|
||||
ns.failAfter(0)
|
||||
err := coll.DropSequence(ctx, pending.Id)
|
||||
require.Error(t, err)
|
||||
|
||||
ns.allowAll()
|
||||
require.NotPanics(t, func() {
|
||||
_ = coll.DropSequence(ctx, id.NewSequence("public", "pending"))
|
||||
})
|
||||
}
|
||||
|
||||
// newTestCollection returns a Collection backed by |ns| with an empty
|
||||
// address map.
|
||||
func newTestCollection(t *testing.T, ns tree.NodeStore) *Collection {
|
||||
t.Helper()
|
||||
addrMap, err := prolly.NewEmptyAddressMap(ns)
|
||||
require.NoError(t, err)
|
||||
return &Collection{
|
||||
accessedMap: map[id.Sequence]*Sequence{},
|
||||
underlyingMap: addrMap,
|
||||
ns: ns,
|
||||
}
|
||||
}
|
||||
|
||||
// newTestSequence returns a Sequence with valid bounds that round-trip
|
||||
// through Serialize and Deserialize. The exact values are not significant.
|
||||
func newTestSequence(schema, name string) *Sequence {
|
||||
return &Sequence{
|
||||
Id: id.NewSequence(schema, name),
|
||||
Start: 1,
|
||||
Current: 1,
|
||||
Increment: 1,
|
||||
Minimum: 1,
|
||||
Maximum: math.MaxInt64,
|
||||
Cache: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// countingFailNodeStore wraps a real test NodeStore so that callers can
|
||||
// induce Write failures at chosen points without otherwise altering
|
||||
// behavior. Used to drive the writeCache flush-failure path.
|
||||
type countingFailNodeStore struct {
|
||||
tree.NodeStore
|
||||
writes int
|
||||
budget int // -1 means unlimited
|
||||
}
|
||||
|
||||
func newCountingFailNodeStore(t *testing.T) *countingFailNodeStore {
|
||||
t.Helper()
|
||||
return &countingFailNodeStore{NodeStore: tree.NewTestNodeStore(), budget: -1}
|
||||
}
|
||||
|
||||
// failAfter permits |allowed| additional Write calls then fails the
|
||||
// rest, until allowAll is called.
|
||||
func (f *countingFailNodeStore) failAfter(allowed int) {
|
||||
f.writes = 0
|
||||
f.budget = allowed
|
||||
}
|
||||
|
||||
// allowAll restores the wrapper to delegate every Write to the
|
||||
// underlying NodeStore.
|
||||
func (f *countingFailNodeStore) allowAll() {
|
||||
f.budget = -1
|
||||
}
|
||||
|
||||
func (f *countingFailNodeStore) Write(ctx context.Context, nd *tree.Node) (hash.Hash, error) {
|
||||
if f.budget >= 0 {
|
||||
f.writes++
|
||||
if f.writes > f.budget {
|
||||
return hash.Hash{}, errors.New("induced node store failure")
|
||||
}
|
||||
}
|
||||
return f.NodeStore.Write(ctx, nd)
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
// 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/doltgresql/core/id"
|
||||
pgmerge "github.com/dolthub/doltgresql/core/merge"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
const (
|
||||
FIELD_NAME_DATA_TYPE = "data_type"
|
||||
FIELD_NAME_PERSISTENCE = "persistence"
|
||||
FIELD_NAME_START = "start"
|
||||
FIELD_NAME_CURRENT = "current"
|
||||
FIELD_NAME_INCREMENT = "increment"
|
||||
FIELD_NAME_MINIMUM = "minimum"
|
||||
FIELD_NAME_MAXIMUM = "maximum"
|
||||
FIELD_NAME_CACHE = "cache"
|
||||
FIELD_NAME_CYCLE = "cycle"
|
||||
FIELD_NAME_IS_AT_END = "is_at_end"
|
||||
FIELD_NAME_HAS_BEEN_CALLED = "has_been_called"
|
||||
FIELD_NAME_OWNER_TABLE = "owner_table"
|
||||
FIELD_NAME_OWNER_COLUMN = "owner_column"
|
||||
)
|
||||
|
||||
// DeserializeRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeSequence(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) DiffRootObjects(ctx context.Context, fromHash string, o objinterface.RootObject, t objinterface.RootObject, a objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
ours := o.(*Sequence)
|
||||
{
|
||||
copiedOurs := *ours
|
||||
ours = &copiedOurs
|
||||
}
|
||||
theirs := t.(*Sequence)
|
||||
var ancestor Sequence
|
||||
hasAncestor := false
|
||||
if ancestorPtr, ok := a.(*Sequence); ok {
|
||||
ancestor = *ancestorPtr
|
||||
hasAncestor = true
|
||||
}
|
||||
var diffs []objinterface.RootObjectDiff
|
||||
if ours.DataTypeID != theirs.DataTypeID {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_DATA_TYPE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.DataTypeID.TypeName(), theirs.DataTypeID.TypeName(), ancestor.DataTypeID.TypeName(), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.DataTypeID = id.NewType(ours.DataTypeID.SchemaName(), diff.OurValue.(string))
|
||||
}
|
||||
}
|
||||
if ours.Persistence != theirs.Persistence {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int32,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_PERSISTENCE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, int32(ours.Persistence), int32(theirs.Persistence), int32(ancestor.Persistence), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Persistence = Persistence(diff.OurValue.(int32))
|
||||
}
|
||||
}
|
||||
if ours.Start != theirs.Start {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_START,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Start, theirs.Start, ancestor.Start, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Start = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Current != theirs.Current {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_CURRENT,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Current, theirs.Current, ancestor.Current, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Current = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.HasBeenCalled != theirs.HasBeenCalled {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_HAS_BEEN_CALLED,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.HasBeenCalled, theirs.HasBeenCalled, ancestor.HasBeenCalled, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.HasBeenCalled = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.Increment != theirs.Increment {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_INCREMENT,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Increment, theirs.Increment, ancestor.Increment, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Increment = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Minimum != theirs.Minimum {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_MINIMUM,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Minimum, theirs.Minimum, ancestor.Minimum, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Minimum = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Maximum != theirs.Maximum {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_MAXIMUM,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Maximum, theirs.Maximum, ancestor.Maximum, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Maximum = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Cache != theirs.Cache {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_CACHE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Cache, theirs.Cache, ancestor.Cache, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Cache = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Cycle != theirs.Cycle {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_CYCLE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Cycle, theirs.Cycle, ancestor.Cycle, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Cycle = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.IsAtEnd != theirs.IsAtEnd {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_IS_AT_END,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.IsAtEnd, theirs.IsAtEnd, ancestor.IsAtEnd, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.IsAtEnd = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.OwnerTable != theirs.OwnerTable {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_OWNER_TABLE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.OwnerTable.TableName(), theirs.OwnerTable.TableName(), ancestor.OwnerTable.TableName(), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.OwnerTable = id.NewTable(ours.OwnerTable.SchemaName(), diff.OurValue.(string))
|
||||
}
|
||||
}
|
||||
if ours.OwnerColumn != theirs.OwnerColumn {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_OWNER_COLUMN,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.OwnerColumn, theirs.OwnerColumn, ancestor.OwnerColumn, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.OwnerColumn = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
return diffs, ours, nil
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Sequence {
|
||||
return errors.Errorf(`sequence %s does not exist`, identifier.String())
|
||||
}
|
||||
return pgs.DropSequence(ctx, id.Sequence(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
switch fieldName {
|
||||
case FIELD_NAME_DATA_TYPE:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_PERSISTENCE:
|
||||
return pgtypes.Int32
|
||||
case FIELD_NAME_START:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_CURRENT:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_INCREMENT:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_MINIMUM:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_MAXIMUM:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_CACHE:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_CYCLE:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_IS_AT_END:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_HAS_BEEN_CALLED:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_OWNER_TABLE:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_OWNER_COLUMN:
|
||||
return pgtypes.Text
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Sequences
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Sequence {
|
||||
return nil, false, nil
|
||||
}
|
||||
seq, err := pgs.GetSequence(ctx, id.Sequence(identifier))
|
||||
return seq, err == nil && seq != nil, err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Sequence {
|
||||
return false, nil
|
||||
}
|
||||
return pgs.HasSequence(ctx, id.Sequence(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Sequence {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
seqID := id.Sequence(identifier)
|
||||
return doltdb.TableName{
|
||||
Name: seqID.SequenceName(),
|
||||
Schema: seqID.SchemaName(),
|
||||
}
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pgs.IterateSequences(ctx, func(seq *Sequence) (stop bool, err error) {
|
||||
return callback(seq)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pgs.iterateIDs(ctx, func(seqID id.Sequence) (stop bool, err error) {
|
||||
return callback(seqID.AsId())
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
seq, ok := rootObj.(*Sequence)
|
||||
if !ok {
|
||||
return errors.Newf("invalid sequence root object: %T", rootObj)
|
||||
}
|
||||
return pgs.CreateSequence(ctx, seq)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *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_Sequence {
|
||||
return errors.New("cannot rename sequence due to invalid name")
|
||||
}
|
||||
oldSeqName := id.Sequence(oldName)
|
||||
newSeqName := id.Sequence(newName)
|
||||
seq, err := pgs.GetSequence(ctx, oldSeqName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pgs.DropSequence(ctx, oldSeqName); err != nil {
|
||||
return err
|
||||
}
|
||||
newSeq := *seq
|
||||
newSeq.Id = newSeqName
|
||||
return pgs.CreateSequence(ctx, &newSeq)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pgs.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return doltdb.TableName{
|
||||
Name: rawID.SequenceName(),
|
||||
Schema: rawID.SchemaName(),
|
||||
}, rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return id.NewSequence(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgs *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,89 @@
|
||||
// Copyright 2024 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sequences
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Sequence as a byte slice. If the Sequence is nil, then this returns a nil slice.
|
||||
func (sequence *Sequence) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if sequence == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Create the writer
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(1) // Version
|
||||
// Write the sequence data
|
||||
writer.Id(sequence.Id.AsId())
|
||||
writer.Id(sequence.DataTypeID.AsId())
|
||||
writer.Uint8(uint8(sequence.Persistence))
|
||||
writer.Int64(sequence.Start)
|
||||
writer.Int64(sequence.Current)
|
||||
writer.Int64(sequence.Increment)
|
||||
writer.Int64(sequence.Minimum)
|
||||
writer.Int64(sequence.Maximum)
|
||||
writer.Int64(sequence.Cache)
|
||||
writer.Bool(sequence.Cycle)
|
||||
writer.Bool(sequence.IsAtEnd)
|
||||
writer.Bool(sequence.HasBeenCalled)
|
||||
writer.Id(sequence.OwnerTable.AsId())
|
||||
writer.String(sequence.OwnerColumn)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeSequence returns the Sequence that was serialized in the byte slice. Returns an empty Sequence if data is
|
||||
// nil or empty.
|
||||
func DeserializeSequence(_ context.Context, data []byte) (*Sequence, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version > 1 {
|
||||
return nil, errors.Errorf("version %d of sequences is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
sequence := &Sequence{}
|
||||
sequence.Id = id.Sequence(reader.Id())
|
||||
sequence.DataTypeID = id.Type(reader.Id())
|
||||
sequence.Persistence = Persistence(reader.Uint8())
|
||||
sequence.Start = reader.Int64()
|
||||
sequence.Current = reader.Int64()
|
||||
sequence.Increment = reader.Int64()
|
||||
sequence.Minimum = reader.Int64()
|
||||
sequence.Maximum = reader.Int64()
|
||||
sequence.Cache = reader.Int64()
|
||||
sequence.Cycle = reader.Bool()
|
||||
sequence.IsAtEnd = reader.Bool()
|
||||
if version >= 1 {
|
||||
sequence.HasBeenCalled = reader.Bool()
|
||||
}
|
||||
sequence.OwnerTable = id.Table(reader.Id())
|
||||
sequence.OwnerColumn = reader.String()
|
||||
if !reader.IsEmpty() {
|
||||
return nil, errors.Errorf("extra data found while deserializing a sequence")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return sequence, nil
|
||||
}
|
||||
Reference in New Issue
Block a user