@@ -2,8 +2,10 @@ package resolver
2
2
3
3
import (
4
4
"context"
5
+ "strings"
5
6
6
7
"github.com/graphql-go/graphql"
8
+ "go.opentelemetry.io/otel/trace"
7
9
"golang.org/x/text/cases"
8
10
"golang.org/x/text/language"
9
11
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -21,8 +23,30 @@ type referenceInfo struct {
21
23
}
22
24
23
25
// 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
24
27
func (r * Service ) RelationResolver (fieldName string , gvk schema.GroupVersionKind ) graphql.FieldResolveFn {
25
28
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
+
26
50
parentObj , ok := p .Source .(map [string ]any )
27
51
if ! ok {
28
52
return nil , nil
@@ -109,3 +133,91 @@ func (r *Service) resolveReference(ctx context.Context, ref referenceInfo, targe
109
133
// Happy path: resource found successfully
110
134
return obj .Object , nil
111
135
}
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
+ }
0 commit comments