Skip to content

Conversation

@anushakolan
Copy link
Contributor

@anushakolan anushakolan commented Dec 20, 2025

Why make this change?

Fixes a KeyNotFoundException in GraphQL queries that include multiple nested sibling relationships under RBAC (Role based access control) and adds an integration test to prevent regressions.

  1. For entities with multiple sibling nested relationships (e.g. Person.addresses.AddressType and Person.phoneNumbers.PhoneNumberType), GraphQL queries that requested both navigation branches could fail with:
    System.Collections.Generic.KeyNotFoundException: The given key 'XYZ' was not present in the dictionary.
  2. The failure was much more reproducible when RBAC was enabled (e.g. role: "authenticated" with X-MS-API-ROLE), and went away when everything was opened up to anonymous with "action": "*".

The root cause:
1.Pagination and nested relationship shaping are driven by PaginationMetadata.Subqueries, keyed by field name.
2. Under RBAC, authorization and filtering can remove or reshape relationships such that:
- A nested field (e.g. PhoneNumberType, reviews, etc.) still appears in the JSON payload, but
-The corresponding entry in PaginationMetadata.Subqueries is missing or not in the expected scope.
3. SqlQueryEngine.ResolveObject assumed that metadata always existed for the current field and did a direct dictionary index:
PaginationMetadata currentMetadata = parentMetadata.Subqueries[fieldSchema.Name]; which throws KeyNotFoundException when the key is absent.

What is this change?

  1. In SqlQueryEngine.ResolveObject, instead of always doing parentMetadata.Subqueries[fieldName] (which crashed when RBAC caused that entry to be missing), it now uses TryGetValue and:
    • If metadata exists and IsPaginated is true -> wrap the JSON as a pagination connection.
    • If metadata is missing -> just return the JSON as-is (no exception).
  2. Added a new test case in MsSqlGraphQLQueryTests, an integration test which queries books with multiple sibling nested relationships (websiteplacement, reviews, authors) under the authenticated role to:
    • Assert no KeyNotFoundException,
    • Verify all nested branches return data.

How was this tested?

Tested both manually and added an integration test (NestedSiblingRelationshipsWithRbac_DoNotThrowAndMaterialize).

Manually if we run this query without the bug fix:
query { persons { items { PersonID FirstName LastName addresses { items { AddressID City AddressType { AddressTypeID TypeName } } } phoneNumbers { items { PhoneNumberID PhoneNumber PhoneNumberType { PhoneNumberTypeID TypeName } } } } } }

We get the following response:

{ "errors": [ { "message": "The given key 'AddressType' was not present in the dictionary.", "locations": [ { "line": 11, "column": 11 } ], "path": [ "persons", "items", 0, "addresses", "items", 1, "AddressType" ] }, { "message": "The given key 'AddressType' was not present in the dictionary.", "locations": [ { "line": 11, "column": 11 } ], "path": [ "persons", "items", 0, "addresses", "items", 0, "AddressType" ] }, { "message": "The given key 'AddressType' was not present in the dictionary.", "locations": [ { "line": 11, "column": 11 } ], "path": [ "persons", "items", 1, "addresses", "items", 0, "AddressType" ] } ], "data": { "persons": { "items": [ { "PersonID": 1, "FirstName": "John", "LastName": "Doe", "addresses": { "items": [ { "AddressID": 1, "City": "New York", "AddressType": null }, { "AddressID": 2, "City": "New York", "AddressType": null } ] }, "phoneNumbers": { "items": [ { "PhoneNumberID": 1, "PhoneNumber": "123-456-7890", "PhoneNumberType": { "PhoneNumberTypeID": 1, "TypeName": "Mobile" } }, { "PhoneNumberID": 2, "PhoneNumber": "111-222-3333", "PhoneNumberType": { "PhoneNumberTypeID": 3, "TypeName": "Work" } } ] } }, { "PersonID": 2, "FirstName": "Jane", "LastName": "Smith", "addresses": { "items": [ { "AddressID": 3, "City": "Los Angeles", "AddressType": null } ] }, "phoneNumbers": { "items": [ { "PhoneNumberID": 3, "PhoneNumber": "987-654-3210", "PhoneNumberType": { "PhoneNumberTypeID": 2, "TypeName": "Home" } } ] } } ] } } }

After the bug fix, we get,

{ "data": { "persons": { "items": [ { "PersonID": 1, "FirstName": "John", "LastName": "Doe", "addresses": { "items": [ { "AddressID": 1, "City": "New York", "AddressType": { "AddressTypeID": 1, "TypeName": "Home" } }, { "AddressID": 2, "City": "New York", "AddressType": { "AddressTypeID": 2, "TypeName": "Work" } } ] }, "phoneNumbers": { "items": [ { "PhoneNumberID": 1, "PhoneNumber": "123-456-7890", "PhoneNumberType": { "PhoneNumberTypeID": 1, "TypeName": "Mobile" } }, { "PhoneNumberID": 2, "PhoneNumber": "111-222-3333", "PhoneNumberType": { "PhoneNumberTypeID": 3, "TypeName": "Work" } } ] } }, { "PersonID": 2, "FirstName": "Jane", "LastName": "Smith", "addresses": { "items": [ { "AddressID": 3, "City": "Los Angeles", "AddressType": { "AddressTypeID": 1, "TypeName": "Home" } } ] }, "phoneNumbers": { "items": [ { "PhoneNumberID": 3, "PhoneNumber": "987-654-3210", "PhoneNumberType": { "PhoneNumberTypeID": 2, "TypeName": "Home" } } ] } } ] } } }

Sample Request(s)

  • Example REST and/or GraphQL request to demonstrate modifications
  • Example of CLI usage to demonstrate modifications

@anushakolan
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a KeyNotFoundException that occurred in GraphQL queries with multiple nested sibling relationships under RBAC (Role-Based Access Control). The fix changes the approach from direct dictionary access to using TryGetValue, allowing graceful handling of scenarios where pagination metadata may be missing due to authorization filtering.

Key Changes:

  • Modified SqlQueryEngine.ResolveObject to use TryGetValue instead of direct dictionary indexing for accessing pagination metadata
  • Added defensive handling to return elements as-is when metadata is unavailable
  • Introduced an integration test to verify the fix and prevent regression

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Core/Resolvers/SqlQueryEngine.cs Replaced direct dictionary access with TryGetValue pattern to handle missing pagination metadata gracefully, preventing KeyNotFoundException when RBAC filters affect nested relationships
src/Service.Tests/SqlTests/GraphQLQueryTests/MsSqlGraphQLQueryTests.cs Added integration test NestedSiblingRelationshipsWithRbac_DoNotThrowAndMaterialize to verify multiple nested sibling relationships work correctly under authenticated role

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…Tests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI commented Dec 20, 2025

@anushakolan I've opened a new pull request, #3030, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Contributor

Copilot AI commented Dec 20, 2025

@anushakolan I've opened a new pull request, #3031, to work on those changes. Once the pull request is ready, I'll request review from you.

@anushakolan
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

Copy link
Collaborator

@Aniruddh25 Aniruddh25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the description, After the bug fix, we get, - we still see an error message. not the response after fixing the bug,

@anushakolan
Copy link
Contributor Author

In the description, After the bug fix, we get, - we still see an error message. not the response after fixing the bug,

Fixed

@anushakolan
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

books(filter: { id: { eq: 1 } }) {
items {
id
reviews(first: 100) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you double check if we already have a test for pagination where the data is more than 100? My review comment was to club this scenario with the nested sibling usecase for which the bug has been filed. This test doesnt seem to combine the two.

Copy link
Collaborator

@Aniruddh25 Aniruddh25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for a review of the new issue to better understand the root cause.

if (parentMetadata.Subqueries.TryGetValue(fieldSchema.Name, out PaginationMetadata? currentMetadata))
{
return SqlPaginationUtil.CreatePaginationConnectionFromJsonElement(element, currentMetadata);
metadata = currentMetadata;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we do something with this metadata variable?

…Tests.cs

Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants