github.com/jbendotnet/noms@v0.0.0-20190904222105-c43e4293ea92/go/datas/database_common.go (about) 1 // Copyright 2016 Attic Labs, Inc. All rights reserved. 2 // Licensed under the Apache License, version 2.0: 3 // http://www.apache.org/licenses/LICENSE-2.0 4 5 package datas 6 7 import ( 8 "errors" 9 "fmt" 10 11 "github.com/attic-labs/noms/go/chunks" 12 "github.com/attic-labs/noms/go/d" 13 "github.com/attic-labs/noms/go/hash" 14 "github.com/attic-labs/noms/go/merge" 15 "github.com/attic-labs/noms/go/types" 16 "github.com/attic-labs/noms/go/util/random" 17 ) 18 19 type database struct { 20 *types.ValueStore 21 rt rootTracker 22 } 23 24 var ( 25 ErrOptimisticLockFailed = errors.New("Optimistic lock failed on database Root update") 26 ErrMergeNeeded = errors.New("Dataset head is not ancestor of commit") 27 ) 28 29 // rootTracker is a narrowing of the ChunkStore interface, to keep Database disciplined about working directly with Chunks 30 type rootTracker interface { 31 Rebase() 32 Root() hash.Hash 33 Commit(current, last hash.Hash) bool 34 } 35 36 func newDatabase(cs chunks.ChunkStore) *database { 37 vs := types.NewValueStore(cs) 38 if _, ok := cs.(*httpChunkStore); ok { 39 vs.SetEnforceCompleteness(false) 40 } 41 42 return &database{ 43 ValueStore: vs, // ValueStore is responsible for closing |cs| 44 rt: vs, 45 } 46 } 47 48 func (db *database) chunkStore() chunks.ChunkStore { 49 return db.ChunkStore() 50 } 51 52 func (db *database) Stats() interface{} { 53 return db.ChunkStore().Stats() 54 } 55 56 func (db *database) StatsSummary() string { 57 return db.ChunkStore().StatsSummary() 58 } 59 60 func (db *database) Flush() { 61 // TODO: This is a pretty ghetto hack - do better. 62 // See: https://github.com/attic-labs/noms/issues/3530 63 ds := db.GetDataset(fmt.Sprintf("-/flush/%s", random.Id())) 64 r := db.WriteValue(types.Bool(true)) 65 ds, err := db.CommitValue(ds, r) 66 d.PanicIfError(err) 67 _, err = db.Delete(ds) 68 d.PanicIfError(err) 69 } 70 71 func (db *database) Datasets() types.Map { 72 rootHash := db.rt.Root() 73 if rootHash.IsEmpty() { 74 return types.NewMap(db) 75 } 76 77 return db.ReadValue(rootHash).(types.Map) 78 } 79 80 func (db *database) GetDataset(datasetID string) Dataset { 81 if !DatasetFullRe.MatchString(datasetID) { 82 d.Panic("Invalid dataset ID: %s", datasetID) 83 } 84 var head types.Value 85 if r, ok := db.Datasets().MaybeGet(types.String(datasetID)); ok { 86 head = r.(types.Ref).TargetValue(db) 87 } 88 89 return newDataset(db, datasetID, head) 90 } 91 92 func (db *database) Rebase() { 93 db.rt.Rebase() 94 } 95 96 func (db *database) Close() error { 97 return db.ValueStore.Close() 98 } 99 100 func (db *database) SetHead(ds Dataset, newHeadRef types.Ref) (Dataset, error) { 101 return db.doHeadUpdate(ds, func(ds Dataset) error { return db.doSetHead(ds, newHeadRef) }) 102 } 103 104 func (db *database) doSetHead(ds Dataset, newHeadRef types.Ref) error { 105 if currentHeadRef, ok := ds.MaybeHeadRef(); ok && newHeadRef.Equals(currentHeadRef) { 106 return nil 107 } 108 commit := db.validateRefAsCommit(newHeadRef) 109 110 currentRootHash, currentDatasets := db.rt.Root(), db.Datasets() 111 commitRef := db.WriteValue(commit) // will be orphaned if the tryCommitChunks() below fails 112 113 currentDatasets = currentDatasets.Edit().Set(types.String(ds.ID()), types.ToRefOfValue(commitRef)).Map() 114 return db.tryCommitChunks(currentDatasets, currentRootHash) 115 } 116 117 func (db *database) FastForward(ds Dataset, newHeadRef types.Ref) (Dataset, error) { 118 return db.doHeadUpdate(ds, func(ds Dataset) error { return db.doFastForward(ds, newHeadRef) }) 119 } 120 121 func (db *database) doFastForward(ds Dataset, newHeadRef types.Ref) error { 122 currentHeadRef, ok := ds.MaybeHeadRef() 123 if ok && newHeadRef.Equals(currentHeadRef) { 124 return nil 125 } 126 127 if ok && newHeadRef.Height() <= currentHeadRef.Height() { 128 return ErrMergeNeeded 129 } 130 131 commit := db.validateRefAsCommit(newHeadRef) 132 return db.doCommit(ds.ID(), commit, nil) 133 } 134 135 func (db *database) Commit(ds Dataset, v types.Value, opts CommitOptions) (Dataset, error) { 136 return db.doHeadUpdate( 137 ds, 138 func(ds Dataset) error { return db.doCommit(ds.ID(), buildNewCommit(ds, v, opts), opts.Policy) }, 139 ) 140 } 141 142 func (db *database) CommitValue(ds Dataset, v types.Value) (Dataset, error) { 143 return db.Commit(ds, v, CommitOptions{}) 144 } 145 146 // doCommit manages concurrent access the single logical piece of mutable state: the current Root. doCommit 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. This method will also fail and return an 'ErrMergeNeeded' error if the |commit| is not a descendent of the current dataset head 147 func (db *database) doCommit(datasetID string, commit types.Struct, mergePolicy merge.Policy) error { 148 if !IsCommit(commit) { 149 d.Panic("Can't commit a non-Commit struct to dataset %s", datasetID) 150 } 151 152 // This could loop forever, given enough simultaneous committers. BUG 2565 153 var err error 154 for err = ErrOptimisticLockFailed; err == ErrOptimisticLockFailed; { 155 currentRootHash, currentDatasets := db.rt.Root(), db.Datasets() 156 commitRef := db.WriteValue(commit) // will be orphaned if the tryCommitChunks() below fails 157 158 // If there's nothing in the DB yet, skip all this logic. 159 if !currentRootHash.IsEmpty() { 160 r, hasHead := currentDatasets.MaybeGet(types.String(datasetID)) 161 162 // First commit in dataset is always fast-forward, so go through all this iff there's already a Head for datasetID. 163 if hasHead { 164 head := r.(types.Ref).TargetValue(db) 165 currentHeadRef := types.NewRef(head) 166 ancestorRef, found := FindCommonAncestor(commitRef, currentHeadRef, db) 167 if !found { 168 return ErrMergeNeeded 169 } 170 171 // This covers all cases where currentHeadRef is not an ancestor of commit, including the following edge cases: 172 // - commit is a duplicate of currentHead. 173 // - we hit an ErrOptimisticLockFailed and looped back around because some other process changed the Head out from under us. 174 if currentHeadRef.TargetHash() != ancestorRef.TargetHash() || currentHeadRef.TargetHash() == commitRef.TargetHash() { 175 if mergePolicy == nil { 176 return ErrMergeNeeded 177 } 178 179 ancestor, currentHead := db.validateRefAsCommit(ancestorRef), db.validateRefAsCommit(currentHeadRef) 180 merged, err := mergePolicy(commit.Get(ValueField), currentHead.Get(ValueField), ancestor.Get(ValueField), db, nil) 181 if err != nil { 182 return err 183 } 184 commitRef = db.WriteValue(NewCommit(merged, types.NewSet(db, commitRef, currentHeadRef), types.EmptyStruct)) 185 } 186 } 187 } 188 currentDatasets = currentDatasets.Edit().Set(types.String(datasetID), types.ToRefOfValue(commitRef)).Map() 189 err = db.tryCommitChunks(currentDatasets, currentRootHash) 190 } 191 return err 192 } 193 194 func (db *database) Delete(ds Dataset) (Dataset, error) { 195 return db.doHeadUpdate(ds, func(ds Dataset) error { return db.doDelete(ds.ID()) }) 196 } 197 198 // 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. 199 func (db *database) doDelete(datasetIDstr string) error { 200 datasetID := types.String(datasetIDstr) 201 currentRootHash, currentDatasets := db.rt.Root(), db.Datasets() 202 var initialHead types.Ref 203 if r, hasHead := currentDatasets.MaybeGet(datasetID); !hasHead { 204 return nil 205 } else { 206 initialHead = r.(types.Ref) 207 } 208 209 var err error 210 for { 211 currentDatasets = currentDatasets.Edit().Remove(datasetID).Map() 212 err = db.tryCommitChunks(currentDatasets, currentRootHash) 213 if err != ErrOptimisticLockFailed { 214 break 215 } 216 // 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. 217 currentRootHash, currentDatasets = db.rt.Root(), db.Datasets() 218 if r, hasHead := currentDatasets.MaybeGet(datasetID); !hasHead || (hasHead && !initialHead.Equals(r)) { 219 err = ErrMergeNeeded 220 break 221 } 222 } 223 return err 224 } 225 226 func (db *database) tryCommitChunks(currentDatasets types.Map, currentRootHash hash.Hash) (err error) { 227 newRootHash := db.WriteValue(currentDatasets).TargetHash() 228 229 if !db.rt.Commit(newRootHash, currentRootHash) { 230 err = ErrOptimisticLockFailed 231 } 232 return 233 } 234 235 func (db *database) validateRefAsCommit(r types.Ref) types.Struct { 236 v := db.ReadValue(r.TargetHash()) 237 238 if v == nil { 239 panic(r.TargetHash().String() + " not found") 240 } 241 if !IsCommit(v) { 242 panic("Not a commit: " + types.EncodedValueMaxLines(v, 10) + " ...\n") 243 } 244 return v.(types.Struct) 245 } 246 247 func buildNewCommit(ds Dataset, v types.Value, opts CommitOptions) types.Struct { 248 parents := opts.Parents 249 if (parents == types.Set{}) { 250 parents = types.NewSet(ds.Database()) 251 if headRef, ok := ds.MaybeHeadRef(); ok { 252 parents = parents.Edit().Insert(headRef).Set() 253 } 254 } 255 256 meta := opts.Meta 257 if meta.IsZeroValue() { 258 meta = types.EmptyStruct 259 } 260 return NewCommit(v, parents, meta) 261 } 262 263 func (db *database) doHeadUpdate(ds Dataset, updateFunc func(ds Dataset) error) (Dataset, error) { 264 err := updateFunc(ds) 265 return db.GetDataset(ds.ID()), err 266 }