From ea430d6f4f55378149f62f32a1788d7b186bcb61 Mon Sep 17 00:00:00 2001 From: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:16:39 -0700 Subject: [PATCH 1/3] Fix bug Autoentities doesn't account for schema (#3468) ## Why make this change? - Fixes bug issue #3458 ## What is this change? This changes how the in-memory entities are created from the `autoentities` property by specifying the schema as well as the object they come from in the `MsSqlMetadataProvider`, in order for DAB to not default to the `dbo` schema and cause a failure. ## How was this tested? - [x] Integration Tests - [ ] Unit Tests Added tests that ensure entities that are in the non-default schema `dbo` are also created without any issues and can be used by the REST and GraphQL endpoints. ## Sample Request(s) ``` "entities": {}, "autoentities": { "default": { "patterns": { "include": [ "foo.%", "bar.%" ], "name": "{schema}_{object}" }, "template": { "mcp": { "dml-tools": true }, "rest": { "enabled": true }, "graphql": { "enabled": true }, "health": { "enabled": true }, "cache": { "enabled": false } }, "permissions": [ { "role": "anonymous", "actions": [ "create", "read", "update", "delete" ] } ] } ``` image --------- Co-authored-by: Souvik Ghosh --- .../MsSqlMetadataProvider.cs | 2 +- .../Configuration/ConfigurationTests.cs | 128 ++++++++++++++++-- 2 files changed, 120 insertions(+), 10 deletions(-) diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index 23d12ec31f..1f16c81e28 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -337,7 +337,7 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona // Currently the source type is always Table for auto-generated entities from database objects. Entity generatedEntity = new( Source: new EntitySource( - Object: objectName, + Object: $"{schemaName}.{objectName}", Type: EntitySourceType.Table, Parameters: null, KeyFields: null), diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index e37b0920e2..a31f215cce 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -5428,10 +5428,10 @@ public async Task TestGraphQLIntrospectionQueriesAreNotImpactedByDepthLimit() } /// - /// + /// Ensures that autoentities are properly generated into in-memory entities /// - /// - /// + /// Boolean that indicates if we should also use regular entities from config + /// The expected number of entities /// [TestCategory(TestCategory.MSSQL)] [DataTestMethod] @@ -5580,13 +5580,123 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities(bool useEntities, int } /// - /// + /// Ensures that autoentities are properly generated into in-memory entities when entities have non-default schemas. /// - /// - /// - /// - /// - /// + /// The pattern to include for autoentities + /// Boolean that indicates if the pattern is for the foo schema + /// + [TestCategory(TestCategory.MSSQL)] + [DataTestMethod] + [DataRow("foo.%", true, DisplayName = "Test Autoentities with foo schema")] + [DataRow("bar.%", false, DisplayName = "Test Autoentities with bar schema")] + public async Task TestAutoentitiesGeneratedWithDifferentSchemas(string includePattern, bool isPatternFoo) + { + // Arrange + Dictionary autoentityMap = new() + { + { + "PublisherAutoEntity", new Autoentity( + Patterns: new AutoentityPatterns( + Include: new[] { includePattern }, + Exclude: null, + Name: null + ), + Template: new AutoentityTemplate( + Rest: new EntityRestOptions(Enabled: true), + GraphQL: new EntityGraphQLOptions( + Singular: string.Empty, + Plural: string.Empty, + Enabled: true + ), + Health: null, + Cache: null + ), + Permissions: new[] { GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) } + ) + } + }; + + // Create DataSource for MSSQL connection + DataSource dataSource = new(DatabaseType.MSSQL, + GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null); + + // Build complete runtime configuration with autoentities + RuntimeConfig configuration = new( + Schema: "TestAutoentitiesSchema", + DataSource: dataSource, + Runtime: new( + Rest: new(Enabled: true), + GraphQL: new(Enabled: true), + Mcp: new(Enabled: false), + Host: new( + Cors: null, + Authentication: new Config.ObjectModel.AuthenticationOptions( + Provider: nameof(EasyAuthType.StaticWebApps), + Jwt: null + ) + ) + ), + Entities: new(new Dictionary()), + Autoentities: new RuntimeAutoentities(autoentityMap) + ); + + File.WriteAllText(CUSTOM_CONFIG_FILENAME, configuration.ToJson()); + + string[] args = new[] { $"--ConfigFileName={CUSTOM_CONFIG_FILENAME}" }; + using (TestServer server = new(Program.CreateWebHostBuilder(args))) + using (HttpClient client = server.CreateClient()) + { + // Act + using HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/magazines"); + using HttpResponseMessage restResponse = await client.SendAsync(restRequest); + + string item = isPatternFoo ? "title" : "comic_name"; + string graphqlQuery = $@" + {{ + magazines {{ + items {{ + {item} + }} + }} + }}"; + + object graphqlPayload = new { query = graphqlQuery }; + HttpRequestMessage graphqlRequest = new(HttpMethod.Post, "/graphql") + { + Content = JsonContent.Create(graphqlPayload) + }; + HttpResponseMessage graphqlResponse = await client.SendAsync(graphqlRequest); + + // Assert + string expectedResponseFragment = isPatternFoo ? @"""title"":""Vogue""" : @"""comic_name"":""NotVogue"""; + + // Verify REST response + Assert.AreEqual(HttpStatusCode.OK, restResponse.StatusCode, "REST request to auto-generated entity should succeed"); + + string restResponseBody = await restResponse.Content.ReadAsStringAsync(); + Assert.IsTrue(!string.IsNullOrEmpty(restResponseBody), "REST response should contain data"); + Assert.IsTrue(restResponseBody.Contains(expectedResponseFragment)); + + // Verify GraphQL response + Assert.AreEqual(HttpStatusCode.OK, graphqlResponse.StatusCode, "GraphQL request to auto-generated entity should succeed"); + + string graphqlResponseBody = await graphqlResponse.Content.ReadAsStringAsync(); + Assert.IsTrue(!string.IsNullOrEmpty(graphqlResponseBody), "GraphQL response should contain data"); + Assert.IsFalse(graphqlResponseBody.Contains("errors"), "GraphQL response should not contain errors"); + Assert.IsTrue(graphqlResponseBody.Contains(expectedResponseFragment)); + } + } + + /// + /// Tests that DAB fails if the entities generated from autoentities property + /// do not contain unique parameters such as rest path, graphql singular/plural names, + /// or if the autoentity pattern conflicts with an existing entity name. + /// + /// Definition name of the generated entity from autoentities + /// GraphQL singular name of the generated entity from autoentities + /// GraphQL plural name of the generated entity from autoentities + /// REST path of the generated entity from autoentities + /// Expected exception message /// [TestCategory(TestCategory.MSSQL)] [DataTestMethod] From 98aa9a48543cb11a596894086f400540ef007c7a Mon Sep 17 00:00:00 2001 From: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:46:50 -0700 Subject: [PATCH 2/3] Change autoentities `patterns.name` default value (#3472) ## Why make this change? - Fixes issue #3455 ## What is this change? This changes the default value of the `patterns.name` property inside `autoentities` so that it uses both the {schema} and {object} in order to allow users to use multiple objects that have the same name but are from different schemas without the need to do additional changes to the config file. This PR is dependent on PR #3468. ## How was this tested? - [X] Integration Tests - [ ] Unit Tests - [X] Manual Tests Used sample below to ensure that different entities with the same object name but different schema name can be created without the need to change the `patterns.name` property. ## Sample Request(s) ``` "AllMagazineObjects": { "patterns": { "include": [ "%.magazines" ] }, "permissions": [ { "role": "anonymous", "actions": [ { "action": "*" } ] } ] } ``` image image --------- Co-authored-by: Souvik Ghosh --- schemas/dab.draft.schema.json | 4 +- src/Cli/Commands/AutoConfigOptions.cs | 2 +- src/Config/ObjectModel/AutoentityPatterns.cs | 2 +- .../Configuration/ConfigurationTests.cs | 122 ++++++++++++++++-- 4 files changed, 118 insertions(+), 12 deletions(-) diff --git a/schemas/dab.draft.schema.json b/schemas/dab.draft.schema.json index 84f8e5cfbd..fcf938b65d 100644 --- a/schemas/dab.draft.schema.json +++ b/schemas/dab.draft.schema.json @@ -808,8 +808,8 @@ }, "name": { "type": "string", - "description": "Entity name interpolation pattern using {schema} and {object}. Null defaults to {object}. Must be unique for every entity inside the pattern", - "default": "{object}" + "description": "Entity name interpolation pattern using {schema} and {object}. Null defaults to {schema}_{object}. Must be unique for every entity inside the pattern", + "default": "{schema}_{object}" } } }, diff --git a/src/Cli/Commands/AutoConfigOptions.cs b/src/Cli/Commands/AutoConfigOptions.cs index 41be943303..2e273eeee4 100644 --- a/src/Cli/Commands/AutoConfigOptions.cs +++ b/src/Cli/Commands/AutoConfigOptions.cs @@ -58,7 +58,7 @@ public AutoConfigOptions( [Option("patterns.exclude", Required = false, HelpText = "T-SQL LIKE pattern(s) to exclude database objects. Space-separated array of patterns. Default: null")] public IEnumerable? PatternsExclude { get; } - [Option("patterns.name", Required = false, HelpText = "Interpolation syntax for entity naming (must be unique for each generated entity). Default: '{object}'")] + [Option("patterns.name", Required = false, HelpText = "Interpolation syntax for entity naming (must be unique for each generated entity). Default: '{schema}_{object}'")] public string? PatternsName { get; } [Option("template.mcp.dml-tools", Required = false, HelpText = "Enable/disable DML tools for generated entities. Allowed values: true, false. Default: true")] diff --git a/src/Config/ObjectModel/AutoentityPatterns.cs b/src/Config/ObjectModel/AutoentityPatterns.cs index d287c56109..818eaa61b6 100644 --- a/src/Config/ObjectModel/AutoentityPatterns.cs +++ b/src/Config/ObjectModel/AutoentityPatterns.cs @@ -51,7 +51,7 @@ public AutoentityPatterns( } else { - this.Name = "{object}"; + this.Name = "{schema}_{object}"; } } diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index a31f215cce..5cf401aa0b 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -5536,12 +5536,12 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities(bool useEntities, int { // Act RuntimeConfigProvider configProvider = server.Services.GetService(); - using HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/publishers"); + using HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/dbo_publishers"); using HttpResponseMessage restResponse = await client.SendAsync(restRequest); string graphqlQuery = @" { - publishers { + dbo_publishers { items { id name @@ -5579,6 +5579,111 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities(bool useEntities, int } } + /// + /// This test validates that multiple autoentities with the same object name + /// but different schemas can be generated and accessed properly with the + /// default 'property.name' which should generate entities named '{schema}_{object}'. + /// + [TestCategory(TestCategory.MSSQL)] + [TestMethod] + public async Task TestAutoentitiesWithSameObjectDifferentSchemas() + { + // Arrange + Dictionary autoentityMap = new() + { + { + "PublisherAutoEntity", new Autoentity( + Patterns: new AutoentityPatterns( + Include: null, + Exclude: new[] { "dbo.GQLmappings", "dbo.graphql_incompatible", "dbo.brokers" }, + Name: null + ), + Template: new AutoentityTemplate( + Rest: new EntityRestOptions(Enabled: true), + GraphQL: new EntityGraphQLOptions( + Singular: string.Empty, + Plural: string.Empty, + Enabled: true + ), + Health: null, + Cache: null + ), + Permissions: new[] { GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) } + ) + } + }; + + // Create DataSource for MSSQL connection + DataSource dataSource = new(DatabaseType.MSSQL, + GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null); + + // Build complete runtime configuration with autoentities + RuntimeConfig configuration = new( + Schema: "TestAutoentitiesSchema", + DataSource: dataSource, + Runtime: new( + Rest: new(Enabled: true), + GraphQL: new(Enabled: true), + Mcp: new(Enabled: false), + Host: new( + Cors: null, + Authentication: new Config.ObjectModel.AuthenticationOptions( + Provider: nameof(EasyAuthType.StaticWebApps), + Jwt: null + ) + ) + ), + Entities: new(new Dictionary()), + Autoentities: new RuntimeAutoentities(autoentityMap) + ); + + File.WriteAllText(CUSTOM_CONFIG_FILENAME, configuration.ToJson()); + + string[] args = new[] { $"--ConfigFileName={CUSTOM_CONFIG_FILENAME}" }; + + using (TestServer server = new(Program.CreateWebHostBuilder(args))) + using (HttpClient client = server.CreateClient()) + { + // Act + using HttpRequestMessage restFooRequest = new(HttpMethod.Get, "/api/foo_magazines"); + using HttpResponseMessage restFooResponse = await client.SendAsync(restFooRequest); + + using HttpRequestMessage restBarRequest = new(HttpMethod.Get, "/api/bar_magazines"); + using HttpResponseMessage restBarResponse = await client.SendAsync(restBarRequest); + + string graphqlQuery = @" + { + foo_magazines { + items { + id + issue_number + } + } + bar_magazines { + items { + comic_name + issue + } + } + }"; + + object graphqlPayload = new { query = graphqlQuery }; + HttpRequestMessage graphqlRequest = new(HttpMethod.Post, "/graphql") + { + Content = JsonContent.Create(graphqlPayload) + }; + HttpResponseMessage graphqlResponse = await client.SendAsync(graphqlRequest); + + // Assert + // Verify REST response + Assert.AreEqual(HttpStatusCode.OK, restFooResponse.StatusCode, "REST request to auto-generated entity 'foo_magazines' should succeed"); + Assert.AreEqual(HttpStatusCode.OK, restBarResponse.StatusCode, "REST request to auto-generated entity 'bar_magazines' should succeed"); + + // Verify GraphQL response + Assert.AreEqual(HttpStatusCode.OK, graphqlResponse.StatusCode, "GraphQL request to auto-generated entity should succeed"); + } + } + /// /// Ensures that autoentities are properly generated into in-memory entities when entities have non-default schemas. /// @@ -5647,13 +5752,14 @@ public async Task TestAutoentitiesGeneratedWithDifferentSchemas(string includePa using (HttpClient client = server.CreateClient()) { // Act - using HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/magazines"); + string path = isPatternFoo ? "foo_magazines" : "bar_magazines"; + using HttpRequestMessage restRequest = new(HttpMethod.Get, $"/api/{path}"); using HttpResponseMessage restResponse = await client.SendAsync(restRequest); string item = isPatternFoo ? "title" : "comic_name"; string graphqlQuery = $@" {{ - magazines {{ + {path} {{ items {{ {item} }} @@ -5700,10 +5806,10 @@ public async Task TestAutoentitiesGeneratedWithDifferentSchemas(string includePa /// [TestCategory(TestCategory.MSSQL)] [DataTestMethod] - [DataRow("publishers", "uniqueSingularPublisher", "uniquePluralPublishers", "/unique/publisher", "Entity 'publishers' conflicts with autoentity pattern 'PublisherAutoEntity'. Use --patterns.exclude to skip it.", DisplayName = "Autoentities fail due to entity name")] - [DataRow("UniquePublisher", "publishers", "uniquePluralPublishers", "/unique/publisher", "Entity publishers generates queries/mutation that already exist", DisplayName = "Autoentities fail due to graphql singular type")] - [DataRow("UniquePublisher", "uniqueSingularPublisher", "publishers", "/unique/publisher", "Entity publishers generates queries/mutation that already exist", DisplayName = "Autoentities fail due to graphql plural type")] - [DataRow("UniquePublisher", "uniqueSingularPublisher", "uniquePluralPublishers", "/publishers", "The rest path: publishers specified for entity: publishers is already used by another entity.", DisplayName = "Autoentities fail due to rest path")] + [DataRow("dbo_publishers", "uniqueSingularPublisher", "uniquePluralPublishers", "/unique/publisher", "Entity 'dbo_publishers' conflicts with autoentity pattern 'PublisherAutoEntity'. Use --patterns.exclude to skip it.", DisplayName = "Autoentities fail due to entity name")] + [DataRow("UniquePublisher", "dbo_publishers", "uniquePluralPublishers", "/unique/publisher", "Entity dbo_publishers generates queries/mutation that already exist", DisplayName = "Autoentities fail due to graphql singular type")] + [DataRow("UniquePublisher", "uniqueSingularPublisher", "dbo_publishers", "/unique/publisher", "Entity dbo_publishers generates queries/mutation that already exist", DisplayName = "Autoentities fail due to graphql plural type")] + [DataRow("UniquePublisher", "uniqueSingularPublisher", "uniquePluralPublishers", "/dbo_publishers", "The rest path: dbo_publishers specified for entity: dbo_publishers is already used by another entity.", DisplayName = "Autoentities fail due to rest path")] public async Task ValidateAutoentityGenerationConflicts(string entityName, string singular, string plural, string path, string exceptionMessage) { // Arrange From ac3c186b7963ca720f8629dc245853cdc784b802 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 11:09:28 -0700 Subject: [PATCH 3/3] Upgrade OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.13.0 to 1.15.3 (#3488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why make this change? Bump `OpenTelemetry.Exporter.OpenTelemetryProtocol` to pick up patch fixes from 1.15.3. ## What is this change? - Updated `OpenTelemetry.Exporter.OpenTelemetryProtocol` from `1.13.0` → `1.15.3` in `src/Directory.Packages.props` (centrally managed versions) ## How was this tested? - [ ] Integration Tests - [ ] Unit Tests ## Sample Request(s) N/A — dependency version bump only. > [!WARNING] > >
> Firewall rules blocked me from connecting to one or more addresses (expand for details) > > #### I tried to connect to the following addresses, but was blocked by firewall rules: > > - `www.nuget.org` > - Triggering command: `/home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js` (dns block) > > If you need me to access, download, or install something from one of these locations, you can either: > > - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled > - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/Azure/data-api-builder/settings/copilot/coding_agent) (admins only) > >
--------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> Co-authored-by: Ruben Cerna --- src/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index dfd605cc8f..c7ada5f8a7 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -40,7 +40,7 @@ - +