kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/graphstore/graphstore.go (about)

     1  /*
     2   * Copyright 2015 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 graphstore defines the Service and Sharded interfaces, and provides
    18  // some useful utility functions.
    19  package graphstore // import "kythe.io/kythe/go/services/graphstore"
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"strings"
    25  
    26  	"kythe.io/kythe/go/util/compare"
    27  
    28  	spb "kythe.io/kythe/proto/storage_go_proto"
    29  )
    30  
    31  // An EntryFunc is a callback from the implementation of a Service to deliver
    32  // entry messages. If the callback returns an error, the operation stops.  If
    33  // the error is io.EOF, the operation returns nil; otherwise it returns the
    34  // error value from the callback.
    35  type EntryFunc func(*spb.Entry) error
    36  
    37  // Service refers to an open Kythe graph store.
    38  type Service interface {
    39  	// Read calls f with each entry having the ReadRequest's given source
    40  	// VName, subject to the following rules:
    41  	//
    42  	//  |----------+---------------------------------------------------------|
    43  	//  | EdgeKind | Result                                                  |
    44  	//  |----------+---------------------------------------------------------|
    45  	//  | ΓΈ        | All entries with kind and target empty (node entries).  |
    46  	//  | "*"      | All entries (node and edge, regardless of kind/target). |
    47  	//  | "kind"   | All edge entries with the given edge kind.              |
    48  	//  |----------+---------------------------------------------------------|
    49  	//
    50  	// Read returns when there are no more entries to send. The Read operation should be
    51  	// implemented with time complexity proportional to the size of the return set.
    52  	Read(ctx context.Context, req *spb.ReadRequest, f EntryFunc) error
    53  
    54  	// Scan calls f with each entries having the specified target VName, kind,
    55  	// and fact label prefix. If a field is empty, any entry value for that
    56  	// field matches and will be returned. Scan returns when there are no more
    57  	// entries to send. Scan is similar to Read, but with no time complexity
    58  	// restrictions.
    59  	Scan(ctx context.Context, req *spb.ScanRequest, f EntryFunc) error
    60  
    61  	// Write atomically inserts or updates a collection of entries into the store.
    62  	// Each update is a tuple of the form (kind, target, fact, value). For each such
    63  	// update, an entry (source, kind, target, fact, value) is written into the store,
    64  	// replacing any existing entry (source, kind, target, fact, value') that may
    65  	// exist. Note that this operation cannot delete any data from the store; entries are
    66  	// only ever inserted or updated. Apart from acting atomically, no other constraints
    67  	// are placed on the implementation.
    68  	Write(ctx context.Context, req *spb.WriteRequest) error
    69  
    70  	// Close and release any underlying resources used by the store.
    71  	// No operations may be used on the store after this has been called.
    72  	Close(ctx context.Context) error
    73  }
    74  
    75  // Sharded represents a store that can be arbitrarily sharded for parallel
    76  // processing.  Depending on the implementation, these methods may not return
    77  // consistent results when the store is being written to.  Shards are indexed
    78  // from 0.
    79  type Sharded interface {
    80  	Service
    81  
    82  	// Count returns the number of entries in the given shard.
    83  	Count(ctx context.Context, req *spb.CountRequest) (int64, error)
    84  
    85  	// Shard calls f with each entry in the given shard.
    86  	Shard(ctx context.Context, req *spb.ShardRequest, f EntryFunc) error
    87  }
    88  
    89  // EntryMatchesScan reports whether entry belongs in the result set for req.
    90  func EntryMatchesScan(req *spb.ScanRequest, entry *spb.Entry) bool {
    91  	return (req.GetTarget() == nil || compare.VNamesEqual(entry.Target, req.Target)) &&
    92  		(req.EdgeKind == "" || entry.EdgeKind == req.EdgeKind) &&
    93  		strings.HasPrefix(entry.FactName, req.FactPrefix)
    94  }
    95  
    96  // BatchWrites returns a channel of WriteRequests for the given entries.
    97  // Consecutive entries with the same Source will be collected in the same
    98  // WriteRequest, with each request containing up to maxSize updates.
    99  func BatchWrites(entries <-chan *spb.Entry, maxSize int) <-chan *spb.WriteRequest {
   100  	ch := make(chan *spb.WriteRequest)
   101  	go func() {
   102  		defer close(ch)
   103  		var req *spb.WriteRequest
   104  		for entry := range entries {
   105  			update := &spb.WriteRequest_Update{
   106  				EdgeKind:  entry.EdgeKind,
   107  				Target:    entry.Target,
   108  				FactName:  entry.FactName,
   109  				FactValue: entry.FactValue,
   110  			}
   111  
   112  			if req != nil && (!compare.VNamesEqual(req.Source, entry.Source) || len(req.Update) >= maxSize) {
   113  				ch <- req
   114  				req = nil
   115  			}
   116  
   117  			if req == nil {
   118  				req = &spb.WriteRequest{
   119  					Source: entry.Source,
   120  					Update: []*spb.WriteRequest_Update{update},
   121  				}
   122  			} else {
   123  				req.Update = append(req.Update, update)
   124  			}
   125  		}
   126  		if req != nil {
   127  			ch <- req
   128  		}
   129  	}()
   130  	return ch
   131  }
   132  
   133  // ValidEntry determines if the given Entry is correctly constructed.
   134  func ValidEntry(e *spb.Entry) error {
   135  	if e.Source == nil {
   136  		return errors.New("entry missing source")
   137  	} else if e.FactName == "" {
   138  		return errors.New("entry missing fact name")
   139  	} else if IsEdge(e) {
   140  		if e.Target == nil {
   141  			return errors.New("edge entry missing target")
   142  		}
   143  	} else if e.Target != nil {
   144  		return errors.New("node fact entry has extraneous target")
   145  	}
   146  	return nil
   147  }
   148  
   149  // IsNodeFact determines if the Entry is a node fact; implies !IsEdge(e).
   150  func IsNodeFact(e *spb.Entry) bool { return e.EdgeKind == "" }
   151  
   152  // IsEdge determines if the Entry describes an edge; implies !IsNodeFact(e).
   153  func IsEdge(e *spb.Entry) bool { return e.EdgeKind != "" }