From 0f5670dcf9a7272a8ec4a49cce21823fde6d0bd2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:04:50 +0000 Subject: [PATCH 1/6] Initial plan From ffcb725e54dd7d27f544784d7abfffdd5dc6f9a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:25:55 +0000 Subject: [PATCH 2/6] Fix case-insensitive schema name comparison for M:N relationships (#3071) Co-authored-by: Alekhya-Polavarapu <67075378+Alekhya-Polavarapu@users.noreply.github.com> --- .../DatabasePrimitives/DatabaseObject.cs | 8 +- .../UnitTests/ConfigValidationUnitTests.cs | 92 +++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/src/Config/DatabasePrimitives/DatabaseObject.cs b/src/Config/DatabasePrimitives/DatabaseObject.cs index 8636e8c005..8b138d7635 100644 --- a/src/Config/DatabasePrimitives/DatabaseObject.cs +++ b/src/Config/DatabasePrimitives/DatabaseObject.cs @@ -43,13 +43,15 @@ public override bool Equals(object? other) public bool Equals(DatabaseObject? other) { return other is not null && - SchemaName.Equals(other.SchemaName) && - Name.Equals(other.Name); + string.Equals(SchemaName, other.SchemaName, StringComparison.OrdinalIgnoreCase) && + string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase); } public override int GetHashCode() { - return HashCode.Combine(SchemaName, Name); + return HashCode.Combine( + SchemaName?.ToUpperInvariant(), + Name?.ToUpperInvariant()); } /// diff --git a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs index 119e6637c6..f251e94ab9 100644 --- a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs +++ b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs @@ -437,6 +437,98 @@ string relationshipEntity configValidator.ValidateRelationships(runtimeConfig, _metadataProviderFactory.Object); } + /// + /// Test method to verify that many-to-many relationships work correctly when the linking object + /// is in a custom schema (not dbo). This test validates that schema names are correctly compared + /// using case-insensitive comparison, which is important for SQL Server where schema names are + /// case-insensitive. + /// + [DataRow("mySchema.TEST_SOURCE_LINK", "mySchema", "TEST_SOURCE_LINK", DisplayName = "Linking object with custom schema")] + [DataRow("MYSCHEMA.TEST_SOURCE_LINK", "MYSCHEMA", "TEST_SOURCE_LINK", DisplayName = "Linking object with uppercase custom schema")] + [DataRow("myschema.test_source_link", "myschema", "test_source_link", DisplayName = "Linking object with lowercase schema and table")] + [DataTestMethod] + public void TestRelationshipWithLinkingObjectInCustomSchema( + string linkingObject, + string expectedSchema, + string expectedTable + ) + { + // Creating an EntityMap with two sample entities + Dictionary entityMap = GetSampleEntityMap( + sourceEntity: "SampleEntity1", + targetEntity: "SampleEntity2", + sourceFields: new string[] { "sourceField" }, + targetFields: new string[] { "targetField" }, + linkingObject: linkingObject, + linkingSourceFields: new string[] { "linkingSourceField" }, + linkingTargetFields: new string[] { "linkingTargetField" } + ); + + RuntimeConfig runtimeConfig = new( + Schema: "UnitTestSchema", + DataSource: new DataSource(DatabaseType: DatabaseType.MSSQL, "", Options: null), + Runtime: new( + Rest: new(), + GraphQL: new(), + Mcp: new(), + Host: new(null, null) + ), + Entities: new(entityMap) + ); + + // Mocking EntityToDatabaseObject - entities are in the custom schema as well + MockFileSystem fileSystem = new(); + FileSystemRuntimeConfigLoader loader = new(fileSystem); + RuntimeConfigProvider provider = new(loader) { IsLateConfigured = true }; + RuntimeConfigValidator configValidator = new(provider, fileSystem, new Mock>().Object); + Mock _sqlMetadataProvider = new(); + + Dictionary mockDictionaryForEntityDatabaseObject = new() + { + { + "SampleEntity1", + new DatabaseTable(expectedSchema, "TEST_SOURCE1") + }, + { + "SampleEntity2", + new DatabaseTable(expectedSchema, "TEST_SOURCE2") + } + }; + + _sqlMetadataProvider.Setup(x => x.EntityToDatabaseObject).Returns(mockDictionaryForEntityDatabaseObject); + + // To mock the schema name and dbObjectName for linkingObject + _sqlMetadataProvider.Setup(x => + x.ParseSchemaAndDbTableName(linkingObject)).Returns((expectedSchema, expectedTable)); + + string discard; + _sqlMetadataProvider.Setup(x => x.TryGetExposedColumnName(It.IsAny(), It.IsAny(), out discard)).Returns(true); + + Mock _metadataProviderFactory = new(); + _metadataProviderFactory.Setup(x => x.GetMetadataProvider(It.IsAny())).Returns(_sqlMetadataProvider.Object); + + // Mock ForeignKeyPair to be defined in DB with the custom schema + // The schema comparison should be case-insensitive + _sqlMetadataProvider.Setup(x => + x.VerifyForeignKeyExistsInDB( + It.Is(t => string.Equals(t.SchemaName, expectedSchema, StringComparison.OrdinalIgnoreCase) && + string.Equals(t.Name, expectedTable, StringComparison.OrdinalIgnoreCase)), + It.Is(t => string.Equals(t.SchemaName, expectedSchema, StringComparison.OrdinalIgnoreCase) && + string.Equals(t.Name, "TEST_SOURCE1", StringComparison.OrdinalIgnoreCase)) + )).Returns(true); + + _sqlMetadataProvider.Setup(x => + x.VerifyForeignKeyExistsInDB( + It.Is(t => string.Equals(t.SchemaName, expectedSchema, StringComparison.OrdinalIgnoreCase) && + string.Equals(t.Name, expectedTable, StringComparison.OrdinalIgnoreCase)), + It.Is(t => string.Equals(t.SchemaName, expectedSchema, StringComparison.OrdinalIgnoreCase) && + string.Equals(t.Name, "TEST_SOURCE2", StringComparison.OrdinalIgnoreCase)) + )).Returns(true); + + // Validation should pass with custom schema + configValidator.ValidateRelationships(runtimeConfig, _metadataProviderFactory.Object); + } + /// /// Test method to check that an exception is thrown when the relationship is one-many /// or many-one (determined by the linking object being null), while both SourceFields From e54990cfd2074cae21851bf84b1277b865c39f2b Mon Sep 17 00:00:00 2001 From: Anusha Kolan Date: Tue, 24 Feb 2026 17:40:22 -0800 Subject: [PATCH 3/6] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Config/DatabasePrimitives/DatabaseObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Config/DatabasePrimitives/DatabaseObject.cs b/src/Config/DatabasePrimitives/DatabaseObject.cs index 8b138d7635..be1eff45ba 100644 --- a/src/Config/DatabasePrimitives/DatabaseObject.cs +++ b/src/Config/DatabasePrimitives/DatabaseObject.cs @@ -50,8 +50,8 @@ public bool Equals(DatabaseObject? other) public override int GetHashCode() { return HashCode.Combine( - SchemaName?.ToUpperInvariant(), - Name?.ToUpperInvariant()); + SchemaName is null ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(SchemaName), + Name is null ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(Name)); } /// From 0d8203e1b7c36c206ecd4904eb02d45868906c92 Mon Sep 17 00:00:00 2001 From: Anusha Kolan Date: Tue, 24 Feb 2026 17:40:53 -0800 Subject: [PATCH 4/6] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs index f251e94ab9..f4233e1087 100644 --- a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs +++ b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs @@ -466,7 +466,7 @@ string expectedTable RuntimeConfig runtimeConfig = new( Schema: "UnitTestSchema", - DataSource: new DataSource(DatabaseType: DatabaseType.MSSQL, "", Options: null), + DataSource: new DataSource(DatabaseType: DatabaseType.MSSQL, ConnectionString: "", Options: null), Runtime: new( Rest: new(), GraphQL: new(), From 009e1da63b974181644ebc1446c4290f00d57a0f Mon Sep 17 00:00:00 2001 From: Anusha Kolan Date: Tue, 24 Feb 2026 17:47:02 -0800 Subject: [PATCH 5/6] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../UnitTests/ConfigValidationUnitTests.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs index f4233e1087..30371a7287 100644 --- a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs +++ b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs @@ -509,20 +509,22 @@ string expectedTable // Mock ForeignKeyPair to be defined in DB with the custom schema // The schema comparison should be case-insensitive + // Use concrete DatabaseTable instances with differing casing so that + // Moq relies on DatabaseTable.Equals for argument matching. + DatabaseTable expectedLinkingTable = new(expectedSchema, expectedTable); + DatabaseTable expectedSource1Table = new(expectedSchema.ToUpperInvariant(), "TEST_SOURCE1"); + DatabaseTable expectedSource2Table = new(expectedSchema.ToUpperInvariant(), "TEST_SOURCE2"); + _sqlMetadataProvider.Setup(x => x.VerifyForeignKeyExistsInDB( - It.Is(t => string.Equals(t.SchemaName, expectedSchema, StringComparison.OrdinalIgnoreCase) && - string.Equals(t.Name, expectedTable, StringComparison.OrdinalIgnoreCase)), - It.Is(t => string.Equals(t.SchemaName, expectedSchema, StringComparison.OrdinalIgnoreCase) && - string.Equals(t.Name, "TEST_SOURCE1", StringComparison.OrdinalIgnoreCase)) + expectedLinkingTable, + expectedSource1Table )).Returns(true); _sqlMetadataProvider.Setup(x => x.VerifyForeignKeyExistsInDB( - It.Is(t => string.Equals(t.SchemaName, expectedSchema, StringComparison.OrdinalIgnoreCase) && - string.Equals(t.Name, expectedTable, StringComparison.OrdinalIgnoreCase)), - It.Is(t => string.Equals(t.SchemaName, expectedSchema, StringComparison.OrdinalIgnoreCase) && - string.Equals(t.Name, "TEST_SOURCE2", StringComparison.OrdinalIgnoreCase)) + expectedLinkingTable, + expectedSource2Table )).Returns(true); // Validation should pass with custom schema From 0f2a111cfa32f08a21a93c81e6db4bc3e231db20 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 01:59:51 +0000 Subject: [PATCH 6/6] Improve test: mismatch casing for linking table and add dbo-fallback verification Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com> --- .../UnitTests/ConfigValidationUnitTests.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs index 30371a7287..94e9667831 100644 --- a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs +++ b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs @@ -511,7 +511,8 @@ string expectedTable // The schema comparison should be case-insensitive // Use concrete DatabaseTable instances with differing casing so that // Moq relies on DatabaseTable.Equals for argument matching. - DatabaseTable expectedLinkingTable = new(expectedSchema, expectedTable); + // Linking table uses lowercase to ensure case-insensitive comparison is working. + DatabaseTable expectedLinkingTable = new(expectedSchema.ToLowerInvariant(), expectedTable.ToLowerInvariant()); DatabaseTable expectedSource1Table = new(expectedSchema.ToUpperInvariant(), "TEST_SOURCE1"); DatabaseTable expectedSource2Table = new(expectedSchema.ToUpperInvariant(), "TEST_SOURCE2"); @@ -529,6 +530,20 @@ string expectedTable // Validation should pass with custom schema configValidator.ValidateRelationships(runtimeConfig, _metadataProviderFactory.Object); + + // Verify that VerifyForeignKeyExistsInDB is never called with 'dbo' schema, + // guarding against the original dbo-fallback regression. + _sqlMetadataProvider.Verify(x => + x.VerifyForeignKeyExistsInDB( + It.Is(t => string.Equals(t.SchemaName, "dbo", StringComparison.OrdinalIgnoreCase)), + It.IsAny() + ), Times.Never); + + _sqlMetadataProvider.Verify(x => + x.VerifyForeignKeyExistsInDB( + It.IsAny(), + It.Is(t => string.Equals(t.SchemaName, "dbo", StringComparison.OrdinalIgnoreCase)) + ), Times.Never); } ///