Пример #1
0
func main() {
	flag.Parse()
	if len(flag.Args()) == 0 {
		flagutil.UsageError("not given any files")
	}

	xs := xrefs.WebClient(*remoteAPI)

	for _, file := range flag.Args() {
		ticket := (&kytheuri.URI{Corpus: *corpus, Path: file}).String()
		decor, err := xs.Decorations(ctx, &xpb.DecorationsRequest{
			Location:   &xpb.Location{Ticket: ticket},
			SourceText: true,
			References: true,
		})
		if err != nil {
			log.Fatalf("Failed to get decorations for file %q", file)
		}

		nmap := xrefs.NodesMap(decor.Nodes)
		var emitted stringset.Set

		for _, r := range decor.Reference {
			if r.Kind != edges.DefinesBinding || emitted.Contains(r.TargetTicket) {
				continue
			}

			ident := string(nmap[r.TargetTicket][identifierFact])
			if ident == "" {
				continue
			}

			offset, err := strconv.Atoi(string(nmap[r.SourceTicket][facts.AnchorStart]))
			if err != nil {
				log.Printf("Invalid start offset for anchor %q", r.SourceTicket)
				continue
			}

			fields, err := getTagFields(xs, r.TargetTicket)
			if err != nil {
				log.Printf("Failed to get tagfields for %q: %v", r.TargetTicket, err)
			}

			fmt.Printf("%s\t%s\t%d;\"\t%s\n",
				ident, file, offsetLine(decor.SourceText, offset), strings.Join(fields, "\t"))
			emitted.Add(r.TargetTicket)
		}
	}
}
Пример #2
0
// CrossReferences implements part of the xrefs.Service interface.
func (t *Table) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) {
	tickets, err := xrefs.FixTickets(req.Ticket)
	if err != nil {
		return nil, err
	}

	stats := refStats{
		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
	}

	var edgesPageToken string
	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 ipb.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)
		edgesPageToken = t.SecondaryToken
	}
	pageToken := stats.skip

	reply := &xpb.CrossReferencesReply{
		CrossReferences: make(map[string]*xpb.CrossReferencesReply_CrossReferenceSet, len(req.Ticket)),
		Nodes:           make(map[string]*cpb.NodeInfo, len(req.Ticket)),

		Total: &xpb.CrossReferencesReply_Total{},
	}
	var nextToken *ipb.PageToken

	wantMoreCrossRefs := edgesPageToken == "" &&
		(req.DefinitionKind != xpb.CrossReferencesRequest_NO_DEFINITIONS ||
			req.DeclarationKind != xpb.CrossReferencesRequest_NO_DECLARATIONS ||
			req.ReferenceKind != xpb.CrossReferencesRequest_NO_REFERENCES ||
			req.DocumentationKind != xpb.CrossReferencesRequest_NO_DOCUMENTATION ||
			req.CallerKind != xpb.CrossReferencesRequest_NO_CALLERS)

	for _, ticket := range tickets {
		// TODO(schroederc): retrieve PagedCrossReferences in parallel
		cr, err := t.crossReferences(ctx, ticket)
		if err == table.ErrNoSuchKey {
			log.Println("Missing CrossReferences:", ticket)
			continue
		} else if err != nil {
			return nil, fmt.Errorf("error looking up cross-references for ticket %q: %v", ticket, err)
		}

		crs := &xpb.CrossReferencesReply_CrossReferenceSet{
			Ticket: ticket,
		}

		if req.ExperimentalSignatures {
			crs.DisplayName, err = xrefs.SlowSignature(ctx, t, ticket)
			if err != nil {
				log.Printf("WARNING: error looking up signature for ticket %q: %v", ticket, err)
			}
		}

		for _, grp := range cr.Group {
			switch {
			case xrefs.IsDefKind(req.DefinitionKind, grp.Kind, cr.Incomplete):
				reply.Total.Definitions += int64(len(grp.Anchor))
				if wantMoreCrossRefs {
					stats.addAnchors(&crs.Definition, grp.Anchor, req.AnchorText)
				}
			case xrefs.IsDeclKind(req.DeclarationKind, grp.Kind, cr.Incomplete):
				reply.Total.Declarations += int64(len(grp.Anchor))
				if wantMoreCrossRefs {
					stats.addAnchors(&crs.Declaration, grp.Anchor, req.AnchorText)
				}
			case xrefs.IsDocKind(req.DocumentationKind, grp.Kind):
				reply.Total.Documentation += int64(len(grp.Anchor))
				if wantMoreCrossRefs {
					stats.addAnchors(&crs.Documentation, grp.Anchor, req.AnchorText)
				}
			case xrefs.IsRefKind(req.ReferenceKind, grp.Kind):
				reply.Total.References += int64(len(grp.Anchor))
				if wantMoreCrossRefs {
					stats.addAnchors(&crs.Reference, grp.Anchor, req.AnchorText)
				}
			}
		}

		if wantMoreCrossRefs && req.CallerKind != xpb.CrossReferencesRequest_NO_CALLERS {
			anchors, err := xrefs.SlowCallersForCrossReferences(ctx, t, req.CallerKind == xpb.CrossReferencesRequest_OVERRIDE_CALLERS, req.ExperimentalSignatures, ticket)
			if err != nil {
				return nil, fmt.Errorf("error in SlowCallersForCrossReferences: %v", err)
			}
			reply.Total.Callers += int64(len(anchors))
			stats.addRelatedAnchors(&crs.Caller, anchors, req.AnchorText)
		}

		if wantMoreCrossRefs && req.DeclarationKind != xpb.CrossReferencesRequest_NO_DECLARATIONS {
			decls, err := xrefs.SlowDeclarationsForCrossReferences(ctx, t, ticket)
			if err != nil {
				return nil, fmt.Errorf("error in SlowDeclarations: %v", err)
			}
			for _, decl := range decls {
				if decl == ticket {
					continue // Added in the original loop above.
				}
				cr, err := t.crossReferences(ctx, decl)
				if err == table.ErrNoSuchKey {
					log.Println("Missing CrossReferences:", decl)
					continue
				} else if err != nil {
					return nil, fmt.Errorf("error looking up cross-references for ticket %q: %v", ticket, err)
				}

				for _, grp := range cr.Group {
					if xrefs.IsDeclKind(req.DeclarationKind, grp.Kind, cr.Incomplete) {
						reply.Total.Declarations += int64(len(grp.Anchor))
						stats.addAnchors(&crs.Declaration, grp.Anchor, req.AnchorText)
					}
				}
			}
		}

		for _, idx := range cr.PageIndex {
			switch {
			case xrefs.IsDefKind(req.DefinitionKind, idx.Kind, cr.Incomplete):
				reply.Total.Definitions += int64(idx.Count)
				if wantMoreCrossRefs && !stats.skipPage(idx) {
					p, err := t.crossReferencesPage(ctx, idx.PageKey)
					if err != nil {
						return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
					}
					stats.addAnchors(&crs.Definition, p.Group.Anchor, req.AnchorText)
				}
			case xrefs.IsDeclKind(req.DeclarationKind, idx.Kind, cr.Incomplete):
				reply.Total.Declarations += int64(idx.Count)
				if wantMoreCrossRefs && !stats.skipPage(idx) {
					p, err := t.crossReferencesPage(ctx, idx.PageKey)
					if err != nil {
						return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
					}
					stats.addAnchors(&crs.Declaration, p.Group.Anchor, req.AnchorText)
				}
			case xrefs.IsDocKind(req.DocumentationKind, idx.Kind):
				reply.Total.Documentation += int64(idx.Count)
				if wantMoreCrossRefs && !stats.skipPage(idx) {
					p, err := t.crossReferencesPage(ctx, idx.PageKey)
					if err != nil {
						return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
					}
					stats.addAnchors(&crs.Documentation, p.Group.Anchor, req.AnchorText)
				}
			case xrefs.IsRefKind(req.ReferenceKind, idx.Kind):
				reply.Total.References += int64(idx.Count)
				if wantMoreCrossRefs && !stats.skipPage(idx) {
					p, err := t.crossReferencesPage(ctx, idx.PageKey)
					if err != nil {
						return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
					}
					stats.addAnchors(&crs.Reference, p.Group.Anchor, req.AnchorText)
				}
			}
		}

		if len(crs.Declaration) > 0 || len(crs.Definition) > 0 || len(crs.Reference) > 0 || len(crs.Documentation) > 0 || len(crs.Caller) > 0 {
			reply.CrossReferences[crs.Ticket] = crs
		}
	}

	if pageToken+stats.total != sumTotalCrossRefs(reply.Total) && stats.total != 0 {
		nextToken = &ipb.PageToken{Index: int32(pageToken + stats.total)}
	}

	if len(req.Filter) > 0 {
		er, err := t.edges(ctx, edgesRequest{
			Tickets:   tickets,
			Filters:   req.Filter,
			Kinds:     func(kind string) bool { return !edges.IsAnchorEdge(kind) },
			PageToken: edgesPageToken,
			TotalOnly: (stats.max <= stats.total),
			PageSize:  stats.max - stats.total,
		})
		if err != nil {
			return nil, fmt.Errorf("error getting related nodes: %v", err)
		}
		reply.Total.RelatedNodesByRelation = er.TotalEdgesByKind
		for ticket, es := range er.EdgeSets {
			var nodes stringset.Set
			crs, ok := reply.CrossReferences[ticket]
			if !ok {
				crs = &xpb.CrossReferencesReply_CrossReferenceSet{
					Ticket: ticket,
				}
			}
			for kind, g := range es.Groups {
				for _, edge := range g.Edge {
					nodes.Add(edge.TargetTicket)
					crs.RelatedNode = append(crs.RelatedNode, &xpb.CrossReferencesReply_RelatedNode{
						RelationKind: kind,
						Ticket:       edge.TargetTicket,
						Ordinal:      edge.Ordinal,
					})
				}
			}
			if len(nodes) > 0 {
				for ticket, n := range er.Nodes {
					if nodes.Contains(ticket) {
						reply.Nodes[ticket] = n
					}
				}
			}

			if !ok && len(crs.RelatedNode) > 0 {
				reply.CrossReferences[ticket] = crs
			}
		}

		if er.NextPageToken != "" {
			nextToken = &ipb.PageToken{SecondaryToken: er.NextPageToken}
		}
	}

	if nextToken != nil {
		rec, err := proto.Marshal(nextToken)
		if err != nil {
			return nil, fmt.Errorf("internal error: error marshalling page token: %v", err)
		}
		reply.NextPageToken = base64.StdEncoding.EncodeToString(rec)
	}

	if req.NodeDefinitions {
		nodeTickets := make([]string, 0, len(reply.Nodes))
		for ticket := range reply.Nodes {
			nodeTickets = append(nodeTickets, ticket)
		}

		// TODO(schroederc): cache this in the serving data
		defs, err := xrefs.SlowDefinitions(ctx, t, nodeTickets)
		if err != nil {
			return nil, fmt.Errorf("error retrieving node definitions: %v", err)
		}

		reply.DefinitionLocations = make(map[string]*xpb.Anchor, len(defs))
		for ticket, def := range defs {
			node, ok := reply.Nodes[ticket]
			if !ok {
				panic(fmt.Sprintf("extra definition returned for unknown node %q: %v", ticket, def))
			}
			node.Definition = def.Ticket
			if _, ok := reply.DefinitionLocations[def.Ticket]; !ok {
				reply.DefinitionLocations[def.Ticket] = def
			}
		}
	}

	return reply, nil
}
Пример #3
0
// Decorations implements part of the xrefs Service interface.
func (t *Table) 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]*cpb.NodeInfo)

		var seenTarget stringset.Set

		// Reference.TargetTicket -> NodeInfo (superset of reply.Nodes)
		var nodes map[string]*cpb.NodeInfo
		if len(patterns) > 0 {
			nodes = make(map[string]*cpb.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
			}
		}

		var bindings []string

		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 = ""
					}

					if req.ExtendsOverrides && r.Kind == edges.DefinesBinding {
						bindings = append(bindings, r.TargetTicket)
					}

					reply.Reference = append(reply.Reference, r)

					if !seenTarget.Contains(r.TargetTicket) && nodes != nil {
						reply.Nodes[r.TargetTicket] = nodes[r.TargetTicket]
						seenTarget.Add(r.TargetTicket)
					}
				}
			}
		}

		var extendsOverrides map[string][]*xpb.DecorationsReply_Override
		var extendsOverridesTargets stringset.Set
		if len(bindings) != 0 {
			extendsOverrides, err = xrefs.SlowOverrides(ctx, t, bindings)
			if err != nil {
				return nil, fmt.Errorf("lookup error for overrides tickets: %v", err)
			}
			if len(extendsOverrides) != 0 {
				reply.ExtendsOverrides = make(map[string]*xpb.DecorationsReply_Overrides, len(extendsOverrides))
				for ticket, eos := range extendsOverrides {
					// Note: extendsOverrides goes out of scope after this loop, so downstream code won't accidentally
					// mutate reply.ExtendsOverrides via aliasing.
					pb := &xpb.DecorationsReply_Overrides{Override: eos}
					for _, eo := range eos {
						extendsOverridesTargets.Add(eo.Ticket)
					}
					reply.ExtendsOverrides[ticket] = pb
				}
			}
		}

		if len(extendsOverridesTargets) != 0 && len(patterns) > 0 {
			// Add missing NodeInfo.
			request := &gpb.NodesRequest{Filter: req.Filter}
			for ticket := range extendsOverridesTargets {
				if _, ok := reply.Nodes[ticket]; !ok {
					request.Ticket = append(request.Ticket, ticket)
				}
			}
			if len(request.Ticket) > 0 {
				nodes, err := t.Nodes(ctx, request)
				if err != nil {
					return nil, fmt.Errorf("lookup error for overrides nodes: %v", err)
				}
				for ticket, node := range nodes.Nodes {
					reply.Nodes[ticket] = node
				}
			}
		}

		// 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)
			}
			for ticket := range extendsOverridesTargets {
				targetTickets = append(targetTickets, ticket)
			}

			defs, err := xrefs.SlowDefinitions(ctx, t, 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 {
				if extendsOverridesTargets.Contains(tgt) {
					if _, ok := reply.DefinitionLocations[tgt]; !ok {
						reply.DefinitionLocations[tgt] = def
					}
				}
				if refsTgt, ok := refs[tgt]; ok {
					for _, ref := range refsTgt {
						if def.Ticket != ref.SourceTicket {
							ref.TargetDefinition = def.Ticket
							if _, ok := reply.DefinitionLocations[def.Ticket]; !ok {
								reply.DefinitionLocations[def.Ticket] = def
							}
						}
					}
				}
			}
		}
	}

	return reply, nil
}
Пример #4
0
func (t *Table) edges(ctx context.Context, req edgesRequest) (*gpb.EdgesReply, error) {
	stats := filterStats{
		max: int(req.PageSize),
	}
	if req.TotalOnly {
		stats.max = 0
	} else 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 ipb.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 nodeTickets stringset.Set

	rs, err := t.pagedEdgeSets(ctx, req.Tickets)
	if err != nil {
		return nil, err
	}
	defer func() {
		// drain channel in case of errors or early return
		for _ = range rs {
		}
	}()

	patterns := xrefs.ConvertFilters(req.Filters)

	reply := &gpb.EdgesReply{
		EdgeSets: make(map[string]*gpb.EdgeSet),
		Nodes:    make(map[string]*cpb.NodeInfo),

		TotalEdgesByKind: make(map[string]int64),
	}
	for r := range rs {
		if r.Err == table.ErrNoSuchKey {
			continue
		} else if r.Err != nil {
			return nil, r.Err
		}
		pes := r.PagedEdgeSet
		countEdgeKinds(pes, req.Kinds, reply.TotalEdgesByKind)

		// Don't scan the EdgeSet_Groups if we're already at the specified page_size.
		if stats.total == stats.max {
			continue
		}

		groups := make(map[string]*gpb.EdgeSet_Group)
		for _, grp := range pes.Group {
			if req.Kinds == nil || req.Kinds(grp.Kind) {
				ng, ns := stats.filter(grp)
				if ng != nil {
					for _, n := range ns {
						if len(patterns) > 0 && !nodeTickets.Contains(n.Ticket) {
							nodeTickets.Add(n.Ticket)
							reply.Nodes[n.Ticket] = nodeToInfo(patterns, n)
						}
					}
					groups[grp.Kind] = ng
					if stats.total == stats.max {
						break
					}
				}
			}
		}

		// TODO(schroederc): ensure that pes.EdgeSet.Groups and pes.PageIndexes of
		// the same kind are grouped together in the EdgesReply

		if stats.total != stats.max {
			for _, idx := range pes.PageIndex {
				if req.Kinds == nil || req.Kinds(idx.EdgeKind) {
					if stats.skipPage(idx) {
						log.Printf("Skipping EdgePage: %s", idx.PageKey)
						continue
					}

					log.Printf("Retrieving EdgePage: %s", idx.PageKey)
					ep, err := t.edgePage(ctx, idx.PageKey)
					if err == table.ErrNoSuchKey {
						return nil, fmt.Errorf("internal error: missing edge page: %q", idx.PageKey)
					} else if err != nil {
						return nil, fmt.Errorf("edge page lookup error (page key: %q): %v", idx.PageKey, err)
					}

					ng, ns := stats.filter(ep.EdgesGroup)
					if ng != nil {
						for _, n := range ns {
							if len(patterns) > 0 && !nodeTickets.Contains(n.Ticket) {
								nodeTickets.Add(n.Ticket)
								reply.Nodes[n.Ticket] = nodeToInfo(patterns, n)
							}
						}
						groups[ep.EdgesGroup.Kind] = ng
						if stats.total == stats.max {
							break
						}
					}
				}
			}
		}

		if len(groups) > 0 {
			reply.EdgeSets[pes.Source.Ticket] = &gpb.EdgeSet{Groups: groups}

			if len(patterns) > 0 && !nodeTickets.Contains(pes.Source.Ticket) {
				nodeTickets.Add(pes.Source.Ticket)
				reply.Nodes[pes.Source.Ticket] = nodeToInfo(patterns, pes.Source)
			}
		}
	}
	totalEdgesPossible := int(sumEdgeKinds(reply.TotalEdgesByKind))
	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)
	}

	if pageToken+stats.total != totalEdgesPossible && stats.total != 0 {
		rec, err := proto.Marshal(&ipb.PageToken{Index: int32(pageToken + stats.total)})
		if err != nil {
			return nil, fmt.Errorf("internal error: error marshalling page token: %v", err)
		}
		reply.NextPageToken = base64.StdEncoding.EncodeToString(rec)
	}

	return reply, nil
}
Пример #5
0
// CrossReferences implements part of the xrefs.Interface.
func (d *DB) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) {
	// TODO(zarko): Callgraph integration.
	tickets, err := xrefs.FixTickets(req.Ticket)
	if err != nil {
		return nil, err
	}

	pageSize := int(req.PageSize)
	if pageSize <= 0 {
		pageSize = defaultPageSize
	} else if pageSize > maxPageSize {
		pageSize = maxPageSize
	}

	var pageOffset int
	var edgesToken string
	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 ipb.PageToken
		if err := proto.Unmarshal(rec, &t); err != nil || t.Index < 0 {
			return nil, fmt.Errorf("invalid page_token: %q", req.PageToken)
		}
		pageOffset = int(t.Index)
		edgesToken = t.SecondaryToken
	}

	reply := &xpb.CrossReferencesReply{
		CrossReferences: make(map[string]*xpb.CrossReferencesReply_CrossReferenceSet),
		Nodes:           make(map[string]*cpb.NodeInfo),
	}

	setQ, ticketArgs := sqlSetQuery(1, tickets)

	var count int
	if edgesToken == "" {
		args := append(ticketArgs, pageSize+1, pageOffset) // +1 to check for next page

		rs, err := d.Query(fmt.Sprintf("SELECT ticket, kind, proto FROM CrossReferences WHERE ticket IN %s ORDER BY ticket LIMIT $%d OFFSET $%d;", setQ, len(tickets)+1, len(tickets)+2), args...)
		if err != nil {
			return nil, err
		}
		defer closeRows(rs)

		var xrs *xpb.CrossReferencesReply_CrossReferenceSet
		for rs.Next() {
			count++
			if count > pageSize {
				continue
			}

			var ticket, kind string
			var rec []byte
			if err := rs.Scan(&ticket, &kind, &rec); err != nil {
				return nil, err
			}
			if xrs != nil && xrs.Ticket != ticket {
				if len(xrs.Definition) > 0 || len(xrs.Documentation) > 0 || len(xrs.Reference) > 0 || len(xrs.RelatedNode) > 0 {
					reply.CrossReferences[xrs.Ticket] = xrs
				}
				xrs = nil
			}
			if xrs == nil {
				xrs = &xpb.CrossReferencesReply_CrossReferenceSet{Ticket: ticket}
			}
			switch {
			// TODO(schroederc): handle declarations
			case xrefs.IsDefKind(req.DefinitionKind, kind, false):
				xrs.Definition, err = addRelatedAnchor(xrs.Definition, rec, req.AnchorText)
				if err != nil {
					return nil, err
				}
			case xrefs.IsDocKind(req.DocumentationKind, kind):
				xrs.Documentation, err = addRelatedAnchor(xrs.Documentation, rec, req.AnchorText)
				if err != nil {
					return nil, err
				}
			case xrefs.IsRefKind(req.ReferenceKind, kind):
				xrs.Reference, err = addRelatedAnchor(xrs.Reference, rec, req.AnchorText)
				if err != nil {
					return nil, err
				}
			}
		}
		if xrs != nil && (len(xrs.Definition) > 0 || len(xrs.Documentation) > 0 || len(xrs.Reference) > 0 || len(xrs.RelatedNode) > 0) {
			reply.CrossReferences[xrs.Ticket] = xrs
		}

		if count > pageSize {
			rec, err := proto.Marshal(&ipb.PageToken{Index: int32(pageOffset + pageSize)})
			if err != nil {
				return nil, fmt.Errorf("internal error: error marshalling page token: %v", err)
			}
			reply.NextPageToken = base64.StdEncoding.EncodeToString(rec)
		}
	}

	if len(req.Filter) > 0 && count <= pageSize {
		// TODO(schroederc): consolidate w/ LevelDB implementation
		er, err := d.edges(ctx, &gpb.EdgesRequest{
			Ticket:    tickets,
			Filter:    req.Filter,
			PageSize:  int32(pageSize - count),
			PageToken: edgesToken,
		}, func(kind string) bool {
			return !edges.IsAnchorEdge(kind)
		})
		if err != nil {
			return nil, fmt.Errorf("error getting related nodes: %v", err)
		}

		for ticket, es := range er.EdgeSets {
			var nodes stringset.Set
			crs, ok := reply.CrossReferences[ticket]
			if !ok {
				crs = &xpb.CrossReferencesReply_CrossReferenceSet{
					Ticket: ticket,
				}
			}
			for kind, g := range es.Groups {
				if !edges.IsAnchorEdge(kind) {
					for _, edge := range g.Edge {
						nodes.Add(edge.TargetTicket)
						crs.RelatedNode = append(crs.RelatedNode, &xpb.CrossReferencesReply_RelatedNode{
							RelationKind: kind,
							Ticket:       edge.TargetTicket,
							Ordinal:      edge.Ordinal,
						})
					}
				}
			}
			if len(nodes) > 0 {
				for ticket, n := range er.Nodes {
					if nodes.Contains(ticket) {
						reply.Nodes[ticket] = n
					}
				}
			}

			if !ok && len(crs.RelatedNode) > 0 {
				reply.CrossReferences[ticket] = crs
			}
		}

		if er.NextPageToken != "" {
			rec, err := proto.Marshal(&ipb.PageToken{SecondaryToken: er.NextPageToken})
			if err != nil {
				return nil, fmt.Errorf("internal error: error marshalling page token: %v", err)
			}
			reply.NextPageToken = base64.StdEncoding.EncodeToString(rec)
		}
	}

	return reply, nil
}