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 }