chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
// 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 conflicts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"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/go-mysql-server/sql"
|
||||
|
||||
"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 conflicts.
|
||||
type Collection struct {
|
||||
accessCache map[id.Id]Conflict // This cache is used for general access when you know the exact ID
|
||||
nameCache map[doltdb.TableName]id.Id // This cache is used for ID resolution, since exact table names are always used
|
||||
idCache []id.Id // This cache simply contains the name of every root object
|
||||
mapHash hash.Hash // This is cached so that we don't have to calculate the hash every time
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// Conflict represents a root object conflict.
|
||||
type Conflict struct {
|
||||
ID id.Id
|
||||
FromHash string
|
||||
RootObjectID objinterface.RootObjectID
|
||||
Ours objinterface.RootObject
|
||||
Theirs objinterface.RootObject
|
||||
Ancestor objinterface.RootObject
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Conflict{}
|
||||
var _ objinterface.Conflict = Conflict{}
|
||||
var _ doltdb.ConflictRootObject = Conflict{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(ctx context.Context, underlyingMap prolly.AddressMap, ns tree.NodeStore) (*Collection, error) {
|
||||
collection := &Collection{
|
||||
accessCache: make(map[id.Id]Conflict),
|
||||
nameCache: make(map[doltdb.TableName]id.Id),
|
||||
idCache: nil,
|
||||
mapHash: hash.Hash{},
|
||||
underlyingMap: underlyingMap,
|
||||
ns: ns,
|
||||
}
|
||||
return collection, collection.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// GetConflict returns the conflict with the given ID. Returns a conflict with an invalid ID if it cannot be found
|
||||
// (Conflict.ID.IsValid() == false).
|
||||
func (pgc *Collection) GetConflict(ctx context.Context, conflictID id.Id) (Conflict, error) {
|
||||
if conflict, ok := pgc.accessCache[conflictID]; ok {
|
||||
return conflict, nil
|
||||
}
|
||||
return Conflict{}, nil
|
||||
}
|
||||
|
||||
// HasConflict returns whether the conflict is present.
|
||||
func (pgc *Collection) HasConflict(ctx context.Context, conflictID id.Id) bool {
|
||||
_, ok := pgc.accessCache[conflictID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AddConflict adds a new conflict.
|
||||
func (pgc *Collection) AddConflict(ctx context.Context, conflict Conflict) error {
|
||||
// First we'll check to see if it exists
|
||||
if _, ok := pgc.accessCache[conflict.ID]; ok {
|
||||
return errors.Errorf(`"%s" already has a conflict`, conflict.Ours.Name())
|
||||
}
|
||||
|
||||
// Now we'll add the conflict to our map
|
||||
data, err := conflict.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pgc.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapEditor := pgc.underlyingMap.Editor()
|
||||
if err = mapEditor.Add(ctx, string(conflict.ID), h); err != nil {
|
||||
return err
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgc.underlyingMap = newMap
|
||||
pgc.mapHash = pgc.underlyingMap.HashOf()
|
||||
return pgc.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// DropConflict drops an existing conflict.
|
||||
func (pgc *Collection) DropConflict(ctx context.Context, conflictIDs ...id.Id) error {
|
||||
if len(conflictIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Check that each name exists before performing any deletions
|
||||
for _, conflictID := range conflictIDs {
|
||||
if _, ok := pgc.accessCache[conflictID]; !ok {
|
||||
return errors.Errorf(`conflict %s does not exist`, conflictID.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll remove the conflicts from the map
|
||||
mapEditor := pgc.underlyingMap.Editor()
|
||||
for _, conflictID := range conflictIDs {
|
||||
err := mapEditor.Delete(ctx, string(conflictID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgc.underlyingMap = newMap
|
||||
pgc.mapHash = pgc.underlyingMap.HashOf()
|
||||
return pgc.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// iterateIDs iterates over all conflict IDs in the collection.
|
||||
func (pgc *Collection) iterateIDs(ctx context.Context, callback func(conflictID id.Id) (stop bool, err error)) error {
|
||||
for _, conflictID := range pgc.idCache {
|
||||
stop, err := callback(conflictID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterateConflicts iterates over all conflicts in the collection.
|
||||
func (pgc *Collection) IterateConflicts(ctx context.Context, callback func(conflict Conflict) (stop bool, err error)) error {
|
||||
for _, conflictID := range pgc.idCache {
|
||||
stop, err := callback(pgc.accessCache[conflictID])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pgc *Collection) Clone(ctx context.Context) *Collection {
|
||||
return &Collection{
|
||||
accessCache: maps.Clone(pgc.accessCache),
|
||||
nameCache: maps.Clone(pgc.nameCache),
|
||||
idCache: slices.Clone(pgc.idCache),
|
||||
underlyingMap: pgc.underlyingMap,
|
||||
mapHash: pgc.mapHash,
|
||||
ns: pgc.ns,
|
||||
}
|
||||
}
|
||||
|
||||
// Map writes any cached sequences to the underlying map, and then returns the underlying map.
|
||||
func (pgc *Collection) Map(ctx context.Context) (prolly.AddressMap, error) {
|
||||
return pgc.underlyingMap, nil
|
||||
}
|
||||
|
||||
// DiffersFrom returns true when the hash that is associated with the underlying map for this collection is different
|
||||
// from the hash in the given root.
|
||||
func (pgc *Collection) DiffersFrom(ctx context.Context, root objinterface.RootValue) bool {
|
||||
hashOnGivenRoot, err := pgc.LoadCollectionHash(ctx, root)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if pgc.mapHash.Equal(hashOnGivenRoot) {
|
||||
return false
|
||||
}
|
||||
// An empty map should match an uninitialized collection on the root
|
||||
count, err := pgc.underlyingMap.Count()
|
||||
if err == nil && count == 0 && hashOnGivenRoot.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// reloadCaches writes the underlying map's contents to the caches.
|
||||
func (pgc *Collection) reloadCaches(ctx context.Context) error {
|
||||
count, err := pgc.underlyingMap.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clear(pgc.accessCache)
|
||||
clear(pgc.nameCache)
|
||||
pgc.mapHash = pgc.underlyingMap.HashOf()
|
||||
pgc.idCache = make([]id.Id, 0, count)
|
||||
|
||||
return pgc.underlyingMap.IterAll(ctx, func(_ string, h hash.Hash) error {
|
||||
if h.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
data, err := pgc.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conflict, err := DeserializeConflict(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgc.accessCache[conflict.ID] = conflict
|
||||
pgc.nameCache[conflict.Name()] = conflict.ID
|
||||
pgc.idCache = append(pgc.idCache, conflict.ID)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DiffCount implements the interface objinterface.Conflict.
|
||||
func (conflict Conflict) DiffCount(ctx *sql.Context) (int, error) {
|
||||
diffs, _, err := conflict.Diffs(ctx)
|
||||
return len(diffs), err
|
||||
}
|
||||
|
||||
// Diffs implements the interface objinterface.Conflict.
|
||||
func (conflict Conflict) Diffs(ctx context.Context) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
return DiffRootObjects(ctx, conflict.RootObjectID, conflict.FromHash, conflict.Ours, conflict.Theirs, conflict.Ancestor)
|
||||
}
|
||||
|
||||
// FieldType implements the interface objinterface.Conflict.
|
||||
func (conflict Conflict) FieldType(ctx context.Context, name string) *pgtypes.DoltgresType {
|
||||
return GetFieldType(ctx, conflict.RootObjectID, name)
|
||||
}
|
||||
|
||||
// GetContainedRootObjectID implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) GetContainedRootObjectID() objinterface.RootObjectID {
|
||||
return conflict.RootObjectID
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) GetID() id.Id {
|
||||
return conflict.ID
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Conflicts
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := conflict.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) Name() doltdb.TableName {
|
||||
if conflict.Ours != nil {
|
||||
return conflict.Ours.Name()
|
||||
} else {
|
||||
return conflict.Theirs.Name()
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveDiffs implements the interface doltdb.ConflictRootObject.
|
||||
func (conflict Conflict) RemoveDiffs(ctx *sql.Context, diffs []doltdb.RootObjectDiff) (doltdb.ConflictRootObject, error) {
|
||||
// This is only called from within Dolt, and it specifically deals with modifying the conflict collection on the
|
||||
// root. It is therefore safe to clear context values, to ensure the root changes are not overwritten.
|
||||
ClearContextValues(ctx)
|
||||
// We need to handle the root object field name in a special way, since it's how we select which side to use in full
|
||||
if len(diffs) == 1 {
|
||||
diff := diffs[0].(objinterface.RootObjectDiff)
|
||||
if diff.FieldName == objinterface.FIELD_NAME_ROOT_OBJECT {
|
||||
if conflict.Ours == nil {
|
||||
conflict.Theirs = nil
|
||||
} else {
|
||||
conflict.Theirs = conflict.Ours
|
||||
}
|
||||
conflict.Ancestor = nil
|
||||
return conflict, nil
|
||||
}
|
||||
}
|
||||
// Deletion is equivalent to setting the value in "theirs" to "ours", as the same value cannot conflict with itself.
|
||||
// We will only reach this point if both "ours" and "theirs" are non-nil entries, so the above relies on safe assumptions.
|
||||
for _, doltDiff := range diffs {
|
||||
diff := doltDiff.(objinterface.RootObjectDiff)
|
||||
newTheirs, err := UpdateField(ctx, conflict.RootObjectID, conflict.Theirs, diff.FieldName, diff.OurValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conflict.Theirs = newTheirs
|
||||
}
|
||||
return conflict, nil
|
||||
}
|
||||
|
||||
// Rows implements the interface doltdb.ConflictRootObject.
|
||||
func (conflict Conflict) Rows(ctx *sql.Context) (sql.RowIter, error) {
|
||||
diffs, _, err := conflict.Diffs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows := make([]sql.Row, len(diffs))
|
||||
for i, diff := range diffs {
|
||||
rows[i], err = diff.ToRow(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sql.RowsToRowIter(rows...), nil
|
||||
}
|
||||
|
||||
// Schema implements the interface doltdb.ConflictRootObject.
|
||||
func (conflict Conflict) Schema(originatingTableName string) sql.Schema {
|
||||
sch := objinterface.RootObjectDiffSchema.Copy()
|
||||
for _, col := range sch {
|
||||
col.Source = originatingTableName
|
||||
}
|
||||
return sch
|
||||
}
|
||||
|
||||
// UpdateField implements the interface doltdb.ConflictRootObject.
|
||||
func (conflict Conflict) UpdateField(ctx *sql.Context, o doltdb.RootObjectDiff, n doltdb.RootObjectDiff) (doltdb.ConflictRootObject, error) {
|
||||
// This is only called from within Dolt, and it specifically deals with modifying the conflict collection on the
|
||||
// root. It is therefore safe to clear context values, to ensure the root changes are not overwritten.
|
||||
ClearContextValues(ctx)
|
||||
oldDiff := o.(objinterface.RootObjectDiff)
|
||||
newDiff := n.(objinterface.RootObjectDiff)
|
||||
// We need to handle the root object field name in a special way, since it's how we select which side to use in full
|
||||
if oldDiff.FieldName == objinterface.FIELD_NAME_ROOT_OBJECT {
|
||||
switch newDiff.OurValue.(string) {
|
||||
case objinterface.FIELD_NAME_OURS:
|
||||
conflict.Theirs = conflict.Ours
|
||||
case objinterface.FIELD_NAME_THEIRS:
|
||||
conflict.Ours = conflict.Theirs
|
||||
case objinterface.FIELD_NAME_ANCESTOR:
|
||||
conflict.Ours = conflict.Ancestor
|
||||
conflict.Theirs = conflict.Ancestor
|
||||
default:
|
||||
return nil, errors.Newf("cannot replace the object with `%s`", newDiff.OurValue.(string))
|
||||
}
|
||||
conflict.Ancestor = nil
|
||||
return conflict, nil
|
||||
}
|
||||
newOurs, err := UpdateField(ctx, conflict.RootObjectID, conflict.Ours, oldDiff.FieldName, newDiff.OurValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conflict.Ours = newOurs
|
||||
return conflict, nil
|
||||
}
|
||||
|
||||
// ClearContextValues clears context values, and is declared in a different package. It is assigned here by an Init
|
||||
// function to get around import cycles.
|
||||
var ClearContextValues = func(ctx *sql.Context) {
|
||||
panic("ClearContextValues was never initialized")
|
||||
}
|
||||
|
||||
// DiffRootObjects handles conflict diffs, and is declared in a different package. It is assigned here by an Init
|
||||
// function to get around import cycles.
|
||||
var DiffRootObjects = func(ctx context.Context, rootObjID objinterface.RootObjectID, fromHash string, ours, theirs, ancestor objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
return nil, nil, errors.New("DiffRootObjects was never initialized")
|
||||
}
|
||||
|
||||
// GetFieldType handles type fetching for fields, and is declared in a different package. It is assigned here by an Init
|
||||
// function to get around import cycles.
|
||||
var GetFieldType = func(ctx context.Context, rootObjID objinterface.RootObjectID, fieldName string) *pgtypes.DoltgresType {
|
||||
panic("GetFieldType was never initialized")
|
||||
}
|
||||
|
||||
// ResolveNameExternal handles name resolution across all collection types, and is declared in a different package. It
|
||||
// is assigned here by an Init function to get around import cycles.
|
||||
var ResolveNameExternal = func(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
panic("ResolveNameExternal was never initialized")
|
||||
}
|
||||
|
||||
// UpdateField handles updating fields in a root object, and is declared in a different package. It is assigned here by
|
||||
// an Init function to get around import cycles.
|
||||
var UpdateField = func(ctx context.Context, rootObjID objinterface.RootObjectID, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
return nil, errors.New("UpdateField was never initialized")
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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 conflicts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"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).ConflictsBytes,
|
||||
RootValueAdd: serial.RootValueAddConflicts,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
// It technically doesn't make sense to merge conflicts, but we'll only error if there are differences
|
||||
ourConflict := mro.OurRootObj.(Conflict)
|
||||
theirConflict := mro.TheirRootObj.(Conflict)
|
||||
// Ensure that they have the same identifier
|
||||
if ourConflict.ID != theirConflict.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different conflicts: `%s` and `%s`",
|
||||
ourConflict.Name().String(), theirConflict.Name().String())
|
||||
}
|
||||
ourHash, err := ourConflict.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirConflict.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ourHash.Equal(theirHash) {
|
||||
return mro.OurRootObj, &merge.MergeStats{
|
||||
Operation: merge.TableUnmodified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
return nil, nil, errors.New("attempted to merge conflicts")
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadConflicts(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
|
||||
}
|
||||
|
||||
// LoadConflicts loads the conflicts collection from the given root.
|
||||
func LoadConflicts(ctx context.Context, root objinterface.RootValue) (*Collection, error) {
|
||||
m, ok, err := storage.GetProllyMap(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
m, err = prolly.NewEmptyAddressMap(root.NodeStore())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return NewCollection(ctx, m, root.NodeStore())
|
||||
}
|
||||
|
||||
// ResolveNameFromObjects implements the interface objinterface.Collection.
|
||||
func (*Collection) ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
// Conflicts make use of this interface function, so we must return nil to prevent infinite recursion
|
||||
return doltdb.TableName{}, id.Null, nil
|
||||
}
|
||||
|
||||
// Serializer implements the interface objinterface.Collection.
|
||||
func (*Collection) Serializer() objinterface.RootObjectSerializer {
|
||||
return storage
|
||||
}
|
||||
|
||||
// UpdateRoot implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
||||
m, err := pgc.Map(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.WriteProllyMap(ctx, root, m)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2025 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package conflicts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
|
||||
"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 (pgc *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeConflict(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgc *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.Errorf("cannot diff the conflict root objects themselves")
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
return pgc.DropConflict(ctx, identifier)
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Conflicts
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
conflict, err := pgc.GetConflict(ctx, identifier)
|
||||
return conflict, err == nil && conflict.ID.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
return pgc.HasConflict(ctx, identifier), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
return doltdb.TableName{Name: string(identifier)}
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pgc.IterateConflicts(ctx, func(conflict Conflict) (stop bool, err error) {
|
||||
return callback(conflict)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pgc.iterateIDs(ctx, func(conflictID id.Id) (stop bool, err error) {
|
||||
return callback(conflictID)
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
conflict, ok := rootObj.(Conflict)
|
||||
if !ok {
|
||||
return errors.Newf("invalid conflict root object: %T", rootObj)
|
||||
}
|
||||
return pgc.AddConflict(ctx, conflict)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) RenameRootObject(ctx context.Context, oldName id.Id, newName id.Id) error {
|
||||
return errors.New("cannot rename root object conflicts")
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
if len(pgc.idCache) == 0 {
|
||||
return doltdb.TableName{}, id.Null, nil
|
||||
}
|
||||
objs := make([]objinterface.RootObject, 0, len(pgc.idCache))
|
||||
for _, conflict := range pgc.accessCache {
|
||||
if conflict.Ours != nil {
|
||||
objs = append(objs, conflict.Ours)
|
||||
} else if conflict.Theirs != nil {
|
||||
objs = append(objs, conflict.Theirs)
|
||||
} else if conflict.Ancestor != nil {
|
||||
objs = append(objs, conflict.Ancestor)
|
||||
}
|
||||
}
|
||||
return ResolveNameExternal(ctx, name, objs)
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
if resolvedId, ok := pgc.nameCache[name]; ok {
|
||||
return resolvedId
|
||||
}
|
||||
return id.Null
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) UpdateField(ctx context.Context, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
return nil, errors.New("conflicts should not have conflict tables themselves, so this update should be impossible")
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// 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 conflicts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Conflict as a byte slice. If the Conflict is invalid, then this returns a nil slice.
|
||||
func (conflict Conflict) Serialize(ctx context.Context) (_ []byte, err error) {
|
||||
if !conflict.ID.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Write all of the conflicts to the writer
|
||||
writer := utils.NewWriter(512)
|
||||
writer.VariableUint(0) // Version
|
||||
// Serialize "ours", "theirs", and "ancestor"
|
||||
var ours, theirs, ancestor []byte
|
||||
if conflict.Ours != nil {
|
||||
ours, err = conflict.Ours.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if conflict.Theirs != nil {
|
||||
theirs, err = conflict.Theirs.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if conflict.Ancestor != nil {
|
||||
ancestor, err = conflict.Ancestor.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Write the conflict data
|
||||
writer.Id(conflict.ID)
|
||||
writer.String(conflict.FromHash)
|
||||
writer.Int64(int64(conflict.RootObjectID))
|
||||
writer.Bool(conflict.Ours != nil)
|
||||
writer.Bool(conflict.Theirs != nil)
|
||||
writer.Bool(conflict.Ancestor != nil)
|
||||
writer.ByteSlice(ours)
|
||||
writer.ByteSlice(theirs)
|
||||
writer.ByteSlice(ancestor)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeConflict returns the Conflict that was serialized in the byte slice. Returns an empty Conflict (invalid
|
||||
// ID) if data is nil or empty.
|
||||
func DeserializeConflict(ctx context.Context, data []byte) (_ Conflict, err error) {
|
||||
if len(data) == 0 {
|
||||
return Conflict{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version > 0 {
|
||||
return Conflict{}, errors.Errorf("version %d of conflicts is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
conflict := Conflict{}
|
||||
conflict.ID = reader.Id()
|
||||
conflict.FromHash = reader.String()
|
||||
conflict.RootObjectID = objinterface.RootObjectID(reader.Int64())
|
||||
hasOurs := reader.Bool()
|
||||
hasTheirs := reader.Bool()
|
||||
hasAncestor := reader.Bool()
|
||||
ours := reader.ByteSlice()
|
||||
theirs := reader.ByteSlice()
|
||||
ancestor := reader.ByteSlice()
|
||||
// Deserialize "ours", "theirs", and "ancestor"
|
||||
if hasOurs {
|
||||
conflict.Ours, err = DeserializeRootObject(ctx, conflict.RootObjectID, ours)
|
||||
if err != nil {
|
||||
return Conflict{}, err
|
||||
}
|
||||
}
|
||||
if hasTheirs {
|
||||
conflict.Theirs, err = DeserializeRootObject(ctx, conflict.RootObjectID, theirs)
|
||||
if err != nil {
|
||||
return Conflict{}, err
|
||||
}
|
||||
}
|
||||
if hasAncestor {
|
||||
conflict.Ancestor, err = DeserializeRootObject(ctx, conflict.RootObjectID, ancestor)
|
||||
if err != nil {
|
||||
return Conflict{}, err
|
||||
}
|
||||
}
|
||||
if !reader.IsEmpty() {
|
||||
return Conflict{}, errors.Errorf("extra data found while deserializing a conflict")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return conflict, nil
|
||||
}
|
||||
|
||||
// DeserializeRootObject handles root object deserialization, and is declared in a different package. It is assigned
|
||||
// here by an Init function to get around import cycles.
|
||||
var DeserializeRootObject = func(ctx context.Context, rootObjID objinterface.RootObjectID, data []byte) (objinterface.RootObject, error) {
|
||||
return nil, errors.New("DeserializeRootObject was never initialized")
|
||||
}
|
||||
Reference in New Issue
Block a user