From c0a50e3b51d26de37ff0d1de5558e431d8045163 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:08:23 +0000 Subject: [PATCH 1/3] Initial plan From 547ba7114ec8e0fd766d4190540c2553905a9cb3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:15:09 +0000 Subject: [PATCH 2/3] Fix nested filter for self-referencing relationships and add integration test Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com> --- src/Core/Models/GraphQLFilterParsers.cs | 16 +++++-- .../GraphQLFilterTests/MsSqlGQLFilterTests.cs | 45 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/Core/Models/GraphQLFilterParsers.cs b/src/Core/Models/GraphQLFilterParsers.cs index 153def832f..064fb13ab4 100644 --- a/src/Core/Models/GraphQLFilterParsers.cs +++ b/src/Core/Models/GraphQLFilterParsers.cs @@ -419,10 +419,18 @@ private void HandleNestedFilterForSql( predicatesForExistsQuery.Push(existsQueryFilterPredicate); // Add JoinPredicates to the subquery query structure so a predicate connecting - // the outer table is added to the where clause of subquery - existsQuery.AddJoinPredicatesForRelatedEntity( - targetEntityName: queryStructure.EntityName, - relatedSourceAlias: queryStructure.SourceAlias, + // the outer table is added to the where clause of subquery. + // For self-referencing relationships (e.g., parent/child hierarchy), we need to use + // the relationship name to look up the correct foreign key definition. + // The parent query (queryStructure) calls AddJoinPredicatesForRelationship which adds + // predicates to the subquery (existsQuery), connecting queryStructure.SourceAlias to existsQuery.SourceAlias. + string relationshipName = filterField.Name; + EntityRelationshipKey fkLookupKey = new(queryStructure.EntityName, relationshipName); + BaseSqlQueryStructure sqlQueryStructure = (BaseSqlQueryStructure)queryStructure; + sqlQueryStructure.AddJoinPredicatesForRelationship( + fkLookupKey: fkLookupKey, + targetEntityName: nestedFilterEntityName, + subqueryTargetTableAlias: existsQuery.SourceAlias, subQuery: existsQuery); // The right operand is the SqlExistsQueryStructure. diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs index 90e46940a9..ccc4e1efea 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs @@ -355,6 +355,51 @@ WHERE [table3].[name] IN ('Aniruddh') await TestNestedFilterWithOrAndIN(existsPredicate, roleName: "authenticated"); } + /// + /// Test Nested Filter for Self-Referencing relationship + /// Tests that nested filters work correctly on self-referencing relationships (e.g., parent/child hierarchy). + /// Uses DimAccount table with parent_account relationship. + /// + [TestMethod] + public async Task TestNestedFilterSelfReferencing() + { + // This query should find all accounts whose parent account has AccountKey = 1 + // Expected to return account with AccountKey 2 (direct child of account 1) + string existsPredicate = $@" + EXISTS( SELECT 1 + FROM {GetPreIndentDefaultSchema()}[DimAccount] AS [table1] + WHERE [table1].[AccountKey] = 1 + AND [table0].[ParentAccountKey] = [table1].[AccountKey] )"; + + string graphQLQueryName = "dbo_DimAccounts"; + // Gets all the accounts that have a parent account with AccountKey = 1 + string gqlQuery = @"{ + dbo_DimAccounts (" + QueryBuilder.FILTER_FIELD_NAME + ": {" + + @"parent_account: { AccountKey: { eq: 1 }}}) + { + items { + AccountKey + ParentAccountKey + } + } + }"; + + string dbQuery = MakeQueryOn( + table: "DimAccount", + queriedColumns: new List { "AccountKey", "ParentAccountKey" }, + existsPredicate, + GetDefaultSchema(), + pkColumns: new List { "AccountKey" }); + + JsonElement actual = await ExecuteGraphQLRequestAsync( + gqlQuery, + graphQLQueryName, + isAuthenticated: false); + + string expected = await GetDatabaseResultAsync(dbQuery); + SqlTestHelper.PerformTestEqualJsonStrings(expected, actual.ToString()); + } + /// /// Gets the default schema for /// MsSql. From b91807e2aa4d8e350fb774b242bec0896483ace8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:19:53 +0000 Subject: [PATCH 3/3] Add safe type checking before casting to BaseSqlQueryStructure Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com> --- src/Core/Models/GraphQLFilterParsers.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Core/Models/GraphQLFilterParsers.cs b/src/Core/Models/GraphQLFilterParsers.cs index 064fb13ab4..779ec64f33 100644 --- a/src/Core/Models/GraphQLFilterParsers.cs +++ b/src/Core/Models/GraphQLFilterParsers.cs @@ -426,7 +426,15 @@ private void HandleNestedFilterForSql( // predicates to the subquery (existsQuery), connecting queryStructure.SourceAlias to existsQuery.SourceAlias. string relationshipName = filterField.Name; EntityRelationshipKey fkLookupKey = new(queryStructure.EntityName, relationshipName); - BaseSqlQueryStructure sqlQueryStructure = (BaseSqlQueryStructure)queryStructure; + + if (queryStructure is not BaseSqlQueryStructure sqlQueryStructure) + { + throw new DataApiBuilderException( + message: "Expected SQL query structure for nested filter processing.", + statusCode: HttpStatusCode.InternalServerError, + subStatusCode: DataApiBuilderException.SubStatusCodes.UnexpectedError); + } + sqlQueryStructure.AddJoinPredicatesForRelationship( fkLookupKey: fkLookupKey, targetEntityName: nestedFilterEntityName,