Skip to content
This repository was archived by the owner on Aug 28, 2025. It is now read-only.

Commit c2bca5b

Browse files
committed
limit relation resolver by getItem only
On-behalf-of: @SAP [email protected] Signed-off-by: Artem Shcherbatiuk <[email protected]>
1 parent f286765 commit c2bca5b

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

gateway/resolver/relations.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package resolver
22

33
import (
44
"context"
5+
"strings"
56

67
"github.com/graphql-go/graphql"
8+
"go.opentelemetry.io/otel/trace"
79
"golang.org/x/text/cases"
810
"golang.org/x/text/language"
911
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -21,8 +23,30 @@ type referenceInfo struct {
2123
}
2224

2325
// RelationResolver creates a GraphQL resolver for relation fields
26+
// Relationships are only enabled for GetItem queries to prevent N+1 problems in ListItems and Subscriptions
2427
func (r *Service) RelationResolver(fieldName string, gvk schema.GroupVersionKind) graphql.FieldResolveFn {
2528
return func(p graphql.ResolveParams) (interface{}, error) {
29+
// Try context first, fallback to GraphQL info analysis
30+
operation := r.getOperationFromContext(p.Context)
31+
if operation == "unknown" {
32+
operation = r.detectOperationFromGraphQLInfo(p)
33+
}
34+
35+
r.log.Debug().
36+
Str("fieldName", fieldName).
37+
Str("operation", operation).
38+
Str("graphqlField", p.Info.FieldName).
39+
Msg("RelationResolver called")
40+
41+
// Check if relationships are allowed in this query context
42+
if !r.isRelationResolutionAllowedForOperation(operation) {
43+
r.log.Debug().
44+
Str("fieldName", fieldName).
45+
Str("operation", operation).
46+
Msg("Relationship resolution disabled for this operation type")
47+
return nil, nil
48+
}
49+
2650
parentObj, ok := p.Source.(map[string]any)
2751
if !ok {
2852
return nil, nil
@@ -109,3 +133,91 @@ func (r *Service) resolveReference(ctx context.Context, ref referenceInfo, targe
109133
// Happy path: resource found successfully
110134
return obj.Object, nil
111135
}
136+
137+
// isRelationResolutionAllowed checks if relationship resolution should be enabled for this operation
138+
// Only allows relationships in GetItem operations to prevent N+1 problems
139+
func (r *Service) isRelationResolutionAllowed(ctx context.Context) bool {
140+
operation := r.getOperationFromContext(ctx)
141+
return r.isRelationResolutionAllowedForOperation(operation)
142+
}
143+
144+
// isRelationResolutionAllowedForOperation checks if relationship resolution should be enabled for the given operation type
145+
func (r *Service) isRelationResolutionAllowedForOperation(operation string) bool {
146+
// Only allow relationships for GetItem and GetItemAsYAML operations
147+
switch operation {
148+
case "GetItem", "GetItemAsYAML":
149+
return true
150+
case "ListItems", "SubscribeItem", "SubscribeItems":
151+
return false
152+
default:
153+
// For unknown operations, be conservative and disable relationships
154+
r.log.Debug().Str("operation", operation).Msg("Unknown operation type, disabling relationships")
155+
return false
156+
}
157+
}
158+
159+
// Context key for tracking operation type
160+
type operationContextKey string
161+
162+
const OperationTypeKey operationContextKey = "operation_type"
163+
164+
// getOperationFromContext extracts the operation name from the context
165+
func (r *Service) getOperationFromContext(ctx context.Context) string {
166+
// Try to get operation from context value first
167+
if op, ok := ctx.Value(OperationTypeKey).(string); ok {
168+
return op
169+
}
170+
171+
// Fallback: try to extract from trace span name
172+
span := trace.SpanFromContext(ctx)
173+
if span == nil {
174+
return "unknown"
175+
}
176+
177+
// This is a workaround - we'll need to get the span name somehow
178+
// For now, assume unknown and rely on context values
179+
return "unknown"
180+
}
181+
182+
// detectOperationFromGraphQLInfo analyzes GraphQL field path to determine operation type
183+
// This looks at the parent field context to determine if we're in a list, single item, or subscription
184+
func (r *Service) detectOperationFromGraphQLInfo(p graphql.ResolveParams) string {
185+
if p.Info.Path == nil {
186+
return "unknown"
187+
}
188+
189+
// Walk up the path to find the parent resolver context
190+
path := p.Info.Path
191+
for path.Prev != nil {
192+
path = path.Prev
193+
194+
// Check if we find a parent field that indicates the operation type
195+
if fieldName, ok := path.Key.(string); ok {
196+
fieldLower := strings.ToLower(fieldName)
197+
198+
// Check for subscription patterns
199+
if strings.Contains(fieldLower, "subscription") {
200+
r.log.Debug().
201+
Str("parentField", fieldName).
202+
Msg("Detected subscription context from parent field")
203+
return "SubscribeItems"
204+
}
205+
206+
// Check for list patterns (plural without args, or explicitly plural fields)
207+
if strings.HasSuffix(fieldName, "s") && !strings.HasSuffix(fieldName, "Status") {
208+
// This looks like a plural field, likely a list operation
209+
r.log.Debug().
210+
Str("parentField", fieldName).
211+
Msg("Detected list context from parent field")
212+
return "ListItems"
213+
}
214+
}
215+
}
216+
217+
// If we can't determine from parent context, assume it's a single item operation
218+
// This is the safe default that allows relationships
219+
r.log.Debug().
220+
Str("currentField", p.Info.FieldName).
221+
Msg("Could not determine operation from path, defaulting to GetItem")
222+
return "GetItem"
223+
}

gateway/resolver/resolver.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package resolver
22

33
import (
44
"bytes"
5+
"context"
56
"encoding/json"
67
"errors"
78
"fmt"
@@ -68,6 +69,11 @@ func (r *Service) ListItems(gvk schema.GroupVersionKind, scope v1.ResourceScope)
6869
ctx, span := otel.Tracer("").Start(p.Context, "ListItems", trace.WithAttributes(attribute.String("kind", gvk.Kind)))
6970
defer span.End()
7071

72+
// Add operation type to context to disable relationship resolution
73+
ctx = context.WithValue(ctx, operationContextKey("operation_type"), "ListItems")
74+
// Update p.Context so field resolvers inherit the operation type
75+
p.Context = ctx
76+
7177
gvk.Group = r.getOriginalGroupName(gvk.Group)
7278

7379
log, err := r.log.ChildLoggerWithAttributes(
@@ -142,6 +148,11 @@ func (r *Service) GetItem(gvk schema.GroupVersionKind, scope v1.ResourceScope) g
142148
ctx, span := otel.Tracer("").Start(p.Context, "GetItem", trace.WithAttributes(attribute.String("kind", gvk.Kind)))
143149
defer span.End()
144150

151+
// Add operation type to context to enable relationship resolution
152+
ctx = context.WithValue(ctx, operationContextKey("operation_type"), "GetItem")
153+
// Update p.Context so field resolvers inherit the operation type
154+
p.Context = ctx
155+
145156
gvk.Group = r.getOriginalGroupName(gvk.Group)
146157

147158
log, err := r.log.ChildLoggerWithAttributes(
@@ -195,6 +206,9 @@ func (r *Service) GetItemAsYAML(gvk schema.GroupVersionKind, scope v1.ResourceSc
195206
p.Context, span = otel.Tracer("").Start(p.Context, "GetItemAsYAML", trace.WithAttributes(attribute.String("kind", gvk.Kind)))
196207
defer span.End()
197208

209+
// Add operation type to context to enable relationship resolution
210+
p.Context = context.WithValue(p.Context, operationContextKey("operation_type"), "GetItemAsYAML")
211+
198212
out, err := r.GetItem(gvk, scope)(p)
199213
if err != nil {
200214
return "", err

gateway/resolver/subscription.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package resolver
22

33
import (
4+
"context"
45
"fmt"
56
"reflect"
67
"sort"
@@ -32,6 +33,10 @@ func (r *Service) SubscribeItem(gvk schema.GroupVersionKind, scope v1.ResourceSc
3233
return func(p graphql.ResolveParams) (interface{}, error) {
3334
_, span := otel.Tracer("").Start(p.Context, "SubscribeItem", trace.WithAttributes(attribute.String("kind", gvk.Kind)))
3435
defer span.End()
36+
37+
// Add operation type to context to disable relationship resolution
38+
p.Context = context.WithValue(p.Context, operationContextKey("operation_type"), "SubscribeItem")
39+
3540
resultChannel := make(chan interface{})
3641
go r.runWatch(p, gvk, resultChannel, true, scope)
3742

@@ -43,6 +48,10 @@ func (r *Service) SubscribeItems(gvk schema.GroupVersionKind, scope v1.ResourceS
4348
return func(p graphql.ResolveParams) (interface{}, error) {
4449
_, span := otel.Tracer("").Start(p.Context, "SubscribeItems", trace.WithAttributes(attribute.String("kind", gvk.Kind)))
4550
defer span.End()
51+
52+
// Add operation type to context to disable relationship resolution
53+
p.Context = context.WithValue(p.Context, operationContextKey("operation_type"), "SubscribeItems")
54+
4655
resultChannel := make(chan interface{})
4756
go r.runWatch(p, gvk, resultChannel, false, scope)
4857

@@ -353,6 +362,8 @@ func CreateSubscriptionResolver(isSingle bool) graphql.FieldResolveFn {
353362
return nil, err
354363
}
355364

365+
// Note: The context already contains operation type from SubscribeItem/SubscribeItems
366+
// This will propagate to relationship resolvers, disabling them for subscriptions
356367
return source, nil
357368
}
358369
}

0 commit comments

Comments
 (0)