577 lines
20 KiB
Go
577 lines
20 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 rootobject
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
|
|
|
"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/procedures"
|
|
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
|
"github.com/dolthub/doltgresql/core/sequences"
|
|
"github.com/dolthub/doltgresql/core/triggers"
|
|
"github.com/dolthub/doltgresql/core/typecollection"
|
|
pgtypes "github.com/dolthub/doltgresql/server/types"
|
|
)
|
|
|
|
var (
|
|
// globalCollections maps each ID to the collection.
|
|
globalCollections = []objinterface.Collection{
|
|
nil, // Corresponds to RootObjectID_None
|
|
&sequences.Collection{},
|
|
&typecollection.TypeCollection{},
|
|
&functions.Collection{},
|
|
&triggers.Collection{},
|
|
&extensions.Collection{},
|
|
&conflicts.Collection{},
|
|
&procedures.Collection{},
|
|
&casts.Collection{},
|
|
}
|
|
)
|
|
|
|
// CreateConflict creates a conflict on the given root for the two root objects.
|
|
func CreateConflict(ctx context.Context, rightSrc doltdb.Rootish, o doltdb.RootObject, t doltdb.RootObject, a doltdb.RootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
|
ours, ok1 := o.(objinterface.RootObject)
|
|
theirs, ok2 := t.(objinterface.RootObject)
|
|
if !ok1 || !ok2 {
|
|
return nil, nil, errors.New("unsupported object found during conflict creation")
|
|
}
|
|
ancestor, _ := a.(objinterface.RootObject) // If this is nil, then the conversion will also be nil (which is fine)
|
|
rightHash, err := rightSrc.HashOf()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if ours.GetID() != theirs.GetID() {
|
|
return nil, nil, errors.Errorf(`cannot create a conflict between "%s" and "%s"`,
|
|
ours.Name().String(), theirs.Name().String())
|
|
}
|
|
conflict := conflicts.Conflict{
|
|
ID: ours.GetID(),
|
|
FromHash: rightHash.String(),
|
|
RootObjectID: ours.GetRootObjectID(),
|
|
Ours: ours,
|
|
Theirs: theirs,
|
|
Ancestor: ancestor,
|
|
}
|
|
diffs, newOurs, err := conflict.Diffs(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if len(diffs) == 0 {
|
|
oldSerialized, err := ours.Serialize(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
newSerialized, err := newOurs.Serialize(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if bytes.Equal(oldSerialized, newSerialized) {
|
|
return newOurs, &merge.MergeStats{
|
|
Operation: merge.TableUnmodified,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 0,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
} else {
|
|
return newOurs, &merge.MergeStats{
|
|
Operation: merge.TableModified,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 1,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 0,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
}
|
|
}
|
|
conflict.Ours = newOurs
|
|
return conflict, &merge.MergeStats{
|
|
Operation: merge.TableUnmodified,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: len(diffs),
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
}
|
|
|
|
// DeserializeRootObject calls the same-named function on the collection that matches the ID that was given.
|
|
func DeserializeRootObject(ctx context.Context, rootObjID objinterface.RootObjectID, data []byte) (objinterface.RootObject, error) {
|
|
if int64(rootObjID) >= int64(len(globalCollections)) {
|
|
return nil, errors.New("unsupported object found, please upgrade the server")
|
|
}
|
|
collection := globalCollections[rootObjID]
|
|
if collection == nil {
|
|
return nil, errors.Errorf("invalid root object ID: %d", rootObjID)
|
|
}
|
|
return collection.DeserializeRootObject(ctx, data)
|
|
}
|
|
|
|
// DiffRootObjects calls the same-named function on the collection that matches the ID that was given.
|
|
func DiffRootObjects(ctx context.Context, rootObjID objinterface.RootObjectID, fromHash string, ours, theirs, ancestor objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
|
if int64(rootObjID) >= int64(len(globalCollections)) {
|
|
return nil, nil, errors.New("unsupported object found, please upgrade the server")
|
|
}
|
|
collection := globalCollections[rootObjID]
|
|
if collection == nil {
|
|
return nil, nil, errors.Errorf("invalid root object ID: %d", rootObjID)
|
|
}
|
|
if ours == nil && theirs == nil {
|
|
return nil, nil, nil
|
|
}
|
|
if ours == nil {
|
|
return []objinterface.RootObjectDiff{{
|
|
Type: pgtypes.Text,
|
|
FromHash: fromHash,
|
|
FieldName: objinterface.FIELD_NAME_ROOT_OBJECT,
|
|
AncestorValue: objinterface.FIELD_NAME_ANCESTOR,
|
|
OurValue: nil,
|
|
TheirValue: objinterface.FIELD_NAME_THEIRS,
|
|
OurChange: objinterface.RootObjectDiffChange_Deleted,
|
|
TheirChange: objinterface.RootObjectDiffChange_Modified,
|
|
}}, nil, nil
|
|
}
|
|
if theirs == nil {
|
|
return []objinterface.RootObjectDiff{{
|
|
Type: pgtypes.Text,
|
|
FromHash: fromHash,
|
|
FieldName: objinterface.FIELD_NAME_ROOT_OBJECT,
|
|
AncestorValue: objinterface.FIELD_NAME_ANCESTOR,
|
|
OurValue: objinterface.FIELD_NAME_OURS,
|
|
TheirValue: nil,
|
|
OurChange: objinterface.RootObjectDiffChange_Modified,
|
|
TheirChange: objinterface.RootObjectDiffChange_Deleted,
|
|
}}, nil, nil
|
|
}
|
|
return collection.DiffRootObjects(ctx, fromHash, ours, theirs, ancestor)
|
|
}
|
|
|
|
// GetFieldType calls the same-named function on the collection that matches the ID that was given.
|
|
func GetFieldType(ctx context.Context, rootObjID objinterface.RootObjectID, fieldName string) *pgtypes.DoltgresType {
|
|
if int64(rootObjID) >= int64(len(globalCollections)) {
|
|
return nil
|
|
}
|
|
collection := globalCollections[rootObjID]
|
|
if collection == nil {
|
|
return nil
|
|
}
|
|
if fieldName == objinterface.FIELD_NAME_ROOT_OBJECT {
|
|
return pgtypes.Text
|
|
}
|
|
return collection.GetFieldType(ctx, fieldName)
|
|
}
|
|
|
|
// UpdateField calls the same-named function on the collection that matches the ID that was given.
|
|
func UpdateField(ctx context.Context, rootObjID objinterface.RootObjectID, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
|
if int64(rootObjID) >= int64(len(globalCollections)) {
|
|
return nil, errors.New("unsupported object found, please upgrade the server")
|
|
}
|
|
collection := globalCollections[rootObjID]
|
|
if collection == nil {
|
|
return nil, errors.Errorf("invalid root object ID: %d", rootObjID)
|
|
}
|
|
// This field should always be handled before this call is made, so it's an error if we see it here
|
|
if fieldName == objinterface.FIELD_NAME_ROOT_OBJECT {
|
|
return nil, errors.New("cannot set the `root_object` field alongside other fields")
|
|
}
|
|
return collection.UpdateField(ctx, rootObject, fieldName, newValue)
|
|
}
|
|
|
|
// GetRootObject returns the root object that matches the given name.
|
|
func GetRootObject(ctx context.Context, root objinterface.RootValue, tName doltdb.TableName) (objinterface.RootObject, bool, error) {
|
|
_, rawID, objID, err := ResolveName(ctx, root, tName)
|
|
if err != nil || objID == objinterface.RootObjectID_None {
|
|
return nil, false, err
|
|
}
|
|
coll, _ := globalCollections[objID].LoadCollection(ctx, root)
|
|
return coll.GetRootObject(ctx, rawID)
|
|
}
|
|
|
|
// GetRootObjectConflicts returns the conflict root object that matches the given name.
|
|
func GetRootObjectConflicts(ctx context.Context, root objinterface.RootValue, tName doltdb.TableName) (conflicts.Conflict, bool, error) {
|
|
coll, err := globalCollections[objinterface.RootObjectID_Conflicts].LoadCollection(ctx, root)
|
|
if err != nil {
|
|
return conflicts.Conflict{}, false, err
|
|
}
|
|
_, rawID, err := coll.ResolveName(ctx, tName)
|
|
if err != nil {
|
|
return conflicts.Conflict{}, false, err
|
|
}
|
|
ro, ok, err := coll.GetRootObject(ctx, rawID)
|
|
if err != nil || !ok {
|
|
return conflicts.Conflict{}, false, err
|
|
}
|
|
return ro.(conflicts.Conflict), true, nil
|
|
}
|
|
|
|
// HandleMerge handles merging root objects.
|
|
func HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
|
if mro.OurRootObj == nil {
|
|
switch {
|
|
case mro.TheirRootObj != nil && mro.AncestorRootObj != nil:
|
|
theirs := mro.TheirRootObj.(objinterface.RootObject)
|
|
ancestor := mro.AncestorRootObj.(objinterface.RootObject)
|
|
rightHash, err := mro.RightSrc.HashOf()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
theirData, err := theirs.Serialize(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ancData, err := ancestor.Serialize(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if bytes.Equal(theirData, ancData) {
|
|
return nil, &merge.MergeStats{
|
|
Operation: merge.TableRemoved,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 0,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
} else {
|
|
return conflicts.Conflict{
|
|
ID: theirs.GetID(),
|
|
FromHash: rightHash.String(),
|
|
RootObjectID: theirs.GetRootObjectID(),
|
|
Ours: nil,
|
|
Theirs: theirs,
|
|
Ancestor: ancestor,
|
|
}, &merge.MergeStats{
|
|
Operation: merge.TableModified,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 1,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
}
|
|
case mro.TheirRootObj != nil && mro.AncestorRootObj == nil:
|
|
return mro.TheirRootObj, &merge.MergeStats{
|
|
Operation: merge.TableAdded,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 0,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
case mro.TheirRootObj == nil && mro.AncestorRootObj != nil:
|
|
return nil, &merge.MergeStats{
|
|
Operation: merge.TableRemoved,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 0,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
case mro.TheirRootObj == nil && mro.AncestorRootObj == nil:
|
|
return nil, &merge.MergeStats{
|
|
Operation: merge.TableUnmodified,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 0,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
default:
|
|
return nil, nil, errors.New("HandleMerge has somehow reached a default case")
|
|
}
|
|
} else if mro.TheirRootObj == nil {
|
|
switch {
|
|
case mro.AncestorRootObj != nil:
|
|
ours := mro.OurRootObj.(objinterface.RootObject)
|
|
ancestor := mro.AncestorRootObj.(objinterface.RootObject)
|
|
rightHash, err := mro.RightSrc.HashOf()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ourData, err := ours.Serialize(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ancData, err := ancestor.Serialize(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if bytes.Equal(ourData, ancData) {
|
|
return nil, &merge.MergeStats{
|
|
Operation: merge.TableRemoved,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 0,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
} else {
|
|
return conflicts.Conflict{
|
|
ID: ours.GetID(),
|
|
FromHash: rightHash.String(),
|
|
RootObjectID: ours.GetRootObjectID(),
|
|
Ours: ours,
|
|
Theirs: nil,
|
|
Ancestor: ancestor,
|
|
}, &merge.MergeStats{
|
|
Operation: merge.TableModified,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 1,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
}
|
|
case mro.AncestorRootObj == nil:
|
|
return mro.OurRootObj, &merge.MergeStats{
|
|
Operation: merge.TableAdded,
|
|
Adds: 0,
|
|
Deletes: 0,
|
|
Modifications: 0,
|
|
DataConflicts: 0,
|
|
SchemaConflicts: 0,
|
|
RootObjectConflicts: 0,
|
|
ConstraintViolations: 0,
|
|
}, nil
|
|
default:
|
|
return nil, nil, errors.New("HandleMerge has somehow reached a default case")
|
|
}
|
|
}
|
|
identifier := mro.OurRootObj.(objinterface.RootObject).GetRootObjectID()
|
|
if int64(identifier) >= int64(len(globalCollections)) {
|
|
return nil, nil, errors.New("unsupported root object found, please upgrade Doltgres to the latest version")
|
|
}
|
|
coll := globalCollections[identifier]
|
|
if coll == nil {
|
|
return nil, nil, errors.Newf("invalid root object found, ID: %d", int64(identifier))
|
|
}
|
|
return coll.HandleMerge(ctx, mro)
|
|
}
|
|
|
|
// LoadAllCollections loads and returns all collections from the root.
|
|
func LoadAllCollections(ctx context.Context, root objinterface.RootValue) ([]objinterface.Collection, error) {
|
|
colls := make([]objinterface.Collection, 0, len(globalCollections))
|
|
for i, emptyColl := range globalCollections {
|
|
if emptyColl == nil || i == int(objinterface.RootObjectID_Conflicts) {
|
|
continue
|
|
}
|
|
coll, err := emptyColl.LoadCollection(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
colls = append(colls, coll)
|
|
}
|
|
return colls, nil
|
|
}
|
|
|
|
// LoadCollection loads the collection matching the given ID from the root.
|
|
func LoadCollection(ctx context.Context, root objinterface.RootValue, collectionID objinterface.RootObjectID) (objinterface.Collection, error) {
|
|
if globalCollections[collectionID] == nil {
|
|
return nil, nil
|
|
}
|
|
return globalCollections[collectionID].LoadCollection(ctx, root)
|
|
}
|
|
|
|
// PutRootObject adds the given root object to the respective Collection in the root, returning the updated root.
|
|
func PutRootObject(ctx context.Context, root objinterface.RootValue, tName doltdb.TableName, rootObj objinterface.RootObject) (objinterface.RootValue, error) {
|
|
if rootObj == nil {
|
|
return root, nil
|
|
}
|
|
coll, err := LoadCollection(ctx, root, rootObj.GetRootObjectID())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
identifier := coll.TableNameToID(tName)
|
|
exists, err := coll.HasRootObject(ctx, identifier)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// If this doesn't exist, it may be because the name is slightly different (e.g. missing schema), and we want to resolve it properly
|
|
if !exists {
|
|
_, resolvedID, err := coll.ResolveName(ctx, tName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resolvedID.IsValid() {
|
|
identifier = resolvedID
|
|
exists = true
|
|
}
|
|
}
|
|
if exists {
|
|
if err = coll.DropRootObject(ctx, identifier); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// If this is a conflict, then we only want to put the conflict in the collection if it produces conflict diffs.
|
|
// Otherwise, we'll put the merged root object instead.
|
|
if conflict, ok := rootObj.(objinterface.Conflict); ok {
|
|
diffs, merged, err := conflict.Diffs(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(diffs) == 0 {
|
|
// If we deleted from the conflicts collection, then we need to update the collection on the root before returning
|
|
if exists {
|
|
root, err = coll.UpdateRoot(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if merged == nil {
|
|
return RemoveRootObjectIfExists(ctx, root, conflict.GetID(), conflict.GetContainedRootObjectID())
|
|
} else {
|
|
return PutRootObject(ctx, root, tName, merged)
|
|
}
|
|
} else {
|
|
if merged == nil {
|
|
root, err = RemoveRootObjectIfExists(ctx, root, conflict.GetID(), conflict.GetContainedRootObjectID())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
root, err = PutRootObject(ctx, root, tName, merged)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if err = coll.PutRootObject(ctx, rootObj); err != nil {
|
|
return nil, err
|
|
}
|
|
return coll.UpdateRoot(ctx, root)
|
|
}
|
|
|
|
// RemoveRootObject removes the matching root object from its respective Collection, returning the updated root.
|
|
func RemoveRootObject(ctx context.Context, root objinterface.RootValue, identifier id.Id, rootObjectID objinterface.RootObjectID) (objinterface.RootValue, error) {
|
|
coll, err := LoadCollection(ctx, root, rootObjectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = coll.DropRootObject(ctx, identifier); err != nil {
|
|
return nil, err
|
|
}
|
|
return coll.UpdateRoot(ctx, root)
|
|
}
|
|
|
|
// RemoveRootObjectIfExists removes the matching root object from its respective Collection, returning the updated root.
|
|
// If the root object does not exist, then this will not attempt the deletion.
|
|
func RemoveRootObjectIfExists(ctx context.Context, root objinterface.RootValue, identifier id.Id, rootObjectID objinterface.RootObjectID) (objinterface.RootValue, error) {
|
|
coll, err := LoadCollection(ctx, root, rootObjectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exists, err := coll.HasRootObject(ctx, identifier)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if exists {
|
|
if err = coll.DropRootObject(ctx, identifier); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return coll.UpdateRoot(ctx, root)
|
|
}
|
|
|
|
// ResolveName returns the fully resolved name of the given item (if the item exists). Also returns the type of the item.
|
|
func ResolveName(ctx context.Context, root objinterface.RootValue, name doltdb.TableName) (doltdb.TableName, id.Id, objinterface.RootObjectID, error) {
|
|
var resolvedName doltdb.TableName
|
|
resolvedRawID := id.Null
|
|
resolvedObjID := objinterface.RootObjectID_None
|
|
|
|
for i, emptyColl := range globalCollections {
|
|
if emptyColl == nil || i == int(objinterface.RootObjectID_Conflicts) {
|
|
continue
|
|
}
|
|
coll, err := emptyColl.LoadCollection(ctx, root)
|
|
if err != nil {
|
|
return doltdb.TableName{}, id.Null, objinterface.RootObjectID_None, err
|
|
}
|
|
if coll == nil {
|
|
continue
|
|
}
|
|
rName, rID, err := coll.ResolveName(ctx, name)
|
|
if err != nil {
|
|
return doltdb.TableName{}, id.Null, objinterface.RootObjectID_None, err
|
|
}
|
|
if rID.IsValid() {
|
|
if resolvedObjID != objinterface.RootObjectID_None {
|
|
return doltdb.TableName{}, id.Null, objinterface.RootObjectID_None, fmt.Errorf(`"%s" is ambiguous`, name.String())
|
|
}
|
|
resolvedName = rName
|
|
resolvedRawID = rID
|
|
resolvedObjID = coll.GetID()
|
|
}
|
|
}
|
|
|
|
return resolvedName, resolvedRawID, resolvedObjID, nil
|
|
}
|
|
|
|
// resolveNameFromObjects resolves the given name on all given objects on all global collections (except for conflicts).
|
|
func resolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
|
var resolvedName doltdb.TableName
|
|
resolvedRawID := id.Null
|
|
|
|
for i, emptyColl := range globalCollections {
|
|
if emptyColl == nil || i == int(objinterface.RootObjectID_Conflicts) {
|
|
continue
|
|
}
|
|
rName, rID, err := emptyColl.ResolveNameFromObjects(ctx, name, rootObjects)
|
|
if err != nil {
|
|
return doltdb.TableName{}, id.Null, err
|
|
}
|
|
if rID.IsValid() {
|
|
if resolvedRawID != id.Null {
|
|
return doltdb.TableName{}, id.Null, fmt.Errorf(`"%s" is ambiguous`, name.String())
|
|
}
|
|
resolvedName = rName
|
|
resolvedRawID = rID
|
|
}
|
|
}
|
|
return resolvedName, resolvedRawID, nil
|
|
}
|