From d378d73d2b07b1e7ae8ee87b1684fb74caf41c26 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 20 Jun 2024 01:03:10 +0800 Subject: [PATCH] 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 --- .../graphqlbackend/codeintel.codenav.graphql | 8 +- cmd/frontend/graphqlbackend/node.go | 5 + .../codenav/transport/graphql/BUILD.bazel | 1 + .../transport/graphql/root_resolver.go | 91 ++++++++++++++++++- internal/codeintel/resolvers/codenav.go | 14 +++ internal/codeintel/resolvers/root_resolver.go | 7 ++ 6 files changed, 122 insertions(+), 4 deletions(-) diff --git a/cmd/frontend/graphqlbackend/codeintel.codenav.graphql b/cmd/frontend/graphqlbackend/codeintel.codenav.graphql index 7f943a896ec..f6fb606f7a9 100644 --- a/cmd/frontend/graphqlbackend/codeintel.codenav.graphql +++ b/cmd/frontend/graphqlbackend/codeintel.codenav.graphql @@ -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. """ diff --git a/cmd/frontend/graphqlbackend/node.go b/cmd/frontend/graphqlbackend/node.go index 890f8d83e79..c6343e3803e 100644 --- a/cmd/frontend/graphqlbackend/node.go +++ b/cmd/frontend/graphqlbackend/node.go @@ -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 +} diff --git a/internal/codeintel/codenav/transport/graphql/BUILD.bazel b/internal/codeintel/codenav/transport/graphql/BUILD.bazel index 9e6dc2e587f..87399b2f95c 100644 --- a/internal/codeintel/codenav/transport/graphql/BUILD.bazel +++ b/internal/codeintel/codenav/transport/graphql/BUILD.bazel @@ -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", diff --git a/internal/codeintel/codenav/transport/graphql/root_resolver.go b/internal/codeintel/codenav/transport/graphql/root_resolver.go index f7989b806ce..6afd0507bf6 100644 --- a/internal/codeintel/codenav/transport/graphql/root_resolver.go +++ b/internal/codeintel/codenav/transport/graphql/root_resolver.go @@ -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 } diff --git a/internal/codeintel/resolvers/codenav.go b/internal/codeintel/resolvers/codenav.go index 259556cfd8d..6f6645eb93d 100644 --- a/internal/codeintel/resolvers/codenav.go +++ b/internal/codeintel/resolvers/codenav.go @@ -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 } diff --git a/internal/codeintel/resolvers/root_resolver.go b/internal/codeintel/resolvers/root_resolver.go index bcfd093c509..be128335b02 100644 --- a/internal/codeintel/resolvers/root_resolver.go +++ b/internal/codeintel/resolvers/root_resolver.go @@ -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) }