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  }