Files
dolt/go/store/datas/workingset.go
T

273 lines
8.1 KiB
Go
Executable File

// Copyright 2021 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 datas
import (
"context"
flatbuffers "github.com/google/flatbuffers/go"
"github.com/dolthub/dolt/go/gen/fb/serial"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/types"
)
const (
workingSetName = "WorkingSet"
workingSetMetaField = "meta"
workingRootRefField = "workingRootRef"
stagedRootRefField = "stagedRootRef"
mergeStateField = "mergeState"
)
const (
mergeStateName = "MergeState"
mergeStateCommitField = "commit"
mergeStateWorkingPreMergeField = "workingPreMerge"
)
const (
workingSetMetaName = "WorkingSetMeta"
workingSetMetaNameField = "name"
workingSetMetaEmailField = "email"
workingSetMetaTimestampField = "timestamp"
workingSetMetaDescriptionField = "description"
workingSetMetaVersionField = "version"
)
const workingSetMetaVersion = "1.0"
type WorkingSetMeta struct {
Name string
Email string
Description string
Timestamp uint64
}
func (m *WorkingSetMeta) toNomsStruct(format *types.NomsBinFormat) (types.Struct, error) {
fields := make(types.StructData)
fields[workingSetMetaNameField] = types.String(m.Name)
fields[workingSetMetaEmailField] = types.String(m.Email)
fields[workingSetMetaTimestampField] = types.Uint(m.Timestamp)
fields[workingSetMetaDescriptionField] = types.String(m.Description)
fields[workingSetMetaVersionField] = types.String(workingSetMetaVersion)
return types.NewStruct(format, workingSetMetaName, fields)
}
func workingSetMetaFromWorkingSetSt(workingSetSt types.Struct) (*WorkingSetMeta, error) {
metaV, ok, err := workingSetSt.MaybeGet(workingSetMetaNameField)
if err != nil || !ok {
return nil, err
}
return workingSetMetaFromNomsSt(metaV.(types.Struct))
}
var mergeStateTemplate = types.MakeStructTemplate(mergeStateName, []string{mergeStateCommitField, mergeStateWorkingPreMergeField})
type WorkingSetSpec struct {
Meta *WorkingSetMeta
WorkingRoot types.Ref
StagedRoot types.Ref
MergeState *MergeState
}
// NewWorkingSet creates a new working set object.
// A working set is a value that has been persisted but is not necessarily referenced by a Commit. As the name implies,
// it's storage for data changes that have not yet been incorporated into the commit graph but need durable storage.
//
// A working set struct has the following type:
//
// ```
//
// struct WorkingSet {
// meta: M,
// workingRootRef: R,
// stagedRootRef: R,
// mergeState: R,
// }
//
// ```
// where M is a struct type and R is a ref type.
func newWorkingSet(ctx context.Context, db *database, meta *WorkingSetMeta, workingRef, stagedRef types.Ref, mergeState *MergeState) (hash.Hash, types.Ref, error) {
if db.Format().UsesFlatbuffers() {
stagedAddr := stagedRef.TargetHash()
data := workingset_flatbuffer(workingRef.TargetHash(), &stagedAddr, mergeState, meta)
r, err := db.WriteValue(ctx, types.SerialMessage(data))
if err != nil {
return hash.Hash{}, types.Ref{}, err
}
ref, err := types.ToRefOfValue(r, db.Format())
if err != nil {
return hash.Hash{}, types.Ref{}, err
}
return ref.TargetHash(), ref, nil
}
metaSt, err := meta.toNomsStruct(workingRef.Format())
if err != nil {
return hash.Hash{}, types.Ref{}, err
}
fields := make(types.StructData)
fields[workingSetMetaField] = metaSt
fields[workingRootRefField] = workingRef
fields[stagedRootRefField] = stagedRef
if mergeState != nil {
fields[mergeStateField] = *mergeState.nomsMergeStateRef
}
st, err := types.NewStruct(workingRef.Format(), workingSetName, fields)
if err != nil {
return hash.Hash{}, types.Ref{}, err
}
wsRef, err := db.WriteValue(ctx, st)
if err != nil {
return hash.Hash{}, types.Ref{}, err
}
ref, err := types.ToRefOfValue(wsRef, db.Format())
if err != nil {
return hash.Hash{}, types.Ref{}, err
}
return ref.TargetHash(), ref, nil
}
func workingset_flatbuffer(working hash.Hash, staged *hash.Hash, mergeState *MergeState, meta *WorkingSetMeta) serial.Message {
builder := flatbuffers.NewBuilder(1024)
workingoff := builder.CreateByteVector(working[:])
var stagedOff, mergeStateOff flatbuffers.UOffsetT
if staged != nil {
stagedOff = builder.CreateByteVector((*staged)[:])
}
if mergeState != nil {
prerootaddroff := builder.CreateByteVector((*mergeState.preMergeWorkingAddr)[:])
fromaddroff := builder.CreateByteVector((*mergeState.fromCommitAddr)[:])
serial.MergeStateStart(builder)
serial.MergeStateAddPreWorkingRootAddr(builder, prerootaddroff)
serial.MergeStateAddFromCommitAddr(builder, fromaddroff)
mergeStateOff = serial.MergeStateEnd(builder)
}
var nameOff, emailOff, descOff flatbuffers.UOffsetT
if meta != nil {
nameOff = builder.CreateString(meta.Name)
emailOff = builder.CreateString(meta.Email)
descOff = builder.CreateString(meta.Description)
}
serial.WorkingSetStart(builder)
serial.WorkingSetAddWorkingRootAddr(builder, workingoff)
if stagedOff != 0 {
serial.WorkingSetAddStagedRootAddr(builder, stagedOff)
}
if mergeStateOff != 0 {
serial.WorkingSetAddMergeState(builder, mergeStateOff)
}
if meta != nil {
serial.WorkingSetAddName(builder, nameOff)
serial.WorkingSetAddEmail(builder, emailOff)
serial.WorkingSetAddDesc(builder, descOff)
serial.WorkingSetAddTimestampMillis(builder, meta.Timestamp)
}
return serial.FinishMessage(builder, serial.WorkingSetEnd(builder), []byte(serial.WorkingSetFileID))
}
func NewMergeState(ctx context.Context, vrw types.ValueReadWriter, preMergeWorking types.Ref, commit *Commit) (*MergeState, error) {
if vrw.Format().UsesFlatbuffers() {
ms := &MergeState{
preMergeWorkingAddr: new(hash.Hash),
fromCommitAddr: new(hash.Hash),
}
*ms.preMergeWorkingAddr = preMergeWorking.TargetHash()
*ms.fromCommitAddr = commit.Addr()
return ms, nil
} else {
v, err := mergeStateTemplate.NewStruct(preMergeWorking.Format(), []types.Value{commit.NomsValue(), preMergeWorking})
if err != nil {
return nil, err
}
ref, err := vrw.WriteValue(ctx, v)
if err != nil {
return nil, err
}
return &MergeState{
nomsMergeStateRef: &ref,
nomsMergeState: &v,
}, nil
}
}
func IsWorkingSet(v types.Value) (bool, error) {
if s, ok := v.(types.Struct); ok {
// We're being more lenient here than in other checks, to make it more likely we can release changes to the
// working set data description in a backwards compatible way.
// types.IsValueSubtypeOf is very strict about the type description.
return s.Name() == workingSetName, nil
} else if sm, ok := v.(types.SerialMessage); ok {
return serial.GetFileID(sm) == serial.WorkingSetFileID, nil
} else {
return false, nil
}
}
func workingSetMetaFromNomsSt(st types.Struct) (*WorkingSetMeta, error) {
// Like other places that deal with working set meta, we err on the side of leniency w.r.t. this data structure's
// contents
name, ok, err := st.MaybeGet(workingSetMetaNameField)
if err != nil {
return nil, err
}
if !ok {
name = types.String("not present")
}
email, ok, err := st.MaybeGet(workingSetMetaEmailField)
if err != nil {
return nil, err
}
if !ok {
email = types.String("not present")
}
timestamp, ok, err := st.MaybeGet(workingSetMetaTimestampField)
if err != nil {
return nil, err
}
if !ok {
timestamp = types.Uint(0)
}
description, ok, err := st.MaybeGet(workingSetMetaDescriptionField)
if err != nil {
return nil, err
}
if !ok {
description = types.String("not present")
}
return &WorkingSetMeta{
Name: string(name.(types.String)),
Email: string(email.(types.String)),
Timestamp: uint64(timestamp.(types.Uint)),
Description: string(description.(types.String)),
}, nil
}