kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/platform/kcd/memdb/memdb.go (about) 1 /* 2 * Copyright 2016 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package memdb implements kcd.ReadWriter with an in-memory representation, 18 // suitable for testing or ephemeral service-based collections. 19 package memdb // import "kythe.io/kythe/go/platform/kcd/memdb" 20 21 import ( 22 "context" 23 "errors" 24 "io" 25 "io/ioutil" 26 "os" 27 "time" 28 29 "kythe.io/kythe/go/platform/kcd" 30 ) 31 32 // DB implements kcd.Reader, and *DB implements kcd.ReadWriter and kcd.Deleter. 33 // Records are stored in exported fields, to assist in testing. The zero value 34 // is ready for use as an empty database. 35 type DB struct { 36 Rev []kcd.Revision 37 Unit map[string]Unit // :: unit digest → compilation record 38 File map[string]string // :: file digest → file content 39 Index map[string]Index // :: unit digest → index entries 40 } 41 42 // A Unit represents a stored compilation unit. 43 type Unit struct { 44 FormatKey string // may be empty 45 Data []byte // the raw data in wire format 46 } 47 48 // An Index is a mapping from index keys (e.g., "corpus", "source") to distinct 49 // values for those keys. 50 type Index map[string][]string 51 52 // String tags for index keys matching the fields of a kcd.FindFilter. 53 const ( 54 RevisionKey = "revision" 55 CorpusKey = "corpus" 56 UnitCorpusKey = "unit_corpus" 57 OutputKey = "output" 58 LanguageKey = "language" 59 TargetKey = "target" 60 SourceKey = "source" 61 ) 62 63 // SetIndex sets an index term for the given digest. 64 func (db *DB) SetIndex(digest, key, value string) { 65 if db.Index == nil { 66 db.Index = make(map[string]Index) 67 } 68 idx := db.Index[digest] 69 for _, v := range idx[key] { 70 if v == value { 71 return 72 } 73 } 74 if idx == nil { 75 db.Index[digest] = Index{key: []string{value}} 76 } else { 77 idx[key] = append(idx[key], value) 78 } 79 } 80 81 // Revisions implements a method of kcd.Reader. 82 func (db DB) Revisions(_ context.Context, want *kcd.RevisionsFilter, f func(kcd.Revision) error) error { 83 revisionMatches, err := want.Compile() 84 if err != nil { 85 return err 86 } 87 for _, rev := range db.Rev { 88 if revisionMatches(rev) { 89 if err := f(rev); err != nil { 90 return err 91 } 92 } 93 } 94 return nil 95 } 96 97 // Find implements a method of kcd.Reader. 98 func (db DB) Find(_ context.Context, filter *kcd.FindFilter, f func(string) error) error { 99 cf, err := filter.Compile() 100 if err != nil { 101 return err 102 } else if cf == nil { 103 return nil 104 } 105 106 for digest, index := range db.Index { 107 if cf.RevisionMatches(index[RevisionKey]...) && 108 cf.BuildCorpusMatches(index[CorpusKey]...) && 109 cf.UnitCorpusMatches(index[UnitCorpusKey]...) && 110 cf.LanguageMatches(index[LanguageKey]...) && 111 cf.TargetMatches(index[TargetKey]...) && 112 cf.OutputMatches(index[OutputKey]...) && 113 cf.SourcesMatch(index[SourceKey]...) { 114 115 if err := f(digest); err != nil { 116 return err 117 } 118 } 119 } 120 return nil 121 } 122 123 // Units implements a method of kcd.Reader. 124 func (db DB) Units(_ context.Context, unitDigests []string, f func(digest, key string, data []byte) error) error { 125 for _, ud := range unitDigests { 126 if unit, ok := db.Unit[ud]; ok { 127 if err := f(ud, unit.FormatKey, unit.Data); err != nil { 128 return err 129 } 130 } 131 } 132 return nil 133 } 134 135 // Files implements a method of kcd.Reader. 136 func (db DB) Files(_ context.Context, fileDigests []string, f func(string, []byte) error) error { 137 for _, fd := range fileDigests { 138 if s, ok := db.File[fd]; ok { 139 if err := f(fd, []byte(s)); err != nil { 140 return err 141 } 142 } 143 } 144 return nil 145 } 146 147 // FilesExist implements a method of kcd.Reader. 148 func (db DB) FilesExist(_ context.Context, fileDigests []string, f func(string) error) error { 149 for _, fd := range fileDigests { 150 if _, ok := db.File[fd]; ok { 151 if err := f(fd); err != nil { 152 return err 153 } 154 } 155 } 156 return nil 157 } 158 159 // WriteRevision implements a method of kcd.Writer. 160 func (db *DB) WriteRevision(_ context.Context, rev kcd.Revision, replace bool) error { 161 if rev.Revision == "" { 162 return errors.New("missing revision marker") 163 } else if rev.Corpus == "" { 164 return errors.New("missing corpus label") 165 } 166 if rev.Timestamp.IsZero() { 167 rev.Timestamp = time.Now() 168 } 169 rev.Timestamp = rev.Timestamp.In(time.UTC) 170 if replace { 171 for i, old := range db.Rev { 172 if old.Corpus == rev.Corpus && old.Revision == rev.Revision { 173 db.Rev[i].Timestamp = rev.Timestamp 174 return nil 175 } 176 } 177 } 178 db.Rev = append(db.Rev, rev) 179 return nil 180 } 181 182 // WriteUnit implements a method of kcd.Writer. On success, the returned 183 // digest is the kcd.HexDigest of whatever unit.MarshalBinary returned. 184 func (db *DB) WriteUnit(_ context.Context, rev kcd.Revision, formatKey string, unit kcd.Unit) (string, error) { 185 revision, corpus := rev.Revision, rev.Corpus 186 if revision == "" { 187 return "", errors.New("empty revision marker") 188 } 189 unit.Canonicalize() 190 bits, err := unit.MarshalBinary() 191 if err != nil { 192 return "", err 193 } 194 digest := unit.Digest() 195 if db.Unit == nil { 196 db.Unit = make(map[string]Unit) 197 } 198 db.Unit[digest] = Unit{FormatKey: formatKey, Data: bits} 199 200 // Update the index. 201 db.SetIndex(digest, RevisionKey, revision) 202 if corpus != "" { 203 db.SetIndex(digest, CorpusKey, corpus) 204 } 205 idx := unit.Index() 206 if idx.Corpus != "" { 207 db.SetIndex(digest, UnitCorpusKey, idx.Corpus) 208 } 209 if idx.Language != "" { 210 db.SetIndex(digest, LanguageKey, idx.Language) 211 } 212 if idx.Output != "" { 213 db.SetIndex(digest, OutputKey, idx.Output) 214 } 215 for _, src := range idx.Sources { 216 db.SetIndex(digest, SourceKey, src) 217 } 218 if idx.Target != "" { 219 db.SetIndex(digest, TargetKey, idx.Target) 220 } 221 return digest, nil 222 } 223 224 // WriteFile implements a method of kcd.Writer. 225 func (db *DB) WriteFile(_ context.Context, r io.Reader) (string, error) { 226 bits, err := ioutil.ReadAll(r) 227 if err != nil { 228 return "", err 229 } 230 digest := kcd.HexDigest(bits) 231 if db.File == nil { 232 db.File = make(map[string]string) 233 } 234 db.File[digest] = string(bits) 235 return digest, nil 236 } 237 238 // DeleteUnit implements a method of kcd.Deleter. 239 func (db *DB) DeleteUnit(_ context.Context, unitDigest string) error { 240 if _, ok := db.Unit[unitDigest]; !ok { 241 return os.ErrNotExist 242 } 243 delete(db.Unit, unitDigest) 244 delete(db.Index, unitDigest) 245 return nil 246 } 247 248 // DeleteFile implements a method of kcd.Deleter. 249 func (db *DB) DeleteFile(_ context.Context, fileDigest string) error { 250 if _, ok := db.File[fileDigest]; !ok { 251 return os.ErrNotExist 252 } 253 delete(db.File, fileDigest) 254 return nil 255 } 256 257 // revisionsEqual reports whether r1 and r2 are equal, diregarding timestamp. 258 func revisionsEqual(r1, r2 kcd.Revision) bool { 259 return r1.Revision == r2.Revision && r1.Corpus == r2.Corpus 260 } 261 262 // DeleteRevision implements a method of kcd.Deleter. 263 func (db *DB) DeleteRevision(_ context.Context, revision, corpus string) error { 264 rev := kcd.Revision{Revision: revision, Corpus: corpus} 265 if err := rev.IsValid(); err != nil { 266 return err 267 } 268 269 found := false 270 for i := len(db.Rev) - 1; i >= 0; i-- { 271 if revisionsEqual(db.Rev[i], rev) { 272 found = true 273 db.Rev = append(db.Rev[:i], db.Rev[i+1:]...) 274 } 275 } 276 if found { 277 return nil 278 } 279 return os.ErrNotExist 280 }