github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/nbs/ghost_store.go (about) 1 // Copyright 2024 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 nbs 16 17 import ( 18 "bufio" 19 "context" 20 "errors" 21 "fmt" 22 "io/fs" 23 "os" 24 "path/filepath" 25 26 "github.com/dolthub/dolt/go/store/chunks" 27 "github.com/dolthub/dolt/go/store/hash" 28 ) 29 30 type GhostBlockStore struct { 31 skippedRefs *hash.HashSet 32 ghostObjectsFile string 33 } 34 35 // We use the Has, HasMany, Get, GetMany, and PersistGhostHashes methods from the ChunkStore interface. All other methods are not supported. 36 var _ chunks.ChunkStore = &GhostBlockStore{} 37 38 // NewGhostBlockStore returns a new GhostBlockStore instance. Currently the only parameter is the path to the directory 39 // where we will create a text file called ghostObjects.txt. This file will contain the hashes of the ghost objects. Creation 40 // and use of this file is constrained to this instance. If there is no ghostObjects.txt file, then the GhostBlockStore will 41 // be empty - never returning any values from the Has, HasMany, Get, or GetMany methods. 42 func NewGhostBlockStore(nomsPath string) (*GhostBlockStore, error) { 43 ghostPath := filepath.Join(nomsPath, "ghostObjects.txt") 44 f, err := os.Open(ghostPath) 45 if err != nil { 46 if errors.Is(err, fs.ErrNotExist) { 47 return &GhostBlockStore{ 48 skippedRefs: &hash.HashSet{}, 49 ghostObjectsFile: ghostPath, 50 }, nil 51 } 52 // Other error, permission denied, etc, we want to hear about. 53 return nil, err 54 } 55 scanner := bufio.NewScanner(f) 56 skiplist := &hash.HashSet{} 57 for scanner.Scan() { 58 h := scanner.Text() 59 if hash.IsValid(h) { 60 skiplist.Insert(hash.Parse(h)) 61 } else { 62 return nil, fmt.Errorf("invalid hash %s in ghostObjects.txt", h) 63 } 64 } 65 66 return &GhostBlockStore{ 67 skippedRefs: skiplist, 68 ghostObjectsFile: ghostPath, 69 }, nil 70 } 71 72 // Get returns a ghost chunk if the hash is in the ghostObjectsFile. Otherwise, it returns an empty chunk. Chunks returned 73 // by this code will always be ghost chunks, ie chunk.IsGhost() will always return true. 74 func (g GhostBlockStore) Get(ctx context.Context, h hash.Hash) (chunks.Chunk, error) { 75 if g.skippedRefs.Has(h) { 76 return *chunks.NewGhostChunk(h), nil 77 } 78 return chunks.EmptyChunk, nil 79 } 80 81 func (g GhostBlockStore) GetMany(ctx context.Context, hashes hash.HashSet, found func(context.Context, *chunks.Chunk)) error { 82 for h := range hashes { 83 if g.skippedRefs.Has(h) { 84 found(ctx, chunks.NewGhostChunk(h)) 85 } 86 } 87 return nil 88 } 89 90 func (g *GhostBlockStore) PersistGhostHashes(ctx context.Context, hashes hash.HashSet) error { 91 if hashes.Size() == 0 { 92 return fmt.Errorf("runtime error. PersistGhostHashes called with empty hash set") 93 } 94 95 f, err := os.OpenFile(g.ghostObjectsFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) 96 if err != nil { 97 return err 98 } 99 100 for h := range hashes { 101 if _, err := f.WriteString(h.String() + "\n"); err != nil { 102 return err 103 } 104 } 105 106 g.skippedRefs = &hash.HashSet{} 107 for h := range hashes { 108 g.skippedRefs.Insert(h) 109 } 110 111 return nil 112 } 113 114 func (g GhostBlockStore) Has(ctx context.Context, h hash.Hash) (bool, error) { 115 if g.skippedRefs.Has(h) { 116 return true, nil 117 } 118 return false, nil 119 } 120 121 func (g GhostBlockStore) HasMany(ctx context.Context, hashes hash.HashSet) (absent hash.HashSet, err error) { 122 return g.hasMany(hashes) 123 } 124 125 func (g GhostBlockStore) hasMany(hashes hash.HashSet) (absent hash.HashSet, err error) { 126 absent = hash.HashSet{} 127 for h := range hashes { 128 if !g.skippedRefs.Has(h) { 129 absent.Insert(h) 130 } 131 } 132 return absent, nil 133 } 134 135 func (g GhostBlockStore) Put(ctx context.Context, c chunks.Chunk, getAddrs chunks.GetAddrsCurry) error { 136 panic("GhostBlockStore does not support Put") 137 } 138 139 func (g GhostBlockStore) Version() string { 140 panic("GhostBlockStore does not support Version") 141 } 142 143 func (g GhostBlockStore) AccessMode() chunks.ExclusiveAccessMode { 144 panic("GhostBlockStore does not support AccessMode") 145 } 146 147 func (g GhostBlockStore) Rebase(ctx context.Context) error { 148 panic("GhostBlockStore does not support Rebase") 149 } 150 151 func (g GhostBlockStore) Root(ctx context.Context) (hash.Hash, error) { 152 panic("GhostBlockStore does not support Root") 153 } 154 155 func (g GhostBlockStore) Commit(ctx context.Context, current, last hash.Hash) (bool, error) { 156 panic("GhostBlockStore does not support Commit") 157 } 158 159 func (g GhostBlockStore) Stats() interface{} { 160 panic("GhostBlockStore does not support Stats") 161 } 162 163 func (g GhostBlockStore) StatsSummary() string { 164 panic("GhostBlockStore does not support StatsSummary") 165 } 166 167 func (g GhostBlockStore) Close() error { 168 panic("GhostBlockStore does not support Close") 169 }