chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:32:25 +08:00
commit e014feafe1
2285 changed files with 1131979 additions and 0 deletions
+576
View File
@@ -0,0 +1,576 @@
// 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
}
+47
View File
@@ -0,0 +1,47 @@
// 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 (
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/doltgresql/core/conflicts"
pgmerge "github.com/dolthub/doltgresql/core/merge"
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
"github.com/dolthub/doltgresql/core/storage"
)
// Init initializes the package
func Init() {
merge.MergeRootObjects = HandleMerge
pgmerge.CreateConflict = CreateConflict
conflicts.DeserializeRootObject = DeserializeRootObject
conflicts.DiffRootObjects = DiffRootObjects
conflicts.GetFieldType = GetFieldType
conflicts.ResolveNameExternal = resolveNameFromObjects
conflicts.UpdateField = UpdateField
doltdb.RootObjectDiffFromRow = objinterface.DiffFromRow
for _, collFuncs := range globalCollections {
if collFuncs == nil {
continue
}
serializer := collFuncs.Serializer()
storage.RootObjectSerializations = append(storage.RootObjectSerializations, storage.RootObjectSerialization{
Bytes: serializer.Bytes,
RootValueAdd: serializer.RootValueAdd,
})
}
}
+113
View File
@@ -0,0 +1,113 @@
// 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 objinterface
import (
"context"
"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/doltgresql/core/id"
"github.com/dolthub/doltgresql/core/storage"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// RootObjectID is an ID that distinguishes names and root objects from one another.
type RootObjectID int64
const (
RootObjectID_None RootObjectID = iota
RootObjectID_Sequences
RootObjectID_Types
RootObjectID_Functions
RootObjectID_Triggers
RootObjectID_Extensions
RootObjectID_Conflicts
RootObjectID_Procedures
RootObjectID_Casts
)
const (
FIELD_NAME_ROOT_OBJECT = "root_object"
FIELD_NAME_ANCESTOR = "ancestor"
FIELD_NAME_OURS = "ours"
FIELD_NAME_THEIRS = "theirs"
)
// Collection is a collection of root objects.
type Collection interface {
// DeserializeRootObject deserializes a root object's data.
DeserializeRootObject(ctx context.Context, data []byte) (RootObject, error)
// DiffRootObjects returns a RootObjectDiff for each change made from the ancestor that is not reflected on both
// "ours" and "theirs".
DiffRootObjects(ctx context.Context, fromHash string, ours RootObject, theirs RootObject, ancestor RootObject) ([]RootObjectDiff, RootObject, error)
// DropRootObject removes the given root object from the collection.
DropRootObject(ctx context.Context, identifier id.Id) error
// GetFieldType returns the type associated with the given diff field name. Returns nil if the name is invalid.
GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType
// GetID returns the identifying ID for the Collection.
GetID() RootObjectID
// GetRootObject returns the root object matching the given ID. Returns false if it cannot be found.
GetRootObject(ctx context.Context, identifier id.Id) (RootObject, bool, error)
// HasRootObject returns whether a root object exactly matching the given ID was found.
HasRootObject(ctx context.Context, identifier id.Id) (bool, error)
// IDToTableName converts the given ID to a table name. The table name will be empty for invalid IDs.
IDToTableName(identifier id.Id) doltdb.TableName
// IterAll iterates over all root objects in the Collection.
IterAll(ctx context.Context, callback func(rootObj RootObject) (stop bool, err error)) error
// IterIDs iterates over all IDs in the Collection.
IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error
// PutRootObject updates the Collection with the given root object. This may error if the root object already exists.
PutRootObject(ctx context.Context, rootObj RootObject) error
// RenameRootObject changes the ID for a root object matching the old ID.
RenameRootObject(ctx context.Context, oldID id.Id, newID id.Id) error
// ResolveName finds the closest matching (or exact) ID for the given name. If an exact match is not found, then
// this may error if the name is ambiguous.
ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error)
// TableNameToID converts the given name to an ID. The ID will be invalid for empty/malformed names.
TableNameToID(name doltdb.TableName) id.Id
// UpdateField updates the field on the given root object with the new value. Returns a new root object with the
// updated field.
UpdateField(ctx context.Context, rootObject RootObject, fieldName string, newValue any) (RootObject, error)
// HandleMerge handles merging of two objects. It is guaranteed that "ours" and "theirs" will not be nil, however
// "ancestor" may or may not be nil.
HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error)
// LoadCollection loads the Collection from the given root.
LoadCollection(ctx context.Context, root RootValue) (Collection, error)
// LoadCollectionHash loads the Collection hash from the given root. This does not load the entire collection from
// the root, and is therefore a bit more performant if only the hash is needed.
LoadCollectionHash(ctx context.Context, root RootValue) (hash.Hash, error)
// ResolveNameFromObjects finds the closest matching (or exact) ID for the given name. If an exact match is not
// found, then this may error if the name is ambiguous. This searches through the given root objects rather than the
// ones stored in the collection itself.
ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []RootObject) (doltdb.TableName, id.Id, error)
// Serializer returns the serializer associated with this Collection.
Serializer() RootObjectSerializer
// UpdateRoot updates the Collection in the given root, returning the updated root.
UpdateRoot(ctx context.Context, root RootValue) (RootValue, error)
}
// RootValue is an interface to get around import cycles, since the core package references this package (and is where
// RootValue is defined).
type RootValue interface {
doltdb.RootValue
// GetStorage returns the storage contained in the root.
GetStorage(context.Context) storage.RootStorage
// WithStorage returns an updated RootValue with the given storage.
WithStorage(context.Context, storage.RootStorage) RootValue
}
+208
View File
@@ -0,0 +1,208 @@
// 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 objinterface
import (
"cmp"
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core/id"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// RootObject is an expanded interface on Dolt's root objects.
type RootObject interface {
doltdb.RootObject
// GetID returns the root object ID.
GetID() id.Id
// GetRootObjectID returns the root object ID.
GetRootObjectID() RootObjectID
// Serialize returns the byte representation of the root object.
Serialize(ctx context.Context) ([]byte, error)
}
// Conflict is an expanded interface on Dolt's conflict root object.
type Conflict interface {
RootObject
doltdb.ConflictRootObject
// GetContainedRootObjectID returns the root object ID of the contained items.
GetContainedRootObjectID() RootObjectID
// Diffs returns the diffs for the conflict, along with the merged root object if there are no diffs.
Diffs(ctx context.Context) ([]RootObjectDiff, RootObject, error)
// FieldType returns the type associated with the given field name. Returns nil if the name does not match a field.
FieldType(ctx context.Context, name string) *pgtypes.DoltgresType
}
// RootObjectDiffChange specifies the type of change that occurred from the ancestor value.
type RootObjectDiffChange uint8
const (
RootObjectDiffChange_Added RootObjectDiffChange = iota
RootObjectDiffChange_Deleted
RootObjectDiffChange_Modified
RootObjectDiffChange_NoChange
)
// RootObjectDiffSchema is the baseline schema that is returned for root object diffs.
var RootObjectDiffSchema = sql.Schema{
{Name: "from_root_ish", Type: pgtypes.Text, Default: nil, Nullable: false},
{Name: "base_value", Type: pgtypes.Text, Default: nil, Nullable: true},
{Name: "our_value", Type: pgtypes.Text, Default: nil, Nullable: true},
{Name: "our_diff_type", Type: pgtypes.Text, Default: nil, Nullable: false},
{Name: "their_value", Type: pgtypes.Text, Default: nil, Nullable: true},
{Name: "their_diff_type", Type: pgtypes.Text, Default: nil, Nullable: false},
{Name: "dolt_conflict_id", Type: pgtypes.Text, Default: nil, Nullable: false},
}
// RootObjectDiff represents a diff between the ancestor value and our/their values. The field name uniquely identifies
// which part of a root object that this diff covers.
type RootObjectDiff struct {
Type *pgtypes.DoltgresType
FromHash string
FieldName string
AncestorValue any
OurValue any
TheirValue any
OurChange RootObjectDiffChange
TheirChange RootObjectDiffChange
}
var _ doltdb.RootObjectDiff = RootObjectDiff{}
// CompareIds implements the interface doltdb.RootObjectDiff.
func (diff RootObjectDiff) CompareIds(ctx context.Context, o doltdb.RootObjectDiff) (int, error) {
other, ok := o.(RootObjectDiff)
if !ok {
return 0, errors.Errorf("root object diff cannot compare with diff of type %T", o)
}
return cmp.Compare(diff.FieldName, other.FieldName), nil
}
// ToRow implements the interface doltdb.RootObjectDiff.
func (diff RootObjectDiff) ToRow(ctx *sql.Context) (_ sql.Row, err error) {
var baseValue any
var ourValue any
var theirValue any
var ourChange any
var theirChange any
if diff.AncestorValue != nil {
baseValue, err = diff.Type.IoOutput(ctx, diff.AncestorValue)
if err != nil {
return nil, err
}
}
if diff.OurValue != nil {
ourValue, err = diff.Type.IoOutput(ctx, diff.OurValue)
if err != nil {
return nil, err
}
}
if diff.TheirValue != nil {
theirValue, err = diff.Type.IoOutput(ctx, diff.TheirValue)
if err != nil {
return nil, err
}
}
switch diff.OurChange {
case RootObjectDiffChange_Added:
ourChange = "added"
case RootObjectDiffChange_Deleted:
ourChange = "deleted"
case RootObjectDiffChange_Modified:
ourChange = "modified"
case RootObjectDiffChange_NoChange:
ourChange = "no_change"
}
switch diff.TheirChange {
case RootObjectDiffChange_Added:
theirChange = "added"
case RootObjectDiffChange_Deleted:
theirChange = "deleted"
case RootObjectDiffChange_Modified:
theirChange = "modified"
case RootObjectDiffChange_NoChange:
ourChange = "no_change"
}
return sql.Row{diff.FromHash, baseValue, ourValue, ourChange, theirValue, theirChange, diff.FieldName}, nil
}
// DiffFromRow converts a row to a conflict diff.
func DiffFromRow(ctx *sql.Context, conflict doltdb.ConflictRootObject, row sql.Row) (_ doltdb.RootObjectDiff, err error) {
if len(row) != len(RootObjectDiffSchema) {
return nil, errors.Newf("expected root object row diff to have %d columns but had %d", len(RootObjectDiffSchema), len(row))
}
fieldName := row[6].(string)
typ := conflict.(Conflict).FieldType(ctx, fieldName)
if typ == nil {
return nil, errors.Newf("cannot find a field named `%s`", fieldName)
}
var baseValue any
var ourValue any
var theirValue any
var ourChange RootObjectDiffChange
var theirChange RootObjectDiffChange
if row[1] != nil {
baseValue, err = typ.IoInput(ctx, row[1].(string))
if err != nil {
return nil, err
}
}
if row[2] != nil {
ourValue, err = typ.IoInput(ctx, row[2].(string))
if err != nil {
return nil, err
}
}
if row[4] != nil {
theirValue, err = typ.IoInput(ctx, row[4].(string))
if err != nil {
return nil, err
}
}
switch row[3].(string) {
case "added":
ourChange = RootObjectDiffChange_Added
case "deleted":
ourChange = RootObjectDiffChange_Deleted
case "modified":
ourChange = RootObjectDiffChange_Modified
case "no_change":
ourChange = RootObjectDiffChange_NoChange
}
switch row[5].(string) {
case "added":
theirChange = RootObjectDiffChange_Added
case "deleted":
theirChange = RootObjectDiffChange_Deleted
case "modified":
theirChange = RootObjectDiffChange_Modified
case "no_change":
ourChange = RootObjectDiffChange_NoChange
}
return RootObjectDiff{
Type: typ,
FromHash: row[0].(string),
FieldName: fieldName,
AncestorValue: baseValue,
OurValue: ourValue,
TheirValue: theirValue,
OurChange: ourChange,
TheirChange: theirChange,
}, nil
}
+102
View File
@@ -0,0 +1,102 @@
// 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 objinterface
import (
"context"
"fmt"
doltserial "github.com/dolthub/dolt/go/gen/fb/serial"
"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/dolt/go/store/types"
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
"github.com/dolthub/doltgresql/core/storage"
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
)
// RootObjectSerializer holds function pointers for the serialization of root objects.
type RootObjectSerializer struct {
Bytes func(*serial.RootValue) []byte
RootValueAdd func(builder *flatbuffers.Builder, sequences flatbuffers.UOffsetT)
}
// CreateProllyMap creates and returns a new, empty Prolly map.
func (serializer RootObjectSerializer) CreateProllyMap(ctx context.Context, root RootValue) (prolly.AddressMap, error) {
return prolly.NewEmptyAddressMap(root.NodeStore())
}
// GetProllyMap loads the Prolly map from the given root, using the internal serialization functions.
func (serializer RootObjectSerializer) GetProllyMap(ctx context.Context, root RootValue) (prolly.AddressMap, bool, error) {
val, ok, err := serializer.getValue(ctx, root)
if err != nil || !ok {
return prolly.AddressMap{}, ok, err
}
serialMessage := val.(types.SerialMessage)
node, fileId, err := tree.NodeFromBytes(serialMessage)
if err != nil {
return prolly.AddressMap{}, false, err
}
if fileId != doltserial.AddressMapFileID {
return prolly.AddressMap{}, false, fmt.Errorf("invalid address map identifier, expected %s, got %s", doltserial.AddressMapFileID, fileId)
}
addressMap, err := prolly.NewAddressMap(node, root.NodeStore())
return addressMap, err == nil, err
}
// WriteProllyMap writes the given Prolly map to the root, returning the updated root.
func (serializer RootObjectSerializer) WriteProllyMap(ctx context.Context, root RootValue, val prolly.AddressMap) (RootValue, error) {
return serializer.writeValue(ctx, root, tree.ValueFromNode(val.Node()))
}
// getValue loads the value from the given root, using the internal serialization functions.
func (serializer RootObjectSerializer) getValue(ctx context.Context, root RootValue) (types.Value, bool, error) {
hashBytes := serializer.Bytes(root.GetStorage(ctx).SRV)
if len(hashBytes) == 0 {
return nil, false, nil
}
h := hash.New(hashBytes)
if h.IsEmpty() {
return nil, false, nil
}
val, err := root.VRW().ReadValue(ctx, h)
return val, err == nil && val != nil, err
}
// setHash writes the given hash to storage, returning the updated storage.
func (serializer RootObjectSerializer) setHash(ctx context.Context, st storage.RootStorage, h hash.Hash) (storage.RootStorage, error) {
if len(serializer.Bytes(st.SRV)) > 0 {
ret := st.Clone()
copy(serializer.Bytes(ret.SRV), h[:])
return ret, nil
} else {
return st.Clone(), nil
}
}
// writeValue writes the given value to the root, returning the updated root.
func (serializer RootObjectSerializer) writeValue(ctx context.Context, root RootValue, val types.Value) (RootValue, error) {
ref, err := root.VRW().WriteValue(ctx, val)
if err != nil {
return nil, err
}
newStorage, err := serializer.setHash(ctx, root.GetStorage(ctx), ref.TargetHash())
if err != nil {
return nil, err
}
return root.WithStorage(ctx, newStorage), nil
}