github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/datas/database_common.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // This file incorporates work covered by the following copyright and
    16  // permission notice:
    17  //
    18  // Copyright 2016 Attic Labs, Inc. All rights reserved.
    19  // Licensed under the Apache License, version 2.0:
    20  // http://www.apache.org/licenses/LICENSE-2.0
    21  
    22  package datas
    23  
    24  import (
    25  	"context"
    26  	"errors"
    27  	"fmt"
    28  	"sync"
    29  
    30  	"github.com/dolthub/dolt/go/store/chunks"
    31  	"github.com/dolthub/dolt/go/store/d"
    32  	"github.com/dolthub/dolt/go/store/hash"
    33  	"github.com/dolthub/dolt/go/store/merge"
    34  	"github.com/dolthub/dolt/go/store/types"
    35  	"github.com/dolthub/dolt/go/store/util/random"
    36  )
    37  
    38  type database struct {
    39  	*types.ValueStore
    40  	rt rootTracker
    41  	mu sync.Mutex
    42  }
    43  
    44  var (
    45  	ErrOptimisticLockFailed = errors.New("optimistic lock failed on database Root update")
    46  	ErrMergeNeeded          = errors.New("dataset head is not ancestor of commit")
    47  )
    48  
    49  // TODO: fix panics
    50  // rootTracker is a narrowing of the ChunkStore interface, to keep Database disciplined about working directly with Chunks
    51  type rootTracker interface {
    52  	Rebase(ctx context.Context) error
    53  	Root(ctx context.Context) (hash.Hash, error)
    54  	Commit(ctx context.Context, current, last hash.Hash) (bool, error)
    55  }
    56  
    57  func newDatabase(cs chunks.ChunkStore) *database {
    58  	vs := types.NewValueStore(cs)
    59  
    60  	return &database{
    61  		ValueStore: vs, // ValueStore is responsible for closing |cs|
    62  		rt:         vs,
    63  	}
    64  }
    65  
    66  var _ Database = &database{}
    67  var _ GarbageCollector = &database{}
    68  
    69  var _ rootTracker = &types.ValueStore{}
    70  var _ GarbageCollector = &types.ValueStore{}
    71  
    72  func (db *database) chunkStore() chunks.ChunkStore {
    73  	return db.ChunkStore()
    74  }
    75  
    76  func (db *database) Stats() interface{} {
    77  	return db.ChunkStore().Stats()
    78  }
    79  
    80  func (db *database) StatsSummary() string {
    81  	return db.ChunkStore().StatsSummary()
    82  }
    83  
    84  func (db *database) Flush(ctx context.Context) error {
    85  	ds, err := db.GetDataset(ctx, fmt.Sprintf("-/flush/%s", random.Id()))
    86  
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	r, err := db.WriteValue(ctx, types.Bool(true))
    92  
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	ds, err = db.CommitValue(ctx, ds, r)
    98  
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	_, err = db.Delete(ctx, ds)
   104  
   105  	return err
   106  }
   107  
   108  func (db *database) Datasets(ctx context.Context) (types.Map, error) {
   109  	rootHash, err := db.rt.Root(ctx)
   110  
   111  	if err != nil {
   112  		return types.EmptyMap, err
   113  	}
   114  
   115  	if rootHash.IsEmpty() {
   116  		return types.NewMap(ctx, db)
   117  	}
   118  
   119  	val, err := db.ReadValue(ctx, rootHash)
   120  
   121  	if err != nil {
   122  		return types.EmptyMap, err
   123  	}
   124  
   125  	return val.(types.Map), nil
   126  }
   127  
   128  func (db *database) GetDataset(ctx context.Context, datasetID string) (Dataset, error) {
   129  	// precondition checks
   130  	if !DatasetFullRe.MatchString(datasetID) {
   131  		return Dataset{}, fmt.Errorf("Invalid dataset ID: %s", datasetID)
   132  	}
   133  
   134  	datasets, err := db.Datasets(ctx)
   135  
   136  	if err != nil {
   137  		return Dataset{}, err
   138  	}
   139  
   140  	var head types.Value
   141  	if r, ok, err := datasets.MaybeGet(ctx, types.String(datasetID)); err != nil {
   142  		return Dataset{}, err
   143  	} else if ok {
   144  		head, err = r.(types.Ref).TargetValue(ctx, db)
   145  
   146  		if err != nil {
   147  			return Dataset{}, err
   148  		}
   149  	}
   150  
   151  	return newDataset(db, datasetID, head)
   152  }
   153  
   154  func (db *database) Rebase(ctx context.Context) error {
   155  	return db.rt.Rebase(ctx)
   156  }
   157  
   158  func (db *database) Close() error {
   159  	return db.ValueStore.Close()
   160  }
   161  
   162  func (db *database) SetHead(ctx context.Context, ds Dataset, newHeadRef types.Ref) (Dataset, error) {
   163  	return db.doHeadUpdate(ctx, ds, func(ds Dataset) error { return db.doSetHead(ctx, ds, newHeadRef) })
   164  }
   165  
   166  func (db *database) doSetHead(ctx context.Context, ds Dataset, newHeadRef types.Ref) error {
   167  	newSt, err := newHeadRef.TargetValue(ctx, db)
   168  
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	headType := newSt.(types.Struct).Name()
   174  
   175  	currentHeadRef, ok, err := ds.MaybeHeadRef()
   176  	if err != nil {
   177  		return err
   178  	}
   179  	if ok {
   180  		if newHeadRef.Equals(currentHeadRef) {
   181  			return nil
   182  		}
   183  
   184  		currSt, err := currentHeadRef.TargetValue(ctx, db)
   185  
   186  		if err != nil {
   187  			return err
   188  		}
   189  
   190  		headType = currSt.(types.Struct).Name()
   191  	}
   192  
   193  	// the new head value must match the type of the old head value
   194  	switch headType {
   195  	case CommitName:
   196  		_, err = db.validateRefAsCommit(ctx, newHeadRef)
   197  	case TagName:
   198  		err = db.validateTag(ctx, newSt.(types.Struct))
   199  	default:
   200  		return fmt.Errorf("Unrecognized dataset value: %s", headType)
   201  	}
   202  
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	currentRootHash, err := db.rt.Root(ctx)
   208  
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	currentDatasets, err := db.Datasets(ctx)
   214  
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	refSt, err := db.WriteValue(ctx, newSt) // will be orphaned if the tryCommitChunks() below fails
   220  
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	ref, err := types.ToRefOfValue(refSt, db.Format())
   226  
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	currentDatasets, err = currentDatasets.Edit().Set(types.String(ds.ID()), ref).Map(ctx)
   232  
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	return db.tryCommitChunks(ctx, currentDatasets, currentRootHash)
   238  }
   239  
   240  func (db *database) FastForward(ctx context.Context, ds Dataset, newHeadRef types.Ref) (Dataset, error) {
   241  	return db.doHeadUpdate(ctx, ds, func(ds Dataset) error { return db.doFastForward(ctx, ds, newHeadRef) })
   242  }
   243  
   244  func (db *database) doFastForward(ctx context.Context, ds Dataset, newHeadRef types.Ref) error {
   245  	currentHeadRef, ok, err := ds.MaybeHeadRef()
   246  
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	if ok && newHeadRef.Equals(currentHeadRef) {
   252  		return nil
   253  	}
   254  
   255  	if ok && newHeadRef.Height() <= currentHeadRef.Height() {
   256  		return ErrMergeNeeded
   257  	}
   258  
   259  	commit, err := db.validateRefAsCommit(ctx, newHeadRef)
   260  
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	return db.doCommit(ctx, ds.ID(), commit, nil)
   266  }
   267  
   268  func (db *database) Commit(ctx context.Context, ds Dataset, v types.Value, opts CommitOptions) (Dataset, error) {
   269  	return db.doHeadUpdate(
   270  		ctx,
   271  		ds,
   272  		func(ds Dataset) error {
   273  			st, err := buildNewCommit(ctx, ds, v, opts)
   274  
   275  			if err != nil {
   276  				return err
   277  			}
   278  
   279  			return db.doCommit(ctx, ds.ID(), st, opts.Policy)
   280  		},
   281  	)
   282  }
   283  
   284  func (db *database) CommitDangling(ctx context.Context, v types.Value, opts CommitOptions) (types.Struct, error) {
   285  	if opts.ParentsList == types.EmptyList || opts.ParentsList.Len() == 0 {
   286  		return types.Struct{}, errors.New("cannot create commit without parents")
   287  	}
   288  
   289  	if opts.Meta.IsZeroValue() {
   290  		opts.Meta = types.EmptyStruct(db.Format())
   291  	}
   292  
   293  	commitStruct, err := NewCommit(ctx, v, opts.ParentsList, opts.Meta)
   294  	if err != nil {
   295  		return types.Struct{}, err
   296  	}
   297  
   298  	_, err = db.WriteValue(ctx, commitStruct)
   299  	if err != nil {
   300  		return types.Struct{}, err
   301  	}
   302  
   303  	err = db.Flush(ctx)
   304  	if err != nil {
   305  		return types.Struct{}, err
   306  	}
   307  
   308  	return commitStruct, nil
   309  }
   310  
   311  func (db *database) CommitValue(ctx context.Context, ds Dataset, v types.Value) (Dataset, error) {
   312  	return db.Commit(ctx, ds, v, CommitOptions{})
   313  }
   314  
   315  // doCommit manages concurrent access the single logical piece of mutable state: the current Root. doCommit is
   316  // optimistic in that it is attempting to update head making the assumption that currentRootHash is the hash of the
   317  // current head. The call to Commit below will return an 'ErrOptimisticLockFailed' error if that assumption fails (e.g.
   318  // because of a race with another writer) and the entire algorithm must be tried again. This method will also fail and
   319  // return an 'ErrMergeNeeded' error if the |commit| is not a descendent of the current dataset head
   320  func (db *database) doCommit(ctx context.Context, datasetID string, commit types.Struct, mergePolicy merge.Policy) error {
   321  	if is, err := IsCommit(commit); err != nil {
   322  		return err
   323  	} else if !is {
   324  		d.Panic("Can't commit a non-Commit struct to dataset %s", datasetID)
   325  	}
   326  
   327  	// This could loop forever, given enough simultaneous committers. BUG 2565
   328  	var tryCommitErr error
   329  	for tryCommitErr = ErrOptimisticLockFailed; tryCommitErr == ErrOptimisticLockFailed; {
   330  		currentRootHash, err := db.rt.Root(ctx)
   331  
   332  		if err != nil {
   333  			return err
   334  		}
   335  
   336  		currentDatasets, err := db.Datasets(ctx)
   337  
   338  		if err != nil {
   339  			return err
   340  		}
   341  
   342  		commitRef, err := db.WriteValue(ctx, commit) // will be orphaned if the tryCommitChunks() below fails
   343  
   344  		if err != nil {
   345  			return err
   346  		}
   347  
   348  		// If there's nothing in the DB yet, skip all this logic.
   349  		if !currentRootHash.IsEmpty() {
   350  			r, hasHead, err := currentDatasets.MaybeGet(ctx, types.String(datasetID))
   351  
   352  			if err != nil {
   353  				return err
   354  			}
   355  
   356  			// First commit in dataset is always fast-forward, so go through all this iff there's already a Head for datasetID.
   357  			if hasHead {
   358  				head, err := r.(types.Ref).TargetValue(ctx, db)
   359  
   360  				if err != nil {
   361  					return err
   362  				}
   363  
   364  				currentHeadRef, err := types.NewRef(head, db.Format())
   365  
   366  				if err != nil {
   367  					return err
   368  				}
   369  
   370  				ancestorRef, found, err := FindCommonAncestor(ctx, commitRef, currentHeadRef, db, db)
   371  
   372  				if err != nil {
   373  					return err
   374  				}
   375  
   376  				if !found {
   377  					return ErrMergeNeeded
   378  				}
   379  
   380  				// This covers all cases where currentHeadRef is not an ancestor of commit, including the following edge cases:
   381  				//   - commit is a duplicate of currentHead.
   382  				//   - we hit an ErrOptimisticLockFailed and looped back around because some other process changed the Head out from under us.
   383  				if currentHeadRef.TargetHash() != ancestorRef.TargetHash() || currentHeadRef.TargetHash() == commitRef.TargetHash() {
   384  					if mergePolicy == nil {
   385  						return ErrMergeNeeded
   386  					}
   387  
   388  					ancestor, err := db.validateRefAsCommit(ctx, ancestorRef)
   389  					if err != nil {
   390  						return err
   391  					}
   392  
   393  					currentHead, err := db.validateRefAsCommit(ctx, currentHeadRef)
   394  					if err != nil {
   395  						return err
   396  					}
   397  
   398  					cmVal, _, err := commit.MaybeGet(ValueField)
   399  					if err != nil {
   400  						return err
   401  					}
   402  
   403  					curVal, _, err := currentHead.MaybeGet(ValueField)
   404  					if err != nil {
   405  						return err
   406  					}
   407  
   408  					ancVal, _, err := ancestor.MaybeGet(ValueField)
   409  					if err != nil {
   410  						return err
   411  					}
   412  
   413  					merged, err := mergePolicy(ctx, cmVal, curVal, ancVal, db, nil)
   414  					if err != nil {
   415  						return err
   416  					}
   417  
   418  					l, err := types.NewList(ctx, db, commitRef, currentHeadRef)
   419  					if err != nil {
   420  						return err
   421  					}
   422  
   423  					newCom, err := NewCommit(ctx, merged, l, types.EmptyStruct(db.Format()))
   424  					if err != nil {
   425  						return err
   426  					}
   427  
   428  					commitRef, err = db.WriteValue(ctx, newCom)
   429  					if err != nil {
   430  						return err
   431  					}
   432  				}
   433  			}
   434  		}
   435  
   436  		ref, err := types.ToRefOfValue(commitRef, db.Format())
   437  		if err != nil {
   438  			return err
   439  		}
   440  
   441  		currentDatasets, err = currentDatasets.Edit().Set(types.String(datasetID), ref).Map(ctx)
   442  		if err != nil {
   443  			return err
   444  		}
   445  
   446  		tryCommitErr = db.tryCommitChunks(ctx, currentDatasets, currentRootHash)
   447  	}
   448  
   449  	return tryCommitErr
   450  }
   451  
   452  func (db *database) Tag(ctx context.Context, ds Dataset, ref types.Ref, opts TagOptions) (Dataset, error) {
   453  	return db.doHeadUpdate(
   454  		ctx,
   455  		ds,
   456  		func(ds Dataset) error {
   457  			st, err := NewTag(ctx, ref, opts.Meta)
   458  
   459  			if err != nil {
   460  				return err
   461  			}
   462  
   463  			return db.doTag(ctx, ds.ID(), st)
   464  		},
   465  	)
   466  }
   467  
   468  // doTag manages concurrent access the single logical piece of mutable state: the current Root. It uses
   469  // the same optimistic writing algorithm as doCommit (see above).
   470  func (db *database) doTag(ctx context.Context, datasetID string, tag types.Struct) error {
   471  	err := db.validateTag(ctx, tag)
   472  
   473  	if err != nil {
   474  		return err
   475  	}
   476  
   477  	// This could loop forever, given enough simultaneous writers. BUG 2565
   478  	var tryCommitErr error
   479  	for tryCommitErr = ErrOptimisticLockFailed; tryCommitErr == ErrOptimisticLockFailed; {
   480  		currentRootHash, err := db.rt.Root(ctx)
   481  
   482  		if err != nil {
   483  			return err
   484  		}
   485  
   486  		currentDatasets, err := db.Datasets(ctx)
   487  
   488  		if err != nil {
   489  			return err
   490  		}
   491  
   492  		tagRef, err := db.WriteValue(ctx, tag) // will be orphaned if the tryCommitChunks() below fails
   493  
   494  		if err != nil {
   495  			return err
   496  		}
   497  
   498  		_, hasHead, err := currentDatasets.MaybeGet(ctx, types.String(datasetID))
   499  
   500  		if err != nil {
   501  			return err
   502  		}
   503  
   504  		if hasHead {
   505  			return fmt.Errorf(fmt.Sprintf("tag %s already exists and cannot be altered after creation", datasetID))
   506  		}
   507  
   508  		ref, err := types.ToRefOfValue(tagRef, db.Format())
   509  		if err != nil {
   510  			return err
   511  		}
   512  
   513  		currentDatasets, err = currentDatasets.Edit().Set(types.String(datasetID), ref).Map(ctx)
   514  		if err != nil {
   515  			return err
   516  		}
   517  
   518  		tryCommitErr = db.tryCommitChunks(ctx, currentDatasets, currentRootHash)
   519  	}
   520  
   521  	return tryCommitErr
   522  }
   523  
   524  func (db *database) UpdateWorkingSet(ctx context.Context, ds Dataset, ref types.Ref, meta WorkingSetMeta, prevHash hash.Hash) (Dataset, error) {
   525  	return db.doHeadUpdate(
   526  		ctx,
   527  		ds,
   528  		func(ds Dataset) error {
   529  			workspace, err := NewWorkingSet(ctx, ref)
   530  			if err != nil {
   531  				return err
   532  			}
   533  
   534  			return db.doUpdateWorkingSet(ctx, ds.ID(), workspace, prevHash)
   535  		},
   536  	)
   537  }
   538  
   539  // doUpdateWorkingSet manages concurrent access the single logical piece of mutable state: the current Root. It uses
   540  // the same optimistic locking write algorithm as doCommit (see above). Unlike doCommit and other methods in this file,
   541  // an error is returned if the current value of the ref being written has changed.
   542  // Workspace updates are serialized, but all other changes to a database's root value can proceed independently with the
   543  // normal optimistic locking.
   544  func (db *database) doUpdateWorkingSet(ctx context.Context, datasetID string, workingSet types.Struct, currHash hash.Hash) error {
   545  	err := db.validateWorkingSet(workingSet)
   546  	if err != nil {
   547  		return err
   548  	}
   549  
   550  	// This could loop forever, given enough simultaneous writers. BUG 2565
   551  	var tryCommitErr error
   552  	testSetFailed := false
   553  	for tryCommitErr = ErrOptimisticLockFailed; tryCommitErr == ErrOptimisticLockFailed && !testSetFailed; {
   554  		tryCommitErr = func() error {
   555  			db.mu.Lock()
   556  			defer db.mu.Unlock()
   557  
   558  			currentRootHash, err := db.rt.Root(ctx)
   559  
   560  			if err != nil {
   561  				return err
   562  			}
   563  
   564  			currentDatasets, err := db.Datasets(ctx)
   565  			if err != nil {
   566  				return err
   567  			}
   568  
   569  			workingSetRef, err := db.WriteValue(ctx, workingSet) // will be orphaned if the tryCommitChunks() below fails
   570  			if err != nil {
   571  				return err
   572  			}
   573  
   574  			ds, err := db.GetDataset(ctx, datasetID)
   575  			if err != nil {
   576  				return err
   577  			}
   578  
   579  			// Second level of locking: assert that the dataset value we read is unchanged from its expected value.
   580  			// This is separate than the whole-DB lock in the outer loop, as it only protects the value of this dataset
   581  			// entry. Other writers can update other values and writes chunks in the database and we will retry
   582  			// indefinitely. But if we find the expected value in the dataset has changed, we immediately abort and the
   583  			// caller must retry after reconciliation.
   584  			if ds.HasHead() {
   585  				head, ok := ds.MaybeHead()
   586  				if !ok {
   587  					panic("no head found")
   588  				}
   589  
   590  				h, err := head.Hash(db.Format())
   591  				if err != nil {
   592  					return err
   593  				}
   594  
   595  				if h != currHash {
   596  					testSetFailed = true
   597  					return ErrOptimisticLockFailed
   598  				}
   599  			} else {
   600  				if !currHash.IsEmpty() {
   601  					panic("No ref found for workspace " + datasetID)
   602  				}
   603  			}
   604  
   605  			valRef, err := types.ToRefOfValue(workingSetRef, db.Format())
   606  			if err != nil {
   607  				return err
   608  			}
   609  
   610  			currentDatasets, err = currentDatasets.Edit().Set(types.String(datasetID), valRef).Map(ctx)
   611  			if err != nil {
   612  				return err
   613  			}
   614  
   615  			return db.tryCommitChunks(ctx, currentDatasets, currentRootHash)
   616  		}()
   617  	}
   618  
   619  	return tryCommitErr
   620  }
   621  
   622  func (db *database) Delete(ctx context.Context, ds Dataset) (Dataset, error) {
   623  	return db.doHeadUpdate(ctx, ds, func(ds Dataset) error { return db.doDelete(ctx, ds.ID()) })
   624  }
   625  
   626  // doDelete manages concurrent access the single logical piece of mutable state: the current Root. doDelete is optimistic in that it is attempting to update head making the assumption that currentRootHash is the hash of the current head. The call to Commit below will return an 'ErrOptimisticLockFailed' error if that assumption fails (e.g. because of a race with another writer) and the entire algorithm must be tried again.
   627  func (db *database) doDelete(ctx context.Context, datasetIDstr string) error {
   628  	datasetID := types.String(datasetIDstr)
   629  	currentRootHash, err := db.rt.Root(ctx)
   630  
   631  	if err != nil {
   632  		return err
   633  	}
   634  
   635  	currentDatasets, err := db.Datasets(ctx)
   636  
   637  	if err != nil {
   638  		return err
   639  	}
   640  
   641  	var initialHead types.Ref
   642  	if r, hasHead, err := currentDatasets.MaybeGet(ctx, datasetID); err != nil {
   643  		return err
   644  	} else if !hasHead {
   645  		return nil
   646  	} else {
   647  		initialHead = r.(types.Ref)
   648  	}
   649  
   650  	for {
   651  		currentDatasets, err = currentDatasets.Edit().Remove(datasetID).Map(ctx)
   652  		if err != nil {
   653  			return err
   654  		}
   655  		err = db.tryCommitChunks(ctx, currentDatasets, currentRootHash)
   656  		if err != ErrOptimisticLockFailed {
   657  			break
   658  		}
   659  
   660  		// If the optimistic lock failed because someone changed the Head of datasetID, then return ErrMergeNeeded. If it failed because someone changed a different Dataset, we should try again.
   661  		currentRootHash, err = db.rt.Root(ctx)
   662  
   663  		if err != nil {
   664  			return err
   665  		}
   666  
   667  		currentDatasets, err = db.Datasets(ctx)
   668  
   669  		if err != nil {
   670  			return err
   671  		}
   672  
   673  		var r types.Value
   674  		var hasHead bool
   675  		if r, hasHead, err = currentDatasets.MaybeGet(ctx, datasetID); err != nil {
   676  			return err
   677  		} else if !hasHead || (hasHead && !initialHead.Equals(r)) {
   678  			err = ErrMergeNeeded
   679  			break
   680  		}
   681  	}
   682  	return err
   683  }
   684  
   685  // GC traverses the database starting at the Root and removes all unreferenced data from persistent storage.
   686  func (db *database) GC(ctx context.Context) error {
   687  	return db.ValueStore.GC(ctx)
   688  }
   689  
   690  func (db *database) tryCommitChunks(ctx context.Context, currentDatasets types.Map, currentRootHash hash.Hash) error {
   691  	newRoot, err := db.WriteValue(ctx, currentDatasets)
   692  
   693  	if err != nil {
   694  		return err
   695  	}
   696  
   697  	newRootHash := newRoot.TargetHash()
   698  
   699  	if success, err := db.rt.Commit(ctx, newRootHash, currentRootHash); err != nil {
   700  		return err
   701  	} else if !success {
   702  		return ErrOptimisticLockFailed
   703  	}
   704  
   705  	return nil
   706  }
   707  
   708  func (db *database) validateRefAsCommit(ctx context.Context, r types.Ref) (types.Struct, error) {
   709  	v, err := db.ReadValue(ctx, r.TargetHash())
   710  
   711  	if err != nil {
   712  		return types.EmptyStruct(r.Format()), err
   713  	}
   714  
   715  	if v == nil {
   716  		panic(r.TargetHash().String() + " not found")
   717  	}
   718  
   719  	is, err := IsCommit(v)
   720  
   721  	if err != nil {
   722  		return types.EmptyStruct(r.Format()), err
   723  	}
   724  
   725  	if !is {
   726  		panic("Not a commit")
   727  	}
   728  
   729  	return v.(types.Struct), nil
   730  }
   731  
   732  func (db *database) validateTag(ctx context.Context, t types.Struct) error {
   733  	is, err := IsTag(t)
   734  	if err != nil {
   735  		return err
   736  	}
   737  	if !is {
   738  		return fmt.Errorf("Tag struct %s is malformed, IsTag() == false", t.String())
   739  	}
   740  
   741  	r, ok, err := t.MaybeGet(TagCommitRefField)
   742  	if err != nil {
   743  		return err
   744  	}
   745  	if !ok {
   746  		return fmt.Errorf("tag is missing field %s", TagCommitRefField)
   747  	}
   748  
   749  	_, err = db.validateRefAsCommit(ctx, r.(types.Ref))
   750  
   751  	if err != nil {
   752  		return err
   753  	}
   754  
   755  	return nil
   756  }
   757  
   758  func (db *database) validateWorkingSet(t types.Struct) error {
   759  	is, err := IsWorkingSet(t)
   760  	if err != nil {
   761  		return err
   762  	}
   763  	if !is {
   764  		return fmt.Errorf("WorkingSet struct %s is malformed, IsWorkingSet() == false", t.String())
   765  	}
   766  
   767  	_, ok, err := t.MaybeGet(WorkingSetRefField)
   768  	if err != nil {
   769  		return err
   770  	}
   771  	if !ok {
   772  		return fmt.Errorf("WorkingSet is missing field %s", WorkingSetRefField)
   773  	}
   774  
   775  	return nil
   776  }
   777  
   778  func buildNewCommit(ctx context.Context, ds Dataset, v types.Value, opts CommitOptions) (types.Struct, error) {
   779  	parents := opts.ParentsList
   780  	if parents == types.EmptyList || parents.Len() == 0 {
   781  		var err error
   782  		parents, err = types.NewList(ctx, ds.Database())
   783  		if err != nil {
   784  			return types.EmptyStruct(ds.Database().Format()), err
   785  		}
   786  
   787  		if headRef, ok, err := ds.MaybeHeadRef(); err != nil {
   788  			return types.EmptyStruct(ds.Database().Format()), err
   789  		} else if ok {
   790  			le := parents.Edit().Append(headRef)
   791  			parents, err = le.List(ctx)
   792  			if err != nil {
   793  				return types.EmptyStruct(ds.Database().Format()), err
   794  			}
   795  		}
   796  	}
   797  
   798  	meta := opts.Meta
   799  	if meta.IsZeroValue() {
   800  		meta = types.EmptyStruct(ds.Database().Format())
   801  	}
   802  	return NewCommit(ctx, v, parents, meta)
   803  }
   804  
   805  func (db *database) doHeadUpdate(ctx context.Context, ds Dataset, updateFunc func(ds Dataset) error) (Dataset, error) {
   806  	err := updateFunc(ds)
   807  
   808  	if err != nil {
   809  		return Dataset{}, err
   810  	}
   811  
   812  	return db.GetDataset(ctx, ds.ID())
   813  }