feat: Add id for CodeGraphData (#63337)

This allows locating a CodeGraphData object later so that the occurrences
can be paginated over correctly.

Fixes GRAPH-692
This commit is contained in:
Varun Gandhi 2024-06-20 01:03:10 +08:00 committed by GitHub
parent 8b5eec2301
commit d378d73d2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 122 additions and 4 deletions

View File

@ -98,7 +98,13 @@ EXPERIMENTAL: This type may change in a backwards-incompatible way.
TODO(issue: GRAPH-614): 'commit' field should have type GitCommit
before stabilizing this API.
"""
type CodeGraphData {
type CodeGraphData implements Node {
"""
ID of this object that can be used to identify the CodeGraphData
for pagination in the codeGraphData field.
"""
id: ID!
"""
Coarse description of the data source for this code graph data.
"""

View File

@ -381,3 +381,8 @@ func (r *NodeResolver) ToSearchJob() (SearchJobResolver, bool) {
n, ok := r.Node.(SearchJobResolver)
return n, ok
}
func (r *NodeResolver) ToCodeGraphData() (resolverstubs.CodeGraphDataResolver, bool) {
n, ok := r.Node.(resolverstubs.CodeGraphDataResolver)
return n, ok
}

View File

@ -43,6 +43,7 @@ go_library(
"//lib/errors",
"//lib/pointers",
"@com_github_graph_gophers_graphql_go//:graphql-go",
"@com_github_graph_gophers_graphql_go//relay",
"@com_github_sourcegraph_go_lsp//:go-lsp",
"@com_github_sourcegraph_log//:log",
"@com_github_sourcegraph_scip//bindings/go/scip",

View File

@ -5,11 +5,14 @@ import (
"fmt"
"sync"
"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
orderedmap "github.com/wk8/go-ordered-map/v2"
"go.opentelemetry.io/otel/attribute"
"github.com/sourcegraph/scip/bindings/go/scip"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/authz"
"github.com/sourcegraph/sourcegraph/internal/codeintel/codenav"
resolverstubs "github.com/sourcegraph/sourcegraph/internal/codeintel/resolvers"
@ -159,6 +162,10 @@ func (r *rootResolver) CodeGraphData(ctx context.Context, opts *resolverstubs.Co
return &[]resolverstubs.CodeGraphDataResolver{}, nil
}
func (r *rootResolver) CodeGraphDataByID(ctx context.Context, id graphql.ID) (resolverstubs.CodeGraphDataResolver, error) {
return newCodeGraphDataResolverFromID(ctx, r.repoStore, r.svc, r.operations, id)
}
func preferUploadsWithLongestRoots(uploads []shared.CompletedUpload) []shared.CompletedUpload {
// Use orderedmap instead of a map to preserve the order of the uploads
// and to avoid introducing non-determinism.
@ -317,7 +324,7 @@ type codeGraphDataResolver struct {
// Arguments
svc CodeNavService
upload shared.CompletedUpload
upload UploadData
opts *resolverstubs.CodeGraphDataOpts
provenance resolverstubs.CodeGraphDataProvenance
@ -325,6 +332,26 @@ type codeGraphDataResolver struct {
operations *operations
}
// UploadData represents the subset of information of shared.CompletedUpload
// that we actually care about for the purposes of the GraphQL API.
//
// All fields are left public for JSON marshaling/unmarshaling.
type UploadData struct {
UploadID int
Commit string
Indexer string
IndexerVersion string
}
func NewUploadData(upload shared.CompletedUpload) UploadData {
return UploadData{
UploadID: upload.ID,
Commit: upload.Commit,
Indexer: upload.Indexer,
IndexerVersion: upload.IndexerVersion,
}
}
func newCodeGraphDataResolver(
svc CodeNavService,
upload shared.CompletedUpload,
@ -337,23 +364,81 @@ func newCodeGraphDataResolver(
/*document*/ nil,
/*documentRetrievalError*/ nil,
svc,
upload,
NewUploadData(upload),
opts,
provenance,
operations,
}
}
// CodeGraphDataID represents the serializable state needed to materialize
// a CodeGraphData value from an opaque GraphQL ID.
//
// All fields are left public for JSON marshaling/unmarshaling.
type CodeGraphDataID struct {
UploadData
Args *resolverstubs.CodeGraphDataArgs
api.RepoID
Commit api.CommitID
Path string
resolverstubs.CodeGraphDataProvenance
}
func newCodeGraphDataResolverFromID(
ctx context.Context,
repoStore database.RepoStore,
svc CodeNavService,
operations *operations,
rawID graphql.ID,
) (resolverstubs.CodeGraphDataResolver, error) {
var id CodeGraphDataID
if err := relay.UnmarshalSpec(rawID, &id); err != nil {
return nil, errors.Wrap(err, "malformed ID")
}
repos, err := repoStore.GetByIDs(ctx, id.RepoID)
if err != nil {
return nil, errors.Wrap(err, "repo for CodeGraphData value no longer exists")
}
opts := resolverstubs.CodeGraphDataOpts{
Args: id.Args,
Repo: repos[0],
Commit: id.Commit,
Path: id.Path,
}
return &codeGraphDataResolver{
sync.Once{},
/*document*/ nil,
/*documentRetrievalError*/ nil,
svc,
id.UploadData,
&opts,
id.CodeGraphDataProvenance,
operations,
}, nil
}
func (c *codeGraphDataResolver) tryRetrieveDocument(ctx context.Context) (*scip.Document, error) {
// NOTE(id: scip-doc-optimization): In the case of pagination, if we retrieve the document ID
// from the database, we can avoid performing a JOIN between codeintel_scip_document_lookup
// and codeintel_scip_documents
c.retrievedDocument.Do(func() {
c.document, c.documentRetrievalError = c.svc.SCIPDocument(ctx, c.upload.ID, c.opts.Path)
c.document, c.documentRetrievalError = c.svc.SCIPDocument(ctx, c.upload.UploadID, c.opts.Path)
})
return c.document, c.documentRetrievalError
}
func (c *codeGraphDataResolver) ID() graphql.ID {
dataID := CodeGraphDataID{
c.upload,
c.opts.Args,
c.opts.Repo.ID,
c.opts.Commit,
c.opts.Path,
c.provenance,
}
return relay.MarshalID(resolverstubs.CodeGraphDataIDKind, dataID)
}
func (c *codeGraphDataResolver) Provenance(_ context.Context) (resolverstubs.CodeGraphDataProvenance, error) {
return c.provenance, nil
}

View File

@ -31,9 +31,13 @@ type CodeNavServiceResolver interface {
// that it is not what is exactly provided as input from the GraphQL
// client.
CodeGraphData(ctx context.Context, opts *CodeGraphDataOpts) (*[]CodeGraphDataResolver, error)
// CodeGraphDataByID materializes a CodeGraphDataResolver purely from a graphql.ID.
CodeGraphDataByID(ctx context.Context, id graphql.ID) (CodeGraphDataResolver, error)
UsagesForSymbol(ctx context.Context, args *UsagesForSymbolArgs) (UsageConnectionResolver, error)
}
const CodeGraphDataIDKind = "CodeGraphData"
type GitBlobLSIFDataArgs struct {
Repo *types.Repo
Commit api.CommitID
@ -161,6 +165,8 @@ type DiagnosticResolver interface {
}
type CodeGraphDataResolver interface {
// ID satisfies the Node interface.
ID() graphql.ID
Provenance(ctx context.Context) (CodeGraphDataProvenance, error)
Commit(ctx context.Context) (string, error)
ToolInfo(ctx context.Context) (*CodeGraphToolInfo, error)
@ -168,6 +174,10 @@ type CodeGraphDataResolver interface {
Occurrences(ctx context.Context, args *OccurrencesArgs) (SCIPOccurrenceConnectionResolver, error)
}
// CodeGraphDataProvenance corresponds to the matching type in the GraphQL API.
//
// Make sure this type maintains its marshaling/unmarshaling behavior in
// case the type definition is changed.
type CodeGraphDataProvenance string
const (
@ -192,6 +202,10 @@ func (f *CodeGraphDataFilter) String() string {
return ""
}
// CodeGraphDataArgs represents the arguments to the codeGraphData(...)
// field on GitBlob in the GraphQL API.
//
// All fields are left public for JSON marshaling/unmarshaling.
type CodeGraphDataArgs struct {
Filter *CodeGraphDataFilter
}

View File

@ -63,6 +63,9 @@ func (r *Resolver) NodeResolvers() map[string]NodeByIDFunc {
"PreciseIndex": func(ctx context.Context, id graphql.ID) (Node, error) {
return r.uploadsRootResolver.PreciseIndexByID(ctx, id)
},
CodeGraphDataIDKind: func(ctx context.Context, id graphql.ID) (Node, error) {
return r.codenavResolver.CodeGraphDataByID(ctx, id)
},
}
}
@ -129,6 +132,10 @@ func (r *Resolver) CodeGraphData(ctx context.Context, opts *CodeGraphDataOpts) (
return r.codenavResolver.CodeGraphData(ctx, opts)
}
func (r *Resolver) CodeGraphDataByID(ctx context.Context, id graphql.ID) (CodeGraphDataResolver, error) {
return r.codenavResolver.CodeGraphDataByID(ctx, id)
}
func (r *Resolver) UsagesForSymbol(ctx context.Context, args *UsagesForSymbolArgs) (UsageConnectionResolver, error) {
return r.codenavResolver.UsagesForSymbol(ctx, args)
}