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,