tags as separate struct type

This commit is contained in:
Andy Arthur
2020-08-27 14:15:12 -07:00
parent 04b2385dee
commit 38888232f3
9 changed files with 263 additions and 50 deletions

View File

@@ -74,20 +74,6 @@ func NewCommitMetaWithUserTS(name, email, desc string, userTS time.Time) (*Commi
return &CommitMeta{n, e, ms, d, userMS}, nil
}
// NewTagMeta returns CommitMeta that can be used to create a tag.
func NewTagMeta(name, email, desc string) *CommitMeta {
n := strings.TrimSpace(name)
e := strings.TrimSpace(email)
d := strings.TrimSpace(desc)
ns := uint64(CommitNowFunc().UnixNano())
ms := ns / uMilliToNano
userMS := int64(ns) / milliToNano
return &CommitMeta{n, e, ms, d, userMS}
}
func getRequiredFromSt(st types.Struct, k string) (types.Value, error) {
if v, ok, err := st.MaybeGet(k); err != nil {
return nil, err

View File

@@ -194,11 +194,35 @@ func getCommitStForRefStr(ctx context.Context, db datas.Database, ref string) (t
}
dsHead, hasHead := ds.MaybeHead()
if hasHead {
if !hasHead {
return types.EmptyStruct(db.Format()), ErrBranchNotFound
}
if dsHead.Name() == datas.CommitName {
return dsHead, nil
}
return types.EmptyStruct(db.Format()), ErrBranchNotFound
if dsHead.Name() == datas.TagName {
cmRef, ok, err := dsHead.MaybeGet(datas.TagCommitRefField)
if err != nil {
return types.EmptyStruct(db.Format()), err
}
if !ok {
err = fmt.Errorf("tag struct does not have field %s", datas.TagCommitRefField)
return types.EmptyStruct(db.Format()), err
}
commitSt, err := cmRef.(types.Ref).TargetValue(ctx, db)
if err != nil {
return types.EmptyStruct(db.Format()), err
}
return commitSt.(types.Struct), nil
}
err = fmt.Errorf("dataset head is neither commit nor tag")
return types.EmptyStruct(db.Format()), err
}
func getCommitStForHash(ctx context.Context, db datas.Database, c string) (types.Struct, error) {
@@ -768,10 +792,6 @@ func (ddb *DoltDB) NewBranchAtCommit(ctx context.Context, dref ref.DoltRef, comm
panic(fmt.Sprintf("invalid branch name %s, use IsValidUserBranchName check", dref.String()))
}
return ddb.newRefAtCommit(ctx, dref, commit)
}
func (ddb *DoltDB) newRefAtCommit(ctx context.Context, dref ref.DoltRef, commit *Commit) error {
ds, err := ddb.db.GetDataset(ctx, dref.String())
if err != nil {
@@ -810,12 +830,43 @@ func (ddb *DoltDB) deleteRef(ctx context.Context, dref ref.DoltRef) error {
}
// NewTagAtCommit create a new tag at the commit given.
func (ddb *DoltDB) NewTagAtCommit(ctx context.Context, tagRef ref.DoltRef, commit *Commit) error {
func (ddb *DoltDB) NewTagAtCommit(ctx context.Context, tagRef ref.DoltRef, c *Commit, meta *CommitMeta) error {
if !IsValidTagRef(tagRef) {
panic(fmt.Sprintf("invalid tag name %s, use IsValidUserTagName check", tagRef.String()))
}
return ddb.newRefAtCommit(ctx, tagRef, commit)
ds, err := ddb.db.GetDataset(ctx, tagRef.String())
if err != nil {
return err
}
_, hasHead, err := ds.MaybeHeadRef()
if err != nil {
return err
}
if hasHead {
return fmt.Errorf("dataset already exists for tag %s", tagRef.String())
}
r, err := types.NewRef(c.commitSt, ddb.Format())
if err != nil {
return err
}
st, err := meta.toNomsStruct(ddb.db.Format())
if err != nil {
return err
}
tag := datas.TagOptions{Meta: st}
ds, err = ddb.db.Tag(ctx, ds, r, tag)
return err
}
func (ddb *DoltDB) DeleteTag(ctx context.Context, tag ref.DoltRef) error {

View File

@@ -0,0 +1,19 @@
package doltdb
import (
"strings"
)
// NewTagMeta returns CommitMeta that can be used to create a tag.
func NewTagMeta(name, email, desc string) *CommitMeta {
n := strings.TrimSpace(name)
e := strings.TrimSpace(email)
d := strings.TrimSpace(desc)
ns := uint64(CommitNowFunc().UnixNano())
ms := ns / uMilliToNano
userMS := int64(ns) / milliToNano
return &CommitMeta{n, e, ms, d, userMS}
}

View File

@@ -58,28 +58,9 @@ func CreateTag(ctx context.Context, dEnv *env.DoltEnv, tagName, startPoint strin
return err
}
err = dEnv.DoltDB.NewTagAtCommit(ctx, tagRef, cm)
if err != nil {
return err
}
root, err := cm.GetRootValue()
if err != nil {
return err
}
h, err := root.HashOf()
if err != nil {
return err
}
meta := doltdb.NewTagMeta(props.TaggerName, props.TaggerEmail, props.Description)
_, err = dEnv.DoltDB.CommitWithParentCommits(ctx, h, tagRef, nil, meta)
return err
return dEnv.DoltDB.NewTagAtCommit(ctx, tagRef, cm, meta)
}
func DeleteTags(ctx context.Context, dEnv *env.DoltEnv, tagNames ...string) error {

View File

@@ -41,15 +41,15 @@ const (
ParentsListField = "parents_list"
ValueField = "value"
MetaField = "meta"
commitName = "Commit"
CommitName = "Commit"
)
var commitTemplate = types.MakeStructTemplate(commitName, []string{MetaField, ParentsField, ParentsListField, ValueField})
var commitTemplate = types.MakeStructTemplate(CommitName, []string{MetaField, ParentsField, ParentsListField, ValueField})
var valueCommitType = nomdl.MustParseType(`Struct Commit {
meta: Struct {},
parents: Set<Ref<Cycle<Commit>>>,
parentsList?: List<Ref<Cycle<Commit>>>,
parents_list?: List<Ref<Cycle<Commit>>>,
value: Value,
}`)

View File

@@ -95,6 +95,8 @@ type Database interface {
// of a conflict, Commit returns an 'ErrMergeNeeded' error.
CommitValue(ctx context.Context, ds Dataset, v types.Value) (Dataset, error)
Tag(ctx context.Context, ds Dataset, ref types.Ref, opts TagOptions) (Dataset, error)
// Delete removes the Dataset named ds.ID() from the map at the root of
// the Database. The Dataset data is not necessarily cleaned up at this
// time, but may be garbage collected in the future.

View File

@@ -417,6 +417,78 @@ func (db *database) doCommit(ctx context.Context, datasetID string, commit types
return tryCommitErr
}
func (db *database) Tag(ctx context.Context, ds Dataset, ref types.Ref, opts TagOptions) (Dataset, error) {
return db.doHeadUpdate(
ctx,
ds,
func(ds Dataset) error {
st, err := NewTag(ctx, ref, opts.Meta)
if err != nil {
return err
}
return db.doTag(ctx, ds.ID(), st)
},
)
}
// doTag manages concurrent access the single logical piece of mutable state: the current Root. It uses
// the same optimistic writing algorithm as doCommit (see above).
func (db *database) doTag(ctx context.Context, datasetID string, tag types.Struct) error {
if is, err := db.validateTag(ctx, tag); err != nil {
return err
} else if !is {
d.Panic("Can't tag using a non-Commit struct (dataset %s)", datasetID)
}
// This could loop forever, given enough simultaneous writers. BUG 2565
var tryCommitErr error
for tryCommitErr = ErrOptimisticLockFailed; tryCommitErr == ErrOptimisticLockFailed; {
currentRootHash, err := db.rt.Root(ctx)
if err != nil {
return err
}
currentDatasets, err := db.Datasets(ctx)
if err != nil {
return err
}
tagRef, err := db.WriteValue(ctx, tag) // will be orphaned if the tryCommitChunks() below fails
if err != nil {
return err
}
_, hasHead, err := currentDatasets.MaybeGet(ctx, types.String(datasetID))
if err != nil {
return err
}
if hasHead {
d.Panic("datasets for tags (refs/tags/*) cannot be altered after creation")
}
ref, err := types.ToRefOfValue(tagRef, db.Format())
if err != nil {
return err
}
currentDatasets, err = currentDatasets.Edit().Set(types.String(datasetID), ref).Map(ctx)
if err != nil {
return err
}
tryCommitErr = db.tryCommitChunks(ctx, currentDatasets, currentRootHash)
}
return tryCommitErr
}
func (db *database) Delete(ctx context.Context, ds Dataset) (Dataset, error) {
return db.doHeadUpdate(ctx, ds, func(ds Dataset) error { return db.doDelete(ctx, ds.ID()) })
}
@@ -519,6 +591,32 @@ func (db *database) validateRefAsCommit(ctx context.Context, r types.Ref) (types
return v.(types.Struct), nil
}
func (db *database) validateTag(ctx context.Context, t types.Struct) (bool, error) {
is, err := IsTag(t)
if err != nil {
return false, err
}
if !is {
return false, nil
}
r, ok, err := t.MaybeGet(TagCommitRefField)
if err != nil {
return false, err
}
if !ok {
return false, fmt.Errorf("tag is missing field %s", TagCommitRefField)
}
_, err = db.validateRefAsCommit(ctx, r.(types.Ref))
if err != nil {
return false, err
}
return true, nil
}
func buildNewCommit(ctx context.Context, ds Dataset, v types.Value, opts CommitOptions) (types.Struct, error) {
parents := opts.ParentsList
if parents == types.EmptyList || parents.Len() == 0 {

View File

@@ -44,10 +44,19 @@ type Dataset struct {
}
func newDataset(db Database, id string, head types.Value) (Dataset, error) {
headNilOrIsCommit := head == nil
if !headNilOrIsCommit {
var err error
headNilOrIsCommit, err = IsCommit(head)
check := head == nil
var err error
if !check {
check, err = IsCommit(head)
if err != nil {
return Dataset{}, err
}
}
if !check {
check, err = IsTag(head)
if err != nil {
return Dataset{}, err
@@ -55,7 +64,7 @@ func newDataset(db Database, id string, head types.Value) (Dataset, error) {
}
// precondition checks
d.PanicIfFalse(headNilOrIsCommit)
d.PanicIfFalse(check)
return Dataset{db, id, head}, nil
}

67
go/store/datas/tag.go Normal file
View File

@@ -0,0 +1,67 @@
// Copyright 2020 Liquidata, 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"
"github.com/liquidata-inc/dolt/go/store/nomdl"
"github.com/liquidata-inc/dolt/go/store/types"
)
const (
TagMetaField = "meta"
TagCommitRefField = "ref"
TagName = "Tag"
)
var tagTemplate = types.MakeStructTemplate(TagName, []string{TagMetaField, TagCommitRefField})
// ref is a Ref<Commit>, but 'Commit' is not defined in this snippet.
// Tag refs are validated to point at Commits during write.
var valueTagType = nomdl.MustParseType(`Struct Tag {
meta: Struct {},
ref: Ref<Value>,
}`)
// TagOptions is used to pass options into Tag.
type TagOptions struct {
// Meta is a Struct that describes arbitrary metadata about this Tag,
// e.g. a timestamp or descriptive text.
Meta types.Struct
}
// NewTag creates a new tag object.
//
// A tag has the following type:
//
// ```
// struct Tag {
// meta: M,
// commitRef: T,
// }
// ```
// where M is a struct type and R is a ref type.
func NewTag(_ context.Context, commitRef types.Ref, meta types.Struct) (types.Struct, error) {
return tagTemplate.NewStruct(meta.Format(), []types.Value{meta, commitRef})
}
func IsTag(v types.Value) (bool, error) {
if s, ok := v.(types.Struct); !ok {
return false, nil
} else {
return types.IsValueSubtypeOf(s.Format(), v, valueTagType)
}
}