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/2] 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/2] 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