github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/doltdb/doltdb.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 package doltdb 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "math/rand" 22 "strings" 23 "time" 24 25 "github.com/dolthub/dolt/go/libraries/doltcore/dbfactory" 26 "github.com/dolthub/dolt/go/libraries/doltcore/ref" 27 "github.com/dolthub/dolt/go/libraries/utils/filesys" 28 "github.com/dolthub/dolt/go/store/chunks" 29 "github.com/dolthub/dolt/go/store/datas" 30 "github.com/dolthub/dolt/go/store/hash" 31 "github.com/dolthub/dolt/go/store/spec" 32 "github.com/dolthub/dolt/go/store/types" 33 "github.com/dolthub/dolt/go/store/types/edits" 34 ) 35 36 func init() { 37 types.CreateEditAccForMapEdits = func(nbf *types.NomsBinFormat) types.EditAccumulator { 38 return edits.NewAsyncSortedEdits(nbf, 16*1024, 4, 2) 39 } 40 } 41 42 const ( 43 creationBranch = "create" 44 MasterBranch = "master" 45 CommitStructName = "Commit" 46 47 defaultChunksPerTF = 256 * 1024 48 ) 49 50 // LocalDirDoltDB stores the db in the current directory 51 var LocalDirDoltDB = "file://./" + dbfactory.DoltDataDir 52 53 // InMemDoltDB stores the DoltDB db in memory and is primarily used for testing 54 var InMemDoltDB = "mem://" 55 56 var ErrNoRootValAtHash = errors.New("there is no dolt root value at that hash") 57 58 // DoltDB wraps access to the underlying noms database and hides some of the details of the underlying storage. 59 // Additionally the noms codebase uses panics in a way that is non idiomatic and We've opted to recover and return 60 // errors in many cases. 61 type DoltDB struct { 62 db datas.Database 63 } 64 65 // DoltDBFromCS creates a DoltDB from a noms chunks.ChunkStore 66 func DoltDBFromCS(cs chunks.ChunkStore) *DoltDB { 67 db := datas.NewDatabase(cs) 68 69 return &DoltDB{db} 70 } 71 72 // LoadDoltDB will acquire a reference to the underlying noms db. If the Location is InMemDoltDB then a reference 73 // to a newly created in memory database will be used. If the location is LocalDirDoltDB, the directory must exist or 74 // this returns nil. 75 func LoadDoltDB(ctx context.Context, nbf *types.NomsBinFormat, urlStr string) (*DoltDB, error) { 76 return LoadDoltDBWithParams(ctx, nbf, urlStr, nil) 77 } 78 79 func LoadDoltDBWithParams(ctx context.Context, nbf *types.NomsBinFormat, urlStr string, params map[string]string) (*DoltDB, error) { 80 if urlStr == LocalDirDoltDB { 81 exists, isDir := filesys.LocalFS.Exists(dbfactory.DoltDataDir) 82 83 if !exists { 84 return nil, errors.New("missing dolt data directory") 85 } else if !isDir { 86 return nil, errors.New("file exists where the dolt data directory should be") 87 } 88 } 89 90 db, err := dbfactory.CreateDB(ctx, nbf, urlStr, params) 91 92 if err != nil { 93 return nil, err 94 } 95 96 return &DoltDB{db}, nil 97 } 98 99 func (ddb *DoltDB) CSMetricsSummary() string { 100 return datas.GetCSStatSummaryForDB(ddb.db) 101 } 102 103 // WriteEmptyRepo will create initialize the given db with a master branch which points to a commit which has valid 104 // metadata for the creation commit, and an empty RootValue. 105 func (ddb *DoltDB) WriteEmptyRepo(ctx context.Context, name, email string) error { 106 return ddb.WriteEmptyRepoWithCommitTime(ctx, name, email, CommitNowFunc()) 107 } 108 109 func (ddb *DoltDB) WriteEmptyRepoWithCommitTime(ctx context.Context, name, email string, t time.Time) error { 110 // precondition checks 111 name = strings.TrimSpace(name) 112 email = strings.TrimSpace(email) 113 114 if name == "" || email == "" { 115 panic("Passed bad name or email. Both should be valid") 116 } 117 118 ds, err := ddb.db.GetDataset(ctx, creationBranch) 119 120 if err != nil { 121 return err 122 } 123 124 if ds.HasHead() { 125 return errors.New("database already exists") 126 } 127 128 rv, err := EmptyRootValue(ctx, ddb.db) 129 130 if err != nil { 131 return err 132 } 133 134 _, err = ddb.WriteRootValue(ctx, rv) 135 136 if err != nil { 137 return err 138 } 139 140 cm, _ := NewCommitMetaWithUserTS(name, email, "Initialize data repository", t) 141 142 parents, err := types.NewList(ctx, ddb.db) 143 if err != nil { 144 return err 145 } 146 147 meta, err := cm.toNomsStruct(ddb.db.Format()) 148 149 if err != nil { 150 return err 151 } 152 153 commitOpts := datas.CommitOptions{ParentsList: parents, Meta: meta, Policy: nil} 154 155 dref := ref.NewInternalRef(creationBranch) 156 ds, err = ddb.db.GetDataset(ctx, dref.String()) 157 158 if err != nil { 159 return err 160 } 161 162 firstCommit, err := ddb.db.Commit(ctx, ds, rv.valueSt, commitOpts) 163 164 if err != nil { 165 return err 166 } 167 168 dref = ref.NewBranchRef(MasterBranch) 169 ds, err = ddb.db.GetDataset(ctx, dref.String()) 170 171 if err != nil { 172 return err 173 } 174 175 headRef, ok, err := firstCommit.MaybeHeadRef() 176 177 if err != nil { 178 return err 179 } 180 181 if !ok { 182 return errors.New("commit without head") 183 } 184 185 _, err = ddb.db.SetHead(ctx, ds, headRef) 186 187 return err 188 } 189 190 func getCommitStForRefStr(ctx context.Context, db datas.Database, ref string) (types.Struct, error) { 191 if !datas.DatasetFullRe.MatchString(ref) { 192 return types.EmptyStruct(db.Format()), fmt.Errorf("invalid ref format: %s", ref) 193 } 194 195 ds, err := db.GetDataset(ctx, ref) 196 197 if err != nil { 198 return types.EmptyStruct(db.Format()), err 199 } 200 201 dsHead, hasHead := ds.MaybeHead() 202 203 if !hasHead { 204 return types.EmptyStruct(db.Format()), ErrBranchNotFound 205 } 206 207 if dsHead.Name() == datas.CommitName { 208 return dsHead, nil 209 } 210 211 if dsHead.Name() == datas.TagName { 212 commitRef, ok, err := dsHead.MaybeGet(datas.TagCommitRefField) 213 if err != nil { 214 return types.EmptyStruct(db.Format()), err 215 } 216 if !ok { 217 err = fmt.Errorf("tag struct does not have field %s", datas.TagCommitRefField) 218 return types.EmptyStruct(db.Format()), err 219 } 220 221 commitSt, err := commitRef.(types.Ref).TargetValue(ctx, db) 222 if err != nil { 223 return types.EmptyStruct(db.Format()), err 224 } 225 226 return commitSt.(types.Struct), nil 227 } 228 229 err = fmt.Errorf("dataset head is neither commit nor tag") 230 return types.EmptyStruct(db.Format()), err 231 } 232 233 func getCommitStForHash(ctx context.Context, db datas.Database, c string) (types.Struct, error) { 234 prefixed := c 235 236 if !strings.HasPrefix(c, "#") { 237 prefixed = "#" + c 238 } 239 240 ap, err := spec.NewAbsolutePath(prefixed) 241 242 if err != nil { 243 return types.EmptyStruct(db.Format()), err 244 } 245 246 val := ap.Resolve(ctx, db) 247 248 if val == nil { 249 return types.EmptyStruct(db.Format()), ErrHashNotFound 250 } 251 252 valSt, ok := val.(types.Struct) 253 254 if !ok || valSt.Name() != CommitStructName { 255 return types.EmptyStruct(db.Format()), ErrFoundHashNotACommit 256 } 257 258 return valSt, nil 259 } 260 261 func getAncestor(ctx context.Context, vrw types.ValueReadWriter, commitSt types.Struct, aSpec *AncestorSpec) (types.Struct, error) { 262 if aSpec == nil || len(aSpec.Instructions) == 0 { 263 return commitSt, nil 264 } 265 266 instructions := aSpec.Instructions 267 for _, inst := range instructions { 268 cm := NewCommit(vrw, commitSt) 269 270 numPars, err := cm.NumParents() 271 272 if err != nil { 273 return types.EmptyStruct(vrw.Format()), err 274 } 275 276 if inst < numPars { 277 commitStPtr, err := cm.getParent(ctx, inst) 278 279 if err != nil { 280 return types.EmptyStruct(vrw.Format()), err 281 } 282 283 if commitStPtr == nil { 284 return types.EmptyStruct(vrw.Format()), ErrInvalidAncestorSpec 285 } 286 commitSt = *commitStPtr 287 } else { 288 return types.EmptyStruct(vrw.Format()), ErrInvalidAncestorSpec 289 } 290 } 291 292 return commitSt, nil 293 } 294 295 // Resolve takes a CommitSpec and returns a Commit, or an error if the commit cannot be found. 296 // If the CommitSpec is HEAD, Resolve also needs the DoltRef of the current working branch. 297 func (ddb *DoltDB) Resolve(ctx context.Context, cs *CommitSpec, cwb ref.DoltRef) (*Commit, error) { 298 if cs == nil { 299 panic("nil commit spec") 300 } 301 302 var commitSt types.Struct 303 var err error 304 switch cs.csType { 305 case hashCommitSpec: 306 commitSt, err = getCommitStForHash(ctx, ddb.db, cs.baseSpec) 307 case refCommitSpec: 308 // For a ref in a CommitSpec, we have the following behavior. 309 // If it starts with `refs/`, we look for an exact match before 310 // we try any suffix matches. After that, we try a match on the 311 // user supplied input, with the following four prefixes, in 312 // order: `refs/`, `refs/heads/`, `refs/tags/`, `refs/remotes/`. 313 candidates := []string{ 314 "refs/" + cs.baseSpec, 315 "refs/heads/" + cs.baseSpec, 316 "refs/tags/" + cs.baseSpec, 317 "refs/remotes/" + cs.baseSpec, 318 } 319 if strings.HasPrefix(cs.baseSpec, "refs/") { 320 candidates = []string{ 321 cs.baseSpec, 322 "refs/" + cs.baseSpec, 323 "refs/heads/" + cs.baseSpec, 324 "refs/tags/" + cs.baseSpec, 325 "refs/remotes/" + cs.baseSpec, 326 } 327 } 328 for _, candidate := range candidates { 329 commitSt, err = getCommitStForRefStr(ctx, ddb.db, candidate) 330 if err == nil { 331 break 332 } 333 if err != ErrBranchNotFound { 334 return nil, err 335 } 336 } 337 case headCommitSpec: 338 commitSt, err = getCommitStForRefStr(ctx, ddb.db, cwb.String()) 339 default: 340 panic("unrecognized commit spec csType: " + cs.csType) 341 } 342 343 if err != nil { 344 return nil, err 345 } 346 347 commitSt, err = getAncestor(ctx, ddb.db, commitSt, cs.aSpec) 348 349 if err != nil { 350 return nil, err 351 } 352 353 return NewCommit(ddb.db, commitSt), nil 354 } 355 356 // ResolveCommitRef takes a DoltRef and returns a Commit, or an error if the commit cannot be found. The ref given must 357 // point to a Commit. 358 func (ddb *DoltDB) ResolveCommitRef(ctx context.Context, ref ref.DoltRef) (*Commit, error) { 359 commitSt, err := getCommitStForRefStr(ctx, ddb.db, ref.String()) 360 if err != nil { 361 return nil, err 362 } 363 return NewCommit(ddb.db, commitSt), nil 364 } 365 366 // ResolveTag takes a TagRef and returns the corresponding Tag object. 367 func (ddb *DoltDB) ResolveTag(ctx context.Context, tagRef ref.TagRef) (*Tag, error) { 368 ds, err := ddb.db.GetDataset(ctx, tagRef.String()) 369 370 if err != nil { 371 return nil, ErrTagNotFound 372 } 373 374 tagSt, hasHead := ds.MaybeHead() 375 376 if !hasHead { 377 return nil, ErrTagNotFound 378 } 379 380 if tagSt.Name() != datas.TagName { 381 return nil, fmt.Errorf("tagRef head is not a tag") 382 } 383 384 return NewTag(ctx, tagRef.GetPath(), ddb.db, tagSt) 385 } 386 387 // ResolveWorkingSet takes a WorkingSetRef and returns the corresponding WorkingSet object. 388 func (ddb *DoltDB) ResolveWorkingSet(ctx context.Context, workingSetRef ref.WorkingSetRef) (*WorkingSet, error) { 389 ds, err := ddb.db.GetDataset(ctx, workingSetRef.String()) 390 391 if err != nil { 392 return nil, ErrWorkingSetNotFound 393 } 394 395 wsSt, hasHead := ds.MaybeHead() 396 397 if !hasHead { 398 return nil, ErrWorkingSetNotFound 399 } 400 401 if wsSt.Name() != datas.WorkingSetName { 402 return nil, fmt.Errorf("workingSetRef head is not a workingSetRef") 403 } 404 405 return NewWorkingSet(ctx, workingSetRef.GetPath(), ddb.db, wsSt) 406 } 407 408 // TODO: convenience method to resolve the head commit of a branch. 409 410 // WriteRootValue will write a doltdb.RootValue instance to the database. This value will not be associated with a commit 411 // and can be committed by hash at a later time. Returns the hash of the value written. 412 func (ddb *DoltDB) WriteRootValue(ctx context.Context, rv *RootValue) (hash.Hash, error) { 413 var err error 414 rv.valueSt, err = rv.valueSt.Set(featureVersKey, types.Int(DoltFeatureVersion)) 415 if err != nil { 416 return hash.Hash{}, err 417 } 418 419 valRef, err := ddb.db.WriteValue(ctx, rv.valueSt) 420 421 if err != nil { 422 return hash.Hash{}, err 423 } 424 425 err = ddb.db.Flush(ctx) 426 427 if err != nil { 428 return hash.Hash{}, err 429 } 430 431 valHash := valRef.TargetHash() 432 433 return valHash, err 434 } 435 436 // ReadRootValue reads the RootValue associated with the hash given and returns it. Returns an error if the value cannot 437 // be read, or if the hash given doesn't represent a dolt RootValue. 438 func (ddb *DoltDB) ReadRootValue(ctx context.Context, h hash.Hash) (*RootValue, error) { 439 val, err := ddb.db.ReadValue(ctx, h) 440 441 if err != nil { 442 return nil, err 443 } 444 if val == nil { 445 return nil, ErrNoRootValAtHash 446 } 447 448 rootSt, ok := val.(types.Struct) 449 if !ok || rootSt.Name() != ddbRootStructName { 450 return nil, ErrNoRootValAtHash 451 } 452 453 return newRootValue(ddb.db, rootSt) 454 } 455 456 // Commit will update a branch's head value to be that of a previously committed root value hash 457 func (ddb *DoltDB) Commit(ctx context.Context, valHash hash.Hash, dref ref.DoltRef, cm *CommitMeta) (*Commit, error) { 458 if dref.GetType() != ref.BranchRefType { 459 panic("can't commit to ref that isn't branch atm. will probably remove this.") 460 } 461 462 return ddb.CommitWithParentSpecs(ctx, valHash, dref, nil, cm) 463 } 464 465 // FastForward fast-forwards the branch given to the commit given. 466 func (ddb *DoltDB) FastForward(ctx context.Context, branch ref.DoltRef, commit *Commit) error { 467 ds, err := ddb.db.GetDataset(ctx, branch.String()) 468 469 if err != nil { 470 return err 471 } 472 473 rf, err := types.NewRef(commit.commitSt, ddb.db.Format()) 474 475 if err != nil { 476 return err 477 } 478 479 _, err = ddb.db.FastForward(ctx, ds, rf) 480 481 return err 482 } 483 484 // CanFastForward returns whether the given branch can be fast-forwarded to the commit given. 485 func (ddb *DoltDB) CanFastForward(ctx context.Context, branch ref.DoltRef, new *Commit) (bool, error) { 486 current, err := ddb.ResolveCommitRef(ctx, branch) 487 488 if err != nil { 489 if err == ErrBranchNotFound { 490 return true, nil 491 } 492 493 return false, err 494 } 495 496 return current.CanFastForwardTo(ctx, new) 497 } 498 499 // SetHeadToCommit sets the given ref to point at the given commit. It is used in the course of 'force' updates. 500 func (ddb *DoltDB) SetHeadToCommit(ctx context.Context, ref ref.DoltRef, cm *Commit) error { 501 502 stRef, err := types.NewRef(cm.commitSt, ddb.db.Format()) 503 504 if err != nil { 505 return err 506 } 507 508 return ddb.SetHead(ctx, ref, stRef) 509 } 510 511 func (ddb *DoltDB) SetHead(ctx context.Context, ref ref.DoltRef, stRef types.Ref) error { 512 ds, err := ddb.db.GetDataset(ctx, ref.String()) 513 514 if err != nil { 515 return err 516 } 517 518 _, err = ddb.db.SetHead(ctx, ds, stRef) 519 return err 520 } 521 522 // CommitWithParentSpecs commits the value hash given to the branch given, using the list of parent hashes given. Returns an 523 // error if the value or any parents can't be resolved, or if anything goes wrong accessing the underlying storage. 524 func (ddb *DoltDB) CommitWithParentSpecs(ctx context.Context, valHash hash.Hash, dref ref.DoltRef, parentCmSpecs []*CommitSpec, cm *CommitMeta) (*Commit, error) { 525 var parentCommits []*Commit 526 for _, parentCmSpec := range parentCmSpecs { 527 cm, err := ddb.Resolve(ctx, parentCmSpec, nil) 528 529 if err != nil { 530 return nil, err 531 } 532 parentCommits = append(parentCommits, cm) 533 } 534 return ddb.CommitWithParentCommits(ctx, valHash, dref, parentCommits, cm) 535 } 536 537 func (ddb *DoltDB) CommitWithParentCommits(ctx context.Context, valHash hash.Hash, dref ref.DoltRef, parentCommits []*Commit, cm *CommitMeta) (*Commit, error) { 538 var commitSt types.Struct 539 val, err := ddb.db.ReadValue(ctx, valHash) 540 541 if err != nil { 542 return nil, err 543 } 544 545 if st, ok := val.(types.Struct); !ok || st.Name() != ddbRootStructName { 546 return nil, errors.New("can't commit a value that is not a valid root value") 547 } 548 549 ds, err := ddb.db.GetDataset(ctx, dref.String()) 550 551 if err != nil { 552 return nil, err 553 } 554 555 l, err := types.NewList(ctx, ddb.db) 556 557 if err != nil { 558 return nil, err 559 } 560 561 parentEditor := l.Edit() 562 563 headRef, hasHead, err := ds.MaybeHeadRef() 564 565 if err != nil { 566 return nil, err 567 } 568 569 if hasHead { 570 parentEditor = parentEditor.Append(headRef) 571 } 572 573 for _, cm := range parentCommits { 574 rf, err := types.NewRef(cm.commitSt, ddb.db.Format()) 575 576 if err != nil { 577 return nil, err 578 } 579 580 parentEditor = parentEditor.Append(rf) 581 } 582 583 parents, err := parentEditor.List(ctx) 584 585 if err != nil { 586 return nil, err 587 } 588 589 st, err := cm.toNomsStruct(ddb.db.Format()) 590 591 if err != nil { 592 return nil, err 593 } 594 595 commitOpts := datas.CommitOptions{ParentsList: parents, Meta: st, Policy: nil} 596 ds, err = ddb.db.GetDataset(ctx, dref.String()) 597 598 if err != nil { 599 return nil, err 600 } 601 602 ds, err = ddb.db.Commit(ctx, ds, val, commitOpts) 603 604 if err != nil { 605 return nil, err 606 } 607 608 var ok bool 609 commitSt, ok = ds.MaybeHead() 610 if !ok { 611 return nil, errors.New("commit has no head but commit succeeded (How?!?!?)") 612 } 613 614 return NewCommit(ddb.db, commitSt), nil 615 } 616 617 // dangling commits are unreferenced by any branch or ref. They are created in the course of programmatic updates 618 // such as rebase. You must create a ref to a dangling commit for it to be reachable 619 func (ddb *DoltDB) CommitDanglingWithParentCommits(ctx context.Context, valHash hash.Hash, parentCommits []*Commit, cm *CommitMeta) (*Commit, error) { 620 var commitSt types.Struct 621 val, err := ddb.db.ReadValue(ctx, valHash) 622 if err != nil { 623 return nil, err 624 } 625 if st, ok := val.(types.Struct); !ok || st.Name() != ddbRootStructName { 626 return nil, errors.New("can't commit a value that is not a valid root value") 627 } 628 629 l, err := types.NewList(ctx, ddb.db) 630 if err != nil { 631 return nil, err 632 } 633 634 parentEditor := l.Edit() 635 636 for _, cm := range parentCommits { 637 rf, err := types.NewRef(cm.commitSt, ddb.db.Format()) 638 if err != nil { 639 return nil, err 640 } 641 642 parentEditor = parentEditor.Append(rf) 643 } 644 645 parents, err := parentEditor.List(ctx) 646 if err != nil { 647 return nil, err 648 } 649 650 st, err := cm.toNomsStruct(ddb.db.Format()) 651 if err != nil { 652 return nil, err 653 } 654 655 commitOpts := datas.CommitOptions{ParentsList: parents, Meta: st, Policy: nil} 656 commitSt, err = ddb.db.CommitDangling(ctx, val, commitOpts) 657 if err != nil { 658 return nil, err 659 } 660 661 return NewCommit(ddb.db, commitSt), nil 662 } 663 664 // ValueReadWriter returns the underlying noms database as a types.ValueReadWriter. 665 func (ddb *DoltDB) ValueReadWriter() types.ValueReadWriter { 666 return ddb.db 667 } 668 669 func (ddb *DoltDB) Format() *types.NomsBinFormat { 670 return ddb.db.Format() 671 } 672 673 func WriteValAndGetRef(ctx context.Context, vrw types.ValueReadWriter, val types.Value) (types.Ref, error) { 674 valRef, err := types.NewRef(val, vrw.Format()) 675 676 if err != nil { 677 return types.Ref{}, err 678 } 679 680 targetVal, err := valRef.TargetValue(ctx, vrw) 681 682 if err != nil { 683 return types.Ref{}, err 684 } 685 686 if targetVal == nil { 687 _, err = vrw.WriteValue(ctx, val) 688 689 if err != nil { 690 return types.Ref{}, err 691 } 692 } 693 694 return valRef, err 695 } 696 697 // ResolveParent returns the n-th ancestor of a given commit (direct parent is index 0). error return value will be 698 // non-nil in the case that the commit cannot be resolved, there aren't as many ancestors as requested, or the 699 // underlying storage cannot be accessed. 700 func (ddb *DoltDB) ResolveParent(ctx context.Context, commit *Commit, parentIdx int) (*Commit, error) { 701 parentCommitSt, err := commit.getParent(ctx, parentIdx) 702 if err != nil { 703 return nil, err 704 } 705 return NewCommit(ddb.ValueReadWriter(), *parentCommitSt), nil 706 } 707 708 func (ddb *DoltDB) ResolveAllParents(ctx context.Context, commit *Commit) ([]*Commit, error) { 709 num, err := commit.NumParents() 710 if err != nil { 711 return nil, err 712 } 713 resolved := make([]*Commit, num) 714 for i := 0; i < num; i++ { 715 parent, err := ddb.ResolveParent(ctx, commit, i) 716 if err != nil { 717 return nil, err 718 } 719 resolved[i] = parent 720 } 721 return resolved, nil 722 } 723 724 // HasRef returns whether the branch given exists in this database. 725 func (ddb *DoltDB) HasRef(ctx context.Context, doltRef ref.DoltRef) (bool, error) { 726 dss, err := ddb.db.Datasets(ctx) 727 728 if err != nil { 729 return false, err 730 } 731 732 return dss.Has(ctx, types.String(doltRef.String())) 733 } 734 735 var branchRefFilter = map[ref.RefType]struct{}{ref.BranchRefType: {}} 736 737 // GetBranches returns a list of all branches in the database. 738 func (ddb *DoltDB) GetBranches(ctx context.Context) ([]ref.DoltRef, error) { 739 return ddb.GetRefsOfType(ctx, branchRefFilter) 740 } 741 742 var tagsRefFilter = map[ref.RefType]struct{}{ref.TagRefType: {}} 743 744 // GetTags returns a list of all tags in the database. 745 func (ddb *DoltDB) GetTags(ctx context.Context) ([]ref.DoltRef, error) { 746 return ddb.GetRefsOfType(ctx, tagsRefFilter) 747 } 748 749 var workspacesRefFilter = map[ref.RefType]struct{}{ref.WorkspaceRefType: {}} 750 751 // GetWorkspaces returns a list of all workspaces in the database. 752 func (ddb *DoltDB) GetWorkspaces(ctx context.Context) ([]ref.DoltRef, error) { 753 return ddb.GetRefsOfType(ctx, workspacesRefFilter) 754 } 755 756 // GetHeadRefs returns a list of all refs that point to a Commit 757 func (ddb *DoltDB) GetHeadRefs(ctx context.Context) ([]ref.DoltRef, error) { 758 return ddb.GetRefsOfType(ctx, ref.HeadRefTypes) 759 } 760 761 func (ddb *DoltDB) GetRefsOfType(ctx context.Context, refTypeFilter map[ref.RefType]struct{}) ([]ref.DoltRef, error) { 762 var refs []ref.DoltRef 763 dss, err := ddb.db.Datasets(ctx) 764 765 if err != nil { 766 return nil, err 767 } 768 769 err = dss.IterAll(ctx, func(key, _ types.Value) error { 770 keyStr := string(key.(types.String)) 771 772 var dref ref.DoltRef 773 if ref.IsRef(keyStr) { 774 dref, err = ref.Parse(keyStr) 775 if err != nil { 776 return err 777 } 778 779 if _, ok := refTypeFilter[dref.GetType()]; ok { 780 refs = append(refs, dref) 781 } 782 } 783 784 return nil 785 }) 786 787 if err != nil { 788 return nil, err 789 } 790 791 return refs, nil 792 } 793 794 // NewBranchAtCommit creates a new branch with HEAD at the commit given. Branch names must pass IsValidUserBranchName. 795 func (ddb *DoltDB) NewBranchAtCommit(ctx context.Context, dref ref.DoltRef, commit *Commit) error { 796 if !IsValidBranchRef(dref) { 797 panic(fmt.Sprintf("invalid branch name %s, use IsValidUserBranchName check", dref.String())) 798 } 799 800 ds, err := ddb.db.GetDataset(ctx, dref.String()) 801 802 if err != nil { 803 return err 804 } 805 806 rf, err := types.NewRef(commit.commitSt, ddb.db.Format()) 807 808 if err != nil { 809 return err 810 } 811 812 _, err = ddb.db.SetHead(ctx, ds, rf) 813 814 return err 815 } 816 817 // DeleteBranch deletes the branch given, returning an error if it doesn't exist. 818 func (ddb *DoltDB) DeleteBranch(ctx context.Context, branch ref.DoltRef) error { 819 return ddb.deleteRef(ctx, branch) 820 } 821 822 func (ddb *DoltDB) deleteRef(ctx context.Context, dref ref.DoltRef) error { 823 ds, err := ddb.db.GetDataset(ctx, dref.String()) 824 825 if err != nil { 826 return err 827 } 828 829 if !ds.HasHead() { 830 return ErrBranchNotFound 831 } 832 833 _, err = ddb.db.Delete(ctx, ds) 834 return err 835 } 836 837 // NewTagAtCommit create a new tag at the commit given. 838 func (ddb *DoltDB) NewTagAtCommit(ctx context.Context, tagRef ref.DoltRef, c *Commit, meta *TagMeta) error { 839 if !IsValidTagRef(tagRef) { 840 panic(fmt.Sprintf("invalid tag name %s, use IsValidUserTagName check", tagRef.String())) 841 } 842 843 ds, err := ddb.db.GetDataset(ctx, tagRef.String()) 844 845 if err != nil { 846 return err 847 } 848 849 _, hasHead, err := ds.MaybeHeadRef() 850 851 if err != nil { 852 return err 853 } 854 if hasHead { 855 return fmt.Errorf("dataset already exists for tag %s", tagRef.String()) 856 } 857 858 r, err := types.NewRef(c.commitSt, ddb.Format()) 859 860 if err != nil { 861 return err 862 } 863 864 st, err := meta.toNomsStruct(ddb.db.Format()) 865 866 if err != nil { 867 return err 868 } 869 870 tag := datas.TagOptions{Meta: st} 871 872 ds, err = ddb.db.Tag(ctx, ds, r, tag) 873 874 return err 875 } 876 877 // UpdateWorkingSet updates the working set with the ref given to the root value given 878 // |prevHash| is the hash of the expected WorkingSet struct stored in the ref, not the hash of the RootValue there. 879 func (ddb *DoltDB) UpdateWorkingSet(ctx context.Context, workingSetRef ref.WorkingSetRef, rootVal *RootValue, prevHash hash.Hash) error { 880 ds, err := ddb.db.GetDataset(ctx, workingSetRef.String()) 881 if err != nil { 882 return err 883 } 884 885 // st, err := NewWorkingSetMeta().toNomsStruct(ddb.Format()) 886 // if err != nil { 887 // return err 888 // } 889 890 rootRef, err := ddb.db.WriteValue(ctx, rootVal.valueSt) 891 if err != nil { 892 return err 893 } 894 895 // workspaceStruct, err := datas.NewWorkspace(ctx, rootRef, st) 896 // if err != nil { 897 // return err 898 // } 899 // 900 // wsRef, err := types.NewRef(workspaceStruct, ddb.Format()) 901 // if err != nil { 902 // return err 903 // } 904 905 // h, err = wsRef.Hash(wsRef.Format()) 906 // fmt.Sprintf("%v", h) 907 908 _, err = ddb.db.UpdateWorkingSet(ctx, ds, rootRef, datas.WorkingSetMeta{}, prevHash) 909 return err 910 } 911 912 func (ddb *DoltDB) DeleteTag(ctx context.Context, tag ref.DoltRef) error { 913 err := ddb.deleteRef(ctx, tag) 914 915 if err == ErrBranchNotFound { 916 return ErrTagNotFound 917 } 918 919 return err 920 } 921 922 // NewWorkspaceAtCommit create a new workspace at the commit given. 923 func (ddb *DoltDB) NewWorkspaceAtCommit(ctx context.Context, workRef ref.DoltRef, c *Commit) error { 924 ds, err := ddb.db.GetDataset(ctx, workRef.String()) 925 if err != nil { 926 return err 927 } 928 929 r, err := types.NewRef(c.commitSt, ddb.Format()) 930 if err != nil { 931 return err 932 } 933 934 ds, err = ddb.db.SetHead(ctx, ds, r) 935 936 return err 937 } 938 939 func (ddb *DoltDB) DeleteWorkspace(ctx context.Context, workRef ref.DoltRef) error { 940 err := ddb.deleteRef(ctx, workRef) 941 942 if err == ErrBranchNotFound { 943 return ErrWorkspaceNotFound 944 } 945 946 return err 947 } 948 949 // GC performs garbage collection on this ddb. Values passed in |uncommitedVals| will be temporarily saved during gc. 950 func (ddb *DoltDB) GC(ctx context.Context, uncommitedVals ...hash.Hash) error { 951 collector, ok := ddb.db.(datas.GarbageCollector) 952 if !ok { 953 return fmt.Errorf("this database does not support garbage collection") 954 } 955 956 err := ddb.pruneUnreferencedDatasets(ctx) 957 if err != nil { 958 return err 959 } 960 961 rand.Seed(time.Now().UnixNano()) 962 tmpDatasets := make([]datas.Dataset, len(uncommitedVals)) 963 for i, h := range uncommitedVals { 964 v, err := ddb.db.ReadValue(ctx, h) 965 if err != nil { 966 return err 967 } 968 if v == nil { 969 return fmt.Errorf("empty value for value hash %s", h.String()) 970 } 971 972 ds, err := ddb.db.GetDataset(ctx, fmt.Sprintf("tmp/%d", rand.Int63())) 973 if err != nil { 974 return err 975 } 976 977 r, err := WriteValAndGetRef(ctx, ddb.db, v) 978 if err != nil { 979 return err 980 } 981 982 ds, err = ddb.db.CommitValue(ctx, ds, r) 983 if err != nil { 984 return err 985 } 986 if !ds.HasHead() { 987 return fmt.Errorf("could not save value %s", h.String()) 988 } 989 990 tmpDatasets[i] = ds 991 } 992 993 err = collector.GC(ctx) 994 if err != nil { 995 return err 996 } 997 998 for _, ds := range tmpDatasets { 999 ds, err = ddb.db.Delete(ctx, ds) 1000 if err != nil { 1001 return err 1002 } 1003 1004 if ds.HasHead() { 1005 return fmt.Errorf("unsuccessful delete for dataset %s", ds.ID()) 1006 } 1007 } 1008 1009 return nil 1010 } 1011 1012 func (ddb *DoltDB) pruneUnreferencedDatasets(ctx context.Context) error { 1013 dd, err := ddb.db.Datasets(ctx) 1014 if err != nil { 1015 return err 1016 } 1017 1018 var deletes []string 1019 _ = dd.Iter(ctx, func(ds, _ types.Value) (stop bool, err error) { 1020 dsID := string(ds.(types.String)) 1021 if !ref.IsRef(dsID) && !ref.IsWorkingSet(dsID) { 1022 deletes = append(deletes, dsID) 1023 } 1024 return false, nil 1025 }) 1026 1027 // e.g. flushes 1028 for _, dsID := range deletes { 1029 ds, err := ddb.db.GetDataset(ctx, dsID) 1030 if err != nil { 1031 return err 1032 } 1033 1034 ds, err = ddb.db.Delete(ctx, ds) 1035 if err != nil { 1036 return err 1037 } 1038 1039 if ds.HasHead() { 1040 return fmt.Errorf("unsuccessful delete for dataset %s", ds.ID()) 1041 } 1042 } 1043 1044 return nil 1045 } 1046 1047 // PushChunks initiates a push into a database from the source database given, at the Value ref given. Pull progress is 1048 // communicated over the provided channel. 1049 func (ddb *DoltDB) PushChunks(ctx context.Context, tempDir string, srcDB *DoltDB, rf types.Ref, progChan chan datas.PullProgress, pullerEventCh chan datas.PullerEvent) error { 1050 if datas.CanUsePuller(srcDB.db) && datas.CanUsePuller(ddb.db) { 1051 puller, err := datas.NewPuller(ctx, tempDir, defaultChunksPerTF, srcDB.db, ddb.db, rf.TargetHash(), pullerEventCh) 1052 1053 if err == datas.ErrDBUpToDate { 1054 return nil 1055 } else if err != nil { 1056 return err 1057 } 1058 1059 return puller.Pull(ctx) 1060 } else { 1061 return datas.Pull(ctx, srcDB.db, ddb.db, rf, progChan) 1062 } 1063 } 1064 1065 func (ddb *DoltDB) PushChunksForRefHash(ctx context.Context, tempDir string, srcDB *DoltDB, h hash.Hash, pullerEventCh chan datas.PullerEvent) error { 1066 if datas.CanUsePuller(srcDB.db) && datas.CanUsePuller(ddb.db) { 1067 puller, err := datas.NewPuller(ctx, tempDir, defaultChunksPerTF, srcDB.db, ddb.db, h, pullerEventCh) 1068 1069 if err == datas.ErrDBUpToDate { 1070 return nil 1071 } else if err != nil { 1072 return err 1073 } 1074 1075 return puller.Pull(ctx) 1076 } else { 1077 return errors.New("this type of chunk store does not support this operation") 1078 } 1079 } 1080 1081 // PullChunks initiates a pull into a database from the source database given, at the commit given. Progress is 1082 // communicated over the provided channel. 1083 func (ddb *DoltDB) PullChunks(ctx context.Context, tempDir string, srcDB *DoltDB, stRef types.Ref, progChan chan datas.PullProgress, pullerEventCh chan datas.PullerEvent) error { 1084 if datas.CanUsePuller(srcDB.db) && datas.CanUsePuller(ddb.db) { 1085 puller, err := datas.NewPuller(ctx, tempDir, 256*1024, srcDB.db, ddb.db, stRef.TargetHash(), pullerEventCh) 1086 1087 if err == datas.ErrDBUpToDate { 1088 return nil 1089 } else if err != nil { 1090 return err 1091 } 1092 1093 return puller.Pull(ctx) 1094 } else { 1095 return datas.PullWithoutBatching(ctx, srcDB.db, ddb.db, stRef, progChan) 1096 } 1097 } 1098 1099 func (ddb *DoltDB) Clone(ctx context.Context, destDB *DoltDB, eventCh chan<- datas.TableFileEvent) error { 1100 return datas.Clone(ctx, ddb.db, destDB.db, eventCh) 1101 } 1102 1103 func (ddb *DoltDB) GetStorageVersion(ctx context.Context) (string, error) { 1104 return datas.GetManifestStorageVersion(ctx, ddb.db) 1105 }