chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user