kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/serving/xrefs/columnar.go (about)

     1  /*
     2   * Copyright 2018 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 xrefs
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"io"
    24  	"regexp"
    25  
    26  	"kythe.io/kythe/go/services/xrefs"
    27  	"kythe.io/kythe/go/serving/xrefs/columnar"
    28  	"kythe.io/kythe/go/storage/keyvalue"
    29  	"kythe.io/kythe/go/storage/table"
    30  	"kythe.io/kythe/go/util/keys"
    31  	"kythe.io/kythe/go/util/kytheuri"
    32  	"kythe.io/kythe/go/util/log"
    33  	"kythe.io/kythe/go/util/schema"
    34  	"kythe.io/kythe/go/util/schema/facts"
    35  	"kythe.io/kythe/go/util/span"
    36  
    37  	"bitbucket.org/creachadair/stringset"
    38  	"google.golang.org/grpc/codes"
    39  	"google.golang.org/grpc/status"
    40  	"google.golang.org/protobuf/proto"
    41  
    42  	cpb "kythe.io/kythe/proto/common_go_proto"
    43  	scpb "kythe.io/kythe/proto/schema_go_proto"
    44  	spb "kythe.io/kythe/proto/serving_go_proto"
    45  	srvpb "kythe.io/kythe/proto/serving_go_proto"
    46  	xpb "kythe.io/kythe/proto/xref_go_proto"
    47  	xspb "kythe.io/kythe/proto/xref_serving_go_proto"
    48  )
    49  
    50  // ColumnarTableKeyMarker is stored within a Kythe columnar table to
    51  // differentiate it from the legacy combined table format.
    52  const ColumnarTableKeyMarker = "kythe:columnar"
    53  
    54  // NewService returns an xrefs.Service backed by the given table.  The format of
    55  // the table with be automatically detected.
    56  func NewService(ctx context.Context, t keyvalue.DB) xrefs.Service {
    57  	_, err := t.Get(ctx, []byte(ColumnarTableKeyMarker), nil)
    58  	if err == nil {
    59  		log.WarningContext(ctx, "detected a experimental columnar xrefs table")
    60  		return NewColumnarTable(t)
    61  	}
    62  	return NewCombinedTable(&table.KVProto{t})
    63  }
    64  
    65  // NewColumnarTable returns a table for the given columnar xrefs lookup table.
    66  func NewColumnarTable(t keyvalue.DB) *ColumnarTable {
    67  	return &ColumnarTable{t, NewCombinedTable(&table.KVProto{t})}
    68  }
    69  
    70  // ColumnarTable implements an xrefs.Service backed by a columnar serving table.
    71  type ColumnarTable struct {
    72  	keyvalue.DB
    73  
    74  	*Table // fallback non-columnar documentation
    75  }
    76  
    77  // Decorations implements part of the xrefs.Service interface.
    78  func (c *ColumnarTable) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
    79  	ticket, err := kytheuri.Fix(req.GetLocation().Ticket)
    80  	if err != nil {
    81  		return nil, status.Errorf(codes.InvalidArgument, "invalid ticket %q: %v", req.GetLocation().Ticket, err)
    82  	} else if req.Location.Kind == xpb.Location_SPAN && req.Location.Span == nil {
    83  		return nil, status.Errorf(codes.InvalidArgument, "missing requested Location span: %v", req.Location)
    84  	}
    85  
    86  	// TODO(schroederc): handle SPAN requests
    87  	// TODO(schroederc): handle dirty buffers
    88  	// TODO(schroederc): file infos
    89  
    90  	fileURI, err := kytheuri.Parse(ticket)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  	file := fileURI.VName()
    95  	prefix, err := keys.Append(columnar.DecorationsKeyPrefix, file)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	it, err := c.DB.ScanPrefix(ctx, prefix, &keyvalue.Options{LargeRead: true})
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	k, val, err := it.Next()
   105  	if err == io.EOF || !bytes.Equal(k, prefix) {
   106  		return nil, xrefs.ErrDecorationsNotFound
   107  	} else if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	// Decode FileDecorations Index
   112  	var idx xspb.FileDecorations_Index
   113  	if err := proto.Unmarshal(val, &idx); err != nil {
   114  		return nil, fmt.Errorf("error decoding index: %v", err)
   115  	}
   116  
   117  	// Setup reply state based on request
   118  	reply := &xpb.DecorationsReply{Location: req.Location}
   119  	if req.References && len(req.Filter) > 0 {
   120  		reply.Nodes = make(map[string]*cpb.NodeInfo)
   121  	}
   122  	if req.TargetDefinitions {
   123  		reply.DefinitionLocations = make(map[string]*xpb.Anchor)
   124  	}
   125  
   126  	// Setup scanning state for constructing reply
   127  	var patcher *span.Patcher
   128  	var norm *span.Normalizer                                          // span normalizer for references
   129  	refsByTarget := make(map[string][]*xpb.DecorationsReply_Reference) // target -> set<Reference>
   130  	defs := stringset.New()                                            // set<needed definition tickets>
   131  	reply.ExtendsOverrides = make(map[string]*xpb.DecorationsReply_Overrides)
   132  	buildConfigs := stringset.New(req.BuildConfig...)
   133  	patterns := xrefs.ConvertFilters(req.Filter)
   134  	emitSnippets := req.Snippets != xpb.SnippetsKind_NONE
   135  
   136  	// The span with which to constrain the set of returned anchor references.
   137  	var startBoundary, endBoundary int32
   138  	spanKind := req.SpanKind
   139  
   140  	// Main loop to scan over each columnar kv entry.
   141  	for {
   142  		k, val, err := it.Next()
   143  		if err == io.EOF {
   144  			break
   145  		} else if err != nil {
   146  			return nil, err
   147  		}
   148  		key := string(k[len(prefix):])
   149  
   150  		// TODO(schroederc): only parse needed entries
   151  		e, err := columnar.DecodeDecorationsEntry(file, key, val)
   152  		if err != nil {
   153  			return nil, err
   154  		}
   155  
   156  		switch e := e.Entry.(type) {
   157  		case *xspb.FileDecorations_Text_:
   158  			// TODO(danielnorberg): Move the handling of this entry type up out of the loop to
   159  			//                      ensure that variables used in other cases have been assigned
   160  			text := e.Text.Text
   161  			if len(req.DirtyBuffer) > 0 {
   162  				patcher, err = span.NewPatcher(text, req.DirtyBuffer)
   163  				if err != nil {
   164  					return nil, status.Errorf(codes.Internal, "error patching decorations for %s: %v", req.Location.Ticket, err)
   165  				}
   166  				text = req.DirtyBuffer
   167  			}
   168  			norm = span.NewNormalizer(text)
   169  
   170  			loc, err := norm.Location(req.GetLocation())
   171  			if err != nil {
   172  				return nil, status.Errorf(codes.InvalidArgument, "invalid Location: %v", err)
   173  			}
   174  			reply.Location = loc
   175  
   176  			if loc.Kind == xpb.Location_FILE {
   177  				startBoundary = 0
   178  				endBoundary = int32(len(text))
   179  				spanKind = xpb.DecorationsRequest_WITHIN_SPAN
   180  			} else {
   181  				startBoundary = loc.Span.Start.ByteOffset
   182  				endBoundary = loc.Span.End.ByteOffset
   183  			}
   184  
   185  			if req.SourceText {
   186  				reply.Encoding = idx.TextEncoding
   187  				if loc.Kind == xpb.Location_FILE {
   188  					reply.SourceText = text
   189  				} else {
   190  					reply.SourceText = text[loc.Span.Start.ByteOffset:loc.Span.End.ByteOffset]
   191  				}
   192  			}
   193  		case *xspb.FileDecorations_Target_:
   194  			if !req.References {
   195  				// TODO(schroederc): seek to next group
   196  				continue
   197  			}
   198  			t := e.Target
   199  			// Filter decorations by requested build configs.
   200  			if len(buildConfigs) != 0 && !buildConfigs.Contains(t.BuildConfig) {
   201  				continue
   202  			}
   203  			kind := t.GetGenericKind()
   204  			if kind == "" {
   205  				kind = schema.EdgeKindString(t.GetKytheKind())
   206  			}
   207  			start, end, exists := patcher.Patch(t.StartOffset, t.EndOffset)
   208  			// Filter non-existent anchor.  Anchors can no longer exist if we were
   209  			// given a dirty buffer and the anchor was inside a changed region.
   210  			if !exists || !span.InBounds(spanKind, start, end, startBoundary, endBoundary) {
   211  				continue
   212  			}
   213  			ref := &xpb.DecorationsReply_Reference{
   214  				TargetTicket: kytheuri.ToString(t.Target),
   215  				BuildConfig:  t.BuildConfig,
   216  				Kind:         kind,
   217  				Span:         norm.SpanOffsets(start, end),
   218  			}
   219  			refsByTarget[ref.TargetTicket] = append(refsByTarget[ref.TargetTicket], ref)
   220  			reply.Reference = append(reply.Reference, ref)
   221  		case *xspb.FileDecorations_TargetOverride_:
   222  			overridingTicket := kytheuri.ToString(e.TargetOverride.Overriding)
   223  			t, ok := reply.ExtendsOverrides[overridingTicket]
   224  			if !ok {
   225  				t = &xpb.DecorationsReply_Overrides{}
   226  				reply.ExtendsOverrides[overridingTicket] = t
   227  			}
   228  			var kind xpb.DecorationsReply_Override_Kind
   229  			switch e.TargetOverride.Kind {
   230  			case spb.FileDecorations_Override_OVERRIDES:
   231  				kind = xpb.DecorationsReply_Override_OVERRIDES
   232  			case spb.FileDecorations_Override_EXTENDS:
   233  				kind = xpb.DecorationsReply_Override_EXTENDS
   234  			}
   235  			t.Override = append(t.Override, &xpb.DecorationsReply_Override{
   236  				Target:           kytheuri.ToString(e.TargetOverride.Overridden),
   237  				Kind:             kind,
   238  				TargetDefinition: e.TargetOverride.OverridingDefinition.Ticket,
   239  			})
   240  			if req.TargetDefinitions {
   241  				reply.DefinitionLocations[e.TargetOverride.OverridingDefinition.Ticket] = a2a(e.TargetOverride.OverridingDefinition, nil, false).Anchor
   242  			}
   243  		case *xspb.FileDecorations_TargetNode_:
   244  			if len(patterns) == 0 {
   245  				// TODO(schroederc): seek to next group
   246  				continue
   247  			}
   248  			n := e.TargetNode.Node
   249  			c := filterNode(patterns, n)
   250  			if c != nil && len(c.Facts) > 0 {
   251  				reply.Nodes[kytheuri.ToString(n.Source)] = c
   252  			}
   253  		case *xspb.FileDecorations_TargetDefinition_:
   254  			if !req.TargetDefinitions {
   255  				continue
   256  			}
   257  			def := e.TargetDefinition
   258  			// refsByTarget will be populated by now due to our chosen key ordering
   259  			// See: kythe/proto/xref_serving.proto
   260  			refs := refsByTarget[kytheuri.ToString(def.Target)]
   261  			if len(refs) == 0 {
   262  				continue
   263  			}
   264  			defTicket := kytheuri.ToString(def.Definition)
   265  			defs.Add(defTicket)
   266  			for _, ref := range refs {
   267  				ref.TargetDefinition = defTicket
   268  			}
   269  		case *xspb.FileDecorations_DefinitionLocation_:
   270  			if !req.TargetDefinitions {
   271  				continue
   272  			}
   273  			def := e.DefinitionLocation
   274  			if !defs.Contains(def.Location.Ticket) {
   275  				continue
   276  			}
   277  			reply.DefinitionLocations[def.Location.Ticket] = a2a(def.Location, nil, emitSnippets).Anchor
   278  		case *xspb.FileDecorations_Override_:
   279  			// TODO(schroederc): handle
   280  		case *xspb.FileDecorations_Diagnostic_:
   281  			if !req.Diagnostics {
   282  				continue
   283  			}
   284  			diag := e.Diagnostic.Diagnostic
   285  			if diag.Span == nil {
   286  				reply.Diagnostic = append(reply.Diagnostic, diag)
   287  			} else {
   288  				start, end, exists := patcher.Patch(span.ByteOffsets(diag.Span))
   289  				// Filter non-existent (or out-of-bounds) diagnostic.  Diagnostics can
   290  				// no longer exist if we were given a dirty buffer and the diagnostic
   291  				// was inside a changed region.
   292  				if !exists || !span.InBounds(spanKind, start, end, startBoundary, endBoundary) {
   293  					continue
   294  				}
   295  
   296  				diag.Span = norm.SpanOffsets(start, end)
   297  				reply.Diagnostic = append(reply.Diagnostic, diag)
   298  			}
   299  		default:
   300  			return nil, fmt.Errorf("unknown FileDecorations entry: %T", e)
   301  		}
   302  	}
   303  	if err := it.Close(); err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	return reply, nil
   308  }
   309  
   310  func addXRefNode(reply *xpb.CrossReferencesReply, patterns []*regexp.Regexp, n *scpb.Node) {
   311  	if len(patterns) == 0 {
   312  		return
   313  	}
   314  	ticket := kytheuri.ToString(n.Source)
   315  	if _, ok := reply.Nodes[ticket]; !ok {
   316  		c := filterNode(patterns, n)
   317  		if c != nil && len(c.Facts) > 0 {
   318  			reply.Nodes[ticket] = c
   319  		}
   320  	}
   321  }
   322  
   323  // CrossReferences implements part of the xrefs.Service interface.
   324  func (c *ColumnarTable) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) {
   325  	reply := &xpb.CrossReferencesReply{
   326  		CrossReferences: make(map[string]*xpb.CrossReferencesReply_CrossReferenceSet),
   327  	}
   328  
   329  	relatedNodes := stringset.New()
   330  	patterns := xrefs.ConvertFilters(req.Filter)
   331  	relatedKinds := stringset.New(req.RelatedNodeKind...)
   332  	if len(patterns) > 0 {
   333  		reply.Nodes = make(map[string]*cpb.NodeInfo)
   334  	}
   335  	if req.NodeDefinitions {
   336  		reply.DefinitionLocations = make(map[string]*xpb.Anchor)
   337  	}
   338  	emitSnippets := req.Snippets != xpb.SnippetsKind_NONE
   339  
   340  	// TODO(schroederc): file infos
   341  	// TODO(schroederc): implement paging xrefs in large CrossReferencesReply messages
   342  
   343  	for _, ticket := range req.Ticket {
   344  		uri, err := kytheuri.Parse(ticket)
   345  		if err != nil {
   346  			return nil, err
   347  		}
   348  		prefix, err := keys.Append(columnar.CrossReferencesKeyPrefix, uri.VName())
   349  		if err != nil {
   350  			return nil, err
   351  		}
   352  		it, err := c.DB.ScanPrefix(ctx, prefix, &keyvalue.Options{LargeRead: true})
   353  		if err != nil {
   354  			return nil, err
   355  		}
   356  		defer it.Close()
   357  
   358  		k, val, err := it.Next()
   359  		if err == io.EOF || !bytes.Equal(k, prefix) {
   360  			continue
   361  		} else if err != nil {
   362  			return nil, err
   363  		}
   364  
   365  		// Decode CrossReferences Index
   366  		var idx xspb.CrossReferences_Index
   367  		if err := proto.Unmarshal(val, &idx); err != nil {
   368  			return nil, fmt.Errorf("error decoding index: %v", err)
   369  		}
   370  		idx.Node.Source = uri.VName()
   371  		addXRefNode(reply, patterns, idx.Node)
   372  
   373  		// TODO(schroederc): handle merge_with
   374  
   375  		set := &xpb.CrossReferencesReply_CrossReferenceSet{
   376  			Ticket:       ticket,
   377  			MarkedSource: idx.MarkedSource,
   378  		}
   379  		reply.CrossReferences[ticket] = set
   380  
   381  		// TODO remove callers without callsites
   382  		callers := make(map[string]*xpb.CrossReferencesReply_RelatedAnchor)
   383  
   384  		// Main loop to scan over each columnar kv entry.
   385  		for {
   386  			k, val, err := it.Next()
   387  			if err == io.EOF {
   388  				break
   389  			} else if err != nil {
   390  				return nil, err
   391  			}
   392  			key := string(k[len(prefix):])
   393  
   394  			// TODO(schroederc): only parse needed entries
   395  			e, err := columnar.DecodeCrossReferencesEntry(uri.VName(), key, val)
   396  			if err != nil {
   397  				return nil, err
   398  			}
   399  
   400  			switch e := e.Entry.(type) {
   401  			case *xspb.CrossReferences_Reference_:
   402  				ref := e.Reference
   403  				kind := getRefKind(ref)
   404  				var anchors *[]*xpb.CrossReferencesReply_RelatedAnchor
   405  				switch {
   406  				case xrefs.IsDefKind(req.DefinitionKind, kind, false):
   407  					anchors = &set.Definition
   408  				case xrefs.IsDeclKind(req.DeclarationKind, kind, false):
   409  					anchors = &set.Declaration
   410  				case xrefs.IsRefKind(req.ReferenceKind, kind):
   411  					anchors = &set.Reference
   412  				}
   413  				if anchors != nil {
   414  					a := a2a(ref.Location, nil, emitSnippets).Anchor
   415  					a.Ticket = ""
   416  					ra := &xpb.CrossReferencesReply_RelatedAnchor{Anchor: a}
   417  					*anchors = append(*anchors, ra)
   418  				}
   419  			case *xspb.CrossReferences_Relation_:
   420  				if len(patterns) == 0 {
   421  					continue
   422  				}
   423  				rel := e.Relation
   424  				kind := rel.GetGenericKind()
   425  				if kind == "" {
   426  					kind = schema.EdgeKindString(rel.GetKytheKind())
   427  				}
   428  				if rel.Reverse {
   429  					kind = "%" + kind
   430  				}
   431  				if xrefs.IsRelatedNodeKind(relatedKinds, kind) {
   432  					relatedNode := kytheuri.ToString(rel.Node)
   433  					relatedNodes.Add(relatedNode)
   434  					set.RelatedNode = append(set.RelatedNode, &xpb.CrossReferencesReply_RelatedNode{
   435  						Ticket:       relatedNode,
   436  						RelationKind: kind,
   437  						Ordinal:      rel.Ordinal,
   438  					})
   439  				}
   440  			case *xspb.CrossReferences_RelatedNode_:
   441  				relatedNode := kytheuri.ToString(e.RelatedNode.Node.Source)
   442  				if relatedNodes.Contains(relatedNode) {
   443  					addXRefNode(reply, patterns, e.RelatedNode.Node)
   444  				}
   445  			case *xspb.CrossReferences_NodeDefinition_:
   446  				if !req.NodeDefinitions || len(reply.Nodes) == 0 {
   447  					continue
   448  				}
   449  
   450  				relatedNode := kytheuri.ToString(e.NodeDefinition.Node)
   451  				if node := reply.Nodes[relatedNode]; node != nil {
   452  					loc := e.NodeDefinition.Location
   453  					node.Definition = loc.Ticket
   454  					a := a2a(loc, nil, emitSnippets).Anchor
   455  					reply.DefinitionLocations[loc.Ticket] = a
   456  				}
   457  			case *xspb.CrossReferences_Caller_:
   458  				if req.CallerKind == xpb.CrossReferencesRequest_NO_CALLERS {
   459  					continue
   460  				}
   461  				c := e.Caller
   462  				a := a2a(c.Location, nil, emitSnippets).Anchor
   463  				a.Ticket = ""
   464  				callerTicket := kytheuri.ToString(c.Caller)
   465  				caller := &xpb.CrossReferencesReply_RelatedAnchor{
   466  					Anchor:       a,
   467  					MarkedSource: c.MarkedSource,
   468  					Ticket:       callerTicket,
   469  				}
   470  				callers[callerTicket] = caller
   471  				set.Caller = append(set.Caller, caller)
   472  			case *xspb.CrossReferences_Callsite_:
   473  				c := e.Callsite
   474  				if req.CallerKind == xpb.CrossReferencesRequest_NO_CALLERS ||
   475  					(req.CallerKind == xpb.CrossReferencesRequest_DIRECT_CALLERS && c.Kind == xspb.CrossReferences_Callsite_OVERRIDE) {
   476  					continue
   477  				}
   478  				caller := callers[kytheuri.ToString(c.Caller)]
   479  				if caller == nil {
   480  					log.WarningContextf(ctx, "missing Caller for callsite: %+v", c)
   481  					continue
   482  				}
   483  				a := a2a(c.Location, nil, emitSnippets).Anchor
   484  				a.Ticket = ""
   485  				// TODO(schroederc): set anchor kind to differentiate kinds?
   486  				caller.Site = append(caller.Site, a)
   487  			default:
   488  				return nil, fmt.Errorf("unhandled internal serving type: %T", e)
   489  			}
   490  		}
   491  	}
   492  
   493  	return reply, nil
   494  }
   495  
   496  func getRefKind(ref *xspb.CrossReferences_Reference) string {
   497  	if k := ref.GetGenericKind(); k != "" {
   498  		return k
   499  	}
   500  	return schema.EdgeKindString(ref.GetKytheKind())
   501  }
   502  
   503  func filterNode(patterns []*regexp.Regexp, n *scpb.Node) *cpb.NodeInfo {
   504  	c := &cpb.NodeInfo{Facts: make(map[string][]byte, len(n.Fact))}
   505  	for _, f := range n.Fact {
   506  		name := schema.GetFactName(f)
   507  		if xrefs.MatchesAny(name, patterns) {
   508  			c.Facts[name] = f.Value
   509  		}
   510  	}
   511  	if kind := schema.GetNodeKind(n); kind != "" && xrefs.MatchesAny(facts.NodeKind, patterns) {
   512  		c.Facts[facts.NodeKind] = []byte(kind)
   513  	}
   514  	if subkind := schema.GetSubkind(n); subkind != "" && xrefs.MatchesAny(facts.Subkind, patterns) {
   515  		c.Facts[facts.Subkind] = []byte(subkind)
   516  	}
   517  	return c
   518  }
   519  
   520  func a2a(a *srvpb.ExpandedAnchor, fileInfos map[string]*srvpb.FileInfo, anchorText bool) *xpb.CrossReferencesReply_RelatedAnchor {
   521  	c := &anchorConverter{fileInfos: fileInfos, anchorText: anchorText}
   522  	return c.Convert(a)
   523  }