func edgeKindLess(kind1, kind2 string) bool { // General ordering: // anchor edge kinds before non-anchor edge kinds // forward edges before reverse edges // edgeOrdering[i] (and variants) before edgeOrdering[i+1:] // edge variants after root edge kind (ordered lexicographically) // otherwise, order lexicographically if kind1 == kind2 { return false } else if a1, a2 := schema.IsAnchorEdge(kind1), schema.IsAnchorEdge(kind2); a1 != a2 { return a1 } else if d1, d2 := schema.EdgeDirection(kind1), schema.EdgeDirection(kind2); d1 != d2 { return d1 == schema.Forward } kind1, kind2 = schema.Canonicalize(kind1), schema.Canonicalize(kind2) for _, kind := range edgeOrdering { if kind1 == kind { return true } else if kind2 == kind { return false } else if v1, v2 := schema.IsEdgeVariant(kind1, kind), schema.IsEdgeVariant(kind2, kind); v1 != v2 { return v1 } else if v1 { return kind1 < kind2 } } return kind1 < kind2 }
// CrossReferences implements part of the xrefs.Service interface. func (t *tableImpl) 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]*xpb.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) } 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 !schema.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 { nodes := stringset.New() 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(t, ctx, 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 }
// CrossReferences implements part of the xrefs Service interface. func (g *GraphStoreService) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) { if len(req.Ticket) == 0 { return nil, errors.New("no cross-references requested") } requestedPageSize := int(req.PageSize) if requestedPageSize == 0 { requestedPageSize = defaultXRefPageSize } eReply, err := g.Edges(ctx, &xpb.EdgesRequest{ Ticket: req.Ticket, PageSize: int32(requestedPageSize), PageToken: req.PageToken, }) if err != nil { return nil, fmt.Errorf("error getting edges for cross-references: %v", err) } reply := &xpb.CrossReferencesReply{ CrossReferences: make(map[string]*xpb.CrossReferencesReply_CrossReferenceSet), NextPageToken: eReply.NextPageToken, } var allRelatedNodes stringset.Set if len(req.Filter) > 0 { reply.Nodes = make(map[string]*xpb.NodeInfo) allRelatedNodes = stringset.New() } // Cache parent files across all anchors files := make(map[string]*fileNode) var totalXRefs int for { for source, es := range eReply.EdgeSets { xr, ok := reply.CrossReferences[source] if !ok { xr = &xpb.CrossReferencesReply_CrossReferenceSet{Ticket: source} } var count int for kind, grp := range es.Groups { switch { // TODO(schroeder): handle declarations case xrefs.IsDefKind(req.DefinitionKind, kind, false): anchors, err := completeAnchors(ctx, g, req.AnchorText, files, kind, edgeTickets(grp.Edge)) if err != nil { return nil, fmt.Errorf("error resolving definition anchors: %v", err) } count += len(anchors) xr.Definition = append(xr.Definition, anchors...) case xrefs.IsRefKind(req.ReferenceKind, kind): anchors, err := completeAnchors(ctx, g, req.AnchorText, files, kind, edgeTickets(grp.Edge)) if err != nil { return nil, fmt.Errorf("error resolving reference anchors: %v", err) } count += len(anchors) xr.Reference = append(xr.Reference, anchors...) case xrefs.IsDocKind(req.DocumentationKind, kind): anchors, err := completeAnchors(ctx, g, req.AnchorText, files, kind, edgeTickets(grp.Edge)) if err != nil { return nil, fmt.Errorf("error resolving documentation anchors: %v", err) } count += len(anchors) xr.Documentation = append(xr.Documentation, anchors...) case allRelatedNodes != nil && !schema.IsAnchorEdge(kind): count += len(grp.Edge) for _, edge := range grp.Edge { xr.RelatedNode = append(xr.RelatedNode, &xpb.CrossReferencesReply_RelatedNode{ Ticket: edge.TargetTicket, RelationKind: kind, Ordinal: edge.Ordinal, }) allRelatedNodes.Add(edge.TargetTicket) } } } if count > 0 { reply.CrossReferences[xr.Ticket] = xr totalXRefs += count } } if reply.NextPageToken == "" || totalXRefs > 0 { break } // We need to return at least 1 xref, if there are any log.Println("Extra CrossReferences Edges call: ", reply.NextPageToken) eReply, err = g.Edges(ctx, &xpb.EdgesRequest{ Ticket: req.Ticket, PageSize: int32(requestedPageSize), PageToken: reply.NextPageToken, }) if err != nil { return nil, fmt.Errorf("error getting edges for cross-references: %v", err) } reply.NextPageToken = eReply.NextPageToken } if len(allRelatedNodes) > 0 { nReply, err := g.Nodes(ctx, &xpb.NodesRequest{ Ticket: allRelatedNodes.Slice(), Filter: req.Filter, }) if err != nil { return nil, fmt.Errorf("error retrieving related nodes: %v", err) } for ticket, n := range nReply.Nodes { reply.Nodes[ticket] = n } } return reply, nil }
// CrossReferences implements part of the xrefs.Interface. func (d *DB) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) { 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]*xpb.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, &xpb.EdgesRequest{ Ticket: tickets, Filter: req.Filter, PageSize: int32(pageSize - count), PageToken: edgesToken, }, func(kind string) bool { return !schema.IsAnchorEdge(kind) }) if err != nil { return nil, fmt.Errorf("error getting related nodes: %v", err) } for ticket, es := range er.EdgeSets { nodes := stringset.New() crs, ok := reply.CrossReferences[ticket] if !ok { crs = &xpb.CrossReferencesReply_CrossReferenceSet{ Ticket: ticket, } } for kind, g := range es.Groups { if !schema.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 }
// CrossReferences returns the cross-references for the given tickets using the // given NodesEdgesService. func CrossReferences(ctx context.Context, xs NodesEdgesService, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) { log.Println("WARNING: using experimental CrossReferences API") if len(req.Ticket) == 0 { return nil, errors.New("no cross-references requested") } requestedPageSize := int(req.PageSize) if requestedPageSize == 0 { requestedPageSize = defaultXRefPageSize } eReply, err := xs.Edges(ctx, &xpb.EdgesRequest{ Ticket: req.Ticket, PageSize: int32(requestedPageSize), PageToken: req.PageToken, }) if err != nil { return nil, fmt.Errorf("error getting edges for cross-references: %v", err) } reply := &xpb.CrossReferencesReply{ CrossReferences: make(map[string]*xpb.CrossReferencesReply_CrossReferenceSet), NextPageToken: eReply.NextPageToken, } var allRelatedNodes stringset.Set if len(req.Filter) > 0 { reply.Nodes = make(map[string]*xpb.NodeInfo) allRelatedNodes = stringset.New() } // Cache parent files across all anchors files := make(map[string]*fileNode) var totalXRefs int for { for _, es := range eReply.EdgeSet { xr, ok := reply.CrossReferences[es.SourceTicket] if !ok { xr = &xpb.CrossReferencesReply_CrossReferenceSet{Ticket: es.SourceTicket} } var count int for _, g := range es.Group { switch { case isDefKind(req.DefinitionKind, g.Kind): anchors, err := completeAnchors(ctx, xs, req.AnchorText, files, g.Kind, g.TargetTicket) if err != nil { return nil, fmt.Errorf("error resolving definition anchors: %v", err) } count += len(anchors) xr.Definition = append(xr.Definition, anchors...) case isRefKind(req.ReferenceKind, g.Kind): anchors, err := completeAnchors(ctx, xs, req.AnchorText, files, g.Kind, g.TargetTicket) if err != nil { return nil, fmt.Errorf("error resolving reference anchors: %v", err) } count += len(anchors) xr.Reference = append(xr.Reference, anchors...) case isDocKind(req.DocumentationKind, g.Kind): anchors, err := completeAnchors(ctx, xs, req.AnchorText, files, g.Kind, g.TargetTicket) if err != nil { return nil, fmt.Errorf("error resolving documentation anchors: %v", err) } count += len(anchors) xr.Documentation = append(xr.Documentation, anchors...) case allRelatedNodes != nil && !schema.IsAnchorEdge(g.Kind): count += len(g.TargetTicket) for _, target := range g.TargetTicket { xr.RelatedNode = append(xr.RelatedNode, &xpb.CrossReferencesReply_RelatedNode{ Ticket: target, RelationKind: g.Kind, }) } allRelatedNodes.Add(g.TargetTicket...) } } if count > 0 { reply.CrossReferences[xr.Ticket] = xr totalXRefs += count } } if reply.NextPageToken == "" || totalXRefs > 0 { break } // We need to return at least 1 xref, if there are any log.Println("Extra CrossReferences Edges call: ", reply.NextPageToken) eReply, err = xs.Edges(ctx, &xpb.EdgesRequest{ Ticket: req.Ticket, PageSize: int32(requestedPageSize), PageToken: reply.NextPageToken, }) if err != nil { return nil, fmt.Errorf("error getting edges for cross-references: %v", err) } reply.NextPageToken = eReply.NextPageToken } if len(allRelatedNodes) > 0 { nReply, err := xs.Nodes(ctx, &xpb.NodesRequest{ Ticket: allRelatedNodes.Slice(), Filter: req.Filter, }) if err != nil { return nil, fmt.Errorf("error retrieving related nodes: %v", err) } for _, n := range nReply.Node { reply.Nodes[n.Ticket] = n } } return reply, nil }
// CrossReferences implements part of the xrefs.Service interface. func (t *tableImpl) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) { tickets, err := 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 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) edgesPageToken = t.SecondaryToken } pageToken := stats.skip var totalRefsPossible int reply := &xpb.CrossReferencesReply{ CrossReferences: make(map[string]*xpb.CrossReferencesReply_CrossReferenceSet, len(req.Ticket)), Nodes: make(map[string]*xpb.NodeInfo, len(req.Ticket)), } var nextToken *srvpb.PageToken if edgesPageToken == "" && (req.DefinitionKind != xpb.CrossReferencesRequest_NO_DEFINITIONS || req.ReferenceKind != xpb.CrossReferencesRequest_NO_REFERENCES || req.DocumentationKind != xpb.CrossReferencesRequest_NO_DOCUMENTATION) { 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, } for _, grp := range cr.Group { if xrefs.IsDefKind(req.DefinitionKind, grp.Kind) { totalRefsPossible += len(grp.Anchor) if stats.addAnchors(&crs.Definition, grp.Anchor, req.AnchorText) { break } } else if xrefs.IsDocKind(req.DocumentationKind, grp.Kind) { totalRefsPossible += len(grp.Anchor) if stats.addAnchors(&crs.Documentation, grp.Anchor, req.AnchorText) { break } } else if xrefs.IsRefKind(req.ReferenceKind, grp.Kind) { totalRefsPossible += len(grp.Anchor) if stats.addAnchors(&crs.Reference, grp.Anchor, req.AnchorText) { break } } } if stats.total < stats.max { for _, idx := range cr.PageIndex { // TODO(schroederc): skip entire read if s.skip >= idx.Count p, err := t.crossReferencesPage(ctx, idx.PageKey) if err != nil { return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey) } if xrefs.IsDefKind(req.DefinitionKind, p.Group.Kind) { totalRefsPossible += len(p.Group.Anchor) if stats.addAnchors(&crs.Definition, p.Group.Anchor, req.AnchorText) { break } } else if xrefs.IsDocKind(req.DocumentationKind, p.Group.Kind) { totalRefsPossible += len(p.Group.Anchor) if stats.addAnchors(&crs.Documentation, p.Group.Anchor, req.AnchorText) { break } } else { totalRefsPossible += len(p.Group.Anchor) if stats.addAnchors(&crs.Reference, p.Group.Anchor, req.AnchorText) { break } } } } if len(crs.Definition) > 0 || len(crs.Reference) > 0 || len(crs.Documentation) > 0 { reply.CrossReferences[crs.Ticket] = crs } } if pageToken+stats.total != totalRefsPossible && stats.total != 0 { nextToken = &srvpb.PageToken{Index: int32(pageToken + stats.total)} } } if len(req.Filter) > 0 && stats.total < stats.max { er, err := t.edges(ctx, edgesRequest{ Tickets: tickets, Filters: req.Filter, Kinds: func(kind string) bool { return !schema.IsAnchorEdge(kind) }, PageToken: edgesPageToken, PageSize: stats.max - stats.total, }) if err != nil { return nil, fmt.Errorf("error getting related nodes: %v", err) } for _, es := range er.EdgeSet { ticket := es.SourceTicket nodes := stringset.New() crs, ok := reply.CrossReferences[ticket] if !ok { crs = &xpb.CrossReferencesReply_CrossReferenceSet{ Ticket: ticket, } } for _, g := range es.Group { if !schema.IsAnchorEdge(g.Kind) { nodes.Add(g.TargetTicket...) for _, t := range g.TargetTicket { crs.RelatedNode = append(crs.RelatedNode, &xpb.CrossReferencesReply_RelatedNode{ RelationKind: g.Kind, Ticket: t, }) } } } if len(nodes) > 0 { for _, n := range er.Node { if nodes.Contains(n.Ticket) { reply.Nodes[n.Ticket] = n } } } if !ok && len(crs.RelatedNode) > 0 { reply.CrossReferences[ticket] = crs } } if er.NextPageToken != "" { nextToken = &srvpb.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) } return reply, nil }