// Nodes implements part of the xrefs Service interface. func (t *tableImpl) Nodes(ctx context.Context, req *xpb.NodesRequest) (*xpb.NodesReply, error) { reply := &xpb.NodesReply{} patterns := xrefs.ConvertFilters(req.Filter) for _, rawTicket := range req.Ticket { ticket, err := kytheuri.Fix(rawTicket) if err != nil { return nil, fmt.Errorf("invalid ticket %q: %v", rawTicket, err) } n, err := t.node(ctx, ticket) if err == table.ErrNoSuchKey { continue } else if err != nil { return nil, fmt.Errorf("lookup error for node %q: %v", ticket, err) } ni := &xpb.NodeInfo{Ticket: n.Ticket} for _, fact := range n.Fact { if len(patterns) == 0 || xrefs.MatchesAny(fact.Name, patterns) { ni.Fact = append(ni.Fact, &xpb.Fact{Name: fact.Name, Value: fact.Value}) } } if len(ni.Fact) > 0 { reply.Node = append(reply.Node, ni) } } return reply, nil }
func mustFix(t *testing.T, ticket string) string { ft, err := kytheuri.Fix(ticket) if err != nil { t.Fatalf("Error fixing ticket %q: %v", ticket, err) } return ft }
// Decorations implements part of the xrefs.Interface. func (d *DB) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) { if d.selectText == nil { var err error d.selectText, err = d.Prepare("SELECT text, text_encoding FROM Nodes WHERE ticket = $1;") if err != nil { return nil, fmt.Errorf("error preparing selectText statement: %v", err) } } // TODO(schroederc): dirty buffers // TODO(schroederc): span locations fileTicket, err := kytheuri.Fix(req.Location.Ticket) if err != nil { return nil, fmt.Errorf("invalid location ticket: %v", err) } req.Location.Ticket = fileTicket decor := &xpb.DecorationsReply{Location: req.Location} r := d.selectText.QueryRow(fileTicket) var text []byte var textEncoding sql.NullString if err := r.Scan(&text, &textEncoding); err != nil { return nil, err } norm := xrefs.NewNormalizer(text) if req.SourceText { decor.SourceText = text decor.Encoding = textEncoding.String } if req.References { var err error decor.Reference, err = d.scanReferences(fileTicket, norm) if err != nil { return nil, err } if len(req.Filter) > 0 && len(decor.Reference) > 0 { nodeTickets := stringset.New() for _, r := range decor.Reference { nodeTickets.Add(r.TargetTicket) } nodes, err := d.Nodes(ctx, &xpb.NodesRequest{ Ticket: nodeTickets.Slice(), Filter: req.Filter, }) if err != nil { return nil, fmt.Errorf("error filtering nodes:%v", err) } decor.Nodes = nodes.Nodes } } return decor, nil }
func fixTickets(rawTickets []string) ([]string, error) { if len(rawTickets) == 0 { return nil, errors.New("no tickets specified") } var tickets []string for _, rawTicket := range rawTickets { ticket, err := kytheuri.Fix(rawTicket) if err != nil { return nil, fmt.Errorf("invalid ticket %q: %v", rawTicket, err) } tickets = append(tickets, ticket) } return tickets, nil }
// Decorations implements part of the xrefs Service interface. func (t *tableImpl) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) { if req.GetLocation() == nil || req.GetLocation().Ticket == "" { return nil, errors.New("missing location") } ticket, err := kytheuri.Fix(req.GetLocation().Ticket) if err != nil { return nil, fmt.Errorf("invalid ticket %q: %v", req.GetLocation().Ticket, err) } decor, err := t.fileDecorations(ctx, ticket) if err == table.ErrNoSuchKey { return nil, xrefs.ErrDecorationsNotFound } else if err != nil { return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err) } text := decor.File.Text if len(req.DirtyBuffer) > 0 { text = req.DirtyBuffer } norm := xrefs.NewNormalizer(text) loc, err := norm.Location(req.GetLocation()) if err != nil { return nil, err } reply := &xpb.DecorationsReply{Location: loc} if req.SourceText { reply.Encoding = decor.File.Encoding if loc.Kind == xpb.Location_FILE { reply.SourceText = text } else { reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset] } } if req.References { patterns := xrefs.ConvertFilters(req.Filter) var patcher *xrefs.Patcher if len(req.DirtyBuffer) > 0 { patcher = xrefs.NewPatcher(decor.File.Text, req.DirtyBuffer) } // The span with which to constrain the set of returned anchor references. var startBoundary, endBoundary int32 spanKind := req.SpanKind if loc.Kind == xpb.Location_FILE { startBoundary = 0 endBoundary = int32(len(text)) spanKind = xpb.DecorationsRequest_WITHIN_SPAN } else { startBoundary = loc.Start.ByteOffset endBoundary = loc.End.ByteOffset } reply.Reference = make([]*xpb.DecorationsReply_Reference, 0, len(decor.Decoration)) reply.Nodes = make(map[string]*xpb.NodeInfo) seenTarget := stringset.New() // Reference.TargetTicket -> NodeInfo (superset of reply.Nodes) var nodes map[string]*xpb.NodeInfo if len(patterns) > 0 { nodes = make(map[string]*xpb.NodeInfo) for _, n := range decor.Target { nodes[n.Ticket] = nodeToInfo(patterns, n) } } // Reference.TargetTicket -> []Reference set var refs map[string][]*xpb.DecorationsReply_Reference // ExpandedAnchor.Ticket -> ExpandedAnchor var defs map[string]*srvpb.ExpandedAnchor if req.TargetDefinitions { refs = make(map[string][]*xpb.DecorationsReply_Reference) reply.DefinitionLocations = make(map[string]*xpb.Anchor) defs = make(map[string]*srvpb.ExpandedAnchor) for _, def := range decor.TargetDefinitions { defs[def.Ticket] = def } } for _, d := range decor.Decoration { start, end, exists := patcher.Patch(d.Anchor.StartOffset, d.Anchor.EndOffset) // Filter non-existent anchor. Anchors can no longer exist if we were // given a dirty buffer and the anchor was inside a changed region. if exists { if xrefs.InSpanBounds(spanKind, start, end, startBoundary, endBoundary) { d.Anchor.StartOffset = start d.Anchor.EndOffset = end r := decorationToReference(norm, d) if req.TargetDefinitions { if def, ok := defs[d.TargetDefinition]; ok { reply.DefinitionLocations[d.TargetDefinition] = a2a(def, false).Anchor } else { refs[r.TargetTicket] = append(refs[r.TargetTicket], r) } } else { r.TargetDefinition = "" } reply.Reference = append(reply.Reference, r) if !seenTarget.Contains(r.TargetTicket) && nodes != nil { reply.Nodes[r.TargetTicket] = nodes[r.TargetTicket] seenTarget.Add(r.TargetTicket) } } } } // Only compute target definitions if the serving data doesn't contain any // TODO(schroederc): remove this once serving data is always populated if req.TargetDefinitions && len(defs) == 0 { targetTickets := make([]string, 0, len(refs)) for ticket := range refs { targetTickets = append(targetTickets, ticket) } defs, err := xrefs.SlowDefinitions(t, ctx, targetTickets) if err != nil { return nil, fmt.Errorf("error retrieving target definitions: %v", err) } reply.DefinitionLocations = make(map[string]*xpb.Anchor, len(defs)) for tgt, def := range defs { for _, ref := range refs[tgt] { if def.Ticket != ref.SourceTicket { ref.TargetDefinition = def.Ticket if _, ok := reply.DefinitionLocations[def.Ticket]; !ok { reply.DefinitionLocations[def.Ticket] = def } } } } } } return reply, nil }
// Decorations implements part of the xrefs Service interface. func (t *tableImpl) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) { if req.GetLocation() == nil || req.GetLocation().Ticket == "" { return nil, errors.New("missing location") } ticket, err := kytheuri.Fix(req.GetLocation().Ticket) if err != nil { return nil, fmt.Errorf("invalid ticket %q: %v", req.GetLocation().Ticket, err) } decor, err := t.fileDecorations(ctx, ticket) if err == table.ErrNoSuchKey { return nil, fmt.Errorf("decorations not found for file %q", ticket) } else if err != nil { return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err) } text := decor.SourceText if len(req.DirtyBuffer) > 0 { text = req.DirtyBuffer } norm := xrefs.NewNormalizer(text) loc, err := norm.Location(req.GetLocation()) if err != nil { return nil, err } reply := &xpb.DecorationsReply{Location: loc} if req.SourceText { reply.Encoding = decor.Encoding if loc.Kind == xpb.Location_FILE { reply.SourceText = text } else { reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset] } } if req.References { // Set of node tickets for which to retrieve facts. These are the nodes // used in the returned references (both anchor sources and node targets). nodeTickets := stringset.New() var patcher *xrefs.Patcher var offsetMapping map[string]span // Map from anchor ticket to patched span if len(req.DirtyBuffer) > 0 { patcher = xrefs.NewPatcher(decor.SourceText, req.DirtyBuffer) offsetMapping = make(map[string]span) } // The span with which to constrain the set of returned anchor references. var startBoundary, endBoundary int32 if loc.Kind == xpb.Location_FILE { startBoundary = 0 endBoundary = int32(len(text)) } else { startBoundary = loc.Start.ByteOffset endBoundary = loc.End.ByteOffset } reply.Reference = make([]*xpb.DecorationsReply_Reference, 0, len(decor.Decoration)) for _, d := range decor.Decoration { start, end, exists := patcher.Patch(d.Anchor.StartOffset, d.Anchor.EndOffset) // Filter non-existent anchor. Anchors can no longer exist if we were // given a dirty buffer and the anchor was inside a changed region. if exists { if start >= startBoundary && end <= endBoundary { if offsetMapping != nil { // Save the patched span to update the corresponding facts of the // anchor node in reply.Node. offsetMapping[d.Anchor.Ticket] = span{start, end} } reply.Reference = append(reply.Reference, decorationToReference(norm, d)) nodeTickets.Add(d.Anchor.Ticket) nodeTickets.Add(d.TargetTicket) } } } // Only request Nodes when there are fact filters given. if len(req.Filter) > 0 { // Retrieve facts for all nodes referenced in the file decorations. nodesReply, err := t.Nodes(ctx, &xpb.NodesRequest{ Ticket: nodeTickets.Slice(), Filter: req.Filter, }) if err != nil { return nil, fmt.Errorf("error getting nodes: %v", err) } reply.Node = nodesReply.Node } // Patch anchor node facts in reply to match dirty buffer if len(offsetMapping) > 0 { for _, n := range reply.Node { if span, ok := offsetMapping[n.Ticket]; ok { for _, f := range n.Fact { switch f.Name { case schema.AnchorStartFact: f.Value = []byte(strconv.Itoa(int(span.start))) case schema.AnchorEndFact: f.Value = []byte(strconv.Itoa(int(span.end))) } } } } } } return reply, nil }
// Edges implements part of the xrefs Service interface. func (t *tableImpl) Edges(ctx context.Context, req *xpb.EdgesRequest) (*xpb.EdgesReply, error) { if len(req.Ticket) == 0 { return nil, errors.New("no tickets specified") } stats := filterStats{ max: int(req.PageSize), } if stats.max < 0 { return nil, fmt.Errorf("invalid page_size: %d", req.PageSize) } else if stats.max == 0 { stats.max = defaultPageSize } else if stats.max > maxPageSize { stats.max = maxPageSize } if req.PageToken != "" { rec, err := base64.StdEncoding.DecodeString(req.PageToken) if err != nil { return nil, fmt.Errorf("invalid page_token: %q", req.PageToken) } var t srvpb.PageToken if err := proto.Unmarshal(rec, &t); err != nil || t.Index < 0 { return nil, fmt.Errorf("invalid page_token: %q", req.PageToken) } stats.skip = int(t.Index) } pageToken := stats.skip var totalEdgesPossible int allowedKinds := stringset.New(req.Kind...) nodeTickets := stringset.New() reply := &xpb.EdgesReply{} for _, rawTicket := range req.Ticket { ticket, err := kytheuri.Fix(rawTicket) if err != nil { return nil, fmt.Errorf("invalid ticket %q: %v", rawTicket, err) } pes, err := t.pagedEdgeSet(ctx, ticket) if err == table.ErrNoSuchKey { continue } else if err != nil { return nil, fmt.Errorf("lookup error for node edges %q: %v", ticket, err) } totalEdgesPossible += int(pes.TotalEdges) var groups []*xpb.EdgeSet_Group for _, grp := range pes.EdgeSet.Group { if len(allowedKinds) == 0 || allowedKinds.Contains(grp.Kind) { ng := stats.filter(grp) if ng != nil { nodeTickets.Add(ng.TargetTicket...) groups = append(groups, ng) if stats.total == stats.max { break } } } } for _, idx := range pes.PageIndex { if len(allowedKinds) == 0 || allowedKinds.Contains(idx.EdgeKind) { ep, err := t.edgePage(ctx, idx.PageKey) if err == table.ErrNoSuchKey { return nil, fmt.Errorf("missing edge page: %q", idx.PageKey) } else if err != nil { return nil, fmt.Errorf("lookup error for node edges %q: %v", ticket, err) } ng := stats.filter(ep.EdgesGroup) if ng != nil { nodeTickets.Add(ng.TargetTicket...) groups = append(groups, ng) if stats.total == stats.max { break } } } } if len(groups) > 0 { nodeTickets.Add(pes.EdgeSet.SourceTicket) reply.EdgeSet = append(reply.EdgeSet, &xpb.EdgeSet{ SourceTicket: pes.EdgeSet.SourceTicket, Group: groups, }) } } if stats.total > stats.max { log.Panicf("totalEdges greater than maxEdges: %d > %d", stats.total, stats.max) } else if pageToken+stats.total > totalEdgesPossible && pageToken <= totalEdgesPossible { log.Panicf("pageToken+totalEdges greater than totalEdgesPossible: %d+%d > %d", pageToken, stats.total, totalEdgesPossible) } // Only request Nodes when there are fact filters given. if len(req.Filter) > 0 { nReply, err := t.Nodes(ctx, &xpb.NodesRequest{ Ticket: nodeTickets.Slice(), Filter: req.Filter, }) if err != nil { return nil, fmt.Errorf("error getting nodes: %v", err) } reply.Node = nReply.Node } if pageToken+stats.total != totalEdgesPossible && stats.total != 0 { // TODO: take into account an empty last page (due to kind filters) rec, err := proto.Marshal(&srvpb.PageToken{Index: int32(pageToken + stats.total)}) if err != nil { return nil, fmt.Errorf("error marshalling page token: %v", err) } reply.NextPageToken = base64.StdEncoding.EncodeToString(rec) } return reply, nil }
// Decorations implements part of the xrefs Service interface. func (t *tableImpl) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) { if req.GetLocation() == nil || req.GetLocation().Ticket == "" { return nil, errors.New("missing location") } ticket, err := kytheuri.Fix(req.GetLocation().Ticket) if err != nil { return nil, fmt.Errorf("invalid ticket %q: %v", req.GetLocation().Ticket, err) } decor, err := t.fileDecorations(ctx, ticket) if err == table.ErrNoSuchKey { return nil, fmt.Errorf("decorations not found for file %q", ticket) } else if err != nil { return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err) } text := decor.SourceText if len(req.DirtyBuffer) > 0 { text = req.DirtyBuffer } norm := xrefs.NewNormalizer(text) loc, err := norm.Location(req.GetLocation()) if err != nil { return nil, err } reply := &xpb.DecorationsReply{Location: loc} if req.SourceText { reply.Encoding = decor.Encoding if loc.Kind == xpb.Location_FILE { reply.SourceText = text } else { reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset] } } if req.References { patterns := xrefs.ConvertFilters(req.Filter) nodeTickets := stringset.New() var patcher *xrefs.Patcher var offsetMapping map[string]span // Map from anchor ticket to patched span if len(req.DirtyBuffer) > 0 { patcher = xrefs.NewPatcher(decor.SourceText, req.DirtyBuffer) offsetMapping = make(map[string]span) } // The span with which to constrain the set of returned anchor references. var startBoundary, endBoundary int32 if loc.Kind == xpb.Location_FILE { startBoundary = 0 endBoundary = int32(len(text)) } else { startBoundary = loc.Start.ByteOffset endBoundary = loc.End.ByteOffset } reply.Reference = make([]*xpb.DecorationsReply_Reference, 0, len(decor.Decoration)) for _, d := range decor.Decoration { start, end, exists := patcher.Patch(d.Anchor.StartOffset, d.Anchor.EndOffset) // Filter non-existent anchor. Anchors can no longer exist if we were // given a dirty buffer and the anchor was inside a changed region. if exists { if start >= startBoundary && end <= endBoundary { if offsetMapping != nil { // Save the patched span to update the corresponding facts of the // anchor node in reply.Node. offsetMapping[d.Anchor.Ticket] = span{start, end} } reply.Reference = append(reply.Reference, decorationToReference(norm, d)) if len(patterns) > 0 && !nodeTickets.Contains(d.Target.Ticket) { nodeTickets.Add(d.Target.Ticket) reply.Node = append(reply.Node, nodeToInfo(patterns, d.Target)) } } } } } return reply, nil }