From 05060d50c5e15f354483cf90deee2d1392ee50b7 Mon Sep 17 00:00:00 2001 From: Robert Markovski <5818108+Roshan931@users.noreply.github.com> Date: Fri, 21 Aug 2026 19:50:18 +0200 Subject: [PATCH] fix: Unable to perform joins from a nested object using @link --- gateway/utils/graphql/graphql.go | 6 +- gateway/utils/graphql/unit_database_test.go | 85 +++++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/gateway/utils/graphql/graphql.go b/gateway/utils/graphql/graphql.go index fb0a7d53e..28cc273b3 100644 --- a/gateway/utils/graphql/graphql.go +++ b/gateway/utils/graphql/graphql.go @@ -293,12 +293,14 @@ func (graph *Module) execGraphQLDocument(ctx context.Context, node ast.Node, tok if schema != nil { fieldStruct, p := schema[field.Name.Value] - if p && fieldStruct.IsLinked { + switch { + case p && fieldStruct.IsLinked: linkedInfo := fieldStruct.LinkedTable schema, _ = graph.schema.GetSchema(linkedInfo.DBType, linkedInfo.Table) + case p && fieldStruct.Kind == model.TypeObject && fieldStruct.NestedObject != nil: + schema = fieldStruct.NestedObject } } - graph.processQueryResult(ctx, field, token, store, currentValue, schema, cb) return diff --git a/gateway/utils/graphql/unit_database_test.go b/gateway/utils/graphql/unit_database_test.go index 9d772e5f8..26a1976a6 100644 --- a/gateway/utils/graphql/unit_database_test.go +++ b/gateway/utils/graphql/unit_database_test.go @@ -3001,6 +3001,91 @@ var queryTestCases = []tests{ }, wantErr: false, wantResult: map[string]interface{}{"trainers": []interface{}{map[string]interface{}{"id": "1", "name": "ash", "pokemons": []interface{}{map[string]interface{}{"id": "1", "name": "squirtle"}, map[string]interface{}{"id": "2", "name": "pikachu"}}}, map[string]interface{}{"id": "2", "name": "james", "pokemons": []interface{}{map[string]interface{}{"id": "1", "name": "squirtle"}, map[string]interface{}{"id": "2", "name": "pikachu"}}}}}, + }, + { + name: "Query: Link present inside a nested object", + crudMockArgs: []mockArgs{ + { + method: "GetDBType", + args: []interface{}{"mg"}, + paramsReturned: []interface{}{"mongo", nil}, + }, + { + method: "IsPreparedQueryPresent", + args: []interface{}{"mg", "person"}, + paramsReturned: []interface{}{false}, + }, + { + method: "Read", + args: []interface{}{mock.Anything, "mg", "person", &model.ReadRequest{ + Extras: map[string]interface{}{}, + Find: map[string]interface{}{}, + Aggregate: map[string][]string{}, + GroupBy: []interface{}{}, + Operation: utils.All, + Options: &model.ReadOptions{ + Select: map[string]int32{"_id": 1, "job": 1}, + }, + IsBatch: true, + PostProcess: map[string]*model.PostProcess{"person": &model.PostProcess{}}, + }, model.RequestParams{}}, + paramsReturned: []interface{}{[]interface{}{map[string]interface{}{"_id": "p1", "job": map[string]interface{}{"salary": 1000, "addressId": "a1"}}}, new(model.SQLMetaData), nil}, + }, + { + method: "Read", + args: []interface{}{mock.Anything, "mg", "address", &model.ReadRequest{ + Find: map[string]interface{}{"_id": "a1"}, + Aggregate: map[string][]string{}, + GroupBy: []interface{}{}, + Operation: utils.All, + Options: &model.ReadOptions{}, + IsBatch: true, + PostProcess: map[string]*model.PostProcess{"address": &model.PostProcess{}}, + }, model.RequestParams{}}, + paramsReturned: []interface{}{[]interface{}{map[string]interface{}{"_id": "a1", "country": "india"}}, new(model.SQLMetaData), nil}, + }, + }, + schemaMockArgs: []mockArgs{ + { + method: "GetSchema", + args: []interface{}{"mg", "person"}, + paramsReturned: []interface{}{model.Fields{"_id": &model.FieldType{FieldName: "_id", IsFieldTypeRequired: true, IsPrimary: true, Kind: model.TypeID}, "job": &model.FieldType{FieldName: "job", Kind: model.TypeObject, NestedObject: model.Fields{"salary": &model.FieldType{FieldName: "salary", Kind: model.TypeFloat}, "addressId": &model.FieldType{FieldName: "addressId", Kind: model.TypeString}, "address": &model.FieldType{FieldName: "address", Kind: model.TypeObject, IsLinked: true, LinkedTable: &model.TableProperties{Table: "address", DBType: "mg", From: "addressId", To: "_id"}}}}}, true}, + }, + { + method: "GetSchema", + args: []interface{}{"mg", "address"}, + paramsReturned: []interface{}{model.Fields{"_id": &model.FieldType{FieldName: "_id", IsFieldTypeRequired: true, IsPrimary: true, Kind: model.TypeID}, "country": &model.FieldType{FieldName: "country", Kind: model.TypeString}}, true}, + }, + }, + authMockArgs: []mockArgs{ + { + method: "IsReadOpAuthorised", + args: []interface{}{mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything}, + paramsReturned: []interface{}{&model.PostProcess{}, model.RequestParams{}, nil}, + }, + }, + args: args{ + req: &model.GraphQLRequest{ + OperationName: "query", + Query: `query { + person @mg { + _id + job { + salary + addressId + address { + _id + country + } + } + } + }`, + Variables: nil, + }, + token: "", + }, + wantErr: false, + wantResult: map[string]interface{}{"person": []interface{}{map[string]interface{}{"_id": "p1", "job": map[string]interface{}{"salary": 1000, "addressId": "a1", "address": map[string]interface{}{"_id": "a1", "country": "india"}}}}}, }} var mutationTestCases = []tests{